How to automate shadow DOM elements using selenium?
Solution 1:
There is a very good plugin that can be used with selenium project shadow-automation-selenium. It helps in writing much better, readable and maintainable code. Using this you can access multi level of shadow DOM (upto 4 levels ) . This uses simple css selector to identify elements.
WebElement findElement(String cssSelector)
: use this method if want single element from DOM
List<WebElement> findElements(String cssSelector)
: use this if you want to find all elements from DOM
WebElement findElements(WebElement parent, String cssSelector)
: use this if you want to find a single elements from parent object DOM
List<WebElement> findElements(WebElement parent, String cssSelector)
: use this if you want to find all elements from parent object DOM
WebElement getShadowElement(WebElement parent,String selector)
: use this if you want to find a single element from parent DOM
List<WebElement> getAllShadowElement(WebElement parent,String selector)
: use this if you want to find all elements from parent DOM
boolean isVisible(WebElement element)
: use this if you want to find visibility of element
boolean isChecked(WebElement element)
: use this if you want to check if checkbox is selected
boolean isDisabled(WebElement element)
: use this if you want to check if element is disabled
String getAttribute(WebElement element,String attribute)
: use this if you want to get attribute like aria-selected and other custom attributes of elements.
void selectCheckbox(String label)
: use this to select checkbox element using label.
void selectCheckbox(WebElement parentElement, String label)
: use this to select checkbox element using label.
void selectRadio(String label)
: use this to select radio element using label.
void selectRadio(WebElement parentElement, String label)
: use this to select radio element from parent DOM using label.
void selectDropdown(String label)
: use this to select dropdown list item using label (use this if only one dropdown is present or loaded on UI).
void selectDropdown(WebElement parentElement, String label)
: use this to select dropdown list item from parent DOM using label.
How to use this plugin: You will have to dependency in your project.
Maven
<dependency>
<groupId>io.github.sukgu</groupId>
<artifactId>automation</artifactId>
<version>0.0.4</version>
<dependency>
for html tag that resides under a shadow-root dom element
<properties-page id="settingsPage">
<textarea id="textarea">
</properties-page>
You can use this code in your framework to grab the textarea element Object.
import io.github.sukgu.*;
Shadow shadow = new Shadow(driver);
WebElement element = shadow.findElement("properties-page#settingsPage>textarea#textarea");
String text = element.getText();
Solution 2:
To demonstrate automation of shadow DOM using Selenium v3.x, ChromeDriver v2.46 and Chrome v73.x here are a couple of approaches which opens the url chrome://downloads/
and using the executeScript()
method sends the character sequence pdf as the search text within the Search Box.
Using document.querySelector()
As a canonical approach you can use document.querySelector()
method as follows:
-
Code Block:
import org.openqa.selenium.JavascriptExecutor; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.chrome.ChromeDriver; import org.openqa.selenium.chrome.ChromeOptions; public class shadow_DOM_search_download_querySelector { public static void main(String[] args) { System.setProperty("webdriver.chrome.driver", "C:\\Utility\\BrowserDrivers\\chromedriver.exe"); ChromeOptions options = new ChromeOptions(); options.addArguments("start-maximized"); options.addArguments("disable-infobars"); options.addArguments("--disable-extensions"); WebDriver driver = new ChromeDriver(options); driver.get("chrome://downloads/"); JavascriptExecutor jse = (JavascriptExecutor) driver; WebElement search_box = (WebElement) jse.executeScript("return document.querySelector('downloads-manager').shadowRoot.querySelector('downloads-toolbar#toolbar').shadowRoot.querySelector('cr-toolbar#toolbar').shadowRoot.querySelector('cr-toolbar-search-field#search').shadowRoot.querySelector('div#searchTerm input#searchInput')"); String js = "arguments[0].setAttribute('value','pdf')"; ((JavascriptExecutor) driver).executeScript(js, search_box); } }
The same solution can be re-written in a step wise fashion as follows:
-
Code Block:
import org.openqa.selenium.By; import org.openqa.selenium.JavascriptExecutor; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.chrome.ChromeDriver; import org.openqa.selenium.chrome.ChromeOptions; public class shadow_DOM { static WebDriver driver; public static void main(String[] args) { System.setProperty("webdriver.chrome.driver", "C:\\Utility\\BrowserDrivers\\chromedriver.exe"); ChromeOptions options = new ChromeOptions(); options.addArguments("start-maximized"); //options.addArguments("disable-infobars"); options.addArguments("--disable-extensions"); driver = new ChromeDriver(options); driver.get("chrome://downloads/"); WebElement root1 = driver.findElement(By.tagName("downloads-manager")); WebElement shadow_root1 = expand_shadow_element(root1); WebElement root2 = shadow_root1.findElement(By.cssSelector("downloads-toolbar#toolbar")); WebElement shadow_root2 = expand_shadow_element(root2); WebElement root3 = shadow_root2.findElement(By.cssSelector("cr-toolbar#toolbar")); WebElement shadow_root3 = expand_shadow_element(root3); WebElement root4 = shadow_root3.findElement(By.cssSelector("cr-toolbar-search-field#search")); WebElement shadow_root4 = expand_shadow_element(root4); WebElement search_term = shadow_root4.findElement(By.cssSelector("div#searchTerm input#searchInput")); String js = "arguments[0].setAttribute('value','pdf')"; ((JavascriptExecutor) driver).executeScript(js, search_term); WebElement search_button = shadow_root4.findElement(By.cssSelector("paper-icon-button#icon")); search_button.click(); System.out.println("Search Button Clicked"); } public static WebElement expand_shadow_element(WebElement element) { WebElement shadow_root = (WebElement)((JavascriptExecutor)driver).executeScript("return arguments[0].shadowRoot", element); return shadow_root; } }
-
Console Output:
Search Button Clicked
- Browser Snapshot:
Outro
As per the discussion in Determine the fate of experimental '>>>' combinator the >>>
combinator, which was the replacement for /deep/
combinator for piercing all the shadow DOM boundaries to style, which was implemented behind the flag in Blink is deprecated.
- Make /deep/ behave like the descendant combinator " " in CSS live profile (in css file or inside of )
- Shadow-piercing descendant combinator (>>>) in snapshot profile
- Shadow-Piercing descendant combinator, '/deep/' (aka '>>>') for dynamic profile (in stylesheets) (removed)
Solution 3:
Steps to find out shadow DOM elements using JSExecutor And CSS:
-
Find out base element i.e parent element of Shadow root element.
-
Get Shadow root of that element.
-
And Find your Element on that shadow-root webelement
example :
<div id="example">
#shadow-root
<div id="root" part="root">
<div id="label" part="label">ShadowRootLabel</div>
</div>
</ptcs-label>
#Method to find out Shadow Root Element
public WebElement getShadowRootElement(WebElement element) {
WebElement ele = (WebElement) ((JavascriptExecutor)driver)
.executeScript("return arguments[0].shadowRoot", element);
return ele;
}
#Step1 for Example i.e find Base Element:
WebElement root1 = driver.findElement(By.id("example"));
#Step2
//Get shadow root element
WebElement shadowRoot1 = getShadowRootElement(root1);
#Step3 - We need to find elements using CSS Selector which are inside shadow root, xpath will not work here
//Here we will get Element inside Shadow Dom Element
WebElement shadowElement = shadowRoot3.findElement(By.cssSelector("div[id=label]"));