如何使用selenium自动化阴影DOM元素?

cig3rfwq  于 2022-12-18  发布在  其他
关注(0)|答案(6)|浏览(240)

我正在使用Java Selenium项目进行网页自动化。网页包含许多多级shadow-root DOM元素,我无法使用Selenium findElement方法与这些元素交互。

我尝试了以下解决方案:

  • 深css(不工作在最新的chrome浏览器)
  • JS Executor.(这真的很乏味,维护起来也很复杂)

注:

如果你知道任何其他解决方案以外的其他以上所列,我可以实现在SeleniumJava框架,请传递的解决方案。提前感谢!

nkkqxpd9

nkkqxpd91#

有一个很好的插件可以和selenium项目shadow-automation-selenium一起使用。它有助于编写更好的,可读性和可维护的代码。使用它你可以访问多级shadow DOM(最多4级)。它使用简单的css选择器来识别元素。
WebElement findElement(String cssSelector):如果需要DOM中的单个元素,请使用此方法
List<WebElement> findElements(String cssSelector):如果要从DOM中查找所有元素,请使用此函数
WebElement findElements(WebElement parent, String cssSelector):如果您想从父对象DOM中查找单个元素,请使用此函数
List<WebElement> findElements(WebElement parent, String cssSelector):如果要从父对象DOM中查找所有元素,请使用此函数
WebElement getShadowElement(WebElement parent,String selector):如果要从父DOM中查找单个元素,请使用此函数
List<WebElement> getAllShadowElement(WebElement parent,String selector):如果要查找父DOM中的所有元素,请使用此函数
boolean isVisible(WebElement element):如果要查找元素的可见性,请使用此选项
boolean isChecked(WebElement element):如果要检查是否选中复选框,请使用此选项
boolean isDisabled(WebElement element):如果要检查元素是否已禁用,请使用此选项
String getAttribute(WebElement element,String attribute):如果您想获得像aria-selected这样属性和元素的其他自定义属性,请使用此函数。
void selectCheckbox(String label):使用此选项选择使用标签的复选框元素。
void selectCheckbox(WebElement parentElement, String label):使用此选项选择使用标签的复选框元素。
void selectRadio(String label):使用此选项选择使用标签的无线电元素。
void selectRadio(WebElement parentElement, String label):使用此选项从使用标签的父DOM中选择单选元素。
void selectDropdown(String label):使用此选项使用标签选择下拉列表项(如果UI上仅存在或加载了一个下拉列表,则使用此选项)。
void selectDropdown(WebElement parentElement, String label):使用此选项从使用标签的父DOM中选择下拉列表项。
如何使用此插件:你将不得不依赖于你的项目.

玛文

<dependency>
  <groupId>io.github.sukgu</groupId>
  <artifactId>automation</artifactId>
  <version>0.0.4</version>
<dependency>

对于驻留在shadow-root dom元素下的html标记

<properties-page id="settingsPage"> 
  <textarea id="textarea">
</properties-page>

您可以在框架中使用这段代码来获取textarea元素Object。

import io.github.sukgu.*;
  Shadow shadow = new Shadow(driver);
  WebElement element = shadow.findElement("properties-page#settingsPage>textarea#textarea");
  String text = element.getText();

vu8f3i0k

vu8f3i0k2#

使用JSExecutor和CSS查找阴影DOM元素的步骤:
1.找出影子根元素的基元素即父元素。
1.获取该元素的影子根。
1.在暗根网络元素上找到你的元素
示例:

<div id="example">
#shadow-root
<div id="root" part="root">
   <div id="label" part="label">ShadowRootLabel</div>
</div>
</ptcs-label>

找出阴影根元素的方法

public WebElement getShadowRootElement(WebElement element) {
WebElement ele = (WebElement) ((JavascriptExecutor)driver)
    .executeScript("return arguments[0].shadowRoot", element);
        return ele;
    }

Step1示例,即查找基本元素:

