Meilleurs conseils pour les experts en sélénium
Par Tony Yoshicedo
5 novembre 2019
8 min lire
Une fois que vous avez utilisé Selenium pendant un certain temps et que vous êtes à l'aise pour rédiger des cas de test, vous pouvez vous concentrer sur les techniques et les principes de conception pour faire passer l'automatisation des tests d'interface utilisateur au niveau supérieur.
Avant que nous commencions
Cet article suppose que vous avez utilisé Selenium et que vous êtes à l'aise pour écrire des cas de test. Vous avez déjà testé l'inspection des DOM pour créer vos propres XPath. Peut-être que vous utilisez le modèle d'objet de page. À présent, vous êtes probablement assez doué pour rechercher des solutions sur Internet. (Si vous voulez de l'aide dans ce département, je recommande vivement cet article de mon collègue avec quelques bons hacks Selenium.)
Ce qui suit est décomposé en techniques et modèles de conception. Vous savez peut-être déjà tout sur certains d'entre eux. C'est bien, passez simplement aux sections qui vous intéressent. J'utiliserai Java ici, mais les techniques et pratiques devraient être généralement applicables.
Techniques
Meilleurs localisateurs
De mauvais localisateurs provoquent des échecs de test parasites. Ils perdent du temps et détournent l'attention de la fonction prévue du test. Les mauvais localisateurs reposent généralement sur une qualité instable de la page Web, qu'il s'agisse d'une valeur dynamique ou de la position de l'élément sur la page. Lorsque ces qualités changent invariablement, le localisateur se brise. Les bons localisateurs fonctionnent. Ils identifient correctement l'élément visé et permettent au test de faire son travail.
La clé ici est d'identifier les qualités de l'élément qui sont stables, puis de choisir le sous-ensemble minimum de ces qualités qui identifient de manière unique cet élément pour réduire votre exposition au changement. Nous savons tous que les XPath absolus sont mauvais, mais il est bon de comprendre exactement pourquoi.
/ html / body / div [25] / div [2] / div / span / span [2] / div / h2 / p
Prenez une sous-section de ce XPath: div [25] / div [2] . Cela représente les qualités suivantes:
- Le nœud est quelque part dans un div
- Ce div est le deuxième div
- Cette div est directement dans une autre div
- Cette div est la 25e div
Dans cette petite sous-section, nous avons au moins 4 qualités utilisées pour identifier un élément. Si l'un d'entre eux change, notre localisateur s'arrête. Nous n'avons aucune raison de croire qu'ils ne changeront pas car aucun d'entre eux ne décrit réellement l'élément. En regardant ce XPath, nous ne pouvions même pas deviner le but de l'élément.
Les localisateurs qui reflètent le but de l'élément sont moins susceptibles de se briser. Bien que la position ou l'apparence de l'élément puisse changer, sa fonction ne devrait pas l'être. Idéalement, vous pouvez deviner ce que fait l'élément en regardant son localisateur.
// p [contient (@class, "contenu")] [contient (., "Conseils pour réussir")]
Avec ce qui précède, il est clair que l'élément représente le contenu qui décrit «Conseils pour réussir». Notez que l'attribut class, bien qu'habituellement utilisé pour contrôler l'apparence, décrit également la fonction de l'élément.
Souvent, les qualités uniques et stables d'un élément ne se trouvent pas dans l'élément lui-même. Au lieu de cela, vous devrez vous rendre à un parent de l'élément dont la fonction est bien décrite dans le DOM. Prenons l'exemple ci-dessus dans l'élément «Guidance for Success». Bien que ce localisateur soit bon, la copie de texte peut souvent changer ou ne pas être une option si le site prend en charge plusieurs langues. En recherchant dans le DOM, nous pourrions trouver que le div parent a un identifiant descriptif. Dans ce cas, nous pourrions préférer un localisateur comme:
// div [@ id = ”success_guide”] // p
Attentes explicites
La tentation ici est de définir une attente implicite et l'espoir qui réglera la plupart des problèmes. Le problème est que cette approche large traite toutes les situations de la même manière sans même aborder la plupart des problèmes liés à l'attente. Que faire si l'élément devient présent après quelques secondes, mais n'est pas encore prêt à être cliqué? Que faire si l'élément est présent, mais masqué par une superposition? La solution est d'utiliser des attentes explicites.
Les conditions d'attente explicites prennent la forme de:
WebDriverWait wait = new WebDriverWait (webdriver, timeOutInSeconds) wait.until (/ * une condition est vraie * /)
Les attentes explicites sont puissantes car elles sont descriptives. Ils vous permettent de préciser les conditions nécessaires pour continuer. Un exemple courant de ceci est lorsque le test doit cliquer sur un élément. Il ne suffit pas que l'élément soit simplement présent; il doit être visible et activé. Les attentes explicites vous permettent de décrire ces exigences directement dans le test. Nous pouvons être sûrs que les conditions ont été remplies avant que le test tente de se poursuivre, ce qui rend vos tests plus stables:
WebDriverWait wait = new WebDriverWait (webdriver, timeOutInSeconds) wait.until (ExpectedConditions.elementToBeClickable (élément))
Les attentes explicites descriptives vous permettent également d'écrire des tests qui se concentrent sur les aspects comportementaux de l'application. Étant donné que le comportement de l'application ne change pas souvent, cela vous permet de créer des tests plus stables. Étant donné l'exemple ci-dessus, il est nécessaire qu'un élément soit visible et activé pour être cliqué, mais il doit également ne pas être masqué par un autre élément. S'il y a une grande superposition de «chargement» couvrant votre élément, le clic échouera. Nous pouvons créer une attente explicite qui décrit ce comportement de chargement et attend que les conditions nécessaires soient remplies:
WebDriverWait wait = new WebDriverWait (webdriver, timeOutInSeconds) wait.until (ExpectedConditions.invisibilityOf (loadingOverlay))
Conditions attendues
Vous avez peut-être remarqué le Conditions attendues méthodes utilitaires utilisées dans les exemples ci-dessus. Cette classe contient un grand nombre de conditions utiles à utiliser lors de l'écriture de vos tests Selenium. Si vous ne l'avez pas déjà fait, cela vaut la peine de prendre un moment pour passer en revue l'API complète. Vous utiliserez couramment ExpectedConditions.elementToBeClickable (..) ou ExpectedConditions.invisibilityOf (..), mais vous pouvez également trouver des utilisations pour alertIsPresent (), jsReturnsValue (…) ou titleContains (..). Vous pouvez même enchaîner les conditions en utilisant ExpectedConditions.et (..) ou ExpectedConditions.ou (..).
Exécution de JavaScript
Les WebDrivers offrent la possibilité d'exécuter JavaScript dans le contexte du navigateur. C'est une fonctionnalité simple avec une polyvalence incroyable. Cela peut être utilisé pour des tâches courantes, telles que forcer une page à faire défiler jusqu'à un élément, comme avec:
driver.executeScript ("arguments [0] .scrollIntoView (false)", élément)
Il peut également être utilisé pour exploiter les bibliothèques JavaScript utilisées dans une application telle que JQuery ou React. Par exemple, vous pouvez vérifier si le texte dans un éditeur enrichi a été modifié en appelant:
driver.executeScript ("return EDITOR.instances.editor.checkDirty ()")
La fonction executeScript ouvre toute l'API de la bibliothèque à votre test. Ces API fournissent souvent des informations utiles sur l'état de l'application qui serait autrement impossible ou instable à interroger à l'aide de WebElements.
L'utilisation des API de bibliothèque associe votre test à une implémentation de bibliothèque. Les bibliothèques peuvent souvent être permutées pendant le développement d'une application, il faut donc faire preuve de prudence lors de l'utilisation de executeScript de cette façon. Vous devriez envisager d'abstraire ces appels spécifiques à la bibliothèque derrière une interface plus abstraite pour aider à réduire l'exposition de vos tests à l'instabilité, comme avec le modèle Bot (voir ci-dessous).
Design
Le modèle de bot
Les modèle de bot résume les appels de l'API Selenium en actions. Les actions peuvent ensuite être utilisées tout au long de vos tests pour les rendre plus lisibles et concis.
Nous avons déjà vu quelques exemples où cela serait utile dans cet article. Puisqu'il est nécessaire qu'un élément soit cliquable avant d'essayer un clic, nous pouvons toujours vouloir attendre que l'élément soit cliquable avant chaque clic:
void test () {/ * code de test * / WebDriverWait wait = new WebDriverWait (pilote, 5); wait.until (ExpectedConditions.elementToBeClickable (élément)); element.click (); wait.until (ExpectedConditions.elementToBeClickable (élément2)); element2.click (); }
Plutôt que d'écrire la condition d'attente chaque fois que le test clique sur un élément, le code peut être résumé dans sa propre méthode:
Public class Bot {public void waitAndClick (élément WebElement, long timeout) {WebDriverWait wait = new WebDriverWait (driver, timeout); wait.until (ExpectedConditions.elementToBeClickable (élément)); element.click (); }}
Alors notre code devient:
void test () {/ * test code * / bot.waitAndClick (élément, 5); bot.waitAndClick (élément2, 5); }
Le bot peut également être étendu pour créer des implémentations spécifiques à la bibliothèque. Si l'application commence à utiliser une bibliothèque différente, tout le code de test peut rester le même et seul le bot doit être mis à jour:
Public class Bot {pilote WebDriver privé; Private RichEditorBot richEditor; Bot public (pilote WebDriver, RichEditorBot richEditor) {this.driver = pilote; this.richEditor = richEditor; } public booléen isEditorDirty () {richEditor.isEditorDirty (); }} classe publique RichEditorBot () {public booléen isEditorDirty () {return ((JavascriptExecutor) driver) .executeScript (“return EDITOR.instances.editor.checkDirty ()”); }} void test () {/ * test code * / bot.isEditorDirty (); }
Un exemple de Bot est disponible dans le cadre de la bibliothèque WebDriverExtensions, ainsi qu'une implémentation spécifique à la bibliothèque:
- https://github.com/webdriverextensions/webdriverextensions/blob/master/src/main/java/com/github/webdriverextensions/Bot.java
- https://github.com/webdriverextensions/webdriverextensions/blob/master/src/main/java/com/github/webdriverextensions/vaadin/VaadinBot.java
Le modèle de bot et le modèle d'objet de page peuvent être utilisés ensemble. Dans vos tests, l'abstraction de niveau supérieur est l'objet de page représentant les éléments fonctionnels de chaque composant. Les objets de page contiennent alors un bot à utiliser dans les fonctions d'objet de page, rendant leurs implémentations plus simples et plus faciles à comprendre:
classe publique LoginComponent {bot bot privé; @FindBy (id = “login”) privé WebElement loginButton; public LoginComponent (Bot bot) {PageFactory.initElements (bot.getDriver (), this); this.bot = bot; } public void clickLogin () {bot.waitAndClick (loginButton, 5); }}
Usine WebDriver
L'instanciation et la configuration d'une instance WebDriver telle que ChromeDriver ou FirefoxDriver directement dans le code de test signifie que le test présente désormais deux préoccupations: la création d'un WebDriver spécifique et le test d'une application. Une usine WebDriver sépare ces problèmes en déplaçant toutes les instanciations et configurations de WebDriver hors du test.
Cela peut être réalisé de plusieurs façons, mais le concept est simple: créer une usine qui fournit un WebDriver entièrement configuré. Dans votre code de test, récupérez le WebDriver de l'usine plutôt que de le construire directement. Désormais, toutes les préoccupations concernant le WebDriver peuvent être traitées en un seul endroit. Toutes les modifications peuvent se produire à cet endroit et chaque test recevra le WebDriver mis à jour.
Le projet Web Driver Factory utilise ce concept pour gérer la durée de vie des WebDrivers sur plusieurs tests. Cette tâche complexe est abstraite à l'usine permettant aux tests de simplement demander un WebDriver avec les options fournies:
L'usine WebDriver facilite la réutilisation d'un seul test sur plusieurs navigateurs. Toute configuration peut être gérée via un fichier externe. Le test demande uniquement à la fabrique WebDriver une instance du WebDriver et la fabrique gère les détails. Un exemple de ceci est utilisé pour prendre en charge les tests de grille parallèle dans le framework TestNG:
Extension du modèle d'objet de page
Le modèle d'objet de page fournit une couche d'abstraction qui présente les fonctions des composants d'une application tout en masquant les détails de l'interaction de Selenium avec ces composants. Il s'agit d'un modèle de conception puissant qui rend le code réutilisable et plus facile à comprendre. Cependant, la création d'une classe pour chaque page et composant peut entraîner une surcharge. Il y a le passe-partout pour chaque classe, puis les composants partagés entre les classes, tels que l'initialisation de l'instance et la transmission de l'objet WebDriver ou Bot. Cette surcharge peut être réduite en étendant le modèle d'objet de page.
Si vous utilisez le modèle Bot avec votre modèle d'objet de page, chaque objet de page aura besoin d'une instance du bot. Cela pourrait ressembler à:
classe publique LoginComponent {bot bot privé; @FindBy (id = “login”) privé WebElement loginButton; public LoginComponent (Bot bot) {PageFactory.initElements (bot.getDriver (), this); this.bot = bot; } public void clickLogin () {bot.waitAndClick (loginButton, 5); }}
Au lieu d'inclure le code Bot dans chaque constructeur, ce code pourrait être déplacé vers une autre classe que chaque composant étend. Cela permet au code du composant individuel de se concentrer sur les détails du composant et non sur le code d'initialisation ou la transmission du Bot:
Composant de classe publique {bot bot privé; Public Component (Bot bot) {PageFactory.initElements (bot.getDriver (), this); this.bot = bot; } Bot public getBot () {bot de retour; }} classe publique LoginComponent étend le composant {@FindBy (id = “login”) private WebElement loginButton; public LoginComponent (Bot bot) {super (bot); } public void clickLogin () {getBot (). waitAndClick (loginButton, 5); }}
De même, il est courant qu'un composant vérifie qu'il est instancié au bon moment, pour faciliter le débogage lorsque les composants sont utilisés de manière incorrecte. Nous pourrions vouloir vérifier que le titre est correct:
la classe publique LoginPage étend le composant {la page de connexion publique (bot bot) {super (bot); bot.waitForTitleContains («Veuillez vous connecter»); }}
Plutôt que d'inclure cet appel au Bot dans chaque classe, nous pouvons déplacer cette vérification vers une version spécialisée de Component que d'autres pages étendent. Cela offre un petit avantage qui s'ajoute lors de la création de nombreux objets de page:
classe publique TitlePage étend Component {public LoginPage (Bot bot, String title) {super (bot); bot.waitForTitleContains (titre); }} classe publique LoginPage étend TitlePage {Public LoginPage (Bot bot) {super (bot, «Veuillez vous connecter»); }}
D'autres bibliothèques fournissent des classes d'assistance exactement dans ce but. La bibliothèque Selenium Java comprend l'objet LoadableComponent qui fait abstraction de la fonctionnalité et vérifie le chargement d'une page:
Les WebDriverExtensions vont encore plus loin en résumant une grande partie du code autour des objets de page dans des annotations, créant des composants plus simples et plus faciles à lire:
Il y a toujours plus à apprendre
Cet article n'a abordé que quelques techniques et modèles de conception utiles. Des livres ont été écrits sur le sujet. Ecrire de bons tests signifie écrire de bons logiciels, et écrire de bons logiciels est une tâche complexe.
J'ai couvert certaines informations que j'ai apprises au fil des ans pour créer de meilleurs tests de sélénium. Parasoft Sélénic met à profit notre expertise pour simplifier davantage la tâche. Avec les outils et les connaissances appropriés, nous pouvons améliorer le processus et créer des tests stables, lisibles et maintenables.