Shadow DOM
Every shadow-root scenario you'll meet in real apps: open, nested, closed, closed-inside-open, SVG inside a shadow root, an iframe inside a shadow root, and shadow roots inside an iframe. Page-level CSS/XPath can't see inside — you have to pierce each boundary the way your tool supports.
1. Open shadow roots
Two custom elements with open shadow roots — the everyday case.
💡 Playwright pierces open shadow roots automatically; Selenium 4 uses host.getShadowRoot(); Cypress uses .shadow() or includeShadowDom: true. XPath never crosses a shadow boundary — use CSS.
2. Nested shadow DOM
A shadow root inside another shadow root.
💡 Selenium: outer.getShadowRoot().findElement(inner).getShadowRoot() — one hop per level. Playwright and Cypress (with includeShadowDom) walk nested open roots for you.
3. Closed shadow DOM
mode: "closed" — element.shadowRoot returns null, so no tool can query inside.
💡 Click the host (it delegates focus to the input), type with the keyboard, press Tab then Enter; assert the host's data-result attribute. Alternative: before the page loads, patch Element.prototype.attachShadow to force mode: "open" (Playwright addInitScript, Cypress onBeforeLoad, Selenium CDP).
4. Closed shadow DOM inside an open one
You can reach the open level normally, then hit a closed boundary.
5. SVG inside a shadow root
Clickable SVG icons that live inside a shadow root.
💡 SVG elements need *[name()='svg'] in XPath — but inside a shadow root use CSS such as svg[data-icon='star'] after piercing the root.
6. iframe inside a shadow root
Pierce the shadow root first, then switch into the frame.
💡 Selenium: get the iframe element from getShadowRoot(), then switchTo().frame(thatElement). Playwright: page.frameLocator('[data-testid=iframe-in-shadow]') works through open roots.
7. Shadow DOM inside an iframe
Switch into the frame first, then pierce its open, nested and closed shadow roots.