selectOption() работает только с нативными элементами <select>: вызови его на React Select или Material UI combobox собранном из div-ов и Playwright бросит ошибку. Кастомные дропдауны требуют другого подхода: клик чтобы открыть, затем клик по опции через role или видимый текст. Эта статья разбирает нативные select-ы, паттерн click-open для кастомных компонентов, дропдауны с фильтрацией по вводу, навигацию с клавиатуры, специфичную структуру React Select, мультиселект, и ассёрт который подтверждает что выбор действительно произошёл, а не просто что клик не бросил ошибку.

Нативные элементы <select>

Простейший случай: стандартный HTML-элемент <select>.

<label for="country">Country</label>
<select id="country">
  <option value="">Select a country</option>
  <option value="us">United States</option>
  <option value="de">Germany</option>
  <option value="pl">Poland</option>
</select>
// Выбор по видимому тексту
await page.getByLabel('Country').selectOption('Germany');

// Выбор по атрибуту value
await page.getByLabel('Country').selectOption({ value: 'de' });

// Выбор по индексу (0-based, лучше избегать — хрупко)
await page.getByLabel('Country').selectOption({ index: 2 });

// Проверка выбора
await expect(page.getByLabel('Country')).toHaveValue('de');

// Мультиселект (для <select multiple>)
await page.getByLabel('Tags').selectOption(['playwright', 'typescript']);

selectOption() работает только с элементами <select>. Для кастомных дропдаунов нужен другой подход.

Кастомные dropdown-компоненты

Большинство современных приложений используют кастомные компоненты (React Select, Headless UI, Material UI) вместо нативного <select>. Внешне они выглядят как дропдауны, но устроены иначе.

Паттерн 1: клик чтобы открыть, клик по опции

Самый распространённый паттерн кастомного дропдауна:

// Кастомный дропдаун: открыть кликом, выбрать кликом
await page.getByRole('combobox', { name: 'Country' }).click();
await page.getByRole('option', { name: 'Germany' }).click();

// Проверка
await expect(page.getByRole('combobox', { name: 'Country' })).toHaveText('Germany');

Если дропдаун использует роль listbox:

await page.getByRole('button', { name: 'Country' }).click();
await page.getByRole('listbox').getByRole('option', { name: 'Germany' }).click();

Паттерн 2: ввод для фильтрации, затем выбор

Многие кастомные дропдауны поддерживают поиск:

// Вводим текст для фильтрации
await page.getByRole('combobox', { name: 'Country' }).fill('Ger');

// Ждём появления отфильтрованных результатов
await page.getByRole('option', { name: 'Germany' }).waitFor();

// Кликаем по опции
await page.getByRole('option', { name: 'Germany' }).click();

Паттерн 3: навигация с клавиатуры

Некоторые дропдауны управляются с клавиатуры:

await page.getByLabel('Country').focus();
await page.keyboard.press('ArrowDown'); // Открыть дропдаун
await page.keyboard.press('ArrowDown'); // Перейти к первой опции
await page.keyboard.press('ArrowDown'); // Перейти ко второй опции
await page.keyboard.press('Enter');     // Выбрать

Или ввод буквы для перехода к совпадающей опции:

await page.getByLabel('Country').focus();
await page.keyboard.type('G'); // Перейти к Germany
await page.keyboard.press('Enter');

Как разобраться в реализации дропдауна

Перед написанием теста изучи элемент в DevTools:

  1. Правая кнопка на дропдауне, Inspect
  2. Проверь тип элемента: <select> или <div>/<button>?
  3. Если кастомный, взаимодействуй с ним вручную и наблюдай изменения в DOM
  4. Проверь атрибут role или ARIA-роли

Правильный подход к тестированию полностью зависит от реализации. Не угадывай.

React Select

React Select одна из самых распространённых библиотек дропдаунов. У неё специфичное поведение:

// React Select combobox
const dropdown = page.locator('.react-select__control');
await dropdown.click();

// Вводим текст для фильтрации
await dropdown.locator('input').fill('Ger');
await page.locator('.react-select__option', { hasText: 'Germany' }).click();

// Проверка
await expect(page.locator('.react-select__single-value')).toHaveText('Germany');

// Сбросить выбор
await page.locator('.react-select__clear-indicator').click();

React Select использует специфичные CSS-классы. Если твоя версия генерирует другие имена классов, адаптируй соответственно. По возможности предпочитай getByRole:

// Лучше — использует семантические роли
await page.getByRole('combobox', { name: 'Country' }).click();
await page.getByRole('option', { name: 'Germany' }).click();

Мультиселект дропдауны

// Нативный <select multiple>
await page.getByLabel('Skills').selectOption(['playwright', 'typescript', 'cicd']);

// Кастомный мультиселект: кликаем несколько опций
await page.getByRole('button', { name: 'Skills' }).click();
await page.getByRole('option', { name: 'Playwright' }).click();
await page.getByRole('option', { name: 'TypeScript' }).click();
await page.getByRole('option', { name: 'CI/CD' }).click();

// Закрыть дропдаун
await page.keyboard.press('Escape');

// Проверить выбранные значения
await expect(page.getByTestId('selected-skills')).toContainText('Playwright');
await expect(page.getByTestId('selected-skills')).toContainText('TypeScript');

Дропдауны дат (день, месяц, год)

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

await page.getByLabel('Month').selectOption('June');
await page.getByLabel('Day').selectOption('15');
await page.getByLabel('Year').selectOption('1990');

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

// Работает во всех браузерах
await page.getByLabel('Date of birth').fill('1990-06-15');

// Проверка
await expect(page.getByLabel('Date of birth')).toHaveValue('1990-06-15');

Проверка опций дропдауна

// Проверить что все опции присутствуют
const options = await page.getByLabel('Country').locator('option').allTextContents();
expect(options).toContain('Germany');
expect(options).toContain('Poland');

// Проверить что конкретная опция задизейблена
await expect(page.getByRole('option', { name: 'Out of stock item' })).toBeDisabled();

// Проверить количество опций
const optionCount = await page.getByLabel('Category').locator('option').count();
expect(optionCount).toBeGreaterThan(0);

Типичные ошибки

selectOption() на кастомном дропдауне. Работает только с <select>. Кастомные дропдауны требуют клика или взаимодействия с клавиатурой.

Нет ожидания появления опций. После открытия дропдауна который загружает опции асинхронно, жди появления опции до клика.

Выбор по индексу. Хрупкий вариант: добавление новой опции перед твоей сломает тест. Используй текст или value.

Нет проверки что показывает дропдаун после выбора. Проверяй что выбранное значение корректно, а не просто что клик не бросил ошибку.

→ См. также: Сравнение локаторов Playwright: getByRole, getByLabel, getByText, getByTestId | Assertions в Playwright: полное руководство | Работа с несколькими вкладками, окнами и iFrame в Playwright