Перейти к основному содержимому

Локаторы

Введение

[Локаторы] являются центральной частью автоматического ожидания и возможности повторных попыток в Playwright. Вкратце, локаторы представляют собой способ найти элемент(ы) на странице в любой момент времени.

Быстрый гайд

Это рекомендуемые встроенные локаторы.

  • page.get_by_role() для поиска по явным и неявным атрибутам доступности.
  • page.get_by_text() для поиска по текстовому содержимому.
  • page.get_by_label() для поиска элемента управления формой по тексту связанной метки.
  • page.get_by_placeholder() для поиска ввода по заполнителю.
  • page.get_by_alt_text() для поиска элемента, обычно изображения, по его текстовой альтернативе.
  • page.get_by_title() для поиска элемента по его атрибуту title.
  • page.get_by_test_id() для поиска элемента на основе его атрибута data-testid (другие атрибуты могут быть настроены).
page.get_by_label("User Name").fill("John")

page.get_by_label("Password").fill("secret-password")

page.get_by_role("button", name="Sign in").click()

expect(page.get_by_text("Welcome, John!")).to_be_visible()

Поиск элементов

Playwright предоставляет несколько встроенных локаторов. Чтобы сделать тесты устойчивыми, мы рекомендуем отдавать приоритет атрибутам, ориентированным на пользователя, и явным контрактам, таким как page.get_by_role().

Например, рассмотрим следующую структуру DOM.

http://localhost:3000
<button>Sign in</button>

Найдите элемент по его роли button с именем "Sign in".

page.get_by_role("button", name="Sign in").click()
примечание

Используйте генератор кода для генерации локатора, а затем отредактируйте его по своему усмотрению.

Каждый раз, когда локатор используется для действия, актуальный элемент DOM находится на странице. В приведенном ниже фрагменте кода базовый элемент DOM будет найден дважды, один раз перед каждым действием. Это означает, что если DOM изменится между вызовами из-за повторного рендеринга, будет использован новый элемент, соответствующий локатору.

locator = page.get_by_role("button", name="Sign in")

locator.hover()
locator.click()

Обратите внимание, что все методы, создающие локатор, такие как page.get_by_label(), также доступны в классах Locator и FrameLocator, поэтому вы можете объединять их и постепенно уточнять ваш локатор.

locator = page.frame_locator("my-frame").get_by_role("button", name="Sign in")

locator.click()

Поиск по роли

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

Например, рассмотрим следующую структуру DOM.

http://localhost:3000

Sign up

<h3>Sign up</h3>
<label>
<input type="checkbox" /> Subscribe
</label>
<br/>
<button>Submit</button>

Вы можете найти каждый элемент по его неявной роли:

expect(page.get_by_role("heading", name="Sign up")).to_be_visible()

page.get_by_role("checkbox", name="Subscribe").check()

page.get_by_role("button", name=re.compile("submit", re.IGNORECASE)).click()

Локаторы ролей включают кнопки, флажки, заголовки, ссылки, списки, таблицы и многие другие и следуют спецификациям W3C для роли ARIA, атрибутов ARIA и доступного имени. Обратите внимание, что многие HTML-элементы, такие как <button>, имеют неявно определенную роль, которая распознается локатором роли.

Обратите внимание, что локаторы ролей не заменяют аудиты доступности и тесты на соответствие, но дают раннюю обратную связь о руководствах ARIA.

Когда использовать локаторы ролей

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

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

Большинство элементов управления формами обычно имеют выделенные метки, которые можно удобно использовать для взаимодействия с формой. В этом случае вы можете найти элемент управления по его связанной метке, используя page.get_by_label().

Например, рассмотрим следующую структуру DOM.

http://localhost:3000
<label>Password <input type="password" /></label>

Вы можете заполнить ввод после его поиска по тексту метки:

page.get_by_label("Password").fill("secret")
Когда использовать локаторы меток

Используйте этот локатор при поиске полей формы.

Поиск по заполнителю

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

Например, рассмотрим следующую структуру DOM.

http://localhost:3000
<input type="email" placeholder="name@example.com" />

Вы можете заполнить ввод после его поиска по тексту заполнителя:

page.get_by_placeholder("name@example.com").fill("playwright@microsoft.com")
Когда использовать локаторы заполнителей

