17

I am using Java Selenium project for web page automation. The web page contains lots of multi-level shadow-root DOM elements that I am not able to interact with using selenium findElement method.

I have tried the following solutions:

  • deep css (Don't work on latest chrome browser)
  • JS Executor. (This is really tedious and becomes complex to maintain)

Note:

If you know any other solution other than listed above that I can implement in Selenium Java framework , please pass on the solution. Thanks in advance !.

SushilG
  • 515
  • 1
  • 4
  • 19

4 Answers4

6

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();
T J
  • 41,966
  • 13
  • 81
  • 134
Garima
  • 92
  • 2
  • Welcome to StackOverflow! Thank you for your detailed response. Please support it with references. We have guidelines on [How to write a good answer](https://stackoverflow.com/help/how-to-answer). – Joseph Cho May 15 '19 at 13:39
6

Steps to find out shadow DOM elements using JSExecutor And CSS:

  1. Find out base element i.e parent element of Shadow root element.

  2. Get Shadow root of that element.

  3. 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]"));
  • It looks like this does not work with Chrome 96+ anymore. When calling `driver.executeScript("return arguments[0].shadowRoot", element)` it will return a com.google.common.collect.Maps$TransformedEntriesMap which cannot be cast to a WebElement. Any Ideas how to fix this? – Jörg Rech Dec 01 '21 at 22:08
  • You need to cast to `SearchContext` or use the new `getShadowRoot()` method: https://titusfortner.com/2021/11/22/shadow-dom-selenium.html – titusfortner Dec 13 '21 at 05:32
5

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:

shadowDOM


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.

undetected Selenium
  • 151,581
  • 34
  • 225
  • 281
3

With Selenium 4 there is now WebElement.getShadowRoot(). For example:

driver.findElement(By.id("parentId")).getShadowRoot().findElement(By.cssSelector( "label" )).findElement(By.tagName("input"))

As normal with a #shadow-root, the navigation choices for the next hop are limited. E.g. against Chrome By.cssSelector() and By.className() are valid, but By.id() and By.tagName() fail with org.openqa.selenium.InvalidArgumentException: invalid argument: invalid locator

df778899
  • 10,365
  • 1
  • 22
  • 33