Testes end-to-end (E2E) com Cypress ou Playwright
1. Fundamentos dos Testes End-to-End (E2E)
Testes end-to-end (E2E) validam o fluxo completo de uma aplicação, simulando a interação real de um usuário com o sistema. Diferentemente dos testes unitários, que verificam funções isoladas, ou dos testes de integração, que avaliam a comunicação entre módulos, os testes E2E percorrem toda a stack: frontend, backend, banco de dados e serviços externos.
No ciclo de desenvolvimento, os testes E2E ocupam o topo da pirâmide de testes. Eles são mais lentos e frágeis que os testes unitários, mas oferecem a maior confiança de que o sistema funciona como um todo. Vale a pena investir neles quando:
- O sistema possui fluxos críticos de negócio (checkout, login, cadastro)
- Múltiplos microsserviços precisam ser validados em conjunto
- A equipe precisa de documentação viva sobre o comportamento esperado
O trade-off principal é o custo de manutenção: cada mudança na interface ou no fluxo pode quebrar vários testes E2E. Por isso, a recomendação é limitar os testes E2E aos caminhos felizes e cenários críticos, deixando validações mais granulares para os níveis inferiores da pirâmide.
2. Cypress: Arquitetura e Primeiros Passos
O Cypress executa os testes diretamente no navegador, dentro do mesmo processo da aplicação. Isso permite que ele tenha acesso ao DOM, ao console do navegador e a eventos de rede de forma nativa, sem a necessidade de drivers externos como o WebDriver.
Para iniciar, instale o Cypress em seu projeto:
npm install cypress --save-dev
Após a instalação, execute npx cypress open para abrir o Test Runner. A estrutura de pastas gerada inclui:
cypress/
e2e/ # Testes E2E
fixtures/ # Dados mockados (JSON)
support/ # Comandos customizados e configurações
Comandos essenciais do Cypress:
// testando o login de um sistema
describe('Login Flow', () => {
it('should log in with valid credentials', () => {
cy.visit('https://example.com/login')
cy.get('[data-cy=email]').type('user@example.com')
cy.get('[data-cy=password]').type('securepassword123')
cy.get('[data-cy=submit]').click()
cy.contains('Welcome, User').should('be.visible')
})
})
O encadeamento de comandos é automático: cada comando retorna uma chain, e o Cypress espera automaticamente que elementos existam antes de interagir com eles.
3. Playwright: Arquitetura e Primeiros Passos
O Playwright, desenvolvido pela Microsoft, utiliza o protocolo CDP (Chrome DevTools Protocol) para controlar navegadores Chromium, Firefox e WebKit. Ele suporta execução headless (sem interface gráfica) e headed (com navegador visível), ideal para depuração.
Instalação:
npm init playwright@latest
Durante a configuração, você pode escolher TypeScript, JavaScript e os navegadores desejados. A estrutura gerada:
tests/ # Testes E2E
playwright.config.ts # Configuração central
Comandos essenciais do Playwright:
import { test, expect } from '@playwright/test'
test('should log in with valid credentials', async ({ page }) => {
await page.goto('https://example.com/login')
await page.locator('[data-testid=email]').fill('user@example.com')
await page.locator('[data-testid=password]').fill('securepassword123')
await page.locator('[data-testid=submit]').click()
await expect(page.locator('text=Welcome, User')).toBeVisible()
})
O Playwright oferece seletores modernos como text=, css=, xpath= e a poderosa função getByRole(), que segue as diretrizes de acessibilidade ARIA.
4. Comparação Direta entre Cypress e Playwright
Performance e Paralelismo: O Playwright executa testes em múltiplos workers por padrão, distribuindo o trabalho entre núcleos da CPU. O Cypress também suporta paralelismo, mas requer um plano pago para execução em nuvem (Cypress Cloud). Ambos se integram bem com CI/CD, mas o Playwright é mais leve em ambientes Docker.
Capacidades de Rede: Ambas as ferramentas permitem interceptar requisições HTTP. No Cypress, usa-se cy.intercept():
cy.intercept('POST', '/api/login', { token: 'fake-token' }).as('login')
No Playwright, usa-se page.route():
await page.route('**/api/login', route => {
route.fulfill({ body: JSON.stringify({ token: 'fake-token' }) })
})
Limitações: O Cypress não suporta múltiplas abas ou domínios cruzados em um mesmo teste (embora existam workarounds). O Playwright lida nativamente com iframes, pop-ups e mudanças de domínio. O Cypress, por outro lado, oferece uma experiência de depuração superior com o time-travel (reprodução passo a passo dos comandos).
5. Padrões de Projeto para Testes E2E Robusto
Page Object Model (POM): Crie classes que representem páginas ou componentes:
// login-page.js
class LoginPage {
constructor(page) {
this.page = page
this.emailInput = page.locator('[data-testid=email]')
this.passwordInput = page.locator('[data-testid=password]')
this.submitButton = page.locator('[data-testid=submit]')
}
async login(email, password) {
await this.emailInput.fill(email)
await this.passwordInput.fill(password)
await this.submitButton.click()
}
}
module.exports = { LoginPage }
Fixtures e Dados Dinâmicos: Use arquivos JSON para dados de teste:
// fixtures/users.json
{
"validUser": { "email": "user@example.com", "password": "secure123" },
"invalidUser": { "email": "invalid@example.com", "password": "wrong" }
}
No Cypress: cy.fixture('users').then((users) => { ... })
No Playwright: const users = JSON.parse(fs.readFileSync('fixtures/users.json'))
Tratamento de Estados Assíncronos: Em vez de waits fixos, use assertions que forçam a espera inteligente:
// Evite:
cy.wait(3000)
// Prefira:
cy.contains('Loading complete').should('be.visible')
No Playwright, o método waitForSelector() ou toBeVisible() já implementam retries automáticos.
6. Integração Contínua e Relatórios
Cypress no GitHub Actions:
name: Cypress Tests
on: [push]
jobs:
cypress-run:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: cypress-io/github-action@v6
with:
build: npm run build
start: npm start
Playwright no GitHub Actions:
name: Playwright Tests
on: [push]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- run: npm ci
- run: npx playwright install --with-deps
- run: npx playwright test
- uses: actions/upload-artifact@v4
if: failure()
with:
name: playwright-report
path: playwright-report/
Ambas as ferramentas geram relatórios HTML com screenshots e vídeos das falhas, facilitando a depuração remota.
7. Boas Práticas e Armadilhas Comuns
Seletores Estáveis: Evite seletores baseados em classes CSS ou posição (.container > div:nth-child(3)). Prefira data-cy (Cypress) ou data-testid (Playwright), que são independentes de estilo e estrutura.
Isolamento de Estado: Cada teste deve ser independente. Use hooks beforeEach para resetar o estado:
beforeEach(() => {
cy.request('POST', '/api/reset-database')
cy.clearCookies()
cy.clearLocalStorage()
})
Gerenciamento de Dados: Crie e limpe dados de teste via API, não pela interface. Isso acelera os testes e evita efeitos colaterais.
Manutenção Periódica: Revisar a suíte de testes a cada sprint. Remover testes obsoletos, atualizar seletores e adicionar novos cenários críticos. Testes que falham constantemente sem motivo real devem ser desativados ou corrigidos imediatamente.
Referências
- Documentação Oficial do Cypress — Guia completo de instalação, comandos e boas práticas para testes E2E com Cypress.
- Documentação Oficial do Playwright — Referência completa sobre API, seletores, multi-navegador e integração CI/CD.
- Cypress vs Playwright: Which Test Framework is Right for You? — Comparação detalhada entre as duas ferramentas, incluindo performance e casos de uso.
- Page Object Model Pattern in Playwright — Tutorial oficial sobre como implementar o padrão Page Object com Playwright.
- Cypress Best Practices Guide — Lista de recomendações oficiais para evitar testes frágeis e melhorar a manutenção.
- Playwright: Test Generator — Ferramenta que gera testes automaticamente a partir da interação com o navegador, ideal para iniciantes.
- Continuous Integration with Cypress — Guia oficial para configurar Cypress em pipelines CI/CD como GitHub Actions e Jenkins.