Testes E2E com Claude Code e Playwright em produção
Use Claude Code e Playwright para E2E, mobile screenshots, auth state, Trace Viewer, seletores e retries no CI.
Pedir ao Claude Code “adicione testes Playwright” não basta para proteger um site em produção. O resultado pode passar uma vez, mas depender de classes CSS frágeis, refazer login em cada teste, ignorar screenshots mobile e não deixar uma trace útil quando o CI falhar.
Use o Claude Code como parceiro de desenho de testes. Você define riscos de negócio, rotas, regras de seletores e comando de validação; ele lê o projeto e propõe configuração Playwright e specs. Este guia cobre fluxos de monetização, mobile screenshots, QA de blocos de código, estado autenticado, Trace Viewer, retries no CI e como evitar testes flaky.
Use as fontes oficiais como base: Claude Code overview, Claude Code common workflows, e no Playwright Locators, Authentication, Screenshots, Trace Viewer, Retries e CI. No ClaudeCodeLab, complemente com estratégia de testes, CI/CD setup e design responsivo.
Escolha os fluxos certos
E2E abre um navegador real, então deve proteger o que unit tests não provam.
| Caso de uso | O que protege | Prova no Playwright |
|---|---|---|
| Artigo para produtos | O leitor chega a /products/ | CTA, URL, toque no mobile |
| Dashboard autenticado | Usuário logado acessa páginas protegidas | storageState, redirects, permissões |
| Layout de artigo técnico | Código e tabelas não quebram no mobile | Screenshot mobile, sem overflow, trace |
flowchart LR
A["Fluxo de receita ou cadastro"] --> B["Playwright E2E"]
C["Risco de layout mobile"] --> B
D["Validação pura"] --> E["Unit tests"]
F["Fronteira API ou componente"] --> G["Integration tests"]
Em um site monetizado, CTA escondido no mobile, código que alarga a página ou área paga fora do CI são problemas pequenos que afetam conversão e confiança.
Dê limites claros ao Claude Code
O prompt deve trazer rotas, seletores aceitos, arquivos permitidos e comando de prova.
Leia o site Astro existente e adicione testes E2E com Playwright.
Objetivos:
- Verificar que `/pt/blog/claude-code-playwright-testing/` leva a `/products/` e `/training/`
- Checar em 390px mobile que artigo, tabelas e blocos de código não têm overflow horizontal
- Usar `storageState` para testes autenticados, sem login por UI em cada teste
- No CI usar 2 retries e `trace: "on-first-retry"`
Restrições:
- Não usar `page.waitForTimeout()`
- Preferir role, label, text ou test id a cadeias de classes CSS
- Alterar apenas `playwright.config.ts` e `tests/e2e/**`
- Executar `npx playwright test` e explicar falhas com Trace Viewer
Na revisão, pergunte se o teste falha por um problema real de usuário e se o diagnóstico será possível.
Configuração copiável
cd site
npm i -D @playwright/test
npx playwright install
mkdir tests/e2e
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
const baseURL = process.env.BASE_URL ?? 'http://127.0.0.1:4321';
const hasAuth = Boolean(process.env.TEST_EMAIL && process.env.TEST_PASSWORD);
const authFile = 'playwright/.auth/user.json';
export default defineConfig({
testDir: './tests/e2e',
timeout: 30_000,
expect: { timeout: 5_000 },
fullyParallel: true,
forbidOnly: Boolean(process.env.CI),
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 2 : undefined,
reporter: process.env.CI ? [['html'], ['github']] : 'html',
use: {
baseURL,
trace: 'on-first-retry',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
},
...(process.env.PLAYWRIGHT_WEB_SERVER === '1'
? {
webServer: {
command: 'npm run preview -- --host 127.0.0.1 --port 4321',
url: baseURL,
reuseExistingServer: !process.env.CI,
timeout: 120_000,
},
}
: {}),
projects: [
...(hasAuth
? [
{
name: 'setup',
testMatch: /.*\.setup\.ts/,
},
]
: []),
{
name: 'desktop-chrome',
use: {
...devices['Desktop Chrome'],
storageState: hasAuth ? authFile : undefined,
},
dependencies: hasAuth ? ['setup'] : [],
},
{
name: 'mobile-safari',
use: {
...devices['iPhone 13'],
storageState: hasAuth ? authFile : undefined,
},
dependencies: hasAuth ? ['setup'] : [],
},
],
});
Localmente, retry desligado deixa o erro visível. No CI, retry só é útil junto com trace, screenshots e HTML report.
Salve o estado autenticado
Não faça login pela UI em todo teste. storageState salva cookies e localStorage de um usuário de teste para reutilizar. Mantenha playwright/.auth fora do git.
// tests/e2e/auth.setup.ts
import { test as setup, expect } from '@playwright/test';
import fs from 'node:fs';
import path from 'node:path';
const authFile = path.resolve('playwright/.auth/user.json');
const email = process.env.TEST_EMAIL;
const password = process.env.TEST_PASSWORD;
setup('save signed-in browser state', async ({ page }) => {
setup.skip(!email || !password, 'Set TEST_EMAIL and TEST_PASSWORD to record auth state.');
await page.goto('/login');
await page.getByLabel(/email|メール|e-mail/i).fill(email!);
await page.getByLabel(/password|パスワード/i).fill(password!);
await page.getByRole('button', { name: /log in|sign in|ログイン/i }).click();
await expect(page).toHaveURL(/dashboard|account|admin/);
await expect(page.locator('body')).toBeVisible();
fs.mkdirSync(path.dirname(authFile), { recursive: true });
await page.context().storageState({ path: authFile });
});
Peça ao Claude Code para separar testes do login de testes que apenas precisam estar autenticados. Isso reduz falhas em cascata.
QA mobile e blocos de código
Artigos técnicos quebram por linhas longas, tabelas e imagens. Este spec valida CTAs, captura mobile e falha se houver overflow horizontal.
// tests/e2e/article-quality.spec.ts
import { test, expect } from '@playwright/test';
const articlePath = process.env.ARTICLE_PATH ?? '/pt/blog/claude-code-playwright-testing/';
test.describe('article quality checks', () => {
test('article has monetization CTAs', async ({ page }) => {
await page.goto(articlePath);
await expect(page.getByRole('heading', { level: 1 })).toContainText(/Playwright|E2E|Claude Code/i);
await expect(page.locator('a[href="/products/"], a[href="/products"]').first()).toBeVisible();
await expect(page.locator('a[href="/training/"], a[href="/training"]').first()).toBeVisible();
});
test('mobile layout has no horizontal overflow', async ({ page }, testInfo) => {
await page.setViewportSize({ width: 390, height: 844 });
await page.goto(articlePath);
await expect(page.locator('main, article').first()).toBeVisible();
const overflow = await page.evaluate(() => ({
viewport: window.innerWidth,
documentWidth: document.documentElement.scrollWidth,
offenders: Array.from(document.querySelectorAll('pre, table, img, iframe, .prose'))
.filter((node) => {
const rect = node.getBoundingClientRect();
return rect.left < -1 || rect.right > window.innerWidth + 1;
})
.map((node) => {
const rect = node.getBoundingClientRect();
return `${node.tagName.toLowerCase()} ${Math.round(rect.left)}-${Math.round(rect.right)}`;
}),
}));
expect(overflow.documentWidth, JSON.stringify(overflow)).toBeLessThanOrEqual(overflow.viewport + 2);
expect(overflow.offenders).toEqual([]);
await page.screenshot({ path: testInfo.outputPath('article-mobile.png'), fullPage: true });
});
test('code examples are present and copyable', async ({ page }) => {
await page.goto(articlePath);
const blocks = page.locator('pre code');
await expect(blocks.first()).toBeVisible();
expect(await blocks.count()).toBeGreaterThanOrEqual(3);
await expect(blocks.nth(0)).toContainText(/playwright|defineConfig|test/i);
});
});
Screenshot ajuda na revisão humana; a asserção numérica decide no CI.
Proteja CTAs de receita e treinamento
// tests/e2e/revenue-flows.spec.ts
import { test, expect } from '@playwright/test';
const articlePath = process.env.ARTICLE_PATH ?? '/pt/blog/claude-code-playwright-testing/';
test.describe('revenue and learning flows', () => {
test('reader can move from article to products', async ({ page }) => {
await page.goto(articlePath);
await page.locator('a[href="/products/"], a[href="/products"]').first().click();
await expect(page).toHaveURL(/\/products\/?$/);
await expect(page.locator('main').first()).toBeVisible();
});
test('training CTA is reachable on mobile', async ({ page }) => {
await page.setViewportSize({ width: 390, height: 844 });
await page.goto(articlePath);
await page.locator('a[href="/training/"], a[href="/training"]').first().click();
await expect(page).toHaveURL(/\/training\/?$/);
await expect(page.locator('main').first()).toBeVisible();
});
test('main navigation can open the blog index', async ({ page }) => {
await page.goto('/');
await expect(page.getByRole('navigation').first()).toBeVisible();
await page.getByRole('link', { name: /blog|記事|articles/i }).first().click();
await expect(page).toHaveURL(/blog/);
});
});
Em sites multilíngues, o texto muda. Para ações críticas, use href estável ou data-testid com significado de negócio.
Seletores menos flaky
| Prioridade | Seletor | Exemplo | Motivo |
|---|---|---|---|
| Alta | Role e nome | page.getByRole('button', { name: /save/i }) | Próximo da experiência acessível |
| Alta | Label | page.getByLabel(/email/i) | Valida semântica do formulário |
| Média | Texto | page.getByText(/Start trial/) | Claro, mas depende do copy |
| Média | Test id | page.getByTestId('checkout-submit') | Bom para ação de negócio estável |
| Baixa | Estrutura CSS | .card:nth-child(3) | Quebra com layout |
Evite page.waitForTimeout(). Use toBeVisible(), toHaveURL() e toContainText() para aguardar condições reais.
Depure com Trace Viewer
npx playwright test --trace on
npx playwright show-report
npx playwright show-trace test-results/path-to-trace/trace.zip
Ao devolver uma falha ao Claude Code, inclua nome do teste, estado visual na trace e comportamento esperado. Isso evita soluções baseadas apenas em aumentar timeout.
CI com retries e artefatos
# .github/workflows/playwright.yml
name: Playwright E2E
on:
pull_request:
push:
branches: [main]
jobs:
e2e:
runs-on: ubuntu-latest
timeout-minutes: 15
defaults:
run:
working-directory: site
env:
BASE_URL: http://127.0.0.1:4321
PLAYWRIGHT_WEB_SERVER: "1"
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
cache-dependency-path: site/package-lock.json
- run: npm ci
- run: npx playwright install --with-deps
- run: npm run build
- run: npx playwright test
- uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: site/playwright-report
retention-days: 7
Retry não cura teste flaky; ele classifica e deixa evidência.
Armadilhas comuns
A primeira é testar tudo com E2E. Cálculos, validações e permissões pequenas cabem melhor em unit ou integration tests.
A segunda é commitar estado autenticado. Use usuários de teste limitados e exclua playwright/.auth.
A terceira é esconder instabilidade com retry. Se só passa no retry, ainda é risco.
A quarta é pedir correções amplas ao Claude Code. Adicione o teste que falha, leia a trace e faça a menor correção de produto.
Para começar sozinho, use os templates em products. Para equipes, training ajuda a alinhar review, CI e onboarding.
Testei esse fluxo em uma página local no estilo ClaudeCodeLab: screenshot em 390px, overflow de blocos de código, navegação para /products/ e /training/, e retries no CI. As primeiras falhas úteis não eram do Playwright, mas linhas de código longas e nomes de links ambíguos. Depois da correção, Trace Viewer virou uma evidência concreta para o próximo prompt no Claude Code.
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
Permission receipt no Claude Code: escopo, prova e rollback
Padrão de permission receipt para Claude Code: ações permitidas, limites de aprovação, comandos de prova, rollback e CTA de receita.
Agent Harness seguro para Claude Code e Codex: permissoes, verificacao e rollback
Monte uma base segura para agentes com Claude Code e Codex usando politicas, plano, verificacao e recuperacao.
Subagentes no Claude Code: guia prático para delegar trabalho com segurança
Guia prático de subagentes no Claude Code para dividir artigos e código: regras, prompts, riscos e checklist.