Un parcours utilisateur qui casse en silence est l’un des bugs les plus douloureux a constater. L utilisateur clique, rien ne se passe, il abandonne, et l équipe ne le sait que par le drop dans les statistiques de conversion. Les tests bout-en-bout (E2E) servent exactement a cela : simuler un utilisateur qui clique, tape, attend, et vérifier que la page répond comme prévu. Playwright 1.59.1 est aujourd’hui l’outil de référence pour ce job. Ce tutoriel construit pas-à-pas une suite E2E sur une application web minimaliste, du npm install jusqu’au rapport HTML.
Prerequis
- Node.js 20 ou plus — Active LTS Node 24 (Krypton) recommande en 2026, Node 22 (Jod) accepte.
- Une application web qui démarre en local sur un port connu (Next, Vite, SvelteKit, Astro).
- Niveau attendu : intermediaire JavaScript / TypeScript.
- Temps total : 2 a 3 heures pour parcourir et adapter a votre projet.
Étape 1 — Installer Playwright dans un projet existant
Avant de toucher au code, on installé les dépendances et on demande a Playwright de télécharger les binaires de navigateur dont il a besoin. Playwright pilote Chromium, Firefox et WebKit a travers leurs APIs DevTools respectives. Les binaires pesent environ 200 Mo cumules, mais c’est la garantie qu’on teste sur le moteur réel et pas sur une version patchee. La commande officielle create-playwright genere aussi un dossier tests/ et un fichier de configuration TypeScript prêt a l emploi.
npm init playwright@latest
L’assistant interactif pose trois questions : TypeScript ou JavaScript (TypeScript par défaut), nom du dossier de tests (tests/), et installation des navigateurs (oui). A la fin, vous avez un fichier playwright.config.ts a la racine, un dossier tests/ avec un test exemple example.spec.ts, et un script npx playwright test qui lance la suite. Si la commande échoue avec une erreur de permission sur Linux, executez sudo npx playwright install-deps pour installer les bibliothèques système manquantes.
Étape 2 — Ecrire le premier test sur la page d accueil
Le test exemple genere par l installer va sur https://playwright.dev. Pour tester votre application, on remplace cette URL par la votre. Avant tout test, il faut définir le baseURL dans la configuration pour pouvoir écrire page.goto('/') au lieu de l’URL complète. Ouvrez playwright.config.ts et reperez la clé use.
// playwright.config.ts (extrait)
export default defineConfig({
testDir: './tests',
use: {
baseURL: 'http://127.0.0.1:3000',
trace: 'on-first-retry',
},
webServer: {
command: 'npm run dev',
url: 'http://127.0.0.1:3000',
reuseExistingServer: !process.env.CI,
},
});
Trois choses se passent ici. Le baseURL rend les tests portables : si demain l application tourne sur 127.0.0.1:5173, on change une ligne. Le trace en mode on-first-retry active le trace viewer uniquement quand un test échoue puis est rejoue, c’est l équilibre entre performance et facilité de debug. Le webServer demande a Playwright de démarrer npm run dev avant les tests et d attendre que http://127.0.0.1:3000 reponde, ce qui évite de gérer manuellement le cycle de vie du serveur dans la CI.
On écrit ensuite le premier vrai test. Créez tests/home.spec.ts.
import { test, expect } from '@playwright/test';
test('la page d accueil affiche le titre', async ({ page }) => {
await page.goto('/');
await expect(page).toHaveTitle(/ITSkillsCenter/);
await expect(page.getByRole('heading', { level: 1 })).toBeVisible();
});
Le lancement se fait avec npx playwright test home.spec.ts. Le test ouvre Chromium, navigue sur la racine, vérifie que le titre de la page contient ITSkillsCenter et qu un H1 est visible. Si tout est vert, vous voyez 1 passed en moins de deux secondes. Si rouge, lancez npx playwright show-report pour ouvrir le rapport HTML avec captures d’écran et trace.
Étape 3 — Choisir des selecteurs robustes
Le piège le plus courant en E2E est de choisir des selecteurs fragiles : div.container > div:nth-child(3) > button casse au premier refactor CSS. Playwright propose une hiérarchie de selecteurs recommandes, classes du plus robuste au plus risque. Cette hiérarchie n’est pas un détail : elle determine combien d heures par mois vous allez perdre a maintenir la suite.
En tête : page.getByRole(), qui s appuie sur les roles ARIA (button, link, heading, textbox). C est aussi ce qu utilisent les lecteurs d’écran, donc tester par role rapproche le test de l experience utilisateur réelle. Ensuite : page.getByLabel() pour les champs de formulaire, page.getByText() pour le texte visible, et en dernier recours page.getByTestId() qui suppose que le code de production porte des attributs data-testid.
// Bon : selecteur lisible et stable
await page.getByRole('button', { name: 'Ajouter au panier' }).click();
await page.getByLabel('Email').fill('test@example.com');
// A eviter : casse au premier refactor
await page.locator('.btn-primary.btn-lg').click();
await page.locator('#form-1 > input:nth-child(2)').fill('test@example.com');
La règle interne a verbaliser dans l équipe : si le designer change la classe CSS, le test ne doit pas casser. Si le PO change le libelle du bouton, le test peut casser, c’est même souhaitable, parce que le libelle du bouton est une fonctionnalité, pas un détail d’implémentation. En adoptant cette hiérarchie dès le debut, on diminue de moitié le coût de maintenance sur l année.
Étape 4 — Comprendre l auto-attente
Une différence structurelle entre Playwright et l ancienne génération Selenium est l auto-attente intégrée. Avant chaque action — click, fill, check — Playwright attend que l élément soit attache au DOM, visible, stable (pas en train d animer), et habilite. Cette attente est intelligente : elle laisse le temps a React ou Vue de finir de rendre, mais elle expire si l élément ne devient jamais cliquable.
L’erreur classique du debutant est d ajouter des page.waitForTimeout(2000) partout. Ces sleeps fixes ralentissent la suite et restent insuffisants quand le réseau est lent. La bonne pratique est d utiliser expect avec un matcher de visibilite, qui retourne quand la condition est vraie ou échoue après le timeout.
// Mauvais : sleep arbitraire
await page.click('text=Sauvegarder');
await page.waitForTimeout(2000);
await expect(page.getByText('Enregistre')).toBeVisible();
// Bon : auto-attente sur l apparition du toast
await page.click('text=Sauvegarder');
await expect(page.getByText('Enregistre')).toBeVisible(); // attend jusqu a 5s
Après le clic, on n’a plus besoin de deviner combien de temps prend l’API : expect(...).toBeVisible() attend tout seul jusqu’au timeout configuré (5 secondes par défaut). Si le toast apparaît au bout de 800 ms, le test continue immédiatement. S il n apparaît jamais, le test échoue avec un message clair. Cette mécanique élimine 90 % des tests flaky de la génération précédente.
Étape 5 — Reutiliser une session connectée avec une fixture
Tester chaque parcours en se connectant a chaque fois est lent et bruyant : si la connexion casse, vous voyez vingt tests rouges, alors que c’est un seul bug. La solution recommandée est de se connecter une seule fois, sauvegarder l état de la session (cookies + storage), et le restaurer au debut de chaque test concerne.
Créez un fichier tests/global-setup.ts qui exécute la connexion une fois pour toutes au démarrage de la suite, puis écrit l état dans un fichier JSON.
// tests/global-setup.ts
import { chromium, FullConfig } from '@playwright/test';
async function globalSetup(config: FullConfig) {
const { baseURL } = config.projects[0].use;
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto(baseURL + '/login');
await page.getByLabel('Email').fill('test-user@example.com');
await page.getByLabel('Mot de passe').fill('test-password');
await page.getByRole('button', { name: 'Se connecter' }).click();
await page.context().storageState({ path: 'tests/.auth/user.json' });
await browser.close();
}
export default globalSetup;
Ensuite on indique a Playwright d utiliser cet état dans le fichier de configuration : use: { storageState: 'tests/.auth/user.json' }. A partir de la, chaque test démarre déjà connecte, ce qui economise une a deux secondes par test. N oubliez pas d ajouter tests/.auth/ a .gitignore pour ne pas committer les cookies, ils peuvent contenir des tokens.
Étape 6 — Debugger avec le trace viewer
Le trace viewer est l’outil qui justifie a lui seul l adoption de Playwright. Quand un test échoue en CI, vous recuperez un fichier .zip qui contient les screenshots de chaque action, le DOM a chaque instant, les requetes réseau, les logs console, et les actions de la souris. Vous l ouvrez en local avec npx playwright show-trace trace.zip et vous rejouez le test pas-à-pas comme s’il tournait devant vous.
npx playwright show-trace trace.zip
L’interface ouvre un onglet navigateur avec une timeline en haut, le viewer de page au centre, et les détails a droite. En cliquant sur une action, vous voyez le DOM tel qu’il etait au moment de l action, et un selecteur pre-rempli pour identifier l élément. C est extrêmement utile pour debugger un test flaky : on voit en general que l élément n avait pas encore le bon texte, ou qu un overlay le cachait.
Étape 7 — Vérifier que la suite passe
Une fois plusieurs tests écrits, on lance la suite complète et on vérifie le rapport. La commande standard exécute tous les fichiers *.spec.ts dans le dossier tests/, sur tous les navigateurs configurés.
npx playwright test
npx playwright show-report
Le rapport HTML s’ouvre sur localhost:9323 et affiche le statut de chaque test, le navigateur utilisé, la durée, les captures d’écran d échec et un lien vers la trace. Si vous voyez du vert partout, la suite est saine. Si un test apparaît en rouge avec flaky en vert au-dessus, c’est qu’il a échoue puis réussi sur le retry. C est un signal d alerte sérieux a traiter rapidement.
Erreurs fréquentes
| Symptome | Cause probable | Solution |
|---|---|---|
| Browser not found au lancement | Binaires non téléchargés | npx playwright install |
| Test passe en local, échoue en CI | Polices ou viewport différents | Fixer la viewport dans config et utiliser le Docker officiel mcr.microsoft.com/playwright |
| Élément trouve mais pas cliquable | Overlay invisible (cookie banner) | Le fermer en beforeEach ou utiliser force: true en dernier recours |
| Tests qui se bloquent indefiniment | Animation infinie ou requete pendante | Activer reducedMotion: 'reduce' et abort les requetes longues via page.route() |
| storageState expire | Token JWT court | Régénérer dans le global-setup ou utiliser un token longue durée pour les tests |
Tutoriels associes
- Tests unitaires avec Vitest en 2026 : tutoriel pas-à-pas — premiers tests, matchers, mocks et coverage v8.
- Tests d’API avec Playwright en 2026 : tutoriel pas-à-pas — fixture request, auth Bearer, codes d’erreur et fixtures personnalisées.
- Tests visuels avec Playwright en 2026 : tutoriel pas-à-pas — snapshots stables, masquage des zones dynamiques et Docker officiel.
- CI parallélisée avec GitHub Actions en 2026 : tutoriel pas-à-pas — sharding Playwright, cache des binaires et fusion des rapports.
Bonnes pratiques approfondies
Au-delà des bases couvertes ci-dessus, quelques pratiques distinguent une suite E2E robuste d’une suite frustrante. La première est l’isolation totale entre tests : chaque test cree ses propres données, ne depend d aucun ordre d exécution, et nettoie ce qu’il a cree si possible. Cette discipline coûte un peu de temps a chaque test écrit mais permet de paralléliser sans risque, ce qui rapporte très vite a l échelle. La deuxième est la convention de nommage des tests : nom-feature.spec.ts avec un describe qui reprend le nom de la feature et des it qui decrivent un comportement, jamais une étape technique. Un test qui s appelle « test 12 » est intestable.
La troisième pratique est l usage parcimonieux des screenshots de debug. Tous les tests Playwright peuvent prendre une capture en cas d échec via la configuration screenshot: 'only-on-failure'. C est suffisant pour 90 % des cas. Ajouter video: 'retain-on-failure' double la verbosite mais aide enormement quand on debugge un test flaky qui depend de l ordre des animations. La quatrième est de ne jamais écrire de test qui depend de la date courante sans figer l horloge cote serveur ou via page.clock, sinon le test cassera fatalement un 1er janvier ou un 29 février.
Cas avancés : iframes, téléchargements, popups
Les cas réels sortent vite des sentiers battus. Pour interagir avec une iframe (un widget de paiement Stripe par exemple), Playwright exposé page.frameLocator() qui scope les selecteurs a l iframe choisie. Pour gérer un téléchargement de PDF déclenche par un clic, on utilisé page.waitForEvent('download') qui attend l événement et exposé un objet Download sur lequel on peut vérifier le nom de fichier ou sauvegarder le contenu. Pour une popup ouverte par window.open, c’est page.waitForEvent('popup') qui retourne la nouvelle Page.
Ces APIs sont rarement utilisées mais quand elles le sont, elles transforment une difficulte de jours en une question de minutes. La doc officielle playwright.dev/docs/events liste tous les événements ecoutables, et le trace viewer aide enormement a confirmer qu un événement a bien ete déclenche au moment attendu.
Ressources officielles
- Documentation Playwright — guide d’installation
- Reference des locators recommandes
- Documentation du trace viewer
Pour la vue d’ensemble stratégique sur les tests modernes, voir le guide principal : Tests modernes en JavaScript en 2026.
Questions fréquentes
Combien de tests E2E faut-il écrire ?
Un par parcours métier critique, pas plus. Inscription, connexion, ajout au panier, paiement, recuperation de mot de passe. Inutile de couvrir chaque CRUD : ce sont des tests d intégration qui s’en chargent mieux et plus vite.
Doit-on tester sur les trois navigateurs ?
En CI sur main, oui — Chromium, Firefox et WebKit detectent souvent des bugs différents. En local pendant le développement, lancer Chromium suffit pour aller vite. Le triple navigateur ralentit par trois la suite.
Playwright peut-il tester une PWA ou un service worker ?
Oui, sans configuration particuliere. Le service worker est intercepte naturellement, et les requetes qu’il genere apparaissent dans le trace viewer.
Les tests E2E peuvent-ils tourner contre la production ?
Techniquement oui, en pratique on l évite : les tests creent des utilisateurs, des commandes, des données qui polluent la base. La pratique saine est d avoir un environnement de pre-production isole, ou de démarrer une instance applicative dans le job CI.
Comment gérer les captchas dans les tests ?
Soit on désactivé le captcha en environnement de test via une variable d environnement, soit on utilisé un mode bypass officiel quand le service en propose un (reCAPTCHA et hCaptcha en ont). Bypass via image-recognition n’est pas une option fiable.