如何在使用 cssSelector 清除 Chrome 浏览器的浏览数据时与#shadow根(打开)中的元素进行交互

2022-09-03 08:45:36

我一直在关注讨论如何使用硒自动化影子DOM元素?来处理元素。#shadow-root (open)

在通过Selenium访问URL时出现的“清除浏览数据”弹出窗口中查找按钮的过程中,我无法找到以下元素:Clear datachrome://settings/clearBrowserData

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

快照:

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
      

如果有帮助,初始代码块(直到上面的行)工作完美:

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()


答案 1

如果您尝试获取“清除数据”元素,则可以使用以下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 中。这就是为什么我们在尝试访问 中的元素时会遇到异常的原因。NoSuchElementExceptionshadow dom

Shadow DOM: enter image description here

注意:我们将参考图片中显示的术语。因此,请仔细阅读图片以更好地理解。

溶液:

为了首先使用,我们必须找到阴影圆顶所附着的。以下是基于shadowHost获取影子根的简单方法。shadow elementshadow host

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

然后,您可以使用阴影根元素访问阴影树元素。

// 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 时,这看起来很简单。但是在这里,在这种情况下,我们有多个级别的影子圆顶。因此,我们必须通过到达每个影子宿主和根来访问该元素。enter image description here

以下是使用上述方法的片段(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')");

截图:enter image description here


答案 2

我不得不做一个类似的测试,需要清除浏览chrome历史记录。一个小小的区别是,我在转到弹出窗口的高级部分后清除了数据。由于您正在努力仅单击“清除数据”按钮,因此我很确定您错误地错过了一两个层次结构元素。或者可能在兄弟姐妹和父母元素之间混淆。根据你的代码,我假设你已经知道要访问一个特定的影子DOM元素,你需要正确的排序,上面也已经很好地解释了这一点。
现在正好解决您的问题,这是我的代码片段,它工作正常。代码将等待数据被清除,然后继续执行下一个操作 -

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

public void clearBrowsingHistory() throws Exception {

    WebDriverWait wait = new WebDriverWait(driver, 15);
    driver.get("chrome://settings/clearBrowserData");
// Get shadow root elements
    WebElement shadowRoot1 = expandRootElement(driver.findElement(By.xpath("/html/body/settings-ui")));

    WebElement root2 = shadowRoot1.findElement(By.cssSelector("settings-main"));
    WebElement shadowRoot2 = expandRootElement(root2);

    WebElement root3 = shadowRoot2.findElement(By.cssSelector("settings-basic-page"));
    WebElement shadowRoot3 = expandRootElement(root3);

    WebElement root4 = shadowRoot3
        .findElement(By.cssSelector("#advancedPage > settings-section > settings-privacy-page"));
    WebElement shadowRoot4 = expandRootElement(root4);

    WebElement root5 = shadowRoot4.findElement(By.cssSelector("settings-clear-browsing-data-dialog"));
    WebElement shadowRoot5 = expandRootElement(root5);

    WebElement root6 = shadowRoot5
        .findElement(By.cssSelector("cr-dialog div[slot ='button-container'] #clearBrowsingDataConfirm"));

    root6.click();
    wait.until(ExpectedConditions.invisibilityOf(root6));
}

如果您不打算更改弹出窗口中默认选择的任何选项,它也应该在您的情况下正常工作(在这种情况下,您将不得不添加一些关于选择这些复选框的代码)。请告诉我这是否解决了您的问题。希望这有帮助,我在这里也添加了屏幕的快照 - 图像


推荐