访问 shadow DOM 中的元素

IT技术 javascript python selenium selenium-webdriver shadow-dom
2021-03-20 03:50:37

是否可以使用 python-selenium在 Shadow DOM 中找到元素

示例用例:

我有这个inputtype="date"

<input type="date">

我想单击右侧的日期选择器按钮并从日历中选择一个日期。

如果您检查 Chrome 开发者工具中的元素并展开日期输入的 shadow-root 节点,您会看到按钮显示为:

<div pseudo="-webkit-calendar-picker-indicator" id="picker"></div>

屏幕截图展示了它在 Chrome 中的外观:

在此处输入图片说明

通过 id 查找“选择器”按钮结果为NoSuchElementException

>>> date_input = driver.find_element_by_name('bday')
>>> date_input.find_element_by_id('picker')
...
selenium.common.exceptions.NoSuchElementException: Message: no such element

我还尝试按照此处的建议使用::shadow/deep/定位器

>>> driver.find_element_by_css_selector('input[name=bday]::shadow #picker')
...
selenium.common.exceptions.NoSuchElementException: Message: no such element
>>>
>>> driver.find_element_by_css_selector('input[name=bday] /deep/ #picker')
...
selenium.common.exceptions.NoSuchElementException: Message: no such element

请注意,我可以通过向其发送密钥来更改输入中的日期:

driver.find_element_by_name('bday').send_keys('01/11/2014')

但是,我想通过从日历中选择来专门设置日期。

2个回答

无法访问原生 HTML 5 元素的影子根。

在这种情况下没有用,但使用 Chrome 可以访问自定义创建的影子根:

var root = document.querySelector("#test_button").createShadowRoot();
root.innerHTML = "<button id='inner_button'>Button in button</button"
<button id="test_button"></button>

然后可以通过这种方式访问​​根:

 var element = document.querySelector("#test_button").shadowRoot;

如果您想使用 selenium python(chromedriver 版本 2.14+)自动点击内部按钮:

 >>> outer = driver.execute_script('return document.querySelector("#test_button").shadowRoot')
 >>> inner = outer.find_element_by_id("inner_button")
 >>> inner.click()

2015 年 6 月 9 日更新

这是 github 上当前 Shadow DOM W3C 编辑器草案的链接:

http://w3c.github.io/webcomponents/spec/shadow/

如果您有兴趣浏览blink源代码,这是一个很好的起点

这完全有道理。您基本上是在说这个新的 chromedriver 功能:问题 852:在 chromedriver 中支持 shadow dom。基本上是关于内部带有 shadow DOM 的自定义元素,对吗?
2021-05-10 03:50:37
没错。Chromedriver 只是浏览器 API 顶部的一个包装器。由于 Chrome 不允许对用户代理 shadow DOM 进行脚本化访问(它们可以设置样式),因此 selenium 也是如此。
2021-05-10 03:50:37
很好的答案,但嵌套阴影根元素有一些缺点,请查看下面的答案
2021-05-13 03:50:37

接受的答案有一个缺点,很多时候阴影宿主元素隐藏在阴影树中,这就是为什么最好的方法是使用 selenium 选择器来查找阴影宿主元素并注入脚本以获取阴影根:

def expand_shadow_element(element):
  shadow_root = driver.execute_script('return arguments[0].shadowRoot', element)
  return shadow_root

#the accepted answer code then becomes 
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()
@Pascal .shadowRoot 返回 Shadow DOM,打开和关闭仅在 Inspector GUI 中,如果返回 null 并且您希望该元素是 shadow root,则您可能选择了错误的元素
2021-04-21 03:50:37
他只有在 Shadow DOM 打开时才有效,对吗?否则 .shadowRoot 将返回 null。有没有办法访问关闭的 Shadow DOM?编写 Chrome 扩展程序不会成为问题
2021-04-30 03:50:37
@Pascal 其实你可能是对的,我不知道这种打开/关闭模式
2021-05-06 03:50:37
是的...我可能会使用扩展名。非常感谢
2021-05-09 03:50:37
感谢您的答复。在这篇博文中,它举例说明了关闭 Shadow DOM 返回 NULL 的事实(blog.revillweb.com/open-vs-closed-shadow-dom-9f3d7427d1af)。
2021-05-11 03:50:37