Estrategia de pruebas con Claude Code: Vitest, Testing Library, Playwright y CI
Diseña pruebas unitarias, de integración, E2E y CI con Claude Code sin crear tests frágiles.
Una estrategia de pruebas no consiste en escribir más tests. Consiste en decidir qué comportamiento debe protegerse con pruebas unitarias rápidas, qué límites necesitan pruebas de integración y qué flujos de negocio merecen una prueba E2E.
Si le pides a Claude Code “agrega tests” sin contexto, puede generar assertions superficiales, selectores CSS frágiles o pruebas que solo reflejan la implementación actual. Una petición mejor define la capa de prueba, el comportamiento esperado, los comandos a ejecutar y los límites de edición.
Esta guía se revisó contra la documentación oficial de Claude Code common workflows, Vitest coverage, Testing Library queries y Playwright para locators, assertions y CI.
Empieza con la pirámide
flowchart TB
E2E["E2E: registro, checkout, CTA de ingresos"]
INT["Integración: API, DB, formularios, componentes"]
UNIT["Unidad: cálculo, validación, permisos"]
E2E --> INT --> UNIT
| Capa | Porcentaje guía | Qué protege | Herramientas |
|---|---|---|---|
| Unitaria | 60-70% | lógica pura, validación, permisos | Vitest |
| Integración | 20-30% | componentes, API, límites de DB | Vitest + Testing Library |
| E2E | 5-10% | registro, compra, rutas de ingresos | Playwright |
| Puerta CI | cada PR | lint, tipos, pruebas, reportes | GitHub Actions |
La lógica de precios va en pruebas unitarias. Un botón de compra va en integración. El paso desde un CTA del artículo hasta la página de productos es un buen candidato para E2E.
Configuración 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";
Vitest documenta v8 e istanbul como proveedores de cobertura. Para proyectos Node o Chromium, v8 suele ser la opción más directa.
Ejemplo 1: Prueba unitaria de precios
// 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"
);
});
});
El error común es perseguir 80% de cobertura solo con casos felices. En precios importan más los límites: cantidad cero, descuentos extremos, tasas inválidas y redondeo.
Ejemplo 2: CTA con Testing Library
Testing Library recomienda consultas parecidas a la forma en que una persona encuentra elementos. Por eso getByRole y getByLabelText suelen ser mejores que selectores 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();
});
});
No uses .primary-button ni snapshots como verificación principal de un CTA. Un CTA afecta ingresos, así que la prueba debe comprobar si el botón se encuentra, se puede usar y dispara el comportamiento correcto.
Ejemplo 3: E2E pequeño con 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("/es/blog/claude-code-testing-strategies");
await page.getByRole("link", { name: /products|templates|productos/i }).click();
await expect(page).toHaveURL(/\/products\/?$/);
await expect(page.getByRole("heading", { name: /products|productos/i })).toBeVisible();
});
Las assertions web-first de Playwright reintentan automáticamente. Prefiere await expect(locator).toBeVisible() en lugar de esperas fijas. Evita rutas CSS profundas y nth().
Ejemplo 4: Puerta de CI
# .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
Si usas Claude Code GitHub Actions, la documentación actual muestra anthropics/claude-code-action@v1 como acción GA. No copies ejemplos antiguos con @beta sin verificar.
Plantillas para Claude Code
Agrega pruebas unitarias con Vitest para src/lib/pricing.ts.
Cubre casos correctos, valores límite y entradas inválidas.
No cambies la implementación salvo que primero listes el posible bug.
Ejecuta npm run test -- pricing y resume el resultado.
Agrega pruebas de Testing Library para src/components/CheckoutButton.tsx.
Usa getByRole y userEvent.setup().
No uses selectores CSS, snapshots como assertion principal ni detalles internos.
Cubre stock disponible, agotado y estado de envío.
Agrega una sola prueba E2E con Playwright para el flujo CTA del artículo a productos.
Evita waitForTimeout, selectores CSS profundos y nth().
Usa assertions web-first y mantén la prueba centrada en la ruta de ingresos.
Revisa el fallo más reciente de CI.
Clasifícalo como lint, typecheck, unit, e2e o entorno.
Corrige la causa raíz con el diff más pequeño.
No omitas pruebas ni aumentes solo timeouts.
Errores frecuentes
No lo mocks todo. Mockea pasarelas de pago y envío de correo, pero prueba precios, permisos y persistencia en la capa correcta.
No uses E2E para congelar todos los detalles visuales. E2E debe proteger acciones de lector, registro, compra y contacto.
No tomes la cobertura como prueba de calidad. 80% puede ignorar justo la rama que cobra, elimina o publica datos.
No le des a Claude Code solo el caso feliz. Incluye prohibiciones: sin selectores frágiles, sin skip, sin snapshots-only y sin acoplarse a detalles internos.
CTA
Las pruebas protegen también las rutas de ingresos. Empieza con la Claude Code cheatsheet, convierte las revisiones repetidas en products and templates y usa Claude Code training si tu equipo necesita CI, reglas de review y propiedad de tests. Para profundizar, lee TDD with Claude Code, CI/CD setup y debugging techniques.
Resultado de la prueba práctica
Masa probó este patrón en los CTA de artículos y flujos hacia productos de ClaudeCodeLab. Lo más útil no fue añadir muchos E2E, sino dejar la lógica de precios en unit tests, el comportamiento del CTA en Testing Library y un solo Playwright para la ruta artículo-productos. Pedir a Claude Code que listara primero los modos de fallo redujo tests omitidos y selectores CSS frágiles. El riesgo restante son pagos externos y scripts de anuncios, que aún necesitan evidencia de CI y una revisión manual breve antes de publicar.
PDF gratis: cheatsheet de Claude Code
Introduce tu email y descarga una hoja con comandos, hábitos de revisión y flujos seguros.
Cuidamos tus datos y no enviamos spam.
Sobre el autor
Masa
Ingeniero enfocado en workflows prácticos con Claude Code.
Artículos relacionados
Permission receipt para Claude Code: alcance, prueba y rollback
Patrón de permission receipt para Claude Code: acciones permitidas, aprobación, pruebas, rollback y CTA de ingresos.
Agent Harness seguro para Claude Code y Codex: permisos, verificacion y rollback
Diseña un Agent Harness seguro para Claude Code y Codex con permisos, plan, verificaciones y rollback.
Subagentes de Claude Code: guía práctica para delegar trabajo de forma segura
Guía práctica de subagentes en Claude Code para dividir artículos y código: reglas, prompts, riesgos y checklist.