WebElement root1 = driver.findElement(By.id("example"));

步骤2

//Get shadow root element
WebElement shadowRoot1 = getShadowRootElement(root1);

Step3 -我们需要使用CSS选择器查找影子根目录中的元素,xpath在这里不起作用

//Here we will get Element inside Shadow Dom Element
WebElement shadowElement = shadowRoot3.findElement(By.cssSelector("div[id=label]"));
bvuwiixz

bvuwiixz3#

对于Selenium 4,现在有WebElement.getShadowRoot()

driver.findElement(By.id("parentId")).getShadowRoot().findElement(By.cssSelector( "label" )).findElement(By.tagName("input"))

#shadow-root的正常情况一样,下一跳的导航选项有限。例如,对于Chrome,By.cssSelector()By.className()有效,但By.id()By.tagName()org.openqa.selenium.InvalidArgumentException: invalid argument: invalid locator下失败

x0fgdtte

x0fgdtte4#

为了演示使用 Selenium v3.xChromeDriver v2.46Chrome v73.xshadow DOM 的 * 自动化 ,这里有两种方法,它们打开url chrome://downloads/,并使用executeScript()方法发送 * 字符序列pdf作为 * 搜索框 * 中的 * 搜索文本 *。

使用document.querySelector()

作为规范方法,您可以使用document.querySelector()方法,如下所示:

  • 代码块:
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;

public class shadow_DOM_search_download_querySelector {

    public static void main(String[] args)
    {
        System.setProperty("webdriver.chrome.driver", "C:\\Utility\\BrowserDrivers\\chromedriver.exe");
        ChromeOptions options = new ChromeOptions();
        options.addArguments("start-maximized");
        options.addArguments("disable-infobars");
        options.addArguments("--disable-extensions"); 
        WebDriver driver = new ChromeDriver(options);
        driver.get("chrome://downloads/");
        JavascriptExecutor jse = (JavascriptExecutor) driver; 
        WebElement search_box = (WebElement) jse.executeScript("return document.querySelector('downloads-manager').shadowRoot.querySelector('downloads-toolbar#toolbar').shadowRoot.querySelector('cr-toolbar#toolbar').shadowRoot.querySelector('cr-toolbar-search-field#search').shadowRoot.querySelector('div#searchTerm input#searchInput')");
        String js = "arguments[0].setAttribute('value','pdf')";
        ((JavascriptExecutor) driver).executeScript(js, search_box);
    }
}

相同的解决方案可以逐步改写如下:

  • 代码块:
import org.openqa.selenium.By;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;

public class shadow_DOM {

    static WebDriver driver;
    public static void main(String[] args) 
    {   
        System.setProperty("webdriver.chrome.driver", "C:\\Utility\\BrowserDrivers\\chromedriver.exe");
        ChromeOptions options = new ChromeOptions();
        options.addArguments("start-maximized");
        //options.addArguments("disable-infobars");
        options.addArguments("--disable-extensions"); 
        driver = new ChromeDriver(options);
        driver.get("chrome://downloads/");
        WebElement root1 = driver.findElement(By.tagName("downloads-manager"));
        WebElement shadow_root1 = expand_shadow_element(root1);

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

        WebElement root3 = shadow_root2.findElement(By.cssSelector("cr-toolbar#toolbar"));
        WebElement shadow_root3 = expand_shadow_element(root3);

        WebElement root4 = shadow_root3.findElement(By.cssSelector("cr-toolbar-search-field#search"));
        WebElement shadow_root4 = expand_shadow_element(root4);

        WebElement search_term = shadow_root4.findElement(By.cssSelector("div#searchTerm input#searchInput"));
        String js = "arguments[0].setAttribute('value','pdf')";
        ((JavascriptExecutor) driver).executeScript(js, search_term);

        WebElement search_button = shadow_root4.findElement(By.cssSelector("paper-icon-button#icon"));
        search_button.click();

        System.out.println("Search Button Clicked");
    }

