【问题标题】:selenium webdriver (chromedriver) and accessing shadow domselenium webdriver (chromedriver) 和访问 shadow dom
【发布时间】:2018-06-25 11:10:38
【问题描述】:

我正在测试一个使用 shadow dom 的新应用程序,如下所示:

 #shadow-root (open)
    <div class="th_filePicker">
        <div class="th_fp_header">
            <div class="th_fp_title" role="heading" aria-level="1" data-l10n-id="th_fp_title">Select Image</div>
                <div class="th_fp_Close"><button class="close-popup" data-l10n-id="close_popup" title="Close"></button></div>
        </div>
    </div>

有没有人知道如何访问文件选择器控件中的元素 - 特别是关闭图标?

【问题讨论】:

  • 它在 IFRAME 内吗?您是否尝试仅定位元素?请发布代码尝试和结果/错误消息。
  • 你能用更多的outerHTML更新问题吗?

标签: selenium selenium-webdriver selenium-chromedriver shadow-dom


【解决方案1】:

这是可能的,但需要几个步骤。作为初步准备,请查看 this page 关于访问 shadow dom 的信息。我发现它非常有用。

从两种方法开始获取shadow dom元素:

private WebElement shadowDom;

private WebElement expandRootElement(WebElement element) {
    WebElement ele = (WebElement) ((JavascriptExecutor) driver)
        .executeScript("return arguments[0].shadowRoot",element);
    return ele;
}
private void findByShadowRoot(WebDriver driver) {
    shadowDom = expandRootElement(
        driver.findElement(By.id("whatEverTheShadowDomIdIs")));
}

从那里,您可以将方法创建为伪 POM

private WebElement findByShadowButton() {
    findByShadowRoot(driver);
    return shadowDom.findElement(By.cssSelector("div.th_fp_Close"));
}

基本上前两个方法是创建一个起点,然后所有其他方法调用这些方法并说,“从这个起点,找到它下面的元素”。

然后你可以这样声明:

findByShadowButton().click();

【讨论】:

    【解决方案2】:

    我从 MivaScott 的回答中获取信息,并为我的解决方案创建了一个递归方法,我认为它可能对其他人有用,所以就在这里。我用它来点击视频播放器中的播放按钮。

    您需要提供的只是 shadow-root 的 CSS 选择器的字符串数组。该方法将返回最终的阴影根元素,因此您可以在末尾添加另一个选择器(在我的例子中,是一个 svg)。请看我的例子:

    The Player's Shadow-Root Structure

    public IWebElement PlayButton {
    
            get {
    
                string[] shadowRootSelectors = { "apc-controls", "apc-control-footer", "apc-toggle-play", "apc-icon-play" };
    
                return FindShadowRootElementRecursive(shadowRootSelectors).FindElement(By.CssSelector("svg"));
    
            }
            set {
            }
    
    }
    

    还有递归方法本身:

    public IWebElement FindShadowRootElementRecursive(string[] selectors = null, IWebElement element = null) {
    
                IWebElement root = null;
                IWebElement selectorElement = null;
                bool baseCase = false;
    
                //Get the first selector from the array
                string selector = selectors[0];
    
                if (selectors.Length == 1)
                {
    
                    baseCase = true;
    
                }
                else {
    
                    //If there are more selectors, then remove this selector and recurse with the rest
                    selectors = selectors.Where(w => w != selectors[0]).ToArray();
    
                }
    
                //If this is the first call...
                if (element == null)
                {
    
                    //Use the driver to select the element
                    selectorElement = Driver.FindElement(By.CssSelector(selector));
    
                }
                else {
    
                    //Otherwise, use the previously found element
                    selectorElement = element.FindElement(By.CssSelector(selector));
    
                }
    
                //Get the shadow root
                root = (IWebElement)((IJavaScriptExecutor)Driver).ExecuteScript("return arguments[0].shadowRoot", selectorElement);
    
                if (baseCase)
                {
    
                    return root;
    
                }
                else {
    
                    //Recurse
                    root = FindShadowRootElementRecursive(selectors, root);
    
                }
    
                return root;
    
            }
    

    然后我像这样点击了按钮:

    PlayButton.Click();
    

    【讨论】:

      【解决方案3】:

      您可以尝试这种“繁重”的方法(C#,但取决于您的语言,它可能是这样的):

      public IWebElement DeepFind(By search)
      {
          try
          {
              // search a result in the main dom
              return Driver.FindElement(search);
          }
          catch (NoSuchElementException)
          {
              // if nothing we will take a look to the shadow dom(s)
              var shadowRoots = new List<IWebElement>();
              try
              {
                  // will use the recursive method that search for all shadow roots
                  ListShadowRoots(search, Driver.FindElements(By.XPath("//*")), shadowRoots);
              }
              catch (NoSuchElementException)
              {
                  //
              }
              // return the first element that match the By search
              return shadowRoots.FirstOrDefault(s => s.FindElement(search) != null);
          }
      }
      
      private void ListShadowRoots(By search, ReadOnlyCollection<IWebElement> elements, List<IWebElement> shadowRoots)
      {
          elements.ToList().ForEach(e =>
          {
              var jsResult = (IWebElement)ExecuteJavascript("return arguments[0].shadowRoot", new object[] { e });
              if (jsResult != null)
              {
                  shadowRoots.Add(jsResult);
                  try
                  {
                      ListShadowRoots(search, jsResult.FindElements(By.XPath("//*")), shadowRoots);
                  }
                  catch (NoSuchElementException)
                  {
                      //
                  }
              }
          });
      }
      
      private object ExecuteJavascript(string code, object[] args)
      {
          IJavaScriptExecutor js = (IJavaScriptExecutor)Driver;
          js.ExecuteScript(code, args);
      }
      

      Driver是web驱动(IWebDriver)

      性能还不错,它可以完成工作;) 希望对你有帮助

      【讨论】:

      • 我将此答案用于 WebDriver 的 c# 解决方案。
      【解决方案4】:

      一种方法是使用穿透式 CSS 选择器(/deep/&gt;&gt;&gt;)。虽然并非所有浏览器都支持它,但它可能会在未来的版本中被删除。

      这个应该会给你 Chrome 62 的关闭按钮:

      driver.findElement(By.css("* /deep/ button[title='Close']"))
      

      【讨论】:

      • 嗨弗洛伦特,我试过你的方法很有魅力
      • /deep/ 或 >>> 是否足以处理 Shadow DOM?
      猜你喜欢
      • 2018-12-23
      • 2014-07-18
      • 1970-01-01
      • 2020-08-16
      • 2016-10-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-12-23
      相关资源
      最近更新 更多