Mocker les requêtes côté serveur dans Playwright (Next.js, TanStack Start)
Dans une app Next.js ou TanStack Start, le serveur appelle votre API pendant qu’il rend une page. Le mock de requêtes de Playwright ne voit jamais ces appels. Les sections ci-dessous expliquent pourquoi, puis comparent quatre façons de les contrôler dans un test Playwright : le mode test de Next.js, MSW dans le serveur, un proxy sortant (forward proxy) comme mockttp, et l’enregistrement de vraies réponses avec test-proxy-recorder. Les faits concernant les autres outils ont été vérifiés dans leur propre documentation et leur code source le 2026-10-03.
Pourquoi page.route() ne voit pas les requêtes côté serveur
Section intitulée « Pourquoi page.route() ne voit pas les requêtes côté serveur »La méthode page.route() de Playwright agit sur les « network requests that are made by a page ». Un Server Component, un loader de route ou une server function s’exécute au contraire dans le processus Node.js de votre app. Son fetch part de ce processus directement vers l’API. La requête ne passe jamais par le navigateur, donc Playwright n’a rien à intercepter. page.route() voit bien la requête de page que le navigateur envoie à votre app, et le HTML qui revient. Il ne voit jamais les requêtes que le serveur a faites pour construire ce HTML.
Les fichiers HAR ont la même limite, car page.routeFromHAR() sert « the network requests that are made in the page ».
La comparaison de MSW avec Playwright dit la même chose : page.route() agit sur « the traffic in the spawned browser, not the Node.js process ». L’article de l’équipe Playwright sur le mock côté serveur indique que page.route() fonctionne pour les requêtes du navigateur, puis : « For server-side HTTP calls, it doesn’t. » La demande de fonctionnalité pour un mock côté serveur intégré, microsoft/playwright#30766, a été fermée le 2026-05-22 avec un lien vers cet article.
Mode test de Next.js
Section intitulée « Mode test de Next.js »Next.js fournit une intégration Playwright expérimentale dans next/experimental/testmode/playwright. Sa seule documentation est un README dans le dépôt Next.js, et le guide Playwright de Next.js ne la mentionne pas. Un test déclare des handlers avec next.onFetch(), et Next.js leur envoie les appels fetch du serveur :
// Version raccourcie de l'exemple du READMEimport { 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/);});Ce qu’il faut :
experimental: { testProxy: true }dansnext.config.js.- Un
playwright.config.tsconstruit avec ledefineConfigdenext/experimental/testmode/playwright, et des tests qui importenttestdepuis le même module.
En coulisses, chaque worker Playwright démarre un petit serveur proxy. La fixture ajoute aux requêtes de la page un en-tête Next-Test-Proxy-Port (ce proxy) et un en-tête Next-Test-Data (l’id du test) (next-fixture.ts). Avec testProxy activé, le serveur lit ces en-têtes et envoie chaque fetch côté serveur à ce proxy. Le proxy transmet la requête aux handlers de ce test (fetch.ts). Les handlers sont conservés par id de test, donc des tests en parallèle peuvent partager un même serveur Next.js. Un fetch serveur auquel aucun handler ne répond lève Proxy request aborted. Un handler peut à la place renvoyer 'continue' pour laisser passer une requête. next.onFetch() reçoit aussi les propres appels fetch de la page vers d’autres origines.
Statut :
- Expérimental. Le chemin d’import et la clé de configuration le disent tous les deux, et le README s’intitule « Experimental test mode for Playwright ».
- Il reçoit encore des correctifs. Le plus récent, vercel/next.js#96525, a été fusionné le 2026-08-13.
- Le README propose aussi des handlers MSW par test via
next/experimental/testmode/playwright/msw. Avec MSW 3.0 (publié le 2026-09-28), ce point d’entrée ne se charge pas. Il importestrict-event-emitter, dont MSW 3.0 ne dépend plus. Il appelle aussihandleRequest()de MSW, que la 3.0 a supprimé (notes de version de MSW 3.0,msw.ts). Il se charge avec MSW 2.15.0. Les deux résultats ont été obtenus avec Next.js 16.3.8, le 2026-10-03.
MSW dans le serveur Next.js
Section intitulée « MSW dans le serveur Next.js »MSW intercepte les requêtes dans Node.js avec setupServer de msw/node (intégration Node.js). Sa documentation n’a pas de guide Next.js. Quand l’auteur de MSW a fermé l’issue sur l’App Router, il a désigné mswjs/examples#101 comme référence d’intégration (commentaire). Cette pull request est ouverte depuis le 2024-01-22. Elle démarre l’intercepteur depuis le root layout, avec une liste fixe de handlers :
// app/layout.tsx, tiré de mswjs/examples#101if (process.env.NEXT_RUNTIME === 'nodejs') { const { server } = require('@/mocks/node'); // setupServer(...handlers) server.listen();}Les handlers vivent alors dans le processus Next.js. L’exemple démarre ce processus avec le webServer de Playwright, qui lance un seul processus pour toute l’exécution (article de l’équipe Playwright). Chaque worker envoie ses pages à cet unique serveur, et des workers en parallèle changent le comportement de ses handlers :
- Un test ne peut pas appeler
server.use()sur ce serveur, car le test tourne dans son propre processus de worker. MSW n’a aucune API publiée pour changer les handlers d’un autre processus. La proposition,setupRemoteServerdans mswjs/msw#1617, est ouverte depuis le 2023-05-12. Le binding Playwright de MSW indique qu’il s’appuie surpage.route()en attendant sa sortie (@msw/playwright). - Si vous ajoutez votre propre canal, comme une route d’API qui appelle
server.use(), le changement atteint tous les tests. La documentation deserver.use()précise que les handlers ajoutés ainsi « persist on the server instance ». L’article sur la server boundary explique que des surcharges concurrentes sur un même serveur deviennent « a global state shared between all tests ». Dans #1617, l’auteur de MSW note qu’en pratique, soit on garde « a fixed list of handlers », soit on démarre « an app instance per test case ».
En pratique, MSW dans un serveur Next.js partagé donne les mêmes réponses à tous les tests parallèles. Deux outils ajoutent des handlers par test par-dessus MSW. Le mode test de Next.js accepte des handlers MSW (avec MSW 2, voir plus haut). scenarist identifie les scénarios grâce à un en-tête x-scenarist-test-id que le code de votre serveur transmet à chaque fetch (adaptateur Next.js).
Un proxy sortant comme mockttp
Section intitulée « Un proxy sortant comme mockttp »Quand l’équipe Playwright a fermé #30766, elle a renvoyé vers l’article de Simon Knott. Celui-ci place mockttp, un serveur de mock et proxy HTTP et HTTPS, devant le trafic sortant du serveur. Chaque test y ajoute des règles, comme mocks.forPost(url).thenJson(200, body).
Le code de l’app ne change pas. Son processus serveur démarre avec des variables d’environnement qui font passer les requêtes sortantes par le proxy :
HTTP_PROXYetHTTPS_PROXY, qui contiennent l’URL du proxy.NODE_USE_ENV_PROXY=1, pour que lefetchintégré de Node utilise ces variables. Node.js l’a ajoutée en 24.0.0 et 22.21.0, et la marque comme en développement actif (documentation de Node.js).NODE_EXTRA_CA_CERTS, qui pointe vers le certificat d’autorité (CA) du proxy, pour que le serveur fasse confiance aux certificats que mockttp génère pour les hôtes HTTPS.
Le proxy appartient à un seul worker Playwright : l’article démarre donc un serveur d’app par worker depuis une fixture de worker, chacun sur un port libre. Il évite le webServer de Playwright, qui démarre un seul processus pour toute l’exécution, avant qu’aucun worker n’existe. Dans la configuration de l’article, les requêtes sans règle passent jusqu’à la vraie API. L’article suggère plutôt d’y répondre par une erreur, pour qu’un mock oublié se remarque.
L’équipe Playwright décrit l’approche comme « agnostic of the server language and framework ». L’article lui-même utilise un simple serveur Node, ni Next.js ni TanStack Start.
Enregistrer de vraies réponses avec test-proxy-recorder
Section intitulée « Enregistrer de vraies réponses avec test-proxy-recorder »Avec les trois options ci-dessus, vous écrivez chaque réponse vous-même. test-proxy-recorder enregistre ce que renvoie votre vraie API pendant une exécution locale, puis le rejoue en CI.
- Le proxy. Lancez
test-proxy-recorder <target-url>à côté de votre app pour l’exécution des tests. Pointez l’URL de base de l’API de l’app vers lui tant queTEST_PROXY_RECORDER_ENABLEDest défini. Chaque proxy transmet à un seul backend, la<target-url>avec laquelle il a été lancé. registerProxyFetch(). Un seul appel sur le serveur patche lefetchglobal. Il recopie l’en-têtex-test-rcrd-idde la requête courante sur chaque requête sortante. Le proxy lit cet en-tête pour classer chaque appel côté serveur sous son test. L’appel est un no-op en production, sauf siTEST_PROXY_RECORDER_ENABLEDest défini.- L’id par test.
playwrightProxy.before(page, testInfo, mode)construit un id de session à partir du fichier de spec, de ses titresdescribeet du titre du test. Il envoie cet id commex-test-rcrd-idsur les requêtes de la page et passe cette session enrecordou enreplay. Les workers en parallèle partagent un même serveur d’app et un même proxy, et chaque test obtient tout de même son propre enregistrement.
import { registerProxyFetch } from 'test-proxy-recorder/nextjs';
registerProxyFetch();import { registerProxyFetch } from 'test-proxy-recorder/tanstack-start';
registerProxyFetch();test.beforeEach(async ({ page }, testInfo) => { await playwrightProxy.before(page, testInfo, 'replay', { url: /localhost:8100/ });});Enregistrez une fois contre la vraie API et committez les fichiers .mock.json (serveur) et .har (navigateur). La CI les rejoue ensuite avec le backend éteint. En replay, une requête sans enregistrement reçoit une 404 qui la nomme. Elle n’atteint jamais l’API.
Les réponses enregistrées ne couvrent que ce que l’API a fait pendant l’enregistrement. Pour forcer une erreur ou un cas limite que l’API ne produit pas à la demande, un handler écrit à la main avec l’une des options ci-dessus est plus simple.
Les détails de configuration se trouvent dans les guides Next.js et TanStack Start.
Comparer les options
Section intitulée « Comparer les options »| Option | Mise en place dans l’app | Tests en parallèle | Handlers ou réponses enregistrées | Next.js et TanStack Start |
|---|---|---|---|---|
| Mode test de Next.js | experimental.testProxy dans next.config.js | Oui, les handlers sont conservés par test | Handlers (next.onFetch(), ou handlers MSW 2) | Next.js uniquement |
| MSW dans le serveur | setupServer démarré dans le code serveur (le root layout dans l’exemple de MSW) | Un seul jeu de handlers pour tous les tests de ce serveur | Handlers | Next.js : un exemple dans une pull request ouverte. TanStack Start : non documenté |
| mockttp | Aucun changement de code. Variables de proxy et certificat CA sur le processus serveur | Oui, avec un serveur d’app par worker | Handlers. Les requêtes sans règle passent | Non documenté. L’équipe Playwright le dit indépendant du framework |
| test-proxy-recorder | URL de base de l’API pointée vers le proxy pendant les tests, plus registerProxyFetch() | Oui, avec un seul serveur d’app partagé | Enregistrées depuis la vraie API | Les deux documentés |
La comparaison couvre d’autres outils, dont Mocky Balboa, scenarist, Polly.js, talkback et proxay, ainsi que les options côté navigateur.