Use Cases (Mis à jour: 03/06/2026)

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.

Tests E2E avec Claude Code : guide Playwright/Cypress et CI

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

CasPourquoi l’E2E est pertinentAttentes clés
Connexion vers tableau de bordAuthentification, redirection et droits se croisentTitre, état utilisateur, déconnexion, erreur
Achat ou formulaire de contactImpact direct sur revenus ou leadsNuméro de commande, confirmation, anti double envoi
Article vers PDF ou formationProtège les revenus hors publicitéhref du CTA, événement, visibilité mobile
Action admin dangereuseÉvite suppression et fuite de droitsModale, audit log, contrôle par rôle
Pages localiséesLes traductions cassent souvent les liensURL 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

AspectPlaywrightCypress
NavigateursChromium, Firefox, WebKitFamille Chrome, Firefox, Edge
ParallélisationIntégrée à Playwright TestSouvent conçue avec Dashboard
Onglets et contextes multiplesTrès bon supportPlus contraint
DébogageTrace Viewer, UI mode, HTML reportInterface interactive agréable
Meilleur usageCI, 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.

#Claude Code #tests E2E #Playwright #Cypress #CI
Gratuit

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.

Masa

À propos de l'auteur

Masa

Ingénieur spécialisé dans les workflows pratiques avec Claude Code.