如何使用 selenium 自动化 shadow DOM 元素?

Sus*_*ilG 6 javascript java selenium shadow-dom polymer

我正在使用 Java Selenium 项目进行网页自动化。该网页包含许多我无法使用 seleniumfindElement方法与之交互的多级 shadow-root DOM 元素。

我尝试了以下解决方案:

  • deep css(不适用于最新的 chrome 浏览器)
  • JS 执行器。(这真的很乏味,维护起来也很复杂)

笔记:

如果您知道我可以在 Selenium Java 框架中实现的除上面列出的任何其他解决方案,请传递该解决方案。提前致谢 !。

df7*_*899 11

Selenium 4 现在有了WebElement.getShadowRoot(). 例如:

driver.findElement(By.id("parentId")).getShadowRoot().findElement(By.cssSelector("label")).findElement(By.tagName("input"))
Run Code Online (Sandbox Code Playgroud)

与正常情况一样#shadow-root,下一跳的导航选择是有限的。例如,针对 Chrome By.cssSelector(), 和By.className()是有效的,但是By.id()By.tagName()失败org.openqa.selenium.InvalidArgumentException: invalid argument: invalid locator


小智 9

使用 JSExecutor 和 CSS 查找影子 DOM 元素的步骤:

  1. 找出基本元素,即 Shadow 根元素的父元素。

  2. 获取该元素的 Shadow root。

  3. 并在该影子根网络元素上找到您的元素

    例子:

<div id="example">
#shadow-root
<div id="root" part="root">
   <div id="label" part="label">ShadowRootLabel</div>
</div>
</ptcs-label>
Run Code Online (Sandbox Code Playgroud)

#找出影子根元素的方法

public WebElement getShadowRootElement(WebElement element) {
WebElement ele = (WebElement) ((JavascriptExecutor)driver)
    .executeScript("return arguments[0].shadowRoot", element);
        return ele;
    }
Run Code Online (Sandbox Code Playgroud)

#Step1 例如,找到基本元素:

WebElement root1 = driver.findElement(By.id("example"));
Run Code Online (Sandbox Code Playgroud)

#第2步

//Get shadow root element
WebElement shadowRoot1 = getShadowRootElement(root1);
Run Code Online (Sandbox Code Playgroud)

#Step3 - 我们需要使用 CSS 选择器查找位于影子根内部的元素,xpath 在这里不起作用

//Here we will get Element inside Shadow Dom Element
WebElement shadowElement = shadowRoot3.findElement(By.cssSelector("div[id=label]"));
Run Code Online (Sandbox Code Playgroud)


Deb*_*anB 6

为了演示使用Selenium v​​3.xChromeDriver v2.46Chrome v73.x实现Shadow DOM自动化,这里提供了几种打开 url 的方法,并使用该方法发送字符序列pdf作为搜索框中搜索文本chrome://downloads/executeScript()


使用document.querySelector()

作为规范方法,您可以使用document.querySelector()如下方法:

  • 代码块:

      import org.openqa.selenium.JavascriptExecutor;
      import org.openqa.selenium.WebDriver;
      import org.openqa.selenium.WebElement;
      import org.openqa.selenium.chrome.ChromeDriver;
      import org.openqa.selenium.chrome.ChromeOptions;
    
      public class shadow_DOM_search_download_querySelector {
    
          public static void main(String[] args)
          {
              System.setProperty("webdriver.chrome.driver", "C:\\Utility\\BrowserDrivers\\chromedriver.exe");
              ChromeOptions options = new ChromeOptions();
              options.addArguments("start-maximized");
              options.addArguments("disable-infobars");
              options.addArguments("--disable-extensions"); 
              WebDriver driver = new ChromeDriver(options);
              driver.get("chrome://downloads/");
              JavascriptExecutor jse = (JavascriptExecutor) driver; 
              WebElement search_box = (WebElement) jse.executeScript("return document.querySelector('downloads-manager').shadowRoot.querySelector('downloads-toolbar#toolbar').shadowRoot.querySelector('cr-toolbar#toolbar').shadowRoot.querySelector('cr-toolbar-search-field#search').shadowRoot.querySelector('div#searchTerm input#searchInput')");
              String js = "arguments[0].setAttribute('value','pdf')";
              ((JavascriptExecutor) driver).executeScript(js, search_box);
          }
      }
    
    Run Code Online (Sandbox Code Playgroud)

