使用Selenium访问Shadow DOM树
可以用Selenium或Chrome webdriver访问Shadow DOM中的元素吗?
用普通的方法去查找元素是行不通的,这也是意料之中的事情。我看到有提到过w3c的switchToSubTree规范,但找不到任何实际的文档、例子等等。
有没有人成功做到这一点?
9 个回答
通常你会这样做:
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')
更多信息:
我正在使用C#和Selenium,并且成功地通过JavaScript找到了一个嵌套在影子DOM中的元素。
我想要获取最后一行的URL。为了做到这一点,我首先选择“downloads-manager”这个标签,然后找到它下面的第一个影子根节点。
进入影子根节点后,我想找到离下一个影子根节点最近的元素。这个元素就是“downloads-item”。选中这个元素后,我就可以进入第二个影子根节点。在那里,我通过ID为“file-icon”的img元素来选择包含URL的项。最后,我可以获取“src”这个属性,它里面就是我想要的URL。
下面这两行C#代码可以完成这个操作:
IJavaScriptExecutor jse2 = (IJavaScriptExecutor)_driver;
var pdfUrl = jse2.ExecuteScript("return document.querySelector('downloads-manager').shadowRoot.querySelector('downloads-item').shadowRoot.getElementById('file-icon').getAttribute('src')");
还要提一下,Selenium的Chrome驱动程序现在支持Shadow DOM(从2015年1月28日起)。你可以在这里查看详细信息:http://chromedriver.storage.googleapis.com/2.14/notes.txt
这个被接受的答案现在已经不太适用了,其他一些答案也有缺点或者不太实际(比如说,/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()
很遗憾,看起来webdriver的规范还不支持这个功能。
我查找了一下,发现了以下内容:
http://www.w3.org/TR/webdriver/#switching-to-hosted-shadow-doms
https://groups.google.com/forum/#!msg/selenium-developers/Dad2KZsXNKo/YXH0e6eSHdAJ