我一直在关注讨论如何使用selenium自动化影子DOM元素?以使用
#shadow-root (open)
元素。
在通过 Selenium 访问 url chrome://settings/clearBrowserData
时出现的
清除浏览数据弹出窗口中定位清除数据按钮的过程中,我无法找到以下元素:
#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()
工作完美无缺。
如果你想获取“清除数据”元素,那么你可以使用下面的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 元素,首先我们必须找到 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 时,这看起来很简单。但在这里,在这种情况下,我们有多个级别的影子 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')");
我必须做一个类似的测试,需要清除浏览 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));
}
如果您不打算更改弹出窗口中默认选择的任何选项,它也应该在您的情况下正常工作(在这种情况下,您将必须添加一些有关选择这些复选框的代码)。请告诉我这是否解决了您的问题。希望这有帮助 我也在这里添加了屏幕快照- 图片
@supputuri 使用 document.querySelector()
的答案中的
定位器策略通过 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
尝试使用 Selenium 4.3.0 和 Chrome 版本 103.0.5060.134 识别 DOM 中的 ShadowRoot 元素时,出现 InvalidArgumentEXception
这个问题的解决办法是
SearchContext se= driver.findElment(By.locator("...").getShadowRoot();
返回类型是SearchContext
在上面的行中尝试使用定位器作为 xpath
其次尝试使用 SearchContext 引用来定位元素,例如
WebElement we= se.findElement(By.locator("....."));
use locater as cssSelector
它就像魅力一样起作用
没有找到可用的解决方案,花了我半天时间才弄清楚 希望这有帮助!!!
Selenium 4 使用 CSS 选择器提供与 Shadow DOM 交互的本机支持。因此,针对OP问题的Java解决方案将是这样的:
WebDriver driver = new ChromeDriver();
driver.get("chrome://settings/clearBrowserData");
TimeUnit.SECONDS.sleep(1); // Wait on elements to load, avoid it in prod code.
// Now you need to select shadow root's parent and call .getShadowRoot().
driver.findElement(By.cssSelector("settings-ui")).getShadowRoot()
.findElement(By.cssSelector("settings-main")).getShadowRoot()
.findElement(By.cssSelector("settings-basic-page")).getShadowRoot()
.findElement(By.cssSelector("settings-privacy-page")).getShadowRoot()
.findElement(By.cssSelector("settings-clear-browsing-data-dialog")).getShadowRoot()
.findElement(By.cssSelector("#clearBrowsingDataConfirm"));
Python 解决方案类似于
driver = webdriver.Chrome()
driver.get("chrome://settings/clearBrowserData")
time.sleep(1) # Wait on elements to load, avoid it in prod code.
# Now you need to select the parent of shadow root and call it's .shadow_root property.
driver.find_element(By.CSS_SELECTOR, "settings-ui").shadow_root\
.find_element(By.CSS_SELECTOR, "settings-main").shadow_root\
.find_element(By.CSS_SELECTOR, "settings-basic-page").shadow_root\
.find_element(By.CSS_SELECTOR, "settings-privacy-page").shadow_root\
.find_element(By.CSS_SELECTOR, "settings-clear-browsing-data-dialog")\
.shadow_root.find_element(By.CSS_SELECTOR, "#clearBrowsingDataConfirm")
这里要记住的一件事是影子根元素内部仅支持 CSS 选择器。所以不可能使用 xpath 或 id 。