DevTools: вкладка Network і дебаг
Зміст
«Не працює» — найдорожче речення в баг-репорті: воно нічого не каже ні розробнику, ні тобі самому через тиждень. Вкладка Network в інструментах розробника (DevTools) — вікно між тим, що видно на екрані, і тим, що насправді літає між клієнтом і сервером. Кожен її рядок — один мережевий запит сторінки: HTML, картинки, скрипти, шрифти й фонові звернення до API. Вона перетворює «щось зламалося» на твердження, з яким можна працювати: «POST /api/orders повернув 422, у тілі — email already taken, Authorization присутній, кеш вимкнено».
Ця глава — канонічний виклад теми на сайті: інші глави посилаються сюди по механіку панелі. Назви кнопок, колонок і прапорців далі — за довідником Chrome DevTools. Механіка Playwright і trace viewer — предмет розділу про інструменти; тут лише те, що видно у браузері.
Наша практика (не канон). Оцінка «прилад діагностики номер один для AQA» і сам зразок формулювання з попереднього абзацу — наші: довідник DevTools таких тез не містить, він описує інтерфейс.
Рядок запиту, колонки й фільтри
Правило, яке нічого не коштує і рятує півгодини: панель відкривають до того, як відтворювати проблему. DevTools пишуть запити автоматично, але лише поки відкриті. Preserve log зберігає записи крізь переходи між сторінками.
Наша практика (не канон). Найдорожчий випадок, на якому це видно, джерело не називає — його додаємо ми: без Preserve log редірект після логіну зітре саме те, що ви хотіли роздивитися, тож для сценаріїв із входом, перенаправленнями й багатокроковими формами прапорець вмикають наперед.
Типовий набір колонок — Name, Status, Type, Initiator, Size, Time і Waterfall. Status показує або код HTTP, або окреме значення CORS error для запитів, що впали через ; Type — MIME-тип ресурсу; Size — заголовки плюс тіло, як їх віддав сервер; Time — повну тривалість до останнього байта; Waterfall — розкладку активності запиту в часі. Решту колонок вмикають правою кнопкою по шапці таблиці, і дві з них закривають половину «складних» питань: Protocol (h2 для HTTP/2, h3 для HTTP/3) і Connection ID — ідентифікатор з'єднання, у якому пішов запит. Через Response Headers і пункт Manage Header Columns додається власна колонка під конкретний заголовок відповіді.
Фільтри дворівневі: кнопки типів ресурсу (All, Fetch/XHR, JS, CSS, Img, Doc, WS та інші) і текстове поле з властивостями — status-code:500, method:POST, domain:api.example.com. Дві деталі, на яких плутаються майже всі: інверсію дає окремий прапорець Invert, а не мінус на початку; кілька властивостей з'єднуються лише через AND.
Обраний запит розкладається по вкладках Headers (блоки General, Response Headers, Request Headers), Payload, Response і Preview. Журнал експортується у HAR — JSON-запис сесії; типовий експорт санітизований, без Cookie, Set-Cookie й Authorization, тому його безпечно чіпляти до тікета. HAR можна завантажити й назад у DevTools і розбирати чужу сесію як власну.
У Firefox це не ті самі колонки з іншими підписами — це інший спосіб показу. Різниця дорога саме в діагностиці збоїв: там, де Chrome пише текстом ((failed), CORS error, (blocked:origin)), Firefox лишає Status порожнім, якщо відповіді не було, або малює для заблокованих «red circle with a diagonal slash» — тобто підказка графічна, і той, хто шукає слово «failed», не знайде нічого. Причина блокування живе в іншій колонці, Transferred, і словами: «CSP», «Malware», «CORS Missing Allow Origin», «Blocked by [Name of Extension]» — останній навіть називає розширення поіменно, чого Chrome у своєму ERR_BLOCKED_BY_CLIENT не робить. Назви теж свої: Chrome Name — Firefox File, Type описаний прямо як «Content-type of the response», колонки Transferred у Chrome немає взагалі, а перемикач збереження журналу зветься «Enable persistent logs» у Settings, а не Preserve log на панелі. Плюс два сигнали без аналога в Chrome: іконка черепахи при перевищенні порогу очікування сервера (типово 500 мс, ключ about:config — devtools.netmonitor.audits.slow) і трикутник біля домену при слабких шифрах.
Про Safari тут навмисно не сказано нічого: довідки Web Inspector у нашому реєстрі джерел немає, а переносити на неї спостереження про Firefox — це вигадувати, а не описувати.
Життєвий цикл запиту як рамка читання логу
Колонки відповідають на питання «що сталося», вкладка Timing — «де згорів час».
Найважливіша фаза — Waiting (TTFB): у неї входить один плюс час на підготовку відповіді. Довга Waiting — це бекенд, а не «повільний інтернет». Тут же пастка з абревіатурою: під живуть два різні виміри. Навігаційний TTFB рахують від запиту сторінки до першого байта, і він включає DNS та встановлення з'єднання; фаза Waiting (TTFB) починається вже після того, як з'єднання встановлено.
Стан pending означає, що запит ініційовано, а повної відповіді ще немає: або сервер думає, або запит стоїть у черзі через ліміт одночасних з'єднань до . Про події документа: DOMContentLoaded каже, що HTML розпарсено й DOM побудовано, load — що завантажено початкові ресурси з картинками й стилями, і жодна не засвідчує подальших запитів, які застосунок часто починає вже після load. Повний розбір — глава «Життєвий цикл запиту».
Заголовки й тіло: що читати першим
HTTP-повідомлення влаштоване однаково в запиті й відповіді: стартовий рядок, заголовки, порожній рядок як межа метаданих і необов'язкове тіло. Заголовки (headers) передаються перед вмістом; усе після порожнього рядка і є тіло (body). Мінімум, який читають першим: Content-Type описує медіа-тип тіла, Content-Length — скільки байтів читати, Accept — побажання клієнта щодо формату, Authorization — облікові дані. Помилковий Content-Type при ручному формуванні тіла — класична причина , і канонічний код тут 415, а не 400.
Вкладка Payload показує тіло запиту — параметри рядка запиту й дані форми. Тіло в GET синтаксис не забороняє, але воно не має загальновизначеного значення й генерувати його клієнту не рекомендовано, тож параметри летять у самому URL (глава «URL і кодування»). Завантаження файлів видно як multipart/form-data: тіло розбите на частини з власними міні-заголовками, а розділяє їх рядок-роздільник, оголошений параметром boundary в Content-Type. У HTTP/2 повідомлення загорнуті в бінарні кадри, але логічна модель «метод або статус плюс заголовки плюс тіло» лишається — саме її показує панель. Канон — глава «HTTP: методи, структура, заголовки».
Статус-коди й редиректи в ланцюжку
Колонка Status — перше, куди дивляться. Перша цифра задає клас: 1xx інформаційні, 2xx успіх, 3xx перенаправлення, 4xx помилка клієнта, 5xx помилка сервера. поруч із кодом не можна: RFC називає її необов'язковою й велить клієнту ігнорувати, а HTTP/3 її взагалі не передає.
Пари, які плутають найчастіше: 400 — «я не зрозумів, що ти написав», 422 — «я зрозумів, але так не можна»; 401 — «не автентифікований», 403 — «автентифікований, але прав немає»; 409 — конфлікт із поточним станом; 429 — перевищено , у відповіді може бути Retry-After. У класі 5xx розрізняють 502 (некоректна відповідь від апстріму), 503 (сервер тимчасово не готовий) і 504 ( не дочекався).
Редирект (redirect) ініціює сервер: код на 3 і заголовок Location. Постійні 301 і 308 кажуть, що стару адресу більше не слід використовувати; при тимчасових 302, 303, 307 канонічною лишається початкова. Практична різниця: 301 і 302 гарантують незмінність методу лише для GET — інший метод клієнт може підмінити на GET і втратити тіло; 307 і 308 зберігають і метод, і тіло; 303 навмисно міняє метод на GET.
Щоб ланцюг пережив перехід між сторінками, у панелі потрібен увімкнений Preserve log — і це ж пояснює, чому редирект легко проґавити: клієнти йдуть за перенаправленнями автоматично, і ви бачите фінальний 200, не помітивши зламану ланку (виняток — curl, який без -L за редиректом не піде). Окремо: 304 Not Modified формально теж 3xx, але це не перенаправлення — сервер підтверджує, що збережена копія валідна, і не передає тіло. Канон — глава «HTTP статус-коди».
Звідки взялася стара відповідь: кеш і Service Worker
«Виправили, задеплоїли, а на екрані старе» — класика з чотирма домівками: HTTP-кеш браузера, кеш Service Worker, CDN чи проміжний й кеш усередині самого застосунку.
Прапорець Disable cache відтворює досвід відвідувача, який заходить уперше; область його дії — панель, а щоб кеш лишався вимкненим в інших панелях DevTools, є панель Network conditions. Далі читають заголовки: поки вік відповіді не перевищив термін свіжості, вона свіжа й мережею не йде взагалі; після цього кеш надсилає умовний запит із валідатором (ETag у If-None-Match, Last-Modified у If-Modified-Since), і 304 означає, що копію можна перевикористати без передачі тіла. Пастка з назвами директив: no-cache — не «не кешувати», а «не віддавати без валідації»; зберігання забороняє no-store.
Service Worker — окремий і найупертіший рівень: подієвий , зареєстрований на конкретне походження і шлях, працює як проксі між застосунком, браузером і мережею й вирішує, віддати відповідь зі свого сховища Cache API чи піти в мережу. Cache API з HTTP-кешем не пов'язаний, тож правильні заголовки на бекенді на нього не впливають; доступний він лише в захищеному контексті — по HTTPS, з винятком http://localhost. «Примару після деплою» пояснюють дві деталі: за наявного активного воркера нова версія лишається в стані очікування, а під час активації події fetch ставляться в чергу. Канон — глава «Кешування».
Скільки з'єднань і яка версія протоколу
З'єднання HTTP/1.x серіалізує запити: наступний іде після відповіді на попередній. Щоб це обійти, браузери відкривають до кожного домену кілька паралельних з'єднань (parallel connections) — нинішнє поширене значення шість на домен. Шість — поточна практика браузерів, а не норма протоколу; у RFC такого числа немає. На HTTP/2 картина протилежна: специфікація каже клієнту не відкривати більш ніж одне з'єднання на пару «хост плюс порт», (pipelining) у сучасних браузерах вимкнений, а домен- MDN позначає як застарілий. Скільки з'єднань браузер підняв насправді, не вгадують — вмикають колонку Connection ID.
Версію протоколу теж не «вмикають»: про неї домовляються на старті. Стандарт HTTP/2 не вимагає TLS, але браузери вимагають — Firefox і Chrome заявили, що піднімають його лише поверх TLS, а умова з боку сервера — вміння узгоджувати протокол через ALPN усередині . Практичний наслідок: локальне середовище на http:// завжди піде по HTTP/1.1, і це не дефект. Колонка Protocol показує картину порядково: h2, h3 і http/1.1 можуть співіснувати на одній сторінці. Ще одна межа: керування з'єднанням діє між двома сусідніми вузлами, а не наскрізно, тож проміжний проксі може «зняти» версію. Канон — глава «HTTP/2 і HTTP/3: еволюція протоколу».
Затримка, пропускна здатність і тротлінг
(latency) — час, за який дані долають шлях від джерела до призначення; міряють її зазвичай як RTT. (bandwidth) — скільки інформації проходить крізь з'єднання за одиницю часу. Вони незалежні: у TCP існує добуток «пропускна здатність × затримка», тобто канал буває водночас «товстим» і «довгим».
У панелі це різні місця. Waiting — про сервер і один RTT; фаза завантаження вмісту визначається пропускною здатністю й розміром файла; колонка Time — повна тривалість до останнього байта. Перший запит до нового хоста дорожчий: у нього входять DNS, TCP-рукостискання й узгодження TLS. Звідси напрямок оптимізацій — менше RTT: з'єднань, мультиплексування HTTP/2, , CDN; HTTP/3 поверх QUIC ще й поєднує встановлення транспорту з узгодженням TLS.
(throttling) — штучне обмеження швидкості: меню Throttling емулює швидкість завантаження, швидкість віддачі й мінімальну затримку. Поточні пресети — fast 4G, slow 4G і 3G, поруч режим Offline і прапорець Disable cache. Набір пресетів між версіями Chrome змінювався, тому для відтворюваних умов надійніше задавати власний профіль із фіксованими числами. Величини — глава «DNS, IP, порти та мережа».
Наша практика (не канон). Роль тротлінгу для AQA джерела не описують — це наш кут. Для нас це не «перевірка продуктивності», а інструмент відтворення: він оголює стани завантаження (чи є спінер і скелетон, чи не «моргає» порожній екран) і витягує назовні гонки, яких на швидкому каналі не видно — класика тут кнопка, що лишається активною, поки запит іще летить, і дає подвійне надсилання. Звідси й формулювання, яке ми вживаємо як робоче: якщо тест падає на повільному профілі, він не «зафлакне колись на швидкому» — він просто ще не впав. І перед тим як списати падіння на «повільний CI», відтворюємо його локально на повільному профілі.
AJAX і fetch: чому запиту немає в логу навігації
AJAX — обмін даними із сервером у фоні, без повного перезавантаження сторінки. Технічно це старий XMLHttpRequest або сучасний Fetch API, і головний висновок для тестування такий: «сторінка відкрилась» ще не означає «дані на місці» — контент приїде окремим запитом після навігації, і в панелі він живе під фільтром Fetch/XHR, а не серед документів.
Три речі видно саме тут і майже ніколи — у DOM. Перша: fetch() не реджектиться на 404 чи 500 — успішно резолвиться об'єктом Response, тож статус перевіряють вручну, і response.ok істинний лише для діапазону 2xx. Друга: тіла запиту й відповіді — потоки, тож одне тіло не можна прочитати двічі. Третя: за замовчуванням fetch не шле облікових даних — режим credentials типово same-origin.
У XMLHttpRequest корисно знати стани readyState: HEADERS_RECEIVED настає лише після того, як пройдено всі редиректи, у LOADING тіло ще тече, а DONE означає «операція завершилась» — успішно або з помилкою. Тобто readyState === 4 сам по собі про успіх не свідчить. Контекст — глава «Асинхронне завантаження: SPA/MPA, CSR/SSR».
Кукі й сховища браузера у зв'язці з запитом
Кукі (cookie) видає сервер заголовком Set-Cookie, а браузер повертає їх заголовком Cookie. Обмін асиметричний: сервер надсилає значення разом з атрибутами, а назад їдуть лише пари «ім'я = значення». Тому в Request Headers не видно ні Secure, ні HttpOnly, ні строку життя — атрибути дивляться в Set-Cookie у відповіді. HttpOnly означає, що кукі не видно скриптовим API (document.cookie), Secure — що вона не піде відкритим каналом. Фільтр панелі звужує пошук властивостями cookie-name, cookie-domain і response-header-set-cookie.
Сховище браузера (Web Storage) поводиться протилежно: його вміст не їде на сервер ніколи автоматично. Якщо токен із localStorage має дістатися бекенду, застосунок мусить явно покласти його в заголовок або тіло — звідси проста вилка: немає Authorization у запиті, значить, шукайте баг у клієнтському коді. localStorage розділений лише за походженням, sessionStorage — ще й за вкладками, тож нова вкладка його не успадковує. Канон — глава «Кукі, сесії та сховище браузера».
CORS і CSP: «запит пішов, а даних немає»
Найпідступніший клас симптомів: рядок у панелі є, статус 200, тіло на місці — а в застосунку порожньо. Майже завжди це браузерна політика, а не бекенд.
CORS перевіряє браузер. Postman, curl і HTTP-клієнт у бекенд-коді політики одного походження не реалізують і заголовки Access-Control-* ігнорують — тому «у Postman працює» контраргументом не є. Для запитів, складніших за ті, що дозволяє HTML-форма, спершу летить preflight — окремий запит методом OPTIONS із заявкою Access-Control-Request-Method і Access-Control-Request-Headers. У панелі це два рядки на один логічний запит: спершу дозвіл, потім самі дані — і Content-Type: application/json до безпечного переліку не належить, тому JSON-запит preflight викликає. Якщо OPTIONS дозволу не дав, справжній запит не піде взагалі. Друга типова картина — відповідь прийшла, а скрипт її не читає: за замовчуванням йому видно лише сім «безпечних» заголовків, решту сервер має перелічити в Access-Control-Expose-Headers. І окремо: заблокований CORS не означає, що дані не змінилися — простий запит сервер виконує повністю.
CSP (Content Security Policy) працює на іншому рівні: сервер віддає заголовок Content-Security-Policy, і браузер за ним обмежує, звідки можна вантажити й виконувати вміст. Порушення видно не в Network, а в — тому «скрипт не виконався, а в мережі все зелено» так часто ставить у глухий кут. Content-Security-Policy-Report-Only дає ту саму політику в режимі спостереження. Автотестам сувора політика теж заважає: допоміжного скрипта чи inline-обробника буде заблокована. Канони — глави «CORS і політика одного походження» і «HTTPS, TLS і безпека».
Перехоплення й підміна відповіді: що з цього видно у вкладці
Механіка й таксономія дублерів — предмет глави «Перехоплення й мокання мережі»; тут лише те, що з цього видно в панелі.
Обробник маршруту має три рішення: пропустити запит далі, підмінити відповідь або обірвати його. Перехоплення вибіркове — те, що не підійшло під шаблон, іде на реальний сервер, тож у панелі буде суміш справжніх і рукотворних відповідей. Обрив моделює недоступність сервісу, і код помилки береться із закритого переліку: 'connectionrefused', 'namenotresolved', 'internetdisconnected' — це різні класи збою, а не синоніми.
// Підміняємо відповідь і перевіряємо, що UI показує саме стан помилки
await page.route('**/api/orders', (route) =>
route.fulfill({ status: 500, body: '{"error":"boom"}' })
);
Другий місток між панеллю й тестом — HAR: DevTools експортують сесію й уміють імпортувати її назад, а той самий запис може стати джерелом підмінених відповідей. Плата чесна: HAR-записи старіють, і тест лишається зеленим на даних, яких бекенд уже не віддає.
WebSocket у вкладці
Реалтайм шукають фільтром WS. З'єднання відкривається звичайним GET із заголовками Upgrade: websocket, Connection: Upgrade і Sec-WebSocket-Key, а успішний перехід підтверджує лише статус 101 Switching Protocols разом із Sec-WebSocket-Accept. Будь-який інший статус означає, що рукостискання не відбулося й далі діє звичайна семантика HTTP — тобто «сокет не працює» найчастіше видно як цілком звичайний код у першому ж рядку.
Після рукостискання це самостійний протокол поверх TCP: усередині запису видно кожен кадр із напрямком, вмістом і часом. Схем дві — ws і wss; у проді вживають лише wss. Кореляції «запит — відповідь» тут немає, тому чекають на подію-наслідок, а не на фіксовану паузу. З'єднання живе в контексті виконання JavaScript вкладки, тож перезавантаження сторінки знищує його разом зі станом підписок. Канон — глава «WebSockets і реалтайм».
Режим пристрою й Lighthouse: межі того, що вони міряють
Режим пристрою (device mode) симулює мобільну , обмеження ЦП і мережі, а через панель Sensors — геолокацію й орієнтацію. Джерело називає межу прямо: це наближення першого порядку, а не запуск на пристрої, бо код виконується на вашому комп'ютері. Наслідків два. Обмеження ЦП відносне до потужності вашої машини, тому його числа не переносяться між комп'ютерами й не годяться в . Тип пристрою впливає не лише на вигляд: від нього залежить, які події породжує взаємодія — сенсорні чи кліки. Тому «на мобільному не працює свайп» перевіряють перемиканням типу, а шукають смугами медіазапитів.
Lighthouse запускають трьома способами: у DevTools, із командного рядка або як Node-модуль. Оцінка доступності — зважене середнє перевірок доступності, кожна з яких дає pass або fail без часткового заліку, а ручні й низьковпливові перевірки в число не входять узагалі. Звідси головне: 100 балів не означає «доступно» — читають перелік проваленого, а не бал. будують на Node-варіанті або Lighthouse CI. Метрики клієнтської продуктивності — предмет розділу про продуктивність. Далі — глави «Кросбраузерність і адаптивна верстка» і «Семантичний HTML і доступність».
Гонка станів: чому «то зелений, то червоний»
Гонка станів (race condition) — залежність результату від порядку завершення асинхронних операцій там, де гарантій щодо цього порядку немає. У браузері кожне завдання обробляється повністю, перш ніж почнеться наступне, тому гонка виникає не всередині завдання, а між ними: два запити пішли, і хто відповість першим — питання мережі, а не коду.
Панель Network — найкоротший шлях до діагнозу: ви бачите, який запит завершує дію, і чекаєте в тесті на нього. (hard wait) гонку не усуває, а маскує — воно не перевіряє стан, а сподівається, що за цей час усе встигне. Правильна альтернатива — очікування за умовою, де є стелею терпіння, а не запланованим простоєм. У коді застосунку ту саму гонку лікують скасуванням застарілого запиту через AbortController.
// Чекаємо на конкретну відповідь, а не на «поки що-небудь станеться»
await Promise.all([
page.waitForResponse((r) => r.url().includes('/api/orders') && r.status() === 200),
page.getByRole('button', { name: 'Створити' }).click(),
]);
інструмента гасить частину гонок, але умови на кшталт «дані в таблиці оновилися» треба виражати явними очікуваннями стану. Далі — глави «Виконання JavaScript та event loop» і «Практичні сценарії AQA: флак і синхронізація».
Алгоритм розбору «червоного» запиту
Усе попереднє складається в одну послідовність, якою читають будь-яке «не працює». Ідуть згори вниз і зупиняються на першому збігу.
Крок 0. Відтворити при відкритій панелі. Журнал пишеться, лише поки DevTools відкриті, тож панель відкривають до дії; за наявності переходів вмикають Preserve log, за підозри на кеш — Disable cache. Далі звужують список: кнопка Fetch/XHR для фонових викликів, потім властивість фільтра на кшталт status-code:500 чи method:POST.
Крок 1. Прочитати Status. Клас коду ділить розслідування навпіл: 4xx — помилка клієнта, 5xx — помилка сервера. Далі за конкретним кодом: 401 — не автентифікований, дивимось Authorization і кукі сесії; 403 — автентифікований, але прав немає; 404 — не той URL; 400 і 422 — погані дані, і подробиці лежать у Payload та Response; 429 — уперлися в рейт-ліміт, і у відповіді може бути Retry-After. У класі 5xx фронтенд ні до чого: фіксують запит і йдуть до серверних логів, розрізняючи 502 (некоректна відповідь від апстріму), 503 (сервер тимчасово не готовий) і 504 (шлюз не дочекався).
Крок 2. Коду немає взагалі. Значить, до логіки сервера не дійшло. Колонка Status має для цього окреме значення — CORS error; тоді читають Access-Control-* у відповіді й перевіряють, чи не зупинився логічний запит на preflight, бо без дозволу від OPTIONS справжній запит не піде. Якщо ж у мережі зелено, а вміст усе одно не зʼявився, дивляться консоль: порушення CSP видно саме там.
Крок 3. 200, але «не працює». Найпідступніший випадок. Відкривають Response і Preview — сервер міг віддати 200 з тілом-помилкою або не тим форматом; перевіряють консоль — скрипт міг упасти вже на обробці відповіді; дивляться Size — якщо тіло взяте з кешу, ви читаєте старі дані, а не свіжу відповідь.
Наша практика (не канон). Сам порядок кроків і поділ «застосунок / дані / політика браузера» — наша методика: довідник DevTools алгоритмів діагностики не пропонує, він описує інтерфейс. Сюди ж належить читання колонки Size на кроці 3: позначки (from memory cache) і (from disk cache), які означають, що мережею не передавалося нічого, у довіднику не описані — ми спираємося на власну практику. Дзеркальний випадок, який із цим плутають, джерельний: 304 Not Modified — це живий запит, у якому сервер підтвердив валідність збереженої копії й не передав тіло.
Типові помилки
- Виглядає як баг бекенду, а насправді — порожній журнал. DevTools пишуть запити, лише поки відкриті.
- Виглядає як «редиректу не було», а насправді — його стерла навігація. Без
Preserve logвидно лише фінальний200. - Виглядає як «у Postman працює, отже баг у фронті», а насправді — CORS. Політику застосовує браузер.
- Виглядає як «сервер не відповів», а насправді — preflight. Один логічний запит дає два рядки, і якщо
OPTIONSдозволу не дав, справжній запит не піде зовсім. - Виглядає як «скрипт не завантажився», а насправді — CSP. У Network зелено, бо порушення видно в консолі.
- Виглядає як «HTTP/2 зламався», а насправді — локальний
http://. У браузері HTTP/2 піднімається лише поверх TLS з ALPN. - Виглядає як «повільний бекенд», а насправді — розмір тіла. Довга
Waiting— це сервер, довге завантаження — пропускна здатність і мегабайти. - Виглядає як «кеш винен», а насправді — Service Worker. Cache API з HTTP-кешем не пов'язаний.
- Виглядає як , а насправді — незв'язане очікування. Тест, що падає на повільному профілі, уже некоректний.
Підсумок
- Рядок у Network — це факт, а не здогад. Статус, заголовки, тіло й таймінг перетворюють «не працює» на перевірюване твердження.
- Половина відповідей ховається за вимкненими колонками й прапорцями:
Protocol,Connection ID,Preserve log,Disable cache,Invert. - Фаза важливіша за загальний час.
Waitingвказує на сервер, завантаження вмісту — на пропускну здатність і розмір, черга — на ліміт з'єднань. - Запит без даних у застосунку — майже завжди браузерна політика. CORS і preflight пояснюють це в Network, CSP — у консолі.
- Тротлінг і Lighthouse нічого не «вимірюють остаточно». Перший — інструмент відтворення гонок, другий — зважений бал із ручними перевірками поза підрахунком.
Можливі питання
- «Покажи, як ти локалізуєш баг „кнопка не працює“». Дивляться на порядок дій: відкрити панель до відтворення, знайти запит, прочитати статус і тіло, відрізнити «запиту не було взагалі» від «запит пішов і повернув помилку».
- «Що таке
Waiting (TTFB)і чим воно відрізняється від TTFB у метриках?» Перевіряють, чи розумієте, що під однією абревіатурою живуть два виміри. - «У Postman запит проходить, у браузері — ні. Чому?» Питання про те, хто застосовує CORS; сильна відповідь називає preflight і те, що простий запит сервер усе одно виконує.
- «Навіщо вмикати колонку
Protocol?» Очікуютьh2,h3,http/1.1, розуміння ALPN і те, що локальнийhttp://по HTTP/2 не піде за побудовою. - «Скільки з'єднань браузер відкриває до одного домену?» Перевіряють акуратність формулювань: близько шести на HTTP/1.1 як практика браузерів, а не норма RFC, і одне з'єднання на HTTP/2.
- «Тест іноді зелений, іноді червоний. Ваші дії?» Чекають шляху «відтворити на повільному профілі, знайти запит, що завершує дію, прив'язати очікування до нього», а не «підняти таймаут».
- «Як передати мережевий контекст у баг-репорт?» Очікують HAR і знання, що типовий експорт санітизований — без
Cookie,Set-CookieйAuthorization.
Джерела
Рядок запиту, колонки й фільтри
- Chrome DevTools — Network features reference — запис журналу лише за відкритих DevTools,
Preserve log, склад і керування колонками,ProtocolіConnection ID, власна колонка під заголовок, типи ресурсів і властивості фільтра, прапорецьInvertта відсутністьOR, вкладки запиту й санітизований HAR. - Firefox DevTools — Network request list — назви колонок (
File,Type,Transferred), порожнійStatusі графічна позначка блокування, причини блокування словами вTransferred, «Enable persistent logs» у Settings, іконка черепахи з порогом 500 мс і трикутник при слабких шифрах.
Життєвий цикл запиту як рамка читання логу
- Chrome DevTools — Network features reference — фази вкладки
Timing, змістWaiting (TTFB),Waterfallза часом старту, станpendingі чергування запитів до одного походження. - web.dev — Time to First Byte (TTFB) — навігаційний TTFB від запиту сторінки до першого байта, з DNS і встановленням з'єднання всередині.
- MDN Glossary — Time to first byte (TTFB) — те саме означення у словнику.
- MDN — Document: DOMContentLoaded event — що означає завершення парсингу HTML.
- MDN — Window: load event — момент завантаження початкових ресурсів сторінки.
Заголовки й тіло: що читати першим
- MDN — HTTP messages — однакова структура запиту й відповіді, порожній рядок як межа, бінарні кадри HTTP/2 при незмінній логічній моделі.
- RFC 9110 — HTTP Semantics — заголовки перед вмістом,
Content-Type,Content-Length,Accept, статус тіла вGET, розмежування400і415. - RFC 7578 — Returning Values from Forms: multipart/form-data — частини тіла з власними заголовками й параметр
boundary. - Chrome DevTools — Network features reference — вкладки
Headers,Payload,ResponseіPreviewобраного запиту.
Статус-коди й редиректи в ланцюжку
- RFC 9110 — HTTP Semantics — класи кодів, необов'язковість пояснювальної фрази, межа
4xxі5xx, збереження методу для307,304як підтвердження валідної збереженої копії. - MDN — HTTP response status codes — значення
400,422,401,403,409,429зRetry-After,500,502,503,504. - RFC 9112 — HTTP/1.1 (message syntax) — вимога клієнту ігнорувати пояснювальну фразу статусу.
- RFC 9114 — HTTP/3 — HTTP/3 не передає версію й пояснювальну фразу статус-рядка.
- RFC 6585 — Additional HTTP Status Codes — код
429як реакція на перевищення рейт-ліміту. - MDN — Redirections in HTTP —
Locationі клас3xx, постійні проти тимчасових, поведінка методу й тіла для301/302/303/307/308, ціна кроку й петля редиректів. - curl — manual page — автопрямування клієнтів і потреба
-Lу curl. - Chrome DevTools — Network features reference — колонка
Statusі збереження журналу крізь навігацію.
Звідки взялася стара відповідь: кеш і Service Worker
- RFC 9111 — HTTP Caching — свіжа й застаріла відповідь, умовний запит із валідатором, зміст
304, різницяno-cacheіno-store. - MDN — HTTP caching — евристичне кешування без
Cache-Controlі читання директив у відповіді. - MDN Glossary — CDN — проміжний спільний кеш між користувачем і origin-сервером.
- Chrome DevTools — Network features reference —
Disable cacheяк емуляція першого візиту й панельNetwork conditionsдля інших панелей. - MDN — Service Worker API — воркер як проксі між застосунком і мережею, реєстрація на походження й шлях, захищений контекст, стан очікування нової версії.
- web.dev — The Offline Cookbook — Cache API окремо від HTTP-кешу й черга подій
fetchпід час активації.
Скільки з'єднань і яка версія протоколу
- MDN — Connection management in HTTP/1.x — серіалізація запитів у HTTP/1.x, шість паралельних з'єднань на домен як поточна практика, вимкнений , застарілий домен-шардінг, hop-by-hop-природа керування з'єднанням.
- RFC 9113 — HTTP/2 — вимога не відкривати більш ніж одне з'єднання на пару «хост плюс порт».
- High Performance Browser Networking — HTTP/2 — способи узгодження версії, TLS з ALPN як рекомендований, позиція браузерів щодо HTTP/2 лише поверх TLS.
- caniuse — HTTP/2 protocol (features-json) — підтримка HTTP/2 у браузерах лише поверх TLS і потреба ALPN на сервері.
- Chrome DevTools — Network features reference — колонки
Connection IDіProtocolзі значеннямиh2таh3і їх увімкнення вручну.
Затримка, пропускна здатність і тротлінг
- MDN — Understanding latency — означення затримки й RTT, вартість першого запиту, фаза завантаження як функція пропускної здатності й розміру, тротлінг як емуляція швидкостей і мінімальної затримки.
- MDN Glossary — Bandwidth — означення пропускної здатності.
- RFC 7323 — TCP Extensions for High Performance — добуток «пропускна здатність × затримка» і незалежність двох величин.
- MDN — Connection management in HTTP/1.x — перевикористання з'єднань як спосіб скоротити RTT.
- RFC 9000 — QUIC: A UDP-Based Multiplexed and Secure Transport — поєднання встановлення транспортного з'єднання з узгодженням TLS.
- Chrome DevTools — Network features reference — колонка
Time, менюThrottlingіз пресетами, власні профілі, режимOfflineпоруч ізDisable cache. - Selenium — Waiting Strategies — падіння на повільному профілі як ознака очікувань, не прив'язаних до подій.
AJAX і fetch: чому запиту немає в логу навігації
- MDN — Using the Fetch API — успішний проміс на
404і500,response.okдля2xx, одноразовість потоку тіла, режимиcredentialsі типовийsame-origin. - MDN Glossary — SPA (Single-page application) — оновлення частини DOM без повного перезавантаження сторінки.
- MDN — XMLHttpRequest — старий механізм фонового запиту й скасування через
abort(). - MDN — XMLHttpRequest: readyState property — п'ять станів,
HEADERS_RECEIVEDпісля всіх редиректів, часткова відповідь уLOADING, двозначністьDONE. - Chrome DevTools — Network features reference — фільтр Fetch/XHR і потрапляння фонових запитів у той самий журнал.
- Playwright — Network — спостереження тих самих запитів інструментом автоматизації.
Кукі й сховища браузера у зв'язці з запитом
- RFC 6265 — HTTP State Management Mechanism —
Set-Cookieу відповіді йCookieв запиті, повернення лише пар «ім'я — значення» без атрибутів, змістSecureіHttpOnly. - MDN — Using HTTP cookies — практичне читання атрибутів кукі й межа доступу скриптів.
- MDN — Set-Cookie — заголовок, у якому атрибути видно повністю.
- MDN — Web Storage API — поділ
localStorageза походженням іsessionStorageза вкладками, дані не надсилаються на сервер самі. - Chrome DevTools — Network features reference — властивості фільтра для кукі й санітизований HAR без
Cookie,Set-CookieтаAuthorization.
CORS і CSP: «запит пішов, а даних немає»
- MDN — Cross-Origin Resource Sharing (CORS) — preflight методом
OPTIONSі його заголовки,Content-Type: application/jsonпоза безпечним переліком,Access-Control-Expose-Headers, перевірка на боці браузера, два записи на один логічний запит і зупинка справжнього запиту, якщоOPTIONSне дав дозволу. - WHATWG Fetch Standard — умови безпечного запиту без preflight і сім заголовків відповіді, видимих скрипту за замовчуванням.
- MDN — Same-origin policy — простий запит виконується сервером повністю, навіть коли браузер не віддає відповідь скрипту.
- W3C — Content Security Policy Level 3 — доставка політики заголовком, режими примусу й звіту,
Content-Security-Policy-Report-Onlyяк спосіб випробувати політику. - MDN — Content Security Policy (CSP) — блокування inline-коду суворою політикою й повідомлення про порушення в консолі.
Перехоплення й підміна відповіді: що з цього видно у вкладці
- Playwright — Network — три рішення обробника маршруту, вибірковість перехоплення, HAR як джерело підмінених відповідей.
- Playwright API — class: Route — закритий перелік кодів обриву й різниця між ними.
- Chrome DevTools — Network features reference — експорт та імпорт HAR у панелі.
- Pact — офіційна документація — дрейф контракту як плата за підміну реального бекенда.
- RFC 6455 — The WebSocket Protocol — рукостискання через
GETзUpgrade, статус101ізSec-WebSocket-Acceptяк єдине підтвердження, схемиwsіwss, самостійність протоколу після переходу. - MDN — The WebSocket API (WebSockets) — з'єднання в контексті виконання сторінки.
- Chrome DevTools — Network features reference — тип ресурсу WS серед фільтрів панелі.
- Selenium — Waiting Strategies — очікування події-наслідку замість фіксованої паузи.
Режим пристрою й Lighthouse: межі того, що вони міряють
- Chrome DevTools — Simulate mobile devices with device mode — наближення першого порядку замість реального пристрою, відносність обмеження ЦП, вплив типу пристрою на події, смуги брейкпоінтів.
- Chrome for Developers — Introduction to Lighthouse — три способи запуску й Node-варіант як основа для CI.
- Chrome for Developers — Lighthouse accessibility score — зважене середнє, правило «все або нічого», виключення ручних і низьковпливових перевірок з оцінки.
- MDN — Accessibility tooling and assistive technology — інструмент не вирішує проблем сам і людину не замінює.
Гонка станів: чому «то зелений, то червоний»
- MDN — JavaScript execution model — завдання обробляється повністю до початку наступного, звідки й береться недетермінований порядок між ними.
- MDN — Using the Fetch API — скасування застарілого запиту через
AbortController. - Selenium — Waiting Strategies — фіксована пауза маскує гонку й непередбачувана, очікування за умовою з таймаутом як стелею.
- Playwright — Auto-waiting (actionability) — автоочікування закриває частину гонок, але не умови на дані.
- Playwright — Network — очікування конкретної відповіді замість паузи.
- Chrome DevTools — Network features reference — панель як спосіб побачити, який запит завершує дію.
Алгоритм розбору «червоного» запиту
- Chrome DevTools — Network features reference — запис журналу лише за відкритих DevTools,
Preserve log,Disable cache, фільтр за типом ресурсу й властивостями, значенняCORS errorу колонціStatus, вкладкиPayload,ResponseіPreview, зміст колонкиSize. - RFC 9110 — HTTP Semantics — межа між класами
4xxі5xx,304як підтвердження валідної збереженої копії. - MDN — HTTP response status codes — значення
400,401,403,404,422,429зRetry-After,502,503і504. - MDN — Cross-Origin Resource Sharing (CORS) — preflight методом
OPTIONSі зупинка справжнього запиту без дозволу. - MDN — Content Security Policy (CSP) — порушення політики видно в консолі, а не в мережевій панелі.
Що показує вкладка Network і навіщо вона тестувальнику?
Це журнал усіх мережевих звернень сторінки: сам документ, стилі, скрипти, шрифти, зображення й фонові виклики до API — кожному запиту відповідає один рядок. Замість здогадів вона видає факти: який метод і адреса пішли, що повернув сервер, що лежало в тілі, скільки часу з'їла кожна фаза. Для AQA це головний прилад локалізації: тут «форма не зберігається» перетворюється на «POST /api/orders віддав 422, у тілі — повідомлення про зайнятий email». Різниця між цими двома формулюваннями — це різниця між тікетом, який розробник закриє за годину, і тікетом, що місяцями висить у статусі «не відтворюється». Тому цінність Network не в переліку колонок, а у вмінні довести, на чиєму боці баг.
Відтворив баг, а в журналі порожньо. Що сталося?
Дві найчастіші причини, і жодна з них не про застосунок. Перша: панель відкрили вже після дії — записувати трафік DevTools починають тільки з моменту відкриття, тож усе, що сталося до цього, для них не існувало. Звідси базова звичка: спершу відкрити панель, потім відтворювати проблему. Друга причина: журнал змило переходом на іншу сторінку, і найчастіше це б'є саме по логіну, після якого одразу йде перенаправлення. Лікується прапорцем Preserve log — він тримає записи крізь навігації. Тому перед складним сценарієм два перемикачі ставлять заздалегідь: збереження журналу і вимкнений кеш.
Як знайти потрібний запит серед сотень рядків?
Фільтрів у панелі два рівні, і сильна відповідь називає обидва. Перший — кнопки типів ресурсу: Doc для самого документа, Fetch/XHR для фонових викликів, Img, CSS, JS, WS для сокетів. Другий — текстове поле, яке розуміє не тільки підрядок в URL, а й властивості: status-code:500, method:POST, domain:api.example.com, а для кукі — cookie-name і response-header-set-cookie. Дві деталі, на яких валяться навіть досвідчені: інверсія вмикається окремим прапорцем Invert, а не мінусом перед виразом; кілька властивостей поєднуються виключно логічним «і», умови «або» тут не існує. Тому запит на кшталт «покажи POST або PUT» одним фільтром не збирається — доведеться фільтрувати по черзі.
Обрали конкретний запит. Що читати першим?
Порядок читання економить найбільше часу: блок General дає метод, адресу й код; Response Headers і Request Headers — метадані обох сторін; Payload — те, що поїхало на сервер; Response — сире тіло, а Preview — його ж, але розібране браузером у зручне дерево. Мінімальний набір заголовків, який дивляться завжди: Content-Type (у якому форматі тіло), Content-Length (скільки байтів очікувати), Accept (що клієнт готовий прийняти) і Authorization (чи взагалі поїхали облікові дані). Класична пастка ручного формування запитів — неправильний Content-Type: сервер відхилить тіло, і канонічною відповіддю тут буде 415, а не 400, тож на «будь-яку четвірку» приховає діагноз. Якщо тестуєте завантаження файлів, у Payload буде multipart/form-data: тіло поділене на частини з власними міні-заголовками, а межу між ними задає рядок, оголошений параметром boundary в Content-Type.
Запит завис у стані pending. Про що це говорить?
pending означає рівно одне: запит відправлено, а повної відповіді ще немає. Далі є дві принципово різні гілки. Перша — сервер справді думає: обробка довга, і час згорає у фазі очікування. Друга — запит навіть не почав виконуватися, бо стоїть у черзі: браузер тримає обмежену кількість одночасних з'єднань до одного , і зайві чекають, поки звільниться слот. Розрізняє їх вкладка Timing і колонка Connection ID: якщо час пішов у чергу, а не в очікування відповіді, це не «повільний бекенд», а конкуренція за з'єднання. Практичний наслідок для тестів: масові pending під час паралельного прогону — привід подивитися на кількість одночасних запитів, а не піднімати .
Що таке фаза Waiting (TTFB) і чим вона відрізняється від TTFB із метрик продуктивності?
Це питання-детектор: під однією абревіатурою ховаються два різні виміри. Фаза Waiting (TTFB) у вкладці Timing починається після того, як з'єднання вже підняте, і містить один до сервера плюс час, який сервер витратив на підготовку відповіді. Навігаційний із метрик рахують ширше — від моменту запиту сторінки до першого байта, тобто разом із пошуком у DNS і встановленням з'єднання. Тому число з панелі й число з метрики можуть відрізнятися в рази на першому запиті до нового хоста, і це не помилка вимірювання. Практичний висновок: довга фаза очікування вказує на серверну частину, а не на «повільний інтернет», бо канал у цей момент простоює.
Загальний Time великий. Це повільний бекенд?
Без розкладу по фазах — ні, це ще не діагноз. Колонка Time показує повну тривалість до останнього отриманого байта, і всередині неї можуть згоріти зовсім різні речі. Якщо час зібрався у фазі очікування — проблема на боці сервера. Якщо у фазі завантаження вмісту — питання до розміру тіла й пропускної здатності каналу: мегабайтна відповідь не стане швидшою від оптимізації запиту до бази. Якщо час пішов у DNS, TCP- і TLS — це вартість першого звернення до нового хоста, і вона зникає на наступних запитах завдяки з'єднання. Окремо стоїть черга: там нічого не «гальмує», просто немає вільного слоту. Тому в баг-репорті про продуктивність цінний не загальний час, а те, у якій фазі він осів.
304 Not Modified формально в класі 3xx. Чому це не перенаправлення?
Перенаправлення — це вказівка піти за іншою адресою, і його ознака — заголовок Location. 304 нікуди не відсилає: сервер підтверджує, що копія, яку клієнт уже має, ще годиться, і навмисно не передає тіло. Механізм такий: браузер надсилає умовний запит із валідатором — ETag у заголовку If-None-Match або дату в If-Modified-Since, — а сервер відповідає або новим вмістом, або коротким «нічого не змінилося». Для тестів це важливо двічі: по-перше, після 304 тіла у відповіді немає, і спроба його розібрати впаде; по-друге, 304 можливий лише тоді, коли клієнт надіслав валідатор, — тобто в тесті це слід збереженої копії, яка лишилася від попередніх кроків.
На старій адресі налаштований редирект, а тест бачить 200. Куди він подівся?
Нікуди — клієнт пройшов ланцюг сам. І браузери, і більшість HTTP-бібліотек за замовчуванням ідуть за перенаправленнями автоматично, тому в коді видно вже фінальний результат; помітний виняток — curl, який без прапорця -L зупиниться на першій сходинці. Щоб побачити ланцюг у браузері, потрібен увімкнений Preserve log, інакше проміжні рядки зникнуть разом із навігацією. Перевіряти сам факт редиректу треба явно: вимкнути автопрямування в HTTP-клієнті й асертити код разом із заголовком Location. І пам'ятайте про різницю в поведінці: 307 і 308 гарантовано зберігають метод і тіло, 303 навмисно переводить клієнта на GET, а от 301 і 302 дають таку гарантію лише для GET — інший метод клієнт має право підмінити й загубити тіло, і це історично реальна поведінка, а не теорія.
Виправили й задеплоїли, а на екрані стара версія. Як звузити коло?
Джерел «примари» рівно чотири, і лікуються вони по-різному: HTTP-кеш браузера, сховище Service Worker, проміжний кеш на кшталт CDN чи , і кеш усередині самого застосунку. Починають із найдешевшого: Disable cache імітує візит новачка, який прийшов на сайт уперше; працює він тільки в самій панелі, а розширити його дію на решту інструментів дає змогу окрема панель Network conditions. Наступний крок — заголовки: доки копія рахується свіжою, браузер віддає її локально й у мережу не йде взагалі, а коли свіжість минула, шле умовний запит із валідатором. Тут же живе класична плутанина в назвах: no-cache не забороняє кешувати, він забороняє віддавати без перевірки, а забороняє зберігати no-store. Якщо заголовки правильні, а стара версія лишається — підозрюваний номер один Service Worker: він працює проксі між застосунком і мережею, віддає відповіді з власного сховища Cache API, ніяк не пов'язаного з HTTP-кешем, а нова версія при активному старому спершу стає в стан очікування.
Навіщо вмикати колонку Protocol?
Бо без неї версію протоколу доводиться вгадувати, а вона впливає на поведінку сторінки. Значення в ній свої для кожного рядка: h2, h3 і http/1.1 мирно уживаються в межах однієї сторінки, бо домовляються про версію окремо на кожне з'єднання. Найчастіший практичний висновок: середовище, підняте локально без шифрування, у браузері працюватиме по HTTP/1.1 завжди, і дефекту тут немає. Причина в тому, що сама специфікація HTTP/2 шифрування не вимагає, але браузери заявили, що піднімають його виключно поверх TLS, а сервер має вміти узгодити протокол через ALPN усередині рукостискання. Ще одна деталь для сеньйорного рівня: керування з'єднанням діє між сусідніми вузлами, а не наскрізно, тому проміжний проксі цілком може «знизити» версію, і в панелі ви побачите його вибір, а не вибір походження.
Скільки з'єднань браузер відкриває до одного домену?
Тут перевіряють акуратність формулювань, а не число. На HTTP/1.x запити в одному з'єднанні йдуть строго по черзі, тому браузери відкривають кілька паралельних з'єднань до домену, і поширене нині значення — близько шести. Важливо додати, що це поточна практика браузерів, а не вимога протоколу: у RFC такого числа немає, і посилатися на нього як на норму — помилка. На HTTP/2 картина протилежна: специфікація радить обмежитися одним з'єднанням на кожну пару «хост і порт», бо запити мультиплексуються всередині нього. браузери сьогодні не вмикають, а розкидання ресурсів по кількох піддоменах MDN відносить до застарілих прийомів. А скільки з'єднань підняв ваш браузер насправді — не вгадують: вмикають колонку Connection ID і дивляться.
«У Postman запит проходить, а в браузері — ні». Що це означає?
Що ви щойно порівняли два різні світи. реалізує саме браузер: він відправляє запит, отримує відповідь і вирішує, чи можна віддати її скрипту, спираючись на заголовки Access-Control-*. Postman, curl і будь-який HTTP-клієнт із бекенд-коду цих правил не застосовують узагалі, тому «у Postman працює» не є аргументом на користь того, що баг у фронтенді. Наслідок, який часто випускають: заблокована відповідь не означає, що на сервері нічого не сталося — простий запит сервер виконує повністю, і запис у базі з'явиться. Друга типова картина того ж роду: відповідь прийшла, статус 200, а скрипт не бачить потрібного заголовка — за замовчуванням йому доступні лише сім «безпечних» заголовків, решту сервер має явно перелічити в Access-Control-Expose-Headers.
Чому один логічний запит дає два рядки в панелі й що з цього має знати автотест?
Це preflight — попередній дозвільний запит методом OPTIONS, який браузер шле сам перед «непростим» зверненням. У ньому він заявляє намір заголовками Access-Control-Request-Method і Access-Control-Request-Headers, а сервер відповідає, що з цього дозволено. Найпоширеніший тригер — звичайний JSON: Content-Type: application/json до переліку безпечних значень не входить, тому майже кожен з фронтенду породжує пару рядків. Перший практичний наслідок: якщо OPTIONS дозволу не дав, справжній запит не полетить зовсім — і шукати причину треба у відповіді на дозвільний рядок, а не в бекенді. Другий стосується автотестів: очікування зі сторінки вішають на змістовну відповідь, а не на дозвільний рядок, а сам OPTIONS перевіряють прямим API-запитом.
Скрипт не виконався, а в Network усе зелено. Де шукати?
У — це майже завжди CSP. Політика безпеки вмісту приїжджає заголовком Content-Security-Policy, і браузер за нею вирішує, звідки дозволено вантажити й що дозволено виконувати. Ресурс при цьому міг цілком успішно завантажитися, тому в мережевій панелі жодного червоного рядка не буде — блокування відбувається на іншому рівні, а повідомлення про порушення падає в консоль. Саме тому кандидати, які дивляться лише в Network, у цьому сценарії застрягають надовго. Є й режим спостереження: Content-Security-Policy-Report-Only вмикає ту саму політику без примусу, щоб зібрати порушення до того, як вони щось зламають. Для AQA у цьому є пряма практична ціна: сувора політика ламає й автоматизацію, бо допоміжного скрипта або inline-обробника буде заблокована так само, як і чужий код.
Чому fetch() не кидає помилку на 500 і що з цього випливає для тесту?
Бо в Fetch API невдалий HTTP-статус — це не збій операції: мережевий обмін відбувся, відповідь отримано, тож успішно резолвиться об'єктом Response. Реджект залишили для випадків, коли запит не вдалося виконати взагалі — наприклад, мережева помилка або некоректна схема адреси. Тому статус перевіряють руками, і зручний скорочений спосіб — властивість response.ok, істинна лише для діапазону 2xx. Наслідок для тестів і для розбору багів: код застосунку, написаний з розрахунком на «поламається саме», спокійно поїде далі з тілом помилки замість даних, і в панелі ви побачите чесний 500, тоді як інтерфейс покаже порожній список. Поряд корисно знати ще дві межі: тіло відповіді — потік, який читається лише один раз, а крос-оріджин fetch за замовчуванням не бере з собою облікових даних, бо режим credentials типово обмежений своїм походженням. У старому XMLHttpRequest аналогічна пастка зі станом readyState, що дорівнює чотирьом: він означає «операція завершилась», а не «завершилась успішно».
Чому в Request Headers не видно HttpOnly і Secure?
Бо обмін кукі асиметричний. Сервер видає кукі заголовком Set-Cookie і надсилає значення разом з атрибутами — строком життя, доменом, шляхом, ознаками Secure і HttpOnly. Назад браузер відправляє лише пари «ім'я = значення» в заголовку Cookie, без жодного атрибута, тому шукати їх у запиті марно: дивитися треба Set-Cookie у відповіді, яка їх встановила. Практично це означає, що перевірка «чи стоїть у сесійної кукі прапорець HttpOnly» — це перевірка відповіді на логін, а не наступних запитів. Поруч живе дзеркальне правило про сховище браузера: вміст localStorage не їде на сервер сам ніколи, і якщо токен там, застосунок мусить явно покласти його в заголовок. Звідси проста вилка при діагностиці: немає Authorization у запиті — шукайте баг у клієнтському коді, а не в бекенді.
Як зрозуміти, чи WebSocket узагалі підключився?
Дивитися перший рядок під фільтром WS, бо з'єднання починається як звичайний HTTP-запит. Клієнт відправляє GET, у якому просить перемкнути протокол заголовками Upgrade і Connection, та додає Sec-WebSocket-Key. Підтвердженням переходу служить рівно один код — 101 у парі з Sec-WebSocket-Accept у відповіді. Усе інше означає, що перемикання не відбулося й сторони лишилися в межах звичайного HTTP, тому «реалтайм не працює» дуже часто виглядає як банальний 401 або 404 у першому ж рядку. Після переходу це вже самостійний протокол поверх TCP, і всередині запису видно окремі кадри з напрямком, вмістом і часом. Дві практичні деталі: пари «запит — відповідь» тут немає, тому в тесті чіпляються за подію, яка є наслідком повідомлення, а не за таймер; а перезавантаження сторінки вбиває з'єднання разом зі станом підписок, бо воно живе в контексті виконання JavaScript цієї вкладки.
Тест то зелений, то червоний. Як панель Network доводить до діагнозу?
Спершу гіпотеза: якщо результат залежить від того, яка з асинхронних операцій завершиться першою, а гарантій щодо порядку немає — це гонка станів. Відтворюють її не повторними прогонами наосліп, а : меню обмеження швидкості емулює канал і мінімальну , і для відтворюваності краще задати власний профіль із фіксованими числами, бо набір готових пресетів між версіями Chrome змінювався. Далі в панелі знаходять запит, який реально завершує перевірювану дію, і прив'язують очікування саме до нього — а не до появи спінера й не до фіксованої паузи. Жорстка пауза гонку не усуває, а ховає: вона нічого не перевіряє й лише сподівається, що система встигне. Головна теза для співбесіди: тест, який падає на повільному профілі, не «колись зафлакає» — він уже некоректний, просто на швидкій машині ще не встиг це показати.
Як передати мережевий контекст у баг-репорт?
Найкоротший шлях — експорт журналу у HAR: це JSON-запис сесії з усіма запитами, заголовками й таймінгами, який приймальна сторона може завантажити назад у DevTools і роздивитися вашу сесію як власну. Це знімає найдорожчу фазу листування «а покажи ще заголовки» і працює навіть тоді, коли баг у вас не відтворюється вдруге. Про безпеку варто сказати окремо, бо це часте питання: типовий експорт санітизований — з нього прибрані Cookie, Set-Cookie й Authorization, тому файл можна чіпляти до тікета без страху злити чужу сесію. Той самий запис має друге життя в автоматизації: HAR можна використати як джерело підмінених відповідей у тесті. Плата за це чесна: збережений знімок поступово розходиться з реальністю, і перевірка бадьоро зеленітиме на відповідях, яких бекенд давно не повертає.
Три кейси з робочого дня AQA: як за півтори хвилини довести, на чиєму боці баг «кнопка не працює»; чому автотест на форму почав плавати після винесення API на окремий домен; і як із плаваючого падіння зробити відтворюваний сценарій та доказовий тікет.
Кейс 1. «Кнопка не працює»: протокол локалізації за 90 секунд
Тестувальниця тисне «Зберегти», сторінка мовчить, тост не з'являється. Формулювання «не працює» непридатне для тікета, тому спершу збираємо факти. Порядок дій жорсткий: відкрити DevTools до повторного натискання, поставити прапорці Preserve log і Disable cache, перейти на фільтр Fetch/XHR — і лише тоді повторити дію.
Далі можливі рівно три картини, і кожна веде до свого висновку.
Name Status Size Time Protocol
(a) — жодного нового рядка після кліку —
(b) orders 422 318 B 140 ms h2
(c) orders 200 1.2 kB 90 ms h2
| Що видно в панелі | Діагноз | Куди дивитися далі |
|---|---|---|
| Рядка немає взагалі | Запит не пішов: не спрацював обробник, впала валідація на клієнті, кнопка вимкнена | Консоль на предмет винятку, стан кнопки, CSP-блокування inline-обробника |
Рядок є, статус 4xx або 5xx | Запит пішов, сервер свідомо відмовив або впав | Вкладка Payload (що саме надіслали) і Response (текст помилки) |
Рядок є, статус 200, а на екрані нічого | Дані приїхали, але застосунок їх не показав | Тіло у Preview, консоль, заголовки Access-Control-* |
Що дивитися і чому:
- Порожній журнал — це не завжди «бекенд не отримав запит». Спершу переконайтеся, що панель була відкрита до дії: записувати трафік DevTools починають лише з моменту відкриття. Якщо порожньо і при повторній спробі — запит справді не формувався, і це вже баг клієнта, а не сервера.
422у другому рядку — найкорисніший результат із трьох. Сервер розібрав запит і відхилив його за змістом, тож у тілі майже завжди лежить причина. ВідкрийтеPayloadі звірте, що поїхало: часто виявляється, що поле відправлене порожнім або в іншому форматі, і баг у формі, а не у валідації.- Не плутайте
400і415. Якщо тіло сформовано вручну (наприклад, у ручному запиті через ) іContent-Typeне відповідає вмісту, канонічна — саме415, а не «якийсь4xx». на «будь-яку четвірку» цю різницю з'їдає й ховає причину. - Третій рядок — найпідступніший. Статус
200, тіло на місці, застосунок порожній. Це майже завжди браузерна політика або мовчазна помилка в коді:fetch()на невдалий статус не кидає винятку, тож обробник спокійно поїде далі й запише в стан те, що прийшло. Перевіряти треба консоль, а не колонкуStatus. - Колонка
Protocolтут не декорація. Якщо локально видноhttp/1.1, а на стейджіh2— це нормально й не є частиною дефекту: браузер піднімає HTTP/2 лише поверх TLS. Один раз пояснивши це в тікеті, ви економите цикл листування.
Готове формулювання для баг-репорта замість «не працює»: на кліку «Зберегти» йде POST /api/orders, відповідь 422, тіло — "deliveryDate must be in the future", при цьому у формі обрана завтрашня дата; заголовок Authorization на місці, кеш був вимкнений, HAR у вкладенні.
Кейс 2. Тест на форму почав плавати після винесення API на окремий домен
Симптом класичний: локально зелено, у CI падає приблизно кожен третій прогін, помилка — очікування. Змінилася при цьому не логіка застосунку, а адреса: фронтенд лишився на app.example.com, API переїхав на api.example.com. У панелі це видно одразу — на одну відправку форми з'явилося два рядки.
Name Method Status Time
orders OPTIONS 204 180 ms
orders POST 201 340 ms
Перший рядок — preflight: браузер сам питає дозволу, перш ніж відправити «непростий» запит. Тригер тут найбанальніший — заголовок Content-Type: application/json, який до безпечного переліку не входить. Логіка застосунку не змінилася, а от кожна відправка форми подорожчала на цілий додатковий обхід — і фіксована пауза, якої «вистачало» до переїзду, на завантаженому агенті CI вистачати перестала. Лікує це не більше число в паузі, а очікування на конкретну відповідь.
import { test, expect } from '@playwright/test';
test('замовлення створюється', async ({ page }) => {
await page.goto('https://app.example.com/orders/new');
// очікування оголошуємо ДО кліку, інакше відповідь може приїхати раніше,
// ніж ми почнемо її чекати
const created = page.waitForResponse(
(r) => r.url().endsWith('/api/orders') && r.request().method() === 'POST'
);
await page.getByTestId('submit-order').click();
const response = await created;
expect(response.status()).toBe(201);
await expect(page.getByRole('status')).toContainText('Замовлення створено');
});
Що дивитися і чому:
- Чекати треба на змістовну відповідь, а не на дозвіл. Умова
request().method() === 'POST'тримає очікування саме на створенні: на одну адресу зазвичай ходить кілька методів, і фоновийGET /api/ordersдля списку закриє очікування замість потрібного запиту. Відповідь сервера на самOPTIONSперевіряють прямим API-запитом, а зі сторінки чекають на змістовну відповідь. - «У Postman працює» тут нічого не доводить. застосовує браузер: він відправляє запит і вирішує, чи віддавати відповідь скрипту. Postman і
curlцих правил не реалізують узагалі, тому зелений запит у них не спростовує CORS-проблеми в застосунку. - Заблокована відповідь не означає, що нічого не сталося. Простий запит сервер виконує повністю, тому «CORS-помилка» на
POST-подібному сценарії цілком може лишити по собі створений запис. При розборі даних у тесті це рятує від хибного висновку «запит не дійшов». - Окремий симптом того ж кореня — відсутній заголовок. Якщо тест читає, наприклад,
X-Request-Idз відповіді, а він порожній при статусі200, справа не в сервері: скрипту типово доступні лише сім «безпечних» заголовків, решту бекенд має перелічити вAccess-Control-Expose-Headers. - Асерт на статус плюс асерт на інтерфейс — різні перевірки. Перший фіксує контракт, другий — що застосунок ним скористався. Залишити тільки другий означає щоразу вгадувати, де саме зламалося.
Кейс 3. Плаваюче падіння: відтворити, довести, віддати
Тест «змінив фільтр — таблиця оновилася» падає раз на десять прогонів і жодного разу не падає локально. Приймати це як «просто флак» не можна: якщо результат залежить від того, який із двох запитів відповість першим, це гонка станів, і на бойовому середовищі вона колись перетвориться на баг для користувача.
Крок перший — зробити падіння відтворюваним. Замість гри в рулетку вмикають меню Throttling: воно емулює швидкість завантаження, швидкість віддачі й мінімальну . Для повторюваності готовий пресет не годиться — набір пресетів між версіями Chrome змінювався, тож «повільна мережа» у двох людей означатиме різні умови; заводять власний профіль із фіксованими числами й ці числа записують у тікет разом із кроками.
Крок другий — знайти в панелі запит, який реально завершує дію, і прив'язати очікування саме до нього, а не до спінера й не до паузи. Крок третій — перевірити, що інтерфейс не бреше, коли запиту не пощастило:
// різні класи збою — це різні коди, а не синоніми
await page.route('**/api/orders**', (route) => route.abort('connectionrefused'));
await expect(page.getByRole('alert')).toContainText('Не вдалося завантажити');
Що дивитися і чому:
- Жорстка пауза гонку не лікує. Вона нічого не перевіряє й лише сподівається, що система встигне; на завантаженому агенті CI вона не встигає. Очікування за умовою чесніше: таймаут у ньому — стеля терпіння, а не запланований простій.
- Падіння на повільному профілі — це вирок тесту, а не мережі. Якщо перевірка розсипається від доданої затримки, вона вже некоректна: на швидкій машині їй просто щастило. Піднімати таймаут у такій ситуації означає лікувати симптом.
- Обрив запиту — окремий клас перевірки. Коди на кшталт
'connectionrefused','namenotresolved'і'internetdisconnected'описують різні збої, і застосунок має право реагувати на них по-різному. Підміняти їх усі одним500— означає не перевірити стан «мережа недоступна» взагалі. - Перехоплення вибіркове, і це видно в панелі. Усе, що не підійшло під шаблон маршруту, поїде на реальний сервер, тому в журналі буде суміш справжніх і рукотворних відповідей. Перед висновком «мок не спрацював» перевірте, чи взагалі шаблон збігся з адресою.
- У тікет — HAR, а не переказ. Експорт журналу дає розробнику вашу сесію цілком, з таймінгами й заголовками; типовий експорт санітизований, тобто без
Cookie,Set-CookieйAuthorization, тому файл безпечно прикладати. Той самий запис можна підключити як джерело відповідей у тесті — але пам'ятайте про ціну: HAR старіє, і зелений тест на застарілих даних гірший за червоний.
Панель, колонки й фільтри
- Готую панель до дії, а не після неї: журнал пишеться лише за відкритих DevTools, а
Preserve logвмикаю наперед для сценаріїв із логіном, редиректами й багатокроковими формами. - Знаю типовий набір колонок —
Name,Status,Type,Initiator,Size,Time,Waterfall, деStatusпоказує або код HTTP, або окреме значенняCORS error, — і вмикаю прихованіProtocolтаConnection ID, щойно питання стосується версії протоколу чи кількості піднятих з'єднань. - Володію обома рівнями фільтрації — кнопки типів ресурсу (Doc, Fetch/XHR, Img, JS, CSS, WS) і властивості
status-code:,method:,domain:,cookie-name— і пам'ятаю, що інверсію даєInvert, а умови поєднуються лише через «і». - Розрізняю вкладки обраного запиту:
Headers— метадані,Payload— що поїхало на сервер,Response— сире тіло,Preview— розібране браузером; помилковийContent-Typeу тілі читаю як канонічний415, а не400. - Знаю, що HAR-експорт типово санітизований (без
Cookie,Set-Cookie,Authorization), тому його безпечно чіпляти до тікета, і що його можна завантажити назад у панель. - Не переношу звички Chrome на Firefox: там
Statusпри збої порожній або з графічною позначкою, причина блокування словами живе в колонціTransferred,NameзветьсяFile, а збереження журналу — «Enable persistent logs» у Settings. Про Safari нічого не стверджую: довідки Web Inspector у нашому реєстрі джерел немає.
Час, фази й статуси
- Можу назвати фази запиту по порядку й сказати, у якій із них згорів час: очікування вказує на бекенд, завантаження вмісту — на розмір тіла й , а перший похід до нового хоста дорожчий на DNS, TCP і TLS.
- Не плутаю два виміри під однією абревіатурою: фаза
Waiting (TTFB)рахується вже після встановлення з'єднання, а навігаційний із метрик включає ще DNS і саме з'єднання. - Знаю два прочитання стану
pending— бекенд ще обробляє звернення або слот під нього досі не звільнився — і що розрізняє їх усе та сама вкладкаTiming. - Розумію, що
304формально3xx, але не перенаправлення (він лише підтверджує валідність збереженої копії), і пам'ятаю поведінку методу при справжніх редиректах:307і308зберігають метод і тіло,303навмисно переводить наGET, а301і302дають гарантію лише дляGET.
Кеш, Service Worker і стара версія
- Знаю чотири домівки «примари після деплою» — HTTP-кеш браузера, сховище Service Worker, CDN чи проміжний , кеш усередині застосунку — і починаю розбір із
Disable cache, пам'ятаючи, що він діє в межах панелі. - Не плутаю директиви:
no-cacheдозволяє зберігати, але вимагає перевірки перед видачею, а забороняє зберігатиno-store. - Можу пояснити, чому правильні заголовки на бекенді не рятують від Service Worker: його Cache API живе окремо від HTTP-кешу, а нова версія при активному старому стає в стан очікування.
З'єднання і версія протоколу
- Формулюю акуратно: близько шести паралельних з'єднань на домен — поточна практика браузерів для HTTP/1.x, а не норма протоколу; для HTTP/2 специфікація радить обмежитися одним з'єднанням на пару «хост і порт». Звідси ж пояснюю, чому локальний сайт без шифрування піде по HTTP/1.1: браузери вмикають HTTP/2 лише поверх TLS, а версію узгоджують через ALPN.
Фонові запити, кукі й сховище
- Розумію, що «сторінка відкрилась» не дорівнює «дані приїхали»: фонові виклики живуть у фільтрі
Fetch/XHR, окремо від рядка з документом, аreadyState === 4у старомуXMLHttpRequestозначає лише «операція закінчилась», хай навіть помилкою. - Знаю межі
fetch(): він не реджектиться на404чи500(статус перевіряють черезresponse.ok), тіло відповіді читається лише раз, а виклик за замовчуванням облікових даних не шле. - Можу пояснити, чому атрибути кукі (
Secure,HttpOnly, строк життя) видно тільки вSet-Cookieу відповіді, і знаю дзеркальне правило: вмістlocalStorageна сервер сам не потрапляє, тому відсутнійAuthorizationвказує на баг у клієнтському коді.
Браузерні політики й розбір «червоного» запиту
- Можу пояснити, чому «у Postman працює» не є аргументом: політику застосовує браузер, а сторонні клієнти заголовки
Access-Control-*ігнорують — і чому порушення CSP при цьому шукають у , а не в Network. - Знаю, що preflight методом
OPTIONSдає два рядки на один логічний запит, що JSON-виклик його викликає і що без дозволу відOPTIONSсправжній запит не піде зовсім. - Тримаю в голові порядок розбору: панель до дії, далі
Status, далі «чи дійшло до сервера взагалі» (CORS error, preflight, консоль), і насамкінець найпідступніше —200без результату, де читаютьResponse, консоль іSize. - Розумію, що для AQA обмеження швидкості — інструмент відтворення гонок, а не вимірювання продуктивності (заради повторюваності краще задати власний профіль), і що очікування чіпляють до запиту, який завершує дію, а не до паузи.
Квіз
Перед стартом
- Питань: 17
- Поріг «зараховано»: ≥70% правильних відповідей.
- Результат впливає на прогрес; завалені питання підуть у чергу повторення.
- Квіз впливає на компліт теми: тема стає «пройдено», лише коли прочитано теорію І квіз складено на ≥70%.
Питання
Чим `502`, `503` і `504` відрізняються між собою?

