E2E testing com Playwright ou Cypress
1. Introdução aos Testes E2E no Ecossistema React
Testes End-to-End (E2E) simulam a experiência real do usuário em uma aplicação React, validando fluxos completos desde a interface até o backend. Diferentemente de testes unitários (que verificam funções isoladas) ou de integração (que testam módulos combinados), os testes E2E garantem que o sistema como um todo funcione corretamente no navegador.
No ecossistema React, onde componentes interagem com hooks, estado global e APIs externas, os testes E2E são essenciais para capturar regressões visuais e de comportamento. Duas ferramentas dominam esse cenário:
- Playwright (Microsoft): Focado em automação cross-browser, com suporte nativo a Chromium, Firefox e WebKit. Ideal para projetos que exigem testes em múltiplos navegadores e paralelização avançada.
- Cypress (Cypress.io): Conhecido por sua experiência de desenvolvimento rica, com time-travel debugging e recarregamento automático. Excelente para equipes que priorizam produtividade no desenvolvimento de testes.
Ambas são escritas em JavaScript/TypeScript e integram-se perfeitamente com Node.js e React.
2. Configuração Inicial do Ambiente de Testes
Playwright
// Instalação
npm init playwright@latest
// ou
yarn create playwright
// playwright.config.js
const { defineConfig } = require('@playwright/test');
export default defineConfig({
testDir: './e2e',
timeout: 30000,
use: {
baseURL: 'http://localhost:3000',
headless: true,
viewport: { width: 1280, height: 720 },
},
projects: [
{ name: 'chromium', use: { browserName: 'chromium' } },
{ name: 'firefox', use: { browserName: 'firefox' } },
],
});
Cypress
// Instalação
npm install cypress --save-dev
npx cypress open
// cypress.config.js
const { defineConfig } = require('cypress');
export default defineConfig({
e2e: {
baseUrl: 'http://localhost:3000',
supportFile: 'cypress/support/e2e.js',
specPattern: 'cypress/e2e/**/*.cy.js',
},
});
Estrutura de diretórios recomendada:
projeto-react/
├── e2e/ # Playwright
│ ├── tests/
│ └── fixtures/
├── cypress/ # Cypress
│ ├── e2e/
│ ├── fixtures/
│ └── support/
└── playwright.config.js / cypress.config.js
3. Escrevendo o Primeiro Teste E2E
Vamos testar um fluxo de login em uma aplicação React com formulário controlado.
Playwright
// e2e/tests/login.spec.js
const { test, expect } = require('@playwright/test');
test('deve fazer login com sucesso', async ({ page }) => {
await page.goto('/login');
// Localizadores modernos (recomendados)
await page.getByLabel('Email').fill('usuario@exemplo.com');
await page.getByLabel('Senha').fill('senha123');
await page.getByRole('button', { name: 'Entrar' }).click();
// Aguarda redirecionamento
await expect(page).toHaveURL('/dashboard');
await expect(page.getByText('Bem-vindo, Usuário!')).toBeVisible();
});
Cypress
// cypress/e2e/login.cy.js
describe('Fluxo de Login', () => {
it('deve fazer login com sucesso', () => {
cy.visit('/login');
cy.get('input[name="email"]').type('usuario@exemplo.com');
cy.get('input[name="senha"]').type('senha123');
cy.get('button[type="submit"]').click();
cy.url().should('include', '/dashboard');
cy.contains('Bem-vindo, Usuário!').should('be.visible');
});
});
Localizadores modernos: Prefira getByRole, getByText e getByTestId (Playwright) ou cy.contains e cy.get com atributos data-cy (Cypress) em vez de seletores CSS frágeis.
4. Interações Avançadas e Asserções
Aguardando elementos dinâmicos
// Playwright - aguardar elemento assíncrono
await page.waitForSelector('[data-testid="loading-spinner"]', { state: 'hidden' });
await expect(page.getByTestId('user-list')).toBeVisible({ timeout: 10000 });
// Cypress - asserções com retry automático
cy.get('[data-cy="modal"]', { timeout: 10000 }).should('be.visible');
cy.get('[data-cy="save-button"]').should('not.be.disabled');
Testando formulários com React Suspense
// Playwright
test('submeter formulário com lazy loading', async ({ page }) => {
await page.goto('/profile');
await page.getByLabel('Nome').fill('Maria');
await page.getByRole('button', { name: 'Salvar' }).click();
// Aguarda componente lazy carregar
await expect(page.getByText('Perfil atualizado!')).toBeVisible();
});
// Cypress
it('deve exibir modal após submit', () => {
cy.visit('/profile');
cy.get('[data-cy="name-input"]').type('Maria');
cy.get('[data-cy="submit-btn"]').click();
cy.get('[data-cy="success-modal"]', { timeout: 8000 }).should('be.visible');
});
Asserções poderosas:
// Playwright
await expect(page.locator('h1')).toHaveText('Dashboard');
await expect(page.locator('.user-card')).toHaveCount(5);
await expect(page).toHaveScreenshot('dashboard.png');
// Cypress
cy.get('h1').should('have.text', 'Dashboard');
cy.get('.user-card').should('have.length', 5);
cy.get('input').should('have.value', 'usuario@exemplo.com');
5. Mocking de APIs e Dados Externos
Playwright - interceptação de rede
test('exibir estado de carregamento e erro', async ({ page }) => {
// Mock de resposta lenta
await page.route('**/api/users', async route => {
await new Promise(resolve => setTimeout(resolve, 2000));
await route.fulfill({ status: 200, body: JSON.stringify({ users: [] }) });
});
await page.goto('/users');
await expect(page.getByTestId('loading')).toBeVisible();
await expect(page.getByTestId('loading')).toBeHidden();
});
// Mock de erro
await page.route('**/api/login', async route => {
await route.fulfill({ status: 401, body: 'Unauthorized' });
});
Cypress - interceptação de requisições
it('testar fluxo com dados mockados', () => {
cy.intercept('GET', '/api/products', {
fixture: 'products.json'
}).as('getProducts');
cy.intercept('POST', '/api/checkout', {
statusCode: 200,
body: { orderId: '123' }
}).as('checkout');
cy.visit('/products');
cy.wait('@getProducts');
cy.get('[data-cy="buy-button"]').first().click();
cy.wait('@checkout');
cy.contains('Pedido confirmado!').should('be.visible');
});
Mocking GraphQL:
// Playwright
await page.route('**/graphql', async route => {
const request = route.request();
if (request.postDataJSON().operationName === 'GetUser') {
await route.fulfill({
data: { user: { id: 1, name: 'João' } }
});
}
});
6. Gerenciamento de Estado e Dados de Teste
Fixtures com Faker.js
// fixtures/userFactory.js
const { faker } = require('@faker-js/faker');
export function createUser() {
return {
name: faker.person.fullName(),
email: faker.internet.email(),
password: faker.internet.password(),
};
}
// Playwright test
test('registrar novo usuário', async ({ page }) => {
const user = createUser();
await page.goto('/register');
await page.getByLabel('Nome').fill(user.name);
await page.getByLabel('Email').fill(user.email);
await page.getByLabel('Senha').fill(user.password);
await page.getByRole('button', { name: 'Criar conta' }).click();
await expect(page.getByText('Conta criada!')).toBeVisible();
});
Limpeza de dados entre testes
// Playwright - global setup
// global-setup.js
const { request } = require('@playwright/test');
export default async () => {
const api = await request.newContext();
await api.post('/api/reset-database');
};
// playwright.config.js
globalSetup: './global-setup.js',
// Cypress - hook before
beforeEach(() => {
cy.request('POST', '/api/reset-test-data');
cy.setCookie('session_id', 'test-session-123');
});
7. Execução em Pipeline CI/CD
GitHub Actions com Playwright
# .github/workflows/e2e.yml
name: E2E Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- run: npm ci
- run: npx playwright install --with-deps
- run: npm run build
- run: npx playwright test --shard=${{ matrix.shard }}/3
- uses: actions/upload-artifact@v3
if: always()
with:
name: playwright-report
path: playwright-report/
GitLab CI com Cypress
# .gitlab-ci.yml
e2e-tests:
image: cypress/browsers:latest
script:
- npm ci
- npm run build
- npx cypress run --parallel --record --key $CYPRESS_RECORD_KEY
artifacts:
when: always
paths:
- cypress/videos/
- cypress/screenshots/
Dicas para CI:
- Use --workers 4 no Playwright para paralelização
- Ative gravação de vídeo apenas em falhas: video: 'on-first-retry'
- Gere relatórios HTML: npx playwright show-report
8. Boas Práticas e Comparação Final
Page Object Model
// pages/LoginPage.js (Playwright)
export class LoginPage {
constructor(page) {
this.page = page;
this.emailInput = page.getByLabel('Email');
this.passwordInput = page.getByLabel('Senha');
this.submitButton = page.getByRole('button', { name: 'Entrar' });
}
async login(email, password) {
await this.emailInput.fill(email);
await this.passwordInput.fill(password);
await this.submitButton.click();
}
}
// Test
test('login com Page Object', async ({ page }) => {
const loginPage = new LoginPage(page);
await page.goto('/login');
await loginPage.login('user@test.com', '123456');
await expect(page).toHaveURL('/dashboard');
});
Quando escolher Playwright vs Cypress
| Critério | Playwright | Cypress |
|---|---|---|
| Navegadores | Chromium, Firefox, WebKit, Edge | Chromium, Firefox, Edge |
| Paralelismo nativo | Sim (workers) | Sim (dashboard pago) |
| Testes mobile | Emulação de dispositivos | Limitado |
| Debugging | Trace Viewer, VS Code | Time-travel, GUI interativa |
| Comunidade | Crescendo rápido | Maturidade consolidada |
| Curva de aprendizado | Moderada | Baixa |
Recomendação: Use Playwright se precisar de testes cross-browser robustos, paralelização gratuita ou testes mobile. Escolha Cypress se valorizar uma experiência de desenvolvimento interativa, time-travel debugging e integração fácil com React.
Manutenção de testes E2E
- Evite fragilidade: Prefira
getByRolea seletores CSS aninhados - Reduza duplicação: Crie custom commands e Page Objects
- Documente fluxos críticos: Mantenha um README com cenários cobertos
- Execute testes regularmente: Integre no CI e monitore relatórios
- Use dados isolados: Nunca dependa de dados de produção
Referências
- Playwright Documentation — Documentação oficial completa do Playwright, incluindo guias de instalação, API reference e exemplos práticos.
- Cypress Documentation — Documentação oficial do Cypress com tutoriais, guias de configuração e melhores práticas.
- Testing Library: Which Query Should I Use? — Guia oficial sobre seletores acessíveis e localizadores modernos para testes com React.
- Playwright vs Cypress: A Detailed Comparison — Artigo técnico da BrowserStack comparando desempenho, recursos e casos de uso de ambas as ferramentas.
- Cypress: Intercepting Network Requests — Tutorial oficial sobre interceptação e mocking de requisições HTTP no Cypress.
- Playwright: Network Mocking — Guia oficial do Playwright sobre interceptação de redes, mocking de APIs e manipulação de requisições.
- React Testing with Playwright: Best Practices — Artigo técnico da This Dot Labs com padrões de Page Object e configuração para projetos React.