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

Playwright

Конфіг, проєкти, паралельність, CI

Конфігурація і проєкти

Головний файл — playwright.config.ts з defineConfig. На верхньому рівні задаються testDir, timeout, retries, workers, fullyParallel, reporter і webServer для автоматичного запуску застосунку перед тестами. Секція use задає налаштування для всіх тестів: baseURL, trace, screenshot, video, viewport, locale.

Projects — це набори тестів з різними налаштуваннями: різні браузери, мобільні пристрої через devices['iPhone 13'], різні ролі чи середовища. Кожен проєкт може перевизначати use, мати свої testMatch і dependencies. Запуск конкретного проєкту — прапорець --project=chromium.

Типові значення для CI: retries: process.env.CI ? 2 : 0, forbidOnly: !!process.env.CI, щоб забутий test.only не пройшов у пайплайн. Також trace: 'on-first-retry' і скріншоти лише при падінні. Змінні середовища підтягують через dotenv, а секрети — із CI.

Паралелізація, воркери і шардинг

Playwright запускає тести паралельно у воркерах — окремих процесах. За замовчуванням паралельно йдуть файли, а тести всередині файлу — послідовно; fullyParallel: true паралелить і тести в межах файлу. Кількість воркерів задається через workers, в CI її часто зменшують через обмежені ресурси.

test.describe.configure({ mode: 'serial' }) запускає тести блоку послідовно, і якщо один падає, решта пропускається — це для залежних кроків, але краще робити тести незалежними. Режим parallel навпаки паралелить тести в конкретному файлі. Для паралельності кожен тест має працювати з власними даними.

Шардинг ділить тести між кількома машинами: npx playwright test --shard=1/4, у GitHub Actions — через matrix. Кожен шард дає свій звіт, тому використовують blob-репортер, збирають артефакти всіх джоб і об'єднують їх через npx playwright merge-reports. Розподіл іде по файлах, тож нерівні за розміром файли дають перекіс між шардами.

Таймаути і пріоритет налаштувань

Таймаутів кілька, і їх часто плутають:

ТаймаутЗа замовчуваннямДе задати
Тест (разом з beforeEach і test-фікстурами)30 сtimeout, test.setTimeout
Асерт expect5 сexpect.timeout
Дія (click, fill)без лімітуuse.actionTimeout
Навігаціябез лімітуuse.navigationTimeout
Весь прогінбез лімітуglobalTimeout

«Без ліміту» означає, що дію фактично обмежує лише таймаут тесту. На практиці я задаю actionTimeout близько 10-15 секунд: тоді впаде конкретний клік з зрозумілою помилкою, а не весь тест по таймауту без деталей.

test.slow() потроює таймаут тесту, test.setTimeout(60_000) задає конкретне значення. Для окремого асерту: toBeVisible({ timeout: 15_000 }).

Налаштування мають ієрархію, від загального до конкретного:

  1. Верхній рівень конфігу і use.
  2. use конкретного проєкту.
  3. test.use у файлі або describe.
  4. Опції конкретної дії чи асерту.

Нижчий рівень перевизначає вищий. test.use не можна викликати всередині тіла тесту, лише на рівні файлу чи describe, бо він може вимагати нового контексту.

globalTimeout корисний у CI: якщо щось зависло, прогін зупиниться, а не з'їсть години runner'а. Схожу роль виконує maxFailures: зупинити прогін після N падінь, коли середовище очевидно лежить.

На співбесіді: За замовчуванням тест має 30 секунд, асерт — 5, а дії й навігація не мають окремого ліміту. Я задаю actionTimeout, щоб падіння вказувало на конкретну дію. У CI додаю globalTimeout і maxFailures, щоб не палити ресурси, коли середовище лежить.

webServer і змінні середовища

Опція webServer запускає застосунок перед тестами й чекає, поки він відповість:

export default defineConfig({
  webServer: [
    {
      command: 'npm run start:api',
      url: 'http://localhost:4000/health',
      reuseExistingServer: !process.env.CI,
      timeout: 120_000,
    },
    {
      command: 'npm run start:web',
      url: 'http://localhost:3000',
      reuseExistingServer: !process.env.CI,
    },
  ],
  use: { baseURL: process.env.BASE_URL ?? 'http://localhost:3000' },
});

reuseExistingServer локально дозволяє використати вже запущений сервер, а в CI змушує стартувати свіжий. Можна передати масив, якщо треба підняти бекенд і фронтенд. Після прогону Playwright сам зупиняє процеси.

Для кількох середовищ (dev, stage) я тримаю .env.dev і .env.stage, підвантажую потрібний через dotenv на початку конфігу за змінною, наприклад TEST_ENV, і з нього беру baseURL. Секрети не лежать у репозиторії: локально — в .env, що в .gitignore, у CI — в secrets пайплайна.

baseURL дозволяє писати page.goto('/login') і request.get('/api/users') з відносними шляхами, тож той самий набір тестів запускається на будь-якому середовищі зміною однієї змінної.

На співбесіді: Застосунок перед тестами я піднімаю через webServer з reuseExistingServer: !process.env.CI. Середовище обирається змінною, з якої конфіг бере baseURL, а секрети йдуть з CI, а не з репозиторію.

CI: шардинг, Docker і швидкі перезапуски

Типовий пайплайн у GitHub Actions з шардингом:

jobs:
  e2e:
    strategy:
      fail-fast: false
      matrix:
        shard: [1/4, 2/4, 3/4, 4/4]
    runs-on: ubuntu-latest
    container: mcr.microsoft.com/playwright:v1.55.0-noble
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npx playwright test --shard=${{ matrix.shard }} --reporter=blob
      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: blob-${{ strategy.job-index }}
          path: blob-report

Окрема джоба після шардів завантажує всі артефакти й робить npx playwright merge-reports --reporter=html ./all-blob-reports.

Деталі, які варто знати:

  • fail-fast: false, щоб падіння одного шарда не скасовувало інші.
  • Версія Docker-образу має збігатися з версією @playwright/test, інакше браузери не знайдуться. Образ дає однакові шрифти, що критично для скріншотних тестів.
  • З fullyParallel: true шардинг розподіляє окремі тести, а не файли, тому шарди балансуються рівніше.
  • workers у CI ставлять від ресурсів runner'а, наприклад '50%' або 1-2 на слабких машинах.

Для швидшої роботи є прапорці: --last-failed перезапускає лише тести, що впали минулого разу, --only-changed запускає тести з файлів, змінених відносно HEAD або вказаної гілки (--only-changed=main). Перший зручний локально після падіння, другий — для швидкого фідбеку в PR перед повним прогоном.

На співбесіді: У CI я запускаю тести в офіційному Docker-образі тієї ж версії, ділю на шарди через matrix з fail-fast: false, а blob-звіти об'єдную через merge-reports. fullyParallel допомагає рівніше балансувати шарди.