Ir al contenido

Mock de peticiones del lado del servidor en Playwright (Next.js, TanStack Start)

En una app de Next.js o TanStack Start, el servidor llama a tu API mientras renderiza una página. El mocking de peticiones de Playwright nunca ve esas llamadas. Las secciones siguientes explican por qué y luego comparan cuatro formas de controlarlas en una prueba de Playwright: el modo de prueba de Next.js (Next.js test mode), MSW dentro del servidor, un proxy de reenvío (forward proxy) como mockttp y la grabación de respuestas reales con test-proxy-recorder. Los datos sobre las demás herramientas se comprobaron con su propia documentación y su código fuente el 2026-10-03.

Por qué page.route() no ve las peticiones del lado del servidor

Sección titulada «Por qué page.route() no ve las peticiones del lado del servidor»

El page.route() de Playwright actúa sobre las «peticiones de red que hace una página». En cambio, un Server Component, un loader de ruta o una server function se ejecutan en el proceso Node.js de tu app. Su fetch va desde ese proceso directamente a la API. La petición nunca pasa por el navegador, así que Playwright no tiene nada que interceptar. page.route() sí ve la petición de la página que el navegador envía a tu app, y el HTML que vuelve. Nunca ve las peticiones que hizo el servidor para construir ese HTML.

Los archivos HAR tienen el mismo límite, porque page.routeFromHAR() sirve «las peticiones de red que se hacen en la página».

La comparación de MSW con Playwright dice lo mismo: page.route() afecta «al tráfico del navegador lanzado, no al proceso de Node.js». La publicación del equipo de Playwright sobre el mocking del lado del servidor dice que page.route() funciona para las peticiones del navegador, y «para las llamadas HTTP del lado del servidor, no». La solicitud de funcionalidad para integrar el mocking del lado del servidor, microsoft/playwright#30766, se cerró el 2026-05-22 con un enlace a esa publicación.

Next.js incluye una integración experimental con Playwright en next/experimental/testmode/playwright. Su única documentación es un README en el repositorio de Next.js, y la guía de Playwright de Next.js no la menciona. Una prueba registra handlers con next.onFetch(), y Next.js les envía las llamadas fetch del servidor:

// Abreviado del README
import { test, expect } from 'next/experimental/testmode/playwright';
test('/product/shoe', async ({ page, next }) => {
next.onFetch((request) => {
if (request.url === 'http://my-db/product/shoe') {
return new Response(JSON.stringify({ title: 'A shoe' }), {
headers: { 'Content-Type': 'application/json' },
});
}
return 'abort';
});
await page.goto('/product/shoe');
await expect(page.locator('body')).toHaveText(/Shoe/);
});

Lo que necesita:

  • experimental: { testProxy: true } en next.config.js.
  • Un playwright.config.ts creado con defineConfig de next/experimental/testmode/playwright, y pruebas que importen test del mismo módulo.

Por dentro, cada worker de Playwright arranca un pequeño servidor proxy. El fixture añade a las peticiones de la página una cabecera Next-Test-Proxy-Port (ese proxy) y una cabecera Next-Test-Data (el id de la prueba) (next-fixture.ts). Con testProxy activado, el servidor lee esas cabeceras y envía cada fetch del lado del servidor a ese proxy. El proxy entrega la petición a los handlers de esa prueba (fetch.ts). Los handlers se guardan por id de prueba, así que las pruebas en paralelo pueden compartir un mismo servidor de Next.js. Un fetch del servidor al que no responde ningún handler lanza Proxy request aborted. En su lugar, un handler puede devolver 'continue' para dejar pasar la petición. next.onFetch() también recibe las llamadas fetch de la propia página a otros orígenes.

