Spec-Zone.ru › Playwright

Локаторы

Введение​

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

Быстрый гид​

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

  • page.getByRole() для поиска по явным и неявным атрибутам доступности.
  • page.getByText() для поиска по текстовому содержимому.
  • page.getByLabel() для поиска элемента управления формы по тексту связанного метки.
  • page.getByPlaceholder() для поиска поля ввода по подписи.
  • page.getByAltText() для поиска элемента, обычно изображения, по его альтернативному тексту.
  • page.getByTitle() для поиска элемента по атрибуту title.
  • page.getByTestId() для поиска элемента на основе его data-testid атрибута (другие атрибуты можно настроить).
await page.getByLabel('User Name').fill('John');

await page.getByLabel('Password').fill('secret-password');

await page.getByRole('button', { name: 'Sign in' }).click();

await expect(page.getByText('Welcome, John!')).toBeVisible();

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

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

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

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

Найдите элемент по его роли button с именем «Войти».

await page.getByRole('button', { name: 'Sign in' }).click();
примечание

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

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

const locator = page.getByRole('button', { name: 'Sign in' });

await locator.hover();
await locator.click();

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

const locator = page
    .frameLocator('#my-frame')
    .getByRole('button', { name: 'Sign in' });

await locator.click();

Поиск по роли​

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

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

http://localhost:3000

Регистрация


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

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

await expect(page.getByRole('heading', { name: 'Sign up' })).toBeVisible();

await page.getByRole('checkbox', { name: 'Subscribe' }).check();

await page.getByRole('button', { name: /submit/i }).click();

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

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

В каких случаях следует использовать локаторы ролей

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

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

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

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

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

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

await page.getByLabel('Password').fill('secret');
В каких случаях следует использовать локаторы меток

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

Поиск по подписи​

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

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

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

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

await page
    .getByPlaceholder('name@example.com')
    .fill('playwright@microsoft.com');
В каких случаях следует использовать локаторы подписи

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

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

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

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

http://localhost:3000
Добро пожаловать, Иван
<span>Welcome, John</span>

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

await expect(page.getByText('Welcome, John')).toBeVisible();

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

await expect(page.getByText('Welcome, John', { exact: true })).toBeVisible();

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

await expect(page.getByText(/welcome, [A-Za-z]+$/i)).toBeVisible();
примечание

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

В каких случаях следует использовать локаторы текста

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

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

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

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

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

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

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

await page.getByAltText('playwright logo').click();
В каких случаях следует использовать локаторы альтернативного текста

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

Поиск по атрибуту title​

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

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

http://localhost:3000
25 проблем
<span title='Issues count'>25 issues</span>

Вы можете проверить количество проблем после поиска по тексту атрибута title:

await expect(page.getByTitle('Issues count')).toHaveText('25 issues');
В каких случаях следует использовать локаторы атрибута title

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

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

Поиск по идентификаторам тестов — наиболее надёжный способ тестирования, поскольку даже если текст или роль атрибута изменятся, тест всё равно пройдёт. QA и разработчики должны определить явные идентификаторы тестов и запросить их с помощью page.getByTestId(). Тем не менее, поиск по идентификаторам тестов не ориентирован на пользователя. Если роль или значение текста для вас важны, рассмотрите использование локаторов, ориентированных на пользователя, таких как локаторы ролей и локаторы текста.

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

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

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

await page.getByTestId('directions').click();
В каких случаях следует использовать локаторы testid

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

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

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

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

import { defineConfig } from '@playwright/test';

export default defineConfig({
  use: {
    testIdAttribute: 'data-pw'
  }
});

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

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

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

await page.getByTestId('directions').click();

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

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

await page.locator('css=button').click();
await page.locator('xpath=//button').click();

await page.locator('button').click();
await page.locator('//button').click();

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

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

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

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

Поиск в Shadow DOM​

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

  • Поиск по XPath не проникает в тени корни.
  • Shadow корни в закрытом режиме не поддерживаются.

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

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

Вы можете искать так же, как если бы корня тени не было.

Чтобы нажать <div>Details</div>:

