Skip to content

Commit

Permalink
feat: add Drag step type
Browse files Browse the repository at this point in the history
  • Loading branch information
jrandolf authored and jrandolf-2 committed Sep 19, 2023
1 parent 66b4e62 commit ac9f81c
Show file tree
Hide file tree
Showing 6 changed files with 143 additions and 3 deletions.
18 changes: 18 additions & 0 deletions src/PuppeteerRunnerExtension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,24 @@ export class PuppeteerRunnerExtension extends RunnerExtension {
},
});
break;
case StepType.Drag: {
let [x, y] = [step.offsetX, step.offsetY];
await mainPage.mouse.move(x, y);
await mainPage.mouse.down({
button: step.button && mouseButtonMap.get(step.button),
});
for (let i = 0; i < step.deltas.length; i += 2) {
[x, y] = [
x + (step.deltas[i] as number),
y + (step.deltas[i + 1] as number),
];
await mainPage.mouse.move(x, y);
}
await mainPage.mouse.up({
button: step.button && mouseButtonMap.get(step.button),
});
break;
}
case StepType.Hover:
await locatorRace(
step.selectors.map((selector) => {
Expand Down
18 changes: 18 additions & 0 deletions src/PuppeteerStringifyExtension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import type {
HoverStep,
KeyDownStep,
KeyUpStep,
DragStep,
NavigateStep,
ScrollStep,
SetViewportStep,
Expand Down Expand Up @@ -302,6 +303,8 @@ export class PuppeteerStringifyExtension extends StringifyExtension {
return this.#appendScrollStep(out, step);
case StepType.Navigate:
return this.#appendNavigationStep(out, step);
case StepType.Drag:
return this.#appendDragStep(out, step);
case StepType.WaitForElement:
return this.#appendWaitForElementStep(out, step);
case StepType.WaitForExpression:
Expand All @@ -322,6 +325,21 @@ export class PuppeteerStringifyExtension extends StringifyExtension {
);
}

#appendDragStep(out: LineWriter, step: DragStep): void {
out.appendLine(`
{
const deltas = new Int8Array("${step.deltas.toString()}".split(","));
let [x, y] = [${step.offsetX}, ${step.offsetY}];
await targetPage.mouse.move(x, y);
for (let i = 0; i < deltas.length; i += 2) {
[x, y] = [x + deltas[i], y + deltas[i + 1]];
await targetPage.mouse.move(x, y);
}
}
`);
}

#appendWaitExpressionStep(
out: LineWriter,
step: WaitForExpressionStep
Expand Down
24 changes: 22 additions & 2 deletions src/Schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export enum StepType {
KeyDown = 'keyDown',
KeyUp = 'keyUp',
Navigate = 'navigate',
Drag = 'move',
Scroll = 'scroll',
SetViewport = 'setViewport',
WaitForElement = 'waitForElement',
Expand Down Expand Up @@ -110,11 +111,13 @@ export type PointerButtonType =

export interface ClickAttributes {
/**
* Pointer type for the event. Defaults to 'mouse'.
* Pointer type for the event.
*
* @defaultValue `'mouse'`
*/
deviceType?: PointerDeviceType;
/**
* Defaults to 'primary' if the device type is a mouse.
* @defaultValue `'primary'` if the {@link deviceType} is `'mouse'`.
*/
button?: PointerButtonType;
/**
Expand All @@ -141,6 +144,22 @@ export interface DoubleClickStep extends ClickAttributes, StepWithSelectors {

export interface ClickStep extends ClickAttributes, StepWithSelectors {
type: StepType.Click;
/**
* Delay (in ms) between the mouse up and mouse down of the click.
*
* @defaultValue `50`
*/
duration?: number;
}

export interface DragStep extends ClickAttributes, StepWithTarget {
type: StepType.Drag;
/**
* An flattened array of (offsetX, offsetY)-deltas to defining the movement.
*
* @defaultValue `[]`
*/
deltas: Int8Array;
}

export interface HoverStep extends StepWithSelectors {
Expand Down Expand Up @@ -224,6 +243,7 @@ export type UserStep =
| EmulateNetworkConditionsStep
| KeyDownStep
| KeyUpStep
| DragStep
| NavigateStep
| ScrollStep
| SetViewportStep;
Expand Down
28 changes: 28 additions & 0 deletions src/SchemaUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
Key,
KeyDownStep,
KeyUpStep,
DragStep,
NavigateStep,
ScrollStep,
Selector,
Expand Down Expand Up @@ -154,6 +155,21 @@ function parseNumber(step: object, prop: string): number {
throw new Error(`Step.${prop} is not a number`);
}

function parseInt8Array(step: object, prop: string): Int8Array {
if (hasProperty(step, prop)) {
const value = step[prop];
if (value instanceof Int8Array) {
return value;
} else if (isIntegerArray(value)) {
return new Int8Array(value);
} else if (isString(value)) {
// The Int8Array constructor automatically does the number conversion.
return new Int8Array(value.split(',') as unknown as number[]);
}
}
throw new Error(`Step.${prop} is not an typed byte array`);
}

function parseBoolean(step: object, prop: string): boolean {
if (hasProperty(step, prop)) {
const maybeBoolean = step[prop];
Expand Down Expand Up @@ -427,6 +443,16 @@ function parseNavigateStep(step: object): NavigateStep {
};
}

function parseDragStep(step: object): DragStep {
return {
...parseStepWithTarget(StepType.Drag, step),
type: StepType.Drag,
offsetX: parseNumber(step, 'offsetX'),
offsetY: parseNumber(step, 'offsetY'),
deltas: parseInt8Array(step, 'deltas'),
};
}

function parseWaitForElementStep(step: object): WaitForElementStep {
const operator = parseOptionalString(step, 'operator');
if (operator && operator !== '>=' && operator !== '==' && operator !== '<=') {
Expand Down Expand Up @@ -533,6 +559,8 @@ export function parseStep(step: unknown, idx?: number): Step {
return parseScrollStep(step);
case StepType.Navigate:
return parseNavigateStep(step);
case StepType.Drag:
return parseDragStep(step);
case StepType.CustomStep:
return parseCustomStep(step);
case StepType.WaitForElement:
Expand Down
22 changes: 22 additions & 0 deletions test/resources/drawable-canvas.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<canvas width="100" height="100"></canvas>
<script>
const canvas = document.querySelector('canvas');

// Drawing code.
const context = canvas.getContext('2d');
window.addEventListener('pointerdown', (event) => {
context.beginPath();
context.moveTo(event.offsetX, event.offsetY);
const handlePointerMove = (event) => {
context.lineTo(event.offsetX, event.offsetY);
context.stroke();
};

const handlePointerUp = () => {
window.removeEventListener('pointermove', handlePointerMove);
window.removeEventListener('pointerup', handlePointerUp);
};
window.addEventListener('pointermove', handlePointerMove);
window.addEventListener('pointerup', handlePointerUp);
});
</script>
36 changes: 35 additions & 1 deletion test/runner.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1009,6 +1009,40 @@ describe('Runner', () => {
assert.ok(frame, 'Frame that the target page navigated to is not found');
});

it.only('should replay movements', async () => {
const runner = await createRunner(
{
title: 'Test Recording',
timeout: 3000,
steps: [
{
type: StepType.Navigate,
url: `${HTTP_PREFIX}/drawable-canvas.html`,
},
{
type: StepType.Drag,
offsetX: 46,
offsetY: 41,
deltas: new Int8Array([
0, 0, 0, 1, 0, 2, 1, 2, 2, 3, 3, 4, 3, 3, 3, 2, 2, 1, 1, 0, 1, 0,
2, -1, 2, -2, 2, -1, 1, -1, 1, -1, 0, -1, 0, -1, 0, -1, 0, -1, 0,
-1, -1, -2, -2, -3, -4, -4, -4, -4, -3, -3, -2, -1, -2, 0, -1, 0,
-1, 0, -1, 0, -2, 0, -3, 1, -3, 1, -3, 1, -2, 1, -1, 0, 0, 0, 0,
0,
]),
},
],
},
new PuppeteerRunnerExtension(browser, page)
);
await runner.run();
assert.strictEqual(
await page.evaluate(() => document.querySelector('canvas')?.toDataURL()),
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAAAXNSR0IArs4c6QAAAspJREFUeF7tmktu1EAURW8gQEZEjGCIyIqSdbIXVgDKEDHhM+ITAipki06npbjxc+eUOZZaainlp/vuqVsuV+coXigHjlBqFBOBwCaBQAQCcwAmx4QIBOYATI4JEQjMAZgcEyIQmAMwOSZEIDAHYHJMiEBgDsDkmBCBwByAyTEhAoE5AJNjQgQCcwAmx4QIZLIDH5M8HUbvmjjfk5xMrtbJQFpC3id5vqd3tB72lH9zOKGZt0lebcj6lOTZxK6+JXmcrOe/Z+4LyOskF4ORv5J8SPJiIoRdw1qN++plhuzbtx66iXdJXg7mte9nhd2sAsqhgVwmeZPkvBDEZqnuoRwayEIcbpTtGsoagTQ63UJZK5BuoawZSJdQ1g6kOyhUINfD1rhKXzfPlKqGq3dP1UC6Scr/BOTzcFhJ7fnPpKaKu0rycAF9belqnwfVka6qRwUyLjFt6WpgKq8G5EuS08qiVbXIQNrvHY8WSMlS6SthQgYypmSJJabVXCJ9s6HQgYxQfgy/e8xueCiwxC6uRFsPQL4meVK8dAlk5vSpNrC63sz2/t7eQ0JGtZVbVoEUTaEGpe2+2hI25xLIHPe27q04l6qoUdhSn0vWpgH/aij6HYR8dDJl9jUo+2yH23h8zz091HdBmmoy9kVwu6negbR+xgf0zyTHWw3il6g1Amk9jede45v95tLU1aTrSuyEB0tLyXi0jjyruquHtQG5q1/83wUCQyQQgcAcgMkxIQKBOQCTY0IEAnMAJseECATmAEyOCREIzAGYHBMiEJgDMDkmRCAwB2ByTIhAYA7A5JgQgcAcgMkxIQKBOQCTY0IEAnMAJseECATmAEyOCREIzAGYHBMiEJgDMDkmRCAwB2ByTIhAYA7A5JgQgcAcgMkxIQKBOQCTY0IEAnMAJseECATmAEyOCREIzAGYHBMiEJgDMDkmRCAwB2ByfgOYo0ZleqjU4gAAAABJRU5ErkJggg==',
'Drawings did not match.'
);
});

it('should replay hovers', async () => {
const runner = await createRunner(
{
Expand Down Expand Up @@ -1040,7 +1074,7 @@ describe('Runner', () => {
await page.evaluate(
() => document.getElementById('hover-button')?.textContent
),
'Hovered'
'Hover button was either not found or not hovered.'
);
});

Expand Down

0 comments on commit ac9f81c

Please sign in to comment.