Tests E2E avec Playwright : Stratégies pour Applications Full-Stack
Les tests E2E sont les tests les plus proches de l’expérience utilisateur réelle — et les plus difficiles à maintenir. Playwright a changé la donne en proposant un outil rapide, fiable et multi-navigateur. Mais l’outil seul ne suffit pas : il faut une stratégie.
Ce guide couvre les patterns et pratiques que j’utilise en production sur des projets Symfony, Laravel et Next.js.
Pourquoi Playwright est devenu le standard
Cypress dominait le marché pendant des années, mais Playwright l’a dépassé sur plusieurs points critiques :
- Multi-navigateur natif : Chromium, Firefox, WebKit — sans plugins
- Isolation par contexte : chaque test tourne dans un BrowserContext isolé, pas besoin de reset global
- Auto-wait intelligent : Playwright attend automatiquement que les éléments soient actionnables
- API moderne : async/await natif, pas de chaînes
.then() - Tracing intégré : replay visuel des tests échoués avec screenshots et vidéos
Stratégie de tests E2E
La pyramide de tests revisitée
La pyramide classique (beaucoup d’unitaires, peu d’E2E) reste valable, mais les proportions évoluent :
/ E2E \ → 10-15% : parcours critiques
/ Intégration \ → 25-30% : API, composants connectés
/ Unitaires \ → 55-65% : domaine, utils, composables
Les E2E ne doivent couvrir que les parcours critiques — ceux dont la casse coûte de l’argent : inscription, achat, paiement, onboarding.
Pour les tests unitaires de domaine qui constituent la base de la pyramide, voyez mes articles sur CQRS avec Symfony et la Clean Architecture Laravel — les deux montrent comment isoler et tester la logique métier sans navigateur.
Identifier les scénarios E2E pertinents
Critères de sélection :
- Impact business élevé : le parcours d’achat, pas la page “mentions légales”
- Traversée de couches : le test implique frontend + API + base de données
- Non couvert par d’autres niveaux : si un test unitaire suffit, pas besoin d’E2E
- Scénarios multi-étapes : inscription → vérification email → premier achat
Patterns Playwright pour la production
Page Object Model (POM)
Le POM encapsule les interactions avec une page dans une classe dédiée :
// pages/checkout.page.ts
export class CheckoutPage {
constructor(private page: Page) {}
async fillShippingAddress(address: Address) {
await this.page.getByLabel('Rue').fill(address.street)
await this.page.getByLabel('Ville').fill(address.city)
await this.page.getByLabel('Code postal').fill(address.zipCode)
}
async selectPaymentMethod(method: 'card' | 'paypal') {
await this.page.getByRole('radio', { name: method }).click()
}
async placeOrder() {
await this.page.getByRole('button', { name: 'Confirmer la commande' }).click()
await this.page.waitForURL(/\/order-confirmation\//)
}
async getOrderNumber() {
return this.page.getByTestId('order-number').textContent()
}
}
Le test devient lisible comme une spec :
test('complete checkout flow', async ({ page }) => {
const checkout = new CheckoutPage(page)
await checkout.fillShippingAddress(testAddress)
await checkout.selectPaymentMethod('card')
await checkout.placeOrder()
const orderNumber = await checkout.getOrderNumber()
expect(orderNumber).toMatch(/^ORD-\d+$/)
})
Fixtures pour l’état initial
Les fixtures Playwright permettent de préparer l’état avant chaque test :
// fixtures/auth.fixture.ts
type AuthFixtures = {
authenticatedPage: Page
}
export const test = base.extend<AuthFixtures>({
authenticatedPage: async ({ page }, use) => {
await page.goto('/login')
await page.getByLabel('Email').fill('test@example.com')
await page.getByLabel('Mot de passe').fill('password')
await page.getByRole('button', { name: 'Connexion' }).click()
await page.waitForURL('/dashboard')
await use(page)
},
})
Visual regression testing
test('product card renders correctly', async ({ page }) => {
await page.goto('/products/featured')
await expect(page.getByTestId('product-card').first()).toHaveScreenshot(
'product-card.png',
{ maxDiffPixelRatio: 0.01 }
)
})
Les screenshots de référence sont versionnés dans git. Toute régression visuelle est détectée automatiquement en CI.
CI/CD avec GitHub Actions
# .github/workflows/e2e.yml
name: E2E Tests
on: [push, pull_request]
jobs:
e2e:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- name: Install dependencies
run: npm ci
- name: Install Playwright browsers
run: npx playwright install --with-deps
- name: Run E2E tests
run: npx playwright test
- name: Upload report
if: failure()
uses: actions/upload-artifact@v4
with:
name: playwright-report
path: playwright-report/
Le if: failure() sur l’upload du report est clé : on ne stocke les artefacts que quand les tests échouent, pour économiser le stockage.
Spécificités par framework
Symfony : utilisez le WebTestCase pour les tests d’intégration API, réservez Playwright pour les parcours E2E qui testent le frontend. Le pattern CQRS facilite le setup des fixtures en exécutant directement des commands.
Laravel : les tests Feature de Laravel couvrent l’API. Playwright couvre le frontend Inertia/Livewire. Utilisez des seeders dédiés pour l’état E2E.
Next.js : les React Server Components changent la donne pour les E2E — le HTML arrive pré-rendu, donc les tests sont naturellement plus rapides et stables.
Pour les projets multi-framework en monorepo, Playwright se configure une fois à la racine et cible les différentes apps via webServer dans la config.
En résumé
Une stratégie E2E robuste avec Playwright repose sur :
- Peu de tests, bien choisis : parcours critiques uniquement
- Page Object Model : maintenabilité et lisibilité
- Fixtures : état reproductible sans magie
- CI/CD : exécution automatique sur chaque PR
- Visual regression : détection des régressions visuelles
Playwright est l’outil. La stratégie est le multiplicateur.
Kevin De Vaubree
Développeur Full-Stack Senior