diff --git a/packages/playwright-core/src/server/dom.ts b/packages/playwright-core/src/server/dom.ts index 55105bd50c..4458a10d93 100644 --- a/packages/playwright-core/src/server/dom.ts +++ b/packages/playwright-core/src/server/dom.ts @@ -457,11 +457,16 @@ export class ElementHandle extends js.JSHandle { if ((options as any).__testHookBeforePointerAction) await (options as any).__testHookBeforePointerAction(); progress.throwIfAborted(); // Avoid action that has side-effects. + let restoreMousePosition: types.Point | undefined; + if (options.trial) + restoreMousePosition = await this._page.mouse.position(); let restoreModifiers: types.KeyboardModifier[] | undefined; if (options && options.modifiers) restoreModifiers = await this._page.keyboard.ensureModifiers(options.modifiers); progress.log(` performing ${actionName} action`); await action(point); + if (restoreMousePosition) + await this._page.mouse.move(restoreMousePosition.x, restoreMousePosition.y); if (restoreModifiers) await this._page.keyboard.ensureModifiers(restoreModifiers); if (hitTargetInterceptionHandle) { diff --git a/packages/playwright-core/src/server/input.ts b/packages/playwright-core/src/server/input.ts index 4e4c95a8f3..3505a512d2 100644 --- a/packages/playwright-core/src/server/input.ts +++ b/packages/playwright-core/src/server/input.ts @@ -180,6 +180,10 @@ export class Mouse { this._keyboard = this._page.keyboard; } + async position(): Promise { + return { x: this._x, y: this._y }; + } + async move(x: number, y: number, options: { steps?: number, forClick?: boolean } = {}, metadata?: CallMetadata) { if (metadata) metadata.point = { x, y }; diff --git a/tests/page/page-mouse.spec.ts b/tests/page/page-mouse.spec.ts index 5d29be2bb7..31368c38a9 100644 --- a/tests/page/page-mouse.spec.ts +++ b/tests/page/page-mouse.spec.ts @@ -305,3 +305,44 @@ it('should dispatch mouse move after context menu was opened', async ({ page, br } }); +it('should support click trial', async ({ page }) => { + await page.setContent(``); + const locator = page.locator('button'); + + await locator.click({ trial: true }); + await expect(locator).toHaveText('initial'); + + await locator.click(); + await expect(locator).toHaveText('updated'); +}); + +it('should support click trial with modifiers', async ({ page }) => { + await page.setContent(``); + const locator = page.locator('button'); + + await page.evaluate(() => { + document.body.addEventListener('keydown', (event: KeyboardEvent) => { + document.querySelector('button').innerText = 'keydown:' + event.key; + }); + }); + + await locator.click({ trial: true, modifiers: ['Shift'] }); + await expect(locator).toHaveText('keydown:Shift'); + + await locator.click({ modifiers: ['Shift'] }); + await expect(locator).toHaveText('clicked'); +}); + +it('should support hover trial', async ({ page }) => { + await page.setContent(``); + const locator = page.locator('button'); + + await locator.hover({ trial: true }); + await expect(locator).toHaveText('initial'); + + // Note: hover right after trial only works because trial restores the mouse + // position. Otherwise, the mouse would not move and hence no mousemove or + // mouseover event would be triggered. + await locator.hover(); + await expect(locator).toHaveText('mouseover'); +});