07 · Інструменти автоматизації
Боротьба з флаком засобами інструмента
Зміст
(flaky test) — це тест, який дає різні результати залежно від того, коли і хто його запускає, хоч код не змінювався. Питання «як ви боретеся з флаком» має друге дно: за ним стоїть не перелік опцій, а те, чи ви розрізняєте два різні класи важелів. Одні прибирають причину недетермінізму — підміняють час, фіксують дані, дають тесту свіжий стан. Інші причину не чіпають узагалі, а лише роблять її видимою — , , відео. Плутати їх дорого: команда, яка вважає ретраї лікуванням, роками платить хвилинами CI за симптом, який ніхто не діагностує.
Ця глава — про механіку конкретного інструмента, і тільки про неї. Таксономія причин , політика ретраїв, (quarantine) і метрики стабільності — це дисциплінарна тема розділу «Автоматизація тестування»; тут — важелі, які реально стоять у playwright.config.ts і в коді . Главу можна пропустити при першому проході: вона спирається на автоочікування й таймаути, фікстури й конфігурацію та трейс, і без них читається як список налаштувань. Зате повернутися до неї варто перед першою серйозною співбесідою на AQA.
Що інструмент лікує, а що лише показує
Почнемо з чесної межі: значну частину інструмент не лікує, бо причина живе не в ньому. Офіційна дока Selenium прямо називає гонку (race condition) між браузером і кодом тесту однією з головних причин флакі-тестів: іноді браузер устигає першим, іноді тест. І одразу закриває найпопулярніший «фікс» — жорстка пауза програє двічі: замало — тест падає, забагато — прогін стає непідйомним за часом.
Дока Cypress перелічує решту типових джерел нестабільності: , доступність сервера й бази даних, залежності від ресурсів, мережа. Це не те, що вимикається прапорцем.
Окремо стоїть категорія, яку варто називати вголос: застосунок справді поводиться недетерміновано, і тест це чесно ловить. Класичний приклад — (eventual consistency). Дока AWS DynamoDB формулює це без евфемізмів: відповідь може не відображати результат нещодавно завершеної операції запису, а якщо повторити читання трохи згодом, вона «зрештою» віддасть свіжіший елемент. Показова деталь: сильна узгодженість там — опція запиту (ConsistentRead). Тобто детермінізм іноді просто перемикається, а не виборюється ретраями.
Ретраї: конфіг, проєкт і три категорії результату
Ретрай (retry) — автоматична повторна спроба виконати тест після падіння. Два факти про нього формують усю подальшу політику.
Перший: ретраї вимкнені за замовчуванням і задаються в конфігурації. Тобто це свідоме рішення команди з ціною, а не поведінка «з коробки». Позиціонує їх дока Cypress однозначно — як засіб виявити флак, а не сховати його.
Другий, і саме він відрізняє сильну відповідь від слабкої: Playwright розводить три категорії результату, а не дві. passed — пройшов з першого разу. flaky — упав з першого разу, але пройшов на ретраї. failed — упав і на першому прогоні, і на всіх ретраях. Тобто «зелений з другої спроби» пройденим тестом не вважається: інструмент віддає цей сигнал безкоштовно, треба лише на нього дивитися.
Ретрай виконується в новому процесі (worker). Наслідок важливий і неочевидний: стан, накопичений у попередньому процесі, не переноситься — а разом із ним «розчиняється» й фейл, який саме цим станом і був спричинений. Тому ретрай не лише маскує причину політично, а й фізично забирає частину доказів.
Тест при цьому бачить, що він на ретраї (testInfo.retry), і штатний приклад доки цим користується: чистить серверний стан перед повторною спробою. Задавати ретраї можна не тільки глобально — а й для окремої групи чи файла, і на рівні проєкту (project). Проєкти розкладають не лише браузери: різні , різні ретраї, різні середовища. Це дає точний важіль замість тупого: підняти ретраї там, де нестабільність зовнішня й визнана, і тримати нуль там, де вона означала б замовчаний дефект.
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
retries: process.env.CI ? 2 : 0, // штатний дефолт доки: ретраї лише в CI
use: { trace: 'on-first-retry' }, // трейс пишемо саме на повторі впалого тесту
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
// проєкт із зовнішньою залежністю: ретраї вищі, і це задокументоване рішення
{ name: 'payments-sandbox', retries: 3, use: { ...devices['Desktop Chrome'] } },
],
});
Ціну ретраїв найкраще сформулював сам Google: схема «вважати падінням лише те, що впало три рази поспіль» — «далеко не ідеальне рішення», бо вона знижує хибні спрацювання, але заохочує розробників ігнорувати флак у власних тестах. І вимірюється ця ціна не абстрактно: 15-хвилинний , позначений як флакі, покаже реальну поломку через 45 хвилин — і потім ще треба зрозуміти, він зламаний чи «флакнув тричі».
Підміна часу: page.clock
Тепер про важіль, який працює проти причини. (clock mocking) — це коли тест керує годинником сторінки замість того, щоб чекати реального часу. Дока називає обидві вигоди разом: точна перевірка залежних від часу речей — рендеру за часом, таймаутів, запланованих задач — без затримок і без мінливості реального виконання. Друге слово тут важливіше за перше: прискорення — приємний бонус, а часу як джерела недетермінізму — власне механізм.
Підміняється не «час абстрактно», а перелічені поіменно глобальні класи й функції: Date, setTimeout, setInterval, requestAnimationFrame, performance. Отже, керованим стає все, що на них тримається: автооновлення списку, лічильник до кінця акції, банер «сесія завершиться через хвилину», дебаунс пошуку.
Порядок вибору режиму дока задає прямо: спершу page.clock.setFixedTime(), і лише якщо це не підходить — page.clock.install(), який дає паузу, перемотування й покрокове просування. page.clock.setSystemTime() названо режимом для складних випадків. Різниця між першими двома — не про зручність, а про поведінку застосунку. Якщо потрібно лише зафіксувати Date.now(), а таймери мають текти природно — беремо фіксований час. Якщо ж таймери самі залежать від Date.now(), застиглий час їх ламає: тоді потрібен встановлений годинник і перемотування.
test('банер сесії зʼявляється за 30 хвилин без очікування 30 хвилин', async ({ page }) => {
// install мусить стояти ПЕРЕД будь-якими іншими викликами, повʼязаними з часом
await page.clock.install({ time: new Date('2026-07-30T09:00:00Z') });
await page.goto('/dashboard');
await page.clock.fastForward('30:00');
await expect(page.getByRole('alert', { name: 'Сесія завершується' })).toBeVisible();
});
Один рядок доки заслуговує на окремий абзац, бо саме він робить із засобу проти флаку джерело флаку. install мусить (MUST) стояти перед будь-якими іншими викликами, повʼязаними з часом; порушення порядку дає невизначену поведінку. Не помилку, не виняток — невизначену поведінку. На ревʼю це виглядає як черговий нестабільний тест, а насправді підміна годинника просто приїхала після того, як сторінка вже завела свій перший таймер.
Анімації: важіль вужчий, ніж здається
Анімації — сусідній засіб детермінізму, і тут потрібна точність, бо тема обросла народними рецептами. Джерельно документована річ одна: -знімок вимикає анімації за замовчуванням. Поводження різне за тривалістю — скінченні перемотуються до кінця (тому подія transitionend таки спрацює), нескінченні скасовуються до початкового стану й програються вже після знімка. Каретка ховається теж за замовчуванням: інакше її блимання давало б різницю між знімками.
А от поза асертом-знімком цей дефолт не діє — тому теза «ми вимкнули анімації і флак зник» коректна саме для візуальних порівнянь, а не для тестів узагалі. Для звичайних дій цю роль виконує : перевірка стабільності елемента вимагає незмінного щонайменше протягом двох послідовних кадрів анімації.
Детермінізм у фікстурах: стан і дані
Фікстура (fixture) — це те, що має бути доступним, аби тест міг виконатися. Канонічне джерело ISTQB звʼязує її прямо з двома властивостями тесту: повторюваністю й атомарністю. Тобто фікстура — не «зручний хелпер», а місце, де детермінізм або зʼявляється, або втрачається.
Базове рішення сформульоване як ключове рішення автоматизації: будувати фікстуру щоразу заново чи перевикористовувати. Свіжа фікстура (fresh fixture) — коли кожен тест будує власну новісіньку фікстуру для власного приватного вжитку й нічого не бере від інших тестів чи прогонів. Джерело каже про неї просто: вона запобігає нестабільним тестам. Порядок відступу, якщо свіжа не по кишені, теж заданий: свіжа → незмінна спільна → і лише потім звичайна спільна.
Три тонкості, які на співбесіді відрізняють вивчене від зрозумілого:
- Приватна бази свіжою фікстурою не є. Наступний прогін дістане той самий стан — а це вже персистентна спільна фікстура, тобто «неповторюваного тесту»: пройшов один раз, далі падає. Патерн Pass-Fail-Fail або Fail-Pass-Pass — не випадковість, а діагностична ознака.
- «Війна прогонів» можлива тільки за глобально спільної фікстури, до якої різні тести звертаються й іноді її змінюють. Тому питання «чи падає тест наодинці» зазвичай ставлять у діагностиці першим.
- Стратегія «прибирати між тестами» програє з двох причин: про прибирання легко забути, а дещо прибрати взагалі неможливо (відвідані посилання).
Механіка ранера додає до цього . Тестова фікстура прибирається після кожного тесту; воркерна — лише коли завершується сам воркер, а сам воркер перевикористовується для наступних файлів, поки воркерні фікстури збігаються. Звідси висновок: воркерний скоуп — це навмисно спільний стан, який переживає файли. Дороге авторизування там доречне, а все, що тест змінює, — ні.
Тепер дані. Data-driven тестування будується поверх структурного скриптингу й знижує вартість додавання нового тесту — але ціна названа тим самим джерелом: потреба в керуванні . Генератори тут головне джерело оманливої впевненості:
- Генератор за замовчуванням дає різні значення щоразу; для сталих результатів задають (seed).
- Зерно не дає повної відтворюваності. Після оновлення бібліотеки те саме зерно може дати інші значення, бо змінилися вихідні набори даних.
- Методи з відносними датами відтворюваними не стають узагалі — їм потрібна фіксована опорна дата. І тут дві теми глави сходяться: фіксована дата — це той самий
setFixedTime.
Межа корисності генерації сформульована точно: унікальні ключі для бази — легітимний випадок; шкідливо, коли згенероване значення йде на вхід алгоритму, поведінка якого від цього значення залежить. Звідси робоче правило: генеруй те, що не впливає на гілку виконання; усе, що впливає, задавай явно.
// Фікстура з фіксованим зерном: імена й адреси випадкові, але прогін відтворюваний
export const test = base.extend<{ user: User }>({
user: async ({}, use) => {
faker.seed(20260730);
await use({
email: faker.internet.email(), // на гілку виконання не впливає
plan: 'trial', // впливає — задано явно, не згенеровано
discount: 0, // межа з тест-дизайну, теж явно
});
},
});
Термінологічна деталь, яку варто знати: у канонічному викладі «фікстура» означає ще й сам набір даних, а фабрика тестових даних — це те саме, що : «catchy name for such a factory». Тому питання «фікстура — це хук чи дані?» коректно має відповідь «залежить від джерела», а не «ви неправильно вживаєте термін».
Трейс і відео як докази флаку
Останній важіль нічого не лікує — він перетворює «здається, це флак» на матеріал, з яким можна щось зробити. Трейс (trace) — інструмент post-mortem-розбору, і саме для падінь у CI, куди не зайдеш дебагером.
Вибір носія дока формулює однозначно: для CI-падінь використовуй trace viewer замість відео й . Причина не в естетиці — у складі. У трейсі лежать використаний і тривалість кожної дії, DOM-знімки до й після, мережеві запити з кодами й тривалостями, , метадані оточення (браузер, розмір вікна) і вкладення з дифами скріншотів. Скріншот відповідає на питання «що було видно», трейс — «що відбувалося». Практична деталь для команд із режимними обмеженнями: трейс завантажується повністю в браузері й нічого не передає назовні.
Політика запису в доках вирішена однаково і збігається з попереднім розділом: трейс пишеться на першому ретраї впалого тесту, а вмикати його на кожен тест дока прямо не рекомендує через вартість. Звідси звʼязка, на якій спотикаються: якщо ретраїв у конфігурації немає, то й трейсів не буде — записувати нічого. Анатомія трейсу і стратегії запису детально розібрані в главі про дебаг і trace viewer.
Канон ISTQB ставить планку вище за «у нас є скріншот». Джерела даних перелічені окремо: логи SUT (UI, API, застосунки, вебсервери, БД), логи фреймворку як аудиторський слід, логи збірки й , скріншоти й записи екрана. Рівнів логування шість — fatal, error, warn, info, debug, trace, причому error валить тест-кейс, а warn ні. І головна вимога: у разі падіння рішення автоматизації має зберегти все, що потрібно для аналізу, включно з дампами й стеками. Один скріншот аудиторським слідом не є, бо його не звести за часовими мітками з логом застосунку.
Ще один інструментальний факт, який щороку когось коштує: --debug не просто відкриває інспектор — він запускає браузер видимим і робить таймаут нульовим. Тому «локально в дебазі проходить, у CI падає» часто означає лише те, що локально ви чекали нескінченно. Поруч живе журнал перевірок придатності: якщо умови недосяжні, дія показується як pending, а не як помилка — і це спосіб відрізнити «локатор не той» від «елемент ще не готовий». UI Mode трейсить кожен тест автоматично, але не враховує setup-тести — рівно та пастка, через яку «в UI падає, у звичайному рані ні».
Типові помилки
- «Упало по таймауту — піднімемо таймаут тесту» → якщо вичерпався таймаут асерту, тестовий не додасть жодної спроби: рівні не повʼязані, і дока каже це прямо. Більше того, до низькорівневих таймаутів вона додає застереження: якщо ви прийшли сюди через флакі-тест, рішення майже напевно не тут.
- «Таймаут — це 30 секунд» → у бібліотеки 30 секунд на більшість операцій, а в ранера більшість операцій самі не тайм-аутяться, зате кожен тест має таймаут, який його валить. Без назви рівня число нічого не означає.
- «Асерт же чекає» → чекає лише ретрайний. Неретрайний асерт на асинхронній сторінці названо причиною флаку окремо; порядок вибору — ретрайний асерт, далі -обгортка або
toPass, який до того ж не успадковує налаштований таймаут асерту. - «Підмінили час, а флак лишився» → перевірте порядок:
installпісля першого таймера сторінки дає невизначену поведінку. Виглядає як флак, а насправді засіб проти флаку вставлено не туди. - «Зафіксували час — і таймери стали» → так і має бути, якщо таймери залежать від
Date.now(). Для цього випадку джерело радить не фіксований час, а встановлений годинник із перемотуванням. - «Вимкнули анімації — і флак зник» → вимкнули їх ви лише в асерті-знімку, де це дефолт. Поза знімком цей дефолт не діє, тож для звичайних дій усе тримається на перевірці стабільності елемента.
- «Ретрай зробив тест зеленим» → ранер позначив його
flaky, а неpassed. Якщо звіт цю категорію не рахує окремо, бреше про свою стабільність.
Підсумок
- Ретраї — прилад, а не ліки. Вимкнені за замовчуванням, задаються глобально, для файла, групи або проєкту; штатний конфіг доки тримає їх лише в CI. Ранер розводить три категорії результату, і
flaky— окрема відpassed. - Ретрай платить часом і увагою. Він виконується в новому воркер-процесі, тож стан-винуватець зникає; а схему «падіння лише після трьох поспіль» сам Google називає далеко не ідеальною — 15-хвилинний тест покаже реальну поломку через 45 хвилин.
- Підміна часу прибирає причину, а не симптом. Перевизначаються
Date, таймери,requestAnimationFrameіperformance; порядок вибору — фіксований час, потім установка годинника, і лише для складних випадків системний.installмусить стояти перед будь-яким іншим викликом, повʼязаним із часом. - «Вимкнути анімації» — вузький важіль. Це дефолт асерта-знімка: скінченні перемотуються до кінця, нескінченні скасовуються, каретка ховається. Поза візуальними порівняннями цей дефолт не діє.
- Докази важливіші за зелений прогін. Для CI-падінь дока радить трейс замість відео й скріншотів і пише його на першому ретраї;
trace: 'on'не рекомендований через вартість. Канон вимагає аудиторського сліду, а не одного скріншота.
Можливі питання
- «У вас флакі-тест. Що робите?» — сильна відповідь називає три варіанти, а не один: полагодити, посадити в карантин із заведеним багом, свідомо видалити. Обидва «прості» виходи джерело називає шкідливими: прибрати тест — це свідомо втрачений тест, лишити червоним — затулити інші падіння. Відповідь «ставлю retries: 3» читається як червоний прапорець.
- «Де і скільки ретраїв ставите?» — сильна відповідь називає рівні (глобально, файл, група, проєкт) і різницю локально проти CI, а не число. Бонус — згадка, що ретрай іде в новому воркер-процесі, тож стан-винуватець зникає разом із доказом.
- «Тест пройшов з другої спроби — він пройдений?» — перевіряють, чи ви знаєте про категорію
flaky. Правильна рамка: інструмент дає цей сигнал безкоштовно, і якщо звіт його не рахує, метрика стабільності фіктивна. - «Чим погана схема "падає лише після трьох разів поспіль"?» — тут цінують саме те, що це не ваш смак, а визнана ціна: вона заохочує ігнорувати власний флак, а справжня поломка проявляється втричі довше.
- «Як протестувати таймер сесії на 30 хвилин, не чекаючи 30 хвилин?» — питання рівно про підміну часу. Відповідь «збільшити таймаут тесту» протилежна за змістом. Плюс бали за перелік перевизначених глобалів і за вимогу порядку виклику.
- «Ми поставили seed — дані детерміновані?» — неповна відповідь. Очікують, що ви назвете дві межі: оновлення бібліотеки може дати інші значення на тому самому зерні, а відносні дати потребують фіксованої опорної дати.
- «Що прикладаєте до тікета про нестабільний тест?» — слабкий рівень — «скріншот падіння». Сильний — трейс першого ретраю плюс аудиторський слід, який можна звести з логами застосунку за часовими мітками.
- «Тест падає лише в сюїті — як шукаєте причину?» — тут перевіряють порядок, а не здогад. Одне з перших питань діагностики — чи падає тест наодинці: воно розділяє залежність від інших тестів від решти причин, а корінь такої залежності — спільна фікстура.
Джерела
Що інструмент лікує, а що лише показує
- xUnit Test Patterns — Erratic Test — означення нестабільного тесту через «різні результати залежно від того, коли і хто запускає».
- Google Testing Blog — Flaky Tests at Google and How We Mitigate Them — вимірювальне формулювання: той самий код дає і зелений, і червоний результат.
- Selenium — Waiting Strategies — гонка як одна з головних причин флаку і подвійна пастка жорсткої паузи.
- Cypress — Test Retries — перелік зовнішніх джерел нестабільності: API, сервер і БД, ресурси, мережа.
- AWS — DynamoDB read consistency — кінцева узгодженість як штатна поведінка й
ConsistentReadяк перемикач детермінізму.
Ретраї: конфіг, проєкт і три категорії результату
- Playwright — Test retries — ретраї вимкнені за замовчуванням, три категорії результату, ретрай у новому воркер-процесі,
testInfo.retry, ретраї для групи чи файла. - Playwright — Test configuration — штатний конфіг доки: ретраї лише в CI, трейс на ретраї, два рівні опцій.
- Playwright — Projects — проєкт як логічна група з власною конфігурацією: різні таймаути, різні ретраї, різні середовища.
- Playwright — Browsers — кросбраузерний прогін як список проєктів у конфізі, тобто той самий рівень, на якому задаються ретраї.
- Cypress — Test Retries — ретраї як засіб виявити флак, а не сховати; вимкнені за замовчуванням.
- Google Testing Blog — Flaky Tests at Google and How We Mitigate Them — «далеко не ідеальне рішення», заохочення ігнорувати флак і ціна в 45 хвилин.
- Playwright — Clock — навіщо керувати часом, перелік перевизначених глобалів, порядок
setFixedTime→install→setSystemTime, вимогаMUSTі два режими підміни. - Playwright — API: PageAssertions — анімації й каретка вимикаються за замовчуванням саме в асерті-знімку; різне поводження скінченних і нескінченних анімацій.
- Playwright — Auto-waiting (actionability) — стабільність елемента: незмінний bounding box щонайменше протягом двох послідовних кадрів анімації.
- Playwright — Test retries — контраст: ретрай лише повторює тест, тоді як підміна часу прибирає причину.
Детермінізм у фікстурах: стан і дані
- ISTQB® Certified Tester Advanced Level Test Automation Engineering (CTAL-TAE) v2.0 — §4.2: фікстури як опора повторюваності й атомарності; §3.1.4, §4.3: data-driven поверх структурного скриптингу і потреба в керуванні тестовими даними.
- xUnit Test Patterns — Fresh Fixture — означення свіжої фікстури, порядок відступу і те, що приватна пісочниця бази свіжою фікстурою не є.
- xUnit Test Patterns — Erratic Test — неповторюваний тест і «війна прогонів» від спільної фікстури; згенероване значення на вході алгоритму проти унікальних ключів.
- Playwright — Test fixtures — скоупи
testіworkerта воркера, поки воркерні фікстури збігаються. - Playwright — Test Isolation (browser contexts) — чому стратегія «прибирати між тестами» програє свіжому стану.
- Faker (@faker-js/faker) — Usage — різні значення щоразу, зерно для сталості й межі відтворюваності: версія бібліотеки та відносні дати.
- Martin Fowler — Object Mother — фабрика тестових даних як Object Mother і «фікстура» у сенсі набору даних.
- Playwright — Trace viewer — трейс як
post-mortem-розбір, його склад, запис на першому ретраї, автономність у браузері та автоматичне трейсування кожного тесту в UI Mode. - Playwright — Best Practices — для CI-падінь трейс замість відео й скріншотів;
trace: 'on'на кожен тест не рекомендований через вартість. - ISTQB® Certified Tester Advanced Level Test Automation Engineering (CTAL-TAE) v2.0 — §4.2, §6.1.1: джерела даних, логи фреймворку як аудиторський слід, шість рівнів логування і вимога зберегти все потрібне для аналізу.
- Playwright — Debugging Tests —
--debugзнімає таймаут і відкриває браузер видимим; журнал придатності та статусpending. - Playwright — UI Mode — обмеження, назване прямо: setup-тести режим не враховує.
- Playwright — Auto-waiting (actionability) — склад перевірок придатності, які й пише журнал.
- Playwright — Timeouts — незалежність рівнів таймаутів і застереження, що для флаку рішення шукати не в них.
- Playwright — Test assertions (expect.poll, toPass) — ретрайні проти неретрайних асертів, порядок вибору й таймаут
toPass. - Playwright — Library — модель бібліотеки (30 секунд на більшість операцій) проти моделі ранера.
- Playwright — Clock — вимога
MUSTщодо порядкуinstallі вибір між фіксованим часом та перемотуванням. - Playwright — API: PageAssertions — анімації й каретка як дефолт саме асерта-знімка.
- Playwright — Test retries — категорія
flakyдля тесту, що пройшов із повтору.
- Playwright — Test retries — дефолт «без ретраїв», три категорії результату, новий воркер-процес, рівні задання ретраїв.
- Playwright — Test configuration — штатний конфіг: ретраї лише в CI, трейс на ретраї.
- Playwright — Projects — проєкт як рівень задання власних ретраїв і таймаутів.
- Cypress — Test Retries — ретраї як засіб виявлення флаку і те, що вони вимкнені за замовчуванням.
- Google Testing Blog — Flaky Tests at Google and How We Mitigate Them — «далеко не ідеальне рішення» і ціна в 45 хвилин.
- Playwright — Clock — перелік перевизначених глобалів, порядок режимів і вимога щодо
install. - Playwright — API: PageAssertions — анімації й каретка вимикаються за замовчуванням в асерті-знімку.
- Playwright — Trace viewer — склад трейсу і запис на першому ретраї.
- Playwright — Best Practices — трейс замість відео й скріншотів для CI;
trace: 'on'не рекомендований. - ISTQB® Certified Tester Advanced Level Test Automation Engineering (CTAL-TAE) v2.0 — логи фреймворку як аудиторський слід і вимога зберегти все потрібне для аналізу.
- Playwright — Test retries — рівні задання ретраїв, категорія
flaky, новий воркер-процес. - Playwright — Test configuration — різна поведінка локально й у CI як штатний дефолт.
- Playwright — Projects — проєкт як один із рівнів, де задаються ретраї.
- Cypress — Test Retries — позиціонування ретраїв як засобу виявлення флаку.
- Google Testing Blog — Flaky Tests at Google and How We Mitigate Them — механізм карантину із заведенням багу, «далеко не ідеальне рішення» і ціна .
- xUnit Test Patterns — Erratic Test — обидва «прості» виходи шкідливі; залежність від інших тестів як перше, що відсікає діагностика.
- ISTQB® Certified Tester Advanced Level Test Automation Engineering (CTAL-TAE) v2.0 — §8.1.1: історія прогонів як спосіб знайти ; §4.2, §6.1.1: аудиторський слід і дані для аналізу падіння.
- Playwright — Clock — перелік перевизначених глобалів і вимога порядку виклику.
- Faker (@faker-js/faker) — Usage — межі відтворюваності зерна: версія бібліотеки та відносні дати.
Що таке флакі-тест і чому «перезапустив — став зелений» не вважається виправленням?
(flaky test) — той, що на незмінному коді дає то зелений, то червоний результат: підсумок залежить від моменту й умов запуску, а не від того, що перевіряється. Звичайне падіння відтворюване: запустив десять разів — десять червоних, і причину видно в дифі. Нестабільність відтворюється лише статистично, тому «полагодити наосліп» її не вийде — спершу треба зловити умови, за яких вона виникає. не змінює жодної з цих умов, він лише витягує іншу карту з тієї самої колоди. Головна ціна такого «виправлення» — довіра: одного нестабільного тесту досить, щоб команда звикла перезапускати замість читати звіт, а разом із почала ігнорувати справжню .
Чому «поставити паузу на три секунди» — погана відповідь на нестабільність?
Тому що пауза не повʼязана з тим, чого тест насправді чекає, і програє в обидва боки: коротка не рятує на завантаженому CI-агенті, довга множиться на сотні тестів і перетворює прогін на непідйомний за часом. Документація Selenium називає корінь проблеми прямо — це гонка (race condition) між браузером і кодом тесту: іноді сторінка встигає раніше, іноді тест, а пауза лише зсуває ймовірність, не прибираючи саму гонку. Робочий підхід протилежний: чекати на умову, а не на час — на видимість елемента, на доступність кнопки, на конкретну відповідь мережі. У Playwright цю роль виконують і ретрайні , у Selenium — явні очікування з умовою. Жорстка пауза лишається легітимною хіба що як тимчасовий діагностичний зонд під час розслідування, і саме в такому статусі її не у main.
Ретраї увімкнені за замовчуванням? На яких рівнях їх задають?
Ні, за замовчуванням ретраїв немає, і це навмисно: автоматичний ретрай має ціну, яку команда платить свідомо, а не отримує «з коробки». Задаються вони в конфігурації , причому не лише глобально — те саме можна виставити для окремого файла, для групи тестів і на рівні проєкту (project). Проєкт у Playwright — не синонім браузера, а логічна група з власними опціями: свій , своє середовище, свої ретраї. Це дає точний важіль замість тупого: три спроби для проти зовнішньої платіжної й нуль там, де повтор означав би замовчаний дефект власного коду. Штатний дефолт із доки — ретраї лише в CI, а локально нуль, щоб розробник бачив падіння одразу, а не через дві спроби.
Тест упав, а на повторі пройшов. Він пройдений?
Ні. Ранер має для цього три статуси, а не два: passed — зелений з першої спроби, flaky — червоний спочатку й зелений на повторі, failed — червоний і на старті, і на кожному з повторів. Тобто результат «зеленіє лише з другого разу» має окрему назву, і сюїта з десятком таких тестів стабільною не є, навіть якщо пайплайн світиться зеленим. Цей сигнал інструмент віддає безкоштовно — питання лише в тому, чи рахує його ваш звіт окремою метрикою. Якщо не рахує, показник стабільності фіктивний: ви вимірюєте не якість тестів, а щедрість налаштування ретраїв. Саме тому категорію flaky варто виносити в дашборд поруч із падіннями, а не ховати у зведеному «зелено».
Які важелі проти флаку дає сам інструмент, а чого він не лікує?
Важелі діляться на два класи, і плутати їх дорого. Перший клас прибирає причину недетермінізму: , фіксовані , свіжий стан на кожен тест, мережі. Другий причину не чіпає взагалі, а робить її видимою: ретраї, , відео — це прилад, а не ліки. Поза межами інструмента лишається все, що живе в інфраструктурі й самому застосунку: доступність сервера й бази, мережа, конкуренція за ресурси, гонки в коді продукту — документація Cypress перелічує ці джерела прямо, і прапорцем вони не вимикаються. Окрема категорія — коли застосунок справді недетермінований, а тест це чесно ловить; тоді питання не «як заглушити тест», а «це дефект чи задокументована поведінка».
Що технічно відбувається під час ретраю і чому це шкодить діагностиці?
Повтор виконується не в тому самому процесі, а в новому (worker). Наслідок парадоксальний: разом зі старим процесом зникає накопичений стан — а саме він часто й був причиною падіння. Тому ретрай не просто політично маскує проблему, він фізично забирає частину доказів: тест зеленіє тому, що умов, за яких він упав, більше не існує. Звідси й канонічна політика писати трейс саме на першій невдалій спробі — інакше розбирати згодом буде нічого. Сам тест при цьому знає, що він на повторі: testInfo.retry дозволяє, наприклад, підчистити серверний стан перед другою спробою — прийом легітимний, але саме прийом, а не заміна розслідуванню.
Чим погана схема «падінням вважаємо лише те, що впало тричі поспіль»?
Тим, що вона лікує звітність, а не тести. Google описує цю схему як далеко не ідеальний компроміс: хибних спрацювань справді меншає, але автори тестів отримують мовчазний дозвіл не розбиратися з власною нестабільністю. Друга частина ціни — час: якщо 15-хвилинний позначено як нестабільний, справжня поломка проявиться приблизно через три чверті години, і після цього ще треба зрозуміти, це регресія чи знову . Для релізного циклу це означає, що зворотний звʼязок про зламаний код приходить із , коли автор уже переключився на іншу задачу. Тому схему як тимчасовий буфер із власником і дедлайном, а не як постійну політику.
Як перевірити банер «сесія завершиться за 30 хвилин», не чекаючи 30 хвилин?
Підміною часу (clock mocking): тест бере годинник сторінки під контроль і перемотує його вперед. У Playwright це page.clock — встановлюємо потрібний момент, відкриваємо сторінку, викликаємо перемотування на потрібний інтервал і одразу асертимо появу банера. Виграш подвійний, але головний не той, про який думають першим: прискорення прогону — приємний бонус, а суть у тому, що час перестає бути джерелом випадковості. Реальний годинник дає різні результати опівночі, на межі місяця, при переході на літній час і на повільному агенті; підмінений — однакові за будь-яких умов. Відповідь «збільшимо таймаут тесту до 31 хвилини» на співбесіді читається як нерозуміння задачі: вона робить прогін довшим і не прибирає жодної причини нестабільності.
Що саме перевизначає page.clock і чого він не зачепить?
Підміняється не «час узагалі», а перелічений поіменно набір глобальних сутностей сторінки: Date, setTimeout, setInterval, requestAnimationFrame і performance. Під контроль тесту переходить усе, що на них спирається: періодичне опитування сервера, зворотний відлік до кінця акції, дебаунс у полі пошуку, попередження про , анімації на кадрах. І навпаки — усе, що рахує час на бекенді, підміна клієнтського годинника не зачепить: серверний токен протухне за реальним часом, скільки б ви не перемотували сторінку. Сильна відповідь називає цю межу сама, без підказки. Практичний наслідок: сценарії з серверними таймерами тестують іншим способом — керованим станом на боці API або тестовим .
Коли брати setFixedTime, а коли install із перемотуванням?
Порядок вибору дока задає явно: спершу пробуємо page.clock.setFixedTime(), і лише якщо не підходить — page.clock.install(); режим setSystemTime() лишають для складних випадків. Критерій — не зручність, а поведінка застосунку. Якщо треба лише зафіксувати «сьогодні», щоб форматування дати чи знімок не залежали від дня прогону, а таймери мають цокати як завжди, вистачає фіксованого часу. Якщо ж таймери самі спираються на поточну мітку, застиглий час їх просто зупинить: інтервал ніколи не «дозріє», і тест зависне на порожньому екрані до таймауту. Для цього випадку потрібен установлений годинник, який уміє паузу, перемотування і покрокове просування — тоді ви керуєте не тільки датою, а й перебігом.
Підмінили час, а тест усе одно нестабільний. Що перевірити першим?
Порядок викликів. Установка годинника мусить відбутися перед будь-якою іншою дією, повʼязаною з часом — тобто до переходу на сторінку, а не після нього. Порушення цієї вимоги дає не помилку й не виняток, а невизначену поведінку: частина таймерів сторінки вже стартувала на справжньому годиннику і живе своїм життям, поки решта підмінена. На ревʼю це виглядає як черговий загадковий флак, хоча насправді засіб проти флаку вставлено не в те місце. Практичне правило: page.clock.install() іде першим рядком тесту або всередині , ще до page.goto(). Другий поширений випадок тієї ж скарги — фіксований час там, де потрібне перемотування: тест не флакає, а стабільно чекає події, яка ніколи не настане.
«Ми вимкнули анімації, і флак зник» — наскільки це коректно?
Коректно рівно для одного контексту — асертів-знімків, де вимкнення анімацій справді є дефолтом. Поводяться вони по-різному за тривалістю: скінченну ранер прокручує до її кінцевого стану, тому подія transitionend усе-таки відбувається, а нескінченну відкочує на початок і програє вже після того, як знімок знято. Текстова каретка ховається з тієї ж причини: її блимання давало б різницю між двома знімками того самого екрана. Поза візуальними порівняннями цей дефолт не діє, тож фраза «ми вимкнули анімації в тестах» найчастіше означає скопійований з форуму CSS-хак, а не документовану поведінку. Для звичайних кліків стабілізатором працює автоочікування: перед дією ранер вимагає, щоб рамка елемента лишалася незмінною щонайменше протягом двох послідовних кадрів анімації, тобто елемент, який ще їде, клікнутий не буде.
Що таке свіжа фікстура (fresh fixture) і чи є нею приватна схема в базі?
Фікстура — це все, що має існувати, аби тест міг виконатися: стан застосунку, дані, автентифікований користувач. Свіжа фікстура означає, що кожен тест будує собі новий екземпляр для приватного вжитку й не успадковує нічого ні від сусідніх тестів, ні від попередніх прогонів; канонічне джерело приписує їй прямий ефект — вона запобігає нестабільним тестам. Приватна схема в базі цією умовою не є: тести вона розводить у межах одного прогону, але наступний прогін отримає ту саму базу з наслідками попереднього, тобто фікстура спільна в часі — персистентна. Діагностичний відбиток упізнаваний: тест проходить один раз, а далі стабільно падає, бо другий прогін спотикається об створений раніше запис. Якщо справді свіжа фікстура задорога, порядок відступу теж заданий — спершу незмінна спільна, і лише потім звичайна спільна, вже з розумінням, що це компроміс.
Що безпечно класти у воркерну фікстуру, а що зробить її джерелом флаку?
Тестова фікстура створюється й прибирається довкола кожного тесту, а воркерна живе, поки живий процес воркера, і сам воркер перевикористовується для наступних файлів, доки набір воркерних фікстур збігається. Тобто це навмисно спільний стан, який переживає межі файлів, — саме тому він і швидкий, і небезпечний. Безпечно класти туди дороге й незмінне: піднятий сервер, автентифікацію службового користувача, отриманий токен, підключення. Небезпечно — усе, що тест модифікує: створене замовлення, змінені налаштування акаунта, лічильник, . Практичне правило: якщо тест здатен зробити стан брудним для сусіда, ця річ має бути тестовою фікстурою, навіть якщо це коштує кількох секунд на кожному тесті.
Ми поставили seed генератору даних — тепер дані детерміновані?
Не повністю, і на співбесіді очікують саме цього уточнення. (seed) справді робить послідовність значень сталою, але лише в межах однієї версії бібліотеки: оновлення пакета може змінити вихідні набори даних, і те саме зерно видасть інші імена й адреси, а тест, привʼязаний до конкретного значення, посиплеться після рутинного апдейту. Друга межа гостріша: методи, що будують дати відносно «зараз», відтворюваними не стають узагалі, бо їхня опорна точка змінюється щосекунди. Тут дві теми глави сходяться — таким методам потрібна фіксована опорна дата, тобто той самий підмінений годинник. Тому повна відповідь звучить як «детерміновані в межах версії бібліотеки і за умови фіксованого часу», а не просто «так, ми ж поставили seed».
Де межа корисності згенерованих тестових даних?
Межа проходить по тому, чи впливає значення на гілку виконання. Генерувати унікальні ключі, щоб не впиратися в бази, — випадок легітимний і корисний: для логіки застосунку такі значення байдужі. Шкідливо інше — коли випадкове значення потрапляє туди, звідки застосунок бере рішення: сума замовлення, вік користувача, країна доставки. Тест у такому разі щоразу перевіряє інший сценарій, його падіння неможливо ні відтворити, ні пояснити, і він мовчки не покриває ту межу, заради якої писався. Робоче формулювання: усе, що впливає на рішення застосунку, задається явно, а генерується лише наповнювач — і саме тому data-driven підхід іде в парі з дисципліною керування тестовими даними, а не замість неї.
Що прикладаєте до тікета про нестабільний тест?
Мінімально прийнятне — трейс (trace) першого ретраю впалого тесту, а не . Причина в складі: скріншот фіксує один кадр, тоді як трейс зберігає весь перебіг — використаний і тривалість кожної дії, стан DOM до неї й після, мережу з кодами та таймінгами, і метадані оточення. Політика запису канонічна й економна: трейс пишеться на першому повторі впалого тесту, а режим «на кожен тест» дока не рекомендує через вартість — звідси відома звʼязка, що при нульових ретраях трейсів не буде взагалі, бо не існує спроби, під час якої вони пишуться. Канон ISTQB ставить планку ще вище й вимагає аудиторського сліду: логи фреймворку, логи самого застосунку й вебсервера, логи збірки та — усе, що зводиться за часовими мітками. Один скріншот аудиторським слідом не є хоча б тому, що його ні з чим не звести, а вимога до рішення автоматизації сформульована жорстко: у разі падіння зберегти все, що знадобиться для аналізу, включно з дампами й стеками.
Тест упав по таймауту. Піднімати таймаут?
Майже завжди ні, і починати треба з назви рівня. Таймаути незалежні: якщо вичерпався таймаут асерту, збільшення таймауту тесту не додасть жодної спроби, бо асерт уже здався сам. Дока супроводжує розділ про низькорівневі таймаути прямим застереженням — якщо ви прийшли сюди через нестабільний тест, рішення майже напевно не тут. Плутанину підживлює ще й те, що звичні «30 секунд» належать бібліотеці, тоді як у ранера більшість операцій власного таймауту не мають, зате кожен тест має свій, який його і валить. Тому коректна реакція — дочитати повідомлення і зʼясувати, який рівень здався і чого саме чекали. Якщо свій бюджет вичерпав ретрайний асерт — умова так і не настала, і розбиратися треба із застосунком чи локатором, а не з числом. Сусідній випадок — неретрайний асерт на асинхронній сторінці: він падає миттєво, без жодного очікування, і саме тому флачить, а лікується переходом на ретрайний асерт, -обгортку або toPass, який, до речі, не успадковує налаштований таймаут асертів.
У вас нестабільний тест, а завтра реліз. Ваші дії?
Варіантів рівно три, і сильна відповідь називає всі: полагодити, посадити в із заведеним багом або свідомо видалити. Обидва «прості» виходи шкідливі самі по собі: мовчки видалений тест — це втрачене , про яке за тиждень ніхто не памʼятає, а залишений червоним — постійний шум, що затуляє справжні падіння сусідів. Карантин легітимний рівно доти, доки в нього є власник, тікет і термін; без цього він перетворюється на те саме видалення, тільки повільніше. Відповідь «поставлю три ретраї» тут читається як червоний прапорець, бо ретрай не змінює жодної причини — він лише прибирає її зі звіту. Для завтрашнього релізу робоче рішення саме карантин плюс баг із трейсом першого ретраю, а розслідування планується одразу після релізу, поки контекст свіжий.
Чи буває, що флак — це чесно спійманий недетермінізм застосунку?
Так, і це найцікавіша категорія. Класика — (eventual consistency): запис уже завершився, але наступне читання може повернути ще стару копію, а через мить та сама операція віддасть свіжу. Документація DynamoDB описує це як штатну поведінку, а не дефект, і показова деталь у тому, що суворе читання там задається прямо в запиті прапорцем ConsistentRead. Тобто потрібну передбачуваність подекуди достатньо ввімкнути, а не вибивати повторними спробами: тест, якому потрібні свіжі дані, просить консистентне читання. Якщо перемикача немає, коректний тест перевіряє умову ретрайним асертом замість фіксованої паузи. А якщо застосунок так поводитися не мав — це взагалі не флак, а дефект продукту, і червоний тест тут працює правильно.
Три кейси, у яких видно межу між «прибрали причину» і «сховали симптом»: тест таймера, який перестав чекати реального часу, конфігурація й червоного прогону в CI, і , що зеленіє наодинці та червоніє в наборі. Скрізь — що дивитися і чому саме так.
Кейс 1. Таймер сесії: від паузи до керованого годинника
Сценарій буденний: після тридцяти хвилин без активності застосунок показує попередження «сесія завершується», а ще через пʼять — розлогінює. Перша версія тесту зазвичай виглядає так і має рівно дві проблеми.
test('банер сесії (як не треба)', async ({ page }) => {
test.setTimeout(35 * 60_000); // проблема 1: прогін на пів години
await page.goto('/dashboard');
await page.waitForTimeout(30 * 60_000); // проблема 2: чекаємо реальний час
await expect(page.getByRole('alert')).toBeVisible();
});
Друга проблема серйозніша за першу. Тридцять хвилин реального часу означають, що тест перетинає межу доби, перехід на літній час і будь-яке підвисання агента — і кожна з цих обставин змінює результат, хоч код не змінювався. Керований годинник прибирає саме це.
import { test, expect } from '@playwright/test';
test('банер сесії зʼявляється через 30 хвилин бездіяльності', async ({ page }) => {
// ПЕРШИЙ рядок: install мусить випередити будь-який виклик, повʼязаний із часом
await page.clock.install({ time: new Date('2026-03-10T09:00:00Z') });
await page.goto('/dashboard');
// до порогу банера ще нема
await page.clock.fastForward('29:00');
await expect(page.getByRole('alert')).toBeHidden();
// перетинаємо поріг — банер має зʼявитися
await page.clock.fastForward('01:30');
await expect(page.getByRole('alert')).toContainText('Сесія завершується');
});
Що дивитися і чому:
- Порядок, а не опції. Якщо
installопиниться післяpage.goto(), таймери, заведені при завантаженні сторінки, встигнуть стартувати на справжньому годиннику. Це не помилка і не виняток — це невизначена поведінка, яка на ревʼю виглядає як «знову флакає». Тому виклик іде першим рядком тесту або всередині . - Фіксований час і встановлений годинник вирішують різні задачі. Якщо потрібно лише, щоб «сьогодні» не залежало від дня прогону (форматування дати, вік користувача, знімок екрана), достатньо
page.clock.setFixedTime(). Якщо ж застосунок сам рахує інтервали від поточної мітки, застиглий час його зупинить — банер не зʼявиться ніколи, і тест чесно дочекається . - «ще немає» так само важливий, як «уже є». Без нього тест пройде навіть тоді, коли банер висить із першої секунди — а це реальний баг, який інакше проїде в прод.
- Клієнтський годинник не керує сервером. Якщо розлогін вирішує бекенд за строком дії токена, перемотування сторінки на нього не подіє: така частина сценарію тестується через API або керований стан на боці сервера.
Кейс 2. Конфігурація, після якої є що розбирати, і тріаж червоного прогону
Типова помилка конфігурації — однакове число на всю сюїту. Правильніше розділити: там, де джерело нестабільності зовнішнє й визнане, повтори виправдані; там, де вони маскували б власний дефект, їх нуль.
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
retries: process.env.CI ? 2 : 0, // локально падіння видно одразу
use: {
trace: 'on-first-retry', // доказ пишеться там, де він потрібен
screenshot: 'only-on-failure',
},
projects: [
{ name: 'core', use: { ...devices['Desktop Chrome'] } },
{
name: 'billing-sandbox', // зовнішня пісочниця: більше повторів, і це рішення задокументоване
retries: 3,
use: { ...devices['Desktop Chrome'], baseURL: process.env.SANDBOX_URL },
},
],
});
Далі — сам розбір. Порядок питань важливіший за здогад, тому зручно тримати таблицю тріажу під рукою.
| Симптом у звіті | Перша гіпотеза | Важіль | Чим підтвердити |
|---|---|---|---|
| Червоний у CI, зелений локально | повільніший агент, паралелізм, реальний час | очікування умови замість інтервалу, page.clock | трейс: тривалість кожної дії й мережеві таймінги |
| Упав, на повторі пройшов | стан у процесі воркера | трейс першого ретраю, чистка через testInfo.retry | категорія flaky у звіті, а не «зелено» |
| Падає лише в складі набору | спільна фікстура, яку хтось змінює | свіжа фікстура, перенесення стану в тестовий скоуп | запуск наодинці й у зміненому порядку |
| Падає раз на місяць чи опівночі | реальна дата в логіці застосунку | setFixedTime на опорну дату | відтворення на фіксованій даті |
| Асерт падає одразу, без очікування | неретрайний асерт на асинхронній сторінці | ретрайний асерт, полінг-обгортка або toPass | у трейсі перевірка миттєва, без повторних спроб |
| На скріншоті потрібний екран, а тест червоний | елемент ще рухається | автоочікування стабільності, дефолти асерта-знімка | журнал придатності: дія чекає на стабільність елемента |
Що дивитися і чому:
- Немає ретраїв — немає . Звʼязка неочевидна, доки не наступиш:
trace: 'on-first-retry'пише артефакт саме під час повтору, тож приretries: 0тека з артефактами лишиться порожньою. Це не поламаний конфіг, а логічний наслідок. trace: 'on'не рятує. Режим «на кожен тест» дока не рекомендує через вартість: час прогону й обсяг артефактів ростуть на тисячі зелених тестів, більшість із яких ніхто не відкриє.- Ретрай стирає частину доказів. Друга спроба стартує в іншому процесі, тож стан, який і спричинив падіння, до неї не доживає; а трейс при
on-first-retry— це запис самого ретраю, не першого падіння. Тому до трейсу в тікеті додають аудиторський слід — логи, які зводяться за часовими мітками. - Числу ретраїв потрібне пояснення.
retries: 3без коментаря про причину — те саме, що замовчаний дефект; рядок із назвою зовнішньої залежності перетворює магічне число на рішення, яке можна переглянути.
Кейс 3. Сюїта зеленіє наодинці й червоніє в наборі
Сценарій: тест «користувач бачить порожній кошик» стабільний, поки запускається сам, і падає в повному прогоні приблизно раз на три рази. Перша ж перевірка — запустити його ізольовано — дає підказку: справа не в тесті, а в тому, що хтось поруч змінює спільний стан.
// Було: акаунт піднімається один раз на воркер — і всі тести правлять його разом
export const test = base.extend<{}, { account: Account }>({
account: [async ({}, use) => {
const account = await api.createAccount(); // дорого, тому «зекономили»
await use(account);
await api.deleteAccount(account.id);
}, { scope: 'worker' }],
});
фікстура живе, поки живий процес воркера, і перевикористовується для наступних файлів. Отже, кошик, який один тест наповнив, а інший очікує порожнім, — це не випадковість, а прямий наслідок . Розділяємо: дороге й незмінне лишається воркерним, усе, що тести мутують, стає тестовим.
// Стало: токен спільний, дані — приватні на кожен тест
export const test = base.extend<{ cart: Cart }, { token: string }>({
token: [async ({}, use) => {
await use(await api.loginServiceUser()); // дороге й незмінне
}, { scope: 'worker' }],
cart: async ({ token }, use) => {
faker.seed(20260730); // відтворюваний прогін
const cart = await api.createCart(token, {
email: faker.internet.email(), // на гілку виконання не впливає
currency: 'UAH', // впливає — задано явно
itemsCount: 0, // межа з тест-дизайну, теж явно
});
await use(cart);
await api.deleteCart(token, cart.id); // прибирання свого, а не чужого
},
});
Що дивитися і чому:
- Приватна схема в базі проблему не закриває. Вона розводить тести в межах одного прогону, але наступний прогін отримає ту саму базу з наслідками попереднього. Упізнаваний відбиток — тест проходить один раз, а далі стабільно падає на конфлікті унікальності.
- фіксує послідовність, але не назавжди. Свіжий реліз бібліотеки здатен видати на тому ж зерні інші значення, тож асерти на конкретне згенероване імʼя — міна сповільненої дії. І окремо: там, де дати рахуються від «зараз», відтворюваності немає взагалі, доки під ними немає фіксованої опорної дати.
- Явне важливіше за випадкове там, де є гілка виконання. Валюта, тариф, знижка, вік визначають, яким шляхом піде застосунок, тому їх задають руками. Генерувати корисно те, що потрібне лише для унікальності — пошту, назву, зовнішній ідентифікатор.
- не заміняє свіжий стан. Про нього забувають при ранньому виході з тесту, а частину слідів прибрати неможливо в принципі. Якщо вибір стоїть між «акуратно прибираємо спільне» і «створюємо своє», друге стабільніше навіть коштом секунд.
Межа інструмента: причина проти симптому
- Розводжу два класи важелів і не плутаю їх у розмові: , фіксовані дані та свіжий стан прибирають причину недетермінізму, а , і відео лише роблять її видимою.
- Можу назвати те, чого не лікує в принципі: гонку між браузером і кодом тесту, доступність сервера й бази, мережу, конкуренцію за ресурси — це не вимикається опцією конфігу, а жорстка пауза проти них програє двічі: замало — тест червоний, забагато — прогін непідйомний.
- Памʼятаю окрему категорію «застосунок справді недетермінований, і тест це чесно ловить»: (eventual consistency) — штатна поведінка, а не дефект, і в DynamoDB суворе читання задається прапорцем
ConsistentReadпрямо в запиті.
Ретраї: рівні, категорії, ціна
- Знаю, що ретраї вимкнені за замовчуванням, і можу перелічити рівні задання: глобально в конфізі, на файл, на групу, на проєкт (project) — а проєкт це не «ще один браузер», а логічна група з власним , середовищем і ретраями.
- Розводжу три категорії результату —
passed,flaky,failed— і розумію, що «зелений з другої спроби» пройденим не рахується; якщо звіт не рахуєflakyокремо, метрика стабільності фіктивна. - Памʼятаю, що ретрай іде в новому -процесі: накопичений стан не переїжджає, тож разом із ним зникає і причина падіння, і частина доказів;
testInfo.retryдозволяє підготуватися до повтору, але це прийом, а не заміна розслідуванню. - Можу пояснити ціну схеми «падінням вважаємо лише те, що впало тричі поспіль» словами самого Google: вона знижує хибні спрацювання, але заохочує ігнорувати власний , а справжня поломка в 15-хвилинному тесті проявляється аж через три чверті години.
- Маю готову відповідь на «що робити з флакі-тестом»: полагодити, із заведеним багом або свідоме видалення — а не підняти число ретраїв.
Підміна часу і анімації
- Можу пояснити, навіщо
page.clock, не зводячи все до швидкості (головне — прибрати час як джерело мінливості), і назвати поіменно, що перевизначається:Date,setTimeout,setInterval,requestAnimationFrame,performance— а також межу: серверні таймери цим не керуються. - Тримаю в голові порядок вибору режиму — спершу
setFixedTime, потімinstallз паузою й перемотуванням,setSystemTimeдля складних випадків — і критерій: якщо таймери самі спираються на поточну мітку часу, застиглий годинник їх зупинить. - Памʼятаю вимогу порядку:
installвикликається раніше за будь-що інше, що торкається часу, інакше поведінка стає невизначеною — і це виглядає як черговий , а не як помилка. - Не переношу тезу «вимкнули анімації» на всі тести: це дефолт саме -знімка (скінченну ранер прокручує до кінця, нескінченну відкочує на початок, каретку ховає), а для звичайних дій працює перевірка стабільності — незмінна рамка елемента щонайменше протягом двох послідовних кадрів.
Стан, фікстури й тестові дані
- Можу дати означення свіжої (fresh fixture) — власний новий екземпляр на кожен тест, без спадку від сусідів і попередніх прогонів — назвати її ефект (запобігає нестабільним тестам) і порядок відступу: незмінна спільна, і лише потім звичайна спільна. Приватну бази з нею не плутаю: наступний прогін дістає той самий стан, а відбиток такої персистентності — «пройшов один раз, далі падає».
- Починаю діагностику з питання «чи падає тест наодинці», бо взаємний вплив можливий лише через спільну фікстуру, а «приберемо за собою після кожного тесту» слабше за свіжий стан: про забувають, та й частину слідів прибрати неможливо взагалі.
- Розводжу : тестова фікстура зникає разом із тестом, воркерна живе, поки живий процес воркера, тож туди йде дороге й незмінне (автентифікація, підключення), а не те, що тест мутує.
- Знаю дві межі відтворюваності (seed) — версія бібліотеки й відносні дати, яким потрібна фіксована опорна точка — і правило генерації: унікальні ключі можна, а значення, що впливає на гілку виконання, задається явно.
Докази: трейс, логи, таймаути
- Знаю, чому при падінні в CI дока ставить трейс вище за відео та : кадр показує лише результат, а трейс — увесь перебіг ( й тривалості дій, стан DOM до і після, мережа, , метадані оточення).
- Памʼятаю канонічну політику запису — трейс на першому повторі впалого тесту, режим «на кожен тест» не рекомендований через вартість — і звʼязку, що при нульових ретраях трейсів не буде взагалі.
- Можу сформулювати вимогу ISTQB до доказів: аудиторський слід із логів фреймворку, застосунку, збірки й , який зводиться за часовими мітками; один скріншот цій вимозі не відповідає.
- Не лікую флак таймаутами: рівні незалежні, тому вичерпаний таймаут асерту не рятується таймаутом тесту, а дока прямо застерігає, що для нестабільного тесту рішення шукати не тут.
Квіз
Перед стартом
- Питань: 17
- Поріг «зараховано»: ≥70% правильних відповідей.
- Результат впливає на прогрес; завалені питання підуть у чергу повторення.
- Квіз впливає на компліт теми: тема стає «пройдено», лише коли прочитано теорію І квіз складено на ≥70%.
Питання
Флакі-тест (flaky test) — що це і чим відрізняється від звичайного падіння?
