Доступ к дереву 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:
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:
Я хочу 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>')")
Это возвращает элемент(Ы), который вы ищете.