python selenium selenium-webdriver shadow-dom

python - Cómo manejar elementos dentro de Shadow DOM desde Selenium



selenium-webdriver shadow-dom (3)

A veces, los elementos raíz de sombra están anidados y la segunda raíz sombra no es visible en la raíz del documento, pero está disponible en su raíz sombra accedida primaria. Creo que es mejor usar los selectores de selenio e inyectar el script solo para sacar la raíz de la sombra:

def expand_shadow_element(element): shadow_root = driver.execute_script(''return arguments[0].shadowRoot'', element) return shadow_root outer = expand_shadow_element(driver.find_element_by_css_selector("#test_button")) inner = outer.find_element_by_id("inner_button") inner.click()

Para poner esto en perspectiva, acabo de agregar un ejemplo comprobable con la página de descarga de Chrome, al hacer clic en el botón de búsqueda debe abrir 3 elementos raíz anidados de sombra:

import selenium from selenium import webdriver driver = webdriver.Chrome() def expand_shadow_element(element): shadow_root = driver.execute_script(''return arguments[0].shadowRoot'', element) return shadow_root driver.get("chrome://downloads") root1 = driver.find_element_by_tag_name(''downloads-manager'') shadow_root1 = expand_shadow_element(root1) root2 = shadow_root1.find_element_by_css_selector(''downloads-toolbar'') shadow_root2 = expand_shadow_element(root2) root3 = shadow_root2.find_element_by_css_selector(''cr-search-field'') shadow_root3 = expand_shadow_element(root3) search_button = shadow_root3.find_element_by_css_selector("#search-button") search_button.click()

Hacer el mismo enfoque sugerido en las otras respuestas tiene el inconveniente de que codifica las consultas, es menos legible y no puede usar las selecciones intermedias para otras acciones:

search_button = driver.execute_script(''return document.querySelector("downloads-manager").shadowRoot.querySelector("downloads-toolbar").shadowRoot.querySelector("cr-search-field").shadowRoot.querySelector("#search-button")'') search_button.click()

editar más tarde:

Recientemente intenté acceder a la configuración de contenido (ver el código a continuación) y tiene más de un elemento raíz oculto imbricado ahora no puedes acceder a uno sin primero expandir el otro, cuando generalmente también tienes contenido dinámico y más de 3 elementos sombreados uno dentro del otro hace imposible la automatización. La respuesta anterior solía funcionar hace un tiempo, pero es suficiente para que solo un elemento cambie de posición y siempre debe ir con el elemento de inspección y levantar el árbol y ver si está en una raíz de sombra, una pesadilla de automatización.

No solo fue difícil encontrar solo la configuración de contenido debido a las raíces oscuras y el cambio dinámico cuando descubrimos que no se puede hacer clic en el botón en este momento.

driver = webdriver.Chrome() def expand_shadow_element(element): shadow_root = driver.execute_script(''return arguments[0].shadowRoot'', element) return shadow_root driver.get("chrome://settings") root1 = driver.find_element_by_tag_name(''settings-ui'') shadow_root1 = expand_shadow_element(root1) root2 = shadow_root1.find_element_by_css_selector(''[page-name="Settings"]'') shadow_root2 = expand_shadow_element(root2) root3 = shadow_root2.find_element_by_id(''search'') shadow_root3 = expand_shadow_element(root3) search_button = shadow_root3.find_element_by_id("searchTerm") search_button.click() text_area = shadow_root3.find_element_by_id(''searchInput'') text_area.send_keys("content settings") root0 = shadow_root1.find_element_by_id(''main'') shadow_root0_s = expand_shadow_element(root0) root1_p = shadow_root0_s.find_element_by_css_selector(''settings-basic-page'') shadow_root1_p = expand_shadow_element(root1_p) root1_s = shadow_root1_p.find_element_by_css_selector(''settings-privacy-page'') shadow_root1_s = expand_shadow_element(root1_s) content_settings_div = shadow_root1_s.find_element_by_css_selector(''#site-settings-subpage-trigger'') content_settings = content_settings_div.find_element_by_css_selector("button") content_settings.click()

Quiero automatizar la comprobación de finalización de descarga de archivos en chromedriver . HTML de cada entrada en la lista de descargas se ve así

<a is="action-link" id="file-link" tabindex="0" role="link" href="http://fileSource" class="">DownloadedFile#1</a>

Entonces uso el siguiente código para encontrar elementos de destino:

driver.get(''chrome://downloads/'') # This page should be available for everyone who use Chrome browser driver.find_elements_by_tag_name(''a'')

Esto devuelve una lista vacía mientras hay 3 nuevas descargas.

Como descubrí, solo se pueden manejar los elementos principales de la etiqueta #shadow-root (open) . Entonces, ¿cómo puedo encontrar elementos dentro de este elemento #shadow-root ?


Añadiría esto como un comentario, pero no tengo suficientes puntos de reputación.

Las respuestas de Eduard Florinescu funcionan bien con la advertencia de que una vez que esté dentro de un shadowRoot, solo tiene los métodos de selenio disponibles que corresponden a los métodos JS disponibles, principalmente seleccionados por id.

Para evitar esto, escribí una función JS más larga en una cadena de Python y utilicé métodos y atributos JS nativos (buscar por id, hijos + indexación, etc.) para obtener el elemento que finalmente necesitaba.

Puede usar este método para acceder también a shadowRoots de elementos secundarios, etc., cuando la cadena JS se ejecuta utilizando driver.execute_script ()


Puede usar el método driver.executeScript() para acceder a los elementos HTML y objetos JavaScript en su página web.

En el ejemplo a continuación, executeScript devolverá en una lista Promise the Node de todos los elementos <a> presentes en el árbol Shadow del elemento cuyo id es el host . Entonces puedes realizar tu prueba de afirmación:

it( ''check shadow root content'', function () { return driver.executeScript( function () { return host.shadowRoot.querySelectorAll( ''a'' ).then( function ( n ) { return expect( n ).to.have.length( 3 ) } } ) } )

Nota: no conozco Python, así que he usado la sintaxis de JavaScript, pero debería funcionar de la misma manera.