Мокирование серверных запросов в Playwright (Next.js, TanStack Start)
В приложении на Next.js или TanStack Start сервер вызывает ваш API, пока рендерит страницу. Мокирование запросов в Playwright эти вызовы никогда не видит. Разделы ниже объясняют почему, а затем сравнивают четыре способа управлять ими в тесте Playwright: тестовый режим Next.js (Next.js test mode), MSW внутри сервера, прямой прокси (forward proxy) вроде mockttp и запись реальных ответов с помощью test-proxy-recorder. Сведения о других инструментах проверены по их собственной документации и исходному коду 2026-10-03.
Почему page.route() не видит серверные запросы
Заголовок раздела «Почему page.route() не видит серверные запросы»page.route() в Playwright работает с «сетевыми запросами, которые делает страница». Server Component, route loader или server function выполняются не там, а в процессе Node.js вашего приложения. Их fetch идёт из этого процесса прямо в API. Запрос никогда не проходит через браузер, поэтому Playwright нечего перехватывать. page.route() видит запрос страницы, который браузер отправляет вашему приложению, и HTML, который приходит в ответ. Запросы, которые сервер сделал, чтобы собрать этот HTML, он не видит никогда.
У файлов HAR то же ограничение, потому что page.routeFromHAR() обслуживает «сетевые запросы, которые делаются на странице».
Сравнение MSW с Playwright говорит то же самое: page.route() влияет на «трафик в запущенном браузере, а не в процессе Node.js». Пост команды Playwright о мокировании на сервере говорит, что page.route() работает для запросов браузера, а «для серверных HTTP-вызовов — нет». Запрос на встроенное мокирование на сервере, microsoft/playwright#30766, был закрыт 2026-05-22 со ссылкой на этот пост.
Тестовый режим Next.js
Заголовок раздела «Тестовый режим Next.js»Next.js поставляет экспериментальную интеграцию с Playwright в next/experimental/testmode/playwright. Единственная документация к ней — README в репозитории Next.js, а руководство Next.js по Playwright её не упоминает. Тест регистрирует обработчики через next.onFetch(), и Next.js отправляет им вызовы fetch сервера:
// Сокращено из 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/);});Что для неё нужно:
experimental: { testProxy: true }вnext.config.js.playwright.config.ts, собранный черезdefineConfigизnext/experimental/testmode/playwright, и тесты, которые импортируютtestиз того же модуля.
Под капотом каждый воркер Playwright запускает небольшой прокси-сервер. Фикстура добавляет к запросам страницы заголовок Next-Test-Proxy-Port (указывает на этот прокси) и заголовок Next-Test-Data (id теста) (next-fixture.ts). Когда testProxy включён, сервер читает эти заголовки и отправляет каждый серверный fetch на этот прокси. Прокси передаёт запрос обработчикам этого теста (fetch.ts). Обработчики хранятся по id теста, поэтому параллельные тесты могут работать с одним сервером Next.js. Серверный fetch, на который не ответил ни один обработчик, выбрасывает Proxy request aborted. Вместо этого обработчик может вернуть 'continue', чтобы пропустить запрос дальше. next.onFetch() также получает собственные вызовы fetch страницы к другим источникам (origin).
Статус:
- Экспериментальный. Об этом говорят и путь импорта, и ключ конфигурации, а README называется «Experimental test mode for Playwright».
- Исправления в него всё ещё вносятся. Последнее из них, vercel/next.js#96525, было влито 2026-08-13.
- README также предлагает обработчики MSW для каждого теста через
next/experimental/testmode/playwright/msw. С MSW 3.0 (выпущен 2026-09-28) эта точка входа не загружается. Она импортируетstrict-event-emitter, от которого MSW 3.0 больше не зависит. Кроме того, она вызываетhandleRequest()из MSW, который в 3.0 удалён (заметки о выпуске MSW 3.0,msw.ts). С MSW 2.15.0 она загружается. Оба результата получены на Next.js 16.3.8 и проверены 2026-10-03.
MSW в сервере Next.js
Заголовок раздела «MSW в сервере Next.js»MSW перехватывает запросы в Node.js с помощью setupServer из msw/node (интеграция с Node.js). В его документации нет руководства по Next.js. Закрывая issue про App Router, автор MSW назвал эталоном интеграции mswjs/examples#101 (комментарий). Этот pull request открыт с 2024-01-22. Он запускает перехватчик из root layout с фиксированным списком обработчиков:
// app/layout.tsx, из mswjs/examples#101if (process.env.NEXT_RUNTIME === 'nodejs') { const { server } = require('@/mocks/node'); // setupServer(...handlers) server.listen();}Тогда обработчики живут в процессе Next.js. Пример запускает этот процесс через webServer из Playwright, который запускает один процесс на весь прогон (пост команды Playwright). Каждый воркер отправляет свои страницы на этот единственный сервер, и параллельные воркеры меняют то, как ведут себя его обработчики:
- Тест не может вызвать
server.use()на этом сервере, потому что тест выполняется в собственном процессе воркера. У MSW нет выпущенного API для изменения обработчиков в другом процессе. Предложение —setupRemoteServerв mswjs/msw#1617 — открыто с 2023-05-12. Привязка MSW к Playwright сообщает, что опирается наpage.route(), пока это API не выйдет (@msw/playwright). - Если вы добавите собственный канал, например API-маршрут, который вызывает
server.use(), изменение дойдёт до каждого теста. Обработчики, добавленные черезserver.use(), «сохраняются в экземпляре сервера». Пост о server boundary объясняет, что одновременные переопределения на одном сервере становятся «глобальным состоянием, общим для всех тестов». В #1617 автор MSW отмечает, что на практике вы либо держите «фиксированный список обработчиков», либо запускаете «отдельный экземпляр приложения на каждый тест».
На практике MSW внутри общего сервера Next.js отдаёт всем параллельным тестам одни и те же ответы. Два инструмента добавляют поверх MSW обработчики для каждого теста. Тестовый режим Next.js принимает обработчики MSW (с MSW 2, см. выше). scenarist различает сценарии по заголовку x-scenarist-test-id, который ваш серверный код пробрасывает в каждом fetch (адаптер для Next.js).
Прямой прокси вроде mockttp
Заголовок раздела «Прямой прокси вроде mockttp»Закрывая #30766, команда Playwright сослалась на пост Simon Knott. В нём mockttp, мок-сервер и прокси для HTTP и HTTPS, ставится перед исходящим трафиком сервера. Каждый тест добавляет в него правила, например mocks.forPost(url).thenJson(200, body).
Код приложения не меняется. Его серверный процесс запускается с переменными окружения, которые направляют исходящие запросы через прокси:
HTTP_PROXYиHTTPS_PROXYсо значением URL прокси.NODE_USE_ENV_PROXY=1, чтобы встроенный в Nodefetchиспользовал эти переменные. Node.js добавил её в 24.0.0 и 22.21.0 и отмечает, что она в активной разработке (документация Node.js).NODE_EXTRA_CA_CERTSс путём к CA-сертификату прокси, чтобы сервер доверял сертификатам, которые mockttp генерирует для HTTPS-хостов.
Прокси принадлежит одному воркеру Playwright, поэтому пост запускает по серверу приложения на каждый воркер из фикстуры воркера, каждый на свободном порту. Он обходится без webServer из Playwright, который запускает один процесс на весь прогон ещё до того, как появится хоть один воркер. В настройке из поста запросы без правила проходят к реальному API. Пост предлагает вместо этого отвечать на них ошибкой, чтобы забытый мок стал заметен.
Команда Playwright описывает этот подход как «не зависящий от языка и фреймворка сервера». Сам пост использует простой сервер на Node, а не Next.js или TanStack Start.
Запись реальных ответов с test-proxy-recorder
Заголовок раздела «Запись реальных ответов с test-proxy-recorder»Во всех трёх вариантах выше каждый ответ вы пишете сами. test-proxy-recorder записывает то, что возвращает ваш реальный API во время локального прогона, а затем воспроизводит это в CI.
- Прокси. Запустите
test-proxy-recorder <target-url>рядом с приложением на время тестового прогона. Направьте на него базовый URL API приложения, пока заданаTEST_PROXY_RECORDER_ENABLED. Каждый прокси пересылает запросы на один бэкенд — тот<target-url>, с которым он был запущен. registerProxyFetch(). Один вызов на сервере патчит глобальныйfetch. Он копирует заголовокx-test-rcrd-idтекущего запроса в каждый исходящий запрос. Прокси читает этот заголовок, чтобы отнести каждый серверный вызов к его тесту. В продакшене вызов ничего не делает, если не заданаTEST_PROXY_RECORDER_ENABLED.- Отдельный id для каждого теста.
playwrightProxy.before(page, testInfo, mode)строит id сессии из файла спецификации, названий его блоковdescribeи названия теста. Он отправляет этот id какx-test-rcrd-idв запросах страницы и переключает эту сессию вrecordилиreplay. Параллельные воркеры используют один сервер приложения и один прокси, и при этом каждый тест получает собственную запись.
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/ });});Запишите один раз против реального API и закоммитьте файлы .mock.json (сервер) и .har (браузер). Затем CI воспроизводит их с выключенным бэкендом. При воспроизведении запрос без записи получает 404, в котором он указан. До API он никогда не доходит.
Записанные ответы охватывают только то, что API делал во время записи. Чтобы принудительно получить ошибку или крайний случай, который API не выдаёт по требованию, проще написать обработчик вручную в одном из вариантов выше.
Подробности настройки — в руководствах по Next.js и TanStack Start.
Сравнение вариантов
Заголовок раздела «Сравнение вариантов»| Вариант | Настройка в приложении | Параллельные тесты | Обработчики или записанные ответы | Next.js и TanStack Start |
|---|---|---|---|---|
| Тестовый режим Next.js | experimental.testProxy в next.config.js | Да, обработчики хранятся отдельно для каждого теста | Обработчики (next.onFetch() или обработчики MSW 2) | Только Next.js |
| MSW в сервере | setupServer, запущенный в серверном коде (в примере MSW — в root layout) | Один набор обработчиков для всех тестов на этом сервере | Обработчики | Next.js: пример в открытом pull request. TanStack Start: не документировано |
| mockttp | Код не меняется. Переменные прокси и CA-сертификат для серверного процесса | Да, с отдельным сервером приложения на каждый воркер | Обработчики. Запросы без правила проходят дальше | Не документировано. Команда Playwright называет подход независимым от фреймворка |
| test-proxy-recorder | Базовый URL API направлен на прокси во время тестов, плюс registerProxyFetch() | Да, с одним общим сервером приложения | Записываются с реального API | Оба документированы |
Сравнение охватывает больше инструментов, включая Mocky Balboa, scenarist, Polly.js, talkback и proxay, а также варианты на стороне браузера.