Una prueba E2E puede quedar en verde sin hacer una sola llamada al servicio que
crees estar verificando. Ocurre cuando page.route() intercepta la petición y
devuelve un JSON fijo. El navegador demostró que la pantalla reacciona a ese
JSON. No demostró que la API acepte el método, los headers, el cuerpo o el
formato de respuesta.
La solución no es abandonar los mocks. Dale a cada prueba una pregunta más
pequeña. Usa route.fulfill para comprobar estados de la interfaz con respuestas
deterministas. Usa route.fetch cuando necesites la respuesta real y quieras
cambiar un detalle. Usa HAR para repetir una conversación de red conocida.
Después ejecuta una comprobación separada contra staging, un sandbox o un
contrato verificado por el proveedor.
Este artículo parte de la guía de pruebas end-to-end con Playwright y acota el problema: cómo aislar la red sin convertir el aislamiento en falsa confianza.

Regla práctica
route.fulfillcomprueba cómo la UI trata un estado conocido.route.fetchmantiene una petición real y cambia la respuesta antes de entregarla al navegador.routeFromHARrepite tráfico grabado, pero depende de que coincidan URL, método y payload.- Una prueba separada debe verificar el contrato real. El mock no puede hacerlo solo.
¿Qué demuestra realmente un mock de API?
Un mock demuestra cómo se comporta el cliente cuando recibe la respuesta que tú
elegiste. La guía de Mock APIs de Playwright
muestra que route.fulfill puede detener la petición y devolver datos definidos
en la prueba. Es útil: el flujo del navegador no depende de la disponibilidad,
latencia o estado de un tercero en cada ejecución.
La evidencia que falta importa igual. La prueba no descubre que un endpoint
cambió de POST a PUT, que el servidor ahora exige un header, que el schema
eliminó un campo o que la autenticación expiró. El mock responde antes de que
esas condiciones puedan aparecer.
No es un defecto de Playwright. Es el límite del nivel de prueba. Una prueba de UI pregunta si una persona puede completar una acción después de recibir un estado. Una prueba de contrato o integración pregunta si dos sistemas siguen de acuerdo sobre un mensaje. La visión general de tipos de pruebas de software ayuda a separar esas preguntas antes de elegir la herramienta.
El falso positivo aparece cuando el equipo llama a la primera prueba "prueba de
API". El nombre promete una cobertura que el código no tiene. Es mejor usar
nombres que declaren la frontera, como checkout muestra un error cuando payment devuelve 503 y sandbox acepta el contrato de creación de payment.
El mismo problema aparece en las pruebas de Playwright generadas por IA: una secuencia plausible y una aserción verde no demuestran que se ejercitó la dependencia correcta.
¿Cuándo usar route.fulfill, route.fetch o HAR?
Elige el método de interceptación según lo que deba permanecer bajo control. La guía Network de Playwright separa interceptar una petición, modificarla, abortarla y cambiar su respuesta. La diferencia de código es pequeña, pero la evidencia que produce cada prueba no es la misma.
| Técnica | ¿La petición llega a la API? | Mejor pregunta | Riesgo principal |
|---|---|---|---|
route.fulfill |
No | ¿La UI trata este estado conocido? | El fixture puede alejarse del contrato real. |
route.fetch + route.fulfill |
Sí, durante la prueba | ¿La UI trata una respuesta real con una variación controlada? | La prueba hereda inestabilidad y datos externos. |
routeFromHAR |
No, usa el archivo | ¿Funciona el flujo con una conversación de red conocida? | El HAR queda viejo o deja de coincidir con el payload. |
request o APIRequestContext |
Sí, en una prueba de API | ¿El servicio acepta la petición y devuelve el formato esperado? | Necesita un entorno controlado y credenciales de prueba. |
La guía Mock APIs de Playwright documenta los tres primeros caminos. Para el cuarto, APIRequestContext puede llamar endpoints, preparar el estado y comprobar el servicio sin pasar por la interfaz. Esta separación suele producir una suite más pequeña y fallos más localizados.
No elijas HAR solo porque grabar una sesión parezca rápido. Funciona bien cuando el flujo necesita varias peticiones estables y el archivo puede revisarse junto con la prueba. Para un estado de error, un fixture explícito enseña mejor qué ve la persona y por qué la prueba pasa.
¿Cómo crear un mock determinista de API en Playwright?
La prueba siguiente comprueba una pantalla que carga recomendaciones desde
/api/recommendations. Es ilustrativa: adapta la URL, el HTML y el contrato a
tu aplicación. Lo importante es registrar la ruta antes de page.goto(),
mantener pequeño el fixture y demostrar que la petición esperada pasó por el
interceptor.
// tests/recommendations.mock.spec.ts
import { test, expect } from "@playwright/test";
test("muestra recomendaciones cuando la API devuelve datos", async ({ page }) => {
let intercepted = false;
await page.route(/\/api\/recommendations\?userId=test-user$/, async (route) => {
intercepted = true;
await route.fulfill({
status: 200,
contentType: "application/json",
json: {
items: [{ id: "book-1", title: "Testing at the Boundary" }],
},
});
});
await page.goto("/recommendations?userId=test-user");
await expect(page.getByRole("heading", { name: "Testing at the Boundary" }))
.toBeVisible();
expect(intercepted).toBe(true);
});
Los campos status y contentType no son decoración. Hacen explícita la
respuesta que recibe la aplicación. Añade casos separados para una lista vacía,
una respuesta inválida y un error temporal. No metas todos los estados en una
prueba con condicionales. Cada fallo debe decir qué comportamiento dejó de
funcionar.
Si la pantalla necesita autenticación, deja el login en el fixture de contexto y haz que el mock represente solo la API controlada por el escenario. Un JSON enorme copiado de producción mezcla datos de preparación, contrato y escenario. Empieza con el objeto mínimo que prueba la decisión de la UI y añade campos solo cuando la aplicación los lea.
¿Cómo conservar la señal de la API real con route.fetch?
route.fetch hace la petición original y devuelve una respuesta que puedes
cambiar antes de llamar a route.fulfill. La API Route de Playwright
documenta este camino para los casos en que importa la llamada real, pero un
detalle de la respuesta debe quedar controlado.
test("muestra el estado vacío con una respuesta real", async ({ page }) => {
await page.route("**/api/recommendations", async (route) => {
const response = await route.fetch();
const body = await response.json();
await route.fulfill({
response,
json: { ...body, items: [] },
});
});
await page.goto("/recommendations?userId=test-user");
await expect(page.getByText("No recommendations yet")).toBeVisible();
});
Esta prueba plantea una pregunta diferente de la primera. Deja participar a la API, pero fuerza una variación que puede ser difícil de producir de forma segura en el entorno. Úsala para explorar la integración y el manejo de respuestas, no como sustituta de la prueba determinista que debe ejecutarse sin red.
Hay un coste: la prueba puede fallar por autenticación, red, límites de uso o un
cambio del servicio. Mantén route.fetch en una capa de integración pequeña o
en una ejecución separada. La ruta rápida de la suite debe seguir usando
route.fulfill para estados conocidos.
¿Cómo repetir una conversación con routeFromHAR?
HAR resulta útil cuando un flujo depende de varias peticiones y el archivo
grabado puede revisarse como un artefacto. La guía de mocks de Playwright
recomienda grabar el HAR, versionarlo junto con la prueba y desactivar las
actualizaciones durante las ejecuciones normales. La coincidencia considera URL
y método. En POST, también importa el payload.
test("repite el flujo grabado de recomendaciones", async ({ page }) => {
await page.routeFromHAR("./hars/recommendations.har", {
url: "**/api/recommendations",
update: false,
});
await page.goto("/recommendations?userId=test-user");
await expect(page.getByRole("heading", { name: "Testing at the Boundary" }))
.toBeVisible();
});
Revisa un HAR como si fuera código. Quita cookies, tokens, datos personales y respuestas que no pertenezcan al escenario. Si la prueba pasa solo porque un HAR antiguo responde a un patrón demasiado amplio, ganaste velocidad y perdiste diagnóstico. Una defensa útil es fallar cuando ninguna entrada coincide, en vez de dejar que la aplicación continúe con una respuesta inesperada.
¿Cómo demostrar que el mock no ocultó el contrato?
La forma más sencilla es separar las capas. La suite del navegador comprueba el
comportamiento visible con route.fulfill. Una suite de API usa request o un
APIRequestContext independiente contra staging, un sandbox o un contrato
verificado por el proveedor. Cuando dos equipos evolucionan un endpoint por
separado, el contract testing orientado por el consumidor comprueba las
expectativas del consumidor contra el proveedor, como explica PactFlow.
// tests/recommendations.contract.spec.ts
import { test, expect } from "@playwright/test";
test("staging mantiene el contrato de recomendaciones", async ({ request }) => {
const response = await request.get("/api/recommendations?userId=test-user");
expect(response.ok()).toBe(true);
const body = await response.json();
expect(body).toEqual(
expect.objectContaining({
items: expect.any(Array),
}),
);
});
Esta prueba necesita un baseURL de staging y datos desechables. No apuntes una
suite automática a producción solo porque el endpoint sea público. Si no existe
un sandbox, coloca la llamada real detrás de una etapa manual o crea un contrato
explícito que el proveedor pueda verificar.
La matriz que buscas es pequeña:
route.fulfillcubre estados de la UI y se ejecuta con cada cambio.route.fetcho HAR cubre una variación que necesita una conversación de red más realista.- Una prueba de API o contrato comprueba método, autenticación, schema y estado del servicio en una ejecución separada.
No cuentes las tres como la misma cobertura. Pueden usar el mismo endpoint, pero responden preguntas distintas.
Cuando la dependencia tiene su propia superficie, las pruebas de contrato para un servidor MCP en TypeScript ofrecen la misma lección de frontera: el transporte y el schema necesitan una prueba separada del comportamiento de la pantalla.
¿Qué fallos vuelven engañoso este patrón?
El primer error es registrar una ruta demasiado amplia. **/api/** puede
capturar la petición que la prueba debía observar y devolver éxito para el
endpoint equivocado. Usa una expresión o glob estrecho y comprueba método,
parámetros de URL y cuerpo cuando esos valores formen parte del contrato.
El segundo es olvidar que SSR y las llamadas fuera del navegador ocurren en otro
proceso. page.route() intercepta el tráfico del contexto del navegador. No
cambia automáticamente una petición hecha durante el render del servidor. Una
discusión reciente en r/Playwright sobre APIs de Server Components
llama la atención sobre ese límite. Controla el proceso del servidor, usa un
proxy de prueba o prueba esa capa con una herramienta adecuada.
El tercero es dejar que un service worker oculte la petición. La guía Network de
Playwright explica que puede ser necesario
configurar serviceWorkers: "block" cuando los eventos de red no aparecen como
esperabas. Confirma la topología antes de cambiar el mock.
El cuarto es tratar los retries como aprobación. La guía de retries de Playwright
clasifica como flaky la prueba que falla en el primer intento y pasa después de
un retry. Más retries pueden mantener el pipeline en marcha, pero no demuestran
que el mock, el fixture o el entorno sean correctos.
Checklist para revisar un mock de API
Antes de aceptar la prueba, responde estas preguntas:
- ¿La prueba dice si comprueba la UI, la integración o el contrato?
- ¿La ruta se registra antes de navegar?
- ¿El patrón incluye método, URL y parámetros relevantes?
- ¿El fixture contiene solo los campos que necesita el escenario?
- ¿Existe un caso de error con status y cuerpo coherentes?
- ¿La prueba demuestra que interceptó la ruta esperada?
- ¿Existe otra prueba que llame a staging, un sandbox o al proveedor del contrato?
- ¿Cookies, tokens y datos personales están fuera del HAR y de los informes?
- ¿Un fallo de red aparece como fallo de integración y no como pantalla verde?
- ¿El primer retry produce una señal de flakiness en vez de ocultarla?
Si la última respuesta es no, la prueba aún puede ser útil. Dale un nombre y un lugar en la suite que correspondan a lo que realmente demuestra.
Preguntas frecuentes
¿Mockear la API vuelve inútil una prueba E2E?
No. El mock mantiene el navegador en estados difíciles de crear y hace que la ejecución sea repetible. No sustituye una prueba de integración o contrato. Usa el mock para el comportamiento de la UI y una llamada independiente para saber si el servicio real aún acepta el acuerdo.
¿route.fetch es siempre mejor que route.fulfill?
No. route.fetch conserva una llamada real y puede heredar inestabilidad, datos
externos y autenticación. route.fulfill es mejor para un caso determinista,
como un error 503 o una lista vacía. Elige según la señal que necesite la prueba,
no según qué opción parezca más cercana a producción.
¿Un HAR sustituye un entorno de staging?
No. HAR repite tráfico grabado y ayuda a probar el cliente sin red. No demuestra que el proveedor actual acepte la petición. Versiona el HAR como fixture y mantén una comprobación separada en staging, un sandbox o el contrato del servicio.
¿Cómo mockear una API usada durante SSR?
page.route() no alcanza automáticamente las llamadas hechas por el servidor.
Controla la dependencia en el proceso de render, apúntalo a un servicio de
prueba o prueba la API del servidor en su propia frontera. Después usa Playwright
para comprobar el HTML y el comportamiento que recibe el navegador.
Fuentes consultadas
- Playwright, "Mock APIs", consultado el 11/08/2026
- Playwright, "Best Practices", consultado el 11/08/2026
- Playwright, "Network", consultado el 11/08/2026
- Playwright, "Route", consultado el 11/08/2026
- Playwright, "APIRequestContext", consultado el 11/08/2026
- Playwright, "Retries", consultado el 11/08/2026
- PactFlow, "What is consumer-driven contract testing?", consultado el 11/08/2026