    public static WebElement expand_shadow_element(WebElement element)
    {
        WebElement shadow_root = (WebElement)((JavascriptExecutor)driver).executeScript("return arguments[0].shadowRoot", element);
        return shadow_root;
    }

}
  • 控制台输出:
Search Button Clicked
  • 浏览器快照:

结束

根据Determine the fate of experimental '>>>' combinator中的讨论,**>>>组合子是用于穿透所有阴影DOM边界以进行样式的/deep/**组合子的替代,在Blink中的标志后面实现。

u1ehiz5o

u1ehiz5o5#

Chrome v96(及更高版本)和Selenium中的阴影DOM

随着Chrome v96的推出,google-chrome已经使其shadow root返回值符合W3C WebDriver规范。
正如@titusfortner在他们的评论中提到的:
现在Chrome通过驱动程序支持shadow root,shadow Root JS调用会根据规范返回一个shadow root元素键(shadow-6066- 11 e4-a52 e-4f 735466 cecf)。Selenium 4有一个新的ShadowRoot类来支持这一点,但我们没有包含执行脚本调用返回元素时所做的转换代码。这个问题已经得到修复,将在Selenium 4.1中提供。
唯一的区别是您需要强制转换到ShadowRoot而不是WebElement。
因此,在Microsoft Edge和Google Chrome v96及更高版本中继续使用Selenium时,我们需要使用新的影子根方法,如下所示:

  • *Java示例 *:
driver.get("http://watir.com/examples/shadow_dom.html");
WebElement shadowHost = driver.findElement(By.cssSelector("#shadow_host"));
SearchContext shadowRoot = shadowHost.getShadowRoot();
WebElement shadowContent = shadowRoot.findElement(By.cssSelector("#shadow_content"));
Assertions.assertEquals("some text", shadowContent.getText());
  • *Python示例 *:
driver.get('http://watir.com/examples/shadow_dom.html')
shadow_host = driver.find_element(By.CSS_SELECTOR, '#shadow_host')
shadow_root = shadow_host.shadow_root
shadow_content = shadow_root.find_element(By.CSS_SELECTOR, '#shadow_content')
assert shadow_content.text == 'some text'
  • *C#示例 *:
_driver.Navigate().GoToUrl("http://watir.com/examples/shadow_dom.html");
var shadowHost = _driver.FindElement(By.CssSelector("#shadow_host"));
var shadowRoot = shadowHost.GetShadowRoot();
var shadowContent = shadowRoot.FindElement(By.CssSelector("#shadow_content"));
  • *Ruby示例 *:
@driver.get('http://watir.com/examples/shadow_dom.html')
shadow_host = @driver.find_element(css: '#shadow_host')
shadow_root = shadow_host.shadow_root
shadow_content = shadow_root.find_element(css: '#shadow_content')
expect(shadow_content.text).to eq 'some text'

时间;日期

更多信息请访问:

okxuctiv

okxuctiv6#

我可以使用这个递归函数访问多级shadow-root DOM元素。

private getHTMLElementWithShadowRoot(htmlElement: HTMLElement, nameHtmlElements: string[]): HTMLElement {
            if (nameHtmlElements.length === 0) return htmlElement;
            return this.getHTMLElementWithShadowRoot(
                htmlElement.shadowRoot ?
                    htmlElement.shadowRoot.querySelector(nameHtmlElements[0]) :
                    htmlElement.querySelector(nameHtmlElements[0]), nameHtmlElements.slice(1)
            );
 }

我这样使用:

let appComponent = document.body.querySelector("app-component") as HTMLElement;
let appHeader = this.getHTMLElementWithShadowRoot(appComponent, ["main-component", "header-component", "#app-header"]);

在这里您可以看到DOM x1c 0d1x
您必须在数组字符串nameHtmlElements中写入下划线名称,但不包括名字,因为名字是在函数的第一个参数中传递的。

相关问题