---
title: Locators | Browser Mode
outline: [2, 3]
---
# Locators
A locator is a representation of an element or a number of elements. Every locator is defined by a string called a selector. Vitest abstracts this selector by providing convenient methods that generate them behind the scenes.
The locator API uses a fork of [Playwright's locators](https://playwright.dev/docs/api/class-locator) called [Ivya](https://npmjs.com/ivya). However, Vitest provides this API to every [provider](/guide/browser/config.html#browser-provider), not just playwright.
::: tip
This page covers API usage. To better understand locators and their usage, read [Playwright's "Locators" documentation](https://playwright.dev/docs/locators).
:::
## getByRole
```ts
function getByRole(
role: ARIARole | string,
options?: LocatorByRoleOptions,
): Locator
```
Creates a way to locate an element by its [ARIA role](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles), [ARIA attributes](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes) and [accessible name](https://developer.mozilla.org/en-US/docs/Glossary/Accessible_name).
::: tip
If you only query for a single element with `getByText('The name')` it's oftentimes better to use `getByRole(expectedRole, { name: 'The name' })`. The accessible name query does not replace other queries such as `*ByAltText` or `*ByTitle`. While the accessible name can be equal to these attributes, it does not replace the functionality of these attributes.
:::
Consider the following DOM structure.
```html
Sign up
```
You can locate each element by its implicit role:
```ts
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
Roles are matched by string equality, without inheriting from the ARIA role hierarchy. As a result, querying a superclass role like `checkbox` will not include elements with a subclass role like `switch`.
By default, many semantic elements in HTML have a role; for example, `` has the "radio" role. Non-semantic elements in HTML do not have a role; `
` and `` without added semantics return `null`. The `role` attribute can provide semantics.
Providing roles via `role` or `aria-*` attributes to built-in elements that already have an implicit role is **highly discouraged** by ARIA guidelines.
:::
##### Options
- `exact: boolean`
Whether the `name` is matched exactly: case-sensitive and whole-string. Disabled by default. This option is ignored if `name` is a regular expression. Note that exact match still trims whitespace.
```tsx
page.getByRole('button', { name: 'hello world' }) // ✅
page.getByRole('button', { name: 'hello world', exact: true }) // ❌
page.getByRole('button', { name: 'Hello World', exact: true }) // ✅
```
- `checked: boolean`
Should checked elements (set by `aria-checked` or ``) be included or not. By default, the filter is not applied.
See [`aria-checked`](https://www.w3.org/TR/wai-aria-1.2/#aria-checked) for more information
```tsx
<>
>
page.getByRole('checkbox', { checked: true }) // ✅
page.getByRole('checkbox', { checked: false }) // ❌
```
- `disabled: boolean`
Should disabled elements be included or not. By default, the filter is not applied. Note that unlike other attributes, `disable` state is inherited.
See [`aria-disabled`](https://www.w3.org/TR/wai-aria-1.2/#aria-disabled) for more information
```tsx
page.getByRole('textbox', { disabled: true }) // ✅
page.getByRole('textbox', { disabled: false }) // ❌
```
- `expanded: boolean`
Should expanded elements be included or not. By default, the filter is not applied.
See [`aria-expanded`](https://www.w3.org/TR/wai-aria-1.2/#aria-expanded) for more information
```tsx
Link
page.getByRole('link', { expanded: true }) // ✅
page.getByRole('link', { expanded: false }) // ❌
```
- `includeHidden: boolean`
Should elements that are [normally excluded](https://www.w3.org/TR/wai-aria-1.2/#tree_exclusion) from the accessibility tree be queried. By default, only non-hidden elements are matched by role selector.
Note that roles `none` and `presentation` are always included.
```tsx
page.getByRole('button') // ❌
page.getByRole('button', { includeHidden: false }) // ❌
page.getByRole('button', { includeHidden: true }) // ✅
```
- `level: number`
A number attribute that is usually present for `heading`, `listitem`, `row`, `treeitem` roles with default values for `
-
` elements. By default, the filter is not applied.
See [`aria-level`](https://www.w3.org/TR/wai-aria-1.2/#aria-level) for more information
```tsx
<>
Heading Level One
Second Heading Level One
>
page.getByRole('heading', { level: 1 }) // ✅
page.getByRole('heading', { level: 2 }) // ❌
```
- `name: string | RegExp`
[An accessible name](https://developer.mozilla.org/en-US/docs/Glossary/Accessible_name). By default, matching is case-insensitive and searches for a substring. Use `exact` option to control this behavior.
```tsx
page.getByRole('button', { name: 'Click Me!' }) // ✅
page.getByRole('button', { name: 'click me!' }) // ✅
page.getByRole('button', { name: 'Click Me?' }) // ❌
```
- `pressed: boolean`
Should pressed elements be included or not. By default, the filter is not applied.
See [`aria-pressed`](https://www.w3.org/TR/wai-aria-1.2/#aria-pressed) for more information
```tsx
page.getByRole('button', { pressed: true }) // ✅
page.getByRole('button', { pressed: false }) // ❌
```
- `selected: boolean`
Should selected elements be included or not. By default, the filter is not applied.
See [`aria-selected`](https://www.w3.org/TR/wai-aria-1.2/#aria-selected) for more information
```tsx
page.getByRole('button', { selected: true }) // ✅
page.getByRole('button', { selected: false }) // ❌
```
##### See also
- [List of ARIA roles at MDN](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles)
- [List of ARIA roles at w3.org](https://www.w3.org/TR/wai-aria-1.2/#role_definitions)
- [testing-library's `ByRole`](https://testing-library.com/docs/queries/byrole/)
## getByAltText
```ts
function getByAltText(
text: string | RegExp,
options?: LocatorOptions,
): Locator
```
Creates a locator capable of finding an element with an `alt` attribute that matches the text. Unlike testing-library's implementation, Vitest will match any element that has a matching `alt` attribute.
```tsx
page.getByAltText(/incredibles.*? poster/i) // ✅
page.getByAltText('non existing alt text') // ❌
```
#### Options
- `exact: boolean`
Whether the `text` is matched exactly: case-sensitive and whole-string. Disabled by default. This option is ignored if `text` is a regular expression. Note that exact match still trims whitespace.
#### See also
- [testing-library's `ByAltText`](https://testing-library.com/docs/queries/byalttext/)
## getByLabelText
```ts
function getByLabelText(
text: string | RegExp,
options?: LocatorOptions,
): Locator
```
Creates a locator capable of finding an element that has an associated label.
The `page.getByLabelText('Username')` locator will find every input in the example below:
```html
// for/htmlFor relationship between label and form element id
// The aria-labelledby attribute with form elements
// Wrapper labels
// Wrapper labels where the label text is in another child element
// aria-label attributes
// Take care because this is not a label that users can see on the page,
// so the purpose of your input must be obvious to visual users.
```
#### Options
- `exact: boolean`
Whether the `text` is matched exactly: case-sensitive and whole-string. Disabled by default. This option is ignored if `text` is a regular expression. Note that exact match still trims whitespace.
#### See also
- [testing-library's `ByLabelText`](https://testing-library.com/docs/queries/bylabeltext/)
## getByPlaceholder
```ts
function getByPlaceholder(
text: string | RegExp,
options?: LocatorOptions,
): Locator
```
Creates a locator capable of finding an element that has the specified `placeholder` attribute. Vitest will match any element that has a matching `placeholder` attribute, not just `input`.
```tsx
page.getByPlaceholder('Username') // ✅
page.getByPlaceholder('not found') // ❌
```
::: warning
It is generally better to rely on a label using [`getByLabelText`](#getbylabeltext) than a placeholder.
:::
#### Options
- `exact: boolean`
Whether the `text` is matched exactly: case-sensitive and whole-string. Disabled by default. This option is ignored if `text` is a regular expression. Note that exact match still trims whitespace.
#### See also
- [testing-library's `ByPlaceholderText`](https://testing-library.com/docs/queries/byplaceholdertext/)
## getByText
```ts
function getByText(
text: string | RegExp,
options?: LocatorOptions,
): Locator
```
Creates a locator capable of finding an element that contains the specified text. The text will be matched against TextNode's [`nodeValue`](https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeValue) or input's value if the type is `button` or `reset`. Matching by text always normalizes whitespace, even with exact match. For example, it turns multiple spaces into one, turns line breaks into spaces and ignores leading and trailing whitespace.
```tsx
About ℹ️
page.getByText(/about/i) // ✅
page.getByText('about', { exact: true }) // ❌
```
::: tip
This locator is useful for locating non-interactive elements. If you need to locate an interactive element, like a button or an input, prefer [`getByRole`](#getbyrole).
:::
#### Options
- `exact: boolean`
Whether the `text` is matched exactly: case-sensitive and whole-string. Disabled by default. This option is ignored if `text` is a regular expression. Note that exact match still trims whitespace.
#### See also
- [testing-library's `ByText`](https://testing-library.com/docs/queries/bytext/)
## getByTitle
```ts
function getByTitle(
text: string | RegExp,
options?: LocatorOptions,
): Locator
```
Creates a locator capable of finding an element that has the specified `title` attribute. Unlike testing-library's `getByTitle`, Vitest cannot find `title` elements within an SVG.
```tsx
page.getByTitle('Delete') // ✅
page.getByTitle('Create') // ❌
```
#### Options
- `exact: boolean`
Whether the `text` is matched exactly: case-sensitive and whole-string. Disabled by default. This option is ignored if `text` is a regular expression. Note that exact match still trims whitespace.
#### See also
- [testing-library's `ByTitle`](https://testing-library.com/docs/queries/bytitle/)
## getByTestId
```ts
function getByTestId(text: string | RegExp): Locator
```
Creates a locator capable of finding an element that matches the specified test id attribute. You can configure the attribute name with [`browser.locators.testIdAttribute`](/guide/browser/config#browser-locators-testidattribute).
```tsx
page.getByTestId('custom-element') // ✅
page.getByTestId('non-existing-element') // ❌
```
::: warning
It is recommended to use this only after the other locators don't work for your use case. Using `data-testid` attributes does not resemble how your software is used and should be avoided if possible.
:::
#### Options
- `exact: boolean`
Whether the `text` is matched exactly: case-sensitive and whole-string. Disabled by default. This option is ignored if `text` is a regular expression. Note that exact match still trims whitespace.
#### See also
- [testing-library's `ByTestId`](https://testing-library.com/docs/queries/bytestid/)
## nth
```ts
function nth(index: number): Locator
```
This method returns a new locator that matches only a specific index within a multi-element query result. It's zero based, `nth(0)` selects the first element. Unlike `elements()[n]`, the `nth` locator will be retried until the element is present.
```html
```
```tsx
page.getByRole('textbox').nth(0) // ✅
page.getByRole('textbox').nth(4) // ❌
```
::: tip
Before resorting to `nth`, you may find it useful to use chained locators to narrow down your search.
Sometimes there is no better way to distinguish than by element position; although this can lead to flake, it's better than nothing.
:::
```tsx
page.getByLabel('two').getByRole('input') // ✅ better alternative to page.getByRole('textbox').nth(3)
page.getByLabel('one').getByRole('input') // ❌ too ambiguous
page.getByLabel('one').getByRole('input').nth(1) // ✅ pragmatic compromise
```
## first
```ts
function first(): Locator
```
This method returns a new locator that matches only the first index of a multi-element query result.
It is sugar for `nth(0)`.
```html
```
```tsx
page.getByRole('textbox').first() // ✅
```
## last
```ts
function last(): Locator
```
This method returns a new locator that matches only the last index of a multi-element query result.
It is sugar for `nth(-1)`.
```html
```
```tsx
page.getByRole('textbox').last() // ✅
```
## and
```ts
function and(locator: Locator): Locator
```
This method creates a new locator that matches both the parent and provided locator. The following example finds a button with a specific title:
```ts
page.getByRole('button').and(page.getByTitle('Subscribe'))
```
## or
```ts
function or(locator: Locator): Locator
```
This method creates a new locator that matches either one or both locators.
::: warning
Note that if locator matches more than a single element, calling another method might throw an error if it expects a single element:
```tsx
<>
Error happened!
>
page.getByRole('button')
.or(page.getByRole('link'))
.click() // ❌ matches multiple elements
```
:::
## filter
```ts
function filter(options: LocatorOptions): Locator
```
This methods narrows down the locator according to the options, such as filtering by text. It can be chained to apply multiple filters.
### has
- **Type:** `Locator`
This options narrows down the selector to match elements that contain other elements matching provided locator. For example, with this HTML:
```html{1,3}
Vitest
Rolldown
```
We can narrow down the locator to only find the `article` with `Vitest` text inside:
```ts
page.getByRole('article').filter({ has: page.getByText('Vitest') }) // ✅
```
::: warning
Provided locator (`page.getByText('Vitest')` in the example) must be relative to the parent locator (`page.getByRole('article')` in the example). It will be queried starting with the parent locator, not the document root.
Meaning, you cannot pass down a locator that queries the element outside of the parent locator:
```ts
page.getByText('Vitest').filter({ has: page.getByRole('article') }) // ❌
```
This example will fail because the `article` element is outside the element with `Vitest` text.
:::
::: tip
This method can be chained to narrow down the element even further:
```ts
page.getByRole('article')
.filter({ has: page.getByRole('button', { name: 'delete row' }) })
.filter({ has: page.getByText('Vitest') })
```
:::
### hasNot
- **Type:** `Locator`
This option narrows down the selector to match elements that do not contain other elements matching provided locator. For example, with this HTML:
```html{1,3}
Vitest
Rolldown
```
We can narrow down the locator to only find the `article` that doesn't have `Rolldown` inside.
```ts
page.getByRole('article')
.filter({ hasNot: page.getByText('Rolldown') }) // ✅
page.getByRole('article')
.filter({ hasNot: page.getByText('Vitest') }) // ❌
```
::: warning
Note that provided locator is queried against the parent, not the document root, just like [`has`](#has) option.
:::
### hasText
- **Type:** `string | RegExp`
This options narrows down the selector to only match elements that contain provided text somewhere inside. When the `string` is passed, matching is case-insensitive and searches for a substring.
```html{1,3}
Vitest
Rolldown
```
Both locators will find the same element because the search is case-insensitive:
```ts
page.getByRole('article').filter({ hasText: 'Vitest' }) // ✅
page.getByRole('article').filter({ hasText: 'Vite' }) // ✅
```
### hasNotText
- **Type:** `string | RegExp`
This options narrows down the selector to only match elements that do not contain provided text somewhere inside. When the `string` is passed, matching is case-insensitive and searches for a substring.
## Methods
All methods are asynchronous and must be awaited. Since Vitest 3, tests will fail if a method is not awaited.
### click
```ts
function click(options?: UserEventClickOptions): Promise
```
Click on an element. You can use the options to set the cursor position.
```ts
import { page } from 'vitest/browser'
await page.getByRole('img', { name: 'Rose' }).click()
```
- [See more at `userEvent.click`](/guide/browser/interactivity-api#userevent-click)
### dblClick
```ts
function dblClick(options?: UserEventDoubleClickOptions): Promise
```
Triggers a double click event on an element. You can use the options to set the cursor position.
```ts
import { page } from 'vitest/browser'
await page.getByRole('img', { name: 'Rose' }).dblClick()
```
- [See more at `userEvent.dblClick`](/guide/browser/interactivity-api#userevent-dblclick)
### tripleClick
```ts
function tripleClick(options?: UserEventTripleClickOptions): Promise
```
Triggers a triple click event on an element. Since there is no `tripleclick` in browser api, this method will fire three click events in a row.
```ts
import { page } from 'vitest/browser'
await page.getByRole('img', { name: 'Rose' }).tripleClick()
```
- [See more at `userEvent.tripleClick`](/guide/browser/interactivity-api#userevent-tripleclick)
### clear
```ts
function clear(options?: UserEventClearOptions): Promise
```
Clears the input element content.
```ts
import { page } from 'vitest/browser'
await page.getByRole('textbox', { name: 'Full Name' }).clear()
```
- [See more at `userEvent.clear`](/guide/browser/interactivity-api#userevent-clear)
### hover
```ts
function hover(options?: UserEventHoverOptions): Promise
```
Moves the cursor position to the selected element.
```ts
import { page } from 'vitest/browser'
await page.getByRole('img', { name: 'Rose' }).hover()
```
- [See more at `userEvent.hover`](/guide/browser/interactivity-api#userevent-hover)
### unhover
```ts
function unhover(options?: UserEventHoverOptions): Promise
```
This works the same as [`locator.hover`](#hover), but moves the cursor to the `document.body` element instead.
```ts
import { page } from 'vitest/browser'
await page.getByRole('img', { name: 'Rose' }).unhover()
```
- [See more at `userEvent.unhover`](/guide/browser/interactivity-api#userevent-unhover)
### fill
```ts
function fill(text: string, options?: UserEventFillOptions): Promise
```
Sets the value of the current `input`, `textarea` or `contenteditable` element.
```ts
import { page } from 'vitest/browser'
await page.getByRole('input', { name: 'Full Name' }).fill('Mr. Bean')
```
- [See more at `userEvent.fill`](/guide/browser/interactivity-api#userevent-fill)
### dropTo
```ts
function dropTo(
target: Locator,
options?: UserEventDragAndDropOptions,
): Promise
```
Drags the current element to the target location.
```ts
import { page } from 'vitest/browser'
const paris = page.getByText('Paris')
const france = page.getByText('France')
await paris.dropTo(france)
```
- [See more at `userEvent.dragAndDrop`](/guide/browser/interactivity-api#userevent-draganddrop)
### selectOptions
```ts
function selectOptions(
values:
| HTMLElement
| HTMLElement[]
| Locator
| Locator[]
| string
| string[],
options?: UserEventSelectOptions,
): Promise
```
Choose one or more values from a `