From n00b to ZeroCool / Profesionalización
Testing frontend sin llorar: tu stack mínimo para sobrevivir al sprint
Stack práctico para testear frontend con Vitest, Testing Library y Playwright: setup, ejemplos, errores comunes y checklist para CI.
Lo que vale la pena leer aquí
Y tú, con la laptop ya dando las últimas, te acuerdas del autoengaño clásico: “al rato le meto tests”. Spoiler: ese “al rato” solo aparece cuando ya te cayó el madrazo.
Intro con gancho
El bug no salió en tu máquina. Salió en production. En el demo con el cliente. En esa pantalla compartida donde también se veía tu Slack parpadeando y tu PM soltando un “¿y esto por qué no lo cachamos antes?”.
Y tú, con la laptop ya dando las últimas, te acuerdas del autoengaño clásico: “al rato le meto tests”. Spoiler: ese “al rato” solo aparece cuando ya te cayó el madrazo.
La neta: testing en frontend suena a castigo porque lo hacemos como si fuera religión. Herramientas pesadas, reglas raras, y la amenaza de “100% coverage o nada”. Mejor vamos a lo que sí jala en la vida real: un stack mínimo, decisiones claras y tests que sí te salvan el sprint (y el overtime).
Qué vas a aprender
- Cómo armar un stack mínimo de testing frontend sin hacerlo eterno.
- Qué probar con unit/integration (rápido) vs E2E (caro, pero necesario).
- Setup práctico con Vitest + Testing Library + Playwright.
- Ejemplos reales: componentes, hooks/servicios, mocks de API, formularios y flujos.
- Cómo meterlo a CI sin que tu pipeline se vuelva un castigo.
Contexto práctico
Si estás en un equipo chico (o te toca ser “fullstack” aunque tu contrato diga otra cosa), el testing no compite contra “calidad”; compite contra:
- deadline, porque el cliente ya pagó o el founder ya lo prometió.
- la talacha de bugs repetidos (“otra vez se rompió el botón de Guardar”).
- el onboarding: llega alguien nuevo, mete un cambio “en corto” y revienta media app.
Y si estás en agencia, ya sabes: el cambio “chiquito” termina en fuego cruzado. Un CSS “inofensivo” rompe Safari. Un refactor “rápido” te cambia el flujo del formulario y el cliente se da cuenta en la madrugada.
Decisión que te ahorra vida:
- No necesitas tests para todo.
- Sí necesitas tests para lo que más duele cuando se rompe.
La regla que más me ha funcionado: testea contratos, no pixeles.
- Contrato = “si le pico aquí, pasa esto; si la API falla, muestro esto; si falta un campo, no dejo avanzar”.
- Pixeles = “que el botón mida 42px”. Eso casi siempre es fragilidad disfrazada.
Paso a paso: un stack mínimo que sí jala
Voy a asumir Vite/React/TS, pero el enfoque aplica igual para Vue/Svelte (nomás cambias el adapter).
1) Decide tu pirámide (y no te auto-sabotees)
Recomendación realista para sobrevivir al jale:
- 70% unit/integration (rápidos, corren en Node)
- 20% tests de UI a nivel DOM (comportamiento)
- 10% E2E (Playwright, flujos críticos)
Si te vas con “puro E2E”, vas a terminar con:
- tests lentos,
- flaky tests (fallan por timing o por ambiente),
- y un equipo que los ignora porque “seguro es el CI”.
Si te vas con “puro unit”, te va a dar falsa paz… hasta que el usuario no pueda terminar el flujo real.
2) Instala lo básico: Vitest + Testing Library
Vitest (runner) + Testing Library (probar como usuario) es el sweet spot para frontend.
npm i -D vitest jsdom @testing-library/react @testing-library/jest-dom
Configura vitest en vite.config.ts:
/// <reference types="vitest" />
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
test: {
environment: 'jsdom',
setupFiles: './src/test/setup.ts',
globals: true,
clearMocks: true,
},
})
Crea src/test/setup.ts:
import '@testing-library/jest-dom'
Agrega scripts:
{
"scripts": {
"test": "vitest",
"test:run": "vitest run",
"test:ui": "vitest --ui"
}
}
Decisión práctica: usa vitest --ui mientras iteras. Baja fricción brutal. Menos “¿ya corrió? ¿por qué falló? ¿dónde está el assert?”.
3) Testea UI sin odiar tu vida (Testing Library)
Ejemplo clásico: botón de guardar que se deshabilita si el form está inválido.
SaveProfileButton.tsx:
type Props = {
canSave: boolean
onSave: () => Promise<void>
}
export function SaveProfileButton({ canSave, onSave }: Props) {
return (
<button
disabled={!canSave}
onClick={() => void onSave()}
>
Guardar
</button>
)
}
Test:
import { render, screen } from '@testing-library/react'
import { SaveProfileButton } from './SaveProfileButton'
describe('SaveProfileButton', () => {
it('deshabilita cuando no se puede guardar', () => {
render(<SaveProfileButton canSave={false} onSave={async () => {}} />)
expect(screen.getByRole('button', { name: /guardar/i })).toBeDisabled()
})
})
Notas de guerra:
getByRolete empuja a componentes accesibles. Eso se traduce en menos bugs raros y menos tickets de “no me deja”.- No pruebes
classNamesi no es contrato. Es deuda disfrazada.
4) Integra API sin mocks feos: MSW (opcional, pero buenísimo)
Cuando el UI depende de fetch/axios, los mocks manuales se vuelven espagueti en dos sprints. MSW intercepta requests como si tuvieras un mini backend para pruebas.
npm i -D msw
Handler simple:
// src/test/handlers.ts
import { http, HttpResponse } from 'msw'
export const handlers = [
http.get('/api/profile', () => {
return HttpResponse.json({ name: 'Lupita', plan: 'pro' })
}),
]
Setup:
// src/test/setup.ts
import '@testing-library/jest-dom'
import { setupServer } from 'msw/node'
import { handlers } from './handlers'
export const server = setupServer(...handlers)
beforeAll(() => server.listen())
afterEach(() => server.resetHandlers())
afterAll(() => server.close())
Decisión práctica: MSW vale la pena si tienes varios endpoints o necesitas probar errores/edge cases sin hackear medio código. Si solo es un fetch, mockea fetch y listo. No te cases con herramientas.
5) Lo mínimo de E2E con Playwright (para lo que sí importa)
E2E no es para testear todo. Es para asegurar flujos críticos: login, checkout, creación de recurso, lo que si se rompe te cuesta lana o usuarios.
Instala:
npm i -D @playwright/test
npx playwright install
Script:
{
"scripts": {
"e2e": "playwright test",
"e2e:ui": "playwright test --ui"
}
}
Ejemplo: login happy path.
import { test, expect } from '@playwright/test'
test('login permite entrar al dashboard', async ({ page }) => {
await page.goto('http://localhost:5173/login')
await page.getByLabel('Email').fill('demo@byteit.mx')
await page.getByLabel('Password').fill('supersecreto')
await page.getByRole('button', { name: /entrar/i }).click()
await expect(page).toHaveURL(/\/dashboard/)
await expect(page.getByRole('heading', { name: /dashboard/i })).toBeVisible()
})
Tips de vida real:
- Si tu app usa auth real, en E2E conviene tener usuario de pruebas y una forma de limpiar data. Si no, vas a estar haciendo rollback mental de estados sucios.
- Usa
--uipara cazar flakiness. Te enseña el timeline y no te deja adivinar.
6) CI sin drama: corre rápido y falla útil
No metas todo siempre. Si lo haces, el pipeline se vuelve lento, la banda lo ignora y terminas aprobando pull requests “por fe”.
- En pull request:
npm run test:run(unit/integration) - En merge a main (o nightly):
npm run e2e
Ejemplo GitHub Actions (mínimo):
name: tests
on: [pull_request]
jobs:
unit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- run: npm run test:run
Decisión práctica: si tu CI está lento (clásico con runners compartidos), el mejor ROI es que unit/integration vuele. Si un PR tarda 25–30 min en validar, la gente va a meter cambios “a ver si pasa”. Y ahí se muere el workflow.

