Se você tem 95% de cobertura de unit tests mas nunca testou o fluxo completo de cadastro até pagamento, você tem uma falsa sensação de segurança. Os bugs que mais doem em produção vivem nas integrações entre componentes, nos caminhos de usuário que ninguém testou de ponta a ponta.
Por que Playwright e não Cypress
Ambos são bons. Playwright tem vantagens práticas: suporte nativo a múltiplas abas e iframes, execução paralela por teste, auto-waiting mais confiável, e suporte a Chromium, Firefox e WebKit no mesmo setup. Para times que já usam Vitest ou Jest, a API do Playwright se integra melhor ao ecossistema existente.
// playwright.config.ts
import { defineConfig } from '@playwright/test'
export default defineConfig({
testDir: './e2e',
timeout: 30000,
retries: process.env.CI ? 2 : 0,
use: {
baseURL: 'http://localhost:3000',
screenshot: 'only-on-failure',
trace: 'on-first-retry',
},
webServer: {
command: 'pnpm dev',
port: 3000,
reuseExistingServer: !process.env.CI,
},
})
Seu primeiro teste E2E
Um teste E2E que vale a pena: o fluxo de login completo. Não é o teste mais complexo, mas valida camadas inteiras: frontend, API, autenticação, sessão.
// e2e/login.spec.ts
import { test, expect } from '@playwright/test'
test('user can log in and see dashboard', async ({ page }) => {
await page.goto('/login')
await page.fill('[data-testid="email"]', 'user@example.com')
await page.fill('[data-testid="password"]', 'password123')
await page.click('[data-testid="submit"]')
await expect(page).toHaveURL('/dashboard')
await expect(page.locator('h1')).toContainText('Welcome')
})
Observe: data-testid como seletores, não classes CSS ou texto visível. Seletores estáveis sobrevivem a mudanças de UI.
Page Object Model: não repita seletores
Quando você tem 20 testes que usam o mesmo formulário de login, selectors duplicados viram manutenção pesada. Page Objects encapsulam a interação com cada página:
// e2e/pages/LoginPage.ts
import { Page, expect } from '@playwright/test'
export class LoginPage {
constructor(private readonly page: Page) {}
async goto() {
await this.page.goto('/login')
}
async login(email: string, password: string) {
await this.page.fill('[data-testid="email"]', email)
await this.page.fill('[data-testid="password"]', password)
await this.page.click('[data-testid="submit"]')
}
async expectError(message: string) {
await expect(this.page.locator('[data-testid="error"]'))
.toContainText(message)
}
}
// e2e/login.spec.ts
test('failed login shows error', async ({ page }) => {
const loginPage = new LoginPage(page)
await loginPage.goto()
await loginPage.login('wrong@example.com', 'bad')
await loginPage.expectError('Credenciais inválidas')
})
Testando estados de erro
Onde E2E agrega mais valor é testando caminhos de erro que unit tests não cobrem: timeout de API, erro de rede, estado de loading, comportamento offline.
test('shows error when API is down', async ({ page }) => {
// Intercepta a API e simula falha
await page.route('**/api/orders', route => route.abort('connectionrefused'))
await page.goto('/orders')
await expect(page.locator('[data-testid="error-message"]'))
.toContainText('Não foi possível carregar os pedidos')
await expect(page.locator('[data-testid="retry-button"]'))
.toBeVisible()
})
Dados de teste: seeding e cleanup
E2E tests precisam de dados consistentes. Seus dados de teste devem ser criados antes de cada teste e removidos depois:
// e2e/fixtures.ts
import { test as base } from '@playwright/test'
export const test = base.extend<{ testUser: User }>({
testUser: async ({ request }, use) => {
// Cria usuário de teste via API
const res = await request.post('/api/test/users', {
data: { email: `test-${Date.now()}@example.com`, password: 'test123' }
})
const user = await res.json()
await use(user)
// Cleanup
await request.delete(`/api/test/users/${user.id}`)
},
})
Execução em CI
Playwright em CI precisa de passos específicos: instalar browsers, rodar com --reporter=html, e上传 artifacts em caso de falha. No GitHub Actions:
- name: Install Playwright
run: pnpm exec playwright install --with-deps
- name: Run E2E tests
run: pnpm test:e2e
- name: Upload test report
if: failure()
uses: actions/upload-artifact@v4
with:
name: playwright-report
path: playwright-report/
Onde parar
Não teste tudo com E2E. Testes E2E são lentos, frágeis, e caros de manter. Use-os para os fluxos críticos de usuário: login, checkout, criação de conta, fluxos de pagamento. Para o resto, unit tests e integration tests são suficientes.
A regra: se uma falha nesse fluxo gera perda de receita ou dados, teste E2E. Se é uma feature secundária, teste com unit ou integration.
Curtiu o conteúdo?
Construo produtos web e soluções com IA do jeito certo — arquitetura sólida, código sustentável e entrega real.
Vamos conversar