--- 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('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 Incredibles 2 Poster 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 `