使用Selenium访问Shadow DOM树

21 投票
9 回答
30235 浏览
提问于 2025-04-18 07:52

可以用Selenium或Chrome webdriver访问Shadow DOM中的元素吗?

用普通的方法去查找元素是行不通的,这也是意料之中的事情。我看到有提到过w3c的switchToSubTree规范,但找不到任何实际的文档、例子等等。

有没有人成功做到这一点?

9 个回答

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

更多信息:

3

我正在使用C#和Selenium,并且成功地通过JavaScript找到了一个嵌套在影子DOM中的元素。

这是我的HTML结构图

我想要获取最后一行的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')");
5

还要提一下,Selenium的Chrome驱动程序现在支持Shadow DOM(从2015年1月28日起)。你可以在这里查看详细信息:http://chromedriver.storage.googleapis.com/2.14/notes.txt

22

这个被接受的答案现在已经不太适用了,其他一些答案也有缺点或者不太实际(比如说,/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()
3

很遗憾,看起来webdriver的规范还不支持这个功能。

我查找了一下,发现了以下内容:

http://www.w3.org/TR/webdriver/#switching-to-hosted-shadow-doms

https://groups.google.com/forum/#!msg/selenium-developers/Dad2KZsXNKo/YXH0e6eSHdAJ

撰写回答