Optimisation de l’Automatisation avec Cypress : Les Bonnes Pratiques
Les bonnes pratiques de l’automatisation revêtent une importance capitale pour la création de tests automatisés performants, couvrant l’ensemble du processus de test, de la planification à la maintenance. Cypress est un outil de test hautement loué, grâce à sa simplicité d’utilisation et à ses fonctionnalités intégrées qui simplifient la tâche des tests. Toutefois, l’usage adéquat de Cypress et le respect de ses bonnes pratiques demeurent cruciaux. En leur absence, vos tests risquent de perdre en performance, d’introduire des erreurs inutiles et de compromettre la fiabilité de votre code de test.
En qualité d’ingénieur en automatisation de tests, il vous incombe de suivre ces bonnes pratiques pour optimiser l’efficacité, la réutilisabilité, la lisibilité et la facilité de maintenance de votre code au sein d’un projet Cypress. Par ailleurs, si chaque membre de l’équipe rédige des tests en suivant sa propre vision idéale, cela peut engendrer le chaos. Certains modèles, adaptés aux tests unitaires en Java ou Python, ne conviennent pas aux tests d’intégration en JavaScript, et vice versa. Les tests de chaque type doivent se montrer cohérents et conformes aux règles acceptées pour le projet. Il est donc impératif d’adopter une approche uniforme et de respecter les normes adéquates afin de garantir la qualité des tests au sein de votre projet Cypress.
1) Éviter les wait inutiles
Il est généralement considéré comme une mauvaise pratique d’utiliser des commandes d’attente statiques comme cy. wait(timeout) dans vos tests Cypress parce qu’elles introduisent des délais inutiles et peuvent rendre vos tests moins fiables.

Dans cet extrait de code, la commande cy. visit() est utilisée pour naviguer vers le site Automation Exercise . et la commande cy.wait(5000) est utilisée pour interrompre l’exécution du test pendant 5 secondes avant de passer à la commande suivante.
Même si le site est chargé dans un délai de 2 secondes, le système attend 5 secondes. La meilleure façon de gérer ce problème est d’utiliser une attente dynamique à l’aide de cy.intercept().
Dans cet exemple, nous capturons une requête réseau à destination de Automation Exercise et lui attribuons l’alias « getData ». Ensuite, nous procédons à la navigation vers la page en attendant la fin de la requête « getData » à l’aide de la commande cy.wait(‘@getData’). Enfin, nous vérifions que la réponse renvoie un code d’état 200.
Dans cet extrait de code, la commande cy. visit() est utilisée pour naviguer vers le site Automation Exercise . et la commande cy.wait(5000) est utilisée pour interrompre l’exécution du test pendant 5 secondes avant de passer à la commande suivante.
Même si le site Automation Exercise est chargé dans un délai de 2 secondes, le système attend 5 secondes. La meilleure façon de gérer ce problème est d’utiliser une attente dynamique à l’aide de cy.intercept().

