Перейти к содержимому

Мокирование серверных запросов в 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() в 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 поставляет экспериментальную интеграцию с Playwright в next/experimental/testmode/playwright. Единственная документация к ней — README в репозитории Next.js, а руководство Next.js по Playwright её не упоминает. Тест регистрирует обработчики через next.onFetch(), и Next.js отправляет им вызовы fetch сервера:

// Сокращено из README
import { 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 перехватывает запросы в 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#101
if (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).

Закрывая #30766, команда Playwright сослалась на пост Simon Knott. В нём mockttp, мок-сервер и прокси для HTTP и HTTPS, ставится перед исходящим трафиком сервера. Каждый тест добавляет в него правила, например mocks.forPost(url).thenJson(200, body).

Код приложения не меняется. Его серверный процесс запускается с переменными окружения, которые направляют исходящие запросы через прокси:

  • HTTP_PROXY и HTTPS_PROXY со значением URL прокси.
  • NODE_USE_ENV_PROXY=1, чтобы встроенный в Node fetch использовал эти переменные. 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 записывает то, что возвращает ваш реальный 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. Параллельные воркеры используют один сервер приложения и один прокси, и при этом каждый тест получает собственную запись.
app/layout.tsx
import { registerProxyFetch } from 'test-proxy-recorder/nextjs';
registerProxyFetch();
src/router.tsx
import { registerProxyFetch } from 'test-proxy-recorder/tanstack-start';
registerProxyFetch();
e2e/fixtures.ts
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.jsexperimental.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, а также варианты на стороне браузера.