DevTools: вкладка Network і дебаг
Зміст
Кнопка натиснулась, а нічого не сталося. Форма «крутиться» вічно. Дані на сторінці старі, хоча в базі вже нові. Тест то зелений, то червоний без жодних змін у коді. У більшості таких випадків відповідь на «чому» лежить у вкладці Network інструментів розробника (DevTools). Це вікно між тим, що ви бачите в браузері, і тим, що насправді літає між клієнтом і сервером. Для AQA це не «інструмент розробника», а основний прилад діагностики: тут видно, який запит пішов, з якими заголовками, що повернув сервер, скільки це тривало і чому інколи не спрацювало.
Далі скрізь орієнтуюся на Chrome DevTools як на найпоширеніший варіант. У Firefox і Safari логіка та сама, назви кнопок відрізняються.
Відкрити панель: F12, або Cmd+Option+I (macOS) / Ctrl+Shift+I (Windows/Linux), далі вкладка Network.
Що показує вкладка Network
Кожен рядок у таблиці Network — це один мережевий запит, який зробила сторінка: завантаження самого HTML, зображень, скриптів, стилів, шрифтів, а також фонові звернення до API. DevTools записує мережу лише поки вони відкриті. Звідси перше правило: відкрийте DevTools перед тим, як відтворювати проблему, і бажано перезавантажте сторінку (Cmd/Ctrl+R), щоб побачити повний цикл із першого запиту.
Ще один важливий вимикач поруч — Preserve log («зберігати журнал»). За замовчуванням список очищається на кожній навігації, тож редірект після логіну зітре все, що ви хотіли роздивитися. Увімкнений Preserve log зберігає запити крізь переходи між сторінками — це must-have для розслідування логінів, редіректів і багатокрокових форм.
Чому це важливо саме для тестувальника? Те, що ви бачите очима, — це вже наслідок. Кнопка «Зберегти» могла відпрацювати ідеально на фронтенді, а помилка — у тому, що POST-запит повернув 422. Без Network ви бачите лише «не зберігається» і будуєте здогади. З Network ви бачите факт.
Колонки таблиці
За замовчуванням у таблиці кілька ключових колонок; їхній склад змінюється правою кнопкою по заголовку.
| Колонка | Що показує | Навіщо AQA |
|---|---|---|
| Name | Ім’я ресурсу / останній сегмент URL | Знайти потрібний запит |
| Status | Код статусу HTTP-відповіді | Перше, на що дивишся при збої |
| Type | Тип ресурсу (xhr, fetch, document, script, png…) | Відфільтрувати «своє» |
| Initiator | Хто ініціював запит (рядок коду, інший запит, редірект) | Зрозуміти, звідки взявся запит |
| Size | Обсяг переданих даних / розмір ресурсу | Виявити кеш і «важкі» відповіді |
| Time | Загальна тривалість запиту | Знайти повільні місця |
| Waterfall | Часова діаграма фаз запиту | Побачити, де саме гальмує |
Колонка Size показує два значення: скільки реально передано мережею (стиснуте тіло плюс заголовки) і повний, розпакований розмір ресурсу. Якщо там написано (from disk cache) або (from memory cache) — мережею не передавалося нічого, відповідь узята з кешу. До цього ще повернемось, це часте джерело плутанини в тестах.
Фільтри за типом ресурсу
Над таблицею — рядок кнопок-фільтрів за типом. Вони критично економлять час, бо на реальній сторінці сотні запитів, а вам потрібен один-два.
| Фільтр | Що лишає | Типовий випадок для AQA |
|---|---|---|
| All | Усе | Загальний огляд |
| Fetch/XHR | Виклики до API (fetch() та XMLHttpRequest) | Тут майже вся робота з бекендом |
| Doc | Головні HTML-документи | Побачити редіректи, статус самої сторінки |
| CSS | Таблиці стилів | Стилі не застосувались |
| JS | Скрипти | Скрипт не завантажився / 404 |
| Font | Шрифти | «Поплив» шрифт |
| Img | Зображення | Битий src, повільні картинки |
| WS | Веб-сокети (WebSocket) | Реалтайм: чати, живі оновлення |
Одна тонкість: у сучасному Chrome окремих кнопок XHR і Fetch немає — вони об’єднані в Fetch/XHR. Історично XHR (XMLHttpRequest) — старий спосіб асинхронних запитів, Fetch — сучасний через fetch(). Для діагностики різниця несуттєва: обидва — фонові звернення до сервера, і саме тут ви шукаєте виклики API.
Поруч із кнопками є текстове поле фільтра. У ньому працюють і прості підрядки (users), і спеціальні оператори — наприклад status-code:500, method:POST, domain:api.example.com, а мінус на початку інвертує умову (-method:GET). Це швидший шлях до потрібного запиту, ніж прокручувати весь список.
Заголовки запиту і відповіді
Клацніть на будь-який запит — праворуч відкриється детальна панель із вкладками. Перша і найважливіша — Headers (заголовки). Заголовки (headers) — це метадані, які клієнт і сервер додають до повідомлення: хто, що, в якому форматі, з якою авторизацією.
Панель Headers ділиться на блоки:
- General — метод (
GET/POST…), повний URL, код статусу, IP-адреса сервера (Remote Address). - Response Headers — що прислав сервер.
- Request Headers — що надіслав браузер.
Заголовки, які AQA зустрічає найчастіше:
| Заголовок | Напрям | Що означає |
|---|---|---|
Content-Type | обидва | Формат тіла: application/json, text/html… |
Authorization | запит | Токен доступу, напр. Bearer <token> |
Cookie | запит | Кукі, які браузер відправляє серверу |
Set-Cookie | відповідь | Кукі, які сервер просить зберегти |
Cache-Control | обидва | Правила кешування (no-cache, max-age=3600) |
ETag | відповідь | «Відбиток» версії ресурсу для перевірки кешу |
Location | відповідь | Куди редіректить (при 3xx) |
Access-Control-Allow-Origin | відповідь | Дозвіл CORS: кому можна читати відповідь |
Приклад типової пари запит-відповідь для логіну:
POST /api/v1/login HTTP/1.1
Host: app.example.com
Content-Type: application/json
Accept: application/json
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Set-Cookie: session=abc123; Path=/; HttpOnly; Secure; SameSite=Lax
Cache-Control: no-store
Тут запит логіну не несе Authorization — токен ви якраз отримуєте у відповідь (у тілі або в Set-Cookie), а Bearer-заголовок з’явиться вже в наступних, авторизованих запитах.
Для тестувальника заголовки — часто перше місце, де ховається баг. Немає Authorization у запиті → сервер повертає 401. Неправильний Content-Type → сервер не розуміє тіло і віддає 400/415. Відсутній Access-Control-Allow-Origin → запит заблокований політикою CORS (про це в кінці).
Payload і тіло відповіді
Вкладка Payload (у старіших версіях — частина Headers) показує, що саме браузер відправив у тілі запиту. Для POST/PUT/PATCH це найцінніше: тут видно фактичні дані, які пішли на сервер.
{
"email": "user@example.com",
"password": "••••••",
"remember": true
}
Часта помилка, яку ловлять саме тут: форма начебто заповнена правильно, але в payload летить порожнє поле або старе значення (стан не встиг оновитися). Це прямий доказ бага на фронтенді, а не на бекенді.
Для GET-запитів окремого payload немає — параметри летять у самому URL (query string), і DevTools зручно розкладає їх у блоці Query String Parameters.
Вкладка Response (поруч — Preview) показує тіло відповіді. Різниця між ними:
- Response — сирий текст відповіді, як прийшов.
- Preview — той самий вміст, але відрендерений: JSON — деревом, HTML — превʼю, картинка — зображенням.
Саме тут ви звіряєте контракт: чи повернув сервер ті поля, що очікуються, у тому форматі й з тими значеннями. Це джерело правди для API-перевірок — DOM може показувати одне, а сервер віддавати інше.
Статуси і кольори
Код статусу HTTP (HTTP status code) — трицифрове число у відповіді, яке коротко каже, чим завершився запит. Перша цифра задає клас.
| Клас | Значення | Часті коди |
|---|---|---|
| 1xx | Інформаційні | 101 (перехід на WebSocket) |
| 2xx | Успіх | 200 OK, 201 Created, 204 No Content |
| 3xx | Перенаправлення | 301, 302, 304 Not Modified, 307, 308 |
| 4xx | Помилка клієнта | 400, 401, 403, 404, 409, 422, 429 |
| 5xx | Помилка сервера | 500, 502, 503, 504 |
Ключове для діагностики: 4xx — «винен» той, хто питав (не той запит, не ті дані, немає прав), 5xx — «винен» сервер (впав, таймаут бекенду, помилка коду). Це відразу ділить розслідування на дві гілки: 4xx веде до фронтенду й даних запиту, 5xx — до серверних логів.
Кілька кодів, які постійно плутають:
401 Unauthorized— насправді «не автентифікований»: немає або невалідний токен. За RFC 9110 такий статус доречний, коли запиту бракує валідних облікових даних, і відповідь має містити заголовокWWW-Authenticate.403 Forbidden— автентифікований, але немає прав на цю дію: сервер зрозумів запит і все одно відмовляє, і повторний логін тут не допоможе. Цінно для тестів рольового доступу.422 Unprocessable Content— тіло синтаксично коректне (інакше був би400) і тип вмісту зрозумілий (інакше415), але не проходить семантичну валідацію (наприклад, email уже зайнятий). У RFC 9110 це саме «Unprocessable Content»; історична назва з RFC 4918, яку досі часто вживають, — «Unprocessable Entity». У багатьох фреймворках (зокрема Rails) саме тут лежать повідомлення про помилки форм.304 Not Modified— не помилка: сервер підтвердив, що кеш актуальний (деталі нижче).
Щодо кольорів: Chrome підсвічує червоним текстом невдалі й заблоковані запити — це найнадійніший візуальний сигнал: пробіг очима по списку, червоне — туди й дивишся.
Нюанс. Колірна розмітка рядків у Network — деталь інтерфейсу, що змінюється між версіями браузера. Головний орієнтир — число в колонці Status, а не відтінок рядка.
Waterfall: читаємо тайминг
Колонка Waterfall — це горизонтальна діаграма, де довжина смужки дорівнює часу, а положення показує, коли запит стартував відносно інших. Вона відповідає на питання «чому довго» і «що на що чекало».
Клацнувши на запит і відкривши вкладку Timing, ви розкладаєте смужку на фази:
| Фаза | Що відбувається | Про що сигналить |
|---|---|---|
| Queueing / Stalled | Запит чекає у черзі | Браузер обмежує к-сть одночасних з’єднань до хоста (для HTTP/1.1 — зазвичай 6) |
| DNS Lookup | Резолвінг домену в IP | Проблеми DNS |
| Initial connection | Встановлення TCP-з’єднання | Мережеві затримки |
| SSL | Рукостискання TLS | Час на шифрування |
| Waiting (TTFB) | Очікування першого байта від сервера | Швидкість бекенду |
| Content Download | Завантаження тіла відповіді | Розмір відповіді / канал |
Найважливіша для тестувальника фаза — TTFB (Time To First Byte), час очікування відповіді сервера. Великий TTFB означає, що гальмує бекенд, а не «інтернет» чи фронтенд. Якщо великий саме Content Download — відповідь надто важка або канал вузький.
Дві вертикальні лінії на waterfall позначають ключові події завантаження сторінки: синя — DOMContentLoaded (HTML розібрано й побудовано DOM), червона — Load (завантажено всі підресурси сторінки). За наведенням DevTools показує точний час кожної події.
Практичний зв’язок з автотестами: waterfall пояснює флак (flakiness). Тест «клікнув і одразу перевірив» падає, бо POST ще у фазі Waiting, а асерт уже виконався. Рішення — не «поспати рандомну секунду», а чекати на конкретну відповідь (у Playwright — waitForResponse), тобто прив’язати очікування (wait) до реальної події, яку ви бачите в Network.
Кеш: живий запит проти кешованого
Одне з питань, на яких «сипляться» і початківці, і тести: це справжня свіжа відповідь чи браузер підсунув старе з кешу?
Ознаки, що відповідь із кешу (мережа не задіяна):
- У колонці Size написано
(from memory cache)або(from disk cache). - Статус
200, але фаза мережі в Timing практично нульова.
Окремий випадок — статус 304 Not Modified. Це живий запит: браузер таки сходив на сервер із заголовком If-None-Match: <ETag> (або If-Modified-Since), а сервер відповів «нічого не змінилось, бери своє з кешу». Тобто мережа була, але тіло повторно не передавалось. Тому Size у 304 малий, а не «from cache».
| Що бачите | Мережа? | Тіло передано? |
|---|---|---|
200, Size = число | так | так (живий, повний) |
200 (from disk/memory cache) | ні | ні (з кешу) |
304 Not Modified | так | ні (кеш підтверджено) |
Чому це болить у тестах: тест міняє дані, перезавантажує сторінку — а бачить старі, бо браузер віддав кеш. Далі — хибний висновок «баг у збереженні», хоча збереження працює.
Disable cache
У панелі Network є прапорець Disable cache. Коли він увімкнений (і DevTools відкриті), браузер ігнорує кеш і щоразу тягне ресурс наново — імітує «першого відвідувача». Це прибирає цілий клас хибних діагнозів на кшталт «чому старі дані».
Важливий нюанс: Disable cache працює лише поки DevTools відкриті. Закрили панель — кеш знову в грі. Стабільні автотести на цей прапорець не покладаються: браузер запускають із чистим профілем/контекстом, тому в тестах проблема майже не виникає — вона більше про ручну діагностику.
Кукі в запиті й відповіді
Кукі (cookies) — маленькі шматки даних, які сервер просить браузер зберегти й повертати з кожним наступним запитом до цього домену. На них найчастіше тримається сесія й авторизація.
Два напрями:
- Сервер у відповіді надсилає
Set-Cookie— «збережи це». - Браузер у наступних запитах шле
Cookie— «ось що я зберіг».
Це видно і в Headers, і в окремій вкладці Cookies обраного запиту (там кукі розкладені в таблицю з атрибутами).
Атрибути куки, які має знати AQA:
| Атрибут | Що робить | Чому важливо для тестів |
|---|---|---|
Expires / Max-Age | Термін життя | Сесійна кука vs постійна |
Domain / Path | Область дії | Чому кука «не долітає» до потрібного запиту |
Secure | Передається лише через HTTPS | Виняток — localhost, який браузери вважають захищеним контекстом; на звичайному http:// (не localhost) не встановиться |
HttpOnly | Недоступна для JS (document.cookie) | Її не прочитати зі скрипта — тільки з Network |
SameSite | Поведінка на крос-сайтових запитах | Часта причина «злетілої» сесії між доменами |
HttpOnly варто підкреслити: таку куку навмисно не видно з JavaScript (захист від крадіжки токена). Тому в автотесті ви не дістанете її через document.cookie — читайте через API браузера для роботи з кукі (у Playwright — context.cookies()) або дивіться в Network.
Кукі — центральна тема ізоляції стану в тестах. Якщо тести течуть один в одного через залишену сесію, кукі й сторедж або чистять між тестами, або дають кожному тесту свіжий контекст браузера.
Throttling: емуляція повільної мережі
У панелі Network є випадаючий список профілів швидкості мережі (throttling). Він штучно ріже пропускну здатність і додає затримку, щоб відтворити поведінку на повільному з’єднанні, не виходячи з-за столу.
Навіщо AQA:
- Перевірити стани завантаження: чи є спінер, скелетон, чи не «моргає» порожній екран.
- Зловити ганки, які на швидкому Wi-Fi не відтворюються (кнопка активна, поки запит іще летить → подвійне надсилання).
- Профіль Offline — перевірити поведінку без мережі: чи є зрозуміле повідомлення, чи все «мовчки ламається».
Можна створити власний профіль із конкретними значеннями завантаження, віддачі й затримки (latency).
Набір готових пресетів змінювався між версіями Chrome: у поточних версіях це, зокрема, Fast 4G, Slow 4G і 3G (раніше були Fast 3G / Slow 3G). Тому для відтворюваних умов краще задавати власний профіль із фіксованими числами, а не покладатися на назву пресета.
Прямий зв’язок із флаком: throttling — це чесний спосіб знайти race condition до того, як його знайде CI. Якщо тест падає на повільному профілі, він не «флакне на швидкому» — він просто ще не впав: очікування (wait) в ньому недостатньо прив’язані до подій.
Вкладка Application → Storage
Мережею дані приходять, але частина їх осідає в клієнтському сховищі. Це вкладка Application (у Firefox — Storage), розділ зліва.
| Сховище | Що зберігає | Живе доки |
|---|---|---|
| Local Storage | Рядки ключ-значення | Поки не видалять |
| Session Storage | Те саме, але на вкладку | Поки відкрита вкладка |
| Cookies | Кукі домену | За Expires/Max-Age |
| IndexedDB | Структуровані дані, об’єкти | Поки не видалять |
| Cache Storage | Кеш Service Worker (офлайн, PWA) | Керує Service Worker |
Для AQA це друге (після кукі) місце, де ховається стан. Токен доступу, вибрана мова, флаги фіч, «побачив онбординг» — усе це часто в Local Storage. Звідси два практичні наслідки:
- Діагностика. «Чому користувач після логіну бачить старий інтерфейс» — може, у Local Storage застряг старий флаг.
- Ізоляція тестів. Залишені між тестами
localStorageчиIndexedDB— типова причина «тест проходить сам, але падає в наборі». Тому стан чистять між тестами; кнопка Clear site data в Application робить це вручну, а автотести стартують із чистого контексту.
У розділі є оглядова сторінка Storage з квотою — скільки місця займає сайт і скільки доступно.
Console: помилки поряд із мережею
Вкладка Console — сусід Network, і ці двоє працюють у парі. Червоні рядки в консолі часто пояснюють те, що в Network виглядає як загадковий збій.
Що читати AQA:
- Uncaught TypeError / ReferenceError — упав JS. Дуже частий сценарій:
fetchповернув не той формат, код спробував прочитати неіснуюче поле, і вся сторінка «застигла». У Network при цьому запит може бути зелений200— а баг у обробці відповіді. - CORS-помилки — саме тут з’являється розгорнутий текст, чому запит заблоковано (у Network ви побачите лише
failed). - Mixed Content — сторінка на HTTPS тягне ресурс по HTTP, браузер блокує.
- CSP (Content-Security-Policy) — політика безпеки заборонила завантажити скрипт/стиль/картинку.
Зручний прийом: тримати Console і Network поруч. Побачили червоний запит у Network → зазирнули в Console по деталі причини. Часто відповідь одним рядком там і лежить.
Для автотестів корисно слухати консоль програмно: збирати помилки консолі під час прогону й фейлити тест на несподіваних error-логах. Це ловить регресії, які візуально ще «наче працюють».
Алгоритм діагностики «червоного» запиту
Зібравши все докупи — ось послідовність, яка веде від «щось червоне / не працює» до причини. Ідемо згори вниз, зупиняємось на першому збігу.
Крок 0. Відтворити при відкритих DevTools. Відкрити Network, за потреби увімкнути Preserve log (є редіректи) і Disable cache (підозра на кеш), відтворити дію. Знайти проблемний запит (фільтр Fetch/XHR + пошук за іменем).
Крок 1. Подивитися на Status.
- Число
4xx→ помилка клієнта. Далі:401→ немає/невалідний токен. ПеревіртеAuthorizationу Request Headers і кукі сесії.403→ токен є, прав немає. Тестуєте роль — це очікувано; ні — питання до прав користувача.404→ неправильний URL/ендпоінт. Звірте General → Request URL.422/400→ погані дані. Відкрийте Payload і Response: там зазвичай список полів, що не пройшли валідацію.429→ уперлися в ліміт частоти (rate limit). Зачекайте / зменшіть навантаження.
- Число
5xx→ помилка сервера. Фронтенд ні до чого. Зафіксуйте запит (URL, тіло, час,x-request-id, якщо є) і йдіть до серверних логів/бекенд-команди.502/503/504часто означають, що бекенд лежить або не встиг відповісти.
Крок 2. Статусу немає — написано (failed), (blocked), (pending). Значить, до логіки сервера навіть не дійшло. Дивимось у Console і колонку Status на деталь:
| Ознака | Причина | Куди копати |
|---|---|---|
CORS error, у Console blocked by CORS policy | Сервер не дозволив крос-доменний запит | Response Headers на Access-Control-Allow-Origin; для складних запитів — попередній OPTIONS (preflight) |
net::ERR_BLOCKED_BY_CLIENT | Блокує розширення (адблок) | Вимкнути розширення / перевірити в режимі інкогніто |
blocked:csp / CSP у Console | Політика безпеки сторінки | Заголовок Content-Security-Policy |
Mixed Content | HTTPS-сторінка тягне HTTP-ресурс | Схема URL ресурсу |
(pending) надовго / ERR_CONNECTION_* / таймаут | Немає з’єднання, сервер недоступний, DNS | Timing (де завис), доступність хоста |
Крок 3. Статус 200, але «не працює». Найпідступніший випадок — мережа ок, а результат ні. Тоді:
- Відкрийте Response/Preview: можливо, сервер повернув
200з тілом-помилкою ({"error": "..."}) або порожнім/не тим форматом. - Перевірте Console: імовірно, JS упав під час обробки відповіді.
- Перевірте Size: якщо
(from cache)— ви дивитесь на старі дані, а не на свіжу відповідь.
Про CORS окремо, бо він лякає найбільше. CORS (Cross-Origin Resource Sharing) — механізм, що дозволяє сторінці з одного походження (origin) читати відповідь із іншого. Для «непростих» запитів (метод не GET/POST/HEAD, кастомні заголовки на кшталт Authorization, або Content-Type, відмінний від трьох простих — application/x-www-form-urlencoded, multipart/form-data, text/plain) браузер спершу шле preflight — окремий OPTIONS-запит — і питає сервер, чи можна. Якщо сервер не відповів потрібними Access-Control-Allow-* заголовками, браузер блокує відповідь. Для «простого» запиту (без preflight) він при цьому міг таки дійти до сервера й виконатись — тіло сформувалось, але браузер не дає його прочитати. Звідси характерна пастка: у логах сервера дія пройшла, а в браузері — «помилка». Це майже завжди питання конфігурації сервера/проксі, а не тесту.
OPTIONS /api/v1/orders HTTP/1.1
Origin: https://app.example.com
Access-Control-Request-Method: POST
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: POST, GET, OPTIONS
Access-Control-Allow-Headers: Authorization, Content-Type
Немає рядка Access-Control-Allow-Origin з вашим походженням у відповіді — браузер заблокує наступний POST, і ви побачите червоне.
Мережа й автотести
Усе вище — це не лише ручна діагностика. Кожна концепція має пряме продовження в автоматизації, і Network — це «джерело правди», проти якого звіряють поведінку тестів.
- Очікування замість пауз. Побачили в Network, який запит завершує дію → у тесті чекайте саме на нього (
waitForResponseза URL/статусом), а не на фіксовану паузу. Це прибирає найпоширеніший вид флаку. - Перехоплення мережі (network interception). Те, що в DevTools ви лише спостерігаєте, у тесті можна підмінити: віддати заздалегідь готову відповідь (мок), примусово повернути
500(перевірити обробку помилок), затримати запит (перевірити спінер), заблокувати сторонні скрипти (стабільність). У Playwright цеroute/fulfill/abort. - Ізоляція стану. Кукі й Storage, які ви бачите в Application, — це те, що між тестами треба обнуляти. Свіжий контекст браузера на тест = немає «тест падає лише в наборі».
- Асерти по контракту. Тіло відповіді з вкладки Response — еталон для перевірок: тест може перевіряти не тільки DOM, а й що API повернув потрібні поля з потрібними значеннями.
- Кеш і детермінізм. Розуміння різниці
200 from cache/304/ живий200рятує від хибних «баг збереження», коли насправді браузер віддав старе.
Вкладка Network перетворює «щось не працює» на конкретне твердження — «запит POST /api/orders повернув 422, у тілі email already taken, Authorization присутній, кеш вимкнено». Саме такі формулювання відрізняють баг-репорт, який беруть у роботу відразу, від тікета, що повертається з питанням «а що конкретно сталося».
Що показує вкладка Network у DevTools і навіщо вона тестувальнику?
Network показує всі мережеві запити, які зробила сторінка: завантаження HTML, скриптів, стилів, картинок і — найголовніше — фонові звернення до API. Кожен рядок таблиці — один запит із методом, статусом, розміром і часом виконання. Для тестувальника це основний прилад діагностики, бо те, що видно очима на сторінці, — вже наслідок: кнопка «Зберегти» могла відпрацювати ідеально на фронтенді, а справжня помилка в тому, що POST-запит повернув 422. Без Network залишаються лише здогади «не зберігається», з Network — факт: який запит пішов, з якими заголовками, що відповів сервер і скільки це тривало. Це перетворює баг-репорт із «щось не працює» на конкретне твердження, яке беруть у роботу відразу.
Чому DevTools треба відкрити до відтворення проблеми і що робить Preserve log?
DevTools записує мережу лише поки панель відкрита, тому запити, зроблені до відкриття, ви вже не побачите. Правильний порядок: відкрити Network, за потреби перезавантажити сторінку, і лише потім відтворювати проблему — так буде видно повний цикл із першого запиту. Preserve log вирішує другу типову втрату: за замовчуванням список очищається на кожній навігації, тож редірект після логіну зітре все, що ви хотіли роздивитися. З увімкненим Preserve log запити зберігаються крізь переходи між сторінками — це must-have для розслідування логінів, редіректів і багатокрокових форм.
На сторінці сотні запитів. Як швидко знайти потрібний виклик API?
Спершу фільтр за типом: кнопка Fetch/XHR лишає тільки фонові звернення до API (виклики через fetch() і XMLHttpRequest) — саме там майже вся робота з бекендом. Далі текстове поле фільтра: працюють прості підрядки на кшталт users і спеціальні оператори — status-code:500, method:POST, domain:api.example.com, а мінус на початку інвертує умову (-method:GET). Додатково допомагає колонка Initiator — вона показує, хто ініціював запит: рядок коду, інший запит чи редірект. У сучасному Chrome окремих кнопок XHR і Fetch немає — вони об'єднані, і для діагностики різниця між ними несуттєва.
Які класи HTTP-статусів існують і як перша цифра допомагає в діагностиці?
Перша цифра коду задає клас: 1xx — інформаційні, 2xx — успіх, 3xx — перенаправлення, 4xx — помилка клієнта, 5xx — помилка сервера. Ключове правило діагностики: 4xx означає «винен той, хто питав» — не той запит, не ті дані, немає прав; 5xx — «винен сервер»: упав, таймаут бекенду, помилка коду. Це відразу ділить розслідування на дві гілки: при 4xx дивишся на фронтенд і дані запиту (заголовки, payload), при 5xx — фіксуєш запит (URL, тіло, час, x-request-id, якщо є) і йдеш до серверних логів чи бекенд-команди. Важливо пам'ятати, що не кожен «не-200» — помилка: 304 Not Modified — це нормальне підтвердження актуальності кешу.
Чим 401 Unauthorized відрізняється від 403 Forbidden?
401 — це «не автентифікований»: у запиті немає валідних облікових даних (токен відсутній, протух або невалідний), і за RFC 9110 відповідь має містити заголовок WWW-Authenticate. 403 — протилежна ситуація: сервер зрозумів, хто ви, але у вас немає прав на цю дію, і повторний логін не допоможе. Практична різниця для тестувальника: при 401 перевіряєш Authorization у Request Headers і кукі сесії, при 403 — питання до ролі й прав користувача. Для тестів рольового доступу 403 — цінний очікуваний результат: він підтверджує, що сервер свідомо відмовляє користувачу без відповідних прав.
Що означає 422 і чим він відрізняється від 400?
422 Unprocessable Content каже: тіло запиту синтаксично коректне (інакше був би 400) і тип вмісту зрозумілий (інакше 415), але дані не проходять семантичну валідацію — наприклад, email уже зайнятий. Історична назва з RFC 4918 — «Unprocessable Entity», у RFC 9110 це саме «Unprocessable Content». У багатьох фреймворках (зокрема Rails) саме під 422 лежать повідомлення про помилки форм. Практика: побачили 422 чи 400 — відкривайте Payload і Response: у payload видно, що фактично полетіло на сервер, а у відповіді зазвичай список полів, які не пройшли валідацію. Часта знахідка саме тут: форма начебто заповнена, а в payload летить порожнє поле або старе значення — прямий доказ бага на фронтенді, а не на бекенді.
Що дивитися у вкладках Headers і Payload обраного запиту?
Headers — метадані запиту й відповіді, поділені на блоки: General (метод, повний URL, статус, IP сервера), Request Headers (що надіслав браузер) і Response Headers (що прислав сервер). Найчастіші заголовки для AQA: Content-Type (формат тіла), Authorization (токен, наприклад Bearer <token>), Cookie / Set-Cookie, Cache-Control, Location (куди редіректить при 3xx), Access-Control-Allow-Origin (дозвіл CORS). Payload показує фактичне тіло, яке браузер відправив у POST/PUT/PATCH; для GET окремого payload немає — параметри летять у URL, і DevTools розкладає їх у блоці Query String Parameters. Заголовки — часто перше місце, де ховається баг: немає Authorization — сервер поверне 401; неправильний Content-Type — сервер не зрозуміє тіло і віддасть 400 чи 415.
Чим вкладка Response відрізняється від Preview?
Обидві показують тіло відповіді, але по-різному: Response — сирий текст, як прийшов від сервера; Preview — той самий вміст, але відрендерений: JSON — розгорнутим деревом, HTML — превʼю, картинка — зображенням. Для тестувальника це місце звірки контракту: чи повернув сервер очікувані поля, у тому форматі й з тими значеннями. Це джерело правди для API-перевірок — DOM може показувати одне, а сервер віддавати інше, і без цієї вкладки не зрозуміти, на чиєму боці розбіжність.
Як зрозуміти, що відповідь прийшла з кешу, і що означає 304 Not Modified?
Ознаки відповіді з кешу: у колонці Size написано (from memory cache) або (from disk cache), статус 200, а фаза мережі в Timing практично нульова — мережею не передавалося нічого. 304 — інший випадок: це живий запит, браузер таки сходив на сервер із заголовком If-None-Match (значення — ETag) або If-Modified-Since, а сервер відповів «нічого не змінилось, бери своє з кешу». Тобто мережа була, але тіло повторно не передавалося — тому Size у 304 малий, а не «from cache». Чому це болить у тестах: тест міняє дані, перезавантажує сторінку — а бачить старі, бо браузер віддав кеш; далі хибний висновок «баг у збереженні», хоча збереження працює. Розрізнення живого 200, 200 from cache і 304 рятує від цілого класу хибних діагнозів.
Що робить прапорець Disable cache і про який нюанс варто пам'ятати?
Disable cache змушує браузер ігнорувати кеш і щоразу тягнути ресурс наново — імітація «першого відвідувача», яка прибирає хибні діагнози на кшталт «чому старі дані». Головний нюанс: прапорець працює лише поки DevTools відкриті — закрили панель, і кеш знову в грі. Тому це інструмент ручної діагностики, а не стабілізації автотестів: стабільні тести на нього не покладаються, бо браузер запускають із чистим профілем чи контекстом, і проблема кешу там майже не виникає.
Що показує Waterfall і що таке TTFB?
Waterfall — горизонтальна діаграма, де довжина смужки дорівнює часу запиту, а положення показує, коли він стартував відносно інших; вона відповідає на «чому довго» і «що на що чекало». Вкладка Timing розкладає смужку на фази: Queueing/Stalled (чекання в черзі — браузер обмежує кількість одночасних з'єднань до хоста, для HTTP/1.1 зазвичай 6), DNS Lookup, Initial connection, SSL, Waiting (TTFB) і Content Download. Найважливіша для тестувальника фаза — TTFB (Time To First Byte), час очікування першого байта відповіді: великий TTFB означає, що гальмує бекенд, а не «інтернет» чи фронтенд; великий Content Download — відповідь надто важка або канал вузький. Дві вертикальні лінії на діаграмі позначають події сторінки: синя — DOMContentLoaded (DOM побудовано), червона — Load (завантажено всі підресурси). Для AQA waterfall ще й пояснює флак: тест «клікнув і одразу перевірив» падає, бо POST ще у фазі Waiting, а асерт уже виконався — рішення не «поспати секунду», а чекати на конкретну відповідь.
Чому куку сесії не видно через document.cookie і як її тоді перевірити?
Бо вона має атрибут HttpOnly — така кука навмисно недоступна для JavaScript, це захист від крадіжки токена скриптом. Механіка кукі двонаправлена: сервер у відповіді шле Set-Cookie («збережи це»), браузер у наступних запитах — заголовок Cookie («ось що я зберіг»), і обидва напрями видно в Headers або в окремій вкладці Cookies обраного запиту, де кукі розкладені в таблицю з атрибутами. Тому HttpOnly-куку перевіряють через Network чи вкладку Application, а в автотесті — через API браузера для роботи з кукі: у Playwright це context.cookies(), а не document.cookie. З інших атрибутів варто знати Expires/Max-Age (сесійна vs постійна), Domain/Path (чому кука «не долітає» до потрібного запиту), Secure (лише HTTPS, з винятком для localhost) і SameSite (часта причина «злетілої» сесії на крос-сайтових запитах).
Навіщо тестувальнику throttling у Network?
Throttling штучно ріже пропускну здатність і додає затримку, відтворюючи повільне з'єднання без реального поганого інтернету. Це дає три класи перевірок: стани завантаження (чи є спінер і скелетон, чи не «моргає» порожній екран), гонки, які на швидкому Wi-Fi не відтворюються (кнопка активна, поки запит іще летить — подвійне надсилання), і профіль Offline — поведінка без мережі взагалі. Нюанс із пресетами: їх набір змінювався між версіями Chrome (зараз, зокрема, Fast 4G, Slow 4G, 3G — раніше були Fast/Slow 3G), тому для відтворюваних умов краще створити власний профіль із фіксованими числами завантаження, віддачі й затримки. Для AQA це чесний спосіб знайти race condition до того, як його знайде CI: якщо тест падає на повільному профілі, він не «флакне на швидкому» — він просто ще не впав, бо його очікування недостатньо прив'язані до подій.
Чим Local Storage відрізняється від Session Storage і кукі, і до чого тут ізоляція тестів?
Local Storage зберігає рядки ключ-значення, поки їх не видалять; Session Storage — те саме, але живе лише поки відкрита вкладка; кукі живуть за Expires/Max-Age і, на відміну від сторедж, автоматично їдуть на сервер із запитами. Усе це видно у вкладці Application (у Firefox — Storage), там же IndexedDB для структурованих даних і Cache Storage сервіс-воркера. Для AQA це друге після кукі місце, де ховається стан: токен доступу, вибрана мова, флаги фіч, позначка «побачив онбординг». Звідси два наслідки: діагностичний — «чому користувач після логіну бачить старий інтерфейс» може пояснюватися старим флагом у Local Storage; і тестовий — залишені між тестами localStorage чи IndexedDB є типовою причиною «тест проходить сам, але падає в наборі». Тому стан або чистять між тестами (вручну — кнопка Clear site data), або дають кожному тесту свіжий контекст браузера.
Запит у Network червоний, але коду статусу немає — (failed), (blocked). Які можливі причини?
Відсутність коду означає, що до логіки сервера справа навіть не дійшла — запит зупинився раніше, і деталі треба шукати в Console. Типові причини: CORS (у Console — «blocked by CORS policy», перевіряти Access-Control-Allow-Origin у Response Headers); net::ERR_BLOCKED_BY_CLIENT — запит зарізало розширення на кшталт адблока, перевіряється в інкогніто; блокування CSP — політика безпеки сторінки заборонила ресурс; Mixed Content — HTTPS-сторінка тягне ресурс по HTTP, браузер блокує; довгий (pending) або ERR_CONNECTION_* — сервер недоступний, немає з'єднання чи проблеми DNS, дивитись Timing, де саме зависло. Зручний прийом — тримати Console і Network поруч: побачили червоний запит у Network, зазирнули в Console по розгорнуту причину, вона часто лежить там одним рядком.
Що таке CORS і preflight? Чому дія в логах сервера пройшла, а браузер показує помилку?
CORS (Cross-Origin Resource Sharing) — механізм, який дозволяє сторінці з одного походження (origin) читати відповідь із іншого. Для «непростих» запитів — метод не з переліку GET/POST/HEAD, кастомні заголовки на кшталт Authorization, або Content-Type, відмінний від трьох простих (application/x-www-form-urlencoded, multipart/form-data, text/plain) — браузер спершу шле preflight: окремий OPTIONS-запит із Access-Control-Request-Method і Access-Control-Request-Headers, питаючи сервер, чи можна. Якщо сервер не відповів потрібними Access-Control-Allow-* заголовками, браузер блокує відповідь. Пастка в тому, що «простий» запит (без preflight) міг таки дійти до сервера й виконатись — тіло сформувалось, але браузер не дає його прочитати: у логах сервера дія пройшла, а в браузері «помилка». Звідси і практичний висновок: CORS-збій — майже завжди питання конфігурації сервера чи проксі, а не бага в тесті чи фронтенді.
Запит повернув 200, але функціонал не працює. Як розслідувати?
Це найпідступніший випадок: мережа ок, а результат ні, — тому перевіряю три речі по черзі. Перше — Response/Preview: сервер міг повернути 200 з тілом-помилкою на кшталт {"error": "..."}, порожнім тілом або не тим форматом, і формально успішний статус нічого не гарантує. Друге — Console: дуже частий сценарій, коли fetch повернув не той формат, JS упав з Uncaught TypeError при обробці відповіді, і сторінка «застигла» — при цьому в Network запит зелений. Третє — колонка Size: якщо там (from cache), ви дивитесь на старі дані, а не на свіжу відповідь. Для автотестів звідси прямий висновок: асертити тільки статус-код недостатньо — перевіряйте і тіло відповіді, і стан сторінки, а помилки консолі корисно збирати програмно й фейлити тест на несподіваних error-логах.
Як спостереження з вкладки Network переносяться в автотести?
Network — джерело правди, проти якого звіряють поведінку тестів, і кожна концепція з ручної діагностики має продовження в автоматизації. Очікування замість пауз: побачили, який запит завершує дію, — чекайте в тесті саме на нього (waitForResponse за URL і статусом у Playwright), а не фіксовану паузу; це прибирає найпоширеніший вид флаку. Перехоплення мережі (network interception): те, що в DevTools лише спостерігаєте, у тесті можна підмінити — віддати мок-відповідь, примусово повернути 500 для перевірки обробки помилок, затримати запит для перевірки спінера чи заблокувати сторонні скрипти; у Playwright це route/fulfill/abort. Ізоляція стану: кукі та Storage з вкладки Application між тестами обнуляють — свіжий контекст на тест означає, що не буде «падає лише в наборі». Асерти по контракту: тіло з Response — еталон, тест перевіряє не тільки DOM, а й що API повернув потрібні поля з потрібними значеннями. І нарешті кеш: розуміння різниці між живим 200, 200 from cache і 304 рятує від хибних баг-репортів про «зламане збереження».
Три кейси, де Network перетворює «щось не працює» на конкретне твердження: ручний розбір «червоної» реєстрації через Status → Payload → Response, переписування флакі-тесту з паузи на waitForResponse за реальною відповіддю, і перехоплення мережі, щоб примусово повернути 500 і перевірити обробку помилки. Скрізь — що дивитися і чому.
Кейс 1. «Не зберігається»: читаємо червоний запит згори вниз
Користувач тисне «Зберегти», нічого не відбувається, на фронтенді жодної помилки. Відкриваємо Network (заздалегідь, до дії), фільтр Fetch/XHR, відтворюємо клік. У списку — червоний рядок. Перше, на що дивимось, — колонка Status:
POST /api/v1/register HTTP/1.1
Host: app.example.com
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJI...
HTTP/1.1 422 Unprocessable Content
Content-Type: application/json; charset=utf-8
Далі відкриваємо вкладку Payload — що саме браузер відправив:
{
"email": "user@example.com",
"password": "hunter2",
"name": ""
}
І вкладку Response — що відповів сервер:
{
"errors": {
"name": ["can't be blank"],
"email": ["has already been taken"]
}
}
Що дивитися і чому:
- Status
422одразу ділить розслідування.4xx— «винен» той, хто питав: тіло синтаксично коректне, тип зрозумілий, але не проходить семантичну валідацію. Це не500— бекенд відпрацював штатно й ще й пояснив, що не так. Йти в серверні логи не треба. - Payload — доказ бага на фронтенді.
"name": ""летить порожнім, хоча в полі щось введено. Класика: стан не встиг оновитися до сабміту. Форма «начебто заповнена», а на сервер пішло старе/порожнє значення — це фронтенд, не бекенд. - Response — це список полів, що не пройшли валідацію.
email has already been takenпоказує, що навіть з правильнимnameреєстрація впаде: email зайнятий. У багатьох фреймворках (зокрема Rails) саме тіло422несе повідомлення про помилки форми. Authorizationприсутній — тож401виключено. Якби токена не було, ми б бачили401, а не422, і копали б у бік сесії. Підсумковий баг-репорт: «POST /api/v1/register→422, у payloadnameпорожній, у responsehas already been taken,Authorizationприсутній».
Кейс 2. Флакі-тест: пауза → очікування на реальну відповідь
Тест «клікнув і одразу перевірив» то зелений, то червоний без змін у коді. У Network видно чому: асерт виконується, поки POST ще у фазі Waiting (TTFB) — відповідь не прийшла, DOM не оновився. Типова, але хибна «латка» — поспати фіксовану секунду:
// так робити не треба: пауза або флакне на повільній мережі, або гальмує на швидкій
await page.getByRole('button', { name: 'Створити' }).click();
await page.waitForTimeout(1000);
await expect(page.getByText('Замовлення створено')).toBeVisible();
Прив'язуємо очікування до тієї самої відповіді, яку видно в Network:
import { test, expect } from '@playwright/test';
test('замовлення створюється', async ({ page }) => {
await page.goto('https://app.example.com/orders');
// чекаємо саме на завершальний запит дії — за URL і статусом
const responsePromise = page.waitForResponse(
(res) => res.url().includes('/api/v1/orders') && res.request().method() === 'POST',
);
await page.getByRole('button', { name: 'Створити' }).click();
const response = await responsePromise;
// асерт по контракту: не лише DOM, а й що API реально повернув
expect(response.status()).toBe(201);
const body = await response.json();
expect(body.status).toBe('created');
await expect(page.getByText('Замовлення створено')).toBeVisible();
});
Що дивитися і чому:
waitForResponseреєструють ДО кліку. Проміс створюють перед дією, що ініціює запит, — інакше можна проґавити відповідь, яка прийшла швидше, ніж рядок з очікуванням. Клік — між створенням проміса іawait.- Прив'язка до події, яку видно в Network, а не до годинника.
waitForTimeout(1000)— це ставка: «сервер устигне за секунду». На Slow 4G не встигне (червоний), на швидкому — секунда простою на кожен тест. Очікування конкретної відповіді знімає обидві проблеми. - Асерт по контракту ловить те, що DOM ховає. Сервер міг повернути
200з тілом-помилкою або не тим форматом, а UI показати старий кеш. Перевіркаstatus()і полів тіла звіряє результат із джерелом правди — самою відповіддю, як у вкладці Response. - Це той самий флак, що ловить throttling. Якщо тест падає на Slow 4G, він не «флакне на швидкому» — просто ще не впав: очікування недостатньо прив'язане до події.
Кейс 3. Перехоплення мережі: примусовий 500 і спіймана помилка консолі
У DevTools мережу лише спостерігають; у тесті її можна підмінити. Перевірити, чи застосунок коректно показує помилку сервера, без реального падіння бекенду — перехопити запит і повернути 500. Заразом слухаємо консоль, щоб спіймати JS-помилки в обробнику відповіді:
import { test, expect } from '@playwright/test';
test('на 500 показуємо повідомлення, а не білий екран', async ({ page }) => {
// збираємо помилки консолі протягом тесту
const consoleErrors: string[] = [];
page.on('console', (msg) => {
if (msg.type() === 'error') consoleErrors.push(msg.text());
});
// примусово повертаємо 500 на цільовий ендпоінт
await page.route('**/api/v1/orders', (route) => {
route.fulfill({
status: 500,
contentType: 'application/json',
body: JSON.stringify({ error: 'internal' }),
});
});
await page.goto('https://app.example.com/orders');
await page.getByRole('button', { name: 'Створити' }).click();
// застосунок має показати зрозумілу помилку, а не «мовчки зламатись»
await expect(page.getByRole('alert')).toContainText('Спробуйте пізніше');
// і не впасти в консоль по дорозі
expect(consoleErrors, `несподівані помилки консолі: ${consoleErrors.join('; ')}`).toEqual([]);
});
Що дивитися і чому:
route/fulfill— це «підмінити», а не «підглянути». DevTools показує, щоPOSTповернув500; тест може цей500згенерувати сам і перевірити, що UI на нього реагує спінером-стопом, тостом, повідомленням — а не застигає. Так самоabortімітує обірваний запит, а затримка уfulfill— перевірку спінера.- Мок ізолює фронтенд від бекенду. Тесту не потрібен реальний зламаний сервер: перевіряємо саме обробку помилки на клієнті, детерміновано й швидко.
- Слухати консоль програмно ловить приховані регресії. Дуже частий сценарій: запит зелений
200, а JS упав на обробці відповіді (Uncaught TypeError, код прочитав неіснуюче поле). Візуально «наче працює», аpage.on('console')зафіксуєerror-лог і зафейлить тест. - Асерт саме на
role="alert", а не «зникла кнопка». Перевіряємо наявність зрозумілого повідомлення про помилку — це і є критерій «не зламалось мовчки» з профілю Offline та5xx-сценаріїв.
Network: захоплення й колонки
- Розумію, чому DevTools пишуть мережу лише поки відкриті, тож першим ділом відкриваю Network і перезавантажую сторінку, щоб побачити повний цикл із першого запиту.
- Знаю, навіщо
Preserve log: без нього редірект після логіну зітре журнал, а з ним запити зберігаються крізь навігації — must-have для логінів, редіректів і багатокрокових форм. - Можу пояснити, що дає кожна ключова колонка (
Name,Status,Type,Initiator,Size,Time,Waterfall) і чомуStatus— перше, на що дивишся при збої. - Розумію, що колонка
Sizeпоказує два числа (передано мережею vs розпакований розмір), а(from disk/memory cache)означає, що мережею не йшло нічого.
Фільтри та деталі запиту
- Знаю, що
Fetch/XHRлишає виклики до API (тут майже вся робота з бекендом), і що в сучасному Chrome окремих кнопок XHR і Fetch уже немає — вони обʼєднані, а для діагностики різниця несуттєва. - Можу користуватися текстовим фільтром з операторами (
status-code:500,method:POST,domain:api.example.com, інверсія через-) замість прокручування всього списку. - Розумію структуру панелі
Headers(General/Response Headers/Request Headers) і що означають найчастіші заголовки:Content-Type,Authorization,Cookie/Set-Cookie,Cache-Control,ETag,Location,Access-Control-Allow-Origin. - Знаю, де дивитися відправлене тіло (
Payload, а дляGET—Query String Parameters) і навіщо звірятиResponse(сирий текст) vsPreview(відрендерений) як джерело правди для API-перевірок.
Статуси, кеш і тайминг
- Розумію поділ:
4xx— «винен» той, хто питав (веде до фронтенду й даних запиту),5xx— «винен» сервер (веде до серверних логів). - Можу розрізнити коди, які плутають:
401(не автентифікований, немає/невалідний токен) vs403(автентифікований, але немає прав),422(тіло валідне, але не проходить семантичну валідацію) і304(не помилка, кеш підтверджено). - Можу читати
Waterfall/Timingпо фазах і розумію, що великийTTFBвказує на бекенд, а великийContent Download— на важку відповідь чи вузький канал. - Розрізняю живий
200,200 (from cache)і304 Not Modified(мережа була, тіло не передавалось) — і розумію, чому кеш породжує хибний діагноз «баг збереження». - Знаю, що
Disable cacheпрацює лише поки DevTools відкриті, тож стабільні автотести на нього не покладаються — беруть чистий профіль/контекст.
Стан, консоль і CORS
- Знаю різницю сховищ у
Application(Local Storage,Session Storage,Cookies,IndexedDB,Cache Storage) і чому залишений між тестами стан дає «тест проходить сам, але падає в наборі». - Розумію, навіщо
throttling(спінери, подвійне надсилання, Offline) і що це чесний спосіб знайти race condition до того, як його знайде CI. - Можу читати
Consoleу парі з Network:200у мережі, але червонийTypeErrorу консолі означає баг в обробці відповіді, а не в запиті; там же лежать деталі CORS, Mixed Content і CSP. - Можу пояснити CORS і preflight (
OPTIONS): чому «непрості» запити питають дозволу, чому безAccess-Control-Allow-*браузер блокує відповідь, і чому дія могла пройти на сервері, а в браузері — «помилка». - Знаю алгоритм діагностики червоного запиту: подивитися Status → якщо статусу немає (
failed/blocked/pending), шукати причину в Console → якщо200, але не працює, звірити Response/Preview, Console і кеш.
Мережа й автотести
- Розумію, чому очікування треба привʼязувати до реальної події з Network (
waitForResponse), а не до фіксованої паузи — це прибирає найпоширеніший флак. - Можу пояснити перехоплення мережі (
route/fulfill/abort): мок відповіді, примусовий500, затримка запиту, блокування сторонніх скриптів — і навіщо кожне з них у тестах.
Кнопка «Зберегти» «нічого не робить». Що першим підкаже вкладка Network про причину?
Питання
Чому DevTools треба відкрити ДО того, як відтворювати проблему?