Beginner Chapter 4 · 14 min read

Locators & Selectors

Master Playwright's built-in locators — getByRole, getByText, getByLabel, CSS, XPath — and learn to chain and filter them.

Why Locators Matter

Choosing the right locator strategy is the single biggest factor in test reliability. Locators that are tightly coupled to implementation details — such as generated class names or element indices — break whenever a developer refactors the markup. Playwright's semantic locators like getByRole and getByLabel mirror how assistive technologies navigate the page, making tests resilient and accessible-by-design.

Recommended Locator Priority

The Playwright team recommends a priority order: getByRole first (matches ARIA roles), then getByLabel (for form controls), then getByPlaceholder, getByText, getByAltText, getByTitle, and finally getByTestId when you control the markup. Fall back to CSS or XPath only when semantic locators are not feasible.

Tip: Add data-testid attributes to important UI components as part of your development workflow. This gives tests a stable, intent-revealing handle that survives design changes.

Chaining and Filtering

Locators can be chained to narrow scope — for example, find the table row that contains a specific customer name and then click the delete button within that row. The .filter() method refines a locator set by inner text or another nested locator, while .nth() picks a specific item from a list. These patterns eliminate the need for brittle index-based CSS selectors.

LocatorBest forExample
getByRoleButtons, links, headings, inputsgetByRole('button', {name:'Save'})
getByLabelForm fields with a labelgetByLabel('Email')
getByTextParagraphs, spans, list itemsgetByText('Welcome back')
getByTestIdCustom data-testid attributesgetByTestId('submit-btn')
locator(css)Specific CSS selectorslocator('.card .title')
locator(xpath)Complex DOM traversallocator('//tr[td[text()="Alice"]]')
locators.spec.js
import { test, expect } from '@playwright/test';

test('locator strategies showcase', async ({ page }) => {
  await page.goto('/components');

  // By ARIA role — most reliable
  await page.getByRole('button', { name: 'Submit' }).click();

  // By associated label element
  await page.getByLabel('Search').fill('playwright');

  // By visible text content
  await expect(page.getByText('No results found')).toBeHidden();

  // By data-testid attribute
  await page.getByTestId('user-avatar').click();

  // CSS selector fallback
  await page.locator('.notification-badge').waitFor();

  // Chaining — find delete button inside a specific row
  const row = page.getByRole('row').filter({ hasText: 'Alice' });
  await row.getByRole('button', { name: 'Delete' }).click();

  // nth() — second item in a list
  await page.getByRole('listitem').nth(1).click();

  // XPath for complex traversal
  await page.locator('xpath=//td[normalize-space()="Active"]/../td[1]').click();
});
LocatorsTest.java
import com.microsoft.playwright.*;
import com.microsoft.playwright.options.AriaRole;
import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;

class LocatorsTest {
  void showcaseLocators(Page page) {
    page.navigate("http://localhost:3000/components");

    // By ARIA role
    page.getByRole(AriaRole.BUTTON,
        new Page.GetByRoleOptions().setName("Submit")).click();

    // By label
    page.getByLabel("Search").fill("playwright");

    // By test ID
    page.getByTestId("user-avatar").click();

    // Chaining — delete button inside specific row
    Locator row = page.getByRole(AriaRole.ROW)
        .filter(new Locator.FilterOptions().setHasText("Alice"));
    row.getByRole(AriaRole.BUTTON,
        new Locator.GetByRoleOptions().setName("Delete")).click();

    // CSS selector fallback
    assertThat(page.locator(".notification-badge")).isVisible();
  }
}
test_locators.py
from playwright.sync_api import Page, expect

def test_locator_strategies(page: Page):
    page.goto("/components")

    # By ARIA role — most robust
    page.get_by_role("button", name="Submit").click()

    # By associated label
    page.get_by_label("Search").fill("playwright")

    # By visible text
    expect(page.get_by_text("No results found")).to_be_hidden()

    # By data-testid
    page.get_by_test_id("user-avatar").click()

    # Chaining — delete button in row containing "Alice"
    row = page.get_by_role("row").filter(has_text="Alice")
    row.get_by_role("button", name="Delete").click()

    # nth() picks item by index
    page.get_by_role("listitem").nth(1).click()

    # XPath fallback
    page.locator("xpath=//td[normalize-space()='Active']/../td[1]").click()

Playwright Beginner Locators & Selectors

Written by PV

© 2026 All Rights Reserved