Aller au contenu

5 min de lecture

Tester ce qui compte, avec Cypress

Pas tout tester. Tester les parcours qui font gagner ou perdre un client. Ma façon de les choisir, de les écrire, et de les garder fiables assez longtemps pour qu’on leur fasse confiance.

, développeuse front-end

  • Tests
  • Cypress
  • Qualité

Un site peut avoir mille tests et casser le jour du lancement. J’ai vu des projets couverts à 90 % où personne n’avait vérifié que le bouton « Payer » envoyait quelque chose. La question n’est pas combien de tests, mais lesquels, et ce qu’ils vérifient vraiment.

Les parcours clés

Je commence par lister ce qui doit marcher sans exception. La liste se fait avec le client, pas devant l’éditeur de code, et elle tient en général en moins de dix lignes :

  • Se connecter et se déconnecter.
  • Créer l’objet principal : un projet, une commande, une réservation.
  • Payer, ou envoyer le formulaire qui remplace le paiement.
  • Retrouver ce qu’on a créé après avoir fermé l’onglet.
  • Recevoir le message qui confirme que c’est fait.

Chacun devient un test de bout en bout, écrit comme un utilisateur le ferait, dans un vrai navigateur. Tout ce qui n’est pas dans la liste peut casser sans que le client perde un euro. Ce n’est pas une raison pour ne pas le tester, mais c’est une raison pour le tester autrement, et plus tard.

Écrire comme on utilise

cypress/e2e/projets.cy.ts
describe('Projets', () => {
  beforeEach(() => {
    cy.login('melanie@example.com')
  })

  it('crée un projet et le retrouve', () => {
    cy.visit('/projets')
    cy.findByRole('button', { name: 'Nouveau projet' }).click()
    cy.findByLabelText('Nom du projet').type('Maison Lacroix')
    cy.findByRole('button', { name: 'Enregistrer' }).click()
    cy.findByRole('status').should('contain', 'Projet enregistré')

    cy.reload()
    cy.findByRole('link', { name: 'Maison Lacroix' }).should('be.visible')
  })
})

Les sélecteurs passent par les rôles et les libellés, avec Testing Library, jamais par des classes CSS ni des identifiants techniques. Deux avantages :

  • Le test survit à une refonte. On peut changer les couleurs, la grille, le framework CSS : tant qu’il y a un bouton nommé « Enregistrer », le test passe.
  • Il échoue si un bouton perd son nom. Une icône sans libellé, un champ sans étiquette, et le test ne trouve plus rien. Le test d’accessibilité est gratuit.

Le cy.login est une commande maison qui passe par cy.session : la connexion se fait une fois, puis se rejoue depuis le cache. Sans ça, chaque test commence par trente secondes de formulaire.

cypress/support/commands.ts
Cypress.Commands.add('login', (email: string) => {
  cy.session(email, () => {
    cy.visit('/connexion')
    cy.findByLabelText('E-mail').type(email)
    cy.findByLabelText('Mot de passe').type(Cypress.env('PASSWORD'))
    cy.findByRole('button', { name: 'Se connecter' }).click()
    cy.findByRole('heading', { name: 'Tableau de bord' })
  })
})

Des données qu’on maîtrise

Un test qui dépend de la base de données de la veille échoue dès que quelqu’un y touche. Deux règles :

Chaque test crée ce dont il a besoin. Le test « supprimer un projet » commence par en créer un, via l’API, pas via l’interface. Un appel cy.request de quelques millisecondes, et le test ne dépend plus de personne.

Les services externes sont simulés. Paiement, envoi d’e-mails, géolocalisation : je les intercepte et je renvoie une réponse connue.

cypress/e2e/paiement.cy.ts
cy.intercept('POST', '/api/paiement', { statusCode: 200, body: { status: 'paid' } }).as('paiement')
cy.findByRole('button', { name: 'Payer 49 €' }).click()
cy.wait('@paiement')
cy.findByRole('heading', { name: 'Merci pour votre commande' })

Le vrai paiement se teste à part, une fois, à la main, avec les cartes de test du prestataire. Pas à chaque mise en ligne.

Les tests qui échouent au hasard

Un test instable finit ignoré, et un test ignoré ne protège plus rien. Je traque ces cas tout de suite, et ils ont presque toujours la même cause :

  • Une attente sur une animation. Un cy.wait(500) qui « laisse le temps » au panneau de s’ouvrir. Il faut attendre l’élément, pas le temps : cy.findByRole('dialog') réessaie seul jusqu’à ce qu’il apparaisse.
  • Un appel réseau pas simulé. Le test passe en local et échoue en intégration continue parce que le serveur de test est plus lent. L’interception règle le problème.
  • Une donnée partagée. Deux tests qui créent « Maison Lacroix » et vérifient qu’il n’y en a qu’une. Un suffixe unique par test, et c’est fini.

Le piège du retry

Cypress réessaie une commande jusqu’à ce qu’elle réussisse, mais seulement la dernière de la chaîne. cy.get('li').first().should('contain', 'Lacroix') réessaie le should, pas le first(). Si la liste se remplit après coup, le premier élément est resté vide. Quand une assertion dépend d’un contenu qui arrive plus tard, je sélectionne par le texte, pas par la position.

Quand ils tournent

Avant chaque mise en ligne. Pas après, pas « ce week-end ». Le déploiement est bloqué tant que les parcours clés ne passent pas, et c’est la seule règle que je ne négocie pas.

.github/workflows/deploy.yml
- run: npm run build
- run: npx cypress run --browser chrome
- run: npm run deploy
  if: success()

Une série de cinq parcours tourne en deux ou trois minutes. C’est le prix d’une mise en ligne sereine, et il est bas.

Ce que je ne teste pas en bout en bout

Les tests Cypress sont lents et chers à maintenir : j’en garde peu. Le reste se teste au niveau où c’est le moins coûteux :

  • La logique pure (un calcul de prix, un formatage de date) : un test unitaire avec Vitest, qui tourne en millisecondes.
  • Un composant isolé (un champ avec sa validation) : un test de composant, sans navigateur complet.
  • Le visuel : pas de capture d’écran comparée pixel par pixel. Ça casse à chaque police mise à jour et personne ne regarde les différences.

Mieux vaut cinq tests de bout en bout fiables que cinquante qu’on relance en croisant les doigts. Les cinq disent, à chaque mise en ligne, que ce qui compte marche encore. C’est exactement ce qu’on leur demande.

Un projet, une question ?

Un projet, une question ? Écrivez-moi, je réponds sous 48 h.

contact@melaniedasilva.fr

À lire ensuite

    • IA
    • Méthode
    • Claude Code

    Coder avec une IA sans perdre la main

    J’utilise Claude Code tous les jours. Voici ce que je lui confie, ce que je garde, et les règles qui m’évitent de livrer du code que je ne comprends pas.

    4 min de lecture

    • IA
    • Interface
    • Accessibilité

    Ce qu’une IA ne voit pas dans une interface

    Une IA génère un écran correct en quelques secondes. Les détails qui le rendent agréable, ou simplement utilisable, viennent après. Liste de ce que je vérifie à la main.

    4 min de lecture