Используйте этот локатор при поиске элементов формы, у которых нет меток, но есть текст заполнителя.

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

Найдите элемент по тексту, который он содержит. Вы можете сопоставить по подстроке, точной строке или регулярному выражению, используя page.get_by_text().

Например, рассмотрим следующую структуру DOM.

http://localhost:3000
Welcome, John
<span>Welcome, John</span>

Вы можете найти элемент по тексту, который он содержит:

expect(page.get_by_text("Welcome, John")).to_be_visible()

Установите точное совпадение:

expect(page.get_by_text("Welcome, John", exact=True)).to_be_visible()

Совпадение с регулярным выражением:

expect(page.get_by_text(re.compile("welcome, john", re.IGNORECASE))).to_be_visible()
примечание

Сопоставление по тексту всегда нормализует пробелы, даже при точном совпадении. Например, оно превращает несколько пробелов в один, превращает разрывы строк в пробелы и игнорирует начальные и конечные пробелы.

Когда использовать локаторы текста

Мы рекомендуем использовать локаторы текста для поиска неинтерактивных элементов, таких как div, span, p и т.д. Для интерактивных элементов, таких как button, a, input и т.д., используйте локаторы ролей.

Вы также можете фильтровать по тексту, что может быть полезно при попытке найти конкретный элемент в списке.

Поиск по альтернативному тексту

Все изображения должны иметь атрибут alt, который описывает изображение. Вы можете найти изображение на основе текстовой альтернативы, используя page.get_by_alt_text().

Например, рассмотрим следующую структуру DOM.

http://localhost:3000
playwright logo
<img alt="playwright logo" src="/img/playwright-logo.svg" width="100" />

Вы можете кликнуть на изображение после его нахождения по текстовой альтернативе:

page.get_by_alt_text("playwright logo").click()
Когда использовать локаторы по alt

Используйте этот локатор, когда ваш элемент поддерживает альтернативный текст, например, элементы img и area.

Поиск по заголовку

Найдите элемент с соответствующим атрибутом title, используя page.get_by_title().

Например, рассмотрим следующую структуру DOM.

http://localhost:3000
25 issues
<span title='Issues count'>25 issues</span>

Вы можете проверить количество проблем после его нахождения по тексту заголовка:

expect(page.get_by_title("Issues count")).to_have_text("25 issues")
Когда использовать локаторы по заголовку

Используйте этот локатор, когда ваш элемент имеет атрибут title.

Поиск по тестовому идентификатору

Тестирование по тестовым идентификаторам является наиболее устойчивым способом тестирования, так как даже если ваш текст или роль атрибута изменится, тест все равно пройдет. QA и разработчики должны определять явные тестовые идентификаторы и запрашивать их с помощью page.get_by_test_id(). Однако тестирование по тестовым идентификаторам не ориентировано на пользователя. Если роль или текстовое значение важны для вас, рассмотрите возможность использования локаторов, ориентированных на пользователя, таких как role и text locators.

Например, рассмотрим следующую структуру DOM.

http://localhost:3000
<button data-testid="directions">Itinéraire</button>

Вы можете найти элемент по его тестовому идентификатору:

page.get_by_test_id("directions").click()
Когда использовать локаторы по testid

Вы также можете использовать тестовые идентификаторы, когда выбираете методологию тестовых идентификаторов или когда не можете найти по role или text.

Установите пользовательский атрибут тестового идентификатора

По умолчанию, page.get_by_test_id() будет находить элементы на основе атрибута data-testid, но вы можете настроить его в конфигурации теста или вызвав selectors.set_test_id_attribute().

Установите тестовый идентификатор для использования пользовательского атрибута данных в ваших тестах.

playwright.selectors.set_test_id_attribute("data-pw")

В вашем HTML теперь вы можете использовать data-pw в качестве тестового идентификатора вместо стандартного data-testid.

http://localhost:3000
<button data-pw="directions">Itinéraire</button>

А затем найдите элемент, как обычно:

page.get_by_test_id("directions").click()

Поиск по CSS или XPath

Если вам абсолютно необходимо использовать локаторы CSS или XPath, вы можете использовать page.locator() для создания локатора, который принимает селектор, описывающий, как найти элемент на странице. Playwright поддерживает селекторы CSS и XPath и автоматически определяет их, если вы опускаете префикс css= или xpath=.

