Локатор (locator) - описание того, как автотест пользовательского интерфейса находит элемент: кнопку, на которую он нажимает, поле, которое заполняет, сообщение, которое проверяет. Локатор состоит из стратегии и значения: «кнопка с доступным именем Sign in», «элемент с атрибутом data-testid, равным cart-count», «CSS-селектор form > button.primary». Тест обращается к элементам только через локаторы, поэтому именно в локаторах тест зависит от разметки страницы.
Стандарт автоматизации браузера W3C WebDriver определяет пять стратегий поиска; его первый уровень (Level 1) стал рекомендацией W3C 5 июня 2018 года, а Level 2 - рабочий черновик (версия от 2 июля 2026 года). Selenium поддерживает восемь традиционных стратегий, среди них id, CSS-селектор и XPath. Playwright, начиная с версии 1.27, и Testing Library предлагают локаторы по атрибутам, которые видит пользователь: роли, подписи, тексту. Документация Playwright рекомендует их вместо CSS и XPath. Роли и доступные имена, на которые они опираются, определяет WAI-ARIA 1.2 - рекомендация W3C от 6 июня 2023 года.
Какую задачу решают локаторы
Тест должен обращаться к элементам страницы, а страница меняется. Разработчики переименовывают CSS-классы, оборачивают элементы в новые контейнеры, переносят блоки, меняют тексты. Тест должен падать, когда ломается поведение продукта, и проходить, когда меняется только разметка вокруг этого поведения.
Каждый вид локатора привязывает тест к своему свойству элемента. Длинная цепочка CSS привязывает его к структуре документа. Документация Playwright приводит такую цепочку как пример плохой практики, которая ведёт к нестабильным тестам:
page.locator('#tsf > div:nth-child(2) > div.A8SBwf > div.RNNXgb > div > div.a4bIc > input');Любой новый div-обёртка или переименованный класс ломает этот локатор, хотя само поле ввода по-прежнему на странице и работает. Выбор вида локатора определяет, какие изменения страницы ломают тест, а какие нет.
Виды локаторов
Примеры в этом разделе относятся к одной форме входа.
<form>
<h1>Sign in</h1>
<label for="email">Email</label>
<input id="email" type="email" placeholder="name@example.com">
<label for="password">Password</label>
<input id="password" type="password">
<img src="/logo.svg" alt="Shop logo">
<span title="Items in cart" data-testid="cart-count">0</span>
<button type="submit" class="btn btn-primary">Sign in</button>
</form>По роли
Локатор по роли находит элемент по его роли ARIA и доступному имени (accessible name). Роль говорит, чем элемент является: button, link, textbox, checkbox, heading. У элементов HTML есть неявные роли: по спецификации W3C ARIA in HTML элемент button имеет роль button, элемент a с href - роль link, а h1-h6 - роль heading. WAI-ARIA определяет доступное имя как имя элемента пользовательского интерфейса; оно может браться из видимого текста, подписи или атрибута, например aria-label.
В Playwright page.getByRole('button', { name: 'Sign in' }) находит кнопку формы. По умолчанию name сравнивается как подстрока без учёта регистра; параметр exact: true требует совпадения всей строки с учётом регистра. Другие параметры фильтруют по состоянию и уровню: checked, disabled, expanded, pressed, selected, level. Документация Playwright отмечает, что локаторы по роли следуют спецификациям W3C для ролей, атрибутов ARIA и доступных имён, но не заменяют аудит доступности.
По подписи
Локатор по подписи находит поле формы по тексту его подписи (label). page.getByLabel('Email') в Playwright сравнивает текст связанного элемента label или элемента, на который ссылается aria-labelledby, либо значение aria-label. В Testing Library аналог называется getByLabelText; его рекомендуют для полей форм, потому что пользователи находят поля по подписям.
По плейсхолдеру
Локатор по плейсхолдеру находит поле ввода по атрибуту placeholder: page.getByPlaceholder('name@example.com'). Testing Library считает его запасным вариантом, потому что плейсхолдер не заменяет подпись.
По тексту
Локатор по тексту находит элемент по его текстовому содержимому: page.getByText('Sign in'). При сравнении текста Playwright нормализует пробелы, даже при точном совпадении, и принимает подстроку, целую строку или регулярное выражение. Документация рекомендует локаторы по тексту для неинтерактивных элементов, таких как div, span и p, а для интерактивных, таких как button, a и input, - локаторы по роли. В форме выше getByText('Sign in') совпадает и с заголовком, и с кнопкой. Локаторы Playwright строгие: действие над локатором, который совпадает больше чем с одним элементом, вызывает исключение. Локатор по роли разделяет их: getByRole('heading', { name: 'Sign in' }) и getByRole('button', { name: 'Sign in' }).
По альтернативному тексту и title
Локатор по альтернативному тексту (alt text) находит элемент, обычно изображение, по его текстовой альтернативе: page.getByAltText('Shop logo'). Локатор по title находит элемент по атрибуту title: page.getByTitle('Items in cart'). Testing Library предупреждает, что программы чтения с экрана читают title непоследовательно и по умолчанию он не виден.
По test id
Test id - атрибут, который добавляют в разметку только для того, чтобы тесты могли найти элемент. page.getByTestId('cart-count') в Playwright по умолчанию ищет атрибут data-testid. Другое имя атрибута задаётся параметром testIdAttribute в конфигурации тестов или вызовом selectors.setTestIdAttribute().
import { defineConfig } from '@playwright/test';
export default defineConfig({
use: {
testIdAttribute: 'data-pw',
},
});Документация Playwright называет тестирование по test id самым устойчивым способом: test id переживает изменения текста и роли. Она же отмечает, что test id не виден пользователю: тест, который находит элемент по test id, не проверяет то, что видит пользователь.
CSS-селекторы
CSS-селектор находит элементы по тегу, id, классам, атрибутам и положению в документе: button.btn-primary, #email, form > button. В Playwright CSS передаётся в page.locator() с префиксом css= или без него. CSS-селектором можно выразить всё, что другие виды выражают через атрибуты, например [data-testid='cart-count'] или [placeholder='name@example.com']. Устойчивость зависит от того, на что он ссылается: id или атрибут test id стабильны, классы и положение - нет.
XPath
XPath - язык выбора частей XML-документа; его версия 1.0 - рекомендация W3C от 16 ноября 1999 года. Выражение XPath выбирает по структуре, атрибутам и тексту: //button[normalize-space()='Sign in'], //form/input[2]. XPath умеет переходить от элемента к родителю и соседям. В Playwright XPath передаётся в page.locator() с префиксом xpath= или начинаясь с //. Документация Playwright отмечает, что XPath не проникает в теневые корни (shadow roots).
Локаторы в WebDriver и Selenium
Спецификация W3C WebDriver определяет пять стратегий поиска. Клиент передаёт стратегию и значение команде Find Element, POST /session/{session id}/element.
| Ключевое слово | Что находит |
|---|---|
css selector |
Элементы, подходящие под CSS-селектор |
link text |
Ссылки, видимый текст которых равен значению |
partial link text |
Ссылки, видимый текст которых содержит значение |
tag name |
Элементы с заданным именем тега |
xpath |
Элементы, выбранные выражением XPath |
Selenium поддерживает восемь традиционных стратегий: пять перечисленных и id, name, class name. В привязке для Python это константы класса By, и их строковые значения совпадают с ключевыми словами: By.CSS_SELECTOR - это "css selector", By.ID - "id". Стратегия class name не принимает составные имена классов.
from selenium import webdriver
from selenium.webdriver.common.by import By
driver = webdriver.Chrome()
driver.get("https://shop.example.com/login")
driver.find_element(By.ID, "email").send_keys("user@example.com")
driver.find_element(By.CSS_SELECTOR, "input[type='password']").send_keys("secret")
driver.find_element(By.XPATH, "//button[normalize-space()='Sign in']").click()Ни одна из восьми стратегий не ищет элемент по роли ARIA и доступному имени, поэтому в Selenium похожий на роль локатор выражается через CSS или XPath. Документация Selenium о рекомендуемых практиках даёт свой порядок: предпочтителен id, если он есть, уникален и предсказуем; иначе - хорошо написанный CSS-селектор; XPath работает не хуже CSS, но его синтаксис сложен и часто труден для отладки. Локатор по имени тега она называет опасным, потому что на странице обычно много элементов с одним тегом, и отмечает, что стратегии по тексту ссылки работают только со ссылками.
Локаторы в Playwright и Testing Library
Playwright перечисляет рекомендуемые встроенные локаторы в таком порядке: getByRole, getByText, getByLabel, getByPlaceholder, getByAltText, getByTitle, getByTestId. Его лучшие практики советуют отдавать приоритет атрибутам, которые видит пользователь, и явным контрактам, а CSS и XPath не рекомендуют, потому что DOM часто меняется. Тест формы входа использует только рекомендуемые локаторы:
import { test, expect } from '@playwright/test';
test('user signs in', async ({ page }) => {
await page.goto('https://shop.example.com/login');
await page.getByLabel('Email').fill('user@example.com');
await page.getByLabel('Password').fill('secret');
await page.getByRole('button', { name: 'Sign in' }).click();
await expect(page.getByRole('heading', { name: 'My orders' })).toBeVisible();
await expect(page.getByTestId('cart-count')).toHaveText('0');
});Локаторы можно соединять в цепочки и фильтровать, чтобы сузить поиск до части страницы. page.getByRole('listitem').filter({ hasText: 'Product 2' }) выбирает элемент списка, который содержит текст, а следующий getByRole('button', { name: 'Add to cart' }) выбирает кнопку внутри него.
Testing Library следует руководящему принципу: «The more your tests resemble the way your software is used, the more confidence they can give you.» - чем больше тесты похожи на то, как пользуются программой, тем больше уверенности они дают. Её документация делит запросы на три группы по приоритету. Таблица сравнивает два порядка.
| Приоритет | Testing Library | Playwright |
|---|---|---|
| 1 | Доступные всем: getByRole, getByLabelText, getByPlaceholderText, getByText, getByDisplayValue |
getByRole, getByText, getByLabel, getByPlaceholder |
| 2 | Семантические: getByAltText, getByTitle |
getByAltText, getByTitle |
| 3 | Test id: getByTestId |
getByTestId |
| Не в списке | CSS и XPath | CSS и XPath, не рекомендуются |
Оба порядка ставят роль первой, а test id последним среди собственных методов. Различие - внутри первой группы: Testing Library ставит подпись перед текстом, Playwright - текст перед подписью.
Устойчивость к изменениям
Виды локаторов различаются тем, какие изменения их ломают. Таблица сравнивает их по трём свойствам: устойчивость к изменениям разметки, читаемость теста и зависимость от текстов интерфейса.
| Вид локатора | Устойчивость к изменениям разметки | Читаемость | Зависит от текстов |
|---|---|---|---|
| Роль и доступное имя | Высокая | Высокая | Да, через имя |
| Подпись | Высокая | Высокая | Да |
| Плейсхолдер | Средняя | Высокая | Да |
| Текст | Средняя | Высокая | Да |
| Альтернативный текст, title | Средняя | Средняя | Да |
| Test id | Высокая | Средняя | Нет |
| Id | Высокая, если id стабилен | Средняя | Нет |
| CSS по классам и структуре | Низкая | Низкая | Нет |
| XPath по структуре | Низкая | Низкая | Нет |
| XPath или CSS по тексту или атрибуту | Средняя | Низкая | Зависит от атрибута |
Оценка следует из того, на что ссылается каждый локатор. Роль и доступное имя меняются, только когда элемент становится другим элементом управления или получает другое имя, - такое изменение замечает и пользователь. Test id меняется, только если его кто-то правит, но пользователю он не виден, поэтому тест, который на него опирается, не заметит, что кнопка потеряла подпись. Классы и положение меняются при рефакторинге и правке стилей без всякого изменения поведения.
Локатор, который зависит от текстов, ломается при правке текста. Это желательно, когда текст - часть того, что проверяет тест, и нежелательно, когда текст - только способ найти элемент. Id, которые генерируются во время работы, например из счётчика, делают локаторы по id неустойчивыми: значение может отличаться между загрузками страницы.
Как выбрать локатор
Рекомендации Playwright, Testing Library и Selenium сводятся к общему порядку предпочтения:
- Роль и доступное имя - для интерактивных элементов: кнопок, ссылок, полей, флажков.
- Подпись - для полей форм; текст - для неинтерактивных элементов.
- Плейсхолдер, альтернативный текст, title - когда у элемента нет роли, подписи или текста.
- Test id - когда видимых пользователю атрибутов нет, они неоднозначны или меняются по причинам, не связанным с тестом.
- CSS или XPath - когда ничего другого не подходит, например в сторонней разметке, которую нельзя изменить; лучше по стабильным атрибутам, чем по структуре.
Локатор, который совпадает с несколькими элементами, сужают цепочкой от уникального контейнера, а не по положению. Если локатор по роли написать нельзя, это сигнал о самой странице: элементом без доступного имени трудно пользоваться и с программой чтения с экрана. Генератор кода Playwright, npx playwright codegen, выбирает локаторы по тому же приоритету: роль, текст и test id.
Ожидание элементов
Локатор описывает элемент, которого может ещё не быть. После нажатия страница отправляет запрос, получает ответ и отрисовывает новое содержимое, и тест должен этого дождаться. Способ ожидания - одна из причин тестов, которые падают без дефекта; такие тесты описаны в статье Нестабильные (flaky) тесты.
Автоожидание (auto-waiting) в Playwright означает, что перед каждым действием инструмент ждёт, пока элемент пройдёт проверки готовности к действию (actionability checks). Для click это проверки: элемент видим, стабилен, получает события и включён; для fill - видим, включён и редактируем. Если проверки не проходят за время тайм-аута, действие завершается ошибкой TimeoutError. Локатор Playwright разрешается в элемент при каждом использовании. Проверки вида expect(locator).toBeVisible() ждут выполнения условия, а locator.isVisible() возвращает результат сразу.
В Selenium два механизма. Неявное ожидание (implicit wait) - глобальная настройка на всю сессию: каждый поиск элемента повторяется до тайм-аута; значение по умолчанию 0, поэтому отсутствующий элемент сразу вызывает ошибку. Явное ожидание (explicit wait) - цикл в коде теста, который опрашивает конкретное условие, пока оно не станет истинным или не истечёт тайм-аут. Документация Selenium предупреждает, что смешивать их нельзя: время ожидания становится непредсказуемым.
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
wait = WebDriverWait(driver, timeout=5)
heading = wait.until(
lambda d: d.find_element(By.XPATH, "//h1[normalize-space()='My orders']")
)
assert heading.is_displayed()WebDriverWait.until вызывает функцию, пока она не вернёт истинное значение, и выбрасывает TimeoutException, когда истекает тайм-аут; по умолчанию между попытками он игнорирует NoSuchElementException. WebElement, найденный Selenium, ссылается на один конкретный элемент: если страница перерисует его, ссылка устаревает, и элемент нужно найти заново. В Testing Library похожее разделение: запросы getBy возвращают результат сразу, запросы findBy возвращают промис и повторяют поиск до тайм-аута по умолчанию 1000 мс.
Локаторы и локализация
Локаторы по роли, подписи, плейсхолдеру, тексту, альтернативному тексту и title зависят от текстов интерфейса, а тексты в разных языках разные. Тест с getByRole('button', { name: 'Sign in' }) упадёт на немецкой версии страницы, где у кнопки другое имя. Собственный пример Playwright для test id показывает этот случай: кнопка с текстом Itinéraire и атрибутом data-testid="directions", которую getByTestId('directions') находит независимо от языка.
У набора, который выполняется на нескольких локалях, есть несколько вариантов:
- одна локаль для большинства тестов: Playwright задаёт локаль браузера параметром
localeв конфигурации, напримерlocale: 'en-GB'; - тексты из ресурсов перевода: тест берёт ожидаемое имя из тех же файлов перевода, что и приложение, и локатор следует за переводом;
- test id для элементов, текст которых меняется: локатор не зависит от языка, а тексты проверяет отдельная проверка;
- регулярные выражения в
nameилиgetByText, когда вариантов немного и они известны.
Выбор зависит и от того, что проверяет тест. Тест самого перевода должен опираться на тексты; тест бизнес-сценария - не обязан.
Локаторы в репозитории объектов
Когда один элемент используется во многих тестах, его локатор записывают один раз и обращаются к нему по имени. Изменение разметки тогда исправляется в одном месте, а не в каждом тесте. Два способа так организовать локаторы - объекты страниц (page objects) и репозиторий объектов со структурой «страницы - элементы» - описаны в статье Репозиторий объектов и Page Object. Каким уровням тестов локаторы нужны вообще, объясняет статья Уровни автоматизации и пирамида тестов.
Локатор, селектор и ссылка на элемент
Три слова обозначают связанные, но разные вещи.
| Селектор | Локатор | Ссылка на элемент | |
|---|---|---|---|
| Что это | Строка в синтаксисе CSS или XPath | Стратегия и значение для поиска элемента | Ссылка на один найденный элемент |
| Пример | button.btn-primary |
getByRole('button', { name: 'Sign in' }) |
WebElement в Selenium, ElementHandle в Playwright |
| Когда разрешается | При использовании | При каждом использовании (Playwright) | Один раз, при поиске |
| После перерисовки | Не применимо | Находит новый элемент | Устаревает |
Селектор - одно из возможных значений локатора: CSS- или XPath-локатор содержит селектор, локатор по роли - нет. Документация Playwright прямо формулирует разницу между двумя последними: ElementHandle указывает на конкретный элемент, а Locator содержит логику того, как элемент получить. Использовать ElementHandle она не рекомендует и предлагает вместо него локаторы и веб-ориентированные проверки (web-first assertions).
Термины
| Термин | Значение |
|---|---|
| Локатор (locator) | Описание того, как UI-автотест находит элемент: стратегия и значение |
| Стратегия поиска (location strategy) | Способ найти элемент: по роли, подписи, тексту, test id, CSS, XPath и другие |
| Доступное имя (accessible name) | Имя элемента пользовательского интерфейса, которое получают вспомогательные технологии |
| Test id | Атрибут, который добавляют в разметку только для того, чтобы тесты могли найти элемент, по умолчанию data-testid |
| Автоожидание (auto-waiting) | Ожидание инструментом перед каждым действием, пока элемент не пройдёт проверки готовности к действию |
| Неявное ожидание (implicit wait) | Глобальная настройка сессии, при которой каждый поиск элемента повторяется до тайм-аута |
| Явное ожидание (explicit wait) | Цикл в коде теста, который опрашивает конкретное условие, пока оно не станет истинным или не истечёт тайм-аут |
| Устаревшая ссылка на элемент (stale element reference) | Ссылка на элемент, который был найден раньше, но больше не находится в документе |
Источники
- WebDriver, W3C Recommendation, 5 June 2018(откроется в новой вкладке) (на английском)
- WebDriver Level 2, W3C Working Draft(откроется в новой вкладке) (на английском)
- Accessible Rich Internet Applications (WAI-ARIA) 1.2, W3C Recommendation, 6 June 2023(откроется в новой вкладке) (на английском)
- ARIA in HTML, W3C Recommendation(откроется в новой вкладке) (на английском)
- XML Path Language (XPath) Version 1.0, W3C Recommendation, 16 November 1999(откроется в новой вкладке) (на английском)
- Locators, Playwright documentation(откроется в новой вкладке) (на английском)
- Best Practices, Playwright documentation(откроется в новой вкладке) (на английском)
- Auto-waiting, Playwright documentation(откроется в новой вкладке) (на английском)
- Page class reference, Playwright documentation(откроется в новой вкладке) (на английском)
- ElementHandle class reference, Playwright documentation(откроется в новой вкладке) (на английском)
- Emulation, Playwright documentation(откроется в новой вкладке) (на английском)
- About Queries, Testing Library documentation(откроется в новой вкладке) (на английском)
- Guiding Principles, Testing Library documentation(откроется в новой вкладке) (на английском)
- Locator strategies, Selenium documentation(откроется в новой вкладке) (на английском)
- Locators, encouraged test practices, Selenium documentation(откроется в новой вкладке) (на английском)
- Waiting strategies, Selenium documentation(откроется в новой вкладке) (на английском)
- Understanding common errors, Selenium documentation(откроется в новой вкладке) (на английском)
- selenium.webdriver.common.by, Selenium Python API(откроется в новой вкладке) (на английском)