diff --git a/packages/playwright-core/src/server/injected/injectedScript.ts b/packages/playwright-core/src/server/injected/injectedScript.ts index dfa15dddeb..6bd763d43e 100644 --- a/packages/playwright-core/src/server/injected/injectedScript.ts +++ b/packages/playwright-core/src/server/injected/injectedScript.ts @@ -208,6 +208,12 @@ export class InjectedScript { throw this.createStacklessError('Internal error: there should not be a capture in the selector.'); } + // Workaround so that ":scope" matches the ShadowRoot. + // This is, unfortunately, because an ElementHandle can point to any Node (including ShadowRoot/Document/etc), + // and not just to an Element, and we support various APIs on ElementHandle like "textContent()". + if (root.nodeType === 11 /* Node.DOCUMENT_FRAGMENT_NODE */ && selector.parts.length === 1 && selector.parts[0].name === 'css' && selector.parts[0].source === ':scope') + return [root as Element]; + this._evaluator.begin(); try { let roots = new Set([root as Element]); diff --git a/tests/page/elementhandle-convenience.spec.ts b/tests/page/elementhandle-convenience.spec.ts index e57d2552ec..21ef4c85bf 100644 --- a/tests/page/elementhandle-convenience.spec.ts +++ b/tests/page/elementhandle-convenience.spec.ts @@ -88,6 +88,20 @@ it('textContent should work', async ({ page, server }) => { expect(await page.textContent('#inner')).toBe('Text,\nmore text'); }); +it('textContent should work on ShadowRoot', async ({ page, server }) => { + await page.setContent(` +
+ + `); + const div = await page.$('div'); + const root = await div.evaluateHandle(div => div.shadowRoot); + expect(await root.textContent()).toBe('hello'); + // We do not match ShadowRoot as ":scope". + expect(await root.$$(':scope div')).toEqual([]); +}); + it('isVisible and isHidden should work', async ({ page }) => { await page.setContent(`
Hi
`);