page.locator("css=button").click()
page.locator("xpath=//button").click()

page.locator("button").click()
page.locator("//button").click()

XPath и CSS селекторы могут быть привязаны к структуре DOM или реализации. Эти селекторы могут сломаться, когда структура DOM изменяется. Длинные цепочки CSS или XPath ниже являются примером плохой практики, которая приводит к нестабильным тестам:

page.locator(
"#tsf > div:nth-child(2) > div.A8SBwf > div.RNNXgb > div > div.a4bIc > input"
).click()

page.locator('//*[@id="tsf"]/div[2]/div[1]/div[1]/div/div[2]/input').click()
Когда использовать это

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

Поиск в Shadow DOM

Все локаторы в Playwright по умолчанию работают с элементами в Shadow DOM. Исключения составляют:

Рассмотрим следующий пример с пользовательским веб-компонентом:

<x-details role=button aria-expanded=true aria-controls=inner-details>
<div>Title</div>
#shadow-root
<div id=inner-details>Details</div>
</x-details>

Вы можете найти элемент так же, как если бы shadow root не существовал.

Чтобы кликнуть <div>Details</div>:

page.get_by_text("Details").click()
<x-details role=button aria-expanded=true aria-controls=inner-details>
<div>Title</div>
#shadow-root
<div id=inner-details>Details</div>
</x-details>

Чтобы кликнуть <x-details>:

page.locator("x-details", has_text="Details").click()
<x-details role=button aria-expanded=true aria-controls=inner-details>
<div>Title</div>
#shadow-root
<div id=inner-details>Details</div>
</x-details>

Чтобы убедиться, что <x-details> содержит текст "Details":

expect(page.locator("x-details")).to_contain_text("Details")

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

Рассмотрим следующую структуру DOM, где мы хотим кликнуть на кнопку покупки второй карточки продукта. У нас есть несколько вариантов, чтобы отфильтровать локаторы и получить нужный.

http://localhost:3000
  • Product 1

  • Product 2

<ul>
<li>
<h3>Product 1</h3>
<button>Add to cart</button>
</li>
<li>
<h3>Product 2</h3>
<button>Add to cart</button>
</li>
</ul>

Фильтрация по тексту

Локаторы могут быть отфильтрованы по тексту с помощью метода locator.filter(). Он будет искать определенную строку где-то внутри элемента, возможно, в дочернем элементе, без учета регистра. Вы также можете передать регулярное выражение.

page.get_by_role("listitem").filter(has_text="Product 2").get_by_role(
"button", name="Add to cart"
).click()

Используйте регулярное выражение:

page.get_by_role("listitem").filter(has_text=re.compile("Product 2")).get_by_role(
"button", name="Add to cart"
).click()

Фильтрация по отсутствию текста

В качестве альтернативы, фильтруйте по отсутствию текста:

# 5 товаров в наличии
expect(page.get_by_role("listitem").filter(has_not_text="Out of stock")).to_have_count(5)

Фильтрация по дочернему/потомку

Локаторы поддерживают опцию выбора только тех элементов, которые имеют или не имеют потомка, соответствующего другому локатору. Таким образом, вы можете фильтровать по любому другому локатору, такому как locator.get_by_role(), locator.get_by_test_id(), locator.get_by_text() и т.д.

http://localhost:3000
  • Product 1

  • Product 2

<ul>
<li>
<h3>Product 1</h3>
<button>Add to cart</button>
</li>
<li>
<h3>Product 2</h3>
<button>Add to cart</button>
</li>
</ul>
page.get_by_role("listitem").filter(
has=page.get_by_role("heading", name="Product 2")
).get_by_role("button", name="Add to cart").click()

Мы также можем проверить карточку продукта, чтобы убедиться, что она только одна:

expect(
page.get_by_role("listitem").filter(
has=page.get_by_role("heading", name="Product 2")
)
).to_have_count(1)

Фильтрующий локатор должен быть относительным к исходному локатору и запрашивается начиная с совпадения исходного локатора, а не с корня документа. Поэтому следующее не сработает, потому что фильтрующий локатор начинает совпадать с элементом списка <ul>, который находится вне элемента списка <li>, совпадающего с исходным локатором:

