The solution provided by IceRevenge didn't quite meet my needs as I encountered issues with pre-filtering the top level elements due to a FireFox bug. In order to work around this bug, I had to retrieve the entire list of children elements instead of directly accessing the root shadowDom element.
To address this limitation, I came up with a workaround solution. While it may not be perfect, it serves its purpose adequately.
private <T> T handleNoSuchElementException(Supplier<T> supplier) {
try {
return supplier.get();
} catch (NoSuchElementException ex) {
return null;
}
}
private WebElement findMatchingShadowDomChild(
final List<WebElement> children,
final Function<WebElement, Boolean> topLevelSearch,
final Function<WebElement, WebElement> grandChildSearch,
final String errorMessage
) {
Optional<WebElement> child = children.stream()
.filter(topLevelSearch::apply)
.findFirst();
if (child.isPresent()) {
return child.get();
}
Optional<WebElement> grandChild = children.stream()
.map(c -> handleNoSuchElementException(() -> grandChildSearch.apply(c)))
.filter(Objects::nonNull)
.findFirst();
return grandChild.orElseThrow(() -> new NoSuchElementException(errorMessage));
}
This approach can be effectively utilized in various scenarios like this:
private List<WebElement> fetchShadowDomChildren(final WebElement rootElement) {
return (List<WebElement>) ((JavascriptExecutor) browser.getWebDriver())
.executeScript("return arguments[0].shadowRoot.children", rootElement);
}
private WebElement accessInnerShadowElementByTagName(final WebElement rootElement,
final String tagName) {
List<WebElement> children = fetchShadowDomChildren(rootElement);
return findMatchingShadowDomChild(
children,
c -> StringUtils.equalsIgnoreCase(c.getTagName(), tagName),
c -> c.findElement(By.tagName(tagName)),
String.format("Could not locate an inner element with TagName=[%s]", tagName));
}