QA Interview Hub · База знань · Тести
Playwright
Емуляція і середовище
Пристрої, viewport і медіа
devices — набір готових профілів: розмір екрана, userAgent, deviceScaleFactor, isMobile, hasTouch і браузер за замовчуванням.
projects: [
{ name: 'mobile-safari', use: { ...devices['iPhone 13'] } },
{ name: 'mobile-chrome', use: { ...devices['Pixel 7'] } },
{ name: 'tablet', use: { ...devices['iPad Pro 11'], colorScheme: 'dark' } },
],
Важливо розуміти межу: це емуляція в десктопному рушії, а не реальний пристрій. Вона добре перевіряє адаптивну верстку, мобільне меню, touch-події через tap(), але не ловить специфічні баги мобільного Safari чи продуктивність на слабкому телефоні.
Для tap() потрібен hasTouch: true, інакше буде помилка.
Інші корисні опції емуляції:
colorScheme: 'dark' або page.emulateMedia({ colorScheme: 'dark' }) — тестування темної теми.reducedMotion: 'reduce' — вимикає анімації, якщо застосунок поважає це налаштування; заодно стабілізує тести.page.emulateMedia({ media: 'print' }) — перевірка друкованої версії виписки чи квитанції.offline: true або context.setOffline(true) посеред тесту — перевірка поведінки без мережі.javaScriptEnabled: false — перевірка SSR-сторінок.viewport: { width: 1280, height: 720 } і page.setViewportSize() для перевірки брейкпоінтів.
На співбесіді: Мобільні сценарії я покриваю проєктами з devices, розуміючи, що це емуляція, а не реальний пристрій. Окремо перевіряю темну тему, офлайн через context.setOffline і вимикаю анімації через reducedMotion.
Геолокація, дозволи, локаль і часовий пояс
Для застосунків з картами, мультимовністю чи часовими поясами ці налаштування важливі:
test.use({
locale: 'uk-UA',
timezoneId: 'Europe/Kyiv',
geolocation: { latitude: 49.8397, longitude: 24.0297 },
permissions: ['geolocation'],
});
test('найближче відділення у Львові', async ({ page, context }) => {
await page.goto('/branches');
await expect(page.getByText('Львів, пл. Ринок')).toBeVisible();
await context.setGeolocation({ latitude: 50.4501, longitude: 30.5234 });
await page.getByRole('button', { name: 'Оновити' }).click();
await expect(page.getByText('Київ')).toBeVisible();
});
Геолокація без дозволу не спрацює: сайт отримає відмову. Дозволи видають через permissions у конфізі або context.grantPermissions(['clipboard-read'], { origin }), а скидають через context.clearPermissions(). Набір підтримуваних дозволів трохи відрізняється між браузерами.
locale впливає на navigator.language, заголовок Accept-Language і форматування чисел та дат через Intl. Це дозволяє перевірити, що сума виводиться як «1 000,50 ₴», а не «1,000.50».
timezoneId змінює часовий пояс браузера. Класичні баги, які так ловлять: транзакція о 23:30 за Києвом потрапляє у «завтрашню» виписку, бо фронтенд конвертує в UTC; або дата народження зсувається на день. Я запускаю такі тести з кількома поясами, наприклад Kyiv і America/New_York, через проєкти чи параметризацію.
На співбесіді: Для локалізації я задаю locale і timezoneId і перевіряю формати сум і дат. Для геолокації потрібен і geolocation, і дозвіл у permissions. Тести з часовими поясами допомагають ловити зсуви дат на межі доби.
Clock API: керування часом
З версії 1.45 є page.clock, який підміняє Date, таймери і requestAnimationFrame у браузері. Це вирішує завдання, які раніше були болючими: таймер сесії, банер «пропозиція діє 10 хвилин», статус, що змінюється опівночі.
Основні методи:
clock.install({ time }) — встановити фейковий годинник з початковим часом. Викликати до goto, щоб сторінка завантажилась уже з ним.clock.fastForward('05:00') — перемотати вперед, спрацюють таймери, що мали спрацювати; рядок у форматі mm:ss або hh:mm:ss, або число мілісекунд.clock.runFor(ms) — прокрутити час послідовно, виконуючи кожен таймер по черзі.clock.pauseAt(time) і clock.resume() — зупинити час у конкретний момент.clock.setFixedTime(time) — Date.now() завжди повертає одне значення, а таймери працюють як зазвичай.clock.setSystemTime(time) — змінити поточний час, не запускаючи таймери.
test('попередження про завершення сесії', async ({ page }) => {
await page.clock.install({ time: new Date('2026-10-08T10:00:00') });
await page.goto('/dashboard');
await page.clock.fastForward('14:00');
await expect(page.getByRole('dialog', { name: 'Сесія завершується' })).toBeVisible();
await page.clock.fastForward('01:00');
await expect(page).toHaveURL(/login/);
});
Важливо: Clock керує часом лише в браузері. Сесія, термін якої перевіряє сервер, від цього не закінчиться — для бекенду потрібні тестові ендпоінти чи короткий TTL на тестовому середовищі.
На співбесіді: Для логіки, залежної від часу, я використовую page.clock: встановлюю час через install до навігації і перемотую через fastForward. Розумію, що це впливає лише на браузер, тому серверний TTL перевіряю окремо.
Візуальні тести: toHaveScreenshot
await expect(page).toHaveScreenshot('dashboard.png') порівнює скріншот з еталоном. Перший запуск створює еталон і падає, наступні порівнюють. Оновлення еталонів — npx playwright test --update-snapshots.
Асерт ретраїть: робить скріншоти, доки два поспіль не збіжуться, і лише потім порівнює з еталоном. Анімації за замовчуванням вимкнені, курсор у полях прихований.
Еталони зберігаються з суфіксом проєкту й платформи, наприклад dashboard-chromium-linux.png. Шрифти й рендеринг на macOS і Linux відрізняються, тому еталони генерують і перевіряють в одному середовищі — зазвичай в офіційному Docker-образі, щоб локальні й CI-прогони збігались.
Динамічні дані — головне джерело флакі у візуальних тестах. Інструменти проти них:
await expect(page).toHaveScreenshot('statement.png', {
mask: [page.getByTestId('balance'), page.getByTestId('timestamp')],
maxDiffPixelRatio: 0.01,
fullPage: true,
});
mask замальовує зазначені елементи.maxDiffPixels і maxDiffPixelRatio дозволяють невелику різницю; threshold (0.2 за замовчуванням) — чутливість до різниці кольору одного пікселя.stylePath підключає CSS лише на час скріншота, наприклад щоб приховати віджет чату.- Фіксовані дані через моки й
page.clock дають стабільний вміст.
Скріншот окремого компонента — expect(locator).toHaveScreenshot(), це стабільніше, ніж уся сторінка. Для не-графічних даних є toMatchSnapshot.
На співбесіді: Візуальні тести я пишу через toHaveScreenshot, генерую еталони в Docker, щоб не було різниці між macOS і Linux, а динамічні дані маскую через mask або стабілізую моками й page.clock. Краще скріншотити окремі компоненти, ніж усю сторінку.