Testes E2E com Claude Code: guia Playwright/Cypress e CI
Crie testes E2E Playwright com Claude Code: CI, falhas reais, exemplos executáveis e CTA.
Defina primeiro o que o E2E precisa proteger
Um teste de login gerado pelo Claude Code e passando em verde não significa que o produto esteja protegido. Um teste E2E valida um fluxo de usuário em um navegador real, por isso é mais lento e mais caro de investigar do que um teste unitário. Se cada detalhe visual virar E2E, a CI fica barulhenta e o time para de confiar nas falhas.
Escolha os fluxos que causam prejuízo quando quebram: login, checkout, formulário de consulta, ações perigosas no admin e CTAs que levam de um artigo para produto ou treinamento. Na operação do ClaudeCodeLab, Masa viu uma página renderizar bem enquanto o link final para treinamento estava quebrado. A tela parecia correta, mas o caminho de monetização tinha sumido.
Este guia segue a documentação atual de instalação do Playwright, locators, CI e Trace Viewer. Para complementar, leia também automação de testes de API e o guia de estratégias de teste.
flowchart LR
A["Pedir ao Claude Code"] --> B["Escolher três fluxos críticos"]
B --> C["Implementar testes Playwright"]
C --> D["Depurar com trace e relatório"]
D --> E["Bloquear CI se falhar"]
C --> F["Verificar CTA e receita"]
Um prompt melhor para o Claude Code
Inclua escopo, critérios de sucesso, critérios de falha, arquivos permitidos e comandos de verificação. Sem isso, o Claude Code tende a criar testes frágeis baseados em classes CSS. O Playwright recomenda locators como getByRole, getByLabel, getByText e getByTestId, pois funcionam com espera automática e assertions com retry.
Objetivo: testes E2E para login, checkout e cadastro em newsletter.
Ferramenta: Playwright Test. Adicionar comparação curta com Cypress.
Regras:
- Priorizar role, label, text ou testid em vez de classes CSS.
- Cobrir erros, não só o caminho feliz.
- Usar workers: 1 na CI e trace: on-first-retry.
- Verificar que o href do CTA de monetização existe.
Arquivos permitidos:
- tests/e2e/**/*.spec.ts
- playwright.config.ts
Verificação:
- npx playwright test
- npx playwright test --headed para depuração local
Também diga o que fica fora. Pagamento real, envio real de e-mail, clique em anúncio e escrita em CRM não devem rodar em todo E2E. Use modo de teste, mocks ou testes de contrato de API. Para esses limites, combine com implementação de webhook e implementação de analytics.
Comandos de instalação
Em um projeto novo, comece com estes comandos. A documentação oficial explica que cada versão do Playwright espera binários específicos de navegador, então reinstale os navegadores após atualizar o Playwright. Em CI Linux, use --with-deps para instalar dependências do sistema.
npm init playwright@latest
npx playwright install
npx playwright test
npx playwright test --headed
npx playwright test --ui
npx playwright show-report
--headed abre um navegador visível para investigar localmente. --ui abre o modo interativo. Na CI, normalmente use headless. Se um teste passa localmente e falha na CI, confira animações, tamanho de tela, fontes, fuso horário, velocidade de CPU e espera por APIs externas.
Exemplo Playwright executável
O spec abaixo é independente. Ele monta uma página pequena com page.setContent, então não precisa de servidor da aplicação. Copie para tests/e2e/claude-code-e2e.spec.ts depois de instalar Playwright. Em um app real, troque renderDemoApp(page) por page.goto('/login') e ajuste labels ou data-testid.
// tests/e2e/claude-code-e2e.spec.ts
import { test, expect, type Page } from '@playwright/test';
async function renderDemoApp(page: Page) {
await page.setContent(`
<!doctype html>
<main>
<form id="login-form" aria-label="Login form">
<label>Email <input id="email" aria-label="Email" /></label>
<label>Password <input id="password" aria-label="Password" type="password" /></label>
<button type="submit">Log in</button>
<p id="login-error" role="alert" hidden></p>
</form>
<section id="dashboard" hidden>
<h1>Dashboard</h1>
<a data-testid="training-cta" href="/training/">Book Claude Code training</a>
<button id="add-plan">Add Pro plan to cart</button>
<span data-testid="cart-count">0</span>
<button id="buy">Complete purchase</button>
<p data-testid="order-status" role="status"></p>
<label>Newsletter email <input id="newsletter-email" aria-label="Newsletter email" /></label>
<button id="newsletter-button" type="button">Join newsletter</button>
<p id="newsletter-error" role="alert" hidden></p>
<p data-testid="newsletter-status" role="status"></p>
</section>
</main>
<script>
const state = { cart: 0 };
document.querySelector('#login-form').addEventListener('submit', (event) => {
event.preventDefault();
const email = document.querySelector('#email').value;
const password = document.querySelector('#password').value;
const error = document.querySelector('#login-error');
if (email === 'masa@example.com' && password === 'password123') {
document.querySelector('#login-form').hidden = true;
document.querySelector('#dashboard').hidden = false;
error.hidden = true;
return;
}
error.textContent = 'Authentication failed';
error.hidden = false;
});
document.querySelector('#add-plan').addEventListener('click', () => {
state.cart += 1;
document.querySelector('[data-testid="cart-count"]').textContent = String(state.cart);
});
document.querySelector('#buy').addEventListener('click', () => {
const status = document.querySelector('[data-testid="order-status"]');
status.textContent = state.cart === 0 ? 'Cart is empty' : 'Order ORD-1001 completed';
});
document.querySelector('#newsletter-button').addEventListener('click', () => {
const email = document.querySelector('#newsletter-email').value;
const error = document.querySelector('#newsletter-error');
const status = document.querySelector('[data-testid="newsletter-status"]');
if (!email.includes('@')) {
error.textContent = 'Enter a valid email';
error.hidden = false;
status.textContent = '';
return;
}
error.hidden = true;
status.textContent = 'Thanks, we will send the checklist.';
});
</script>
`);
}
async function loginAsDemoUser(page: Page) {
await page.getByLabel('Email').fill('masa@example.com');
await page.getByLabel('Password').fill('password123');
await page.getByRole('button', { name: 'Log in' }).click();
}
test.describe('Claude Code E2E starter', () => {
test('use case 1: login shows dashboard and keeps the training CTA', async ({ page }) => {
await renderDemoApp(page);
await loginAsDemoUser(page);
await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
await expect(page.getByTestId('training-cta')).toHaveAttribute('href', '/training/');
});
test('use case 2: checkout flow creates an order number', async ({ page }) => {
await renderDemoApp(page);
await loginAsDemoUser(page);
await page.getByRole('button', { name: 'Add Pro plan to cart' }).click();
await expect(page.getByTestId('cart-count')).toHaveText('1');
await page.getByRole('button', { name: 'Complete purchase' }).click();
await expect(page.getByTestId('order-status')).toContainText('ORD-1001');
});
test('use case 3: newsletter validation blocks invalid leads', async ({ page }) => {
await renderDemoApp(page);
await loginAsDemoUser(page);
await page.getByRole('button', { name: 'Join newsletter' }).click();
await expect(page.getByRole('alert')).toContainText('Enter a valid email');
await page.getByLabel('Newsletter email').fill('reader@example.com');
await page.getByRole('button', { name: 'Join newsletter' }).click();
await expect(page.getByTestId('newsletter-status')).toContainText('Thanks');
});
});
Esse arquivo cobre três casos concretos: login, checkout e captura de lead. Ele também verifica o href do CTA de treinamento, detalhe que suítes E2E superficiais costumam esquecer.
Configuração prática do Playwright
A configuração deve deixar evidência quando algo falhar. A documentação de CI do Playwright recomenda workers: 1 na CI para priorizar estabilidade e reprodutibilidade.
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests/e2e',
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
use: {
baseURL: process.env.BASE_URL ?? 'http://127.0.0.1:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
],
});
trace: 'on-first-retry' mostra o que estava visível, qual clique falhou e qual assertion expirou. Com isso, Claude Code consegue investigar melhor do que com uma mensagem genérica da CI.
GitHub Actions para CI
Na CI, muitos erros vêm de navegadores ou dependências Linux. Comece simples e adicione cache ou sharding só quando o tempo virar problema.
# .github/workflows/playwright.yml
name: Playwright Tests
on:
push:
branches: [main, master]
pull_request:
branches: [main, master]
jobs:
test:
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-node@v6
with:
node-version: lts/*
- name: Install dependencies
run: npm ci
- name: Install Playwright browsers
run: npx playwright install --with-deps
- name: Run E2E tests
run: npx playwright test
- uses: actions/upload-artifact@v5
if: ${{ !cancelled() }}
with:
name: playwright-report
path: playwright-report/
retention-days: 30
Se testar uma URL de preview, passe BASE_URL para o workflow. Se a aplicação precisa subir dentro da CI, adicione webServer no playwright.config.ts.
Casos de uso que merecem E2E
| Caso | Por que usar E2E | Expectativas principais |
|---|---|---|
| Login até dashboard | Autenticação, redirect e permissões se encontram | Título, usuário, logout, erro |
| Checkout ou formulário de consulta | Impacta receita ou leads | Número do pedido, confirmação, sem envio duplicado |
| Artigo para PDF ou treinamento | Protege monetização além de anúncios | href do CTA, evento, visibilidade móvel |
| Ação perigosa no admin | Evita exclusão e falhas de permissão | Modal, log de auditoria, checagem por papel |
| Páginas localizadas | Traduções costumam quebrar links | URL com locale, texto natural, links externos |
Entregue essa tabela ao Claude Code antes de pedir arquivos. Assim o teste valida resultados, não apenas cliques.
Falhas frequentes
Evite waitForTimeout(3000). Esperas fixas passam em uma máquina e quebram na CI. Espere o estado real: elemento visível, URL alterada ou texto de status.
Evite .btn-primary:nth-child(2). Uma mudança de design quebra o teste. Prefira role, label, text e data-testid estável.
Não compartilhe estado entre testes. Carrinho, Cookie ou registro de banco criado por um teste não deve ser pré-condição de outro.
Não ignore diferenças entre headed e headless. Fontes, GPU, scroll, área de transferência, upload de arquivo e viewport podem mudar na CI. Use headless na CI e --headed ou --ui para investigação.
Não chame pagamento, e-mail, anúncios ou CRM reais em toda execução. Modo de teste, mocks ou contratos de API são mais seguros.
Playwright ou Cypress
| Aspecto | Playwright | Cypress |
|---|---|---|
| Navegadores | Chromium, Firefox, WebKit | Família Chrome, Firefox, Edge |
| Paralelização | Integrada ao Playwright Test | Muitas vezes usa Dashboard |
| Múltiplas abas e contextos | Forte | Mais limitado |
| Depuração | Trace Viewer, UI mode, HTML report | GUI interativa amigável |
| Melhor encaixe | CI, múltiplos navegadores, auth isolada | Times frontend com Cypress existente |
Cypress continua sendo uma boa escolha. Se o time já tem cobertura Cypress, não migre por moda. Adicione Playwright a um fluxo crítico e compare instabilidade, qualidade do trace e tempo de CI. Confirme sempre na documentação oficial de Playwright e Cypress.
CTA de monetização e resultado
E2E também protege receita. Um link de consulta, card de produto, formulário gratuito ou CTA de treinamento quebrado pode custar mais que um bug visual. Para começar sozinho, use o cheatsheet gratuito de Claude Code. Para prompts reutilizáveis, veja produtos e modelos. Para equipes que precisam de CI, CLAUDE.md, regras de revisão e responsabilidade de E2E, use treinamento e consultoria Claude Code.
O exemplo foi verificado extraindo o spec e a configuração como trechos TypeScript para validação de sintaxe. O hábito mais útil foi colocar a expectativa de negócio no nome do teste: manter o CTA de treinamento. Em um projeto real, comece com três testes: login, checkout ou lead, e CTA do artigo. Amplie depois que eles estiverem estáveis na CI.
PDF grátis: cheatsheet do Claude Code
Informe seu e-mail e baixe uma página com comandos, hábitos de revisão e workflows seguros.
Cuidamos dos seus dados e não enviamos spam.
Sobre o autor
Masa
Engenheiro focado em workflows práticos com Claude Code.
Artigos relacionados
Workflow Obsidian para CLAUDE.md com Claude Code
Transforme notas de trabalho do Obsidian em notas operacionais CLAUDE.md para preservar contexto.
Claude Code Revenue CTA Routing: artigos para PDF, Gumroad e consultoria
Um fluxo com Claude Code para levar leitores ao PDF grátis, Gumroad ou consultoria conforme intenção.
Regras de handoff para equipes com Claude Code: evidências, permissões, rollback e receita
Formato prático para entregar trabalho do Claude Code com prova, permissões, rollback, PDF grátis, Gumroad e consultoria.