QA Interview Hub · База знань · Тести
Playwright
Авторизація і storageState
Авторизація: storageState і setup-проєкт
StorageState — JSON-знімок стану контексту: cookies і localStorage для кожного origin. Логін виконують один раз і зберігають стан через page.context().storageState({ path: 'playwright/.auth/user.json' }), а тести підключають файл через use: { storageState }. Папку з файлами стану додають у .gitignore, бо там живі токени.
Рекомендований спосіб — setup-проєкт: окремий проєкт у конфізі з testMatch: /.*\.setup\.ts/, від якого інші проєкти залежать через dependencies. На відміну від globalSetup, setup-проєкт — це повноцінні тести: з фікстурами, ретраями, trace і відображенням у звіті. globalSetup лишається для речей без браузера, як підготовка БД чи запуск контейнерів.
Для кількох ролей роблять окремі файли стану (admin.json, user.json) і підключають їх через окремі проєкти або test.use у файлі. Коли в одному тесті потрібні дві ролі одночасно, створюють два контексти через browser.newContext({ storageState }). SessionStorage у storageState не зберігається — його прокидають вручну через addInitScript.
Логін через API в setup-проєкті
Setup-проєкт не обов'язково має логінитися через UI. Якщо логін-форму вже покрито окремими тестами, швидше отримати сесію через API:
// auth.setup.ts
import { test as setup, expect } from '@playwright/test';
setup('логін адміністратора', async ({ request }) => {
const res = await request.post('/api/auth/login', {
data: { email: process.env.ADMIN_EMAIL, password: process.env.ADMIN_PASSWORD },
});
await expect(res).toBeOK();
await request.storageState({ path: 'playwright/.auth/admin.json' });
});
Це працює, якщо сесія тримається на cookies. Якщо застосунок зберігає токен у localStorage, після API-логіну треба відкрити сторінку й записати токен через page.evaluate, а потім зберегти page.context().storageState().
Важливе обмеження спільного стану: усі воркери використовують одного користувача. Якщо тести змінюють стан акаунта (баланс, налаштування, кошик), вони почнуть заважати один одному паралельно. Тоді переходять на акаунт на воркер: worker-фікстура логінить окремого користувача з індексом test.info().parallelIndex і зберігає стан у файл з цим індексом.
Ще один нюанс — термін дії токена. Якщо сесія живе 15 хвилин, а прогін триває 40, тести наприкінці почнуть падати з 401. Тоді або збільшують TTL на тестовому середовищі, або перелогінюються у фікстурі.
На співбесіді: У setup-проєкті я логінюсь через API і зберігаю storageState, бо це швидше за UI. Якщо тести змінюють дані акаунта, використовую окремого користувача на кожен воркер через parallelIndex. Також враховую TTL сесії відносно тривалості прогону.
Кілька ролей і розлогінений стан
Якщо файл тестує лише одну роль, достатньо test.use({ storageState: 'playwright/.auth/admin.json' }) на початку файлу чи describe. Якщо в одному тесті потрібні дві ролі одночасно, зручно зробити фікстури, що повертають готові сторінки:
type Roles = { adminPage: Page; clientPage: Page };
export const test = base.extend<Roles>({
adminPage: async ({ browser }, use) => {
const ctx = await browser.newContext({ storageState: 'playwright/.auth/admin.json' });
await use(await ctx.newPage());
await ctx.close();
},
clientPage: async ({ browser }, use) => {
const ctx = await browser.newContext({ storageState: 'playwright/.auth/client.json' });
await use(await ctx.newPage());
await ctx.close();
},
});
Тест тоді виглядає як сценарій: клієнт подає заявку на кредит, адміністратор її схвалює, клієнт бачить новий статус.
Протилежна ситуація: глобально налаштований storageState, але треба тест для неавторизованого користувача. Тоді стан скидають: test.use({ storageState: { cookies: [], origins: [] } }).
SessionStorage у storageState не потрапляє. Якщо застосунок тримає сесію там, її зчитують у setup через page.evaluate і відновлюють через context.addInitScript, який виконується перед скриптами сторінки.
Ролі також можна розвести по проєктах у конфізі: проєкт admin з одним файлом стану і testMatch для адмінських тестів, проєкт client — з іншим.
На співбесіді: Для сценаріїв з двома ролями я роблю фікстури adminPage і clientPage, кожна з власним контекстом і storageState. Для тестів неавторизованого користувача скидаю стан через порожній storageState. SessionStorage відновлюю через addInitScript.
globalSetup чи setup-проєкт: порівняння
Обидва механізми виконують підготовку до тестів, але різниця суттєва:
| Можливість | globalSetup | setup-проєкт |
|---|
| Видно в HTML-звіті | ні | так |
| Trace і скріншоти | лише вручну | як у звичайних тестах |
| Фікстури page, request | ні | так |
| Ретраї | ні | так |
| Можна запустити частину | ні | через dependencies |
globalSetup — це функція в окремому файлі, яка виконується один раз перед усім прогоном. Вона отримує конфіг і може передати значення тестам через process.env. Якщо вона повертає функцію, та стане teardown.
Для setup-проєкту прибирання робиться через властивість teardown у проєкті: вказується назва проєкту, який запуститься після завершення всіх залежних.
projects: [
{ name: 'setup', testMatch: /global\.setup\.ts/, teardown: 'cleanup' },
{ name: 'cleanup', testMatch: /global\.teardown\.ts/ },
{ name: 'e2e', dependencies: ['setup'], use: { storageState: 'playwright/.auth/user.json' } },
],
Мій вибір: усе, що стосується браузера й логіну, — у setup-проєкт. globalSetup лишаю для інфраструктури: підняти контейнери, накотити міграції, перевірити доступність середовища.
Нюанс: якщо запустити тест з фільтром, наприклад npx playwright test checkout, setup-проєкт все одно виконається, бо він у dependencies. Пропустити його можна прапорцем --no-deps, коли стан уже збережений.
На співбесіді: Setup-проєкт кращий для логіну, бо має trace, ретраї, фікстури й видно у звіті, а прибирання робиться через властивість teardown. globalSetup я лишаю для інфраструктурної підготовки без браузера.