Skip to content

Instantly share code, notes, and snippets.

@emanlove
Created October 11, 2025 20:27
Show Gist options
  • Select an option

  • Save emanlove/dd72f6604880f17bb0514023b1237326 to your computer and use it in GitHub Desktop.

Select an option

Save emanlove/dd72f6604880f17bb0514023b1237326 to your computer and use it in GitHub Desktop.
ShadowDOM piercing locator via a plugin for SeleniumLibrary + Robot Framework
*** Settings ***
Library SeleniumLibrary plugins=${CURDIR}/ShadowDOMPlugin.py
*** Test Cases ***
Nested Shadow DOM
Open Browser http://localhost:7272/shadow_dom.html chrome
Page Should Contain Element id:shadow_host >> id:nested_shadow_host >> id:nested_div
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Shadow DOM</title>
</head>
<body>
<div id="on_page">
<span>This is on the page DOM</span>
</div></br>
<div id="shadow_host"></div></br>
<div id="delayed_attached"></div>
<a href="javascript:return false;" onclick="attachAfterDelay(); return false;">Attach with delay</a><br/>
<div id="closed_host">This is a closed host</div></br>
<div id="delayed_open"></div></br>
<a href="javascript:return false;" onclick="openAfterDelay(); return false;">Open with delay</a><br/>
<script>
let shadowRoot = document.getElementById('shadow_host').attachShadow({mode: 'open'});
shadowRoot.innerHTML = `
<span>This is within the shadow dom</span>
<div id="nested_shadow_host"></div>
<input type="edit" />
`;
let nestedShadowRoot = shadowRoot.getElementById('nested_shadow_host').attachShadow({mode: 'open'});
nestedShadowRoot.innerHTML = `
<div id="nested_div"><div>This is within a nested shadowDOM</div></div>
`;
</script>
<script>
function attachAfterDelay() {
setTimeout('attachShadowDom()', 750)
};
function attachShadowDom() {
let delayedAttachedShadowRoot = document.getElementById('delayed_attached').attachShadow({mode: 'open'});
delayedAttachedShadowRoot.innerHTML = `
<div id="delayed_attached_content">delayed attached</div>
`;
};
let delayedOpenShadowRoot = document.getElementById('delayed_open').attachShadow({mode: 'closed'});
delayedOpenShadowRoot.innerHTML = `
<div id="delayed_opened_content">
<div>
This is within once/currently closed shadowDOM</br>
and will be/has been opened after a delay.
</div>
</div>
`;
function openAfterDelay() {
setTimeout('openShadowDom()', 750)
};
function openShadowDom() {
// delayedOpenShadowRoot.attachShadow({mode: 'open'});
document.getElementById('delayed_open').attachShadow({mode: 'open'});
};
</script>
</body>
</html>
from robot.api import logger
from selenium.common.exceptions import NoSuchShadowRootException
from SeleniumLibrary.errors import ElementNotFound
from SeleniumLibrary.base import LibraryComponent, keyword
from SeleniumLibrary.locators import ElementFinder
class ShadowDOMFinder(ElementFinder):
def _find(self, locator, tag=None, first_only=True, required=True, parent=None):
logger.info('Using ShadowDOM enabled finder')
parent_is_shadowdom_host = False
element_type = "Element" if not tag else tag.capitalize()
if parent and not self._is_webelement(parent):
raise ValueError(
f"Parent must be Selenium WebElement but it was {type(parent)}."
)
if self._is_webelement(locator):
return locator
try:
if parent:
parent_is_shadowdom_host = parent.shadow_root
except NoSuchShadowRootException:
pass
if parent_is_shadowdom_host:
parent = parent_is_shadowdom_host
prefix, criteria = self._parse_locator(locator)
strategy = self._strategies[prefix]
tag, constraints = self._get_tag_and_constraints(tag)
elements = strategy(criteria, tag, constraints, parent=parent or self.driver)
if required and not elements:
raise ElementNotFound(f"{element_type} with locator '{locator}' not found.")
if first_only:
if not elements:
return None
return elements[0]
return elements
class ShadowDOMPlugin(LibraryComponent):
def __init__(self, ctx):
LibraryComponent.__init__(self, ctx)
ctx._original_element_finder = ElementFinder(ctx)
self.element_finder = ShadowDOMFinder(ctx)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment