Playwright
Playwright Locators: How to Find Elements Reliably
Learn how Playwright locators work, how to choose role, label, text, and test-id locators, and how strictness and web-first assertions affect reliability.
- Published
- Reading time
- 3 min read
- Difficulty
- Beginner
- Audience
- For Automation tester, Developer, QA engineer
Article overview
What you will learn
- What a locator represents
- How role, label, placeholder, text, alt, title, and test-id locators differ
- How strictness, chaining, and filtering work
- Why first, last, and nth can hide ambiguity
- How web-first assertions improve observable checks
Before you begin
Prerequisites
- Node.js installed
- An existing Playwright Test project
- Basic TypeScript or JavaScript knowledge
What is a locator?
Locator stability matters because UI structure changes often. A selector tied to several container classes can break during harmless redesign work. A locator based on a button’s role and accessible name often expresses user intent more directly.
User-facing and explicit locators
| Locator | Typical use | Example |
|---|---|---|
| getByRole | Controls and landmarks with an accessible name | getByRole('button', { name: 'Sign in' }) |
| getByLabel | Form controls associated with a label | getByLabel('Email') |
| getByPlaceholder | A placeholder when no stronger label exists | getByPlaceholder('name@example.com') |
| getByText | Visible non-interactive text | getByText('Order confirmed') |
| getByAltText | Images with alternative text | getByAltText('Company logo') |
| getByTitle | Elements with a title attribute | getByTitle('Issues count') |
| getByTestId | An explicit test contract | getByTestId('checkout-total') |
Practical login-form example
import { test, expect } from '@playwright/test'; test('shows an error for invalid credentials', async ({ page }) => { await page.goto('/login'); await page.getByLabel('Email').fill('reader@example.test'); await page.getByLabel('Password').fill('wrong-password'); await page.getByRole('button', { name: 'Sign in' }).click(); await expect( page.getByRole('alert') ).toHaveText('Email or password is incorrect');});The labels identify the fields, and the button role plus name identifies the action. The alert assertion checks an observable result and retries until the message appears or the assertion times out. In a real project, arrange an isolated account and avoid relying on shared credentials.
Strictness, chaining, and filtering
Playwright operations that imply one target are strict. If a locator matches two buttons, a click fails instead of choosing silently. Narrow the intent by chaining within a region or filtering a list item by text or another locator.
const product = page .getByRole('listitem') .filter({ hasText: 'Desk lamp' }); await product.getByRole('button', { name: 'Add to cart' }).click();This locator first identifies product rows, narrows them to the row containing the product name, and then finds the action inside that row. It avoids depending on the row’s numeric position.
Risks of first, last, and nth
first(), last(), and nth() can be appropriate when position is the behavior being tested. They are risky when used only to silence a strictness error because a new element can change the position without changing the test code. Prefer a unique description of intent.
CSS and XPath considerations
CSS and XPath are not always wrong. They may be necessary for legacy markup, canvas-adjacent elements, or an interface with no useful accessibility or test contract. Keep them short, avoid deep structural chains, and ask whether improving the application’s labels would also improve accessibility.
Use web-first assertions
Assertions such as toBeVisible() and toHaveText() retry against the locator. A one-time check such as expect(await locator.isVisible()).toBe(true) does not provide the same retry behavior. Waiting should be tied to the expected UI state, not an arbitrary timeout.
Common mistakes
- Using a deep CSS path that mirrors the current DOM
- Selecting the first match without explaining why position matters
- Using placeholder text when a stable label is available
- Adding a test ID to every element instead of using user-facing semantics
- Checking visibility once instead of using a retrying assertion
- Forcing an action before understanding why actionability checks fail
Locator-selection checklist
- Start with the way a user or assistive technology identifies the control.
- Prefer role and accessible name for interactive elements.
- Use labels for form fields.
- Use visible text for meaningful content when wording is part of the contract.
- Use a deliberate test ID when no dependable user-facing locator exists.
- Use CSS or XPath carefully when the application provides no better contract.
- Confirm that the locator identifies one intended target and remains readable.
References and further reading
- LocatorsPlaywright documentation · Verified 2026-07-19
- Auto-waitingPlaywright documentation · Verified 2026-07-19
- AssertionsPlaywright documentation · Verified 2026-07-19