Dans cet exemple, nous capturons une requête réseau à destination de Automation Exercise et lui attribuons l’alias « getData ». Ensuite, nous procédons à la navigation vers la page en attendant la fin de la requête « getData » à l’aide de la commande cy.wait(‘@getData’). Enfin, nous vérifions que la réponse renvoie un code d’état 200.
2) Usage approprié des hooks
Dans Cypress, nous avons 4 hooks : before(), beforeEach(), after() et afterEach().
Écrire du code répétitif n’est pas recommandé. Supposons que nous ayons plusieurs cas de test, et que chacun de ces cas de test comporte un ensemble de lignes de code communes à tous. Pour éviter cette répétition, il est judicieux d’utiliser un hook, tel que beforeEach().
Les hooks before() et after() doivent être employés avec discernement. Étant donné que ces hooks s’exécutent une seule fois pour l’ensemble de la suite de tests, ils peuvent être lents et compliquer l’isolation des échecs des tests individuels. Il est généralement préférable d’utiliser les hooks beforeEach() et afterEach() pour la mise en place et la remise en état du scénario de test.
Définition des hooks :
- beforeEach() : Ce hook est utilisé pour exécuter du code avant chaque cas de test (it) de la suite de tests. Il permet de préparer l’état initial nécessaire pour chaque test.
- afterEach() : Ce hook est utilisé pour exécuter du code après chaque cas de test (it). Il est couramment utilisé pour nettoyer ou remettre en état l’environnement après l’exécution d’un test.
- before() : Ce hook s’exécute une seule fois avant le début de la suite de tests. Il est utilisé pour effectuer des actions qui doivent être accomplies une seule fois, comme la configuration initiale.
- after() : Ce hook s’exécute une seule fois après la fin de la suite de tests. Il est généralement utilisé pour nettoyer ou effectuer des opérations de clôture.
Exemple d’application
Dans l’exemple ci-dessous, vous pouvez voir que nous avons utilisé les hooks beforeEach() et afterEach(). Dans ce scénario, ces hooks correspondent le mieux, mais nous n’avons pas besoin de toujours utiliser ces hooks, cela dépend totalement de l’exigence.

Voici une capture d’écran de l’exécution des tests ci-dessus

Utilisez les hooks beforeEach() et afterEach() pour mettre en place et nettoyer le scénario de test : En utilisant ces hooks pour réinitialiser l’état entre les tests, vous pouvez rendre vos tests plus indépendants et réduire le risque d’effets secondaires et de tests défectueux.
3) Définir baseUrl dans le fichier de configuration de Cypress
Coder en dur l’URL de base en utilisant cy.visit() dans le bloc before() de chaque fichier spec n’est pas la meilleure approche car cela peut conduire à du code redondant et rendre vos tests plus difficiles à maintenir.
Une meilleure approche serait de définir l’URL de base dans le fichier de configuration cypress.config.js et d’utiliser la commande cy.visit() avec des URL relatives dans vos fichiers de test.
Par exemple, dans votre fichier cypress.json, vous pouvez définir l’URL de base comme étant l’URL de la page de connexion.

Ensuite, dans vos fichiers de test, vous pouvez utiliser des URL relatives pour naviguer vers d’autres pages de votre application. Par exemple, vous pouvez utiliser des URL relatives pour naviguer vers d’autres pages de votre application.


4) Utiliser l’attribut « data-cy » pour identifier les localisateurs
L’utilisation des attributs data-cy pour l’identification des localisateurs est une bonne pratique dans Cypress, car elle fournit un moyen fiable et cohérent de cibler les éléments de votre application. L’attribut data-cy est un attribut personnalisé que vous pouvez ajouter à n’importe quel élément de votre application pour faciliter le ciblage de cet élément dans vos tests Cypress.
Voici un exemple de la manière dont vous pouvez utiliser les attributs data-cy pour cibler un formulaire de connexion dans votre application :
Supposons que nous ayons le code HTML suivant pour un formulaire de connexion :


Pour utiliser les attributs « data-cy-* » afin d’identifier les localisateurs, vous pouvez ajouter l’attribut « data-cy » à chaque élément que nous voulons sélectionner dans nos tests. En voici un exemple :


Vous pouvez maintenant utiliser ces attributs data-cy pour sélectionner les éléments dans nos tests Cypress, comme ceci :


