我正在研究一个广泛使用阴影dom的自动化项目。所以每次使用execute_script函数来访问shadow root
例如。
root = driver.execute_script('return document.querySelector(".flex.vertical.layout").shadowRoot')
然后使用root访问其中的元素。由于我们在很多层面都有影子根源,这让我很烦恼。是否有比这更好的解决方案来访问阴影根中的元素?
我正在使用Chrome 2.20驱动程序。
通过谷歌搜索我找到了另一个解决这个问题的方法 - 使用“/ deep / combinator”。
例如,我能够通过访问所有阴影根元素
driver.find_elements_by_css_selector( '体/深/ .layout.horizontal.center')
无论它具有多少阴影根,它都可以访问具有复合类名称“layout horizontal center”的元素。
但这只适用于Chrome驱动程序,我看到注释“/ deep /”是一种弃用的方法。
关于Shadow DOM的WebDriver规范仍然是doesn't have anything specific to say。
也不是Selenium project pages - 这是可以理解的,因为它们严格遵循规范。然而,存在some low-level code。
因此,简短的回答是:不,目前在Selenium的WebDriver API或实现代码中,规范中没有特别的支持。
是的,该功能似乎存在于ChromeDriver 2.14中(作为Chrome的包装)。但是,据我所知,没有Selenium或WebDriver级别的绑定可以让你使用它。
但是有关更多细节和可能的解决方法,请参阅:Accessing Shadow DOM tree with Selenium,还有:Accessing elements in the shadow DOM,尤其是:Finding elements in the shadow DOM
您可以编写扩展方法来操作IWebElement以扩展根目录,如下所示。
public static class SeleniumExtension
{
public static IWebElement ExpandRootElement(this IWebElement element, IWebDriver driver)
{
return (IWebElement)((IJavaScriptExecutor)driver)
.ExecuteScript("return arguments[0].shadowRoot", element);
}
}
您可以使用上面的扩展方法遍历元素层次结构以到达intrest的元素。
By downloads_manager_ShadowDom= By.TagName("downloads-manager");
By downloadToolBarShadowDom = By.CssSelector("downloads-toolbar");
By toolBarElement = By.CssSelector("cr-toolbar");
IWebElement ToolBarElement = driver.FindElement(downloads_manager_ShadowDom).ExpandRootElement(driver)
.FindElement(downloadToolBarShadowDom).ExpandRootElement(driver)
.FindElement(toolBarElement);
试图在Chrome上实现这种自动化我想出了一个不明智的解决方案,使用以下方法递归搜索每个阴影dom:
driver.executeScript(scriptToRun, cssSelector);
这是javascript(以字符串形式传递):
function recursiveSearch(element, target) {
let result = element.querySelector(target);
if (result) { return result; }
let subElements = element.querySelectorAll("*");
for (let i = 0; i < subElements.length; i++) {
let subElement = subElements[i];
if (subElement && subElement.shadowRoot) {
let result = recursiveSearch(subElement.shadowRoot, target);
if (result) return result;
}
}
}
return recursiveSearch(document, arguments[0]);
由于shadowRoot的内容最初可能为空,因此可以使用driver.wait
和until.elementIsVisible
来避免返回null元素。
异步示例:
return await driver.wait(until.elementIsVisible(await driver.wait(async () => {
return await driver.executeScript(scriptToRun, cssSelector);
}, timeOut)));
我之前的解决方案是明确地使用阴影遍历元素,但不那么自主。与上面相同,但使用此脚本:
let element = document.querySelector(arguments[0][0]);
let selectors = arguments[0].slice(1);
for (i = 0; i < selectors.length; i++) {
if (!element || !element.shadowRoot) {return false;}
element = element.shadowRoot.querySelector(selectors[i]);
}
return element;
selectors
会是这样的:
['parentElement1', 'parentElement2', 'targetElement']
我发现在Firefox Quantum 57.0上运行我的自动化测试不会受到隐藏阴影doms的影响,并且可以通过简单的方式找到任何元素:
driver.findElement(searchQuery);
由于您经常使用,您可以创建一个函数,然后上面变为:
def select_shadow_element_by_css_selector(selector):
running_script = 'return document.querySelector("%s").shadowRoot' % selector
element = driver.execute_script(running_script)
return element
shadow_section = select_shadow_element_by_css_selector(".flex.vertical.layout")
shadow_section.find_element_by_css(".flex")
在结果元素上,您可以放置任何方法:
find_element_by_id find_element_by_name find_element_by_xpath find_element_by_link_text find_element_by_partial_link_text find_element_by_tag_name find_element_by_class_name find_element_by_css_selector
要查找多个元素(这些方法将返回一个列表):
find_elements_by_name find_elements_by_xpath find_elements_by_link_text find_elements_by_partial_link_text find_elements_by_tag_name find_elements_by_class_name find_elements_by_css_selector
稍后编辑:
有时阴影主机元素隐藏了阴影树,这就是为什么最好的方法是使用selenium选择器来查找阴影主机元素并注入脚本只是为了获取阴影根:
def expand_shadow_element(element):
shadow_root = driver.execute_script('return arguments[0].shadowRoot', element)
return shadow_root
#the above becomes
shadow_section = expand_shadow_element(find_element_by_tag_name("neon-animatable"))
shadow_section.find_element_by_css(".flex")
为了说明这一点,我刚刚在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
selenium.__file__
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()
也许你可以使用IJavaScriptExecutor?
IWebDriver driver;
IJavaScriptExecutor jsExecutor = (IJavaScriptExecutor)driver;
jsExecutor.ExecuteScript('yourShadowDom.func()');
不确定它适用于所有浏览器,但对我来说::shadow
在chromedriver 2.38中运行良好例如:
div::shadow div span::shadow a