Зарегистрируйтесь, чтобы продолжить обучение

Локаторы Тестирование с Playwright

Локаторы - это механизм для нахождения и взаимодействия с элементами на веб-странице. Они позволяют точно указывать, с какими элементами нужно работать в процессе автоматизации тестов. Посмотрим на локатор, с которым мы уже встречались. Здесь мы выбираем элемент формы по его плейсхолдеру. В результате возвращается не DOM-элемент, а объект типа Locator. Ниже поговорим о том, почему это важно.

const input = page.getByPlaceholder('What needs to be done?')

// Что то делаем
await input.click() // кликаем на поле
await input.fill('open hexlet') // заполняем

Локаторы устроены хитрее чем кажется на первый взгляд. Предположим, что элемента с таким плейсхолдером на странице не существует. Как эта ошибка будет показана? Ошибка будет указывать не на строчку с методом getByPlaceholder(), а на ту строчку, где этот локатор использовался в первый раз, в примере выше это input.click(). То есть получается, что метод getByPlaceholder() не выполняет реальное действие, он его запоминает, чтобы выполнить позже. Более того, если элемент был найден и выполнение теста дошло до второго события input.fill(), то локатор начнет искать элемент на странице заново. То есть это будет происходить каждый раз во время взаимодействия с этим элементом.

// Элемент не ищется, запрос запоминается внутри объекта input
const input = page.getByPlaceholder('What needs to be done?')

// Выполняется поиск элемента с плейсхолдером на странице
await input.click() // кликаем на поле
// Снова выполняется поиск элемента с плейсхолдером на странице
await input.fill('open hexlet') // заполняем

Но и это еще не все. Локаторы автоматически дожидаются появления элемента на экране, что, обычно, происходит не моментально из-за асинхронности. Если элемент не найден, то через интервал установленный в конфигурации, выполняется повторный поиск до тех пор, пока не будет достигнут таймаут. Если таймаут прошел, то выбрасывается исключение. Этот механизм называется auto-waiting.

Локаторы работают в строгом режиме. Это означает, что если было найдено больше одного элемента, там где ожидается один, будет выброшено исключение. Количество ожидаемых элементов определяется тем, как локатор используется.

const input = page.getByPlaceholder('What needs to be done?')

// Если инпутов с таким плейсхолдером больше одного, то будет исключение
await input.click()

Вызов click() подразумевает выборку одного элемента. Если элементов ноль или больше одного, то мы получим исключение. Playwright так же автоматически определяет ситуации, когда ожидается больше одного элемента.

const buttonsCount = await page.getByRole('button').count()

Популярные локаторы

Playwright содержит множество встроенных локаторов. Ниже мы разберем некоторые из них, в порядке приоритета использования. Самый первый, самый правильный если подходит.

Поиск по роли

Метод page.getByRole() соответствует тому, как пользователи или ридеры (для людей с ограниченными возможностями) взаимодействуют со страницей, поэтому является приоритетным способом поиска элементов на странице. Список ролей можно подсматривать тут.

Для поиска по роли нужно два параметра. Название роли и имя конкретного элемента (accessible name). В некоторых случаях этим именем будет значение атрибута name, а в некоторых текст расположенный внутри тега, например у заголовков.

await page.getByRole('checkbox', { name: 'Подписаться' }).check()
// Работает и с регулярными выражениями
await page.getByRole('button', { name: /submit/i }).click()

// Проверяем что этот заголовок находится внутри DOM
await expect(page.getByRole('heading', { name: 'Хекслет' })).toBeVisible()

Поиск по label

Большинство правильно созданных форм содержат лейблы, по которым можно найти элементы формы используя метод page.getByLabel(). Например:

<label>Пароль <input type="password" /></label>

Локатор:

const password = page.getByLabel('Пароль')
await passwpord.fill('secret')

Поиск по тексту

page.getByText() ищет элемент, по его текстовому содержимому. Это наиболее универсальный метод, через который можно найти почти все что угодно.

// <h1>Хекслет</h1>
const locator = page.getByText('Хекслет')
await expect(locator).toBeVisible()

По умолчанию, поиск ведется по вхождению подстроки. Для точного соответствия нужно добавить опцию exact:

const locator = page.getByText('Хекслет', { exact: true })

Поиск по тексту всегда производит нормализацию текста, убирает двойные пробелы, концевые пробелы, переводы строк и так далее.

Поиск по testid

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

<div data-testid="custom-element" />

После этого заработает поиск:

const element = screen.getByTestId('custom-element')

В отличие от классов и других элементов связанных с HTML, testid используется только для тестов, поэтому хрупкость подобных тестов ниже. Несмотря на это, использовать testid стоит только в крайних случаях, потому что он удаляет нас от работы со страницей в режиме пользователя. В основном, они нужны тогда, когда элемент невозможно извлечь по роли или содержимому.

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

Фильтрация локаторов

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

<ul>
  <li>
    <h2>Python-разработчик</h2>
    <button>Начать</button>
  </li>
  <li>
    <h2>Frontend-разработчик</h2>
    <button>Начать</button>
  </li>
</ul>

Как добраться до второй кнопки? Методы, которые мы рассмотрели, не могут этого сделать, но если мы добавим к ним фильтрацию, то все получится. Например:

const button = page
  .getByRole('listitem')
  .filter({ hasText: 'Frontend-разработчик' })
  .getByRole('button', { name: 'Начать' })
await button.click()

Разберем этот код подробнее. Первым шагом, мы извлекаем все элементы всех списков на странице. В результате возвращается объект Locator. Дальше, с помощью метода filter() мы оставляем только те элементы (<li>), которые содержат текст Frontend-разработчик. Сам текст может находиться в любом месте внутри элемента списка, в примере выше он вложен в теге <h2>. Метод filter(), тоже возвращает объект Locator, который позволяет строить дальнейшую цепочку. С помощью метода getByRole() мы ищем кнопку, внутри элементов списка, которые содержат текст Frontend-разработчик. В конце концов эта цепочка находит ровно один элемент.

Locator позволяет строить цепочки любой сложности, где каждый следующий поиск, происходит среди элементов, вернувшихся на предыдущем этапе. Фактически мы можем комбинировать методы getBy* и filter() в любых конфигурациях и в любом количестве: getByRole().filter().filter().getByText().getByText().filter().


Дополнительные материалы

  1. Официальная документация

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff