From 9472f79d3240aa17b358e189da02b568f329c1b3 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Fri, 12 May 2023 13:21:49 -0700 Subject: [PATCH] fix(reuse): reset mouse position in Firefox (#22973) Otherwise, Firefox sometimes keeps the current position and triggers unexpected hover effects. Fixes #22432. --- .../src/server/chromium/crPage.ts | 3 ++ .../src/server/firefox/ffPage.ts | 8 +++++ packages/playwright-core/src/server/page.ts | 4 +++ .../src/server/webkit/wkPage.ts | 3 ++ tests/library/browsercontext-reuse.spec.ts | 32 +++++++++++++++++++ 5 files changed, 50 insertions(+) diff --git a/packages/playwright-core/src/server/chromium/crPage.ts b/packages/playwright-core/src/server/chromium/crPage.ts index 080a835ca6..7a4a2ffce2 100644 --- a/packages/playwright-core/src/server/chromium/crPage.ts +++ b/packages/playwright-core/src/server/chromium/crPage.ts @@ -356,6 +356,9 @@ export class CRPage implements PageDelegate { await this._mainFrameSession._client.send('Page.enable').catch(e => {}); } + async resetForReuse(): Promise { + } + async pdf(options: channels.PagePdfParams): Promise { return this._pdf.generate(options); } diff --git a/packages/playwright-core/src/server/firefox/ffPage.ts b/packages/playwright-core/src/server/firefox/ffPage.ts index 1e1db5b908..9683261d59 100644 --- a/packages/playwright-core/src/server/firefox/ffPage.ts +++ b/packages/playwright-core/src/server/firefox/ffPage.ts @@ -575,6 +575,14 @@ export class FFPage implements PageDelegate { async inputActionEpilogue(): Promise { } + async resetForReuse(): Promise { + // Firefox sometimes keeps the last mouse position in the page, + // which affects things like hovered state. + // See https://github.com/microsoft/playwright/issues/22432. + // Move mouse to (-1, -1) to avoid anything being hovered. + await this.rawMouse.move(-1, -1, 'none', new Set(), new Set(), false); + } + async getFrameElement(frame: frames.Frame): Promise { const parent = frame.parentFrame(); if (!parent) diff --git a/packages/playwright-core/src/server/page.ts b/packages/playwright-core/src/server/page.ts index 8bdbb58d60..e275b725bd 100644 --- a/packages/playwright-core/src/server/page.ts +++ b/packages/playwright-core/src/server/page.ts @@ -94,6 +94,8 @@ export interface PageDelegate { inputActionEpilogue(): Promise; // Work around for asynchronously dispatched CSP errors in Firefox. readonly cspErrorsAsynchronousForInlineScipts?: boolean; + // Work around for mouse position in Firefox. + resetForReuse(): Promise; } type EmulatedSize = { screen: types.Size, viewport: types.Size }; @@ -265,6 +267,8 @@ export class Page extends SdkObject { this._delegate.updateEmulateMedia(), this._delegate.updateFileChooserInterception(), ]); + + await this._delegate.resetForReuse(); } _didClose() { diff --git a/packages/playwright-core/src/server/webkit/wkPage.ts b/packages/playwright-core/src/server/webkit/wkPage.ts index e7934a4d29..47955290c6 100644 --- a/packages/playwright-core/src/server/webkit/wkPage.ts +++ b/packages/playwright-core/src/server/webkit/wkPage.ts @@ -995,6 +995,9 @@ export class WKPage implements PageDelegate { async inputActionEpilogue(): Promise { } + async resetForReuse(): Promise { + } + async getFrameElement(frame: frames.Frame): Promise { const parent = frame.parentFrame(); if (!parent) diff --git a/tests/library/browsercontext-reuse.spec.ts b/tests/library/browsercontext-reuse.spec.ts index 4d48404686..7540c7efb0 100644 --- a/tests/library/browsercontext-reuse.spec.ts +++ b/tests/library/browsercontext-reuse.spec.ts @@ -202,3 +202,35 @@ test('should ignore binding from beforeunload', async ({ reusedContext }) => { expect(called).toBe(false); }); + +test('should reset mouse position', async ({ reusedContext, browserName, platform }) => { + // Note: this test only reproduces the issue locally when run with --repeat-each=20. + test.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/22432' }); + test.fixme(browserName === 'chromium' && platform !== 'darwin', 'chromium keeps hover on linux/win'); + + const pageContent = ` + +
one
+
two
+ `; + + let context = await reusedContext(); + let page = await context.newPage(); + await page.setContent(pageContent); + await expect(page.locator('#one')).toHaveCSS('background-color', 'rgb(0, 0, 255)'); + await expect(page.locator('#two')).toHaveCSS('background-color', 'rgb(0, 0, 255)'); + + await page.mouse.move(10, 45); + await expect(page.locator('#one')).toHaveCSS('background-color', 'rgb(0, 0, 255)'); + await expect(page.locator('#two')).toHaveCSS('background-color', 'rgb(255, 0, 0)'); + + context = await reusedContext(); + page = context.pages()[0]; + await page.setContent(pageContent); + await expect(page.locator('#one')).toHaveCSS('background-color', 'rgb(0, 0, 255)'); + await expect(page.locator('#two')).toHaveCSS('background-color', 'rgb(0, 0, 255)'); +});