Життєвий цикл запиту
Зміст
Між натисканням Enter в адресному рядку і появою сторінки на екрані відбувається довгий ланцюг подій. Для користувача це просто «сайт відкрився». Для AQA це послідовність окремих фаз, у кожної з яких власна тривалість, власні режими відмови й власні причини, чому автотест раптом «червоніє» без жодних змін у коді.
Ця глава проходить повний шлях HTTPS-запиту фаза за фазою й після кожної показує, де тест може зависнути (hang) чи флакнути (flake). До кінця розділу стануть зрозумілими практичні питання на кшталт «чому запит працює в Postman, але падає в браузері» та «що означає pending навпроти запиту в DevTools» — і як повʼязати відповідь із поведінкою тест-фреймворку.
Загальна карта шляху
Спрощено повний шлях виглядає так:
DNS → TCP → TLS → надсилання запиту → очікування (сервер) → відповідь → рендер
Кожна стрілка — це час і можлива точка збою. Важлива інтуїція для тестувальника: більшість «повільних» або «флакі» тестів ламаються не на етапі рендеру, який ми бачимо, а на невидимих ранніх фазах — резолвінгу імені, встановленні зʼєднання чи очікуванні першого байта від сервера. Тому спершу пройдемо шлях по порядку, а потім зведемо симптоми в одну таблицю.
Крок 1. DNS: від імені до адреси
Браузер не вміє підключатися до example.com — йому потрібна IP-адреса. Перетворення імені на адресу робить DNS (Domain Name System).
Пошук іде каскадом кешів, від найближчого до найдальшого:
- Кеш самого браузера.
- Кеш операційної системи (і файл
hosts, який має пріоритет над мережевим запитом, якщо там є запис). - Резолвер провайдера або корпоративний DNS.
- Якщо ніде немає — рекурсивний обхід: кореневі сервери → сервери зони (TLD, напр.
.com) → авторитетний сервер домену.
Кожен запис має час життя (TTL, Time To Live), після якого кеш вважається застарілим і резолвінг повторюється. Перевірити резолвінг вручну можна так:
# показати, у яку адресу резолвиться імʼя, і хто відповів
dig +short staging.example.com
nslookup staging.example.com
Де флакне тест. DNS — класичне джерело «магічних» падінь, особливо на стейджингах:
- Запис у
/etc/hostsперекриває реальний DNS. Тест на машині розробника ходить на локальний бекенд, а в CI — на справжній, і поведінка різна. - Свіжостворений домен ще не розійшовся по кешах (пропагація), тому частина агентів бачить його, частина — ні.
- Повільний або перевантажений корпоративний резолвер додає сотні мілісекунд до кожного запиту, і тест, що ходить на десятки ендпоінтів, стабільно впирається в таймаут.
Практичний висновок: якщо тест падає лише в одному оточенні, перша гіпотеза — не код, а те, куди насправді резолвиться імʼя.
Крок 2. TCP: встановлення зʼєднання
Маючи IP, браузер відкриває TCP-зʼєднання. Це «потискання руки» з трьох кроків (three-way handshake):
клієнт → сервер: SYN
сервер → клієнт: SYN-ACK
клієнт → сервер: ACK
Після цього канал відкритий і по ньому можна передавати дані. Зʼєднання не одноразове: HTTP тримає його відкритим (keep-alive) і повторно використовує для наступних запитів, щоб не платити щоразу за handshake (а на HTTPS — ще й за TLS). Саме тому перший запит до нового хоста завжди «дорожчий» за наступні.
Де флакне тест. На рівні TCP збої зазвичай грубі й помітні:
ECONNREFUSED— на порту ніхто не слухає. Сервіс іще не піднявся (типова гонка в CI: тест стартував раніше, ніж застосунок), або неправильний порт.ETIMEDOUTна зʼєднанні — пакети йдуть «у нікуди»: фаєрвол, неправильна мережа, VPN.- Вичерпання пулу зʼєднань під навантаженням — нові запити стають у чергу (див. розділ про
pending).
Крок 3. TLS: захищене рукостискання
Літера «S» у HTTPS — це TLS (Transport Layer Security). Поверх уже відкритого TCP-зʼєднання клієнт і сервер домовляються про шифрування. Оглядово фаза така:
- Клієнт надсилає
ClientHello: які версії TLS і набори шифрів (cipher suites) він підтримує. Тут же передається імʼя хоста (SNI, Server Name Indication) — щоб сервер, який обслуговує багато доменів на одній IP, знав, який саме сертифікат віддати. - Сервер відповідає
ServerHello, надсилає свій сертифікат (ланцюг до довіреного центру сертифікації) і параметри. - Сторони узгоджують спільний ключ і переходять на шифрований канал.
Ключова для розуміння таймінгів деталь — кількість «кругів» (round trips, RTT):
| Версія | Раунди на рукостискання | Примітка |
|---|---|---|
| TLS 1.2 | ~2 RTT | більша затримка |
| TLS 1.3 | ~1 RTT | і 0-RTT при відновленні сесії |
Тобто TLS — це реальний, вимірюваний внесок у затримку до першого байта, особливо на «холодному» зʼєднанні, де до нього додається ще й RTT самого TCP-рукостискання. Режим 0-RTT (нульовий раунд для даних) можливий лише при відновленні раніше встановленої сесії й має відомий компроміс — ризик повторного відтворення (replay) раннього трафіку, тому сервери вмикають його вибірково.
Де флакне тест. Найпоширеніша причина падінь тут — сертифікати на нестабільних оточеннях:
- самопідписаний (self-signed) або прострочений сертифікат на стейджингу;
- невідповідність імені: запит іде на
staging.example.com, а сертифікат виданий наexample.com(той самий SNI, що передається вClientHello, і має збігтися з іменем у сертифікаті).
Браузер за замовчуванням відхиляє такі зʼєднання. У тест-фреймворках для цього є явний вимикач перевірки — але користуйся ним свідомо, лише для тестових оточень:
// Playwright: ігнорувати помилки HTTPS (тільки для тестових доменів!)
const context = await browser.newContext({ ignoreHTTPSErrors: true });
Крок 4. Надсилання запиту
Канал захищений — браузер надсилає власне HTTP-запит: рядок запиту (метод, шлях, версія), заголовки (headers) і, за потреби, тіло (body).
GET /api/users/42 HTTP/1.1
Host: api.example.com
Accept: application/json
Cookie: session=abc123
Origin: https://app.example.com
User-Agent: Mozilla/5.0 ...
Тут зʼявляються заголовки, які додає саме браузер і які згодом пояснять різницю з Postman: Origin, Cookie, User-Agent, Referer. Розробник у коді їх не пише — браузер підставляє автоматично, виходячи з контексту сторінки й свого сховища cookie.
Крок 5. Відповідь і TTFB
Далі — очікування. Сервер отримує запит, обробляє його (ходить у базу, рахує, серіалізує) і починає віддавати відповідь. Момент, коли до клієнта доходить перший байт відповіді, задає ключову метрику — TTFB (Time To First Byte).
Важливо: TTFB — це не час обробки на сервері окремо. У нього входить усе, що передувало появі першого байта:
| Складова TTFB | Що це |
|---|---|
| DNS | резолвінг імені |
| TCP | встановлення зʼєднання |
| TLS | захищене рукостискання |
| Request | час на передачу запиту до сервера |
| Server processing | власне робота бекенду |
| Waiting | час, поки перший байт іде назад |
Якщо на шляху був редирект, його час теж входить у TTFB: стандартна веб-метрика веде відлік від початку навігації, тож кожен проміжний «стрибок» додається до підсумку.
Нюанс. Різні інструменти рахують TTFB від різних точок відліку: від старту навігації разом із редиректами або від моменту відправлення конкретного запиту. Перш ніж порівнювати числа з DevTools і стороннього моніторингу, звір, від чого саме кожен веде відлік.
Практична цінність TTFB для AQA: якщо він великий, а решта (завантаження тіла, рендер) нормальна, проблема майже напевно на бекенді або в мережі до нього, а не у фронтенді. Це вже конкретний напрям для баг-репорту, а не розмите «сторінка гальмує».
У DevTools час запиту розкладено на фази (Queued, Stalled, DNS Lookup, Initial connection, SSL, Request sent, Waiting for server response / TTFB, Content Download). Уміння читати цю розкладку — базова навичка: вона одразу каже, на якій саме фазі втрачено час.
Крок 6. Рендер
Отримавши HTML, браузер парсить його й будує DOM (Document Object Model) — дерево обʼєктів сторінки. Паралельно з CSS будується CSSOM, а з їх поєднання постає дерево рендеру, яке браузер розкладає (layout) і малює (paint). Цей конвеєр і називають критичним шляхом рендеру (critical rendering path).
Натрапляючи на посилання на підресурси (CSS, JS, шрифти, зображення), браузер ініціює нові запити — і для кожного цикл DNS→TCP→TLS→відповідь повторюється (часто вже поверх перевикористаного зʼєднання). Частина ресурсів блокує рендер: зовнішній CSS і синхронний <script> без async/defer зупиняють парсинг, поки не завантажаться й не виконаються. JavaScript, своєю чергою, може змінювати DOM і породжувати ще запити (XHR/fetch) — уже після початкового завантаження сторінки.
Тут важливі дві події, на які часто «чекають» тести:
DOMContentLoaded— HTML розпарсено й DOM побудовано (підресурси можуть іще вантажитися).load— завантажено все, зокрема картинки й стилі.
Де флакне тест. Це найтонше місце для автоматизації. Тест, який чекає лише на load, може «прокинутися» до того, як дані підвантажилися через fetch і відмалювалися — бо load засвідчує лише завантаження початкових ресурсів сторінки, а не подальших XHR/fetch, які JavaScript часто запускає вже після цієї події (або відповідь на які приходить пізніше за неї). Звідси головне правило сучасних фреймворків: чекай на елемент або на конкретний мережевий запит, а не на подію завантаження сторінки чи фіксовану паузу. Жорсткі sleep/waitForTimeout — головне джерело флаку: на швидкій машині це зайве очікування, на повільній — недостатнє.
// погано: гонка з реальним часом завантаження
await page.waitForTimeout(3000);
// добре: чекаємо на факт, а не на секунди
await page.getByRole('row', { name: 'John Doe' }).waitFor();
// або чекаємо саме на потрібну відповідь API
await page.waitForResponse(r => r.url().includes('/api/users') && r.ok());
Ланцюг редиректів (redirect chain)
Не завжди перша відповідь фінальна. Сервер може відповісти статусом 3xx і заголовком Location, і браузер піде за новою адресою. Таких «стрибків» може бути кілька поспіль — це і є ланцюг редиректів.
| Код | Назва | Метод при переході | Постійний? |
|---|---|---|---|
| 301 | Moved Permanently | історично може змінити на GET | так |
| 302 | Found | історично може змінити на GET | ні |
| 303 | See Other | завжди на GET | ні |
| 307 | Temporary Redirect | зберігає метод і тіло | ні |
| 308 | Permanent Redirect | зберігає метод і тіло | так |
Типові ланцюги: http:// → https://, потім example.com → www.example.com, потім / → /login. Кожен крок — це окремий повний запит із власними DNS/TCP/TLS, тобто редиректи додають затримку.
Де флакне тест. Часті пастки:
- Тест перевіряє URL одразу після переходу й ловить проміжний, а не кінцевий, бо ланцюг ще не завершився.
- API-тест, який має бачити тіло відповіді, налаштований не йти за редиректами (
302без автопереходу) — і отримує порожнє тіло замість очікуваних даних. - Циклічний редирект (напр. неправильно налаштована авторизація) — браузер обриває його з помилкою після певної кількості кроків.
Подивитися весь ланцюг зручно так:
# показати кожен крок редиректу і фінальний код
curl -sIL https://example.com | grep -i -E 'HTTP/|location'
Коли виникає preflight
CORS (Cross-Origin Resource Sharing) — механізм браузера, що контролює запити з однієї origin на іншу (origin = схема + домен + порт; зміна будь-якої з трьох частин робить запит крос-доменним). Для частини «небезпечних» крос-доменних запитів браузер спершу надсилає попередній запит методом OPTIONS — це і є preflight. Він питає сервер: «чи можна мені зробити те, що я збираюся?». Лише після дозвільної відповіді летить справжній запит.
Спрощено, preflight не потрібен для «простих» запитів і потрібен для решти:
| Умова | Простий запит (без preflight) | Небезпечний (з preflight) |
|---|---|---|
| Метод | GET, HEAD, POST | PUT, PATCH, DELETE тощо |
Content-Type | обмежений набір (form-urlencoded, multipart, text/plain) | application/json |
| Заголовки | лише зі стандартного «безпечного» списку | будь-які кастомні (напр. X-Request-Id) |
До «безпечного» (safelisted) списку заголовків специфікація Fetch відносить лише кілька — насамперед Accept, Accept-Language, Content-Language й Content-Type (останній лише з тими самими трьома значеннями), ще й з обмеженнями на довжину та символи значення. Усе поза цим списком вважається кастомним і тригерить preflight. Тому типовий REST-виклик із Content-Type: application/json майже завжди спричиняє preflight. Виглядає він так:
OPTIONS /api/users/42 HTTP/1.1
Origin: https://app.example.com
Access-Control-Request-Method: DELETE
Access-Control-Request-Headers: authorization, content-type
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, DELETE
Access-Control-Allow-Headers: authorization, content-type
Access-Control-Max-Age: 600
Access-Control-Max-Age дозволяє браузеру кешувати дозвіл і не повторювати OPTIONS перед кожним викликом.
Окремий нюанс — запити з обліковими даними (cookie або Authorization). Щоб браузер прийняв відповідь на такий крос-доменний запит, сервер має повернути Access-Control-Allow-Credentials: true, і при цьому Access-Control-Allow-Origin не може бути * — потрібен конкретний origin. Це часта причина, чому запит із cookie ламається саме в браузері.
Де флакне тест. По-перше, preflight подвоює кількість запитів — у DevTools ти побачиш OPTIONS перед кожним «справжнім» викликом, і це нормально. По-друге, класична пастка: сам запит правильний, але сервер неправильно відповідає на OPTIONS — і браузер блокує основний запит ще до відправлення. У тесті це виглядає як «дані не приходять», хоча сервер до основного запиту навіть не дійшов. Дивись на OPTIONS, а не лише на цільовий запит.
Що означає стан pending
Pending навпроти запиту в DevTools означає, що браузер уже ініціював запит, але ще не отримав (повної) відповіді. Це не помилка сама по собі — це «в процесі». Але якщо запит висить у pending довго, причини бувають різні:
- Waiting (TTFB): запит пішов, сервер думає й мовчить — довга обробка на бекенді.
- Queued / Stalled: запит навіть не відправлено. Браузер обмежує кількість одночасних зʼєднань до однієї origin (у багатьох браузерах — близько 6 на хост для HTTP/1.1); зайві запити чекають своєї черги. Під час навантаження чи великої кількості паралельних викликів це помітно. На HTTP/2 запити мультиплексуються в одному зʼєднанні, і ця межа фактично зникає (натомість діє серверний ліміт одночасних потоків).
- Blocked: запит стримується (напр. очікує звільнення зʼєднання або на нього впливає інша логіка браузера).
- Довгі зʼєднання: для стрімів, SSE чи WebSocket-апгрейдів
pending— нормальний і очікуваний стан, бо зʼєднання тримається відкритим навмисно.
Де флакне тест. Якщо тест «зависає» на дії, яка тригерить мережевий виклик, а в DevTools цей виклик у вічному pending — фреймворк чесно чекає на відповідь, якої немає, аж до свого таймауту. Діагностика: подивитися, у якій саме фазі застряг запит (черга проти очікування сервера), — це одразу розводить «повільний бекенд» і «браузер уперся в ліміт зʼєднань».
Чому запит успішний у Postman, але не в браузері
Дуже частий репорт: «в Postman усе працює, а в застосунку/тесті — ні». Причина майже завжди в тому, що Postman і браузер — різні клієнти з різними правилами. Postman просто надсилає HTTP-запит. Браузер додає цілий шар політик безпеки й контексту.
| Аспект | Браузер | Postman |
|---|---|---|
| Same-origin / CORS | застосовує, може заблокувати крос-доменний запит | ігнорує повністю |
Preflight (OPTIONS) | шле автоматично за потреби | не шле |
| Cookies / сесія | підставляє автоматично для домену | лише якщо задав вручну |
Заголовки Origin, Referer | ставить сам | не ставить, поки не додаси |
| Змішаний контент (mixed content) | блокує HTTP-ресурси на HTTPS-сторінці | байдуже |
| CSP, Service Worker, кеш браузера | впливають на запит | відсутні |
Практичні наслідки для діагностики:
- CORS. Найтиповіший випадок: у Postman
200 OK, у браузері — помилка CORS у консолі. Це не «баг фронтенду», а те, що сервер не дозволяє крос-доменний доступ, і лише браузер це перевіряє. - Авторизація через cookie. У браузері ти залогінений і cookie летить сам; у Postman запит іде без сесії — і або падає, або (гірше) працює під іншим користувачем, бо ти вручну підставив інший токен.
- Заголовки. Сервер може вимагати
Originчи певнийContent-Type, які браузер ставить, а ти в Postman — ні (або навпаки).
Висновок для тестувальника: «в Postman працює» — це доказ того, що бекенд віддає дані, а не того, що з браузера теж спрацює. Це різні рівні перевірки, і плутати їх у баг-репорті не можна.
Timeout: що це і які бувають
Таймаут (timeout) — це верхня межа часу, який компонент готовий чекати, перш ніж визнати операцію невдалою. Таймаутів на шляху запиту багато, і вони діють на різних рівнях. Плутанина між ними — часте джерело незрозумілих падінь.
| Рівень | Таймаут | Що обмежує |
|---|---|---|
| Мережа/ОС | connection timeout | час на встановлення TCP/TLS-зʼєднання |
| HTTP-клієнт | read / response timeout | час очікування відповіді після відправлення запиту |
| HTTP-клієнт | idle timeout | максимум простою в зʼєднанні |
| Сервер/проксі | gateway timeout | бекенд не встиг відповісти проксі → клієнт бачить 504 |
| Тест-фреймворк | navigation timeout | завантаження сторінки |
| Тест-фреймворк | action / command timeout | окрема дія (клік, введення) |
| Тест-фреймворк | assertion / expect timeout | скільки перечікувати умову перевірки |
| Тест-фреймворк | test timeout | увесь тест цілком |
Орієнтовні дефолти, щоб було з чим порівнювати (звіряй з версією свого інструмента):
// Playwright (playwright.config)
// тест: 30 000 мс; expect: 5 000 мс;
// action/navigation за замовчуванням не мають окремого ліміту
// й обмежені лише таймаутом тесту
export default {
timeout: 30_000,
expect: { timeout: 5_000 },
};
Дефолти на сьогодні (вони змінюються між релізами — звіряй з докою своєї версії): Playwright — таймаут тесту 30 с,
expect5 с, аactionTimeoutіnavigationTimeoutза замовчуванням не обмежені, їх стримує загальний таймаут тесту; Cypress —defaultCommandTimeout4 с,pageLoadTimeout60 с,requestTimeout5 с,responseTimeout30 с.
Ключова ідея: коли тест падає з таймаута, спершу зрозумій, чий саме таймаут спрацював. 504 від шлюзу, обрив на рівні HTTP-клієнта й «expect не дочекався елемента» — три різні діагнози з різними виправленнями. І окреме правило: не «лікуй» флак простим підняттям таймауту. Більший таймаут ховає проблему, робить тести повільнішими й перетворює швидкі падіння на довгі зависання.
Retry: повторні спроби
Retry — повторення операції після невдачі. Як і таймаути, ретраї живуть на кількох рівнях:
- Мережа. TCP сам ретрансмітить втрачені пакети — це нижче за HTTP і ти цього не бачиш.
- HTTP-клієнт / інфраструктура. Клієнти й проксі можуть повторювати запити (часто на 502/503/504 чи мережеві обриви). Розумний ретрай робить паузу, що зростає між спробами (експоненційний відступ, exponential backoff), щоб не добивати вже перевантажений сервіс.
- Тест-фреймворк. Механізм ретраю впалих тестів (напр.
retriesу конфізі) — щоб приховати рідкісний флак.
Головне застереження — ідемпотентність (idempotency). Ідемпотентний запит можна безпечно повторити, бо повтор не змінює результат (умовно — GET, а також DELETE конкретного ресурсу: видалити вже видалене дає той самий стан). А от повтор POST, що створює замовлення, може створити дублікат. Тому «просто додати retry» — не безпечне рішення за замовчуванням.
Для AQA ретрай тестів — двосічна річ. З одного боку, він гасить рідкісний зовнішній флак і не валить весь прогон. З іншого — маскує реальні нестабільності: тест, що проходить із другої спроби, це не «зелений» тест, а прихований баг у тесті або в продукті. Розумний підхід: ретраї ввімкнені, але кожен «passed on retry» видно у звіті й хтось за ним іде розбиратися, а не радіє зеленому статусу.
// Playwright: 2 ретраї лише в CI; локально бачимо флак одразу
export default {
retries: process.env.CI ? 2 : 0,
};
Зведення: де на шляху ламається тест
| Фаза | Симптом у тесті | Типова причина | Куди дивитися |
|---|---|---|---|
| DNS | падає лише в одному оточенні | hosts, повільний резолвер, пропагація | dig, /etc/hosts |
| TCP | ECONNREFUSED, ETIMEDOUT | сервіс не піднявся, порт/фаєрвол | логи запуску, порт |
| TLS | помилка сертифіката | self-signed / прострочений на стейджингу | сертифікат, ignoreHTTPSErrors |
| Запит/відповідь | таймаут очікування | повільний бекенд, великий TTFB | фаза Waiting у DevTools |
| Редирект | не той URL / порожнє тіло | ловимо проміжний крок, не йдемо за 3xx | curl -IL, Location |
| CORS/preflight | «дані не приходять» | сервер зле відповідає на OPTIONS | запит OPTIONS, консоль |
| Рендер | елемента ще немає | чекаємо на load, а не на факт | waitFor на елемент/відповідь |
pending назавжди | тест зависає до таймауту | черга зʼєднань або мовчазний сервер | фаза запиту в DevTools |
Наскрізний принцип: тест бачить лише кінцевий результат — «зелено» чи «червоно», — але причина майже завжди на конкретній фазі шляху. Тому за будь-якого нестабільного падіння спершу локалізуй фазу (мережа проти сервера проти рендеру), а вже потім став гіпотезу. І скрізь, де можеш, замінюй очікування часу на очікування факту — саме це відрізняє стабільний автотест від такого, що «іноді проходить».
Що відбувається між натисканням Enter в адресному рядку і появою сторінки?
Запит проходить ланцюг фаз: DNS-резолвінг → TCP-зʼєднання → TLS-рукостискання → надсилання HTTP-запиту → очікування відповіді сервера → отримання відповіді → рендер. Спершу браузер перетворює імʼя на IP-адресу, потім відкриває TCP-канал, поверх нього домовляється про шифрування, і лише тоді летить сам HTTP-запит. Сервер обробляє його й віддає відповідь, а браузер парсить HTML, будує DOM і домальовує сторінку, дозавантажуючи підресурси окремими запитами. Кожна стрілка в цьому ланцюгу — це час і можлива точка збою, тому для AQA важливо вміти локалізувати, на якій саме фазі тест зависнув чи флакнув: більшість «повільних» тестів ламаються не на видимому рендері, а на невидимих ранніх фазах.
Як браузер дізнається IP-адресу за доменним іменем?
Через DNS (Domain Name System), причому пошук іде каскадом кешів від найближчого до найдальшого: кеш браузера → кеш ОС і файл hosts → резолвер провайдера чи корпоративний DNS → рекурсивний обхід від кореневих серверів через TLD до авторитетного сервера домену. Кожен запис має TTL (Time To Live), після якого кеш вважається застарілим і резолвінг повторюється. Файл hosts має пріоритет над мережевим запитом — і це класична пастка для AQA: тест на машині розробника через запис у /etc/hosts ходить на локальний бекенд, а в CI — на справжній, і поведінка різна. Тому якщо тест падає лише в одному оточенні, перша гіпотеза — не код, а те, куди насправді резолвиться імʼя; перевірити можна через dig +short або nslookup.
Що таке TCP three-way handshake і навіщо keep-alive?
Це «потискання руки» з трьох кроків для відкриття зʼєднання: клієнт шле SYN, сервер відповідає SYN-ACK, клієнт підтверджує ACK — після цього канал відкритий. Зʼєднання не одноразове: HTTP тримає його відкритим (keep-alive) і повторно використовує для наступних запитів, щоб не платити щоразу за handshake, а на HTTPS — ще й за TLS. Саме тому перший запит до нового хоста завжди «дорожчий» за наступні. Для тестувальника корисно розрізняти помилки цього рівня: ECONNREFUSED означає, що на порту ніхто не слухає (типова гонка в CI — тест стартував раніше, ніж піднявся застосунок), а ETIMEDOUT — що пакети йдуть «у нікуди»: фаєрвол, неправильна мережа чи VPN.
Чим відрізняються помилки ECONNREFUSED і ETIMEDOUT?
ECONNREFUSED — сервер активно відмовив: до машини дістатися вдалося, але на вказаному порту ніхто не слухає. Найчастіша причина в CI — сервіс іще не встиг піднятися, коли тест уже пішов, або переплутаний порт. ETIMEDOUT — відповіді немає взагалі: пакети губляться дорогою, і клієнт чекає до вичерпання ліміту; типові винуватці — фаєрвол, неправильна мережа, VPN. Практична різниця для діагностики: перша помилка каже «хост є, сервісу немає» (дивись логи запуску і порт), друга — «до хоста не достукатися» (дивись мережу). Плутати їх у баг-репорті не варто, бо виправлення зовсім різні.
Що відбувається під час TLS-рукостискання і чому тести падають на сертифікатах?
TLS (Transport Layer Security) — це «S» у HTTPS: поверх уже відкритого TCP клієнт і сервер домовляються про шифрування. Клієнт шле ClientHello з підтримуваними версіями та наборами шифрів, а також SNI (Server Name Indication) — імʼя хоста, щоб сервер із багатьма доменами на одній IP віддав правильний сертифікат; сервер відповідає ServerHello і сертифікатом, далі сторони узгоджують спільний ключ. У тестах найчастіші падіння тут — самопідписаний або прострочений сертифікат на стейджингу та невідповідність імені: запит іде на staging.example.com, а сертифікат виданий на example.com. Браузер такі зʼєднання відхиляє за замовчуванням; у Playwright є явний вимикач ignoreHTTPSErrors: true, але вмикати його варто свідомо й лише для тестових оточень, бо він ховає реальні проблеми з сертифікатами.
Які заголовки браузер додає до запиту автоматично і чому це важливо?
Браузер сам підставляє Origin, Cookie, User-Agent, Referer — розробник у коді їх не пише, вони беруться з контексту сторінки та сховища cookie. Це принципово, бо саме ці «невидимі» заголовки пояснюють різницю між браузером і інструментами на кшталт Postman: Postman нічого з цього не шле, поки не додаси вручну. Практичний наслідок: сервер може вимагати Origin або авторизацію через cookie, і тоді запит із браузера працює, а «той самий» запит із Postman — ні (або навпаки). Для AQA це означає, що при відтворенні запиту поза браузером треба свідомо відтворити і його контекст, інакше порівнюєш різні речі.
Чим DOMContentLoaded відрізняється від load і чому не можна чекати лише на них?
DOMContentLoaded спрацьовує, коли HTML розпарсено й DOM побудовано — підресурси можуть іще вантажитися; load — коли завантажено все, зокрема картинки й стилі. Але обидві події засвідчують лише завантаження початкових ресурсів сторінки: JavaScript часто запускає XHR/fetch уже після load, або відповідь приходить пізніше за неї. Тому тест, який чекає лише на load, може «прокинутися» до того, як дані підвантажилися й відмалювалися. Звідси головне правило сучасних фреймворків: чекай на елемент або на конкретний мережевий запит, а не на подію завантаження сторінки — наприклад, waitFor() на рядок таблиці або page.waitForResponse() на потрібний API-виклик.
Що таке TTFB і що в нього входить?
TTFB (Time To First Byte) — час до моменту, коли до клієнта доходить перший байт відповіді. Важливо, що це не «час обробки на сервері» окремо: у TTFB входить усе, що передувало першому байту — DNS, TCP, TLS, передача запиту, робота бекенду і шлях першого байта назад; якщо на шляху був редирект, його час теж додається. Практична цінність для AQA: якщо TTFB великий, а завантаження тіла й рендер нормальні, проблема майже напевно на бекенді або в мережі до нього — це вже конкретний напрям для баг-репорту, а не розмите «сторінка гальмує». Нюанс для сеньйора: різні інструменти рахують TTFB від різних точок відліку (від старту навігації з редиректами чи від відправлення конкретного запиту), тому перш ніж порівнювати числа з DevTools і стороннього моніторингу, звір, від чого кожен веде відлік.
Що означає стан pending навпроти запиту в DevTools?
Pending означає, що браузер уже ініціював запит, але ще не отримав повної відповіді — це «в процесі», а не помилка сама по собі. Якщо ж запит висить у pending довго, причини різні: Waiting — запит пішов, а сервер довго обробляє (великий TTFB); Queued/Stalled — запит навіть не відправлено, бо браузер обмежує кількість одночасних зʼєднань до однієї origin (для HTTP/1.1 у багатьох браузерах близько 6 на хост) і зайві чекають черги; для стрімів, SSE чи WebSocket вічний pending — узагалі нормальний стан, бо зʼєднання тримається відкритим навмисно. Для тесту це виглядає як зависання на дії до таймауту фреймворку: він чесно чекає на відповідь, якої немає. Діагностика — подивитися, у якій саме фазі застряг запит: черга проти очікування сервера одразу розводить «повільний бекенд» і «браузер уперся в ліміт зʼєднань».
Які бувають коди редиректів і чим 307/308 відрізняються від 301/302?
Сервер відповідає статусом 3xx і заголовком Location, і клієнт іде за новою адресою; таких стрибків може бути кілька — це ланцюг редиректів. Ключова різниця: 301 (постійний) і 302 (тимчасовий) історично можуть змінити метод на GET, 303 завжди переводить на GET, а 307 і 308 гарантовано зберігають метод і тіло — 307 тимчасовий, 308 постійний. Кожен крок ланцюга — окремий повний запит із власними DNS/TCP/TLS, тобто редиректи додають затримку. Типові пастки в тестах: перевірка URL одразу після переходу ловить проміжний крок, а не кінцевий; API-тест, налаштований не йти за редиректами, отримує порожнє тіло замість даних; циклічний редирект браузер обриває з помилкою. Весь ланцюг зручно подивитися через curl -sIL.
Чому запит працює в Postman, але падає в браузері?
Тому що Postman і браузер — різні клієнти з різними правилами: Postman просто надсилає HTTP-запит, а браузер додає цілий шар політик безпеки й контексту. Браузер застосовує CORS і може заблокувати крос-доменний запит, автоматично шле preflight OPTIONS, сам підставляє cookie й заголовки Origin/Referer, блокує змішаний контент, а ще на запит впливають CSP, Service Worker і кеш — Postman усе це ігнорує. Найтиповіший випадок: у Postman 200 OK, у браузері — помилка CORS у консолі; це не баг фронтенду, а те, що сервер не дозволяє крос-доменний доступ, і лише браузер це перевіряє. Висновок для баг-репорту: «в Postman працює» доводить лише те, що бекенд віддає дані, а не те, що з браузера теж спрацює — це різні рівні перевірки, і плутати їх не можна.
Коли браузер шле preflight-запит і як він виглядає?
Preflight — це попередній запит методом OPTIONS, який браузер шле перед «небезпечним» крос-доменним запитом, питаючи сервер, чи дозволена запланована дія; лише після дозвільної відповіді летить справжній запит. Простими (без preflight) є GET, HEAD і POST з обмеженим набором Content-Type (form-urlencoded, multipart, text/plain) і лише «безпечними» заголовками; PUT/PATCH/DELETE, Content-Type: application/json чи будь-який кастомний заголовок на кшталт X-Request-Id тригерять preflight — тому типовий REST-виклик із JSON майже завжди його спричиняє. У preflight браузер передає Access-Control-Request-Method і Access-Control-Request-Headers, а сервер відповідає заголовками Access-Control-Allow-*; Access-Control-Max-Age дозволяє кешувати дозвіл і не повторювати OPTIONS щоразу. Класична пастка в тестах: сам запит правильний, але сервер зле відповідає на OPTIONS — і браузер блокує основний запит ще до відправлення; у тесті це виглядає як «дані не приходять», тож дивитися треба на OPTIONS, а не лише на цільовий виклик.
Чому крос-доменний запит із cookie ламається, хоча CORS начебто налаштований?
Для запитів з обліковими даними (cookie або заголовок Authorization) діють жорсткіші правила: сервер має повернути Access-Control-Allow-Credentials: true, і при цьому Access-Control-Allow-Origin не може бути * — потрібен конкретний origin. Типова конфігурація «дозволити всім» через зірочку працює для анонімних запитів, але браузер відхилить відповідь на запит із cookie. При цьому origin — це схема + домен + порт, і зміна будь-якої з трьох частин робить запит крос-доменним, тож навіть інший порт того самого домену вмикає всі ці перевірки. Це часта причина, чому запит із cookie ламається саме в браузері, тоді як у Postman той самий виклик проходить — Postman правил CORS не застосовує взагалі.
Тест упав із таймаутом. Які бувають таймаути і як зрозуміти, чий саме спрацював?
Таймаут — верхня межа часу, який компонент готовий чекати, перш ніж визнати операцію невдалою, і вони живуть на різних рівнях: connection timeout на встановлення TCP/TLS, read/response timeout HTTP-клієнта, gateway timeout проксі (клієнт бачить 504), а у тест-фреймворку — окремі navigation, action, expect і test timeout. Перший крок діагностики — зрозуміти, чий саме таймаут спрацював: 504 від шлюзу, обрив на рівні HTTP-клієнта й «expect не дочекався елемента» — три різні діагнози з різними виправленнями. Корисно знати дефолти свого інструмента: у Playwright таймаут тесту 30 с і expect 5 с, а action/navigation за замовчуванням окремого ліміту не мають і стримуються загальним таймаутом тесту; у Cypress — command 4 с, pageLoad 60 с. І окреме правило: не «лікуй» флак простим підняттям таймауту — більший таймаут ховає проблему, уповільнює тести й перетворює швидкі падіння на довгі зависання.
Чому фіксовані паузи на кшталт waitForTimeout — головне джерело флаку і що використовувати замість них?
Жорсткий sleep/waitForTimeout — це гонка з реальним часом завантаження: на швидкій машині пауза зайва і лише уповільнює прогін, на повільній — недостатня, і тест падає. Реальний час відповіді залежить від усіх фаз шляху (DNS, зʼєднання, TTFB, дозавантаження через fetch), і жодна фіксована цифра не покриває їх стабільно. Правильний підхід — чекати на факт, а не на секунди: waitFor() на конкретний елемент або page.waitForResponse() на потрібну відповідь API. Саме заміна очікування часу на очікування факту відрізняє стабільний автотест від такого, що «іноді проходить» — це наскрізний принцип для всіх фаз життєвого циклу запиту.
Що таке ідемпотентність і чому її треба враховувати, перш ніж додавати retry?
Ідемпотентний запит можна безпечно повторити, бо повтор не змінює результат: умовно GET, а також DELETE конкретного ресурсу — видалити вже видалене дає той самий стан. А от повтор POST, що створює замовлення, може створити дублікат — тому «просто додати retry» не є безпечним рішенням за замовчуванням. Ретраї при цьому живуть на кількох рівнях: TCP сам ретрансмітить втрачені пакети нижче за HTTP, HTTP-клієнти й проксі можуть повторювати запити на 502/503/504, і розумний ретрай використовує експоненційний відступ (exponential backoff), щоб не добивати перевантажений сервіс. Для AQA це означає: перш ніж вмикати автоповтори в API-тестах чи хелперах, перевір, які методи повторюються і чи не породить повтор побічних ефектів у тестових даних.
Ретраї впалих тестів у CI — це добра практика чи маскування проблем?
Двосічна: з одного боку, ретрай гасить рідкісний зовнішній флак і не валить весь прогін через одне моргання мережі; з іншого — маскує реальні нестабільності, бо тест, що проходить із другої спроби, це не «зелений» тест, а прихований баг у тесті або в продукті. Розумний підхід: ретраї ввімкнені, але кожен «passed on retry» видно у звіті, і хтось за ним іде розбиратися, а не радіє зеленому статусу. Практична конфігурація в Playwright — retries: process.env.CI ? 2 : 0: у CI повтори згладжують інфраструктурний шум, а локально флак видно одразу, поки він свіжий і його легко відтворити. Сеньйорська відповідь тут завжди включає процес: ретрай — інструмент спостереження за флаком, а не спосіб його «вилікувати».
Тест стабільно падає лише в одному оточенні. З чого почати діагностику з погляду життєвого циклу запиту?
Почати з локалізації фази, а не з коду тесту: пройтися по шляху DNS → TCP → TLS → відповідь → рендер і зіставити симптом із фазою. Падає лише в одному оточенні — перша гіпотеза DNS: запис у hosts, повільний корпоративний резолвер, пропагація свіжого домену (перевіряється через dig). ECONNREFUSED/ETIMEDOUT — рівень TCP: сервіс не піднявся або фаєрвол. Помилка сертифіката — TLS на стейджингу. Порожнє тіло чи не той URL — ланцюг редиректів (curl -IL). «Дані не приходять» у браузері — CORS/preflight, дивись на OPTIONS. Вічний pending — черга зʼєднань або мовчазний сервер, дивись фазу запиту в DevTools. Тест бачить лише кінцеве «червоно», але причина майже завжди на конкретній фазі — тому спершу локалізуй фазу (мережа проти сервера проти рендеру), і лише потім став гіпотезу про виправлення.
Три кейси, що ловлять флак на конкретних фазах шляху запиту: читання розкладки часу в DevTools і заміна sleep на очікування факту в Playwright, пастка preflight-у, коли «дані не приходять», а сервер до основного запиту й не дійшов, і ланцюг редиректів, який ловить не той URL і порожнє тіло. Скрізь — що дивитися і чому.
Кейс 1. pending назавжди: чекаємо на факт, а не на секунди
Тест клікає кнопку, яка тригерить запит за списком, і зависає до свого таймауту. У DevTools цей запит висить у pending. Перше питання — не «чому код зламався», а на якій фазі застряг запит. Розкладка часу в панелі Network (Queued, Stalled, Waiting for server response / TTFB, Content Download) відповідає одразу:
- Час осів у Waiting (TTFB) — запит пішов, сервер мовчить: довга обробка на бекенді.
- Час осів у Queued / Stalled — запит навіть не відправлено. Браузер тримає близько 6 одночасних зʼєднань на хост для HTTP/1.1, зайві чекають черги. Під навантаженням це розводить «повільний бекенд» і «браузер уперся в ліміт зʼєднань».
Тепер сам тест. Класична причина, чому він то падає, то ні, — очікування часу замість очікування факту:
// погано: гонка з реальним часом завантаження
await page.getByRole('button', { name: 'Оновити' }).click();
await page.waitForTimeout(3000);
await expect(page.getByRole('row', { name: 'John Doe' })).toBeVisible();
load засвідчує лише початкові ресурси сторінки, а не подальші fetch, які JS запускає вже після події (або відповідь на які приходить пізніше). Тому чекати треба на елемент або на конкретну відповідь:
// добре: чекаємо на факт
await page.getByRole('button', { name: 'Оновити' }).click();
await page.getByRole('row', { name: 'John Doe' }).waitFor();
// або саме на потрібну відповідь API — зручно, коли рядок зʼявляється не одразу
await Promise.all([
page.waitForResponse((r) => r.url().includes('/api/users') && r.ok()),
page.getByRole('button', { name: 'Оновити' }).click(),
]);
Що дивитися і чому:
- Фаза застрягання в DevTools — перший крок діагностики. Черга проти очікування сервера — два різні діагнози з різними виправленнями. Фреймворк у цей час чесно чекає відповіді, якої немає, аж до свого таймауту.
waitForTimeout(3000)— головне джерело флаку. На швидкій машині це зайве очікування, на повільному CI — недостатнє, і перевірка зрізає ще живий запит.Promise.allнавколо кліку йwaitForResponse. Слухач вішається до дії, інакше швидка відповідь встигне прилетіти раніше, ніж ми почали її чекати.
Кейс 2. Preflight: «дані не приходять», а сервер їх і не бачив
Репорт: у застосунку DELETE /api/users/42 не спрацьовує, у відповідь «нічого». У Network видно OPTIONS перед цільовим запитом — і це нормально: REST-виклик із Content-Type: application/json майже завжди тригерить preflight, бо application/json не входить до «безпечного» (safelisted) списку значень. Пастка в тому, що сам DELETE правильний, але сервер зле відповідає на OPTIONS — і браузер блокує основний запит ще до відправлення. У тесті це виглядає як «дані не приходять», хоча до бекенда справа не дійшла.
Спершу відтворюємо preflight напряму, без застосунку:
# імітуємо preflight, який шле браузер перед крос-доменним DELETE
curl -i -X OPTIONS https://api.example.com/api/users/42 \
-H 'Origin: https://app.example.com' \
-H 'Access-Control-Request-Method: DELETE' \
-H 'Access-Control-Request-Headers: authorization, content-type'
У здоровій відповіді має бути дозвіл саме на наш метод, заголовки й origin:
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, DELETE
Access-Control-Allow-Headers: authorization, content-type
Якщо запит іде з cookie або Authorization, є окрема вимога: сервер має повернути Access-Control-Allow-Credentials: true, і тоді Access-Control-Allow-Origin не може бути * — потрібен конкретний origin. Це часта причина, чому запит із cookie ламається саме в браузері.
Пастка інструмента: у Chromium (дефолтний браузер Playwright) preflight OPTIONS не потрапляє в мережеві події page.route/waitForResponse — чекати його зі сторінки марно, тест просто зависне до таймаута. Тому відповідь сервера на OPTIONS перевіряємо прямим API-запитом, а сторінковим тестом — лише наслідок:
test('сервер коректно відповідає на preflight', async ({ request }) => {
// прямий OPTIONS тим самим API-контекстом — незалежно від браузерних обмежень
const opt = await request.fetch('https://api.example.com/api/users/42', {
method: 'OPTIONS',
headers: {
Origin: 'https://app.example.com',
'Access-Control-Request-Method': 'DELETE',
},
});
// якщо preflight не 2xx — браузер основний запит навіть не відправить
expect(opt.status()).toBeLessThan(300);
expect(opt.headers()['access-control-allow-methods'] ?? '').toContain('DELETE');
});
test('видалення проходить наскрізь', async ({ page }) => {
await page.getByRole('button', { name: 'Видалити' }).click();
await expect(page.getByText('Користувача видалено')).toBeVisible();
});
Що дивитися і чому:
- Дивись на
OPTIONS, а не лише на цільовий запит. Preflight подвоює кількість запитів — це очікувано; збій на ньому блокує основний виклик мовчки. Access-Control-Max-Age. Дозволяє браузеру кешувати дозвіл і не слатиOPTIONSперед кожним викликом — тому в Network preflight може бути не перед кожнимDELETE.- Запит із cookie +
Allow-Origin: *. Робоча на вигляд конфігурація, яка валиться саме на облікових даних: із*браузер відкине відповідь, хоча в Postman усе «зелено».
Кейс 3. Ланцюг редиректів: не той URL і порожнє тіло
Два різні прояви однієї причини — за 3xx треба (або не треба) йти далі, і тест ловить проміжний крок.
Перший — UI-тест перевіряє URL одразу після переходу й ловить проміжний, бо ланцюг ще не завершився. Типовий ланцюг: http:// → https://, потім example.com → www.example.com, потім / → /login, і кожен крок — окремий повний запит зі своїми DNS/TCP/TLS. Тому перевіряй не голий URL відразу, а чекай на кінцевий стан:
// погано: ловимо проміжний крок ланцюга
await page.goto('http://example.com/');
expect(page.url()).toContain('/login'); // може впасти: редирект ще в дорозі
// добре: чекаємо, поки браузер осяде на фінальній адресі
await page.goto('http://example.com/');
await page.waitForURL('**/login');
Другий прояв — API-тест, який має читати тіло відповіді, налаштований не йти за редиректами й отримує порожнє тіло замість даних. Весь ланцюг видно так:
# показати кожен крок редиректу і фінальний код
curl -sIL https://example.com | grep -i -E 'HTTP/|location'
HTTP/1.1 301 Moved Permanently
location: https://www.example.com/
HTTP/1.1 302 Found
location: https://www.example.com/login
HTTP/1.1 200 OK
Що дивитися і чому:
waitForURLзамість миттєвої перевіркиpage.url(). Редиректи додають затримку — міжgotoі фінальною адресою браузер устигає пройти кілька стрибків.- Прапор «йти за редиректами» в API-клієнті. Якщо тест лишає його вимкненим, на
302він бачить порожнє тіло й заголовокLocation, а не очікувані дані. Свідомо вирішуй, що перевіряєш: сам факт редиректу чи вміст фінальної сторінки. - Метод при переході залежить від коду.
301/302історично можуть змінити метод на GET, а307/308зберігають метод і тіло. Для тесту, що шлеPOSTі чекає редирект, це різниця між «тіло доїхало» і «загубилося». - Циклічний редирект. Неправильно налаштована авторизація зациклює переходи, і браузер обриває їх з помилкою після певної кількості кроків — у тесті це навігаційний таймаут, а не «повільна сторінка».
Шлях запиту та ранні фази
- Можу назвати фази шляху по порядку: DNS → TCP → TLS → надсилання запиту → очікування сервера → відповідь → рендер.
- Розумію, чому «повільні» й флакі тести частіше ламаються на невидимих ранніх фазах (резолвінг, зʼєднання, очікування першого байта), а не на видимому рендері.
- Знаю каскад DNS-резолвінгу (кеш браузера → кеш ОС і
hosts→ резолвер провайдера → рекурсивний обхід) і що таке TTL. - Можу пояснити, чому запис у
/etc/hostsдає різну поведінку тесту локально й у CI, і чому «падає лише в одному оточенні» — це перша гіпотеза про DNS, а не про код. - Розумію три кроки TCP-рукостискання (SYN → SYN-ACK → ACK) і чому keep-alive робить перший запит до хоста дорожчим за наступні.
- Знаю, що означають
ECONNREFUSED(ніхто не слухає порт, сервіс не піднявся) іETIMEDOUTна зʼєднанні (фаєрвол/мережа/VPN).
TLS, запит і TTFB
- Можу пояснити фази TLS-рукостискання (
ClientHelloз SNI →ServerHelloіз сертифікатом → узгодження ключа) і різницю в раундах TLS 1.2 (~2 RTT) vs 1.3 (~1 RTT, 0-RTT при відновленні). - Розумію, чому self-signed / прострочений сертифікат чи невідповідність SNI імені валить тест, і навіщо
ignoreHTTPSErrorsлише для тестових доменів. - Знаю, які заголовки додає саме браузер (
Origin,Cookie,Referer,User-Agent) і що розробник їх у коді не пише. - Можу пояснити, що TTFB — це не лише обробка на сервері, а сума DNS + TCP + TLS + передача запиту + робота бекенду + очікування першого байта (і час редиректів теж).
- Розумію практичний висновок: великий TTFB при нормальному рендері вказує на бекенд/мережу, а не на фронтенд.
Рендер і очікування в тестах
- Знаю різницю між
DOMContentLoaded(DOM побудовано) іload(завантажено всі початкові ресурси), і чому обидві не гарантують, що дані з fetch/XHR уже прийшли. - Можу пояснити, чому синхронний
<script>безasync/deferі зовнішній CSS блокують рендер. - Розумію головне правило: чекати на елемент або конкретну мережеву відповідь, а не на подію
loadчи фіксованийsleep/waitForTimeout.
CORS, редиректи та pending
- Можу пояснити, коли потрібен preflight (
OPTIONS): небезпечний метод,Content-Type: application/jsonчи кастомні заголовки поза safelist — і чому типовий REST-виклик його тригерить. - Розумію, чому запит із cookie/
AuthorizationвимагаєAccess-Control-Allow-Credentials: trueі конкретного origin замість*. - Знаю коди редиректів і різницю: 307/308 зберігають метод і тіло, 301/302 історично можуть перейти на GET, а 303 — завжди GET; і що кожен крок ланцюга — окремий повний запит із власними DNS/TCP/TLS.
- Розумію пастки редиректів у тестах: ловимо проміжний URL замість кінцевого; API-тест без автопереходу за 3xx отримує порожнє тіло.
- Можу пояснити стани
pendingу DevTools: Waiting (TTFB), Queued/Stalled (ліміт ~6 зʼєднань на хост для HTTP/1.1), Blocked, а також нормальнийpendingдля стрімів/SSE/WebSocket.
Postman vs браузер, таймаути й ретраї
- Можу пояснити, чому запит успішний у Postman, але падає в браузері: CORS, preflight, автопідстановка cookie й заголовків, mixed content, CSP/Service Worker — усе це є в браузері й відсутнє в Postman.
- Розумію, що «в Postman працює» доводить лише що бекенд віддає дані, а не що спрацює з браузера.
- Знаю, що таймаути діють на різних рівнях (connection, read/idle HTTP-клієнта, gateway
504, navigation/action/expect/test у фреймворку), і що при падінні треба зрозуміти, чий саме таймаут спрацював. - Розумію, чому не можна «лікувати» флак підняттям таймауту — це ховає проблему й перетворює швидкі падіння на довгі зависання.
- Можу пояснити звʼязок retry й ідемпотентності:
GET/DELETEбезпечно повторити, а повторPOSTможе створити дублікат; і чому «passed on retry» — це прихований баг, а не зелений тест.
У якому порядку йдуть фази HTTPS-запиту до появи відповіді?
Питання
Назвіть повний шлях HTTPS-запиту від Enter до сторінки. Де найчастіше ховаються «повільні» тести?