ShadowDOM is used to separate certain HTML structure, JS and CSS from the actual application DOM.
A DOM can have multiple shadowDOM elements.
To understand more, please refer https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM
To understand which ShadowDOM elements we can automate or not, you can refer to this below video –
As normal DOM elements, we can’t directly inspect and act on shadowDOM elements, selenium will throw NoSuchElementException.
For this we have to use JavaScriptExecutor or Selenium4 default method.
Refer the visual demo here –
Let’s see them one by one
We will use this demo site – https://qavbox.github.io/demo/shadowDOM/
Let’s see how to handle the OPEN ShadowDOM elements –
driver = new ChromeDriver(); driver.get("https://qavbox.github.io/demo/shadowDOM/"); js = (JavascriptExecutor) driver; Thread.sleep(2000); SearchContext shadowHostEls = (SearchContext) (js.executeScript("return document.querySelector(\""+my-open-component+"\").shadowRoot")); shadowHostEls.findElement(By.cssSelector("input")).sendKeys("qavboxOpen");
Js.executeScript(….).shadowRoot returns the SearchContext which is parent to the WebElement, so we can use the SearchContext object/variable “shadowHostEls” to fetch all the root elements of the shadowDOM and perform actions.
This above code will work if you are using Selenium 3 or 4.
Note – If you are using Chrome > 96 version and using WebElement as return type of JS.executeScript, then you will get an exception as “org.openqa.selenium.remote.ShadowRoot cannot be cast to org.openqa.selenium.WebElement java.lang.ClassCastException: org.openqa.selenium.remote.ShadowRoot cannot be cast to org.openqa.selenium.WebElement“
So please use SearchContext instead of WebElement
There is an alternate way to javascriptExecutor in Selenium4.
Selenium 4.1 introduced a new method getShadowRoot() which returns the SearchContext.
driver = new ChromeDriver(); driver.get("https://qavbox.github.io/demo/shadowDOM/"); Thread.sleep(2000); SearchContext shadowHostEls = driver.findElement(By.cssSelector("my-open-component")).getShadowRoot(); shadowHostEls.findElement(By.cssSelector("input")).sendKeys("qavboxOpen");
Note – this above method will work only if you have an OPEN shadowDOM.
Let’s see how to handle the CLOSED ShadowDOM elements –
For closed shadowDOM let’s see to this below text box, which is inside a closed shadowDOM but has a reference attached which we wan tweak to automation using JavaScriptExecutor (Selenium4 default getShadowRoot() doesn’t work for these closed shadowDom)
and the respective JS associated with this above shadowRoot is –
class MyWebComponent_close extends HTMLElement { constructor() { super(); this._root = this.attachShadow({ mode: "closed" }); } connectedCallback() { this._root.innerHTML = '<div>Close shadow with ref=_root</div><input type="text"></input><br>' } }
this._root is the reference to the closed shadowDOM element and we can automate the elements inside this shadowRoot.
driver = new ChromeDriver(); driver.get("https://qavbox.github.io/demo/shadowDOM/"); js = (JavascriptExecutor) driver; Thread.sleep(2000); SearchContext shadowHostEls = (SearchContext) (js.executeScript("return document.querySelector(\""+my-close-component+"\")._root")); shadowHostEls.findElement(By.cssSelector("input")).sendKeys("qavboxOpen");
Observe the 7th line of above code, we used ._root instead of shadowRoot to identify the closed shadowRoot with reference.
hope this helps!