是否可以使用 Selenium/Chrome webdriver 访问 Shadow DOM 中的元素?
正如预期的那样,使用正常的元素搜索方法不起作用。我在 w3c 上看到了对switchToSubTree规范的引用,但找不到任何实际的文档、示例等。
有人成功了吗?
是否可以使用 Selenium/Chrome webdriver 访问 Shadow DOM 中的元素?
正如预期的那样,使用正常的元素搜索方法不起作用。我在 w3c 上看到了对switchToSubTree规范的引用,但找不到任何实际的文档、示例等。
有人成功了吗?
接受的答案不再有效,其他一些答案有一些缺点或不实用(/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()
还应该注意的是,Selenium 二进制 Chrome 驱动程序现在支持 Shadow DOM(自 2015 年 1 月 28 日起): http ://chromedriver.storage.googleapis.com/2.14/notes.txt
不幸的是,看起来 webdriver 规范还不支持这个。
我的窥探被发现:
http://www.w3.org/TR/webdriver/#switching-to-hosted-shadow-doms
https://groups.google.com/forum/#!msg/selenium-developers/Dad2KZsXNKo/YXH0e6eSHdAJ
我正在使用 C# 和 Selenium,并设法使用 java 脚本在嵌套的影子 DOM 中找到一个元素。这是我的 html 树:
我想要最后一行的 url,为了得到它,我首先选择“downloads-manager”标签,然后选择它正下方的第一个影子根。一旦进入影子根,我想找到最接近下一个影子根的元素。该元素是“下载项目”。选择后,我可以输入第二个影子根。从那里我选择包含 id = "file-icon" 的 url 的 img 项目。最后我可以得到包含我正在寻找的 url 的属性“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')");
通常你会这样做:
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')
更多关于这个:
我找到了一种从 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;
}
现在如图所示,我们必须展开三个阴影根元素才能获得我们的搜索图标。要单击图标,我们需要做的就是:-
[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”的第一个参数,第二个阴影根元素作为函数的第二个参数,依此类推,最后是最后一个参数函数将是您的实际元素的标识符(在这种情况下,它的 'search-button')
在 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"));
或者如果您使用硒化物:
SelenideElement searchButton =
$(ByShadow.css("downloads-manager >>> downloads-toolbar >>> cr-search-field >>> #search-button"));
这对我有用(使用 selenium javascript 绑定):
driver.executeScript("return $('body /deep/ <#selector>')")
这将返回您正在寻找的元素。
用于在 Chrome 中获取最新下载文件的文件名
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
用法:
driver.get_url('chrome://downloads')
filename = driver.get_downloaded_file()
以及用于配置在 selenium 中为 chrome 浏览器设置默认下载目录的选项,其中可以获取相应的文件:
..
chrome_options = webdriver.ChromeOptions()
..
prefs = {'download.default_directory': '/desired-path-to-directory'} # unix
chrome_options.add_experimental_option('prefs', prefs)
..