07 · Інструменти автоматизації
Паралельний запуск: workers, шардінг, браузерні ферми
Зміст
росте лінійно, а терпіння команди — ні. Шістсот тестів по чотири секунди — це сорок хвилин чекання перед кожним , і першою реакцією завжди буває «а давайте паралельно». Далі одне з двох: або прогін стискається до пʼяти хвилин, або половина тестів червоніє, конфіг відкочують і роблять висновок «нам паралельність не підходить». Різниця лежить не в конфігу: паралельний запуск нічого не ламає — він перестає приховувати те, що тести й так залежали один від одного.
Цю главу можна пропустити при першому проході: щоб писати тести, вона не потрібна. Але саме тут закінчуються питання «чому в паралелі падає, а по одному зелене». Навіщо паралелити взагалі, економіка CI-хвилин і політика — теми розділів про автоматизацію та CI/CD; тут — механіка інструмента: що він запускає одночасно, де межа однієї машини і де починається чужа інфраструктура.
Три стелі, які легко змішати
Слово «паралельно» покриває три різні речі, і плутанина між ними — головна причина того, що прискорення не настає.
- (worker) — те, що масштабує прогін у межах однієї машини: окремий процес зі своїм браузером (у частині — потік, про це нижче).
- (sharding) — розбиття сюїти на частини, які розкладаються між машинами: кожен шард виконується на своїй машині й поводиться як окрема CI, незалежна від інших.
- (browser farm) — чужі браузери, доступні тесту по мережі. Вона не розкладає тести, а відповідає на питання, де саме цим воркерам і шардам узяти браузер.
Стелі рахуються окремо й не підмінюють одна одну, тож «додати шардів» без ємності ферми не прискорює нічого. Поділ не специфічний для одного інструмента: у CodeceptJS два вбудовані способи описані буквально як воркери в межах одного ранера і шардінг файлів між машинами CI в матричному .
Воркер — процес операційної системи
Найважливіше про воркер сказано в доці одним рядком: це процеси ОС, які працюють незалежно, під керуванням ранера, і кожен запускає власний браузер. З цього випливає все інше.
Спільного стану немає: ані глобальних змінних, ані обміну повідомленнями — комунікація між воркерами прямо названа неможливою. Тому «передам id замовлення через змінну модуля» працює рівно доти, доки обидва тести випадково опиняються в одному воркері.
Одиниця паралельності за замовчуванням — файл, а не тест. Файли йдуть паралельно, а тести всередині файла — по черзі, в одному воркері, поки цього явно не змінили. Ранер ще й перевикористовує воркер настільки, наскільки може, тож кілька файлів зазвичай проходять в одному процесі один за одним.
Деталь, яка економить години дебагу: після падіння тесту воркер гарантовано вбивають, щоб наступні стартували в чистому оточенні. Зіпсований стан браузера за межі впалого тесту не тече — а от зіпсовані дані в базі течуть, і це вже не проблема ранера.
Вимкнути паралельність окремим прапорцем не можна — її вимикають обмеженням до одного воркера:
// playwright.config.ts
export default defineConfig({
workers: 1, // або --workers=1 у командному рядку
});
Воркер не обовʼязково процес. У CodeceptJS run-workers піднімає потоки, кожен — незалежний екземпляр інструмента зі своїм зрізом сюїти; результати потім зливаються. Побічний наслідок названий прямо: покрокового виводу в у цьому режимі немає взагалі — вивід окремих потоків неможливо чисто переплести. Той самий інструмент дає й вибір стратегії розкладки: test (кожен тест закріплено за воркером наперед — коли тести приблизно однакові за часом), suite (сюїта не розривається — коли є спільний BeforeSuite) і pool (воркери беруть наступний тест зі спільної черги — коли тривалості різні, найкраще балансування).
Звідси перше питання про будь-який ранер: воркер тут процес чи потік, і хто вирішує, який тест кому дістанеться.
Воркерні фікстури: що живе довше за тест
Якщо воркер — довгий процес, у нього зʼявляється власний життєвий цикл, і (fixture) можна привʼязати саме до нього. Різниця проста: тестова фікстура прибирається після кожного тесту, воркерна — лише коли завершується сам воркер.
import { test as base } from '@playwright/test';
export const test = base.extend<{}, { apiToken: string }>({
apiToken: [async ({}, use) => {
const token = await issueServiceToken(); // дорого, але один раз на воркер
await use(token);
}, { scope: 'worker' }],
});
Дві деталі легко проґавити. Воркер перевикористовується для наступних файлів лише поки воркерні фікстури збігаються — щедра параметризація воркерного скоупу непомітно множить перезапуски процесу, і виграш на дорогому setup віддається на старти браузера. А сама воркерна фікстура має власний , рівний дефолтному таймауту тесту: підняття сервісу, що чесно займає хвилину, впаде не як «повільний тест», а як фікстура.
І третя деталь, найдорожча. Воркерна фікстура, яка тримає змінюваний стан, це вже не оптимізація, а спільна фікстура на групу тестів: рівно та конструкція, у якій тести починають залежати одне від одного. Канонічний виклад називає причину прямо: тести, що взаємодіють між собою, зазвичай спричинені спільною фікстурою, де один залежить від наслідків іншого. Критерій із цього простий: незмінне й дороге — воркерний скоуп (токен сервісного акаунта, підняте оточення), усе, що змінює стан — тестовий. Порядок відступу джерело задає теж явно: свіжа фікстура → незмінна спільна → і лише потім звичайна спільна.
Фікстури тут не косметика над хуками: вони дають інкапсуляцію setup і teardown в одному місці, між файлами і роботу on-demand. Канон автоматизації звʼязує їх із двома властивостями тесту прямо: повторюваністю й атомарністю. Детально — у главі про фікстури, хуки й конфігурацію.
fullyParallel і serial: два кроки в протилежні боки
Дефолт (файл як одиниця) — компроміс. Обидві опції зсувають його, і в різні боки.
fullyParallel: true знімає послідовність усередині файла: тести розкладаються поштучно. Наслідок, про який часто дізнаються з падінь: у повністю паралельному режимі beforeAll і afterAll виконуються для кожного тесту окремо. Логін у beforeAll, який економив час, раптом виконується не раз на файл, а раз на тест — і прогін не швидшає, а сповільнюється.
Протилежний зсув — послідовний режим. Він потрібен, коли тести справді залежні, і дока прямо називає його не рекомендованим, з двома точно названими наслідками: якщо один тест групи впав, усі наступні пропускаються, а ретрай іде на всю групу.
test.describe.configure({ mode: 'serial' });
// ...або mode: 'parallel' — щоб розпаралелити один конкретний файл
Ось чому «позначили serial — стабілізували» це не стабілізація, а обмін: пʼять незалежних тестів перетворюються на один довгий блок, який падає цілком і ретраїться цілком. Правильний висновок дока формулює за вас: краще зробити тести ізольованими, щоб вони виконувалися незалежно.
--shard: розкладка між машинами CI
Шардінг вмикається одним прапорцем --shard=x/y — і саме тому його недооцінюють. Обмеження назване прямо: шардяться лише ті тести, які можна виконувати паралельно, а за замовчуванням це означає файли. Звідси головна практична різниця:
| Гранулярність | Коли | Що з рівністю шардів |
|---|---|---|
| Файлова (дефолт) | без fullyParallel | шард дістає цілі файли; нерівні за розміром файли дають нерівні шарди — один виконає значно більше тестів, інший менше або жодного |
| Тестова | fullyParallel: true | розподіл поштучно, кожен шард намагається взяти однакову кількість тестів |
Рецепт джерела рівно з двох варіантів: або fullyParallel: true, або дрібні й приблизно рівні файли. Третього — «розкладати за історією таймінгів» — офіційна дока не обіцяє.
Друге, про що дізнаються post factum: кожен шард дає власний звіт. Єдиного звіту шардінг сам по собі не робить — його отримують злиттям blob-звітів, які несуть і вкладення (, діфи ) і є проміжним форматом, з якого роблять будь-який інший. Детальніше — у главі про дебаг, trace viewer і репортинг.
Третє — взаємодія з проєктами (project) у конфігурації. Будь-яка фільтрація, зокрема за шардом, вибирає лише основні тести, а тести залежностей підтягуються самі, тож шард на CI не лишається без підготовки: setup-проєкт із логіном відпрацює в кожному шарді. Дзеркальний бік: якщо тести залежності впали, залежні не запускаються взагалі — шард устигає виконати хіба жменьку setup-тестів, тож у його звіті дивляться не лише на падіння, а й на кількість виконаних тестів.
Про стелю прискорення. (Amdahl's law) обмежує прискорення задачі при додаванні ресурсів: навіть з нескінченною кількістю процесорів воно впирається в непаралелізовану частку, і якщо паралелиться половина роботи, максимум — ×2, а якщо 95% — ×20. Формально це закон про обчислення, а не про сюїту, тож на прогін його переносять як орієнтир: serial-групи, спільна підготовка й перед прогоном — той знаменник, який жодною кількістю машин не скорочується.
Ферма: чужі браузери по мережі
Ферма виглядає як магія: той самий тест раптом виконується на машині в іншій країні, і код не змінився. Магії немає, є вимога специфікації. Протокол WebDriver описує ланцюг вузлів поіменно — local end (мовна бібліотека), intermediary node (, що реалізує обидва боки протоколу) і endpoint node (реалізований самим браузером), — тобто проміжний вузол тут законна частина моделі, а не милиця. І далі стоїть вимога, з якої все й працює: усі типи віддалених вузлів мусять бути невідрізнимими як чорна скринька з погляду клієнта. Саме тому переїзд у хмару постачальник ферми описує у власній довідці як зміну адреси хабу й креденшелів, а не переписування тестів.
Свій Selenium Grid. Selenium Grid — набір компонентів, а не монолітний сервер, і той самий функціонал розгортається трьома способами: усе в одному процесі (standalone), хаб із вузлами, повністю розподілено. Найпростіший режим дає повноцінний парк однією командою, але виконується тільки на одній машині. Головний мотив багатомашинного режиму дока називає не швидкістю, а комбінацією середовищ: різні ОС і версії браузерів в одному парку. Далі — числа, які й роблять розмову про ємність предметною:
- Стеля паралельності вузла — кількість CPU: вісім ядер означають до восьми одночасних сеансів, з окремим винятком для Safari, у якого сеанс завжди один. На кожен сеанс закладається близько 1 ГБ памʼяті.
- Рекомендація — дробити вузли, а не вирощувати один великий: тридцять два маленькі краще за один на 32 ядра, бо падіння вузла лишається локальним. Docker названо інструментом такого дроблення. Самі розміри парку дока подає як орієнтири, прямо кажучи, що одного розміру на всіх немає.
- Парк треба закрити фаєрволом: відкритий доступ дока кваліфікує як , а не як зручність.
Хмарна ферма. Тут купують не швидкість, а відсутність власного заліза. LambdaTest (сторінка довідки тепер підписана TestMu AI) описує пакет так: адреса хабу плюс capabilities, а в комплекті — паралельне виконання, відео, логи й скріншоти на кожен сеанс, без інфраструктури для обслуговування; заявлений масштаб — «3000+ комбінацій браузерів, реальних пристроїв і ОС». І формулювання, і число — заява постачальника станом на 2026-07-29 (дата забору сторінки), а не незалежно перевірена цифра.
Реальні пристрої. Коли в матриці зʼявляються мобільні, ферма перестає бути про браузери й стає про залізо. Межу MDN формулює чітко: найбільшу точність дає реальний пристрій, і мінімальний device lab починається з фізичних апаратів — Mac, Windows-ПК, телефон і планшет на Android, телефон і планшет на iOS, плюс пара слабких (продуктивність перевіряють не на флагмані). Комерційні сервіси — MDN називає Sauce Labs і BrowserStack — забирають цю інфраструктуру на себе. А симуляція в DevTools це наближення першого порядку: код виконується на вашій машині, і архітектуру мобільних процесорів режим пристрою не змоделює ніколи. Сама матриця — тема глави про кросбраузерність і адаптивну верстку.
І головне: ферма й шардінг — різні осі. Якщо шарди простоюють у черзі, упирається не і не конфіг ранера, а ліміт паралельних сеансів.
Вимоги до тестів: ізоляція — передумова, не гігієна
Усе вище працює за однієї умови, і вона про самі тести. Ізоляція (isolation) знімає питання порядку виконання — саме тому вона й робить паралельність і шардінг можливими. Формулювання доки не залишає місця для компромісу: кожен тест має бути повністю ізольований і виконуватися незалежно — з власними localStorage, sessionStorage, даними й кукі. Три названі наслідки: падіння не переноситься, дебажити легше (тест можна ганяти окремо скільки завгодно) і не треба думати про порядок у паралельному прогоні та шардінгу.
Дві стратегії ізоляції нерівноцінні: починати з нуля або прибирати між тестами. Про легко забути, а дещо прибрати взагалі неможливо — приклад доки, «відвідані посилання», хороший саме тим, що не лікується жодним afterEach. На рівні браузера задачу закриває свіжий (browser context) — глава про архітектуру, браузери й контексти, — а дорогий логін виносять у (storage state) і setup-проєкт (глава про автентифікацію й повторне використання стану). Канон незалежності тестів як принципу — глава про анатомію автотесту; тут — його механіка в паралельному прогоні.
Реальні порушення живуть не в браузері, а в даних. Канонічний виклад дає точну межу: паралельні прогони над спільним станом (Test Run War) можливі тільки за глобально спільної фікстури, до якої різні тести звертаються і яку іноді змінюють; спільним станом може бути файл або записи в тестовій базі. Тому перед вмиканням паралельності питання ставиться не «скільки воркерів», а «що в нас глобально спільне»: акаунт, промокод, черга, файл, рядок у довіднику. Ознака теж названа: тест, який сам по собі зелений, падає, коли до сюїти додали або з неї прибрали інший. Лікування джерело формулює як усунення , а не симптому — свіжа фікстура на кожен тест.
Останній аргумент — з боку канону автоматизації: вона дає тести, які неможливо виконати вручну, і паралельне виконання названо серед прикладів. Тобто це не оптимізація прогону, а окрема здатність, за яку платять дисципліною ізоляції.
Типові помилки
- «Поставили вісім воркерів — половина сюїти червона, паралельність нам не підходить» — виглядає як обмеження інструмента, а насправді прогін залежність не створив, а виявив: антипатерн
Test Run Warможливий лише за глобально спільної фікстури, яку тести змінюють. - «Тест зелений, коли ганяю його окремо» — виглядає як середовища, а насправді це маркер тестів, що взаємодіють: падати починає саме тоді, коли в сюїту додали або з неї прибрали інший.
- «Позначили групу як serial і стабілізували» — виглядає як лікування, а насправді обмін: падіння одного тесту тепер пропускає всі наступні, а ретрай іде на всю групу; дока цей режим не рекомендує.
- «Увімкнули fullyParallel — прогін сповільнився» — виглядає як парадокс, а насправді в цьому режимі
beforeAllвиконується для кожного тесту, і логін із хука робиться не раз на файл, а раз на тест. - «Додали шардів — швидше не стало» — виглядає як проблема пайплайну, а насправді або шарди чекають на браузер (стеля — квота сеансів), або файли нерівні й один шард дістав більшість тестів, а інший — жодного.
- «Шардували прогін — отримали пʼять звітів замість одного» — виглядає як поломка, а насправді єдиний звіт це окремий крок злиття
blob-звітів, а не побічний ефект шардінгу. - «Виніс усе дороге у воркерну фікстуру» — виглядає як оптимізація, а насправді воркер перевикористовується далі лише поки воркерні фікстури збігаються, а змінюваний стан у цьому скоупі — та сама спільна фікстура.
- «Візьмемо одну потужну машину на 32 ядра, сітку підняли — готово» — виглядає як економія й завершена інфраструктура, а насправді дока радить протилежне (маленькі вузли ізолюють процеси) і окремо вимагає закрити доступ фаєрволом.
- «Прогнали мобільну матрицю в емуляції — покриття є» — виглядає як , а насправді симуляція пристрою це наближення першого порядку: код виконується на вашій машині, і архітектуру мобільного процесора вона не змоделює.
Підсумок
- Три стелі рахуються окремо: воркери — у межах машини, шарди — між машинами, сеанси — у фермі. «Більше шардів» без ємності ферми не прискорює нічого.
- Воркер — процес ОС з власним браузером і без спільного стану; одиниця паралельності за замовчуванням — файл, і після падіння воркер убивають.
- Гранулярність розкладки визначає рівність шардів:
fullyParallelрозкладає поштучно, дефолт — файлами, а нерівні файли дають шард без жодного тесту. serial— не стабілізація, а обмін: група пропускається після першого падіння і ретраїться цілком; дока прямо не рекомендує цей режим.- Ізоляція — передумова, а не гігієна: без неї паралельності немає, а
Test Run Warможливий лише за глобально спільної фікстури — і лікується усуненням передумови, а не старанним прибиранням.
Можливі питання
- «Чим шардінг відрізняється від паралельного запуску?» — типове формулювання теми, і воно про межу «машина проти процесу». Сильна відповідь називає воркер процесом у межах машини, шард — окремою незалежною джобою між машинами, і додає третій рівень: ємність ферми, яку ці джоби ділять.
- «Скільки воркерів поставите і чому?» — інтервʼюер дивиться, чи є модель ресурсу, а не число. Виграшна логіка: ємність рахується в ядрах і памʼяті (у сітці Selenium це прямо CPU і близько 1 ГБ на сеанс), а не в тестах, і Safari тут окремий випадок.
- «Тести падають у паралелі, по одному зелені — що робиш?» — чекають не «поставлю retries», а метод: спершу «що в нас глобально спільне», далі маркер «зелений сам, падає в сюїті», далі свіжа фікстура. Згадка
serialприпустима лише з називанням ціни. - «Що таке worker-scoped фікстура і коли вона доречна?» — розводять скоупи (тестова прибирається після тесту, воркерна — після воркера) і називають критерій: незмінне й дороге — воркерне, усе, що змінює стан — тестове.
- «Навіщо ферма, якщо є CI-агенти?» — відповідь про комбінацію середовищ, а не про швидкість: різні ОС і версії браузерів в одній сітці, реальні пристрої там, де емуляція не годиться. Сильний кандидат додасть, що специфікація вимагає невідрізнимості віддаленого вузла, тому код тесту не змінюється.
- «Як зібрати один звіт із шардованого прогону?» — перевіряють досвід реального CI: кожен шард дає власний звіт, єдиний отримують злиттям
blob-звітів, і самеblobнесе вкладення на кшталт трейсів і діфів скріншотів.
Джерела
- Playwright — Parallelism — воркер як процес ОС і одиниця масштабування в межах машини.
- Playwright — Sharding — шард як окрема незалежна джоба CI, поділ «у межах машини проти між машинами».
- CodeceptJS — Parallel Execution — той самий поділ у другому інструменті: воркери на одному ранері, шардінг між машинами матричного білда.
- Selenium — Grid: Getting Started — ємність віддалених вузлів як окрема межа масштабування.
Воркер — процес операційної системи
- Playwright — Parallelism — воркери як процеси ОС із власним браузером, відсутність спільного стану й комунікації, файл як одиниця паралельності, перевикористання воркера, вбивство воркера після падіння,
workers: 1як єдиний спосіб вимкнути паралельність. - CodeceptJS — Parallel Execution — воркер як потік із незалежним екземпляром інструмента, відсутність покрокового виводу, три стратегії розкладки з умовами застосування.
Воркерні фікстури: що живе довше за тест
- Playwright — Test fixtures — скоупи
testіworker, прибирання воркерної фікстури разом із воркером, перевикористання воркера лише за збігу воркерних фікстур, власний таймаут воркерної фікстури, три властивості фікстур проти хуків. - ISTQB® Certified Tester Advanced Level Test Automation Engineering (CTAL-TAE) v2.0 — §4.2: фікстури дають контроль над оточенням і даними та роблять тести повторюваними й атомарними.
- xUnit Test Patterns — Erratic Test — спільна фікстура як причина тестів, що взаємодіють між собою.
- xUnit Test Patterns — Fresh Fixture — вибір «щоразу заново проти перевикористання» як ключове рішення і порядок відступу «свіжа → незмінна спільна → спільна».
fullyParallel і serial: два кроки в протилежні боки
- Playwright — Parallelism —
beforeAll/afterAllдля кожного тесту в повністю паралельному режимі,serialяк не рекомендований режим, пропуск наступних тестів групи й ретрай усієї групи. - Playwright — Sharding —
fullyParallelяк перехід до розкладки на рівні окремих тестів.
--shard: розкладка між машинами CI
- Playwright — Sharding —
--shard=x/y, шард як окрема джоба, шардяться лише паралелізовні тести, файлова проти тестової гранулярності й нерівні шарди, рецепт «fullyParallelабо дрібні рівні файли», власний звіт кожного шарда і злиттяblob-звітів із вкладеннями. - Playwright — Reporters —
blobяк проміжний формат, з якого роблять будь-який інший звіт. - Playwright — Projects — фільтрація за шардом вибирає основні тести, а тести залежностей підтягуються самі; впала залежність — залежні тести не запускаються.
- CodeceptJS — Parallel Execution —
--shardяк розкладка файлів між машинами матричного білда. - Wikipedia — Amdahl's law — формулювання закону («навіть із нескінченною кількістю процесорів прискорення обмежене непаралелізованою часткою») і числові приклади ×2 і ×20 (
trust: secondary, dec-0710).
Ферма: чужі браузери по мережі
- W3C — WebDriver Level 2 — ланцюг local end / intermediary node / endpoint node, проміжний вузол як законна частина моделі, вимога невідрізнимості віддаленого вузла як чорної скриньки.
- Selenium — Grid: Getting Started — три способи розгортання, межа standalone «одна машина», комбінація ОС і версій як мотив, ємність вузла в CPU і ~1 ГБ на сеанс, виняток Safari, рекомендація дробити вузли, орієнтовні розміри парку, вимога закрити доступ фаєрволом.
- TestMu AI (колишній LambdaTest) — Get Started With Selenium Testing — склад пакета хмарної ферми та число «3000+ комбінацій» як заява постачальника; міграція як зміна адреси хабу й креденшелів (
trust: secondary, dec-1811 — канонічної опори під складом пакета немає). - MDN — Strategies for carrying out testing — реальний пристрій як найточніший, базовий склад
device lab, слабкі апарати для продуктивності, комерційні сервіси Sauce Labs і BrowserStack. - Chrome DevTools — Simulate mobile devices with device mode — симуляція як наближення першого порядку; архітектура мобільних процесорів не моделюється.
- Playwright — Sharding — шард як окрема незалежна джоба: ферма й шардінг як різні осі масштабування.
Вимоги до тестів: ізоляція — передумова, не гігієна
- Playwright — Test Isolation (browser contexts) — ізоляція знімає питання порядку й робить можливими паралельність і шардінг; три названі наслідки; дві стратегії ізоляції й неможливість прибрати «відвідані посилання».
- Playwright — Best Practices — вимога повної ізоляції тесту з власними
localStorage,sessionStorage, даними й кукі. - xUnit Test Patterns — Erratic Test —
Test Run Warможлива лише за глобально спільної фікстури; маркер «зелений сам, падає в сюїті»; файл і записи в базі як спільний стан. - xUnit Test Patterns — Fresh Fixture — свіжа фікстура як усунення передумови неповторюваного тесту.
- ISTQB® Certified Tester Advanced Level Test Automation Engineering (CTAL-TAE) v2.0 — §1.1: автоматизація дає тести, неможливі вручну, серед прикладів — паралельне виконання; §4.2: фікстури й повторюваність.
- Playwright — Parallelism —
serialяк не рекомендований режим із пропуском і груповим ретраєм;beforeAllдля кожного тесту в повністю паралельному режимі. - Playwright — Sharding — нерівні файли й шард без тестів; окремі звіти шардів і злиття
blob. - Playwright — Test fixtures — перевикористання воркера лише за збігу воркерних фікстур.
- xUnit Test Patterns — Erratic Test — умова
Test Run Warі маркер «зелений сам, падає в сюїті». - Selenium — Grid: Getting Started — багато маленьких вузлів замість одного великого; вимога закрити парк фаєрволом.
- Chrome DevTools — Simulate mobile devices with device mode — межі симуляції пристрою.
- Playwright — Sharding — межа «воркери в межах машини проти шардів між машинами», окремі звіти шардів і злиття
blobіз вкладеннями. - Playwright — Parallelism — воркер як процес ОС, наслідки
serial. - Playwright — Test fixtures — і умова перевикористання воркера.
- Selenium — Grid: Getting Started — ємність вузла в CPU і памʼяті, виняток Safari, комбінація ОС і версій браузерів як мотив багатомашинного режиму.
- W3C — WebDriver Level 2 — вимога невідрізнимості віддаленого вузла з погляду клієнта.
- xUnit Test Patterns — Erratic Test — маркер тестів, що взаємодіють, і умова
Test Run War. - MDN — Strategies for carrying out testing — реальні пристрої там, де емуляція недостатня.
Відео до теми
Що таке воркер у тест-ранері і що саме він запускає?
(worker) — це одиниця масштабування в межах однієї машини: окремий процес операційної системи, яким керує і якому належить власний екземпляр браузера. «Чотири воркери» означає чотири незалежні процеси й чотири браузери на тому самому агенті, а не чотири вкладки в одному вікні. Звідси головна властивість: спільної памʼяті немає, глобальні змінні одного процесу невидимі іншому, і обмінюватися повідомленнями воркери не вміють. Практичний наслідок — усе, що тести передавали один одному через змінну модуля, у паралельному прогоні спрацює лише випадково: доки обидва тести опинилися в одному процесі. Другий наслідок ресурсний: воркер коштує процес плюс браузер, тому ємність машини рахується в ядрах і памʼяті, а не в кількості тестів.
Чим шардінг відрізняється від паралельного запуску воркерами?
Це різні осі, і плутанина між ними — найчастіша причина того, що прискорення не настає. Воркери множать прогін усередині однієї машини; шард (shard) розкладає між машинами, і кожен шард живе як самостійна CI зі своїм чекаутом, своїми воркерами й повним незнанням про сусідів. Тому «додати воркерів» упирається в ядра й памʼять одного агента, а «додати шардів» — у кількість агентів, які CI дасть одночасно. Сильна відповідь називає й третій рівень: , чию квоту паралельних сеансів усі ці джоби ділять між собою. Стелі рахуються окремо й не підмінюють одна одну: шість шардів проти квоти на чотири сеанси дадуть два шарди в черзі, а не вдвічі швидший прогін.
Що таке браузерна ферма і навіщо вона, якщо в CI вже є агенти?
Ферма (browser farm) — це чужі браузери, доступні тесту по мережі; вона нічого не розкладає, а лише відповідає на питання, де воркерам і шардам узяти браузер. Мотив тут не швидкість, а матриця середовищ: різні ОС, різні версії браузерів, а для мобільних — реальні пристрої, яких на CI-агенті немає взагалі. Працює це тому, що специфікація WebDriver закладає проміжний вузол (intermediary node) у саму модель і вимагає, щоб усі типи віддалених вузлів були невідрізнимі як чорна скринька з погляду клієнта — тест не має способу дізнатися, чи говорить він із браузером напряму, чи через . Саме через цю вимогу переїзд на хмарну ферму зводиться до нової адреси хабу й креденшелів, а не до переписування тестів. Ціна — власна квота одночасних сеансів, яка легко стає справжньою стелею прогону.
Яка одиниця паралельності за замовчуванням — файл чи тест?
Файл. Тести всередині одного файла виконуються послідовно в одному воркері, а паралельно розкладаються самі файли. Ранер ще й економить на стартах: закінчивши файл, той самий процес зазвичай бере наступний, щоб не піднімати браузер заново. Наслідок перший, планувальний: сюїта з двох гігантських файлів на восьми воркерах реально задіє два — решта простоюватиме. Наслідок другий стосується : між машинами розкладаються тільки ті одиниці, які взагалі можна виконувати паралельно, тож за замовчуванням шарди отримують цілі файли з усією нерівністю їхніх розмірів.
Як вимкнути паралельність, коли треба продебажити прогін?
Окремого прапорця «вимкнути паралельність» немає — її вимикають обмеженням кількості воркерів до одного: workers: 1 у конфігурації або --workers=1 у команді. Це саме те, що потрібно, коли перевіряєш гіпотезу «у нас гонка за спільними даними»: позеленіло на одному воркері — причина в спільному стані, а не в . Важливо не плутати цей прийом із послідовним режимом групи: workers: 1 робить однопотоковим весь прогін і нічого не змінює в логіці груп, тоді як test.describe.configure({ mode: 'serial' }) змінює поведінку конкретної групи, лишаючи решту сюїти паралельною. Перше — інструмент діагностики, друге — рішення в дизайні сюїти, і платять за них по-різному.
Що відбувається з воркером після падіння тесту?
Його гарантовано вбивають, і наступний тест стартує у свіжому процесі з новим браузером — щоб зіпсоване оточення не потягнулося далі. Це чесне рівно в межах процесу: памʼять, стан браузера, відкриті сторінки зникають разом із воркером. А от усе, що тест устиг наробити зовні, смерть процесу не скасовує: створений і не видалений користувач, зайнятий промокод, змінений рядок у довіднику, файл на диску. Тому теза «ранер сам за собою прибирає» справедлива тільки про браузер, і саме дані лишаються головним джерелом падінь у паралелі. Побічна вартість теж є: кожне падіння оплачується новим стартом процесу й браузера, тож сюїта з великою кількістю не тільки червона, а й повільна.
Що таке worker-scoped фікстура і коли вона доречна?
(fixture) визначає час її життя: тестова зникає одразу після свого тесту, а воркерна тримається доти, доки живий сам процес. Тому у воркерний виносять дороге разове: сервісний токен, підняте оточення, згенерований довідник — те, за що не хочеться платити на кожному тесті. Критерій варто назвати на співбесіді прямо: дороге й таке, що ніхто не змінює — у воркерний скоуп, будь-що змінюване — у тестовий. Дві деталі відрізняють кандидата, який це справді робив. Перша: воркер перевикористовується для наступних файлів лише поки набір воркерних збігається, тож щедра параметризація цього скоупу тихо множить перезапуски процесу, і виграш на setup віддається назад на старти браузера. Друга: воркерна фікстура має власний , який дорівнює дефолтному таймауту тесту, тож сервіс, який чесно піднімається хвилину, звалить саме фікстуру, а не тест — і повідомлення про помилку прилетить зовсім не звідти, де його чекаєш.
Чому змінюваний стан у воркерній фікстурі — погана ідея?
Бо це вже не оптимізація: обʼєкт стає спільним для цілої групи тестів — саме та конструкція, з якої й починається взаємозалежність. Якщо кілька тестів по черзі беруть один і той самий обʼєкт із воркерного скоупу й дописують у нього свої зміни, результат кожного починає залежати від того, хто відпрацював раніше. Класика тестових патернів указує на джерело прямо: взаємозалежні тести майже завжди виростають зі спільної фікстури, у якій один спирається на те, що наробив інший. Порядок відступу теж канонічний: спершу свіжа фікстура на кожен тест, якщо надто дорого — незмінна спільна, і лише в останню чергу звичайна спільна, з якої й народжуються плаваючі падіння. Практичний фільтр перед виносом у воркерний скоуп: «чи може другий тест побачити наслідки першого через цю фікстуру?» — якщо так, скоуп тестовий.
Що робить fullyParallel і чому після його вмикання прогін іноді стає повільнішим?
fullyParallel: true знімає послідовність усередині файла: розкладка переходить із файлів на окремі тести, і вісім воркерів нарешті завантажуються навіть двома великими файлами. Той самий перемикач вирівнює шарди, бо тепер між машинами ділять не файли, а тести. Пастка живе в хуках: у повністю паралельному режимі beforeAll і afterAll відпрацьовують для кожного тесту окремо. Логін, який автор поклав у beforeAll, щоб зробити його раз на файл, раптом виконується раз на тест — і сумарний час зростає, попри більшу паралельність. Лікування не в тому, щоб відкотити , а в тому, щоб перенести дорогу підготовку туди, де вона справді одноразова: (storage state), setup-проєкт або воркерна фікстура з незмінним артефактом.
Коли доречний serial і яка його справжня ціна?
Послідовний режим потрібен там, де тести справді залежні й переписати їх зараз неможливо: майстер із кроками, який фізично не ділиться. Дока цей режим прямо не рекомендує, і називає дві ціни поіменно: після падіння одного тесту всі наступні в групі пропускаються, а ретрай іде на всю групу, а не на впалий тест. Тому фраза «загорнули в serial — стабілізували» описує не стабілізацію, а угоду: замість пʼятьох самостійних тестів у сюїті зʼявляється один довгий неподільний блок, який червоніє цілком і переганяється цілком. Такий блок ще й стає знаменником прискорення: скільки машин не додай, довший за нього прогін не стане. На співбесіді згадувати serial можна, але лише разом із ціною і з фразою «правильне рішення — зробити тести ізольованими».
Чому файлова гранулярність дає нерівні шарди й що з цим робити?
Шардяться лише ті одиниці, які можна виконувати паралельно, а за замовчуванням це файли. Тому шард отримує цілі файли: якщо у вас один файл на двісті тестів і десяток файлів по пʼять, розподіл «порівну за файлами» дасть один шард, що працює двадцять хвилин, і сусідній, який закінчує за півтори. Крайній випадок легко впізнати в CI: шард завершився за тридцять секунд і не виконав жодного тесту, бо всі файли поїхали до сусідів. Офіційних варіантів рецепту рівно два: увімкнути fullyParallel і ділити по тестах, або тримати файли дрібними й приблизно рівними за часом. Розкладка «за історією таймінгів минулих прогонів» звучить логічно, але як вбудована гарантія в доці не обіцяна — це вже самописний планувальник.
Як зібрати один звіт із шардованого прогону?
Ніяк не автоматично: кожен шард — окрема джоба, тож і звіт у кожного власний. Єдиний звіт роблять окремим кроком — злиттям blob-звітів, які кожен шард публікує як артефакт. blob тут не просто ще один формат, а проміжний: він несе вкладення (, діфи ), і вже з нього генерують будь-який фінальний звіт — HTML, JUnit чи інший. Помилка, яку роблять один раз: шардували прогін, а в лишили генерацію HTML у кожній джобі — на виході пʼять окремих звітів і жодної загальної картини. У відповіді варто назвати саме послідовність «кожен шард віддає blob-артефакт → окремий крок зливає → фінальний звіт», бо вона показує досвід реального CI, а не читання довідки.
Як шардінг взаємодіє з проєктами й залежностями?
Проєкти (project) з залежностями — типовий спосіб винести логін чи прогрів у окремий setup-крок, і в шардованому прогоні це працює краще, ніж очікують. Фільтр шарда відбирає тільки основні тести, тоді як їхні залежності додаються автоматично — тож кожен шард відпрацює свій setup і не лишиться без автентифікації. Дзеркальний бік цієї механіки треба знати наперед: коли setup-крок падає, залежні тести навіть не стартують — шард устигає виконати лише кілька setup-тестів, а основна частина сюїти лишається непрогнаною. Для пайплайну це підступний стан: статус джоби сам по собі не каже, скільки перевірок реально відбулося. Тому у звіті шардованого прогону перевіряють не лише кількість падінь, а й кількість виконаних тестів.
Скільки воркерів поставите і чому?
Інтервʼюер тут перевіряє не число, а наявність моделі ресурсу. Ємність рахується в ядрах і памʼяті: воркер — це процес плюс браузер, і в довідці Selenium Grid межа названа прямо — стеля паралельних сеансів вузла дорівнює кількості CPU, а на кожен сеанс закладають близько 1 ГБ памʼяті. Тобто вісім ядер означають до восьми одночасних сеансів, і Safari тут окремий випадок: у нього сеанс завжди один. Далі йде поправка на середовище: на CI-агенті ті самі ядра ділять збірка, база й сам ранер, тож «за кількістю ядер» — це стеля, а не рекомендація. Сильна відповідь закінчується методом, а не цифрою: підняти паралельність до першого зростання часу окремого тесту або появи таймаутів — це і є точка, де машина вперлася.
Тести падають у паралелі, а по одному зелені. Що робите?
Перше, що варто сказати вголос: паралельність нічого не зламала — вона перестала приховувати залежність, яка вже була. Симптом «сам по собі зелений, у сюїті червоний» — це не абстрактний , а точний підпис конкретної причини: тести взаємодіють через щось спільне, і падіння зʼявляються рівно після того, як склад сюїти змінили — сусіда додали або прибрали. Далі йде метод, а не конфіг: інвентаризація всього, що в прогоні одне на всіх — акаунт, черга, файл на диску, промокод, рядок у довіднику, записи в тестовій базі. Конфлікт паралельних прогонів виникає лише там, де такий спільний ресурс існує і хтось із тестів його переписує, тож знайти його — це і є діагноз. Лікують , а не симптом: свіжі дані на кожен тест замість старанного прибирання, бо прибирання завжди забуває про щось. Відповіді «поставлю retries» або «загорну в serial» тут програшні: перша ховає причину за зеленим кольором, друга обмінює її на пропуск тестів і груповий ретрай.
Додали шардів, а швидше не стало. Де шукати?
Питання діагностичне, і відповідати варто по гілках. Перша: чи завантажені ядра машин — якщо ні, воркерів замало або одиниця розкладки досі файл, а файлів менше, ніж воркерів. Друга: чи закінчують шарди приблизно разом — якщо ні, файли нерівні, і хтось дістав більшість тестів, а хтось порожньо. Третя: чи не чекають шарди на браузер — якщо чекають, стеля вже не у вашому конфігу, а в квоті паралельних сеансів ферми. Якщо всі три гілки чисті, лишається непаралелізована частина: (Amdahl's law) формально про обчислення, але як орієнтир працює й тут — прискорення впирається в частку, яку розпаралелити не можна. Практично цей знаменник складається з serial-груп, спільної підготовки й перед прогоном, і жодною кількістю машин він не скорочується.
Чому ізоляція — передумова паралельності, а не просто гігієна?
Бо ізоляція (isolation) знімає питання порядку виконання, а паралельний і шардований прогін порядку не гарантують у принципі. Тест має стартувати з власними кукі, localStorage, sessionStorage і власними даними — тоді падіння одного не переноситься на решту, тест можна ганяти окремо скільки завгодно під час дебагу, і не треба тримати в голові, хто після кого. Стратегій ізоляції дві, і вони нерівноцінні: починати з нуля або прибирати між тестами. Прибирання програє двічі — про частину слідів завжди забувають, а частину прибрати неможливо в принципі: канонічний приклад — уже відвідані посилання, які жодним afterEach не «розвідуєш». На рівні браузера задачу закриває свіжий контекст (browser context) на кожен тест, а дорогий логін виносять у збережений стан автентифікації, щоб ізоляція не коштувала хвилин. Формально це ще й окрема здатність автоматизації, а не оптимізація: паралельне виконання — приклад того, що вручну не відтворити взагалі.
Чим воркери в CodeceptJS відрізняються від воркерів Playwright?
Поділ «воркери в межах машини проти шардінгу між машинами» однаковий, а от реалізація воркера — ні, і це хороше питання на уважність. У Playwright воркер — процес ОС, у CodeceptJS run-workers піднімає потоки, кожен зі своїм незалежним екземпляром інструмента, а результати потім зливаються. Наслідок практичний: покроковий вивід у тут просто недоступний — чисто переплести рядки кількох потоків неможливо. Той самий інструмент дає ще й вибір стратегії розкладки: test закріплює тести за воркерами наперед (годиться, коли вони приблизно однакові за часом), suite не розриває сюїту (потрібно, коли є спільний BeforeSuite), pool роздає наступний тест із черги тому, хто звільнився (найкраще балансування за різних тривалостей). Тому про кожен новий ранер варто питати дві речі одразу: воркер у ньому процес чи потік — і за яким правилом тести дістаються воркерам.
Як розгортати Selenium Grid і які в нього обмеження ємності та безпеки?
Selenium Grid збирається з кількох компонентів, а не постачається монолітним сервером, тому одна й та сама функціональність існує в трьох формах розгортання: усе в одному процесі (standalone), класична пара «хаб плюс вузли» і повністю розподілена схема. Standalone піднімає робочий парк однією командою, проте за межі однієї машини не виходить — це зручність запуску, а не масштабування. Багатомашинний режим беруть не заради швидкості: мотивом у довідці названо саме матрицю середовищ, щоб в одному парку співіснували кілька операційних систем і кілька версій браузерів. Ємність вузла впирається в CPU (плюс близько 1 ГБ памʼяті на сеанс і окремий виняток Safari з єдиним сеансом), а рекомендація щодо форми парку контрінтуїтивна: краще тридцять два маленькі вузли, ніж один на тридцять два ядра — падіння тоді лишається локальним, і Docker названо інструментом такого дроблення. І останнє, про що забувають до першого інциденту: парк треба закрити фаєрволом — відкритий доступ кваліфікується як , а не як зручність для колег.
Прогнали мобільну матрицю в емуляції DevTools. Це покриття?
Ні, це наближення першого порядку — корисне для верстки й брейкпойнтів, але не для висновків про поведінку на пристрої. Причина механічна: код виконується на вашій машині й вашому процесорі, тож архітектуру мобільного процесора режим пристрою не змоделює ніколи — а саме там живе більшість сюрпризів із продуктивністю. Найточніший результат дає реальний пристрій, і мінімальний device lab починається з фізичного набору: Mac, ПК на Windows, телефон і планшет на Android, телефон і планшет на iOS — плюс обовʼязково пара слабких апаратів, бо продуктивність перевіряють не на флагмані. Утримувати це самотужки дорого, тому інфраструктуру віддають комерційним сервісам на кшталт Sauce Labs чи BrowserStack. Практичний висновок для звіту: емуляцію варто позначати саме як емуляцію, інакше матриця в документі обіцяє більше, ніж прогін реально перевірив.
Три ситуації, у яких паралельність вирішує долю прогону: «сорок хвилин, і швидше не стає» — де саме стеля; розбір , яка почервоніла рівно на восьми , із переїздом зі спільного акаунта на свіжі дані; і шардований , де замість одного звіту приїхало пʼять, а один шард віддав порожньо. Скрізь — що дивитися і чому.
Кейс 1. Прогін довгий: як за чотири виміри знайти справжню стелю
Сюїта на 620 тестів іде 38 хвилин. Команда просить «додати паралельності», але додавати наосліп немає сенсу: у прогону три різні стелі, і кожна лікується по-своєму. Перш ніж чіпати конфіг, збирають чотири числа — і кожне з них можна отримати за один прогін.
| Що вимірюємо | Як подивитися | Що означає результат | Що робити |
|---|---|---|---|
| Завантаження ядер агента | htop чи метрики ранера під час прогону; скільки процесів браузера живі одночасно | Ядра простоюють, а воркерів вісім — розкладати нема чого: файлів менше, ніж воркерів, або всі тести сидять у двох великих файлах | Увімкнути fullyParallel: true або порізати файли дрібніше |
| Розкид часу шардів | Тривалість кожної джоби в матриці CI | Шард 1 — 18 хв, шард 4 — 2 хв: файлова гранулярність роздала нерівні шматки | Той самий fullyParallel або приблизно рівні файли; третього рецепту дока не обіцяє |
| Очікування сеансу | Час від старту тесту до відкриття браузера; черга на боці ферми | Тести стоять у черзі — стеля не в пайплайні й не в конфігу, а у квоті одночасних сеансів | Підняти квоту або зменшити одночасність: більше шардів тут лише подовжить чергу |
| Найдовший неподільний блок | Тривалість найповільнішої serial-групи плюс спільна підготовка й деплой | Ядра завантажені, шарди рівні, черги немає, а прогін не коротшає — уперлися в непаралелізовану частину | Розбирати serial-групи й виносити підготовку; машини тут уже не допоможуть |
Що дивитися і чому:
- Порядок вимірів не випадковий. Три перші перевірки коштують один прогін і відсікають більшість причин; четверта — вже архітектурна робота на дні. Типова помилка — почати з неї («перепишемо serial-групи») там, де насправді шість воркерів чекали на чотири сеанси ферми.
- «Швидше не стало» після подвоєння шардів — це діагноз, а не невдача. Якщо час прогону не змінився, а удвічі більше, ви щойно виміряли квоту ферми: та сама, черга довша, рахунок за CI-хвилини більший.
- Стеля з четвертого рядка формулюється як . Він про обчислення, а не про сюїти, але як орієнтир чесний: якщо десять хвилин з тридцяти восьми — і спільний прогрів, то навіть нескінченна ферма дасть не менше десяти. Це число варто назвати вголос, перш ніж просити в команди більше агентів.
- Одну змінну за раз. Одночасно ввімкнути
fullyParallel, підняти воркерів і додати шардів — значить отримати зелений швидкий прогін і жодного розуміння, що саме допомогло; через місяць, коли він знову сповільниться, розбиратися доведеться з нуля.
Кейс 2. Вісім воркерів — половина сюїти червона
Локально на одному воркері все зелене, у CI на восьми — плаваючі падіння: то 409 на реєстрації, то «промокод уже використано», то кошик із чужим товаром. Спокуса — відкотити воркерів до двох. Правильний хід — прочитати симптом: паралельність залежність не створила, вона її показала.
// БУЛО: усе спільне — акаунт, промокод і навіть id замовлення через змінну модуля
import { test, expect } from '@playwright/test';
const USER = { email: 'qa.checkout@example.com', password: 'Qa12345!' };
const PROMO = 'BLACKFRIDAY';
let lastOrderId: string; // «передамо в наступний тест» — тримається, лише поки обидва тести в одному воркері
test('оформлення замовлення з промокодом', async ({ page }) => {
await login(page, USER);
await page.getByTestId('promo-input').fill(PROMO); // одноразовий на акаунт
await page.getByTestId('place-order').click();
lastOrderId = await page.getByTestId('order-id').innerText();
});
test('замовлення видно в історії', async ({ page }) => {
await login(page, USER);
await expect(page.getByText(lastOrderId)).toBeVisible(); // undefined, щойно тести розійшлися по процесах
});
Тут три різні дефекти, і кожен ламається по-своєму. Спільний акаунт — глобально спільна : два воркери одночасно кладуть товари в один кошик. Промокод — спільний ресурс, який змінюється: перший тест його гасить, другий отримує . А lastOrderId — міна сповільненої дії: доки обидва тести лежать в одному файлі, а розкладка йде файлами, вони гарантовано в одному воркері, і змінна «працює». Вона стане undefined рівно тоді, коли тести розʼїдуться по процесах: увімкнули fullyParallel, рознесли тести по файлах — або перший упав, воркер убили, і другого пішов у свіжому процесі. Спільної памʼяті між воркерами немає, тож ця конструкція не має шансів у паралелі.
// СТАЛО: дороге й незмінне — у воркерний скоуп, усе, що змінюється — на кожен тест
import { test as base, expect } from '@playwright/test';
type Fixtures = { shopper: { email: string; password: string }; promo: string };
type WorkerFixtures = { apiToken: string };
export const test = base.extend<Fixtures, WorkerFixtures>({
// незмінне й дороге: один токен сервісного акаунта на весь процес
apiToken: [async ({}, use) => {
await use(await issueServiceToken());
}, { scope: 'worker' }],
// усе, що змінює стан, — свіже на кожен тест
shopper: async ({ apiToken }, use) => {
const user = await api.createUser(apiToken); // унікальний email
await use(user);
await api.deleteUser(apiToken, user.id);
},
promo: async ({ apiToken }, use) => {
const code = await api.issuePromo(apiToken); // одноразовий, свій на тест
await use(code);
},
});
test('оформлення замовлення з промокодом', async ({ page, shopper, promo }) => {
await login(page, shopper);
await page.getByTestId('promo-input').fill(promo);
await page.getByTestId('place-order').click();
await expect(page.getByTestId('order-id')).toBeVisible();
});
Що дивитися і чому:
- Спершу інвентаризація, потім конфіг. Питання не «скільки воркерів витримає стенд», а «які ресурси в нас одні на всіх»: акаунт, черга, промокод, файл на диску, рядок у довіднику, записи в тестовій базі. Антипатерн
Test Run Warможливий тільки там, де такий ресурс існує і хтось із тестів його переписує — знайшли його, знайшли причину. - Маркер точний, ним і користуйтеся. «Зелений окремо, червоний у сюїті» означає не «середовище флакне», а «тест залежить від іншого тесту». Перевіряється дешево: прогнати впалий тест поодинці, потім разом із сусідом, який чіпає той самий ресурс.
- Другий тест не врятувати передачею
lastOrderId. Або він створює собі замовлення сам (через API, швидко), або два кроки чесно живуть в одному тесті. Спроба «зберегти в файл замість змінної» лише перетворює проблему процесів на проблему спільного файла — тобто на ту саму спільну фікстуру, тільки повільнішу. retriesіserialтут не лікування. Ретрай пофарбує прогін у зелений і залишить причину в коді.serialперетворить пʼять тестів на один блок, який після першого падіння пропустить решту й переганятиметься цілком — а спільний промокод усе одно лишиться спільним.- Токен у воркерному — приклад правильного винятку. Він дорогий і незмінний, його ніхто не псує. Але не варто параметризувати цей скоуп чим завгодно: воркер перевикористовується під наступні файли лише поки воркерні фікстури збігаються, тож зайвий параметр тихо перетворюється на перезапуски процесу.
Кейс 3. Шардований пайплайн: пʼять звітів, порожній шард і черга
Сюїту розклали на пʼять шардів у матриці GitHub Actions. Прогін став швидшим — і одразу приніс три нові симптоми, які легко прийняти за поломку.
jobs:
e2e:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3, 4, 5]
steps:
- uses: actions/checkout@v4
- run: npm ci && npx playwright install --with-deps
- run: npx playwright test --shard=${{ matrix.shard }}/5 --reporter=blob
- uses: actions/upload-artifact@v4
with:
name: blob-report-${{ matrix.shard }}
path: blob-report
retention-days: 1
report:
runs-on: ubuntu-latest
needs: [e2e]
if: always()
steps:
- uses: actions/checkout@v4
- run: npm ci
- uses: actions/download-artifact@v4
with:
pattern: blob-report-*
merge-multiple: true
path: all-blob-reports
- run: npx playwright merge-reports --reporter html ./all-blob-reports
| Симптом у пайплайні | Що це насправді | Дія |
|---|---|---|
| Пʼять окремих HTML-звітів, спільної картини немає | Кожен шард — самостійна джоба зі своїм звітом; єдиного шардінг не робить | Публікувати blob як артефакт і зливати окремим кроком, як у джобі report вище |
| Шард 4 завершився за 40 секунд, майже нічого не виконавши | Або файлова гранулярність не дала йому жодного файла, або впав тест залежності — й основні тести не стартували взагалі | Перевірити не кількість падінь, а кількість виконаних тестів; вирівняти розкладку fullyParallel |
| Кожен шард робить свій логін у setup-проєкті | Так і має бути: фільтрація за шардом бере лише основні тести, а залежності підтягуються самі | Нічого не міняти; переконатися, що setup ідемпотентний і не бʼється за спільний акаунт |
| Джоби стартували, а браузери відкриваються з затримкою | Шарди стоять у черзі на сеанс ферми | Рахувати одночасні сеанси (шарди × воркери) проти квоти; додавати шарди тут безглуздо |
Що дивитися і чому:
--reporter=blob— не «ще один формат», а проміжний. Саме він переносить вкладення: й діфи . Якщо кожен шард одразу генерує HTML, вкладення розʼїдуться по пʼятьох артефактах, і зібрати з них цілісний звіт уже не вийде.fail-fast: falseтут принциповий. Дефолтна поведінка матриці скасує решту шардів після першого падіння — і ви отримаєте неповний набірblob-звітів, тобто звіт, у якому «решта тестів» просто відсутня, а не зелена.if: always()на джобі звіту з тієї ж причини. Звіт потрібен саме тоді, коли щось упало; без цього рядка найцікавіший прогін лишиться без зведення.- Майже порожній шард — симптом, а не удача. Сорок секунд роботи означають або розкладку, що лишила машину без файлів, або впалий setup, після якого основні тести не стартували. Тому в перевірці якості пайплайна і сумарну кількість виконаних тестів, а не лише статус джоб.
- Рахуйте одночасні сеанси, а не шарди. Пʼять шардів по чотири воркери — це до двадцяти браузерів одночасно. Якщо ферма дає вісім, дванадцять з них чекають, і збільшення матриці лише подовжить чергу й рахунок за CI-хвилини.
Три стелі прискорення
- Розрізняю три осі: масштабують прогін у межах машини, шарди розкладають між машинами, ферма видає квоту одночасних браузерних сеансів — і рахую їх окремо.
- Знаю, у що впирається кожна (ядра й памʼять агента, кількість доступних CI, ліміт сеансів) і чому непаралелізована частина —
serial-блоки, спільний прогрів, — лишається стелею навіть за нескінченної кількості машин.
Воркер і його життєвий цикл
- Знаю, що воркер у Playwright — процес ОС із власним браузером: спільної памʼяті немає, обмінюватися повідомленнями воркери не вміють, тож передача даних через змінну модуля в паралелі не працює.
- Памʼятаю, що за дефолту паралельно розкладаються файли (усередині файла тести йдуть по черзі), а перевикористовує воркер під наступні файли, щоб не піднімати браузер щоразу.
- Розумію, що після падіння тесту воркер убивають — і що ця чистка стосується процесу з браузером, а не створеного юзера, зайнятого промокоду чи рядка в базі.
- Знаю, що паралельність вимикається тільки обмеженням
workers: 1, і що в CodeceptJS воркер — потік, а не процес, зі стратегіями розкладкиtest,suiteіpool.
Воркерні фікстури
- Можу розвести : тестова зникає одразу після свого тесту, воркерна тримається, доки живий процес.
- Знаю критерій вибору скоупу і порядок відступу: дороге й недоторкане — у воркерний, будь-що змінюване — у тестовий; спершу свіжа фікстура, потім спільна незмінна, і лише в останню чергу спільна змінювана.
- Памʼятаю дві пастки: воркер перевикористовується далі лише поки набір воркерних фікстур збігається, а в самої воркерної фікстури є свій ліміт часу — той самий, що дефолтний для тесту.
fullyParallel і serial
- Розумію, що
fullyParallel: trueпереводить розкладку на окремі тести (це ж вирівнює шарди), і знаю пастку:beforeAllтаafterAllпочинають виконуватися для кожного тесту окремо. - Можу назвати ціну
serialдвома пунктами — пропуск решти групи після першого падіння і переганяння всього блоку — і не плутаю цей режим ізworkers: 1.
Шардінг між машинами CI
- Знаю, що
--shard=x/yробить кожен шард самостійною джобою, і що між машинами розкладаються лише ті одиниці, які взагалі паралелізовні — за дефолту файли. - Можу пояснити, чому нерівні файли дають розкид аж до порожнього шарда, і памʼятаю офіційний рецепт із двох варіантів:
fullyParallelабо дрібні приблизно рівні файли. - Знаю, що спільний звіт збирається окремим кроком — злиттям
blob-звітів, які переносять й діфи . - Розумію взаємодію з проєктами: фільтр шарда бере основні тести, залежності додаються самі, а після падіння setup основні тести не стартують — тож у звіті шарда перевіряю й кількість виконаних тестів, не лише падіння.
Ферма, Selenium Grid і реальні пристрої
- Можу пояснити мотив ферми (матриця ОС і версій браузерів, а не швидкість) і чому переїзд у хмару зводиться до адреси хабу й креденшелів: специфікація вимагає невідрізнимості віддаленого вузла.
- Памʼятаю ємність і форму парку Selenium Grid: стеля сеансів вузла — кількість CPU, близько 1 ГБ на сеанс, Safari — один сеанс; багато маленьких вузлів краще за один великий, а доступ треба закрити фаєрволом.
- Знаю межі емуляції: режим пристрою — наближення першого порядку, найточніший результат дає фізичний апарат, і в
device labобовʼязкова пара слабких.
Ізоляція як передумова
- Можу пояснити, чому ізоляція знімає питання порядку виконання й тим робить паралельність можливою, і чому між тестами програє старту з нуля (відвідані посилання не чистить жоден
afterEach). - Знаю умову
Test Run Warі маркер цього антипатерну: потрібен спільний для всіх ресурс, який хтось переписує, а симптом — зелений поодинці тест, що падає після зміни складу сюїти; лікую , а не симптом.
Квіз
Перед стартом
- Питань: 18
- Поріг «зараховано»: ≥70% правильних відповідей.
- Результат впливає на прогрес; завалені питання підуть у чергу повторення.
- Квіз впливає на компліт теми: тема стає «пройдено», лише коли прочитано теорію І квіз складено на ≥70%.
Питання
Три стелі прискорення прогону — які і чому їх не можна змішувати?


