Стратегии поиска элементов, встроенные локаторы, цепочки, работа с текстом и атрибутами
Локатор — это способ описать, какой элемент на странице вы хотите найти. Правильный локатор — основа стабильного теста. Хрупкий локатор — главная причина падающих тестов.
Playwright рекомендует строгий приоритет при выборе способа поиска элемента:
| Приорит | Метод | Когда использовать |
|---|---|---|
| 1 | get_by_role() | Кнопки, ссылки, заголовки — любой элемент с ARIA-ролью |
| 2 | get_by_text() | Поиск по видимому тексту на странице |
| 3 | get_by_label() | Поля форм, связанные с <label> |
| 4 | get_by_placeholder() | Поля с атрибутом placeholder |
| 5 | get_by_test_id() | Кастомные элементы без семантики |
| 6 | get_by_alt_text() | Изображения с alt |
| 7 | get_by_title() | Элементы с атрибутом title |
| 8 | locator() | CSS-селекторы или XPath как крайний случай |
Главное правило: используйте тот же способ, которым пользователь нашёл бы элемент на странице. Пользователь видит кнопку «Сохранить» — используйте get_by_role('button', name='Сохранить'), а не CSS-селектор .btn-primary.
get_by_role() ищет элементы по их ARIA-роли. Это самый устойчивый и семантичный способ.
# Кнопка с текстом
await page.get_by_role('button', name='Сохранить').click()
# Ссылка
await page.get_by_role('link', name='Профиль').click()
# Заголовок
heading = page.get_by_role('heading', name='Настройки')
# Чекбокс
await page.get_by_role('checkbox', name='Запомнить меня').check()Параметр name поддерживает точное совпадение, подстроку и регулярное выражение:
# Точное совпадение
page.get_by_role('button', name='Отправить')
# Подстрока
page.get_by_role('button', name='Отпр')
# Регулярное выражение
page.get_by_role('button', name=re.compile(r'отпр', re.IGNORECASE))Важно: роль элемента определяется его HTML-тегом. <button> имеет роль button, <a href> — роль link, <h1> — роль heading. Если элементу задана role="..." явно — используется она.
Все локаторы в Playwright строгие. Если локатор находит более одного элемента, действие выбросит ошибку:
# На странице 5 кнопок 'Удалить'
await page.get_by_role('button', name='Удалить').click()
# Error: strict mode violation: locator resolved to 5 elements >Это не баг — это защита. Тест не должен случайно кликнуть не туда.
Как исправить:
# 1. Сузить контекст
card = page.locator('.product-card').first
await card.get_by_role('button', name='Удалить').click()
# 2. Использовать .first / .last
await page.get_by_role('button', name='Удалить').first.click()
# 3. Использовать .nth()
await page.get_by_role('button', name='Удалить').nth(2).click()
# 4. Добавить фильтрацию
await page.get_by_role('button', name='Удалить').filter(
has_text='Комментарий'
).click()Рекомендация: используйте
.firstтолько если уверены, что порядок элементов стабилен. Лучше сузить контекст через родительский локатор.
Локаторы можно комбинировать. Вызовите get_by_* или locator() на другом локаторе — поиск будет внутри найденного элемента:
# Найти карточку товара, внутри неё — кнопку
card = page.locator('.product-card', has_text='Playwright в действии')
await card.get_by_role('button', name='В корзину').click()
# Найти таблицу, внутри неё — строку
table = page.get_by_role('table')
row = table.locator('tr', has_text='admin@example.com')
await row.get_by_role('button', name='Редактировать').click()Параметр has в locator() позволяет найти элемент, содержащий другой элемент:
# Найти article, который содержит кнопку 'Delete'
article = page.locator('article', has=page.get_by_role('button', name='Delete'))get_by_text() ищет по видимому тексту:
# Найти элемент с текстом
await page.get_by_text('Добро пожаловать!').click()
# Регистр важен
page.get_by_text('save') # не найдёт 'Save'
# Регулярное выражение для нечувствительного поиска
page.get_by_text(re.compile(r'save', re.IGNORECASE))get_by_label() — для полей форм, связанных с <label>:
<label for="email">Email</label>
<input id="email" type="email" />await page.get_by_label('Email').fill('user@example.com')page.locator() принимает CSS-селектор или XPath:
# CSS-селектор
await page.locator('.btn.btn-primary').click()
await page.locator('#main-nav > li').click()
# XPath (когда CSS недостаточен)
await page.locator('xpath=//div[contains(@class, "card")]').click()CSS быстрее и читаемее XPath. Используйте XPath только когда нужна навигация по дереву:
# Найти родительский элемент
parent = page.locator('xpath=//button[contains(., "Delete")]/ancestor::div')
# Найти соседний элемент
sibling = page.locator('xpath=//td[text()="Price"]/following-sibling::td')# Установка атрибута в HTML
# <button data-testid="submit-btn">Отправить</button>
# В Playwright
await page.get_by_test_id('submit-btn').click()data-testid не зависит от текста, CSS-классов и ARIA-ролей. Но он не отражает пользовательский опыт — реальный пользователь не видит этот атрибут.
Когда использовать data-testid:
Когда НЕ использовать:
get_by_role()get_by_text()Playwright предоставляет встроенные assertion'ы через expect:
from playwright.sync_api import expect
# Видимость
expect(page.locator('#success-msg')).to_be_visible()
# Текст
expect(page.locator('h1')).to_have_text('Добро пожаловать!')
# Атрибут
expect(page.locator('input')).to_have_value('user@email.com')
# CSS-класс
expect(page.locator('.alert')).to_have_class('alert alert-success')
# Количество элементов
expect(page.locator('.item')).to_have_count(5)
# Отрицание
expect(page.locator('.error')).not_to_be_visible()Каждый assertion автоматически ждёт выполнения условия до таймаута (по умолчанию 5 секунд). Не нужно time.sleep().
❌ Использование XPath для всего. XPath медленнее CSS и менее читаем. Используйте его только для навигации по дереву (родители, соседи).
❌ Игнорирование strict mode. .first — это быстрый фикс, но не решение. Сузьте контекст через родительский элемент.
❌ Локаторы по CSS-классам стилей. Класс .btn-primary может измениться при редизайне. Роль button не изменится.
✅ Правильный подход: get_by_role() → get_by_text() → get_by_label() → get_by_test_id() → locator().
https://demo.playwright.dev/todomvc/ через Codegen. Посмотрите, какие локаторы генерирует Playwright для добавления задачи.locator(..., has=...) для нахождения карточки, содержащей определённый текст, и кликните кнопку внутри неё.Перейдите к работе с формами, чтобы освоить заполнение полей, чекбоксов, выпадающих списков и отправку форм.
Вопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.