diff --git a/src/PuppeteerRunnerExtension.ts b/src/PuppeteerRunnerExtension.ts index 2e111138..eccaffc7 100644 --- a/src/PuppeteerRunnerExtension.ts +++ b/src/PuppeteerRunnerExtension.ts @@ -151,6 +151,21 @@ export class PuppeteerRunnerExtension extends RunnerExtension { await element.dispose(); } break; + case 'hover': + { + const element = await waitForSelectors(step.selectors, localFrame, { + timeout, + visible: waitForVisible, + }); + if (!element) { + throw new Error('Could not find element: ' + step.selectors[0]); + } + await scrollIntoViewIfNeeded(element, timeout); + startWaitingForEvents(); + await element.hover(); + await element.dispose(); + } + break; case 'emulateNetworkConditions': { startWaitingForEvents(); @@ -691,6 +706,7 @@ interface ElementHandle }; }): Promise; type(input: string): Promise; + hover(): Promise; focus(): Promise; $$( selector: string diff --git a/src/PuppeteerStringifyExtension.ts b/src/PuppeteerStringifyExtension.ts index 68524962..f8b98551 100644 --- a/src/PuppeteerStringifyExtension.ts +++ b/src/PuppeteerStringifyExtension.ts @@ -32,6 +32,7 @@ import type { WaitForElementStep, WaitForExpressionStep, DoubleClickStep, + HoverStep, } from './Schema.js'; import { StringifyExtension } from './StringifyExtension.js'; @@ -167,6 +168,11 @@ export class PuppeteerStringifyExtension extends StringifyExtension { out.appendLine('});'); } + #appendHoverStep(out: LineWriter, step: HoverStep): void { + this.#appendWaitForSelector(out, step); + out.appendLine('await element.hover();'); + } + #appendChangeStep(out: LineWriter, step: ChangeStep): void { this.#appendWaitForSelector(out, step); out.appendLine('const type = await element.evaluate(el => el.type);'); @@ -248,6 +254,8 @@ export class PuppeteerStringifyExtension extends StringifyExtension { return this.#appendClickStep(out, step); case 'doubleClick': return this.#appendDoubleClickStep(out, step); + case 'hover': + return this.#appendHoverStep(out, step); case 'change': return this.#appendChangeStep(out, step); case 'emulateNetworkConditions': diff --git a/src/Schema.ts b/src/Schema.ts index 5ad22e89..f4a3c792 100644 --- a/src/Schema.ts +++ b/src/Schema.ts @@ -63,15 +63,23 @@ export interface StepWithSelectors extends StepWithFrame { selectors: Selector[]; } +export type PointerDeviceType = 'mouse' | 'pen' | 'touch'; +export type PointerButtonType = + | 'primary' + | 'auxiliary' + | 'secondary' + | 'back' + | 'forward'; + export interface ClickAttributes { /** * Pointer type for the event. Defaults to 'mouse'. */ - deviceType?: 'mouse' | 'pen' | 'touch'; + deviceType?: PointerDeviceType; /** * Defaults to 'primary' if the device type is a mouse. */ - button?: 'primary' | 'auxiliary' | 'secondary' | 'back' | 'forward'; + button?: PointerButtonType; /** * in px, relative to the top-left corner of the element content box. Defaults * to the center of the element @@ -97,6 +105,10 @@ export interface ClickStep extends ClickAttributes, StepWithSelectors { duration?: number; } +export interface HoverStep extends StepWithSelectors { + type: 'hover'; +} + export interface ChangeStep extends StepWithSelectors { type: 'change'; value: string; @@ -167,6 +179,7 @@ export type CustomStep = export type UserStep = | ChangeStep | ClickStep + | HoverStep | CloseStep | CustomStep | DoubleClickStep diff --git a/test/resources/main.html b/test/resources/main.html index 026ab0d3..21834682 100644 --- a/test/resources/main.html +++ b/test/resources/main.html @@ -29,6 +29,7 @@ + diff --git a/test/runner_test.ts b/test/runner_test.ts index 759855d2..75798793 100644 --- a/test/runner_test.ts +++ b/test/runner_test.ts @@ -865,4 +865,39 @@ describe('Runner', () => { ); assert.ok(frame, 'Frame that the target page navigated to is not found'); }); + + it('should replay hovers', async () => { + const runner = await createRunner( + { + title: 'Test Recording', + timeout: 3000, + steps: [ + { + type: 'navigate', + url: `${HTTP_PREFIX}/main.html`, + assertedEvents: [ + { + title: '', + type: 'navigation', + url: `${HTTP_PREFIX}/main.html`, + }, + ], + }, + { + type: 'hover', + target: 'main', + selectors: [['#hover-button']], + }, + ], + }, + new PuppeteerRunnerExtension(browser, page) + ); + await runner.run(); + assert.ok( + await page.evaluate( + () => document.getElementById('hover-button')?.textContent + ), + 'Hovered' + ); + }); });