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

Почему не CSS и xpath

page.locator(".btn-primary > span:nth-child(2)") ломается, как только дизайнер поменяет вёрстку, хотя кнопка осталась на месте и работает. CSS- и xpath-селекторы завязаны на устройство DOM, а оно меняется часто и не по делу теста. Привязка к class/структуре — главный источник хрупкости.

User-facing локаторы

Playwright предлагает локаторы, отражающие то, как пользователь воспринимает страницу:

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

getByRole — по роли и доступному имени (кнопка с текстом «Оформить заказ»); getByLabel — поле по его метке; getByText — по видимому тексту. Эти локаторы переживают перестройку вёрстки: пока кнопка остаётся кнопкой с тем же текстом, тест её находит.

Приоритет такой: сначала getByRole (самый устойчивый и осмысленный), затем getByLabel/getByPlaceholder для полей, getByText для нефокусируемого контента. getByTestId (по атрибуту data-testid) — осознанный запасной вариант, когда по-человечески элемент не выделить; не первый выбор.

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

Это не совпадение, что устойчивый локатор — это доступный элемент. getByRole("button", { name: ... }) находит элемент так же, как его объявляет скринридер. Если тест не может найти кнопку по роли и имени — её, скорее всего, не найдёт и вспомогательная технология, то есть это ещё и дефект доступности. Хорошие локаторы и хорошая a11y растут из одного корня — семантической разметки.

Цепочки и фильтрация

Когда на странице несколько похожих элементов, локаторы сужают цепочкой и фильтром.

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

filter({ hasText }) оставляет строку с нужным товаром, дальнейший getByRole ищет кнопку уже внутри неё. Это устойчивее, чем индексы (nth(2)), которые ломаются при изменении порядка.

Где это в UCP

Локаторы — фундамент устойчивого e2e: искать как пользователь (по роли, метке, тексту), а не по устройству DOM. Это убирает главную причину хрупких тестов и связывает e2e с доступностью — два качества из одного корня. Для продукт-инженера это тесты, которые проверяют поведение продукта, а не его текущую вёрстку, и потому не краснеют на каждом рефакторинге. Как организовать такие локаторы в растущем наборе — структура тестов.