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 |
| Асерт expect | 5 с | expect.timeout |
| Дія (click, fill) | без ліміту | use.actionTimeout |
| Навігація | без ліміту | use.navigationTimeout |
| Весь прогін | без ліміту | globalTimeout |
«Без ліміту» означає, що дію фактично обмежує лише таймаут тесту. На практиці я задаю actionTimeout близько 10-15 секунд: тоді впаде конкретний клік з зрозумілою помилкою, а не весь тест по таймауту без деталей.
test.slow() потроює таймаут тесту, test.setTimeout(60_000) задає конкретне значення. Для окремого асерту: toBeVisible({ timeout: 15_000 }).
Налаштування мають ієрархію, від загального до конкретного:
- Верхній рівень конфігу і
use. use конкретного проєкту.test.use у файлі або describe.- Опції конкретної дії чи асерту.
Нижчий рівень перевизначає вищий. 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 допомагає рівніше балансувати шарди.