Playwright Locators — 20 Real Examples (2026 Guide)
Every Playwright locator strategy explained with 20 real examples: getByRole, getByLabel, chaining, filtering, and the locator patterns senior SDETs prefer over XPath.

Last updated 2026-07-20 · 12 min read · By Avinash K
Locators are where 80% of Playwright test debt lives. Get them right and the suite stays green through 6 months of UI refactors; get them wrong and every sprint costs a day of maintenance. This is the locator playbook we use across 5,000+ tests in production.
Key takeaways
- The 5 built-in locator strategies ranked by resilience.
- 20 worked examples across forms, tables, modals, and iframes.
- When to use filter() vs nth() vs chaining.
- Why getByRole beats XPath in 95% of cases.
1. The priority order Playwright recommends
page.getByRole()— accessibility-first, resilient to DOM changes.page.getByLabel()— for form fields.page.getByPlaceholder()— when no label exists.page.getByText()— for buttons and links with unique copy.page.getByTestId()— last resort, when semantic queries fail.
Only fall back to CSS or XPath for third-party widgets you cannot instrument. See the official locators reference.
2. Ten basic examples
// 1. Button by role + name
await page.getByRole('button', { name: 'Sign in' }).click();
// 2. Form field by label
await page.getByLabel('Email').fill('a@b.com');
// 3. Link
await page.getByRole('link', { name: 'Pricing' }).click();
// 4. Checkbox
await page.getByRole('checkbox', { name: 'I agree' }).check();
// 5. Select option
await page.getByLabel('Country').selectOption('India');
// 6. Radio
await page.getByRole('radio', { name: 'Monthly' }).check();
// 7. Text (exact)
await page.getByText('Welcome back', { exact: true }).waitFor();
// 8. Placeholder
await page.getByPlaceholder('Search products').fill('shoes');
// 9. Alt text (image)
await page.getByAltText('Company logo').click();
// 10. Test id
await page.getByTestId('cart-count').should('have.text', '3');3. Ten advanced examples — filter, chain, iframe
// 11. Filter a list by text
await page.getByRole('listitem').filter({ hasText: 'Product A' }).click();
// 12. Chain within a card
await page.getByRole('article', { name: 'Order 4211' })
.getByRole('button', { name: 'Cancel' }).click();
// 13. Nth (avoid if possible)
await page.getByRole('row').nth(2).click();
// 14. Combine has and hasNot
await page.getByRole('row').filter({ hasNot: page.getByText('Delivered') });
// 15. Iframe
await page.frameLocator('#stripe').getByLabel('Card number').fill('4242 ...');
// 16. Shadow DOM (built-in support)
await page.getByRole('button', { name: 'Open' }).click();
// 17. Regex text
await page.getByText(/order #\d+/i).click();
// 18. Wait for locator
await page.getByText('Loaded').waitFor({ state: 'visible' });
// 19. Assert count
await expect(page.getByRole('row')).toHaveCount(10);
// 20. Screenshot a single element
await page.getByRole('article', { name: 'Order 4211' }).screenshot({ path: 'card.png' });4. Anti-patterns to avoid
page.locator('xpath=//div[3]/span[2]')— positional XPath dies on any refactor.- CSS with generated class names (
.css-abc123) — Tailwind and CSS-in-JS make these unstable. - Chaining
.nth(0)instead of.first()— less readable. - Missing
awaitbefore an action — silent race condition.
Compare against Selenium's locator model to see why Playwright's role-based approach is more maintainable.