Next.js
Los frameworks SSR como Next.js hacen llamadas fetch del lado del servidor que pasan por el proxy sin un contexto de navegador. El proxy identifica a qué sesión pertenecen esas peticiones mediante la cabecera x-test-rcrd-id. El playwrightProxy.before() de Playwright ya la establece en la navegación del navegador que dispara el SSR, así que el id está disponible en next/headers — el trabajo es adjuntarlo a las peticiones salientes del lado del servidor. (Las pruebas solo de navegador no necesitan nada de esto; el proxy recurre a la sesión establecida globalmente.)
registerProxyFetch (recomendado)
Sección titulada «registerProxyFetch (recomendado)»Una línea en tu root layout etiqueta cada fetch del lado del servidor — Server Components, Route Handlers, en los runtimes Node y Edge:
import { registerProxyFetch } from 'test-proxy-recorder/nextjs';
registerProxyFetch(); // no-op en producción salvo TEST_PROXY_RECORDER_ENABLED=trueParchea el fetch global para copiar el x-test-rcrd-id de la petición actual en las peticiones salientes, de modo que el proxy pueda distinguir sesiones de reproducción concurrentes. Llámalo desde el root layout — no desde instrumentation.ts, cuyo contexto difiere del que renderiza tus rutas en el runtime Edge, así que un parche allí nunca se dispara de forma silenciosa.
axios — registerProxyAxios
Sección titulada «axios — registerProxyAxios»Si tus peticiones del lado del servidor van por axios, registra cada instancia del lado del servidor una vez:
import { registerProxyAxios } from 'test-proxy-recorder/nextjs';
registerProxyAxios(axiosForServer);Añade un interceptor de petición que estampa el id (sin tocar el fetch global), así que es inmune a la advertencia del servidor de desarrollo de arriba. No-op en producción / en el navegador; idempotente por instancia; nunca sobrescribe un id establecido por el llamador.
Por llamada — createHeadersWithRecordingId
Sección titulada «Por llamada — createHeadersWithRecordingId»Sin parchear, y funciona también bajo next dev. Úsalo para un único fetch, o cuando prefieras no parchear el fetch global:
import { headers } from 'next/headers';import { createHeadersWithRecordingId } from 'test-proxy-recorder/nextjs';
const res = await fetch('http://localhost:8100/api/data', { headers: createHeadersWithRecordingId(await headers(), { 'Content-Type': 'application/json', }),});Middleware (opcional)
Sección titulada «Middleware (opcional)»Un proxy.ts (Next.js 16+, función exportada proxy) o middleware.ts (15 y anteriores, función exportada middleware) que llame a setNextProxyHeaders hace el id disponible vía next/headers, pero no etiqueta los fetch salientes — así que no es necesario si usas uno de los helpers de arriba. Recurre a él solo si ya tienes un middleware (auth, etc.), y aún así combínalo con un helper para hacer el etiquetado:
// proxy.ts (Next.js 16+)import { NextResponse } from 'next/server';import type { NextRequest } from 'next/server';import { setNextProxyHeaders } from 'test-proxy-recorder/nextjs';
export function proxy(request: NextRequest) { const response = NextResponse.next(); setNextProxyHeaders(request, response); // expone el id; combínalo con un helper de arriba return response;}
export const config = { matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],};Mira la referencia de la API para las firmas completas de los helpers de test-proxy-recorder/nextjs. Un proyecto Edge completo y ejecutable vive en el ejemplo de runtime Edge.
Caché e ISR
Sección titulada «Caché e ISR»No deshabilites el cacheo para las pruebas — el grabador funciona con una ruta cacheada/ISR. Pero hay una regla que define todo el diseño: para reproducir un fetch SSR, la página debe ejecutar ese fetch en el momento de la petición. Una ruta que sirve HTML prerenderizado o un render cacheado obsoleto nunca hace el fetch, así que el proxy no tiene nada que servir y la aserción ve contenido obsoleto.
La forma que se mantiene determinista es cachear el fetch SSR con next.revalidate + next.tags a nivel de fetch, y luego invalidar bajo demanda antes de la aserción:
// app/isr/page.tsx — sin `export const dynamic`, sin `export const revalidate`const res = await fetch(`${BACKEND_URL}/todos`, { next: { revalidate: 30, tags: ['isr-todos'] },});import { revalidateTag } from 'next/cache';revalidateTag('isr-todos', 'max'); // Next.js 16 requiere el 2º argumento de perfilawait page.request.post('/api/revalidate'); // purga duraawait page.goto('/isr'); // una navegación — deterministaawait expect(page.getByTestId('todo-text')).toHaveCount(1);revalidateTag sobre una entrada de caché de fetch es una purga dura: la siguiente lectura es un fallo de caché que se bloquea y vuelve a hacer el fetch a través del proxy. Debes purgar antes de la navegación de reproducción porque la caché de datos sobrevive entre las fases de grabación → reproducción de un mismo proceso next start — de lo contrario la reproducción sirve la caché de la fase de grabación y nunca llega al proxy (un falso positivo).
Durante las pruebas el fetch parcheado lee headers(), así que la página se renderiza dinámicamente y realmente ejecuta el fetch. En producción (grabador deshabilitado) nada lee headers() y la página es ISR estática como siempre — el render dinámico está acotado a las pruebas, y es intrínseco a grabar un fetch SSR.
La revalidación bajo demanda es privilegiada (purga la caché y fuerza la regeneración), así que protege la ruta con un secreto compartido — falla en cerrado si no está definido, compara en tiempo constante, y adjunta el token desde la prueba vía use.extraHTTPHeaders de Playwright para que el spec nunca lo maneje.
Mira el ejemplo completo y ejecutable (parte del ejemplo de Next.js 16):
app/isr/page.tsx— la página cacheada (next.tagsa nivel de fetch)app/api/revalidate/route.ts— cómo protegerrevalidateTag: falla en cerrado + comparación del secreto en tiempo constantee2e/isr.spec.ts— invalida, luego una navegación; comprueba que la llamada de revalidación tuvo éxitoplaywright.config.ts— carga.envy adjunta el secreto víaextraHTTPHeaders
Scripts de package.json
Sección titulada «Scripts de package.json»Inicia los servicios desde scripts, no desde playwright.config.ts:
{ "scripts": { "mock": "node mock-backend/server.mjs", "proxy": "test-proxy-recorder http://localhost:3002 -p 8100 -d ./e2e/recordings", "start:all": "concurrently \"pnpm mock\" \"pnpm proxy\" \"pnpm build && next start --port 3000\"" }}Un proyecto completo y ejecutable vive en el ejemplo de Next.js 16.