QA Interview Hub · База знань · Тести

Playwright

Фікстури й структура тестів

Фікстури і скоупи

Фікстури в Playwright — механізм dependency injection: тест оголошує, що йому потрібно, наприклад { page, request }, і раннер це готує. Кастомні фікстури створюють через test.extend: код до await use(value) — це setup, після — teardown. Фікстури можуть залежати одна від одної і створюються лише тоді, коли тест їх запросив.

Скоупів два: test (за замовчуванням) — фікстура створюється для кожного тесту окремо, і worker — один раз на воркер для всіх його тестів. Скоуп задається третім параметром: [async ({}, use) => {...}, { scope: 'worker' }]. Worker-скоуп підходить для дорогих речей, як підключення до БД чи тестовий користувач.

Воркерів кілька, тож worker-фікстура створиться в кожному з них — для унікальних даних використовують workerInfo.workerIndex. Опція auto: true запускає фікстуру без явного запиту, а option: true робить її параметром, що задається в конфізі. Типовий патерн — фікстури з готовими Page Objects, щоб тест одразу отримував loginPage.

Хуки, describe і умовні тести

Структура тестів: test.describe групує тести, хуки beforeEach, afterEach, beforeAll, afterAll виконують підготовку й прибирання. beforeEach має доступ до тих самих фікстур, що й тест, наприклад page. beforeAll — лише до worker-фікстур, бо виконується один раз на воркер, а не на тест.

Коли обирати хук, а коли фікстуру? Хук простіший для одного файлу. Фікстура краща, коли логіка повторюється у багатьох файлах, коли потрібен гарантований teardown і коли важливо, щоб підготовка запускалась лише для тестів, яким вона потрібна.

Керування виконанням:

  • test.skip(condition, 'причина') — пропустити за умовою, наприклад для браузера.
  • test.fixme() — тест відомо зламаний, не запускати.
  • test.fail() — тест має впасти; якщо пройде, це помилка. Корисно для відомого бага: щойно його виправлять, тест про це повідомить.
  • test.slow() — потроїти таймаут.
test('експорт PDF', async ({ page, browserName }) => {
  test.skip(browserName === 'webkit', 'Баг PAY-512 у WebKit');
  await page.goto('/reports');
});

test('швидкий пошук', { tag: ['@smoke', '@search'] }, async ({ page }) => {
  await page.goto('/search');
});

Теги задаються в деталях тесту і фільтруються через --grep @smoke або --grep-invert @slow. Анотації annotation: { type: 'issue', description: 'PAY-512' } видно в HTML-звіті.

На співбесіді: Хуки я використовую для простої підготовки в одному файлі, а повторювану логіку з teardown виношу у фікстури. Для відомих багів беру test.fail з посиланням на тікет, щоб тест сам повідомив, коли баг виправлять. Смоук-набір позначаю тегами й запускаю через --grep.

test.step і режими describe.configure

test.step ділить тест на іменовані кроки. У звіті й trace видно не плаский список кліків, а «Логін», «Додати товар», «Оплатити», і одразу зрозуміло, на якому бізнес-кроці впало. Крок може повертати значення:

test('покупка', async ({ page }) => {
  const orderId = await test.step('Оформити замовлення', async () => {
    await page.getByRole('button', { name: 'Купити' }).click();
    return page.getByTestId('order-id').innerText();
  });
  await test.step('Перевірити статус', async () => {
    await expect(page.getByText(`Замовлення ${orderId}`)).toBeVisible();
  });
});

Кроки зручно огортати в методи Page Object. Опція { box: true } робить так, що помилка вказує на виклик кроку в тесті, а не на рядок усередині хелпера, що спрощує читання звіту.

test.describe.configure задає режим виконання файлу чи блоку:

РежимПоведінка
defaultтести файлу послідовно в одному воркері, але падіння одного не скасовує інші
parallelтести блоку розподіляються між воркерами
serialпослідовно; при падінні решта пропускається, а при ретраї перезапускається вся група

Також через configure можна задати retries і timeout для групи. Serial — виняток для довгих залежних сценаріїв; майже завжди краще зробити тести незалежними через підготовку даних по API.

На співбесіді: Я огортаю бізнес-етапи в test.step, щоб звіт читався як сценарій. Режим serial використовую рідко, бо при ретраї він перезапускає всю групу; натомість роблю тести незалежними. Parallel вмикаю для файлів, де тести не ділять дані.

Фікстури глибше: worker, auto, option, mergeTests

Worker-фікстура з унікальними даними для кожного воркера:

type WorkerFixtures = { account: { email: string; password: string } };

export const test = base.extend<{}, WorkerFixtures>({
  account: [async ({ playwright }, use, workerInfo) => {
    const api = await playwright.request.newContext({ baseURL: process.env.API_URL });
    const email = `qa+w${workerInfo.workerIndex}-${Date.now()}@test.com`;
    await api.post('/api/test/users', { data: { email, password: 'Secret123' } });
    await use({ email, password: 'Secret123' });
    await api.delete(`/api/test/users/${email}`);
    await api.dispose();
  }, { scope: 'worker' }],
});

Увага: вбудовані фікстури request і page мають test-скоуп, тому worker-фікстура не може від них залежати — Playwright кине помилку. Тут використовується worker-фікстура playwright, з якої створюється власний API-контекст, а в кінці він закривається через dispose().

Auto-фікстура запускається для кожного тесту без явного запиту: [async ({ page }, use) => {...}, { auto: true }]. Підходить для збору логів, перевірки JS-помилок, прикріплення артефактів.

Option-фікстура — параметр зі значенням за замовчуванням, який можна перевизначити в проєкті конфігу чи через test.use:

export const test = base.extend<{ currency: string }>({
  currency: ['UAH', { option: true }],
});

Вбудовані фікстури можна перевизначати: наприклад, page, що одразу відкриває baseURL.

Коли фікстур багато, їх розносять по модулях і об'єднують через mergeTests(dbTest, apiTest, uiTest), а кастомні матчери — через mergeExpects. Фікстурі також можна задати окремий timeout, щоб повільна підготовка не з'їдала час тесту.

На співбесіді: Для дорогої підготовки я роблю worker-фікстуру з workerIndex, щоб воркери не конфліктували за дані. Auto-фікстури використовую для крос-тестових перевірок, option-фікстури — для параметрів, що змінюються між проєктами. Великий набір фікстур розбиваю на модулі й збираю через mergeTests.