Use Cases (Atualizado: 03/06/2026)

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.

Testes E2E com Claude Code: guia Playwright/Cypress e CI

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

CasoPor que usar E2EExpectativas principais
Login até dashboardAutenticação, redirect e permissões se encontramTítulo, usuário, logout, erro
Checkout ou formulário de consultaImpacta receita ou leadsNúmero do pedido, confirmação, sem envio duplicado
Artigo para PDF ou treinamentoProtege monetização além de anúncioshref do CTA, evento, visibilidade móvel
Ação perigosa no adminEvita exclusão e falhas de permissãoModal, log de auditoria, checagem por papel
Páginas localizadasTraduções costumam quebrar linksURL 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

AspectoPlaywrightCypress
NavegadoresChromium, Firefox, WebKitFamília Chrome, Firefox, Edge
ParalelizaçãoIntegrada ao Playwright TestMuitas vezes usa Dashboard
Múltiplas abas e contextosForteMais limitado
DepuraçãoTrace Viewer, UI mode, HTML reportGUI interativa amigável
Melhor encaixeCI, múltiplos navegadores, auth isoladaTimes 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.

#Claude Code #testes E2E #Playwright #Cypress #CI
Grátis

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.

Masa

Sobre o autor

Masa

Engenheiro focado em workflows práticos com Claude Code.