Shadow DOM
Shadow DOM refers to the ability of the browser to include a subtree of DOM elements into the rendering of a document, but not into the main document DOM tree. Shadow DOM enables encapsulation. With Shadow DOM, a component can have its own “shadow” DOM tree that cannot be accidentally accessed from the main document. This subtree may have local style rules and more.
Identify Shadow DOM
You can identify if a site uses Shadow DOM through a browser's developer tools. Let's look at an example.
To identify Shadow DOM:
- In Google Chrome, navigate to https://shop.polymer-project.org.
- Open Developer tools (press the shortcut keys Fn+F12).
- On the Elements tab, expand the
<body>
element and the first element inside the<body>
element and notice the#shadow-root
line.
The following images show the element tree rendered for the desktop web view and for the mobile view.
Work with Shadow DOM elements using RemoteWebDriver
Trying to find Shadow DOM elements using Selenium locators results in NoSuchElementException
. To access these Shadow DOM elements, we need to use the JavascriptExecutor executeScript()
function. If you look at the DOM structure, every element that includes Shadow DOM also has a shadowRoot
property that describes the underlying elements.
The following methods help you interact with Shadow DOM elements. All parameters listed are mandatory.
A sample GitHub project is located at https://github.com/PerfectoMobileSA/ShadowDOMSample. The Shadow DOM Utility methods are located here.
findElementShadowDOM
This method finds the first Shadow DOM element matching the CSS selector and returns it.
Example: findElementShadowDOM method
WebElement parentShadowElement = driver.findElement(By.cssSelector("[page='home']"));
Map<String, Object> params = new HashMap<>();
params.put("parentElement", parentShadowElement);
params.put("innerSelector", "div#tabContainer shop-tabs shop-tab:nth-child(1) a");
WebElement innerDOMElement1 = findElementShadowDOM(((RemoteWebDriver)driver), params);
Parameters
Name | Type | Description |
---|---|---|
|
WebElement |
The |
|
String |
The CSS selector of the Shadow DOM inner element to search for |
findElementsShadowDOM
This method finds all Shadow DOM elements matching the CSS selector and returns the list.
Example: findElementShadowDOM method
WebElement parentShadowElement = driver.findElement(By.cssSelector("[page='home']"));
Map<String, Object> params = new HashMap<>();
params.put("parentElement", parentShadowElement);
params.put("innerSelector", "div#tabContainer shop-tabs shop-tab a");
List<WebElement> allMatchingLinks = findElementsShadowDOM(((RemoteWebDriver)driver), params);
Parameters
Name | Type | Description |
---|---|---|
|
WebElement |
The |
|
String |
The CSS selector of the Shadow DOM inner element to search for |
clickElementShadowDOM
This method performs a click operation on the first matching Shadow DOM element of the specified CSS locator.
Example: clickElementShadowDOM method
WebElement parentShadowElement = driver.findElement(By.cssSelector("[page='home']"));
Map<String, Object> params = new HashMap<>();
params.put("parentElement", parentShadowElement);
params.put("innerSelector", "div#tabContainer shop-tabs shop-tab:nth-child(1) a");
clickElementShadowDOM(((RemoteWebDriver)driver), params);
Parameters
Name | Type | Description |
---|---|---|
|
WebElement |
The |
|
String |
The CSS selector of the shadow DOM inner element to search for |
sendKeysShadowDOM
This method enters text on the first matching Shadow DOM element of the specified CSS locator.
Example: sendKeysShadowDOM method
WebElement parentShadowElement = driver.findElement(By.cssSelector("[page='home']"));
Map<String, Object> params = new HashMap<>();
params.put("parentElement", parentShadowElement);
params.put("innerSelector", "div#tabContainer input#id1");
params.put("characterSequence", "Text to enter");
sendKeysShadowDOM(((RemoteWebDriver)driver), params);
Parameters
Name | Type | Description |
---|---|---|
|
WebElement |
The |
|
String |
The CSS-selector of the Shadow DOM inner element to search for |
|
String |
The text to insert in the edit field |
getTextShadowDOM
This method returns the text of the first matching Shadow DOM element of the specified CSS locator.
Example: getTextShadowDOM method
WebElement homeEleShadowParent = driver.findElement(By.cssSelector("[page='home']"));
Map<String, Object> params = new HashMap<>();
params.put("parentElement", homeEleShadowParent);
params.put("innerSelector", "div#tabContainer shop-tabs shop-tab:nth-child(1) a");
String elementText = getTextShadowDOM(((RemoteWebDriver)driver), params);
Parameters
Name | Type | Description |
---|---|---|
|
WebElement |
The |
|
String |
The CSS selector of the Shadow DOM inner element to search for |
getAttributeShadowDOM
This method returns the value of the given attribute of the first matching Shadow DOM element of the specified CSS locator.
Example: getAttributeShadowDOM method
WebElement homeEleShadowParent = driver.findElement(By.cssSelector("[page='home']"));
Map<String, Object> params = new HashMap<>();
params.put("parentElement", homeEleShadowParent);
params.put("innerSelector", "div#tabContainer shop-tabs shop-tab:nth-child(1) a");
params.put("attribute", "href");
String linkURL = getAttributeShadowDOM(((RemoteWebDriver)driver), params);
Parameters
Name | Type | Description |
---|---|---|
|
WebElement |
The |
|
String |
The CSS selector of the Shadow DOM inner element to search for |
|
String |
The name of the attribute |
Work with nested Shadow DOM elements
Working with nested shadow DOM elements is similar to working with single-level Shadow DOM elements:
- Get the
WebElement
of theshadow-root
usingfindElementShadowDOM
method, . - Provide this root
WebElement
as aparentElement
to its child Shadow DOM element. - Proceed until you reach the final Shadow DOM element.
Example: Nested Shadow DOM element
//Find the parent Shadow DOM element
WebElement parentShadowEle = driver.findElement(By.xpath("//vt-virustotal-app"));
Map<String, Object> params = new HashMap<>();
params.put("parentElement", parentShadowEle);
params.put("innerSelector", "home-view.iron-selected");
WebElement innerDOMElement1 = findElementShadowDOM(driver, params);
Map<String, Object> params2 = new HashMap<>();
params2.put("parentElement", innerDOMElement1);
params2.put("innerSelector", "#urlSearchInput");
WebElement innerDOMElement2 = findElementShadowDOM(driver, params);
//Find until the final Shadow DOM element is found.
Map<String, Object> params3 = new HashMap<>();
params3.put("parentElement", innerDOMElement2);
params3.put("innerSelector", "input#input");
WebElement finalShadowDOMElement = findElementShadowDOM (driver, params);
Integrate with Perfecto Smart Reporting
To integrate Shadow DOM with Perfecto Smart Reporting, contact the Perfecto Professional Services team.
The following images show the sample TestShadowDOM report in the CI Dashboard view.