Qué es un test E2E
Un test end-to-end automatiza un navegador real (o headless) para reproducir un flujo completo de usuario: abre la página, escribe en un formulario, hace clic, espera la respuesta del backend y verifica lo que ve. Si pasa, tienes una prueba fuerte de que el sistema entero funciona junto.
Herramientas: Playwright y Cypress
- Playwright (Microsoft): controla Chromium, Firefox y WebKit con la misma API; buen auto-waiting, paralelismo y trazas.
- Cypress: corre dentro del propio navegador, con una experiencia de depuración muy visual.
// Conceptual (Playwright no corre en este sandbox):
await page.goto("/login");
await page.fill("#email", "ana@x.com");
await page.click("text=Entrar");
await expect(page.getByText("Bienvenida")).toBeVisible();
¿Cuándo merece la pena?
Reserva los E2E para los flujos críticos de negocio: registro, login, compra, el "camino feliz" que no puede romperse. No intentes cubrir cada caso límite con E2E; eso se hace mejor con unit/integración (más baratos y precisos).
Flakiness (fragilidad)
Un test flaky pasa a veces y falla otras sin que cambie el código. Es el mayor enemigo de los E2E: erosiona la confianza en la suite. Causas y remedios:
- Esperas fijas (
sleep(500)): sustitúyelas por esperas a una condición (que el elemento sea visible, que la red termine). Las buenas herramientas hacen auto-waiting. - Selectores frágiles (clases CSS de estilo): usa selectores estables como
data-testid. - Datos compartidos / orden: cada test debe preparar y limpiar sus propios datos y ser independiente del resto.
Testing en CI
En integración continua (CI) la suite se ejecuta automáticamente en cada push o pull request, normalmente con el navegador en modo headless. Buenas prácticas: ejecutar los unit primero (fallan rápido y barato), paralelizar, guardar capturas/vídeos/trazas de los E2E fallidos para depurar, y bloquear el merge si la suite no pasa.
Ejemplos
Esperar a una condición, no a un tiempo fijo
// Mal: frágil ante variaciones de tiempo.
// await sleep(500); expect(visible(boton));
// Bien: esperamos a que la condición se cumpla.
async function esperarHasta(cond, intentos = 10) {
for (let i = 0; i < intentos; i++) {
if (cond()) return true;
await new Promise((r) => setTimeout(r, 10));
}
return false;
}
let listo = false;
setTimeout(() => { listo = true; }, 30);
console.log(await esperarHasta(() => listo)); // true