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.

VS Code mostrando test mockear respuesta con route.fulfill status 200 dos clientes Empresa Inventada SA Córdoba y Playwright Mock Corp Buenos Aires, assertions toHaveCount 2 y toContainText
route.fulfill() devolviendo 2 clientes inventados. El servidor nunca recibe este request — Playwright lo intercepta antes.

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:

VS Code mostrando network-interception.spec.ts con page.on request y response, filtro Customer List, console.log de URL Método Body Status y Clientes recibidos
El spec completo del test 1: page.on() registra listeners para request y response, filtro por Customer/List para ignorar el ruido.

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

Terminal mostrando REQUEST interceptado URL Services Customer List Método POST, RESPONSE interceptada Status 200 Clientes recibidos 91 Primer cliente Alfreds Futterkiste, 4 passed
page.on() escuchando el tráfico. Sin tocar nada — solo observando qué manda el frontend y qué devuelve el servidor.

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.

VS Code mostrando test mockear respuesta con route.fulfill status 200 dos clientes Empresa Inventada SA Córdoba y Playwright Mock Corp Buenos Aires, assertions toHaveCount 2 y toContainText
route.fulfill() devolviendo 2 clientes inventados. El servidor nunca recibe este request — Playwright lo intercepta antes.

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

Grilla Serenity mostrando TEST1 Empresa Inventada SA César Beas y TEST2 Playwright Mock Corp Test User, Playwright Inspector con código route.fulfill al lado
La grilla renderizando clientes que no existen en el servidor. route.fulfill() los inyectó directamente en el browser.

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.

Reporte Playwright test mockear respuesta passed 10.2s, toHaveCount 2 en 55ms, toContainText Empresa Inventada SA 15ms, toContainText Playwright Mock Corp 8ms
El reporte confirma: 2 filas, datos inventados verificados campo a campo.

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:

VS Code mostrando test simular error 500 con route.fulfill status 500 body Internal Server Error simulado, assertion toHaveCount 0
route.fulfill() con status 500. La API "falla" y verificamos que la grilla queda vacía.

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

Reporte Playwright test simular error 500 passed 6.2s, toHaveCount 0 en 37ms, grilla vacía cuando API falla
Error 500 simulado. La grilla queda vacía — Serenity no muestra mensaje de error al usuario.

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.

VS Code mostrando test bloquear CSS con page.route CSS route.abort, assertion body not toBeEmpty
route.abort() bloqueando todos los archivos CSS. Una línea para cancelar cualquier tipo de recurso.

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().

Reporte Playwright test bloquear CSS passed 5.0s, not toBeEmpty en 34ms, página cargó sin estilos
CSS bloqueado, página cargó igual. El body tiene contenido — la app no se rompió sin estilos.

¿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
El request llega al servidor 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

Reporte HTML Playwright 5 passed 0 failed, interceptar request 5.5s, mockear respuesta 5.4s, simular error 500 5.1s, bloquear CSS 5.0s, total 26.4s
5 passed. 4 escenarios de network interception en un solo spec.

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