QA Interview Hub · База знань · Тести
Playwright
Взаємодія: дії, вкладки, фрейми, файли, діалоги
Дії з елементами: що коли використовувати
Основні дії і нюанси, які питають на співбесіді:
fill очищає поле й вставляє значення одразу. Це стандарт для інпутів.pressSequentially друкує посимвольно з подіями клавіатури. Потрібен для полів з автокомплітом чи масками, які реагують на кожне натискання. Старий type застарів.press('Enter') і комбінації на кшталт press('ControlOrMeta+A') — модифікатор, що працює і на Windows, і на macOS.check і uncheck для чекбоксів і радіо: вони перевіряють, що стан справді змінився. setChecked(bool) зручний у параметризованих тестах.selectOption для нативного select: за value, label або масивом для multiple. Кастомний дропдаун на div так не працює — там клік по опції через getByRole('option').hover, dblclick, click({ button: 'right' }), click({ modifiers: ['Shift'] }).dragTo(target) для drag-and-drop. Якщо бібліотека DnD нестандартна, роблять вручну через page.mouse.down, move, up.
await page.getByLabel('Місто').pressSequentially('Льв', { delay: 100 });
await page.getByRole('option', { name: 'Львів' }).click();
await page.getByLabel('Валюта').selectOption({ label: 'UAH' });
await page.getByTestId('card-1').dragTo(page.getByTestId('column-done'));
Для полів дати fill очікує формат, який приймає інпут: для type="date" це 2026-10-08, незалежно від локалі.
На співбесіді: Для звичайних полів я використовую fill, а pressSequentially — лише коли поле реагує на кожне натискання, як автокомпліт. Для нативного select — selectOption, для кастомних дропдаунів — клік по option. check кращий за click для чекбоксів, бо перевіряє, що стан змінився.
Нові вкладки, popup і iframe
Нова вкладка — це нова Page в тому ж контексті. Її треба «зловити» подією, і промис створюється до кліку, інакше подія може прийти раніше, ніж я почну слухати:
const popupPromise = page.waitForEvent('popup');
await page.getByRole('link', { name: 'Договір' }).click();
const popup = await popupPromise;
await popup.waitForLoadState();
await expect(popup).toHaveURL(/contract/);
page.waitForEvent('popup') ловить вкладки, відкриті з конкретної сторінки. context.waitForEvent('page') — будь-яку нову сторінку в контексті. Спрощений варіант для тестів, де вкладка не важлива, — прибрати target="_blank", але тоді не перевіряється реальна поведінка.
Iframe потребує явного переходу в його документ. Є два способи:
const card = page.frameLocator('#payment-iframe');
await card.getByLabel('Номер картки').fill('4242424242424242');
const frame = page.locator('#payment-iframe').contentFrame();
await frame.getByRole('button', { name: 'Оплатити' }).click();
frameLocator і contentFrame() — ліниві, як звичайні локатори, і чекають появи фрейму. Cross-origin фрейми, наприклад платіжний віджет з іншого домену, працюють так само, бо Playwright керує браузером ззовні.
Вкладені фрейми — ланцюжком: page.frameLocator('#outer').frameLocator('#inner').
На співбесіді: Для нової вкладки я спочатку створюю промис waitForEvent('popup'), потім клікаю, потім чекаю промис — інакше можна пропустити подію. Для iframe використовую frameLocator або contentFrame(), і cross-origin фрейми платіжних систем працюють без обхідних шляхів.
Завантаження і вивантаження файлів
Upload робиться через setInputFiles на input з type="file", навіть якщо він прихований під гарною кнопкою:
await page.getByLabel('Документ').setInputFiles('fixtures/passport.pdf');
await page.getByLabel('Документи').setInputFiles(['a.pdf', 'b.pdf']);
await page.getByLabel('Документ').setInputFiles({
name: 'report.csv',
mimeType: 'text/csv',
buffer: Buffer.from('id,amount\n1,100'),
});
await page.getByLabel('Документ').setInputFiles([]);
Варіант з buffer дозволяє генерувати файл прямо в тесті, без зберігання в репозиторії. Порожній масив очищає вибір. Якщо інпута в DOM немає, а діалог відкривається динамічно, ловлять подію filechooser: page.waitForEvent('filechooser'), потім fileChooser.setFiles(...).
Download перевіряють через подію download:
const downloadPromise = page.waitForEvent('download');
await page.getByRole('button', { name: 'Експорт CSV' }).click();
const download = await downloadPromise;
expect(download.suggestedFilename()).toBe('transactions.csv');
const filePath = test.info().outputPath(download.suggestedFilename());
await download.saveAs(filePath);
Далі файл можна прочитати через fs і перевірити вміст: кількість рядків, суми, кодування. Це важливо у фінтеху, де звіти для бухгалтерії — частина вимог. Файли завантажень видаляються після закриття контексту, тому saveAs потрібен, якщо файл треба зберегти.
На співбесіді: Для upload я використовую setInputFiles, часто з buffer, щоб генерувати файл у тесті. Для download ловлю подію download, зберігаю файл в outputPath тесту і перевіряю не лише назву, а і вміст.
Діалоги alert, confirm, prompt
Нативні діалоги браузера Playwright за замовчуванням автоматично закриває (dismiss). Тобто confirm поверне false, і якщо тест очікує видалення після підтвердження, воно не станеться. Щоб керувати діалогом, підписуються на подію до дії:
page.once('dialog', async (dialog) => {
expect(dialog.type()).toBe('confirm');
expect(dialog.message()).toContain('Видалити рахунок?');
await dialog.accept();
});
await page.getByRole('button', { name: 'Видалити' }).click();
await expect(page.getByText('Рахунок видалено')).toBeVisible();
Для prompt у accept передають текст: dialog.accept('Причина'). Для відмови — dialog.dismiss().
Пастка: якщо є слухач dialog, Playwright вже не закриває діалог сам. Якщо в обробнику забути accept чи dismiss, сторінка зависне, бо нативний діалог блокує виконання JavaScript. Тому я використовую page.once, а не page.on, коли діалог очікується один раз, щоб обробник не впливав на інші кроки.
Окремий випадок — beforeunload, діалог «Покинути сторінку?». У тестах він з'являється при page.close({ runBeforeUnload: true }) і обробляється тим самим способом.
Кастомні модальні вікна на HTML — це не діалоги браузера. Для них звичайні локатори: getByRole('dialog').
На співбесіді: Нативні діалоги Playwright за замовчуванням відхиляє, тому для confirm я заздалегідь реєструю page.once('dialog') і викликаю accept. Якщо слухач є, діалог обов'язково треба закрити в ньому, інакше сторінка зависне.