Postman: скрипти, data-driven, Newman
Зміст
Попередня глава навчила збирати запити, тримати змінні й додавати перевірки зі сніпетів. Але доти запити були поодинокими: надіслав — глянув очима — забув. Реальні API-сценарії влаштовані інакше: це ланцюжки. Залогінься → отримай токен → створи замовлення → перевір, що воно зʼявилось зі статусом pending. Кожен наступний крок залежить від відповіді попереднього. А ще той самий сценарій хочеться прогнати не раз, а на десятку наборів даних — валідні, невалідні, крайові. І бажано, щоб усе це крутилось у CI без людини за кермом. Для цих трьох задач у Postman є три інструменти: скрипти, data-driven прогін і Newman. Про них ця глава.
Якщо ти при першому проході ще не працюєш з автоматизованими прогонами, цю главу можна відкласти й повернутися перед співбесідою — про Newman і ланцюжки питають часто, особливо коли у вакансії стоїть «Postman + CI». Разом з тим це канонічний виклад теми на сайті: інші глави, торкаючись скриптів чи Newman, посилаються сюди. Базу — колекції, рівні змінних, спадкування авторизації — вважаємо пройденою в главі «Postman: основи».
Скрипти: pre-request і post-response
Postman дозволяє вставити JavaScript у дві точки життєвого циклу кожного запиту:
- pre-request script (пре-реквест скрипт) — виконується до надсилання. Тут готують запит: обчислюють підпис, генерують унікальний email, підставляють поточний timestamp, дописують заголовок.
- post-response script (пост-респонс скрипт; у старих версіях вкладка називалась Tests) — виконується після отримання відповіді. Тут перевіряють відповідь і витягують із неї дані для наступного кроку.
Важлива межа, яку часто недооцінюють: скрипти виконуються не в повноцінному Node.js, а в пісочниці (sandbox) — обмеженому середовищі з фіксованим набором вбудованих бібліотек. Асерти дає Chai, є Lodash, доступні деякі утиліти для криптографії й роботи з датами. Але звернутися до файлової системи чи поставити пакет так, як у проєкті на диску, не вийде — це керований рантайм, а не тестовий фреймворк (новіші версії Postman уміють підтягувати публічні пакети з npm через власний механізм пакетів, але в Newman він не працює). Уся взаємодія з Postman іде через глобальний обʼєкт pm (звідси pm.test, pm.response, pm.environment — усе, що ми бачили в главі про основи).
Скрипти можна писати на трьох рівнях — колекції, папки й запиту, — і вони виконуються від ширшого до вужчого: спершу pre-request колекції, потім папки, потім самого запиту; після відповіді — так само post-response колекції → папки → запиту. Практичний наслідок: спільну підготовку (наприклад, оновлення токена перед кожним запитом) кладуть на рівень колекції один раз, а не копіюють у кожен запит.
Ланцюжки запитів через змінні
Ось де скрипти показують справжню цінність. Відповідь одного запиту майже завжди містить дані, потрібні наступному: створив користувача — сервер повернув його id, і цей id треба підставити в URL наступного запиту GET /users/{id}. Захардкодити його не можна — на кожному прогоні він новий. Розвʼязання: у post-response скрипті витягти значення з відповіді й записати у змінну, а наступний запит читає цю змінну через {{...}}.
// post-response запиту Login
pm.test("Логін віддав токен", () => {
const body = pm.response.json();
pm.expect(body.token).to.be.a("string");
pm.collectionVariables.set("token", body.token);
});
Тепер будь-який наступний запит використовує Bearer {{token}} в авторизації — і токен «підʼїжджає» сам.
Тут є пастка зі скоупами, яка коштує новачкам години дебагу. Для запису стану ланцюжка потрібен персистентний скоуп — pm.collectionVariables.set або pm.environment.set: такі змінні переживають до наступного запиту в прогоні. А ось pm.variables.set створює локальну змінну: вона ніде не зберігається й при надсиланні запитів по одному — а саме так ланцюжок збирають і дебажать — зникає одразу після поточного запиту. У межах одного прогону раннера локальні змінні формально доживають до його кінця, але будувати на них стан ланцюжка не варто: поза раннером він розсипається. Симптом плутанини класичний: «я ж записав userId, чому в наступному запиті він порожній?» — бо записав не в той скоуп.
Друга, глибша річ, яку варто розуміти: ланцюжок — це порядок-залежні запити. Запит Get orders не має сенсу, якщо перед ним не відпрацював Login, бо {{token}} ще не заповнений. Це зручно для наскрізного сценарію, але робить окремий запит невідтворюваним у відриві від ланцюжка й ускладнює ізоляцію. Та сама проблема порядку й спільного стану — наскрізна для автоматизації взагалі; докладніше про залежність тестів і підготовку стану — у главі «Тестові дані та стан в API-тестах».
Collection Runner і data-driven
Досі один запит перевіряв один випадок. Але техніки тест-дизайну — класи еквівалентності, граничні значення — вимагають прогнати той самий запит на багатьох входах: валідний email, порожній, без @, наддовгий, з unicode. Копіювати запит десять разів — марно. Правильний шлях — data-driven прогін: один запит, зовнішня таблиця входів, ітерація по рядках.
Робить це Collection Runner — вбудований раннер, який жене колекцію (або папку) запитами по порядку. Йому можна згодувати файл даних (data file) у двох форматах:
| Формат | Структура | Коли зручніший |
|---|---|---|
| CSV | Перший рядок — імена змінних, далі рядок = ітерація | Табличні набори, легко вести в таблиці |
| JSON | Масив обʼєктів, кожен обʼєкт = ітерація | Вкладені дані, масиви, необхідні типи |
Runner робить одну ітерацію на рядок файлу. Значення з поточного рядка доступні або прямо в запиті через {{email}}, або в скрипті через pm.iterationData.get("email"). Ключовий прийом — покласти в таблицю не лише вхід, а й очікуваний результат, і звіряти з ним у пості:
email,password,expectedStatus
qa@example.com,Valid123!,200
qa@example.com,wrong,401
not-an-email,Valid123!,422
,Valid123!,422
// post-response, той самий для всіх ітерацій
pm.test(`Статус відповідає очікуваному для ${pm.iterationData.get("email")}`, () => {
const expected = Number(pm.iterationData.get("expectedStatus"));
pm.expect(pm.response.code).to.eql(expected);
});
Один запит, чотири рядки — чотири незалежні кейси в одному прогоні, і кожен підписаний своїм входом у звіті. Це прямий місток від таблиць тест-дизайну до виконання; як добирати самі рядки для API — у главі «Тест-дизайн для API: CRUD, параметри, межі».
Одне застереження: ітерації йдуть послідовно й ділять персистентні змінні. Якщо в колекції одночасно живуть ланцюжок ({{token}} зі спільного скоупу) і data-driven таблиця, друга ітерація бачить те, що лишила перша. Іноді це те, що треба; частіше — джерело плутанини. Тримай стан ланцюжка й дані ітерації свідомо роздільно.
Mock-сервери та examples
Postman вміє не лише надсилати запити, а й відповідати на них. Основа тут — збережені приклади (examples), з якими ми познайомились у главі про основи: пара «запит + відповідь», прикріплена до запиту. Mock-сервер (mock server) — це хмарний ендпоінт, який Postman піднімає на основі цих прикладів: він приймає запит, підбирає відповідний приклад за методом і шляхом і віддає збережені статус, заголовки й тіло.
Навіщо це QA й команді:
- Фронтенд раніше за бекенд. Ендпоінта ще нема, а контракт узгоджений — мок дає фронтенду по чому працювати вже сьогодні.
- Важковідтворювані відповіді. Змусити реальний сервіс віддати
500, специфічну помилку чи екзотичний крайовий формат буває складно; збережений приклад віддає його на замовлення. - Стабільні дані для тестів. Мок не залежить від стану БД і не «пливе» між прогонами.
Головне обмеження чесно назвати одразу: мок не має логіки. Він віддає те, що ти в нього поклав, і не перевіряє тіло запиту, не рахує, не змінює стан. Тому мок годиться, щоб перевірити форму взаємодії й розблокувати роботу, але не поведінку. Це вбудований, найлегший рівень мокання; серйозніше мокання на рівні сервісів (WireMock, msw) і генерація мока зі специфікації (Prism з OpenAPI) — окрема велика тема глави «Мокання залежностей: стаби, mock-сервери, вебхуки», а специфікація як джерело такого мока — у главі «OpenAPI/Swagger: специфікація як джерело істини».
Newman: запуск колекції в CI
Collection Runner — це кнопка в застосунку, потрібна людина. Щоб та сама колекція крутилась автоматично — на кожен push, щоночі, у пайплайні, — існує Newman: офіційний CLI-раннер колекцій Postman, npm-пакет. Ідея проста: усе, що робить Runner в UI, Newman робить із командного рядка, а отже, його можна поставити в CI.
Робочий цикл: експортуєш колекцію та середовище як JSON, комітиш їх у репозиторій — і запускаєш.
# базовий прогін колекції з середовищем і файлом даних
newman run collection.json -e staging.postman_environment.json -d data.csv
Кілька прапорців, які варто знати:
-e— файл середовища (environment) з базовим URL і змінними стенда.-d— файл даних для data-driven прогону (той самий CSV/JSON, що й у Runner).-n— кількість ітерацій (скільки разів прогнати колекцію); працює і з файлом даних, і без нього.-r— репортери: вбудованіcli,json,junit; популярнийhtmlextraставиться окремим пакетом і дає читабельний HTML-звіт.
Найважливіше для CI — код виходу (exit code). Якщо хоч один pm.test упав або запит не дійшов, Newman завершується з ненульовим кодом. Саме це перетворює колекцію на гейт: CI-крок падає, і пайплайн не пускає зміну далі. Без цього прогін нічого не «тримає» — тести крутяться, але нікого не зупиняють.
Механіка самого пайплайна — де саме поставити цей крок, як передавати артефакти між стадіями, коли ганяти smoke, а коли повний набір — належить розділу про Git і CI/CD; тут важливо лише, що Newman дає рівно те, що CI вміє споживати: команду й код виходу. Секрети (токени в env.json) при цьому не комітять — їх передають через змінні CI або прапорець --env-var, бо закомічений токен чи токен у логах прогону — це витік.
Межі Postman: коли починається код
Postman масштабується далеко, але не нескінченно. Він найсильніший у дослідженні API, ручних перевірках, швидкій регресії без інфраструктури й у тому, щоб поділитися сценарієм з людиною, яка не пише код. Ознаки, що ти вперся в стелю:
- Складна логіка й повторюване використання. Спільні хелпери, які треба тягати між колекціями, обростають копіпастом — пісочниця не дає нормального модуля.
- Code review тестової логіки. Колекція — це один великий JSON; дифи в пул-реквесті нечитабельні, і рецензент фізично не бачить, що змінилось у тесті.
- Типізація, IDE, дебаг. Немає автодоповнення по схемі відповіді, немає нормальних брейкпоінтів, помилку в скрипті ловиш лише на прогоні.
- Інтеграція з рештою. Підготувати стан через БД, перевикористати ті самі клієнти, що й UI-тести, зібрати все в один звіт — у Postman це або незручно, або неможливо.
Коли ці болі починають переважати зручність — сценарій переносять у код: APIRequestContext у Playwright, supertest, звичайний fetch/axios. Той самий ланцюжок «логін → токен → захищений запит» у коді виглядає так:
// той самий ланцюжок у Playwright: логін → токен → захищений запит
const login = await request.post('/auth/login', { data: { email, password } });
const { token } = await login.json();
const orders = await request.get('/orders', {
headers: { Authorization: `Bearer ${token}` },
});
expect(orders.status()).toBe(200);
Тут є типи, версіонування по рядках, спільні фікстури й повний дебаг — усе, чого пісочниці бракує. Це не «Postman гірший»: це різні інструменти під різні задачі, і зрілий кандидат називає межу, а не доводить, що «Postman вміє все». Як влаштовані API-клієнти в коді, структура й ланцюжки — у главі «API-автотести в коді: клієнти, структура, ланцюжки», а огляд усього інструментарію — у главі «API-тестування: обʼєкт перевірки, рівні й інструменти». На практиці команди часто тримають і те, й те: Postman для дослідження й ручних перевірок, код — для автоматизованого набору.
Типові помилки
- Виглядає як «змінна порожня в наступному запиті», а насправді не той скоуп. Значення записали через
pm.variables.set— локальну змінну, яка при ручному надсиланні запитів не доживає до наступного. Для ланцюжка потрібенpm.environment.setабоpm.collectionVariables.set. - Виглядає як «Newman зелений, отже, все добре», а насправді порожні перевірки.
pm.testбез жодного асерта завжди passed; прогін зелений, а нічого не перевіряється. Кожна перевірка має містити хоча б одинpm.expect— і не лише на статус, а й на тіло (пастка «200 OK з помилкою в тілі» розібрана в главі «Перевірки відповіді»). - Виглядає як «CI не реагує на фейл», а насправді загублений exit code. Крок обгорнули в
|| true, або скрипт-обгортка губить код виходу Newman — пайплайн зелений попри червоні тести. Гейт тримає лише ненульовий код виходу. - Виглядає як «стабільний ланцюжок», а насправді порядок-залежність. Запусти запит із середини колекції окремо — падає, бо
{{token}}ще не заповнений. Data-driven зі спільними змінними множить це: ітерації бачать стан одна одної. - Виглядає як «мок = бекенд», а насправді статичний приклад. Mock-сервер віддає збережену відповідь і не має логіки: не читає тіло запиту, не змінює стан. Ним перевіряють форму взаємодії, не поведінку.
- Виглядає як «токен у Newman безпечний», а насправді витік.
env.jsonз реальним токеном закомічено в git або токен світиться в логах прогону. Секрети — через CI-змінні чи--env-var, ніколи в репозиторій.
Підсумок
- Скрипти працюють у двох точках — pre-request готує запит, post-response перевіряє відповідь і витягує з неї дані — і виконуються в обмеженій пісочниці через обʼєкт
pm, не в повному Node. - Ланцюжки будуються на персистентних змінних (
environment/collection), а не на локальнихpm.variables; їхня ціна — порядок-залежність запитів. - Collection Runner з файлом CSV/JSON дає data-driven прогін: один запит, багато рядків-кейсів, у кожному рядку — вхід і очікуваний результат.
- Newman виносить колекцію в CI; гейт тримає саме ненульовий код виходу, а не сам факт прогону.
- Postman масштабується до межі; складна логіка, code review й типізація — сигнал переносити сценарій у код.
Що питають на співбесіді
- «Як у Postman передати дані з однієї відповіді в наступний запит?» — очікують: у post-response витягти значення через
pm.response.json(), записати в персистентну змінну (pm.collectionVariables.set/pm.environment.set), використати в наступному запиті через{{...}}. Плюс розуміння, чому саме персистентний, а не локальний скоуп. - «Чим відрізняються pre-request і post-response скрипти?» — перевіряють, чи розумієш ти момент виконання (до/після надсилання) і типове призначення кожного: підготовка запиту проти перевірки й екстракції.
- «Що таке data-driven тестування в Postman і як його зробити?» — Collection Runner + файл даних CSV/JSON, одна ітерація на рядок, доступ через
pm.iterationData; сильна відповідь звʼязує це з класами еквівалентності й межами. - «Що таке Newman і навіщо він?» — CLI-раннер колекцій для CI; ключове — код виходу як гейт і те, що колекція та середовище живуть у репозиторії як JSON.
- «Коли Postman перестає вистачати?» — питання на зрілість: інтервʼюер хоче почути чесну межу (складна логіка, review, типізація, інтеграція → код), а не «Postman вміє все».
Загальний фільтр тут — чи бачиш ти Postman як систему прогону (скрипти, скоупи, ітерації, гейт у CI), чи лише як «клікалку для запитів».
Джерела
- Postman Learning Center — Pre-request and post-response scripts — дві точки виконання, sandbox, рівні скриптів.
- Postman JavaScript reference (
pmAPI) —pm.response, скоупи змінних,pm.iterationData. - Postman Learning Center — Running collections with data files — Collection Runner, CSV/JSON, ітерації.
- Postman Learning Center — Mock servers — моки на основі examples і їх межі.
- Newman documentation — прапорці, репортери, запуск у CI.
Що таке pre-request і post-response скрипти в Postman і чим вони відрізняються?
Це дві точки, куди Postman дозволяє вставити JavaScript у життєвий цикл кожного запиту. Pre-request скрипт (пре-реквест) відпрацьовує до надсилання — його справа підготувати запит: порахувати підпис, згенерувати свіжий email чи timestamp, докинути заголовок. Post-response скрипт (пост-респонс; у старих версіях вкладка звалась Tests) відпрацьовує після відповіді — його справа перевірити її через pm.test і забрати з неї дані для наступного кроку. Проста мнемоніка: pre-request — «підготуй запит», post-response — «перевір і забери з відповіді». На співбесіді цінують саме розуміння моменту виконання (до/після) і типового призначення кожного, а не назви вкладок.
У якому середовищі виконуються скрипти Postman?
Не в повноцінному Node.js, а в пісочниці (sandbox) — обмеженому рантаймі з фіксованим набором вбудованих бібліотек: Chai для асертів (pm.expect), Lodash, трохи утиліт для криптографії й дат. Файлової системи немає, а пакети ставляться не як у проєкті на диску: новіші версії Postman уміють підтягувати публічні npm-пакети через власний механізм пакетів, але в Newman це не працює. Уся взаємодія з Postman іде через глобальний обʼєкт pm: pm.test, pm.response, pm.environment. Практичний наслідок цієї межі виринає пізніше — коли логіка ускладнюється, пісочниця не дає нормального модуля, і це один із сигналів переносити сценарій у код.
На яких рівнях можна писати скрипти і в якому порядку вони виконуються?
Скрипти живуть на трьох рівнях — колекція, папка, запит — і відпрацьовують від ширшого рівня до вужчого: pre-request у порядку колекція → папка → запит, а після відповіді post-response у тому самому порядку. Звідси практика: спільні речі на кшталт оновлення токена визначають один раз на рівні колекції, замість дублювати їх у кожному запиті. Це той самий принцип, що й спадкування авторизації: визначай спільне якнайвище, перевизначай точково нижче. Розуміння порядку рятує від дебагу «чому мій колекційний скрипт відпрацював раніше, ніж запитовий».
Як передати дані з відповіді одного запиту в наступний запит?
Це класичне питання про ланцюжки. Відповідь: у post-response скрипті витягнути значення з тіла через pm.response.json(), записати його в персистентну змінну (pm.collectionVariables.set або pm.environment.set), а наступний запит читає цю змінну через подвійні фігурні дужки — {{token}} в URL, заголовку чи тілі. Приклад: запит Login у пості дістає body.token і робить pm.collectionVariables.set("token", body.token), після чого будь-який наступний запит ставить Bearer {{token}} в авторизацію — і свіже значення підставляється саме. Захардкодити значення не можна: на кожному прогоні токен чи id новий. Сильна відповідь окремо пояснює, чому саме персистентний, а не локальний скоуп — це наступне питання.
У чому різниця між pm.variables.set і pm.collectionVariables.set / pm.environment.set?
У часі життя змінної, і саме тут новачки втрачають години дебагу. pm.collectionVariables.set і pm.environment.set пишуть у персистентний скоуп — значення гарантовано доступне наступним запитам, тому лише вони годяться для стану ланцюжка. pm.variables.set створює локальну змінну: вона ніде не зберігається й при надсиланні запитів по одному зникає одразу після поточного (у межах прогону раннера формально доживає до його кінця, але покладатися на це не варто — поза раннером ланцюжок розсипається). Типовий симптом: записав userId, а наступний запит бачить порожнє значення — запис пішов не в той скоуп. Правило для ланцюжка: усе, що потрібне наступному кроку, — тільки в environment або collection.
Що таке порядок-залежність ланцюжка і в чому її ціна?
Ланцюжок — це набір порядок-залежних запитів: Get orders без попереднього Login приречений, бо {{token}} ніхто не заповнив. Це зручно для наскрізного сценарію — один прогін проходить реальний шлях користувача, — але окремий запит уже не запустиш у відриві від ланцюжка, і з ізоляцією кейсів стає гірше. Запусти запит із середини колекції окремо — він упаде, і це не баг продукту, а конструкція ланцюжка. Та сама проблема порядку й спільного стану наскрізна для автоматизації взагалі. Зрілий кандидат називає це компромісом: наскрізний сценарій проти ізольованості кейсу, а не «зробив ланцюжок і забув».
Що таке data-driven тестування в Postman і як його зробити?
Це прогін того самого запиту на багатьох входах замість копіювання запиту десять разів. Робить його Collection Runner — вбудований раннер, який жене колекцію по порядку й приймає файл даних (data file) у форматі CSV або JSON. Runner робить одну ітерацію на рядок файлу; значення з поточного рядка доступні прямо в запиті через {{email}} або в скрипті через pm.iterationData.get("email"). Це прямий місток від технік тест-дизайну — класів еквівалентності й граничних значень — до виконання: кожен клас входу (валідний, порожній, зламаний формат, задовгий, unicode) стає окремим рядком таблиці. Сильна відповідь звʼязує data-driven саме з тест-дизайном, а не описує лише механіку кнопки.
Чим CSV відрізняється від JSON як файлу даних, і коли який зручніший?
Обидва задають набір ітерацій, але по-різному. CSV: перший рядок — імена змінних, кожен наступний рядок — одна ітерація; зручний для табличних наборів, які легко вести в таблиці. JSON: масив обʼєктів, кожен обʼєкт — одна ітерація; потрібен, коли дані вкладені, є масиви або важливі точні типи (число проти рядка). Груба евристика: плоскі колонки — бери CSV, вкладена структура чи типізація — бери JSON. Ключовий прийом для обох — покласти в таблицю не лише вхід, а й очікуваний результат (наприклад, колонку expectedStatus) і звіряти з ним у пості. Тоді кожна ітерація — самодостатній кейс, підписаний своїм входом у звіті.
Що відбувається зі спільними змінними, коли data-driven крутить кілька ітерацій?
Персистентні змінні спільні для всього прогону: ітерації йдуть одна за одною, і кожна наступна бачить усе, що записала попередня. Тож якщо в колекції одночасно живуть ланцюжок ({{token}} зі спільного скоупу) і data-driven таблиця, стан першої ітерації протікає в другу. Іноді це саме те, що треба (наприклад, залогінитись один раз і гнати ітерації під одним токеном), але частіше це джерело плутанини: кейс проходить чи падає залежно від того, що натворила попередня ітерація. Лікування — свідомо тримати стан ланцюжка й дані ітерації роздільно, не змішувати «наскрізний токен» і «незалежні кейси» в одній сутності. Це та сама тема ізоляції стану, що переслідує автотести взагалі.
Що таке mock-сервер у Postman і на чому він базується?
Mock-сервер (mock server) — хмарний ендпоінт, який Postman піднімає на основі збережених прикладів (examples): пар «запит + відповідь», прикріплених до запиту. Він приймає запит, підбирає відповідний приклад за методом і шляхом і віддає збережені статус, заголовки й тіло. Навіщо це QA: розблокувати фронтенд, коли ендпоінт ще не написаний, а контракт уже узгоджений; діставати на замовлення відповіді, яких від реального сервісу важко домогтися (500, специфічні помилки, крайові формати); мати передбачувані дані, незалежні від стану БД між прогонами. Це найлегший, вбудований рівень мокання — серйозніше (WireMock, msw, Prism з OpenAPI) виходить за межі Postman.
Яке головне обмеження mock-сервера, і що ним можна перевіряти?
Головне обмеження назвати треба одразу: мок не має логіки. Він просто повертає покладений у нього приклад: тіла запиту не читає, обчислень не робить, стану не змінює. Тому мок годиться, щоб перевірити форму взаємодії — чи фронтенд правильно шле запит і розбирає відповідь узгодженого вигляду, — але не поведінку: жодної бізнес-логіки за ним немає. Типова пастка формулюється як «мок = бекенд, отже, інтеграцію перевірено», а насправді перевірено лише статичний приклад. Зрілий підхід: моком розблоковують роботу й тестують контракт форми, а поведінку — вже проти реального сервісу чи серйознішого стабу.
Що таке Newman і навіщо він потрібен?
Newman — офіційний CLI-раннер колекцій Postman, npm-пакет. Ідея проста: усе, що робить Collection Runner в UI, Newman робить із командного рядка, а отже, його можна поставити в CI й крутити колекцію автоматично — на кожен push, щоночі, у пайплайні, без людини за кермом. Робочий цикл: експортуєш колекцію та середовище як JSON, комітиш їх у репозиторій і запускаєш, наприклад newman run collection.json -e staging.postman_environment.json -d data.csv. Ключове в сильній відповіді — не сам факт CLI, а що колекція та середовище живуть у репозиторії як JSON і що прогін віддає код виходу, придатний для гейта. Саме код виходу перетворює колекцію на CI-крок, який щось «тримає».
Які прапорці Newman варто знати?
Мінімальний робочий набір: -e — файл середовища (environment) з базовим URL і змінними стенда; -d — файл даних для data-driven прогону (той самий CSV/JSON, що й у Runner); -n — кількість ітерацій, скільки разів прогнати колекцію (працює і з файлом даних, і без нього); -r — репортери. Вбудовані репортери — cli, json, junit; популярний htmlextra ставиться окремим пакетом і дає читабельний HTML-звіт. junit особливо цінний у CI, бо його розбирають більшість систем і показують результати тестів у своєму UI. Знати ці чотири прапорці достатньо, щоб зібрати робочу CI-команду; решта — деталі під конкретний пайплайн.
Чому для CI найважливіший код виходу Newman?
Бо саме код виходу (exit code) перетворює колекцію на гейт. Падіння будь-якого pm.test чи недоступний запит дає ненульовий код — CI-крок червоніє, і пайплайн не пускає зміну далі. Без цього прогін нічого не «тримає»: тести крутяться, але нікого не зупиняють. Класична пастка — крок обгорнули в || true або скрипт-обгортка губить код виходу Newman, і пайплайн зелений попри червоні тести; гейт тримає лише ненульовий код. Тому «Newman дає рівно те, що CI вміє споживати: команду й код виходу» — це і є суть інтеграції, а не звіти чи прапорці.
Як безпечно поводитися з секретами (токенами) у Newman і CI?
Секрети — реальні токени в env.json — не комітять у репозиторій. Їх передають через змінні CI або прапорець --env-var, бо закомічений токен чи токен, що світиться в логах прогону, — це витік. Пастка формулюється як «токен у Newman безпечний, бо це ж наш приватний репозиторій», а насправді історія git памʼятає все, і логи CI часто доступні ширшому колу. Правило: файл середовища в git тримають без чутливих значень (порожні плейсхолдери), а справжні значення підставляють на льоту з секрет-сховища CI. Це стандартна гігієна, і на співбесіді її очікують почути одразу, щойно згадали env.json у репозиторії.
Чому «Newman зелений» не завжди означає «все добре»?
Бо pm.test без жодного асерта завжди passed. Можна написати pm.test("перевірка", () => {}) без єдиного pm.expect — прогін зелений, а нічого не перевіряється. Кожна перевірка має містити хоча б один pm.expect, і не лише на статус, а й на тіло: пастка «200 OK з помилкою в тілі» ловиться лише тоді, коли асертиш вміст відповіді, а не сам код. Тому зелений Newman — необхідна, але не достатня умова; треба ще, щоб перевірки реально щось стверджували. Це те саме, що порожній тест у будь-якому фреймворку: він не червоніє не тому, що все працює, а тому, що ні про що не питає.
Коли Postman перестає вистачати і час переходити в код?
Це питання на зрілість — інтервʼюер хоче почути чесну межу, а не «Postman вміє все». Ознаки, що вперся в стелю: складна логіка й спільні хелпери, які обростають копіпастом, бо пісочниця не дає нормального модуля; потреба в code review тестової логіки — колекція це один великий JSON, дифи в пул-реквесті нечитабельні; брак типізації, автодоповнення й нормального дебагу — помилку в скрипті ловиш лише на прогоні; інтеграція з рештою — підготувати стан через БД, перевикористати ті самі клієнти, що й UI-тести, зібрати все в один звіт. Коли ці болі переважають зручність, сценарій переносять у код: APIRequestContext у Playwright, supertest, fetch/axios. Це не «Postman гірший» — це різні інструменти під різні задачі, і на практиці команди тримають і те, й те: Postman для дослідження й ручних перевірок, код — для автоматизованого набору.
Три кейси, що покривають хребет глави: ланцюжок «логін → токен → захищений запит» у скриптах Postman разом із пасткою скоупу; data-driven прогін одного запиту на таблиці входів з очікуваним результатом; і винесення того самого прогону в CI через Newman — з гейтом на коді виходу й тим самим ланцюжком, переписаним у Playwright, коли Postman стає тісно. Скрізь — що дивитися і чому.
Кейс 1. Ланцюжок у Postman і пастка скоупу
Сценарій наскрізний: залогінитись, забрати токен, сходити з ним у захищений ендпоінт. Токен щоразу новий, тож захардкодити його не можна — його треба витягти з відповіді Login у пості й покласти в персистентну змінну, щоб наступний запит прочитав її через {{token}}.
// post-response запиту Login
pm.test("Логін віддав токен", () => {
const body = pm.response.json();
pm.expect(body.token).to.be.a("string");
// персистентний скоуп — доживе до наступного запиту
pm.collectionVariables.set("token", body.token);
});
Наступний запит Get orders просто ставить у заголовок авторизації Bearer {{token}} — і токен «підʼїжджає» сам. Його пост перевіряє вже саму відповідь:
// post-response запиту Get orders
pm.test("Список замовлень доступний із токеном", () => {
pm.expect(pm.response.code).to.eql(200);
const body = pm.response.json();
pm.expect(body).to.be.an("array");
});
Що дивитися і чому:
- Скоуп вирішує все. Заміни запис на
pm.variables.set("token", body.token)— і при ручному проході ланцюжка, запит за запитом, отримаєш класичний симптом: «я ж записав токен, чому в наступному запиті{{token}}порожній?».pm.variables— локальна змінна: вона ніде не зберігається й зникає одразу після запиту Login. Для стану ланцюжка потрібен самеpm.collectionVariablesабоpm.environment— вони працюють однаково і в ручному проході, і в раннері. - Порядок-залежність — не баг, а конструкція. Запусти
Get ordersокремо, без Login перед ним, — він упаде з401, бо{{token}}не заповнений. Це нормально для наскрізного сценарію, але робить окремий запит невідтворюваним у відриві від ланцюжка. Перш ніж заводити баг «ендпоінт замовлень віддає 401», перевір, чи відпрацював логін. - Асерт має щось стверджувати.
pm.testз тілом, але без жодногоpm.expect, завжди зелений. Обидва тести вище перевіряють реальні інваріанти — тип токена й форму відповіді, — а не просто «запит дійшов».
Кейс 2. Data-driven: один запит, таблиця входів, очікуваний результат
Технік тест-дизайну — класи еквівалентності, межі — вимагають прогнати той самий запит логіну на десятку входів. Копіювати запит марно; правильний шлях — файл даних і Collection Runner, одна ітерація на рядок. Ключ — покласти в таблицю не лише вхід, а й очікуваний статус:
email,password,expectedStatus
qa@example.com,Valid123!,200
qa@example.com,wrong,401
not-an-email,Valid123!,422
,Valid123!,422
Один пост звіряє фактичний код із колонкою expectedStatus — той самий для всіх ітерацій:
// post-response, спільний для всіх рядків
pm.test(`Статус відповідає очікуваному для ${pm.iterationData.get("email")}`, () => {
const expected = Number(pm.iterationData.get("expectedStatus"));
pm.expect(pm.response.code).to.eql(expected);
});
Що дивитися і чому:
- Чотири рядки — чотири незалежні кейси в одному прогоні. Кожен підписаний своїм входом у назві тесту, тож у звіті одразу видно, який саме вхід упав. Це прямий місток від таблиці тест-дизайну до виконання: рядок таблиці = кейс.
Number(...)не випадковий. CSV не задає типів: чи приїдеexpectedStatusрядком"200", чи вже числом — залежить від раннера та його налаштувань, а строгийeqlрядок із числом не зведе. Явне приведення робить звірку детермінованою. JSON як файл даних цю неоднозначність знімає — тип у ньому заданий явно.- Стережися спільних змінних. Якщо в тій самій колекції живе ланцюжок із
{{token}}зі спільного скоупу, друга ітерація побачить токен, що лишила перша. Для чистого негативного набору логіну тримай дані ітерації й стан ланцюжка роздільно — інакше кейс проходить чи падає залежно від сусіда по таблиці.
Кейс 3. Newman у CI і межа, за якою починається код
Collection Runner — кнопка в застосунку, потрібна людина. Щоб та сама колекція крутилась на кожен push, її жене Newman із командного рядка. Експортуємо колекцію та середовище в JSON, комітимо (без секретів) і запускаємо:
# junit-репортер, щоб CI показав результати тестів у своєму UI
newman run collection.json \
-e staging.postman_environment.json \
-d data.csv \
-r cli,junit
Найважливіше для гейта — код виходу. Ось як крок легко зіпсувати:
# ПОГАНО: пайплайн завжди зелений, гейт не тримає
- run: newman run collection.json -e env.json || true
# ДОБРЕ: ненульовий код Newman валить крок і зупиняє пайплайн
- run: newman run collection.json -e env.json
Що дивитися і чому:
- Гейт тримає саме exit code, а не факт прогону. Якщо хоч один
pm.testупав, Newman завершується ненульовим кодом, крок червоніє, зміна не їде далі. Обгортка|| true(чи проковтнутий код виходу в скрипті) робить пайплайн зеленим попри червоні тести — прогін є, а гейта немає. - Секрети не в репозиторії.
env.jsonу git тримають із порожніми плейсхолдерами, а реальний токен підставляють на льоту через--env-var "token=$STAGING_TOKEN"зі секрет-сховища CI. Закомічений токен або токен у логах прогону — це витік, і історія git памʼятає його назавжди.
Коли логіка колекції розростається — спільні хелпери обростають копіпастом, дифи JSON у пул-реквесті нечитабельні, немає типів і дебагу — той самий ланцюжок переносять у код. У Playwright він виглядає так:
// той самий логін → токен → захищений запит, але в коді
test('замовлення доступні із токеном', async ({ request }) => {
const login = await request.post('/auth/login', {
data: { email: 'qa@example.com', password: 'Valid123!' },
});
expect(login.status()).toBe(200);
const { token } = await login.json();
const orders = await request.get('/orders', {
headers: { Authorization: `Bearer ${token}` },
});
expect(orders.status()).toBe(200);
});
Тут є типи, версіонування по рядках, спільні фікстури й повний дебаг — усе, чого пісочниці бракує. Це не «Postman гірший»: моком і колекцією досліджують API й ганяють швидку регресію без інфраструктури, а код тримає автоматизований набір. Зрілий інженер називає цю межу, а не доводить, що «Postman вміє все».
Скрипти й пісочниця
- Знаю різницю pre-request vs post-response: перший готує запит до надсилання, другий перевіряє відповідь і витягує з неї дані після.
- Можу пояснити, що скрипти виконуються не в повному Node.js, а в пісочниці (sandbox) з фіксованим набором бібліотек (Chai, Lodash, крипто/дати), без файлової системи й звичного npm install (пакети — лише через окремий механізм Postman, який не працює в Newman), і вся взаємодія з Postman іде через глобальний обʼєкт
pm(pm.test,pm.response,pm.environment). - Знаю три рівні скриптів (колекція → папка → запит) і що виконуються вони від ширшого до вужчого; спільну підготовку кладу на рівень колекції один раз.
Ланцюжки й скоупи змінних
- Можу описати схему ланцюжка: у пості
pm.response.json()→ запис у персистентну змінну → наступний запит читає її через{{...}}. - Знаю різницю
pm.variables.set(локальна, ніде не зберігається — при ручному надсиланні зникає після поточного запиту) vspm.collectionVariables.set/pm.environment.set(персистентні, гарантовано доступні наступним запитам); симптом «змінна порожня в наступному запиті» — майже завжди запис не в той скоуп, а не баг продукту. - Можу пояснити ціну ланцюжка — порядок-залежність: окремий запит із середини колекції падає, бо
{{token}}ще не заповнений.
Data-driven і Collection Runner
- Знаю, що Collection Runner робить одну ітерацію на рядок файлу даних (CSV/JSON), а значення беруться через
{{var}}абоpm.iterationData.get("var"). - Можу пояснити різницю CSV vs JSON як файлу даних: плоскі колонки проти вкладених структур і потрібних типів.
- Розумію ключовий прийом — класти в таблицю і вхід, і очікуваний результат (
expectedStatus) та звіряти з ним у пості. - Знаю, що ітерації ділять персистентні змінні: друга ітерація бачить стан першої — тримаю стан ланцюжка й дані ітерації роздільно.
- Можу звʼязати data-driven з тест-дизайном: класи еквівалентності й межі стають рядками таблиці.
Mock-сервери
- Розумію, що mock-сервер піднімається з examples (пар «запит + відповідь») і підбирає відповідь за методом і шляхом.
- Знаю головне обмеження: мок не має логіки — не читає тіло запиту, не рахує, не змінює стан; ним перевіряю форму взаємодії, не поведінку.
- Можу назвати, навіщо мок QA: фронтенд раніше за бекенд, важковідтворювані відповіді (
500, крайові), стабільні дані незалежно від БД.
Newman і CI
- Можу пояснити, що Newman — CLI-раннер колекцій; усе, що робить Runner в UI, він робить із командного рядка, тож ставиться в CI.
- Знаю базові прапорці:
-e(environment),-d(файл даних),-n(кількість ітерацій),-r(репортери:cli/json/junit, окремоhtmlextra). - Розумію, що гейт тримає саме ненульовий код виходу, а не факт прогону;
|| trueчи загублений exit code роблять пайплайн зеленим попри червоні тести. - Знаю, що
pm.testбез жодногоpm.expectзавжди passed — зелений Newman без асертів нічого не перевіряє (і асертити треба тіло, не лише статус). - Пам'ятаю про секрети: реальні токени не комітяться в
env.json, а йдуть через CI-змінні чи--env-var— закомічений токен або токен у логах це витік.
Межі Postman
- Можу назвати чесну межу, за якою сценарій переносять у код (
APIRequestContext,supertest,fetch): складна логіка й хелпери, code review тестів, типізація/IDE/дебаг, інтеграція з БД і спільними клієнтами — це різні інструменти під різні задачі, і команди часто тримають і те, й те.
Коли виконується pre-request скрипт?
Питання
Pre-request vs post-response скрипт — коли виконується кожен і навіщо?