简体   繁体   English

使用 Selenium 访问 Shadow DOM 树

[英]Accessing Shadow DOM tree with Selenium

Is it possible to access elements within a Shadow DOM using Selenium/Chrome webdriver?是否可以使用 Selenium/Chrome webdriver 访问 Shadow DOM 中的元素?

Using the normal element search methods doesn't work, as is to be expected.正如预期的那样,使用普通元素搜索方法不起作用。 I've seen references to theswitchToSubTree spec on w3c, but couldn't locate any actual docs, examples, etc.我在 w3c 上看到了对switchToSubTree规范的引用,但找不到任何实际的文档、示例等。

Anyone had success with this?有人成功了吗?

The accepted answer is no longer valid and some of the other answers have some drawbacks or are not practical (the /deep/ selector doesn't work and is deprecated, document.querySelector('').shadowRoot works only with the first shadow element when shadow elements are nested), sometimes the shadow root elements are nested and the second shadow root is not visible in document root, but is available in its parent accessed shadow root.接受的答案不再有效,其他一些答案有一些缺点或不实用( /deep/选择器不起作用且已弃用, document.querySelector('').shadowRoot仅适用于第一个 shadow 元素当阴影元素被嵌套时),有时阴影根元素被嵌套,第二个阴影根在文档根中不可见,但在其父访问的阴影根中可用。 I think is better to use the selenium selectors and inject the script just to take the shadow root:我认为最好使用 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()

To put this into perspective I just added a testable example with Chrome's download page, clicking the search button needs open 3 nested shadow root elements:为了正确理解这一点,我刚刚在 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()

Doing the same approach suggested in the other answers has the drawback that it hard-codes the queries, is less readable and you cannot use the intermediary selections for other actions:执行其他答案中建议的相同方法的缺点是它对查询进行了硬编码,可读性较差,并且您不能将中间选择用于其他操作:

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

I am using C# and Selenium and managed to find an element inside a nestled shadow DOM using java script.我正在使用 C# 和 Selenium,并设法使用 java 脚本在嵌套的 shadow DOM 中找到一个元素。 This is my html tree:这是我的 html 树:

html tree html 树

I want the url on the last line and to get it I first select the "downloads-manager" tag and then the first shadow root right below it.我想要最后一行的 url 并获得它,我首先选择“downloads-manager”标签,然后选择它下方的第一个影子根。 Once inside the shadow root I want to find the element closest to the next shadow root.一旦进入阴影根,我想找到最接近下一个阴影根的元素。 That element is "downloads-item".该元素是“下载项”。 With that selected I can enter the second shadow root.选择后,我可以进入第二个影子根。 From there I select the img item containing the url by id = "file-icon".从那里我通过 id = "file-icon" 选择包含 url 的 img 项目。 At last I can get the attribute "src" which contains the url I am seeking.最后,我可以获得包含我正在寻找的 url 的属性“src”。

The two lines of C# code that does the trick:两行 C# 代码可以解决这个问题:

IJavaScriptExecutor jse2 = (IJavaScriptExecutor)_driver;
var pdfUrl = jse2.ExecuteScript("return document.querySelector('downloads-manager').shadowRoot.querySelector('downloads-item').shadowRoot.getElementById('file-icon').getAttribute('src')");

Normally you'd do this:通常你会这样做:

element = webdriver.find_element_by_css_selector(
    'my-custom-element /deep/ .this-is-inside-my-custom-element')

And hopefully that'll continue to work.希望这将继续有效。


However, note that /deep/ and ::shadow are deprecated (and not implemented in browsers other than Opera and Chrome).但是,请注意/deep/::shadow弃用(并且未在 Opera 和 Chrome 以外的浏览器中实现)。 There's much talk about allowing them in the static profile.有很多关于在静态配置文件中允许它们的讨论。 Meaning, querying for them will work, but not styling.意思是,查询它们会起作用,但不能设置样式。

If don't want to rely on /deep/ or ::shadow because their futures are a bit uncertain, or because you want to work better cross-browser or because you hate deprecation warnings, rejoice as there's another way:如果不想依赖/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')

More about this:更多关于这个:

I found a much easier way to get the elements from Shadow Dom.我找到了一种更简单的方法来从 Shadow Dom 中获取元素。 I am taking the same example given above, for search icon of Chrome Download Page .我正在使用上面给出的相同示例,用于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;
        }

Google Chrome Download Page谷歌浏览器下载页面

Now as shown in image we have to expand three shadow root elements in order to get our search icon.现在如图所示,我们必须展开三个阴影根元素以获得我们的搜索图标。 To to click on icon all we need to do is :-要点击图标,我们需要做的就是:-

  [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();
        }

So just one line will give you your Web Element, just need to make sure you pass first shadow root element as first argument of the function "getUIObject" second shadow root element as second argument of the function and so on, finally last argument for the function will be the identifier for your actual element (for this case its 'search-button' )所以只有一行会给你你的 Web 元素,只需要确保你将第一个影子根元素作为函数“getUIObject”的第一个参数传递,第二个影子根元素作为函数的第二个参数,依此类推,最后是function 将是您实际元素的标识符(在这种情况下,它的'search-button'

Until Selenium supports shadow DOM out of the box, you can try the following workaround in Java.在 Selenium 支持开箱即用的 shadow DOM 之前,您可以在 Java 中尝试以下解决方法。 Create a class that extends By class:创建一个扩展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;
        }
    }
}

And you can use it without writing any additional functions or wrappers.而且您无需编写任何额外的函数或包装器就可以使用它。 This should work with any kind of framework.这应该适用于任何类型的框架。 For example, in pure Selenium code this would look like this:例如,在纯 Selenium 代码中,这看起来像这样:

WebElement searchButton =
    driver.findElement(ByShadow.css(
        "downloads-manager >>> downloads-toolbar >>> cr-search-field >>> #search-button"));

or if you use Selenide:或者如果您使用硒化物:

SelenideElement searchButton =
    $(ByShadow.css("downloads-manager >>> downloads-toolbar >>> cr-search-field >>> #search-button"));

This worked for me (using selenium javascript bindings):这对我有用(使用 selenium javascript 绑定):

driver.executeScript("return $('body /deep/ <#selector>')")

That returns the element(s) you're looking for.这将返回您正在寻找的元素。

For getting the filename of the latest downloaded file in Chrome用于在 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

Usage:用法:

driver.get_url('chrome://downloads')
filename = driver.get_downloaded_file()

And for configuring the option for setting the default download directory in selenium for chrome browser, where the corresponding file could be gotten:以及配置chrome浏览器selenium默认下载目录的选项,可以得到对应的文件:

..
chrome_options = webdriver.ChromeOptions()
..
prefs = {'download.default_directory': '/desired-path-to-directory'} # unix
chrome_options.add_experimental_option('prefs', prefs)
..

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM