Answer
Handling Shadow DOM in Selenium
Shadow DOM is an encapsulated DOM tree that hides internal implementation of web components. Standard findElement cannot cross the shadow boundary.
Understand the Structure
HTML
<!-- Host element -->
<custom-input id="myInput">
<!-- Shadow DOM (not visible to standard XPath/CSS) -->
#shadow-root
<input type="text" class="internal-input">
</custom-input>
Method 1 — Selenium 4 getShadowRoot() (Native Support)
Java
// Selenium 4.x has native shadow DOM support
WebElement host = driver.findElement(By.cssSelector("custom-input#myInput"));
SearchContext shadowRoot = host.getShadowRoot();
// Now find element inside shadow DOM
WebElement innerInput = shadowRoot.findElement(By.cssSelector("input.internal-input"));
innerInput.sendKeys("Hello Shadow DOM");
Method 2 — JavascriptExecutor (Selenium 3 / Fallback)
Java
JavascriptExecutor js = (JavascriptExecutor) driver;
// Access shadowRoot and find inner element
WebElement innerInput = (WebElement) js.executeScript(
"return document.querySelector('custom-input#myInput').shadowRoot.querySelector('input')"
);
innerInput.sendKeys("Hello");
Nested Shadow DOM
Java
WebElement outerInput = (WebElement) js.executeScript(
"return document.querySelector('outer-element')" +
".shadowRoot.querySelector('inner-element')" +
".shadowRoot.querySelector('input')"
);
outerInput.click();
Selenium 4 Chained Shadow Roots
Java
WebElement host1 = driver.findElement(By.cssSelector("outer-element"));
SearchContext shadow1 = host1.getShadowRoot();
WebElement host2 = shadow1.findElement(By.cssSelector("inner-element"));
SearchContext shadow2 = host2.getShadowRoot();
WebElement target = shadow2.findElement(By.cssSelector("button.submit"));
target.click();
When You Encounter Shadow DOM
- ✓Browser extensions UI
- ✓Google Pay / Stripe checkout widgets
- ✓Chrome built-in elements (like
<input type="date">) - ✓Custom web components (Lit, Stencil, Polymer)
Key Limitations
- ✓Cannot use XPath inside shadow DOM — only CSS selectors work within
getShadowRoot() - ✓
findElementsworks within shadow context but not across multiple shadow roots in one call - ✓Some closed shadow roots (
{mode: ''closed''}) cannot be accessed at all
