QA Interview Hub · База знань · Тести
Playwright
Browser, Context, Page
Вбудовані фікстури і ручне створення
Раннер дає фікстури browser, context і page. browser має worker-скоуп: один запущений браузер на воркер. context і page — test-скоуп: новий контекст і сторінка для кожного тесту, які раннер сам закриває після тесту.
Коли треба більше контролю, контекст створюють вручну з фікстури browser:
test('гість бачить банер cookies', async ({ browser }) => {
const context = await browser.newContext({ locale: 'uk-UA' });
const page = await context.newPage();
await page.goto('/');
await expect(page.getByRole('dialog', { name: 'Cookies' })).toBeVisible();
await context.close();
});
Ручний контекст раннер не закриває — це моя відповідальність. Якщо забути context.close(), при падінні тесту залишаться відкриті сторінки, а відео й HAR можуть не записатися, бо вони фіналізуються саме при закритті контексту.
Опції контексту — це те ж саме, що секція use у конфізі: viewport, locale, timezoneId, storageState, permissions, extraHTTPHeaders, baseURL. Різниця лише в тому, що через use вони застосовуються до вбудованої фікстури context, а через newContext — до мого контексту.
На співбесіді: browser живе на рівні воркера, а context і page створюються під кожен тест і закриваються автоматично. Якщо мені потрібен окремий контекст з іншими налаштуваннями, я створюю його через browser.newContext() і сам закриваю, найкраще у фікстурі, щоб teardown спрацював навіть при падінні.
Два користувачі в одному тесті
Класичне завдання: перевірити чат чи переказ коштів між двома людьми. Тут потрібні два незалежні контексти, бо вкладки одного контексту ділять cookies, і другий користувач просто «перезапише» сесію першого.
test('повідомлення доходить до отримувача', async ({ browser }) => {
const alice = await browser.newContext({ storageState: '.auth/alice.json' });
const bob = await browser.newContext({ storageState: '.auth/bob.json' });
const alicePage = await alice.newPage();
const bobPage = await bob.newPage();
await alicePage.goto('/chat/bob');
await alicePage.getByRole('textbox').fill('Привіт');
await alicePage.getByRole('button', { name: 'Надіслати' }).click();
await bobPage.goto('/chat/alice');
await expect(bobPage.getByText('Привіт')).toBeVisible();
await alice.close();
await bob.close();
});
Обидва контексти працюють в одному браузері, тому це дешево. Окремий браузер для другого користувача не потрібен.
Протилежна ситуація — кілька сторінок в одному контексті через context.newPage(). Вони ділять сесію, і це підходить для перевірки синхронізації між вкладками одного користувача, наприклад logout в одній вкладці має розлогінити іншу.
На співбесіді: Для двох користувачів я створюю два контексти з різними storageState, бо контекст — це окрема сесія. Кілька сторінок одного контексту ділять cookies, тому їх я використовую лише для сценаріїв одного користувача в кількох вкладках.
Події сторінки як додаткова перевірка
Page генерує події, на які можна підписатися: console, pageerror, request, response, requestfailed, dialog, popup, download. Це корисно не лише для дебагу, а і як автоматична перевірка якості.
Наприклад, необроблені JavaScript-помилки на сторінці часто ламають функціонал непомітно для UI-асертів. Їх можна збирати й валити тест:
test('сторінка оплати без JS-помилок', async ({ page }) => {
const errors: string[] = [];
page.on('pageerror', (err) => errors.push(err.message));
page.on('console', (msg) => {
if (msg.type() === 'error') errors.push(msg.text());
});
await page.goto('/checkout');
await page.getByRole('button', { name: 'Оплатити' }).click();
expect(errors).toEqual([]);
});
Різниця: pageerror — це виняток, який ніхто не перехопив, а console з типом error — те, що код сам вивів через console.error. Друге часто шумить через сторонні скрипти, тому список зазвичай фільтрують.
Подія requestfailed допомагає ловити запити, що впали на рівні мережі, наприклад заблоковані CORS або обірване з'єднання. Щоб не дублювати такий код у кожному тесті, його виносять в auto-фікстуру.
На співбесіді: Я підписуюсь на pageerror і console, щоб ловити JS-помилки, які UI-асерти не бачать. Щоб не копіювати це в кожен тест, роблю auto-фікстуру, яка після тесту перевіряє, що помилок не було.