Продвинутые локаторы
Updated Jul 2026
Реальные приложения имеют сложные UI: таблицы с сотнями строк, вложенные компоненты, динамические списки, iframe и shadow DOM. API локаторов Playwright предоставляет инструменты композиции — фильтрацию, цепочки и скопирование — позволяющие точно нацеливаться на элементы без хрупких селекторов.
Фильтрация локаторов
filter() сужает локатор по дополнительным критериям — текстовому содержимому, дочерним элементам или видимости.
// Найти строку, содержащую «Иванов», и нажать кнопку редактирования
await page.getByRole('row')
.filter({ hasText: 'Иванов' })
.getByRole('button', { name: 'Редактировать' })
.click();
// Фильтр по дочернему элементу
await page.getByRole('listitem')
.filter({ has: page.getByRole('heading', { name: 'Премиум план' }) })
.getByRole('button', { name: 'Выбрать' })
.click();
// Негативный фильтр: строки, НЕ содержащие «Архив»
await page.getByRole('row')
.filter({ hasNotText: 'Архив' });
Цепочки локаторов
Цепочки ограничивают область локатора внутри другого локатора — как поиск элемента внутри контейнера.
// Найти боковую панель, затем ссылку внутри неё
const sidebar = page.getByRole('navigation', { name: 'Sidebar' });
await sidebar.getByRole('link', { name: 'Настройки' }).click();
// Найти конкретную карточку, затем взаимодействовать с элементами внутри
const productCard = page.getByTestId('product-card').filter({ hasText: 'Ноутбук' });
await productCard.getByRole('button', { name: 'В корзину' }).click();
const price = await productCard.getByTestId('price').textContent();
Выбор по индексу (Nth)
Когда несколько элементов совпадают и нужен конкретный:
// Первый совпадающий элемент
await page.getByRole('button', { name: 'Удалить' }).first().click();
// Последний совпадающий элемент
await page.getByRole('button', { name: 'Удалить' }).last().click();
// Конкретный индекс (с нуля)
await page.getByRole('listitem').nth(2).click();
// Подсчёт элементов
await expect(page.getByRole('listitem')).toHaveCount(5);
Предпочитайте фильтрацию вместо nth()
nth() — позиционный и ломается при изменении порядка элементов. Предпочитайте фильтрацию по содержимому:
// ХРУПКО: зависит от позиции
await page.getByRole('row').nth(3).getByRole('button').click();
// УСТОЙЧИВО: нацелено на содержимое
await page.getByRole('row')
.filter({ hasText: 'Заказ №1234' })
.getByRole('button', { name: 'Просмотр' })
.click();
Фрейм-локаторы
Iframe распространены в платёжных формах, сторонних виджетах и устаревших приложениях. Playwright предоставляет frameLocator() для работы внутри фреймов.
// Вход в iframe по CSS-селектору
const paymentFrame = page.frameLocator('#payment-iframe');
await paymentFrame.getByLabel('Номер карты').fill('4242424242424242');
await paymentFrame.getByLabel('Срок действия').fill('12/28');
await paymentFrame.getByRole('button', { name: 'Оплатить' }).click();
// Вход в именованный фрейм
const adFrame = page.frameLocator('iframe[name="ad-banner"]');
// Вложенные фреймы
const inner = page.frameLocator('#outer').frameLocator('#inner');
await inner.getByRole('button').click();
Shadow DOM
Playwright проникает через shadow DOM по умолчанию — в отличие от Selenium, где обход shadow DOM требует инъекции JavaScript.
// Playwright автоматически достигает элементов внутри shadow DOM
await page.getByRole('button', { name: 'Переключить' }).click();
// Для CSS-локаторов используйте >> для явного пересечения границ shadow
await page.locator('my-component >> .inner-element').click();
Лучшие практики для сложных UI
Таблицы данных
// Найти конкретную ячейку в таблице
const row = page.getByRole('row').filter({ hasText: 'user@example.com' });
const status = await row.getByRole('cell').nth(3).textContent();
// Проверить всю строку
await expect(row).toContainText(['user@example.com', 'Активен', 'Админ']);
Динамические списки
// Дождаться загрузки списка, затем взаимодействовать с конкретным элементом
await expect(page.getByRole('listitem')).toHaveCount(10);
const item = page.getByRole('listitem').filter({ hasText: 'Важная задача' });
await item.getByRole('checkbox').check();
await expect(item).toHaveClass(/completed/);
Модальные диалоги
// Ограничить взаимодействия областью модального окна
const modal = page.getByRole('dialog');
await modal.getByLabel('Название').fill('Новый проект');
await modal.getByRole('button', { name: 'Создать' }).click();
await expect(modal).not.toBeVisible();
Ключевые выводы
- Используйте
filter()для сужения локаторов по текстовому содержимому или дочерним элементам — устойчивее позиционных селекторов - Создавайте цепочки локаторов для ограничения взаимодействий внутри контейнеров (навигация, карточки, модальные окна)
- Предпочитайте
filter({ hasText })вместоnth()— выбор по содержимому переживает переупорядочивание frameLocator()работает внутри iframe без JavaScript-хаков- Playwright проникает через shadow DOM автоматически — специальная обработка не нужна
- Комбинируйте фильтрацию, цепочки и скопирование для точного нацеливания на элементы в сложных UI