# ✖ НЕПРАВИЛЬНО
expect(
page.get_by_role("listitem").filter(
has=page.get_by_role("list").get_by_role("heading", name="Product 2")
)
).to_have_count(1)

Фильтрация по отсутствию дочернего/потомка

Мы также можем фильтровать по отсутствию соответствующего элемента внутри.

expect(
page.get_by_role("listitem").filter(
has_not=page.get_by_role("heading", name="Product 2")
)
).to_have_count(1)

Обратите внимание, что внутренний локатор совпадает, начиная с внешнего, а не с корня документа.

Операторы локаторов

Совпадение внутри локатора

Вы можете объединять методы, создающие локатор, такие как page.get_by_text() или locator.get_by_role(), чтобы сузить поиск до определенной части страницы.

В этом примере мы сначала создаем локатор под названием product, находя его роль listitem. Затем мы фильтруем по тексту. Мы можем снова использовать локатор product, чтобы получить роль кнопки и нажать на нее, а затем использовать утверждение, чтобы убедиться, что есть только один продукт с текстом "Product 2".

product = page.get_by_role("listitem").filter(has_text="Product 2")

product.get_by_role("button", name="Add to cart").click()

Вы также можете объединить два локатора вместе, например, чтобы найти кнопку "Сохранить" внутри определенного диалога:

save_button = page.get_by_role("button", name="Save")
# ...
dialog = page.get_by_test_id("settings-dialog")
dialog.locator(save_button).click()

Совпадение двух локаторов одновременно

Метод locator.and_() сужает существующий локатор, сопоставляя дополнительный локатор. Например, вы можете объединить page.get_by_role() и page.get_by_title(), чтобы сопоставить как по роли, так и по заголовку.

button = page.get_by_role("button").and_(page.getByTitle("Subscribe"))

Совпадение одного из двух альтернативных локаторов

Если вы хотите нацелиться на один из двух или более элементов, и вы не знаете, какой из них это будет, используйте locator.or_(), чтобы создать локатор, который соответствует любому из них или обоим альтернативам.

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

примечание

Если и кнопка "Новое письмо", и диалог безопасности появляются на экране, локатор "или" будет соответствовать обоим из них, возможно, вызывая ошибку "strict mode violation". В этом случае вы можете использовать locator.first, чтобы соответствовать только одному из них.

new_email = page.get_by_role("button", name="New")
dialog = page.get_by_text("Confirm security settings")
expect(new_email.or_(dialog).first).to_be_visible()
if (dialog.is_visible()):
page.get_by_role("button", name="Dismiss").click()
new_email.click()

Поиск только видимых элементов

примечание

Обычно лучше найти более надежный способ уникально идентифицировать элемент, чем проверять видимость.

Рассмотрим страницу с двумя кнопками: первая невидима, а вторая видима.

<button style='display: none'>Invisible</button>
<button>Visible</button>
  • Это найдет обе кнопки и вызовет ошибку строгости:

    page.locator("button").click()
  • Это найдет только вторую кнопку, так как она видима, и затем кликнет по ней.

    page.locator("button").filter(visible=True).click()

Списки

Подсчет элементов в списке

Вы можете использовать локаторы, чтобы подсчитать количество элементов в списке.

Например, рассмотрим следующую структуру DOM:

http://localhost:3000
  • apple
  • banana
  • orange
<ul>
<li>apple</li>
<li>banana</li>
<li>orange</li>
</ul>

Используйте утверждение count, чтобы убедиться, что в списке 3 элемента.

expect(page.get_by_role("listitem")).to_have_count(3)

Утверждение всех текстов в списке

Вы можете использовать локаторы, чтобы найти все тексты в списке.

Например, рассмотрим следующую структуру DOM:

http://localhost:3000
  • apple
  • banana
  • orange
<ul>
<li>apple</li>
<li>banana</li>
<li>orange</li>
</ul>

Используйте expect(locator).to_have_text(), чтобы убедиться, что в списке есть тексты "apple", "banana" и "orange".

expect(page.get_by_role("listitem")).to_have_text(["apple", "banana", "orange"])

Получение конкретного элемента

Существует множество способов получить конкретный элемент в списке.

Получение по тексту

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

Например, рассмотрим следующую структуру DOM:

http://localhost:3000
  • apple
  • banana
  • orange
<ul>
<li>apple</li>
<li>banana</li>
<li>orange</li>
</ul>