5) Isoler les blocs it()
La meilleure pratique implique l’utilisation de blocs it() autonomes qui ne sont pas tributaires du résultat d’autres blocs it() pour fonctionner correctement. Chaque bloc it() doit posséder ses propres étapes d’initialisation et de nettoyage, sans dépendre de l’état de l’application après l’exécution d’un test précédent.
Voici quelques avantages de l’utilisation de blocs it() autonomes dans Cypress :
-
Isolation des tests : Grâce à l’utilisation de blocs it() indépendants, vous garantissez que chaque test s’exécute en toute isolation, sans être influencé par l’état de l’application résultant d’un test précédent. Cela renforce la fiabilité de nos tests et simplifie leur maintenance.
-
Compréhension accrue : Les blocs it() indépendants sont plus faciles à comprendre, car chaque bloc représente un cas de test distinct. Cette clarté facilite le processus de dépannage et la résolution des problèmes.
-
Exécution plus rapide des tests : Étant donné que chaque bloc it() fonctionne de manière autonome, les tests s’exécutent plus rapidement. Il n’est pas nécessaire de rétablir l’état de l’application à chaque test, ce qui contribue à accélérer l’exécution des tests.
Voici un exemple illustrant l’utilisation de blocs it() autonomes dans Cypress.




Sortie
Dans la capture d’écran ci-dessous, vous pouvez voir que les cas de test s’exécutent indépendamment et réussissent.


6) Plusieurs assertions par test
L’écriture d’assertions uniques dans un test peut ralentir vos tests et causer des problèmes de performance. La meilleure pratique consiste à ajouter plusieurs assertions avec une seule commande, ce qui rend les tests plus rapides et améliore l’organisation et la clarté des tests.


Sortie
Dans la capture d’écran ci-dessous, vous pouvez voir que nous avons vérifié toutes les assertions en une seule ligne au lieu d’écrire différentes lignes de code pour chaque assertion.


7) Séparer les données de test
Garder les données de test séparées du code de test est une bonne pratique dans l’automatisation Cypress. Les données séparées du code de test peuvent aider à rendre vos tests plus faciles à maintenir et à mettre à jour.
Voici un exemple de la façon de séparer les données de test dans Cypress :
Créez un fichier séparé pour stocker les données de test. Par exemple, vous pouvez créer un fichier JSON appelé « data-test.json » dans le répertoire racine de votre projet.
Dans ce fichier, définissez les données de test sous forme de paires clé-valeur. Par exemple :


-
Dans votre code de test, importez les données de test à partir du fichier JSON à l’aide de fixture.
-
Utilisez les données de test dans vos tests en faisant référence aux clés définies dans le fichier JSON.


En enregistrant les données de test dans un fichier distinct, vous avez la possibilité de mettre à jour aisément ces données de test sans avoir à altérer le code de test en lui-même.
8) Utiliser des alias
Employez des alias pour enchaîner les commandes au lieu de reproduire les mêmes sélecteurs dans chaque commande. Cette approche confère au code de test une meilleure lisibilité et facilite sa maintenance.
Par exemple, vous pouvez créer un alias pour un bouton de connexion et l’utiliser dans les tests ultérieurs sans avoir besoin de le réidentifier à chaque fois.
Dans cet exemple, nous utilisons la commande « as » pour attribuer des alias à l’entrée de l’adresse électronique, à l’entrée du mot de passe, et au bouton d’envoi. Ensuite, nous utilisons ces alias dans le test lui-même. Cette pratique rend le test plus lisible et plus facile à maintenir, surtout si plusieurs tests font usage des mêmes sélecteurs.


Sortie
Dans la capture d’écran ci-dessous, vous pouvez voir les alias dans le journal et le cas de test est exécuté avec succès.


9) Pas de tests sans assertions (vérifications)
Il ne devrait pas y avoir d’étapes de test sans vérifications.
Ne créez pas d’étapes de test de ce type :


Insérez toujours des assertions à la fin


10) Pas de test commenté, utilise .skip
Si le test doit être désactivé, il doit être ignoré par la fonction du cadre de test (skip), et non par le code commenté.
Au lieu de :


Utilise :


Le rapport de test affichera le nombre de tests qui ont été délibérément omis.
Si un test est devenu obsolète ou n’a plus de pertinence, il devrait être supprimé sans aucune hésitation.
11) Pas de localisateurs sans signification
Les localisateurs doivent avoir des significations.


