Tests E2E avec Claude Code : guide Playwright/Cypress et CI
Concevez des tests E2E Playwright avec Claude Code : CI, pièges, exemples exécutables et CTA.
Choisir d’abord ce que le test E2E doit protéger
Un test de connexion généré par Claude Code et passé au vert ne suffit pas à sécuriser une mise en production. Un test E2E vérifie un parcours utilisateur dans un vrai navigateur. Il est donc plus lent et plus coûteux à diagnostiquer qu’un test unitaire. Si chaque détail d’interface devient un test E2E, la CI devient bruyante et l’équipe cesse de prendre les échecs au sérieux.
Commencez par les parcours qui coûtent cher quand ils cassent : connexion, achat, formulaire de contact, action dangereuse dans l’administration, CTA d’article vers un produit ou une formation. Sur ClaudeCodeLab, Masa a déjà vu une page s’afficher correctement alors que le lien final vers la formation était cassé. Le rendu semblait bon, mais le chemin de monétisation ne fonctionnait plus.
Ce guide s’appuie sur la documentation actuelle de Playwright installation, des locators, de la CI et du Trace Viewer. Pour compléter, lisez aussi l’automatisation des tests API et le guide des stratégies de test.
flowchart LR
A["Demander à Claude Code"] --> B["Choisir trois parcours critiques"]
B --> C["Écrire les tests Playwright"]
C --> D["Analyser trace et rapport"]
D --> E["Bloquer la CI si besoin"]
C --> F["Vérifier CTA et revenus"]
Un meilleur prompt pour Claude Code
Indiquez le périmètre, les critères de réussite, les cas d’échec, les fichiers autorisés et les commandes de vérification. Sinon, Claude Code risque de produire des tests fragiles basés sur des classes CSS. Playwright recommande des locators comme getByRole, getByLabel, getByText et getByTestId, car ils fonctionnent avec l’attente automatique et les assertions réessayées.
Objectif : tests E2E pour connexion, achat et inscription newsletter.
Outil : Playwright Test. Ajouter une comparaison courte avec Cypress.
Règles :
- Privilégier role, label, text ou testid plutôt que les classes CSS.
- Couvrir les erreurs, pas seulement le parcours idéal.
- Utiliser workers: 1 en CI et trace: on-first-retry.
- Vérifier que le href du CTA de monétisation existe.
Fichiers autorisés :
- tests/e2e/**/*.spec.ts
- playwright.config.ts
Vérification :
- npx playwright test
- npx playwright test --headed pour le diagnostic local
Précisez aussi ce qui est hors périmètre. Paiements réels, envoi réel d’e-mails, clics publicitaires et écritures CRM ne doivent pas partir à chaque E2E. Utilisez un mode test, des mocks ou des tests de contrat API. Pour ces frontières, combinez ce guide avec l’implémentation webhook et l’implémentation analytics.
Commandes de départ
Sur un nouveau projet, commencez avec ces commandes. La documentation officielle précise que chaque version de Playwright dépend de binaires navigateur précis. Après une mise à jour de Playwright, réinstallez donc les navigateurs. En CI Linux, --with-deps installe aussi les dépendances système.
npm init playwright@latest
npx playwright install
npx playwright test
npx playwright test --headed
npx playwright test --ui
npx playwright show-report
--headed ouvre un navigateur visible pour diagnostiquer en local. --ui ouvre le mode interactif. En CI, l’exécution doit rester headless dans la plupart des cas. Si un test passe localement mais échoue en CI, vérifiez animations, viewport, polices, fuseau horaire, vitesse CPU et attentes d’API externes.
Exemple Playwright exécutable
Le spec suivant est autonome. Il construit une petite page avec page.setContent, donc il ne demande pas de serveur applicatif. Copiez-le dans tests/e2e/claude-code-e2e.spec.ts après installation de Playwright. Dans une vraie application, remplacez renderDemoApp(page) par page.goto('/login') et adaptez les labels ou data-testid.
// tests/e2e/claude-code-e2e.spec.ts
import { test, expect, type Page } from '@playwright/test';
async function renderDemoApp(page: Page) {
await page.setContent(`
<!doctype html>
<main>
<form id="login-form" aria-label="Login form">
<label>Email <input id="email" aria-label="Email" /></label>
<label>Password <input id="password" aria-label="Password" type="password" /></label>
<button type="submit">Log in</button>
<p id="login-error" role="alert" hidden></p>
</form>
<section id="dashboard" hidden>
<h1>Dashboard</h1>
<a data-testid="training-cta" href="/training/">Book Claude Code training</a>
<button id="add-plan">Add Pro plan to cart</button>
<span data-testid="cart-count">0</span>
<button id="buy">Complete purchase</button>
<p data-testid="order-status" role="status"></p>
<label>Newsletter email <input id="newsletter-email" aria-label="Newsletter email" /></label>
<button id="newsletter-button" type="button">Join newsletter</button>
<p id="newsletter-error" role="alert" hidden></p>
<p data-testid="newsletter-status" role="status"></p>
</section>
</main>
<script>
const state = { cart: 0 };
document.querySelector('#login-form').addEventListener('submit', (event) => {
event.preventDefault();
const email = document.querySelector('#email').value;
const password = document.querySelector('#password').value;
const error = document.querySelector('#login-error');
if (email === 'masa@example.com' && password === 'password123') {
document.querySelector('#login-form').hidden = true;
document.querySelector('#dashboard').hidden = false;
error.hidden = true;
return;
}
error.textContent = 'Authentication failed';
error.hidden = false;
});
document.querySelector('#add-plan').addEventListener('click', () => {
state.cart += 1;
document.querySelector('[data-testid="cart-count"]').textContent = String(state.cart);
});
document.querySelector('#buy').addEventListener('click', () => {
const status = document.querySelector('[data-testid="order-status"]');
status.textContent = state.cart === 0 ? 'Cart is empty' : 'Order ORD-1001 completed';
});
document.querySelector('#newsletter-button').addEventListener('click', () => {
const email = document.querySelector('#newsletter-email').value;
const error = document.querySelector('#newsletter-error');
const status = document.querySelector('[data-testid="newsletter-status"]');
if (!email.includes('@')) {
error.textContent = 'Enter a valid email';
error.hidden = false;
status.textContent = '';
return;
}
error.hidden = true;
status.textContent = 'Thanks, we will send the checklist.';
});
</script>
`);
}
async function loginAsDemoUser(page: Page) {
await page.getByLabel('Email').fill('masa@example.com');
await page.getByLabel('Password').fill('password123');
await page.getByRole('button', { name: 'Log in' }).click();
}
test.describe('Claude Code E2E starter', () => {
test('use case 1: login shows dashboard and keeps the training CTA', async ({ page }) => {
await renderDemoApp(page);
await loginAsDemoUser(page);
await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
await expect(page.getByTestId('training-cta')).toHaveAttribute('href', '/training/');
});
test('use case 2: checkout flow creates an order number', async ({ page }) => {
await renderDemoApp(page);
await loginAsDemoUser(page);
await page.getByRole('button', { name: 'Add Pro plan to cart' }).click();
await expect(page.getByTestId('cart-count')).toHaveText('1');
await page.getByRole('button', { name: 'Complete purchase' }).click();
await expect(page.getByTestId('order-status')).toContainText('ORD-1001');
});
test('use case 3: newsletter validation blocks invalid leads', async ({ page }) => {
await renderDemoApp(page);
await loginAsDemoUser(page);
await page.getByRole('button', { name: 'Join newsletter' }).click();
await expect(page.getByRole('alert')).toContainText('Enter a valid email');
await page.getByLabel('Newsletter email').fill('reader@example.com');
await page.getByRole('button', { name: 'Join newsletter' }).click();
await expect(page.getByTestId('newsletter-status')).toContainText('Thanks');
});
});
Ce fichier couvre trois cas concrets : connexion, achat et génération de lead. Il vérifie aussi le href du CTA de formation, un point souvent oublié par les suites trop fines.
Configuration Playwright pratique
La configuration doit conserver des preuves en cas d’échec. La documentation CI de Playwright recommande workers: 1 en CI pour privilégier stabilité et reproductibilité.
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests/e2e',
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
use: {
baseURL: process.env.BASE_URL ?? 'http://127.0.0.1:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
],
});
trace: 'on-first-retry' montre ce qui était visible, quel clic a échoué et quelle assertion a expiré. Claude Code peut alors analyser une trace réelle au lieu d’un simple message d’erreur.
GitHub Actions pour la CI
En CI, les échecs viennent souvent des navigateurs ou des dépendances Linux. Commencez simple, puis ajoutez cache ou sharding seulement si le temps devient un problème.
# .github/workflows/playwright.yml
name: Playwright Tests
on:
push:
branches: [main, master]
pull_request:
branches: [main, master]
jobs:
test:
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-node@v6
with:
node-version: lts/*
- name: Install dependencies
run: npm ci
- name: Install Playwright browsers
run: npx playwright install --with-deps
- name: Run E2E tests
run: npx playwright test
- uses: actions/upload-artifact@v5
if: ${{ !cancelled() }}
with:
name: playwright-report
path: playwright-report/
retention-days: 30
Si vous testez une URL de prévisualisation, passez BASE_URL au workflow. Si l’application doit démarrer dans la CI, ajoutez webServer dans playwright.config.ts.
Cas d’usage à couvrir
| Cas | Pourquoi l’E2E est pertinent | Attentes clés |
|---|---|---|
| Connexion vers tableau de bord | Authentification, redirection et droits se croisent | Titre, état utilisateur, déconnexion, erreur |
| Achat ou formulaire de contact | Impact direct sur revenus ou leads | Numéro de commande, confirmation, anti double envoi |
| Article vers PDF ou formation | Protège les revenus hors publicité | href du CTA, événement, visibilité mobile |
| Action admin dangereuse | Évite suppression et fuite de droits | Modale, audit log, contrôle par rôle |
| Pages localisées | Les traductions cassent souvent les liens | URL locale, texte naturel, liens externes |
Donnez ce tableau à Claude Code avant de demander les fichiers. Il testera alors des résultats utiles, pas seulement des clics.
Échecs fréquents
Évitez waitForTimeout(3000). Une attente fixe passe sur une machine et casse en CI. Attendez l’état réel : élément visible, URL changée ou texte de statut.
Évitez .btn-primary:nth-child(2). Un changement de design suffit à casser le test. Préférez role, label, text et data-testid stable.
Ne partagez pas d’état entre tests. Un panier, un Cookie ou un enregistrement créé par un test ne doit pas être requis par le suivant.
Ne négligez pas la différence headed/headless. Polices, GPU, scroll, presse-papiers, upload de fichier et viewport peuvent changer en CI. Gardez headless en CI, puis utilisez --headed ou --ui pour diagnostiquer.
N’appelez pas paiements, e-mails, publicité ou CRM réels à chaque exécution. Utilisez mode test, mocks ou tests de contrat API.
Playwright ou Cypress
| Aspect | Playwright | Cypress |
|---|---|---|
| Navigateurs | Chromium, Firefox, WebKit | Famille Chrome, Firefox, Edge |
| Parallélisation | Intégrée à Playwright Test | Souvent conçue avec Dashboard |
| Onglets et contextes multiples | Très bon support | Plus contraint |
| Débogage | Trace Viewer, UI mode, HTML report | Interface interactive agréable |
| Meilleur usage | CI, multi-navigateur, auth isolée | Équipes front avec Cypress existant |
Cypress reste un bon choix. Si votre équipe a déjà une base Cypress, ne migrez pas par effet de mode. Ajoutez Playwright sur un seul flux critique et comparez instabilité, qualité de trace et temps de CI. Vérifiez toujours dans les docs officielles Playwright et Cypress.
CTA de monétisation et résultat
L’E2E protège aussi les revenus. Un lien de consultation, une carte produit, un formulaire gratuit ou un CTA de formation cassé peut coûter plus cher qu’un bug visuel. En solo, commencez avec l’antisèche Claude Code gratuite. Pour des prompts réutilisables, consultez les produits et modèles. Pour une équipe qui doit cadrer CI, CLAUDE.md, règles de revue et responsabilités E2E, utilisez la formation et consultation Claude Code.
J’ai vérifié l’exemple en extrayant le spec et la configuration comme fragments TypeScript pour valider la syntaxe. L’habitude la plus utile est de nommer l’attente métier dans le test : conserver le CTA de formation. Dans un vrai projet, commencez par trois tests seulement : connexion, achat ou lead, puis CTA d’article. Élargissez après stabilisation en CI.
PDF gratuit: cheatsheet Claude Code
Saisissez votre email et téléchargez une page avec commandes, habitudes de review et workflow sûr.
Nous protégeons vos données et n'envoyons pas de spam.
À propos de l'auteur
Masa
Ingénieur spécialisé dans les workflows pratiques avec Claude Code.
Articles liés
Workflow Obsidian vers CLAUDE.md avec Claude Code
Transformer des notes Obsidian en notes CLAUDE.md concises pour reprendre les sessions sans réexpliquer.
Claude Code Revenue CTA Routing : relier articles, PDF, Gumroad et consultation
Un workflow Claude Code pour orienter les lecteurs vers PDF gratuit, Gumroad ou consultation selon l'intention.
Règles de handoff Claude Code en équipe: preuves, permissions, rollback et revenus
Un format concret pour transmettre un travail Claude Code avec preuves, permissions, rollback, PDF gratuit, Gumroad et consultation.