Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to automate shadow DOM elements using selenium?

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 !.

like image 883
SushilG Avatar asked Apr 19 '19 12:04

SushilG


People also ask

Which selector can work for shadow DOM elements?

The :host selector allows to select the shadow host (the element containing the shadow tree).

How to work with shadow DOM in Selenium?

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.


3 Answers

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

like image 144
df778899 Avatar answered Oct 22 '22 16:10

df778899


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]"));
like image 36
Pranita Dhasade Avatar answered Oct 22 '22 18:10

Pranita Dhasade


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.

  • 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)
like image 34
undetected Selenium Avatar answered Oct 22 '22 16:10

undetected Selenium