Accès à Shadow DOM tree avec du sélénium

est-il possible d'accéder à des éléments dans un DOM Shadow en utilisant le webdriver Selenium/Chrome?

utiliser les méthodes normales de recherche d'éléments ne fonctionne pas, comme il faut s'y attendre. J'ai vu des références à la switchToSubTree spec sur le w3c, mais ne pouvait pas localiser n'importe quel réel docs, des exemples, etc.

Quelqu'un a eu du succès avec ça?

14
demandé sur Eduard Florinescu 2014-05-28 23:22:12

7 réponses

malheureusement, il semble que la spécification webdriver ne supporte pas encore cela.

Mon espionnage découvert :

http://www.w3.org/TR/webdriver/#switching-to-hosted-shadow-doms

https://groups.google.com/forum/#!msg/selenium-developers/Dad2KZsXNKo/YXH0e6eSHdAJ

4
répondu Damen TheSifter 2014-08-06 01:55:08

accepté La réponse n'est plus valide, et quelques autres réponses ont quelques inconvénients ou ne sont pas pratiques (/deep/ sélecteur ne fonctionne pas et est obsolète, document.querySelector('').shadowRoot ne fonctionne qu'avec le premier élément shadow lorsque les éléments shadow sont imbriqués), parfois les éléments shadow root sont imbriqués et la seconde racine shadow n'est pas visible dans la racine du document, mais est disponible dans la racine shadow accédée par son parent. Je pense qu'il est préférable d'utiliser les sélecteurs de sélénium et d'injecter le script juste pour prendre l'ombre de la racine:

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

pour mettre cela en perspective je viens d'ajouter un exemple testable avec la page de téléchargement de Chrome, en cliquant sur le bouton de recherche doit ouvrir 3 éléments racine ombre imbriquée: enter image description here

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

Faire la même approche suggérée dans les autres réponses a l'inconvénient d'être difficile-codes des requêtes, est moins lisible et vous ne pouvez pas utiliser l'intermédiaire des sélections pour les autres actions:

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()
7
répondu Eduard Florinescu 2017-10-18 20:50:54

il faut aussi noter que le pilote de Chrome binaire de sélénium supporte maintenant le DOM Shadow (depuis Jan 28, 2015) : http://chromedriver.storage.googleapis.com/2.14/notes.txt

5
répondu djangofan 2015-02-27 16:22:04

j'utilise C# et Selenium et j'ai réussi à trouver un élément à l'intérieur d'un shadow DOM niché en utilisant le script java. Voici mon arbre html:

arbre html

je veux l'url sur la dernière ligne et pour l'obtenir, je sélectionne d'abord la balise "downloads-manager", puis la première racine de shadow juste en dessous. Une fois dans la racine de l'ombre, je veux trouver l'élément le plus proche de la racine de l'ombre suivante. Cet élément est "téléchargements-élément". Avec ce sélectionné je peux entrer le deuxième l'ombre de la racine. À partir de là, je sélectionne l'élément img contenant l'url par id = "fichier d'icône". Enfin je peux obtenir l'attribut " src " qui contient l'url que je cherche.

Les deux lignes de code C# qui fait le truc:

IJavaScriptExecutor jse2 = (IJavaScriptExecutor)_driver;
var pdfUrl = jse2.ExecuteScript("return document.querySelector('downloads-manager').shadowRoot.querySelector('downloads-item').shadowRoot.getElementById('file-icon').getAttribute('src')");
3
répondu Erik Selin 2017-03-30 07:13:29

normalement vous feriez ceci:

element = webdriver.find_element_by_css_selector(
    'my-custom-element /deep/ .this-is-inside-my-custom-element')

Et j'espère que ça va continuer à travailler.


Toutefois, notez que /deep/ et ::shadow deprecated (et non implémenté dans les navigateurs autres que Opera et Chrome). On parle beaucoup de les autoriser dans le profil statique. Sens, la recherche de leur travail, mais pas de style.

Si vous ne voulez pas à s'appuyer sur /deep/ ou ::shadow parce que leur avenir est un peu incertain, ou parce que vous voulez travailler mieux cross-browser ou parce que vous détestez les avertissements de dépréciation, réjouissez-vous car il y a une autre façon:

# Get the shadowRoot of the element you want to intrude in on,
# and then use that as your root selector.
shadow_root = webdriver.execute_script('''
    return document.querySelector(
        'my-custom-element').shadowRoot;
    ''')
element = shadow_root.find_element_by_css_selector(
    '.this-is-inside-my-custom-element')

Plus à ce sujet:

2
répondu odinho - Velmont 2016-01-06 13:53:08

j'ai trouvé un moyen beaucoup plus facile d'obtenir les éléments de Shadow Dom. Je prends l'exemple donné ci-dessus, pour icône de rechercheChrome Page De Téléchargement.

IWebDriver driver;

public IWebElement getUIObject(params By[] shadowRoots)
        {
            IWebElement currentElement = null;
            IWebElement parentElement = null;
            int i = 0;
            foreach (var item in shadowRoots)
            {
                if (parentElement == null)
                {
                    currentElement = driver.FindElement(item);
                }
                else
                {
                    currentElement = parentElement.FindElement(item);
                }
                if(i !=(shadowRoots.Length-1))
                {
                    parentElement = expandRootElement(currentElement);
                }
                i++;
            }
            return currentElement;
        }

 public IWebElement expandRootElement(IWebElement element)
        {
            IWebElement rootElement = (IWebElement)((IJavaScriptExecutor)driver)
        .ExecuteScript("return arguments[0].shadowRoot", element);
            return rootElement;
        }

Page De Téléchargement De Google Chrome

maintenant, comme indiqué dans l'image, nous devons étendre trois éléments de racine de l'ombre afin d'obtenir notre icône de recherche. De cliquer sur l'icône tout ce que nous devons faire est de :-

  [TestMethod]
        public void test()
        {
           IWebElement searchButton= getUIObject(By.CssSelector("downloads-manager"),By.CssSelector("downloads-toolbar"),By.Id("search-input"),By.Id("search-buton"));
            searchButton.Click();
        }

donc une seule ligne vous donnera votre élément Web, juste besoin de s'assurer que vous transmettez ombre premier élément racine comme premier argument de la fonction "getUIObject" ombre deuxième élément racine comme deuxième argument de la fonction, et ainsi de suite, enfin, dernier argument pour la fonction de l'identificateur de votre réelle de l'élément (pour ce cas son 'recherche-bouton')

2
répondu Akash Jha 2017-12-28 18:35:08

cela a fonctionné pour moi (en utilisant des fixations javascript de sélénium):

driver.executeScript("return $('body /deep/ <#selector>')")

renvoie le ou les éléments que vous recherchez.

1
répondu jeremysklarsky 2015-06-14 03:13:05