You need to take care of a couple of things:
- Now that Chrome supports shadow root via the driver,
shadowRoot
JS calls are returned per the spec with a shadow root element key (shadow-6066-11e4-a52e-4f735466cecf
). selenium4 has a new ShadowRoot
class to support this, but it didn't include the translation code that we earlier did when execute script calls return elements. This has been fixed and will be available in Selenium v4.1. The only difference is that you'll need to cast to ShadowRoot
instead of WebElement
.
- The actual state is that the return value of that JavaScript changed in v96 of ChromeDriver in order to be w3c compliant. Selenium 3.141.59 can not parse this new return value. You can use
getShadowRoot()
in Selenium 4, or you'll be able to get a ShadowRoot
instance returned from the JS in Selenium 4.1.
Implementation
From the documentation
package org.openqa.selenium.remote;
import org.openqa.selenium.By;
import org.openqa.selenium.SearchContext;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.WrapsDriver;
import org.openqa.selenium.internal.Require;
import java.util.List;
import java.util.Map;
import static java.util.Collections.singletonMap;
import static org.openqa.selenium.remote.Dialect.W3C;
import static org.openqa.selenium.remote.DriverCommand.FIND_ELEMENTS_FROM_SHADOW_ROOT;
import static org.openqa.selenium.remote.DriverCommand.FIND_ELEMENT_FROM_SHADOW_ROOT;
// Note: we want people to code against the SearchContext API, so we keep this class package private
class ShadowRoot implements SearchContext, WrapsDriver {
private final RemoteWebDriver parent;
private final String id;
ShadowRoot(RemoteWebDriver parent, String id) {
this.parent = Require.nonNull("Owning remote webdriver", parent);
this.id = Require.nonNull("Shadow root ID", id);
}
@Override
public List<WebElement> findElements(By by) {
return parent.findElements(
this,
(using, value) -> FIND_ELEMENTS_FROM_SHADOW_ROOT(id, using, String.valueOf(value)),
by);
}
@Override
public WebElement findElement(By by) {
return parent.findElement(
this,
(using, value) -> FIND_ELEMENT_FROM_SHADOW_ROOT(id, using, String.valueOf(value)),
by);
}
@Override
public WebDriver getWrappedDriver() {
return parent;
}
public String getId() {
return this.id;
}
private Map<String, Object> toJson() {
return singletonMap(W3C.getShadowRootElementKey(), id);
}
}
Example Code:
WebElement shadowHost = seleniumWebDriver.findElement(By.cssSelector("#shadowrootcontainer"));
JavascriptExecutor javascriptExecutor = (JavascriptExecutor) seleniumWebDriver;
SearchContext shadowRoot = (SearchContext) javascriptExecutor.executeScript("return arguments[0].shadowRoot", shadowHost);
WebElement shadowContent = shadowRoot.findElement(By.cssSelector("#shadowcontentcss"));
This Usecase
Your modified code block will be:
WebElement shadowDomHostElement = uiDriver.webDr.findElement(By.cssSelector("authoring-ui[ng-version='12.0.1']"));
JavascriptExecutor execute = (JavascriptExecutor)uiDriver.webDr;
SearchContext shadowRoot = (SearchContext) execute.executeScript("return arguments[0].shadowRoot", shadowDomHostElement);
WebElement executeshadow = shadowRoot.webDr.findElement(By.cssSelector("authoring-ui[ng-version='12.0.1']"));
TL; DR
Some helpful discussions: