“`html
Testes E2E com Playwright: guia prático
Vou te mostrar como estruturar testes de ponta a ponta com Playwright de um jeito estável, legível e pronto para rodar localmente e em CI,
evitando os problemas clássicos de flakiness.
1) Estrutura do projeto e estratégia de execução
Antes de escrever qualquer teste, eu organizo o projeto para reduzir ruído e facilitar manutenção. Em E2E, a maior fonte de custo é o “ciclo
de erro”: quanto mais tempo você perde para identificar o que quebrou e por quê, mais a suíte vira um problema.
Minhas escolhas para começar bem:
- Um padrão de pastas para pages/fluxos, fixtures e arquivos de testes.
- Separar testes por intenção (smoke, regressão, pagamentos, permissões etc.).
- Rodar rápido: ter um subconjunto curto que roda em todo commit e um completo em agendamentos.
Separação por tags ajuda muito:
- Smoke: valida rotas e health do fluxo (rápido).
- Regressão: cobre cenários mais detalhados (mais pesado).
- Testes sensíveis: mexem com dados de produção simulada, permissões complexas, integrações.
Regra de ouro: evite testes gigantes que fazem “tudo ao mesmo tempo”. Se um cenário falha, você quer identificar o
ponto exato sem reexecutar o resto.
2) Locators confiáveis e validações úteis
A estabilidade dos E2E costuma depender mais de como você encontra elementos do que de “esperas” no código.
Eu prefiro locators que representem o que o usuário vê e faz, e não detalhes frágeis de implementação.
Boas práticas de locator:
- Preferir “papéis” e acessibilidade: botões por nome, campos por label, itens por role.
- Usar data-testid com critério quando não existir alternativa semântica.
- Evitar seletores genéricos (ex.: div:nth-child, classes que mudam com frequência).
- Construir a asserção com contexto (ex.: dentro do formulário específico).
Validações melhores são específicas e significativas: em vez de só “URL mudou”, eu também verifico conteúdo esperado,
estado do botão e mensagens de erro.
Um padrão que funciona bem é: localizar → interagir → aguardar estado verificável → validar conteúdo.
Isso elimina a dependência de tempos e foca no “resultado” do comportamento.
3) Autenticação, estado e isolamento entre testes
Flakiness em E2E frequentemente nasce do estado global: cookies, localStorage, permissões e dados de sessão.
Para mim, autenticação é onde eu mais invisto tempo em previsibilidade.
Três estratégias (da mais prática à mais robusta):
- Login por teste: funciona no começo, mas aumenta tempo e chance de falha por UI.
- Login uma vez e reusar estado: melhor equilíbrio; reduz repetição.
- Isolar por contexto: cada teste roda com seu estado, garantindo independência.
O que eu considero “estado correto”:
- Usuário autenticado no ambiente correto.
- Permissões equivalentes ao que o teste pretende validar.
- Dados coerentes (sem depender de listas “que podem estar vazias”).
Se você precisa testar várias permissões, crie um “perfil” por conjunto de dados e serialize o estado de cada um.
Isso deixa seus testes mais claros e menos verbosos.
4) Estabilidade: waits corretos, logs e execução em CI
Playwright já ajuda bastante com espera automática para ações e condições. O segredo é você não “combinar” waits fixas com
lógica que deveria ser condicional.
Checklist de estabilidade:
- Evitar wait for timeout (esperas fixas): prefira validar condições (visível, habilitado, texto presente).
- Usar expectativa antes da validação: “depois de clicar, o que deve aparecer?”
- Garantir consistência em endpoints e dados: quando possível, prepare dados de teste.
- Habilitar evidências (screenshots/vídeos/trace) para reduzir tempo de investigação.
Em CI, eu gosto de tratar a suíte como parte do produto: coletar diagnóstico quando falha e manter logs fáceis de correlacionar com o teste.
Quando falhar, você deve conseguir responder rapidamente: qual teste, em que etapa, qual estado e qual evidência.
O objetivo é encurtar o “tempo até o conserto”.
Exemplo prático: autenticar uma vez e testar um fluxo E2E com validações
Abaixo vai um exemplo com storageState para reaproveitar autenticação e um teste que valida o resultado do fluxo,
usando expectativas focadas no comportamento.
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests/e2e',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 2 : undefined,
reporter: [['html', { outputFolder: 'playwright-report' }]],
use: {
baseURL: process.env.BASE_URL || 'http://localhost:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
],
});
// tests/e2e/auth-and-dashboard.spec.ts
import { test, expect } from '@playwright/test';
test.describe('Autenticado - Dashboard', () => {
test.use({ storageState: 'tests/fixtures/auth-state.json' });
test('deve abrir o dashboard e exibir os cards esperados', async ({ page }) => {
await page.goto('/dashboard');
// Locators semânticos e validações do que importa
await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
const cards = page.locator('[data-testid="dashboard-card"]');
await expect(cards.first()).toBeVisible();
await expect(cards).toHaveCountGreaterThan(0); // se quiser, remova caso não exista util; veja ajuste abaixo.
// Exemplo: valida um item específico
await expect(page.getByText('Resumo do mês', { exact: false })).toBeVisible();
});
test('deve permitir navegar para o perfil do usuário', async ({ page }) => {
await page.goto('/dashboard');
await page.getByRole('button', { name: 'Meu perfil' }).click();
await expect(page).toHaveURL(/\/perfil$/);
await expect(page.getByRole('heading', { name: 'Seu perfil' })).toBeVisible();
});
});
// scripts/generate-auth-state.ts (script opcional para criar tests/fixtures/auth-state.json)
import { chromium } from '@playwright/test';
async function main() {
const browser = await chromium.launch();
const context = await browser.newContext();
const page = await context.newPage();
await page.goto(process.env.BASE_URL || 'http://localhost:3000/login');
await page.getByLabel('Email').fill(process.env.TEST_EMAIL || 'user@example.com');
await page.getByLabel('Senha').fill(process.env.TEST_PASSWORD || 'password');
await page.getByRole('button', { name: 'Entrar' }).click();
// Garanta que está logado antes de salvar
await page.waitForURL(/\/dashboard/);
await context.storageState({ path: 'tests/fixtures/auth-state.json' });
await browser.close();
}
main().catch(err => {
console.error(err);
process.exit(1);
});
Observação: se você não quiser usar utilitário como toHaveCountGreaterThan, substitua por
const count = await cards.count(); expect(count).toBeGreaterThan(0).
Quer deixar sua suíte ainda mais forte?
Agora que você tem um caminho prático para testes E2E com Playwright, vale a pena continuar evoluindo:
além de organizar por intenção, foque em locators confiáveis, estado previsível e evidências em falhas.
“`
Sou Apaixonado pela programação e estou trilhando o caminho de ter cada diz mais conhecimento e trazer toda minha experiência vinda do Design para a programação resultando em layouts incríveis e idéias inovadoras! Conecte-se Comigo!