Skip to content

Instantly share code, notes, and snippets.

@diervo
Created September 19, 2019 06:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save diervo/7ce4437bde4a382679b22306af9b5b6c to your computer and use it in GitHub Desktop.
Save diervo/7ce4437bde4a382679b22306af9b5b6c to your computer and use it in GitHub Desktop.
# WebDriver + Shadow DOM traversal code samples
## Page Object Shadow DOM traversal methods
Accepts a list of CSS selectors and iterates over them, querying off of the shadowRoot after each new element is found:
```
public WebElement findElementInShadowRoot(WebElement element, By... selectors) {
WebElement curr = expandShadowIfPresent(element);
for (int i = 0; i < selectors.length; i++) {
curr = curr.findElement(selectors[i]);
if (i != selectors.length -1) {
curr = expandShadowIfPresent(curr);
}
}
return curr;
}
```
To determine if the element has a shadowRoot or not we need to execute Javascript on the client since there is no native Selenium support:
```
public WebElement expandShadowIfPresent(WebElement element) {
String checkShadowRoot =
"var shadowRoot = arguments[0].shadowRoot;"
+ "if (shadowRoot && shadowRoot.nodeType === 11 && !!shadowRoot.host) {"
+ " return true;"
+ "}"
+ "return false;";
Boolean shadowRoot = (Boolean)((JavascriptExecutor)getCurrentWebDriver())
.executeScript(checkShadowRoot, element);
boolean hasShadowRoot = shadowRoot.booleanValue();
return hasShadowRoot ? new ShadowRootWebElement(element) : element;
}
```
If we know the element has a shadowRoot, save the overhead of executing a script on the client:
```
public ShadowRootWebElement expandShadow(WebElement element) {
return new ShadowRootWebElement(element);
}
```
Create our own WebElement object that represents a DOM elements shadowRoot. This saves a reference to the parent so when the WebDriver `findElement` API is called we drop down to the client to do a Javascript query off of the `shadowRoot`. This is a big reason why only CSS selectors are supported since they can almost directly translate to a `querySelector` call.
```
public class ShadowRootWebElement implements WebElement, WrapsElement, WrapsDriver {
// The host element of the shadowRoot. Needed to execute queries off of.
private WebElement rootElement;
public ShadowRootWebElement(WebElement root) {
this.rootElement = root;
WrapsDriver parentDriver = (WrapsDriver)rootElement;
this.executor = (JavascriptExecutor)parentDriver.getWrappedDriver();
}
// ... lots of other code emitted ...
public WebElement findElement(By by) {
String byString = by.toString();
if (!byString.startsWith("By.cssSelector")) {
throw new InvalidArgumentException("Must search for subelements of a
shadowRoot element with By.ByCssSelector. Instead got: " + byString);
}
String queryString = byString.trim().replace("By.cssSelector: ", "");
WebElement ret = (WebElement)executor
.executeScript("return arguments[0].shadowRoot.querySelector('"
+ escapeForQuery(queryString) + "')", rootElement);
if (ret == null) {
throw new NoSuchElementException("Unable to locate element: " + byString);
}
return ret;
}
private String escapeForQuery(String queryString) {
return queryString.replace("\\", "\\\\").replace("'", "\\'")
.replace("\"", "\\\"");
}
}
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment