# Locators

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

Source: https://rehearsal.dev/retest/docs/essentials/locators

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

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.

```ts filename="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

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.

| Finder | Use 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](https://rehearsal.dev/retest/docs/platforms/browsers.md) covers engine support.

## 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.

```ts
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

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.

```ts
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

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](https://rehearsal.dev/retest/docs/essentials/checks.md) explains how to wait for visibility and other states.

## 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](https://rehearsal.dev/retest/docs/guides/frames-and-shadow-dom.md) for examples.

The [Test API](https://rehearsal.dev/retest/docs/reference/test-api.md) lists all available finders and locator methods. For a query with several matches, use the scoping and positional methods above.

## Next steps

- [Actions](https://rehearsal.dev/retest/docs/essentials/actions.md): Click and type through the locators you found.
- [Checks](https://rehearsal.dev/retest/docs/essentials/checks.md): Wait for the state an element should reach.
- [Frames and shadow DOM](https://rehearsal.dev/retest/docs/guides/frames-and-shadow-dom.md): Find elements inside other documents and web components.
