Локаторы: ищем как пользователь
Главный приём Playwright: находить элементы по их роли и тексту, а не по хрупкой разметке.
Локатор — это способ указать Playwright, какой элемент на странице нас интересует. Локатор не ищет элемент сразу, а описывает «как его найти, когда понадобится».
Философия: как пользователь
Пользователь не знает про CSS-классы и id. Он видит кнопку «Войти», поле «Email», ссылку «Подробнее». Playwright предлагает искать элементы так же — по роли и видимому тексту. Такие локаторы устойчивы: если дизайнер поменяет CSS-класс, тест не сломается, ведь кнопка по-прежнему называется «Войти».
Рекомендуемые локаторы по приоритету
Playwright советует выбирать локаторы в таком порядке предпочтения.
| Локатор | Что находит | Пример элемента |
getByRole | по ARIA-роли и доступному имени | кнопка, ссылка, заголовок, чекбокс |
getByLabel | поле формы по тексту его метки | input с label «Email» |
getByText | элемент по видимому тексту | абзац, span с текстом |
getByTestId | по атрибуту data-testid | когда нет роли и текста |
getByRole — главный инструмент
Роль — это семантика элемента: кнопка, ссылка, заголовок, поле ввода. getByRole находит элемент по роли и его доступному имени (тексту, который читает скринридер). Это самый надёжный способ, и он заодно проверяет доступность: если элемента нет в дереве доступности, его не «увидит» и скринридер.
// кнопка с текстом «Отправить»
await page.getByRole('button', { name: 'Отправить' }).click();
// ссылка «Главная»
await page.getByRole('link', { name: 'Главная' }).click();
// заголовок h1/h2 с текстом «Корзина»
await expect(page.getByRole('heading', { name: 'Корзина' })).toBeVisible();getByLabel, getByText, getByTestId
Поля ввода удобно искать по их метке. Если в HTML есть <label>, связанная с <input>, то getByLabel найдёт именно это поле.
<label for="email">Email</label>
<input id="email" type="email">await page.getByLabel('Email').fill('[email protected]');
await expect(page.getByText('Заказ оформлен')).toBeVisible();getByTestId — запасной вариант, когда у элемента нет ни внятной роли, ни стабильного текста. Разработчик заранее добавляет в HTML атрибут data-testid:
<div data-testid="cart-total">1500 ₽</div>await expect(page.getByTestId('cart-total')).toHaveText('1500 ₽');CSS и XPath: почему избегать
CSS-селектор и XPath находят элемент по его положению в дереве разметки, а не по тому, что видит пользователь.
Playwright умеет искать и по CSS, и по XPath — это привычно тем, кто пришёл из Selenium. Но классы и структура DOM — это детали реализации: дизайнер переименовал класс .btn-primary, разработчик обернул кнопку в лишний <div> — и локатор ломается. Пользователь при этом ничего не заметил.
// Хрупко: завязано на классы и структуру
await page.locator('div.modal > div.footer > button.btn.btn-primary').click();
// Устойчиво: завязано на смысл
await page.getByRole('button', { name: 'Сохранить' }).click();Особенно опасен XPath по индексам вида //table/tr[3]/td[2] — «третья строка, вторая ячейка». Такой локатор завязан на порядок: добавили строку — и тест проверяет уже не то, что нужно. Это один из самых частых источников флаки.
| Локатор | Оценка |
getByRole('button', { name: '...' }) | лучший выбор |
input[name="email"] | приемлемо (стабильный атрибут) |
.btn.btn-primary | хрупко (классы стилей) |
//div[2]/button[1] | очень хрупко (позиция) |
Полностью отказываться от CSS не нужно: если у элемента нет роли, текста и data-testid, CSS по осмысленному атрибуту (name, aria-label) приемлем. Правило простое: цепляйтесь за то, что отражает намерение, а не за оформление.
Итог
- Локатор описывает, как найти элемент; приоритет:
getByRole→getByLabel→getByText→getByTestId. - Семантические локаторы устойчивы к смене CSS и заодно проверяют доступность.
- CSS и XPath завязаны на детали разметки — это делает тесты хрупкими.
- XPath по индексам (порядку) — частый источник флаки; CSS уместен лишь по стабильным смысловым атрибутам.