Module · DOM & Web Components
Shadow DOM Lab
Locate the Host → Traverse the Shadow Root → Interact → Validate.
Practice automating modern Web Components and Shadow DOM boundaries. Work with open Shadow Roots, nested components, closed Shadow DOM, controls hidden inside components, and external navigation triggered from Shadow DOM.
Open Shadow Root Login Form
Locate the Shadow Host, access the controls inside the open Shadow Root, complete the login workflow, and validate the result.
Nested Shadow DOM
Shadow DOM can nest: a component inside a component inside a component, each with its own Shadow Root. Automation has to traverse every boundary, not just the outermost one.
Shadow DOM lets a component attach its own, separate mini-DOM tree to an element in the page — that element is the Shadow Host, and the mini-tree hanging off it is the Shadow Root. Everything inside the Shadow Root is encapsulated: its markup and CSS don't leak out, and by default the page's own CSS doesn't leak in either.
Open vs. closed
host.shadowRoot returns the real ShadowRoot object. Any script — including Playwright's own — can read into it. This is what every control on this page uses.
host.shadowRoot returns null. There is no public API in, by design — see Section 3.
Web component libraries lean on this to ship reusable, drop-in elements — the login form here behaves like a real one you'd find in a design system or a third-party widget.
Each host below attaches its own independent, open Shadow Root — Host 2 lives inside Host 1's shadow tree, and Host 3 lives inside Host 2's. The Submit button is three shadow boundaries deep from the page's own light DOM.
This is a realistic scenario, not a contrived one: a design system's <data-table> might contain a <row-actions> component, which itself contains a <icon-button>. Each layer is authored and shipped independently, so each gets its own shadow root.
Closed Shadow Root
This component uses a closed Shadow Root. The internal DOM is intentionally not exposed through the public Shadow DOM API. Can you automate this?
🔒 Closed Shadow Root
element.shadowRoot === null
No public traversal API available.
Requires application changes. Closed mode has no public traversal API. This demonstrates a real interview limitation.
Product Search (all controls inside shadow roots)
These controls live inside a Shadow Root. Your automation must locate and interact with them correctly — search, filter by category, filter by price, then read the results.
attachShadow({ mode: 'closed' }) is a real, standard option — not a Playwright limitation. Once set, the browser itself refuses to hand back a reference to the shadow root:
const host = document.querySelector('#closed-shadow-host');
host.shadowRoot; // null — even though the shadow root genuinely existsOpen Shadow DOM can be traversed by any script, including Playwright's. Closed Shadow DOM cannot be directly traversed through the normal public API — full stop. This is an application/component design boundary, not simply a bad XPath or CSS selector that needs fixing.
Component authors reach for closed mode when they want to guarantee their internal implementation is truly private — not even other scripts on the same page should be able to poke inside it.
The search input, category dropdown, price dropdown, and Search button are all inside one open Shadow Root. The result cards render in the light DOM, which is a deliberate, realistic split: plenty of real component libraries render their own controls in a shadow tree while leaving results/output in the surrounding page.
Treat this as a practical assignment: locate every control by its testid inside the shadow root, drive a real search, and assert on the light-DOM results — including the no-results state.
Shadow Settings Panel
Select a theme, toggle Email Alerts, click Apply Settings, and validate the resulting state — every control lives inside an open Shadow Root.
External Navigation Challenge
Locate an external link inside a Shadow Root and handle the newly opened page — Shadow DOM and popup/new-page handling often show up together in real applications.
The theme radios are plain native inputs — check()/isChecked() work on them exactly like in the light DOM, once your locator reaches inside the shadow root. The Email Alerts control is a real <input type="checkbox"> too, but it's visually hidden behind a custom slider (a common toggle-switch pattern), so it isn't directly clickable — automate it by clicking its wrapping <label>, the same element a real user would click.
Each link below is a normal <a target="_blank"> element, just rendered inside an open Shadow Root — finding it works exactly like Section 1's form controls. What's new here is the second half: handling the browser opening a new tab/page as a result of the click.