Estado:

  • Experimental. Tanto la ruta de importación como la clave de configuración lo indican, y el README se titula «Experimental test mode for Playwright».
  • Sigue recibiendo correcciones. La más reciente, vercel/next.js#96525, se fusionó el 2026-08-13.
  • El README también ofrece handlers de MSW por prueba a través de next/experimental/testmode/playwright/msw. Con MSW 3.0 (publicado el 2026-09-28) ese punto de entrada no llega a cargarse. Importa strict-event-emitter, del que MSW 3.0 ya no depende. También llama a handleRequest() de MSW, que la 3.0 eliminó (notas de la versión 3.0 de MSW, msw.ts). Con MSW 2.15.0 sí se carga. Ambos resultados son de Next.js 16.3.8, comprobados el 2026-10-03.

MSW intercepta peticiones en Node.js con setupServer de msw/node (integración con Node.js). Su documentación no tiene una guía para Next.js. Cuando el autor de MSW cerró el issue sobre el App Router, señaló mswjs/examples#101 como la referencia de integración (comentario). Ese pull request está abierto desde el 2024-01-22. Arranca el interceptor desde el root layout, con una lista fija de handlers:

// app/layout.tsx, de mswjs/examples#101
if (process.env.NEXT_RUNTIME === 'nodejs') {
const { server } = require('@/mocks/node'); // setupServer(...handlers)
server.listen();
}

Los handlers viven entonces en el proceso de Next.js. El ejemplo arranca ese proceso con el webServer de Playwright, que ejecuta un solo proceso para toda la ejecución (publicación del equipo de Playwright). Todos los workers envían sus páginas a ese único servidor, y los workers en paralelo cambian cómo se comportan sus handlers:

  • Una prueba no puede llamar a server.use() en ese servidor, porque la prueba se ejecuta en su propio proceso de worker. MSW no tiene ninguna API publicada para cambiar handlers en otro proceso. La propuesta, setupRemoteServer en mswjs/msw#1617, está abierta desde el 2023-05-12. La integración de MSW para Playwright dice que depende de page.route() hasta que eso se publique (@msw/playwright).
  • Si añades tu propio canal, como una ruta de API que llame a server.use(), el cambio llega a todas las pruebas. Los handlers añadidos con server.use() «persisten en la instancia del servidor». La publicación que presenta server boundary explica que las sobrescrituras concurrentes en un mismo servidor se convierten en «un estado global compartido entre todas las pruebas». En #1617, el autor de MSW señala que, en la práctica, o mantienes «una lista fija de handlers» o arrancas «una instancia de la app por caso de prueba».

En la práctica, MSW dentro de un servidor de Next.js compartido da las mismas respuestas a todas las pruebas en paralelo. Dos herramientas añaden handlers por prueba sobre MSW. El modo de prueba de Next.js acepta handlers de MSW (con MSW 2, ver arriba). scenarist identifica los escenarios por una cabecera x-scenarist-test-id que el código de tu servidor reenvía en cada fetch (adaptador de Next.js).

Cuando el equipo de Playwright cerró #30766, remitió a la publicación de Simon Knott. Esta pone mockttp, un servidor de mocks y proxy HTTP y HTTPS, delante del tráfico saliente del servidor. Cada prueba le añade reglas, como mocks.forPost(url).thenJson(200, body).

El código de la app no cambia. Su proceso de servidor arranca con variables de entorno que envían las peticiones salientes a través del proxy:

  • HTTP_PROXY y HTTPS_PROXY con la URL del proxy.
  • NODE_USE_ENV_PROXY=1, para que el fetch integrado de Node use esas variables. Node.js la añadió en 24.0.0 y 22.21.0, y la marca como en desarrollo activo (documentación de Node.js).
  • NODE_EXTRA_CA_CERTS apuntando al certificado de CA del proxy, para que el servidor confíe en los certificados que mockttp genera para los hosts HTTPS.

