【问题标题】:Accessing Shadow DOM tree with Selenium使用 Selenium 访问 Shadow DOM 树
【发布时间】:2014-07-18 04:21:00
【问题描述】:

是否可以使用 Selenium/Chrome webdriver 访问 Shadow DOM 中的元素?

正如预期的那样,使用普通的元素搜索方法不起作用。我在 w3c 上看到了对 switchToSubTree 规范的引用,但找不到任何实际的文档、示例等。

有人成功了吗?

【问题讨论】:

  • U 可以扩展 Selenium 功能,所以最后,你将有一个 @FindByInShadow 注释用于处理 Shadow DOM 下的元素,以及 @FindBy 。 Maksym Barvinskyi 在他的回答中已经描述了如何为 Java 创建自定义逻辑,我受到了他的启发,只改进了他的解决方案。在这里你可以找到代码示例:github.com/starosta357/ExampleHowToWorkWithShawodDomInSelenium P.S.这个小插件可以帮助你在 Inspector 中找到元素,即使在 Shadow selectorshub.com

标签: javascript python selenium selenium-chromedriver shadow-dom


【解决方案1】:

【讨论】:

  • 正如 Eduard Florinescu 和 Djangofan 下面所指出的,这已经过时了,浏览器已经赶上了。时代变迁。
【解决方案2】:

还应该注意的是,Selenium 二进制 Chrome 驱动程序现在支持 Shadow DOM(自 2015 年 1 月 28 日起):http://chromedriver.storage.googleapis.com/2.14/notes.txt

