Tester de bout en bout
Un vrai navigateur contre l’application construite pour la production, sur une base et un stockage réservés aux tests : sept parcours qui vérifient ce que les gardes statiques ne peuvent pas voir — les droits, les brouillons, la limite d’essais, un e-mail reçu. Comment chaque lancement repart du même état, comment un test prouve qu’il échouerait, et ce que le premier lancement a trouvé.
Précédemment. Les gardes maison, le typecheck et le lint lisent le code sans l’exécuter : ils ne voient ni une règle de droits mal appliquée, ni une page qui répond ce qu’elle ne devrait pas.
Un test de bout en bout se place là où se place un utilisateur : devant un navigateur. Il ouvre une page, remplit un formulaire, clique, et vérifie ce que l’écran affiche ou ce que le serveur répond. Entre les deux, tout s’exécute pour de vrai : le composant React, la procédure tRPC, la requête Prisma, la base, le stockage, l’envoi d’un e-mail. Rien n’est simulé ; c’est ce qui le rend lent, et c’est ce qui le rend probant.
Le projet utilise Playwright, qui pilote un vrai Chromium. Les tests vivent dans e2e/ à la racine, et se lancent par une commande à part, pnpm e2e : quelques minutes, contre quelques secondes pour pnpm check, qui reste la vérification de chaque commit.
Les tests de bout en bout ne remplacent pas les gardes du chapitre 1 : ils en sont le complément. Une garde statique dit « ce code est cohérent » ; un test de bout en bout dit « ce parcours, sur ce site, fait ce qu’il doit ». Le premier est rapide et exhaustif sur ce qu’il sait lire, le second est lent et ne vérifie que ce qu’on lui a écrit.
Une base et un stockage à part 🧪#
Un test qui écrit dans la base de développement la salit : un compte de test apparaît dans la liste des membres, un mot de passe change, un document reçu n’est plus « non lu ». Un test qui écrirait en production serait une faute. Les tests ont donc leur environnement, décrit dans un seul fichier :
e2e/env.mjs — l’environnement des tests
Clique une étape pour mettre en évidence la zone correspondante dans le fichier complet.
// ⚠ Fichier introuvable : e2e/env.mjs// Il a été déplacé ou supprimé depuis la rédaction de ce chapitre.Les données de production ne servent jamais de données de test, même copiées. Un test crée lui-même ce dont il a besoin, avec des comptes @e2e.test qui n’existent nulle part ailleurs.
Chaque lancement repart du même état#
Un test qui dépend de ce qu’a laissé le précédent échoue au hasard : le premier lancement passe, le second trouve un mot de passe déjà changé. pnpm e2e commence donc par e2e/prepare.mjs, qui remet tout à zéro avant que l’application démarre :
e2e/prepare.mjs
Clique une étape pour mettre en évidence la zone correspondante dans le fichier complet.
// ⚠ Fichier introuvable : e2e/prepare.mjs// Il a été déplacé ou supprimé depuis la rédaction de ce chapitre.Les données sont écrites directement par Prisma, pas en cliquant dans l’interface : ce n’est pas ce que les tests vérifient, et chaque clic ajouterait une raison d’échouer sans rapport avec le parcours testé. Leurs noms et identifiants sont posés une fois, dans un fichier que les tests importent :
Une application construite comme en production#
Les tests tournent contre next build puis next start, pas contre pnpm dev. Le mode développement n’est pas le site que les membres utilisent : il compile à la demande, affiche les erreurs en surimpression, sert les bancs d’essai, et laisse passer des messages d’erreur détaillés que la production remplace par un message neutre.
playwright.config.ts
Clique une étape pour mettre en évidence la zone correspondante dans le fichier complet.
// ⚠ Fichier introuvable : playwright.config.ts// Il a été déplacé ou supprimé depuis la rédaction de ce chapitre.La construction des tests ne doit pas écraser celle de pnpm dev. next.config.ts lui donne son propre dossier quand AKFC_E2E=1 :
.next-e2e est exclu du typecheck du web (son include couvre tous les .ts du dossier, chapitre 1) et effacé par pnpm clean avec .next : après un pnpm check, le prochain pnpm e2e reconstruit.
Dix parcours#
Fichier | Ce qu’il vérifie | Pourquoi un test de bout en bout |
|---|---|---|
connexion.spec.ts | Se connecter depuis le header ; même message pour un mauvais mot de passe et une adresse inconnue ; après dix essais faux, même le bon mot de passe est refusé. | La limite d’essais vit en mémoire du serveur : seul un serveur qui tourne la montre. |
brouillons.spec.ts | Un brouillon répond 404 au visiteur et au membre, s’affiche en aperçu à l’administrateur avec son badge et noindex, et n’entre pas dans le plan du site. | La règle dépend de qui regarde : trois sessions, une même adresse. |
documents.spec.ts | Le destinataire voit et ouvre son document ; un autre membre ne le voit pas et son adresse répond 404 ; sans session, 401. | Le droit se lit en base à chaque requête : il faut une base, des groupes, une session. |
avatars.spec.ts | Un administrateur ne peut pas poser l’avatar d’un autre membre dans une page ; il peut poser le sien. | La règle est côté serveur : on l’interroge comme le ferait un client modifié, sans passer par l’interface qui ne propose déjà plus l’avatar d’autrui. |
droit-a-l-image.spec.ts | Un formulaire signé avant les droits en vigueur est archivé sans changer le statut ; un formulaire récent le change. | Envoi d’un vrai fichier au stockage, vérification par le serveur, transaction, puis l’écran rafraîchi. |
tutoriel.spec.ts | Le tuto « Créer une actualité » se suit jusqu’au bout (clic, saisie, explications, changement de page) ; Échap l’interrompt. | Un tuto traverse deux pages, une base, et le clavier : il n’existe qu’en marchant (partie « Étude de cas »). |
tutos-parcours.spec.ts | Les tutos des visiteurs et des membres : lancés depuis une fiche du guide ou le bouton « ? » ; les étapes facultatives sautées quand leur cible manque ; aucun n’envoie le formulaire qu’il montre. | Une étape facultative se décide sur la page réelle (une liste vide, un seul destinataire) ; et « rien n’est envoyé » ne se constate qu’en essayant de cliquer. |
tutos-admin.spec.ts | Les tutos d’administration : un brouillon de flash info créé ; une page, un compte, une réponse montrés sans être envoyés ; une demande suivie de la boîte à sa page. | Que rien ne parte se vérifie sur la vraie base : la page toujours en brouillon après un clic forcé, aucun compte créé. |
seances.spec.ts | Un administrateur ajoute une séance à un stage depuis son édition ; elle survit au rechargement. | Le formulaire des séances avait disparu d’une page sans qu’aucun outil le remarque : seul un parcours suivi le voit. |
mot-de-passe-oublie.spec.ts | Le lien reçu par e-mail permet de choisir un nouveau mot de passe ; l’ancien ne vaut plus rien ; le lien ne sert qu’une fois. | Le parcours traverse une boîte e-mail : Mailpit la donne aux tests. |
Showing entries1to10entries
Chaque parcours vérifie une règle décidée pour le club (les brouillons réservés aux administrateurs, un avatar qui ne représente que son titulaire, des droits qui ne changent que pour des droits plus récents), plutôt qu’un détail d’affichage. Un test qui vérifie la couleur d’un bouton casse à chaque retouche et ne protège rien ; un test qui vérifie qu’un membre ne lit pas le document d’un autre protège ce qui compte.
Se connecter sans formulaire#
Seul le test de connexion passe par le formulaire. Les autres ont besoin d’une session, pas de vérifier à nouveau comment on l’obtient : ils appellent la procédure de connexion directement, et le cookie de session se pose dans le navigateur du test.
e2e/helpers.ts — appeler tRPC, ouvrir une session
Clique une étape pour mettre en évidence la zone correspondante dans le fichier complet.
// ⚠ Fichier introuvable : e2e/helpers.ts// Il a été déplacé ou supprimé depuis la rédaction de ce chapitre.Un refus vérifié des deux côtés#
Le test de la limite d’essais vérifie l’écran, puis le serveur : le message affiché ne suffit pas, il faut aussi qu’aucune session n’ait été ouverte.
Les dix échecs passent par l’API, pas par le formulaire : la limite compte les essais par adresse, quelle que soit leur origine, et dix soumissions de formulaire n’apprendraient rien de plus. Le onzième essai, avec le bon mot de passe, passe par l’écran : c’est ce que verrait un membre.
404 et 401 ne disent pas la même chose#
Un membre connecté qui demande le document d’un autre reçoit 404, comme pour un document qui n’existe pas : le site ne confirme même pas son existence (partie « Membres et collaboration »). Sans session, 401 : la question n’est pas encore de savoir à qui est le document, mais qui demande. Le test fixe les deux réponses ; une modification qui les échangerait le ferait échouer.
Attendre un e-mail#
L’e-mail de réinitialisation part sans faire attendre la réponse du site. Le test interroge donc Mailpit jusqu’à ce qu’il arrive, vingt secondes au plus :
expect.poll relance la fonction jusqu’à ce qu’elle rende autre chose que null. C’est la bonne façon d’attendre en Playwright : jamais une pause fixe (waitForTimeout(3000)), trop longue quand tout va bien, trop courte un jour où la machine est chargée. Le filtre since écarte les e-mails d’un lancement précédent.
Un test doit pouvoir échouer#
Un test qui passe ne prouve rien tant qu’on ne l’a pas vu échouer pour la bonne raison. Une assertion qui ne regarde pas ce qu’on croit (un texte présent ailleurs sur la page, une réponse 200 d’une page d’erreur) passe toujours, et protège d’autant moins qu’elle rassure.
À l’écriture de ces tests, deux règles ont été cassées exprès, le temps d’un lancement :
Règle cassée | Test qui a échoué |
|---|---|
La route des documents ne vérifie plus à qui est le document (where: { id } au lieu de la condition d’accès). | documents.spec.ts : « un autre membre ne le voit pas, et son adresse répond 404 ». |
Le dépôt d’un formulaire de droit à l’image l’applique toujours (applied = true). | droit-a-l-image.spec.ts : « un formulaire signé avant les droits en vigueur est archivé ». |
Showing entries1to2entries
Les deux tests ont échoué, avec un message qui désignait la règle ; le code a été rétabli et ils sont repassés. C’est la vérification d’un test : on casse ce qu’il protège, il doit le dire.
Écrire le test d’une règle avant de la corriger, quand c’est possible, donne cette preuve gratuitement : le test échoue d’abord (la règle est fausse), puis passe (elle est corrigée).
Ce que le premier lancement a trouvé#
Les six premiers parcours sont passés au premier lancement complet. La sortie du serveur, elle, montrait une erreur à chaque ouverture de la fiche d’un membre : ReferenceError: DOMMatrix is not defined.
Le visionneur de PDF (react-pdf, sur pdf.js) utilise au chargement de son module une API du navigateur, DOMMatrix, que Node n’a pas. Importé directement, il faisait échouer le rendu serveur de chaque page qui le contient : la fiche d’un membre, le profil, la page des documents. Next rattrape l’erreur et laisse le navigateur rendre la page — elle s’affiche, le test passe — mais l’erreur se répète dans les journaux à chaque visite, et la page arrive sans son HTML. La vignette PDF du Finder avait déjà rencontré le problème ; le visionneur suit maintenant la même solution :
ssr: false diffère le chargement du module au navigateur ; le visionneur ne s’ouvre de toute façon qu’après un clic. Les deux composants qui l’utilisent importent désormais PdfViewerClient.
Le septième parcours, ajouté plus tard pour le moteur des tutoriels, a trouvé le problème inverse : un composant serveur envoyé au navigateur avec les formulaires d’administration. La partie « Étude de cas » le raconte.
Un test qui passe n’est pas toute l’information d’un lancement. La sortie du serveur montre ce que l’écran cache : une erreur rattrapée, un e-mail qui n’est pas parti, une requête lente. Elle se lit à chaque lancement, comme le résultat des tests.
Lancer les tests#
Commande | Ce qu’elle fait |
|---|---|
pnpm dev:infra | Démarre Postgres, MinIO et Mailpit (une fois par session de travail) ; les tests en ont besoin. |
pnpm e2e | Prépare la base et le bucket des tests, construit l’application, lance les sept parcours. Quelques minutes, dont l’essentiel pour la construction. |
E2E_NO_BUILD=1 pnpm e2e | Même chose sans reconstruire : quand seuls les tests ont changé depuis le dernier lancement. |
pnpm e2e connexion | Seulement les fichiers dont le nom contient « connexion ». |
pnpm e2e:report | Ouvre le rapport du dernier lancement : pour chaque échec, la capture d’écran et la trace. |
Showing entries1to5entries
Les tests ne font pas partie de pnpm check : ils se lancent avant un déploiement, et après toute modification d’une règle qu’ils couvrent. Plus tard, une intégration continue (GitHub Actions) pourra les lancer à chaque envoi sur GitHub, sur une machine qui monte elle-même Postgres, MinIO et Mailpit.
Un test qui échoue « une fois sur dix » n’est pas un test capricieux : c’est un test qui dépend de quelque chose qu’il ne contrôle pas (l’ordre, l’heure, un état laissé par un autre). On cherche cette dépendance ; on n’ajoute pas de nouvel essai automatique pour la masquer.
Récapitulatif#
Question | Réponse du projet |
|---|---|
Avec quel outil ? | Playwright, qui pilote Chromium ; les tests sont dans e2e/, typés et lintés avec le reste. |
Contre quelle application ? | La construction de production (next build, next start), dans apps/web/.next-e2e, sur le port 3100. |
Sur quelles données ? | Une base akfc_e2e et un bucket akfc-e2e locaux, remis à zéro à chaque lancement par e2e/prepare.mjs, avec des comptes @e2e.test. |
Qu’est-ce qui empêche de viser la production ? | assertLocal : rien ne se lance si la base ou le stockage ne sont pas locaux et dédiés aux tests. |
Que vérifient les tests ? | Dix parcours, chacun sur une règle décidée pour le club : connexion et limite d’essais, brouillons, documents, avatars, droit à l’image, mot de passe oublié, les séances d’un stage — et les tutoriels interactifs, suivis jusqu’au bout. |
Comment sait-on qu’un test protège quelque chose ? | On casse la règle qu’il couvre : il doit échouer, avec un message qui la désigne. |
Comment attendre un résultat asynchrone ? | expect.poll, jamais une pause fixe. |
Les tests tournent-ils à chaque commit ? | Non : pnpm check reste la vérification de chaque commit ; pnpm e2e se lance avant un déploiement et après toute modification d’une règle couverte. |
Showing entries1to8entries
À suivre#
La qualité se vérifie sur la machine du développeur. Reste à mettre le site en ligne, et à l’y garder : l’image Docker, le serveur, les variables de production, les sauvegardes.
Partie suivante : infrastructure et déploiement. L’image Docker, la mise en production et la sandbox.