Le code des tests doit faire quelque chose : exécuter des actions et/ou des assertions.
Utilises l’assertion .should(‘be.visible’) à la place.
12) Une attente pour chaque étape de test
Les étapes de test doivent être courtes, en se focalisant sur la validation d’un seul élément à la fois. Il est préférable de limiter une étape de test à une ou deux assertions au maximum. Évitez de chercher à réaliser une multitude de tâches ou de vérifications en une seule étape. En optant pour des étapes de test plus « élémentaires », les rapports et les journaux de test seront plus clairs et plus faciles à comprendre.
13) Éviter les expressions régulières dans les contrôles
Les vérifications avec des expressions régulières rendent les tests trop sensibles et n’ajoutent pas beaucoup de fiabilité aux tests, mais rendent difficile l’analyse après les échecs.
Il existe deux exceptions :
-
les expressions régulières pour la vérification des URL ;
-
regexp pour la date et l’heure.
Ces deux types de données peuvent être vérifiés par des expressions rationnelles.
Si votre projet de test comprend des identifiants d’un domaine spécifique qui peuvent être attribués à un certain modèle, il est également possible de les tester à l’aide de la fonction regexp.
14) Ne pas disperser les cas de test
Il est essentiel d’appliquer une vérification uniforme de la même fonctionnalité dans tous les scénarios.
Par exemple, plutôt que d’avoir un fichier nommé « scenarios-1.cy.ts » où vous effectuez une vérification d’une bannière en utilisant l’attente A, puis un fichier « scenarios-2.cy.ts » où vous vérifiez la même bannière (potentiellement sur une autre page) en utilisant l’attente B, vous devriez plutôt inclure les deux attentes (A et B) dans chacun des tests, que ce soit dans « scenarios-1.cy.ts » ou « scenarios-2.cy.ts ».
15) Ne pas mélanger différents types de tests
Si vous souhaitez vérifier à la fois l’API et l’interface utilisateur pour une seule action de l’utilisateur, réalisez deux tests distincts : un test API et un test UI.
Si vous souhaitez contrôler simultanément la fonctionnalité de l’interface utilisateur et la présentation par capture d’écran, effectuez deux tests distincts : un test de l’interface utilisateur et un test de capture d’écran.
Si vous souhaitez examiner les scénarios API de bout en bout et les schémas JSON en même temps, créez deux tests d’intégration API distincts, chacun d’entre eux exécutant des vérifications spécifiques.
16) Utiliser des linters et des formateurs du projet de test
L’usage de linters tels qu’ESLint et d’outils de formatage comme Prettier offre de nombreux avantages lors du développement de tests automatisés en génie logiciel. Cela garantit la cohérence du code, détecte rapidement les erreurs, améliore la productivité en automatisant la mise en forme, facilite la collaboration grâce à un code uniforme et lisible, élève la qualité du code en repérant les problèmes et diminue les désaccords liés au style. En outre, cela simplifie la maintenance à long terme des tests automatisés, assurant une gestion plus efficace du code de test et une plus grande résilience des tests.
17) Objets de page – l’approche anti-pattern
De nombreuses personnes continuent d’adopter une approche non recommandée, qui consiste à créer des instances de page directement dans la couche de test à chaque transition d’une page à l’autre au sein du même cas de test.
Prenons un exemple concret :


l’extrait de code ci-dessus comporte des commandes asynchrones Cypress ainsi que du code pour instancier des objets de page. Quel est le problème ici ?
Eh bien, le code suggère que nous devons d’abord accéder à l’application, puis créer une instance de « loginPage ». Cependant, dans la réalité, la visite de la page sera effectuée ultérieurement par Cypress, et l’instance « loginPage » sera créée en premier. Cela peut entraîner des effets secondaires indésirables.
Vous vous demandez peut-être ce qui se passe si l’on utilise le mot-clé « then() » ou des promesses à cet endroit ?


