| name | playwright |
| description | Tests cross-navigateur et automation avec Playwright — Chromium, Firefox, WebKit, API, fixtures, Component Testing, Codegen, et CI. |
| version | 1.0.0 |
| author | EVA |
| license | MIT |
| metadata | {"hermes":{"tags":["playwright","e2e","testing","browser","automation","chromium","firefox","webkit"],"related_skills":["cypress","selenium","e2e-testing","integration-testing"]}} |
Playwright — Tests Cross-Navigateur
Overview
Playwright (Microsoft) est un framework d'automation navigateur multi-navigateur (Chromium, Firefox, WebKit). Une seule API pour les 3 moteurs, support natif des shadow DOM, des iframes, des popups, des téléchargements, des network intercepts, et des screenshots/vidéos.
When to Use
- Tests E2E cross-navigateur (Chromium + Firefox + WebKit)
- Automatisation de tâches navigateur (scraping, screenshots, PDF)
- Tests de composants (React, Vue, Svelte, Solid)
- Tests d'API + UI dans le même framework
- Ne pas utiliser pour : tests purement backend, tests mobiles natifs (utiliser Appium/Detox)
Installation
npm init playwright@latest
npm install --save-dev @playwright/test
npx playwright install
npx playwright install-deps
Structure :
e2e/
├── playwright.config.ts
├── tests/
│ ├── login.spec.ts
│ └── dashboard.spec.ts
├── fixtures/
│ └── test-data.ts
└── utils/
└── helpers.ts
Configuration
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './e2e/tests',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: [
['html'],
['json', { outputFile: 'test-results.json' }],
['list'],
],
use: {
baseURL: 'http://localhost:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
actionTimeout: 10000,
navigationTimeout: 30000,
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'firefox',
use: { ...devices[] },
},
{
: ,
: { ...devices[] },
},
{
: ,
: { ...devices[] },
},
{
: ,
: { ...devices[] },
},
],
: {
: ,
: ,
: !process..,
: ,
},
});
Écrire des Tests
import { test, expect } from '@playwright/test';
test('affiche la page d\'accueil', async ({ page }) => {
await page.goto('/');
await expect(page).toHaveTitle(/Mon App/);
await expect(page.locator('h1')).toContainText('Bienvenue');
});
test('connexion réussie', async ({ page }) => {
await page.goto('/login');
await page.fill('[data-test=email]', 'user@example.com');
await page.fill('[data-test=password]', 'secret123');
await page.click('[data-test=submit]');
await expect(page).toHaveURL(/\/dashboard/);
await expect(page.locator('[data-test=welcome]')).toBeVisible();
});
test('vérifie message d\'erreur connexion', async ({ page }) => {
await page.();
page.(, );
page.(, );
page.();
(page.()).();
(page.()).();
});
Sélecteurs
page.locator('button:has-text("Valider")');
page.locator('[data-test=submit]');
page.getByText('Bienvenue');
page.getByRole('button', { name: 'Valider' });
page.getByLabel('Email');
page.getByPlaceholder('Entrez votre email');
page.getByTestId('submit-btn');
await page.getByRole('heading', { name: 'Mon Profil' }).click();
await page.getByRole('button', { name: 'Sauvegarder' }).click();
page.locator('.user-list').filter({ hasText: 'Alice' }).locator('.edit-btn');
await page.waitForSelector('[data-test=loading]');
await page.waitForFunction(() => !document.());
Actions
await page.fill('input#email', 'user@test.com');
await page.type('input#name', 'Alice', { delay: 50 });
await page.click('button#submit');
await page.dblclick('.item');
await page.click('.menu-item', { button: 'right' });
await page.check('#accept-terms');
await page.selectOption('#country', 'FR');
await page.selectOption('#country', { label: 'France', value: 'FR' });
await page.setInputFiles('input[type=file]', 'path/to/file.pdf');
await page.setInputFiles('input[type=file]', []);
await page.evaluate(() => .(, ..));
page.().();
Assertions
await expect(page).toHaveURL('/dashboard');
await expect(page).toHaveTitle(/Mon App/);
await expect(page.locator('h1')).toHaveText('Bienvenue');
await expect(page.locator('.alerte')).toBeVisible();
await expect(page.locator('#loading')).toBeHidden();
await expect(page.locator('button')).toBeEnabled();
await expect(page.locator('input')).toHaveValue('test');
await expect(page.locator('li')).toHaveCount(5);
await expect(page.locator('table')).toContainText('Alice');
await expect(page.())..();
Interception Réseau
test.use({
blockURLs: ['*.google-analytics.com', '*.doubleclick.net'],
});
test('utilisateurs mockés', async ({ page }) => {
await page.route('**/api/users', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify([
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
]),
});
});
await page.goto('/users');
await expect(page.locator('.user-card')).toHaveCount(2);
});
test('attend la fin du chargement', async ({ page }) => {
const responsePromise = page.waitForResponse('**/api/data');
await page.click('[data-test=load]');
response = responsePromise;
(response.()).();
});
page.(, (route) => {
route.();
});
Tests API (sans navigateur)
import { test, expect } from '@playwright/test';
test('API santé', async ({ request }) => {
const response = await request.get('http://localhost:4000/api/health');
expect(response.status()).toBe(200);
expect(await response.json()).toEqual({ status: 'ok' });
});
test('créer un utilisateur', async ({ request }) => {
const response = await request.post('http://localhost:4000/api/users', {
data: { name: 'Alice', email: 'alice@test.com' },
});
expect(response.status()).toBe(201);
const body = await response.json();
expect(body.id).toBeDefined();
});
Fixtures Personnalisées
import { test as base, expect } from '@playwright/test';
type MyFixtures = {
authPage: any;
};
export const test = base.extend<MyFixtures>({
authPage: async ({ page }, use) => {
await page.goto('/login');
await page.fill('[data-test=email]', 'admin@test.com');
await page.fill('[data-test=password]', 'admin123');
await page.click('[data-test=submit]');
await page.waitForURL('/dashboard');
await use(page);
},
});
export { expect };
Trace Viewer et Débogage
npx playwright show-report
npx playwright show-trace trace.zip
npx playwright test --debug
PWDEBUG=1 npx playwright test
npx playwright test --ui
Codegen (Génération de Tests)
npx playwright codegen http://localhost:3000
npx playwright codegen --viewport-size=390,844 http://localhost:3000
Test Mobile et Emulation
test.use({ ...devices['iPhone 14'] });
test.use({
viewport: { width: 390, height: 844 },
isMobile: true,
userAgent: 'Mozilla/5.0 (iPhone; ...)',
});
Parallélisation
export default defineConfig({
fullyParallel: true,
workers: 4,
});
npx playwright test --workers=6
npx playwright test --shard=1/3
CI/CD
name: Playwright Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
project: [chromium, firefox, webkit]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '20' }
- run: npm ci
- run: npx playwright install --with-deps
- run: npx playwright test --project=${{ matrix.project }}
- uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: playwright-report-${{ matrix.project }}
Common Pitfalls
- Tests flaky sur CI — ajouter
retries: 2 et toujours garder les traces sur échec
- Oubli de
await — Playwright lance l'action et continue, sans await le test part avant la fin
- Sélecteurs fragiles — préférer
getByRole(), getByTestId() aux sélecteurs CSS profonds
- Pas assez de timeout dans webServer — certains builds prennent > 60s
- Bloquer les analytics/tiers — accélère les tests ×2
Verification Checklist