Playwright: network interception — interceptar, mockear y bloquear requests HTTP
Interceptar, mockear y bloquear requests HTTP en Playwright con page.route() y page.on() — cuatro escenarios de network interception sin mock servers externos ni proxies, en un solo spec.
Contexto: por qué interceptar tráfico
Este post documenta network interception en Playwright: interceptar, modificar y bloquear requests HTTP durante tests de UI. Es parte de mi serie de Playwright + TypeScript.
En el post anterior hice API testing nativo — Playwright mandando requests directos, sin browser. Yo era el cliente.
Acá es distinto. Yo no hago el request. La app lo hace sola.
Cuando la grilla de clientes carga en Serenity, el frontend hace un POST a /Services/Northwind/Customer/List para traer los datos. Eso pasa automáticamente. Lo vi en DevTools en el post anterior.
Lo que hace page.route() es meterte en el medio de eso. Entre el browser y el servidor. Interceptás ese request y podés:
- Leerlo — ver qué mandó, qué recibió
- Modificar la respuesta — el servidor devuelve 91 clientes, pero vos le decís al browser "recibiste 2 clientes inventados"
- Simular un error — el servidor responde bien, pero vos le decís al browser "recibiste un 500"
- Bloquearlo — el request nunca llega al servidor
La app no sabe que la estás manipulando. Cree que habló con el servidor real.
¿Hice algo de esto en la serie de Postman?
No. En la serie de Postman, yo hacía requests — le pegaba a la API, mandaba un GET, un POST, verificaba la respuesta. Yo era el cliente. Network interception es lo opuesto: la app hace el request y yo me meto en el medio.
¿Y en Selenium?
En Selenium no existe. No hay forma nativa de interceptar tráfico HTTP durante un test. Si querías algo así, necesitabas herramientas externas como BrowserMob Proxy — otra dependencia, otra configuración, otro mundo.
En Playwright es una línea: page.route().
Test 1: escuchar tráfico con page.on()
Antes de modificar nada, quise observar. page.on() registra listeners de eventos — cada request y cada response que pasa por el browser.
Creé tests/learning/network-interception.spec.ts:

Filtro por Customer/List para no ver todo el ruido de requests de CSS, JS, imágenes. Solo me interesa la API de la grilla.
Resultado

Sin tocar nada, sin DevTools abierto, desde el test estoy viendo exactamente qué manda el frontend y qué devuelve el servidor:
- POST a
Services/Northwind/Customer/List - Body con
Take:100,IncludeColumns,EqualityFilter - Response: Status 200, 91 clientes, primer cliente Alfreds Futterkiste
Esto ya tiene valor: podés auditar tráfico desde tests automatizados. Pero es solo observar. Ahora viene lo potente.
Test 2: mockear respuesta con route.fulfill()
page.route() intercepta un request antes de que llegue al servidor. route.fulfill() lo responde con lo que yo quiera — el servidor nunca se entera.

El patrón **/Services/Northwind/Customer/List matchea cualquier URL que termine con esa ruta. route.fulfill() devuelve un JSON con 2 clientes que inventé: "Empresa Inventada SA" en Córdoba y "Playwright Mock Corp" en Buenos Aires. Clientes que nunca existieron en la base de datos de Serenity.
Resultado

La grilla de Serenity renderizando "Empresa Inventada SA" con "César Beas" como contacto, y "Playwright Mock Corp" debajo. Datos que nunca existieron en el servidor. El browser cree que los recibió de la API.

El reporte confirma: toHaveCount(2), toContainText('Empresa Inventada SA'), toContainText('Playwright Mock Corp'). La UI renderizó exactamente lo que le mandé.
¿Para qué sirve esto en la vida real?
Testear la UI sin depender de datos del servidor. Ejemplos concretos:
- Verificar que la grilla muestra correctamente 0 registros, 1 registro, 1000 registros — sin tener que preparar esos datos en la base de datos
- Testear cómo se ve un nombre de empresa muy largo, o caracteres especiales, o campos vacíos
- Correr tests de UI sin necesitar un backend funcionando
- Reproducir un bug que solo pasa con datos específicos que el servidor ya no tiene
Test 3: simular error 500 con route.fulfill()
Mismo mecanismo, pero en vez de devolver datos, devuelvo un error:

Esto testea algo que casi nadie testea: qué pasa cuando la API falla. En producción, las APIs fallan. Timeouts, errores de servidor, mantenimiento. ¿La app se rompe? ¿Muestra un mensaje de error? ¿La grilla queda vacía?
Resultado

Serenity no muestra mensaje de error. La grilla queda vacía, sin filas. toHaveCount(0) pasó. No es necesariamente el mejor comportamiento de UX — un mensaje tipo "Error al cargar datos" sería más claro para el usuario — pero es lo que Serenity hace, y ahora lo sé porque lo testeé.
En Selenium, para testear esto necesitarías levantar un mock server externo como WireMock, configurar puertos, apuntar la app al mock. En Playwright es una función de route.fulfill().
Test 4: bloquear recursos con route.abort()
route.abort() cancela el request. El browser nunca recibe respuesta.

Acá bloqueo todos los archivos .css. La página carga sin estilos — toda rota visualmente pero funcional.
Un error real que tuve
Mi primer intento verificaba que el título "Clientes" fuera visible:
const titulo = page.locator('h2:has-text("Clientes")');
await expect(titulo).toBeVisible();
Falló. Sin CSS, la estructura visual de Serenity se rompe — el h2 que contiene "Clientes" no se renderiza como esperaba. Cambié a getByText('Clientes') y falló también: strict mode violation, 3 elementos contenían "Clientes" (dos en el sidebar, uno en el título).
Terminé simplificando la assertion a verificar que el body no esté vacío. El punto del test no era verificar qué se ve, sino demostrar route.abort().

¿Para qué sirve bloquear recursos?
- Performance testing — bloquear scripts de analytics, ads, tracking para medir cuánto ralentizan la carga
- Testing de resiliencia — ¿la app funciona si un CDN externo cae?
- Reducir ruido en tests — bloquear imágenes y fonts para que los tests corran más rápido
- Debugging — aislar qué recurso causa un problema
page.on() vs page.route(): la diferencia
page.on() |
page.route() |
|
|---|---|---|
| Qué hace | Escucha eventos | Intercepta y modifica |
| Modifica el request | No | Sí |
| El request llega al servidor | Sí | Depende (fulfill = no, continue = sí) |
| Uso típico | Logging, auditoría | Mocking, testing de errores |
page.on('request') y page.on('response') son observadores pasivos. page.route() es intervención activa.
Hay una tercera opción que no usé en este post: route.continue(). Deja pasar el request al servidor pero te permite modificar headers, URL o body antes de que salga. Es útil para inyectar headers de testing o redirigir a otro endpoint.
Contraste con Selenium
| Concepto | Selenium | Playwright |
|---|---|---|
| Interceptar request | BrowserMob Proxy (herramienta externa) | page.on() nativo |
| Mockear respuesta de API | WireMock + config de puertos + setup | route.fulfill() en el test |
| Simular error de servidor | Mock server externo | route.fulfill({ status: 500 }) |
| Bloquear recursos | No disponible nativamente | route.abort() |
| Líneas de código extra | 50-100+ (setup de proxy/mock server) | 5-10 (inline en el test) |
En Selenium, testear comportamiento de red requiere infraestructura adicional. En Playwright es parte del framework — mismo archivo, misma sintaxis, mismas assertions.
Los 4 tests pasando

5 passed. 26 segundos total. Los 4 escenarios cubiertos con un solo spec de ~100 líneas.
Estado actual
4 tests de network interception en tests/learning/network-interception.spec.ts. Escuchar, mockear, simular errores, bloquear — todo con page.route() y page.on(), sin herramientas externas, sin mock servers, sin proxies.
Próximo post: Lo que queda fuera del alcance de network interception es lo visual — cambios de layout, colores rotos, elementos que se movieron de lugar. Un test funcional que verifica texto y conteos no detecta eso. Eso es territorio de toHaveScreenshot(), que compara screenshots pixel a pixel contra una baseline.
—
🔗 Todo el código de esta serie está en: github.com/cesarbeassuarez/playwright-typescript-framework