Screenshots sugeridos
- Vitest UI con una suite fallando y el diff claro.
- Un test con
screen.getByRole(...)en tu editor, resaltando roles/labels. - Playwright UI con el timeline de pasos del test.
- Reporte simple de cobertura (si lo activas) para ubicar módulos pelones.
Errores comunes (y cómo salir del hoyo)
1) Tests frágiles por selectors chafas
Síntoma: usas .querySelector('.btn-primary') o data-testid para todo.
Arreglo:
- Primero intenta
getByRole,getByLabelText,getByText. data-testidsolo cuando no hay semántica posible (por ejemplo, un icono sin texto o un wrapper muy técnico).
2) “Mockeé todo” y ahora no confío en nada
Síntoma: el test pasa aunque el componente esté roto en UI.
Arreglo:
- Mockea lo externo (API, storage, fecha), no tu propia lógica.
- Si todo está mockeado, lo que probaste fue tu habilidad para escribir mocks, no tu app.
3) Flakiness en E2E por timing
Síntoma: “en mi máquina pasa”, en CI falla 1 de cada 5. Y justo cuando traes deadline.
Arreglo:
- No uses
waitForTimeout. Es deuda. - Espera por señales reales:
expect(locator).toBeVisible(),toHaveURL,toHaveText. - Controla data: seeds, limpiar estado, usuario de pruebas estable.
4) Snapshot testing como metralleta
Síntoma: actualizas snapshots sin leerlos y un cambio real se cuela a production.
Arreglo:
- Snapshots solo para outputs estables (por ejemplo, un formatter) o componentes muy simples.
- Prefiere asserts explícitos (“debe mostrar error X”).
5) Cobertura como KPI para regañar
Síntoma: la banda mete tests inútiles para subir el numerito.
Arreglo:
- Usa cobertura para detectar zonas sin tests, no para castigar.
- Mide riesgo: pagos, auth, permisos, flujos core. Eso primero.

