07 · Інструменти автоматизації
Playwright: автентифікація і повторне використання стану
Зміст
Логін — найнудніший крок у й одночасно найдорожчий. Якщо кожен із двохсот тестів починається з відкриття форми, введення пароля й очікування редиректу, ви платите двічі: хвилинами прогону і надійністю. Хвилини очевидні. Надійність — ні: форма логіну стає спільною точкою , і будь-яке моргання на ній забарвлює червоним усю сюїту, хоч перевіряли ви зовсім інше.
Тому «як не логінитися в кожному тесті» — стандартне питання рівня middle. Слабка відповідь звучить як «зберігаємо кукі у файл». Сильна має дві частини: механізм (логін один раз на прогін і повторне використання збереженого стану) плюс політика (що робити, коли цей стан протухне, і де він лежить, щоб не поїхати в репозиторій разом із живою сесією). Друга частина ламається першою — і ламається в нічному прогоні, коли поруч нікого немає.
Чистий контекст — і чому стан доводиться вкладати
Playwright виконує тести в ізольованих (browser context). Кожен тест дістає свіжий контекст, еквівалентний новому профілю браузера, і ця ізоляція коштує майже нічого. Дока називає причину прямо: така модель покращує відтворюваність і не дає падінням каскадитися одне за одним.
Ціна цієї ізоляції — та сама властивість, що й вигода. Чистий профіль означає профіль розлогінений: немає кукі — немає сесії. Отже, автентифікований стан треба вкладати в тест. Офіційна формула звучить так: тести можуть завантажити наявний автентифікований стан, і це знімає потребу автентифікуватися в кожному тесті та прискорює виконання.
Далі в главі під «станом» мається на увазі (storage state) — файл із кукі та сховищем автентифікованого браузера, який тести завантажують замість логіну. Механіка контекстів як така — у главі про архітектуру, браузери й контексти, а кукі й серверні сесії як явище — у Кукі, сесії та сховище браузера.
Що збережений стан покриває, а що ні
Це найкорисніший факт глави, бо він відповідає на половину питань «чому не працює». Повторне використання автентифікованого стану покриває кукі, localStorage, IndexedDB і автентифікацію на основі (WebAuthn).
А от sessionStorage не покриває — API для його збереження інструмент не надає. Це не дрібниця й не недогляд: sessionStorage розділений і за , і за вкладкою, тож закриття вкладки знищує всі звʼязані з нею дані. Новий контекст його не успадкує за побудовою. Практичний наслідок: якщо застосунок тримає токен саме там, збереженим станом ви його не «залогінете»: відновлювати доводиться вручну, окремим кроком у тесті.
Сам файл робиться одним викликом на контексті:
// Зберегти стан автентифікованого контексту у файл
await context.storageState({ path: 'playwright/.auth/user.json' });
А підставляється — опцією оточення тесту:
// playwright.config.ts — тести стартують уже автентифікованими
use: {
storageState: 'playwright/.auth/user.json',
},
Setup-проєкт: логін один раз на прогін
Проєкт (project) у конфігурації — це логічна група тестів, що виконуються з однаковою конфігурацією. Залежності — список проєктів, які мають відпрацювати перед тестами іншого проєкту; саме так конфігурують глобальну підготовку. З цих двох деталей і складається весь рецепт: логін живе в окремому проєкті, а решта проєктів оголошують його своєю залежністю.
Дока формулює це як рекомендований підхід для тестів без серверного стану: автентифікуватися один раз у setup-проєкті, зберегти стан і бутстрапити ним кожен тест. Умову «без серверного стану» запамʼятайте — до неї ми повернемось окремим підрозділом, бо саме її порушення дає найтиповіший клас падінь.
У конфігурації це виглядає так:
// playwright.config.ts
export default defineConfig({
projects: [
{ name: 'setup', testMatch: /auth\.setup\.ts/ },
{
name: 'chromium',
use: { ...devices['Desktop Chrome'], storageState: 'playwright/.auth/user.json' },
dependencies: ['setup'],
},
],
});
Сам setup — звичайний тест, який нічого не перевіряє, крім факту логіну:
import { test as setup, expect } from '@playwright/test';
const authFile = 'playwright/.auth/user.json';
setup('authenticate', async ({ page }) => {
await page.goto('/login');
await page.getByLabel('Email').fill(process.env.USER_EMAIL!);
await page.getByLabel('Password').fill(process.env.USER_PASSWORD!);
await page.getByRole('button', { name: 'Sign in' }).click();
// Без цієї перевірки збережеться стан гостя, і всі тести впадуть пізніше й незрозуміло
await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
await page.context().storageState({ path: authFile });
});
Перевірка перед збереженням — не формальність. Клік по кнопці не означає, що сесія зʼявилася; чекати треба на стан, а не на дію (механіка автоочікування й web-first перевірок).
Чотири деталі механізму, які варто знати ще до першого прогону:
- Setup-тести видно в репортері, і на них теж записується. Тобто підготовка дебажиться тими самими інструментами, що й тести, — див. дебаг, trace viewer і репортинг.
- Провал залежності не робить тести червоними — він їх не запускає. Якщо тести залежності впали, залежні тести не виконуються взагалі. Зламаний логін дає один червоний setup і двісті не запущених тестів, а не двісті однакових фейлів.
- Кілька залежностей виконуються паралельно — це важливо, якщо крім логіну ви піднімаєте ще щось.
- Фільтрація тягне залежності за собою. Будь-яка фільтрація —
--grep,--shard, вибір за розташуванням,test.only()— вибирає лише основні тести, а тести залежностей підтягуються самі. Тому шард на CI не лишається без логіну. Вимкнути це можна прапорцем--no-deps, і тоді ви лишаєтеся без підготовки свідомо.
чіпляється не до кожного проєкту, а до setup-проєкту окремою властивістю teardown — і відпрацьовує після всіх залежних проєктів.
Дрібниця, на якій спотикаються при першій спробі: конфіг має два рівні — опції самого ранера лежать нагорі, опції оточення тесту — у секції use. Дока попереджає про це прямо: опції ранера — верхнього рівня, у use їх не кладуть. А storageState — саме опція оточення. Ширший розбір конфігурації і — окрема глава.
Кілька ролей: окремі файли стану
Коли ролей більше однієї, але акаунти можна перевикористовувати між тестами, схема не змінюється — множиться лише кількість файлів. У setup-проєкті автентифікуються кілька разів, а далі стан вказують для кожного файла або групи тестів, а не в конфізі глобально:
// admin.spec.ts — увесь файл їде під роллю адміна
test.use({ storageState: 'playwright/.auth/admin.json' });
Якщо ж дві ролі потрібні в одному тесті (класика: адмін щось публікує, користувач це бачить), то це два контексти з різними станами:
const adminContext = await browser.newContext({ storageState: 'playwright/.auth/admin.json' });
const userContext = await browser.newContext({ storageState: 'playwright/.auth/user.json' });
І симетричний випадок, який чомусь завжди забувають: тест незалогіненого користувача окремого проєкту не вимагає. Стан скидається на рівні файла:
// guest.spec.ts — гостьові сценарії: скидаємо стан, який поставив проєкт
test.use({ storageState: { cookies: [], origins: [] } });
Спільний акаунт чи акаунт на воркер
Тепер повернімось до умови, яку легко проґавити. Один файл стану на всю сюїту дока рекомендує для тестів, які не змінюють серверний стан. Для тестів, які його змінюють, рекомендація інша: кожен паралельний автентифікується один раз під власним акаунтом, усі тести цього воркера перевикористовують той самий стан, а розрізняють воркери за testInfo.parallelIndex. Акаунтів, відповідно, потрібно стільки, скільки воркерів.
Причина — в моделі паралельності. Воркер (worker) — це процес операційної системи зі своїм браузером; спільного стану й глобальних змінних між воркерами немає, а одиниця паралельності за замовчуванням — файл, а не тест. Отже, два тести на одному акаунті цілком легально їдуть одночасно в різних процесах: один видаляє сутність, поки другий її редагує. Симптом класичний — «поодинці зелені, разом червоні», і виглядає він як , хоча це гонка за даними (див. боротьбу з флаком засобами інструмента і главу про воркери й шардінг (sharding)).
Технічно «раз на воркер» — це воркерна фікстура (fixture): тестова фікстура прибирається після кожного тесту, а воркерна — лише коли завершується сам воркер. Дорога автентифікація — типовий кандидат саме на воркерний : «акаунт на воркер» і реалізують воркерною фікстурою. Є й нюанс: воркер перевикористовується для наступних файлів лише поки воркерні фікстури збігаються, тож роль, вшита у воркерну фікстуру, впливає на розкладку файлів по воркерах.
Стратегія та ізоляції загалом — не тема цієї глави: канон живе в розділі «Автоматизація: стратегія». Тут важливо лише те, що акаунт — це теж тестові дані, і спільний акаунт у тестах, які правлять стан, — червоний на рев'ю.
Протухлий стан
Головне про протухання формулюється одним реченням: інструмент за ним не стежить. Дока прямо кладе це на автора тестів — збережений стан треба видалити, коли він протух. Ніякої автоматичної перевірки «а ця сесія ще жива?» перед прогоном немає, і файл із мертвою кукою виглядає точно так само, як із живою.
Звідси два штатні рішення. Перше: якщо стан між прогонами не потрібен, писати його в теку виводу проєкту (testProject.outputDir), яка автоматично чиститься перед кожним прогоном — тоді логін просто відбувається щоразу заново. Друге стосується локальної роботи: UI-режим за замовчуванням не виконує setup-проєкт (щоб не гальмувати), тож автентифікацію там оновлюють, час від часу запускаючи auth.setup.ts руками.
Чому стан протухає взагалі — механіка з боку сервера, і вона не інструментальна. Сесійна кукі не має ні Expires, ні Max-Age і зникає із браузера, а постійна живе до вказаного моменту; сервер, який видав сесію, може обірвати її раніше з власних причин. Розбір цієї частини — у главі про кукі й сесії та автентифікацію й авторизацію.
Цікаво порівняти з іншим підходом: у CodeceptJS та сама задача закрита плагіном — сесія зберігається в памʼять або файл, а протухлу сесію плагін перелогінює сам. Це архітектурний вибір, а не перевага: Playwright віддає політику протухання вам і не робить нічого за вашою спиною, CodeceptJS бере її на себе разом із наслідками (див. CodeceptJS: актор, хелпери і сценарний стиль).
Логін через API замість UI
Проклацувати форму логіну в setup — найпростіший, але не єдиний шлях. Контекст API-запитів (APIRequestContext) вміє надсилати будь-які HTTP(S)-запити мережею, і офіційна дока сама перелічує три його застосування: тестувати серверне API, готувати серверний стан перед відкриттям застосунку в тесті й перевіряти серверні пост-умови після дій у браузері.
Ключове твердження, на якому тримається весь прийом: стан сховища взаємозамінний між браузерним контекстом і контекстом API-запитів. Тобто офіційно підтримуваний шлях — залогінитися , а тоді створити контекст, у якому кукі вже є. У доці автентифікації це той самий рецепт іншими словами: надіслати запит через APIRequestContext і зберегти автентифікований стан, як завжди.
import { test as setup } from '@playwright/test';
setup('authenticate via API', async ({ request }) => {
await request.post('/api/session', {
data: { email: process.env.USER_EMAIL, password: process.env.USER_PASSWORD },
});
await request.storageState({ path: 'playwright/.auth/user.json' });
});
Виграш очевидний: логін перестає залежати від верстки форми, від на стенді, від редиректів SSO. Два нюанси, які варто знати:
- Контексти API-запитів бувають двох типів. Той, що доступний через
browserContext.requestіpage.request, заповнює заголовокCookieз браузерного контексту й сам оновлює кукі браузера, коли у відповіді єSet-Cookie. Окремо створений екземпляр має власне ізольоване сховище кукі. Різниця вирішує, чи «побачить» браузер результат вашого запиту. - Фікстура
requestуспадковує конфіг —baseURL,extraHTTPHeaders, а також ; під капотом це виклик створення нового контексту запитів, який можна зробити й вручну, якщо потрібен повний контроль.
І висновок, який із цього прийому не випливає: обхід UI-логіну не скасовує самого UI-логіну як функціональності продукту. Хоч один тест мусить проходити форму по-справжньому — інакше вхідна точка, через яку в застосунок заходять усі користувачі, не покрита нічим. Ширше про роботу з мережею й підготовку даних — у главі про перехоплення, мокання й очікування відповідей; API як предмет тестування — окремий розділ «API-тестування».
Що не можна класти в репозиторій
Файл стану — це не конфігурація. Це живий секрет: у ньому лежать чутливі кукі й заголовки, якими можна вдати вас або ваш тестовий акаунт. Дока не радить «бути обережним», а наполегливо не радить такі файли в приватні чи публічні репозиторії. Слово «приватні» тут несе половину змісту: приватність репозиторію не перетворює живу сесію на конфіг. Штатна практика — окрема тека playwright/.auth, додана до .gitignore.
Далі — три речі, які легко зробити неправильно.
.gitignore не лікує вже закомічене. Його дока формулює межу дослівно: файли, які Git уже відслідковує, правило не зачіпає. Щоб перестати відслідковувати доданий файл, потрібен git rm --cached — прибрати його з індексу, і лише тоді патерн у .gitignore не дасть файлу вернутися в наступні коміти.
Прибрати з індексу — не те саме, що відкликати доступ. Секрет, який уже потрапив у історію (або в лог прогону), скомпрометований. Припис GitHub для секрету, що засвітився в лозі прогону, прямий: видалити лог і ротувати секрет; регулярна ротація ще й скорочує вікно, у якому скомпрометований секрет ще діє. Практичний порядок дій — спершу ротація (тільки вона відкликає доступ), потім прибирання з місця, де секрет засвітився, і лише потім думки про історію.
Креденшели беруться із середовища, а не з коду. Лакмусовий тест на це є в дванадцятифакторній методології: чи можна зробити кодову базу опенсорсною будь-якої миті, не скомпрометувавши жодного креденшела. Конфіг-файл поза контролем версій — велике поліпшення проти констант у коді, але слабкість лишається: такий файл легко випадково закомітити.
Два факти про CI, які ламають інтуїцію й прямо стосуються цієї теми. По-перше, будь-хто з правом запису в репозиторій має право читати всі його секрети — тож «тестовий акаунт із доступом до всього» неявно роздає цей доступ усій команді, що може пушити. По-друге, за винятком GITHUB_TOKEN, секрети не передаються ранеру, коли запущений із форку: e2e на PR із форку падає на автентифікації не через баг , а через документовану поведінку. Механіка секретів, маскування і його межі — канон розділу «Git і CI/CD».
Типові помилки
- «Логін проходить, а наступна сторінка бачить гостя». Виглядає як баг застосунку, а насправді токен живе в
sessionStorage: API для його збереження інструмент не надає, та й саме сховище привʼязане до вкладки. Стан треба відновлювати вручну, окремим кроком у тесті. - «У UI-режимі все розлогінене, а в консолі зелено». Виглядає як зламаний конфіг, а насправді UI-режим за замовчуванням не запускає setup-проєкт. Лікується запуском
auth.setup.tsруками. - «Нічний прогін червоний увесь, локально зелено». Виглядає як проблема оточення, а насправді протух файл стану, і ніхто про це не дізнався: за протуханням інструмент не стежить.
- «Тести падають тільки в паралелі». Виглядає як , а насправді порушена умова: спільний акаунт рекомендований для тестів без серверного стану, а ваші його змінюють. Лікування — акаунт на воркер, не .
- «Шард на CI упав, бо в ньому не було логіну». Виглядає як гонка залежностей, а насправді залежності підтягуються за будь-якої фільтрації, включно з
--shard. Якщо логіну справді не було — або прогін ішов із--no-deps, або setup узагалі не оголошений залежністю в конфізі. - «Файл стану закомітили, потім додали в
.gitignore— полагодили». Виглядає як полагоджено, а насправді правило не чіпає вже відслідковувані файли, а секрет усе одно лежить в історії. Працює лише ротація. - «Тест на форму логіну падає, бо вже залогінений». Виглядає як баг тесту, а насправді проєкт роздає стан усім своїм тестам. Для гостьових сценаріїв стан скидають на рівні файла.
Підсумок
- Чистий контекст — це розлогінений контекст. Ізоляція, яка дає відтворюваність, і є причиною, чому стан доводиться вкладати в тест явно.
- Збережений стан покриває кукі,
localStorage, IndexedDB і passkey — і не покриваєsessionStorage. Це перше, що перевіряють, коли «стан підхопився, а застосунок вважає користувача гостем». - «Логін раз на прогін» — це setup-проєкт, оголошений залежністю, а не
beforeAllу кожному файлі. Залежності підтягуються навіть при фільтрації й , а провал підготовки не запускає залежні тести. - Один акаунт на всіх легальний лише для тестів, які не змінюють серверний стан. Щойно тести правлять дані — акаунт на кожен воркер, бо воркер це окремий процес без спільного стану.
- Протухання — ваша відповідальність, а файл стану — секрет. Політика («де лежить, коли перегенерується, що робити при витоку») — така сама частина рішення, як і сам
storageState.
Можливі питання
- «Як ви не логінитеся в кожному тесті?» — інтерв'юер чекає не назви API, а схеми: логін один раз у setup-проєкті, збереження стану у файл, підстановка стану проєктам через залежності. Згадка про протухання одразу відрізняє того, хто це запускав, від того, хто про це читав.
- «Що потрапляє в storageState, а що ні?» — перевіряють конкретику. Правильна відповідь називає кукі,
localStorage, IndexedDB і passkey — і окремо те, щоsessionStorageне зберігається, тому застосунки, які тримають токен там, вимагають іншого рішення. - «У вас тести під двома ролями — як організуєте?» — очікують «кілька файлів стану з одного setup-проєкту, роль вибирається на рівні файла або групи тестів», а для сценарію «адмін і користувач в одному тесті» — два контексти з різними станами.
- «Тести на спільному акаунті падають у паралелі. Що робите?» — дивляться, чи бачите ви за симптомом гонку, а не флак. Сильна відповідь називає межу застосовності спільного акаунта й перехід на акаунт-на-воркер, а не збільшення кількості ретраїв.
- «Де у вас лежать креденшели й файл стану?» — це питання про гігієну. Слабка відповідь — «у репозиторії, він же приватний». Очікують: пароль зі змінних середовища або секретів CI, стан у теці під
.gitignore, при витоку — ротація, бо.gitignoreвже закомічене не лікує. - «Логінитеся через UI чи через API?» — правильна відповідь ставить обидва варіанти на місце: у setup швидше й стабільніше через API (стан взаємозамінний між API- і браузерним контекстом), але сам UI-флоу логіну лишається під тестом хоча б в одному сценарії.
Джерела
Чистий контекст — і чому стан доводиться вкладати
- Playwright — Authentication — ізоляція контекстів як причина відтворюваності й завантаження готового стану замість логіну в кожному тесті.
- Playwright — головна сторінка — свіжий контекст на тест як еквівалент нового профілю браузера з ізоляцією майже без накладних витрат.
Що збережений стан покриває, а що ні
- Playwright — Authentication — що саме покриває повторне використання стану і чому
sessionStorageдо нього не входить. - MDN — Web Storage API —
sessionStorageрозділений за вкладкою і походженням, тож дані гинуть із вкладкою.
Setup-проєкт: логін один раз на прогін
- Playwright — Projects — проєкт як логічна група з однаковою конфігурацією, залежності проєктів, поведінка при провалі залежності,
teardownі те, що фільтрація підтягує залежності. - Playwright — Authentication — setup-проєкт як рекомендований підхід для тестів без серверного стану й умова застосовності.
- Playwright — Test configuration — два рівні конфігурації: опції ранера проти опцій оточення в секції
use.
Кілька ролей: окремі файли стану
- Playwright — Authentication — кілька автентифікацій в одному setup-проєкті, вибір стану на рівні файла чи групи тестів і скидання стану для гостьових сценаріїв.
- Playwright — Test Isolation (browser contexts) — кілька контекстів із різними станами в одному тесті.
Спільний акаунт чи акаунт на воркер
- Playwright — Authentication — окремий акаунт на воркер для тестів, що змінюють серверний стан, автентифікація раз на воркер і розрізнення за
parallelIndex. - Playwright — Parallelism — воркер як процес ОС без спільного стану й файл як одиниця паралельності за замовчуванням.
- Playwright — Test fixtures — : тестова прибирається після тесту, воркерна — з завершенням воркера.
- Playwright — Authentication — протухання як обовʼязок автора тестів, тека виводу проєкту як спосіб не тримати стан між прогонами, UI-режим без setup-проєкту.
- CodeceptJS — Plugins — сесія у файлі чи памʼяті й автоматичний перелогін після протухання.
- MDN — Using HTTP cookies — сесійна кукі без
Expires/Max-Ageпроти постійної з терміном життя.
- Playwright — API testing — три застосування контексту API-запитів, взаємозамінність стану між браузерним і API-контекстом, два типи контексту й успадкування опцій фікстурою
request. - Playwright — Authentication — автентифікація запитом до API зі збереженням стану у файл, як після UI-логіну.
Що не можна класти в репозиторій
- Playwright — Authentication — файл стану містить чутливі кукі й заголовки; наполеглива порада не комітити його й тримати в теці з
.gitignore. - Git Reference — gitignore — правило не зачіпає вже відслідковувані файли;
git rm --cachedяк спосіб прибрати файл з індексу. - GitHub Docs — Secure use reference (GitHub Actions) — видалити лог і ротувати секрет після витоку; ротація скорочує вікно дії; право запису дає право читати всі секрети.
- GitHub Docs — Using secrets in GitHub Actions — секрети не передаються ранеру для воркфлоу з форку, крім
GITHUB_TOKEN. - The Twelve-Factor App — III. Config — лакмусовий тест на відкриття кодової бази й межа конфіг-файлів поза контролем версій.
- Playwright — Authentication —
sessionStorageпоза станом, UI-режим без setup-проєкту, протухання як обовʼязок автора, умова спільного акаунта, скидання стану на рівні файла. - Playwright — Projects — фільтрація (включно з
--shard) підтягує тести залежностей;--no-depsце вимикає. - MDN — Web Storage API — привʼязка
sessionStorageдо вкладки й походження. - Git Reference — gitignore — межа правила для вже відслідковуваних файлів.
- Playwright — Authentication — склад збереженого стану, кілька ролей і файлів стану, умова спільного акаунта проти акаунта на воркер, гігієна файлу стану, логін через API.
Відео до теми
Чому тести Playwright стартують розлогіненими і чому це не можна полагодити одним beforeAll?
Бо кожен тест отримує власний (browser context), а це фактично свіжий профіль: ні кукі, ні сесії в ньому немає. Така ізоляція — не побічний ефект, а свідоме рішення: вона робить прогони відтворюваними й не дає падінню одного тесту тягнути за собою решту. Ціна рівно та сама властивість: чистий профіль завжди означає профіль без сесії, тому автентифікований стан треба класти в тест самому. beforeAll цю проблему не закриває, бо працює в межах одного файла й одного процесу: двадцять файлів — двадцять логінів, а в паралельному прогоні кожен повторить їх ще раз у себе. Схема, яка масштабується, інша: логін відбувається один раз на прогін в окремому проєкті, результат зберігається у файл, а тести читають його на старті.
Що таке збережений стан автентифікації (storage state) і як ним користуються?
Це файл, у який Playwright вивантажує кукі та сховища автентифікованого контексту, щоб інші тести піднялися вже залогіненими замість проходити форму. Записується він одним викликом на контексті — context.storageState() з шляхом до файла, зазвичай десь у теці playwright/.auth. Підставляється як опція оточення тесту: у конфізі use: { storageState: 'playwright/.auth/user.json' } для проєкту, або через test.use(...) для окремого файла чи групи. Вигода двостороння: прогін коротшає, а форма логіну перестає бути спільною точкою , через яку падає вся . Важливо тримати в голові, що це не конфігурація, а живий секрет: у файлі — чутливі кукі й заголовки, з якими можна видати себе за тестовий акаунт.
Що саме потрапляє у збережений стан, а що ні?
стану покриває кукі, localStorage, IndexedDB і автентифікацію через (WebAuthn). Не покриває sessionStorage — API для його збереження інструмент просто не дає, і це принципово, а не недогляд. sessionStorage розділений і за , і за вкладкою, тож його вміст гине разом із вкладкою; новий контекст успадкувати його не може за побудовою. Практичний висновок: якщо застосунок тримає токен саме там, файлом стану ви його не залогінете — сховище доводиться наповнювати вручну, для чого підходить ініціалізаційний скрипт контексту. На співбесіді це питання на конкретику: сильний кандидат перелічує чотири покриті речі й одразу окремо називає sessionStorage як виняток.
Файл стану підхопився, але застосунок вважає користувача гостем. Де шукати причину?
Перший підозрюваний — місце, де живе токен: якщо це sessionStorage, стан його не переносить, і кількість перезапусків тут не допоможе. Другий — сам файл: у ньому могла зберегтися сесія гостя, якщо setup клікнув «Sign in» і зберіг стан, не дочекавшись підтвердження, що логін відбувся. Третій — протухання: мертва кука виглядає у файлі точно так само, як жива, і за терміном її життя ніхто не стежить. Четвертий — походження: стан привʼязаний до домену, і файл, знятий на одному стенді, на іншому базовому URL нічого не автентифікує. Порядок перевірки дешевий: спершу подивитися в самому файлі, чи є там очікувана кука, потім прогнати логін-setup руками й порівняти результат.
Опиши схему «логінимося один раз на прогін» у Playwright.
Схема тримається на двох поняттях конфігурації. Проєкт (project) — це логічна група тестів з однаковою конфігурацією. Залежності (dependencies) — перелік проєктів, які мусять відпрацювати перед тестами іншого проєкту; саме так і роблять глобальну підготовку. Далі рецепт складається сам: логін живе в окремому проєкті (наприклад, setup з testMatch: /auth\.setup\.ts/), решта проєктів оголошують його в dependencies і отримують storageState у своїй секції use. Setup при цьому — теж тест, просто з єдиною перевіркою — фактом логіну; наприкінці він записує стан у файл. Умова застосовності, яку варто озвучити одразу: такий один спільний стан рекомендований для тестів, що не змінюють серверний стан — саме її порушення дає найтиповіші падіння в паралелі.
Навіщо в setup-тесті перевірка після натискання кнопки входу?
Бо клік — це дія, а сесія — це стан, і одне з другого автоматично не випливає. Якщо зберегти стан одразу після кліку, у файл цілком може поїхати профіль гостя: запит ще в дорозі, редирект не відбувся, кука не поставилася. Найгірше в цьому те, коли ви про це дізнаєтеся: setup буде зелений, а впадуть двісті тестів пізніше й з приводу, який зовні ніяк не стосується логіну. Тому перед storageState() ставлять перевірку на ознаку залогіненості — видимий елемент дашборда, імʼя користувача в хедері, — і саме web-first перевірку, яка сама дочекається стану. Це той рідкісний випадок, коли у підготовці не зайвий, а є її основною частиною.
Setup-проєкт упав. Скільки червоних тестів ви побачите у звіті?
Один. Провал залежності не забарвлює залежні тести червоним — він їх узагалі не запускає. Тобто зламаний логін дає один червоний setup і купу не запущених тестів, а не двісті однакових фейлів, у яких доводиться шукати спільну причину. Це зручно для : за формою звіту одразу видно, що проблема в підготовці, а не в продукті. Корисний супровідний факт: setup-тести видно в репортері як звичайні, і на них записується — отже, дебажити логін можна тим самим trace viewer, а не через console.log. Ще одна деталь механізму: якщо залежностей кілька, вони виконуються паралельно, тож логін і, скажімо, підняття даних не будуть чекати одне одного.
Ви ганяєте сюїту шардами на CI. Чи треба окремо дбати, щоб логін відпрацював у кожному шарді?
Не треба — фільтрація тягне залежності за собою. Будь-яке звуження набору (--grep, --shard, вибір за розташуванням, test.only()) вибирає лише основні тести, а тести залежностей підтягуються самі, тож шард без підготовки не лишається. Тому «шард упав, бо в ньому не було логіну» — не пояснення, а симптом: далі треба дивитися, чи не запускали прогін із --no-deps (він якраз це вимикає) і чи оголошений setup залежністю саме в тому проєкті, який ганяєте. Другий частий варіант — файл стану пишеться в теку, яка чиститься не там, де потім читається (наприклад, різні в різних job без передачі артефакту). Тобто відповідь на це питання перевіряє, чи ви розрізняєте поведінку й топологію свого .
У конфізі не працює storageState. Що найчастіше зроблено не так?
Найчастіше він покладений не на той рівень. У playwright.config.ts є два різні набори: опції самого ранера — на верхньому рівні, а опції оточення тесту — в секції use. storageState належить до других, і в корені файла поруч із workers чи retries він просто нічого не робить. Симетрична помилка навпаки — покласти опцію ранера в use і чекати, що вона подіє; дока про цю пару рівнів попереджає прямо. Друга типова причина — стан задано в конфізі глобально, тоді як він потрібен точково: тоді правильне місце — test.use(...) у файлі. Перевіряється це за пів хвилини: подивитися, чи проєкт справді бачить опцію, і чи не перебиває її файловий test.use.
У вас тести під двома ролями. Як це організувати?
Якщо акаунти дозволено ділити між тестами, схему міняти не треба — додаються тільки файли стану. У setup-проєкті автентифікуєтесь кілька разів (адмін, звичайний користувач, менеджер) і зберігаєте кожного в свій файл. Далі стан вибираєте не глобально в конфізі, а на рівні файла або групи: test.use({ storageState: 'playwright/.auth/admin.json' }) на початку admin.spec.ts відправляє весь файл під роллю адміна. Плодити окремий проєкт на кожну роль не потрібно — це роздуває матрицю прогону без жодної вигоди. Умова та сама: спільні акаунти легальні, поки тести не правлять серверний стан.
Потрібен сценарій «адмін публікує — користувач це бачить» в одному тесті. Як його зібрати?
Двома контекстами з різними станами в межах одного тесту: один створюєте з файлом адміна, другий — з файлом користувача, і кожен дає свою сторінку. Це дешево, бо контекст — не процес: один браузер спокійно тримає кілька ізольованих профілів одночасно. Файловий test.use тут не підходить принципово — він задає стан для всього оточення тесту, а вам потрібні дві різні сесії паралельно. Практична деталь: обидва контексти варто закривати явно в кінці тесту, інакше вони живуть до кінця воркера. І типова помилка сценарію — перевіряти результат у користувача одразу після дії адміна: чекати треба на появу стану на другій сторінці, а не на завершення кліку на першій.
Проєкт роздає storageState усім тестам. Як тепер тестувати саму форму логіну?
Скинути стан на рівні файла: test.use({ storageState: { cookies: [], origins: [] } }) на початку guest.spec.ts — і тести цього файла бачать застосунок очима незалогіненого користувача. Окремий проєкт «без стану» для цього не потрібен, хоча його часто заводять із цієї причини. Питання неочевидне саме через симетрію: усі памʼятають, як стан підкласти, і забувають, що його так само треба вміти прибрати. Практично це важливо для трьох груп сценаріїв: логін і реєстрація, редиректи на форму входу з захищених сторінок, і публічна частина продукту. І ще одне: обхід UI-логіну в setup не скасовує потреби мати хоча б один тест, який реально проходить форму — інакше найпопулярніша вхідна точка продукту не покрита нічим.
Коли один спільний акаунт на всю сюїту — помилка?
Щойно тести починають змінювати серверний стан. Для тестів, які лише читають, один файл стану на прогін — рекомендований підхід. Для тестів, які створюють, редагують і видаляють дані, рекомендація інша: у кожного паралельного воркера — власний акаунт, під яким він логіниться один раз і роздає цей стан усім своїм тестам; котрому воркеру який акаунт — вирішує testInfo.parallelIndex. Отже, акаунтів потрібно рівно стільки, скільки воркерів, і це стає частиною вимог до тестового середовища, а не деталлю коду. На ревʼю спільний акаунт у тестах, що правлять дані, — червоний : акаунт це теж , і ділити його між паралельними процесами так само небезпечно, як ділити один запис у базі.
Тести зелені поодинці й червоні в паралелі. Ваш перший діагноз?
Це схоже на , але поводиться як гонка за даними, і саме так її треба перевіряти. Причина в моделі паралельності: воркер (worker) — це окремий процес операційної системи зі своїм браузером, спільних змінних між воркерами немає, а одиницею паралельності за замовчуванням є файл, а не тест. Тому ранер має повне право виконувати два тести на одному акаунті одночасно у двох процесах — і поки перший видаляє сутність, другий саме її редагує. Перевірка дешева — прогнати сюїту в один воркер: якщо зелено, гіпотеза «гонка» підтверджена, і далі шукати треба спільний ресурс, а не -. Лікування — акаунт на воркер і власні дані на тест, а не збільшення retries: тут ховають симптом і роблять сюїту довшою й брехливішою.
Чому «логін раз на воркер» реалізують воркерною фікстурою, а не тестовою?
Через різні життя. Тестова (fixture) прибирається після кожного тесту, воркерна — лише коли завершується сам воркер, тож дорога операція в ній виконується один раз на процес і обслуговує всі його тести. Автентифікація — типовий кандидат саме на такий скоуп: логін коштує секунди, а потрібен один. Є нюанс, про який згадують рідше: наступний файл воркер підхопить лише тоді, коли його воркерні фікстури ті самі, — тому роль, зашита у воркерний скоуп, змінює розкладку файлів по воркерах. Практично це означає, що велика кількість різних ролей у воркерному скоупі може дати більше перезапусків воркерів, ніж ви очікували, — тому ролі частіше роздають файлами стану, а воркерний скоуп лишають для «акаунта на процес».
Хто відповідає за протухання збереженого стану?
Автор тестів — інструмент за цим не стежить. Перед прогоном ніхто не питає сервер, чи сесія ще дійсна, а файл із мертвою кукою зовні не відрізняється від робочого, тому симптом виїжджає у вигляді «нічний прогін увесь червоний, локально зелено». Штатних рішень два. Перше: коли тримати стан між прогонами нема сенсу, класти файл у теку виводу проєкту (outputDir) — ранер чистить її сам, і кожен прогін починається зі свіжого логіну. Друге — про локальну роботу: UI-режим заради швидкості пропускає setup-проєкт за замовчуванням, тож протухлу автентифікацію там поновлюють ручним запуском auth.setup.ts. Чому сесія вмирає взагалі — вже не інструментальна механіка: сесійна кука зникає із браузера, постійна живе до вказаного моменту, а сервер може обірвати сесію раніше з власних причин.
Логінитися в setup через UI чи через API? Обґрунтуй.
У setup зазвичай через API, а UI-логін лишити під тестом хоча б в одному сценарії — і це не компроміс, а два різні завдання. API-шлях офіційно підтримуваний: стан сховища взаємозамінний між контекстом API-запитів (APIRequestContext) і браузерним контекстом, тож можна залогінитися запитом і зберегти стан так само, як після кліків. Вигода в тому, що підготовка перестає залежати від верстки форми, на стенді й редиректів SSO — тобто найкрихкіший крок зникає з критичного шляху всієї сюїти. Той самий контекст запитів корисний і поза логіном: ним готують серверний стан перед відкриттям застосунку й перевіряють серверні пост-умови після дій у браузері. Але висновку «UI-логін нам більше не потрібен» звідси не випливає: обхід форми в підготовці не скасовує самої форми як функціональності продукту, через яку в застосунок заходять усі користувачі.
Чим page.request відрізняється від окремо створеного контексту API-запитів?
Сховищем кукі. Контекст, доступний через browserContext.request і page.request, звʼязаний із браузером: він підставляє заголовок Cookie з браузерного контексту й сам оновлює кукі браузера, коли у відповіді приходить Set-Cookie. Створений окремо екземпляр натомість тримає кукі у власному ізольованому сховищі, тож його логін браузер не побачить, поки ви не перекладете стан явно. Саме ця різниця вирішує, чи подіє ваш API-запит на сторінку, яку ви потім відкриваєте, — і саме на ній ловляться «залогінився запитом, а браузер усе одно гість». Друга корисна деталь: фікстура request успадковує конфіг — baseURL, extraHTTPHeaders, , — бо під капотом це створення нового контексту запитів, яке за потреби можна зробити руками й отримати повний контроль.
Де у вас лежать креденшели й файл стану? І що робити, якщо стан уже поїхав у репозиторій?
Пароль — зі змінних середовища або секретів CI, файл стану — в окремій теці під .gitignore (звична конвенція — playwright/.auth). Аргумент «репозиторій же приватний» не працює: приватність не перетворює живу сесію на конфігурацію, і рекомендація не такі файли стосується приватних репозиторіїв так само, як публічних. Якщо файл уже закомічений, послідовність важлива. Спершу ротація креденшелів — тільки вона відкликає доступ; секрет, який побував в історії Git або в логах прогону, вважається скомпрометованим. Потім : додати патерн у .gitignore недостатньо, бо правило не діє на вже відслідковувані файли, — потрібен git rm --cached, щоб вийняти файл з індексу. І лише після цього мають сенс розмови про переписування історії. Хороший орієнтир на майбутнє — лакмусовий тест дванадцятифакторної методології: чи можна відкрити кодову базу будь-якої миті, не скомпрометувавши жодного креденшела; конфіг-файл поза контролем версій цей тест проходить слабко, бо його легко закомітити випадково.
Які факти про секрети в CI ламають інтуїцію саме в цій темі?
Два, і обидва прямо стосуються тестового акаунта. По-перше, право запису в репозиторій дає право читати всі його секрети — тобто «тестовий акаунт з доступом до всього» неявно роздає цей доступ усім, хто може пушити, і це аргумент за окремі вузькоправні акаунти для автотестів. По-друге, з форку ранер секретів не отримує — єдиний виняток GITHUB_TOKEN; тож коли e2e на такому PR валиться на автентифікації, це документована поведінка, а не баг пайплайна, і правильна реакція тут не «полагодити тест», а вибрати політику запуску для форків. Обидва факти корисно озвучувати саме в парі з файлом стану: перший визначає, який акаунт ви взагалі маєте право використовувати, другий — де ваша схема логіну гарантовано не працюватиме.
Три кейси, у яких із теми «логін один раз на прогін» виростає робочий код: збірка setup-проєкту від конфігу до вмісту файла стану, три ролі (адмін, користувач, гість) в одній без множення проєктів і акаунт на кожен разом із таблицею для падінь, які тільки виглядають як . Скрізь — що дивитися і чому.
Кейс 1. Setup-проєкт: від конфігу до вмісту файла стану
Сюїта на двісті тестів, кожен починається з форми входу. Мета — щоб форму проходили один раз за прогін, а всі інші тести стартували вже всередині застосунку. Розкладка мінімальна: один файл підготовки, одна тека під стан, дві правки в конфізі.
playwright.config.ts
playwright/.auth/ # сюди пишеться стан, тека — у .gitignore
tests/
auth.setup.ts # єдиний тест, який реально логіниться
dashboard.spec.ts
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
const AUTH_FILE = 'playwright/.auth/user.json';
export default defineConfig({
use: { baseURL: process.env.BASE_URL ?? 'https://staging.example.com' },
projects: [
{ name: 'setup', testMatch: /auth\.setup\.ts/ },
{
name: 'chromium',
use: { ...devices['Desktop Chrome'], storageState: AUTH_FILE },
dependencies: ['setup'],
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'], storageState: AUTH_FILE },
dependencies: ['setup'],
},
],
});
Сама підготовка — тест, у якого одна перевірка, і вона ж : поки не видно ознаки залогіненості, зберігати нема чого.
// tests/auth.setup.ts
import { test as setup, expect } from '@playwright/test';
const AUTH_FILE = 'playwright/.auth/user.json';
setup('authenticate', async ({ page }) => {
await page.goto('/login');
await page.getByLabel('Email').fill(process.env.QA_EMAIL!);
await page.getByLabel('Password').fill(process.env.QA_PASSWORD!);
await page.getByRole('button', { name: 'Sign in' }).click();
// оракул підготовки: сесія є, тільки якщо застосунок показав приватний екран
await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
await page.context().storageState({ path: AUTH_FILE });
});
Коли форма логіну на стенді закрита або веде через SSO, той самий файл знімають без браузера:
// tests/auth.setup.ts — варіант через API
setup('authenticate via API', async ({ request }) => {
const response = await request.post('/api/session', {
data: { email: process.env.QA_EMAIL, password: process.env.QA_PASSWORD },
});
expect(response.ok(), 'логін через API не пройшов — далі збережеться порожня сесія').toBeTruthy();
await request.storageState({ path: AUTH_FILE });
});
Що дивитися і чому:
-
Загляньте у сам файл — це звичайний JSON, і він відповідає на половину питань. Найшвидший спосіб зрозуміти, що саме ви зберегли:
jq '.cookies[] | {name, domain, expires}' playwright/.auth/user.json{ "name": "session", "domain": "staging.example.com", "expires": 1785500000 } { "name": "csrf", "domain": "staging.example.com", "expires": -1 }Порожній масив
cookiesозначає, що збереглася сесія гостя.expires: -1— сесійна кука без терміну життя. А домен, який не збігається з вашимbaseURL, пояснює «стан підхопився, а застосунок усе одно просить увійти»: файл зі стейджа на іншому стенді не автентифікує нічого. -
перед
storageState()— не формальність, а місце, де падіння коштує найдешевше. Без нього зелена підготовка віддає файл із гостем, і біль приїжджає через двісті тестів, у симптомі, який зовні ніяк не стосується логіну. -
Форма звіту — теж діагноз. Провал підготовки залежні тести не запускає, тож зламаний логін дає один червоний setup і купу пропущених. Побачили двісті однакових фейлів — шукайте причину не в логіні.
-
storageState— опція оточення тесту, її місце в секціїuse. У корені конфігу поруч ізworkersіretriesвона мовчки нічого не робить: там живуть опції самого . -
Тека потрапляє в
.gitignoreдо першого прогону, а не після. Порядок тут односторонній: файл, який уже засвітився в історії, лікується ротацією креденшелів, а не правилом ігнорування.playwright/.auth/
Кейс 2. Три ролі в одній сюїті: адмін, користувач, гість
Продукт має адмінку, кабінет користувача й публічну частину. Спокуса — завести три проєкти в конфізі; правильна відповідь — три файли стану з одного проєкту підготовки, а роль вибирати там, де вона потрібна.
// tests/auth.setup.ts — кілька автентифікацій в одному проєкті підготовки
import { test as setup, expect } from '@playwright/test';
const roles = [
{ name: 'admin', email: process.env.ADMIN_EMAIL!, marker: 'Admin panel' },
{ name: 'user', email: process.env.USER_EMAIL!, marker: 'My projects' },
];
for (const role of roles) {
setup(`authenticate as ${role.name}`, async ({ page }) => {
await page.goto('/login');
await page.getByLabel('Email').fill(role.email);
await page.getByLabel('Password').fill(process.env.QA_PASSWORD!);
await page.getByRole('button', { name: 'Sign in' }).click();
await expect(page.getByRole('heading', { name: role.marker })).toBeVisible();
await page.context().storageState({ path: `playwright/.auth/${role.name}.json` });
});
}
Далі роль оголошується на рівні файла — один рядок нагорі спеки:
// tests/admin/users.spec.ts
test.use({ storageState: 'playwright/.auth/admin.json' });
// tests/guest/login.spec.ts — публічна частина й сама форма входу
test.use({ storageState: { cookies: [], origins: [] } });
Сценарій, де ролі потрібні одночасно, живе не в опціях, а в коді: два контексти в одному браузері.
// tests/publish.spec.ts
test('пост, опублікований адміном, зʼявляється у стрічці користувача', async ({ browser }) => {
const adminContext = await browser.newContext({ storageState: 'playwright/.auth/admin.json' });
const userContext = await browser.newContext({ storageState: 'playwright/.auth/user.json' });
const adminPage = await adminContext.newPage();
const userPage = await userContext.newPage();
const title = `Реліз ${Date.now()}`;
await adminPage.goto('/admin/posts/new');
await adminPage.getByLabel('Title').fill(title);
await adminPage.getByRole('button', { name: 'Publish' }).click();
await userPage.goto('/feed');
// чекаємо на появу стану в іншій сесії, а не на завершення кліку в першій
await expect(userPage.getByRole('link', { name: title })).toBeVisible();
await adminContext.close();
await userContext.close();
});
Що дивитися і чому:
- Роль на файл, а не на проєкт. Окремий проєкт під кожну роль роздуває матрицю прогону й змушує дублювати
dependencies, не даючи нічого натомість:test.use(...)перекриває стан рівно там, де це треба. - Гостьові сценарії — це не «проєкт без стану», а скидання. Порожні
cookiesйoriginsзнімають те, що поставив проєкт, і саме так тестують форму входу, редиректи із захищених сторінок і публічні лендінги. - Хоч один тест мусить проходити форму по-справжньому. Підготовка через API прибирає логін із критичного шляху сюїти, але не скасовує його як функціональність: вхідні двері продукту не можуть лишитися непокритими.
- Контексти закривайте явно. Інакше вони доживають до кінця воркера й тягнуть памʼять, а в довгій сюїті це вже помітно.
- Файловий
test.useдля двох ролей в одному тесті не годиться принципово. Він задає одне оточення на весь тест, а вам потрібні дві живі сесії паралельно.
Кейс 3. Акаунт на воркер і тріаж «падає тільки в паралелі»
Сюїта створює й видаляє сутності, ганяється в чотири воркери й почала червоніти впереміш. Спільний файл стану тут уже за межею застосовності: воркер — це окремий процес, і два тести на одному акаунті легально їдуть одночасно. Лікується не ретраями, а власним акаунтом на процес — тобто воркерною .
// tests/fixtures.ts
import { test as base } from '@playwright/test';
import fs from 'node:fs';
export const test = base.extend<{}, { workerStorageState: string }>({
// кожен тест бере стан свого воркера
storageState: ({ workerStorageState }, use) => use(workerStorageState),
workerStorageState: [async ({ browser }, use, workerInfo) => {
const id = workerInfo.parallelIndex;
const file = `playwright/.auth/worker-${id}.json`;
if (!fs.existsSync(file)) {
const context = await browser.newContext({ storageState: undefined });
// акаунти qa+0@…, qa+1@… заведені заздалегідь — їх стільки ж, скільки воркерів
await context.request.post('/api/session', {
data: { email: `qa+${id}@example.com`, password: process.env.QA_PASSWORD },
});
await context.request.storageState({ path: file });
await context.close();
}
await use(file);
}, { scope: 'worker' }],
});
Логін тут виконується один раз на процес, бо воркерна фікстура живе до кінця воркера, а не до кінця тесту. Кількість акаунтів стає вимогою до тестового середовища: скільки воркерів у конфізі, стільки записів має бути заведено на стенді.
Далі — таблиця, якою зручно розбирати вхідні скарги на «нестабільні тести»: майже всі вони в цій темі мають дешеву перевірку за хвилину.
| Симптом | Перша підозра | Перевірка за хвилину | Лікування |
|---|---|---|---|
| Поодинці зелені, разом червоні | гонка за спільним акаунтом, не флак | прогін із --workers=1 | акаунт на воркер, а не retries |
| Логін пройшов, наступна сторінка бачить гостя | токен у sessionStorage | вкладка Application у DevTools: де лежить токен | наповнювати сховище окремим кроком у тесті |
| Локально зелено, нічний прогін увесь червоний | стан протух, і ніхто не дізнався | подивитися expires кукі у файлі стану | писати стан в outputDir або перегенеровувати перед прогоном |
| У UI-режимі все розлогінене | setup-проєкт там не виконувався | запустити auth.setup.ts руками | це не баг конфігу, а поведінка за замовчуванням |
| Шард на CI упав без логіну | прогін із --no-deps або setup не в dependencies | звірити команду запуску й секцію проєкту | оголосити залежність, прибрати прапорець |
| Тест форми входу падає: користувач уже залогінений | стан роздав проєкт | подивитися use проєкту й початок спеки | скинути стан на рівні файла |
| Стан зі стейджа не працює на іншому стенді | інше походження | звірити домен кукі з baseURL | окремий файл стану на кожне середовище |
Що дивитися і чому:
--workers=1— найдешевший тест гіпотези. Позеленіло в один процес — це не і не мережа, а спільний ресурс; далі шукайте, що саме ділять тести: акаунт, запис у базі, глобальний .parallelIndex— стабільний ключ,workerIndex— ні. Індекс паралельності обмежений кількістю воркерів, тому й акаунтів потрібно рівно стільки; після перезапуску воркера номер перевикористовується.- Роль у воркерній фікстурі впливає на розкладку файлів. Воркер підхоплює наступний файл, лише поки воркерні фікстури збігаються, тож ролі краще роздавати файлами стану, а воркерний лишити під «акаунт на процес».
- в цій таблиці не лікують жодного рядка. Вони подовжують прогін і роблять звіт брехливим: зелений з другої спроби виглядає як успіх, хоча гонка нікуди не поділася.
Ізоляція контекстів і збережений стан
- Можу пояснити, чому свіжий контекст на тест означає розлогінений старт: ізоляція, яка дає відтворюваність, і є причиною, чому сесію доводиться підкладати явно.
- Знаю, що (storage state) — це файл з кукі й сховищами залогіненого контексту: пишеться викликом
context.storageState(), читається опцієюstorageStateуuseпроєкту або черезtest.use(...). - Памʼятаю склад напамʼять: кукі,
localStorage, IndexedDB, (WebAuthn) — і щоsessionStorageдо нього не входить, бо API для його збереження немає, а саме сховище розділене за і за вкладкою.
Setup-проєкт і залежності
- Знаю визначення обох понять: проєкт (project) — логічна група тестів з однаковою конфігурацією, залежності (dependencies) — проєкти, які мусять відпрацювати перед тестами цього проєкту.
- Можу описати схему «логін раз на прогін» без підказок: окремий setup-проєкт → файл стану →
dependenciesплюсstorageStateу решти проєктів. - Розумію, чому в setup-тесті потрібен перед збереженням: без нього у файл поїде стан гостя, і впадуть тести, а не підготовка.
- Знаю, що провал залежності залежні тести не червонить, а взагалі не запускає, і що setup-тести видно в репортері разом із — тому зламаний логін дебажать trace viewer.
- Памʼятаю, що фільтрація (
--grep,--shard, вибір за розташуванням,test.only()) підтягує тести залежностей сама,--no-depsце вимикає, кілька залежностей ідуть паралельно, а чіпляють властивістюteardownдо setup-проєкту. - Не плутаю два рівні конфігурації: опції — верхнього рівня, опції оточення (зокрема
storageState) — у секціїuse.
Кілька ролей і гостьові сценарії
- Знаю, що кілька ролей — це кілька автентифікацій в одному setup-проєкті й кілька файлів стану з вибором на рівні файла чи групи через
test.use(...), а не кілька проєктів; дві ролі в одному тесті — два контексти з різними станами. - Памʼятаю, як скинути стан для гостя (
test.use({ storageState: { cookies: [], origins: [] } })на рівні файла) і що обхід UI-логіну в підготовці не скасовує хоча б одного тесту, який реально проходить форму.
Паралельність: спільний акаунт проти акаунта на воркер
- Знаю межу застосовності: один файл стану на всіх — для тестів, які не змінюють серверний стан; змінюють — окремий акаунт на кожен , розрізнення за
testInfo.parallelIndex, акаунтів стільки ж, скільки воркерів. - Можу пояснити механізм через модель паралельності: воркер (worker) — процес ОС без спільного стану, а одиниця паралельності за замовчуванням — файл, не тест; тому «зелені поодинці, червоні разом» — гонка за даними, а не , і її не лікують.
- Розумію різницю : тестова прибирається після кожного тесту, воркерна — із завершенням воркера, тому дорогий логін кладуть у воркерну; нюанс — роль у воркерній впливає на розкладку файлів по воркерах.
Протухання стану
- Можу сказати одним реченням, хто стежить за протуханням: ніхто — це обовʼязок автора тестів, а мертва кука у файлі виглядає так само, як жива; звідси два штатні ходи — писати стан у теку виводу проєкту (
outputDir), яка чиститься автоматично, і памʼятати, що UI-режим setup-проєкт за замовчуванням не запускає, тож локальноauth.setup.tsзапускають руками. - Розумію серверний бік: сесійна кука вмирає із сесією браузера, постійна живе до вказаного моменту, а сервер може обірвати сесію раніше з власних причин.
Логін через API
- Можу назвати твердження, на якому тримається прийом: стан сховища взаємозамінний між браузерним контекстом і контекстом API-запитів; ним же готують серверний стан до відкриття застосунку й перевіряють серверні пост-умови.
- Розрізняю два типи контексту запитів:
page.request/browserContext.requestділять кукі з браузером і оновлюють їх ізSet-Cookie, окремо створений має власне ізольоване сховище кукі; фікстураrequestуспадковуєbaseURL,extraHTTPHeadersі з конфігу.
Секрети, репозиторій і CI
- Ставлюся до файла стану як до секрету: тека під
.gitignore, аргумент «репозиторій приватний» не приймається; правило не зачіпає вже відслідковувані файли, тому потрібенgit rm --cached, а порядок після витоку — спершу ротація, потім прибирання з місця, де секрет засвітився, потім історія. - Знаю два CI-факти: право запису дає право читати всі секрети репозиторію, а для з форку секрети ранеру не передаються, крім
GITHUB_TOKEN.
Квіз
Перед стартом
- Питань: 17
- Поріг «зараховано»: ≥70% правильних відповідей.
- Результат впливає на прогрес; завалені питання підуть у чергу повторення.
- Квіз впливає на компліт теми: тема стає «пройдено», лише коли прочитано теорію І квіз складено на ≥70%.
Питання
Ціна ізоляції контекстів для тестів із логіном
