Skill: playwright-e2e
Padrão para criar e atualizar testes E2E com Playwright. Garante que toda mudança que afeta UI ou fluxo crítico tenha cobertura, evidência salva e asserções determinísticas — sem sleep arbitrário, sem mock pra fazer passar.
Trigger
- Quando a task envolve fluxo de usuário ponta a ponta (login, checkout, onboarding, etc.).
- Quando uma feature web nova for entregue e ainda não tem cobertura E2E.
- Quando um bug se manifestar na UI e a regressão exigir teste.
- Quando o usuário pedir explicitamente "escreve teste e2e", "playwright", "smoke test web".
- Antes de fechar PR que toca rota, página ou interação visível.
Steps
- Identifique o cenário. Liste o caminho feliz e os principais erros (input inválido, sessão expirada, 404/500, viewport mobile/desktop). Cada cenário vira um
test(...). - Crie o arquivo em
tests/e2e/<feature>.spec.ts. Nome emkebab-casecasando com a feature (ex.:tests/e2e/login.spec.ts). - Importe fixtures padrão do Playwright (
test,expect) e qualquer fixture customizada do projeto (tests/e2e/fixtures/). - Configure o
test.describecom nome legível (ex.:Login flow). Agrupe cenários relacionados. - Use Page Object quando o mesmo seletor aparecer em 3+ testes. Caso contrário, seletores inline são aceitáveis.
- Asserte estado final com
await expect(...). Asserte URL, DOM visível e textos esperados — não asserte só ausência de erro. - Confirme evidência.
playwright.config.tsjá ligatrace: 'on',screenshot: 'only-on-failure'evideo: 'on-first-retry'. Não desligue isso por teste. - Rode local com
npx playwright test tests/e2e/<feature>.spec.ts --reporter=list. Verifique HTML report emplaywright-report/. - Commit. Inclua o arquivo
.spec.ts. Não commitetest-results/nemplaywright-report/(estão no.gitignore). - No PR, anexe screenshot do estado final do caminho feliz e descreva os cenários cobertos.
Padrões
- Naming:
<feature>.spec.ts,<feature>.<sub>.spec.tspara sub-fluxos longos. - Localização: sempre
tests/e2e/. Page Objects emtests/e2e/pages/<Page>.ts. - Seletores: prefira
getByRole,getByLabel,getByTestId. Evitelocator('div.classe123')— frágil. - Espera: nunca
await page.waitForTimeout(ms). Useawait expect(locator).toBeVisible(),toHaveURL,toHaveText. - Asserções: prefira
await expect(...).toBeVisible()em vez deif (locator.isVisible()). Auto-retry built-in. - Setup/Teardown: use
test.beforeEachpara login/seed;test.afterEachsó se realmente precisar limpar. - Dados: gere dados únicos por teste (
crypto.randomUUID()) para evitar colisão entre runs paralelos. - Viewports: cubra mobile (
375x667), tablet (768x1024) e desktop (1280x800) quando o layout muda. - i18n: se o app é multi-locale, parametrize
localenotest.use({ locale: 'pt-BR' })ou rode como projeto. - Sem
console.logdeixado nos specs. Usetest.info().annotationsse precisar anotar.
Definition of Done
- Spec roda local sem erro:
npx playwright test tests/e2e/<feature>.spec.ts. - Cenários documentados: caminho feliz + ao menos 1 erro + 1 viewport alternativo (quando aplicável).
- Evidência salva em
test-results/(trace, screenshot, vídeo conforme config). - HTML report gerado em
playwright-report/e validado visualmente. - PR menciona o screenshot do estado final e lista os cenários cobertos.
- Nenhum
waitForTimeoutousleeparbitrário no código. - Seletores resistentes (role/label/testid), não classes CSS voláteis.
- CI (
.github/workflows/ci.yml) verde para o jobplaywright.
Exemplo
import { test, expect } from '@playwright/test';
test.describe('Login flow', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/login');
});
test('user logs in with valid credentials', async ({ page }) => {
await page.getByLabel('Email').fill('user@example.com');
await page.getByLabel('Password').fill('correct-horse-battery');
await page.getByRole('button', { name: 'Sign in' }).click();
await expect(page).toHaveURL(/\/dashboard$/);
await expect(page.getByRole('heading', { name: /welcome/i })).toBeVisible();
});
test('shows error on invalid password', async ({ page }) => {
await page.getByLabel('Email').fill('user@example.com');
await page.getByLabel('Password').fill('wrong');
await page.getByRole('button', { name: 'Sign in' }).click();
await expect(page.getByRole('alert')).toContainText(/invalid credentials/i);
await expect(page).toHaveURL(/\/login$/);
});
});
Notas
- Configuração canônica em
playwright.config.ts(raiz do projeto). Não duplique opções no spec. - Testes flaky são bug. Se um teste falha intermitente, abra issue e marque com
test.fixme(...)até resolver — não usetest.skipsilenciosamente. - Para debugar local:
npx playwright test --uiabre o time-travel debugger. - Trace viewer:
npx playwright show-trace test-results/<trace>.zip.