Локаторы
Локатор — это представление одного или нескольких элементов. Каждый локатор определяется строкой, называемой селектором. Vitest абстрагирует этот селектор, предоставляя удобные методы, которые генерируют его автоматически.
API локаторов использует форк локаторов Playwright под названием Ivya. Однако Vitest предоставляет этот API всем провайдерам, а не только Playwright.
TIP
Эта страница описывает использование API. Чтобы лучше понять локаторы и их применение, ознакомьтесь с документацией Playwright "Locators".
getByRole
function getByRole(
role: ARIARole | string,
options?: LocatorByRoleOptions
): Locator;
Создает локатор для поиска элемента по его ARIA-роли, ARIA-атрибутам и доступному имени.
TIP
Если вы запрашиваете только один элемент с помощью getByText('The name')
, часто предпочтительнее использовать getByRole(expectedRole, { name: 'The name' })
. Запрос по доступному имени не заменяет другие запросы, такие как *ByAltText
и *ByTitle
. Хотя доступное имя может совпадать с этими атрибутами, оно не дублирует их функциональность.
Рассмотрим следующую структуру DOM:
<h3>Sign up</h3>
<label>
Login
<input type="text" />
</label>
<label>
Password
<input type="password" />
</label>
<br />
<button>Submit</button>
Вы можете найти каждый элемент по его неявной роли:
await expect
.element(page.getByRole('heading', { name: 'Sign up' }))
.toBeVisible();
await page.getByRole('textbox', { name: 'Login' }).fill('admin');
await page.getByRole('textbox', { name: 'Password' }).fill('admin');
await page.getByRole('button', { name: /submit/i }).click();
WARNING
Роли сопоставляются путем сравнения строк, без наследования из иерархии ролей ARIA. В результате запрос роли суперкласса (например, checkbox
) не будет включать элементы с ролью подкласса (например, switch
).
По умолчанию многие семантические элементы в HTML имеют свои роли; например, <input type="radio">
имеет роль "radio". Несемантические элементы HTML не имеют роли; <div>
и <span>
без явно заданной семантики возвращают null
. Атрибут role
может предоставлять семантику.
Предоставление ролей через атрибуты role
или aria-*
встроенным элементам, которые уже имеют неявную роль, настоятельно не рекомендуется в соответствии с рекомендациями ARIA.
Опции
exact: boolean
Определяет, должно ли
name
совпадать точно: с учетом регистра и всей строки. По умолчанию отключено. Эта опция игнорируется, еслиname
является регулярным выражением. Обратите внимание, что точное совпадение все равно удаляет пробелы.tsx<button>Hello World</button>; page.getByRole('button', { name: 'hello world' }); // ✅ page.getByRole('button', { name: 'hello world', exact: true }); // ❌ page.getByRole('button', { name: 'Hello World', exact: true }); // ✅
checked: boolean
Следует ли включать или исключать отмеченные элементы (определяемые атрибутом
aria-checked
или элементом<input type="checkbox"/>
). По умолчанию фильтрация не выполняется.См.
aria-checked
для получения дополнительной информации.tsx<> <button role="checkbox" aria-checked="true" /> <input type="checkbox" checked /> </>; page.getByRole('checkbox', { checked: true }); // ✅ page.getByRole('checkbox', { checked: false }); // ❌
disabled: boolean
Следует ли включать или исключать отключенные элементы. По умолчанию фильтрация не выполняется. Обратите внимание, что в отличие от других атрибутов, состояние
disabled
является наследуемым.См.
aria-disabled
для получения дополнительной информации.tsx<input type="text" disabled />; page.getByRole('textbox', { disabled: true }); // ✅ page.getByRole('textbox', { disabled: false }); // ❌
expanded: boolean
Следует ли включать или исключать развернутые элементы. По умолчанию фильтрация не выполняется.
См.
aria-expanded
для получения дополнительной информации.tsx<a aria-expanded="true" href="example.com"> Link </a>; page.getByRole('link', { expanded: true }); // ✅ page.getByRole('link', { expanded: false }); // ❌
includeHidden: boolean
Следует ли искать элементы, которые обычно исключаются из дерева доступности. По умолчанию селектор роли находит только видимые элементы.
Обратите внимание, что роли
none
иpresentation
всегда включаются.tsx<button style="display: none" />; page.getByRole('button'); // ❌ page.getByRole('button', { includeHidden: false }); // ❌ page.getByRole('button', { includeHidden: true }); // ✅
level: number
Числовой атрибут, обычно используемый для ролей
heading
,listitem
,row
,treeitem
, а также имеющий значения по умолчанию для элементов<h1>-<h6>
. По умолчанию фильтрация не выполняется.См.
aria-level
для получения дополнительной информации.tsx<> <h1>Heading Level One</h1> <div role="heading" aria-level="1"> Second Heading Level One </div> </>; page.getByRole('heading', { level: 1 }); // ✅ page.getByRole('heading', { level: 2 }); // ❌
name: string | RegExp
Доступное имя. По умолчанию сопоставление нечувствительно к регистру и выполняет поиск подстроки. Используйте опцию
exact
для управления этим поведением.tsx<button>Click Me!</button>; page.getByRole('button', { name: 'Click Me!' }); // ✅ page.getByRole('button', { name: 'click me!' }); // ✅ page.getByRole('button', { name: 'Click Me?' }); // ❌
pressed: boolean
Следует ли включать или исключать нажатые элементы. По умолчанию фильтрация не выполняется.
См.
aria-pressed
для получения дополнительной информации.tsx<button aria-pressed="true">👍</button>; page.getByRole('button', { pressed: true }); // ✅ page.getByRole('button', { pressed: false }); // ❌
selected: boolean
Следует ли включать или исключать выбранные элементы. По умолчанию фильтрация не выполняется.
См.
aria-selected
для получения дополнительной информации.tsx<button role="tab" aria-selected="true"> Vue </button>; page.getByRole('button', { selected: true }); // ✅ page.getByRole('button', { selected: false }); // ❌
См. также
getByAltText
function getByAltText(text: string | RegExp, options?: LocatorOptions): Locator;
Создает локатор, способный найти элемент с атрибутом alt
, который соответствует тексту. В отличие от реализации testing-library, Vitest будет находить любой элемент с соответствующим атрибутом alt
.
<img alt="Incredibles 2 Poster" src="/incredibles-2.png" />;
page.getByAltText(/incredibles.*? poster/i); // ✅
page.getByAltText('non existing alt text'); // ❌
Опции
exact: boolean
Определяет, должно ли
text
совпадать точно: с учетом регистра и всей строки. По умолчанию отключено. Эта опция игнорируется, еслиtext
является регулярным выражением. Обратите внимание, что точное совпадение все равно удаляет пробелы.
См. также
getByLabelText
function getByLabelText(
text: string | RegExp,
options?: LocatorOptions
): Locator;
Создает локатор, способный найти элемент, имеющий связанную метку.
Локатор page.getByLabelText('Username')
найдет все элементы ввода в примере ниже:
// for/htmlFor — связь между меткой и идентификатором элемента формы
<label for="username-input">Username</label>
<input id="username-input" />
// Атрибут aria-labelledby, используемый с элементами формы
<label id="username-label">Username</label>
<input aria-labelledby="username-label" />
// Метки-обертки
<label>Username <input /></label>
// Метки-обертки, где текст метки находится в другом дочернем элементе
<label>
<span>Username</span>
<input />
</label>
// Атрибуты aria-label // Будьте внимательны, так как это не метка, видимая
пользователям на странице, поэтому назначение вашего поля ввода должно быть
очевидным для пользователей с визуальным восприятием.
<input aria-label="Username" />
Опции
exact: boolean
Определяет, должно ли
text
совпадать точно: с учетом регистра и всей строки. По умолчанию отключено. Эта опция игнорируется, еслиtext
является регулярным выражением. Обратите внимание, что точное совпадение все равно удаляет пробелы.
См. также
getByPlaceholder
function getByPlaceholder(
text: string | RegExp,
options?: LocatorOptions
): Locator;
Создает локатор, способный найти элемент, имеющий указанный атрибут placeholder
. Vitest будет находить любой элемент с соответствующим атрибутом placeholder
, а не только элементы <input>
.
<input placeholder="Username" />;
page.getByPlaceholder('Username'); // ✅
page.getByPlaceholder('not found'); // ❌
WARNING
Как правило, предпочтительнее использовать метку с помощью getByLabelText
, чем заполнитель.
Опции
exact: boolean
Определяет, должно ли
text
совпадать точно: с учетом регистра и всей строки. По умолчанию отключено. Эта опция игнорируется, еслиtext
является регулярным выражением. Обратите внимание, что точное совпадение все равно удаляет пробелы.
См. также
getByText
function getByText(text: string | RegExp, options?: LocatorOptions): Locator;
Создает локатор, способный найти элемент, содержащий указанный текст. Текст будет сопоставляться со значением nodeValue
текстового узла или значением поля ввода, если его тип — button
или reset
. Сопоставление по тексту всегда нормализует пробелы, даже при точном совпадении. Например, оно преобразует множественные пробелы в один, переводы строк — в пробелы, а также игнорирует начальные и конечные пробелы.
<a href="/about">About ℹ️</a>;
page.getByText(/about/i); // ✅
page.getByText('about', { exact: true }); // ❌
TIP
Этот локатор полезен для поиска неинтерактивных элементов. Если вам нужно найти интерактивный элемент, такой как кнопка или ввод, рекомендуется использовать getByRole
.
Опции
exact: boolean
Определяет, должно ли
text
совпадать точно: с учетом регистра и всей строки. По умолчанию отключено. Эта опция игнорируется, еслиtext
является регулярным выражением. Обратите внимание, что точное совпадение все равно удаляет пробелы.
См. также
getByTitle
function getByTitle(text: string | RegExp, options?: LocatorOptions): Locator;
Создает локатор, способный найти элемент, имеющий указанный атрибут title
. В отличие от getByTitle
в testing-library, Vitest не может найти элементы с атрибутом title
внутри SVG.
<span title="Delete" id="2"></span>;
page.getByTitle('Delete'); // ✅
page.getByTitle('Create'); // ❌
Опции
exact: boolean
Определяет, должно ли
text
совпадать точно: с учетом регистра и всей строки. По умолчанию отключено. Эта опция игнорируется, еслиtext
является регулярным выражением. Обратите внимание, что точное совпадение все равно удаляет пробелы.
См. также
getByTestId
function getByTestId(text: string | RegExp): Locator;
Создает локатор, способный найти элемент, соответствующий указанному атрибуту тестового идентификатора. Вы можете настроить имя атрибута с помощью browser.locators.testIdAttribute
.
<div data-testid="custom-element" />;
page.getByTestId('custom-element'); // ✅
page.getByTestId('non-existing-element'); // ❌
WARNING
Рекомендуется использовать это только тогда, когда другие локаторы не применимы к вашему сценарию. Использование атрибутов data-testid
не соответствует реальному взаимодействию пользователей с вашим программным обеспечением, и по возможности следует избегать.
Опции
exact: boolean
Определяет, должно ли
text
совпадать точно: с учетом регистра и всей строки. По умолчанию отключено. Эта опция игнорируется, еслиtext
является регулярным выражением. Обратите внимание, что точное совпадение все равно удаляет пробелы.
См. также
nth
function nth(index: number): Locator;
Этот метод возвращает новый локатор, который соответствует только определенному индексу в результате запроса нескольких элементов. Индексация начинается с нуля, nth(0)
выбирает первый элемент. В отличие от elements()[n]
, локатор nth
будет повторять попытки до тех пор, пока элемент не появится.
<div aria-label="one"><input /><input /><input /></div>
<div aria-label="two"><input /></div>
page.getByRole('textbox').nth(0); // ✅
page.getByRole('textbox').nth(4); // ❌
TIP
Прежде чем прибегать к nth
, может быть полезно использовать цепочки локаторов для сужения поиска. Иногда невозможно отличить элементы иначе, кроме как по их положению; хотя это может привести к нестабильным тестам, это лучше, чем ничего.
page.getByLabel('two').getByRole('input'); // ✅ лучшая альтернатива page.getByRole('textbox').nth(3)
page.getByLabel('one').getByRole('input'); // ❌ слишком неоднозначно
page.getByLabel('one').getByRole('input').nth(1); // ✅ прагматичный компромисс
first
function first(): Locator;
Этот метод возвращает новый локатор, который соответствует только первому индексу результата запроса нескольких элементов. Это синтаксический сахар для nth(0)
.
<input /> <input /> <input />
page.getByRole('textbox').first(); // ✅
last
function last(): Locator;
Этот метод возвращает новый локатор, который соответствует только последнему индексу результата запроса нескольких элементов. Это синтаксический сахар для nth(-1)
.
<input /> <input /> <input />
page.getByRole('textbox').last(); // ✅
and
function and(locator: Locator): Locator;
Этот метод создает новый локатор, который соответствует как родительскому, так и предоставленному локатору. Следующий пример находит кнопку с определенным заголовком:
page.getByRole('button').and(page.getByTitle('Subscribe'));
or
function or(locator: Locator): Locator;
Этот метод создает новый локатор, который соответствует одному или обоим локаторам.
WARNING
Обратите внимание, что если локатор соответствует нескольким элементам, вызов другого метода может привести к ошибке, если он ожидает только один элемент:
<>
<button>Click me</button>
<a href="https://vitest.dev">Error happened!</a>
</>;
page.getByRole('button').or(page.getByRole('link')).click(); // ❌ соответствует нескольким элементам
filter
function filter(options: LocatorOptions): Locator;
Этот метод сужает область действия локатора в соответствии с заданными опциями, например, фильтрацией по тексту. Его можно объединять в цепочку для применения нескольких фильтров.
has
- Тип:
Locator
Эта опция сужает селектор, чтобы он соответствовал элементам, которые содержат другие элементы, соответствующие указанному локатору. Например, с этим HTML:
<article>
<div>Vitest</div>
</article>
<article>
<div>Rolldown</div>
</article>
Мы можем сузить локатор, чтобы найти только article
с текстом Vitest
внутри:
page.getByRole('article').filter({ has: page.getByText('Vitest') }); // ✅
WARNING
Указанный локатор (page.getByText('Vitest')
в примере) должен быть относительным к родительскому локатору (page.getByRole('article')
в примере). Поиск будет выполняться, начиная с родительского локатора, а не с корня документа.
Это означает, что вы не можете передать локатор, который запрашивает элемент вне родительского локатора:
page.getByText('Vitest').filter({ has: page.getByRole('article') }); // ❌
Этот пример завершится ошибкой, потому что элемент article
находится за пределами элемента с текстом Vitest
.
TIP
Этот метод можно объединять в цепочку для дальнейшего уточнения поиска элемента:
page
.getByRole('article')
.filter({ has: page.getByRole('button', { name: 'delete row' }) })
.filter({ has: page.getByText('Vitest') });
hasNot
- Тип:
Locator
Эта опция сужает селектор, чтобы он соответствовал элементам, которые не содержат другие элементы, соответствующие указанному локатору. Например, с этим HTML:
<article>
<div>Vitest</div>
</article>
<article>
<div>Rolldown</div>
</article>
Мы можем сузить локатор, чтобы найти только article
, который не содержит Rolldown
внутри.
page.getByRole('article').filter({ hasNot: page.getByText('Rolldown') }); // ✅
page.getByRole('article').filter({ hasNot: page.getByText('Vitest') }); // ❌
WARNING
Обратите внимание, что указанный локатор ищется относительно родителя, а не корня документа, аналогично опции has
.
hasText
- Тип:
string | RegExp
Эта опция сужает селектор, чтобы он соответствовал только элементам, которые содержат указанный текст где-либо внутри. При передаче строки сопоставление нечувствительно к регистру и выполняется поиск подстроки.
<article>
<div>Vitest</div>
</article>
<article>
<div>Rolldown</div>
</article>
Оба локатора найдут один и тот же элемент, потому что поиск выполняется без учета регистра:
page.getByRole('article').filter({ hasText: 'Vitest' }); // ✅
page.getByRole('article').filter({ hasText: 'Vite' }); // ✅
hasNotText
- Тип:
string | RegExp
Эта опция сужает селектор, чтобы он соответствовал только элементам, которые не содержат указанный текст где-либо внутри. При передаче строки сопоставление нечувствительно к регистру и выполняется поиск подстроки.
Методы
Все методы асинхронны и требуют использования await
. Начиная с Vitest 3, тесты будут завершаться ошибкой, если метод не был дождан.
click
function click(options?: UserEventClickOptions): Promise<void>;
Выполняет щелчок по элементу. Вы можете использовать опции для указания положения курсора.
import { page } from '@vitest/browser/context';
await page.getByRole('img', { name: 'Rose' }).click();
dblClick
function dblClick(options?: UserEventDoubleClickOptions): Promise<void>;
Инициирует событие двойного щелчка на элементе. Вы можете использовать опции для указания положения курсора.
import { page } from '@vitest/browser/context';
await page.getByRole('img', { name: 'Rose' }).dblClick();
tripleClick
function tripleClick(options?: UserEventTripleClickOptions): Promise<void>;
Инициирует событие тройного щелчка на элементе. Поскольку в API браузера отсутствует tripleclick
, этот метод последовательно вызовет три события щелчка.
import { page } from '@vitest/browser/context';
await page.getByRole('img', { name: 'Rose' }).tripleClick();
clear
function clear(options?: UserEventClearOptions): Promise<void>;
Очищает содержимое поля ввода.
import { page } from '@vitest/browser/context';
await page.getByRole('textbox', { name: 'Full Name' }).clear();
hover
function hover(options?: UserEventHoverOptions): Promise<void>;
Перемещает курсор к выбранному элементу.
import { page } from '@vitest/browser/context';
await page.getByRole('img', { name: 'Rose' }).hover();
unhover
function unhover(options?: UserEventHoverOptions): Promise<void>;
Это работает так же, как locator.hover
, но перемещает курсор к элементу document.body
вместо этого.
import { page } from '@vitest/browser/context';
await page.getByRole('img', { name: 'Rose' }).unhover();
fill
function fill(text: string, options?: UserEventFillOptions): Promise<void>;
Устанавливает значение текущего элемента <input>
, <textarea>
или contenteditable
.
import { page } from '@vitest/browser/context';
await page.getByRole('input', { name: 'Full Name' }).fill('Mr. Bean');
dropTo
function dropTo(
target: Locator,
options?: UserEventDragAndDropOptions
): Promise<void>;
Перетаскивает текущий элемент в целевое место.
import { page } from '@vitest/browser/context';
const paris = page.getByText('Paris');
const france = page.getByText('France');
await paris.dropTo(france);
selectOptions
function selectOptions(
values: HTMLElement | HTMLElement[] | Locator | Locator[] | string | string[],
options?: UserEventSelectOptions
): Promise<void>;
Позволяет выбрать одно или несколько значений из элемента <select>
.
import { page } from '@vitest/browser/context';
const languages = page.getByRole('select', { name: 'Languages' });
await languages.selectOptions('EN');
await languages.selectOptions(['ES', 'FR']);
await languages.selectOptions([
languages.getByRole('option', { name: 'Spanish' }),
languages.getByRole('option', { name: 'French' }),
]);
screenshot
function screenshot(
options: LocatorScreenshotOptions & { save: false }
): Promise<string>;
function screenshot(
options: LocatorScreenshotOptions & { base64: true }
): Promise<{
path: string;
base64: string;
}>;
function screenshot(
options?: LocatorScreenshotOptions & { base64?: false }
): Promise<string>;
Делает скриншот элемента, соответствующего селектору локатора.
Вы можете указать место сохранения скриншота с помощью опции path
, путь которой относителен к текущему тестовому файлу. Если опция path
не установлена, Vitest по умолчанию будет использовать browser.screenshotDirectory
(по умолчанию __screenshot__
), а также имена файла и теста для формирования пути к файлу скриншота.
Если вам также нужно содержимое скриншота, вы можете указать base64: true
, чтобы вернуть его вместе с путем к файлу, в котором сохранен скриншот.
import { page } from '@vitest/browser/context';
const button = page.getByRole('button', { name: 'Click Me!' });
const path = await button.screenshot();
const { path, base64 } = await button.screenshot({
path: './button-click-me.png',
base64: true, // также вернуть строку base64
});
// path - полный путь к скриншоту
// base64 - строка скриншота, закодированная в формате base64
ВНИМАНИЕ 3.2.0+
Обратите внимание, что screenshot
всегда будет возвращать строку base64, если save
установлено в значение false
. В этом случае path
также игнорируется.
query
function query(): Element | null;
Этот метод возвращает один элемент, соответствующий селектору локатора, или null
, если элемент не был найден.
Если селектору соответствует несколько элементов, этот метод сгенерирует ошибку. Используйте .elements()
, когда вам нужны все соответствующие элементы DOM, или .all()
, если вам нужен массив локаторов, соответствующих данному селектору.
Рассмотрим следующую структуру DOM:
<div>Hello <span>World</span></div>
<div>Hello</div>
Эти локаторы не вызовут ошибку:
page.getByText('Hello World').query(); // ✅ HTMLDivElement
page.getByText('Hello Germany').query(); // ✅ null
page.getByText('World').query(); // ✅ HTMLSpanElement
page.getByText('Hello', { exact: true }).query(); // ✅ HTMLSpanElement
Эти локаторы вызовут ошибку:
// возвращает несколько элементов
page.getByText('Hello').query(); // ❌
page.getByText(/^Hello/).query(); // ❌
element
function element(): Element;
Этот метод возвращает один элемент, соответствующий селектору локатора.
Если ни один элемент не соответствует селектору, будет сгенерирована ошибка. Рассмотрите возможность использования .query()
, если вам просто нужно проверить наличие элемента.
Если несколько элементов соответствуют селектору, будет сгенерирована ошибка. Используйте .elements()
, когда вам нужны все соответствующие элементы DOM, или .all()
, если вам нужен массив локаторов, соответствующих селектору.
TIP
Этот метод может быть полезен, если вам нужно передать его во внешнюю библиотеку. Он вызывается автоматически, когда локатор используется с expect.element
при каждой повторной попытке утверждения:
await expect.element(page.getByRole('button')).toBeDisabled();
Рассмотрим следующую структуру DOM:
<div>Hello <span>World</span></div>
<div>Hello Germany</div>
<div>Hello</div>
Эти локаторы не вызовут ошибку:
page.getByText('Hello World').element(); // ✅
page.getByText('Hello Germany').element(); // ✅
page.getByText('World').element(); // ✅
page.getByText('Hello', { exact: true }).element(); // ✅
Эти локаторы вызовут ошибку:
// возвращает несколько элементов
page.getByText('Hello').element(); // ❌
page.getByText(/^Hello/).element(); // ❌
// не возвращает элементов
page.getByText('Hello USA').element(); // ❌
elements
function elements(): Element[];
Этот метод возвращает массив элементов, соответствующих селектору локатора.
Эта функция никогда не генерирует ошибку. Если ни один элемент не соответствует селектору, этот метод вернет пустой массив.
Рассмотрим следующую структуру DOM:
<div>Hello <span>World</span></div>
<div>Hello</div>
Эти локаторы всегда будут успешными:
page.getByText('Hello World').elements(); // ✅ [HTMLElement]
page.getByText('World').elements(); // ✅ [HTMLElement]
page.getByText('Hello', { exact: true }).elements(); // ✅ [HTMLElement]
page.getByText('Hello').element(); // ✅ [HTMLElement, HTMLElement]
page.getByText('Hello USA').elements(); // ✅ []
all
function all(): Locator[];
Этот метод возвращает массив новых локаторов, соответствующих селектору.
Внутри этот метод вызывает .elements
и оборачивает каждый элемент, используя page.elementLocator
.
Свойства
selector
selector
— это строка, которая будет использоваться провайдером браузера для поиска элемента. Playwright будет использовать синтаксис локаторов Playwright, в то время как preview
и webdriverio
будут использовать CSS.
DANGER
Эту строку не следует использовать в вашем тестовом коде. Строка selector
должна использоваться исключительно при работе с Commands API:
import type { BrowserCommand } from 'vitest/node';
const test: BrowserCommand<string> = function test(context, selector) {
// playwright
await context.iframe.locator(selector).click();
// webdriverio
await context.browser.$(selector).click();
};
import { test } from 'vitest';
import { commands, page } from '@vitest/browser/context';
test('works correctly', async () => {
await commands.test(page.getByText('Hello').selector); // ✅
// vitest автоматически преобразует его в строку
await commands.test(page.getByText('Hello')); // ✅
});
Пользовательские локаторы 3.2.0+ продвинутый
Вы можете расширить встроенный API локаторов, определив объект, содержащий фабрики локаторов. Эти методы будут доступны как методы объекта page
и любого созданного локатора.
Эти локаторы могут быть полезны, если встроенных локаторов недостаточно. Например, когда вы используете пользовательский фреймворк для своего пользовательского интерфейса.
Фабрика локаторов должна возвращать строку селектора или сам объект локатора.
TIP
Синтаксис селектора идентичен синтаксису локаторов Playwright. Пожалуйста, прочитайте их руководство, чтобы лучше понять принципы работы с ними.
import { locators } from '@vitest/browser/context';
locators.extend({
getByArticleTitle(title) {
return `[data-title="${title}"]`;
},
getByArticleCommentsCount(count) {
return `.comments :text("${count} comments")`;
},
async previewComments() {
// у вас есть доступ к текущему локатору через ключевое слово "this"
// будьте осторожны: если метод был вызван на объекте page, this будет указывать на page,
// а не на локатор!
if (this !== page) {
await this.click();
}
// ...
},
});
// если вы используете TypeScript, вы можете расширить интерфейс LocatorSelectors
// для обеспечения автодополнения в методах locators.extend, page.* и locator.*
declare module '@vitest/browser/context' {
interface LocatorSelectors {
// если пользовательский метод возвращает строку, она будет автоматически преобразована в локатор
// если он возвращает что-либо иное, то будет возвращено как есть
getByArticleTitle(title: string): Locator;
getByArticleCommentsCount(count: number): Locator;
// Vitest вернет промис и не будет пытаться преобразовать его в локатор
previewComments(this: Locator): Promise<void>;
}
}
Если метод вызывается на глобальном объекте page
, селектор будет применен ко всей странице. В примере ниже getByArticleTitle
найдет все элементы с атрибутом data-title
, значение которого равно title
. Однако, если метод вызывается на локаторе, его область действия будет ограничена этим локатором.
<article data-title="Hello, World!">
Hello, World!
<button id="comments">2 comments</button>
</article>
<article data-title="Hello, Vitest!">
Hello, Vitest!
<button id="comments">0 comments</button>
</article>
const articles = page.getByRole('article');
const worldArticle = page.getByArticleTitle('Hello, World!'); // ✅
const commentsElement = worldArticle.getByArticleCommentsCount(2); // ✅
const wrongCommentsElement = worldArticle.getByArticleCommentsCount(0); // ❌
const wrongElement = page.getByArticleTitle('No Article!'); // ❌
await commentsElement.previewComments(); // ✅
await wrongCommentsElement.previewComments(); // ❌