Когда тест Playwright падает с net::ERR_CONNECTION_REFUSED, ошибка говорит точно на каком уровне сетевого стека что-то сломалось: TCP: на этом порту никто не слушал когда тест попытался подключиться. Каждый запрос проходит четыре шага: DNS-запрос, TCP-соединение, TLS-рукопожатие и HTTP-обмен, и у каждого уровня своя сигнатура отказа. В этой статье: каждый шаг, статус-коды и заголовки которые встречаются в реальных падениях тестов, как куки управляют сессиями в Playwright и как читать сетевой трафик в trace viewer.
Что происходит когда Playwright открывает страницу
Когда тест выполняет await page.goto('https://lab.becomeqa.com'), происходят четыре последовательных действия:
1. DNS-запрос
Компьютер спрашивает DNS-сервер: «Какой IP-адрес у lab.becomeqa.com?»
DNS-сервер отвечает чем-то вроде 104.21.8.42. Доменные имена: читаемые псевдонимы. Реальная сеть работает с IP-адресами.
Что ломается здесь
ERR_NAME_NOT_RESOLVED означает что DNS не нашёл домен: либо домен не существует, либо DNS-сервер недоступен, либо контейнер в CI не может достучаться до внешнего DNS. Внутренние хосты вроде api.internal требуют правильной настройки сети CI чтобы DNS работал.
2. TCP-соединение
Компьютер открывает TCP-соединение к этому IP-адресу на конкретный порт.
- Порт 443: HTTPS (зашифрованный)
- Порт 80: HTTP (незашифрованный)
- Порты 3000, 8080 и т.д.: серверы разработки
Что ломается здесь
ERR_CONNECTION_REFUSED означает что никто не слушает на этом порту: сервер не запущен или ты попадаешь на неправильный порт. ERR_CONNECTION_TIMED_OUT означает что сервер недостижим (файрвол, неправильная сеть, сервер упал). В CI это часто означает что сервер приложения не успел запуститься до старта теста.
3. TLS-рукопожатие (только HTTPS)
Браузер и сервер обмениваются сертификатами и согласовывают шифрование. Это «S» в HTTPS.
Что ломается здесь
ERR_CERT_AUTHORITY_INVALID означает что SSL-сертификат самоподписанный или просроченный, что часто встречается на staging-окружениях. ERR_SSL_PROTOCOL_ERROR указывает на несовпадение версий TLS или ошибку конфигурации. В конфиге Playwright есть ignoreHTTPSErrors: true для пропуска проверки сертификатов в тестовых окружениях:
// playwright.config.ts
use: {
ignoreHTTPSErrors: true, // для staging с самоподписанными сертами
}4. HTTP-запрос и ответ
Теперь происходит передача контента. Браузер отправляет запрос; сервер возвращает ответ.
Запрос выглядит так (упрощённо):
GET /dashboard HTTP/1.1
Host: lab.becomeqa.com
Cookie: session=abc123
Accept: text/htmlОтвет выглядит так:
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 4521
<!DOCTYPE html>...Статус-код говорит что произошло.
HTTP-статус-коды которые встретишь
| Код | Значение | Частая причина |
|-----|----------|----------------|
| 200 | OK | Всё сработало |
| 201 | Created | POST успешен, ресурс создан |
| 204 | No Content | Успех, без тела (часто у DELETE) |
| 301/302 | Redirect | Страница переехала, браузер переходит автоматически |
| 400 | Bad Request | Запрос некорректен: неверный формат, отсутствует поле |
| 401 | Unauthorized | Не аутентифицирован: нужно залогиниться |
| 403 | Forbidden | Аутентифицирован, но нет доступа: неверная роль/права |
| 404 | Not Found | Ресурс не существует по этому пути |
| 422 | Unprocessable Entity | Формат запроса верен, но содержимое не прошло валидацию |
| 429 | Too Many Requests | Rate limiting |
| 500 | Internal Server Error | Баг в коде сервера |
| 502 | Bad Gateway | Сервер получил плохой ответ от вышестоящего сервиса |
| 503 | Service Unavailable | Сервер недоступен или перегружен |
| 504 | Gateway Timeout | Вышестоящий сервис ответил слишком медленно |
Почему это важно для тестирования
401 и 403: разные баги. «Не залогинен» и «залогинен, но нет доступа» выглядят одинаково в UI (оба редиректят на логин или показывают ошибку), но требуют разного исправления.
422 означает что запрос дошёл до сервера и сервер его понял, но данные не прошли валидацию. 400 означает что сервер не смог разобрать запрос.
HTTP-методы: что каждый означает
Каждый запрос использует метод описывающий намеренную операцию:
| Метод | Применение | Идемпотентен? |
|-------|------------|---------------|
| GET | Чтение данных, загрузка страницы, получение списка | Да. Два вызова возвращают одинаковый результат |
| POST | Создание, отправка формы, добавление записи | Нет. Два вызова создают две записи |
| PUT | Замена, обновление всего ресурса | Да. Два вызова дают одинаковый результат |
| PATCH | Частичное обновление, изменение одного поля | Обычно да |
| DELETE | Удаление ресурса | Да. Удалить дважды даёт тот же результат |
Почему это важно для тестирования
Если тест вызывает POST дважды (например кнопка кликнулась дважды), должны появиться две записи в БД. Если тестируешь идемпотентность PUT-эндпоинта, два вызова должны оставлять систему в том же состоянии что и один.
Когда видишь баг «форма отправилась дважды», нужно понять: это было два POST-запроса или один. Это видно во вкладке Network в DevTools.
Заголовки: метаданные каждого запроса и ответа
Заголовки несут метаданные. Не основное содержимое, но они управляют обработкой запроса и ответа.
Заголовки запроса которые встретишь в тестах
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9... ← токен аутентификации
Content-Type: application/json ← «отправляю JSON»
Cookie: session=abc123; csrf_token=xyz ← куки сессии
Accept: application/json ← «хочу JSON в ответ»Заголовки ответа которые встретишь
Content-Type: application/json; charset=utf-8 ← ответ в формате JSON
Set-Cookie: session=abc123; HttpOnly; Secure ← сервер устанавливает куку
Cache-Control: no-store ← не кэшировать ответ
Location: /dashboard ← цель редиректа (при 302)В API-тестах Playwright
test('API returns JSON content type', async ({ request }) => {
const response = await request.get('https://lab.becomeqa.com/api/items');
expect(response.status()).toBe(200);
expect(response.headers()['content-type']).toContain('application/json');
});Куки и сессии: как работает «залогинен»
При логине сервер создаёт сессию и возвращает куку:
Set-Cookie: session_id=abc123; HttpOnly; Secure; SameSite=StrictБраузер сохраняет эту куку и отправляет её с каждым последующим запросом:
Cookie: session_id=abc123Сервер читает куку, находит сессию и знает кто ты.
Почему это важно для тестирования
Именно так работает storageState в Playwright. Он сохраняет куки из залогиненной сессии в файл, затем загружает их в новые контексты браузера. Сервер видит ту же куку, считает что ты всё ещё залогинен и пропускает аутентификацию.
// Сохранить состояние аутентификации после логина
await page.context().storageState({ path: 'auth.json' });
// Загрузить в тестах
use: {
storageState: 'auth.json'
}Когда тест падает с «перенаправлен на страницу логина», значит сессионная кука истекла, не была сохранена правильно или файл auth.json устарел.
Чтение сетевого трафика в Playwright
Playwright умеет перехватывать и инспектировать каждый запрос который делает тест:
test('login sends correct credentials', async ({ page }) => {
// Ждём API-вызова логина
const loginRequest = page.waitForRequest('**/api/auth/login');
await page.goto('/');
await page.getByRole('button', { name: 'Login' }).click();
await page.getByLabel('Username').fill('admin@becomeqa.com');
await page.getByLabel('Password').fill('testpass123');
await page.getByRole('button', { name: 'Submit' }).click();
const request = await loginRequest;
const body = request.postDataJSON();
expect(body.email).toBe('admin@becomeqa.com');
});И ответы:
test('items API returns correct data structure', async ({ page }) => {
// Перехватываем API-вызов при загрузке страницы
const itemsResponse = page.waitForResponse('**/api/items');
await page.goto('/dashboard');
const response = await itemsResponse;
expect(response.status()).toBe(200);
const items = await response.json();
expect(Array.isArray(items)).toBeTruthy();
});Частые паттерны падений и что они означают
«net::ERR_CONNECTION_REFUSED» в CI, но не локально
Сервер приложения не запущен когда стартует тест. Добавь ожидание готовности сервера или используй конфиг webServer в playwright.config.ts:
webServer: {
command: 'npm start',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
}«Request failed: 401 Unauthorized»
Сессия истекла или состояние аутентификации не загрузилось. Проверь что storageState настроен, или что глобальный setup запускается до тестов.
«Request failed: 429 Too Many Requests»
Тест попадает под rate limiter. Добавь задержки между запросами или отключи rate limiting в тестовых окружениях.
«Response timeout: 30000ms exceeded»
Сервер отвечал дольше 30 секунд. Либо увеличь таймаут для этой конкретной операции, либо расследуй почему сервер медленный (запрос к БД, вызов внешнего API и т.д.).
Тест проходит локально, в CI падает с ошибкой 500
В CI отсутствует переменная окружения: URL базы данных, API-ключ, флаг фичи. Сервер падает с необработанной ошибкой потому что обязательное значение конфига оказывается undefined.
Вкладка Network в DevTools
При отладке падения открывай Chrome DevTools → вкладка Network перед ручным воспроизведением. Каждый запрос отображается с:
- URL и методом
- Статус-кодом
- Заголовками и телом запроса
- Заголовками и телом ответа
- Тайммингом
Это показывает точно что браузер отправил и что вернул сервер. Если UI показывает ошибку, но Network показывает 200: баг в клиентском JavaScript который интерпретирует ответ. Если Network показывает 500: баг на бэкенде.
Trace viewer Playwright показывает ту же информацию для автоматизированных тестовых прогонов: открываешь трассировку, переключаешься на вкладку Network и видишь каждый запрос сделанный во время теста.
Что пока не нужно знать
- WebSockets и Server-Sent Events: соединения реального времени, актуальны только при тестировании real-time функций
- HTTP/2 и HTTP/3: версии протокола; Playwright обрабатывает их прозрачно
- Управление TLS-сертификатами: тема для DevOps, не для ежедневной QA-работы
- Балансировка нагрузки и настройка CDN: инфраструктурные знания для старших ролей
- Детали OAuth 2.0 flow: полезно при тестировании функций авторизации конкретно
FAQ
Нужно ли знать сети чтобы писать тесты Playwright?
Не глубоко. Но понимание четырёхшагового процесса (DNS → TCP → TLS → HTTP) позволяет диагностировать класс ошибки за 30 секунд вместо 30 минут. Реализовывать ничего не нужно. Нужно только распознать какой уровень сломался.
Смотреть вкладку Network или сообщение об ошибке теста при падении?
Оба, по порядку. Сообщение об ошибке теста говорит какая проверка не прошла. Вкладка Network (или трассировка Playwright) говорит почему: что сервер реально вернул. Сначала сообщение об ошибке, потом сеть.
В чём разница между 401 и 403?
401: не аутентифицирован (не залогинен). 403: аутентифицирован, но не авторизован (залогинен, но нет прав на этот ресурс). Оба могут выдавать ответ «нет доступа» в UI, но это разные баги с разными исправлениями.
→ See also: API-тестирование в Playwright: выходим за рамки UI | SQL для QA: запросы, которые вам действительно нужны