Checklist final
- Tengo Vitest + Testing Library corriendo local (
npm run test:ui). - Mis tests usan roles/labels en vez de clases.
- Tengo al menos 1 test por flujo crítico (no por componente).
- E2E corre separado y no bloquea cada PR si es pesado.
- Cuando una request importa, uso MSW o un mock consistente.
- Mis tests fallan con mensajes útiles (no “expected true to be true”).
- El pipeline tarda lo suficiente para confiar en él (no 30 min por PR).
FAQ
1) ¿Vitest o Jest?
Vitest si estás en Vite (o quieres velocidad y mejor DX). Jest sigue siendo válido, pero Vitest se siente más ligero para frontend y el setup suele ser menos dramático.
2) ¿Testing Library sirve si no uso React?
Sí. Hay variantes para Vue/Svelte/Angular. La idea se mantiene: pruebas comportamiento desde la perspectiva del usuario, no la implementación.
3) ¿Cuándo sí usar data-testid?
Cuando el elemento no tiene un rol/label/texto estable (iconos, piezas puramente visuales o wrappers). Si hay forma semántica, mejor úsala.
4) ¿Qué tanto E2E necesito?
Lo mínimo para dormir: login + 1 o 2 flujos que si se rompen te cuestan dinero/usuarios. Lo demás vive mejor en unit/integration.
5) ¿Cómo evito que testing se sienta como “otra chamba más”?
Mételo al workflow: un test pequeño junto al bugfix. Empieza por lo que ya te dolió en production. Ese dolor es el mejor roadmap.
Siguiente episodio
Ya corre tu suite, pero ahora viene el verdadero upgrade: cómo diseñar componentes y hooks para que sean testeables sin meter 40 props.
Spoiler: muchas veces el problema no es el test. Es el diseño de tu arquitectura.
Idea para cerrar bien este post: toma una sola práctica de aquí y conviértela en algo que tu equipo pueda aplicar hoy.
Cuando un artículo aterriza en decisiones reales, deja de ser contenido y se vuelve ventaja.


