Термінал і Linux для QA
Зміст
Рано чи пізно кожен QA впирається в чорне вікно з курсором. Логи застосунку лежать на віддаленому сервері, куди немає доступу через браузер. CI-агент, на якому впав автотест, — це Linux-машина без графічного інтерфейсу, і єдиний спосіб зрозуміти, що сталося, — прочитати її вивід у терміналі. Тестовий стенд треба перезапустити, скинути файл із даними або підглянути, чи взагалі жива служба. Усе це робиться командами, а не мишкою.
Ця глава — фундамент CLI, який далі мовчки припускають розділи про Docker, CI-інструменти та автотести в пайплайні. Це глава-поглиблення: якщо ви тільки будуєте базу мануального тестування, її можна пропустити при першому проході й повернутися, коли зʼявляться перші автотести або доступ до стенду. Але щойно ви торкаєтеся автоматизації чи CI — без терміналу далі буде важко: половина відповідей на питання «чому впало» живе саме там.
Bash-мінімум: навігація, файли, права, процеси
Bash (Bourne Again Shell) — це і командний інтерпретатор, і мова. Коли ви пишете команду й тиснете Enter, оболонка (shell) розбирає рядок, знаходить програму, запускає її та показує результат. На macOS дефолтна оболонка тепер zsh, але базовий синтаксис у них спільний, тож усе нижче працює однаково.
Навігація
Файлова система Linux — це дерево з коренем /. Ви завжди перебуваєте в якійсь директорії (робочій), і команди виконуються відносно неї.
pwd # print working directory — де я зараз
ls # список файлів
ls -la # усі файли (навіть приховані на крапку) + деталі
cd /var/log # перейти в директорію за абсолютним шляхом
cd .. # на рівень вгору
cd ~ # у домашню директорію
cd - # назад у попередню директорію
Різниця між абсолютним шляхом (/var/log/app.log, від кореня) і відносним (./app.log, від поточної директорії) — не дрібниця. У CI-скриптах відносний шлях залежить від того, звідки джоб запустив команду, і це класичне джерело «локально працює, у пайплайні файл не знайдено».
Файли
cat file.txt # вивести весь файл
less file.txt # гортати великий файл (вихід — q)
head -n 20 file # перші 20 рядків
cp src dst # копіювати
mv old new # перемістити або перейменувати
rm file # видалити (без «кошика» — назавжди)
mkdir -p a/b/c # створити дерево директорій
find . -name "*.log" # знайти файли за маскою
Головне застереження: rm не питає й не має відкату. rm -rf на неправильній директорії — це втрачені дані без надії на Ctrl+Z. На спільних стендах будьте обережні так само, як з DELETE без WHERE у базі.
Права доступу
Кожен файл має власника, групу і три набори прав: для власника, групи й решти. У виводі ls -l це перший стовпчик:
-rwxr-xr-x 1 qa staff 1240 Jul 18 10:00 run-tests.sh
Перший символ — тип (- файл, d директорія, l символьне посилання). Далі три трійки rwx: read (читати), write (писати), execute (виконувати). У прикладі власник може все (rwx), група й решта — читати й виконувати (r-x).
Права зручно задавати вісімковим числом, де r=4, w=2, x=1, а сума — набір на трійку:
chmod 755 run-tests.sh # rwx для власника, r-x для інших
chmod 644 config.json # rw- для власника, r-- для інших
chmod +x deploy.sh # просто додати право на виконання
Навіщо це QA: коли CI лається Permission denied на скрипті, майже завжди причина — забутий біт x. А коли автотест не може прочитати тестовий файл на стенді — дивіться права й власника, а не код тесту.
Процеси і сигнали
Кожна запущена програма — процес зі своїм номером (PID, process id). Тестувальнику це потрібно, щоб знайти завислий сервер, перевірити, чи слухається порт, або прибити застряглий процес перед новим прогоном.
ps aux # усі процеси системи
ps aux | grep node # відфільтрувати за назвою
top # процеси в реальному часі (вихід — q)
kill 4821 # ввічливо попросити процес 4821 завершитися
kill -9 4821 # вбити примусово
kill насправді не «вбиває», а надсилає сигнал. За замовчуванням це SIGTERM (номер 15) — ввічливе «заверши роботу», яке процес може перехопити й коректно закритися. kill -9 — це SIGKILL, який операційна система виконує сама: процес не може його ні перехопити, ні проігнорувати, тому дані можуть лишитися недописаними. Ctrl+C у терміналі — це SIGINT (переривання), а Ctrl+Z — призупинення процесу.
| Сигнал | Як надіслати | Що означає |
|---|---|---|
SIGINT (2) | Ctrl+C | Перервати, дати завершитися чисто |
SIGTERM (15) | kill PID | Ввічливе прохання завершитися |
SIGKILL (9) | kill -9 PID | Примусово, без шансу прибрати за собою |
Практичне правило: спершу SIGTERM, і лише якщо процес не реагує — SIGKILL. Звичка бити все kill -9 маскує те, що застосунок не вміє коректно вимикатися, а це буває справжнім багом.
Потоки, пайпи та перенаправлення
Це ідея, яка перетворює набір команд на інструмент. Кожна програма в Unix має три стандартні потоки: вхід (stdin), вивід (stdout) і потік помилок (stderr). За замовчуванням stdout і stderr ідуть на екран, але їх можна перенаправляти й зчіплювати.
Пайп (|) бере stdout однієї команди й подає його на stdin наступної — так будують ланцюжки. Перенаправлення пише потік у файл замість екрана.
grep ERROR app.log | wc -l # порахувати рядки з ERROR
cat app.log | grep 500 | tail -5 # останні 5 рядків з кодом 500
npm test > out.txt 2> err.txt # stdout у один файл, помилки в інший
npm test > all.txt 2>&1 # обидва потоки в один файл
command > /dev/null 2>&1 # викинути весь вивід у «нікуди»
Синтаксис 2>&1 читається як «спрямуй потік 2 (stderr) туди ж, куди йде потік 1 (stdout)». Він постійно трапляється в CI-логах і скриптах. А /dev/null — це системна «чорна діра»: усе, що туди записано, зникає; так глушать зайвий шум. Оператор > перезаписує файл, >> — дописує в кінець.
Розуміння потоків одразу пояснює дві типові загадки CI: чому важливе повідомлення «зникло» (воно пішло в stderr, а лог зберігав лише stdout) і чому джоб «зелений», хоча в логах видно помилку (програма написала помилку в stderr, але повернула код виходу 0).
Робота з логами: tail, grep, less
Читання логів — щоденний хліб QA. Лог на бойовому стенді може бути на сотні тисяч рядків, тож відкривати його цілком безглуздо — треба вміти дивитися хвіст, шукати за шаблоном і стежити наживо.
tail -n 100 app.log # останні 100 рядків
tail -f app.log # стежити наживо: нові рядки зʼявляються самі
less app.log # інтерактивний перегляд великого файлу
tail -f (follow) — найкорисніша команда під час відтворення бага: запускаєте стеження за логом, робите дію в застосунку й бачите, що сервер пише в цю мить. Вихід — Ctrl+C.
У less навігація як у текстовому редакторі: /слово — пошук уперед, n — наступний збіг, G — у кінець файлу, g — на початок, q — вихід. less не тримає весь файл у памʼяті, тому відкриває навіть гігабайтні логи миттєво.
grep — фільтр за шаблоном, головний інструмент пошуку в логах:
grep "ERROR" app.log # рядки зі словом ERROR
grep -i "timeout" app.log # без урахування регістру
grep -n "500" app.log # з номерами рядків
grep -v "healthcheck" app.log # усе, КРІМ рядків з healthcheck
grep -C 3 "Exception" app.log # збіг + 3 рядки контексту навколо
grep -rn "TODO" ./src # рекурсивно по директорії
Прапорець -C (context) особливо цінний у логах: сама помилка часто менш інформативна, ніж рядки до й після неї — стек виклику, вхідні дані, наступна дія. А зʼєднавши grep у пайп, отримуєте точкову вибірку: grep "correlation-id: abc123" app.log витягне всі події одного запиту з мільйона рядків — за умови, що застосунок пише correlation id (наскрізний ідентифікатор запиту; докладніше про це — у главі «Логи, моніторинг і спостережуваність для QA»).
Логи не завжди лежать простим файлом. Служби під systemd читають через journalctl -u назва-служби -f, а логи контейнера — через docker logs -f назва (див. главу «Docker і тестові середовища»). Але граматика та сама: показати хвіст, стежити наживо, відфільтрувати грепом.
curl: швидка перевірка HTTP з терміналу
curl — це «браузер без браузера»: він робить HTTP-запит і показує відповідь. Для QA це найшвидший спосіб перевірити, чи живий ендпоінт, який статус повертає API і що саме в тілі відповіді — без Postman і без відкриття застосунку.
curl https://api.example.com/health # GET, тіло у stdout
curl -i https://api.example.com/users/1 # -i: показати й заголовки
curl -I https://api.example.com/ # -I: лише заголовки (HEAD)
curl -v https://api.example.com/ # -v: увесь діалог, запит і відповідь
Для перевірки записів і авторизації додають метод, заголовки й тіло:
curl -X POST https://api.example.com/login \
-H "Content-Type: application/json" \
-d '{"email":"qa@example.com","password":"secret"}'
Кілька прапорців, які варто знати: -L іде за редиректами, -o file зберігає відповідь у файл, -s прибирає індикатор прогресу (зручно в пайпах і скриптах), -u user:pass додає базову автентифікацію. А -w '%{http_code}' дописує код статусу у вивід — у зв'язці -o /dev/null -s дає рівно один рядок із кодом, ідеально для смоук-перевірки в CI: чи відповідає стенд узагалі.
curl природно дружить з пайпами. Класична зв'язка для читабельного JSON — прогнати відповідь через jq:
curl -s https://api.example.com/users | jq '.[] | .email'
Що саме означають методи, заголовки й коди статусу — тема розділу «Веб і мережі для AQA»; тут curl цінний як інструмент, що дозволяє перевірити гіпотезу за секунди, не піднімаючи важких клієнтів. Коли автотест на API падає, найшвидший спосіб зрозуміти, тест винен чи бекенд, — повторити той самий запит руками через curl і подивитися сиру відповідь.
ssh і scp: робота з віддаленим стендом
Тестові стенди зазвичай живуть на віддалених серверах. SSH (Secure Shell) — це зашифрований канал, який дає термінал на чужій машині так, ніби ви сидите за нею.
ssh qa@staging.example.com # підключитися (порт 22 за замовчуванням)
ssh -p 2222 qa@staging.example.com # якщо порт нестандартний
Автентифікуватися можна паролем, але на практиці використовують пару ключів: приватний ключ лежить у вас (~/.ssh/id_ed25519), публічний — на сервері. Сервер перевіряє, що ви володієте приватним ключем, і пускає без пароля. Приватний ключ — це секрет рівня пароля: його не комітять у репозиторій і не пересилають (про гігієну секретів — глава «Секрети та конфігурація в CI»).
Опинившись на стенді через SSH, ви користуєтеся тими самими tail -f, grep, ps — уже на віддаленій машині. Це і є типовий сценарій розслідування: підключитися до стенду, знайти лог служби, стежити за ним наживо, поки відтворюєте баг.
scp (secure copy) копіює файли тим самим захищеним каналом — в обидва боки:
scp report.zip qa@staging:/tmp/ # залити файл на стенд
scp qa@staging:/var/log/app.log ./ # забрати лог зі стенду до себе
scp -r ./fixtures qa@staging:/data/ # -r: рекурсивно, цілу директорію
Забрати лог або артефакт зі стенду до себе, щоб спокійно розібрати, — щоденна операція AQA. Ті самі механізми (SSH-ключі, копіювання артефактів) працюють і всередині CI, коли пайплайн деплоїть код на середовище або забирає результати прогону.
Змінні середовища і PATH
Змінна середовища (environment variable) — це іменоване значення, яке оболонка передає всім процесам, що вона запускає. Через них конфігурують застосунки й тести, не вшиваючи значення в код: базовий URL стенду, рівень логування, токени доступу.
echo $HOME # вивести значення змінної
export BASE_URL=https://staging.example.com # задати на поточну сесію
printenv # усі змінні середовища
Ключове для розуміння: змінні успадковуються дочірніми процесами, але не повертаються назад до батьківського. Тому export у скрипті бачать команди всередині нього, а от нова змінна, задана всередині запущеної програми, не «протече» у вашу оболонку. Постійні змінні прописують у файлах на кшталт ~/.bashrc чи ~/.zshrc, а секрети в проєктах зазвичай кладуть у файл .env і підвантажують бібліотекою (dotenv) — сам .env при цьому не комітять.
У тестовому коді ці змінні читаються з process.env. Саме так автотести переїжджають між середовищами без правок:
// playwright.config.ts
export default defineConfig({
use: {
baseURL: process.env.BASE_URL ?? 'http://localhost:3000',
},
});
Особлива змінна — PATH. Це список директорій через двокрапку, у яких оболонка шукає програму, коли ви вводите node, npx чи git. Оболонка перебирає директорії зліва направо й запускає перший знайдений збіг.
Це пояснює цілий клас помилок «command not found» у CI: програма встановлена, але її директорії немає в PATH, тому оболонка її не бачить. Перевірити, який саме файл запуститься, можна командою which:
which node # /usr/local/bin/node — звідки береться node
echo $PATH # побачити весь список директорій пошуку
Ця сама логіка «шукати виконуваний файл у PATH» лежить в основі багатьох CLI-інструментів, з якими стикається QA. Наприклад, adb для роботи з Android-пристроями (тема мобільного розділу) чи клієнт бази psql (розділ про SQL) — обидва треба мати в PATH, інакше термінал відповість command not found, хоча інструмент установлено.
Типові помилки
Виглядає як баг застосунку, а насправді — права або власник файлу. CI падає з Permission denied на скрипті або тест не читає файл на стенді. Причина не в коді, а в забутому біті x чи в тому, що файл належить іншому користувачеві. Дивіться ls -l, а не логіку.
Виглядає як «джоб зелений», а насправді — помилка пішла в stderr. Програма написала помилку в потік stderr й повернула код виходу 0. Пайплайн бачить нульовий код і вважає крок успішним. Перевіряйте не лише текст логів, а й код виходу ($?), і збирайте обидва потоки (2>&1).
Виглядає як «команда не існує», а насправді — її немає в PATH. command not found частіше означає не відсутність програми, а те, що оболонка не знає, де її шукати. which і echo $PATH розкривають правду за секунду.
Виглядає як «стенд не відповідає», а насправді — завислий старий процес тримає порт. Новий інстанс не піднімається, бо порт зайнятий попереднім прогоном. ps aux | grep знаходить винуватця, kill звільняє порт.
Виглядає як «скрипт не бачить токен», а насправді — змінну не експортовано або вона не успадкувалася. Значення задане в одній оболонці, а команда виконується в іншому процесі, куди воно не долетіло. Без export або в новій сесії змінної просто немає.
Підсумок
- Термінал для QA — це доступ до трьох речей, яких немає в браузері: логів на стенді, CI-агентів і керування процесами на віддалених машинах.
- Пайпи (
|) і перенаправлення (>,2>&1) перетворюють окремі команди на інструмент; stdout і stderr — різні потоки, і плутанина між ними породжує «зелені» джоби з помилками всередині. - Логи читають хвостом і фільтром:
tail -fдля стеження наживо,grepдля пошуку за шаблоном і correlation id,lessдля великих файлів. - curl дає перевірити HTTP-ендпоінт за секунди й відповісти на питання «тест винен чи бекенд», повторивши запит руками.
- Права (
chmod,ls -l), змінні середовища й PATH — джерело цілого класу помилок CI, які виглядають як баги коду, але лікуються не в коді.
Що питають на співбесіді
Термінал на співбесідах QA має середню вагу — глибокого Linux не чекають, але базову грамотність перевіряють майже завжди, особливо на позиціях з ухилом в автоматизацію.
- «Як подивитися логи застосунку на сервері?» Інтерв'юер хоче почути
sshна стенд, потімtail -fабоlessдля файлу,grepдля пошуку за шаблоном чи id запиту. Сильний кандидат згадає, що логи можуть бути у systemd (journalctl) чи в контейнері (docker logs). - «Чим
killвідрізняється відkill -9?» Перевіряють розуміння сигналів:SIGTERMдає процесу завершитися чисто,SIGKILLвбиває примусово й може лишити дані недописаними. Правильний порядок — спершу мʼяко, потім жорстко. - «Що таке PATH і чому може бути command not found?» Дивляться, чи розумієте ви, як оболонка знаходить програму. Відповідь: PATH — список директорій пошуку; якщо каталогу програми там немає, вона «не знайдена», хоча встановлена.
- «Як швидко перевірити, що API живий, без Postman?» Очікують curl: базовий GET,
-iдля заголовків,-X POST -dдля запису,-w '%{http_code}'для смоуку в CI. - «Навіщо змінні середовища в тестах?» Хочуть почути про конфігурацію під середовища без правок коду: базовий URL, токени, рівень логування через
process.env, і що секрети не комітять.
На що дивиться інтерв'юер насправді: не на завчені прапорці, а на те, чи вмієте ви самостійно докопатися до причини — підключитися до стенду, знайти лог, відфільтрувати потрібне й відрізнити проблему середовища від бага застосунку.
Джерела
- The Linux Command Line — William Shotts, безкоштовна книга-підручник з bash і базового Linux.
- GNU Bash Reference Manual — офіційна довідка з оболонки: синтаксис, перенаправлення, змінні.
- curl documentation — офіційна документація й манпейджі curl.
- OpenSSH Manual — офіційні манпейджі
ssh,scpта конфігурації клієнта.
Ця тема не входить у силабус ISTQB CTFL 4.0 напряму — це інженерний фундамент, який силабус мовчки припускає в модулі про автоматизацію й інструменти тестування. На співбесідах його перевіряють як практичну гігієну, а не як окремий розділ теорії.
Навіщо QA взагалі термінал, якщо є браузер, Postman і графічні клієнти?
Бо три речі, критичні для розслідування, живуть поза браузером. Логи застосунку лежать файлом на віддаленому сервері, куди мишкою не дотягнешся. CI-агент, на якому впав автотест, — це машина без екрана, і єдиний спосіб дізнатися, що там сталося, — прочитати її текстовий вивід. Керування процесами на стенді (перезапустити службу, звільнити зайнятий порт, перевірити, чи вона взагалі жива) теж робиться командами. Щойно робота торкається автоматизації чи CI, половина відповідей на питання «чому впало» дістається саме через CLI, тож базова термінальна грамотність стає робочим інструментом, а не факультативом.
Чим абсолютний шлях відрізняється від відносного і чому це породжує «локально працює, у пайплайні ні»?
Абсолютний шлях відлічується від кореня файлової системи (/var/log/app.log) і вказує на той самий файл звідки завгодно. Відносний (./app.log, ../fixtures) відлічується від поточної робочої директорії, тож те, на що він указує, залежить від того, звідки команду запустили. У цьому й пастка: на своїй машині ви стартуєте тест із кореня проєкту, і відносний шлях до фікстури збігається, а CI-джоб запускає команду з іншого каталогу — той самий шлях указує в порожнечу, звідси «файл не знайдено». Правило безпеки: у скриптах і конфігах, що поїдуть у CI, або спирайтеся на абсолютні шляхи, або явно фіксуйте робочу директорію, а не сподівайтеся на «звідки прийшло».
Як прочитати рядок прав у виводі ls -l, наприклад -rwxr-xr-x?
Перший символ — тип обʼєкта: - звичайний файл, d директорія, l символьне посилання. Далі йдуть три трійки по три біти — права для власника, для групи й для всіх решти. У кожній трійці r дозволяє читати, w писати, x виконувати (для директорії x означає право зайти всередину). У прикладі власник має повний набір rwx, а група й решта — r-x, тобто можуть читати й виконувати, але не змінювати. Для QA це не теорія: коли скрипт «не запускається» або тест «не бачить файл» на стенді, перший стовпчик ls -l часто показує причину швидше, ніж читання коду.
Що робить chmod 755 і навіщо це знати тестувальнику?
chmod змінює права доступу, а число 755 — компактний вісімковий запис: кожна цифра задає набір для однієї трійки (власник, група, решта), де читання важить 4, запис 2, виконання 1. Отже 7 (4+2+1) — повний набір власнику, а 5 (4+1) — читання й виконання групі та решті; 644 дає власнику читання-запис, іншим лише читання. Практична причина знати це: коли CI падає з Permission denied на скрипті, майже завжди бракує біта виконання, і chmod +x (чи 755) лікує це за секунду. Так само доступ до тестового файлу на стенді впирається в права й власника, а не в логіку тесту.
Як подивитися логи застосунку на віддаленому сервері?
Спершу зайти на сервер по SSH (ssh qa@staging...), а далі працювати з логом трьома командами залежно від задачі. tail -f app.log стежить за файлом наживо — нові рядки зʼявляються самі, ідеально під час відтворення бага. less app.log відкриває навіть гігантський файл миттєво, бо не тримає його весь у памʼяті, і дає пошук усередині. grep фільтрує потрібне за шаблоном чи ідентифікатором запиту. Сильна відповідь додає, що лог не завжди простий файл: під systemd його читають командою journalctl -u служба -f, а лог контейнера — docker logs -f, але граматика та сама — хвіст, стеження наживо, фільтр.
Чим tail -f цінний саме під час відтворення бага?
tail -f (follow) не просто показує кінець файлу, а лишається відкритим і домальовує кожен новий рядок, щойно застосунок його записав. Це дає точну звʼязку «дія в застосунку → реакція сервера»: ви запускаєте стеження за логом, робите крок у продукті й одразу бачите, що бекенд написав саме в цю мить. Без цього довелося б раз по раз перевідкривати файл і губити контекст. Вихід зі стеження — Ctrl+C. На проді той самий прийом працює через journalctl -f чи docker logs -f.
Навіщо grep прапорці -i, -n, -v та -C?
Це чотири щоденні модифікатори пошуку в логах. -i робить пошук нечутливим до регістру, тож error, Error і ERROR ловляться разом. -n додає до кожного збігу номер рядка — зручно, щоб потім стрибнути туди в редакторі чи less. -v інвертує умову й показує все, КРІМ збігів, — так відсіюють шум на кшталт рядків healthcheck. -C 3 друкує сам збіг плюс по три рядки контексту навколо, і це часто важливіше за саму помилку: стек виклику, вхідні дані й наступна дія розкривають причину краще, ніж один рядок винятку.
Що таке пайп (|) і чим він відрізняється від перенаправлення у файл?
Пайп зʼєднує дві команди: бере стандартний вивід (stdout) лівої й подає його на стандартний вхід (stdin) правої, тож ланцюжок з grep і wc -l рахує рядки з помилками, не створюючи проміжного файлу. Перенаправлення (>, >>) натомість спрямовує потік не в іншу команду, а у файл: > out.txt перезаписує, >> out.txt дописує в кінець. Різниця по суті: пайп передає дані між процесами в памʼяті, перенаправлення зберігає їх на диск. Комбінуючи їх, з простих утиліт складають точковий інструмент — наприклад, витягти всі події одного запиту за наскрізним ідентифікатором з мільйона рядків.
Що означає 2>&1 і чому цей запис постійно трапляється в CI-скриптах?
Кожна програма має два вихідні потоки: stdout (номер 1) для звичайного виводу й stderr (номер 2) для помилок. Запис 2>&1 означає «спрямуй потік 2 туди ж, куди зараз іде потік 1», тобто злий помилки з основним виводом в одне місце. Це потрібно, бо за замовчуванням вони окремі, і якщо лог збирає лише stdout, повідомлення про помилку зі stderr просто зникне з журналу. У CI це рятує від загадки «в логах чисто, а щось явно пішло не так»: зібравши обидва потоки в один файл, ви бачите повну картину. Важливий нюанс — перенаправлення читаються зліва направо, тому 2>&1 ставлять після того, як уже задали, куди йде потік 1.
Джоб у CI «зелений», але в логах видно помилку. Як таке можливо?
Бо пайплайн судить про успіх не за текстом логів, а за кодом виходу останньої команди: 0 — успіх, будь-що інше — провал. Програма могла написати повідомлення про помилку в stderr й усе одно завершитися з кодом 0 — тоді джоб бачить нуль і фарбує крок зеленим, хоча всередині щось зламалося. Друга частина тієї ж пастки — потоки: помилка пішла в stderr, а збирали лише stdout, тож у збереженому лозі її взагалі немає. Лікування подвійне: перевіряти реальний код виходу ($?) і зводити обидва потоки разом через 2>&1, а не довіряти самому зеленому кружечку.
Чим kill відрізняється від kill -9?
kill за замовчуванням не «вбиває», а надсилає процесу сигнал SIGTERM (номер 15) — ввічливе прохання завершитися, яке програма може перехопити, щоб коректно закритися: дописати файли, звільнити ресурси, закрити зʼєднання. kill -9 шле SIGKILL, який виконує вже сама операційна система: процес не має шансу ні перехопити його, ні прибрати за собою, тому дані можуть лишитися недописаними. Правильний порядок — спершу мʼяко SIGTERM, і лише якщо процес не реагує, застосовувати жорсткий SIGKILL. Звичка одразу бити все kill -9 небезпечна тим, що маскує реальну проблему: якщо застосунок не вимикається чисто по SIGTERM, це часто справжній баг, який варто завести, а не глушити.
Як знайти й прибрати завислий процес, що тримає порт?
Симптом упізнаваний: новий інстанс сервера не піднімається, бо порт зайнятий, — його не звільнив попередній прогін. Спершу треба знайти винуватця: ps aux із фільтром за назвою покаже процеси і їхні номери (PID), а точніше «хто сидить на порту» дадуть lsof -i :3000 або ss -ltnp. Знайшовши PID, звільняють порт командою kill PID, і лише якщо процес не реагує — kill -9. Для QA це типова причина флаку в локальних і CI-прогонах: тест стартує сервер, падає, не прибирає за собою, і наступний запуск валиться вже на піднятті. Надійне прибирання (teardown), що гарантовано гасить процес, лікує це в корені.
Як швидко перевірити, що API живий, без Postman?
Через curl — це «браузер без браузера», який робить HTTP-запит і показує відповідь прямо в терміналі. Базовий curl https://api.../health віддасть тіло; -i додасть до виводу заголовки й статус-рядок; -I попросить лише заголовки (метод HEAD); -v покаже весь діалог — і запит, і відповідь — що безцінно, коли треба зрозуміти, які саме заголовки полетіли. Для запису й авторизації додають метод і дані: -X POST разом із -H для заголовка типу вмісту й -d для тіла. А для смоук-перевірки в CI ідеальна звʼязка -o /dev/null -s -w '%{http_code}' — вона викидає тіло, глушить прогрес і лишає рівно один рядок із кодом статусу: чи відповідає стенд узагалі.
Автотест на API впав. Як через термінал зрозуміти, тест винен чи бекенд?
Найшвидший спосіб — повторити той самий запит руками через curl і подивитися сиру відповідь, поки тестовий фреймворк ще не додав своєї інтерпретації. Якщо валідний запит curl-ом відтворює ту саму помилку — підозра на бекенд, і це вже доказ, а не здогад. Якщо ж curl повертає нормальну відповідь, а падає лише тест, зміщуємося на тест: не підставилася змінна середовища, протух токен, крива фікстура або не те середовище. Другий крок — заглянути в лог сервера через SSH і grep за ідентифікатором того запиту: помилка 5xx майже завжди лишає стектрейс, який прямо показує, де впало. Дисципліна тут головніша за команди: не називати причину, поки під нею немає відтворення чи стектрейсу.
Що таке SSH і навіщо пара ключів замість пароля?
SSH (Secure Shell) — це зашифрований канал до віддаленої машини, який дає термінал на ній так, ніби ви фізично за нею сидите; типовий сценарій QA — зайти на стенд, знайти лог служби й стежити за ним, поки відтворюєш баг. Автентифікуватися можна паролем, але на практиці використовують пару ключів: приватний ключ лежить лише у вас (~/.ssh/id_ed25519), публічний — на сервері. Сервер криптографічно перевіряє, що ви володієте приватним ключем, і пускає без пароля — це і зручніше, і безпечніше, бо пароль не літає мережею й не витікає. Приватний ключ — секрет рівня пароля: його не комітять у репозиторій і не пересилають месенджером. Ті самі ключі й механізми копіювання артефактів працюють і всередині CI, коли пайплайн деплоїть код чи забирає результати прогону.
Як забрати лог або артефакт зі стенду до себе на машину?
Командою scp (secure copy) — вона копіює файли тим самим захищеним SSH-каналом, в обидва боки. Щоб забрати файл із сервера, джерелом указують віддалений шлях, а призначенням локальний: scp qa@staging:/var/log/app.log ./. Щоб залити на стенд — навпаки: scp report.zip qa@staging:/tmp/. Прапорець -r копіює цілу директорію рекурсивно (наприклад, теку фікстур). Це щоденна операція AQA — стягнути лог чи артефакт зі стенду, щоб спокійно розібрати локально, а не длубатися в ньому через вузький канал термінала на самому сервері.
Що таке PATH і чому виникає command not found, хоча програма встановлена?
PATH — це змінна середовища зі списком директорій через двокрапку, у яких оболонка шукає виконуваний файл, коли ви вводите node, git чи npx. Оболонка перебирає ці директорії зліва направо й запускає перший знайдений збіг, а якщо каталогу програми в списку немає — відповідає command not found, навіть коли файл фізично лежить на диску. Тобто помилка частіше означає не «програми немає», а «оболонка не знає, де її шукати». Розкрити правду за секунду допомагають which node (покаже, який саме файл запуститься й звідки) та echo $PATH (весь список пошуку). Це цілий клас помилок CI: інструмент установлено в нестандартне місце, а його директорію забули додати в PATH.
Навіщо змінні середовища в автотестах?
Щоб конфігурувати тести під різні середовища, не вшиваючи значень у код: базовий URL стенду, токени доступу, рівень логування задаються ззовні й читаються з process.env. Саме так один і той самий набір тестів переїжджає між локаллю, staging і CI — змінюється лише BASE_URL, а код лишається тим самим. Постійні змінні прописують у ~/.bashrc чи ~/.zshrc, а секрети проєкту зазвичай кладуть у файл .env і підвантажують бібліотекою dotenv — сам .env при цьому не комітять. Сильна відповідь підкреслює саме гігієну секретів: токени не в коді й не в репозиторії, а в змінних оточення й секрет-сторі CI.
Чому змінна, задана всередині запущеної програми, «не протікає» назад у вашу оболонку?
Через однобічне успадкування: оболонка передає свої експортовані змінні всім процесам, які запускає (дочірнім), але зворотного каналу немає — дочірній процес не може змінити оточення батьківського. Тому export BASE_URL=... у скрипті бачать команди всередині цього скрипта, а от змінна, яку встановила вже запущена програма, залишиться в її власному процесі й зникне разом із ним. Звідси дві типові пастки: без export змінну не побачать навіть дочірні процеси (вона лишиться локальною для оболонки), а в новій сесії термінала тимчасово задані змінні просто відсутні. Практичний наслідок — «скрипт не бачить токен» часто означає не баг, а те, що значення задали в одній оболонці, а команда виконується в іншому процесі, куди воно не долетіло.
CI падає з Permission denied на скрипті. Де шукати причину?
Не в коді скрипта, а в його правах і власнику — це класичний випадок «виглядає як баг, а насправді метадані файлу». Найчастіша причина — забутий біт виконання: файл скопіювали чи згенерували без права x, і оболонка відмовляється його запускати; лікує chmod +x скрипт (чи 755). Друга причина — файл належить іншому користувачеві або групі, ніж той, під яким працює джоб, і в поточного просто немає доступу; тут дивляться колонки власника та групи у ls -l. Тобто діагностика починається з ls -l, а не з читання логіки: перший стовпчик прав і колонки власника/групи зазвичай показують проблему одразу.
Три кейси, де термінал вирішує, знайдете ви причину за пʼять хвилин чи полюватимете на примару півдня: наскрізне розслідування нічного падіння API-тесту (SSH на стенд, стеження за логом, повтор запиту curl-ом), розбір «джоб зелений, а фіча зламана» через потоки й код виходу, і конфігурація Playwright через змінні середовища з типовою пасткою command not found у CI.
Кейс 1. Розслідування «нічний API-тест впав» через термінал
Уранці в CI червоний один тест на створення замовлення: expected 201, got 500. Локально він зелений. Питання не «де в коді баг», а «на чиєму боці проблема» — і весь маршрут проходиться в терміналі, не відкриваючи застосунок.
Крок 1 — зайти на стенд і почати стежити за логом служби ще до відтворення:
ssh qa@staging.example.com
tail -f /var/log/orders/app.log
Крок 2 — у сусідньому терміналі повторити рівно той запит, що робить тест, і подивитися сиру відповідь:
curl -i -X POST https://staging.example.com/api/orders \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"productId":42,"qty":1}'
Якщо curl валідним запитом теж повертає 500 — це вже не здогад, а відтворення на боці бекенда. Крок 3 — витягти саме цей інцидент із логу за наскрізним ідентифікатором, який сервер повернув у заголовку відповіді, разом із контекстом навколо помилки:
grep -C 5 "correlation-id: 7f3a9c" /var/log/orders/app.log
Стектрейс у цих рядках прямо показує рядок коду, що впав, — наприклад, звернення до неіснуючого поля в тілі. Це і є доказ замість «здається, баг бекенда».
Куди веде кожен результат:
| Що показав термінал | Найімовірніший діагноз | Наступний крок |
|---|---|---|
curl теж дає 500, у лозі стектрейс | Баг бекенда | Дефект зі стектрейсом і тілом запиту |
curl дає 201, падає лише тест | Проблема тесту | Перевірити токен, фікстуру, середовище |
Масові 502/503 одразу після деплою | Стан середовища | Дочекатися прогріву стенду, перепрогнати |
Permission denied при читанні логу | Права або власник файлу | ls -l на лог, а не логіка тесту |
Дисципліна кейсу: не називати винного, поки під висновком немає відтворення curl-ом або стектрейсу з логу. «Схоже на бекенд, стектрейс указує на контролер замовлень» коштує рівно стільки, скільки під ним доказів.
Кейс 2. «Джоб зелений, а фіча зламана»: потоки й код виходу
Крок збірки в CI позначено зеленим, але користувачі скаржаться, що дані не мігрували. У логах джоба — жодної помилки. Розгадка в тому, що пайплайн судить про успіх за кодом виходу, а помилка пішла в інший потік і не потрапила ні в лог, ні в код.
Спрощений скрипт кроку виглядав так:
node migrate.js > migration.log
echo "Done"
migrate.js написав помилку у stderr, а > migration.log перехопив лише stdout, тож у файлі порожньо. Гірше — код виходу кроку визначає остання команда echo, яка завжди успішна, тому джоб бачить 0 і фарбує крок зеленим. Перевірити реальний код виходу можна одразу після команди:
node migrate.js > migration.log
echo "exit code = $?" # надрукує ненульовий код, якщо міграція впала
Правильна версія збирає обидва потоки в один файл і не ковтає код виходу проміжним echo:
set -e # обірвати крок на першій же помилці
node migrate.js > migration.log 2>&1 # stdout і stderr в один лог
Що дивитися і чому:
2>&1без нього помилки невидимі. stdout і stderr — різні потоки; зібравши лише stdout, ви втрачаєте саме те, заради чого читаєте лог.- Код виходу — джерело правди про успіх, не текст логу.
$?після команди чиset -eу скрипті не дають «зеленому» брехати; фінальнийecho/|| trueмовчки ховає провал. /dev/nullглушить свідомо, а не випадково. Викидати вивід у «нікуди» доречно для шуму, але якщо туди ж полетіла помилка — джоб знову збреше про успіх.
Кейс 3. Playwright: конфіг через змінні середовища і command not found у CI
Автотести мають ганятися по локалі, staging і CI без правок коду — це роблять змінними середовища, які тест читає з process.env. Значення за замовчуванням страхує локальний запуск:
// playwright.config.ts
import { defineConfig } from '@playwright/test';
export default defineConfig({
use: {
baseURL: process.env.BASE_URL ?? 'http://localhost:3000',
},
});
Локально URL задають на сесію (значення видно командам, які ця оболонка запускає далі), а секрети тримають у .env, який не комітять:
export BASE_URL=https://staging.example.com
npx playwright test
Типова пастка №1 — «скрипт не бачить змінну». Якщо BASE_URL задали без export або в іншому вікні термінала, дочірній процес тесту її не успадкує, і конфіг тихо візьме локальний дефолт замість стенду. Швидка перевірка, що змінна реально в оточенні:
printenv BASE_URL # порожньо = змінну не експортовано або сесія інша
Типова пастка №2 — npx: command not found у CI, хоча локально все працює. Причина майже завжди не у відсутності Node, а в PATH: директорії з npx немає у списку пошуку оболонки того джоба. Діагностика — два рядки:
which npx # порожньо = виконуваного файлу немає в PATH
echo $PATH # подивитися, які директорії оболонка взагалі переглядає
Що дивитися і чому:
process.env+ дефолт — контракт переносимості тесту. Один код, різні середовища; забутий дефолт валить локальний прогін, а не тільки CI.exportі сесія вирішують видимість змінної. Змінна успадковується вниз, у дочірні процеси, але не назад; задане в одному вікні не існує в іншому.command not found— це PATH, а не «немає програми».whichіecho $PATHза секунду відрізняють «не встановлено» від «встановлено, але оболонка не знаходить».
Bash: навігація, файли, права
- Знаю різницю між абсолютним (
/var/log/app.log) і відносним (./app.log) шляхом і чому відносний ламає CI-джоби, коли команду запустили з іншого каталогу. - Памʼятаю, що
rmвидаляє без кошика й без відкату, тожrm -rfна спільному стенді небезпечний так само, якDELETEбезWHERE. - Читаю перший стовпчик
ls -l: тип (-файл,dдиректорія,lпосилання) плюс три трійкиrwxдля власника, групи й решти. - Розумію вісімкову нотацію (
r=4,w=2,x=1:755=rwx r-x r-x,644=rw- r-- r--) і щоPermission deniedу CI майже завжди — забутий бітx(chmod +x) чи чужий власник, а не баг коду.
Процеси і сигнали
- Розумію, що
killне «вбиває», а шле сигнал:SIGTERM(15) ввічливо просить завершитися,SIGKILL(9,kill -9) вбиває примусово й без прибирання; порядок — спершу TERM, і лише якщо процес не реагує — KILL. - Можу знайти й прибити завислий процес, що тримає порт: PID через
ps aux(чиlsof -i :порт) і звільнити порт командоюkill. - Памʼятаю, що
Ctrl+C— це сигналSIGINT(перервати чисто), аCtrl+Z— призупинення, а не завершення.
Потоки, пайпи, перенаправлення
- Розрізняю три стандартні потоки — stdin, stdout (1) і stderr (2) — і можу пояснити
2>&1(«злий stderr туди ж, куди stdout») та чому без цього повідомлення про помилку зникає зі зібраного логу. - Знаю різницю
>(перезаписати файл) vs>>(дописати в кінець) і що/dev/null— це «чорна діра», куди викидають зайвий вивід. - Розумію, чому джоб буває «зелений» з помилкою всередині: код виходу 0 плюс помилка, що пішла в stderr, а не в код виходу.
Логи: tail, grep, less
- Знаю, коли що:
tail -fдля стеження наживо,lessдля гігантських файлів,grepдля фільтра за шаблоном. - Володію грепом (
-i,-n,-v,-C,-r) і можу витягти всі події одного запиту, грепнувши лог за наскрізним ідентифікатором (correlation id). - Розумію, що логи можуть бути не файлом:
journalctl -u служба -fпід systemd,docker logs -fу контейнері — граматика та сама.
curl
- Можу за секунди перевірити ендпоінт:
curl -i(заголовки з тілом),-I(лише заголовки, HEAD),-v(весь діалог); POST — метод-X, заголовок-H, тіло-d. - Памʼятаю смоук-звʼязку для CI:
-o /dev/null -s -w '%{http_code}'віддає рівно код статусу стенду. - Розумію, що curl без
-Lне піде за редіректом (на відміну від браузера), і що повторити запит curl-ом — найшвидший спосіб відділити баг тесту від бага бекенда.
ssh і scp
- Розумію, що SSH дає термінал на віддаленій машині, а пара ключів (приватний у себе, публічний на сервері) безпечніша й зручніша за пароль; приватний ключ — секрет рівня пароля, його не комітять і не пересилають.
- Можу копіювати файли обома напрямками через
scp(на стенд і зі стенду), а цілу директорію — черезscp -r.
Змінні середовища і PATH
- Розумію, що змінні успадковуються дочірніми процесами, але не течуть назад у батьківську оболонку (тому без
exportчи в новій сесії змінної «немає»), а тести читають конфіг ізprocess.env; секрети кладуть у.env, який не комітять. - Можу пояснити, що таке PATH і чому буває
command not foundпри встановленій програмі; перевіряю черезwhichіecho $PATH.
Ви ввели `kill 4821` без прапорців. Що станеться з процесом 4821?
Питання
Абсолютний і відносний шлях — у чому різниця й чому це ламає CI?