Действия с элементами

Урок: Действия с элементами (click, fill и др.)

Введение

Когда пользователь работает с сайтом, он постоянно выполняет действия: нажимает кнопки, вводит текст, выбирает пункты в списке, отправляет формы.

Если посмотреть на это со стороны автоматизации, задача теста — повторить эти действия.

Представь, что ты вручную проверяешь форму логина:

  • вводишь email;
  • вводишь пароль;
  • нажимаешь кнопку «Войти»;
  • проверяешь результат.

Playwright делает ровно то же самое, только через код.

И ключевая часть этой работы — действия с элементами. Это те команды, с помощью которых мы «управляем» страницей: кликаем, вводим текст, выбираем значения.

Важно не просто знать названия методов, а понимать, как они работают и почему иногда тесты с ними ведут себя нестабильно.

Официальный обзор действий с полями, кликами, клавиатурой, файлами и перетаскиванием: Actions | Playwright. Про проверки «можно ли кликнуть» (видимость, перекрытие, стабильность позиции) — Actionability.


Базовая идея: действие = взаимодействие с элементом

Любое действие в Playwright выглядит примерно так:

await page.getByRole('button', { name: 'Войти' }).click();

Разберём:

  • сначала мы находим элемент (локатор);
  • затем вызываем метод (click);
  • Playwright выполняет действие.

Важно: действие всегда выполняется над локатором.


Текст и значения в полях (fill)

locator.fill() — основной способ заполнить поле: фокус, ввод текста, событие input. Подходит для <input>, <textarea> и элементов с [contenteditable].

await page.getByRole('textbox', { name: 'Имя' }).fill('Пётр');

// типы input из документации
await page.getByLabel('Дата рождения').fill('2020-02-02');
await page.getByLabel('Время визита').fill('13:15');
await page.getByLabel('Локальная дата и время').fill('2020-03-02T05:15');

fill полностью подменяет значение (как «выделил всё и вставил»). Для обычных форм этого достаточно.

Очистить поле отдельно:

await page.getByLabel('Email').clear();

Чаще clear не нужен — перед заполнением fill сам приводит поле к новому значению.


Чекбоксы и радиокнопки (check, uncheck, setChecked)

Для input[type=checkbox], input[type=radio] и роли checkbox:

await page.getByLabel('Согласен с условиями').check();
await page.getByLabel('Размер: XL').check(); // радиокнопка

await expect(page.getByLabel('Подписка на рассылку')).toBeChecked();

await page.getByRole('checkbox', { name: 'Опция' }).uncheck();

Явно выставить состояние «вкл/выкл»:

await page.getByRole('checkbox', { name: 'Уведомления' }).setChecked(true);
await page.getByRole('checkbox', { name: 'Уведомления' }).setChecked(false);

Выпадающий список (selectOption)

Работай через локатор на <select>, а не через сырой селектор страницы.

<select aria-label="Язык">
  <option value="ru">Русский</option>
  <option value="en">English</option>
</select>
const lang = page.getByLabel('Язык');

await lang.selectOption('ru'); // по value или по видимой подписи option

await lang.selectOption({ label: 'English' }); // точно по тексту option

// несколько выбранных значений (multiple)
await page.getByLabel('Теги').selectOption(['bug', 'docs', 'feature']);

Клик, наведение, модификаторы (click, dblclick, hover)

Обычный клик:

await page.getByRole('button', { name: 'Войти' }).click();

Дополнительные варианты из документации:

await page.getByText('Карточка').dblclick();
await page.getByText('Элемент').click({ button: 'right' }); // правый клик
await page.getByText('Элемент').click({ modifiers: ['Shift'] });

// Ctrl на Windows/Linux или Cmd на macOS
await page.getByText('Ссылка').click({ modifiers: ['ControlOrMeta'] });

await page.getByText('Пункт меню').hover();

// клик в конкретной точке элемента (например, угол)
await page.getByText('Область').click({ position: { x: 10, y: 10 } });

Под капотом Playwright ждёт появления в DOM, видимости, окончания движения (например, после CSS-перехода), прокрутки в зону видимости и того, что в точке клика нет перекрытия другим элементом — см. Actionability.

Если перекрытие осознанное и нужно обойти проверки (осторожно):

await page.getByRole('button', { name: 'OK' }).click({ force: true });

«Программный» клик без реального указателя (имитация HTMLElement.click()):

await page.getByRole('button', { name: 'Скрытая кнопка' }).dispatchEvent('click');

Посимвольный ввод (pressSequentially)

Если на странице важна обработка каждого нажатия клавиши (автодополнение, маска, live-поиск), используй посимвольный ввод — в документации рекомендуется pressSequentially:

await page.locator('#search').pressSequentially('playwright', { delay: 50 });

Для обычных полей по-прежнему предпочитай fill — он проще и быстрее.


Клавиши и сочетания (press)

Один вызов — одно нажатие после фокуса на элементе:

await page.getByRole('textbox', { name: 'Чат' }).press('Enter');
await page.getByRole('textbox').press('Control+ArrowRight');
await page.getByRole('textbox').press('$'); // символ с клавиатуры
await page.locator('#name').press('Shift+A');

Имена клавиш совместимы с тем, что описано в KeyboardEvent.key (Backspace, Escape, KeyA …). Модификаторы: Shift, Control, Alt, Meta. Комбинации вроде Control+o поддерживаются.


Загрузка файлов (setInputFiles)

Для <input type="file">:

await page.getByLabel('Файл').setInputFiles('path/to/report.pdf');

await page.getByLabel('Несколько файлов').setInputFiles(['fixtures/a.txt', 'fixtures/b.txt']);

await page.getByLabel('Папка').setInputFiles('path/to/dir');

// сброс выбора
await page.getByLabel('Файл').setInputFiles([]);

// из памяти (без файла на диске)
await page.getByLabel('Файл').setInputFiles({
  name: 'note.txt',
  mimeType: 'text/plain',
  buffer: Buffer.from('текст для теста'),
});

Если диалог выбора файла открывается не через обычный input, а через кастомную кнопку:

const fileChooserPromise = page.waitForEvent('filechooser');
await page.getByText('Выбрать файл').click();
const fileChooser = await fileChooserPromise;
await fileChooser.setFiles('path/to/doc.pdf');

Фокус (focus)

Для сценариев, где важны события фокуса:

await page.getByLabel('Пароль').focus();

Перетаскивание (dragTo и мышь)

Готовый сценарий «потянуть — отпустить»:

await page.locator('#drag').dragTo(page.locator('#drop'));

Точный контроль — hover, затем page.mouse.down() / mouse.move() / mouse.up() (в документации есть нюансы с dragover: иногда нужен второй move или повторный hover на цель перед mouse.up()).


Прокрутка

Перед действием Playwright обычно сам прокручивает элемент в видимую область. Явно прокрутить к элементу:

await page.getByText('Подвал страницы').scrollIntoViewIfNeeded();

Колёсико мыши или программная прокрутка контейнера — см. mouse.wheel и locator.evaluate в разделе Scrolling той же страницы.


Чтение значений (вспомогательно для сценария)

Для проверок чаще используй expect, но иногда нужно прочитать данные:

const title = await page.getByRole('heading').textContent();
const value = await page.getByLabel('Логин').inputValue();

Проверка через expect

Предпочтительный способ дождаться состояния и проверить его:

await expect(page.getByText('Успешно')).toBeVisible();
await expect(page.getByRole('checkbox', { name: 'Согласен' })).toBeChecked();

expect автоматически ретраит проверку в течение таймаута — надёжнее, чем одноразовое чтение textContent без ожидания.


Комбинирование действий

Рассмотрим полный сценарий:

await page.goto('https://example.com/login');

await page.getByLabel('Email').fill('test@mail.com');
await page.getByLabel('Пароль').fill('secret');

await page.getByRole('button', { name: 'Войти' }).click();

await expect(page.getByText('Добро пожаловать')).toBeVisible();

Что происходит:

  1. открываем страницу;
  2. вводим данные;
  3. нажимаем кнопку;
  4. проверяем результат.

Это уже полноценный пользовательский сценарий.


Как Playwright делает действия стабильными

Когда ты вызываешь действие у локатора, Playwright перед выполнением проверяет цепочку условий (элемент в DOM, виден, получает события указателя в точке действия, при необходимости не двигается и т.д.) — см. Actionability.

await page.getByRole('button', { name: 'Отправить' }).click();

Поэтому не нужно вручную выставлять паузы «на глаз»:

await page.waitForTimeout(2000); // хуже: дольше и нестабильнее

Встроенные ожидания в действиях и в expect обычно достаточны.


Частые ошибки

Ошибка 1 — клик по неуникальному селектору или слишком общему локатору:

await page.locator('button').click(); // если кнопок несколько — неоднозначно

Лучше уточнять через getByRole, getByTestId или filter.

Ошибка 2 — лишние ожидания:

await page.waitForTimeout(3000);

Это замедляет тесты и делает их нестабильными.

Ошибка 3 — разовое чтение DOM без ожидания вместо expect:

const text = await page.locator('.message').textContent();
if (text !== 'OK') throw new Error('fail');

Лучше: await expect(page.getByRole('status')).toHaveText('OK') — с ретраями и понятным сообщением при таймауте.


Где это используется на практике

Действия с элементами — это основа любых тестов.

Они используются:

  • в тестировании форм;
  • в e2e сценариях;
  • в автоматизации пользовательских потоков;
  • в CI/CD.

Любой тест — это последовательность действий + проверка результата.


Итоговое понимание

Действия с элементами — это способ «управлять» страницей через код.

Ключевая идея в том, что ты не просто вызываешь методы (click, fill, selectOption…), а воспроизводишь поведение пользователя там, где это важно для проверки.

Playwright даёт единый набор операций из документации по действиям: ввод и очистка, чекбоксы и списки, клики и наведение, клавиатура, файлы, фокус, drag-and-drop и прокрутка — с auto-waiting и проверками actionability.

Хорошо написанные действия — это понятный сценарий, стабильный тест и предсказуемый результат. Именно через них e2e-тест превращается из набора строк в проверку реального потока по интерфейсу.


Веб-страница из видео

Демо для отработки действий с элементами: "Экшены" — демо-страница.