java - Как найти#shadow-root(open) элементы через cssSelector




selenium css-selectors (3)

Если вы пытаетесь получить элемент «Очистить данные», вы можете использовать приведенные ниже js, чтобы получить элемент, а затем выполнить.

return document.querySelector('settings-ui').shadowRoot.querySelector('settings-main').shadowRoot.querySelector('settings-basic-page').shadowRoot.querySelector('settings-section > settings-privacy-page').shadowRoot.querySelector('settings-clear-browsing-data-dialog').shadowRoot.querySelector('#clearBrowsingDataDialog').querySelector('#clearBrowsingDataConfirm')

Вот пример сценария.

driver.get("chrome://settings/clearBrowserData");
driver.manage().window().maximize();
JavascriptExecutor js = (JavascriptExecutor) driver; 
WebElement clearData = (WebElement) js.executeScript("return document.querySelector('settings-ui').shadowRoot.querySelector('settings-main').shadowRoot.querySelector('settings-basic-page').shadowRoot.querySelector('settings-section > settings-privacy-page').shadowRoot.querySelector('settings-clear-browsing-data-dialog').shadowRoot.querySelector('#clearBrowsingDataDialog').querySelector('#clearBrowsingDataConfirm')");
// now you can click on clear data button
clearData.click();

Изменить 2: Объяснение

Проблема: Selenium не предоставляет явной поддержки для работы с элементами Shadow DOM , поскольку их нет в текущем dom. По этой причине мы получим исключение NoSuchElementException при попытке доступа к элементам в shadow dom .

Shadow DOM:

Примечание: мы будем ссылаться на термины, показанные на картинке. Поэтому, пожалуйста, пройдите по картинке для лучшего понимания.

Решение:

Для того, чтобы сначала работать с теневым элементом, нам нужно найти shadow host к которому присоединен теневой домен. Вот простой способ получить теневой корень на основе shadowHost.

private static WebElement getShadowRoot(WebDriver driver,WebElement shadowHost) {
    JavascriptExecutor js = (JavascriptExecutor) driver;
    return (WebElement) js.executeScript("return arguments[0].shadowRoot", shadowHost);
}

И затем вы можете получить доступ к элементу теневого дерева с помощью элемента shadowRoot.

// get the shadowHost in the original dom using findElement
WebElement shadowHost = driver.findElement(By.cssSelector("shadowHost_CSS"));
// get the shadow root
WebElement shadowRoot = getShadowRoot(driver,shadowHost);
// access shadow tree element
WebElement shadowTreeElement = shadowRoot.findElement(By.cssSelector("shadow_tree_element_css"));

Для того, чтобы упростить все вышеперечисленные шаги, создан метод ниже

public static WebElement getShadowElement(WebDriver driver,WebElement shadowHost, String cssOfShadowElement) {
    WebElement shardowRoot = getShadowRoot(driver, shadowHost);
    return shardowRoot.findElement(By.cssSelector(cssOfShadowElement));
}

Теперь вы можете получить элемент shadowTree одним вызовом метода

