Idées pour configurer, organiser et consolider vos tests Cypress #frontend@twiliosendgrid

Publié: 2020-12-15

Nous avons écrit de nombreux tests Cypress sur différentes applications Web et équipes frontend ici à Twilio SendGrid. Au fur et à mesure que nos tests s'étendaient à de nombreuses fonctionnalités et pages, nous sommes tombés sur des options de configuration utiles et avons développé des moyens de mieux gérer notre nombre croissant de fichiers, de sélecteurs d'éléments de notre page et de valeurs de délai d'attente en cours de route.

Notre objectif est de vous montrer ces conseils et idées pour configurer, organiser et consolider vos propres éléments liés à Cypress, alors n'hésitez pas à en faire ce qui fonctionne le mieux pour vous et votre équipe.

Cet article de blog suppose que vous avez une connaissance pratique des tests Cypress et que vous recherchez des idées pour mieux maintenir et améliorer vos tests Cypress. Cependant, si vous êtes curieux d'en savoir plus sur les fonctions, les assertions et les modèles communs que vous pourriez trouver utiles pour écrire des tests Cypress dans des environnements distincts, vous pouvez consulter cet article de blog de présentation de mille pieds à la place.

Sinon, poursuivons et expliquons d'abord quelques conseils de configuration pour Cypress.

Configuration de votre cypress.json

Le fichier cypress.json est l'endroit où vous pouvez définir toute la configuration de vos tests Cypress, tels que les délais d'attente de base pour vos commandes Cypress, les variables d'environnement et d'autres propriétés.

Voici quelques conseils à essayer dans votre configuration :

  • Ajustez les délais d'expiration de vos commandes de base, de sorte que vous n'ayez pas à toujours ajouter { timeout: timeoutInMs } dans vos commandes Cypress. Bricolez avec les chiffres jusqu'à ce que vous trouviez le bon équilibre pour des paramètres tels que "defaultCommandTimeout", "requestTimeout" et "responseTimeout".
  • Si votre test implique des iframes, vous devrez probablement définir "chromeWebSecurity" sur false afin de pouvoir accéder aux iframes d'origine croisée dans votre application.
  • Essayez de bloquer les scripts de marketing, d'analyse et de journalisation tiers lorsque vous exécutez vos tests Cypress pour augmenter la vitesse et ne pas déclencher d'événements indésirables. Vous pouvez facilement configurer une liste de refus avec leur propriété "blacklistHosts" (ou propriété "blockHosts" dans Cypress 5.0.0), en prenant un tableau de globs de chaînes correspondant aux chemins d'accès des hôtes tiers.
  • Ajustez les dimensions de la fenêtre d'affichage par défaut avec « viewportWidth » et « viewportHeight » lorsque vous ouvrez l'interface graphique Cypress pour rendre les choses plus faciles à voir.
  • S'il y a des problèmes de mémoire dans Docker pour des tests lourds ou si vous souhaitez aider à rendre les choses plus efficaces, vous pouvez essayer de modifier « numTestsKeptInMemory » en un nombre inférieur à la valeur par défaut et définir « videoUploadOnPasses » sur false pour vous concentrer sur le téléchargement de vidéos en cas d'échec du test. fonctionne uniquement.

Sur une autre note, après avoir peaufiné votre configuration Cypress, vous pouvez également ajouter TypeScript et mieux taper vos tests Cypress comme nous l'avons fait dans cet article de blog. Ceci est particulièrement utile pour la saisie semi-automatique, les avertissements de type ou les erreurs lors de l'appel et de l'enchaînement de fonctions (comme cy.task('someTaskPluginFunction) , Cypress.env('someEnvVariable') ou cy.customCommand() ).

Vous pouvez également essayer de configurer un fichier cypress.json de base et de charger un fichier de configuration Cypress distinct pour chaque environnement de test, tel qu'un staging.json lorsque vous exécutez différents scripts Cypress dans votre package.json . La documentation Cypress fait un excellent travail pour vous guider dans cette approche.

Organisation de votre dossier Cypress

