Доступ к дереву Shadow DOM с селеном

можно ли получить доступ к элементам в теневом DOM с помощью Selenium / Chrome webdriver?

использование обычных методов поиска элементов не работает, как и следовало ожидать. Я видел ссылки на switchToSubTree spec на w3c, но не удалось найти никаких фактических документов, примеров и т. д.

кто-нибудь имел успех с этим?

7 ответов


к сожалению, похоже, что спецификация webdriver еще не поддерживает это.

мое вынюхивание раскрыто:

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

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


принятый ответ больше не действителен, а некоторые другие ответы имеют некоторые недостатки или не являются практичными (/deep/ селектор не работает и устарела, document.querySelector('').shadowRoot работает только с первым теневым элементом, когда теневые элементы вложены), иногда теневые корневые элементы вложены, а второй теневой корень не отображается в корне документа, но доступен в его родительском доступном теневом корне. Я думаю, что лучше использовать селекторы селена и вводить скрипт только для Возьмите корень тени:

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 вложенных корневых элемента shadow: enter image description here

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 binary Chrome теперь поддерживает Shadow DOM (начиная с января 28, 2015) : http://chromedriver.storage.googleapis.com/2.14/notes.txt


Я использую C# и Selenium и сумел найти элемент внутри скрытого теневого DOM с помощью Java-скрипта. Это мое дерево html:

HTML-дерево

Я хочу url в последней строке, и чтобы получить его, я сначала выбираю тег "downloads-manager", а затем первый теневой корень прямо под ним. Оказавшись внутри теневого корня, я хочу найти элемент, ближайший к следующему теневому корню. Этот элемент - "downloads-item". С этим выбранным я могу войти во второй тень корня. Оттуда я выбираю элемент img, содержащий url по id = "file-icon". Наконец, я могу получить атрибут "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')");

обычно вы делаете это:

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

и, надеюсь, это будет продолжать работать.


однако обратите внимание, что /deep/ и ::shadow are устаревший (и не реализован в браузерах, отличных от 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. Я беру тот же пример, приведенный выше, для поиск значок of Страница Загрузки 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

теперь, как показано на рисунке, мы должны развернуть три теневых корневых элемента, чтобы получить значок поиска. Чтобы нажать на иконку все, что нам нужно сделать, это : -

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

Так что только одна строка даст вам ваш веб-элемент, просто нужно убедиться, что вы пройти первый теневой корневой элемент в качестве первого аргумента функции "getUIObject" второй теневой корневой элемент в качестве второго аргумента функции и так далее, наконец, последний аргумент функции будет идентификатором для вашего фактического элемента (для этого случая его 'search-button')


это сработало для меня (используя привязки javascript selenium):

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

Это возвращает элемент(Ы), который вы ищете.