Registar

Locators in UI tests

This article is not available in Portuguese yet. It is shown in English.

A locator is a description of how an automated UI test finds an element of the user interface: the button it clicks, the field it fills, the message it checks. A locator consists of a strategy and a value: "the button whose accessible name is Sign in", "the element with the attribute data-testid equal to cart-count", "the CSS selector form > button.primary". The test refers to elements only through locators, so locators are the point where the test depends on the markup of the page.

The browser automation standard, W3C WebDriver, defines five location strategies; its Level 1 became a W3C Recommendation on 5 June 2018, and Level 2 is a Working Draft (the version of 2 July 2026). Selenium supports eight traditional strategies, among them id, CSS selector and XPath. Playwright, since version 1.27, and Testing Library offer locators by user-facing attributes: role, label, text. Playwright's documentation recommends them over CSS and XPath. The roles and accessible names they rely on are defined by WAI-ARIA 1.2, a W3C Recommendation of 6 June 2023.

The problem locators solve

A test must refer to elements of the page, and the page changes. Developers rename CSS classes, wrap elements in new containers, move blocks, change texts. A test should fail when the behavior of the product breaks and should keep passing when only the markup around the behavior changes.

Each kind of locator ties the test to a different property of the element. A long CSS chain ties it to the structure of the document. Playwright's documentation shows such a chain as an example of bad practice that leads to unstable tests:

typescript
page.locator('#tsf > div:nth-child(2) > div.A8SBwf > div.RNNXgb > div > div.a4bIc > input');

Any new wrapper div or renamed class breaks this locator, although the input field itself is still on the page and works. The choice of locator kind decides which changes of the page break the test and which do not.

Kinds of locators

The examples in this section refer to one login form.

html
<form>
  <h1>Sign in</h1>
  <label for="email">Email</label>
  <input id="email" type="email" placeholder="name@example.com">
  <label for="password">Password</label>
  <input id="password" type="password">
  <img src="/logo.svg" alt="Shop logo">
  <span title="Items in cart" data-testid="cart-count">0</span>
  <button type="submit" class="btn btn-primary">Sign in</button>
</form>

By role

A locator by role finds an element by its ARIA role and its accessible name. The role says what the element is: button, link, textbox, checkbox, heading. HTML elements have implicit roles: according to the W3C specification ARIA in HTML, a button element has the role button, an a element with href has the role link, and h1 to h6 have the role heading. WAI-ARIA defines the accessible name as the name of a user interface element; it can come from the visible text, a label or an attribute such as aria-label.

In Playwright, page.getByRole('button', { name: 'Sign in' }) finds the button of the form. By default the name matches as a case-insensitive substring; the option exact: true requires a case-sensitive match of the whole string. Further options filter by state and level: checked, disabled, expanded, pressed, selected, level. Playwright's documentation notes that role locators follow the W3C specifications for ARIA roles, attributes and accessible names, but do not replace accessibility audits.

By label

A locator by label finds a form field by the text of its label. Playwright's page.getByLabel('Email') matches the text of the associated label element or of the element referenced by aria-labelledby, or the value of aria-label. Testing Library calls its equivalent getByLabelText and recommends it for form fields, because users find fields by their labels.

By placeholder

A locator by placeholder finds an input by its placeholder attribute: page.getByPlaceholder('name@example.com'). Testing Library treats it as a fallback, because a placeholder is not a substitute for a label.

By text

A locator by text finds an element by its text content: page.getByText('Sign in'). Playwright normalizes whitespace when it matches text, even with an exact match, and accepts a substring, a whole string or a regular expression. Its documentation recommends text locators for non-interactive elements such as div, span and p, and role locators for interactive elements such as button, a and input. In the form above, getByText('Sign in') matches both the heading and the button. Playwright locators are strict: an action on a locator that matches more than one element throws an exception. A role locator separates the two: getByRole('heading', { name: 'Sign in' }) and getByRole('button', { name: 'Sign in' }).

By alt text and title

A locator by alt text finds an element, usually an image, by its text alternative: page.getByAltText('Shop logo'). A locator by title finds an element by its title attribute: page.getByTitle('Items in cart'). Testing Library warns that title is not read consistently by screen readers and is not visible by default.

By test id

A test id is an attribute added to the markup only so that tests can find the element. Playwright's page.getByTestId('cart-count') looks for the attribute data-testid by default. Another attribute name can be set with testIdAttribute in the test configuration or with selectors.setTestIdAttribute().

typescript
import { defineConfig } from '@playwright/test';

export default defineConfig({
  use: {
    testIdAttribute: 'data-pw',
  },
});

Playwright's documentation calls testing by test ids the most resilient way, because a test id survives changes of text and role. It also notes that test ids are not user-facing: a test that finds an element by test id does not check what the user sees.

CSS selectors

A CSS selector finds elements by tag, id, classes, attributes and position in the document: button.btn-primary, #email, form > button. In Playwright, CSS is passed to page.locator(), with or without the prefix css=. CSS selectors can express everything the other kinds express through attributes, for example [data-testid='cart-count'] or [placeholder='name@example.com']. Their stability depends on what they refer to: an id or a test id attribute is stable, classes and positions are not.

