Сеть в E2E - это две разные границы, и путать их нельзя. Есть ваша собственная граница - фронтенд, ваш API, ваша база, - связность которой тест и должен доказывать. И есть чужая граница - платёжный провайдер, карты, погода, СМС-шлюз, - нестабильная, платная или опасная. Правило простое: своё держат настоящим, чужое - контролируемым. Всё мастерство работы с сетью в Playwright сводится к тому, чтобы не перепутать эти две стороны.
Своя граница чаще всего нужна для подготовки данных, и здесь помогает request - HTTP-клиент Playwright. Через него тест делает настоящий запрос к своему тестовому API: создаёт заказ, проверяет response.ok(), забирает созданную сущность из json(). Это тот же приём, что и в главе про данные, но подчёркивает суть: запрос идёт к вашему реальному backend, а не к моку. Подготовка данных через API быстрее и надёжнее прокликивания их создания в UI.
APIRequestContext полезен и сам по себе, а не только для setup. Через тот же request можно проверять API напрямую - без браузера, на уровне HTTP: отправить запрос, проверить статус, тело, заголовки. Это не подменяет E2E через UI, а дополняет его: часть контрактов дешевле и точнее проверить на уровне API, оставив браузерным сценариям то, что действительно требует интерфейса.
Чужую границу контролируют перехватом сети. page.route ловит запросы по шаблону URL и позволяет ответить за внешний сервис через route.fulfill - с заданным статусом, типом содержимого и телом. Так тест избавляется от зависимости от чужого нестабильного сервиса: вместо реального обращения к карте или погоде страница получает контролируемый, предсказуемый ответ, и сценарий перестаёт зависеть от доступности и капризов третьей стороны.
Полезно один раз увидеть обе стороны рядом - подготовку через свой API и контроль внешнего сервиса через route. Ниже - создание заказа настоящим запросом к своему backend и подмена ответа стороннего сервиса карт. К этой паре возвращаются каждый раз, когда в сценарии появляется сетевое взаимодействие и нужно решить, какая это граница - своя или чужая.
Главная ошибка сети - замокать предмет проверки. Если E2E-тест должен доказать, что ваш фронтенд и ваш backend правильно работают вместе, перехватывать собственный API нельзя: подменив его, тест проверяет мок, а не интеграцию, ради которой написан. Мок оправдан на внешней нестабильной границе, а не на своей. Спутать это - значит получить зелёный тест, ничего не доказывающий про связность вашей системы.
Отсюда рабочий критерий: мокать можно то, чем вы не владеете и что не проверяете этим тестом. Платёжный шлюз в сценарии оформления заказа - если тест про UI корзины, а не про сам платёж - разумно заменить контролируемым ответом. Но если тест именно про то, что заказ доходит до вашего backend и сохраняется, ваш API обязан быть настоящим. Граница мока проходит по тому, что именно доказывает сценарий.
Типичные провалы сети сводятся к путанице границ. Перехват собственного API в тесте, который должен доказать интеграцию, - зелёный, но пустой результат. Реальное обращение к нестабильному стороннему сервису без route - флак от чужой недоступности, 429 и таймаутов. И моканье всей сети сразу вместо точечной подмены внешней границы, из-за которого тест перестаёт проверять реальную систему. Своё - настоящим, чужое - контролируемым, и всегда по тому, что доказывает сценарий.
// Своя граница: подготовка настоящим запросом к своему backend
test('показывает созданный заказ', async ({ request, page }) => {
const response = await request.post('/api/test/orders', {
data: { status: 'paid', total: 1490 },
})
expect(response.ok()).toBeTruthy()
const order = await response.json()
await page.goto(`/orders/${order.id}`)
await expect(page.getByText('Оплачен')).toBeVisible()
})// Чужая граница: контролируемый ответ вместо реального сервиса
await page.route('https://maps.example.com/**', route =>
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ city: 'Ташкент' }),
}),
)