From 36b92d884700c5196152bb484170e65d9150a5c4 Mon Sep 17 00:00:00 2001 From: Ross Wollman Date: Mon, 8 Aug 2022 15:34:58 -0700 Subject: [PATCH] fix: toBeFocused should match shadow elements (#16362) Fixes #16268. --- .../src/server/injected/injectedScript.ts | 11 +++++-- .../playwright.expect.true.spec.ts | 29 +++++++++++++++++++ 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/packages/playwright-core/src/server/injected/injectedScript.ts b/packages/playwright-core/src/server/injected/injectedScript.ts index 898573c7ea..371aa6ec51 100644 --- a/packages/playwright-core/src/server/injected/injectedScript.ts +++ b/packages/playwright-core/src/server/injected/injectedScript.ts @@ -668,14 +668,19 @@ export class InjectedScript { return 'done'; } + private _activelyFocused(node: Node): { activeElement: Element | null, isFocused: boolean } { + const activeElement = (node.getRootNode() as (Document | ShadowRoot)).activeElement; + const isFocused = activeElement === node && !!node.ownerDocument && node.ownerDocument.hasFocus(); + return { activeElement, isFocused }; + } + focusNode(node: Node, resetSelectionIfNotFocused?: boolean): 'error:notconnected' | 'done' { if (!node.isConnected) return 'error:notconnected'; if (node.nodeType !== Node.ELEMENT_NODE) throw this.createStacklessError('Node is not an element'); - const activeElement = (node.getRootNode() as (Document | ShadowRoot)).activeElement; - const wasFocused = activeElement === node && node.ownerDocument && node.ownerDocument.hasFocus(); + const { activeElement, isFocused: wasFocused } = this._activelyFocused(node); if ((node as HTMLElement).isContentEditable && !wasFocused && activeElement && (activeElement as HTMLElement | SVGElement).blur) { // Workaround the Firefox bug where focusing the element does not switch current // contenteditable to the new element. However, blurring the previous one helps. @@ -1029,7 +1034,7 @@ export class InjectedScript { } else if (expression === 'to.be.enabled') { elementState = progress.injectedScript.elementState(element, 'enabled'); } else if (expression === 'to.be.focused') { - elementState = document.activeElement === element; + elementState = this._activelyFocused(element).isFocused; } else if (expression === 'to.be.hidden') { elementState = progress.injectedScript.elementState(element, 'hidden'); } else if (expression === 'to.be.visible') { diff --git a/tests/playwright-test/playwright.expect.true.spec.ts b/tests/playwright-test/playwright.expect.true.spec.ts index 8b6b892a6f..a4ea3fbcc7 100644 --- a/tests/playwright-test/playwright.expect.true.spec.ts +++ b/tests/playwright-test/playwright.expect.true.spec.ts @@ -352,6 +352,35 @@ test('should support toBeFocused', async ({ runInlineTest }) => { expect(result.exitCode).toBe(0); }); +test('should support toBeFocused with shadow elements', async ({ runInlineTest }) => { + const result = await runInlineTest({ + 'a.test.ts': ` + const { test } = pwt; + + test('focused', async ({ page }) => { + await page.setContent(\` +
+
+ + \`); + + await page.locator("input").focus(); + expect(await page.evaluate(() => document.activeElement.shadowRoot.activeElement.id)).toBe("my-input"); + await expect(page.locator("#app")).toBeFocused(); + await expect(page.locator("input")).toBeFocused(); + }); + `, + }, { workers: 1 }); + expect(result.passed).toBe(1); + expect(result.exitCode).toBe(0); +}); + test('should print unknown engine error', async ({ runInlineTest }) => { const result = await runInlineTest({ 'a.test.ts': `