Playwright - Interacting with Closed Shadow DOM in iFrames
The Problem: Closed Shadow DOM link
When testing iFrames with Playwright (which, unlike Cypress, has excellent iframe support), you may encounter a particularly tricky scenario: the closed Shadow DOM. In your browser's DevTools, it looks like this:
Notice the #shadow-root (closed) label. This is where things get interesting.
Why Shadow DOM is Closed link
The Shadow DOM provides encapsulation for web components, isolating their internal structure from the rest of the page. When a Shadow DOM is set to closed mode, it's done for security and encapsulation reasons:
- Security: Prevents external JavaScript from accessing or manipulating internal component structure
- Encapsulation: Ensures component internals remain private and protected from outside interference
- API Contract: Forces interaction through the component's public API only
While these are valid architectural decisions, they create a challenge for automated testing where you need to verify the component's internal state.
The Testing Challenge link
When you try to use Playwright to interact with elements inside a closed Shadow DOM, you'll encounter errors because Playwright cannot pierce through the closed boundary. The DOM query simply fails to find the elements you're looking for.
For most test engineers, this seems like game over. However, there's a clever workaround.
The Solution: Force Open Shadow DOM link
The key insight is that we can intercept the Shadow DOM creation at the browser level and force all Shadow DOMs to be created in open mode instead:
Notice the difference: #shadow-root (open) allows Playwright to access the internal elements.
Implementation link
Here's the function that makes this possible:
This function uses Playwright's addInitScript() to inject JavaScript that runs before any page scripts load. It:
- Saves the original
attachShadowmethod - Overrides
Element.prototype.attachShadow - Intercepts all Shadow DOM creation calls
- Forces the mode to
'open'regardless of what the component requests - Maintains all other initialization options
Usage in Tests link
Add this to your test setup to apply it to all tests:
Alternatively, for specific tests:
Important Considerations link
- Test Environment Only: This technique should only be used in testing environments. It fundamentally changes how the application behaves.
- Timing Matters: Call
forceOpenShadowDOM()before navigating to your page, as it needs to inject the script before the page loads. - iFrame Support: This works within iFrames as well, making it perfect for testing embedded components.
This approach gives you the testing capability you need while respecting that closed Shadow DOM serves legitimate purposes in production code.
Feel free to update this blog post on GitHub, thanks in advance!