Locators are the instructions an automation tool uses to find an element on a page. A good locator points to the intended element clearly and keeps working when the interface changes in harmless ways.

Types of locators

Accessible role

Finds an element as a user or assistive technology perceives it, such as a button named “Save”.

Label, placeholder or text

Useful for form fields and visible content. Prefer stable, user-facing wording.

Test ID / data attribute

A dedicated contract such as data-testid="checkout". Very stable when semantic locators are not suitable.

CSS selector

Targets IDs, classes, attributes, relationships and element states using browser-native syntax.

XPath

Traverses the DOM by structure, attributes or text. Powerful, but long structural paths are brittle.

ID, name, tag or link text

Classic WebDriver strategies. Simple and effective when the selected value is unique and stable.

CSS selector syntax

TargetSyntaxExample
ID#id#email
Class.class.primary-button
Attribute[attribute="value"][data-testid="save"]
Attribute contains[attribute*="part"][href*="checkout"]
Descendantparent childform input
Direct childparent > childnav > a
Multiple conditionstag.class[attribute]button.primary[type="submit"]
Position:nth-child(n)tbody tr:nth-child(2)
Browser console: test a CSS selector with document.querySelector('selector') for the first match or document.querySelectorAll('selector') for every match.

XPath syntax

TargetExample
Any element by tag//button
Exact attribute//input[@name='email']
Exact visible text//button[normalize-space()='Save']
Partial text//a[contains(normalize-space(), 'Account')]
Partial attribute//div[contains(@class, 'alert')]
Child relationship//form[@id='login']//input
Following sibling//label[.='Email']/following-sibling::input
Parent relationship//span[.='Total']/parent::div
Chrome DevTools: use $x("//button") in the Console to inspect XPath matches. Avoid absolute paths such as /html/body/div[2]/...; small DOM changes will break them.

Framework examples

Playwright · TypeScript

await page.getByRole('button', { name: 'Save' }).click(); await page.getByLabel('Email').fill('qa@example.com'); await page.getByTestId('checkout').click(); await page.locator('[data-status="ready"]').click();

Cypress

cy.get('[data-cy="save"]').click(); cy.contains('button', 'Save').click(); cy.get('input[name="email"]').type('qa@example.com');

Selenium · Python

from selenium.webdriver.common.by import By driver.find_element(By.ID, 'email') driver.find_element(By.CSS_SELECTOR, '[data-testid="save"]') driver.find_element(By.XPATH, "//button[normalize-space()='Save']")

Robot Framework

Click Element css:[data-testid="save"] Input Text id:email qa@example.com Click Element xpath://button[normalize-space()='Save']

Best practices

  1. Prefer how users identify the element. Accessible role, name, label and visible text usually produce readable tests.
  2. Use a dedicated test attribute when needed. Agree on data-testid, data-cy or one project-wide equivalent.
  3. Make the locator unique. Confirm that it matches exactly the intended element.
  4. Avoid styling classes and deep DOM paths. They often change without any functional change.
  5. Scope before indexing. Locate a stable container, then the element inside it. Use nth() only when position is genuinely meaningful.
  6. Let the framework wait. Prefer Playwright auto-waiting, Cypress retrying or explicit WebDriver waits over fixed sleeps.
  7. Keep locators readable. A teammate should understand the target without opening the page source.
Simple priority: role/name or label → stable text → test ID → concise CSS → XPath when its traversal or text features are genuinely useful.

Tools and links