Playwright Locators
A Locator is one of the core concepts in Playwright. It represents a way to find elements on a page.
This chapter introduces Playwright's recommended locating methods: locating elements by role, text, label, and placeholder.
What is a Locator
A Locator is an abstract object that Playwright uses to find elements on a page.
Unlike traditionaldocument.querySelector()ways, Playwright's Locator hasautomatic waiting and automatic retryingcapabilities.
When the page content changes dynamically, the Locator will continuously re-find elements until it finds a matching element or times out.
Example
const button = page.getByRole('button', { name: 'Submit' });
// When performing an action, Playwright automatically waits for the button to appear and become actionable
await button.click();
How Locators Work
When you create a Locator, Playwright only records a lookup rule.
When you call.click()、.fill()actions orexpect(locator)assertions, the Locator actually starts to find elements.
If the element cannot be found, the Locator will keep retrying until the operation times out.
Locating by Role: getByRole()
getByRole()is Playwright's most recommended locating method.
It uses the element'saccessibility role (ARIA Role)to find elements, which is the closest to how users perceive them.
Basic Usage
Example
const submitBtn = page.getByRole('button', { name: 'Submit' });
await submitBtn.click();
// Locate a link - by role "link"
const homeLink = page.getByRole('link', { name: 'Home' });
await homeLink.click();
// Locate a heading - by role "heading"
const mainTitle = page.getByRole('heading', { name: 'Welcome to EXAMPLE' });
// Locate a text box - by role "textbox"
const searchBox = page.getByRole('textbox', { name: 'Search' });
await searchBox.fill('Playwright');
Common ARIA Roles
| Role | Corresponding HTML Elements | Usage Example |
|---|---|---|
'button' | <button>、role="button" | getByRole('button', { name: '登录' }) |
'link' | <a> | getByRole('link', { name: '了解更多' }) |
'heading' | <h1>~<h6> | getByRole('heading', { name: 'EXAMPLE 教程' }) |
'textbox' | <input type="text">、<textarea> | getByRole('textbox', { name: '用户名' }) |
'checkbox' | <input type="checkbox"> | getByRole('checkbox', { name: '记住我' }) |
'radio' | <input type="radio"> | getByRole('radio', { name: '男' }) |
'combobox' | <select> | getByRole('combobox', { name: '城市' }) |
'img' | <img> | getByRole('img', { name: 'Logo' }) |
'list' | <ul>、<ol> | getByRole('list') |
'listitem' | <li> | getByRole('listitem') |
'navigation' | <nav> | getByRole('navigation') |
'table' | <table> | getByRole('table') |
The name Option
nameThe option uses the element'saccessible nameto narrow down the scope.
Sources of the accessible name include: the element's text content,aria-labelattributes, associated<label>etc.
Example
page.getByRole('button', { name: 'Login' });
// Match by aria-label
page.getByRole('button', { name: 'Close dialog' });
// Regular expression matching
page.getByRole('link', { name: /EXAMPLE|rookie/ });
State Options
In addition toname,getByRoleit also supports multiple state options:
Example
page.getByRole('checkbox', { checked: true });
// Locate the disabled button
page.getByRole('button', { disabled: true });
// Locate the expanded dropdown menu
page.getByRole('button', { expanded: true });
// Locate the selected option
page.getByRole('option', { selected: true });
// Combine multiple states
page.getByRole('checkbox', { name: 'Agree to the terms', checked: false });
Locating by Text: getByText()
getByText()By the element'stext contentto locate.
Example
page.getByText('Welcome to EXAMPLE');
// Partial match (default)
page.getByText('EXAMPLE');
// Force exact match
page.getByText('EXAMPLE', { exact: true });
// Regular expression match
page.getByText(/EXAMPLE|rookie tutorial/);
getByTextIt matches the complete text content of the element, sogetByText('EXAMPLE', { exact: true })it will only match elements whose text content is exactly "EXAMPLE".
By default, when exact matching is not used, substring matching is sufficient (for example,getByText('EXAMPLE')it will match "Welcome to EXAMPLE").
Locating by Label: getByLabel()
getByLabel()Used to locate<label>associated form controls.
Example
// <label for="username">Username</label>
// <input id="username" type="text" />
// Locate the input via the label text
await page.getByLabel('Username').fill('example_user');
// HTML structure:
// <label>Password <input type="password" /></label>
// Nested labels are also valid
await page.getByLabel('Password').fill('password123');
It also supports{ exact: true }exact matching options.
Locating by Placeholder: getByPlaceholder()
getByPlaceholder()Through theplaceholderplaceholder attribute to locate input fields.
Example
await page.getByPlaceholder('Please enter a search keyword').fill('Playwright');
// HTML structure: <textarea placeholder="Tell us what you think..."></textarea>
await page.getByPlaceholder('Tell us what you think...').fill('The EXAMPLE tutorial is great!');
Choosing Between getByLabel and getByPlaceholder
| Method | Applicable scenario | Priority |
|---|---|---|
getByLabel() | When there is a <label> element | Use it first |
getByPlaceholder() | When there is no label, but there is a placeholder | Second choice |
getByRoleis Playwright's most recommended locating method because it simulates how users identify elements by role. The next isgetByLabelandgetByPlaceholder. Only consider using it when there are no semantic elementsgetByTestIdor CSS selectors.
getByAltText() for Image Locating
getByAltText()Through the image'saltalt attribute to locate<img>the <img> element.
Example
await expect(page.getByAltText('EXAMPLE Logo')).toBeVisible();
// Regular expression match
await expect(page.getByAltText(/logo/i)).toBeVisible();
getByTitle() for Title Attribute Locating
getByTitle()Through the element'stitletitle attribute to locate.
Example
await page.getByTitle('Close current page').click();
getByAltTextandgetByTitleUsed less frequently; only when the element has an explicit alt or title attribute and cannot be located by other means.
getByTestId() for Test ID Locating
getByTestId()YesFallback solution, viadata-testidattribute to locate the element.
Example
await page.getByTestId('submit-btn').click();
// Can contain multiple matches (e.g., list items)
const items = page.getByTestId('todo-item');
await expect(items).toHaveCount(3);
Customizing the testId Attribute
If your project does not usedata-testid, you canplaywright.config.tscustomize the attribute name in:
Example
export default defineConfig({
use: {
// Use data-cy instead of data-testid (for Cypress migration)
testIdAttribute: 'data-cy',
// Or use id as testId
// testIdAttribute: 'id',
},
});
page.locator() CSS and XPath Locating
page.locator()Supports CSS selectors and XPath, serving as a more flexible fallback.
CSS Selectors
Example
await page.locator('.submit-btn').click();
// By ID
await page.locator('#username').fill('example_user');
// By attribute
await page.locator('[data-type="primary"]').click();
// By tag + class combination
await page.locator('button.primary').click();
// By parent-child relationship
await page.locator('nav a').first().click();
// CSS selector containing specific text
await page.locator('button:has-text("Submit")').click();
XPath Locating
Example
await page.locator('xpath=//button[@type="submit"]').click();
// Locate by text
await page.locator('xpath=//h1[contains(text(), "EXAMPLE")]').click();
CSS selectors and XPath are the last resort; prioritize using
getByRole、getByTextsemantic location methods. CSS selectors are prone to breaking due to style refactoring, and XPath has poor readability and slower performance.
Locator Chaining
Playwright's Locator supports chaining, allowing further filtering on the current matching results.
.first()、.last()、.nth()
Example
const firstItem = page.getByRole('listitem').first();
// Get the last one
const lastItem = page.getByRole('listitem').last();
// Get the 2nd one (index starts at 0)
const secondItem = page.getByRole('listitem').nth(1);
.filter() Filtering
Example
page.getByRole('button').filter({ hasText: 'Save' });
// Filter out those not containing specific text
page.getByRole('listitem').filter({ hasNotText: 'Deleted' });
// Filter those containing a specific child element
page.getByRole('listitem').filter({ has: page.getByRole('button') });
// Filter those not containing a specific child element
page.getByRole('listitem').filter({ hasNot: page.getByTestId('badge') });
.locator() for Child Element Locating
Example
const list = page.getByTestId('todo-list');
const deleteBtn = list.locator('button.delete');
await deleteBtn.click();
// Multi-level nesting
page
.getByRole('dialog')
.locator('div.content')
.locator('button')
.filter({ hasText: 'Confirm' })
.click();
.and() and .or() Logical Combinations (1.63+)
Example
const btn = page.getByRole('button')
.and(page.getByText('Submit'));
// Satisfy either condition
const target = page.getByText('EXAMPLE')
.or(page.getByTitle('EXAMPLE'));
Summary of Locator Strategy Priority
Arranged from most to least recommended:
| Priority | Method | Usage condition |
|---|---|---|
| 1 (Most recommended) | getByRole() | Element has a clear ARIA role |
| 2 | getByLabel() | Form element has an associated label |
| 3 | getByPlaceholder() | Input field has a placeholder |
| 4 | getByText() | Element has clear text content |
| 5 | getByAltText() | Image has alt attribute |
| 6 | getByTitle() | Element has title attribute |
| 7 (Fallback) | getByTestId() | When unable to locate using the above methods |
| 8 (Last resort) | locator() | CSS selectors / XPath |