XPath

XPath is a language for selecting parts of an XML document; its version 1.0 is a W3C Recommendation of 16 November 1999. An XPath expression can select by structure, attributes and text: //button[normalize-space()='Sign in'], //form/input[2]. XPath can move from an element to its parent and siblings. In Playwright, XPath is passed to page.locator(), with the prefix xpath= or starting with //. Playwright's documentation notes that XPath does not pierce shadow roots.

Locators in WebDriver and Selenium

The W3C WebDriver specification defines five location strategies. A client passes a strategy and a value to the command Find Element, POST /session/{session id}/element.

Keyword What it finds
css selector Elements matching a CSS selector
link text Links whose visible text equals the value
partial link text Links whose visible text contains the value
tag name Elements with the given tag name
xpath Elements selected by an XPath expression

Selenium supports eight traditional strategies: the five above and id, name and class name. In the Python binding they are constants of the class By; their string values match the keywords: By.CSS_SELECTOR is "css selector", By.ID is "id". The strategy class name does not accept compound class names.

python
from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
driver.get("https://shop.example.com/login")

driver.find_element(By.ID, "email").send_keys("user@example.com")
driver.find_element(By.CSS_SELECTOR, "input[type='password']").send_keys("secret")
driver.find_element(By.XPATH, "//button[normalize-space()='Sign in']").click()

None of the eight strategies finds an element by ARIA role and accessible name, so in Selenium a role-like locator is expressed through CSS or XPath. Selenium's documentation on encouraged practices gives its own order: an id is preferred when it is available, unique and predictable; otherwise a well-written CSS selector; XPath works as well as CSS, but its syntax is complicated and often difficult to debug. It calls a locator by tag name dangerous, because a page usually contains many elements with the same tag, and notes that link text strategies work only on links.

Locators in Playwright and Testing Library

Playwright lists its recommended built-in locators in this order: getByRole, getByText, getByLabel, getByPlaceholder, getByAltText, getByTitle, getByTestId. Its best practices recommend prioritizing user-facing attributes and explicit contracts, and state that CSS and XPath are not recommended because the DOM can often change. A test of the login form uses only recommended locators:

typescript
import { test, expect } from '@playwright/test';

test('user signs in', async ({ page }) => {
  await page.goto('https://shop.example.com/login');

  await page.getByLabel('Email').fill('user@example.com');
  await page.getByLabel('Password').fill('secret');
  await page.getByRole('button', { name: 'Sign in' }).click();

  await expect(page.getByRole('heading', { name: 'My orders' })).toBeVisible();
  await expect(page.getByTestId('cart-count')).toHaveText('0');
});

Locators can be chained and filtered to narrow the search to a part of the page. page.getByRole('listitem').filter({ hasText: 'Product 2' }) selects the list item that contains the text, and a further getByRole('button', { name: 'Add to cart' }) selects the button inside it.

Testing Library follows the guiding principle: "The more your tests resemble the way your software is used, the more confidence they can give you." Its documentation divides queries into three groups by priority. The table compares the two orders.

Priority Testing Library Playwright
1 Accessible to everyone: getByRole, getByLabelText, getByPlaceholderText, getByText, getByDisplayValue getByRole, getByText, getByLabel, getByPlaceholder
2 Semantic: getByAltText, getByTitle getByAltText, getByTitle
3 Test ids: getByTestId getByTestId
Not listed CSS and XPath CSS and XPath, not recommended

Both orders put the role first and the test id last among their own methods. The difference is inside the first group: Testing Library places the label before the text, Playwright the text before the label.

Resilience to changes

The kinds of locators differ in which changes break them. The table compares them by three properties: stability under changes of markup, readability of the test, and dependence on the texts of the interface.

Locator kind Stability under markup changes Readability Depends on texts
Role and accessible name High High Yes, through the name
Label High High Yes
Placeholder Medium High Yes
Text Medium High Yes
Alt text, title Medium Medium Yes
Test id High Medium No
Id High if the id is stable Medium No
CSS by classes and structure Low Low No
XPath by structure Low Low No
XPath or CSS by text or attribute Medium Low Depends on the attribute

The assessment follows from what each locator refers to. A role and an accessible name change only when the element becomes a different control or gets a different name, which is a change the user notices too. A test id changes only when someone edits it, but it is invisible to the user, so a test that relies on it does not notice that the button lost its label. Classes and positions change with refactoring and styling, without any change of behavior.

A locator that depends on texts breaks when the text is edited. This is intended when the text is part of what the test checks, and unwanted when the text is only a way to find the element. Ids generated at runtime, for example from a counter, make locators by id unstable: the value can differ between page loads.

Choosing a locator

