Como automatizar testes em aplicações web
1. Introdução à automação de testes web
A automação de testes em aplicações web deixou de ser um diferencial para se tornar uma necessidade em projetos de qualquer escala. Economia de tempo é o benefício mais imediato: um conjunto de testes automatizados pode executar centenas de cenários em minutos, enquanto testes manuais consumiriam horas ou dias. A consistência é outro pilar — testes automatizados executam exatamente as mesmas ações a cada execução, eliminando erros humanos por cansaço ou distração. Já a cobertura permite validar fluxos complexos e casos de borda que seriam impraticáveis manualmente.
A diferença fundamental entre testes manuais e automatizados reside na reprodutibilidade e no custo de execução. Testes manuais são ideais para exploração inicial e validação de usabilidade, enquanto testes automatizados brilham em regressões contínuas. As camadas de teste seguem a pirâmide clássica: testes unitários na base (rápidos e isolados), testes de integração no meio (validando interações entre componentes) e testes funcionais/e2e no topo (simulando fluxos completos do usuário).
2. Estruturação da suíte de testes
Uma suíte bem organizada começa com uma estrutura de diretórios clara. Adote convenções como:
/projeto
/src
/tests
/unit
/integration
/e2e
/fixtures
/mocks
Naming conventions como *.test.js ou *.spec.js facilitam a descoberta automática pelos runners. A escolha do framework depende do contexto: Jest é excelente para projetos React e oferece cobertura integrada; Mocha é flexível e agnóstico; Cypress e Playwright dominam o cenário e2e.
A configuração inicial envolve instalar dependências e definir scripts no package.json:
npm install --save-dev jest @testing-library/react @testing-library/jest-dom
npm install --save-dev playwright @playwright/test
npm install --save-dev supertest axios-mock-adapter
Scripts de execução:
"scripts": {
"test:unit": "jest --coverage",
"test:integration": "jest --config jest.integration.config.js",
"test:e2e": "playwright test",
"test:all": "npm run test:unit && npm run test:integration && npm run test:e2e"
}
3. Testes unitários para componentes e funções
Testes unitários isolam a menor unidade de código — funções puras, hooks ou componentes renderizados. Mocks e stubs substituem dependências externas (API, módulos). O objetivo é testar lógica de negócio e estados de UI sem efeitos colaterais.
Exemplo prático: validação de formulário e cálculo de frete.
// utils/frete.js
export function calcularFrete(cep, peso) {
if (!cep || cep.length !== 8) throw new Error('CEP inválido');
if (peso <= 0) throw new Error('Peso deve ser positivo');
const taxaBase = 10;
const taxaPorKg = 2.5;
return taxaBase + (peso * taxaPorKg);
}
// __tests__/frete.test.js
import { calcularFrete } from '../utils/frete';
describe('calcularFrete', () => {
test('deve calcular frete para CEP válido e peso positivo', () => {
expect(calcularFrete('12345678', 5)).toBe(22.5);
});
test('deve lançar erro para CEP inválido', () => {
expect(() => calcularFrete('123', 5)).toThrow('CEP inválido');
});
test('deve lançar erro para peso zero', () => {
expect(() => calcularFrete('12345678', 0)).toThrow('Peso deve ser positivo');
});
});
Para componentes React, use Testing Library para testar estados:
// __tests__/Formulario.test.jsx
import { render, screen, fireEvent } from '@testing-library/react';
import Formulario from '../components/Formulario';
test('exibe mensagem de erro quando campo obrigatório está vazio', () => {
render(<Formulario />);
fireEvent.click(screen.getByText('Enviar'));
expect(screen.getByText('Nome é obrigatório')).toBeInTheDocument();
});
4. Testes de integração com API e banco de dados
Testes de integração validam a comunicação entre módulos: requisições HTTP, autenticação e persistência. Use supertest para simular chamadas ao servidor Express ou axios-mock-adapter para interceptar chamadas no frontend.
// integration/api.test.js
const request = require('supertest');
const app = require('../src/app');
describe('POST /api/login', () => {
test('deve retornar token para credenciais válidas', async () => {
const response = await request(app)
.post('/api/login')
.send({ email: 'user@test.com', password: '123456' });
expect(response.status).toBe(200);
expect(response.body).toHaveProperty('token');
});
test('deve retornar 401 para credenciais inválidas', async () => {
const response = await request(app)
.post('/api/login')
.send({ email: 'invalido@test.com', password: 'wrong' });
expect(response.status).toBe(401);
});
});
Para banco de dados, configure um ambiente isolado:
// integration/db.test.js
const sqlite3 = require('sqlite3').verbose();
const db = new sqlite3.Database(':memory:');
beforeAll(() => {
db.run('CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)');
db.run('INSERT INTO users (name) VALUES ("Alice")');
});
test('deve retornar usuário por ID', (done) => {
db.get('SELECT name FROM users WHERE id = 1', (err, row) => {
expect(row.name).toBe('Alice');
done();
});
});
afterAll(() => {
db.close();
});
5. Testes end-to-end (e2e) com ferramentas modernas
Testes e2e simulam o comportamento real do usuário no navegador. Playwright e Cypress são as ferramentas mais robustas, oferecendo suporte a múltiplos navegadores, screenshots e vídeos.
Exemplo com Playwright:
// e2e/login.spec.js
const { test, expect } = require('@playwright/test');
test('usuário consegue fazer login com sucesso', async ({ page }) => {
await page.goto('https://meuapp.com/login');
await page.fill('[data-testid="email"]', 'user@test.com');
await page.fill('[data-testid="password"]', '123456');
await page.click('[data-testid="submit"]');
await expect(page).toHaveURL(/dashboard/);
await expect(page.locator('[data-testid="welcome"]')).toContainText('Bem-vindo');
});
test('exibe erro para senha incorreta', async ({ page }) => {
await page.goto('https://meuapp.com/login');
await page.fill('[data-testid="email"]', 'user@test.com');
await page.fill('[data-testid="password"]', 'wrong');
await page.click('[data-testid="submit"]');
await expect(page.locator('[data-testid="error"]')).toBeVisible();
});
Para lidar com elementos dinâmicos e timeouts, use estratégias como waitForSelector e autoWaiting:
await page.waitForSelector('[data-testid="loading-spinner"]', { state: 'hidden' });
await page.waitForTimeout(2000); // evitar race conditions
6. Integração contínua e pipeline de testes
Integrar testes ao pipeline CI/CD garante que falhas sejam detectadas antes do deploy. Exemplo de configuração para GitHub Actions:
# .github/workflows/test.yml
name: Test Suite
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- run: npm ci
- run: npm run test:unit
- run: npm run test:integration
- run: npx playwright install --with-deps
- run: npm run test:e2e
- uses: actions/upload-artifact@v4
if: failure()
with:
name: playwright-screenshots
path: test-results/
Para paralelização, configure o Playwright para rodar em múltiplos workers:
// playwright.config.js
module.exports = {
workers: process.env.CI ? 4 : 1,
fullyParallel: true,
};
Relatórios de cobertura com Istanbul:
npm test -- --coverage --collectCoverageFrom='src/**/*.js'
7. Manutenção e boas práticas em testes automatizados
Testes frágeis são o maior inimigo da automação. Para evitá-los:
- Use
data-testidem vez de classes CSS ou seletores aninhados - Evite depender de timing exatos; prefira
waitForetoBeVisible - Isolar testes (cada teste deve ser independente)
Estratégias de refatoração:
// Ruim: testa implementação
expect(component.state().isLoading).toBe(true);
// Bom: testa comportamento
expect(screen.getByText('Carregando...')).toBeInTheDocument();
Métricas de qualidade recomendadas:
- Cobertura mínima: 80% para unitários, 60% para integração
- Tempo de execução: suíte completa < 10 minutos
- Falsos positivos: menos de 1% das execuções
Manter testes junto com o código (TDD ou BDD) reduz dívida técnica e garante que a suíte evolua organicamente com o projeto.
Referências
- Documentação oficial do Playwright — Guia completo para testes e2e com suporte a múltiplos navegadores
- Testing Library: princípios e boas práticas — Abordagem centrada no usuário para testes de componentes
- Jest: documentação oficial — Framework de testes unitários com cobertura integrada
- Supertest: testes de API HTTP — Biblioteca para simular requisições a servidores Express
- GitHub Actions: testes automatizados em CI — Tutorial oficial para configurar pipelines de teste
- Cypress: testes end-to-end modernos — Alternativa ao Playwright com foco em desenvolvedores frontend
- Istanbul: relatórios de cobertura de código — Ferramenta para medir e gerar relatórios de cobertura de testes