Cypress configure déjà un dossier cypress de niveau supérieur avec une structure guidée lorsque vous démarrez Cypress pour la première fois dans votre base de code. Cela inclut d'autres dossiers tels que l' integration pour vos fichiers de spécifications, fixtures pour les fichiers de données JSON, les plugins pour vos cy.task(...) et autres configurations, et un dossier de support pour vos commandes et types personnalisés.

Une bonne règle à suivre lors de l'organisation de vos dossiers Cypress, de vos composants React ou de tout code en général consiste à regrouper les éléments susceptibles de changer ensemble. Dans ce cas, puisque nous avons affaire à des applications Web dans le navigateur, une approche qui évolue bien consiste à regrouper les éléments dans un dossier par page ou par fonctionnalité globale.

Nous avons créé un dossier de pages séparé partitionné par nom de fonctionnalité tel que "SenderAuthentication" pour contenir tous les objets de page sous la route /settings/sender_auth tels que "DomainAuthentication" (/settings/sender_auth/domains/**/*) et "LinkBranding" (/settings/sender_auth/links/**/*). Dans notre dossier de plugins , nous avons également fait la même chose en organisant tous les fichiers de plugin cy.task(...) pour une certaine fonctionnalité ou page dans le même dossier pour l'authentification de l'expéditeur, et nous avons suivi la même approche pour notre dossier d' integration pour les fichiers de spécifications. Nous avons étendu nos tests à des centaines de fichiers de spécifications, d'objets de page, de plug-ins et d'autres fichiers dans l'une de nos bases de code, et il est facile et pratique de s'y retrouver.

Voici un aperçu de la façon dont nous avons organisé le dossier cypress .

Une autre chose à considérer lors de l'organisation de votre dossier d' integration (où se trouvent toutes vos spécifications) est de diviser potentiellement les fichiers de spécifications en fonction de la priorité du test. Par exemple, vous pouvez avoir tous les tests de priorité et de valeur les plus élevés dans un dossier "P1" et les tests de deuxième priorité dans un dossier "P2" afin que vous puissiez facilement exécuter tous les tests "P1" en définissant l'option --spec comme --spec 'cypress/integration/P1/**/*' .

Créez une hiérarchie de dossiers de spécifications qui fonctionne pour vous afin que vous puissiez facilement regrouper les spécifications non seulement par page ou fonctionnalité telles que --spec 'cypress/integration/SomePage/**/*' , mais également par d'autres critères tels que la priorité, le produit , ou environnement.

Consolidation des sélecteurs d'éléments

Lors du développement des composants React de notre page, nous ajoutons généralement un certain niveau de tests unitaires et d'intégration avec Jest et Enzyme. Vers la fin du développement des fonctionnalités, nous ajoutons une autre couche de tests Cypress E2E pour nous assurer que tout fonctionne avec le backend. Les tests unitaires pour nos composants React et les objets de page pour nos tests Cypress E2E nécessitent des sélecteurs pour les composants/éléments DOM sur la page avec laquelle nous souhaitons interagir et affirmer.

Lorsque nous mettons à jour ces pages et composants, il y a une chance qu'il y ait une dérive et des erreurs dues à la nécessité de synchroniser plusieurs endroits des sélecteurs de test unitaire à l'objet de page Cypress au code de composant lui-même. Si nous nous appuyions uniquement sur les noms de classe liés aux styles, il serait pénible de se souvenir de mettre à jour tous les endroits qui pourraient casser. Au lieu de cela, nous ajoutons "data-hook", "data-testid" ou tout autre attribut "data-*" nommé de manière cohérente aux composants et éléments spécifiques de la page que nous souhaitons mémoriser et écrivons un sélecteur pour cela dans nos couches de test.

Nous pouvons ajouter des attributs "data-hook" à plusieurs de nos éléments, mais nous avions toujours besoin d'un moyen de les regrouper tous en un seul endroit pour les mettre à jour et les réutiliser dans d'autres fichiers. Nous avons trouvé un moyen typé de gérer tous ces sélecteurs de "crochets de données" à répartir sur nos composants React et à utiliser dans nos tests unitaires et nos sélecteurs d'objets de page pour une réutilisation accrue et une maintenance plus facile dans les objets exportés.

