From 639b28db3bc6ac4c3fa2d2ad8960b361b1021808 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Fri, 7 Oct 2022 12:43:48 -0700 Subject: [PATCH] fix: noWaitAfter option for hover (#17856) Fixes https://github.com/microsoft/playwright/issues/17833 --- docs/src/api/class-elementhandle.md | 3 ++ docs/src/api/class-frame.md | 2 ++ docs/src/api/class-locator.md | 2 ++ docs/src/api/class-page.md | 2 ++ .../playwright-core/src/protocol/validator.ts | 2 ++ packages/playwright-core/src/server/dom.ts | 2 +- packages/playwright-core/src/server/frames.ts | 2 +- packages/playwright-core/types/types.d.ts | 28 +++++++++++++++++++ packages/protocol/src/channels.ts | 4 +++ packages/protocol/src/protocol.yml | 2 ++ tests/page/locator-misc-1.spec.ts | 10 +++++++ tests/page/page-mouse.spec.ts | 10 +++++++ 12 files changed, 67 insertions(+), 2 deletions(-) diff --git a/docs/src/api/class-elementhandle.md b/docs/src/api/class-elementhandle.md index bce036150b..484ea9f4e1 100644 --- a/docs/src/api/class-elementhandle.md +++ b/docs/src/api/class-elementhandle.md @@ -555,6 +555,9 @@ When all steps combined have not finished during the specified [`option: timeout ### option: ElementHandle.hover.trial = %%-input-trial-%% * since: v1.11 +### option: ElementHandle.hover.noWaitAfter = %%-input-no-wait-after-%% +* since: v1.28 + ## async method: ElementHandle.innerHTML * since: v1.8 - returns: <[string]> diff --git a/docs/src/api/class-frame.md b/docs/src/api/class-frame.md index c237022eea..26fe8b4083 100644 --- a/docs/src/api/class-frame.md +++ b/docs/src/api/class-frame.md @@ -1059,6 +1059,8 @@ When all steps combined have not finished during the specified [`option: timeout * since: v1.8 ### option: Frame.hover.trial = %%-input-trial-%% * since: v1.11 +### option: Frame.hover.noWaitAfter = %%-input-no-wait-after-%% +* since: v1.28 ## async method: Frame.innerHTML * since: v1.8 diff --git a/docs/src/api/class-locator.md b/docs/src/api/class-locator.md index 72308eb2a7..d03154ee68 100644 --- a/docs/src/api/class-locator.md +++ b/docs/src/api/class-locator.md @@ -734,6 +734,8 @@ When all steps combined have not finished during the specified [`option: timeout * since: v1.14 ### option: Locator.hover.trial = %%-input-trial-%% * since: v1.14 +### option: Locator.hover.noWaitAfter = %%-input-no-wait-after-%% +* since: v1.28 ## async method: Locator.innerHTML * since: v1.14 diff --git a/docs/src/api/class-page.md b/docs/src/api/class-page.md index 8a55560d51..18eb299bf3 100644 --- a/docs/src/api/class-page.md +++ b/docs/src/api/class-page.md @@ -2369,6 +2369,8 @@ Shortcut for main frame's [`method: Frame.hover`]. * since: v1.8 ### option: Page.hover.trial = %%-input-trial-%% * since: v1.11 +### option: Page.hover.noWaitAfter = %%-input-no-wait-after-%% +* since: v1.28 ## async method: Page.innerHTML * since: v1.8 diff --git a/packages/playwright-core/src/protocol/validator.ts b/packages/playwright-core/src/protocol/validator.ts index a63bb2c99c..d595a8dd44 100644 --- a/packages/playwright-core/src/protocol/validator.ts +++ b/packages/playwright-core/src/protocol/validator.ts @@ -1331,6 +1331,7 @@ scheme.FrameHoverParams = tObject({ position: tOptional(tType('Point')), timeout: tOptional(tNumber), trial: tOptional(tBoolean), + noWaitAfter: tOptional(tBoolean), }); scheme.FrameHoverResult = tOptional(tObject({})); scheme.FrameInnerHTMLParams = tObject({ @@ -1717,6 +1718,7 @@ scheme.ElementHandleHoverParams = tObject({ position: tOptional(tType('Point')), timeout: tOptional(tNumber), trial: tOptional(tBoolean), + noWaitAfter: tOptional(tBoolean), }); scheme.ElementHandleHoverResult = tOptional(tObject({})); scheme.ElementHandleInnerHTMLParams = tOptional(tObject({})); diff --git a/packages/playwright-core/src/server/dom.ts b/packages/playwright-core/src/server/dom.ts index ac0c4758d1..8db0f76331 100644 --- a/packages/playwright-core/src/server/dom.ts +++ b/packages/playwright-core/src/server/dom.ts @@ -499,7 +499,7 @@ export class ElementHandle extends js.JSHandle { }, this._page._timeoutSettings.timeout(options)); } - _hover(progress: Progress, options: types.PointerActionOptions & types.PointerActionWaitOptions): Promise<'error:notconnected' | 'done'> { + _hover(progress: Progress, options: types.PointerActionOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions): Promise<'error:notconnected' | 'done'> { return this._retryPointerAction(progress, 'hover', false /* waitForEnabled */, point => this._page.mouse.move(point.x, point.y), options); } diff --git a/packages/playwright-core/src/server/frames.ts b/packages/playwright-core/src/server/frames.ts index 464a5d4d50..10d5a816ed 100644 --- a/packages/playwright-core/src/server/frames.ts +++ b/packages/playwright-core/src/server/frames.ts @@ -1287,7 +1287,7 @@ export class Frame extends SdkObject { return this._elementState(metadata, selector, 'checked', options); } - async hover(metadata: CallMetadata, selector: string, options: types.PointerActionOptions & types.PointerActionWaitOptions = {}) { + async hover(metadata: CallMetadata, selector: string, options: types.PointerActionOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}) { const controller = new ProgressController(metadata, this); return controller.run(async progress => { return dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, options.strict, handle => handle._hover(progress, options))); diff --git a/packages/playwright-core/types/types.d.ts b/packages/playwright-core/types/types.d.ts index 2951d77dd1..2acfc4517a 100644 --- a/packages/playwright-core/types/types.d.ts +++ b/packages/playwright-core/types/types.d.ts @@ -2753,6 +2753,13 @@ export interface Page { */ modifiers?: Array<"Alt"|"Control"|"Meta"|"Shift">; + /** + * Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can + * opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to + * inaccessible pages. Defaults to `false`. + */ + noWaitAfter?: boolean; + /** * A point to use relative to the top-left corner of element padding box. If not specified, uses some visible point of the * element. @@ -5785,6 +5792,13 @@ export interface Frame { */ modifiers?: Array<"Alt"|"Control"|"Meta"|"Shift">; + /** + * Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can + * opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to + * inaccessible pages. Defaults to `false`. + */ + noWaitAfter?: boolean; + /** * A point to use relative to the top-left corner of element padding box. If not specified, uses some visible point of the * element. @@ -8710,6 +8724,13 @@ export interface ElementHandle extends JSHandle { */ modifiers?: Array<"Alt"|"Control"|"Meta"|"Shift">; + /** + * Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can + * opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to + * inaccessible pages. Defaults to `false`. + */ + noWaitAfter?: boolean; + /** * A point to use relative to the top-left corner of element padding box. If not specified, uses some visible point of the * element. @@ -10171,6 +10192,13 @@ export interface Locator { */ modifiers?: Array<"Alt"|"Control"|"Meta"|"Shift">; + /** + * Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can + * opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to + * inaccessible pages. Defaults to `false`. + */ + noWaitAfter?: boolean; + /** * A point to use relative to the top-left corner of element padding box. If not specified, uses some visible point of the * element. diff --git a/packages/protocol/src/channels.ts b/packages/protocol/src/channels.ts index 953ee6f1ef..408f568914 100644 --- a/packages/protocol/src/channels.ts +++ b/packages/protocol/src/channels.ts @@ -2441,6 +2441,7 @@ export type FrameHoverParams = { position?: Point, timeout?: number, trial?: boolean, + noWaitAfter?: boolean, }; export type FrameHoverOptions = { strict?: boolean, @@ -2449,6 +2450,7 @@ export type FrameHoverOptions = { position?: Point, timeout?: number, trial?: boolean, + noWaitAfter?: boolean, }; export type FrameHoverResult = void; export type FrameInnerHTMLParams = { @@ -3095,6 +3097,7 @@ export type ElementHandleHoverParams = { position?: Point, timeout?: number, trial?: boolean, + noWaitAfter?: boolean, }; export type ElementHandleHoverOptions = { force?: boolean, @@ -3102,6 +3105,7 @@ export type ElementHandleHoverOptions = { position?: Point, timeout?: number, trial?: boolean, + noWaitAfter?: boolean, }; export type ElementHandleHoverResult = void; export type ElementHandleInnerHTMLParams = {}; diff --git a/packages/protocol/src/protocol.yml b/packages/protocol/src/protocol.yml index 4c20c06b95..b73f6bde62 100644 --- a/packages/protocol/src/protocol.yml +++ b/packages/protocol/src/protocol.yml @@ -1767,6 +1767,7 @@ Frame: position: Point? timeout: number? trial: boolean? + noWaitAfter: boolean? tracing: snapshot: true pausesBeforeInput: true @@ -2332,6 +2333,7 @@ ElementHandle: position: Point? timeout: number? trial: boolean? + noWaitAfter: boolean? tracing: snapshot: true pausesBeforeInput: true diff --git a/tests/page/locator-misc-1.spec.ts b/tests/page/locator-misc-1.spec.ts index d42f9e6c48..bd04cf86a7 100644 --- a/tests/page/locator-misc-1.spec.ts +++ b/tests/page/locator-misc-1.spec.ts @@ -33,6 +33,16 @@ it('should hover when Node is removed', async ({ page, server }) => { expect(await page.evaluate(() => document.querySelector('button:hover').id)).toBe('button-6'); }); +it('hover should support noWaitAfter', async ({ page, server }) => { + await page.goto(server.EMPTY_PAGE); + await page.setContent(``); + await Promise.all([ + new Promise(fulfill => server.setRoute('/next', fulfill)), + page.locator('button').hover({ noWaitAfter: true }) + ]); + expect(page.url()).toBe(server.EMPTY_PAGE); +}); + it('should fill input', async ({ page, server }) => { await page.goto(server.PREFIX + '/input/textarea.html'); const handle = page.locator('input'); diff --git a/tests/page/page-mouse.spec.ts b/tests/page/page-mouse.spec.ts index 5db94071ec..2de1ba34ed 100644 --- a/tests/page/page-mouse.spec.ts +++ b/tests/page/page-mouse.spec.ts @@ -173,6 +173,16 @@ it('should trigger hover state', async ({ page, server }) => { expect(await page.evaluate(() => document.querySelector('button:hover').id)).toBe('button-91'); }); +it('hover should support noWaitAfter', async ({ page, server }) => { + await page.goto(server.EMPTY_PAGE); + await page.setContent(``); + await Promise.all([ + new Promise(fulfill => server.setRoute('/next', fulfill)), + page.hover('button', { noWaitAfter: true }) + ]); + expect(page.url()).toBe(server.EMPTY_PAGE); +}); + it('should trigger hover state on disabled button', async ({ page, server }) => { await page.goto(server.PREFIX + '/input/scrollable.html'); await page.$eval('#button-6', (button: HTMLButtonElement) => button.disabled = true);