await page.getByText('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>:

await page.locator('x-details', { hasText: '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> содержит текст «Подробности»:

await expect(page.locator('x-details')).toContainText('Details');

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

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

http://localhost:3000
  • Продукт 1

  • Продукт 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(). Он будет искать определённую строку где-то внутри элемента, возможно, в дочернем элементе, регистронезависимо. Вы также можете передать регулярное выражение.

await page
    .getByRole('listitem')
    .filter({ hasText: 'Product 2' })
    .getByRole('button', { name: 'Add to cart' })
    .click();

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

await page
    .getByRole('listitem')
    .filter({ hasText: /Product 2/ })
    .getByRole('button', { name: 'Add to cart' })
    .click();

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

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

// 5 in-stock items
await expect(page.getByRole('listitem').filter({ hasNotText: 'Out of stock' })).toHaveCount(5);

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

Локейторы поддерживают возможность выбора только тех элементов, у которых есть или нет дочерний элемент, соответствующий другому локейтору. Таким образом, вы можете фильтровать по любому другому локейтору, например, locator.getByRole(), locator.getByTestId(), locator.getByText() и т. д.

http://localhost:3000
  • Продукт 1

  • Продукт 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>
await page
    .getByRole('listitem')
    .filter({ has: page.getByRole('heading', { name: 'Product 2' }) })
    .getByRole('button', { name: 'Add to cart' })
    .click();

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

await expect(page
    .getByRole('listitem')
    .filter({ has: page.getByRole('heading', { name: 'Product 2' }) }))
    .toHaveCount(1);

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

// ✖ WRONG
await expect(page
    .getByRole('listitem')
    .filter({ has: page.getByRole('list').getByText('Product 2') }))
    .toHaveCount(1);

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

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

await expect(page
    .getByRole('listitem')
    .filter({ hasNot: page.getByText('Product 2') }))
    .toHaveCount(1);

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

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

Соответствие внутри локейтора​

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

В этом примере мы сначала создаём локейтор «product», локализуя его роль listitem. Затем мы фильтруем по тексту. Мы можем снова использовать локейтор «product», чтобы получить кнопку по роли и нажать её, а затем использовать утверждение, чтобы убедиться, что существует только один продукт с текстом «Продукт 2».

const product = page.getByRole('listitem').filter({ hasText: 'Product 2' });

await product.getByRole('button', { name: 'Add to cart' }).click();

await expect(product).toHaveCount(1);

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

const saveButton = page.getByRole('button', { name: 'Save' });
// ...
const dialog = page.getByTestId('settings-dialog');
await dialog.locator(saveButton).click();

Одновременное соответствие двум локейторам​

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

const button = page.getByRole('button').and(page.getByTitle('Subscribe'));

Соответствие одному из двух альтернативных локейторов​

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

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

Примечание

Если и кнопка «Новый email», и диалоговое окно настроек безопасности отображаются на экране, локейтор «or» будет соответствовать обоим, возможно, вызвав ошибку "нарушение режима строгости". В этом случае вы можете использовать locator.first(), чтобы сопоставить только один из них.

const newEmail = page.getByRole('button', { name: 'New' });
const dialog = page.getByText('Confirm security settings');
await expect(newEmail.or(dialog).first()).toBeVisible();
if (await dialog.isVisible())
  await page.getByRole('button', { name: 'Dismiss' }).click();
await newEmail.click();

Соответствие только видимым элементам​

Примечание

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

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

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

    await page.locator('button').click();
  • Это найдёт только вторую кнопку, потому что она видимая, и затем нажмёт её.

    await page.locator('button').locator('visible=true').click();

Списки​

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

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

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

http://localhost:3000
  • яблоко
  • банан
  • апельсин
<ul>
  <li>apple</li>
  <li>banana</li>
  <li>orange</li>
</ul>

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

await expect(page.getByRole('listitem')).toHaveCount(3);

Проверка всего текста в списке​

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

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

http://localhost:3000
  • яблоко
  • банан
  • апельсин
<ul>
  <li>apple</li>
  <li>banana</li>
  <li>orange</li>
</ul>

Используйте expect(locator).toHaveText(), чтобы убедиться, что в списке есть текст «яблоко», «банан» и «апельсин».

await expect(page
    .getByRole('listitem'))
    .toHaveText(['apple', 'banana', 'orange']);

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

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

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

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

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

http://localhost:3000
  • яблоко
  • банан
  • апельсин
<ul>
  <li>apple</li>
  <li>banana</li>
  <li>orange</li>
</ul>

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

await page.getByText('orange').click();

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

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

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

http://localhost:3000
  • яблоко
  • банан
  • апельсин
<ul>
  <li>apple</li>
  <li>banana</li>
  <li>orange</li>
</ul>

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

await page
    .getByRole('listitem')
    .filter({ hasText: 'orange' })
    .click();

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

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

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

http://localhost:3000
  • яблоко
  • банан
  • апельсин
<ul>
  <li data-testid='apple'>apple</li>
  <li data-testid='banana'>banana</li>
  <li data-testid='orange'>orange</li>
</ul>

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

await page.getByTestId('orange').click();

Получение по порядковому номеру элемента​

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

const banana = await page.getByRole('listitem').nth(1);

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

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

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

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

http://localhost:3000
  • Иван
  • Мария
  • Иван
  • Мария
<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>

Чтобы сделать снимок экрана строки с "Марией" и "Прощаться":

const rowLocator = page.getByRole('listitem');

await rowLocator
    .filter({ hasText: 'Mary' })
    .filter({ has: page.getByRole('button', { name: 'Say goodbye' }) })
    .screenshot({ path: 'screenshot.png' });

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

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

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

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

for (const row of await page.getByRole('listitem').all())
  console.log(await row.textContent());

Итерация с помощью цикла for:

const rows = page.getByRole('listitem');
const count = await rows.count();
for (let i = 0; i < count; ++i)
  console.log(await rows.nth(i).textContent());

Оценивать на странице​

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

const rows = page.getByRole('listitem');
const texts = await rows.evaluateAll(
    list => list.map(element => element.textContent));

Строгость​

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

Генерирует ошибку, если совпадений больше одного​

await page.getByRole('button').click();

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

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

await page.getByRole('button').count();

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

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

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

© 2024 Microsoft
Licensed under the Apache License, Version 2.0.
https://playwright.dev/docs/locators

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API