diff --git a/java/src/org/openqa/selenium/remote/ShadowRoot.java b/java/src/org/openqa/selenium/remote/ShadowRoot.java index 48ff0d2a1ba0a..a89157f3a8362 100644 --- a/java/src/org/openqa/selenium/remote/ShadowRoot.java +++ b/java/src/org/openqa/selenium/remote/ShadowRoot.java @@ -31,6 +31,7 @@ import org.openqa.selenium.WebElement; import org.openqa.selenium.WrapsDriver; import org.openqa.selenium.internal.Require; +import org.openqa.selenium.remote.shadow.FirefoxClassNameWorkaround; // Note: we want people to code against the SearchContext API, so we keep this class package private class ShadowRoot implements SearchContext, WrapsDriver { @@ -44,18 +45,20 @@ class ShadowRoot implements SearchContext, WrapsDriver { @Override public List findElements(By by) { + By resolved = FirefoxClassNameWorkaround.resolveForShadow(by, this); return parent.findElements( this, (using, value) -> FIND_ELEMENTS_FROM_SHADOW_ROOT(id, using, String.valueOf(value)), - by); + resolved); } @Override public WebElement findElement(By by) { + By resolved = FirefoxClassNameWorkaround.resolveForShadow(by, this); return parent.findElement( this, (using, value) -> FIND_ELEMENT_FROM_SHADOW_ROOT(id, using, String.valueOf(value)), - by); + resolved); } @Override @@ -73,12 +76,8 @@ private Map toJson() { @Override public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; ShadowRoot that = (ShadowRoot) o; return Objects.equals(parent, that.parent) && Objects.equals(id, that.id); } diff --git a/java/src/org/openqa/selenium/remote/shadow/FirefoxClassNameWorkaround.java b/java/src/org/openqa/selenium/remote/shadow/FirefoxClassNameWorkaround.java new file mode 100644 index 0000000000000..e759a630bde1e --- /dev/null +++ b/java/src/org/openqa/selenium/remote/shadow/FirefoxClassNameWorkaround.java @@ -0,0 +1,44 @@ +package org.openqa.selenium.remote.shadow; + +import org.openqa.selenium.By; +import org.openqa.selenium.InvalidSelectorException; +import org.openqa.selenium.SearchContext; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WrapsDriver; +import org.openqa.selenium.remote.HasCapabilities; +import org.openqa.selenium.Capabilities; + +final class FirefoxClassNameWorkaround { + + static boolean shouldUseCssSelector(By by, SearchContext context) { + if (!(by instanceof By.ByClassName)) return false; + if (!(context instanceof WrapsDriver)) return false; + + try { + WebDriver driver = ((WrapsDriver) context).getWrappedDriver(); + Capabilities caps = ((HasCapabilities) driver).getCapabilities(); + return "firefox".equalsIgnoreCase(caps.getBrowserName()); + } catch (Exception ignored) { + return false; + } + } + + static By convertToCss(By by) { + String className = extractClassName(by); + if (className.contains(" ")) { + throw new InvalidSelectorException( + "Compound class names not supported in Firefox Shadow DOM. Use single class name or By.cssSelector instead." + ); + } + return By.cssSelector("." + className); + } + + private static String extractClassName(By by) { + String raw = by.toString().replace("By.className:", "").trim(); + return raw.replaceAll("\\s+", ""); + } + + public static By resolveForShadow(By by, SearchContext context) { + return shouldUseCssSelector(by, context) ? convertToCss(by) : by; + } +}