QA Interview Hub · База знань · Тести
Playwright
Мережа: route, моки, API-тести
Робота з мережею: page.route і моки
page.route перехоплює запити за URL-патерном або регулярним виразом. Далі запит можна підмінити через route.fulfill зі своїм статусом і тілом, пропустити далі через route.continue зі зміненими заголовками чи тілом або перервати через route.abort. Маршрут треба оголосити до дії, яка ініціює запит.
Поширений патерн — route.fetch(): отримати справжню відповідь, змінити частину даних і повернути через fulfill. Це дає змогу протестувати граничні стани UI без підготовки даних на бекенді: порожній список, помилку 500, дуже довгий текст. Для повністю офлайнових тестів можна записати трафік у HAR і відтворювати його через routeFromHAR.
Для очікування запиту використовують page.waitForResponse — промис треба створити до дії, а await зробити після. context.route застосовує перехоплення до всіх сторінок контексту. Не варто мокати все підряд: частина E2E-тестів має ходити в справжній бекенд, інакше інтеграційні баги не буде видно.
API-тестування в Playwright
Для API є фікстура request типу APIRequestContext, яка надсилає HTTP-запити без браузера: request.get, post, put, patch, delete. baseURL і extraHTTPHeaders, наприклад токен, задаються в конфізі. Відповідь перевіряють через response.status(), response.json() і асершен expect(response).toBeOK().
API використовують не лише для окремих API-тестів, а й для підготовки даних у E2E: створити користувача чи замовлення через API, а через UI перевірити лише потрібний флоу. Це робить тести швидшими й стабільнішими. Також API зручно використовувати для прибирання даних після тесту в teardown фікстури.
page.request і context.request ділять cookies з браузерним контекстом, тож після логіну через UI запити йдуть уже авторизованими, і навпаки. Для валідації схеми відповіді підключають zod або ajv. Окремий контекст з іншими налаштуваннями створюють через playwright.request.newContext.
Спостерігати чи підміняти: waitForResponse, waitForRequest, route
Тут важливо розрізняти два завдання. page.waitForResponse і page.waitForRequest лише спостерігають за реальним трафіком: запит іде на бекенд, а я чекаю й перевіряю його. page.route перехоплює запит і вирішує, що з ним робити.
Перевірка, що фронтенд відправив правильний payload:
const reqPromise = page.waitForRequest((r) =>
r.url().includes('/api/transfers') && r.method() === 'POST');
await page.getByRole('button', { name: 'Переказати' }).click();
const req = await reqPromise;
expect(req.postDataJSON()).toMatchObject({ amount: 500, currency: 'UAH' });
Через waitForResponse чекаю завершення конкретного запиту й можу дістати дані з відповіді, наприклад id створеного об'єкта.
Нюанси route:
- Патерн — glob (
**/api/users*), RegExp або функція від URL. Glob з * не проходить через /, а ** проходить. - Якщо на URL зареєстровано кілька обробників, спрацьовує останній.
route.fallback() передає запит попередньому обробнику, а route.continue() — одразу в мережу. page.unroute знімає обробник. page.unrouteAll({ behavior: 'ignoreErrors' }) корисний у кінці тесту, коли обробники ще виконуються.- Service Worker може перехоплювати запити раніше за Playwright, тоді route їх не бачить. Рішення —
serviceWorkers: 'block' у конфізі.
На співбесіді: waitForResponse і waitForRequest я використовую, щоб спостерігати реальний трафік і перевіряти payload, а route — щоб підмінити відповідь. Знаю, що спрацьовує останній зареєстрований обробник, і що fallback передає запит далі по ланцюжку, а continue — одразу в мережу.
HAR: запис і відтворення трафіку
HAR — файл з записом HTTP-трафіку: запити, відповіді, заголовки, тіла. Playwright уміє і записати його, і відтворити як мок. Це корисно, коли бекенд нестабільний, дорогий або залежить від сторонніх сервісів, а тестувати треба фронтенд.
Запис і відтворення одним методом:
test('каталог з HAR', async ({ page }) => {
await page.routeFromHAR('hars/catalog.har', {
url: '**/api/**',
update: process.env.UPDATE_HAR === '1',
notFound: 'fallback',
});
await page.goto('/catalog');
await expect(page.getByRole('listitem')).toHaveCount(12);
});
З update: true запити йдуть у реальну мережу, а HAR перезаписується. Без нього відповіді беруться з файлу. url обмежує, які запити мокати: зазвичай лише API, а статика вантажиться як є. notFound: 'fallback' пропускає в мережу запити, яких немає в HAR; за замовчуванням вони обриваються.
Альтернативи запису: опція контексту recordHar: { path, urlFilter } або npx playwright codegen --save-har=file.har.
Ризики, про які варто сказати: HAR застаріває разом з API, тому його треба регулярно оновлювати. У файл потрапляють токени і персональні дані, тож перед комітом його треба перевірити. Відповідність запиту шукається за URL, методом і тілом, тому динамічні параметри, наприклад timestamp у query, можуть ламати співставлення.
На співбесіді: HAR я використовую для стабільних фронтенд-тестів без залежності від нестабільного бекенду: записую трафік через routeFromHAR з update, а потім відтворюю. Обмежую його лише API через опцію url, регулярно оновлюю і перевіряю, щоб у файл не потрапили токени.
APIRequestContext глибше
Крім фікстури request, можна створити окремий API-контекст з власними налаштуваннями. Це потрібно для іншого сервісу, іншого користувача або в worker-фікстурі:
import { request, expect } from '@playwright/test';
const admin = await request.newContext({
baseURL: 'https://api.test.bank',
extraHTTPHeaders: { Authorization: `Bearer ${process.env.ADMIN_TOKEN}` },
});
const res = await admin.post('/accounts', {
data: { currency: 'UAH', type: 'savings' },
params: { dryRun: 'false' },
});
await expect(res).toBeOK();
const { id } = await res.json();
await admin.dispose();
Формат тіла задається опцією: data — JSON, якщо передано об'єкт; form — application/x-www-form-urlencoded; multipart — multipart/form-data з файлами. Query-параметри — через params.
За замовчуванням request не кидає помилку на 4xx чи 5xx — відповідь просто повертається. Це зручно для негативних тестів, де я очікую 422. Якщо хочу, щоб будь-яка помилка валила виклик, є опція failOnStatusCode: true. toBeOK() перевіряє діапазон 200-299, а response.ok() повертає той самий результат як boolean.
APIRequestContext має власні cookies. Після логіну через API можна зберегти стан: await ctx.storageState({ path: '.auth/user.json' }), і використати його в браузерних тестах.
Для чисто API-тестів зручно зробити окремий проєкт без браузерних налаштувань. Браузер стартує ліниво, тому якщо тест не запитує page, він і не запускається.
На співбесіді: Окремий API-контекст я створюю через request.newContext з власним baseURL і токеном, і обов'язково викликаю dispose. Пам'ятаю, що за замовчуванням 4xx і 5xx не кидають помилку, тому статус перевіряю явно. Після логіну через API можу зберегти storageState і використати його в UI-тестах.