Cómo interactuar con los elementos dentro de #shadow-root (abrir) mientras se borran los datos de navegación del navegador Chrome usando cssSelector
Había estado siguiendo la discusión ¿Cómo automatizar elementos DOM ocultos usando selenio? para trabajar con #shadow-root (open)
elementos.
Mientras estoy en el proceso de ubicar el Clear databotón dentro de la ventana emergente Borrar datos de navegación , que aparece al acceder a la URL chrome://settings/clearBrowserData
a través de Selenium , no puedo ubicar el siguiente elemento:
#shadow-root (open)
<settings-privacy-page>
Instantánea:
Usando Selenium, las siguientes son mis pruebas de código y los errores asociados encontrados:
Intento 1:
WebElement root5 = shadow_root4.findElement(By.tagName("settings-privacy-page"));
Error:
Exception in thread "main" org.openqa.selenium.JavascriptException: javascript error: b.getElementsByTagName is not a function
Intento 2:
WebElement root5 = shadow_root4.findElement(By.cssSelector("settings-privacy-page"));
Error:
Exception in thread "main" org.openqa.selenium.NoSuchElementException: no such element: Unable to locate element: {"method":"css selector","selector":"settings-privacy-page"}
Intento 3:
WebElement root5 = (WebElement)((JavascriptExecutor)shadow_root4).executeScript("return document.getElementsByTagName('settings-privacy-page')[0]");
Error:
Exception in thread "main" java.lang.ClassCastException: org.openqa.selenium.remote.RemoteWebElement cannot be cast to org.openqa.selenium.JavascriptExecutor
En caso de que sea útil, el bloque de código inicial (hasta la línea anterior) funciona perfecto:
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);
PD: expand_shadow_element()
funciona perfecto.
Si está intentando obtener el elemento 'Borrar datos', puede usar el siguiente js para obtener el elemento y luego ejecutarlo.
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')
Aquí está el guión de muestra.
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();
Edición 2: explicación
Problema: Selenium no proporciona soporte explícito para trabajar con elementos Shadow DOM , ya que no están en el dom actual. Esa es la razón por la que obtendremos NoSuchElementException
una excepción cuando intentemos acceder a los elementos del archivo shadow dom
.
DOM en la sombra:
Nota: Nos referiremos a los términos que se muestran en la imagen. Así que revise la imagen para comprenderla mejor.
Solución:
Para poder trabajar shadow elementprimero tenemos que encontrar el objeto shadow host
al que está unido el dom de sombra. Este es el método simple para obtener la raíz de la sombra basada en ShadowHost.
private static WebElement getShadowRoot(WebDriver driver,WebElement shadowHost) {
JavascriptExecutor js = (JavascriptExecutor) driver;
return (WebElement) js.executeScript("return arguments[0].shadowRoot", shadowHost);
}
Y luego puede acceder al elemento del árbol de sombra utilizando el elemento 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"));
Para simplificar todos los pasos anteriores, se creó el siguiente método.
public static WebElement getShadowElement(WebDriver driver,WebElement shadowHost, String cssOfShadowElement) {
WebElement shardowRoot = getShadowRoot(driver, shadowHost);
return shardowRoot.findElement(By.cssSelector(cssOfShadowElement));
}
Ahora puede obtener el elemento ShadowTree con una llamada a un solo método.
WebElement shadowHost = driver.findElement(By.cssSelector("shadowHost_CSS_Goes_here));
WebElement shadowTreeElement = getShadowElement(driver,shadowHost,"shadow_tree_element_css");
Y realice las operaciones como de costumbre como .click()
,..getText()
shadowTreeElement.click()
Esto parece simple cuando solo tienes un nivel de DOM oculto. Pero aquí, en este caso, tenemos múltiples niveles de sombras. Entonces tenemos que acceder al elemento llegando a cada host oculto y raíz.
A continuación se muestra el fragmento que utiliza los métodos mencionados anteriormente (getShadowElement y 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();
Puede realizar todos los pasos anteriores en una sola llamada js como se menciona al comienzo de la respuesta (agregado a continuación solo para reducir la confusión).
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')");
Captura de pantalla:
Tuve que hacer una prueba similar que requería borrar el historial de navegación de Chrome. Una pequeña diferencia fue que estaba borrando los datos después de ir a la sección avanzada de la ventana emergente. Como tiene dificultades para hacer clic solo en el botón "Borrar datos", estoy bastante seguro de que se ha perdido uno o dos elementos de la jerarquía por error. O probablemente me confundí entre los elementos de hermanos y padres. Al ver su código, supongo que ya sabe que para acceder a un elemento DOM oculto en particular necesita una secuencia adecuada y también se ha explicado bastante bien anteriormente.
Ahora abordando su problema, aquí está mi fragmento de código que funciona correctamente. El código espera hasta que se limpien los datos y luego procederá a la siguiente acción.
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));
}
También debería funcionar correctamente en su caso si no tiene intención de cambiar ninguna de las opciones seleccionadas de forma predeterminada en la ventana emergente (en ese caso, tendrá que agregar algunos códigos más para seleccionar esas casillas de verificación). Por favor dígame si esto resuelve su problema. Espero que esto sea útil. Agregué una instantánea de la pantalla aquí también .