El proxy pertenece a un worker de Playwright, así que la publicación arranca un servidor de la app por worker desde un fixture de worker, cada uno en un puerto libre. Evita el webServer de Playwright, que arranca un solo proceso para toda la ejecución antes de que exista ningún worker. En la configuración de la publicación, las peticiones sin regla pasan a la API real. La publicación sugiere responderlas con un error, para que un mock olvidado salga a la luz.

El equipo de Playwright describe el enfoque como «independiente del lenguaje y del framework del servidor». La propia publicación usa un servidor Node sencillo, no Next.js ni TanStack Start.

Grabar respuestas reales con test-proxy-recorder

Sección titulada «Grabar respuestas reales con test-proxy-recorder»

Con las tres opciones anteriores, escribes tú cada respuesta. test-proxy-recorder graba lo que devuelve tu API real durante una ejecución local y luego lo reproduce en CI.

  • El proxy. Arranca test-proxy-recorder <target-url> junto a tu app para la ejecución de las pruebas. Apunta la URL base de la API de la app hacia él mientras TEST_PROXY_RECORDER_ENABLED esté definida. Cada proxy reenvía a un solo backend, el <target-url> con el que se arrancó.
  • registerProxyFetch(). Una llamada en el servidor parchea el fetch global. Copia la cabecera x-test-rcrd-id de la petición actual en cada petición saliente. El proxy lee esa cabecera para archivar cada llamada del lado del servidor bajo su prueba. La llamada es un no-op en producción salvo que TEST_PROXY_RECORDER_ENABLED esté definida.
  • El id por prueba. playwrightProxy.before(page, testInfo, mode) construye un id de sesión a partir del archivo de spec, sus títulos de describe y el título de la prueba. Envía ese id como x-test-rcrd-id en las peticiones de la página y cambia esa sesión a record o replay. Los workers en paralelo comparten un mismo servidor de la app y un mismo proxy, y aun así cada prueba obtiene su propia grabación.
app/layout.tsx
import { registerProxyFetch } from 'test-proxy-recorder/nextjs';
registerProxyFetch();
src/router.tsx
import { registerProxyFetch } from 'test-proxy-recorder/tanstack-start';
registerProxyFetch();
e2e/fixtures.ts
test.beforeEach(async ({ page }, testInfo) => {
await playwrightProxy.before(page, testInfo, 'replay', { url: /localhost:8100/ });
});

Graba una vez contra la API real y haz commit de los archivos .mock.json (servidor) y .har (navegador). Después, CI los reproduce con el backend apagado. Al reproducir, una petición sin grabación recibe un 404 que la identifica. Nunca llega a la API.

Las respuestas grabadas solo cubren lo que hizo la API mientras grababas. Para forzar un error o un caso límite que la API no produce a petición, un handler escrito a mano con una de las opciones anteriores es más sencillo.

Los detalles de configuración están en las guías de Next.js y TanStack Start.

OpciónConfiguración en la appPruebas en paraleloHandlers o respuestas grabadasNext.js y TanStack Start
Modo de prueba de Next.jsexperimental.testProxy en next.config.jsSí, los handlers se guardan por pruebaHandlers (next.onFetch() o handlers de MSW 2)Solo Next.js
MSW en el servidorsetupServer arrancado en el código del servidor (el root layout en el ejemplo de MSW)Un único conjunto de handlers para todas las pruebas de ese servidorHandlersNext.js: un ejemplo en un pull request abierto. TanStack Start: no documentado
mockttpSin cambios de código. Variables de proxy y un certificado de CA en el proceso del servidorSí, con un servidor de la app por workerHandlers. Las peticiones sin regla se dejan pasarNo documentado. El equipo de Playwright lo considera independiente del framework
test-proxy-recorderURL base de la API apuntando al proxy durante las pruebas, más registerProxyFetch()Sí, con un servidor de la app compartidoGrabadas de la API realAmbos documentados

La comparación cubre más herramientas, entre ellas Mocky Balboa, scenarist, Polly.js, talkback y proxay, además de las opciones del lado del navegador.