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

Posted

技术标签:

【中文标题】如何在使用 cssSelector 清除 Chrome 浏览器的浏览数据时与 #shadow-root (open) 中的元素进行交互【英文标题】:How to interact with the elements within #shadow-root (open) while Clearing Browsing Data of Chrome Browser using cssSelector 【发布时间】:2019-10-16 05:14:32 【问题描述】:

我一直在关注How to automate shadow DOM elements using selenium? 的讨论以使用#shadow-root (open) 元素。

清除浏览数据弹出窗口中定位清除数据按钮的过程中,通过Selenium访问urlchrome://settings/clearBrowserData时出现em> 我找不到以下元素:

#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

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

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() 完美无瑕。

【问题讨论】:

我想知道,如果这是一个单独的子窗口,因为它在前台,这就是为什么无法在屏幕上找到 uielement 的原因。拿到子窗口句柄后,应该可以抓取uielement了吧? 清除数据不属于任何子窗口,chrome 已实现 Shadow DOM 作为此增强功能的一部分(不记得发生此更改时的确切版本)。检查我的答案,这将使您更有洞察力。 查看更简洁的实现here 【参考方案1】:

如果你正在尝试获取'Clear Data'元素,那么你可以使用下面的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 elements 一起使用的明确支持,因为它们不在当前 dom 中。这就是为什么我们在尝试访问shadow dom 中的元素时会得到NoSuchElementException 异常的原因。

影子 DOM:

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

解决方案:

为了使用 shadow 元素,首先我们必须找到 shadow dom 附加到的shadow host。下面是基于shadowHost获取shadow root的简单方法。

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 时,这看起来很简单。但是在这里,在这种情况下,我们有多个级别的 shadow dom。所以我们必须通过到达每个影子主机和根来访问元素。

下面是使用上面提到的方法(getShadowElement和getShadowRoot)的sn-p

// 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')");

截图:

【讨论】:

嗯。您可以尝试在 chrome 控制台中检查 js 是否正确。我能够运行 sn-p 而不会出现 eclipse 的任何错误。顺便说一句,我添加了 chrome 控制台屏幕截图以供参考。 @DebanjanB 添加了详细说明。如果您需要任何说明,请告诉我。 顺便说一句,我们还可以添加预期条件以确保在进行下一步之前存在 shadowHost/shadowRoot/shadowElement。 几个问题:在从一个#shadow-root (open) 遍历到另一个时,您使用了document.querySelector('element').shadowRoot.querySelector('element'),这没有问题。但是当从 settings-section 遍历到 settings-privacy-page 时,您并没有使用 .shadowRoot.querySelector('element'),而是使用了 .shadowRoot.querySelector('settings-section &gt; settings-privacy-page')。你能帮我解释一下原因吗? settings-privacy-page 是同一阴影树的一部分,其中settings-section 是,settings-section 是父级。让我更新截图。还更新了屏幕截图以指向右侧的第 4 个影子根。【参考方案2】:

我不得不做一个类似的测试,需要清除浏览 chrome 历史记录。一个小的区别是我在进入弹出窗口的高级部分后清除了数据。当您努力仅单击“清除数据”按钮时,我很确定您错误地错过了一两个层次结构元素。或者可能在兄弟元素和父元素之间感到困惑。根据查看您的代码,我假设您已经知道要访问特定的影子 DOM 元素,您需要适当的排序,并且上面也很好地解释了这一点。 现在解决您的问题,这是我的代码 sn-p 工作正常。代码会一直等待,直到数据被清除,然后才会继续您的下一个操作-

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

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

【讨论】:

【参考方案3】:

@supputuri 的答案中使用document.querySelector() 的Locator Strategy 通过google-chrome-devtools 完美运行

但是,当所需的元素从 shadow-dom 打开时,您需要为 elementToBeClickable() 诱导 WebDriverWait,您可以使用以下解决方案:

代码块:

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

【讨论】:

以上是关于如何在使用 cssSelector 清除 Chrome 浏览器的浏览数据时与 #shadow-root (open) 中的元素进行交互的主要内容,如果未能解决你的问题,请参考以下文章

selenium css定位元素,如何通过By.cssSelector识别元素呢

cssSelector 的 Selenium WebDriver 问题

Selenium WebDriver - 使用 cssSelector 和第 n 个子元素查找元素

用于硒的 cssSelector 与 XPath

CSS 选择器-如何定位父元素

Selenium----css selector的使用