Pour le dossier de niveau supérieur de chaque page, nous créons un fichier hooks.ts qui gère et exporte un objet avec un nom de clé lisible pour l'élément et le sélecteur CSS de chaîne réel pour l'élément comme valeur. Nous les appellerons les "Read Selectors" car nous devons lire et utiliser le formulaire de sélecteur CSS pour un élément dans des appels tels que wrapper.find(“[data-hook='selector']”) d'Enzyme dans nos tests unitaires ou Cypress. cy.get(“[data-hook='selector']”) dans nos objets de page. Ces appels auraient alors un aspect plus propre comme wrapper.find(Selectors.someElement) ou cy.get(Selectors.someElement) .

L'extrait de code suivant explique plus en détail pourquoi et comment nous utilisons ces sélecteurs de lecture dans la pratique.

De même, nous exportons également des objets avec un nom de clé lisible pour l'élément et avec un objet comme { “data-hook”: “selector” } comme valeur. Nous les appellerons les « Sélecteurs d'écriture » car nous devons écrire ou diffuser ces objets sur un composant React en tant qu'accessoires pour ajouter avec succès l'attribut « data-hook » aux éléments sous-jacents. Le but serait de faire quelque chose comme ça et l'élément DOM réel en dessous - en supposant que les accessoires sont correctement transmis aux éléments JSX - aura également l'attribut "data-hook =" défini.

L'extrait de code suivant explique plus en détail pourquoi et comment nous utilisons ces sélecteurs d'écriture dans la pratique.

Nous pouvons créer des objets pour consolider nos sélecteurs de lecture et des sélecteurs d'écriture pour mettre à jour moins d'endroits, mais que se passerait-il si nous devions écrire de nombreux sélecteurs pour certaines de nos pages les plus complexes ? Cela peut être plus sujet aux erreurs à construire vous-même, alors créons des fonctions pour générer facilement ces sélecteurs de lecture et écrire des sélecteurs à exporter éventuellement pour une certaine page.

Pour la fonction de générateur de sélecteur de lecture, nous allons parcourir les propriétés d'un objet d'entrée et former la chaîne de sélecteur CSS [data-hook=”selector”] pour chaque nom d'élément. Si la valeur correspondante d'une clé est null , nous supposerons que le nom de l'élément dans l'objet d'entrée sera le même que la valeur "data-hook" telle que { someElement: null } => { someElement: '[data-hook=”someElement”] } . Sinon, si la valeur correspondante d'une clé n'est pas nulle, nous pouvons choisir de remplacer cette valeur "data-hook" telle que { someElement: “newSelector” } => { someElement: '[data-hook=”newSelector”]' } .

