使用Selenium访问Shadow DOM树

时间:2014-05-28 19:22:13

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

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

使用普通元素搜索方法无法正常工作。我见过关于w3c的switchToSubTree规范的引用,但找不到任何实际的文档,示例等。

有人在这方面取得了成功吗?

9 个答案:

答案 0 :(得分:9)

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

document.querySelector('').shadowRoot

为了说明这一点,我刚刚在Chrome的下载页面中添加了一个可测试的示例,点击搜索按钮需要打开3个嵌套的影子根元素: enter image description here

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()

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

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()

答案 1 :(得分:5)

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

答案 2 :(得分:4)

答案 3 :(得分:3)

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

html tree

我想要最后一行的网址并获取它我首先选择“downloads-manager”标签,然后选择它下方的第一个阴影根。 一旦进入阴影根,我想找到最接近下一个阴影根的元素。该元素是“downloads-item”。选择该选项后,我可以输入第二个阴影根。从那里我通过id =“file-icon”选择包含url的img项目。最后我可以得到属性“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')");

答案 4 :(得分:2)

通常你会这样做:

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 :(得分:2)

我找到了一种更容易从Shadow Dom获取元素的方法。 我正在采用上面给出的相同示例, Chrome下载页搜索图标

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”的第一个参数第二个影子根元素作为函数的第二个参数等等,最后函数的最后一个参数将是你的实际元素的标识符(对于这种情况,它的'搜索按钮'

答案 6 :(得分:1)

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

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

返回您正在寻找的元素。

答案 7 :(得分:1)

直到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"));

答案 8 :(得分:0)

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

<?xml version="1.0" encoding="utf-8"?>

<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle"
    android:useLevel="false"
    >

    <gradient android:useLevel="false"
        android:endColor="#6be400"
        android:centerColor="#008500"
        android:startColor="#6be400"/>

用法:

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

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

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