Найдите элемент по его текстовому содержимому и кликните по нему.

page.get_by_text("orange").click()

Фильтрация по тексту

Используйте locator.filter(), чтобы найти конкретный элемент в списке.

Например, рассмотрим следующую структуру DOM:

http://localhost:3000
  • apple
  • banana
  • orange
<ul>
<li>apple</li>
<li>banana</li>
<li>orange</li>
</ul>

Найдите элемент по роли "listitem", затем отфильтруйте по тексту "orange" и кликните по нему.

page.get_by_role("listitem").filter(has_text="orange").click()

Получение по тестовому идентификатору

Используйте метод page.get_by_test_id(), чтобы найти элемент в списке. Возможно, вам потребуется изменить HTML и добавить тестовый идентификатор, если у вас его еще нет.

Например, рассмотрим следующую структуру DOM:

http://localhost:3000
  • apple
  • banana
  • orange
<ul>
<li data-testid='apple'>apple</li>
<li data-testid='banana'>banana</li>
<li data-testid='orange'>orange</li>
</ul>

Найдите элемент по его тестовому идентификатору "orange" и кликните по нему.

page.get_by_test_id("orange").click()

Получение по порядковому номеру

Если у вас есть список идентичных элементов, и единственный способ отличить их - это порядок, вы можете выбрать конкретный элемент из списка с помощью locator.first, locator.last или locator.nth().

banana = page.get_by_role("listitem").nth(1)

Однако используйте этот метод с осторожностью. Часто страница может измениться, и локатор будет указывать на совершенно другой элемент, чем вы ожидали. Вместо этого постарайтесь придумать уникальный локатор, который пройдет критерии строгости.

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

Когда у вас есть элементы с различными сходствами, вы можете использовать метод locator.filter(), чтобы выбрать нужный. Вы также можете объединять несколько фильтров, чтобы сузить выбор.

Например, рассмотрим следующую структуру DOM:

http://localhost:3000
  • John
  • Mary
  • John
  • Mary
<ul>
<li>
<div>John</div>
<div><button>Say hello</button></div>
</li>
<li>
<div>Mary</div>
<div><button>Say hello</button></div>
</li>
<li>
<div>John</div>
<div><button>Say goodbye</button></div>
</li>
<li>
<div>Mary</div>
<div><button>Say goodbye</button></div>
</li>
</ul>

Чтобы сделать скриншот строки с "Mary" и "Say goodbye":

row_locator = page.get_by_role("listitem")

row_locator.filter(has_text="Mary").filter(
has=page.get_by_role("button", name="Say goodbye")
).screenshot(path="screenshot.png")

Теперь у вас должен быть файл "screenshot.png" в корневом каталоге вашего проекта.

Редкие случаи использования

Выполнение действий с каждым элементом в списке

Итерация по элементам:

for row in page.get_by_role("listitem").all():
print(row.text_content())

Итерация с использованием обычного цикла for:

rows = page.get_by_role("listitem")
count = rows.count()
for i in range(count):
print(rows.nth(i).text_content())

Выполнение кода на странице

Код внутри locator.evaluate_all() выполняется на странице, там можно вызывать любые DOM API.

rows = page.get_by_role("listitem")
texts = rows.evaluate_all("list => list.map(element => element.textContent)")

Строгость

Локаторы являются строгими. Это означает, что все операции с локаторами, которые подразумевают некоторый целевой элемент DOM, вызовут исключение, если будет найдено более одного элемента. Например, следующий вызов вызовет ошибку, если в DOM несколько кнопок:

Вызывает ошибку, если более одного элемента

page.get_by_role("button").click()

С другой стороны, Playwright понимает, когда вы выполняете операцию с несколькими элементами, поэтому следующий вызов работает нормально, когда локатор разрешается в несколько элементов.

Работает нормально с несколькими элементами

page.get_by_role("button").count()

Вы можете явно отказаться от проверки строгости, указав Playwright, какой элемент использовать, когда несколько элементов совпадают, через locator.first, locator.last и locator.nth(). Эти методы не рекомендуются, потому что, когда ваша страница изменяется, Playwright может нажать на элемент, который вы не планировали. Вместо этого следуйте лучшим практикам, чтобы создать локатор, который уникально идентифицирует целевой элемент.

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

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