diff --git a/src/firefox/snapshot/injected/elementCollector.ts b/src/firefox/snapshot/injected/elementCollector.ts index cfcc1460..fb766035 100644 --- a/src/firefox/snapshot/injected/elementCollector.ts +++ b/src/firefox/snapshot/injected/elementCollector.ts @@ -59,7 +59,7 @@ export function isVisible(el: Element): boolean { } catch { return false; } - current = current.parentElement; + current = current.parentElement ?? (current.getRootNode() as ShadowRoot).host ?? null; } return true; diff --git a/src/firefox/snapshot/injected/treeWalker.ts b/src/firefox/snapshot/injected/treeWalker.ts index 12774e8b..0e31897f 100644 --- a/src/firefox/snapshot/injected/treeWalker.ts +++ b/src/firefox/snapshot/injected/treeWalker.ts @@ -115,13 +115,14 @@ export function walkTree( } } else { // Walk regular children - for (let i = 0; i < el.children.length; i++) { + const children = el.shadowRoot ? [...el.shadowRoot.children, ...el.children] : el.children; + for (let i = 0; i < children.length; i++) { if (nodeCount >= MAX_NODES) { truncated = true; break; } - const child = el.children[i]; + const child = children[i]; if (!child) { continue; } diff --git a/tests/integration/firefox/snapshot.test.ts b/tests/integration/firefox/snapshot.test.ts index f2441e7d..45e1df78 100644 --- a/tests/integration/firefox/snapshot.test.ts +++ b/tests/integration/firefox/snapshot.test.ts @@ -233,4 +233,46 @@ describe('Snapshot Integration Tests', () => { expect(hasInvisibleButton).toBe(false); }, 10000); + + describe('shadow DOM', () => { + const SHADOW_PAGE = + 'data:text/html,' + + ''; + + function shadowNode(snapshot: { json: { root: SnapshotNode } }, id: string) { + return findNodeInSnapshot(snapshot.json.root, (node) => node.id === id); + } + + it('should include the elements of an open shadow root', async () => { + await firefox.navigate(SHADOW_PAGE); + + const snapshot = await firefox.takeSnapshot(); + + expect(shadowNode(snapshot, 'deepBtn')?.name).toBe('Deep'); + }, 10000); + + it('should let shadow DOM elements be filled and clicked by UID', async () => { + await firefox.navigate(SHADOW_PAGE); + const snapshot = await firefox.takeSnapshot(); + + await firefox.fillByUid(shadowNode(snapshot, 'deep')!.uid, 'abc'); + await firefox.clickByUid(shadowNode(snapshot, 'deepBtn')!.uid); + + expect(await firefox.evaluate('document.getElementById("out").textContent')).toBe('open:abc'); + }, 10000); + + it('should leave out the shadow root of a hidden host', async () => { + await firefox.navigate(SHADOW_PAGE); + + const snapshot = await firefox.takeSnapshot({ includeAll: true }); + + expect(findNodesInSnapshot(snapshot.json.root, (node) => node.id === 'deepBtn')).toHaveLength( + 1 + ); + }, 10000); + }); });