Webinaire en vedette : MISRA C++ 2023 : tout ce que vous devez savoir | Voir le séminaire

Meilleurs conseils pour les experts en sélénium

Tête de Tony, développeur de logiciels senior chez Parasoft
le 9 novembre 2023
10 min lire

Une fois que vous utilisez Selenium depuis 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 de vos tests d'interface utilisateur au niveau supérieur. Découvrez ces techniques et pratiques préparées pour les utilisateurs de Selenium.

Avant que nous commencions

Cet article suppose que vous utilisez Selenium et que vous êtes à l’aise pour rédiger des cas de test. Vous avez déjà effectué l'essai d'inspection des DOM pour créer vos XPaths. 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 souhaitez de l'aide dans ce domaine, je vous 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.

7 conseils sur le sélénium pour les testeurs experts

1. De meilleurs localisateurs d'éléments Web

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[contains(@class, “content”)][contains(.,”Guidance for Success”)]

Avec ce qui précède, il est clair que l'élément représente un 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 vers 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 « Conseils pour réussir ». Bien que ce localisateur soit efficace, la copie du texte peut souvent changer ou peut ne pas être une option si le site prend en charge plusieurs langues. En recherchant dans le DOM, nous pourrions constater que le div parent a un identifiant descriptif. Dans ce cas, nous pourrions préférer un localisateur comme :

//div[@id=”success_guide”]//p

2. 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(/* some condition is true */)

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

Les attentes explicites descriptives vous permettent également écrire des tests qui se concentrent sur les aspects comportementaux de l’application. 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 permette de cliquer, mais il ne doit pas non plus ê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))

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

4. 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)", element)

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

5. Maîtriser le modèle de robot

La 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() {
    /* test code */
    WebDriverWait wait = new WebDriverWait(driver, 5);
    wait.until(ExpectedConditions.elementToBeClickable(element));
    element.click();

    wait.until(ExpectedConditions.elementToBeClickable(element2));
    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(WebElement element, long timeout) {
    WebDriverWait wait = new WebDriverWait(driver, timeout);
    wait.until(ExpectedConditions.elementToBeClickable(element));
    element.click();
    }
}

Alors notre code devient:

void test() {
    /* test code */
    bot.waitAndClick(element, 5);
    bot.waitAndClick(element2, 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 {
    private WebDriver driver;
    private RichEditorBot richEditor;

    public Bot(WebDriver driver, RichEditorBot richEditor) {
        this.driver = driver;
        this.richEditor = richEditor;
    }
    public boolean isEditorDirty() {
        richEditor.isEditorDirty();
    }
}

public class RichEditorBot() {
    public boolean 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:

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:

public class LoginComponent {
    private Bot bot;

    @FindBy(id = “login”)
    private WebElement loginButton;

    public LoginComponent(Bot bot) {
        PageFactory.initElements(bot.getDriver(), this);
        this.bot = bot;
    }

    public void clickLogin() {
        bot.waitAndClick(loginButton, 5);
    }
}

6. Simplifier la gestion des pilotes Web

L'instanciation et la configuration d'une instance WebDriver telle que ChromeDriver ou FirefoxDriver directement dans le code de test signifie que le test a désormais deux préoccupations :

    1. Construire un WebDriver spécifique.
    2. Tester une application.

Une usine WebDriver sépare ces problèmes en supprimant toutes les instanciations et configurations WebDriver du test. Cela peut être accompli de plusieurs manières, 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. Tout changement peut 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:

La fabrique WebDriver facilite la réutilisation d’un seul test sur plusieurs navigateurs. Toutes les configurations peuvent être gérées via un fichier externe. Le test demande uniquement à l’usine WebDriver une instance de WebDriver et l’usine 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 :

7. Extension du modèle objet de page

Le modèle Page Object fournit une couche d'abstraction qui présente les fonctions des composants d'une application tout en masquant les détails de la façon dont Selenium interagit 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 chaque composant peut entraîner une surcharge importante. 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 Page Object. 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 à :

public class LoginComponent {
    private Bot bot;

    @FindBy(id = “login”)
    private 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:

public class Component {
    private Bot bot;

    public Component(Bot bot) {
        PageFactory.initElements(bot.getDriver(), this);
        this.bot = bot;
    }

    public Bot getBot() {
        return bot;
    }
}

public class LoginComponent extends Component {
    @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:

public class LoginPage extends Component {
    public LoginPage(Bot bot) {
        super(bot);
        bot.waitForTitleContains(“Please login”);
    }
}

Plutôt que d'inclure cet appel au Bot dans chaque classe, nous pouvons déplacer cette vérification dans 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 :

public class TitlePage extends Component {
    public LoginPage(Bot bot, String title) {
        super(bot);
        bot.waitForTitleContains(title);
    }
}

public class LoginPage extends TitlePage {
    public LoginPage(Bot bot) {
        super(bot, “Please login”);
    }
}

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:

Meilleures pratiques pour les tests de sélénium

Les meilleures pratiques garantissent que vous tirez le meilleur parti de vos scénarios de test Selenium, à la fois immédiatement et à long terme. Les bonnes pratiques suivantes exploitent les conseils répertoriés ci-dessus pour créer des scénarios robustes, multiplateformes et multi-navigateurs. Garder ces pratiques à l’esprit contribuera à simplifier la transition de la création de scripts moins optimaux vers des scénarios entièrement optimisés et résilients au changement.

Code de test maintenable

Améliorez l’efficacité et la productivité en écrivant des tests faciles à maintenir. Vous passerez moins de temps à mettre à jour les tests et plus de temps à vous concentrer sur les problèmes réels lorsque les applications Web changent. La plupart des techniques présentées dans ce blog sont utiles en partie parce qu'elles améliorent la maintenabilité.

L'écriture de scénarios de test faciles à comprendre simplement en les regardant, ou d'un code auto-documenté, facilite leur maintenance. Les localisateurs d'éléments Web qui décrivent l'élément, les conditions d'attente explicites qui spécifient les conditions préalables nécessaires et les objets de page qui correspondent aux pages réelles rendent le code plus facile à lire et à comprendre. Même le modèle de bot permet de rendre les scripts de test auto-documentés en mappant les actions réelles des utilisateurs avec les noms de fonctions définis dans le bot.

Les localisateurs d'éléments Web descriptifs et les attentes explicites sont également moins susceptibles de s'interrompre avec le temps, car ils se concentrent sur les fonctionnalités de l'application Web plutôt que sur les détails de mise en œuvre. Par exemple, des localisateurs comme ceux-ci :

//p[contains(@class, “content”)][contains(.,”Guidance for Success”)]

font un bien meilleur travail de description de l'élément que ceux qui s'appuient sur des détails structurels sans rapport tels que :

/html/body/div[25]/div[2]/div/span/span[2]/div/h2/p

Les fonctionnalités d'une application Web sont moins susceptibles de changer que l'implémentation sous-jacente des fonctionnalités. Par conséquent, les scripts de test écrits pour les fonctionnalités sont moins susceptibles de nécessiter des mises à jour.

Tests basés sur les données

En fournissant une variété de données à vos scénarios de test, vous exercez mieux votre application et offre une couverture de test plus large.

Les frameworks de test tels que JUnit et TestNG fournissent des ensembles de fonctionnalités intégrés pour prendre en charge les tests basés sur les données. Tout scénario de test qui saisit des données, comme remplir un formulaire ou se connecter en tant qu'utilisateur, peut bénéficier de tests basés sur les données.

Lorsque vous améliorez vos scénarios grâce à des tests basés sur les données, évitez la tendance à créer différents flux logiques dans votre scénario pour différentes données. Séparez plutôt vos scénarios en fonction du type de données attendues. Cela permet des scénarios de test clairement définis avec des étapes simples et linéaires.

Tests multi-navigateurs

Bien que les détails puissent différer selon les navigateurs, la fonctionnalité principale de l'application Web reste généralement cohérente, de sorte que les tests centrés sur cette fonctionnalité principale via des localisateurs d'éléments Web descriptifs et des attentes explicites sont moins susceptibles d'être interrompus lorsqu'ils sont exécutés dans différents navigateurs.

Le modèle de bot est un endroit où les différences de bas niveau entre les navigateurs peuvent être gérées. Par exemple, si différents navigateurs nécessitent un code JavaScript différent pour accomplir la même action, le bot peut servir à résumer cette différence. Les deux implémentations JavaScript peuvent être incluses dans une seule fonction qui décrit l'action et gère le branchement entre les deux implémentations JavaScript, en fonction du navigateur. Le code du script de test peut ensuite appeler la fonction sur le bot sans avoir à se soucier des détails d'implémentation, ce qui rend le code du scénario de test plus facile à lire et exclut les détails spécifiques au navigateur.

De même, le code objet de la page peut être utilisé pour résumer les différences entre les navigateurs dans l'interface de l'application Web. Par exemple, si la connexion nécessite des étapes supplémentaires dans certains navigateurs, cette différence peut être gérée dans une fonction de haut niveau telle que « doLogin ». Les scénarios de test ont uniquement besoin de savoir comment se connecter et aucun des détails requis pour se connecter à l'aide de différents navigateurs.

Exécution de tests parallèles

Pour exécuter vos scénarios sur plusieurs navigateurs et environnements, vous souhaiterez exécuter des tests en parallèle pour gagner du temps. Il s'agit généralement d'une étape finale après vous être assuré que vos scénarios de test s'exécutent de manière transparente sur tous les navigateurs et qu'ils sont basés sur les données, le cas échéant.
Selenium Grid, combiné aux fonctionnalités de frameworks tels que JUnit et TestNG, fournit une prise en charge intégrée pour les tests parallèles. D'autres services tels que BrowserStack fournissent des tests parallèles, des environnements prédéfinis et des fonctionnalités de diagnostic pour maintenir plus facilement vos exécutions de tests parallèles.

Master Selenium pour des tests efficaces

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 exploite 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.

Découvrez comment créer des tests d'interface utilisateur Web Selenium fiables avec l'IA.