l'histoire:
Ici, sur StackOverflow, j'ai vu des utilisateurs signaler qu'ils ne pouvaient pas cliquer sur un élément via la commande "click" de Selenium WebDriver et qu'ils pouvaient le contourner 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 cliquer sur "via JavaScript" fonctionne-t-il alors qu'un clic WebDriver classique ne fonctionne pas? Quand cela se produit-il exactement et quels sont les inconvénients de cette solution de contournement (le cas échéant)?
J'ai personnellement utilisé cette solution de contournement sans bien comprendre pourquoi je dois le faire et à quels problèmes cela peut conduire.
Contrairement à ce que la réponse actuellement acceptée suggère, il n'y a rien de spécifique à PhantomJS en ce qui concerne la différence entre WebDriver faire un clic et le faire en JavaScript.
La différence essentielle entre les deux méthodes est commune à tous les navigateurs et peut s’expliquer simplement:
WebDriver: Lorsque WebDriver enfonce le clic, il tente autant que possible de simuler ce qui se produit lorsqu'un utilisateur réel utilise le navigateur. Supposons que vous disposiez d'un élément A qui est un bouton qui dit "Cliquez sur moi" et un élément B qui est un élément div
qui est transparent mais dont les dimensions et zIndex
est défini de manière à recouvrir complètement A. WebDriver à cliquer sur A. WebDriver simule le clic pour que B le reçoive premier. Pourquoi? Parce que B couvre A, et si un utilisateur essayait de cliquer sur A, B obtiendrait l'événement en premier. Le fait que A obtienne ou non l'événement click dépend de la manière dont B gère l'événement. Quoi qu'il en soit, le comportement avec WebDriver dans ce cas est le même que lorsqu'un utilisateur réel essaie de cliquer sur A.
JavaScript: supposons maintenant que vous utilisiez JavaScript pour faire A.click()
. Cette méthode de clic ne reproduit pas ce qui se passe réellement lorsque l'utilisateur essaie de cliquer sur A. JavaScript envoie l'événement click
directement à A, et B ne recevra aucun événement.
Comme je l'ai mentionné ci-dessus, WebDriver essaiera de simuler au mieux ce qui se passe lorsqu'un utilisateur 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 permet pas de cliquer sur ces éléments. Outre le chevauchement de cas 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 débordement de pile est celui qui essaie d'interagir avec un élément d'interface graphique déjà présent dans le DOM mais qui devient visible uniquement 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 afficher le menu déroulant avant qu'un élément de menu puisse être sélectionné. Si quelqu'un essaie de cliquer sur l'élément de menu avant que le menu ne soit visible, WebDriver hésitera et dira que l'élément ne peut pas être manipulé. Si la personne tente ensuite de le faire avec JavaScript, cela fonctionnera car l'événement est livré directement à l'élément, quelle que soit sa visibilité.
Si vous utilisez Selenium pour tester une application, ma réponse à cette question est "presque jamais". Par et De grande taille, votre test Selenium doit reproduire ce que ferait un utilisateur avec le navigateur. Prenons l'exemple du menu déroulant: un test doit cliquer sur le bouton qui fait apparaître le menu déroulant en premier, 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 ne permet pas d'afficher 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 être très rares, cependant.
Si vous utilisez Selenium pour supprimer des sites, il est alors moins important de tenter de reproduire le comportement de l'utilisateur. Donc, utiliser JavaScript pour contourner l’interface graphique est moins un problème.
Le clic exécuté par le pilote tente de simuler le plus fidèlement possible le comportement d'un utilisateur réel pendant que JavaScript HTMLElement.click()
exécute l'action par défaut pour l'événement click
, 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 interactable .
Le pilote va générer une erreur:
disabled
est true
)pointer-events
est none
)
Un JavaScript HTMLElement.click()
effectuera toujours l'action par défaut ou échouera au mieux de manière silencieuse si l'élément est désactivé.
On s'attend à ce que le pilote active l'élément s'il est activé.
Un JavaScript HTMLElement.click()
ne sera pas.
Le pilote doit émettre tous les événements (mousemove, mousedown, mouseup, click, ...) exactement comme un vrai utilisateur.
Un JavaScript HTMLElement.click()
n'émet que l'événement click
. La page peut s'appuyer sur ces événements supplémentaires et 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 invoquée:
https://developer.mozilla.org/en/docs/Web/API/Event/isTrusted
https://googlechrome.github.io/samples/event-istrusted/index.html
Notez que certains pilotes génèrent toujours des événements non approuvés. C'est le cas avec 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, layerY
Sont définies sur 0
. La page peut compter sur eux et se comporter différemment.
Il est peut-être correct d'utiliser un JavaScript .click()
pour supprimer certaines données, mais ce n'est pas dans un contexte de test. Cela va à l'encontre de l'objectif du test, car il ne simule pas le comportement d'un utilisateur. Par conséquent, si le clic du pilote échoue, un utilisateur réel ne pourra probablement pas exécuter le même clic dans les mêmes conditions.
Qu'est-ce qui empêche le pilote de cliquer sur un élément quand on s'attend à ce qu'il réussisse?
L'élément ciblé n'est pas encore visible/interactable 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 (côté déroulant bar)
Contournements:
Ajoutez un serveur pour attendre la visibilité, une taille minimale 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 l'opération 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é finit par être 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, annonces, pied de page, notification, règle relative aux cookies, etc.), l'élément peut être recouvert et ne sera plus visible/interactable.
Exemple: https://Twitter.com/?lang=fr
Solutions de contournement:
Définissez la taille de la fenêtre sur une taille supérieure pour éviter le défilement ou l'élément flottant.
Déplacez le curseur sur l'élément avec un offset négatif Y
puis cliquez dessus:
browser.actions()
.mouseMove(elem, {x: 0, y: -250})
.click()
.perform();
Faites défiler l'élément 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 être évité:
browser.executeScript(function scrollCenter(elem) {
elem.style.display = 'none';
}, element);
NOTE: appelons 'clic' est le clic de l'utilisateur final. 'js click' est un clic via JS
Pourquoi cliquer sur "via JavaScript" fonctionne-t-il quand un clic WebDriver classique ne fonctionne pas?
Il y a 2 cas pour que cela se produise:
Il s’agit alors du comportement le plus connu de PhantomJS
. Certains éléments ne sont parfois pas cliquables, par exemple <div>
. Ceci est dû au fait que PhantomJS
a été conçu à l'origine pour simuler le moteur des navigateurs (comme HTML + CSS initial -> calcul CSS -> rendu). Mais cela ne signifie pas qu'il faille interagir avec les utilisateurs finaux (visualiser, cliquer, faire glisser). Par conséquent, PhamtomJS
n'est que partiellement pris en charge avec l'interaction des utilisateurs finaux.
POURQUOI JS CLICK FONCTIONNE-T-IL? En ce qui concerne l'un ou l'autre clic, ils sont tous un clic moyen. C'est comme une arme à feu avec 1 baril et 2 déclencheurs . Un de la fenêtre, un de JS. Puisque PhamtomJS
est excellent pour la simulation du moteur de navigateur, un clic JS devrait fonctionner parfaitement.
Par exemple, nous avons un <div>
-> Nous faisons des calculs
-> puis nous lions l’événement de clic au <div>
.
-> Plus avec un mauvais codage de angular (par exemple, ne pas gérer correctement le cycle de l'oscilloscope))
Nous pouvons nous retrouver avec le même résultat. Click ne fonctionnera pas, car WebdriverJS essaie de cliquer sur l'élément alors qu'il ne possède pas de gestionnaire d'événements click.
POURQUOI JS CLICK FONCTIONNE-T-IL? Js click équivaut à une injection de js directement dans le navigateur. Possible avec 2 voies,
Fist passe par la console devtools (oui, WebdriverJS communique avec la console de devtools).
La seconde consiste à injecter une balise <script>
Directement dans 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 existe déjà (clic des utilisateurs finaux), js click passe par la porte dérobée.
Et pour js click apparaîtra comme une tâche asynchrone. Ceci est lié à un sujet assez complexe de ' navigateur asynchrone et ordonnancement des tâches de la CPU ' (le lire il y a quelque temps ne trouve pas l'article à nouveau ). Pour résumer, cela résultera principalement du fait que js click devra attendre un cycle de planification de la tâche de la CPU et sera exécuté un peu plus lentement après la liaison. de l'événement click. [Vous pourriez connaître ce cas lorsque vous avez trouvé l'élément parfois cliquable, parfois non.)
Quand cela se produit-il exactement et quels sont les inconvénients 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:
=> Pour les performances, difficile à dire car il s’appuie sur les navigateurs. Mais généralement:
=> Inconvénients:
browser.wait()
( , cochez cette option pour plus d'informations )(Je veux être bref, mais j'ai mal fini. Tout ce qui a trait à la théorie est compliqué à expliquer ...)