Pour la fonction de générateur de sélecteur d'écriture, nous allons parcourir les propriétés d'un objet d'entrée et former les objets { “data-hook”: “selector” } pour chaque nom d'élément. Si la valeur correspondante d'une clé est null , nous supposerons que le nom de l'élément dans l'objet d'entrée sera le même que la valeur "data-hook" telle que { someElement: null } => { someElement: { “data-hook”: “someElement” } } . Sinon, si la valeur correspondante d'une clé n'est pas null , nous pouvons choisir de remplacer cette valeur "data-hook" telle que { someElement: “newSelector” } => { someElement: { “data-hook”: “newSelector” }' } .

À l'aide de ces deux fonctions de générateur, nous construisons nos objets de sélecteur de lecture et d'écriture pour une page et les exportons pour les réutiliser dans nos tests unitaires et nos objets de page Cypress. Un autre avantage est que ces objets sont typés de telle manière que nous ne pouvons pas faire accidentellement Selectors.unknownElement ou WriteSelectors.unknownElement dans nos fichiers TypeScript également. Avant d'exporter nos sélecteurs de lecture, nous autorisons également l'ajout de mappages d'éléments et de sélecteurs CSS supplémentaires pour les composants tiers sur lesquels nous n'avons aucun contrôle. Dans certains cas, nous ne pouvons pas ajouter un attribut "data-hook" à certains éléments, nous devons donc toujours sélectionner par d'autres attributs, identifiants et classes et ajouter plus de propriétés à l'objet sélecteur de lecture, comme indiqué ci-dessous.

Ce modèle nous aide à rester organisés avec tous nos sélecteurs pour une page et lorsque nous devons mettre à jour les choses. Nous vous recommandons de rechercher des moyens de gérer tous ces sélecteurs dans une sorte d'objet ou par tout autre moyen afin de minimiser le nombre de fichiers dont vous avez besoin pour modifier les modifications futures.

Consolidation des délais d'attente

Une chose que nous avons remarquée après avoir écrit de nombreux tests Cypress était que nos sélecteurs pour certains éléments expireraient et prendraient plus de temps que les valeurs de délai d'attente par défaut définies dans notre cypress.json telles que "defaultCommandTimeout" et "responseTimeout". Nous revenions en arrière et ajustions certaines pages qui nécessitaient des délais d'expiration plus longs, mais au fil du temps, le nombre de valeurs de délai d'expiration arbitraires a augmenté et le maintien est devenu plus difficile pour les modifications à grande échelle.

En conséquence, nous avons consolidé nos délais d'attente dans un objet commençant au-dessus de notre "defaultCommandTimeout", qui se situe quelque part dans la plage de cinq à dix secondes pour couvrir la plupart des délais d'attente généraux pour nos sélecteurs tels que cy.get(...) ou cy.contains(...) . Au-delà de ce délai d'attente par défaut, nous passerions à "court", "moyen", "long", "xlong" et "xxlong" dans un objet de délai d'attente que nous pouvons importer n'importe où dans nos fichiers à utiliser dans nos commandes Cypress telles que cy.get(“someElement”, { timeout: timeouts.short }) ou cy.task('pluginName', {}, { timeout: timeouts.xlong }) . Après avoir remplacé nos délais d'attente par ces valeurs de notre objet importé, nous avons un endroit à mettre à jour pour augmenter ou réduire le temps nécessaire pour certains délais d'attente.

Un exemple de la façon dont vous pouvez facilement commencer avec ceci est montré ci-dessous.

Emballer

Au fur et à mesure que vos suites de tests Cypress se développent, vous pouvez rencontrer certains des mêmes problèmes que nous avons rencontrés lors de la détermination de la meilleure façon de mettre à l'échelle et de maintenir vos tests Cypress. Vous pouvez choisir d'organiser vos fichiers en fonction de la page, de la fonctionnalité ou d'une autre convention de regroupement, de sorte que vous sachiez toujours où rechercher les fichiers de test existants et où en ajouter de nouveaux à mesure que davantage de développeurs contribuent à votre base de code.

Au fur et à mesure que l'interface utilisateur change, vous pouvez utiliser une sorte d'objet typé (tel que les sélecteurs de lecture et d'écriture) pour maintenir vos sélecteurs d'attribut "data-" sur les éléments clés de chaque page que vous souhaitez affirmer ou interagir dans vos tests unitaires et Cypress essais. Si vous commencez à appliquer trop de valeurs arbitraires pour des choses comme les valeurs de délai d'attente pour vos commandes Cypress, il est peut-être temps de configurer un objet rempli de valeurs mises à l'échelle afin que vous puissiez mettre à jour ces valeurs en un seul endroit.

Au fur et à mesure que les choses changent dans votre interface utilisateur frontale, votre API backend et vos tests Cypress, vous devez toujours réfléchir à des moyens de maintenir plus facilement ces tests. Moins d'endroits pour mettre à jour et faire des erreurs et moins d'ambiguïté quant à l'endroit où mettre les choses font une énorme différence car de nombreux développeurs ajoutent de nouvelles pages, fonctionnalités et (inévitablement) des tests Cypress sur la route.

Intéressé par plus de messages sur Cypress? Jetez un œil aux articles suivants :

  • Éléments à prendre en compte lors de la rédaction de tests E2E
  • Vue d'ensemble de 1 000 pieds des tests d'écriture Cypress
  • TypeScript Toutes les choses dans vos tests Cypress
  • Gestion des flux de messagerie dans les tests Cypress
  • Intégration des tests Cypress avec Docker, Buildkite et CICD