Estratégia de testes com Claude Code: Vitest, Testing Library, Playwright e CI
Planeje testes unitários, integração, E2E e CI com Claude Code sem criar testes frágeis.
Uma estratégia de testes não é apenas escrever mais testes. É decidir o que deve ser protegido por testes unitários rápidos, que fronteiras precisam de testes de integração e quais fluxos de negócio merecem E2E.
Se você pedir ao Claude Code apenas “adicione testes”, ele pode gerar assertions superficiais, seletores CSS frágeis ou testes que só espelham a implementação atual. Um pedido melhor informa a camada de teste, o comportamento esperado, o comando a executar e os limites de edição.
Este guia foi conferido com a documentação oficial de Claude Code common workflows, Vitest coverage, Testing Library queries e Playwright para locators, assertions e CI.
Comece pela pirâmide
flowchart TB
E2E["E2E: cadastro, checkout, CTA de receita"]
INT["Integração: API, DB, formulários, componentes"]
UNIT["Unitário: cálculo, validação, permissões"]
E2E --> INT --> UNIT
| Camada | Referência | O que protege | Ferramentas |
|---|---|---|---|
| Unitária | 60-70% | lógica pura, validação, permissões | Vitest |
| Integração | 20-30% | componentes, API, fronteiras de DB | Vitest + Testing Library |
| E2E | 5-10% | cadastro, compra, caminhos de receita | Playwright |
| CI gate | todo PR | lint, tipos, testes, relatórios | GitHub Actions |
Lógica de preço fica em teste unitário. Um botão de checkout fica em teste de integração. A navegação de um CTA do artigo para a página de produtos é um bom E2E.
Configuração mínima
npm i -D vitest @vitest/coverage-v8 jsdom \
@testing-library/react @testing-library/jest-dom @testing-library/user-event \
@playwright/test
npx playwright install --with-deps
{
"scripts": {
"test": "vitest --run",
"test:watch": "vitest",
"test:coverage": "vitest --run --coverage",
"test:e2e": "playwright test"
}
}
// vitest.config.ts
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
environment: "jsdom",
setupFiles: ["./test/setup.ts"],
coverage: {
provider: "v8",
reporter: ["text", "html"],
thresholds: {
lines: 80,
functions: 80,
branches: 75,
statements: 80,
},
},
},
});
// test/setup.ts
import "@testing-library/jest-dom/vitest";
O Vitest documenta os providers v8 e istanbul. Em projetos Node ou Chromium, v8 costuma ser a escolha mais simples.
Exemplo 1: teste unitário de preço
// src/lib/pricing.ts
export type PriceInput = {
unitPrice: number;
quantity: number;
discountRate?: number;
taxRate?: number;
};
export function calculateTotal({
unitPrice,
quantity,
discountRate = 0,
taxRate = 0.1,
}: PriceInput): number {
if (!Number.isInteger(quantity) || quantity < 0) {
throw new Error("quantity must be a non-negative integer");
}
if (unitPrice < 0) throw new Error("unitPrice must be non-negative");
if (discountRate < 0 || discountRate > 1) {
throw new Error("discountRate must be between 0 and 1");
}
const discounted = unitPrice * quantity * (1 - discountRate);
return Math.round(discounted * (1 + taxRate));
}
// src/lib/pricing.test.ts
import { describe, expect, it } from "vitest";
import { calculateTotal } from "./pricing";
describe("calculateTotal", () => {
it("calculates a tax-included total", () => {
expect(calculateTotal({ unitPrice: 1000, quantity: 2 })).toBe(2200);
});
it("applies discount before tax", () => {
expect(
calculateTotal({ unitPrice: 1000, quantity: 2, discountRate: 0.2 })
).toBe(1760);
});
it("allows zero quantity", () => {
expect(calculateTotal({ unitPrice: 1000, quantity: 0 })).toBe(0);
});
it("rejects invalid inputs", () => {
expect(() => calculateTotal({ unitPrice: 1000, quantity: -1 })).toThrow(
"quantity must be a non-negative integer"
);
});
});
O erro comum é buscar 80% de cobertura apenas com caminhos felizes. Em preço, os casos importantes são quantidade zero, desconto total, taxas inválidas e arredondamento.
Exemplo 2: CTA com Testing Library
Testing Library recomenda queries próximas de como usuários encontram elementos. getByRole e getByLabelText são mais estáveis que classes CSS.
// src/components/CheckoutButton.tsx
import { useState } from "react";
type Props = {
stock: number;
onCheckout: () => Promise<void>;
};
export function CheckoutButton({ stock, onCheckout }: Props) {
const [submitting, setSubmitting] = useState(false);
const soldOut = stock <= 0;
async function handleClick() {
setSubmitting(true);
try {
await onCheckout();
} finally {
setSubmitting(false);
}
}
return (
<button
type="button"
disabled={soldOut || submitting}
aria-busy={submitting}
onClick={handleClick}
>
{soldOut ? "Sold out" : submitting ? "Processing..." : "Buy now"}
</button>
);
}
// src/components/CheckoutButton.test.tsx
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { describe, expect, it, vi } from "vitest";
import { CheckoutButton } from "./CheckoutButton";
describe("CheckoutButton", () => {
it("calls checkout when in stock", async () => {
const user = userEvent.setup();
const onCheckout = vi.fn().mockResolvedValue(undefined);
render(<CheckoutButton stock={3} onCheckout={onCheckout} />);
await user.click(screen.getByRole("button", { name: "Buy now" }));
expect(onCheckout).toHaveBeenCalledTimes(1);
});
it("prevents checkout when sold out", async () => {
const user = userEvent.setup();
const onCheckout = vi.fn().mockResolvedValue(undefined);
render(<CheckoutButton stock={0} onCheckout={onCheckout} />);
const button = screen.getByRole("button", { name: "Sold out" });
expect(button).toBeDisabled();
await user.click(button);
expect(onCheckout).not.toHaveBeenCalled();
});
});
Um CTA de receita não deve depender de .primary-button nem de snapshot como verificação principal. O teste precisa confirmar papel, texto e comportamento.
Exemplo 3: E2E pequeno com Playwright
// playwright.config.ts
import { defineConfig, devices } from "@playwright/test";
const baseURL =
process.env.PLAYWRIGHT_TEST_BASE_URL ?? "http://127.0.0.1:5173";
export default defineConfig({
testDir: "./e2e",
retries: process.env.CI ? 2 : 0,
use: { baseURL, trace: "on-first-retry" },
projects: [{ name: "chromium", use: { ...devices["Desktop Chrome"] } }],
webServer: process.env.PLAYWRIGHT_TEST_BASE_URL
? undefined
: {
command: "npm run dev -- --host 127.0.0.1",
url: baseURL,
reuseExistingServer: !process.env.CI,
},
});
// e2e/article-cta.spec.ts
import { expect, test } from "@playwright/test";
test("reader can move from article CTA to products", async ({ page }) => {
await page.goto("/pt/blog/claude-code-testing-strategies");
await page.getByRole("link", { name: /products|templates|produtos/i }).click();
await expect(page).toHaveURL(/\/products\/?$/);
await expect(page.getByRole("heading", { name: /products|produtos/i })).toBeVisible();
});
As assertions web-first do Playwright fazem retry automático. Prefira await expect(locator).toBeVisible() a esperas fixas. Evite caminhos CSS profundos e nth().
Exemplo 4: CI gate
# .github/workflows/test.yml
name: Test
on:
pull_request:
push:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v6
with:
node-version: 22
cache: npm
- run: npm ci
- run: npm run test:coverage
- run: npx playwright install --with-deps
- run: npm run test:e2e
env:
CI: "true"
- uses: actions/upload-artifact@v5
if: ${{ !cancelled() }}
with:
name: playwright-report
path: playwright-report/
retention-days: 30
Se usar Claude Code GitHub Actions, siga a ação GA atual anthropics/claude-code-action@v1. Não copie exemplos antigos com @beta sem conferir a documentação.
Templates para Claude Code
Adicione testes unitários Vitest para src/lib/pricing.ts.
Cubra casos válidos, valores limite e entradas inválidas.
Não altere a implementação, a menos que primeiro liste o bug suspeito.
Execute npm run test -- pricing e resuma o resultado.
Adicione testes Testing Library para src/components/CheckoutButton.tsx.
Use getByRole e userEvent.setup().
Não use seletores CSS, snapshot como assertion principal nem detalhes internos.
Cubra em estoque, esgotado e estado de envio.
Adicione apenas um teste Playwright E2E para o fluxo CTA do artigo para produtos.
Evite waitForTimeout, seletores CSS profundos e nth().
Use assertions web-first e mantenha o teste focado na rota de receita.
Analise a falha mais recente da CI.
Classifique como lint, typecheck, unit, e2e ou ambiente.
Corrija a causa raiz com o menor diff.
Não pule testes nem apenas aumente timeouts.
Armadilhas comuns
Não faça mock de tudo. Gateways de pagamento e envio de e-mail podem ser simulados, mas preço, permissões e persistência crítica precisam ser testados na camada correta.
Não use E2E para congelar todos os detalhes visuais. E2E deve proteger cadastro, compra, contato e cliques em produtos.
Não trate cobertura como prova de qualidade. 80% ainda pode deixar fora a branch que cobra, apaga ou publica dados.
Não dê ao Claude Code apenas o caminho feliz. Inclua restrições: sem seletores frágeis, sem skip, sem snapshot-only e sem acoplamento a detalhes internos.
CTA
Testes também protegem caminhos de receita. Comece pela Claude Code cheatsheet, transforme regras repetidas em products and templates e use Claude Code training se o time precisar alinhar CI, review e responsabilidade por testes. Para continuar, veja TDD with Claude Code, CI/CD setup e debugging techniques.
Resultado prático
Masa testou este padrão nos CTAs de artigos e nos fluxos para produtos do ClaudeCodeLab. O melhor resultado não veio de adicionar muitos E2E, mas de manter preço em unit tests, comportamento do CTA em Testing Library e um único Playwright para o caminho artigo-produtos. Pedir ao Claude Code para listar modos de falha primeiro reduziu testes ignorados e seletores CSS frágeis. O risco restante está em pagamentos externos e scripts de anúncios, que ainda exigem evidência de CI e uma verificação manual curta antes da publicação.
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.