WebElement shadowHost = driver.findElement(By.cssSelector("shadowHost_CSS_Goes_here));
WebElement shadowTreeElement = getShadowElement(driver,shadowHost,"shadow_tree_element_css");

И выполняйте операции как обычно, например .click() , .getText() .

shadowTreeElement.click()

Это выглядит просто, когда у вас есть только один уровень теневого DOM. Но здесь, в этом случае у нас есть несколько уровней теневых домов. Таким образом, мы должны получить доступ к элементу, достигнув каждого теневого хоста и корня.

Ниже приведен фрагмент кода с использованием методов, упомянутых выше (getShadowElement и getShadowRoot).

// Locate shadowHost on the current dom
WebElement shadowHostL1 = driver.findElement(By.cssSelector("settings-ui"));

// now locate the shadowElement by traversing all shadow levels
WebElement shadowElementL1 = getShadowElement(driver, shadowHostL1, "settings-main");
WebElement shadowElementL2 = getShadowElement(driver, shadowElementL1,"settings-basic-page");
WebElement shadowElementL3 = getShadowElement(driver, shadowElementL2,"settings-section > settings-privacy-page");
WebElement shadowElementL4 = getShadowElement(driver, shadowElementL3,"settings-clear-browsing-data-dialog");
WebElement shadowElementL5 = getShadowElement(driver, shadowElementL4,"#clearBrowsingDataDialog");
WebElement clearData = shadowElementL5.findElement(By.cssSelector("#clearBrowsingDataConfirm"));
System.out.println(clearData.getText());
clearData.click();

Вы можете выполнить все вышеперечисленные шаги в одном вызове js, как уже упоминалось в начале ответа (добавлено ниже только для уменьшения путаницы).

WebElement clearData = (WebElement) js.executeScript("return document.querySelector('settings-ui').shadowRoot.querySelector('settings-main').shadowRoot.querySelector('settings-basic-page').shadowRoot.querySelector('settings-section > settings-privacy-page').shadowRoot.querySelector('settings-clear-browsing-data-dialog').shadowRoot.querySelector('#clearBrowsingDataDialog').querySelector('#clearBrowsingDataConfirm')");

Скриншот:

Я следил за обсуждением Как автоматизировать теневые элементы DOM, используя селен? работать с элементами #shadow-root (open) .

В процессе поиска кнопки « Очистить данные» во всплывающем окне « Очистить данные просмотра» , которое появляется при доступе к URL-адресу chrome://settings/clearBrowserData через Selenium, я не могу найти следующий элемент:

#shadow-root (open)
<settings-privacy-page>

Снимок:

Используя Selenium ниже приведены мои испытания кода и связанные с ними ошибки:

  • Попытка 1:

    WebElement root5 = shadow_root4.findElement(By.tagName("settings-privacy-page"));
    • Ошибка:

      Exception in thread "main" org.openqa.selenium.JavascriptException: javascript error: b.getElementsByTagName is not a function
  • Попытка 2:

    WebElement root5 = shadow_root4.findElement(By.cssSelector("settings-privacy-page"));
    • Ошибка:

      Exception in thread "main" org.openqa.selenium.NoSuchElementException: no such element: Unable to locate element: {"method":"css selector","selector":"settings-privacy-page"}
  • Попытка 3:

    WebElement root5 = (WebElement)((JavascriptExecutor)shadow_root4).executeScript("return document.getElementsByTagName('settings-privacy-page')[0]");
    • Ошибка:

      Exception in thread "main" java.lang.ClassCastException: org.openqa.selenium.remote.RemoteWebElement cannot be cast to org.openqa.selenium.JavascriptExecutor

Incase, если это полезно, начальный блок кода (до строки выше) работает отлично:

driver.get("chrome://settings/clearBrowserData");
WebElement root1 = driver.findElement(By.tagName("settings-ui"));
WebElement shadow_root1 = expand_shadow_element(root1);

WebElement root2 = shadow_root1.findElement(By.cssSelector("settings-main#main"));
WebElement shadow_root2 = expand_shadow_element(root2);

WebElement root3 = shadow_root2.findElement(By.cssSelector("settings-basic-page[role='main']"));
WebElement shadow_root3 = expand_shadow_element(root3);

WebElement root4 = shadow_root3.findElement(By.cssSelector("settings-section[page-title='Privacy and security']"));
WebElement shadow_root4 = expand_shadow_element(root4);

PS: expand_shadow_element() работает безупречно.

Кто-нибудь может мне помочь, пожалуйста?


Мне интересно, если это отдельное дочернее окно, так как оно находится на переднем плане и поэтому не может найти элемент на экране. Как только вы получите ручку дочернего окна, вы сможете захватить элемент?


Ответ @ supputuri является рабочим и принятым ответом, так как Стратегия локатора через document.querySelector() прекрасно работает через google-chrome-devtools google-chrome-devtools

Однако, когда нужный элемент открывается из shadow-dom вам необходимо вызвать WebDriverWait для elementToBeClickable() и вы можете найти следующее решение:

  • Блок кода:

    driver.get("chrome://settings/clearBrowserData");
    new WebDriverWait(driver, 5).until(ExpectedConditions.elementToBeClickable((WebElement) ((JavascriptExecutor)driver).executeScript("return document.querySelector('settings-ui').shadowRoot.querySelector('settings-main').shadowRoot.querySelector('settings-basic-page').shadowRoot.querySelector('settings-section > settings-privacy-page').shadowRoot.querySelector('settings-clear-browsing-data-dialog').shadowRoot.querySelector('#clearBrowsingDataDialog').querySelector('#clearBrowsingDataConfirm')"))).click();
    System.out.println("Clear data Button Clicked");
  • Консольный вывод:

    Clear data Button Clicked




shadow-dom