API-тестування: об'єкт перевірки, рівні й інструменти
Зміст
Половина того, що робить застосунок, ніколи не показується на екрані. Кнопка «Оформити замовлення» — це лише обгортка над запитом, який летить на сервер, змінює баланс, списує товар зі складу, надсилає лист і повертає відповідь. UI показує вам щасливий кінець («Дякуємо за замовлення!»), але не показує, що в тілі відповіді ціна прийшла від'ємна, що замовлення створилося двічі або що чужий користувач може прочитати ваш чек, підмінивши один номер у запиті. Усе це живе на рівні API — і саме тому API-тестування має високу вагу на співбесідах: воно перевіряє логіку там, де вона насправді працює.
Ця глава — карта розділу. Ми розберемо, що таке API-тестування і чим воно відрізняється від тестування через інтерфейс, де воно стоїть у піраміді тестів, що саме на цьому рівні перевіряють, які бувають типи API і чим їх ганяти — від однорядкового curl до автотестів у коді. Далі кожна тема отримає власну главу; тут — фундамент, на який вони спираються.
Що означає «тестувати API»
API (application programming interface) — це контракт, за яким одна програма звертається до іншої. У вебі це майже завжди HTTP-запит на певний URL з певним методом і тілом, у відповідь на який сервер повертає статус-код, заголовки й тіло (зазвичай JSON). Якщо клієнт-серверна модель і формати даних для вас ще туман — почніть із глав «Клієнт-сервер і як працює веб» та «REST API та формати даних»; тут ми на них спираємось.
Тестувати API означає слати запити безпосередньо до цього інтерфейсу й перевіряти відповідь — не відкриваючи браузер і не клікаючи кнопок. Замість «заповнити форму й натиснути Save» ви надсилаєте POST /api/orders з готовим JSON-тілом і перевіряєте, що прийшов статус 201, у тілі є id створеного замовлення, а сам запис справді з'явився. Ви працюєте з системою мовою, якою розмовляють її власні компоненти.
Ключова ідея вже на цій діаграмі: UI-тест проходить увесь стек — браузер, верстку, JavaScript, мережу, бекенд, базу. API-тест заходить збоку, одразу в бекенд, оминаючи весь фронтенд. Тому він швидший, стабільніший і бачить те, чого не видно з екрана.
Чим API-тести відрізняються від тестування через UI
«Чому» тут важливіше за «як». UI-тест відповідає на питання «чи може користувач зробити X через інтерфейс?» — і платить за це повнотою стека: він повільний, бо чекає рендеринг і анімації, і крихкий, бо ламається від зміни верстки, яка ніяк не стосується логіки. API-тест відповідає на інше питання — «чи правильно поводиться бізнес-логіка й дані?» — і за рахунок вужчого зрізу отримує швидкість і стабільність.
| Аспект | Через UI | Через API |
|---|---|---|
| Що перевіряє | Увесь стек очима користувача | Логіку й дані бекенду |
| Швидкість | Секунди на тест | Десятки–сотні мілісекунд |
| Стабільність | Ламається від зміни верстки | Ламається від зміни контракту, а не верстки |
| Що видно | Тільки те, що показує UI | Статус, усі поля тіла, заголовки |
| Підготовка даних | Довга, через кроки в UI | Швидка, одним запитом |
З таблиці не варто робити висновок «API краще за UI». Вони відповідають на різні питання. Кнопка може бути прив'язана до неправильного ендпоінта, поле — не відправлятися, помилка з API — не показуватися користувачу: усе це ловить лише UI-тест. Тому це не заміна, а розподіл праці — і його формалізує піраміда.
Місце в піраміді тестування
Піраміда тестування — орієнтир, що показує, скільки тестів варто мати на кожному рівні. Знизу — багато дешевих і швидких модульних (unit) тестів; посередині — інтеграційні та API-тести; згори — мало повільних наскрізних (E2E) тестів через UI.
API-рівень — та сама золота середина. Він на порядок швидший і стабільніший за E2E, але, на відміну від unit-тестів, ганяє систему зібраною: справжній сервер, справжня база, справжня серіалізація. Одним API-тестом ви покриваєте цілий бізнес-сценарій, який через UI тягнув би десяток крихких кроків. Тому типова порада — переносити перевірки якнайнижче: усе, що можна перевірити на рівні API, не варто перевіряти через браузер. Детальний розбір рівнів, вартості й антипатернів (як-от «пісочний годинник» чи «ріжок морозива») — у розділі «Автоматизація»; тут достатньо запам'ятати, що API-тести — робоча конячка більшості команд.
Що саме перевіряємо на рівні API
Об'єкт перевірки на рівні API — п'ять речей. Кожній далі присвячена окрема глава, тут — короткий орієнтир.
- Функціональність — чи правильно ендпоінт робить свою роботу: створює, читає, оновлює, видаляє потрібний ресурс, рахує суму, застосовує знижку. Це серце тест-дизайну для API — «Тест-дизайн для API: CRUD, параметри, межі».
- Дані — форма і зміст відповіді: правильні типи полів, немає зайвого, дати в очікуваному форматі, числа не округлені криво. Це тема глави «Перевірки відповіді: статус, тіло, заголовки, схема».
- Контракт — чи відповідає API домовленості між командами: ті самі поля, ті самі статуси, зворотна сумісність зі старими клієнтами. Про це — «OpenAPI/Swagger: специфікація як джерело істини» і «Контрактне тестування і сумісність версій».
- Помилки — як система поводиться, коли все йде не так: невалідний JSON, відсутнє обов'язкове поле, неправильний тип, конфлікт. Правильний код і зрозуміле тіло помилки — «Негативні перевірки й обробка помилок».
- Доступи — хто що може: чи віддає ендпоінт
401без токена,403чужому користувачу, чи не можна дістатися чужих даних перебором id. Це «Авторизація в API: ролі, доступи, негативні сценарії»; механіка самих токенів — у «Автентифікація та авторизація».
Типи API оглядово
«API» — не один протокол. На співбесіді корисно розрізняти хоча б п'ять і розуміти, чим тестування кожного відрізняється.
| Тип | Транспорт і формат | Що характерно для тестування |
|---|---|---|
| REST | HTTP + JSON | Ресурси й методи (GET/POST/PUT/DELETE), статус-коди несуть сенс |
| SOAP | XML поверх HTTP | Суворий контракт (WSDL), громіздкі конверти, живе в enterprise/легасі |
| GraphQL | HTTP + JSON, один ендпоінт | Клієнт сам описує запит; майже завжди 200, помилки — в масиві errors |
| gRPC | Protobuf поверх HTTP/2 | Бінарний формат, у браузері не подивишся як JSON, потрібен grpcurl/код |
| Вебхуки | Вихідний HTTP-запит | Це сервер стукає до вас; треба приймач, а не запит |
REST — найпоширеніший стиль: система як набір ресурсів, доступних за URL, з методами HTTP і осмисленими статус-кодами. Йому присвячена більшість цього розділу. SOAP старіший і формальніший — обмін XML-конвертами за строгим описом WSDL; ви зустрінете його переважно в банках і телекомі. GraphQL перевертає підхід: один ендпоінт, а клієнт запитом описує, які саме поля хоче; це породжує власні пастки, розібрані в «GraphQL: особливості тестування». gRPC — бінарний і швидкий, для зв'язку між сервісами; його разом із SOAP і чергами розбирає «SOAP, gRPC і асинхронні API».
Окремо стоять вебхуки (webhooks): тут напрям зворотний — не ви шлете запит, а зовнішня система сама надсилає HTTP-запит на ваш URL, коли стається подія (оплата пройшла, лист доставлено). Щоб їх перевірити, потрібен не клієнт, а приймач; стратегія — у «Мокання залежностей: стаби, mock-сервери, вебхуки». Реалтайм-канали на кшталт WebSocket — ще один окремий світ, описаний у веб-главі «WebSockets і реалтайм».
Інструментарій: від curl до коду
Інструменти вишиковуються в природну сходинку — від найшвидшого разового пострілу до повноцінної автоматизації.
curl — консольний HTTP-клієнт, який є майже на кожній машині. Ідеальний, щоб швидко перевірити один запит або вкласти точну команду відтворення в баг-репорт:
curl -i -X POST https://api.example.com/orders \
-H "Authorization: Bearer TOKEN" \
-H "Content-Type: application/json" \
-d '{"productId": 42, "qty": 2}'
Прапорець -i покаже статус і заголовки відповіді — те, що на рівні API часто важливіше за тіло.
Postman — найпопулярніший графічний клієнт: колекції запитів, змінні оточення, вбудовані скрипти-перевірки, спільна робота команди. З нього зручно починати ручне дослідження API; йому присвячені «Postman: основи» і глава про скрипти й Newman. Insomnia — легший клієнт із тією ж ідеєю. Bruno — молодший конкурент, який зберігає колекції звичайними файлами прямо в репозиторії, тож їх видно в git поряд із кодом; це зручно для рев'ю й версіонування.
Верхня сходинка — код. Коли перевірок стають десятки, вони мають ганятися в CI на кожен коміт і сплітатися в ланцюжки (створити → прочитати → видалити), графічний клієнт стає тісним. Тоді запити пишуть у тестовому фреймворку. У Playwright для цього є вбудований APIRequestContext:
test('створення замовлення повертає 201 і id', async ({ request }) => {
const response = await request.post('/api/orders', {
data: { productId: 42, qty: 2 },
});
expect(response.status()).toBe(201);
const body = await response.json();
expect(body.id).toBeTruthy();
});
Той самий запит, що й у curl, але вже з перевіркою, повторюваний і придатний для CI. Як будувати такі тести, клієнти й ланцюжки — у «API-автотести в коді: клієнти, структура, ланцюжки». А підглянути реальні запити застосунку, перш ніж відтворювати їх у тесті, найзручніше через вкладку Network у DevTools.
Ручне vs автоматизація
Ручне API-тестування (через Postman чи curl) незамінне, коли ви вивчаєте незнайомий API, робите дослідницьку перевірку гіпотези, відтворюєте баг або ганяєте одноразовий сценарій. Тут швидкість зворотного зв'язку важливіша за повторюваність.
Автоматизація виправдана там, де перевірки треба повторювати часто й однаково: регресія, ланцюжки CRUD, запуск на кожен коміт у CI, перевірка десятків комбінацій параметрів. API-рівень — узагалі найвдячніший кандидат на автоматизацію: тести швидкі, стабільні й не залежать від верстки. На практиці ці два режими не конкурують — руками досліджують і формулюють перевірку, а те, що варто стерегти постійно, переносять у код. Чому API-тести все одно бувають нестабільними й що з цим робити — у «Стабільність API-тестів: флак, асинхронність, дебаг».
Баги, які видно лише на рівні API
Це головний аргумент, чому QA спускається під UI. Ціла категорія дефектів на екрані невидима — інтерфейс їх приховує або просто не показує.
200 OK, а всередині помилка. Сервер повернув «успіх», але в тілі —{"error": "insufficient funds"}або порожній список замість даних. UI може відрендерити це як порожній екран, і тест «зелений». На рівні API ви бачите розбіжність статусу й тіла одразу.- Зайві поля у відповіді. Ендпоінт користувача повертає ще й
passwordHashчиisAdmin— фронтенд їх просто не показує, тож витік даних лишається непоміченим, поки хтось не гляне в сире тіло. - Валідація лише на фронтенді. Форма не пускає від'ємну кількість, але сам ендпоінт приймає
qty: -5без питань. Реальний зловмисник шле запит повз браузер — саме так треба тестувати й вам. - Доступ до чужих даних перебором id.
GET /api/orders/123віддає чуже замовлення, бо сервер не перевіряє власника (клас IDOR/BOLA). З UI такого посилання просто немає, а запит працює. - Гонки й неідемпотентність. Два однакові
POSTстворюють два замовлення; повторний платіж проходить двічі. Крізь UI такий сценарій відтворити важко, а прямими запитами — тривіально. - Неправильні статус-коди. На неіснуючий ресурс приходить
200замість404, на чужий доступ —404замість403. Для користувача різниці немає, для клієнта API — критична.
Саме ця категорія робить API-тестування обов'язковою навичкою, а не приємним доповненням.
Типові помилки
- Виглядає як «тест зелений, статус
200— усе працює», а насправді статус каже лише, що запит дійшов і сервер не впав; помилка може сидіти в тілі відповіді. Статус і тіло перевіряють окремо. - Виглядає як «UI не пускає невалідні дані, отже, бекенд захищений», а насправді валідація часто лише на фронтенді, і прямий запит проходить те, що форма блокує. Негативні перевірки треба слати повз UI.
- Виглядає як «API-тести швидкі й стабільні, замінімо ними всі UI-тести», а насправді API не бачить зламану верстку, неправильну прив'язку кнопки чи те, що помилка не показується користувачу. Рівні доповнюють, а не заміняють один одного.
- Виглядає як «поле не показується в застосунку, значить, його немає», а насправді відповідь може містити зайві чутливі поля, які фронтенд просто не рендерить. Дивіться сире тіло, а не екран.
- Виглядає як «GraphQL повернув
200, запит успішний», а насправді GraphQL майже завжди відповідає200, а реальні помилки лежать у масивіerrors— статус тут не показник.
Підсумок
- API-тестування — це запити безпосередньо до інтерфейсу між компонентами, в обхід UI; воно швидше, стабільніше й бачить те, чого не видно з екрана.
- API-тести й UI-тести відповідають на різні питання й доповнюють один одного; API-рівень — «золота середина» піраміди й головний кандидат на автоматизацію.
- Об'єкт перевірки — п'ять зрізів: функціональність, дані, контракт, помилки, доступи.
- Типів API кілька (REST, SOAP, GraphQL, gRPC, вебхуки), і тестуються вони по-різному; статус-код сам по собі ніколи не є доказом успіху.
- Інструмент вибирають під задачу:
curl/Postman — для дослідження й відтворення, код — для регресії в CI.
Що питають на співбесіді
- «Чим API-тестування відрізняється від UI-тестування?» Інтерв'юер перевіряє, чи розумієте ви, що це різні зрізи, а не заміна: API — швидкість, стабільність і доступ до сирих даних/статусів; UI — увесь стек очима користувача. Слабка відповідь зводиться до «API без браузера»; сильна називає, що саме кожен рівень ловить, а другий пропускає.
- «Де API-тести в піраміді й чому саме там?» Хочуть почути про баланс вартості й стабільності: API-рівень покриває бізнес-логіку дешевше й надійніше за E2E, тому перевірки переносять якнайнижче.
- «Що ви перевіряєте у відповіді на запит?» Тут відсіюють тих, хто дивиться лише на статус. Сильний кандидат перелічує статус-код, тіло (значення й типи полів), заголовки, схему й побічний ефект (чи справді змінився стан).
- «Наведіть баг, який видно лише на рівні API.» Улюблене питання. Годяться
200з помилкою в тілі, валідація лише на фронтенді, зайві поля у відповіді, доступ до чужих даних перебором id. Конкретний приклад цінується вище за визначення. - «Чим ви тестуєте API?» Перевіряють кругозір і доречність:
curl/Postman для ручного дослідження, код у фреймворку для регресії в CI — і розуміння, коли переходити від одного до іншого.
Джерела
- ISTQB® Certified Tester Foundation Level Syllabus v4.0 — розділ «Test Levels» (інтеграційне тестування, тестування взаємодії компонентів; окремої теми «API testing» силабус не виділяє): istqb.org.
- MDN Web Docs — An overview of HTTP: developer.mozilla.org.
- Martin Fowler — The Practical Test Pyramid: martinfowler.com/articles/practical-test-pyramid.html.
- OpenAPI Specification — стандарт машиночитного опису REST API: spec.openapis.org.
- GraphQL Specification: spec.graphql.org.
Що означає «тестувати API» і чим це відрізняється від тестування через UI?
API (application programming interface) — домовленість, через яку програми спілкуються між собою; у вебі це майже завжди HTTP-запит на URL з певним методом і тілом, на який сервер віддає статус-код, заголовки й тіло. Тестувати API означає звертатися до цього інтерфейсу напряму й перевіряти відповідь, без браузера і кліків: замість «заповнити форму й натиснути Save» ви шлете POST /api/orders з готовим JSON і перевіряєте, що прийшов 201, у тілі є id, а запис справді створився. Різниця з UI-тестом — у зрізі: тест через інтерфейс жене сценарій крізь браузер, верстку і JS до бекенду й бази, а API-тест б'є в бекенд без цього шару, тому виграє у швидкості й стабільності та бачить сирі дані, яких на екрані немає. Головне не плутати ці рівні: вони відповідають на різні питання, а не заміняють один одного.
На яке питання відповідає API-тест, а на яке — UI-тест?
UI-тест відповідає на питання «чи може користувач зробити X через інтерфейс?», API-тест — «чи коректно система обробляє дані й логіку?». За повноту стека UI-тест платить двічі: часом (рендеринг, анімації, очікування елементів) і крихкістю — його валить будь-яка зміна верстки, навіть коли логіка не чіпалася. API-тест бере вужчий зріз і тому виграє: десятки-сотні мілісекунд проти секунд, а зламати його може зміна контракту, не верстки. Але з цього не випливає «API краще»: кнопку можуть прив'язати до неправильного ендпоінта, поле — не відправлятися, помилку з API — не показати користувачу, і все це ловить лише UI-тест. Тому це розподіл праці, а не конкуренція.
Де стоять API-тести в піраміді тестування і чому саме там?
API-рівень — «золота середина» піраміди: посередині, між масою дешевих unit-тестів знизу й нечисленними повільними E2E через UI згори. Від E2E він виграє на порядок за швидкістю і стабільністю, а від unit відрізняється тим, що перевіряє не ізольовану функцію, а реально зібрану систему — з живим сервером, базою і серіалізацією. Бізнес-сценарій, що в UI розсипався б на десяток крихких кроків, тут закривається одним тестом. Звідси типова порада — переносити перевірки якнайнижче: усе, що можна перевірити на рівні API, не варто ганяти через браузер. Саме тому API-тести називають робочою конячкою більшості команд.
Що саме перевіряють на рівні API?
Об'єкт перевірки — п'ять зрізів. Функціональність: ендпоінт справді робить заявлене — створює, читає, оновлює, видаляє ресурс, рахує суму, застосовує знижку. Дані: форма і зміст відповіді — правильні типи полів, немає зайвого, дати й числа в очікуваному форматі. Контракт: API тримається домовленості з клієнтами й сусідніми командами — ті самі поля, статуси, зворотна сумісність. Помилки: реакція на невалідний ввід — зламаний JSON, відсутнє поле, неправильний тип, конфлікт — має бути осмисленим кодом і зрозумілим тілом. Доступи: хто що може — 401 без токена, 403 чужому користувачу, недосяжність чужих даних перебором id. Кожному зрізу далі присвячена окрема глава розділу.
Що ви перевіряєте у відповіді на запит?
Не лише статус — це головна пастка, на якій відсіюють слабких кандидатів. Перевіряють щонайменше чотири речі плюс побічний ефект. Статус-код — чи він осмислений для операції (201 на створення, а не просто 200). Тіло — значення й типи полів, відсутність зайвого, формат дат і чисел. Заголовки — Content-Type, Location на створеному ресурсі, заголовки кешу. Схему — чи відповідь структурно збігається з контрактом (типи, обов'язкові поля). І окремо — побічний ефект: чи справді змінився стан системи, тобто чи з'явився/зник запис у базі, а не тільки прийшла гарна відповідь. Статус і тіло перевіряють окремо, бо 200 каже лише, що запит дійшов і сервер не впав.
Чому статус 200 сам по собі не є доказом, що все працює?
Тому що статус повідомляє тільки долю транспорту — «запит дійшов, сервер не впав», а не коректність результату. Сервер може повернути 200 OK, а в тілі буде {"error": "insufficient funds"} або порожній список замість даних; UI відрендерить це як порожній екран, і тест «зелений». На рівні API ви бачите розбіжність статусу й тіла одразу, тому статус і вміст завжди перевіряють окремими асертами. Крайній випадок — GraphQL, який майже завжди відповідає 200, а реальні помилки кладе в масив errors: там орієнтуватися на статус безглуздо за побудовою.
Назвіть баг, який видно лише на рівні API.
Це улюблене питання, і конкретний приклад цінується вище за визначення. Класика: 200 OK, а всередині {"error": ...} — UI малює порожній екран, дефект невидимий. Зайві чутливі поля у відповіді — ендпоінт користувача віддає ще й passwordHash чи isAdmin, фронтенд їх просто не рендерить, тож витік лишається непоміченим. Валідація лише на фронтенді — форма не пускає від'ємну кількість, а сам ендпоінт приймає qty: -5 без питань, бо реальний зловмисник шле запит повз браузер. Доступ до чужих даних перебором id — запит GET /api/orders/123 з чужим номером повертає не ваше замовлення, бо власника ніхто не звірив (клас IDOR/BOLA). Гонки й неідемпотентність — два однакові POST створюють два замовлення, повторний платіж проходить двічі. Неправильні статус-коди — на неіснуючий ресурс 200 замість 404. Через цей клас дефектів API-тестування і є обов'язковою навичкою, а не бонусом.
Чому «UI не пускає невалідні дані» не означає, що бекенд захищений?
Бо валідація часто живе лише на фронтенді, а прямий запит проходить повз неї. Форма блокує від'ємну кількість, порожнє обов'язкове поле чи надто довгий рядок — але це перевірка в браузері, і сам ендпоінт може приймати qty: -5 без питань. Реальний зловмисник (та й будь-який клієнт, що б'є в API напряму) обходить форму й шле сирий запит, тому негативні перевірки треба слати повз UI: curl або код прямо до ендпоінта з невалідним тілом. Добре спроєктований сервер на кривий ввід має свідомо відповісти 400/422, а не мовчки прийняти сміття й не впасти з 500.
Які бувають типи API і чому їх тестують по-різному?
Мінімальний набір для співбесіди — п'ять типів. REST — найпоширеніший стиль: система як набір ресурсів за URL, методи HTTP несуть дію, статус-коди несуть сенс; на нього спирається більшість розділу. SOAP — старіший і формальніший обмін XML-конвертами за строгим описом WSDL, живе в банках і телекомі. GraphQL — один ендпоінт, клієнт сам описує потрібні поля запитом; майже завжди відповідає 200, а помилки кладе в масив errors, тому статус там не показник. gRPC — бінарний формат (Protobuf) поверх HTTP/2, у браузері як JSON не подивишся, потрібен grpcurl або код. Вебхуки (webhooks) — напрям зворотний: не ви шлете запит, а зовнішня система сама стукає HTTP-запитом на ваш URL, коли стається подія, тож для перевірки потрібен не клієнт, а приймач. Різний транспорт, формат і напрям означають різні інструменти й різні пастки.
Чим тестують API — від curl до коду?
Інструменти вишиковуються в природну сходинку від разового пострілу до автоматизації. curl — консольний HTTP-клієнт майже на кожній машині, ідеальний, щоб швидко перевірити один запит або вкласти точну команду відтворення в баг-репорт; прапорець -i покаже статус і заголовки. Postman — найпопулярніший графічний клієнт: колекції, змінні оточення, вбудовані скрипти-перевірки, командна робота; з нього зручно починати ручне дослідження. Insomnia — легша альтернатива з тією ж ідеєю, Bruno зберігає колекції звичайними файлами прямо в репозиторії, тож їх видно в git поряд із кодом. Верхня сходинка — код: коли перевірок десятки, вони мають ганятися в CI на кожен коміт і сплітатися в ланцюжки (створити → прочитати → видалити), і тоді запити пишуть у тестовому фреймворку (у Playwright для цього є APIRequestContext). Інструмент вибирають під задачу, а не «раз і назавжди».
Коли API-тестування роблять руками, а коли автоматизують?
Руками (через Postman чи curl) — коли швидкість зворотного зв'язку важливіша за повторюваність: ви вивчаєте незнайомий API, робите дослідницьку перевірку гіпотези, відтворюєте баг або ганяєте одноразовий сценарій. Автоматизація виправдана там, де перевірки треба повторювати часто й однаково: регресія, ланцюжки CRUD, запуск на кожен коміт у CI, перебір десятків комбінацій параметрів. API-рівень узагалі найвдячніший кандидат на автоматизацію — такі тести бігають швидко і не залежать від змін верстки. На практиці режими не конкурують: ручний прогін народжує перевірку, а в код іде те, що мусить ганятися постійно.
Чому «поле не показується в застосунку» не означає, що його немає у відповіді?
Бо фронтенд рендерить лише те, що йому потрібно, і мовчки ігнорує решту полів відповіді. Ендпоінт може повертати passwordHash, isAdmin, внутрішні прапорці чи чужі персональні дані — на екрані їх немає, але в сирому тілі вони є, і це готовий витік даних. Такий дефект неможливо побачити очима через UI; його ловлять, лише дивлячись на саму відповідь — у DevTools на вкладці Network, у curl -i чи в асерті автотесту на точний набір полів. Тому правило просте: перевіряйте сире тіло, а не екран, і асертьте не тільки наявність потрібних полів, а й відсутність зайвих.
Що таке IDOR і чому цей баг видно тільки на рівні API?
IDOR (insecure direct object reference; в OWASP API Security Top 10 той самий клас зветься BOLA — broken object level authorization) — це коли сервер віддає ресурс за прямим ідентифікатором, не перевіряючи, що запитувач є його власником. Практично: GET /api/orders/123 повертає чуже замовлення, бо перевірка авторизації на цьому ендпоінті пропущена — токен валідний, отже «пускаємо», а чий саме це запис, ніхто не звірив. З UI такого сценарію не відтворити: у чужого користувача просто немає посилання на цей ресурс, кнопки нема, шлях недосяжний. А прямим запитом він тривіальний — міняєш один id у URL і перебираєш. Тому перевіряти доступи треба саме на рівні API: залогінитися одним користувачем і спробувати дістати ресурс іншого, очікуючи 403 (або 404, якщо контракт навмисно ховає існування).
Ви шлете два однакові POST підряд — і створюються два замовлення. Про що це і як тестувати?
Це проблема ідемпотентності: повторення операції, яка мала виконатися один раз, дає повторний ефект — два замовлення, подвійне списання, дубль платежу. Крізь UI такий сценарій відтворити важко (треба дуже швидко клікнути двічі або зловити гонку), а прямими запитами тривіально: шлеш той самий POST двічі й дивишся, чи не з'явився дубль. Захист від цього — ключ ідемпотентності (Idempotency-Key) або серверна дедуплікація, і саме їх перевіряє негативний тест: два ідентичні запити з тим самим ключем мають дати один запис, а другий — повернути той самий результат, а не створити новий. Це та категорія багів, заради якої QA спускається під UI.
Чому неправильний статус-код (200 замість 404, 404 замість 403) — це дефект, хоча користувач різниці не бачить?
Тому що статус-код — частина контракту з клієнтом API, а не косметика для людини. Для користувача 200 на неіснуючий ресурс і 404 виглядають однаково (порожній екран), але клієнт-програма приймає рішення саме за кодом: 4xx — виправ запит, 5xx — сервер зламався, 404 — ресурсу немає, 401 — залогінься, 403 — прав немає. Неправильний код ламає цю логіку: клієнт не зробить ретрай, не покаже правильну помилку, не залогінить користувача заново. Окремий нюанс — безпека: 404 замість 403 буває свідомим рішенням контракту, щоб не розкривати сам факт існування закритого ресурсу, тож перед асертом на код відмови треба звірятися з контрактом конкретного API, а не з підручником.
Три ситуації з робочого життя QA, де рівень перевірки вирішує, чи побачите ви дефект: зелений UI поверх помилки в тілі, вибір рівня й інструмента під конкретну задачу, і доступ до чужих даних перебором id, який через екран не відтворити. Скрізь — що дивитися і чому.
Кейс 1. 200 OK, а всередині помилка
Тестувальниця оформлює замовлення через сайт: форма проходить, з'являється «Дякуємо за замовлення!», екран щасливий. Але в адмінці замовлення немає. Через UI все виглядає справним — і саме тут ховається дефект. Дивимося не на екран, а на сирий обмін: вкладка Network у DevTools або той самий запит через curl.
curl -i -X POST https://api.example.com/orders \
-H "Authorization: Bearer TOKEN" \
-H "Content-Type: application/json" \
-d '{"productId": 42, "qty": 2}'
HTTP/1.1 200 OK
Content-Type: application/json
{"success": false, "error": "insufficient stock", "orderId": null}
Сервер повернув 200, і браузер чесно відрендерив «успіх» — фронтенд глянув лише на статус, а не на тіло. Насправді замовлення не створилося (orderId: null), товару не вистачило на складі. У коді ту саму розбіжність ловить один асерт:
test('POST /orders: успіх підтверджують і статус, і тіло', async ({ request }) => {
const res = await request.post('/api/orders', {
data: { productId: 42, qty: 2 },
});
const body = await res.json();
// статус і тіло перевіряємо ОКРЕМО — 200 ще не означає створення
expect(res.status(), 'створення має відповідати 201').toBe(201);
expect(body.orderId, 'замовлення не створилося — orderId порожній').toBeTruthy();
});
Що дивитися і чому:
- Статус і тіло — це різні перевірки.
200каже лише, що запит дійшов і сервер не впав. Тут дефект подвійний: бекенд повертає200на бізнес-помилку (мав би409/422), а фронтенд орієнтується на статус замість тіла. Обидві складові варто зафіксувати в баг-репорті. - UI маскує розбіжність. Порожній екран, «успіх» без результату, зниклі дані — усе це на екрані виглядає невинно. На рівні API невідповідність статусу й тіла видно одразу.
curl -iдає точну команду відтворення. Її можна вкласти в баг прямо як є — розробник відтворить запит без здогадок про те, що саме натискала тестувальниця.
Кейс 2. Який рівень і який інструмент вибрати
Типова помилка початківця — усе перевіряти через браузер або, навпаки, повірити, що API-тести замінять UI. Рівні відповідають на різні питання; інструмент вибирають під задачу. Ось як розкладаються реальні перевірки з одного спринту:
| Задача | Рівень | Інструмент | Чому |
|---|---|---|---|
| Швидко глянути, що новий ендпоінт узагалі відповідає | API | curl | Разовий постріл, точна команда для баг-репорту |
| Дослідити незнайомий API, погратися з параметрами | API | Postman | Колекція, змінні оточення, ручні перевірки |
| Регресія CRUD-замовлення на кожен коміт | API | Код (Playwright) | Повторюваність, ланцюжки, запуск у CI |
| Перебрати 30 комбінацій знижок і меж | API | Код | Дешево й швидко там, де UI задихнеться |
| Чи показує форма помилку з бекенду користувачу | UI | Playwright (браузер) | Це видно лише через рендер |
| Чи не зламалася верстка сторінки оплати | UI | Playwright (браузер) | API верстки не бачить за побудовою |
Логіка вибору: спершу питаємо «на якому рівні живе те, що я перевіряю?». Бізнес-логіка й дані — вниз, на API; те, що бачить лише око користувача (верстка, показ помилки, прив'язка кнопки) — угору, на UI. Далі на рівні API питаємо «це разово чи постійно?»: разове дослідження — curl/Postman, постійна регресія — код. Правило «переносити перевірки якнайнижче» означає: усе, що можна закрити на API, не варто ганяти через браузер — але не навпаки.
Кейс 3. Доступ до чужих даних перебором id (IDOR)
Замовлення відкривається за адресою /api/orders/{id}. Питання, якого не поставить UI: а що буде, якщо залогінений користувач попросить чужий id? З інтерфейсу такого посилання немає — кнопки нема, шлях недосяжний. Прямим запитом він тривіальний: береш свій токен і перебираєш чужі номери.
import { test, expect, request as apiRequest } from '@playwright/test';
test('чуже замовлення недосяжне за прямим id', async () => {
// 1) логінимося користувачем A, дістаємо його токен і id його замовлення
const alice = await apiRequest.newContext({
baseURL: 'https://api.example.com',
extraHTTPHeaders: { Authorization: `Bearer ${ALICE_TOKEN}` },
});
const own = await alice.get('/api/orders/1001');
expect(own.status(), 'своє замовлення користувач бачить').toBe(200);
// 2) тим самим токеном A пробуємо дістати замовлення користувача B
const foreign = await alice.get('/api/orders/2002');
// сервер має відмовити, а не віддати чужі дані
expect(foreign.status(), 'чужий ресурс мусить бути закритий').toBe(403);
});
Що дивитися і чому:
- Валідний токен ≠ право на конкретний ресурс. Найчастіша діра: сервер перевіряє «ти взагалі залогінений?» і пускає, але не звіряє «а це твій запис?». Валідний токен користувача A не має відкривати замовлення B.
- Очікуваний код — за контрактом. Тут асерт на
403(прав немає). Але частина API навмисно віддає404на чужі ресурси, щоб не розкривати сам факт їх існування, — тоді асертити треба404. Перед перевіркою звіряйтеся з контрактом конкретного API, а не з «як має бути за підручником». - Це не відтворити через UI. У чужого користувача немає ні кнопки, ні посилання на цей ресурс — сценарій існує лише на рівні прямого запиту. Саме тому доступи перевіряють на API, а не через браузер.
API vs UI: суть і межа рівнів
- Можу пояснити, що тестувати API — це слати запити прямо до інтерфейсу між компонентами, в обхід браузера й кліків.
- Знаю, що UI-тест проходить увесь стек, а API-тест заходить одразу в бекенд, оминаючи фронтенд, і тому швидший, стабільніший, бачить сире тіло.
- Розумію, що API-тест і UI-тест відповідають на різні питання й ловлять різне: лише UI бачить зламану верстку, криву прив'язку кнопки й непоказану користувачу помилку; лише API — сире тіло, статуси й доступи.
Піраміда і вибір рівня
- Розумію, чому API-рівень — «золота середина» піраміди: швидший і стабільніший за E2E, але, на відміну від unit, ганяє систему зібраною; один тест покриває бізнес-сценарій, що через UI тягнув би десяток крихких кроків.
- Можу пояснити правило «переносити перевірки якнайнижче»: усе, що перевіряється на рівні API, не варто ганяти через UI.
Об'єкт перевірки на рівні API
- Можу перелічити п'ять зрізів: функціональність, дані, контракт, помилки, доступи.
- Знаю, що у відповіді перевіряють не лише статус, а й тіло (значення й типи полів), заголовки, схему і побічний ефект (чи змінився стан).
- Розумію, чому статус
200сам по собі — не доказ успіху: він каже лише, що запит дійшов і сервер не впав, а помилка може сидіти в тілі.
Типи API і чим їх тестувати
- Знаю різницю REST vs SOAP vs GraphQL vs gRPC vs вебхуки за транспортом, форматом і напрямом запиту.
- Можу пояснити, чому GraphQL майже завжди віддає
200, а помилки лежать у масивіerrors, тож статус там не показник. - Розумію, що вебхук — це вхідний запит до вас, тож для перевірки потрібен приймач, а не клієнт.
- Знаю, що gRPC бінарний (Protobuf поверх HTTP/2) і в браузері як JSON не подивишся — потрібен grpcurl або код.
Інструменти: від curl до коду
- Розумію сходинку інструментів:
curlдля разового пострілу й баг-репорту (-iпоказує статус і заголовки), Postman/Insomnia/Bruno для дослідження (Bruno тримає колекції файлами в git — зручно для рев'ю), код для регресії в CI. - Можу пояснити, коли переходити від графічного клієнта до коду: десятки перевірок, запуск на кожен коміт, ланцюжки CRUD.
- Знаю, що в Playwright для API-запитів є
APIRequestContext(request.post(...)), і що підглянути реальний запит застосунку можна у вкладці Network DevTools.
Ручне vs автоматизація
- Можу пояснити, коли API тестують руками (незнайомий API, дослідження, відтворення бага, разовий сценарій), а коли автоматизують (регресія, CRUD-ланцюжки, CI, перебір параметрів).
- Розумію, що ці режими не конкурують: руками формулюють перевірку, у код переносять те, що варто стерегти постійно.
Баги, які видно лише на рівні API
- Знаю приклад
200 OKз помилкою в тілі й можу пояснити, чому UI ховає цей дефект. - Розумію ризик зайвих чутливих полів у відповіді (
passwordHash,isAdmin), яких фронтенд не рендерить, і валідацію лише на фронтенді — тому негативні перевірки шлють повз UI. - Знаю IDOR/BOLA (доступ до чужих даних перебором id), а також гонки й неідемпотентність (два
POST→ два замовлення) і неправильні статус-коди як окремі класи багів рівня API.
Чим API-тест принципово відрізняється від UI-тесту?
Питання
Що означає «тестувати API»?