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
Crie um log de orçamento Claude Code antes do custo ficar confuso
Registre quem usou Claude Code, para qual trabalho e qual resultado gerou no time.
Checagem de 3 minutos antes do commit: confira o que o Claude Code mexeu antes de fechar
Roteiro de 3 minutos para flagrar antes do commit as mudanças que o Claude Code ampliou sozinho: escopo, diff e quais arquivos stagear.
Registro de riscos: o que montar antes de levar Claude Code para a equipe
Como montar um registro de riscos para levar Claude Code à equipe sem acidentes de permissão, CI e deploy. Com exemplos e código.