【讨论】:

    【解决方案3】:

    这对我有用(使用 selenium javascript 绑定):

    driver.executeScript("return $('body /deep/ <#selector>')")
    

    返回您正在寻找的元素。

    【讨论】:

      【解决方案4】:

      通常你会这样做:

      element = webdriver.find_element_by_css_selector(
          'my-custom-element /deep/ .this-is-inside-my-custom-element')
      

      希望这将继续有效。


      但是,请注意/deep/::shadow弃用(并且未在除 Opera 和 Chrome 之外的浏览器中实现)。有很多关于在静态配置文件中允许它们的讨论。意思是,查询它们会起作用,但样式不起作用。

      如果不想依赖/deep/::shadow,因为它们的未来有点不确定,或者因为您想更好地跨浏览器工作,或者因为您讨厌弃用警告,请为有另一种方式而欢欣鼓舞:

      # Get the shadowRoot of the element you want to intrude in on,
      # and then use that as your root selector.
      shadow_root = webdriver.execute_script('''
          return document.querySelector(
              'my-custom-element').shadowRoot;
          ''')
      element = shadow_root.find_element_by_css_selector(
          '.this-is-inside-my-custom-element')
      

      更多信息:

      【讨论】:

        【解决方案5】:

        接受的答案不再有效,其他一些答案有一些缺点或不实用(/deep/ 选择器不起作用并已弃用,document.querySelector('').shadowRoot 仅适用于阴影元素时的第一个阴影元素是嵌套的),有时影子根元素是嵌套的,第二个影子根在文档根中不可见,但在其父访问的影子根中可用。我认为最好使用 selenium 选择器并注入脚本只是为了获取影子根:

        def expand_shadow_element(element):
          shadow_root = driver.execute_script('return arguments[0].shadowRoot', element)
          return shadow_root
        
        outer = expand_shadow_element(driver.find_element_by_css_selector("#test_button"))
        inner = outer.find_element_by_id("inner_button")
        inner.click()
        

        为了透视这一点,我刚刚在 Chrome 的下载页面中添加了一个可测试的示例,单击搜索按钮需要打开 3 个嵌套的阴影根元素:

        import selenium
        from selenium import webdriver
        driver = webdriver.Chrome()
        
        
        def expand_shadow_element(element):
          shadow_root = driver.execute_script('return arguments[0].shadowRoot', element)
          return shadow_root
        
        driver.get("chrome://downloads")
        root1 = driver.find_element_by_tag_name('downloads-manager')
        shadow_root1 = expand_shadow_element(root1)
        
        root2 = shadow_root1.find_element_by_css_selector('downloads-toolbar')
        shadow_root2 = expand_shadow_element(root2)
        
        root3 = shadow_root2.find_element_by_css_selector('cr-search-field')
        shadow_root3 = expand_shadow_element(root3)
        
        search_button = shadow_root3.find_element_by_css_selector("#search-button")
        search_button.click()
        

        执行其他答案中建议的相同方法的缺点是它对查询进行硬编码,可读性较差,并且您不能将中间选择用于其他操作:

        search_button = driver.execute_script('return document.querySelector("downloads-manager").shadowRoot.querySelector("downloads-toolbar").shadowRoot.querySelector("cr-search-field").shadowRoot.querySelector("#search-button")')
        search_button.click()
        

        【讨论】:

        • 你知道这是否也适用于 firefox geckodriver?
        • @vnportnoy 它应该可以工作,还没有测试过,所以无法确认
        【解决方案6】:

        我正在使用 C# 和 Selenium,并设法使用 java 脚本在嵌套的影子 DOM 中找到一个元素。 这是我的 html 树:

        html tree

        我想要最后一行的 url,为了得到它,我首先选择“downloads-manager”标签,然后选择它正下方的第一个影子根。 一旦进入影子根,我想找到最接近下一个影子根的元素。该元素是“下载项目”。选择后,我可以输入第二个影子根。从那里我选择包含 id = "file-icon" 的 url 的 img 项目。最后我可以得到包含我正在寻找的url的属性“src”。

        两行 C# 代码可以解决问题:

        IJavaScriptExecutor jse2 = (IJavaScriptExecutor)_driver;
        var pdfUrl = jse2.ExecuteScript("return document.querySelector('downloads-manager').shadowRoot.querySelector('downloads-item').shadowRoot.getElementById('file-icon').getAttribute('src')");
        

        【讨论】:

          【解决方案7】:

          我找到了一种从 Shadow Dom 获取元素的更简单的方法。 对于 Chrome 下载页面search icon,我使用上面给出的相同示例。

          IWebDriver driver;
          
          public IWebElement getUIObject(params By[] shadowRoots)
                  {
                      IWebElement currentElement = null;
                      IWebElement parentElement = null;
                      int i = 0;
                      foreach (var item in shadowRoots)
                      {
                          if (parentElement == null)
                          {
                              currentElement = driver.FindElement(item);
                          }
                          else
                          {
                              currentElement = parentElement.FindElement(item);
                          }
                          if(i !=(shadowRoots.Length-1))
                          {
                              parentElement = expandRootElement(currentElement);
                          }
                          i++;
                      }
                      return currentElement;
                  }
          
           public IWebElement expandRootElement(IWebElement element)
                  {
                      IWebElement rootElement = (IWebElement)((IJavaScriptExecutor)driver)
                  .ExecuteScript("return arguments[0].shadowRoot", element);
                      return rootElement;
                  }
          

          Google Chrome Download Page

          现在如图所示,我们必须展开三个阴影根元素才能获得我们的搜索图标。 要点击图标,我们需要做的就是:-

            [TestMethod]
                  public void test()
                  {
                     IWebElement searchButton= getUIObject(By.CssSelector("downloads-manager"),By.CssSelector("downloads-toolbar"),By.Id("search-input"),By.Id("search-buton"));
                      searchButton.Click();
                  }
          

          所以只有一行会给你你的 Web 元素,只需要确保你传递 第一个阴影根元素作为函数的第一个参数“getUIObject”第二个阴影根元素作为函数的第二个参数,依此类推,函数的最后一个参数将是您的实际元素的标识符(在本例中为 'search-button'

          【讨论】:

            【解决方案8】:

            在 Selenium 支持开箱即用的影子 DOM 之前,您可以在 Java 中尝试以下解决方法。创建一个扩展 By 类的类:

            import org.openqa.selenium.By;
            import org.openqa.selenium.JavascriptExecutor;
            import org.openqa.selenium.SearchContext;
            import org.openqa.selenium.WebDriverException;
            import org.openqa.selenium.WebElement;
            import org.openqa.selenium.WrapsDriver;
            import org.openqa.selenium.internal.FindsByCssSelector;
            
            import java.io.Serializable;
            import java.util.List;
            
            public class ByShadow {
                public static By css(String selector) {
                    return new ByShadowCss(selector);
                }
            
                public static class ByShadowCss extends By implements Serializable {
            
                    private static final long serialVersionUID = -1230258723099459239L;
            
                    private final String cssSelector;
            
                    public ByShadowCss(String cssSelector) {
                        if (cssSelector == null) {
                            throw new IllegalArgumentException("Cannot find elements when the selector is null");
                        }
                        this.cssSelector = cssSelector;
                    }
            
                    @Override
                    public WebElement findElement(SearchContext context) {
                        if (context instanceof FindsByCssSelector) {
                            JavascriptExecutor jsExecutor;
                            if (context instanceof JavascriptExecutor) {
                                jsExecutor = (JavascriptExecutor) context;
                            } else {
                                jsExecutor = (JavascriptExecutor) ((WrapsDriver) context).getWrappedDriver();
                            }
                            String[] subSelectors = cssSelector.split(">>>");
                            FindsByCssSelector currentContext = (FindsByCssSelector) context;
                            WebElement result = null;
                            for (String subSelector : subSelectors) {
                                result = currentContext.findElementByCssSelector(subSelector);
                                currentContext = (FindsByCssSelector) jsExecutor.executeScript("return arguments[0].shadowRoot", result);
                            }
                            return result;
                        }
            
                        throw new WebDriverException(
                                "Driver does not support finding an element by selector: " + cssSelector);
                    }
            
                    @Override
                    public List<WebElement> findElements(SearchContext context) {
                        if (context instanceof FindsByCssSelector) {
                            JavascriptExecutor jsExecutor;
                            if (context instanceof JavascriptExecutor) {
                                jsExecutor = (JavascriptExecutor) context;
                            } else {
                                jsExecutor = (JavascriptExecutor) ((WrapsDriver) context).getWrappedDriver();
                            }
                            String[] subSelectors = cssSelector.split(">>>");
                            FindsByCssSelector currentContext = (FindsByCssSelector) context;
                            for (int i = 0; i < subSelectors.length - 1; i++) {
                                WebElement nextRoot = currentContext.findElementByCssSelector(subSelectors[i]);
                                currentContext = (FindsByCssSelector) jsExecutor.executeScript("return arguments[0].shadowRoot", nextRoot);
                            }
                            return currentContext.findElementsByCssSelector(subSelectors[subSelectors.length - 1]);
                        }
            
                        throw new WebDriverException(
                                "Driver does not support finding elements by selector: " + cssSelector);
                    }
            
                    @Override
                    public String toString() {
                        return "By.cssSelector: " + cssSelector;
                    }
                }
            }
            

            而且您可以在不编写任何附加函数或包装器的情况下使用它。这应该适用于任何类型的框架。例如,在纯 Selenium 代码中,它看起来像这样:

            WebElement searchButton =
                driver.findElement(ByShadow.css(
                    "downloads-manager >>> downloads-toolbar >>> cr-search-field >>> #search-button"));
            

            或者如果您使用 Selenide:

            SelenideElement searchButton =
                $(ByShadow.css("downloads-manager >>> downloads-toolbar >>> cr-search-field >>> #search-button"));
            

            【讨论】:

              【解决方案9】:

              用于在 Chrome 中获取最新下载文件的文件名

              def get_downloaded_file(self):
                filename = self._driver.execute_script("return document.querySelector('downloads-manager').shadowRoot.querySelector('#downloadsList downloads-item').shadowRoot.querySelector('div#content  #file-link').text")
                return filename
              

              用法:

              driver.get_url('chrome://downloads')
              filename = driver.get_downloaded_file()
              

              以及用于配置在selenium中为chrome浏览器设置默认下载目录的选项,可以在其中获取相应的文件:

              ..
              chrome_options = webdriver.ChromeOptions()
              ..
              prefs = {'download.default_directory': '/desired-path-to-directory'} # unix
              chrome_options.add_experimental_option('prefs', prefs)
              ..
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2018-06-25
                • 2016-10-30
                • 1970-01-01
                • 1970-01-01
                • 2020-03-02
                • 2021-01-04
                • 2015-05-08
                相关资源
                最近更新 更多