|

Playwright Locators Masterclass: All 18 Strategies With Real Code Examples

Playwright offers 18 different locator strategies — but most engineers only use 3 or 4. Choosing the right locator is the difference between a test suite that survives UI changes and one that breaks every sprint.

Contents

Tier 1: Semantic Locators (Always Prefer These)

These locators are based on accessibility roles and visible content. They are the most resilient to DOM changes.

// 1. getByRole — BEST choice, ARIA-based
await page.getByRole('button', { name: 'Submit' }).click();

// 2. getByLabel — for form inputs with labels
await page.getByLabel('Email address').fill('test@example.com');

// 3. getByText — matches visible text
await page.getByText('Welcome back').click();

// 4. getByPlaceholder — for inputs with placeholder text
await page.getByPlaceholder('Search...').fill('playwright');

// 5. getByAltText — for images
await page.getByAltText('Company logo').click();

// 6. getByTitle — for elements with title attribute
await page.getByTitle('Close dialog').click();

Tier 2: Test ID Locators (Best for Dynamic Content)

// 7. getByTestId — uses data-testid attribute
await page.getByTestId('submit-button').click();

// Configure custom test ID attribute
// playwright.config.ts: use: { testIdAttribute: 'data-qa' }

Tier 3: CSS and XPath (Use Sparingly)

// 8. CSS selector
await page.locator('.btn-primary').click();

// 9. CSS with pseudo-classes
await page.locator('button:has-text("Submit")').click();
await page.locator('div:visible').click();

// 10. XPath
await page.locator('xpath=//button[@type="submit"]').click();

// 11. CSS with nth-child
await page.locator('li:nth-child(3)').click();

Tier 4: Advanced Locators

// 12. filter() — chain conditions
await page.getByRole('listitem').filter({ hasText: 'Active' }).click();

// 13. .and() — combine locators
const button = page.getByRole('button').and(page.getByText('Save'));

// 14. .or() — match either
const cta = page.getByRole('button', { name: 'Buy' }).or(page.getByRole('button', { name: 'Purchase' }));

// 15. frameLocator() — for iframes
await page.frameLocator('#payment-iframe').getByRole('button', { name: 'Pay' }).click();

// 16. Shadow DOM — pierces automatically
await page.locator('my-component').getByRole('button').click();

// 17. Layout-based (left-of, right-of, above, below, near)
await page.getByText('Price').locator('xpath=..').getByRole('textbox').fill('100');

// 18. has/hasNot filtering
await page.locator('div', { has: page.getByText('Error') }).screenshot();

Locator Decision Tree

  1. Does the element have a visible role? Use getByRole()
  2. Is it a labeled form field? Use getByLabel()
  3. Does it have unique visible text? Use getByText()
  4. Does your team use data-testid? Use getByTestId()
  5. None of the above? Use CSS selector as last resort
  6. Never use XPath unless absolutely necessary

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.