WebDriver clic () vs JavaScript clic ()

126

L'histoire:

Ici, sur StackOverflow, j'ai vu des utilisateurs rapporter qu'ils ne peuvent pas cliquer sur un élément via la commande "clic" de sélénium WebDriver et peuvent contourner ce problème avec un clic JavaScript en exécutant un script.

Exemple en Python:

element = driver.find_element_by_id("myid")
driver.execute_script("arguments[0].click();", element)

Exemple dans WebDriverJS / Protractor:

var elm = $("#myid");
browser.executeScript("arguments[0].click();", elm.getWebElement());

La question:

Pourquoi le clic "via JavaScript" fonctionne-t-il alors qu'un clic WebDriver normal ne fonctionne pas? Quand exactement cela se produit-il et quel est l'inconvénient de cette solution de contournement (le cas échéant)?

J'ai personnellement utilisé cette solution de contournement sans comprendre pleinement pourquoi je dois le faire et quels problèmes cela peut entraîner.

alecxe
la source

Réponses:

150

Contrairement à ce que suggère la réponse actuellement acceptée , il n'y a rien de spécifique à PhantomJS en ce qui concerne la différence entre le fait que WebDriver fasse un clic et le fasse en JavaScript.

La différence

La différence essentielle entre les deux méthodes est commune à tous les navigateurs et peut être expliquée assez simplement:

  • WebDriver: lorsque WebDriver fait le clic, il essaie du mieux qu'il peut de simuler ce qui se passe lorsqu'un utilisateur réel utilise le navigateur. Supposons que vous ayez un élément A qui est un bouton qui dit "Cliquez sur moi" et un élément B qui est un divélément qui est transparent mais a ses dimensions et zIndexdéfini de manière à couvrir complètement A. Ensuite, vous dites à WebDriver de cliquer A. WebDriver va simulez le clic pour que B reçoive le clic en premier . Pourquoi? Parce que B couvre A, et si un utilisateur essayait de cliquer sur A, alors B obtiendrait l'événement en premier. Le fait que A obtienne ou non l'événement de clic dépend de la manière dont B gère l'événement. Dans tous les cas, le comportement avec WebDriver dans ce cas est le même que lorsqu'un utilisateur réel essaie de cliquer sur A.

  • JavaScript: Maintenant, supposons que vous utilisiez JavaScript pour le faire A.click(). Cette méthode de clic ne reproduit pas ce qui se passe réellement lorsque l'utilisateur essaie de cliquer A. JavaScript envoie l' clickévénement directement à A, et B n'obtiendra aucun événement.

Pourquoi un clic JavaScript fonctionne alors qu'un clic WebDriver ne fonctionne pas?

Comme je l'ai mentionné ci-dessus, WebDriver essaiera de simuler au mieux ce qui se passe lorsqu'un utilisateur réel utilise un navigateur. Le fait est que le DOM peut contenir des éléments avec lesquels un utilisateur ne peut pas interagir, et WebDriver ne vous permettra pas de cliquer sur ces éléments. Outre le cas de chevauchement que j'ai mentionné, cela implique également que les éléments invisibles ne peuvent pas être cliqués. Un cas courant que je vois dans les questions de Stack Overflow est quelqu'un qui essaie d'interagir avec un élément GUI qui existe déjà dans le DOM mais qui ne devient visible que lorsqu'un autre élément a été manipulé. Cela se produit parfois avec les menus déroulants: vous devez d'abord cliquer sur le bouton pour faire apparaître la liste déroulante avant de pouvoir sélectionner un élément de menu. Si quelqu'un essaie de cliquer sur l'élément de menu avant que le menu ne soit visible,Si la personne essaie ensuite de le faire avec JavaScript, cela fonctionnera car l'événement est livré directement à l'élément, quelle que soit la visibilité.

Quand devriez-vous utiliser JavaScript pour cliquer?

Si vous utilisez Selenium pour tester une application , ma réponse à cette question est "presque jamais". Dans l'ensemble, votre test Selenium devrait reproduire ce qu'un utilisateur ferait avec le navigateur. Prenons l'exemple du menu déroulant: un test doit d'abord cliquer sur le bouton qui fait apparaître le menu déroulant, puis cliquer sur l'élément de menu. S'il y a un problème avec l'interface graphique parce que le bouton est invisible, ou si le bouton n'affiche pas les éléments de menu, ou quelque chose de similaire, votre test échouera et vous aurez détecté le bogue. Si vous utilisez JavaScript pour cliquer, vous ne pourrez pas détecter ces bogues grâce à des tests automatisés.

