Next.js
Frameworks SSR como o Next.js fazem chamadas fetch do lado do servidor que passam pelo proxy sem um contexto de navegador. O proxy identifica a qual sessão essas requisições pertencem por meio do header x-test-rcrd-id. O playwrightProxy.before() do Playwright já o define na navegação do navegador que dispara o SSR, então o id fica disponível em next/headers — o trabalho é anexá-lo às requisições do lado do servidor de saída. (Testes somente de navegador não precisam de nada disso; o proxy recai na sessão definida globalmente.)
registerProxyFetch (recomendado)
Seção intitulada “registerProxyFetch (recomendado)”Uma linha no seu root layout marca cada fetch do lado do servidor — Server Components, Route Handlers, nos runtimes Node e Edge:
import { registerProxyFetch } from 'test-proxy-recorder/nextjs';
registerProxyFetch(); // no-op em produção a menos que TEST_PROXY_RECORDER_ENABLED=trueEle faz patch do fetch global para copiar o x-test-rcrd-id da requisição atual para as requisições de saída, para que o proxy consiga distinguir sessões de reprodução concorrentes. Chame-o a partir do root layout — não do instrumentation.ts, cujo contexto difere daquele que renderiza suas rotas no runtime Edge, então um patch ali silenciosamente nunca dispara.
axios — registerProxyAxios
Seção intitulada “axios — registerProxyAxios”Se suas requisições do lado do servidor passam pelo axios, registre cada instância do lado do servidor uma vez:
import { registerProxyAxios } from 'test-proxy-recorder/nextjs';
registerProxyAxios(axiosForServer);Ele adiciona um interceptor de requisição que carimba o id (nunca tocando no fetch global), então fica imune à ressalva do servidor de desenvolvimento acima. No-op em produção / no navegador; idempotente por instância; nunca sobrescreve um id definido pelo chamador.
Por chamada — createHeadersWithRecordingId
Seção intitulada “Por chamada — createHeadersWithRecordingId”Sem patch, e funciona também sob next dev. Use-o para um único fetch, ou quando você preferir não fazer patch do 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)
Seção intitulada “Middleware (opcional)”Um proxy.ts (Next.js 16+, exportado como proxy) ou middleware.ts (15 e anteriores, exportado como middleware) chamando setNextProxyHeaders torna o id disponível via next/headers, mas não marca as buscas de saída — então não é obrigatório quando você usa um dos helpers acima. Use-o apenas se você já tem um middleware (auth, etc.) e ainda assim combine-o com um helper para fazer a marcação:
// 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); // expõe o id; combine com um helper acima return response;}
export const config = { matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],};Veja a referência da API para as assinaturas completas dos helpers de test-proxy-recorder/nextjs. Um projeto Edge completo e executável fica no exemplo de Edge runtime.
Cache e ISR
Seção intitulada “Cache e ISR”Não desative o cache para os testes — o recorder funciona com uma rota cacheada/ISR. Mas há uma regra que decide todo o design: para reproduzir um fetch SSR, a página precisa executar esse fetch no momento da requisição. Uma rota que serve HTML pré-renderizado ou um render cacheado obsoleto nunca faz o fetch, então o proxy não tem nada para servir e a asserção vê conteúdo obsoleto.
A forma de manter isso determinístico é cachear o fetch SSR com next.revalidate + next.tags no nível do fetch e depois invalidar sob demanda antes da asserção:
// app/isr/page.tsx — sem `export const dynamic`, sem `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'); // o Next.js 16 exige o 2º argumento de perfilawait page.request.post('/api/revalidate'); // purge forçadoawait page.goto('/isr'); // uma navegação — determinísticoawait expect(page.getByTestId('todo-text')).toHaveCount(1);O revalidateTag em uma entrada de cache de fetch é um purge forçado: a próxima leitura é um cache miss que bloqueia e refaz o fetch pelo proxy. Você precisa fazer o purge antes da navegação de reprodução porque o cache de dados sobrevive entre as fases de gravar → reproduzir de um único processo next start — caso contrário, a reprodução serve o cache da fase de gravação e nunca acessa o proxy (um falso positivo).
Durante os testes, o fetch com patch lê headers(), então a página renderiza dinamicamente e de fato executa o fetch. Em produção (recorder desativado), nada lê headers() e a página é ISR estática como de costume — o render dinâmico fica restrito aos testes e é intrínseco à gravação de um fetch SSR.
A revalidação sob demanda é privilegiada (ela purga o cache e força a regeneração), então proteja a rota com um segredo compartilhado — falhe de forma fechada se não estiver definido, compare em tempo constante e anexe o token a partir do teste via use.extraHTTPHeaders do Playwright, para que a spec nunca o manipule.
Veja o exemplo completo e executável (parte do exemplo de Next.js 16):
app/isr/page.tsx— a página cacheada (next.tagsno nível do fetch)app/api/revalidate/route.ts— como proteger orevalidateTag: falha fechada + comparação de segredo em tempo constantee2e/isr.spec.ts— invalide e faça uma navegação; afirma que a chamada de revalidação teve sucessoplaywright.config.ts— carrega o.enve anexa o segredo viaextraHTTPHeaders
Scripts do package.json
Seção intitulada “Scripts do package.json”Inicie os serviços a partir de scripts, não do 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\"" }}Um projeto completo e executável fica no exemplo de Next.js 16.