ФАЗА 48. E2E Локаторы

Элемент находится как пользовательский контракт

Role · Label · Filters

getByRole, getByLabel и getByText находят элементы по доступному смыслу, фильтры сужают область без хрупких CSS и XPath.

1. CSS и XPath зависят от структуры

1 / 5

Вложенность, автогенерированный класс и порядок узлов меняются при рефакторинге, хотя пользовательский сценарий остаётся прежним.

  • Класс не стабилен.
  • Nth-child хрупок.
  • Длинный XPath скрывает цель.
Хрупкий селекторДоступный контракт

2. getByRole ищет роль и имя

2 / 5

Кнопка, ссылка и заголовок выбираются по семантике и accessible name, точность текста задаётся при необходимости.

  • Роль соответствует элементу.
  • Имя читается пользователем.
  • Скрытое ограничивается явно.
getByRoleRole и nameДействие

3. Поле ищется по связанному тексту

3 / 5

getByLabel использует label, getByPlaceholder подсказку, getByText видимый текст, getByTestId остаётся согласованным запасным.

  • Label доступен.
  • Placeholder не единственное имя.
  • TestId не меняется по стилю.
ПолеLabelPlaceholderTestId

4. Фильтр и вложенность сужают область

4 / 5

filter по тексту или другому локатору выбирает карточку, внутри неё следующий локатор находит кнопку без глобального nth.

  • Область имеет смысл.
  • Поиск остаётся читаемым.
  • Совпадения видны.
Секция или карточкаВложенный локаторКонкретный элемент

5. Локатор улучшает доступность

5 / 5

Если по роли и имени элемент не находится, причиной может быть отсутствующий label или неверная семантика, а не только плохой тест.

  • Ошибка полезна.
  • Разметка исправляется.
  • Strict mode ловит дубли.
Не найденоПроверка семантикиУстойчивый локатор

Локаторы в Playwright: как находить элементы на странице

← назад к разделу

Большинство нестабильных e2e-тестов ломаются не из-за ошибки в логике — они ломаются из-за локаторов. Разберём, почему так происходит и что с этим делать.

Почему CSS и XPath делают тесты хрупкими

Привычный способ найти элемент — по CSS-селектору или XPath:

await page.locator(".btn-primary > span:nth-child(2)").click();
await page.locator("//div[@class='form']//input[2]").fill("text");

Такие селекторы описывают устройство DOM — классы, положение в дереве, индексы. Как только вёрстка меняется (даже без изменения поведения), локатор перестаёт работать.

Кнопка «Оформить заказ» никуда не делась. Она по-прежнему нажимается. Но .btn-primary > span:nth-child(2) её больше не находит — и тест красный. Разработчик тратит время на починку теста вместо работы над фичей.

User-facing локаторы: искать как пользователь

Playwright предлагает другой подход: искать элементы так, как их видит пользователь — по роли, подписи, тексту. Такие локаторы не зависят от вёрстки.

getByRole — по роли и имени

await page.getByRole("button", { name: "Оформить заказ" }).click();
await page.getByRole("textbox", { name: "Email" }).fill("user@example.com");
await page.getByRole("heading", { name: "Корзина" }).isVisible();

getByRole ищет элемент по его семантической роли (кнопка, поле, заголовок, ссылка) и доступному имени. Пока кнопка остаётся кнопкой с тем же текстом — тест её найдёт, даже если вёрстка полностью переписана.

Это самый устойчивый и рекомендуемый локатор в Playwright. Используйте его в первую очередь.

getByLabel — поля формы по подписи

await page.getByLabel("Email").fill("user@example.com");
await page.getByLabel("Пароль").fill("secret");

Ищет поле ввода по тексту рядом стоящего <label>. Привязан к семантике формы, а не к атрибутам id или name.

getByText — по видимому тексту

await page.getByText("Заказ оформлен").isVisible();
await page.getByText("Нет товаров в корзине").waitFor();

Ищет любой элемент по видимому тексту. Удобно для проверок состояния: появился нужный текст — значит, действие выполнено.

getByPlaceholder — по тексту-подсказке

await page.getByPlaceholder("Введите email").fill("user@example.com");

Подходит для полей, у которых нет видимой подписи, но есть placeholder.

getByTestId — запасной вариант

await page.getByTestId("checkout-button").click();

Ищет по атрибуту data-testid. Используйте, когда элемент семантически не выражен и добавить нормальную роль или подпись не получается. Это осознанный запасной вариант, а не первый выбор.

Как выбрать нужный локатор

Простой порядок приоритетов:

  1. getByRole — для интерактивных элементов (кнопки, ссылки, поля, чекбоксы);
  2. getByLabel / getByPlaceholder — для полей формы;
  3. getByText — для проверки появления текста на странице;
  4. getByTestId — когда другие варианты не подходят.

CSS и XPath в Playwright тоже работают (page.locator("css=...")), но они снижают устойчивость тестов. Используйте их только в крайнем случае.

Как сузить поиск: фильтры и цепочки

Если на странице несколько похожих элементов, локаторы можно уточнить.

filter — оставить нужный из нескольких

const orderRow = page.getByRole("row").filter({ hasText: "Кофемолка" });
await orderRow.getByRole("button", { name: "Удалить" }).click();

filter({ hasText }) оставляет только строки таблицы с текстом «Кофемолка», а дальше внутри неё ищем кнопку «Удалить». Это гораздо устойчивее, чем .nth(2) — порядок строк может меняться.

Вложенные локаторы

const form = page.getByRole("form", { name: "Регистрация" });
await form.getByLabel("Email").fill("user@example.com");
await form.getByLabel("Пароль").fill("secret");

Если на странице несколько форм, сначала находим нужную форму, а уже внутри неё ищем поля. Контекст сужает область поиска.

Связь с доступностью

Это не совпадение, что устойчивый локатор — это доступный элемент. getByRole("button", { name: "Оформить заказ" }) находит элемент так же, как его воспринимает скринридер.

Если тест не может найти кнопку по роли и имени — её, скорее всего, не найдёт и вспомогательная технология. Это одновременно проблема теста и дефект доступности.

Хорошие локаторы и доступная разметка растут из одного корня: семантического HTML. Кнопка должна быть <button>, поле должно иметь <label>, заголовки — использовать <h1>–<h6>.

Коротко

  • CSS и XPath привязывают тест к устройству DOM, а не к поведению — поэтому они делают тесты нестабильными.
  • Playwright предлагает user-facing локаторы: getByRole, getByLabel, getByText, getByPlaceholder, getByTestId.
  • Главный локатор — getByRole: ищет по семантической роли и имени, переживает рефакторинг вёрстки.
  • getByTestId — запасной вариант для случаев, когда семантика не помогает.
  • Для уточнения поиска используют filter() и вложенные локаторы — вместо хрупких индексов.
  • Устойчивый локатор = доступный элемент: если скринридер его не найдёт, тест тоже не найдёт.

Что почитать дальше