Playwright Locators Cheat Sheet With Examples
Pick the right locator and your tests stay stable when the page changes. Here is every built-in Playwright locator, when to use it and how to combine them.
- Prefer user-facing Playwright locators:
getByRole,getByLabel,getByText, thengetByTestId. - Use CSS or XPath only when nothing better exists.
- Locators auto-wait and re-query the page, so you rarely need manual waits.
- Narrow results with
filter()and chaining instead of long selectors.
Almost every flaky UI test starts with a fragile selector, which is why Playwright locators matter. A button moves, a class name changes and suddenly half the suite is red.
Playwright locators solve most of this. They find elements the way a user would, by role, label or visible text, and they wait for the element to be ready before acting.
This Playwright locators cheat sheet lists every built-in option with examples, so you can choose quickly and write tests that survive design changes.
What are Playwright locators?
A locator is an object that describes how to find an element. It is lazy: Playwright searches the page each time you use it, so it always works with the current DOM. Create one with page.getByRole(), page.locator() and similar methods. The official locators guide explains the full API.
Playwright locators cheat sheet table
| Locator | Finds by | Example |
|---|---|---|
| getByRole | ARIA role and name | page.getByRole('button', { name: 'Save' }) |
| getByLabel | Form control label | page.getByLabel('Email') |
| getByPlaceholder | Input placeholder | page.getByPlaceholder('Search') |
| getByText | Visible text | page.getByText('Welcome back') |
| getByAltText | Image alt text | page.getByAltText('Company logo') |
| getByTitle | title attribute | page.getByTitle('Close') |
| getByTestId | data-testid attribute | page.getByTestId('cart-total') |
| locator | CSS or XPath | page.locator('css=.card') |
Playwright getByRole: your first choice
Role locators match what assistive technology sees, so they test your app the way users experience it, and they also nudge you toward accessible markup.
await page.getByRole('button', { name: 'Sign in' }).click();
await page.getByRole('heading', { name: 'Dashboard', level: 1 });
await page.getByRole('link', { name: /pricing/i }).click();
await page.getByRole('checkbox', { name: 'Remember me' }).check();
await page.getByRole('textbox', { name: 'Email' }).fill('user@example.com');
Useful options include name (string or regex), exact, level, checked and disabled.
getByLabel, getByText and getByTestId
Form fields: getByLabel
await page.getByLabel('Password').fill('Secret123');
await page.getByLabel('Country').selectOption('India');
Visible content: getByText
await expect(page.getByText('Order placed')).toBeVisible();
await page.getByText('Terms', { exact: true }).click();
Text matching is case-insensitive and matches substrings by default, so use exact: true when two elements share similar text.
Stable hooks: getByTestId
When an element has no good role or text, ask developers for a data-testid attribute. If your team uses a different attribute name, change it once in the config:
// playwright.config.ts
export default defineConfig({
use: { testIdAttribute: 'data-qa' },
});
CSS and XPath locators
Use page.locator() when the user-facing options do not work, for example with legacy markup or canvas-based widgets.
await page.locator('#checkout-form input[name="zip"]').fill('560001');
await page.locator('xpath=//table//tr[2]/td[1]').textContent();
Keep these short and avoid depending on layout classes such as .col-md-6 or deep chains like div > div > span. Playwright also documents other locator types such as :has-text() and layout selectors.
Filtering and chaining Playwright locators
Real pages repeat elements. Narrow down with filter(), chain locators, or use position helpers.
// find a product card by its heading, then click its button
const card = page.getByRole('listitem').filter({ hasText: 'Wireless Mouse' });
await card.getByRole('button', { name: 'Add to cart' }).click();
// filter by a child element
await page.getByRole('row').filter({ has: page.getByText('Overdue') }).count();
// position helpers
await page.getByRole('listitem').first().click();
await page.getByRole('listitem').nth(2).click();
await page.getByRole('listitem').last().click();
// combine conditions
const save = page.getByRole('button', { name: 'Save' }).and(page.getByTitle('Save draft'));
const either = page.getByText('Success').or(page.getByText('Saved'));
Quick tip: Scope first, then search. Locate the container (a dialog, a card, a table row), then find the button inside it. This avoids “strict mode violation” errors and keeps selectors short.
Frames and lists
await page.frameLocator('#payment-frame').getByLabel('Card number').fill('4242 4242 4242 4242');
for (const item of await page.getByRole('listitem').all()) {
console.log(await item.textContent());
}
Auto-waiting and strict mode
Before an action such as click(), Playwright waits until the element is attached, visible, stable, enabled and able to receive events. So you almost never need waitForTimeout().
Locators are also strict: if your locator matches more than one element and you try to act on it, Playwright throws an error instead of guessing. Fix it by making the locator more specific, not by adding .first() everywhere. See the Playwright best practices for more advice, and our guide to end to end testing best practices for the wider picture.
Common locator mistakes
- Copying long selectors from DevTools that break after the next UI change.
- Using text that changes with language or A/B tests when a test ID would be safer.
- Adding sleeps instead of asserting the expected state.
- Using
nth()by default, which hides bugs when the order changes. - Skipping page objects, which leaves duplicate selectors in dozens of files. Our test automation framework example shows a cleaner structure.
Not sure whether Playwright is the right tool? Compare it in Playwright vs Cypress, or see how an AI assistant can drive it in Playwright MCP.
Final thoughts
The best Playwright locators are the ones that describe what a user sees: roles, labels and text first, test IDs second, CSS last. Add filtering and chaining for repeated elements, and let auto-waiting handle timing. Review your existing tests this week and replace the three most fragile selectors with getByRole.
Frequently asked questions
What is the best locator in Playwright?
getByRole is the recommended first choice because it matches how users and assistive technology see the page. Use getByTestId when no accessible role or text is available.
What is the difference between locator() and getByRole()?
page.locator() accepts CSS or XPath selectors, while getByRole() finds elements by ARIA role and accessible name. The second is usually more resilient.
Why do I get a strict mode violation?
Your locator matched more than one element. Make it more specific by scoping to a container, filtering with hasText, or adding the right role name.
Do Playwright locators wait automatically?
Yes. Actions wait for the element to be attached, visible, stable and enabled, and assertions retry until they pass or time out.
Can I use XPath in Playwright?
Yes, with page.locator(‘xpath=…’), but CSS and user-facing locators are easier to read and maintain.
Keep learning
Need help with testing, WordPress or SEO?
We help teams ship better software and grow their sites. Tell us what you are working on and we will suggest a clear next step.