Що це: Тест-звіт — документ або повідомлення, яке переводить сирі результати тестування у відповідь на питання «що ми знаємо про якість цієї збірки і що з цим робити». На практиці їх кілька: щоденний статус (прогрес і блокери), test execution report (що запускали і з яким результатом), test summary або test completion report наприкінці спринту чи релізу, і test incident report про окрему аномалію.
Навіщо і коли: Різні люди приймають різні рішення. Розробнику зранку потрібен список нових падінь, тімліду — чи встигаємо до кінця спринту, продакт-менеджеру перед релізом — чи можна випускати і з яким ризиком. Один звіт «для всіх» зазвичай не підходить нікому.
Приклад з проєкту: У команді мобільного банкінгу ми мали три формати: автоматичне повідомлення в Slack після нічного прогону, щотижневий короткий звіт для тімліда і один test summary перед кожним релізом у стор. Коли продакт попросив «ще один звіт», ми спершу спитали, яке рішення він з нього прийматиме, — і виявилося, що вистачить одного рядка в релізному звіті.
Підводні камені: Звіт перетворюється на таблицю з сотнями рядків, де висновок треба шукати самому. Або звіт пишуть «для галочки» і ніхто його не відкриває.
Уточнююче питання: «Хто читає ваш звіт і що він з ним робить?» — назви конкретного адресата і рішення, яке він приймає.
What it is: A test report is a document or message that turns raw test results into an answer to «what do we know about the quality of this build, and what should we do about it». In practice there are several kinds: the daily status (progress and blockers), the test execution report (what ran and with what result), the test summary or test completion report at the end of a sprint or release, and the test incident report about a single anomaly.
Why and when: Different people make different decisions. A developer needs the list of new failures in the morning, a team lead needs to know whether the sprint is on track, and a product manager needs to know before release whether it can ship and with what risk. One report «for everyone» usually serves nobody.
From a project: On a mobile banking team we had three formats: an automatic Slack message after the nightly run, a short weekly report for the team lead and one test summary before every app-store release. When the product owner asked for «one more report», we first asked which decision it would support, and it turned out one line in the release report was enough.
Pitfalls: The report grows into a table with hundreds of rows where readers have to dig for the conclusion themselves. Or it is written as a formality and nobody opens it.
Follow-up question: «Who reads your report and what do they do with it?» Name a specific reader and the decision they make.
Звіт Коли Читач Рішення
--------------------- -------------------- ------------------ -----------------------------
Щоденний статус щодня, 10:00 команда, тімлід що розблокувати сьогодні
Test execution report після кожного прогону розробники що лагодити першим
Test summary report кінець спринту/релізу PO, реліз-менеджер випускати чи ні, з яким ризиком
Test incident report при аномалії розробник, тімлід чи це дефект, хто розбирає
Report When Reader Decision
--------------------- -------------------- ------------------ ------------------------------
Daily status daily, 10:00 team, team lead what to unblock today
Test execution report after every run developers what to fix first
Test summary report end of sprint/release PO, release manager ship or not, with what risk
Test incident report on an anomaly developer, lead is it a defect, who owns it
Що це: Test summary report (у ISO/IEC/IEEE 29119-3 — test completion report) підсумовує тестування за спринт, реліз чи етап. Типові розділи: висновок і рекомендація, обсяг (що тестували і що свідомо ні), середовище і збірка, результати виконання, відкриті дефекти за severity, виконання exit criteria, ризики і known issues.
Навіщо і коли: Його пишуть, коли етап завершується і хтось має вирішити, чи рухатися далі. Документ також лишається історією: через пів року можна подивитися, що саме перевіряли в версії 3.4 і з якими відомими проблемами її випустили.
Приклад з проєкту: Для релізу 3.4 інтернет-магазину звіт почався з одного речення: «Рекомендуємо реліз за умови, що фіча Apple Pay лишається вимкненою прапорцем». Далі — 412 кейсів, 401 пройдено, 6 впали, 5 заблоковані, два відкриті Major з обхідним шляхом. Реліз-менеджеру вистачило першого абзацу.
Підводні камені: Висновок у кінці документа, куди ніхто не доходить. Відсутній розділ «що не тестували»: без нього читач думає, що покрито все. Цифри без збірки і дати — незрозуміло, до чого вони відносяться.
Уточнююче питання: «Що ви пишете, якщо тестування не завершене, а дедлайн настав?» — чесно вказую непокриті області як ризик і не підганяю цифри.
What it is: A test summary report (called a test completion report in ISO/IEC/IEEE 29119-3) sums up testing for a sprint, release or phase. Typical sections: conclusion and recommendation, scope (what was tested and what deliberately was not), environment and build, execution results, open defects by severity, status of exit criteria, risks and known issues.
Why and when: It is written when a phase ends and someone has to decide whether to move on. It also stays as history: six months later you can check exactly what was covered in version 3.4 and which known problems it shipped with.
From a project: For release 3.4 of an online shop the report opened with one sentence: «We recommend releasing provided the Apple Pay feature stays switched off behind its flag». Then came 412 cases, 401 passed, 6 failed, 5 blocked, and two open Major bugs with a workaround. The release manager only needed the first paragraph.
Pitfalls: The conclusion sits at the end of the document where nobody gets to it. There is no «not tested» section, so readers assume everything was covered. Numbers come without a build and a date, so it is unclear what they refer to.
Follow-up question: «What do you write if testing is not finished and the deadline has arrived?» I list the uncovered areas honestly as a risk and do not massage the numbers.
TEST SUMMARY REPORT · Shop Web 3.4.0 · build 3.4.0-rc2 · 2026-09-18
Висновок: GO з умовою — Apple Pay лишається вимкненим (flag applepay_v2=off)
Обсяг: кошик, checkout, промокоди, профіль, нова сторінка доставки
Не тестували: Apple Pay (сертифікат sandbox недоступний), IE-режим Edge
Середовище: staging-eu, Chrome 129, Firefox 130, Safari 17, iOS 17/Android 14
Виконання План Пройдено Впало Заблоковано Не запускали
Регрес 340 333 4 3 0
Нові фічі 72 68 2 2 0
Разом 412 401 6 5 0
Відкриті дефекти: Blocker 0 · Critical 0 · Major 2 (є обхід) · Minor 7
Exit criteria: 4 з 5 виконано (покриття вимог 96% при цілі 95%; Apple Pay — ні)
Ризики: SHOP-2211 у Safari повільно оновлюється кошик (до 3 с)
TEST SUMMARY REPORT · Shop Web 3.4.0 · build 3.4.0-rc2 · 2026-09-18
Conclusion: GO with a condition — Apple Pay stays off (flag applepay_v2=off)
Scope: cart, checkout, promo codes, profile, new delivery page
Not tested: Apple Pay (sandbox certificate unavailable), Edge IE mode
Environment: staging-eu, Chrome 129, Firefox 130, Safari 17, iOS 17/Android 14
Execution Planned Passed Failed Blocked Not run
Regression 340 333 4 3 0
New features 72 68 2 2 0
Total 412 401 6 5 0
Open defects: Blocker 0 · Critical 0 · Major 2 (workaround) · Minor 7
Exit criteria: 4 of 5 met (requirements coverage 96% vs 95% target; Apple Pay not met)
Risks: SHOP-2211 cart refresh in Safari is slow (up to 3 s)
Що це: Test execution report описує один або кілька конкретних прогонів: які тести запускались, на якій збірці і середовищі, який статус кожного, скільки тривало, де артефакти падінь. Його здебільшого генерує інструмент (JUnit XML, Allure, звіт Playwright, TestRail run). Test summary report — людський висновок за весь етап, що спирається на багато таких прогонів.
Навіщо і коли: Execution report потрібен одразу після прогону тим, хто розбирає падіння: розробнику і автоматизатору. Summary потрібен тим, хто приймає рішення, і пишеться рідше. Змішування двох форматів дає або перевантажений summary, або execution report, з якого незрозуміло, що робити.
Приклад з проєкту: Нічний регрес на 1 180 API-тестів видавав execution report в Allure. Зранку черговий QA переглядав 9 падінь, 6 з них виявились однією причиною (зламаний тестовий акаунт), а 3 — новими дефектами. У тижневий summary потрапили вже не 9 червоних рядків, а «3 нові дефекти, одна проблема тестових даних виправлена».
Підводні камені: Пересилати менеджеру посилання на Allure замість висновку. Рахувати в summary статуси останнього прогону, а не тренд за спринт.
Уточнююче питання: «Скільки падінь у звіті — це скільки дефектів?» Ні: спершу групуємо падіння за причиною, і лише частина з них — дефекти продукту.
What it is: A test execution report describes one or more specific runs: which tests ran, on which build and environment, the status of each one, how long it took and where the failure artefacts are. It is mostly generated by a tool (JUnit XML, Allure, the Playwright report, a TestRail run). A test summary report is a human conclusion for the whole phase, based on many such runs.
Why and when: The execution report is needed right after a run by whoever investigates failures: developers and automation engineers. The summary is for decision makers and is written less often. Mixing the two gives you either a bloated summary or an execution report that does not say what to do.
From a project: A nightly regression of 1,180 API tests produced an execution report in Allure. In the morning the on-duty QA went through 9 failures: 6 turned out to share one cause (a broken test account) and 3 were new defects. The weekly summary then said not «9 red rows» but «3 new defects, one test data problem fixed».
Pitfalls: Sending a manager an Allure link instead of a conclusion. Counting the statuses of the last run in the summary instead of the trend across the sprint.
Follow-up question: «Does the number of failures equal the number of defects?» No: first group failures by cause, and only some of them are product defects.
Прогін: api-regression #2317 · build 5.12.0-481 · env qa-2 · 2026-09-22 02:10 UTC
Тривалість: 18 хв 42 с · тестів 1180 · passed 1171 · failed 9 · skipped 0
Падіння, згруповані за причиною:
6 AssertionError 401 Unauthorized -> тестовий акаунт qa_billing_07 заблоковано (проблема даних)
2 expected 200, got 500 /orders/refund -> новий дефект ORD-3302
1 timeout 30 s /reports/export -> новий дефект RPT-118, відтворюється 3/3
Артефакти: allure/2317/index.html · logs s3://ci-logs/2317/
Run: api-regression #2317 · build 5.12.0-481 · env qa-2 · 2026-09-22 02:10 UTC
Duration: 18 min 42 s · tests 1180 · passed 1171 · failed 9 · skipped 0
Failures grouped by cause:
6 AssertionError 401 Unauthorized -> test account qa_billing_07 locked (data problem)
2 expected 200, got 500 /orders/refund -> new defect ORD-3302
1 timeout 30 s /reports/export -> new defect RPT-118, reproduces 3/3
Artefacts: allure/2317/index.html · logs s3://ci-logs/2317/
Що це: Прогрес-звіт — коротке регулярне повідомлення: скільки історій уже перевірено, що в роботі, що заблоковано і чи встигаємо до кінця спринту. Зазвичай це 5–7 рядків у командному каналі або на стендапі, а не окремий документ.
Навіщо і коли: Щоб проблеми було видно на третій день спринту, а не в останній. Якщо тестове середовище лежить два дні, тімлід має дізнатися про це, поки ще можна перепланувати. Найкраще працює фіксований час і фіксований формат, які команда звикає читати.
Приклад з проєкту: У спринті з 11 історіями на шостий день було перевірено 4, три чекали на деплой у QA-середовище, а одна була заблокована відсутнім ключем до платіжного sandbox. Повідомлення в каналі з конкретним власником блокера («потрібен ключ, @devops, до завтра») дало ключ за дві години.
Підводні камені: Писати відсотки виконаних тест-кейсів без контексту: «60% виконано» нічого не каже, якщо решта 40% — найризиковіші сценарії. Мовчати про блокери, сподіваючись, що вони розв'яжуться самі. Повторювати те саме щодня без змін — тоді повідомлення перестають читати.
Уточнююче питання: «Що робите, якщо бачите, що не встигаєте?» Кажу про це одразу, пропоную, що тестувати в першу чергу за ризиком, і що перенести.
What it is: A progress report is a short regular message: how many stories are verified, what is in progress, what is blocked and whether we will finish by the end of the sprint. Usually it is 5 to 7 lines in the team channel or at stand-up, not a separate document.
Why and when: So that problems are visible on day three of the sprint, not on the last day. If the test environment is down for two days, the team lead needs to know while there is still time to re-plan. A fixed time and a fixed format that the team gets used to reading work best.
From a project: In a sprint with 11 stories, by day six 4 were verified, three were waiting for deployment to the QA environment and one was blocked by a missing payment sandbox key. A channel message naming the blocker's owner («need the key, @devops, by tomorrow») got the key within two hours.
Pitfalls: Reporting the percentage of executed test cases without context: «60% done» says nothing if the remaining 40% are the riskiest scenarios. Staying quiet about blockers in the hope they resolve themselves. Posting the same text every day with no change, so people stop reading it.
Follow-up question: «What do you do when you see you will not make it?» I say so straight away, propose what to test first by risk and what to move out.
QA статус · Спринт 42 · день 6 з 10
Перевірено: 4/11 історій (PAY-310, PAY-312, PAY-315, PAY-318)
В роботі: 3 — PAY-311, PAY-316 чекають деплой на qa-1 (очікуємо сьогодні 14:00)
Заблоковано: PAY-313 — немає ключа до sandbox платіжного провайдера, @devops, потрібен до завтра
Нові дефекти за добу: 2 (1 Major PAY-331, 1 Minor PAY-332)
Ризик: якщо PAY-313 не розблокуємо до четверга, регрес платежів не встигне до демо
QA status · Sprint 42 · day 6 of 10
Verified: 4/11 stories (PAY-310, PAY-312, PAY-315, PAY-318)
In progress: 3 — PAY-311, PAY-316 waiting for deploy to qa-1 (expected today 14:00)
Blocked: PAY-313 — no key for the payment provider sandbox, @devops, needed by tomorrow
New defects in last 24 h: 2 (1 Major PAY-331, 1 Minor PAY-332)
Risk: if PAY-313 is not unblocked by Thursday, payments regression will miss the demo
Що це: Blocked означає, що тест неможливо виконати через зовнішню причину: не піднялося середовище, немає тестових даних чи доступу, або попередній дефект не дає дійти до перевірки. На відміну від Failed, заблокований тест нічого не каже про якість перевірюваної функції — результату просто немає.
Навіщо і коли: Blocked окремо від Failed і Not Run потрібен, щоб звіт не брехав. Якщо позначити заблокований кейс як Failed, дефектів здається більше; якщо як Passed «бо там точно все добре» — з'являється хибна впевненість. У звіті блоковані кейси завжди йдуть із причиною і посиланням на блокер.
Приклад з проєкту: 38 кейсів повернень коштів були заблоковані одним дефектом REF-90: кнопка «Повернути» не з'являлась для замовлень старших за 24 години. У звіті це був один рядок «38 blocked by REF-90», і саме цей дефект отримав найвищий пріоритет, бо закривав доступ до цілої функціональності.
Підводні камені: Вважати Blocked «майже пройденими» у pass rate. Не перевіряти блоковані кейси після розблокування: вони так і висять у звіті місяцями.
Уточнююче питання: «Чим Blocked відрізняється від Skipped в автотестах?» Skipped — свідоме рішення не запускати тест (анотація чи умова), Blocked — неможливість виконати його в цьому прогоні.
What it is: Blocked means the test cannot be executed for an external reason: the environment is not up, test data or access is missing, or an earlier defect prevents you from reaching the check. Unlike Failed, a blocked test says nothing about the quality of the feature under test; there is simply no result.
Why and when: Keeping Blocked separate from Failed and Not Run stops the report from lying. Mark a blocked case as Failed and there seem to be more defects; mark it Passed «because it is surely fine» and you create false confidence. In a report, blocked cases always come with a reason and a link to the blocker.
From a project: 38 refund test cases were blocked by one defect, REF-90: the Refund button did not appear for orders older than 24 hours. The report showed it as a single line «38 blocked by REF-90», and that defect got top priority because it cut off access to an entire feature.
Pitfalls: Treating Blocked as «almost passed» in the pass rate. Forgetting to rerun blocked cases once unblocked, so they hang in the report for months.
Follow-up question: «How is Blocked different from Skipped in automated tests?» Skipped is a deliberate decision not to run a test (an annotation or a condition); Blocked is being unable to run it in this cycle.
Статус виконання · Refunds · build 2.8.1
Passed 64 · Failed 5 · Blocked 41 · Not run 0 · Разом 110
Blocked за причиною:
38 REF-90 кнопка «Повернути» відсутня для замовлень старших за 24 год (дефект, Critical)
3 ENV-12 sandbox банку повертає 503 з 09:00 (середовище, @devops)
Pass rate виконаних: 64 / (64 + 5) = 92.8%
Виконано від плану: (64 + 5) / 110 = 62.7% <- це і є реальна картина
Execution status · Refunds · build 2.8.1
Passed 64 · Failed 5 · Blocked 41 · Not run 0 · Total 110
Blocked by cause:
38 REF-90 Refund button missing for orders older than 24 h (defect, Critical)
3 ENV-12 bank sandbox returns 503 since 09:00 (environment, @devops)
Pass rate of executed: 64 / (64 + 5) = 92.8%
Executed of planned: (64 + 5) / 110 = 62.7% <- this is the real picture
Що це: Спринтовий QA-звіт — коротке резюме на пів сторінки: що з запланованого перевірено, які дефекти знайдено і де, що перенесено, які тенденції змінилися порівняно з минулим спринтом і що команда змінює далі. Jira зберігає факти поштучно, а звіт показує картину і висновок.
Навіщо і коли: Його читають на ретроспективі і на sprint review, а також люди поза командою: продакт, суміжні команди, керівник напряму. Без звіту кожен сам будує фільтри і робить свої, часто різні висновки. Звіт також фіксує рішення «прийняли ризик і випустили з відомою проблемою» — у тікетах цього не видно.
Приклад з проєкту: Три спринти поспіль у звітах було видно, що більшість дефектів знаходили в модулі імпорту CSV, і майже всі — на файлах з кирилицею чи порожніми рядками. На ретро команда вирішила додати набір «незручних» файлів у контрактні тести, і в наступному спринті дефектів імпорту стало 2 замість 9.
Підводні камені: Копіювати в звіт список тікетів без аналізу. Писати звіт, який ніхто не обговорює: якщо на ретро його не відкривають, формат треба змінити.
Уточнююче питання: «Що з цього звіту обговорюють на ретроспективі?» Тенденції і причини, а не список тікетів.
What it is: A sprint QA report is a half-page summary: what of the plan was verified, which defects were found and where, what was carried over, which trends changed compared with the previous sprint and what the team changes next. Jira stores facts one by one, while the report shows the picture and the conclusion.
Why and when: It is read at the retrospective and the sprint review, and by people outside the team: the product owner, neighbouring teams, the head of the area. Without a report everyone builds their own filters and draws their own, often different, conclusions. The report also records decisions like «we accepted the risk and shipped with a known problem», which tickets do not show.
From a project: Three sprints in a row the reports showed most defects came from the CSV import module, nearly all on files with Cyrillic text or empty lines. At the retro the team decided to add a set of «awkward» files to the contract tests, and in the next sprint import defects dropped from 9 to 2.
Pitfalls: Pasting the ticket list into the report without analysis. Writing a report nobody discusses: if it is not opened at the retro, change the format.
Follow-up question: «What from this report gets discussed at the retrospective?» Trends and causes, not the ticket list.
QA-звіт · Спринт 37 (2–13 вересня)
Перевірено: 14 з 16 історій; IMP-221 і IMP-224 перенесено (дизайн не затверджено)
Дефекти: знайдено 17 (у спринті 15, після релізу 2) · закрито 19 · відкрито на кінець 6
Де знаходимо: імпорт CSV 9, звіти 4, профіль 2, інше 2
Тенденція: імпорт CSV — третій спринт поспіль лідер за дефектами (9, 8, 9)
Прийнятий ризик: реліз 7.3 вийшов з IMP-230 (повільний імпорт файлів понад 50 МБ)
Пропозиція на ретро: набір «незручних» CSV (кирилиця, BOM, порожні рядки) у контрактні тести
QA report · Sprint 37 (2–13 September)
Verified: 14 of 16 stories; IMP-221 and IMP-224 carried over (design not approved)
Defects: found 17 (15 in sprint, 2 after release) · closed 19 · open at end 6
Where we find them: CSV import 9, reports 4, profile 2, other 2
Trend: CSV import is the top defect source for the third sprint in a row (9, 8, 9)
Accepted risk: release 7.3 shipped with IMP-230 (slow import of files over 50 MB)
Proposal for retro: a set of «awkward» CSVs (Cyrillic, BOM, empty lines) in contract tests
Що це: Test incident report — запис про будь-яку подію під час тестування, яка потребує розбору: результат не збігся з очікуваним, тест поводився дивно, середовище впало посеред прогону. Термін походить з IEEE 829, у ISO/IEC/IEEE 29119-3 це incident report. Ключова різниця з баг-репортом: на момент запису ще невідомо, чи це дефект продукту, помилка в тесті, проблема даних чи середовища.
Навіщо і коли: Його пишуть у регульованих і контрактних проєктах (медицина, фінанси, державні замовлення), де кожна аномалія має бути зафіксована і закрита з рішенням. У звичайній продуктовій команді ту саму роль часто грає тікет типу Test Incident або коментар до тест-рану, але суть та сама: факт, вплив, аналіз, рішення.
Приклад з проєкту: Під час приймального тестування системи для лабораторії 12 результатів аналізів показали одиниці ммоль/л замість мг/дл. Ми оформили incident: після аналізу з'ясувалося, що тестові дані завантажили з європейського профілю. Інцидент закрили як «помилка тестових даних», дефект не створювали, але зафіксували перевірку профілю в чек-листі підготовки.
Підводні камені: Одразу реєструвати кожну аномалію як баг — це засмічує дефектні метрики хибними дефектами. Або навпаки, не фіксувати «дивне» взагалі, і через місяць ніхто не пам'ятає, чому тест-ран позначили повторним.
Уточнююче питання: «Що стається з інцидентом після аналізу?» Він закривається з класифікацією: дефект (створюємо баг і лінкуємо), помилка тесту, дані, середовище або не відтворюється.
What it is: A test incident report records any event during testing that needs investigation: a result did not match the expectation, a test behaved oddly, the environment went down in the middle of a run. The term comes from IEEE 829; in ISO/IEC/IEEE 29119-3 it is the incident report. The key difference from a bug report is that at the time of writing nobody knows yet whether it is a product defect, a test mistake, or a data or environment problem.
Why and when: It is required in regulated and contract projects (medical, finance, government), where every anomaly must be recorded and closed with a decision. In an ordinary product team the same role is often played by a Test Incident ticket type or a comment on the test run, but the substance is identical: fact, impact, analysis, decision.
From a project: During acceptance testing of a laboratory system, 12 lab results showed mmol/L instead of mg/dL. We raised an incident, and analysis showed the test data had been loaded from the European profile. The incident was closed as «test data error», no defect was created, and a profile check was added to the preparation checklist.
Pitfalls: Filing every anomaly straight away as a bug, which fills defect metrics with false defects. Or the opposite: not recording «odd» things at all, so a month later nobody remembers why a test run was marked as repeated.
Follow-up question: «What happens to an incident after analysis?» It is closed with a classification: defect (a bug is created and linked), test error, data, environment, or cannot reproduce.
TEST INCIDENT TI-0147
Дата/час: 2026-09-10 11:42 · Тестувальник: О. Коваль · Етап: приймальне тестування
Об'єкт: LabResults 4.2.0-rc1, середовище uat-lab, тест-кейс LR-TC-088
Опис: 12 результатів глюкози показано в ммоль/л, очікувались мг/дл (US-профіль)
Вплив: блокує 14 кейсів звітності по глюкозі; ризик для пацієнта, якщо це дефект
Аналіз: дані імпортовано з профілю EU (unit_system=SI); у US-профілі одиниці коректні
Класифікація: помилка тестових даних (не дефект продукту)
Рішення: дані перезавантажено, LR-TC-088 повторено — Passed
Запобігання: пункт «перевірити unit_system» додано в чек-лист підготовки UAT
Статус: закрито 2026-09-10 16:05 · підтвердив тест-лід
TEST INCIDENT TI-0147
Date/time: 2026-09-10 11:42 · Tester: O. Koval · Phase: acceptance testing
Item: LabResults 4.2.0-rc1, environment uat-lab, test case LR-TC-088
Description: 12 glucose results shown in mmol/L, expected mg/dL (US profile)
Impact: blocks 14 glucose reporting cases; patient risk if this is a defect
Analysis: data imported from the EU profile (unit_system=SI); units are correct in US profile
Classification: test data error (not a product defect)
Resolution: data reloaded, LR-TC-088 rerun — Passed
Prevention: «check unit_system» added to the UAT preparation checklist
Status: closed 2026-09-10 16:05 · confirmed by test lead
Що це: Це класична пастка: pass rate відображає частку зелених тестів, а не частку ризику. Два падіння в оплаті важать більше, ніж сотня зелених перевірок профілю. Висновок звіту має починатися не з 98%, а з того, що критичний бізнес-потік зламаний і реліз у поточному вигляді не рекомендований.
Навіщо і коли: Керівник, який бачить «98% passed», інтуїтивно читає «майже готово». Тому висновок формулюється через наслідок для бізнесу: хто постраждає, як часто, чи є обхід, що потрібно для зміни рекомендації. Цифри лишаються, але як доказ.
Приклад з проєкту: 588 з 600 тестів зелені, але PAY-501 (подвійне списання при повторному натисканні «Сплатити» на повільній мережі) і PAY-507 (3-D Secure не повертає користувача в застосунок на Android 14). Висновок: NO-GO до виправлення PAY-501; PAY-507 можна випускати, тільки якщо на Android 14 вимкнути нову форму оплати прапорцем. Після фіксу потрібна перевірка оплати на реальних картках у sandbox, приблизно пів дня.
Підводні камені: Ховати криті дефекти в таблицю на третій сторінці. Писати «на розсуд менеджменту» без рекомендації. Або, навпаки, оголошувати реліз заблокованим без варіантів — фіча-прапорець, обхід, частковий реліз.
Уточнююче питання: «А якщо бізнес усе одно вирішить випускати?» Фіксую рішення, власника ризику і план моніторингу подвійних списань після релізу.
What it is: This is the classic trap: pass rate reflects the share of green tests, not the share of risk. Two failures in payments outweigh a hundred green profile checks. The conclusion must not start with 98%; it must start with the fact that a critical business flow is broken and the release is not recommended as it stands.
Why and when: A manager who sees «98% passed» intuitively reads «nearly done». So the conclusion is phrased in terms of business consequence: who is affected, how often, whether there is a workaround, and what would change the recommendation. The numbers stay, but as evidence.
From a project: 588 of 600 tests were green, but PAY-501 (double charge when the Pay button is pressed again on a slow network) and PAY-507 (3-D Secure does not return the user to the app on Android 14) were open. Conclusion: NO-GO until PAY-501 is fixed; PAY-507 can ship only if the new payment form is switched off by flag on Android 14. After the fix, a payment check with real cards in the sandbox is needed, about half a day.
Pitfalls: Burying critical defects in a table on page three. Writing «at management's discretion» without a recommendation. Or the opposite: declaring the release blocked with no options such as a feature flag, a workaround or a partial release.
Follow-up question: «What if the business decides to ship anyway?» I record the decision, the risk owner and a plan to monitor double charges after release.
ВИСНОВОК · Mobile 6.1.0 build 611
Рекомендація: NO-GO до виправлення PAY-501.
Чому: 2 відкриті Critical у платежах.
PAY-501 повторне натискання «Сплатити» при повільній мережі -> подвійне списання.
Відтворюється 4/5 на 3G-профілі. Обходу для користувача немає.
PAY-507 3-D Secure не повертає в застосунок на Android 14 (~31% Android-аудиторії).
Можна випускати, якщо flag new_checkout_form=off для Android 14.
Що змінить рекомендацію на GO: фікс PAY-501 + 0.5 дня перевірки оплати в sandbox.
Для довідки: виконано 600/600, пройдено 588 (98%); решта 10 падінь — Minor у профілі.
CONCLUSION · Mobile 6.1.0 build 611
Recommendation: NO-GO until PAY-501 is fixed.
Why: 2 open Critical defects in payments.
PAY-501 pressing Pay again on a slow network -> double charge.
Reproduces 4/5 on a 3G profile. No workaround for the user.
PAY-507 3-D Secure does not return to the app on Android 14 (~31% of Android users).
Can ship if flag new_checkout_form=off for Android 14.
What changes the recommendation to GO: fix for PAY-501 + 0.5 day of sandbox payment checks.
For reference: executed 600/600, passed 588 (98%); the other 10 failures are Minor in profile.
Що це: Звіт з продуктивності порівнює виміряні значення з заздалегідь погодженими цілями (SLO або нефункціональними вимогами): перцентилі часу відповіді (p95, p99), пропускну здатність, частку помилок і ресурси під певним навантаженням. Performance-дефект — це порушення такої цілі, описане з профілем навантаження, а не фраза «сторінка повільна».
Навіщо і коли: Без цілі і профілю цифри не можна інтерпретувати: 900 мс — це добре для генерації PDF і погано для автодоповнення. Звіт потрібен перед релізами з інфраструктурними змінами, перед сезонними піками і після оптимізацій, щоб показати ефект порівняно з базовою лінією.
Приклад з проєкту: Перед Black Friday ми ганяли сценарій «пошук → картка → кошик» на 1 200 віртуальних користувачах протягом 30 хвилин. Пошук тримав p95 410 мс при цілі 500 мс, а додавання в кошик — 1 850 мс при цілі 800 мс і 2.3% помилок 504. Дефект описали з профілем, збіркою, графіком і тим, що деградація починається з ~800 користувачів; причиною виявився пул з'єднань до Redis на 50.
Підводні камені: Звітувати середнє замість перцентилів — воно ховає хвіст повільних запитів. Порівнювати прогони з різними даними чи середовищем. Не вказувати, на якому навантаженні почалася деградація.
Уточнююче питання: «Чому p95, а не середнє?» Бо середнє 300 мс може означати, що кожен двадцятий користувач чекає 3 секунди.
What it is: A performance report compares measured values with targets agreed in advance (SLOs or non-functional requirements): response time percentiles (p95, p99), throughput, error rate and resource usage under a given load. A performance defect is a breach of such a target described together with the load profile, not the phrase «the page is slow».
Why and when: Without a target and a profile the numbers cannot be interpreted: 900 ms is fine for generating a PDF and bad for autocomplete. The report is needed before releases with infrastructure changes, before seasonal peaks and after optimisations, to show the effect against a baseline.
From a project: Before Black Friday we ran a «search → product page → cart» scenario with 1,200 virtual users for 30 minutes. Search held p95 at 410 ms against a 500 ms target, while add-to-cart hit 1,850 ms against 800 ms with 2.3% of 504 errors. The defect was filed with the profile, build, a chart and the fact that degradation starts at about 800 users; the cause was a Redis connection pool of 50.
Pitfalls: Reporting the average instead of percentiles, which hides the tail of slow requests. Comparing runs with different data or environments. Not stating the load at which degradation began.
Follow-up question: «Why p95 and not the average?» Because an average of 300 ms can mean one user in twenty waits 3 seconds.
LOAD TEST · shop-api 9.4.0 · env perf-1 (копія прод-конфігурації, 3 поди) · 2026-09-15
Профіль: 1200 VU, розгін 5 хв, утримання 30 хв, сценарій search -> product -> add_to_cart
Операція p50 p95 p99 Помилки Ціль p95 Статус
search 120 ms 410 ms 690 ms 0.0% 500 ms OK
product_page 95 ms 280 ms 520 ms 0.0% 400 ms OK
add_to_cart 640 ms 1850 ms 3900 ms 2.3% 800 ms ПОРУШЕНО
Деградація add_to_cart починається з ~800 VU (див. графік latency vs VU)
Дефект: PERF-77 — пул з'єднань Redis = 50, під навантаженням черга до 1.2 с
Базова лінія 9.3.0: add_to_cart p95 = 720 ms при 1200 VU -> регресія
LOAD TEST · shop-api 9.4.0 · env perf-1 (copy of prod config, 3 pods) · 2026-09-15
Profile: 1200 VU, 5 min ramp-up, 30 min hold, scenario search -> product -> add_to_cart
Operation p50 p95 p99 Errors Target p95 Status
search 120 ms 410 ms 690 ms 0.0% 500 ms OK
product_page 95 ms 280 ms 520 ms 0.0% 400 ms OK
add_to_cart 640 ms 1850 ms 3900 ms 2.3% 800 ms BREACHED
add_to_cart degradation starts at ~800 VU (see latency vs VU chart)
Defect: PERF-77 — Redis connection pool = 50, queue up to 1.2 s under load
Baseline 9.3.0: add_to_cart p95 = 720 ms at 1200 VU -> regression
Що це: JUnit XML — текстовий формат результатів тестів, який історично з'явився разом із JUnit і Ant, а тепер його вміє писати майже кожен раннер: Playwright (reporter: 'junit'), pytest (--junitxml), Maven Surefire, Jest через jest-junit. Кореневий <testsuites> містить набори <testsuite> з лічильниками tests, failures, errors, skipped і time, а кожен <testcase> може мати дочірній <failure>, <error> або <skipped>.
Навіщо і коли: Це спільна мова між раннером і платформою: GitLab показує вкладку Tests у merge request, Jenkins малює тренд через плагін JUnit, Azure DevOps і GitHub-екшени для звітів теж читають саме його. Коли в команді три стеки (Java API, Python-сервіси, Playwright UI), один формат дає одну зведену картину без трьох різних порталів.
Приклад з проєкту: У монорепо ми збирали 4 файли junit-*.xml з різних джобів, і GitLab показував у MR, що з 1 212 тестів упав один новий, а два падали й до цієї гілки. Ревʼюер бачив це без жодного відкриття логів.
Підводні камені: Різниця між failure (не виконалась перевірка) і error (тест зламався до перевірки) у різних раннерах трактується по-різному. У форматі немає поняття «flaky» і ретраїв: залежно від раннера перезапущений тест може зʼявитись двічі або лише з останнім результатом. І XML не несе скриншотів — лише текст і шляхи.
Уточнююче питання: «Чим відрізняється failure від error у звіті?» — failure означає, що продукт повів себе не так, як очікувалось; error частіше вказує на проблему тесту або оточення, і таке падіння спершу розбирає автоматизатор.
What it is: JUnit XML is a plain-text format for test results that grew out of JUnit and Ant, and today nearly every runner can write it: Playwright (reporter: 'junit'), pytest (--junitxml), Maven Surefire, Jest via jest-junit. A root <testsuites> holds <testsuite> elements with tests, failures, errors, skipped and time counters, and each <testcase> may carry a <failure>, <error> or <skipped> child.
Why and when: It is the shared language between a runner and a platform: GitLab shows a Tests tab on the merge request, Jenkins draws a trend with the JUnit plugin, and Azure DevOps and GitHub reporting actions read the same file. When a team runs three stacks (Java API, Python services, Playwright UI), one format gives one combined picture instead of three portals.
From a project: In a monorepo we collected four junit-*.xml files from separate jobs, and GitLab showed on the MR that out of 1,212 tests one new test had failed while two were already failing before the branch. The reviewer saw that without opening a single log.
Pitfalls: Runners disagree on failure (an assertion did not hold) versus error (the test broke before reaching the check). The format has no notion of flaky tests or retries, so depending on the runner a rerun test may appear twice or only with its last outcome. And the XML carries no screenshots, only text and paths.
Follow-up question: "How does failure differ from error in the report?" A failure says the product behaved unexpectedly; an error usually points at the test or the environment, so an automation engineer looks at it first.
<testsuites tests="3" failures="1" errors="0" skipped="1" time="14.2">
<testsuite name="checkout.spec.ts" tests="3" failures="1" errors="0" skipped="1" time="14.2">
<testcase name="guest can pay by card" classname="checkout.spec.ts" time="6.1"/>
<testcase name="promo code reduces total" classname="checkout.spec.ts" time="7.9">
<failure message="expect(received).toHaveText(expected)" type="FAILURE">
Expected: "90.00"
Received: "100.00"
</failure>
</testcase>
<testcase name="pay with Apple Pay" classname="checkout.spec.ts" time="0.2">
<skipped/>
</testcase>
</testsuite>
</testsuites>
testsuite — те, що CI показує в заголовку звіту; вкладений failure несе повідомлення перевірки, а порожній skipped позначає пропущений тест. Сума атрибутів має збігатися з кількістю testcase, інакше графіки в CI розʼїдуться.testsuite are what CI shows in the report header; the nested failure carries the assertion message, and an empty skipped marks a skipped test. The counters must match the number of testcase elements, otherwise CI charts drift.Що це: allure-playwright — репортер, який під час прогону пише сирі результати (JSON і вкладення) у папку allure-results. Окремий крок — allure generate або allure serve — перетворює їх на HTML-звіт з кроками, вкладеннями, групуванням за suite, feature і severity.
Навіщо і коли: Три речі роблять Allure корисним саме для звітності, а не лише для перегляду падінь. Environment показує, на якому стенді, браузері й білді йшов прогін. Categories розкладає падіння за правилами на регулярних виразах: «продуктові дефекти», «таймаути інфраструктури», «зламані локатори». History дає тренд і позначку нестабільності тесту між прогонами.
Приклад з проєкту: Після налаштування categories з'ясувалось, що з 37 червоних тестів нічного прогону 29 — це TimeoutError на одному стенді, а реальних продуктових падінь лише 8. Ранкова розмова з командою тривала 5 хвилин замість години.
Підводні камені: History не зберігається сама: CI-джоба мусить перед генерацією скопіювати папку history з попереднього звіту в нові allure-results, інакше тренд завжди порожній. Файл categories.json теж треба класти в results до генерації. Якщо не чистити allure-results між прогонами, результати змішуються і лічильники брешуть.
Уточнююче питання: «Чому тренд в Allure порожній, хоча прогонів було двадцять?» — бо в CI не переносять history між звітами або кожен раз стартують з чистого робочого простору.
What it is: allure-playwright is a reporter that writes raw results (JSON plus attachments) into an allure-results folder during the run. A separate step, allure generate or allure serve, turns them into an HTML report with steps, attachments and grouping by suite, feature and severity.
Why and when: Three features make Allure useful for reporting rather than only for browsing failures. Environment records which stand, browser and build the run used. Categories sorts failures by regex rules into buckets such as product defects, infrastructure timeouts and broken locators. History gives the trend and marks a test as unstable across runs.
From a project: Once categories were in place, it turned out that 29 of the 37 red tests in a nightly run were a TimeoutError on one stand, and only 8 were real product failures. The morning sync with the team took five minutes instead of an hour.
Pitfalls: History does not persist on its own: the CI job has to copy the history folder from the previous report into the new allure-results before generating, or the trend stays empty. The categories.json file must also land in results before generation. If allure-results is not cleaned between runs, results mix and the counters lie.
Follow-up question: "Why is the Allure trend empty after twenty runs?" Because CI does not carry history from one report to the next, or each run starts from a clean workspace.
// playwright.config.ts
reporter: [
['line'],
['allure-playwright', {
resultsDir: 'allure-results',
detail: true,
environmentInfo: { stand: 'staging-2', build: process.env.BUILD_ID },
}],
],
// allure-results/categories.json
[
{ "name": "Інфраструктура: таймаути", "matchedStatuses": ["broken", "failed"],
"traceRegex": ".*TimeoutError.*" },
{ "name": "Продуктові дефекти", "matchedStatuses": ["failed"],
"messageRegex": ".*expect.*" }
]
# CI: перенести history перед генерацією
cp -r previous-report/history allure-results/history
allure generate allure-results --clean -o allure-report
// playwright.config.ts
reporter: [
['line'],
['allure-playwright', {
resultsDir: 'allure-results',
detail: true,
environmentInfo: { stand: 'staging-2', build: process.env.BUILD_ID },
}],
],
// allure-results/categories.json
[
{ "name": "Infrastructure: timeouts", "matchedStatuses": ["broken", "failed"],
"traceRegex": ".*TimeoutError.*" },
{ "name": "Product defects", "matchedStatuses": ["failed"],
"messageRegex": ".*expect.*" }
]
# CI: carry history over before generating
cp -r previous-report/history allure-results/history
allure generate allure-results --clean -o allure-report
environmentInfo потрапляє у віджет Environment; кожне правило categories — це назва кошика, статуси і регулярний вираз по повідомленню чи стеку. Рядок cp -r — саме той крок, без якого немає тренду.environmentInfo feeds the Environment widget; each categories rule is a bucket name, the statuses it applies to and a regex on the message or stack trace. The cp -r line is exactly the step without which there is no trend.Що це: Вкладення — докази до звіту: скриншот стану сторінки, відео прогону, trace Playwright з DOM-знімками, мережею і консоллю на кожен крок. У Playwright вони вмикаються в use і мають режими, що зберігають файли лише тоді, коли вони потрібні.
Навіщо і коли: Червоний тест без артефакту змушує когось перезапускати його локально, а це 10–30 хвилин на кожне падіння. Натомість screenshot: 'only-on-failure', video: 'retain-on-failure' і trace: 'on-first-retry' дають повну картину саме для тих тестів, які впали, і майже нічого не пишуть для зелених.
Приклад з проєкту: Регресія з 900 тестів з video: 'on' залишала близько 6 ГБ артефактів за прогін, і за тиждень ми вперлися в квоту сховища GitHub. Після переходу на режими «тільки при падінні» і retention-days: 7 артефакт нічного прогону важив 150–300 МБ, а розбір падінь не погіршився: trace з першого ретраю показував усе потрібне.
Підводні камені: Без if: always() крок завантаження не виконається, бо попередній крок з тестами завершився помилкою, — і артефакти зникнуть якраз тоді, коли вони потрібні. on-first-retry не дасть trace, якщо ретраї вимкнені. Відео і trace можуть містити токени й персональні дані, тож доступ до артефактів має бути обмежений.
Уточнююче питання: «Чому trace саме на першому ретраї, а не завжди?» — так ми платимо за запис лише там, де тест уже впав один раз, і отримуємо докази саме для нестабільних або зламаних сценаріїв.
What it is: Attachments are the evidence behind a report: a screenshot of the page state, a video of the run, a Playwright trace with DOM snapshots, network and console for every step. In Playwright they are switched on under use, with modes that keep files only when they are needed.
Why and when: A red test with no artefact forces someone to rerun it locally, which costs 10 to 30 minutes per failure. With screenshot: 'only-on-failure', video: 'retain-on-failure' and trace: 'on-first-retry' you get the full picture for exactly the tests that failed and almost nothing stored for green ones.
From a project: A 900-test regression with video: 'on' left about 6 GB of artefacts per run, and within a week we hit the GitHub storage quota. After switching to failure-only modes and retention-days: 7, the nightly artefact weighed 150 to 300 MB, and failure analysis got no worse: the trace from the first retry showed everything we needed.
Pitfalls: Without if: always() the upload step does not run, because the test step before it failed, so the artefacts vanish exactly when you need them. on-first-retry produces no trace if retries are disabled. Videos and traces can contain tokens and personal data, so access to artefacts should be restricted.
Follow-up question: "Why record the trace on the first retry and not always?" That way you pay for recording only where the test has already failed once, and you get evidence precisely for broken or unstable scenarios.
// playwright.config.ts
retries: process.env.CI ? 1 : 0,
use: {
screenshot: 'only-on-failure',
video: 'retain-on-failure',
trace: 'on-first-retry',
},
# .github/workflows/e2e.yml
- name: Завантажити звіт і докази
if: always() # навіть якщо тести впали
uses: actions/upload-artifact@v4
with:
name: playwright-report-${{ github.run_id }}
path: |
playwright-report/
test-results/
retention-days: 7 # не тримати місяцями
// playwright.config.ts
retries: process.env.CI ? 1 : 0,
use: {
screenshot: 'only-on-failure',
video: 'retain-on-failure',
trace: 'on-first-retry',
},
# .github/workflows/e2e.yml
- name: Upload report and evidence
if: always() # even when tests failed
uses: actions/upload-artifact@v4
with:
name: playwright-report-${{ github.run_id }}
path: |
playwright-report/
test-results/
retention-days: 7 # do not keep for months
retries: 1 у CI потрібен, щоб on-first-retry взагалі спрацював; test-results/ містить скриншоти, відео і trace.zip, а playwright-report/ — HTML-звіт, що на них посилається. retention-days обмежує, скільки днів GitHub зберігає артефакт.retries: 1 in CI is what lets on-first-retry fire at all; test-results/ holds screenshots, videos and trace.zip files, while playwright-report/ is the HTML report that links to them. retention-days caps how long GitHub keeps the artefact.Що це: ReportPortal — сервер зі своєю базою, куди агенти раннерів у реальному часі надсилають запуски (launches), тести, логи і вкладення. Статичний звіт на кшталт Allure чи Playwright HTML — це знімок одного прогону; ReportPortal зберігає всі прогони разом і дає їх порівнювати.
Навіщо і коли: Головна фіча — класифікація падінь за типами дефектів: To Investigate, Product Bug, Automation Bug, System Issue, No Defect. Людина один раз розмічає падіння, а Auto-Analysis за схожістю логів пропонує той самий тип для нових падінь. Плюс історія тесту через десятки запусків, фільтри за атрибутами і віджети для дашбордів. Він окупається, коли прогонів багато, команд кілька і на розбір падінь щодня йде година-дві.
Приклад з проєкту: Команда з 4 000 API- і UI-тестів щодня мала 60–80 падінь. Через місяць розмітки Auto-Analysis сам класифікував близько двох третин, і в щотижневому звіті ми показували не «pass rate 94%», а «12 продуктових дефектів, 30 проблем автоматизації, 18 збоїв оточення».
Підводні камені: Це ще один сервіс з базою, оновленнями і бекапами, і комусь його треба підтримувати. Auto-Analysis вчиться лише на ручній розмітці: якщо все лишається в To Investigate, магії не буде. API-ключ не можна комітити в конфіг.
Уточнююче питання: «Коли ReportPortal — надлишок?» — для однієї команди з кількома сотнями тестів і щоденним прогоном зазвичай вистачає Allure з history.
What it is: ReportPortal is a server with its own database to which runner agents stream launches, tests, logs and attachments in real time. A static report such as Allure or the Playwright HTML report is a snapshot of one run; ReportPortal keeps every run together and lets you compare them.
Why and when: The headline feature is classifying failures by defect type: To Investigate, Product Bug, Automation Bug, System Issue, No Defect. A person labels a failure once, and Auto-Analysis suggests the same type for new failures with similar logs. On top of that you get test history across dozens of launches, filters by attributes and widgets for dashboards. It pays off when there are many runs, several teams and an hour or two of daily failure triage.
From a project: A team with 4,000 API and UI tests saw 60 to 80 failures a day. After a month of labelling, Auto-Analysis classified about two thirds on its own, and the weekly report stopped saying "pass rate 94%" and started saying "12 product bugs, 30 automation issues, 18 environment failures".
Pitfalls: It is one more service with a database, upgrades and backups, and someone has to own it. Auto-Analysis only learns from manual labelling: if everything stays in To Investigate, there is no magic. The API key must never be committed into the config.
Follow-up question: "When is ReportPortal overkill?" For a single team with a few hundred tests and one daily run, Allure with history is usually enough.
// playwright.config.ts
const rpConfig = {
apiKey: process.env.RP_API_KEY, // ключ лише зі змінної оточення
endpoint: 'https://rp.example.com/api/v1',
project: 'shop_e2e',
launch: 'nightly-regression',
attributes: [
{ key: 'stand', value: 'staging-2' },
{ key: 'build', value: process.env.BUILD_ID },
],
description: 'Нічна регресія checkout і кабінету',
};
export default defineConfig({
reporter: [['line'], ['@reportportal/agent-js-playwright', rpConfig]],
});
// playwright.config.ts
const rpConfig = {
apiKey: process.env.RP_API_KEY, // key only from an environment variable
endpoint: 'https://rp.example.com/api/v1',
project: 'shop_e2e',
launch: 'nightly-regression',
attributes: [
{ key: 'stand', value: 'staging-2' },
{ key: 'build', value: process.env.BUILD_ID },
],
description: 'Nightly regression for checkout and the account area',
};
export default defineConfig({
reporter: [['line'], ['@reportportal/agent-js-playwright', rpConfig]],
});
launch — назва запуску, за якою ReportPortal будує історію; attributes — пари ключ-значення для фільтрів і віджетів, наприклад стенд і білд. project має вже існувати на сервері.launch is the launch name ReportPortal builds history on; attributes are key-value pairs for filters and widgets, such as stand and build. The project must already exist on the server.Що це: Дашборд у Jira — сторінка з гаджетами, кожен з яких працює поверх збереженого фільтра. Тож спершу пишуться і зберігаються JQL-запити, а вже потім на них накладаються гаджети: Filter Results (список), Two Dimensional Filter Statistics (таблиця, наприклад компонент × severity), Created vs. Resolved Chart (приплив і закриття в часі), Pie Chart (розподіл).
Навіщо і коли: Дашборд прибирає ручні зведення в Excel перед кожним стендапом і демо. Він потрібен, коли стейкхолдери щотижня питають «скільки відкритих критичних» і «чи встигаємо закривати», і відповідь має бути однакова для всіх.
Приклад з проєкту: Для команди з 9 розробників я зібрав дашборд із п'яти гаджетів. Created vs. Resolved за 30 днів показав, що з середини спринту створювали по 14 багів на тиждень, а закривали 6. Це стало аргументом на плануванні взяти в наступний спринт 20% ємності на борг, і за два спринти криві зійшлися.
Підводні камені: Severity — зазвичай кастомне поле, і назви значень у різних проєктах різні, тож фільтри ламаються при копіюванні. Гаджети бачать лише ті задачі, до яких має доступ глядач, тому менеджер і QA можуть бачити різні цифри. Фільтр без statusCategory тягне закриті задачі. І дашборд без власника за пів року заростає мертвими гаджетами.
Уточнююче питання: «Як відрізнити справжній ріст дефектів від того, що ми просто більше тестуємо?» — дивитись на приплив разом з обсягом тестування і на частку знайдених у проді, а не лише на кількість створених.
What it is: A Jira dashboard is a page of gadgets, each running on top of a saved filter. So you first write and save JQL queries, and then put gadgets over them: Filter Results (a list), Two Dimensional Filter Statistics (a table such as component × severity), Created vs. Resolved Chart (inflow versus closure over time), Pie Chart (a breakdown).
Why and when: A dashboard removes the hand-made Excel summaries before every stand-up and demo. It is needed once stakeholders ask each week "how many criticals are open" and "are we keeping up", and everyone should get the same answer.
From a project: For a team of nine developers I built a five-gadget dashboard. The 30-day Created vs. Resolved chart showed that from mid-sprint 14 bugs a week were being created and 6 closed. That became the argument at planning to reserve 20% of the next sprint for the backlog, and within two sprints the curves met.
Pitfalls: Severity is usually a custom field with value names that differ between projects, so filters break when copied. Gadgets only show issues the viewer has permission to see, so a manager and a tester can see different numbers. A filter without statusCategory drags in closed issues. And a dashboard with no owner fills up with dead gadgets within six months.
Follow-up question: "How do you tell a real rise in defects from simply testing more?" Look at inflow together with testing volume and the share found in production, not just the count of created bugs.
# Відкриті баги поточного спринту за severity -> Two Dimensional Filter Statistics
project = SHOP AND issuetype = Bug AND sprint in openSprints()
AND statusCategory != Done ORDER BY priority DESC
# Приплив і закриття -> Created vs. Resolved Chart (30 днів)
project = SHOP AND issuetype = Bug
# Знайдені в проді за 30 днів -> Filter Results
project = SHOP AND issuetype = Bug AND labels = found-in-prod AND created >= -30d
# Перевідкриті за 2 тижні -> Pie Chart за Component
project = SHOP AND issuetype = Bug
AND status CHANGED FROM Done TO Reopened AFTER -14d
# Open bugs in the current sprint by severity -> Two Dimensional Filter Statistics
project = SHOP AND issuetype = Bug AND sprint in openSprints()
AND statusCategory != Done ORDER BY priority DESC
# Inflow and closure -> Created vs. Resolved Chart (30 days)
project = SHOP AND issuetype = Bug
# Found in production over 30 days -> Filter Results
project = SHOP AND issuetype = Bug AND labels = found-in-prod AND created >= -30d
# Reopened within two weeks -> Pie Chart by Component
project = SHOP AND issuetype = Bug
AND status CHANGED FROM Done TO Reopened AFTER -14d
statusCategory != Done не залежить від назв статусів у workflow, а CHANGED FROM … TO … AFTER читає історію переходів, а не поточний статус.statusCategory != Done does not depend on workflow status names, and CHANGED FROM … TO … AFTER reads the transition history rather than the current status.Що це: Grafana сама нічого не зберігає: вона малює запити до джерела даних. Тому спершу потрібен конвеєр: після кожного прогону CI парсить JUnit XML або JSON-результати і пише рядки в таблицю PostgreSQL (прогін, тест, статус, тривалість, ретраї, гілка, коміт). Далі панелі — це SQL з макросами Grafana, наприклад $__timeFilter для обраного періоду.
Навіщо і коли: Jira бачить дефекти, CI бачить окремі прогони, а тренди за місяці — pass rate main-гілки, кількість нестабільних тестів, p95 тривалості набору — не видно ніде. Grafana потрібна, коли рішення про інвестиції в автоматизацію або інфраструктуру треба ухвалювати на даних, а не відчуттях.
Приклад з проєкту: Панель p95 тривалості показала, що регресія повільно зросла з 18 до 41 хвилини за квартал, а панель ретраїв — що половина росту дали 30 тестів із повторними спробами. Після їх виправлення прогін повернувся до 22 хвилин.
Підводні камені і чого не показувати: Панелі «тести на людину» чи «баги на розробника» швидко стають інструментом тиску і метрики починають підганяти. Одне велике число pass rate без знаменника — vanity panel. Не варто змішувати гілки фіч і main: червоні фіча-гілки — нормальна частина роботи. Кожна панель повинна відповідати на питання, яке хтось реально ставить, інакше її прибирають.
Уточнююче питання: «Як рахувати flaky на цих даних?» — тест, що в одному прогоні спершу впав, а на ретраї пройшов, або дав різні результати на тому самому коміті.
What it is: Grafana stores nothing itself; it draws queries against a data source. So you need a pipeline first: after each run CI parses the JUnit XML or JSON results and writes rows into a PostgreSQL table (run, test, status, duration, retries, branch, commit). The panels are then SQL with Grafana macros, for example $__timeFilter for the selected range.
Why and when: Jira sees defects and CI sees individual runs, but trends over months, such as main-branch pass rate, the number of unstable tests or the p95 suite duration, are visible nowhere. Grafana earns its place when investment decisions about automation or infrastructure have to be made on data rather than gut feeling.
From a project: A p95 duration panel showed that the regression had crept from 18 to 41 minutes over a quarter, and a retries panel showed that half of the growth came from 30 tests that kept retrying. Fixing those brought the run back to 22 minutes.
Pitfalls and what to leave out: Panels like "tests per person" or "bugs per developer" quickly turn into pressure tools, and people start gaming them. A single big pass-rate number with no denominator is a vanity panel. Do not mix feature branches with main: red feature branches are a normal part of the work. Every panel has to answer a question someone actually asks, or it goes.
Follow-up question: "How would you count flaky tests from this data?" A test that failed and then passed on retry within one run, or gave different outcomes on the same commit.
-- одна строка = один тест в одному прогоні
CREATE TABLE test_result (
run_id text,
test_id text,
status text, -- passed / failed / skipped
retries int,
duration_ms int,
branch text,
started_at timestamptz
);
-- панель: щоденний pass rate, нестабільні тести і p95 тривалості на main
SELECT
$__timeGroupAlias(started_at, '1d'),
round(100.0 * count(*) FILTER (WHERE status = 'passed')
/ nullif(count(*) FILTER (WHERE status <> 'skipped'), 0), 1) AS pass_rate,
count(DISTINCT test_id) FILTER (WHERE status = 'passed' AND retries > 0) AS flaky_tests,
percentile_cont(0.95) WITHIN GROUP (ORDER BY duration_ms) / 1000 AS p95_sec
FROM test_result
WHERE $__timeFilter(started_at) AND branch = 'main'
GROUP BY 1
ORDER BY 1;
-- one row = one test in one run
CREATE TABLE test_result (
run_id text,
test_id text,
status text, -- passed / failed / skipped
retries int,
duration_ms int,
branch text,
started_at timestamptz
);
-- panel: daily pass rate, unstable tests and p95 duration on main
SELECT
$__timeGroupAlias(started_at, '1d'),
round(100.0 * count(*) FILTER (WHERE status = 'passed')
/ nullif(count(*) FILTER (WHERE status <> 'skipped'), 0), 1) AS pass_rate,
count(DISTINCT test_id) FILTER (WHERE status = 'passed' AND retries > 0) AS flaky_tests,
percentile_cont(0.95) WITHIN GROUP (ORDER BY duration_ms) / 1000 AS p95_sec
FROM test_result
WHERE $__timeFilter(started_at) AND branch = 'main'
GROUP BY 1
ORDER BY 1;
$__timeGroupAlias(started_at, '1d') групує рядки по днях і називає колонку time, як чекає Grafana; $__timeFilter підставляє період з дашборду. Skipped не входить у знаменник pass rate, а «пройшов після ретраю» рахується як нестабільний.$__timeGroupAlias(started_at, '1d') buckets rows by day and names the column time, as Grafana expects; $__timeFilter injects the dashboard's range. Skipped tests stay out of the pass-rate denominator, and "passed after a retry" counts as unstable.Що це: Це метадані задачі в трекері. Component — частина продукту (Checkout, Search, Auth), зазвичай фіксований список з власником. Labels — вільні теги для наскрізних ознак: regression, found-in-prod, automation-gap. Issue links з'єднують баг з історією чи вимогою, яку він порушує, і з тест-кейсом, що його ловить.
Навіщо і коли: Будь-яка метрика у звіті — це зріз: «скільки дефектів у Checkout», «скільки втекло в прод», «які історії спринту ще мають відкриті баги». Без послідовно заповнених полів такий зріз неможливий або його роблять вручну. Заповнювати їх треба в момент створення бага, а не перед звітом.
Приклад з проєкту: Команда домовилась про 6 обов'язкових міток і компонент на кожному баг-репорті. Через квартал один запит показав, що 11 із 17 дефектів з проду припали на компонент Payments і мали мітку automation-gap. Це прямо визначило, куди додати тести.
Підводні камені: Мітки чутливі до написання: regression, Regression і regresion — три різні значення, тож потрібен узгоджений список. Функція issueFunction — з плагіна ScriptRunner, а не стандартна JQL; на іншому інстансі запит просто не спрацює. Стандартна linkedIssues() є, але повертає зв'язки лише однієї задачі.
Уточнююче питання: «Чим компонент кращий за мітку?» — компонент має обмежений список значень і власника, тому для зрізу за частиною продукту він надійніший.
What it is: This is issue metadata in the tracker. A component is a part of the product (Checkout, Search, Auth), usually a fixed list with an owner. Labels are free tags for cross-cutting traits: regression, found-in-prod, automation-gap. Issue links connect a bug to the story or requirement it breaks and to the test case that catches it.
Why and when: Every metric in a report is a slice: "how many defects in Checkout", "how many escaped to production", "which sprint stories still have open bugs". Without consistently filled fields that slice is impossible or done by hand. The fields have to be filled when the bug is created, not right before the report.
From a project: The team agreed on six mandatory labels and a component on every bug. A quarter later, one query showed that 11 of 17 production defects were in the Payments component and carried the automation-gap label. That told us directly where to add tests.
Pitfalls: Labels are spelling-sensitive: regression, Regression and regresion are three separate values, so you need an agreed list. issueFunction comes from the ScriptRunner plugin, not standard JQL, so the query simply fails on another instance. The standard linkedIssues() does exist, but it returns the links of just one issue.
Follow-up question: "Why prefer a component over a label?" A component has a closed list of values and an owner, so it is the more reliable way to slice by product area.
# Дефекти з проду в Payments за квартал
project = SHOP AND issuetype = Bug AND component = Payments
AND labels = found-in-prod AND created >= startOfMonth(-2)
# Ескейпи, для яких бракувало автотесту
project = SHOP AND issuetype = Bug AND labels in (found-in-prod)
AND labels = automation-gap
# Усі баги, пов'язані з історією SHOP-120 (стандартна JQL-функція)
issuetype = Bug AND issue in linkedIssues(SHOP-120)
# Production defects in Payments over the quarter
project = SHOP AND issuetype = Bug AND component = Payments
AND labels = found-in-prod AND created >= startOfMonth(-2)
# Escapes that lacked an automated test
project = SHOP AND issuetype = Bug AND labels in (found-in-prod)
AND labels = automation-gap
# All bugs linked to story SHOP-120 (a standard JQL function)
issuetype = Bug AND issue in linkedIssues(SHOP-120)
startOfMonth(-2) дає початок місяця два місяці тому, тобто приблизно квартал; дві умови по labels вимагають обидві мітки одночасно. linkedIssues(SHOP-120) працює без плагінів і повертає задачі, пов'язані з цією історією.startOfMonth(-2) is the start of the month two months back, roughly a quarter; two separate labels conditions require both labels at once. linkedIssues(SHOP-120) needs no plugin and returns the issues linked to that story.Що це: Тестова метрика — це число, яке відповідає на конкретне питання про тестування чи продукт: скільки перевірено, скільки впало, де з'являються дефекти, як швидко їх виправляють. Без питання метрика перетворюється на цифру заради цифри.
Як групують: Найчастіше ділять на три групи. Процесні описують, як працює тестування: швидкість виконання, частка автоматизації, час на регрес. Продуктові описують сам продукт: кількість і щільність дефектів, leakage у прод, кількість інцидентів. Проєктні описують план: прогрес виконання проти графіка, витрачені години, залишок ризиків. Друга вісь — базові (прямо пораховані: 480 тестів виконано, 12 багів знайдено) і похідні (обчислені з базових: pass rate, defect density, DRE).
Приклад з проєкту: На мобільному банкінгу ми показували менеджменту три похідні метрики — прогрес регресу, відкриті critical і leakage за місяць, — а базові числа лишали в додатку до звіту для тих, хто хоче перевірити розрахунок.
Підводні камені: Збирати все підряд. Двадцять графіків ніхто не читає, а кожна метрика потребує, щоб дані в Jira чи TMS заповнювали однаково.
Уточнююче питання: «Яку одну метрику ви б залишили, якби можна було лише одну?» Гарна відповідь — ту, що найближча до користувача, наприклад leakage, з поясненням чому.
What it is: A test metric is a number that answers a specific question about testing or the product: how much was checked, how much failed, where defects appear and how fast they get fixed. Without a question behind it, a metric is just a figure on a slide.
How they are grouped: The usual split has three groups. Process metrics describe how testing works: execution speed, automation share, time spent on regression. Product metrics describe the product itself: defect count and density, leakage to production, incidents. Project metrics describe the plan: execution progress against schedule, hours spent, risks still open. A second axis is base metrics, counted directly (480 tests executed, 12 bugs found), versus derived ones computed from them (pass rate, defect density, DRE).
From a project: On a mobile banking app we showed management three derived numbers — regression progress, open criticals and monthly leakage — and kept the base counts in an appendix for anyone who wanted to check the maths.
Pitfalls: Collecting everything. Nobody reads twenty charts, and every metric depends on people filling Jira or the TMS in a consistent way.
Follow-up question: "If you could keep only one metric, which would it be?" A strong answer picks the one closest to users, such as leakage, and explains why.
Група | Метрика | Тип | Формула / джерело
------------+----------------------+----------+-----------------------------------------
Процесні | Час регресу | базова | тривалість пайплайна, хв
Процесні | Частка автоматизації | похідна | автотести / усі кейси регресу * 100
Продуктові | Defect density | похідна | дефекти / KLOC або / story points
Продуктові | Leakage | похідна | баги з проду / усі баги релізу * 100
Проєктні | Прогрес виконання | похідна | виконано / заплановано * 100
Проєктні | Відкриті critical | базова | JQL-фільтр, штук
# Приклад: 312 із 400 запланованих кейсів виконано -> 312 / 400 = 78%
Group | Metric | Kind | Formula / source
---------+--------------------+----------+-----------------------------------------
Process | Regression time | base | pipeline duration, min
Process | Automation share | derived | automated / all regression cases * 100
Product | Defect density | derived | defects / KLOC or / story points
Product | Leakage | derived | prod bugs / all bugs of release * 100
Project | Execution progress | derived | executed / planned * 100
Project | Open criticals | base | JQL filter, count
# Example: 312 of 400 planned cases executed -> 312 / 400 = 78%
Що це: Pass rate — частка тестів, що пройшли, від певної бази. Питання завжди в базі: від виконаних чи від запланованих. Від цього залежить, чи буде в звіті 95,9% чи 94%.
Навіщо і коли: Метрика зручна для щоденного статусу і трендів між білдами: якщо вчора було 97%, а сьогодні 88%, щось зламалося. Але вона нічого не каже про те, що саме впало. Один провалений тест оплати важить більше за двадцять тестів на тултіпи.
Приклад з проєкту: У релізі було 500 запланованих кейсів: 470 пройшли, 20 впали, 10 заблоковані через недоступний платіжний сендбокс. Від виконаних (490) виходить 95,9%, від запланованих — 94%. Ми показували обидва числа і окремо рядок «заблоковано: 10, причина — сендбокс».
Підводні камені: Skipped і blocked тести тихо випадають зі знаменника. Якщо 60 тестів позначити як skip, то 420 пройдених із 440 виконаних дають 95,5%, хоча від повного набору це лише 84%. Ще одна пастка — однакова вага для всіх тестів і повторні запуски, які перетворюють червоний тест на зелений.
Уточнююче питання: «98% пройшло, можна релізити?» Відповідь — залежить від того, що в тих 2%: подивитися на пріоритет упалих тестів і відкриті критичні дефекти, а не на відсоток.
What it is: Pass rate is the share of tests that passed against some base. The whole question is which base: executed tests or planned ones. That choice decides whether the report says 95.9% or 94%.
Why and when: It works well for a daily status and for trends between builds: 97% yesterday and 88% today means something broke. It says nothing about what failed, though. One failed payment test outweighs twenty failed tooltip checks.
From a project: A release had 500 planned cases: 470 passed, 20 failed and 10 were blocked because the payment sandbox was down. Against executed tests (490) that is 95.9%, against planned ones it is 94%. We reported both numbers plus a separate line: "blocked: 10, cause: sandbox".
Pitfalls: Skipped and blocked tests silently drop out of the denominator. Mark 60 tests as skipped and 420 passed out of 440 executed gives 95.5%, while against the full suite it is only 84%. Equal weight for every test and retries that turn a red test green are two more traps.
Follow-up question: "98% passed, can we release?" The answer is that it depends on the 2%: look at the priority of the failed tests and the open critical defects, not at the percentage.
Заплановано 500 | Пройшло 470 | Впало 20 | Заблоковано 10
Pass rate від виконаних = 470 / (470 + 20) = 470 / 490 = 95,9%
Pass rate від запланованих = 470 / 500 = 94,0%
Прогрес виконання = 490 / 500 = 98,0%
# Як skip роздуває цифру
Набір 500, skip 60, пройшло 420, впало 20
Від виконаних: 420 / 440 = 95,5%
Від повного набору: 420 / 500 = 84,0%
Planned 500 | Passed 470 | Failed 20 | Blocked 10
Pass rate of executed = 470 / (470 + 20) = 470 / 490 = 95.9%
Pass rate of planned = 470 / 500 = 94.0%
Execution progress = 490 / 500 = 98.0%
# How skips inflate the number
Suite 500, skipped 60, passed 420, failed 20
Of executed: 420 / 440 = 95.5%
Of full suite: 420 / 500 = 84.0%
Що це: Покриття — це відповідь на питання «яку частину того, що мало бути перевірено, ми перевірили». Залежно від того, що вважати «цілим», покриття буває різним, і в звіті треба прямо писати, про яке йдеться.
Види: Покриття вимог — частка user stories чи вимог, до яких прив'язаний хоча б один тест (а краще — виконаний). Покриття ризиків — частка ризиків із ризик-реєстру, для яких є перевірка. Покриття коду — частка рядків чи гілок, виконаних під час тестів. Покриття конфігурацій — частка комбінацій браузерів, ОС чи пристроїв, на яких реально прогнали набір.
Приклад з проєкту: У релізі маркетплейсу було 120 stories, до 102 прив'язані тести — 85%. Із 18 ризиків високого рівня перевірки мали 15 — 83,3%. Матриця 4 браузери × 3 ОС дає 12 комбінацій, прогнали 7 — 58,3%. Саме остання цифра пояснила, чому баг у Safari на macOS прослизнув у прод.
Підводні камені: Змішувати види покриття в одне число. Писати «покриття 85%» без уточнення. Рахувати тест «прив'язаним», навіть якщо він не запускався в цьому релізі.
Уточнююче питання: «Яке покриття важливіше показати бізнесу?» Здебільшого покриття ризиків, бо воно говорить мовою наслідків, а не рядків коду.
What it is: Coverage answers "what share of what should have been checked did we actually check". Depending on what counts as the whole, coverage means different things, and a report has to say explicitly which kind it shows.
Kinds: Requirements coverage is the share of user stories or requirements with at least one linked test (ideally an executed one). Risk coverage is the share of items in the risk register that have a check. Code coverage is the share of lines or branches executed by tests. Configuration coverage is the share of browser, OS or device combinations the suite really ran on.
From a project: A marketplace release had 120 stories and 102 of them had linked tests: 85%. Of 18 high-level risks, 15 had checks: 83.3%. A matrix of 4 browsers × 3 operating systems gives 12 combinations and we ran 7: 58.3%. That last number explained why a Safari-on-macOS bug slipped into production.
Pitfalls: Blending the kinds into one number. Writing "coverage 85%" with no qualifier. Counting a test as linked even though it never ran in this release.
Follow-up question: "Which coverage matters most to the business?" Usually risk coverage, because it talks in consequences rather than lines of code.
Покриття — реліз 3.8.0
-----------------------------------------------------------
Вимоги 102 / 120 stories з тестами = 85,0%
96 / 120 з виконаними тестами = 80,0%
Ризики 15 / 18 високих ризиків = 83,3%
Конфігурації 7 / 12 (4 браузери x 3 ОС) = 58,3%
Код (unit) 7 800 / 10 400 рядків = 75,0%
-----------------------------------------------------------
Не покрито: Safari/macOS, Firefox/Windows, Edge/Linux ...
Coverage — release 3.8.0
-----------------------------------------------------------
Requirements 102 / 120 stories with tests = 85.0%
96 / 120 with executed tests = 80.0%
Risks 15 / 18 high risks = 83.3%
Configs 7 / 12 (4 browsers x 3 OS) = 58.3%
Code (unit) 7,800 / 10,400 lines = 75.0%
-----------------------------------------------------------
Not covered: Safari/macOS, Firefox/Windows, Edge/Linux ...
Що це: Матриця трасування (RTM) зв'язує кожну вимогу чи story з тест-кейсами, результатом їх останнього запуску і відкритими дефектами. Для звіту з неї беруть не саму таблицю, а висновки: скільки вимог покрито, скільки покрито і пройшло, де покрито, але падає.
Навіщо і коли: Перед go/no-go і в кінці спринту. Менеджера цікавить не «скільки тестів зелених», а «які функції готові». RTM відповідає саме на це: PAY-104 має три тести, один червоний і відкритий critical, отже функція не готова.
Приклад з проєкту: У Jira з Xray кожна story мала статус покриття: OK, NOK, NOTRUN або UNCOVERED. Ми вивантажили це по fixVersion і отримали шість stories: три OK, дві NOK, одна UNCOVERED. У TestRail те саме дає поле References у кейсах і звіт покриття за посиланнями. У звіт пішли рядки NOK з номерами багів і рядок UNCOVERED з поясненням, чому тестів немає.
Підводні камені: Вважати вимогу покритою, бо до неї прив'язано кейс, який ніхто не запускав. Не оновлювати зв'язки, коли story розбивають на дві. Пхати в звіт усю матрицю на сотню рядків.
Уточнююче питання: «Що робити, якщо в кінці спринту знайшлася story без тестів?» Не малювати покриття — записати її як відомий ризик і сказати, хто і коли її перевірить.
What it is: A requirements traceability matrix (RTM) links each requirement or story to its test cases, their latest result and open defects. What goes into a report is not the table itself but the conclusions: how many requirements are covered, how many are covered and passing, and where something is covered yet failing.
Why and when: Before a go/no-go and at the end of a sprint. A manager wants to know which features are ready, not how many tests are green. The RTM answers exactly that: PAY-104 has three tests, one red, plus an open critical, so the feature is not ready.
From a project: In Jira with Xray every story had a coverage status: OK, NOK, NOTRUN or UNCOVERED. We exported it by fixVersion and got six stories: three OK, two NOK, one UNCOVERED. In TestRail the References field on cases and the coverage-for-references report give the same picture. The report listed the NOK rows with bug keys and the UNCOVERED row with the reason it had no tests.
Pitfalls: Treating a requirement as covered because a case is linked that nobody ran. Not updating links when a story is split in two. Pasting a hundred-row matrix into the report.
Follow-up question: "What if a story turns up without tests at the end of the sprint?" Do not dress up the coverage — record it as a known risk and say who will check it and when.
Story | Тест-кейси | Останній прогін | Відкриті баги | Статус
---------+-----------------------+-------------------+-----------------+----------
PAY-101 | TC-11, TC-12 | 2 пройшли | — | OK
PAY-102 | TC-13 | пройшов | — | OK
PAY-103 | TC-14, TC-15 | 2 пройшли | PAY-331 (minor) | OK
PAY-104 | TC-16, TC-17, TC-18 | 2 пройшли, 1 впав | PAY-340 (crit) | NOK
PAY-105 | TC-19 | впав | PAY-342 (major) | NOK
PAY-106 | — | — | — | UNCOVERED
Покрито тестами: 5 / 6 = 83,3%
Покрито і пройшло: 3 / 6 = 50,0%
Покрито, але падає: PAY-104, PAY-105
Без тестів: PAY-106 (експорт CSV, тестує Олена до 14.05)
Story | Test cases | Latest run | Open bugs | Status
---------+-----------------------+-------------------+-----------------+----------
PAY-101 | TC-11, TC-12 | 2 passed | — | OK
PAY-102 | TC-13 | passed | — | OK
PAY-103 | TC-14, TC-15 | 2 passed | PAY-331 (minor) | OK
PAY-104 | TC-16, TC-17, TC-18 | 2 passed, 1 fail | PAY-340 (crit) | NOK
PAY-105 | TC-19 | failed | PAY-342 (major) | NOK
PAY-106 | — | — | — | UNCOVERED
Covered by tests: 5 / 6 = 83.3%
Covered and passing: 3 / 6 = 50.0%
Covered but failing: PAY-104, PAY-105
No tests: PAY-106 (CSV export, Olena to test by 14 May)
Що це: Покриття коду — частка коду, яку виконали тести: рядки (line), інструкції (statement), гілки умов (branch), функції. Його рахують інструменти на кшталт JaCoCo для JVM чи Istanbul/nyc для JavaScript.
Що воно каже насправді: Низьке покриття — надійний сигнал: код, який жодного разу не виконувався, точно не перевірений. Високе покриття нічого не гарантує, бо інструмент бачить лише, що рядок виконався, а не те, що результат перевірили. Тест без жодного assert може дати 100% рядків.
Приклад з проєкту: Сервіс розрахунку знижок мав 92% рядків і лише 62,5% гілок. Різниця вказала на умови з кількома вітками, де тестували тільки щасливий шлях. Саме в непокритій гілці «знижка більша за суму кошика» сидів баг з від'ємною ціною. Після цього ми замінили глобальний поріг на перевірку покриття змінених рядків у PR: diff-cover з порогом 80% лише для нового коду.
Підводні камені: Робити загальний відсоток KPI — команда почне писати тести без перевірок. Дивитися тільки на line coverage. Рахувати покриття коду як покриття вимог.
Уточнююче питання: «Як перевірити, що тести справді щось перевіряють?» Мутаційне тестування (PIT, Stryker): якщо мутант вижив, асерту не вистачає.
What it is: Code coverage is the share of code executed by tests: lines, statements, condition branches, functions. Tools such as JaCoCo for the JVM or Istanbul/nyc for JavaScript measure it.
What it really says: Low coverage is a reliable signal: code that never ran is definitely untested. High coverage guarantees nothing, because the tool only sees that a line executed, not that anyone checked the result. A test with no assertion at all can give 100% line coverage.
From a project: A discount service had 92% line coverage but only 62.5% branch coverage. The gap pointed at multi-way conditions where only the happy path was exercised. The uncovered branch "discount larger than cart total" held a bug that produced a negative price. After that we replaced the global threshold with a check on changed lines in each PR: diff-cover with an 80% bar for new code only.
Pitfalls: Making the overall percentage a KPI, which leads to tests without checks. Looking at line coverage alone. Treating code coverage as requirements coverage.
Follow-up question: "How do you verify that tests actually check something?" Mutation testing (PIT, Stryker): a surviving mutant means a missing assertion.
# npx nyc report --reporter=text-summary
Statements : 91.2% ( 1824/2000 )
Branches : 62.5% ( 250/400 )
Functions : 88.0% ( 176/200 )
Lines : 92.0% ( 1748/1900 )
// covers every line of applyDiscount, verifies nothing
test('applies discount', () => {
applyDiscount({ total: 50 }, { percent: 120 });
});
# PR gate: only lines changed against main must be covered
diff-cover coverage.xml --compare-branch=origin/main --fail-under=80
Що це: Flaky-тест на тому самому коді (тому самому коміті) то проходить, то падає. Ключ — «той самий коміт»: якщо результат змінився після зміни коду, це може бути нормальна реакція тесту, а не нестабільність.
Як виміряти: Потрібна історія запусків: тест, коміт, статус, час. Далі для кожної пари «тест + коміт» дивимося, чи були там і passed, і failed. Рівень нестабільності тесту — частка комітів, на яких він «мигав». Рівень для набору — частка тестів, що мигнули хоча б раз за період. Ще одна корисна цифра — скільки червоних пайплайнів були червоними тільки через flaky.
Приклад з проєкту: Ми писали результати з JUnit XML у Postgres. За 14 днів 38 із 1 250 тестів мали хоча б один коміт з обома результатами — 3,04% набору. Із 180 запусків main 27 були червоними тільки через такі тести — 15% пайплайнів, тобто кожен сьомий «червоний» був хибною тривогою. Найгірший тест мигав на 9 із 20 комітів.
Підводні камені: Рахувати flaky на різних комітах. Вважати все, що пройшло з ретраю, нормою. Не відрізняти нестабільний тест від справжнього періодичного бага в продукті.
Уточнююче питання: «Що ви робите з тестом, у якого 45% нестабільності?» Виводжу в карантин з власником і терміном, а не просто додаю ретраї.
What it is: A flaky test passes and fails on the same code, meaning the same commit. The same commit is the key part: if the result changed after a code change, the test may simply be doing its job.
How to measure it: You need run history: test, commit, status, timestamp. For every test-and-commit pair, check whether both passed and failed occurred. A test's flakiness rate is the share of commits where it flipped. The suite rate is the share of tests that flipped at least once in the period. Another useful figure is how many red pipelines were red only because of flaky tests.
From a project: We loaded JUnit XML results into Postgres. Over 14 days 38 of 1,250 tests had at least one commit with both outcomes, which is 3.04% of the suite. Of 180 runs on main, 27 were red solely because of those tests: 15% of pipelines, so one in seven red builds was a false alarm. The worst test flipped on 9 of 20 commits.
Pitfalls: Counting flips across different commits. Treating anything that passed on retry as fine. Not telling a flaky test apart from a genuinely intermittent product bug.
Follow-up question: "What do you do with a test that is 45% flaky?" Quarantine it with an owner and a deadline instead of adding more retries.
-- test_runs(test_id, commit_sha, status, started_at)
WITH per_commit AS (
SELECT test_id, commit_sha,
COUNT(DISTINCT status) AS outcomes
FROM test_runs
WHERE started_at >= NOW() - INTERVAL '14 days'
AND status IN ('passed', 'failed')
GROUP BY test_id, commit_sha
)
SELECT test_id,
COUNT(*) FILTER (WHERE outcomes > 1) AS flaky_commits,
COUNT(*) AS commits,
ROUND(100.0 * COUNT(*) FILTER (WHERE outcomes > 1) / COUNT(*), 1) AS flaky_pct
FROM per_commit
GROUP BY test_id
HAVING COUNT(*) FILTER (WHERE outcomes > 1) > 0
ORDER BY flaky_pct DESC;
-- suite rate: 38 flaky tests / 1250 tests = 3.04%
-- pipeline noise: 27 flaky-only red runs / 180 runs = 15%
Проблема: Коли кожна третя збірка червона без причини, люди перестають читати звіти і тиснуть «перезапустити». Тоді справжнє падіння теж ігнорують. Нестабільність шкодить не тільки часу, а й довірі до всього сигналу.
Що роблю: По-перше, окремий статус. Playwright із ретраями позначає тест, що впав і потім пройшов, як flaky, і в JSON-звіті є окремий лічильник stats.flaky. Allure теж показує позначку flaky з історії. Ці числа йдуть у звіт окремим рядком, а не ховаються серед зелених. По-друге, карантин: тест з тегом @quarantine виконується в окремому проєкті, який не блокує мердж, але має власника і термін — наприклад, 10 робочих днів на виправлення або видалення. По-третє, ліміт: якщо в карантині понад 2% набору, нові тести туди не беруть, доки не розберуть старі.
Приклад з проєкту: Після запуску карантину частка хибних червоних збірок на main упала з 15% до 3% за місяць, а 11 з 38 тестів ми видалили як дублікати.
Компроміс: Ретраї рятують конвеєр, але приховують сигнал; без ретраїв сигнал чесний, але конвеєр постійно стоїть. Тому ретраї лише в CI, максимум два, а кожен flaky-результат рахується.
Уточнююче питання: «А якщо тест flaky, бо продукт сам нестабільний?» Тоді це баг продукту, його заводять і тест у карантин не відправляють.
The problem: When every third build is red for no reason, people stop reading reports and just hit rerun. Then a real failure gets ignored too. Flakiness costs time, but it also costs trust in the whole signal.
What I do: First, a separate status. With retries enabled, Playwright marks a test that failed and then passed as flaky, and its JSON report has a dedicated stats.flaky counter. Allure also shows a flaky marker from history. These numbers go into the report on their own line instead of hiding among the greens. Second, quarantine: a test tagged @quarantine runs in a separate project that does not block merges, but it has an owner and a deadline, say ten working days to fix or delete. Third, a cap: if quarantine holds more than 2% of the suite, nothing new goes in until old entries are cleared.
From a project: After quarantine went live, false red builds on main dropped from 15% to 3% in a month, and we deleted 11 of 38 tests as duplicates.
Trade-off: Retries keep the pipeline moving but hide the signal; no retries keeps the signal honest but the pipeline keeps stalling. So retries run only in CI, two at most, and every flaky result is counted.
Follow-up question: "What if the test is flaky because the product itself is unstable?" Then it is a product bug: file it, and do not quarantine the test.
// playwright.config.ts
import { defineConfig } from '@playwright/test';
export default defineConfig({
retries: process.env.CI ? 2 : 0, // пройшов після ретраю -> статус flaky
reporter: [['html'], ['json', { outputFile: 'results.json' }]],
projects: [
{ name: 'gate', grepInvert: /@quarantine/ }, // блокує мердж
{ name: 'quarantine', grep: /@quarantine/ }, // лише звітує
],
});
// Правила карантину
// 1. Кожен тест у карантині має власника і тікет.
// 2. 10 робочих днів: виправити або видалити.
// 3. Карантин > 2% набору -> нові тести не додаємо.
// 4. У звіті окремий рядок: flaky = stats.flaky з results.json.
// playwright.config.ts
import { defineConfig } from '@playwright/test';
export default defineConfig({
retries: process.env.CI ? 2 : 0, // passed after a retry -> status flaky
reporter: [['html'], ['json', { outputFile: 'results.json' }]],
projects: [
{ name: 'gate', grepInvert: /@quarantine/ }, // blocks merge
{ name: 'quarantine', grep: /@quarantine/ }, // reports only
],
});
// Quarantine rules
// 1. Every quarantined test has an owner and a ticket.
// 2. 10 working days: fix it or delete it.
// 3. Quarantine > 2% of the suite -> no new entries.
// 4. The report has its own line: flaky = stats.flaky from results.json.
Що це: Метрики автоматизації показують, чи набір автотестів ще допомагає, чи вже заважає. Кількість тестів тут майже нічого не каже; важливіше, як швидко вони дають відповідь, наскільки їй можна вірити і скільки годин ідуть на їхнє утримання.
Що відстежувати: Час виконання: медіана і p95 тривалості набору, бо середнє ховає повільні прогони. Стабільність: частка flaky і частка хибних червоних збірок. Вартість підтримки: години на лагодження тестів відносно всіх годин на автоматизацію. Динаміка набору: скільки тестів додали і скільки прибрали за тиждень. І корисність: скільки справжніх дефектів зловили автотести за період.
Приклад з проєкту: За шість тижнів p95 регресу виріс з 26 до 41 хвилини, а частка підтримки дійшла до 30%. Розбір показав, що новий логін через SSO додавав по 20 секунд на тест. Ми перейшли на збережений стан авторизації, і p95 повернувся до 24 хвилин.
Підводні камені: Рахувати «кількість автотестів» як успіх. Не вести облік годин на підтримку — тоді автоматизація здається безкоштовною. Дивитися на один тиждень, а не на тренд.
Уточнююче питання: «Яка частка підтримки нормальна?» Чіткої норми немає; тривожний сигнал — стабільне зростання тиждень до тижня.
What it is: Automation metrics tell you whether the test suite still helps or has started getting in the way. The number of tests says almost nothing here; what matters is how fast the suite answers, how far you can trust the answer and how many hours go into keeping it alive.
What to track: Execution time: median and p95 suite duration, because an average hides the slow runs. Stability: flaky share and the share of false red builds. Maintenance cost: hours spent fixing tests relative to all automation hours. Suite churn: tests added and removed per week. And usefulness: how many real defects the automated tests caught in the period.
From a project: Over six weeks regression p95 grew from 26 to 41 minutes and the maintenance share reached 30%. The analysis showed that a new SSO login added about 20 seconds to every test. We switched to a saved authentication state and p95 went back to 24 minutes.
Pitfalls: Treating the number of automated tests as success. Not logging maintenance hours, so automation looks free. Looking at a single week instead of the trend.
Follow-up question: "What maintenance share is normal?" There is no fixed norm; the warning sign is steady week-on-week growth.
Тиждень | Тести | p50 | p95 | Flaky | Лагодження, год | Додано/прибрано | Спіймано багів
--------+-------+-------+-------+-------+-----------------+-----------------+---------------
W14 | 1180 | 18 хв | 26 хв | 2,1% | 6 | +22 / -4 | 5
W16 | 1215 | 21 хв | 31 хв | 2,8% | 11 | +19 / -2 | 3
W18 | 1248 | 27 хв | 38 хв | 3,5% | 19 | +17 / -1 | 4
W19 | 1250 | 29 хв | 41 хв | 3,6% | 24 | +3 / -1 | 2
# Частка підтримки (W19) = години на лагодження / усі години автоматизації
# = 24 / 80 (2 інженери x 40 год) = 30%
Week | Tests | p50 | p95 | Flaky | Fix hours | Added/removed | Bugs caught
-----+-------+--------+--------+-------+-----------+---------------+------------
W14 | 1180 | 18 min | 26 min | 2.1% | 6 | +22 / -4 | 5
W16 | 1215 | 21 min | 31 min | 2.8% | 11 | +19 / -2 | 3
W18 | 1248 | 27 min | 38 min | 3.5% | 19 | +17 / -1 | 4
W19 | 1250 | 29 min | 41 min | 3.6% | 24 | +3 / -1 | 2
# Maintenance share (W19) = fix hours / all automation hours
# = 24 / 80 (2 engineers x 40 h) = 30%
Формула: ROI = (заощаджене − вкладене) / вкладене. Заощаджене — це ручна робота, яку автотести замінили, мінус те, що вони самі коштують у підтримці. Вкладене — години на побудову фреймворку й тестів.
Приклад з проєкту: Ручний регрес займав 3 дні у двох тестувальників: 48 годин на реліз, 24 релізи на рік — 1 152 години. Після автоматизації лишився день ручного дослідницького тестування одним тестувальником (8 годин × 24 = 192) і близько 6 годин підтримки на реліз (144 на рік). Економія — 1 152 − 192 − 144 = 816 годин на рік. Побудова коштувала 600 годин. ROI першого року: (816 − 600) / 600 = 36%. Окупність: 816 / 24 = 34 години економії на реліз, 600 / 34 ≈ 17,6, тобто на 18-му релізі, приблизно через 9 місяців.
Коли число нечесне: Коли рахують «години ручного виконання» для тестів, які вручну ніхто б і не запускав. Коли забувають підтримку, інфраструктуру CI і час на розбір flaky-падінь. Коли в набір входять тести, що за рік не впали жодного разу через реальний баг. І навпаки — формула не бачить головного виграшу: відповідь за 20 хвилин замість трьох днів, що дає змогу релізити частіше.
Уточнююче питання: «ROI вийшов від'ємним — автоматизацію закривати?» Не обов'язково: спершу подивитися, що саме дорого (нестабільні UI-тести?), і перенести перевірки нижче по піраміді.
The formula: ROI = (savings − investment) / investment. Savings are the manual work the automated tests replaced, minus what the tests themselves cost to maintain. Investment is the hours spent building the framework and the tests.
From a project: Manual regression took two testers three days: 48 hours per release, 24 releases a year, 1,152 hours. After automation, one tester still spent a day on exploratory testing (8 hours × 24 = 192) and maintenance took about 6 hours per release (144 a year). Savings: 1,152 − 192 − 144 = 816 hours a year. Building it cost 600 hours. First-year ROI: (816 − 600) / 600 = 36%. Break-even: 816 / 24 = 34 hours saved per release, 600 / 34 ≈ 17.6, so on the 18th release, about nine months in.
When the number is dishonest: When it counts "manual execution hours" for tests nobody would ever have run by hand. When it forgets maintenance, CI infrastructure and time spent triaging flaky failures. When the suite includes tests that did not fail once in a year because of a real bug. And the other way round: the formula misses the biggest gain, an answer in 20 minutes instead of three days, which is what lets a team release more often.
Follow-up question: "ROI came out negative — should we drop automation?" Not necessarily: first find what is expensive (flaky UI tests?) and move those checks lower in the pyramid.
Вхідні дані (на рік, 24 релізи)
Ручний регрес: 2 тестувальники x 3 дні x 8 год = 48 год/реліз -> 1152 год
Лишилось вручну: 1 x 1 день x 8 год = 8 год/реліз -> 192 год
Підтримка автотестів: 6 год/реліз -> 144 год
Побудова фреймворку: 600 год (одноразово)
Економія за рік = 1152 - 192 - 144 = 816 год
ROI рік 1 = (816 - 600) / 600 = 0,36 -> 36%
ROI рік 2 = (816 * 2 - 600) / 600 = 1,72 -> 172%
Окупність = 600 / (816 / 24) = 600 / 34 = 17,6 -> 18-й реліз (~9 міс.)
# Не входить у формулу: швидкість зворотного зв'язку (20 хв проти 3 днів),
# час на розбір flaky, тести, що за рік не зловили жодного бага
Inputs (per year, 24 releases)
Manual regression: 2 testers x 3 days x 8 h = 48 h/release -> 1152 h
Still manual: 1 x 1 day x 8 h = 8 h/release -> 192 h
Test maintenance: 6 h/release -> 144 h
Framework build: 600 h (one-off)
Savings per year = 1152 - 192 - 144 = 816 h
ROI year 1 = (816 - 600) / 600 = 0.36 -> 36%
ROI year 2 = (816 * 2 - 600) / 600 = 1.72 -> 172%
Break-even = 600 / (816 / 24) = 600 / 34 = 17.6 -> 18th release (~9 months)
# Not in the formula: feedback speed (20 min vs 3 days),
# time spent triaging flaky tests, tests that caught no bug all year
Що це: У звітності регресія — це те, що працювало в попередньому білді й перестало працювати в поточному. Для звіту важливо не просто кількість упалих тестів, а саме нові падіння: тести, які на білді N−1 були зелені, а на білді N стали червоні.
Навіщо і коли: Регресії найнебезпечніші для релізу: користувачі вже звикли, що функція працює, і зламана стара функція болить сильніше, ніж недороблена нова. Тому в звіті по регресійному прогону роблять окремий розділ «нові падіння порівняно з попереднім білдом», із номером білду, де тест востаннє пройшов.
Приклад з проєкту: Білд 2.14.0 проти 2.13.2: із 640 тестів регресу 9 впали, з них 4 — нові. Два нові падіння в пошуку, одне в експорті, одне в налаштуваннях сповіщень. Решта 5 падали й раніше через відомі баги. У звіті ми виділили саме чотири нові й указали, в якому діапазоні комітів шукати причину. Allure показує це з коробки, якщо зберігати історію між запусками.
Підводні камені: Змішувати нові й давні падіння в одне число. Порівнювати з білдом, де частину тестів не запускали. Не показувати тренд: кількість нових регресій за кілька релізів поспіль.
Уточнююче питання: «Звідки брати попередній результат?» З історії Allure, збережених JUnit XML або з бази результатів.
What it is: In reporting, a regression is something that worked in the previous build and stopped working in the current one. The report should not just count failed tests; it should call out new failures: tests that were green on build N−1 and turned red on build N.
Why and when: Regressions are the riskiest thing for a release: users already rely on the feature, and a broken old feature hurts more than an unfinished new one. So a regression run report gets its own section, "new failures compared to the previous build", with the build where each test last passed.
From a project: Build 2.14.0 against 2.13.2: 9 of 640 regression tests failed, and 4 of those were new. Two new failures were in search, one in export and one in notification settings. The other 5 had been failing before because of known bugs. The report singled out the four new ones and named the commit range to look at. Allure shows this out of the box when history is kept between runs.
Pitfalls: Merging new and old failures into one number. Comparing with a build where part of the suite did not run. Not showing the trend: new regressions across several releases in a row.
Follow-up question: "Where does the previous result come from?" From Allure history, stored JUnit XML files or a results database.
Регресія: білд 2.14.0 проти 2.13.2 (640 тестів)
Впало всього: 9 | Нові падіння: 4 | Відомі (з відкритими багами): 5
Нові падіння
Тест | Область | Востаннє пройшов | Баг
-----------------------------------+---------------+------------------+---------
search_filters_by_price_range | Пошук | 2.13.2 | SRCH-512
search_sorting_keeps_page | Пошук | 2.13.2 | SRCH-513
export_orders_csv_utf8 | Експорт | 2.13.2 | EXP-88
notifications_email_toggle_saved | Налаштування | 2.13.1 | NTF-47
Діапазон комітів для пошуку причини: a41f9c2..e07d3b1
Тренд нових регресій: 2.12 -> 1, 2.13 -> 2, 2.14 -> 4
Regression: build 2.14.0 vs 2.13.2 (640 tests)
Failed total: 9 | New failures: 4 | Known (open bugs): 5
New failures
Test | Area | Last passed | Bug
-----------------------------------+---------------+-------------+---------
search_filters_by_price_range | Search | 2.13.2 | SRCH-512
search_sorting_keeps_page | Search | 2.13.2 | SRCH-513
export_orders_csv_utf8 | Export | 2.13.2 | EXP-88
notifications_email_toggle_saved | Settings | 2.13.1 | NTF-47
Commit range to investigate: a41f9c2..e07d3b1
New regressions trend: 2.12 -> 1, 2.13 -> 2, 2.14 -> 4
Що це: Кожна метрика дефектів — це просто запит до трекера, згрупований за якимось полем. Leakage рахується за полем «де знайдено», density — за компонентом, розподіл за важливістю — за severity. Якщо поле порожнє або кожен заповнює його по-своєму, графік показує не якість продукту, а дисципліну заповнення. Як написати сам репорт (заголовок, кроки, expected/actual, вкладення), детально розібрано в гайді «Bug Reporting»; тут мова про поля, з яких потім будуються цифри.
Навіщо і коли: Мінімальний набір для звітності: severity і priority за письмовою шкалою команди, environment, affected version (build, де знайдено) і fix version, component, фаза виявлення (QA, UAT, production), root cause після виправлення, labels за домовленим словником і лінк на user story або вимогу. Лінк дає трасованість: видно, які історії найбільш «дірявлять».
Приклад з проєкту: У звіті за квартал leakage раптом упав до 1%. Виявилося, що в 34 з 41 продакшн-бага поле Found in лишилося порожнім, бо баги заводила підтримка через іншу форму. Після того як поле стало обов'язковим на екрані створення, реальний показник виявився 7%.
Підводні камені: Вільний текст замість списку значень (Prod, prod, production) розбиває одну категорію на три. Root cause, який заповнює QA замість розробника, часто вгадується.
Уточнююче питання: «Як змусити команду заповнювати поля?» — зробити їх обов'язковими у workflow на конкретному переході, наприклад root cause при переході в Resolved, і показувати на дашборді кількість багів із порожніми полями.
What it is: Every defect metric is just a tracker query grouped by some field. Leakage is counted by the "found in" field, density by component, the severity split by severity. If a field is empty or everyone fills it differently, the chart shows data-entry discipline rather than product quality. How to write the report itself (title, steps, expected/actual, attachments) is covered in detail in the Bug Reporting guide; this card is about the fields the numbers are built from.
Why and when: The minimum for reporting: severity and priority on the team's written scale, environment, affected version (the build where it was found) and fix version, component, detection phase (QA, UAT, production), root cause once fixed, labels from an agreed vocabulary, and a link to the user story or requirement. The link gives traceability: you can see which stories leak the most.
From a project: In a quarterly report leakage suddenly dropped to 1%. It turned out that 34 of the 41 production bugs had an empty Found in field because support filed them through a different form. Once the field became mandatory on the create screen, the real figure turned out to be 7%.
Pitfalls: Free text instead of a value list (Prod, prod, production) splits one category into three. A root cause filled in by QA instead of the developer is often a guess.
Follow-up question: "How do you make the team fill the fields?" Make them required in the workflow on a specific transition, for example root cause on the move to Resolved, and show the count of bugs with empty fields on the dashboard.
Поле Тип Обов'язкове коли Метрика, що від нього залежить
Severity список створення розподіл за важливістю, exit criteria
Priority список triage черга виправлень
Environment список створення дефекти за середовищем
Affected version версія створення density на реліз, регресії
Fix version версія Resolved що увійде в реліз
Component список створення density за модулем
Found in QA/UAT/Prod створення leakage, DRE
Root cause список Resolved Pareto-аналіз причин
Labels словник за потреби зрізи (regression, a11y, perf)
Link to story issue link створення трасованість, покриття вимог
# Контроль якості даних (JQL):
project = SHOP AND issuetype = Bug AND "Found in[Dropdown]" is EMPTY
Field Type Required when Metric that depends on it
Severity list create severity split, exit criteria
Priority list triage fix queue
Environment list create defects per environment
Affected version version create density per release, regressions
Fix version version Resolved what ships in the release
Component list create density per module
Found in QA/UAT/Prod create leakage, DRE
Root cause list Resolved root cause Pareto
Labels vocabulary as needed slices (regression, a11y, perf)
Link to story issue link create traceability, requirement coverage
# Data quality check (JQL):
project = SHOP AND issuetype = Bug AND "Found in[Dropdown]" is EMPTY
Що це: Defect density — кількість підтверджених дефектів, поділена на розмір того, що тестували. Класична одиниця — дефекти на 1000 рядків коду (KLOC); в Agile-командах частіше рахують на story point, на фічу або на модуль. Сама по собі цифра мало що каже; цінність у порівнянні модулів між собою або одного модуля між релізами.
Навіщо і коли: Density показує, куди спрямувати регресію, code review і рефакторинг. Модуль із щільністю втричі вищою за середню — кандидат на додаткові тести і на розмову з архітектором. Метрика корисна на ретро релізу і при плануванні тестового покриття.
Приклад з проєкту: За реліз у Checkout знайшли 30 дефектів на 12 KLOC — 2,5 на KLOC. У Catalog 36 дефектів, більше в абсолютних цифрах, але на 40 KLOC — лише 0,9. Без нормалізації менеджмент хотів «посилити» Catalog; після розрахунку додаткові тести і парний review пішли в Checkout, і наступний реліз там дав 1,1 на KLOC.
Підводні камені: Дублікати і «Not a Bug» у чисельнику завищують показник. Рядки коду — погана міра розміру: згенерований код і конфіги роздувають знаменник. Порівнювати density між командами з різною культурою заведення багів некоректно: команда, яка чесно фіксує все, виглядатиме гірше. І найгірше — ставити density ціллю для розробників: баги почнуть закривати в чаті без тікетів.
Уточнююче питання: «Яку одиницю розміру обрати?» — ту, що стабільна у вашій команді: KLOC для зрілого моноліту, story points або кількість змінених файлів для команди, що часто рефакторить.
What it is: Defect density is the number of confirmed defects divided by the size of what was tested. The classic unit is defects per 1000 lines of code (KLOC); Agile teams more often count per story point, per feature or per module. The figure alone says little; the value is in comparing modules with each other or one module across releases.
Why and when: Density shows where to aim regression, code review and refactoring. A module with three times the average density is a candidate for extra tests and a talk with the architect. The metric is useful in a release retro and when planning test coverage.
From a project: In one release Checkout had 30 defects on 12 KLOC, which is 2.5 per KLOC. Catalog had 36 defects, more in absolute terms, but on 40 KLOC that is only 0.9. Without normalising, management wanted to "reinforce" Catalog; after the calculation the extra tests and pair review went to Checkout, and the next release there came in at 1.1 per KLOC.
Pitfalls: Duplicates and "Not a Bug" in the numerator inflate the figure. Lines of code are a poor size measure: generated code and configs bloat the denominator. Comparing density across teams with different bug-filing habits is unfair, because the team that honestly logs everything looks worse. Worst of all is turning density into a target for developers: bugs start getting fixed in chat with no ticket.
Follow-up question: "Which size unit should you choose?" Whichever is stable for your team: KLOC for a mature monolith, story points or changed files for a team that refactors a lot.
Defect density = підтверджені дефекти / розмір
Модуль Дефекти KLOC На KLOC
Checkout 30 12 30 / 12 = 2,50
Catalog 36 40 36 / 40 = 0,90
Profile 4 8 4 / 8 = 0,50
Разом 70 60 70 / 60 = 1,17
# Альтернатива для Agile: на story point
Спринт 42: 21 дефект / 42 SP = 0,5 дефекту на SP
# У чисельник НЕ йдуть: Duplicate, Not a Bug, Cannot Reproduce
Defect density = confirmed defects / size
Module Defects KLOC Per KLOC
Checkout 30 12 30 / 12 = 2.50
Catalog 36 40 36 / 40 = 0.90
Profile 4 8 4 / 8 = 0.50
Total 70 60 70 / 60 = 1.17
# Agile alternative: per story point
Sprint 42: 21 defects / 42 SP = 0.5 defects per SP
# Excluded from the numerator: Duplicate, Not a Bug, Cannot Reproduce
Що це: Defect leakage — частка дефектів, які пройшли повз тестування і були знайдені вже після релізу: користувачами, підтримкою чи моніторингом. Такий баг називають escape defect або escaped defect. Формула: дефекти, знайдені після релізу, поділені на всі дефекти цього релізу (до і після), помножено на 100.
Навіщо і коли: Це одна з небагатьох метрик, що вимірює результат роботи QA, а не активність. Рахують на реліз або за період, із фіксованим вікном спостереження, наприклад 30 днів після виходу, інакше старі релізи постійно «доростають».
Приклад з проєкту: Реліз 3.4: QA і UAT знайшли 76 дефектів, за 30 днів після виходу в продакшн — ще 4. Leakage = 4 / (76 + 4) × 100 = 5%. Два з чотирьох були в інтеграції з платіжним провайдером, якої не було на стейджингу — це і стало темою ретро.
Підводні камені: Перше — рахувати лише баги з тікетами: інциденти, які підтримка закрила сама, випадають. Друге — не розрізняти важливість: 5% тривіальних опечаток і 5% критичних збоїв оплати — різні історії, тому leakage варто показувати окремо для Critical і Major. Третє — зараховувати до escape дефекти, що існували в продакшні до релізу: у поля «affected version» має бути версія, де баг з'явився.
Уточнююче питання: «Чи може leakage бути 0%?» — може, але це привід перевірити дані: або вікно замале, або баги з продакшну не доходять до трекера.
What it is: Defect leakage is the share of defects that slipped past testing and were found after release: by users, support or monitoring. Such a bug is called an escape defect or escaped defect. The formula: defects found after release, divided by all defects of that release (before and after), times 100.
Why and when: It is one of the few metrics that measures the outcome of QA work rather than activity. You calculate it per release or per period, with a fixed observation window such as 30 days after launch, otherwise old releases keep "growing".
From a project: Release 3.4: QA and UAT found 76 defects, and within 30 days in production another 4 turned up. Leakage = 4 / (76 + 4) × 100 = 5%. Two of the four were in the payment provider integration, which did not exist on staging, and that became the retro topic.
Pitfalls: First, counting only bugs with tickets: incidents that support closed on their own drop out. Second, ignoring severity: 5% trivial typos and 5% critical payment failures are different stories, so show leakage separately for Critical and Major. Third, counting as escapes the defects that were already in production before the release: the affected version field must hold the version where the bug appeared.
Follow-up question: "Can leakage be 0%?" It can, but it is a reason to check the data: either the window is too short, or production bugs are not reaching the tracker.
Defect leakage % = escaped defects / (pre-release defects + escaped defects) x 100
Release 3.4: 4 / (76 + 4) x 100 = 5.0 %
-- escaped defects (numerator)
project = SHOP AND issuetype = Bug AND affectedVersion = "3.4"
AND labels = found-in-prod AND created >= "2026-06-02" AND created <= "2026-07-02"
-- all defects of the release (denominator)
project = SHOP AND issuetype = Bug AND affectedVersion = "3.4"
AND resolution not in (Duplicate, "Cannot Reproduce")
Що це: Зменшення leakage починається не з «тестувати більше», а з escape analysis: для кожного бага з продакшну відповісти, чому тестування його пропустило. Відповідей зазвичай чотири: тесту не було; тест був, але з невдалими даними; середовище відрізнялося від продакшну; вимога була неповна або змінилася.
Навіщо і коли: Кожна причина має свою дію, і вона не завжди в зоні QA. Відсутній тест — додати кейс і, якщо сценарій стабільний, автотест. Погані дані — розширити набір граничних значень. Розрив у середовищах — питання до DevOps (конфіги, фіче-флаги, інтеграції). Прогалина у вимогах — зміни в refinement і acceptance criteria. Аналіз роблять після кожного релізу для Critical і Major.
Приклад з проєкту: За квартал ми розібрали 9 escape-дефектів: 3 без тесту, 2 через дані (кирилиця в імені на квитанції), 3 через середовище (на стейджингу був вимкнений фіче-флаг, увімкнений у проді) і 1 через вимогу. Найдешевшою виявилась зміна для середовищ: синхронізація флагів перед регресією. Наступного кварталу escape-дефектів було 4.
Підводні камені: Шукати винного замість причини: люди почнуть ховати баги. Додавати тест на кожен escape без аналізу: регресія розростається, а клас проблем лишається. Не повертатися до дій: таблиця без виконаних пунктів нічого не змінює.
Уточнююче питання: «Що робити, якщо більшість escape — через вимоги?» — залучати QA до refinement, писати приклади (specification by example) і рев'ювити acceptance criteria до початку розробки.
What it is: Reducing leakage starts not with "test more" but with escape analysis: for every production bug, answer why testing missed it. There are usually four answers: there was no test; there was a test but with weak data; the environment differed from production; the requirement was incomplete or changed.
Why and when: Each cause has its own action, and it is not always within QA's reach. Missing test: add a case and, if the scenario is stable, an automated test. Weak data: widen the set of boundary values. Environment gap: a question for DevOps (configs, feature flags, integrations). Requirement gap: changes to refinement and acceptance criteria. The analysis is done after every release for Critical and Major bugs.
From a project: Over a quarter we went through 9 escaped defects: 3 had no test, 2 were data-related (Cyrillic in a name on a receipt), 3 were environment gaps (a feature flag switched off on staging but on in production) and 1 was a requirement gap. The cheapest fix turned out to be the environment one: syncing flags before the regression run. The next quarter had 4 escapes.
Pitfalls: Looking for someone to blame instead of a cause: people will start hiding bugs. Adding a test for every escape without analysis: the regression suite bloats while the class of problem remains. Never coming back to the actions: a table with no completed items changes nothing.
Follow-up question: "What if most escapes come from requirements?" Bring QA into refinement, write concrete examples (specification by example) and review acceptance criteria before development starts.
Баг Severity Чому пропустили Дія Власник Статус
SHOP-812 Critical Флаг вимкнений на stage Синхронізувати флаги перед регресією DevOps зроблено
SHOP-829 Major Немає тесту на повернення Кейс + API-автотест refund QA зроблено
SHOP-834 Major Дані: кирилиця в імені Додати юнікод-імена в тестові дані QA зроблено
SHOP-851 Major Вимога: ліміт не описаний AC з прикладами на refinement PO + QA в роботі
Підсумок: без тесту 3 | дані 2 | середовище 3 | вимоги 1 (разом 9)
Bug Severity Why it was missed Action Owner Status
SHOP-812 Critical Flag off on staging Sync flags before regression DevOps done
SHOP-829 Major No test for refunds Test case + refund API test QA done
SHOP-834 Major Data: Cyrillic in name Add Unicode names to test data QA done
SHOP-851 Major Requirement: limit missing AC with examples at refinement PO + QA in progress
Summary: no test 3 | data 2 | environment 3 | requirements 1 (total 9)
Що це: DRE — частка дефектів, які команда прибрала до релізу, від усіх дефектів, що зрештою виявились у цій версії. Формула: дефекти, знайдені до релізу, поділені на суму знайдених до і після, помножено на 100. Якщо рахувати з тим самим вікном і тими самими фільтрами, DRE і leakage у сумі дають 100%: це дві сторони одного дробу.
Навіщо і коли: Leakage звучить як «скільки ми пропустили», DRE — як «наскільки ефективно ми ловимо». Для керівництва друга формулювання часто зрозуміліша. Крім того, DRE можна рахувати по фазах: скільки дефектів, наявних на вході в фазу, ця фаза знайшла. Так видно, яка активність — code review, системне тестування, UAT — працює слабше.
Приклад з проєкту: За реліз знайдено 184 дефекти до виходу і 16 після. DRE = 184 / 200 × 100 = 92%, leakage = 8%. По фазах: на вході в системне тестування в коді «сиділо» 150 дефектів (120 знайшло саме тестування, 30 — пізніші фази і продакшн), тож DRE системного тестування — 80%. UAT знайшов 14 із 30, що дійшли до нього, — лише 47%, і ми переглянули сценарії UAT.
Підводні камені: Рахувати DRE одразу після релізу, коли пост-релізних багів ще не встигли знайти: цифра завжди буде ідеальною. Змішувати в одному числі дефекти всіх рівнів важливості.
Уточнююче питання: «Що краще показувати менеджменту — DRE чи leakage?» — одне з двох, не обидва, щоб не виглядало як дві різні новини; і завжди з вікном спостереження.
What it is: DRE is the share of defects the team removed before release out of all defects that eventually turned up in that version. The formula: defects found before release, divided by the sum found before and after, times 100. When calculated with the same window and the same filters, DRE and leakage add up to 100%: they are two sides of one fraction.
Why and when: Leakage reads as "how much we missed", DRE as "how effectively we catch things". For leadership the second phrasing is often easier to take in. DRE can also be calculated per phase: how many of the defects present when a phase started did that phase find. That shows which activity, whether code review, system testing or UAT, is the weak link.
From a project: In one release 184 defects were found before launch and 16 after. DRE = 184 / 200 × 100 = 92%, leakage = 8%. By phase: 150 defects were in the code when system testing started (testing found 120, later phases and production found 30), so system testing DRE was 80%. UAT found 14 of the 30 that reached it, only 47%, and we reworked the UAT scenarios.
Pitfalls: Calculating DRE right after release, before anyone has had time to find post-release bugs: the figure will always look perfect. Mixing every severity level into one number.
Follow-up question: "Which should you show management, DRE or leakage?" One of the two, not both, so it does not read as two separate pieces of news; and always with the observation window.
DRE % = до релізу / (до релізу + після релізу) x 100
Leakage % = після релізу / (до релізу + після релізу) x 100
Реліз 5.2 (вікно 30 днів): 184 / (184 + 16) x 100 = 92 % leakage = 8 %
DRE фази = знайдено у фазі / (знайдено у фазі + знайдено пізніше)
Системне тестування: 120 / (120 + 30) = 80 %
UAT: 14 / (14 + 16) = 47 %
DRE % = pre-release / (pre-release + post-release) x 100
Leakage % = post-release / (pre-release + post-release) x 100
Release 5.2 (30-day window): 184 / (184 + 16) x 100 = 92 % leakage = 8 %
Phase DRE = found in phase / (found in phase + found later)
System testing: 120 / (120 + 30) = 80 %
UAT: 14 / (14 + 16) = 47 %
Що це: Reopen rate — частка багів, які після переходу в Resolved або Done повернули назад, бо перевірка показала, що проблема лишилась. Формула: кількість багів, перевідкритих за період, поділена на кількість виправлених за той самий період, помножено на 100.
Навіщо і коли: Кожне перевідкриття — це ще одне коло «розробник → QA → розробник», тобто прихована вартість і затримка релізу. Метрика показує якість виправлень і якість самих репортів. Типові причини: виправили симптом, а не причину; фікс перевірили лише на одному браузері чи ролі; кроки в репорті неповні, і розробник полагодив інший сценарій; фікс не доїхав у середовище, де його перевіряє QA.
Приклад з проєкту: За спринт закрили 60 багів, 9 з них перевідкрили — reopen rate 15%. Розбір показав, що 5 із 9 стосувались мобільного Safari, на якому розробники не перевіряли. Додали в Definition of Done пункт «перевірено на матриці браузерів з тікета», і за два спринти показник опустився до 6%.
Підводні камені: Перевідкривати тікет, коли знайдено новий, лише схожий баг: це штучно підвищує показник і плутає історію. Новий дефект — новий тікет із лінком. Інший перекіс — рахувати перевідкриття через те, що фікс ще не задеплоєний; це проблема процесу поставки, а не якості виправлення.
Уточнююче питання: «Який reopen rate нормальний?» — універсальної норми немає; важливий тренд і причини, а стабільні 20% і більше — сигнал розібрати процес code review і перевірки фіксу.
What it is: Reopen rate is the share of bugs that moved to Resolved or Done and were then sent back because verification showed the problem was still there. The formula: bugs reopened in a period, divided by bugs fixed in the same period, times 100.
Why and when: Every reopen is another loop of developer to QA to developer, which means hidden cost and a delayed release. The metric reflects the quality of fixes and the quality of the reports themselves. Typical causes: the symptom was fixed rather than the cause; the fix was checked on only one browser or role; the report steps were incomplete, so the developer fixed a different scenario; the fix never reached the environment where QA verifies it.
From a project: In one sprint 60 bugs were closed and 9 of them reopened, a reopen rate of 15%. The breakdown showed that 5 of the 9 involved mobile Safari, which developers were not checking. We added "verified on the browser matrix from the ticket" to the Definition of Done, and within two sprints the figure fell to 6%.
Pitfalls: Reopening a ticket when you have found a new, merely similar bug: that inflates the metric and muddles the history. A new defect gets a new ticket with a link. Another distortion is counting reopens because the fix is not deployed yet; that is a delivery process issue, not a fix quality one.
Follow-up question: "What reopen rate is normal?" There is no universal norm; the trend and the causes matter, and a steady 20% or more is a signal to look at code review and fix verification.
-- reopened during the last sprint (numerator)
project = SHOP AND issuetype = Bug
AND status CHANGED FROM "Resolved" TO "Reopened" DURING ("2026-09-14", "2026-09-27")
-- resolved during the same sprint (denominator)
project = SHOP AND issuetype = Bug
AND status CHANGED TO "Resolved" DURING ("2026-09-14", "2026-09-27")
Reopen rate = 9 / 60 x 100 = 15 %
CHANGED ... DURING з однаковим вікном дат, тож чисельник і знаменник рахуються за той самий спринт. Назви статусів беруться з вашого workflow; якщо він інший, підставте свої.CHANGED ... DURING operator with the same date window, so numerator and denominator cover the same sprint. Status names come from your workflow; if yours differs, substitute your own.Що це: Тут дві різні метрики. Defect age — скільки часу вже живе відкритий баг: сьогоднішня дата мінус дата створення. Вона про поточний беклог. Time to resolve, яку в командах часто теж називають MTTR (mean time to resolve/repair), рахується для вже закритих: дата вирішення мінус дата створення, усереднена за період. Вона про швидкість реакції.
Навіщо і коли: Age показують у щотижневому звіті як «найстаріші відкриті Critical/Major» і як розподіл за віковими кошиками (до 7 днів, 7–30, понад 30). Time to resolve порівнюють між релізами і між рівнями severity; для Critical часто є SLA, наприклад 2 робочі дні.
Приклад з проєкту: П'ять закритих Major-багів вирішили за 1, 2, 2, 3 і 32 дні. Середнє — 40 / 5 = 8 днів, медіана — 2 дні. Середнє створювало враження, що команда повільна, хоча насправді один баг чекав на сторонню бібліотеку. У звіті ми почали показувати медіану і окремо список «хвостів» понад 30 днів.
Підводні камені: Середнє чутливе до викидів, тому краще медіана або 85-й перцентиль. Час від створення включає очікування triage, тож корисно розбити на «до взяття в роботу» і «в роботі». Не варто змішувати інцидентний MTTR (відновлення сервісу, години) з часом виправлення бага (дні): це різні процеси.
Уточнююче питання: «Що робити з багами віком понад 90 днів?» — переглянути на triage: або запланувати, або чесно закрити як Won't Fix з поясненням, щоб беклог відображав реальні наміри.
What it is: These are two different metrics. Defect age is how long an open bug has been alive: today's date minus the creation date. It is about the current backlog. Time to resolve, which teams often also call MTTR (mean time to resolve/repair), is calculated for closed bugs: resolution date minus creation date, averaged over a period. It is about how fast the team reacts.
Why and when: Age shows up in the weekly report as "oldest open Critical/Major" and as a distribution across age buckets (under 7 days, 7 to 30, over 30). Time to resolve is compared across releases and across severity levels; Critical bugs often have an SLA such as 2 working days.
From a project: Five closed Major bugs were resolved in 1, 2, 2, 3 and 32 days. The mean is 40 / 5 = 8 days, the median is 2 days. The mean made the team look slow, when in fact one bug had been waiting on a third-party library. In the report we switched to the median plus a separate list of "tails" over 30 days.
Pitfalls: The mean is sensitive to outliers, so the median or the 85th percentile works better. Time from creation includes waiting for triage, so it helps to split it into "until picked up" and "in progress". Do not mix incident MTTR (restoring a service, measured in hours) with bug fix time (measured in days): they are different processes.
Follow-up question: "What do you do with bugs older than 90 days?" Review them at triage: either schedule them or honestly close them as Won't Fix with a reason, so the backlog reflects real intentions.
Time to resolve, 5 Major bugs (days): 1, 2, 2, 3, 32
Mean = (1 + 2 + 2 + 3 + 32) / 5 = 8 days
Median = 2 days
-- open bugs older than 30 days (defect age), oldest first
project = SHOP AND issuetype = Bug AND statusCategory != Done
AND created <= -30d ORDER BY created ASC
-- median time to resolve per month (PostgreSQL over a Jira export)
SELECT date_trunc('month', resolved_at) AS month,
percentile_cont(0.5) WITHIN GROUP (
ORDER BY EXTRACT(EPOCH FROM resolved_at - created_at) / 86400
) AS median_days
FROM bugs
WHERE resolved_at IS NOT NULL AND severity = 'Major'
GROUP BY 1 ORDER BY 1;
percentile_cont по вивантаженій таблиці багів.percentile_cont over an exported bug table.Що це: Це метрики надійності з експлуатації. MTBF (mean time between failures) — середній час роботи системи між відмовами: час безвідмовної роботи, поділений на кількість відмов. MTTR (mean time to repair/restore) — середній час від початку збою до відновлення: сумарний час простою, поділений на кількість збоїв. Разом вони дають доступність: MTBF / (MTBF + MTTR).
Навіщо і коли: Ці цифри належать SRE і on-call, але QA впливає на обидві. На MTBF — через регресійні тести на кожен інцидент, навантажувальні й chaos-перевірки, контроль конфігурацій. На MTTR — через швидкі smoke-тести після hotfix і rollback, через чекліст перевірки відновлення і через участь у postmortem, де QA формулює, яким тестом цей збій можна було зловити.
Приклад з проєкту: За 30 днів (720 годин) сервіс оплати мав 4 збої тривалістю 1,5; 0,5; 3 і 1 година, разом 6 годин. MTTR = 6 / 4 = 1,5 години. MTBF = (720 − 6) / 4 = 178,5 години. Доступність = 178,5 / 180 ≈ 99,17% — нижче за SLO 99,5%. Два збої з чотирьох були після релізів, тож ми додали post-deploy smoke на оплату і canary на 5% трафіку.
Підводні камені: Рахувати MTBF у системах, де відмови залежать від навантаження чи релізів, як у залізі з рівномірним зносом: середнє приховує кластери збоїв. Вважати «відмовою» тільки повний даун, ігноруючи деградацію. Приписувати QA метрику, на яку команда не має важелів.
Уточнююче питання: «Що важливіше покращувати — MTBF чи MTTR?» — для частих релізів зазвичай MTTR: швидкий rollback і добрий моніторинг дешевші, ніж спроба виключити всі збої.
What it is: These are operational reliability metrics. MTBF (mean time between failures) is the average time a system runs between failures: uptime divided by the number of failures. MTTR (mean time to repair/restore) is the average time from the start of an outage to recovery: total downtime divided by the number of outages. Together they give availability: MTBF / (MTBF + MTTR).
Why and when: These numbers belong to SRE and on-call, but QA influences both. MTBF through regression tests for every incident, load and chaos checks, and configuration checks. MTTR through fast smoke tests after a hotfix or rollback, a recovery verification checklist and taking part in the postmortem, where QA spells out which test could have caught the failure.
From a project: Over 30 days (720 hours) the payment service had 4 outages lasting 1.5, 0.5, 3 and 1 hours, 6 hours in total. MTTR = 6 / 4 = 1.5 hours. MTBF = (720 − 6) / 4 = 178.5 hours. Availability = 178.5 / 180 ≈ 99.17%, below the 99.5% SLO. Two of the four outages came right after releases, so we added a post-deploy smoke test on payments and a canary on 5% of traffic.
Pitfalls: Treating MTBF for software whose failures depend on load or releases like hardware with even wear: the average hides clusters of failures. Counting only a full outage as a "failure" and ignoring degradation. Assigning QA a metric the team has no levers on.
Follow-up question: "Which matters more to improve, MTBF or MTTR?" With frequent releases usually MTTR: fast rollback and good monitoring are cheaper than trying to rule out every failure.
Період: 30 днів = 720 год
Збої (год простою): 1,5 + 0,5 + 3 + 1 = 6 год, кількість = 4
MTTR = 6 / 4 = 1,5 год
MTBF = (720 - 6) / 4 = 178,5 год
Доступність = MTBF / (MTBF + MTTR) = 178,5 / 180 = 99,17 %
Перевірка: (720 - 6) / 720 = 99,17 %
SLO 99,5 % -> допустимий простій за 30 днів = 3,6 год (фактично 6)
# Дії QA після postmortem:
- регресійний тест на кожен інцидент (лінк на incident-тікет)
- post-deploy smoke на оплату, запуск у пайплайні після релізу
Period: 30 days = 720 h
Outages (hours down): 1.5 + 0.5 + 3 + 1 = 6 h, count = 4
MTTR = 6 / 4 = 1.5 h
MTBF = (720 - 6) / 4 = 178.5 h
Availability = MTBF / (MTBF + MTTR) = 178.5 / 180 = 99.17 %
Check: (720 - 6) / 720 = 99.17 %
SLO 99.5 % -> allowed downtime in 30 days = 3.6 h (actual 6)
# QA actions after the postmortem:
- a regression test per incident (linked to the incident ticket)
- post-deploy smoke on payments, run in the pipeline after release
Що це: Не кожен тікет типу Bug є дефектом продукту. Duplicate — той самий баг, заведений удруге; Not a Bug або Won't Do — поведінка відповідає вимогам; Cannot Reproduce — підтвердити не вдалося. Їх прибирають із density, leakage і розподілу за severity. Deferred — інша історія: це справжній дефект, виправлення якого відклали, тож він лишається в метриках і окремо відображається як відомий борг. Improvement request — пропозиція змінити поведінку, яка працює за вимогами; якщо її завели як Bug, вона завищує дефектні цифри.
Навіщо і коли: Правила фільтрації фіксують один раз і пишуть у звіті, інакше дві людини отримають різні числа з тих самих даних. Проміжні статуси на кшталт In Review (фікс на рев'ю коду) не є закриттям: такі баги рахуються відкритими, поки фікс не перевірено.
Приклад з проєкту: За квартал у проєкті було 140 тікетів типу Bug. Після фільтрації: 18 дублікатів, 11 Not a Bug і Won't Do, 9 Cannot Reproduce і 7 покращень, помилково заведених як баги. Лишилось 95 реальних дефектів, і density знизилась на третину. Водночас частка дублікатів 13% стала окремим сигналом: команда погано шукала перед створенням тікета.
Підводні камені: Ховати незручні баги в Deferred, щоб «почистити» звіт. Масово закривати старі баги як Cannot Reproduce без перевірки.
Уточнююче питання: «А чи варто рахувати відхилені тікети взагалі?» — так, окремо: висока частка Not a Bug вказує на неясні вимоги або на те, що QA і PO по-різному їх розуміють.
What it is: Not every ticket of type Bug is a product defect. Duplicate is the same bug filed a second time; Not a Bug or Won't Do means the behaviour matches the requirements; Cannot Reproduce means it could not be confirmed. These are removed from density, leakage and the severity split. Deferred is a different story: it is a real defect whose fix was postponed, so it stays in the metrics and is shown separately as known debt. An improvement request proposes changing behaviour that works as specified; filed as a Bug, it inflates the defect numbers.
Why and when: Filtering rules are agreed once and written into the report, otherwise two people will get different numbers from the same data. Intermediate statuses like In Review (the fix is in code review) are not closure: such bugs count as open until the fix is verified.
From a project: Over a quarter the project had 140 tickets of type Bug. After filtering: 18 duplicates, 11 Not a Bug and Won't Do, 9 Cannot Reproduce and 7 improvements misfiled as bugs. That left 95 real defects, and density dropped by a third. At the same time the 13% duplicate share became a signal of its own: the team was not searching before filing.
Pitfalls: Hiding awkward bugs in Deferred to "clean up" the report. Mass-closing old bugs as Cannot Reproduce without checking.
Follow-up question: "Should rejected tickets be counted at all?" Yes, separately: a high Not a Bug share points to unclear requirements or to QA and the PO reading them differently.
-- real defects for the quarter
project = SHOP AND issuetype = Bug
AND created >= startOfQuarter(-1) AND created <= endOfQuarter(-1)
AND (resolution is EMPTY
OR resolution not in (Duplicate, "Won't Do", "Cannot Reproduce"))
-- deferred defects, kept in metrics and listed as known debt
project = SHOP AND issuetype = Bug AND status = Deferred ORDER BY priority DESC
All Bug tickets 140
- Duplicate -18
- Not a Bug / Won't Do -11
- Cannot Reproduce -9
- improvements, retyped -7
Real defects 95
resolution is EMPTY потрібна, бо not in не повертає тікети без резолюції. Другий запит показує відкладені дефекти окремо, а таблиця пояснює, звідки різниця між 140 і 95.resolution is EMPTY clause is needed because not in does not return tickets with no resolution. The second query lists deferred defects separately, and the table explains the gap between 140 and 95.Що це: Root cause — справжня причина дефекту, яку фіксують після виправлення: помилка в логіці коду, неповна чи суперечлива вимога, верстка UI, інтеграція з іншим сервісом, конфігурація середовища, тестові дані. Коли кожен баг має причину зі сталого списку, їх можна згрупувати і побудувати Pareto: відсортувати категорії за частотою і дивитись на кумулятивну частку.
Навіщо і коли: Список зі ста багів нічого не підказує; Pareto показує, які дві-три причини дають більшість проблем, і куди вкладати зусилля. Тут же розводять UI-дефекти і функціональні: UI-дефект — про відображення (зсунута кнопка, обрізаний текст), функціональний — про поведінку (неправильна сума, не зберігся запис). Кнопка, яка виглядає нормально, але не відправляє форму, — функціональний дефект.
Приклад з проєкту: За два релізи класифікували 120 багів: логіка коду — 42 (35%), вимоги — 30 (25%), інтеграції — 18 (15%), верстка — 14, конфігурація — 10, тестові дані — 6. Перші три категорії дали 75%. Замість «більше тестувати» ми запровадили review acceptance criteria з QA і контрактні тести для двох інтеграцій.
Підводні камені: Категорія «Інше» на 40% робить аналіз марним. Root cause не повинен вказувати на людину. Заповнювати причину має той, хто виправляв, а QA перевіряє, чи вона правдоподібна.
Уточнююче питання: «Скільки категорій має бути?» — шість-вісім: менше — зливаються різні причини, більше — люди плутаються і обирають навмання.
What it is: Root cause is the real reason for a defect, recorded after the fix: a logic error in code, an incomplete or contradictory requirement, UI layout, integration with another service, environment configuration, test data. When every bug has a cause from a stable list, you can group them and build a Pareto: sort categories by frequency and look at the cumulative share.
Why and when: A list of a hundred bugs suggests nothing; a Pareto shows which two or three causes produce most of the trouble and where to invest. This is also where UI defects and functional defects are separated: a UI defect is about presentation (a shifted button, clipped text), a functional one is about behaviour (a wrong total, a record not saved). A button that looks fine but does not submit the form is a functional defect.
From a project: Across two releases we classified 120 bugs: code logic 42 (35%), requirements 30 (25%), integrations 18 (15%), layout 14, configuration 10, test data 6. The top three categories made up 75%. Instead of "testing more", we introduced acceptance criteria reviews with QA and contract tests for two integrations.
Pitfalls: An "Other" category at 40% makes the analysis useless. A root cause must never point at a person. The person who fixed the bug should fill in the cause, and QA checks that it is plausible.
Follow-up question: "How many categories should there be?" Six to eight: fewer and different causes merge, more and people get confused and pick at random.
Причина Баги Частка Кумулятивно
Логіка коду 42 35,0 % 35,0 %
Вимоги 30 25,0 % 60,0 %
Інтеграції 18 15,0 % 75,0 %
Верстка UI 14 11,7 % 86,7 %
Конфігурація 10 8,3 % 95,0 %
Тестові дані 6 5,0 % 100,0 %
Разом 120
# Дії на топ-3 (75 %): unit-тести на правила знижок, review AC з QA,
# контрактні тести для платежів і доставки
Root cause Bugs Share Cumulative
Code logic 42 35.0 % 35.0 %
Requirements 30 25.0 % 60.0 %
Integrations 18 15.0 % 75.0 %
UI layout 14 11.7 % 86.7 %
Configuration 10 8.3 % 95.0 %
Test data 6 5.0 % 100.0 %
Total 120
# Actions on the top 3 (75 %): unit tests for discount rules, AC review with QA,
# contract tests for payments and delivery
Що це: Як проводити саму зустріч triage, розписано в гайді «Bug Reporting». Тут — що з неї виносять у звіт. Основні числа: скільки багів надійшло і скільки закрито за тиждень (і різниця між ними), відкритий беклог за severity, баги без власника або без пріоритету, час від створення до рішення на triage і найстаріші відкриті Critical і Major.
Навіщо і коли: Incoming проти closed — найпростіший індикатор тренду: якщо кілька тижнів поспіль надходить більше, ніж закривається, до релізу беклог не встигне зменшитись. Баги без власника — ризик, що їх просто ніхто не робить. Час до triage показує, чи не лежать нові критичні дефекти днями без рішення. Звіт виходить щотижня в один і той самий день, щоб тренд було видно.
Приклад з проєкту: Тиждень 39: надійшло 23, закрито 19, приріст +4 — третій тиждень поспіль. Беклог 49: 1 Critical, 9 Major, 27 Minor, 12 Trivial. П'ять багів без власника, медіана часу до triage 1,5 дня. Замість просто таблиці висновок у звіті був конкретним: при такому темпі до code freeze Major-беклог не опуститься нижче восьми, тож потрібне рішення PO, які з них переносити.
Підводні камені: Звітувати кількістю без severity: 20 закритих Trivial і 2 нові Critical не означають «покращення». Не показувати тренд за кілька тижнів. Дублювати дашборд замість того, щоб дати висновок і запит на рішення.
Уточнююче питання: «Що робити, якщо incoming постійно більший за closed?» — показати прогноз до релізу і запропонувати варіанти: bug-fix спринт, звуження скоупу або перенесення Minor у наступну версію.
What it is: How to run the triage meeting itself is described in the Bug Reporting guide. This card is about what you take from it into the report. The core numbers: how many bugs came in and how many were closed this week (and the difference), the open backlog by severity, bugs with no owner or no priority, time from creation to a triage decision, and the oldest open Critical and Major bugs.
Why and when: Incoming versus closed is the simplest trend indicator: if more comes in than goes out for several weeks running, the backlog will not shrink before the release. Bugs with no owner are a risk that nobody is working on them. Time to triage shows whether new critical defects sit for days without a decision. The report goes out weekly on the same day so the trend is visible.
From a project: Week 39: 23 in, 19 closed, a net +4, for the third week in a row. Backlog of 49: 1 Critical, 9 Major, 27 Minor, 12 Trivial. Five bugs with no owner, median time to triage 1.5 days. Rather than just a table, the report's conclusion was specific: at this pace the Major backlog will not drop below eight by code freeze, so the PO needs to decide which ones to move.
Pitfalls: Reporting counts without severity: 20 closed Trivial bugs and 2 new Critical ones do not mean "improvement". Not showing the trend over several weeks. Duplicating the dashboard instead of giving a conclusion and a request for a decision.
Follow-up question: "What if incoming keeps exceeding closed?" Show a forecast to the release and offer options: a bug-fix sprint, a narrower scope, or moving Minor bugs to the next version.
Якість: тиждень 39 (22–28 вересня)
Надійшло / закрито: 23 / 19 (+4; тижні 37–39: +3, +5, +4)
Беклог за severity: Critical 1 | Major 9 | Minor 27 | Trivial 12 = 49
Без власника: 5 (SHOP-901, 903, 907, 912, 915)
Медіана до triage: 1,5 дня (ціль 2 дні)
Найстаріший Critical: SHOP-874, 9 днів, чекає на фікс від вендора
Висновок: при поточному темпі Major-беклог на code freeze (10.10) буде 8+.
Рішення потрібне від PO до 02.10: які Major переносимо в 5.3.
Quality: week 39 (22-28 September)
In / closed: 23 / 19 (+4; weeks 37-39: +3, +5, +4)
Backlog by severity: Critical 1 | Major 9 | Minor 27 | Trivial 12 = 49
No owner: 5 (SHOP-901, 903, 907, 912, 915)
Median to triage: 1.5 days (target 2 days)
Oldest Critical: SHOP-874, 9 days, waiting on a vendor fix
Conclusion: at the current pace the Major backlog at code freeze (10.10) will be 8+.
Decision needed from the PO by 02.10: which Major bugs move to 5.3.
Що це: Критерії завершення тестування (в ISTQB — test completion criteria, у побуті exit criteria) — це заздалегідь погоджені умови, за яких команда вважає тестування етапу достатнім. Їх записують у тест-план ще до початку спринту чи релізу: прохідність критичного шляху, кількість відкритих дефектів за severity, покриття вимог, стан регресу.
Навіщо і коли: Без таких умов фраза «тестування завершено» означає лише «закінчився час». Критерії перетворюють суперечку «готові чи ні» на перевірку фактів: кожен рядок має ціль, фактичне значення і статус. Звітують щодо них у підсумковому тест-звіті та на go/no-go зустрічі.
Приклад з проєкту: Перед релізом мобільного банкінгу 4.2 з п'яти критеріїв чотири були виконані, а покриття вимог склало 91% замість 95%: три історії про експорт виписок не мали жодного тесту. Ми не приховали це в загальному «майже готово», а показали червоний рядок і план — дописати 7 кейсів за день.
Підводні камені: Критерії, які змінюють заднім числом, щоб «вписатися», втрачають сенс. Ще одна пастка — лише кількісні умови без якісних: 100% пройдених тестів нічого не варті, якщо критичний платіжний сценарій взагалі не входив у набір.
Уточнююче питання: «Що робити, якщо один критерій не виконано, а дата релізу фіксована?» — показати розрив, оцінити ризик і винести рішення про виняток на власника релізу, зафіксувавши його письмово.
What it is: Test completion criteria (the ISTQB term; people usually say exit criteria) are conditions agreed in advance under which the team considers testing of a stage sufficient. They go into the test plan before the sprint or release starts: critical-path pass rate, open defects by severity, requirements coverage, regression status.
Why and when: Without them, "testing is done" only means "we ran out of time". Criteria turn the argument about readiness into a check of facts: every line has a target, an actual value and a status. You report against them in the test summary report and at the go/no-go meeting.
From a project: Before mobile banking release 4.2, four of five criteria were met, but requirements coverage was 91% instead of 95%: three statement-export stories had no tests at all. We did not bury that in a general "almost ready"; we showed a red row with a plan to write 7 cases in one day.
Pitfalls: Criteria rewritten after the fact to fit the result lose their meaning. Another trap is purely numeric conditions: a 100% pass rate is worthless if the critical payment flow was never in the suite.
Follow-up question: "What if one criterion is not met and the release date is fixed?" — show the gap, estimate the risk and let the release owner decide on an exception, recorded in writing.
Критерій | Ціль | Факт | Статус
-----------------------------------------|-------------|-------------|-------
Критичний шлях (smoke + оплата) пройдено | 100% | 100% (48/48)| OK
Відкриті Blocker / Critical | 0 | 0 | OK
Покриття вимог (RTM) | >= 95% | 91% (62/68) | НІ
Відкриті Major з workaround | <= 5 | 3 | OK
Регрес виконано на build 4.2.0-rc3 | 100% | 100% | OK
Висновок: 4 з 5 критеріїв виконано. Розрив: 6 вимог без тестів
(експорт виписок). План: 7 кейсів до 14.10, повторна оцінка 15.10.
Criterion | Target | Actual | Status
-----------------------------------------|-------------|-------------|-------
Critical path (smoke + payment) passed | 100% | 100% (48/48)| OK
Open Blocker / Critical | 0 | 0 | OK
Requirements coverage (RTM) | >= 95% | 91% (62/68) | NO
Open Major with workaround | <= 5 | 3 | OK
Regression run on build 4.2.0-rc3 | 100% | 100% | OK
Conclusion: 4 of 5 criteria met. Gap: 6 requirements without tests
(statement export). Plan: 7 cases by 14 Oct, reassess on 15 Oct.
Що це: Known issue — відомий дефект, з яким команда свідомо випускає реліз: його знайшли, оцінили і вирішили виправити пізніше. На відміну від просто відкритого бага, known issue має погоджене рішення «випускаємо так» і опис для тих, кого він зачепить.
Навіщо і коли: Список відомих проблем захищає підтримку і клієнтів від сюрпризів: саппорт не ескалює те, що вже відомо, а користувач бачить обхідний шлях. Для кожного пункту потрібні вплив, хто постраждає, workaround і орієнтовна дата виправлення. Саме workaround часто вирішує, чи припустимо випускати з цим дефектом.
Приклад з проєкту: У релізі CRM 7.3 імпорт CSV понад 50 тисяч рядків падав за тайм-аутом. Таких клієнтів було 4 з 1200, і обхід існував — розбити файл на частини. Ми внесли дефект у known issues з кроками обходу, а саппорт отримав готову відповідь для цих чотирьох акаунтів.
Підводні камені: Одне формулювання для всіх аудиторій — помилка. Внутрішній звіт містить ID тікета, причину і ризик; клієнтський текст пояснює симптом людською мовою без назв сервісів і без обіцянок, які команда може не виконати. Інша пастка — known issue, що переходить з релізу в реліз роками і вже ніким не переглядається.
Уточнююче питання: «Чим known issue відрізняється від Deferred-бага?» — Deferred описує статус у трекері, а known issue — це публічне зобов'язання повідомити про проблему тих, кого вона стосується.
What it is: A known issue is a defect the team knowingly ships with: it was found, assessed and scheduled for a later fix. Unlike an ordinary open bug, it carries an agreed decision to release anyway and a description for the people it will affect.
Why and when: A known-issues list protects support and customers from surprises: support does not escalate what is already known, and users see a way around it. Each entry needs the impact, who is affected, a workaround and an estimated fix date. The workaround is often what decides whether shipping with the defect is acceptable at all.
From a project: In CRM release 7.3, CSV import of more than 50,000 rows failed with a timeout. Only 4 of 1,200 customers had files that large, and a workaround existed: split the file. We listed it as a known issue with the workaround steps, and support got a ready answer for those four accounts.
Pitfalls: One wording for every audience is a mistake. The internal report carries the ticket ID, cause and risk; the customer text describes the symptom in plain language, with no service names and no promises the team may not keep. Another trap is a known issue that rolls over from release to release for years and nobody reviews it.
Follow-up question: "How is a known issue different from a Deferred bug?" — Deferred is a tracker status, while a known issue is a commitment to tell the affected people about the problem.
ВНУТРІШНІЙ ЗВІТ (реліз CRM 7.3)
ID | Вплив | Кого зачіпає | Workaround | Виправлення
----------|--------------------------------|-------------------|-------------------------|------------
CRM-4412 | Імпорт CSV > 50k рядків падає | 4 з 1200 клієнтів | Ділити файл по 40k | 7.4 (28.10)
| по тайм-ауту 60 с | (список у тікеті) | рядків |
CRM-4398 | Звіт PDF: кирилиця в колонтит. | Усі з укр. локал. | Експорт у XLSX | 7.3.1
RELEASE NOTES ДЛЯ КЛІЄНТІВ
Відомі проблеми:
- Імпорт дуже великих файлів CSV (понад 50 000 рядків) може
перерватися. Поки що розділіть файл на кілька менших.
- У PDF-звітах назви в колонтитулах можуть відображатися
некоректно. Скористайтеся експортом у Excel.
INTERNAL REPORT (CRM release 7.3)
ID | Impact | Who is affected | Workaround | Fix
----------|--------------------------------|-------------------|-------------------------|------------
CRM-4412 | CSV import > 50k rows fails | 4 of 1200 tenants | Split file into 40k | 7.4 (28 Oct)
| with 60 s timeout | (list in ticket) | row chunks |
CRM-4398 | PDF report: Cyrillic in footer | All uk-locale | Export to XLSX | 7.3.1
CUSTOMER RELEASE NOTES
Known issues:
- Importing very large CSV files (over 50,000 rows) may stop
before it finishes. For now, split the file into smaller ones.
- In PDF reports, footer titles may not display correctly.
Use the Excel export instead.
Що це: Release notes — документ, який супроводжує версію: що нового, що виправлено, які відомі проблеми лишилися, іноді що змінилося для інтеграцій. Пишуть його зазвичай продакт або техрайтер, але QA відповідає за точність двох списків: «виправлено» і «відомі проблеми».
Навіщо і коли: Якщо в нотатках написано «виправлено», а баг досі відтворюється, це найшвидший спосіб втратити довіру клієнтів. Тому QA звіряє кожен пункт з тікетами у статусі Verified на конкретному білді, додає в внутрішню версію обсяг тестування і середовища, де перевіряли, і позначає зміни, що ламають зворотну сумісність.
Приклад з проєкту: Для API платіжного шлюзу 3.8 у чернетці було 12 «виправлених» пунктів. Під час звірки з Jira виявилось, що два з них злиті в main, але не потрапили в реліз-гілку. Ми прибрали їх зі списку до публікації, а один перенесли в known issues, бо клієнт на нього вже скаржився.
Підводні камені: Генерувати нотатки автоматично з назв комітів — погана ідея без ручної редакції: «fix NPE in mapper» нічого не каже клієнту. Також часто забувають зміни поведінки, які формально не є багами, але зламають чиюсь інтеграцію, наприклад нове обмеження довжини поля.
Уточнююче питання: «Як перевірити, що список виправлень відповідає білду?» — JQL по fixVersion і статусу плюс порівняння з git log між тегами релізів.
What it is: Release notes accompany a version: what is new, what was fixed, which known issues remain, and sometimes what changed for integrations. A product manager or technical writer usually writes them, but QA owns the accuracy of two lists: "fixed" and "known issues".
Why and when: Listing something as fixed while the bug still reproduces is the fastest way to lose customer trust. So QA checks every entry against tickets in Verified status on a specific build, adds the test scope and environments to the internal version, and flags changes that break backward compatibility.
From a project: For payment gateway API 3.8 the draft listed 12 fixed items. Cross-checking with Jira showed two of them were merged to main but never reached the release branch. We removed them before publishing, and moved one into known issues because a customer was already complaining about it.
Pitfalls: Generating notes straight from commit titles without editing is a bad idea: "fix NPE in mapper" tells a customer nothing. Teams also forget behaviour changes that are not formally bugs but will break someone's integration, such as a new length limit on a field.
Follow-up question: "How do you check the fixed list matches the build?" — a JQL query by fixVersion and status, plus a comparison with git log between release tags.
Payment Gateway API 3.8.0 (build 3.8.0+412, 07.10)
Нове
- Повернення коштів частинами: POST /v2/refunds приймає amount.
Виправлено (перевірено QA на 3.8.0+412)
- PAY-2210 Дублікат webhook при повторі запиту з тим самим Idempotency-Key.
- PAY-2231 Невірне округлення комісії для JPY (валюта без копійок).
Зміни поведінки
- Поле description обмежене 255 символами, довші запити -> 422.
Відомі проблеми
- PAY-2240 Звіт settlement за день може з'явитися із затримкою до 2 год.
Для внутрішнього використання
Обсяг: регрес 1 340 API-тестів, 96 e2e; середовища staging-eu, staging-us.
Перевірка списку: fixVersion = 3.8.0 AND status = Verified -> 11 тікетів.
Payment Gateway API 3.8.0 (build 3.8.0+412, 7 Oct)
New
- Partial refunds: POST /v2/refunds now accepts amount.
Fixed (verified by QA on 3.8.0+412)
- PAY-2210 Duplicate webhook when a request is retried with the same Idempotency-Key.
- PAY-2231 Wrong fee rounding for JPY (currency without minor units).
Behaviour changes
- The description field is limited to 255 characters; longer requests -> 422.
Known issues
- PAY-2240 The daily settlement report may be delayed by up to 2 hours.
Internal only
Scope: regression of 1,340 API tests, 96 e2e; environments staging-eu, staging-us.
List check: fixVersion = 3.8.0 AND status = Verified -> 11 tickets.
Що це: Go/no-go — коротка зустріч або письмове рішення перед релізом, де власник продукту чи релізу вирішує, випускати версію чи ні. Роль QA тут — дати рекомендацію з доказами, а не оголосити вердикт: рішення про бізнес-ризик приймає той, хто за нього відповідає.
Як я формулюю: Рекомендація має одну з трьох форм: GO, GO з умовами або NO-GO. Далі — докази (критерії завершення, відкриті дефекти, що не перевірено), залишковий ризик людською мовою і умови, за яких рекомендація зміниться. Все це вміщається в одне повідомлення, яке можна прочитати за хвилину.
Приклад з проєкту: Перед релізом нового онбордингу я рекомендував «GO з умовами»: усі критерії виконано, крім одного Major-бага в Safari 16 — кнопка «Далі» ховається за клавіатурою на iPhone SE. Умови: реліз за фіче-флагом на 10% трафіку, алерт на конверсію онбордингу, фікс у хотфіксі за 2 дні. Продакт погодився, через 3 дні флаг підняли до 100%.
Підводні камені: QA, що вважає себе «воротарем» і блокує реліз одноосібно, швидко втрачає вплив. Протилежна крайність — розмите «загалом усе ок», яке перекладає відповідальність на QA, якщо щось впаде. Також не можна мовчати про те, що не тестувалося: непокриті зони — теж частина ризику.
Уточнююче питання: «Що, як продакт вирішив GO всупереч вашій NO-GO?» — зафіксувати рішення і прийнятий ризик письмово, домовитись про моніторинг і план відкату, і далі працювати на успіх релізу.
What it is: Go/no-go is a short meeting or written decision before a release where the product or release owner decides whether to ship. QA's role is to give a recommendation backed by evidence, not to announce a verdict: the business-risk decision belongs to whoever is accountable for it.
How I phrase it: The recommendation takes one of three forms: GO, GO with conditions, or NO-GO. Then comes the evidence (exit criteria, open defects, what was not tested), the residual risk in plain language, and the conditions under which the recommendation would change. It all fits in one message you can read in a minute.
From a project: Before shipping a new onboarding flow I recommended "GO with conditions": every criterion was met except one Major bug in Safari 16, where the Next button hid behind the keyboard on iPhone SE. Conditions: release behind a feature flag at 10% of traffic, an alert on onboarding conversion, and a hotfix within 2 days. The product owner agreed, and three days later the flag went to 100%.
Pitfalls: A QA who acts as a gatekeeper and blocks releases alone quickly loses influence. The opposite extreme is a vague "mostly fine", which quietly moves accountability onto QA if something breaks. And never stay silent about what was not tested: uncovered areas are part of the risk too.
Follow-up question: "What if the product owner says GO against your NO-GO?" — record the decision and the accepted risk in writing, agree on monitoring and a rollback plan, then work to make the release succeed.
ЧЕК-ЛИСТ GO/NO-GO (онбординг 2.0, build 5.12.0-rc2)
[x] Критерії завершення: 5 з 5, крім Major ONB-318 (див. нижче)
[x] Smoke на проді-дзеркалі пройшов
[x] Міграції БД мають скрипт відкату, перевірено на staging
[x] Моніторинг: дашборд конверсії онбордингу + алерт < 35%
[ ] Не перевірено: Android 9 (немає пристрою у фермі) - ризик низький, 1.2% аудиторії
[x] План відкату: вимкнути флаг onboarding_v2, час < 5 хв
РЕКОМЕНДАЦІЯ QA: GO з умовами
Докази: регрес 612/612, 0 Blocker/Critical, 1 Major.
Ризик: ONB-318 - у Safari 16 на малих екранах кнопка «Далі» під
клавіатурою; стосується ~3% нових користувачів, обхід - закрити клавіатуру.
Умови: флаг на 10% трафіку; фікс ONB-318 у хотфіксі до 16.10.
Рекомендація зміниться на NO-GO, якщо конверсія впаде нижче 35%.
Рішення: @product-owner (Олена), дедлайн 15.10 12:00.
GO/NO-GO CHECKLIST (onboarding 2.0, build 5.12.0-rc2)
[x] Exit criteria: 5 of 5, except Major ONB-318 (see below)
[x] Smoke on the production mirror passed
[x] DB migrations have a rollback script, verified on staging
[x] Monitoring: onboarding conversion dashboard + alert at < 35%
[ ] Not tested: Android 9 (no device in the farm) - low risk, 1.2% of users
[x] Rollback plan: switch off flag onboarding_v2, under 5 min
QA RECOMMENDATION: GO with conditions
Evidence: regression 612/612, 0 Blocker/Critical, 1 Major.
Risk: ONB-318 - in Safari 16 on small screens the Next button sits under
the keyboard; affects ~3% of new users, workaround - dismiss the keyboard.
Conditions: flag at 10% of traffic; ONB-318 fixed in a hotfix by 16 Oct.
The recommendation turns into NO-GO if conversion drops below 35%.
Decision: @product-owner (Olena), deadline 15 Oct 12:00.
Що це: Вразливість — дефект, опис якого сам по собі є ризиком: детальні кроки в широкому звіті фактично стають інструкцією для атаки. Тому security-баги ведуть в окремому тікеті з обмеженим доступом (security level у Jira або приватний advisory у GitHub), а в загальних звітах згадують лише факт, серйозність і статус.
Навіщо і коли: Статус-звіт читають десятки людей, його пересилають підрядникам і вставляють у презентації. При цьому менеджмент має знати, що реліз затримується через критичну вразливість. Рішення — дозований рівень деталей: для широкої аудиторії — ID, оцінка CVSS, компонент загального рівня і ETA; для команди з допуском — повний опис.
Приклад з проєкту: Під час тестування ми знайшли IDOR: змінивши id у запиті до API рахунків, можна було побачити чужий рахунок. У щотижневому звіті з'явився рядок «SEC-117, High (CVSS 6.5, підвищено через фінансові дані), білінг, фікс у 2.9.1, деталі в закритому тікеті». Розширений опис бачили лише 6 людей з групи безпеки, а публічний запис у changelog з'явився після розгортання фіксу.
Підводні камені: Часто витік відбувається через вкладення: HAR-файл з токенами або скріншот з URL у загальнодоступному тікеті. Також небезпечно дублювати security-баг звичайним тікетом «щоб розробник швидше побачив» — ембарго тоді зникає.
Уточнююче питання: «Коли можна розкрити деталі?» — після того, як фікс розгорнуто всюди і, за потреби, клієнти оновились, згідно з політикою відповідального розкриття компанії.
What it is: A vulnerability is a defect whose description is itself a risk: detailed steps in a widely read report effectively become attack instructions. So security bugs live in a separate ticket with restricted access (a Jira security level or a private GitHub advisory), and general reports mention only the fact, the severity and the status.
Why and when: A status report is read by dozens of people, forwarded to contractors and pasted into slides. At the same time management must know the release is delayed by a critical vulnerability. The answer is graded detail: for the wide audience, the ID, CVSS score, a high-level component and ETA; for the cleared team, the full description.
From a project: During testing we found an IDOR: changing the id in a request to the invoices API exposed another customer's invoice. The weekly report got one line: "SEC-117, High (CVSS 6.5, raised for financial data), billing, fix in 2.9.1, details in a restricted ticket". Only 6 people in the security group saw the full write-up, and the public changelog entry appeared after the fix was deployed.
Pitfalls: Leaks often come through attachments: a HAR file with tokens or a screenshot showing the URL in a publicly visible ticket. Duplicating the security bug as a normal ticket "so the developer sees it faster" is also dangerous, because the embargo is then gone.
Follow-up question: "When can details be disclosed?" — once the fix is deployed everywhere and, if needed, customers have upgraded, following the company's responsible disclosure policy.
1) ЩОТИЖНЕВИЙ СТАТУС (усі стейкхолдери)
Безпека: 1 відкрита вразливість High (SEC-117, CVSS 6.5),
модуль «Білінг». Фікс у 2.9.1, ETA 18.10. Реліз 2.9.0 не блокує
за умови, що ендпоінт вимкнено флагом. Деталі - закритий тікет.
2) ЗАКРИТИЙ ТІКЕТ SEC-117 (Jira security level: Security Team)
Тип: IDOR. GET /api/v1/invoices/{id} не перевіряє, що рахунок
належить поточному tenant. Кроки, запит, два тестові акаунти.
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N -> 6.5 базова,
підвищено до High через фінансові дані (рішення security lead).
3) CHANGELOG ПІСЛЯ ФІКСУ
2.9.1: Посилено перевірку доступу до рахунків.
1) WEEKLY STATUS (all stakeholders)
Security: 1 open High vulnerability (SEC-117, CVSS 6.5),
Billing module. Fix in 2.9.1, ETA 18 Oct. Does not block 2.9.0
provided the endpoint stays disabled by a flag. Details: restricted ticket.
2) RESTRICTED TICKET SEC-117 (Jira security level: Security Team)
Type: IDOR. GET /api/v1/invoices/{id} does not check that the invoice
belongs to the current tenant. Steps, request, two test accounts.
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N -> 6.5 base,
raised to High because of financial data (security lead decision).
3) CHANGELOG AFTER THE FIX
2.9.1: Strengthened access checks for invoices.
Що це: Одна й та сама новина про якість має різних читачів з різними питаннями. Менеджер питає: чи встигаємо, що це означає для клієнтів і що мені вирішити. Розробник питає: що саме впало, де, як відтворити і чи це моє. Хороший QA пише дві версії, а не одну «для всіх».
Навіщо і коли: Повідомлення з назвами тестів і стек-трейсами менеджер не дочитає і пропустить ризик. А фраза «оплата під загрозою» без посилань змусить розробника витратити пів години на пошук. Для менеджменту — три рядки: вплив на бізнес, ризик для дати, потрібне рішення. Для розробників — конкретні тести, асерт, що впав, посилання на trace і коміт, з якого почалось.
Приклад з проєкту: Після мержу оновлення бібліотеки валют впали 14 тестів кошика. Менеджер отримав: «Оплата в EUR на staging зламана з учорашнього вечора; реліз у четвер під ризиком, якщо не виправимо до середи; рішення від вас не потрібне, команда вже працює». Розробники отримали список тестів, асерт з очікуваною і фактичною сумою та посилання на Playwright trace.
Підводні камені: Жаргон у повідомленні для менеджменту («flaky», «selector», «rc3») і розмиті формулювання для розробників. Інша помилка — надсилати менеджеру проміжні тривоги, які розвіються за годину: краще коротко підтвердити, коли причину вже зрозуміло.
Уточнююче питання: «Як змусити менеджмент читати ваші звіти?» — починати з висновку і дії, а деталі давати посиланням.
What it is: The same piece of quality news has readers with different questions. A manager asks: are we on track, what does it mean for customers, what do I need to decide. A developer asks: what exactly failed, where, how to reproduce it, and is it mine. A good QA writes two versions instead of one "for everyone".
Why and when: A message full of test names and stack traces will not be finished by a manager, and the risk gets missed. A line like "payments at risk" with no links makes a developer spend half an hour searching. For management: three lines covering business impact, risk to the date, and any decision needed. For developers: the specific tests, the failed assertion, a trace link and the commit where it started.
From a project: After a currency library upgrade was merged, 14 cart tests failed. The manager got: "EUR payments on staging are broken since last night; Thursday's release is at risk unless fixed by Wednesday; no decision needed from you, the team is on it." Developers got the list of tests, the assertion with expected and actual totals, and a link to the Playwright trace.
Pitfalls: Jargon in the management message ("flaky", "selector", "rc3") and vague wording for developers. Another mistake is sending managers interim alarms that disappear an hour later: it is better to send a short confirmation once the cause is understood.
Follow-up question: "How do you get management to read your reports?" — lead with the conclusion and the action, and give details as links.
ДЛЯ МЕНЕДЖМЕНТУ (#release-status)
Оплата в EUR на staging не працює з 21:40 учора.
Ризик: реліз у четвер зсунеться, якщо фікс не буде до середи 18:00.
Від вас рішень поки не треба; наступне оновлення - сьогодні о 15:00.
ДЛЯ РОЗРОБНИКІВ (#team-checkout)
Після мержу PR #2291 (currency-lib 4.0 -> 5.1) падають 14 тестів
у cart/checkout.spec.ts, наприклад:
checkout > applies VAT for EUR cart
expect(total).toHaveText('119,00 EUR')
Received: '11 900,00 EUR' (схоже, сума вже в центах)
Trace: https://ci.example.com/runs/48213/trace.zip
Відтворення локально: npx playwright test cart/checkout.spec.ts -g EUR
FOR MANAGEMENT (#release-status)
EUR payments on staging have been broken since 21:40 yesterday.
Risk: Thursday's release slips if the fix is not in by Wednesday 18:00.
No decisions needed from you yet; next update today at 15:00.
FOR DEVELOPERS (#team-checkout)
Since PR #2291 was merged (currency-lib 4.0 -> 5.1), 14 tests fail
in cart/checkout.spec.ts, for example:
checkout > applies VAT for EUR cart
expect(total).toHaveText('119,00 EUR')
Received: '11 900,00 EUR' (looks like the amount is already in cents)
Trace: https://ci.example.com/runs/48213/trace.zip
Reproduce locally: npx playwright test cart/checkout.spec.ts -g EUR
Що це: DORA-метрики — чотири показники ефективності доставки ПЗ з досліджень DevOps Research and Assessment: частота деплоїв, lead time for changes (від коміту до проду), change failure rate (частка деплоїв, що спричинили збій) і час відновлення після збою; у новіших звітах його називають failed deployment recovery time. Перші дві описують швидкість, останні дві — стабільність.
Де тут QA: Жодна з метрик не називається «тестування», але QA впливає на всі. Повільний або нестабільний пайплайн тестів подовжує lead time і зменшує частоту деплоїв. Прогалини в регресі підвищують change failure rate. Швидкі smoke-перевірки після деплою і хороші алерти скорочують час відновлення.
Приклад з проєкту: За квартал команда зробила 120 деплоїв, 9 з них вимагали хотфіксу або відкату: CFR = 9 / 120 = 7,5%. Розбір показав, що 6 із 9 збоїв стосувались міграцій БД, які не перевірялись на копії продових даних. Ми додали в пайплайн прогін міграцій на анонімізованому знімку, і наступного кварталу було 4 збої на 140 деплоїв, тобто близько 2,9%.
Підводні камені: Рахувати «збій» по-різному в різних командах і потім порівнювати їх між собою. Штучно дробити деплої, щоб підняти частоту. І цитувати «еталонні» пороги елітних команд з пам'яті: вони змінюються від звіту до звіту, тож краще порівнювати команду з нею самою в динаміці.
Уточнююче питання: «Що QA може зробити для lead time?» — паралелізувати і стабілізувати тести, перенести повільний e2e з блокуючого етапу в нічний, лишивши в пайплайні швидкий критичний набір.
What it is: DORA metrics are four software delivery performance measures from the DevOps Research and Assessment research: deployment frequency, lead time for changes (from commit to production), change failure rate (the share of deployments causing a failure) and time to restore service, called failed deployment recovery time in more recent reports. The first two describe speed, the last two stability.
Where QA fits: None of them is called "testing", yet QA affects all four. A slow or flaky test pipeline stretches lead time and lowers deployment frequency. Gaps in regression raise the change failure rate. Fast post-deploy smoke checks and good alerts shorten recovery time.
From a project: In one quarter a team made 120 deployments, and 9 needed a hotfix or rollback: CFR = 9 / 120 = 7.5%. The review showed 6 of the 9 failures involved database migrations never run against production-like data. We added a pipeline step that runs migrations on an anonymised snapshot, and the next quarter had 4 failures in 140 deployments, about 2.9%.
Pitfalls: Defining "failure" differently across teams and then comparing them. Splitting deployments artificially to inflate frequency. Quoting "elite" thresholds from memory: they shift between reports, so compare a team with itself over time instead.
Follow-up question: "What can QA do for lead time?" — parallelise and stabilise tests, and move the slow e2e suite from a blocking stage to a nightly run while keeping a fast critical set in the pipeline.
Q3: деплоїв у прод = 120
деплоїв, що потребували хотфіксу/відкату = 9
Change failure rate = 9 / 120 = 7,5%
Час відновлення по 9 збоях (хв): 12, 18, 25, 31, 40, 44, 58, 95, 210
Медіана = 40 хв (середнє 59 хв спотворене одним збоєм на 210 хв)
Причини збоїв: міграції БД - 6, конфіг - 2, код - 1
Дія QA: прогін міграцій на анонімізованому знімку проду в пайплайні
Q4: 4 / 140 = 2,9%, медіана відновлення = 22 хв
Q3: production deployments = 120
deployments needing a hotfix/rollback = 9
Change failure rate = 9 / 120 = 7.5%
Recovery time for the 9 failures (min): 12, 18, 25, 31, 40, 44, 58, 95, 210
Median = 40 min (the 59 min mean is skewed by one 210 min failure)
Failure causes: DB migrations - 6, config - 2, code - 1
QA action: run migrations on an anonymised production snapshot in the pipeline
Q4: 4 / 140 = 2.9%, median recovery = 22 min
Що це: Закон Ґудгарта в популярному формулюванні звучить так: коли показник стає ціллю, він перестає бути хорошим показником. Люди оптимізують те, за що їх оцінюють, і часто найдешевшим способом — змінюючи число, а не реальність за ним.
Як це виглядає в QA: Якщо тестувальників оцінюють за кількістю знайдених багів, один дефект дробиться на п'ять тікетів. Якщо ціль — pass rate, падаючі тести позначають skip або видаляють. Ціль по покриттю коду породжує тести без жодного асерту. Ціль по MTTR змушує закривати баги як Cannot Reproduce, щоб годинник зупинився.
Приклад з проєкту: В одній команді KPI «кількість автотестів» за пів року зріс з 800 до 2 300, а кількість пропущених у прод дефектів не змінилась. Аудит показав, що близько 40% нових тестів перевіряли лише статус 200 різних ендпоінтів. Ми прибрали KPI з оцінки людей і замінили його парою: escaped defects за реліз плюс частка критичних сценаріїв з автотестами.
Як захищатись: До кожної метрики, яка може бути ціллю, ставити контрметрику, що росте при накрутці. Використовувати метрики для діагностики процесу, а не для оцінки конкретних людей. Регулярно дивитись на сирі дані вибірково, а не лише на агрегат.
Уточнююче питання: «Отже, метрики взагалі не потрібні?» — потрібні, але як сигнал для розмови, а не як автоматичний вирок чи бонус.
What it is: Goodhart's law, in its popular form, says that when a measure becomes a target, it ceases to be a good measure. People optimise what they are judged on, often in the cheapest way — changing the number rather than the reality behind it.
What it looks like in QA: Judge testers by bugs found, and one defect gets split into five tickets. Make pass rate a target, and failing tests get marked skip or deleted. A code coverage target breeds tests without a single assertion. An MTTR target pushes people to close bugs as Cannot Reproduce so the clock stops.
From a project: In one team a KPI on the number of automated tests went from 800 to 2,300 in six months, while escaped defects did not change at all. An audit found about 40% of the new tests only checked for a 200 status on various endpoints. We removed the KPI from individual performance reviews and replaced it with a pair: escaped defects per release plus the share of critical scenarios with automated tests.
How to defend against it: Pair every metric that could become a target with a counter-metric that rises when it is gamed. Use metrics to diagnose the process, not to rate individuals. Regularly sample the raw data instead of looking only at the aggregate.
Follow-up question: "So we shouldn't use metrics at all?" — we should, but as a prompt for a conversation, not as an automatic verdict or bonus.
Метрика-ціль | Як накручують | Контрметрика
--------------------------|---------------------------------------|-------------------------------------
Баги на тестувальника | Один дефект дробиться на кілька | Частка Duplicate / злитих тікетів
Pass rate | skip, видалення падаючих тестів | Кількість skip і видалених тестів за тиждень
Покриття коду, % | Тести без асертів | Mutation score (напр. PIT, Stryker)
MTTR дефектів | Закриття як Cannot Reproduce | Reopen rate, частка CR-резолюцій
Кількість автотестів | Дрібні тести на статус-код | Критичні сценарії з автотестами, %
Кількість тест-кейсів | Копіпаст кейсів з іншими даними | Escaped defects за реліз
Target metric | How it gets gamed | Counter-metric
--------------------------|---------------------------------------|-------------------------------------
Bugs per tester | One defect split into several | Share of Duplicate / merged tickets
Pass rate | skip, deleting failing tests | Skipped and deleted tests per week
Code coverage, % | Tests without assertions | Mutation score (e.g. PIT, Stryker)
Defect MTTR | Closing as Cannot Reproduce | Reopen rate, share of CR resolutions
Number of autotests | Tiny status-code-only tests | Critical scenarios automated, %
Number of test cases | Copy-pasted cases with other data | Escaped defects per release
З чого почати: Не з переліку «популярних QA-метрик», а з цілей команди. Зручна рамка — Goal-Question-Metric (GQM): спершу ціль («менше інцидентів після релізу»), потім питання, яке покаже, чи ми до неї рухаємось («скільки дефектів доходить до користувачів?»), і лише потім метрика, що відповідає на питання.
Правила відбору: П'ять-сім метрик, не більше, інакше ніхто не дивиться жодну. У кожної є власник, джерело даних, частота перегляду і рішення, яке вона запускає: «якщо flaky rate понад 3% два тижні поспіль — спринт стабілізації». Метрика, яка не змінює жодного рішення, — це шум, її прибирають.
Приклад з проєкту: Я прийшов у команду з 11 показниками на дашборді, на ретро ніхто їх не обговорював. Разом з лідом ми сформулювали три цілі: стабільні релізи, швидкий зворотний зв'язок у PR і менше повторних дефектів. Залишили п'ять метрик, решту шість заархівували. Через квартал перегляд дашборду став 10-хвилинним пунктом ретро, а за двома метриками вже були ухвалені конкретні рішення.
Підводні камені: Копіювати набір метрик з іншої компанії з іншими проблемами. Збирати дані вручну — такі метрики вмирають за місяць. Починати з цілей по числах без базової лінії: спершу 4–6 тижнів просто вимірюємо.
Уточнююче питання: «Коли переглядати набір метрик?» — раз на квартал або коли змінюється ціль команди; метрика, яка три місяці не викликала жодної дії, кандидат на видалення.
Where to start: Not with a list of "popular QA metrics" but with the team's goals. A handy framework is Goal-Question-Metric (GQM): first the goal ("fewer incidents after releases"), then the question that shows whether we are moving towards it ("how many defects reach users?"), and only then the metric that answers the question.
Selection rules: Five to seven metrics at most, otherwise nobody looks at any of them. Each has an owner, a data source, a review cadence and a decision it triggers: "if flaky rate is above 3% two weeks in a row, we run a stabilisation sprint". A metric that changes no decision is noise and gets removed.
From a project: I joined a team with 11 indicators on its dashboard that nobody discussed at retros. With the lead we defined three goals: stable releases, fast feedback in PRs and fewer repeat defects. We kept five metrics and archived the other six. A quarter later the dashboard review was a 10-minute retro item, and two of the metrics had already driven concrete decisions.
Pitfalls: Copying another company's metric set built for different problems. Collecting data by hand — such metrics die within a month. Setting numeric targets without a baseline: measure for 4–6 weeks first.
Follow-up question: "When do you revisit the metric set?" — once a quarter or when the team's goal changes; a metric that triggered no action for three months is a candidate for removal.
Ціль | Питання | Метрика | Власник | Поріг -> рішення
------------------------|--------------------------------------|----------------------------------|----------|-------------------------------------
Стабільні релізи | Скільки дефектів доходить до юзерів? | Escaped defects / реліз | QA lead | > 3 два релізи поспіль -> розбір причин
Стабільні релізи | Як часто реліз вимагає хотфіксу? | Change failure rate, % | Dev lead | > 10% за місяць -> ревізія регресу
Швидкий фідбек у PR | Скільки чекає розробник? | p90 часу пайплайну PR, хв | DevOps | > 15 хв -> шардинг / перенос e2e
Довіра до автотестів | Чи можна вірити червоному статусу? | Flaky rate, % | QA | > 3% 2 тижні -> спринт стабілізації
Менше повторних дефектів| Чи фікси справді виправляють? | Reopen rate, % | Dev lead | > 8% -> перевірка DoD для фіксів
Перегляд: щотижня на ретро, 10 хв. Ревізія набору: щокварталу.
Goal | Question | Metric | Owner | Threshold -> decision
------------------------|--------------------------------------|----------------------------------|----------|-------------------------------------
Stable releases | How many defects reach users? | Escaped defects / release | QA lead | > 3 two releases running -> cause review
Stable releases | How often does a release need a fix? | Change failure rate, % | Dev lead | > 10% a month -> regression review
Fast PR feedback | How long does a developer wait? | p90 PR pipeline time, min | DevOps | > 15 min -> sharding / move e2e out
Trust in autotests | Can we believe a red status? | Flaky rate, % | QA | > 3% for 2 weeks -> stabilisation sprint
Fewer repeat defects | Do fixes really fix things? | Reopen rate, % | Dev lead | > 8% -> review DoD for fixes
Review: weekly at the retro, 10 min. Revisit the set: quarterly.
Що стоїть за запитом: Менеджер насправді хоче впевненості, що реліз безпечний. 100% pass rate здається простим способом це виміряти, але як ціль він працює проти якості: найдешевший шлях до зеленого — вимкнути або «пом'якшити» тести, що падають, а тести, що ніколи не падають, нічого не повідомляють.
Як я відповідаю: Я не відмовляю, а пропоную точніший договір. Критичний шлях — вхід, оплата, оформлення замовлення — має бути 100% зеленим, і це справді блокер релізу. Для решти набору ціль — не менше 97% з правилом: кожне падіння або є відомим відкритим багом, або в карантині з власником і терміном. І головне — додати метрику результату: скільки дефектів дійшло до користувачів.
Приклад з проєкту: Після такої вимоги в одній команді за місяць кількість test.skip зросла з 12 до 57, а pass rate став 100%. Наступний реліз приніс два інциденти саме в зонах, де тести були вимкнені. Після переходу на схему «критичний шлях 100% + карантин з SLA 5 днів» skip повернувся до 15, а escaped defects впали з 6 до 2 за реліз.
Компроміс: Інколи менеджменту потрібне одне число для звіту вище. Тоді даю його, але з поясненням, з чого воно складається: «критичний шлях 100%, загалом 98,4%, 2 тести в карантині».
Уточнююче питання: «А якщо менеджер наполягає?» — показати дані про skip і escaped defects і домовитись про пробний період нової схеми на два релізи.
What is behind the request: The manager actually wants confidence that a release is safe. A 100% pass rate looks like a simple way to measure that, but as a target it works against quality: the cheapest route to green is disabling or "softening" failing tests, and tests that never fail tell you nothing.
How I respond: I don't say no; I offer a more precise deal. The critical path — login, payment, checkout — must be 100% green, and that genuinely blocks a release. For the rest of the suite the target is at least 97% with a rule: every failure is either a known open bug or quarantined with an owner and a deadline. Most importantly, we add an outcome metric: how many defects reached users.
From a project: After such a demand, one team's number of test.skip calls grew from 12 to 57 in a month, and pass rate hit 100%. The next release brought two incidents exactly in the areas where tests had been disabled. After switching to "critical path 100% + quarantine with a 5-day SLA", skips went back to 15 and escaped defects fell from 6 to 2 per release.
Trade-off: Sometimes management needs one number for the report upstairs. Then I give it, but with its breakdown: "critical path 100%, overall 98.4%, 2 tests in quarantine".
Follow-up question: "What if the manager insists?" — show the data on skips and escaped defects and agree to trial the new scheme for two releases.
Привіт, Андрію! Розумію, що потрібна впевненість у релізах. Пропоную
замість «100% усіх тестів» такий договір:
1. Критичний шлях (вхід, кошик, оплата; 64 тести) - 100% зелений.
Будь-яке падіння блокує реліз.
2. Решта набору (1 180 тестів) - не менше 97%. Кожне падіння або
прив'язане до відкритого бага, або в карантині з власником і
терміном до 5 робочих днів.
3. Щоб бачити результат, а не лише процес, у звіті з'являється
escaped defects за реліз (зараз 6, ціль - до 2 за квартал).
Чому не 100% загалом: за останній місяць skip у тестах виріс з 12
до 57, і два інциденти релізу 8.3 сталися саме у вимкнених зонах.
Готовий обговорити на 15 хв; пропоную спробувати на двох релізах.
Hi Andrii, I understand we need confidence in our releases. Instead of
"100% of all tests" I'd suggest this agreement:
1. Critical path (login, cart, payment; 64 tests) - 100% green.
Any failure blocks the release.
2. The rest of the suite (1,180 tests) - at least 97%. Every failure is
either linked to an open bug or quarantined with an owner and a
deadline of up to 5 working days.
3. To see the outcome, not just the process, the report adds
escaped defects per release (6 now, target 2 or fewer within a quarter).
Why not 100% overall: last month skips in our tests grew from 12
to 57, and both incidents in release 8.3 happened in disabled areas.
Happy to discuss for 15 minutes; let's try it for two releases.
Якщо коротко: Інколи так, але це рішення бізнесу, а завдання QA — описати ризик так, щоб його можна було свідомо прийняти або відхилити. «Критичний» за severity не завжди означає «критичний для цього релізу»: важить, скільки користувачів зачеплено, наскільки ймовірний сценарій і чи є спосіб обмежити шкоду.
Як формулювати: Risk statement має фіксовану структуру: що саме зламано, частка користувачів, імовірність, наслідки, пом'якшення (фіче-флаг, обхід, алерт), план відкату, власник і хто ухвалює рішення до якого часу. Без плану пом'якшення це вже не «прийнятий ризик», а надія.
Приклад з проєкту: За день до релізу знайшли Critical: експорт звіту в PDF падає для компаній з понад 500 працівниками. Таких клієнтів 11 з 2 400, звіт формують раз на місяць, найближча дата — через 9 днів. Ми випустили реліз з вимкненою кнопкою PDF для цих 11 акаунтів через флаг, повідомили їх через account-менеджерів, фікс вийшов на 5-й день.
Коли відповідь — ні: Втрата або пошкодження даних без можливості відновлення, вразливість з доступом до чужих даних, некоректні суми в платежах чи нарахуваннях, порушення регуляторних вимог. Тут жодні умови не компенсують ризик, і я пишу NO-GO прямо.
Уточнююче питання: «А якщо ризик прийняли, а він стався?» — діємо за планом відкату, а на ретро розбираємо не «хто винен», а чи правильно ми оцінили ймовірність.
In short: Sometimes, yes, but it is a business decision; QA's job is to describe the risk well enough that it can be consciously accepted or rejected. "Critical" by severity is not always "critical for this release": what matters is how many users are affected, how likely the scenario is and whether the damage can be contained.
How to phrase it: A risk statement has a fixed shape: what exactly is broken, the share of users, likelihood, impact, mitigation (feature flag, workaround, alert), rollback plan, owner, and who decides by when. Without mitigation it is no longer an accepted risk, it is a hope.
From a project: A day before release we found a Critical: PDF report export crashed for companies with more than 500 employees. That was 11 of 2,400 customers, the report runs monthly, and the next run was 9 days away. We shipped with the PDF button switched off by a flag for those 11 accounts, told them through their account managers, and the fix went out on day 5.
When the answer is no: Loss or corruption of data that cannot be restored, a vulnerability exposing other customers' data, wrong amounts in payments or billing, breaking regulatory requirements. No conditions offset those risks, and I write NO-GO plainly.
Follow-up question: "What if the accepted risk materialises?" — follow the rollback plan, and at the retro look not for someone to blame but at whether we estimated the likelihood correctly.
RISK STATEMENT - реліз 6.4.0, дефект REP-902 (Critical)
Що зламано: експорт звіту в PDF падає з 500 для компаній > 500 працівників
Кого зачіпає: 11 з 2 400 клієнтів (0,46%), список у тікеті
Імовірність: висока для цих 11, звіт формують 1-го числа (через 9 днів)
Наслідки: клієнт не отримує місячний звіт; даних не втрачено
Пом'якшення: флаг pdf_export вимкнено для 11 акаунтів; XLSX працює;
account-менеджери повідомляють клієнтів до 12:00 завтра
Моніторинг: алерт на 5xx /reports/export > 1% за 10 хв
Відкат: не потрібен (флаг); фікс REP-902 у 6.4.1, ETA 5 днів
Власник: Dev lead команди Reports
Рішення: Product owner, до 16:00 сьогодні
Рекомендація QA: GO з умовами (флаг + повідомлення клієнтам)
Стоп-фактор: NO-GO, якщо з'ясується, що збій пошкоджує збережені звіти
RISK STATEMENT - release 6.4.0, defect REP-902 (Critical)
What is broken: PDF report export returns 500 for companies > 500 employees
Who is hit: 11 of 2,400 customers (0.46%), list in the ticket
Likelihood: high for those 11; the report runs on the 1st (9 days away)
Impact: customer does not get the monthly report; no data lost
Mitigation: flag pdf_export off for the 11 accounts; XLSX works;
account managers notify customers by 12:00 tomorrow
Monitoring: alert on 5xx /reports/export > 1% over 10 min
Rollback: not needed (flag); REP-902 fix in 6.4.1, ETA 5 days
Owner: Reports team dev lead
Decision: Product owner, by 16:00 today
QA recommendation: GO with conditions (flag + customer notice)
Stop factor: NO-GO if the failure turns out to corrupt stored reports
Що це означає: Розрив між зеленим дашбордом і скаргами — майже завжди ознака того, що ми вимірюємо не те, що переживає користувач. Тести перевіряють сценарії, які ми собі уявили, на даних і пристроях, які ми обрали. Скарги приходять з реальних браузерів, мереж, обсягів даних і шляхів, яких ніхто не передбачив.
Як я розслідую: Спершу категоризую скарги: тікети підтримки, відгуки в App Store і Google Play, повідомлення в соцмережах. Потім зводжу їх з продовими сигналами — рівнем помилок, RUM-метриками швидкодії, логами. Далі порівнюю топ-категорії скарг з тим, що покрито тестами: де розрив, там і сліпа зона.
Приклад з проєкту: Регрес 100% зелений, але за два тижні 140 звернень про «повільний пошук». Тести ганяли пошук на базі з 5 тисяч товарів, а у 30 найбільших клієнтів їх понад 400 тисяч. RUM показав p75 часу відповіді пошуку 6,8 с саме для цих акаунтів. Ми додали перфоманс-тест на великому датасеті і метрику p75 пошуку з проду на дашборд якості.
Підводні камені: Списувати скарги на «користувачі не вміють» або на поодинокі випадки без даних. Шукати винного в QA замість того, щоб закрити сліпу зону. І не змінити дашборд після розслідування — тоді ситуація повториться.
Уточнююче питання: «Які сигнали з проду додати на дашборд якості?» — escaped defects, інциденти від клієнтів, рівень помилок і p75 ключових дій за RUM.
What it means: A gap between a green dashboard and complaints almost always means we measure something other than what users experience. Tests check the scenarios we imagined, on the data and devices we chose. Complaints come from real browsers, networks, data volumes and paths nobody anticipated.
How I investigate: First I categorise the complaints: support tickets, App Store and Google Play reviews, social media mentions. Then I line them up with production signals — error rates, RUM performance metrics, logs. Next I compare the top complaint categories with what our tests cover: where there is a gap, there is a blind spot.
From a project: Regression was 100% green, yet two weeks brought 140 tickets about "slow search". Our tests ran search against a catalogue of 5,000 products, while the 30 largest customers had more than 400,000. RUM showed a p75 search response of 6.8 s exactly for those accounts. We added a performance test on a large dataset and put production p75 for search on the quality dashboard.
Pitfalls: Writing complaints off as "users don't know how to use it" or as rare cases without data. Looking for someone in QA to blame instead of closing the blind spot. And leaving the dashboard unchanged after the investigation, which guarantees a repeat.
Follow-up question: "Which production signals belong on a quality dashboard?" — escaped defects, customer-reported incidents, error rate and p75 of key actions from RUM.
-- Скарги підтримки за останні 14 днів у розрізі функцій,
-- поруч - скільки автотестів покриває кожну функцію
SELECT f.name AS feature,
COUNT(DISTINCT t.id) AS tickets_14d,
COALESCE(c.test_count, 0) AS automated_tests,
MAX(r.p75_ms) AS prod_p75_ms
FROM support_tickets t
JOIN features f ON f.id = t.feature_id
LEFT JOIN (SELECT feature_id, COUNT(*) AS test_count
FROM test_cases WHERE automated = true
GROUP BY feature_id) c ON c.feature_id = f.id
LEFT JOIN rum_daily r ON r.feature_id = f.id
AND r.day >= CURRENT_DATE - 14
WHERE t.created_at >= CURRENT_DATE - 14
GROUP BY f.name, c.test_count
ORDER BY tickets_14d DESC
LIMIT 10;
-- Результат: search | 140 | 22 | 6800 -> тести є, але на малих даних
-- Support tickets over the last 14 days by feature,
-- next to how many automated tests cover each feature
SELECT f.name AS feature,
COUNT(DISTINCT t.id) AS tickets_14d,
COALESCE(c.test_count, 0) AS automated_tests,
MAX(r.p75_ms) AS prod_p75_ms
FROM support_tickets t
JOIN features f ON f.id = t.feature_id
LEFT JOIN (SELECT feature_id, COUNT(*) AS test_count
FROM test_cases WHERE automated = true
GROUP BY feature_id) c ON c.feature_id = f.id
LEFT JOIN rum_daily r ON r.feature_id = f.id
AND r.day >= CURRENT_DATE - 14
WHERE t.created_at >= CURRENT_DATE - 14
GROUP BY f.name, c.test_count
ORDER BY tickets_14d DESC
LIMIT 10;
-- Result: search | 140 | 22 | 6800 -> tests exist, but on small data