When testing web apps, the DOM is not always as straightforward as it looks.
Sometimes the element you “see” in DevTools isn’t accessible to your test. Why? Because it’s wrapped inside iframes or shadow DOM.
In this article, let’s break down what shadow DOM is, why it matters, and how you can test it with Cypress.
What is Shadow DOM?
Shadow DOM is an encapsulated subtree of the DOM that lives inside a host element.
It was introduced as part of the Web Components spec and is widely used in modern UI libraries (Material UI, Ionic, Lit, Angular components).
Key benefits:
- CSS and JS are scoped (no global leaks)
- Components are reusable
- Structure is hidden from the “main” DOM
Think of it as a mini DOM inside the DOM.
Types of Shadow DOM
Normal DOM (baseline)
<div id="login"> <input id="username" /> </div>
Cypress:
cy.get('#username').type('hello')
Shadow DOM (open)
<my-login> #shadow-root (open) <input id="username" /> </my-login>
Option A: Use .shadow()
in Cypress
cy.get('my-login') .shadow() .find('#username') .type('hello')
Option B: Enable globally in cypress.config.js
module.exports = { e2e: { includeShadowDom: true } }
Shadow DOM (closed)
<my-login> # — - > shadow-root (closed) <input id="username" /> </my-login>
⚠️ Closed shadow roots cannot be accessed with Cypress or regular query selectors.
They are designed to be private.
Workaround: you’ll need to use component-level testing, or rely on app exposing test hooks (e.g. props or attributes).
Slotted Elements
Sometimes elements are “projected” into a shadow DOM via a slot:
<my-login> #shadow-root (open) <slot name="email"></slot> </my-login> <input slot="email" id="email-input" />
Custom Cypress command:
Cypress.Commands.add('getSlottedElement', (selector) => { return cy.get(selector).then(($el) => { return cy.wrap($el[0].assignedNodes()[0]) }) })
Usage
cy.getSlottedElement('#email-input').type('hello@qa.com')
Why Shadow DOM Matters in Automation
- It’s everywhere in modern SPAs and Web Components
- Your tests may fail with “element not found” unless you account for it
- Handling shadow DOM properly makes tests stable and future-proof
Remember that:
- DOM ≠always accessible elements
- Web apps can include: plain DOM, iframes, Shadow DOM (open/closed)
- Cypress supports open shadow DOM with .shadow() or includeShadowDom config property
- For slotted elements, you may need custom helpers.
.shadow()
can’t see slotted content directly, because it lives outside the shadow root and is just projected into it - For closed shadow DOM, collaborate with devs for test hooks
If you’re new to automation:
Start experimenting with Cypress and a simple Web Component demo.
You’ll quickly see why handling
shadow DOM is a must have skill.
Top comments (0)