Skip to content

Commit

Permalink
added interactions e2e tests
Browse files Browse the repository at this point in the history
Added new e2e tests for chart interactions such as mouse and touch interactions.
  • Loading branch information
SlicedSilver committed Sep 14, 2022
1 parent 3992003 commit 05ea046
Show file tree
Hide file tree
Showing 19 changed files with 831 additions and 178 deletions.
17 changes: 17 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,19 @@ jobs:
- store_test_results:
path: test-results/

interactions:
executor: node16-browsers-executor
environment:
NO_SANDBOX: "true"
TESTS_REPORT_FILE: "test-results/interactions/results.xml"
steps:
- checkout-with-deps
- attach_workspace:
at: ./
- run: scripts/run-interactions-tests.sh
- store_test_results:
path: test-results/

size-limit:
executor: node16-executor
steps:
Expand Down Expand Up @@ -336,6 +349,10 @@ workflows:
filters: *default-filters
requires:
- build
- interactions:
filters: *default-filters
requires:
- build
- lint-dts:
filters: *default-filters
requires:
Expand Down
10 changes: 10 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,16 @@
"${input:testStandalonePath}"
],
"internalConsoleOptions": "openOnSessionStart"
},
{
"type": "node",
"request": "launch",
"name": "Interaction tests",
"program": "${workspaceFolder}/tests/e2e/interactions/runner.js",
"args": [
"${input:testStandalonePath}"
],
"internalConsoleOptions": "openOnSessionStart"
}
],
"inputs": [
Expand Down
8 changes: 8 additions & 0 deletions scripts/run-interactions-test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/bin/bash
set -e
echo "Preparing"

npm run build

echo "Interactions tests"
node ./tests/e2e/interactions/runner.js ./dist/lightweight-charts.standalone.development.js
18 changes: 18 additions & 0 deletions tests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,3 +107,21 @@ Alternatively, you can run the test on a specific file like this:
```bash
node ./tests/e2e/memleaks/runner.js ./dist/lightweight-charts.standalone.development.js
```

### Interactions

The interactions tests check whether the library is correctly handling user interaction on the chart. Interactions include: mouse scrolling, mouse dragging, and touches.

#### Running the Interactions tests

You can run the interactions tests with the following command:

```bash
./scripts/run-interactions-tests.sh
```

Alternatively, you can run the tests on a specific file like this:

```bash
node ./tests/e2e/interactions/runner.js ./dist/lightweight-charts.standalone.development.js
```
181 changes: 4 additions & 177 deletions tests/e2e/coverage/coverage.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,12 @@ import puppeteer, {
HTTPResponse,
launch as launchPuppeteer,
Page,
type CDPSession,
} from 'puppeteer';

import { doHorizontalDrag, doKineticAnimation, doVerticalDrag } from '../helpers/mouse-drag-actions';
import { doMouseScrolls } from '../helpers/mouse-scroll-actions';
import { doLongTouch, doPinchZoomTouch, doSwipeTouch } from '../helpers/touch-actions';

import { expectedCoverage, threshold } from './coverage-config';

const coverageScript = fs.readFileSync(path.join(__dirname, 'coverage-script.js'), { encoding: 'utf-8' });
Expand All @@ -24,31 +27,6 @@ const testStandalonePathEnvKey = 'TEST_STANDALONE_PATH';

const testStandalonePath: string = process.env[testStandalonePathEnvKey] || '';

async function doMouseScrolls(page: Page, element: ElementHandle): Promise<void> {
const boundingBox = await element.boundingBox();
if (!boundingBox) {
throw new Error('Unable to get boundingBox for element.');
}

// move mouse to center of element
await page.mouse.move(
boundingBox.x + boundingBox.width / 2,
boundingBox.y + boundingBox.height / 2
);

await page.mouse.wheel({ deltaX: 10.0 });

await page.mouse.wheel({ deltaY: 10.0 });

await page.mouse.wheel({ deltaX: -10.0 });

await page.mouse.wheel({ deltaY: -10.0 });

await page.mouse.wheel({ deltaX: 10.0, deltaY: 10.0 });

await page.mouse.wheel({ deltaX: -10.0, deltaY: -10.0 });
}

async function doZoomInZoomOut(page: Page): Promise<void> {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const prevViewport = page.viewport()!;
Expand All @@ -60,157 +38,6 @@ async function doZoomInZoomOut(page: Page): Promise<void> {
await page.setViewport(prevViewport);
}

async function doVerticalDrag(page: Page, element: ElementHandle): Promise<void> {
const elBox = await element.boundingBox() as BoundingBox;

const elMiddleX = elBox.x + elBox.width / 2;
const elMiddleY = elBox.y + elBox.height / 2;

// move mouse to the middle of element
await page.mouse.move(elMiddleX, elMiddleY);

await page.mouse.down({ button: 'left' });
await page.mouse.move(elMiddleX, elMiddleY - 20);
await page.mouse.move(elMiddleX, elMiddleY + 40);
await page.mouse.up({ button: 'left' });
}

async function doHorizontalDrag(page: Page, element: ElementHandle): Promise<void> {
const elBox = await element.boundingBox() as BoundingBox;

const elMiddleX = elBox.x + elBox.width / 2;
const elMiddleY = elBox.y + elBox.height / 2;

// move mouse to the middle of element
await page.mouse.move(elMiddleX, elMiddleY);

await page.mouse.down({ button: 'left' });
await page.mouse.move(elMiddleX - 20, elMiddleY);
await page.mouse.move(elMiddleX + 40, elMiddleY);
await page.mouse.up({ button: 'left' });
}

// await a setTimeout delay evaluated within page context
async function pageTimeout(page: Page, delay: number): Promise<void> {
return page.evaluate(
(ms: number) => new Promise<void>(
(resolve: () => void) => setTimeout(resolve, ms)
),
delay
);
}

async function doKineticAnimation(page: Page, element: ElementHandle): Promise<void> {
const elBox = await element.boundingBox() as BoundingBox;

const elMiddleX = elBox.x + elBox.width / 2;
const elMiddleY = elBox.y + elBox.height / 2;

// move mouse to the middle of element
await page.mouse.move(elMiddleX, elMiddleY);

await page.mouse.down({ button: 'left' });
await pageTimeout(page, 50);
await page.mouse.move(elMiddleX - 40, elMiddleY);
await page.mouse.move(elMiddleX - 55, elMiddleY);
await page.mouse.move(elMiddleX - 105, elMiddleY);
await page.mouse.move(elMiddleX - 155, elMiddleY);
await page.mouse.move(elMiddleX - 205, elMiddleY);
await page.mouse.move(elMiddleX - 255, elMiddleY);
await page.mouse.up({ button: 'left' });

await pageTimeout(page, 200);
// stop animation
await page.mouse.down({ button: 'left' });
await page.mouse.up({ button: 'left' });
}

// Simulate a long touch action in a single position
async function doLongTouch(page: Page, element: ElementHandle, duration: number): Promise<void> {
const elBox = (await element.boundingBox()) as BoundingBox;

const elCenterX = elBox.x + elBox.width / 2;
const elCenterY = elBox.y + elBox.height / 2;

const client = await page.target().createCDPSession();

await client.send('Input.dispatchTouchEvent', {
type: 'touchStart',
touchPoints: [
{ x: elCenterX, y: elCenterY },
],
});
await pageTimeout(page, duration);
return client.send('Input.dispatchTouchEvent', {
type: 'touchEnd',
touchPoints: [
{ x: elCenterX, y: elCenterY },
],
});
}

// Simulate a touch swipe gesture
async function doSwipeTouch(
devToolsSession: CDPSession,
element: ElementHandle,
{
horizontal = false,
vertical = false,
}: { horizontal?: boolean; vertical?: boolean }
): Promise<void> {
const elBox = (await element.boundingBox()) as BoundingBox;

const elCenterX = elBox.x + elBox.width / 2;
const elCenterY = elBox.y + elBox.height / 2;
const xStep = horizontal ? elBox.width / 8 : 0;
const yStep = vertical ? elBox.height / 8 : 0;

for (let i = 2; i > 0; i--) {
const type = i === 2 ? 'touchStart' : 'touchMove';
await devToolsSession.send('Input.dispatchTouchEvent', {
type,
touchPoints: [{ x: elCenterX - i * xStep, y: elCenterY - i * yStep }],
});
}
return devToolsSession.send('Input.dispatchTouchEvent', {
type: 'touchEnd',
touchPoints: [{ x: elCenterX - xStep, y: elCenterY - yStep }],
});
}

// Perform a pinch or zoom touch gesture within the specified element.
async function doPinchZoomTouch(
devToolsSession: CDPSession,
element: ElementHandle,
zoom?: boolean
): Promise<void> {
const elBox = (await element.boundingBox()) as BoundingBox;

const sign = zoom ? -1 : 1;
const elCenterX = elBox.x + elBox.width / 2;
const elCenterY = elBox.y + elBox.height / 2;
const xStep = (sign * elBox.width) / 8;
const yStep = (sign * elBox.height) / 8;

for (let i = 2; i > 0; i--) {
const type = i === 2 ? 'touchStart' : 'touchMove';
await devToolsSession.send('Input.dispatchTouchEvent', {
type,
touchPoints: [
{ x: elCenterX - i * xStep, y: elCenterY - i * yStep },
{ x: elCenterX + i * xStep, y: elCenterY + i * xStep },
],
});
}
return devToolsSession.send('Input.dispatchTouchEvent', {
type: 'touchEnd',
touchPoints: [
{ x: elCenterX - xStep, y: elCenterY - yStep },
{ x: elCenterX + xStep, y: elCenterY + xStep },
],
});
}

async function doUserInteractions(page: Page): Promise<void> {
const chartContainer = await page.$('#container') as ElementHandle<Element>;
const chartBox = await chartContainer.boundingBox() as BoundingBox;
Expand Down
67 changes: 67 additions & 0 deletions tests/e2e/helpers/mouse-drag-actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { BoundingBox, ElementHandle, Page } from 'puppeteer';

import { pageTimeout } from './page-timeout';

export async function doVerticalDrag(
page: Page,
element: ElementHandle
): Promise<void> {
const elBox = (await element.boundingBox()) as BoundingBox;

const elMiddleX = elBox.x + elBox.width / 2;
const elMiddleY = elBox.y + elBox.height / 2;

// move mouse to the middle of element
await page.mouse.move(elMiddleX, elMiddleY);

await page.mouse.down({ button: 'left' });
await page.mouse.move(elMiddleX, elMiddleY - 20);
await page.mouse.move(elMiddleX, elMiddleY + 40);
await page.mouse.up({ button: 'left' });
}

export async function doHorizontalDrag(
page: Page,
element: ElementHandle
): Promise<void> {
const elBox = (await element.boundingBox()) as BoundingBox;

const elMiddleX = elBox.x + elBox.width / 2;
const elMiddleY = elBox.y + elBox.height / 2;

// move mouse to the middle of element
await page.mouse.move(elMiddleX, elMiddleY);

await page.mouse.down({ button: 'left' });
await page.mouse.move(elMiddleX - 20, elMiddleY);
await page.mouse.move(elMiddleX + 40, elMiddleY);
await page.mouse.up({ button: 'left' });
}

export async function doKineticAnimation(
page: Page,
element: ElementHandle
): Promise<void> {
const elBox = (await element.boundingBox()) as BoundingBox;

const elMiddleX = elBox.x + elBox.width / 2;
const elMiddleY = elBox.y + elBox.height / 2;

// move mouse to the middle of element
await page.mouse.move(elMiddleX, elMiddleY);

await page.mouse.down({ button: 'left' });
await pageTimeout(page, 50);
await page.mouse.move(elMiddleX - 40, elMiddleY);
await page.mouse.move(elMiddleX - 55, elMiddleY);
await page.mouse.move(elMiddleX - 105, elMiddleY);
await page.mouse.move(elMiddleX - 155, elMiddleY);
await page.mouse.move(elMiddleX - 205, elMiddleY);
await page.mouse.move(elMiddleX - 255, elMiddleY);
await page.mouse.up({ button: 'left' });

await pageTimeout(page, 200);
// stop animation
await page.mouse.down({ button: 'left' });
await page.mouse.up({ button: 'left' });
}
48 changes: 48 additions & 0 deletions tests/e2e/helpers/mouse-scroll-actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { ElementHandle, Page } from 'puppeteer';

export async function centerMouseOnElement(
page: Page,
element: ElementHandle
): Promise<void> {
const boundingBox = await element.boundingBox();
if (!boundingBox) {
throw new Error('Unable to get boundingBox for element.');
}

// move mouse to center of element
await page.mouse.move(
boundingBox.x + boundingBox.width / 2,
boundingBox.y + boundingBox.height / 2
);
}

interface MouseScrollDelta {
x?: number;
y?: number;
}

export async function doMouseScroll(
deltas: MouseScrollDelta,
page: Page
): Promise<void> {
await page.mouse.wheel({ deltaX: deltas.x || 0, deltaY: deltas.y || 0 });
}

export async function doMouseScrolls(
page: Page,
element: ElementHandle
): Promise<void> {
await centerMouseOnElement(page, element);

await doMouseScroll({ x: 10.0 }, page);

await doMouseScroll({ y: 10.0 }, page);

await doMouseScroll({ x: -10.0 }, page);

await doMouseScroll({ y: -10.0 }, page);

await doMouseScroll({ x: 10.0, y: 10.0 }, page);

await doMouseScroll({ x: -10.0, y: -10.0 }, page);
}
Loading

0 comments on commit 05ea046

Please sign in to comment.