Playwright Locators: The Definitive Guide
Playwright has completely changed how we find elements on a page. We no longer rely on brittle CSS classes or fragile XPaths. Instead, we use User-Facing Locators (also known as Semantic Locators).
Why User-Facing Locators?
Playwright's philosophy is: "Test the application exactly how a human user interacts with it."
A user doesn't look for . A user looks for a button that says "Submit".
By testing exactly what the user sees, your tests become incredibly resilient to structural code changes. If developers rewrite the entire frontend from React to Vue, but the button still says "Submit", your Playwright tests *will still pass!*
---
π The "Golden Rule" Hierarchy of Locators
Playwright officially recommends prioritizing locators in the following strict order. Memorize this for your interviews!
π₯ 1. getByRole() (The Absolute Best)
This is the most powerful and resilient locator. It uses the accessibility tree. If you use getByRole, you are simultaneously testing that your app is accessible to screen readers!
typescript
// Finds a
π₯ 2. getByText() (For non-interactive text)
Use this when you just need to verify a paragraph, span, or div that contains specific text, but isn't a semantic role like a button or link.
typescript
// Partial match (default)
await expect(page.getByText('Welcome back,')).toBeVisible();
// Exact match
await expect(page.getByText('Order Complete', { exact: true })).toBeVisible();
π₯ 3. getByLabel() (For Form Inputs)
Whenever you are filling out a form, you should target the input field by its associated . This mimics how a user reads the label and clicks the input box next to it.
typescript
// Finds the that is linked to the
π
4. getByPlaceholder() (Fallback for Inputs)
If an input lacks a proper label (which is bad for accessibility!), fallback to the placeholder text.
typescript
await page.getByPlaceholder('Search for products...').fill('Laptop');
ποΈ 5. getByTestId() (The Ultimate Fallback)
Sometimes, the UI is highly dynamic, multi-lingual (translations change the text), or lacks accessible roles. In these cases, developers can add a special data-testid attribute to the HTML.
html
...
typescript
await page.getByTestId('user-profile-card').click();
*Note: While perfectly stable, this is the lowest priority because it doesn't test the application from the user's perspective.*
---
β Locators to Avoid (Anti-Patterns)
Never use XPath or CSS unless absolutely necessary.
typescript
// β TERRIBLE: Breaks if any div is added or removed.
await page.locator('//*[@id="root"]/div/div[2]/div/button').click();
// β BAD: Breaks if developers change the styling framework (e.g., migrating to Tailwind).
await page.locator('.submit-btn-active').click();