Handling Shadow DOM in Selenium

What is a Shadow DOM ?

Simply speaking, shadow DOM is an extra layer of security in the DOM. As per the the numerous articles in the internet "Shadow DOM serves for encapsulation. It allows a component to have its very own “shadow” DOM tree, that can’t be accidentally accessed from the main document, may have local style rules, and more."

How to access it?

It is like iframe or frames in the DOM structure. In Selenium, we can switch to an iframe/frame from the default context, but for shadow DOM, we can't do that. We will have to use the JavascriptExecutor interface.

How does it look like in DOM?

Let's take an example of the site : https://selectorshub.com/iframe-in-shadow-dom/

DOM Structure for the field UserName :

If we want to find the element using the Developer's tool, there will 0 results :

Finding the element using Java Script :

But we can see the "userName" element from the Console tab using Java Script in the Developer's tool. We need to write below :

document.querySelector("#userName")

Here document means the root element of the DOM. querySelector() methods takes only CSS selectors as inputs.

Expanding the div element, it will show the shadow DOM elements :

Finding element using JavascriptExecutor :

JavascriptExecutor is an interface that enables a WebDriver to interact with the HTML. It provides various methods. We will use the executeScript() to find the element under the shadow DOM.

public class HandlingShadowDOM {
    public static void main(String args[]) {
        WebDriver driver = new ChromeDriver();        
        driver.get("https://selectorshub.com/iframe-in-shadow-dom/");
        JavascriptExecutor js = (JavascriptExecutor)driver;        
        WebElement userName = (WebElement)js.executeScript
           ("return document.querySelector(\"#userName\")
                   .shadowRoot.querySelector(\"#kils\")");        
        userName.sendKeys("Shadow dom test");
    }
}

Explanation :

In order to interact with the HTML element using Java script, we need to first convert the Webdriver type driver to a JavascriptExecutor type driver.

JavascriptExecutor js = (JavascriptExecutor)driver;

JavascriptExecutor provides the executeScript() method in which we can provide a Javascript script as "return <Javascript script>". Executing the below script will return a browser element. Which is not same as a Selenium WebElement. So we need to convert it to a WebElement as follows :

WebElement userName = (WebElement)js.executeScript
           ("return document.querySelector(\"#userName\")
                   .shadowRoot.querySelector(\"#kils\")");    
userName.sendKeys("Shadow dom test");

The above code will enter the text in the user name field on the page :

Note : This site is a bit slow. You might have to use Thread.sleep(long milli) for execution. Static wait like Thread.sleep should not be used. In future blogs, we will see the mechanisms of WebDriverWait which provides the dynamic wait functionalities.

Happy learning !!