可以按照以下方式逐步重写相同的解决方案:

  • 代码块:

      import org.openqa.selenium.By;
      import org.openqa.selenium.JavascriptExecutor;
      import org.openqa.selenium.WebDriver;
      import org.openqa.selenium.WebElement;
      import org.openqa.selenium.chrome.ChromeDriver;
      import org.openqa.selenium.chrome.ChromeOptions;
    
      public class shadow_DOM {
    
          static WebDriver driver;
          public static void main(String[] args) 
          {   
              System.setProperty("webdriver.chrome.driver", "C:\\Utility\\BrowserDrivers\\chromedriver.exe");
              ChromeOptions options = new ChromeOptions();
              options.addArguments("start-maximized");
              //options.addArguments("disable-infobars");
              options.addArguments("--disable-extensions"); 
              driver = new ChromeDriver(options);
              driver.get("chrome://downloads/");
              WebElement root1 = driver.findElement(By.tagName("downloads-manager"));
              WebElement shadow_root1 = expand_shadow_element(root1);
    
              WebElement root2 = shadow_root1.findElement(By.cssSelector("downloads-toolbar#toolbar"));
              WebElement shadow_root2 = expand_shadow_element(root2);
    
              WebElement root3 = shadow_root2.findElement(By.cssSelector("cr-toolbar#toolbar"));
              WebElement shadow_root3 = expand_shadow_element(root3);
    
              WebElement root4 = shadow_root3.findElement(By.cssSelector("cr-toolbar-search-field#search"));
              WebElement shadow_root4 = expand_shadow_element(root4);
    
              WebElement search_term = shadow_root4.findElement(By.cssSelector("div#searchTerm input#searchInput"));
              String js = "arguments[0].setAttribute('value','pdf')";
              ((JavascriptExecutor) driver).executeScript(js, search_term);
    
              WebElement search_button = shadow_root4.findElement(By.cssSelector("paper-icon-button#icon"));
              search_button.click();
    
              System.out.println("Search Button Clicked");
          }
    
          public static WebElement expand_shadow_element(WebElement element)
          {
              WebElement shadow_root = (WebElement)((JavascriptExecutor)driver).executeScript("return arguments[0].shadowRoot", element);
              return shadow_root;
          }
    
      }
    
    Run Code Online (Sandbox Code Playgroud)

  • 控制台输出:
  import org.openqa.selenium.JavascriptExecutor;
  import org.openqa.selenium.WebDriver;
  import org.openqa.selenium.WebElement;
  import org.openqa.selenium.chrome.ChromeDriver;
  import org.openqa.selenium.chrome.ChromeOptions;

  public class shadow_DOM_search_download_querySelector {

      public static void main(String[] args)
      {
          System.setProperty("webdriver.chrome.driver", "C:\\Utility\\BrowserDrivers\\chromedriver.exe");
          ChromeOptions options = new ChromeOptions();
          options.addArguments("start-maximized");
          options.addArguments("disable-infobars");
          options.addArguments("--disable-extensions"); 
          WebDriver driver = new ChromeDriver(options);
          driver.get("chrome://downloads/");
          JavascriptExecutor jse = (JavascriptExecutor) driver; 
          WebElement search_box = (WebElement) jse.executeScript("return document.querySelector('downloads-manager').shadowRoot.querySelector('downloads-toolbar#toolbar').shadowRoot.querySelector('cr-toolbar#toolbar').shadowRoot.querySelector('cr-toolbar-search-field#search').shadowRoot.querySelector('div#searchTerm input#searchInput')");
          String js = "arguments[0].setAttribute('value','pdf')";
          ((JavascriptExecutor) driver).executeScript(js, search_box);
      }
  }
Run Code Online (Sandbox Code Playgroud)
  • 浏览器快照:

影子DOM


尾奏

根据确定实验性“>>>”组合器的命运中的讨论,组合>>>器是用于将/deep/所有影子 DOM 边界刺穿样式的组合器的替代品,它在 Blink 中的标志后面实现,已被弃用。


小智 6

有一个非常好的插件可以与 selenium 项目shadow-automation-selenium 一起使用。它有助于编写更好、可读和可维护的代码。使用它您可以访问多级 shadow DOM(最多 4 级)。这使用简单的 css 选择器来标识元素。

WebElement findElement(String cssSelector) : 如果想要来自 DOM 的单个元素,请使用此方法

List<WebElement> findElements(String cssSelector) : 如果你想从 DOM 中查找所有元素,请使用它

WebElement findElements(WebElement parent, String cssSelector) : 如果你想从父对象 DOM 中找到单个元素,请使用它

List<WebElement> findElements(WebElement parent, String cssSelector) : 如果你想从父对象 DOM 中查找所有元素,请使用它

WebElement getShadowElement(WebElement parent,String selector) : 如果你想从父 DOM 中找到单个元素,请使用它

