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