Je dis «presque jamais» car il peut y avoir des exceptions où il est logique d'utiliser JavaScript. Ils devraient cependant être très rares.

Si vous utilisez Selenium pour gratter des sites , il n'est pas aussi critique d'essayer de reproduire le comportement de l'utilisateur. Donc, utiliser JavaScript pour contourner l'interface graphique est moins problématique.

Louis
la source
1
Bien meilleure réponse, celle-ci devrait être acceptée. La réponse ci-dessus parle d'un cas de bord spécifique à PhantomJS, celui-ci est beaucoup plus précis à mon humble avis.
Ardesco
@Ardesco définitivement. Marqué comme accepté. Parfait et une réponse assez détaillée.
alecxe le
Accorder la prime à Linh comme prévu, mais je vais en commencer une nouvelle pour vous remercier pour une autre réponse géniale. Merci.
alecxe
1
Réponse très bonne et compréhensible. D'après mon expérience, WebDriver a eu beaucoup de problèmes avec les pages AngularJS: avec certains éléments, les méthodes WebDriver aiment clickou sendKeysfonctionnent - mais pas toujours. Il n'y a eu aucun problème de localisation ou autre exception, le cas de test n'a tout simplement pas progressé. La journalisation a montré que l'action était exécutée. Parfois, l'utilisation a Actionaidé. Si j'ai cliqué sur l'élément manuellement (après l'arrêt du scénario de test), tout fonctionnait bien. Nous sommes donc passés au JS brut à cette occasion. Je n'ai pas travaillé avec AngularJS depuis 2 ans, donc les choses pourraient être mieux maintenant.
Würgspaß
1
@Alex Je n'ai pas de lien pratique. Ma réponse est tirée de l'expérience avec Selenium en particulier, et avec la simulation d'événements utilisateur en général. J'ai commencé à utiliser Selenium il y a 5 ans. Pendant le temps que j'ai utilisé Selenium, j'ai dû lire le code de Selenium pour résoudre certains problèmes et j'ai déposé pas mal de rapports de bogues sur la répartition des événements et discuté des bogues avec les développeurs de Selenium. "du mieux qu'il peut" est le but. J'ai certainement rencontré des bogues (maintenant corrigés) qui l'ont empêché d'atteindre cet objectif. Certains bugs peuvent subsister.
Louis
30

Le clic exécuté par le pilote tente de simuler le comportement d'un utilisateur réel aussi près que possible tandis que le JavaScript HTMLElement.click()exécute l'action par défaut pour l' clickévénement, même si l'élément n'est pas interactif.