Étant donné que Cypress gère les opérations asynchrones de manière particulière, l’utilisation de promesses n’est pas appropriée dans ce contexte. Par conséquent, les promesses ne seront pas efficaces ici.
Quant à l’utilisation excessive du mot-clé « then() », cela peut entraîner une accumulation de ces mots-clés, rendant parfois les tests instables et les scripts de test difficiles à comprendre. En fin de compte, cela se traduit par des coûts de maintenance élevés.
18) Chaque test doit être indépendant
La raison de cette recommandation est que chaque fois qu’il y a une dépendance entre les tests, cela peut rapidement entraîner un effet domino, ce qui signifie que si un test échoue, tous les autres tests associés échoueront également.
Prenons l’exemple du test « Should be able to login and add items on cartr ». Dans ce cas de test, j’essaie d’accomplir deux actions distinctes dans un seul scénario, ce qui n’est pas une bonne pratique pour la maintenance à long terme du scénario de test.
Si le test de connexion échoue ou si la fonctionnalité de modification de l’image de profil change, cela peut avoir un impact sur le test, et d’autres personnes auront du mal à identifier la source du problème.
Au lieu de cela, il est préférable de diviser le test.
19) Ne pas coder l’attente en dur
Cypress offre la capacité d’attendre automatiquement le chargement de la page, et si vous avez besoin de prolonger l’attente jusqu’à ce qu’une condition spécifique soit remplie, vous pouvez utiliser la méthode should() ou le plugin cypress-wait-until. Cependant, il est fortement déconseillé d’utiliser une attente temporelle codée en dur, comme dans l’exemple suivant :


Si vous êtes en attente d’une requête API particulière, je vous recommande fortement d’utiliser Cypress intercept pour surveiller et attendre cette requête spécifique.
Par exemple :


20) Éviter les URL/points d’extrémité codés en dur
il est préférable de ne pas insérer directement les URL spécifiques à un environnement (comme les URL de développement, de test ou de production) dans les scripts de vos tests automatisés. Au lieu de cela, vous devriez utiliser une approche plus flexible pour gérer ces URL, car elles peuvent changer en fonction de l’environnement de votre application.
Cypress propose une manière de gérer dynamiquement les URL en utilisant l’objet « env » de Cypress, qui vous permet de stocker des variables d’environnement et de les utiliser dans vos tests.
Supposons que vous ayez configuré une variable d’environnement nommée « baseUrl » dans votre configuration Cypress, par exemple :



Dans votre script de test, vous pouvez utiliser cette variable comme base pour construire l’URL complète :


De cette manière, si l’URL de base change en fonction de l’environnement (par exemple, de développement à production), vous n’aurez qu’à mettre à jour la valeur de la variable d’environnement « baseUrl » dans votre configuration Cypress au lieu de modifier tous vos scripts de test. Cela rend votre automatisation plus flexible et plus facile à entretenir.
21) Pilotage par les données : Ne pas exécuter le même test pour plus de 2 ou 3 données de test différentes.
L’approche de test pilotée par les données implique de séparer les scripts de test des données de test. Ces données de test sont habituellement stockées sous forme de fichiers au format JSON, CSV, dans une base de données ou même un fichier Excel. Cette méthode permet de tester un même scénario fonctionnel en utilisant diverses variations de données de test.
Cypress prend en charge cette approche basée sur les données. Voici un exemple qui illustre cette méthode :


