# Playwright Locators · PW-05

Locators are expressions that identify elements on a web page. This document provides standards on using effective locators in Playwright tests.

## Standard Metadata

- Area: Playwright
- Owner: Technical Leadership group
- Last reviewed: 2026-10-07
- Next review due from: 2026-12-01
- Enforcement: Code review
- Status: Active

## Playwright locators should reference their underlying elements · PW-05.1 · **SHOULD** · **TEST**

When implementing Page Object Model, locators reflect the user's perspective - this is also referred to as having user-centric locators.

[Playwright's documentation](https://playwright.dev/docs/locators#locating-elements) details the different methods which can be used to locate an element.

For example, to "Login" a user would identify the login button by its appearance as a button, and its name.

```ts
// pages/login.page.ts

// Identifies the button the way a user would: by its role and visible name
loginButton = () => this.page.getByRole('button', { name: 'Login' });
```

## Playwright locator names must use the element's visible text or function, followed by its type · PW-05.2 · **MUST** · **TEST**

Base the first part of a locator's name on the text, label or placeholder a user sees on the element. Where an element has no visible text, name it after its function. This makes locators easy to match to the page, and helps new engineers become familiar with the system and its automated tests.

End the name with the element type, using the suffixes below. This keeps locator names consistent across pages and tests.

| Element | Suffix | Example |
| --- | --- | --- |
| Button | `Button` | `saveDetailsButton()` |
| Text input or text area | `Input` | `emailInput()` |
| Checkbox | `Checkbox` | `subscribeCheckbox()` |
| Dropdown | `Dropdown` | `countryDropdown()` |
| Link | `Link` | `forgotPasswordLink()` |
| Static text or heading | `Text` | `welcomeText()` |
| Toast or alert | `Toast` | `customerRemovedToast()` |
| Image | `Image` | `logoImage()` |
| Table, row or cell | `Table`, `Row`, `Cell` | `customersTable()` |

```ts
// Named after its visible text, "Sign Up"
signUpButton = () => this.page.getByRole('button', { name: 'Sign Up' });

// No visible label, so named after its function
passwordInput = () => this.page.getByPlaceholder('Enter your password');
```

## Playwright locators must be action-independent · PW-05.3 · **MUST** · **TEST**

Locators must not be tied to any actions that manipulates or retrieves information from the element, to ensure;

1. The element is versatile and re-usable
2. Actions being performed on locators are readable
3. Test failures and timeouts are observable

Any actions to be performed on an element, i.e typing or getting the value, should be defined separately in tests or functions.

```ts
// Login Page example of the page object model within the Audacia Template

// Locators
usernameInput = () => this.page.getByLabel('Username');
passwordInput = () => this.page.getByLabel('Password');
loginButton = () => this.page.getByRole('button', { name: 'Login' });

// Actions
public async confirmLogin() {
  // Start waiting for the login request before clicking, so the response isn't missed
  const loginResponse = this.page.waitForResponse(response =>
    response.url().includes('/connect/token') && response.status() === 200);

  await this.loginButton().click();
  await loginResponse;
}
```

## Playwright locators should use the most user-centric method available · PW-05.4 · **SHOULD** · **TEST**

Use the first method in this list that can find the element. Methods higher in the list match how a user sees the page, so they read more clearly and are less likely to break when the DOM changes. The order follows [The Testing Library's priority](https://testing-library.com/docs/queries/about#priority).

| Priority | Method | Use for |
| --- | --- | --- |
| 1 | [getByRole()](https://playwright.dev/docs/api/class-page#page-get-by-role) | Buttons, links, checkboxes, options, tables and other elements with a role |
| 2 | [getByLabel()](https://playwright.dev/docs/api/class-page#page-get-by-label) or [getByPlaceholder()](https://playwright.dev/docs/api/class-page#page-get-by-placeholder) | Form fields |
| 3 | [getByText()](https://playwright.dev/docs/api/class-page#page-get-by-text) or [getByAltText()](https://playwright.dev/docs/api/class-page#page-get-by-alt-text) | Non-interactive text and images |
| 4 | [getByTestId()](https://playwright.dev/docs/api/class-page#page-get-by-test-id) | Elements with no usable role or text, such as dynamic text. Test IDs follow the [CSS selectors for automated tests format](/standards/engineering/coding-standards/html-and-css/css/css-selectors-for-automated-tests#css-selectors-must-follow-a-consistent-format) |

```ts
subscribeCheckbox = () => this.page.getByRole('checkbox', { name: 'Subscribe' });
emailInput = () => this.page.getByLabel('Email');
searchInput = () => this.page.getByPlaceholder('Search...');
welcomeText = () => this.page.getByText('Welcome to Audacia!');
logoImage = () => this.page.getByAltText('Company Logo');
directionsText = () => this.page.getByTestId('view-route__directions-panel__directions-text');
```

## `.locator()` must only be used as a last resort · PW-05.5 · **MUST** · **TEST**

Only use `.locator()` with a CSS or XPath selector when none of the methods in [PW-05.4](#playwright-locators-should-use-the-most-user-centric-method-available) can find the element. The [Playwright documentation](https://playwright.dev/docs/locators#locate-by-css-or-xpath) recommends against it, because these selectors break when the DOM changes. Where possible, ask the developers to add a `data-testid` instead.

Use the most stable selector available:

```ts
// ✔️ A unique ID or stable attribute
usernameInput = () => this.page.locator('#username');
usernameInput = () => this.page.locator('input[name="username"]');

// ❌ Class names change with styling and may come from a third-party library such as MudBlazor
usernameInput = () => this.page.locator('.mud-input-slot');

// ❌ Positions and DOM paths break when the order or layout of the page changes
usernameInput = () => this.page.locator('input').first();
usernameInput = () => this.page.locator('//div[2]/form/input');
```

## Use chained Playwright locators for readability · PW-05.6 · **COULD** · **TEST**

Chained locators can be used to select an element that is nested within another identifiable area of the page. This method is particularly useful when multiple elements share the same name or title but are distinguished by their location within the page’s structure.

Example:

```ts
// Locate the table row containing a specific username
userRow = (name: string) => this.page.getByRole('row').filter({ has: this.page.getByRole('cell', { name, exact: true }) });

// Chain locators to find the 'Select user' button within the context of a specific username
selectUserButton = (name: string) => this.userRow(name).getByTitle('Select user');

// Chain locators to find the 'Switch user' button within the context of a specific username
switchUserButton = (name: string) => this.userRow(name).getByLabel('Switch user');
```

### Restrictions on chained locators

Chained locators do not work in scenarios where you need to interact with one element and then another in a non-nested context.

Chaining locators also shouldn't be used for dropdowns, as there is a built in `.selectOption()` function available. [See the Playwright documentation on selectOption](https://playwright.dev/docs/input#select-options).
