Tests de bout en bout (e2e)
Un test de bout en bout exécute l'application réelle, construite et démarrée comme les utilisateurs l'obtiennent, et la pilote depuis l'extérieur : un navigateur naviguant à travers les pages, un shell exécutant une CLI, ou un client appelant une API. Il détecte ce que les tests unitaires ne peuvent pas : le câblage, la configuration, le routage et les joints entre les parties. C'est aussi le type de test le plus lent et le plus fragile, donc chacun doit mériter sa place et être écrit pour donner la même réponse à chaque exécution.
La compétence sœur unit-testing couvre les tests d'unités individuelles. Lorsqu'un comportement peut être vérifié à l'un ou l'autre niveau, vérifiez-le avec un test unitaire.
Les règles du dépôt priment
Utilisez le framework e2e, la configuration, le dossier, les fixtures et les scripts du dépôt. Lisez les tests existants et la configuration (playwright.config.*, cypress.config.*, etc.) avant d'en écrire un, et conformez-vous à eux. AGENTS.md, CONTRIBUTING.md et les scripts du package indiquent comment les tests e2e sont exécutés et contre quoi. En cas de conflit avec cette compétence, ils prévalent. N'ajoutez pas un second framework e2e.
Outil par défaut
Lorsque le dépôt n'a pas encore d'outil e2e, utilisez Playwright (@playwright/test) pour tout ce qui implique un navigateur. Il fonctionne pour toute application web, quel que soit le langage du backend. Les exemples ici utilisent Playwright. Les règles s'appliquent avec n'importe quel outil.
Ce qui mérite un test e2e
- Parcours critiques : inscription, connexion, la tâche principale pour laquelle le produit existe, le paiement, et tout ce qui a déjà cassé en production.
- Points d'intégration : une page qui a besoin du vrai backend, de l'authentification ou du routage pour fonctionner.
- Un chemin heureux par parcours, plus les échecs qu'un utilisateur rencontrerait réellement (un mauvais mot de passe, un paiement refusé).
Repoussez tout le reste vers les tests unitaires : chaque règle de validation, chaque cas de formatage, chaque branche d'un calcul. Une suite e2e qui tente de couvrir tous les cas devient trop lente à exécuter et trop instable pour être fiable.
Localisateurs
Trouvez les éléments comme le ferait un utilisateur, afin que les tests survivent aux changements de balisage et de style.
- Préférez, dans l'ordre : le rôle et le nom accessible (
getByRole('button', { name: 'Enregistrer' })), l'étiquette (getByLabel('Email')), le texte visible, puis un ID de test (getByTestId) lorsque rien d'orienté utilisateur n'est stable. - Évitez : les classes CSS, XPath,
nth-childet les positions d'éléments. Ils cassent lorsque la disposition change et ne disent rien sur ce que l'utilisateur voit. - Si un élément ne peut pas être trouvé par rôle ou étiquette, c'est souvent un bogue d'accessibilité. Dites-le plutôt que d'utiliser un sélecteur fragile.
Attente
N'attendez jamais un temps fixe. sleep, waitForTimeout et setTimeout sont trop courts sur une machine CI lente et font perdre du temps sur une machine rapide.
- Utilisez des assertions qui réessayent jusqu'à ce qu'elles réussissent ou expirent :
await expect(page.getByRole('alert')).toHaveText('Enregistré'). - Attendez la condition que l'utilisateur attendrait : un élément visible, une URL modifiée, une réponse reçue (
page.waitForResponse), un fichier écrit. - N'augmentez pas les délais d'attente pour corriger un échec. Si quelque chose prend légitimement du temps, augmentez le délai pour cette seule étape et expliquez pourquoi.
- Ne forcez pas les actions au-delà des vérifications du framework (
{ force: true }). Si un clic nécessite un forçage, quelque chose recouvre l'élément ou il n'est pas prêt, et un véritable utilisateur rencontrerait le même problème.
Données de test et isolement
- Chaque test configure son propre état et s'exécute seul, dans n'importe quel ordre, et en parallèle avec les autres. Aucun test ne dépend de l'exécution préalable d'un autre.
- Rendez les données uniques par test (un email ou un ID généré), afin que les travailleurs parallèles et les reprises n'entrent pas en collision.
- Configurez par la porte arrière. Créez des utilisateurs et des enregistrements via une API, un script d'amorçage ou une fixture, pas en cliquant dans l'interface utilisateur. Seul le parcours testé passe par l'interface utilisateur.
- Connectez-vous une fois et réutilisez-le. Enregistrez l'état authentifié dans une étape de configuration (
storageStatede Playwright) au lieu de vous connecter via le formulaire dans chaque test. - Simulez uniquement ce que vous ne possédez pas. Stubbez les services tiers (paiements, email, cartes) à la limite du réseau avec
page.route()ou un faux local. Gardez votre propre frontend, backend et base de données réels, sinon ce n'est pas un test de bout en bout.
Tests de bout en bout CLI et API
Les mêmes règles s'appliquent sans navigateur. Exécutez les tests CLI avec l'exécuteur de tests unitaires du dépôt (Vitest pour JavaScript et TypeScript), dans une configuration ou un projet séparé afin qu'ils ne ralentissent pas la suite unitaire. Les tests API peuvent utiliser la fixture request de Playwright, ou le même exécuteur.
- Exécutez l'artefact compilé, de la même manière que les utilisateurs l'exécutent, pas le source via un point d'entrée réservé aux tests.
- Donnez à chaque test un répertoire temporaire frais comme répertoire de travail, et pointez
HOME, les chemins de configuration et les caches dedans, afin que les tests ne lisent ni ne modifient les fichiers réels du développeur. - Affirmez le code de sortie, stdout, stderr et les fichiers créés, séparément. Un test qui ne vérifie que "ça n'a pas lancé d'exception" manque une sortie erronée.
- Supprimez ou figez ce qui change entre les exécutions (horodatages, chemins temporaires, durées) avant de comparer la sortie.
Quand un test échoue
Lisez les preuves avant de changer quoi que ce soit.
- Récupérez les artefacts. Activez les traces, captures d'écran et vidéos pour les échecs (Playwright :
trace: 'on-first-retry',screenshot: 'only-on-failure'), et ouvrez la trace depuis l'exécution CI (npx playwright show-trace <trace.zip>). La trace montre la page, le réseau et la console à chaque étape. - Décidez à qui est la faute. Si l'application est erronée, corrigez l'application ; le test a fait son travail. Si le test est erroné, corrigez le test. Si l'environnement est erroné (un service hors ligne, un port utilisé), corrigez la configuration.
- Reproduisez-le avec la même commande, le même navigateur et les mêmes paramètres que CI, y compris le mode sans tête et le nombre de travailleurs.
- Pour un test instable, trouvez la condition de concurrence. Les causes habituelles : une attente fixe, une action avant que la page ne soit prête, des données partagées avec un autre test, ou une animation ou un toast recouvrant l'élément. Corrigez cette cause, puis exécutez le test plusieurs fois de suite (
npx playwright test <fichier> --repeat-each=20 --retries=0) avant de le déclarer corrigé.
Les reprises CI peuvent maintenir un pipeline vert, mais un test qui n'a réussi qu'après une reprise échoue toujours. Considérez "instable" dans un rapport comme un bogue à corriger, pas un statut. Ne sautez, ne supprimez ni ne mettez en quarantaine un test pour devenir vert sans l'accord de l'utilisateur, et ne relâchez jamais une assertion pour le faire réussir.
Liste de vérification
Avant de remettre des tests e2e, ou lors de leur révision :
- Le parcours nécessite un test e2e. Les cas qu'un test unitaire peut vérifier sont des tests unitaires.
- Les localisateurs utilisent le rôle, l'étiquette, le texte ou un ID de test. Pas de chaînes CSS ni XPath.
- Pas d'attentes fixes, d'actions forcées ou de délais d'attente globaux augmentés.
- Chaque test crée ses propres données uniques et s'exécute seul et en parallèle.
- Seuls les services tiers sont stubés.
- Les échecs laissent une trace ou une capture d'écran dans CI.
- Le test nouveau ou corrigé a réussi plusieurs fois de suite avec les reprises désactivées.