Cypress propose également une fonctionnalité de remontée dans le temps pour diagnostiquer les échecs de test.
Chaque fois que vous lancez vos tests Cypress, toutes les captures d’écran et les données de commande sont conservées en mémoire.
Lorsque vous exécutez plus de 3 à 4 tests avec des jeux de données différents dans le même groupe de descriptions, Cypress stocke en mémoire l’ensemble des captures d’écran et des données de commande, ce qui peut entraîner une utilisation excessive de la mémoire.
En raison de cette consommation importante de mémoire, il peut arriver que Cypress se bloque de manière inattendue pendant l’exécution des tests.
22) Exécutez le test n fois avant de l’activer dans le pipeline
Il peut arriver que votre test automatisé réussisse avec succès en local, mais qu’il échoue lorsqu’il est exécuté dans le pipeline. De plus, une récurrence de ces échecs risque de faire perdre confiance aux développeurs dans la fiabilité des résultats des tests.
Pour éviter cette situation complexe, il est recommandé d’exécuter votre test plusieurs fois (n fois) dans le pipeline de tests, de manière à tester les nouveaux ajouts sans perturber le fonctionnement global du pipeline.
L’idéal serait d’exécuter votre test au moins 10 fois dans le pipeline pour évaluer sa stabilité. Si des échecs se produisent, analysez le type de défaillance et travaillez à sa correction jusqu’à ce que le test devienne stable.
En adoptant cette approche, vous maintiendrez la stabilité et la fiabilité de votre pipeline.
J’ai eu l’occasion d’appliquer cette pratique au sein de l’une de mes anciennes organisations, et je peux vous assurer que c’était un moyen très efficace de construire un pipeline de tests stable et fiable.
23) Utiliser Typescript plutôt que Javascript pour Cypress
Si vous travaillez sur un projet d’automatisation des tests Cypress avec une équipe importante, composée à la fois de développeurs et de testeurs, il est fortement recommandé d’adopter Typescript comme langage de script pour vos tests Cypress.
L’utilisation de Typescript offre de nombreux avantages :
-
Une meilleure organisation de votre code de test.
-
La détection précoce des erreurs lors de la compilation, ce qui diffère de Javascript, générant des erreurs lors de l’exécution.
-
Une amélioration de la lisibilité du code par rapport à Javascript (bien que cela puisse varier selon les opinions personnelles).
-
La possibilité de refactoriser votre code plus aisément.
L’adoption de Typescript peut grandement contribuer à la qualité et à la maintenabilité de vos tests Cypress, en particulier lorsque vous travaillez avec une équipe de grande envergure.
24) Gardez toujours le fichier package-lock .json dans votre référentiel de code
Ajouter package.lock.json à git ignore est l’une des erreurs les plus courantes que nous faisons au début. Ce n’est pas la bonne chose à faire, nous devrions plutôt l’ajouter avec notre dépôt de code.
Le fichier package-lock.json stocke un arbre de dépendances exact et versionné et garantira que les mêmes paquets seront installés dans différents environnements.
Par exemple :


Dans le fichier package.json, la version de Cypress est la suivante :
Comment s’assurer qu’un pipeline de test ou une machine de développement choisira la même version « 12.3.0 » et non la version supérieure lors de l’exécution de npm install.
La réponse est – package-lock.json
C’est pourquoi le fichier package-lock.json doit être conservé dans le dépôt de code, et non dans le fichier git ignore.
Conclusion
L’outil que vous choisissez pour l’automatisation des tests n’est pas le facteur déterminant. Ce qui compte vraiment, c’est que vous respectiez les meilleures pratiques et les règles établies. En adhérant à ces bonnes pratiques, vous éviterez les problèmes potentiels.
Le respect de ces pratiques vous apportera un avantage considérable en tant qu’ingénieur de test dans le domaine de l’assurance qualité automatisée. Cela facilitera votre travail et contribuera à garantir la fiabilité et l’efficacité de vos tests automatisés. Ces bonnes pratiques peuvent inclure l’organisation structurée de votre code de test, la gestion des données de test, la documentation adéquate, la gestion des dépendances, la maintenance proactive et la collaboration efficace avec votre équipe de développement. En suivant ces principes fondamentaux, vous serez mieux préparé pour relever les défis de l’automatisation des tests de manière efficiente et efficace, quel que soit l’outil spécifique que vous utilisez.