Apprendre / Aide-mémoire
Aide-mémoire Playwright
Les API de Playwright Test que vous utilisez tous les jours, sur une seule fiche. Les locators sont classés dans l’ordre recommandé par Playwright : rôle, label et texte avant les test ids, puis CSS ou XPath en dernier recours. Toutes les assertions présentées ici sont web-first : elles réessaient jusqu’à réussir ou expirer, vous n’avez donc pas besoin d’attentes manuelles.
Vérifié avec Playwright Test 1.5x, TypeScript
Exécuter et déboguer
| Tâche | Code |
|---|---|
| Nouveau projet | npm init playwright@latest |
| Lancer tous les tests | npx playwright test |
| Un fichier, une ligne | npx playwright test tests/login.spec.ts:12 |
| Par titre ou par tag | npx playwright test --grep @smoke |
| Un seul projet navigateur | npx playwright test --project=chromium |
| Voir le navigateur | npx playwright test --headed |
| Avancer pas à pas avec l’inspecteur | npx playwright test --debug |
| Mode UI (watch, voyage dans le temps) | npx playwright test --ui |
| Seulement les échecs du dernier run | npx playwright test --last-failed |
| Traquer un test flaky | npx playwright test my.spec.ts --repeat-each=20 |
| Répartir (shard) sur plusieurs machines CI | npx playwright test --shard=1/4 |
| Ouvrir le rapport HTML | npx playwright show-report |
| Ouvrir une trace | npx playwright show-trace trace.zip |
| Enregistrer un script | npx playwright codegen https://example.com |
Locators (les préférés d’abord)
| Tâche | Code |
|---|---|
| Par rôle et nom accessible | page.getByRole('button', { name: 'Sign in' }) |
| Champ de formulaire par son label | page.getByLabel('Email') |
| Par placeholder | page.getByPlaceholder('Search') |
| Par texte (exact) | page.getByText('Welcome back', { exact: true }) |
| Par test id (data-testid) | page.getByTestId('checkout-submit') |
| CSS | page.locator('#orders tbody tr') |
| XPath | page.locator('xpath=//table[@id="orders"]//tr') |
| Filtrer par texte | page.getByRole('row').filter({ hasText: 'Mei Lin' }) |
| Filtrer par enfant | page.getByRole('listitem').filter({ has: page.getByRole('button', { name: 'Buy' }) }) |
| Chaîner dans un élément trouvé | row.getByRole('button', { name: 'Cancel' }) |
| Choisir parmi plusieurs | .first() .last() .nth(2) |
| Dans une iframe | page.frameLocator('#payment').getByLabel('Card number') |
Les locators sont paresseux : rien n’est recherché avant une action ou une assertion, définissez-les donc une fois et réutilisez-les.
Actions
| Tâche | Code |
|---|---|
| Naviguer (relatif à baseURL) | await page.goto('/login') |
| Cliquer | await locator.click() |
| Remplacer la valeur d’un champ | await locator.fill('mei@example.com') |
| Taper touche par touche | await locator.pressSequentially('abc', { delay: 50 }) |
| Appuyer sur une touche | await locator.press('Enter') |
| Case à cocher ou bouton radio | await locator.check() / await locator.uncheck() |
| Sélectionner une option | await page.getByLabel('Country').selectOption('UK') |
| Téléverser un fichier | await locator.setInputFiles('fixtures/invoice.pdf') |
| Survoler | await locator.hover() |
| Glisser-déposer | await source.dragTo(target) |
| Accepter la prochaine boîte de dialogue | page.once('dialog', (d) => d.accept()) |
Assertions web-first
| Tâche | Code |
|---|---|
| Visible / masqué | await expect(locator).toBeVisible() / .toBeHidden() |
| Texte exact | await expect(locator).toHaveText('Order placed') |
| Le texte contient | await expect(locator).toContainText('placed') |
| Valeur d’un champ | await expect(input).toHaveValue('42') |
| Nombre de correspondances | await expect(page.getByRole('row')).toHaveCount(5) |
| Activé / désactivé / coché | toBeEnabled() toBeDisabled() toBeChecked() |
| Attribut | await expect(menu).toHaveAttribute('aria-expanded', 'true') |
| URL et titre | await expect(page).toHaveURL(/dashboard/) / .toHaveTitle('Orders') |
| Négation | await expect(spinner).not.toBeVisible() |
| Continuer après un échec | await expect.soft(total).toHaveText('£40.00') |
| Réessayer n’importe quelle vérification asynchrone | await expect.poll(async () => (await api.get(url)).status()).toBe(200) |
| Comparaison visuelle | await expect(page).toHaveScreenshot('home.png') |
Réseau et API
| Tâche | Code |
|---|---|
| Mocker une réponse | await page.route('**/api/orders', (r) => r.fulfill({ json: [] })) |
| Faire échouer une requête | await page.route('**/api/orders', (r) => r.abort()) |
| Modifier une vraie réponse | await page.route('**/api/me', async (r) => { const res = await r.fetch(); const json = await res.json(); await r.fulfill({ response: res, json: { ...json, plan: 'pro' } }); }) |
| Attendre une réponse | const res = page.waitForResponse('**/api/orders');
await page.getByRole('button', { name: 'Save' }).click();
expect((await res).ok()).toBeTruthy(); |
| Test d’API | test('list users', async ({ request }) => {
const res = await request.get('/api/users');
await expect(res).toBeOK();
expect(await res.json()).toHaveLength(3);
}); |
Structure, fixtures et hooks
| Tâche | Code |
|---|---|
| Regrouper des tests | test.describe('checkout', () => { /* tests */ }) |
| Exécuter avant chaque test | test.beforeEach(async ({ page }) => { await page.goto('/'); }) |
| Taguer un test | test('pays by card', { tag: '@smoke' }, async ({ page }) => { /* ... */ }) |
| Ignorer sous condition | test.skip(browserName === 'webkit', 'Not supported on WebKit') |
| Parallèle au sein d’un fichier | test.describe.configure({ mode: 'parallel' }) |
| Fixture personnalisée | export const test = base.extend<{ loginPage: LoginPage }>({
loginPage: async ({ page }, use) => { await use(new LoginPage(page)); },
}); |
| Réutiliser une session connectée | test.use({ storageState: 'playwright/.auth/user.json' }) |
| Nommer une étape dans le rapport | await test.step('add to cart', async () => { /* ... */ }) |
playwright.config.ts
| Paramètre | Exemple |
|---|---|
| URL de base pour page.goto | use: { baseURL: 'http://localhost:3000' } |
| Preuves en cas d’échec | use: { trace: 'on-first-retry', screenshot: 'only-on-failure', video: 'retain-on-failure' } |
| Retries uniquement en CI | retries: process.env.CI ? 2 : 0 |
| Parallélisme | fullyParallel: true, workers: process.env.CI ? 2 : undefined |
| Échouer si un test.only est commité | forbidOnly: !!process.env.CI |
| Démarrer l’application d’abord | webServer: { command: 'npm run start', url: 'http://localhost:3000', reuseExistingServer: !process.env.CI } |
| Navigateurs | projects: [{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }] |
| Timeouts par défaut | timeout: 30_000, expect: { timeout: 5_000 } |