Les différences sont:

  • Le pilote s'assure que l'élément est visible en le faisant défiler dans la vue et vérifie que l'élément est interactif .

    Le pilote lèvera une erreur:

    • lorsque l'élément en haut aux coordonnées du clic n'est pas l'élément ciblé ou un descendant
    • lorsque l'élément n'a pas une taille positive ou s'il est totalement transparent
    • lorsque l'élément est une entrée ou un bouton désactivé (l'attribut / la propriété disabledest true)
    • lorsque l'élément a le pointeur de la souris désactivé (CSS pointer-eventsest none)


    Un JavaScript HTMLElement.click()effectuera toujours l'action par défaut ou, au mieux, échouera silencieusement si l'élément est désactivé.

  • Le pilote est censé mettre l'élément au point s'il est focalisable.

    Un JavaScript HTMLElement.click()ne le fera pas.

  • Le pilote est censé émettre tous les événements (mousemove, mousedown, mouseup, click, ...) comme un vrai utilisateur.

    Un JavaScript HTMLElement.click()émet uniquement l' clickévénement. La page peut s'appuyer sur ces événements supplémentaires et peut se comporter différemment s'ils ne sont pas émis.

    Voici les événements émis par le pilote pour un clic avec Chrome:

    mouseover {target:#topic, clientX:222, clientY:343, isTrusted:true, ... }
    mousemove {target:#topic, clientX:222, clientY:343, isTrusted:true, ... }
    mousedown {target:#topic, clientX:222, clientY:343, isTrusted:true, ... }
    mouseup {target:#topic, clientX:222, clientY:343, isTrusted:true, ... }
    click {target:#topic, clientX:222, clientY:343, isTrusted:true, ... }

    Et voici l'événement émis avec une injection JavaScript:

    click {target:#topic, clientX:0, clientY:0, isTrusted:false, ... }
  • L'événement émis par un JavaScript .click() n'est pas approuvé et l'action par défaut ne peut pas être appelée:

    https://developer.mozilla.org/en/docs/Web/API/Event/isTrusted
    https://googlechrome.github.io/samples/event-istrusted/index.html

    Notez que certains des pilotes génèrent toujours des événements non approuvés. C'est le cas de PhantomJS à partir de la version 2.1.

  • L'événement émis par un JavaScript .click() n'a pas les coordonnées du clic .

    Les propriétés clientX, clientY, screenX, screenY, layerX, layerYsont définies sur 0. La page peut s'appuyer sur eux et se comporter différemment.


Il peut être acceptable d'utiliser un JavaScript .click()pour supprimer certaines données, mais ce n'est pas dans un contexte de test. Il va à l'encontre de l'objectif du test car il ne simule pas le comportement d'un utilisateur. Ainsi, si le clic du pilote échoue, un utilisateur réel échouera probablement également à effectuer le même clic dans les mêmes conditions.


Qu'est-ce qui empêche le pilote de cliquer sur un élément alors que nous nous attendons à ce qu'il réussisse?

  • L'élément ciblé n'est pas encore visible / interactif en raison d'un retard ou d'un effet de transition.

    Quelques exemples :

    https://developer.mozilla.org/fr/docs/Web (menu de navigation déroulant) http://materializecss.com/side-nav.html (barre latérale déroulante)

    Solutions de contournement:

    Ajoutez un serveur pour attendre la visibilité, une taille minimum ou une position stable:

    // wait visible
    browser.wait(ExpectedConditions.visibilityOf(elem), 5000);
    
    // wait visible and not disabled
    browser.wait(ExpectedConditions.elementToBeClickable(elem), 5000);
    
    // wait for minimum width
    browser.wait(function minimumWidth() {
        return elem.getSize().then(size => size.width > 50);
    }, 5000);

    Réessayez de cliquer jusqu'à ce que cela réussisse:

    browser.wait(function clickSuccessful() {
        return elem.click().then(() => true, (ex) => false);
    }, 5000);

    Ajoutez un délai correspondant à la durée de l'animation / transition:

    browser.sleep(250);


  • L'élément ciblé est recouvert par un élément flottant une fois défilé dans la vue:

    Le pilote fait automatiquement défiler l'élément dans la vue pour le rendre visible. Si la page contient un élément flottant / collant (menu, publicités, pied de page, notification, politique de cookies ..), l'élément peut finir couvert et ne sera plus visible / interactif.

    Exemple: https://twitter.com/?lang=en

    Solutions de contournement:

    Définissez la taille de la fenêtre sur une plus grande pour éviter le défilement ou l'élément flottant.

    Déplacez-vous sur l'élément avec un Ydécalage négatif , puis cliquez dessus:

      browser.actions()
         .mouseMove(elem, {x: 0, y: -250})
         .click()
         .perform();

    Faites défiler l'élément jusqu'au centre de la fenêtre avant le clic:

    browser.executeScript(function scrollCenter(elem) {
      var win = elem.ownerDocument.defaultView || window,
        box = elem.getBoundingClientRect(),
        dy = box.top - (win.innerHeight - box.height) / 2;
      win.scrollTo(win.pageXOffset, win.pageYOffset + dy);
    }, element);
    
    element.click();

    Cachez l'élément flottant s'il ne peut pas être évité:

    browser.executeScript(function scrollCenter(elem) {
      elem.style.display = 'none';
    }, element);
Florent B.
la source
17

REMARQUE: appelons «clic» est un clic de l'utilisateur final. 'js click' est un clic via JS

Pourquoi le clic "via JavaScript" fonctionne-t-il alors qu'un clic WebDriver normal ne fonctionne pas?

Il y a 2 cas pour que cela se produise:

I. Si vous utilisez PhamtomJS

C'est alors le comportement connu le plus courant de PhantomJS. Certains éléments ne sont parfois pas cliquables, par exemple <div>. En effet, il PhantomJSa été conçu à l'origine pour simuler le moteur des navigateurs (comme HTML + CSS initial -> calcul CSS -> rendu). Mais cela ne signifie pas être interagi avec la manière d'un utilisateur final (voir, cliquer, faire glisser). Par conséquent, PhamtomJSn'est que partiellement pris en charge avec l'interaction des utilisateurs finaux.

POURQUOI JS CLICK FONCTIONNE-T-IL? Quant à chaque clic, ils sont tous un clic moyen. C'est comme une arme à feu avec 1 canon et 2 déclencheurs . Un de la fenêtre, un de JS. Comme il est PhamtomJSexcellent pour simuler le moteur du navigateur, un clic JS devrait fonctionner parfaitement.

II. Le gestionnaire d'événements de "click" a pu se lier dans la mauvaise période.

Par exemple, nous avons un <div>

  • -> Nous faisons quelques calculs

  • -> puis nous lions l'événement de clic au <div>.

  • -> Plus avec un mauvais codage angulaire (par exemple, ne pas gérer correctement le cycle de l'oscilloscope)

Nous pouvons aboutir au même résultat. Le clic ne fonctionnera pas, car WebdriverJS essaie de cliquer sur l'élément alors qu'il n'a pas de gestionnaire d'événements de clic.

POURQUOI JS CLICK FONCTIONNE-T-IL? Js click revient à injecter js directement dans le navigateur. Possible de 2 façons,

Le premier est via la console devtools (oui, WebdriverJS communique avec la console de devtools).

La deuxième consiste à injecter une <script>balise directement dans le HTML.

Pour chaque navigateur, le comportement sera différent. Mais peu importe, ces méthodes sont plus compliquées que de cliquer sur le bouton. Click utilise ce qui est déjà là (les utilisateurs finaux cliquent), js click passe par la porte dérobée.

Et pour js, le clic apparaîtra comme une tâche asynchrone. Ceci est lié à un sujet un peu complexe de « tâche asynchrone du navigateur et planification des tâches CPU » (lisez-le il y a quelque temps, je ne trouve pas l'article à nouveau). Pour faire court, cela se traduira principalement par le fait que js click devra attendre un cycle de planification des tâches du processeur et il sera exécuté un peu plus lentement après la liaison de l'événement de clic. (Vous pourriez connaître ce cas lorsque vous avez trouvé l'élément parfois cliquable, parfois non.)

Quand exactement cela se produit-il et quel est l'inconvénient de cette solution de contournement (le cas échéant)?

=> Comme mentionné ci-dessus, les deux signifient dans un seul but, mais sur l'utilisation de quelle entrée:

  • Click: utilise ce qui fournit par défaut le navigateur.
  • Clic JS: passe par la porte dérobée.

=> Pour les performances, c'est difficile à dire car il repose sur les navigateurs. Mais généralement:

  • Cliquez: ne signifie pas plus rapide, mais seulement une position supérieure signée dans la liste de planification de la tâche d'exécution du processeur.
  • Clic JS: ne veut pas dire plus lent mais seulement il s'est connecté à la dernière position de la liste de planification de la tâche CPU.

=> Inconvénients:

  • Cliquez: ne semble pas avoir d'inconvénient sauf que vous utilisez PhamtomJS.
  • Clic JS: très mauvais pour la santé. Vous pouvez accidentellement cliquer sur quelque chose qui ne figure pas dans la vue. Lorsque vous l'utilisez, assurez-vous que l'élément est là et disponible pour être visualisé et cliquez comme point de vue de l'utilisateur final.

PS si vous cherchez une solution.

  • Vous utilisez PhantomJS? Je suggérerai plutôt d'utiliser Chrome sans tête. Oui, vous pouvez configurer Chrome sans tête sur Ubuntu. La chose fonctionne comme Chrome, mais elle n'a pas de vue et moins de buggy comme PhantomJS.
  • Vous n'utilisez pas PhamtomJS mais vous rencontrez toujours des problèmes? Je suggérerai d'utiliser la condition attendue du rapporteur avec browser.wait()( vérifiez ceci pour plus d'informations )

(Je veux faire court, mais j'ai mal fini. Tout ce qui a trait à la théorie est compliqué à expliquer ...)

Linh Pham
la source