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
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.