List<WebElement> getAllShadowElement(WebElement parent,String selector) : 如果你想从父 DOM 中查找所有元素,请使用它

boolean isVisible(WebElement element) :如果你想找到元素的可见性,请使用它

boolean isChecked(WebElement element) : 如果你想检查复选框是否被选中,请使用它

boolean isDisabled(WebElement element) : 如果你想检查元素是否被禁用,请使用它

String getAttribute(WebElement element,String attribute) : 如果你想获得像 aria-selected 这样的属性和元素的其他自定义属性,请使用它。

void selectCheckbox(String label) :使用它来选择使用标签的复选框元素。

void selectCheckbox(WebElement parentElement, String label) :使用它来选择使用标签的复选框元素。

void selectRadio(String label) :使用它来选择使用标签的单选元素。

void selectRadio(WebElement parentElement, String label) : 使用它从父 DOM 中使用标签选择单选元素。

void selectDropdown(String label) : 使用它来选择使用标签的下拉列表项(如果 UI 上仅存在或加载了一个下拉列表,则使用此选项)。

void selectDropdown(WebElement parentElement, String label) : 使用它从父 DOM 中使用标签选择下拉列表项。

如何使用这个插件:你必须依赖你的项目。

马文

<dependency>
  <groupId>io.github.sukgu</groupId>
  <artifactId>automation</artifactId>
  <version>0.0.4</version>
<dependency>
Run Code Online (Sandbox Code Playgroud)

对于位于 shadow-root dom 元素下的 html 标签

<properties-page id="settingsPage"> 
  <textarea id="textarea">
</properties-page>
Run Code Online (Sandbox Code Playgroud)

您可以在您的框架中使用此代码来获取 textarea 元素对象。

  import io.github.sukgu.*;
  Shadow shadow = new Shadow(driver);
  WebElement element = shadow.findElement("properties-page#settingsPage>textarea#textarea");
  String text = element.getText();
Run Code Online (Sandbox Code Playgroud)


Deb*_*anB 5

Chrome v96(及更高版本)和 Selenium 中的 Shadow DOM

随着 Chrome v96 的推出,已使其影子根返回值符合W3C WebDriver 规范

正如他们的评论@titusfortner中提到的:

现在 Chrome 通过驱动程序支持影子根,shadowRoot JS 调用将根据规范返回影子根元素键 (shadow-6066-11e4-a52e-4f735466cecf)。Selenium 4 有一个新的 ShadowRoot 类来支持这一点,但我们没有包含执行脚本调用返回元素时执行的转换代码。该问题已得到修复,并将在 Selenium 4.1 中提供。

唯一的区别是您需要转换为 ShadowRoot 而不是 WebElement。

因此,在 Microsoft Edge 和 Google Chrome v96 及更高版本中使用Selenium时,我们需要使用新的影子根方法,如下所示:

  • Java 示例

    driver.get("http://watir.com/examples/shadow_dom.html");
    WebElement shadowHost = driver.findElement(By.cssSelector("#shadow_host"));
    SearchContext shadowRoot = shadowHost.getShadowRoot();
    WebElement shadowContent = shadowRoot.findElement(By.cssSelector("#shadow_content"));
    Assertions.assertEquals("some text", shadowContent.getText());
    
    Run Code Online (Sandbox Code Playgroud)
  • Python 示例

    driver.get("http://watir.com/examples/shadow_dom.html");
    WebElement shadowHost = driver.findElement(By.cssSelector("#shadow_host"));
    SearchContext shadowRoot = shadowHost.getShadowRoot();
    WebElement shadowContent = shadowRoot.findElement(By.cssSelector("#shadow_content"));
    Assertions.assertEquals("some text", shadowContent.getText());
    
    Run Code Online (Sandbox Code Playgroud)
  • C# 示例

    _driver.Navigate().GoToUrl("http://watir.com/examples/shadow_dom.html");
    var shadowHost = _driver.FindElement(By.CssSelector("#shadow_host"));
    var shadowRoot = shadowHost.GetShadowRoot();
    var shadowContent = shadowRoot.FindElement(By.CssSelector("#shadow_content"));
    
    Run Code Online (Sandbox Code Playgroud)
  • 红宝石示例

    driver.get('http://watir.com/examples/shadow_dom.html')
    shadow_host = driver.find_element(By.CSS_SELECTOR, '#shadow_host')
    shadow_root = shadow_host.shadow_root
    shadow_content = shadow_root.find_element(By.CSS_SELECTOR, '#shadow_content')
    assert shadow_content.text == 'some text'
    
    Run Code Online (Sandbox Code Playgroud)

TL; 博士

阅读更多内容: