Большинству тестов Playwright достаточно click() и fill(), но дропдауны по наведению, мультиселект через Ctrl+click и drag-реализации которые отслеживают события mousemove по траектории требуют низкоуровневых API keyboard и mouse. page.keyboard.type() генерирует события keydown, keypress, keyup и input для каждого символа: важно для автодополнения и real-time валидации которые fill() обходит стороной. Эта статья разбирает нажатия клавиш, комбинации с модификаторами, hover, drag-and-drop через dragTo() и сырые события мыши, прокрутку и паттерн мультиселекта через Ctrl+click.

Клавиатурные события

Базовые нажатия клавиш

// нажать одну клавишу
await page.keyboard.press('Enter');
await page.keyboard.press('Tab');
await page.keyboard.press('Escape');
await page.keyboard.press('ArrowDown');

// комбинации с модификатором
await page.keyboard.press('Control+a');  // выделить всё
await page.keyboard.press('Control+c');  // копировать
await page.keyboard.press('Control+v');  // вставить
await page.keyboard.press('Shift+Tab'); // Tab в обратном направлении
await page.keyboard.press('Meta+k');    // Cmd+K на Mac

Имена клавиш соответствуют спецификации KeyboardEvent.key. Часто используемые: 'Enter', 'Tab', 'Escape', 'Backspace', 'Delete', 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Home', 'End', 'PageUp', 'PageDown', 'F1''F12'.

Ввод текста

// вводим в сфокусированный элемент
await page.getByRole('searchbox').focus();
await page.keyboard.type('playwright testing');

// ввод с задержкой между нажатиями (имитирует реальный набор)
await page.keyboard.type('slow typing', { delay: 50 });

keyboard.type() генерирует события keydown, keypress, keyup и input для каждого символа. Это важно для приложений которые обрабатывают отдельные нажатия: автодополнение, real-time валидация, редакторы rich text.

Удержание клавиш-модификаторов

// Shift + клик для выделения диапазона
await page.keyboard.down('Shift');
await page.getByRole('row').nth(5).click();
await page.keyboard.up('Shift');

Тестирование клавиатурной навигации

Проверка доступности приложения с клавиатуры: одновременно требование к доступности и QA-задача.

test('modal closes on Escape', async ({ page }) => {
  await page.getByRole('button', { name: 'Open modal' }).click();
  await expect(page.getByRole('dialog')).toBeVisible();

  await page.keyboard.press('Escape');

  await expect(page.getByRole('dialog')).not.toBeVisible();
});

test('dropdown navigates with arrow keys', async ({ page }) => {
  await page.getByRole('combobox', { name: 'Country' }).focus();
  await page.keyboard.press('ArrowDown'); // открывает дропдаун, выбирает первый
  await page.keyboard.press('ArrowDown'); // выбирает второй
  await page.keyboard.press('Enter');     // подтверждает выбор

  await expect(page.getByRole('combobox', { name: 'Country' })).toHaveValue('Australia');
});

События мыши

Варианты клика

// двойной клик
await page.getByRole('row').first().dblclick();

// правый клик (контекстное меню)
await page.getByText('Document.pdf').click({ button: 'right' });
await expect(page.getByRole('menuitem', { name: 'Download' })).toBeVisible();

// клик в конкретных координатах относительно элемента
await page.getByRole('slider').click({ position: { x: 10, y: 0 } });

// клик с зажатым модификатором
await page.getByRole('checkbox', { name: 'Item 3' }).click({ modifiers: ['Shift'] });

Наведение

// наводим чтобы показать тултип или дропдаун
await page.getByRole('button', { name: 'Help' }).hover();
await expect(page.getByRole('tooltip')).toBeVisible();
await expect(page.getByRole('tooltip')).toHaveText('Click for documentation');

// наводим на строку таблицы чтобы показать скрытую кнопку действия
await page.getByRole('row', { name: 'Alice Johnson' }).hover();
await page.getByRole('button', { name: 'Edit' }).click();

Движение мыши

// перемещаем мышь к абсолютным координатам страницы
await page.mouse.move(100, 200);

// перетаскиваем из одной позиции в другую
await page.mouse.move(100, 200);
await page.mouse.down();
await page.mouse.move(300, 200, { steps: 10 }); // steps делает движение плавнее
await page.mouse.up();

Параметр steps в mouse.move() разбивает перемещение на промежуточные точки: важно для drag-and-drop реализаций которые отслеживают события mousemove по пути.

Drag and drop

Для стандартного HTML5 drag-and-drop:

// через dragTo — самый простой способ
await page.getByText('Card A').dragTo(page.getByText('Column B'));

// с конкретной позицией сброса внутри цели
await page.getByText('File.pdf').dragTo(page.locator('.upload-zone'), {
  targetPosition: { x: 50, y: 50 },
});

Для кастомных drag-реализаций которые используют события mousedown/mousemove/mouseup:

const source = page.getByTestId('draggable-card');
const target = page.getByTestId('drop-zone');

const sourceBounds = await source.boundingBox();
const targetBounds = await target.boundingBox();

await page.mouse.move(sourceBounds!.x + sourceBounds!.width / 2, sourceBounds!.y + sourceBounds!.height / 2);
await page.mouse.down();
await page.mouse.move(targetBounds!.x + targetBounds!.width / 2, targetBounds!.y + targetBounds!.height / 2, { steps: 20 });
await page.mouse.up();

Прокрутка

// прокрутка страницы
await page.mouse.wheel(0, 500); // прокрутить вниз на 500px

// прокрутка внутри конкретного элемента
await page.getByRole('log').hover();
await page.mouse.wheel(0, 300);

// прокрутить к элементу (перед взаимодействием с элементом за пределами экрана)
await page.getByTestId('submit-section').scrollIntoViewIfNeeded();

Комбинирование клавиатуры и мыши

Наиболее реалистичные паттерны взаимодействия комбинируют оба:

test('multi-select items with Ctrl+click', async ({ page }) => {
  await page.goto('/files');

  // выбираем первый элемент
  await page.getByRole('row').nth(0).click();

  // Ctrl+click добавляет к выделению
  await page.getByRole('row').nth(2).click({ modifiers: ['Control'] });
  await page.getByRole('row').nth(4).click({ modifiers: ['Control'] });

  // удаляем выбранные элементы
  await page.keyboard.press('Delete');

  await expect(page.getByRole('row')).toHaveCount(7); // было 10, удалили 3
});

Когда использовать эти API

Большинство тестов должны использовать высокоуровневые методы локаторов (click(), fill(), selectOption()). К API клавиатуры и мыши обращайся когда:

  • Тестируешь горячие клавиши или доступность
  • Тестируешь UI-элементы которые появляются по наведению
  • Тестируешь drag-and-drop взаимодействия
  • Тестируешь контекстные меню
  • Тестируешь редакторы rich text
  • Симулируешь сложные многошаговые взаимодействия

Прямые события клавиатуры и мыши медленнее и хрупче чем высокоуровневые действия. Используй их только когда высокоуровневый API не может выразить то что нужно.

→ See also: Локаторы Playwright: getByRole, getByLabel, getByText, getByTestId — сравнение | Тестирование доступности с Playwright: автоматизированные a11y проверки | Загрузка и скачивание файлов в тестах Playwright