Next.js
Les frameworks SSR comme Next.js font des appels fetch côté serveur qui passent par le proxy sans contexte de navigateur. Le proxy identifie à quelle session appartiennent ces requêtes via l’en-tête x-test-rcrd-id. Le playwrightProxy.before() de Playwright le définit déjà sur la navigation navigateur qui déclenche le SSR, donc l’id est disponible dans next/headers — le travail consiste à l’attacher aux requêtes sortantes côté serveur. (Les tests navigateur uniquement n’ont besoin de rien de tout ça ; le proxy revient à la session définie globalement.)
registerProxyFetch (recommandé)
Section intitulée « registerProxyFetch (recommandé) »Une ligne dans votre root layout tagge chaque fetch côté serveur — Server Components, Route Handlers, sur les runtimes Node et Edge :
import { registerProxyFetch } from 'test-proxy-recorder/nextjs';
registerProxyFetch(); // no-op en production sauf si TEST_PROXY_RECORDER_ENABLED=trueIl patche le fetch global pour copier le x-test-rcrd-id de la requête courante sur les requêtes sortantes, afin que le proxy puisse distinguer les sessions de replay concurrentes. Appelez-le depuis le root layout — pas instrumentation.ts, dont le contexte diffère de celui qui rend vos routes sur le runtime Edge, donc un patch là-bas ne se déclenche jamais silencieusement.
axios — registerProxyAxios
Section intitulée « axios — registerProxyAxios »Si vos requêtes côté serveur passent par axios, enregistrez une fois chaque instance côté serveur :
import { registerProxyAxios } from 'test-proxy-recorder/nextjs';
registerProxyAxios(axiosForServer);Il ajoute un intercepteur de requête qui pose l’id (sans jamais toucher au fetch global), il est donc immunisé contre la réserve du serveur de dev ci-dessus. No-op en production / dans le navigateur ; idempotent par instance ; n’écrase jamais un id défini par l’appelant.
Par appel — createHeadersWithRecordingId
Section intitulée « Par appel — createHeadersWithRecordingId »Sans patch, et fonctionne aussi sous next dev. À utiliser pour un fetch unique, ou quand vous préférez ne pas patcher le 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 (optionnel)
Section intitulée « Middleware (optionnel) »Un proxy.ts (Next.js 16+, qui exporte proxy) ou middleware.ts (15 et antérieur, qui exporte middleware) appelant setNextProxyHeaders rend l’id disponible via next/headers, mais ne tagge pas les fetches sortants — il n’est donc pas requis quand vous utilisez l’un des helpers ci-dessus. Y recourir seulement si vous possédez déjà un middleware (auth, etc.), et le coupler tout de même avec un helper pour faire le tagging :
// 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); // expose l'id ; à coupler avec un helper ci-dessus return response;}
export const config = { matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],};Voir la référence de l’API pour les signatures complètes des helpers test-proxy-recorder/nextjs. Un projet Edge complet et exécutable se trouve dans l’exemple Edge runtime.
Mise en cache et ISR
Section intitulée « Mise en cache et ISR »Ne désactivez pas la mise en cache pour les tests — le recorder fonctionne avec une route mise en cache/ISR. Mais une règle décide de toute la conception : pour rejouer un fetch SSR, la page doit exécuter ce fetch au moment de la requête. Une route qui sert du HTML prérendu ou un rendu mis en cache obsolète ne fait jamais le fetch, donc le proxy n’a rien à servir et l’assertion voit du contenu obsolète.
La façon qui reste déterministe est de mettre en cache le fetch SSR avec next.revalidate + next.tags au niveau du fetch, puis d’invalider à la demande avant l’assertion :
// app/isr/page.tsx — pas de `export const dynamic`, pas de `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 exige le 2e argument de profilawait page.request.post('/api/revalidate'); // purge dureawait page.goto('/isr'); // une seule navigation — déterministeawait expect(page.getByTestId('todo-text')).toHaveCount(1);revalidateTag sur une entrée de cache de fetch est une purge dure : la lecture suivante est un cache miss qui bloque et refait le fetch à travers le proxy. Vous devez purger avant la navigation de replay car le cache de données survit entre les phases enregistrement → replay d’un même processus next start — sinon le replay sert le cache de la phase d’enregistrement et n’atteint jamais le proxy (un faux positif).
Pendant les tests, le fetch patché lit headers(), donc la page est rendue dynamiquement et exécute réellement le fetch. En production (recorder désactivé), rien ne lit headers() et la page est en ISR statique comme d’habitude — le rendu dynamique est limité aux tests, et il est intrinsèque à l’enregistrement d’un fetch SSR.
La revalidation à la demande est privilégiée (elle purge le cache et force une régénération), donc protégez la route avec un secret partagé — échouez en mode fermé s’il n’est pas défini, comparez en temps constant, et attachez le token depuis le test via use.extraHTTPHeaders de Playwright pour que le spec ne le manipule jamais.
Voir l’exemple complet et exécutable (partie de l’exemple Next.js 16) :
app/isr/page.tsx— la page mise en cache (next.tagsau niveau du fetch)app/api/revalidate/route.ts— comment protégerrevalidateTag: échec en mode fermé + comparaison du secret en temps constante2e/isr.spec.ts— invalide, puis une seule navigation ; vérifie que l’appel de revalidation a réussiplaywright.config.ts— charge.envet attache le secret viaextraHTTPHeaders
Scripts package.json
Section intitulée « Scripts package.json »Démarrez les services depuis des scripts, pas depuis 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 projet complet et exécutable se trouve dans l’exemple Next.js 16.