Shadow DOM Playground
Most of this page lives inside shadow roots, where document.querySelector
can't see it. Type a CSS selector and the playground matches it the way Playwright
does — piercing every open shadow root — and tells you how many
of those matches plain DOM queries would have missed.
CSS
Challenges
How the page is built
| Host element | Shadow root | Inside |
|---|---|---|
user-card#card-host | open | .card → h2.name, button.edit |
settings-panel#panel-host | open | select.theme, and a nested toggle-switch |
toggle-switch#notify-host | open (nested) | button.toggle |
payment-widget#payment-host | closed | input.card-number — unreachable |
Try input.card-number: it really is on the page, but a closed root
hides it from scripts, so neither Selenium nor Playwright can locate it. In real
projects, ask developers for an open root or a test hook.
Selenium vs Playwright
| Playwright | Selenium 4 | |
|---|---|---|
| Open roots | CSS and getBy* pierce them automatically | Get the host, then getShadowRoot() |
| Nested roots | One selector, e.g. toggle-switch button | Chain getShadowRoot() once per level |
| XPath | Does not pierce shadow roots | Not supported inside a shadow root |
| Closed roots | Unreachable | Unreachable |
// Selenium 4 (Java): one getShadowRoot() per level, CSS only inside a root
SearchContext panel = driver.findElement(By.cssSelector("settings-panel")).getShadowRoot();
SearchContext toggle = panel.findElement(By.cssSelector("toggle-switch")).getShadowRoot();
toggle.findElement(By.cssSelector("button.toggle")).click();
// Playwright (TypeScript): one selector pierces every open root
await page.locator('toggle-switch button').click();Want to automate a real shadow DOM component end to end? Head to the Shadow DOM practice page.