Locators

Find controls by their names or test ids, then narrow a locator when several elements match.

To click Save or check a saved title, first choose a locator that identifies the element. This page shows how to choose a finder and narrow it when several controls match.

Find a control Link to Find a control

Use a role and accessible name for a button, or a label for a form field. A test id gives you a stable name when the visible text is not enough to identify an element.

tests/task.retest.ts
import { expect, test } from '@rehearsal-labs/retest'test('saves a task', async ({ page }) => {  await page.goto('/')  const title = page.getByLabel('Title')  const save = page.getByRole('button', { name: 'Save' })  const savedTask = page.getByTestId('saved-task')  await title.fill('Release checklist')  await save.click()  await expect(savedTask).toHaveText('Release checklist')})

Retest finds each element when an action or check uses its locator, then finds it again on the next use. You can keep a locator in a variable even when your app replaces the element.

In the task app, getByTestId('task-title') and getByTestId('save-task') identify the same controls as the label and role above. On a web page, getByTestId matches the data-testid attribute exactly.

Choose a finder Link to Choose a finder

Choose the finder that describes the part of the interface you care about. Roles and labels follow the app's accessible names, while text and placeholders follow the words written on the page.

FinderUse it for
getByRole('button', { name: 'Save' })A control with a known role and accessible name.
getByLabel('Title')A form control with an accessible label.
getByTestId('saved-task')An element with a test id your app provides.
getByText('Release checklist')The innermost element with matching text.
getByPlaceholder('Search tasks')A field with a known placeholder.
locator('[data-testid="saved-task"]')An element you can identify with CSS.

An accessible name may come from a label, aria-label, aria-labelledby or the control's content, according to the browser. If a role locator finds nothing, check that name rather than assuming it equals every word inside the element.

WebKit refuses many role and label lookups as unsupported. Common causes include images without alt, SVGs, native select options, hidden labels, table semantics and cells named only by text. A role outside its required parent or an editable element other than a div or span can also trigger refusals; getByRole('generic') is always refused. Use test ids on these pages; Browsers covers engine support.

Match text Link to Match text

Text and accessible-name lookups match the whole string and respect case by default. Retest trims both ends and treats each run of spaces or line breaks as one space, so formatting whitespace does not change a match.

TypeScript
const exactSave = page.getByRole('button', { name: 'Save' })const anySave = page.getByRole('button', { name: 'save', exact: false })const saveDraft = page.getByRole('button', { name: /^Save draft$/ })

exactSave matches Save, but not Save draft or save. anySave accepts all three because exact: false makes the match a case-insensitive substring search. A regular expression uses its own flags and takes no exact option.

Narrow a locator Link to Narrow a locator

When a page has several matching controls, find their container first. Every finder is also available on a locator, where it searches the descendants of the elements already matched.

TypeScript
const taskForm = page.locator('form')await taskForm.getByRole('button', { name: 'Save' }).click()const secondItem = page.getByRole('listitem').nth(1)await secondItem.getByRole('button', { name: 'Save' }).click()

The first example limits Save to buttons inside a form. The second selects the second list item before finding its button. Use it on a page with those repeated items, and prefer a named container or test id when the item's position can change.

first() and last() keep the first or last match in document order. nth(0) keeps the first, and a negative index counts from the end, so nth(-1) keeps the last. You can choose a position once, then search inside it; chaining two choices such as first().nth(1) fails with usage.

Understand a missing match Link to Understand a missing match

An action needs exactly one matching element. If none matches, Retest waits until the action budget ends, then reports not_found. If several match, it reports ambiguous immediately and sends no input, so narrow the locator before trying again.

Role and label lookups leave out elements excluded from the browser's accessibility tree, such as hidden or inert controls. Text lookups can find hidden elements, but finding one does not make it visible or clickable. Checks explains how to wait for visibility and other states.

Use CSS and other documents Link to Use CSS and other documents

locator takes CSS selectors. XPath and Playwright selector syntax such as text=Save or div >> button fail with usage, as do selectors the browser cannot parse, such as :has-text().

Web locators search through open shadow roots, but a closed root stays inaccessible. They do not enter frames automatically; use page.frame to name a frame first. Firefox and WebKit refuse role or label searches when a relevant open shadow root could hold a match. Frame locators work on Chrome and Chromium; Electron windows, Firefox and WebKit refuse them. See Frames and shadow DOM for examples.

The Test API lists all available finders and locator methods. For a query with several matches, use the scoping and positional methods above.

Next steps Link to Next steps