The recommendations of Playwright, Testing Library and Selenium lead to a common order of preference:

  1. Role and accessible name, for interactive elements: buttons, links, fields, checkboxes.
  2. Label, for form fields; text, for non-interactive elements.
  3. Placeholder, alt text, title, when the element has no role, label or text.
  4. Test id, when the user-facing attributes are absent, ambiguous or change for reasons unrelated to the test.
  5. CSS or XPath, when nothing else works, for example in third-party markup that cannot be changed; preferably by stable attributes rather than structure.

A locator that matches several elements is narrowed by chaining from a unique container rather than by position. A locator by role that cannot be written is a signal about the page itself: an element without an accessible name is also hard to use with a screen reader. Playwright's code generator, npx playwright codegen, picks locators by the same priority: role, text and test id.

Waiting for elements

A locator describes an element that may not exist yet. After a click, the page sends a request, receives a response and renders new content, and the test must wait for it. The way the tool waits is one of the causes of tests that fail without a defect; such tests are described in the article Flaky tests.

Auto-waiting in Playwright means that before each action the tool waits until the element passes actionability checks. For click the checks are: the element is visible, stable, receives events and is enabled; for fill it is visible, enabled and editable. If the checks do not pass within the timeout, the action fails with TimeoutError. A Playwright locator is resolved to an element each time it is used. Assertions such as expect(locator).toBeVisible() wait until the condition is met, while locator.isVisible() returns immediately.

Selenium has two mechanisms. An implicit wait is a global setting for the whole session: every element search retries until the timeout; its default value is 0, so a missing element causes an error immediately. An explicit wait is a loop in the test code that polls for a specific condition until it is true or the timeout expires. Selenium's documentation warns against mixing the two, because it can cause unpredictable wait times.

python
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait

wait = WebDriverWait(driver, timeout=5)
heading = wait.until(
    lambda d: d.find_element(By.XPATH, "//h1[normalize-space()='My orders']")
)
assert heading.is_displayed()

WebDriverWait.until calls the function until it returns a truthy value and raises TimeoutException when the timeout expires; by default it ignores NoSuchElementException between attempts. A WebElement found by Selenium refers to one specific element: if the page re-renders it, the reference becomes stale, and the element must be located again. Testing Library has a similar split: getBy queries return immediately, findBy queries return a promise and retry until a default timeout of 1000 ms.

Locators and localization

Locators by role, label, placeholder, text, alt text and title depend on the texts of the interface, and these texts differ between languages. A test written as getByRole('button', { name: 'Sign in' }) fails on the German version of the page, where the button has a different name. Playwright's own example of a test id shows the case: a button with the text Itinéraire and the attribute data-testid="directions", found by getByTestId('directions') regardless of the language.

A suite that runs against several locales has a few options:

  • one locale for most tests: Playwright sets the browser locale with the locale option in the configuration, for example locale: 'en-GB';
  • texts from the translation resources: the test takes the expected name from the same translation files that the application uses, so the locator follows the translation;
  • test ids for elements whose text varies: the locator does not depend on the language, and a separate check verifies the texts;
  • regular expressions in name or getByText, when the variants are few and known.

The choice also depends on what the test checks. A test of the translation itself must use texts; a test of a business flow need not.

Locators in an object repository

When the same element is used in many tests, its locator is written once and referred to by name. A change of the markup is then fixed in one place instead of every test. Two ways to organize this, page objects and an object repository with the structure "pages, elements", are described in the article Object repository and the Page Object pattern. Which test levels need locators at all is explained in the article Test automation levels and the test pyramid.

Locator, selector and element handle

Three words are used for related things.

Selector Locator Element handle
What it is A string in CSS or XPath syntax A strategy and value for finding an element A reference to one found element
Example button.btn-primary getByRole('button', { name: 'Sign in' }) Selenium WebElement, Playwright ElementHandle
When it is resolved When it is used Each time it is used (Playwright) Once, at the search
After re-rendering Not applicable Finds the new element Becomes stale

A selector is one possible value of a locator: a CSS or XPath locator contains a selector, a locator by role does not. Playwright's documentation states the difference between the last two directly: an ElementHandle points to a particular element, while a Locator captures the logic of how to retrieve an element. It discourages the use of ElementHandle in favor of locators and web-first assertions.

Terms

Term Meaning
Locator Description of how a UI test finds an element: a strategy and a value
Location strategy Method of finding an element: by role, label, text, test id, CSS, XPath and others
Accessible name Name of a user interface element exposed to assistive technology
Test id Attribute added to the markup only so that tests can find the element, by default data-testid
Auto-waiting Waiting by the tool, before each action, until the element passes actionability checks
Implicit wait Global session setting under which every element search retries until a timeout
Explicit wait Loop in the test code that polls for a specific condition until it is true or a timeout expires
Stale element reference Reference to an element that was found earlier but is no longer in the document

Sources

Series

Test automation
  1. Test automation levels and the test pyramid
  2. Locators in UI testsCurrent
  3. Object repository and the Page Object pattern
  4. Flaky tests
  5. Automated tests in CI

Wiki articles are written and maintained by the ComplexQA team.