Playwright Locators Explained: Best Practices for Stable Automation Tests
Locators are the backbone of UI automation. Weak locators create flaky tests, slow maintenance, and constant firefighting. Strong locators make automation feel boring in the best possible way. Playwright was designed with this reality in mind, and its locator system is one of the biggest reasons Playwright tests remain stable.
In this guide, you’ll learn how Playwright locators work, the best locator strategies, common anti-patterns, and how to design locators that survive UI changes.
This article is essential reading if you want long-term success with Playwright automation testing.
What Are Playwright Locators?
A locator in Playwright is a way to identify and interact with elements on a web page. Unlike traditional tools, Playwright locators are live objects.
This means:
They automatically re-evaluate when the DOM changes
They wait for elements to become actionable
They work seamlessly with auto-waiting and assertions
This is fundamentally different from static element references used in older frameworks.
Why Locator Strategy Matters
Most flaky tests are not caused by timing. They are caused by fragile locators.
Bad locators:
Break when UI layout changes
Depend on CSS or DOM structure
Require frequent updates
Good locators:
Reflect user behavior
Survive refactoring
Read like documentation
Playwright encourages the second approach.
The Golden Rule of Playwright Locators
Prefer user-facing attributes over technical attributes.
This single rule eliminates most locator problems.
Playwright provides built-in methods that align tests with accessibility and user intent.
Recommended Locator Priority (Best to Worst)
1. getByRole (Best Practice)
await page.getByRole('button', { name: 'Submit' }).click();
Why this is ideal:
Matches how users interact with the UI
Uses accessibility roles
Resistant to DOM changes
This should be your default choice whenever possible.
2. getByLabel (Great for Forms)
await page.getByLabel('Email').fill('test@example.com');
Why it works well:
Tied to form semantics
Independent of layout
Extremely readable
3. getByPlaceholder
await page.getByPlaceholder('Enter username').fill('admin');
Use this when labels are not present, but placeholders are stable.
4. getByText
await page.getByText('Order successful').isVisible();
Best for:
Static messages
Toast notifications
Avoid using it for dynamic content that changes frequently.
5. getByTestId (Controlled Escape Hatch)
await page.getByTestId('checkout-button').click();
Use test IDs when:
UI has no accessible roles
Text is dynamic or localized
Elements are visually complex
Test IDs should be purpose-built, not reused randomly.
CSS and XPath Locators (Use Carefully)
CSS Selectors
await page.locator('.btn.primary').click();
CSS selectors are fast but brittle. Use them only when semantic locators are unavailable.
XPath (Last Resort)
await page.locator("//button[text()='Submit']").click();
XPath tightly couples tests to DOM structure. Small UI changes often break these locators.
Locator vs Element Handle (Critical Difference)
Element Handle (Avoid)
const button = await page.$('#submit');
await button.click();
This captures a snapshot of the DOM and bypasses auto-waiting.
Locator (Recommended)
await page.locator('#submit').click();
Locators remain live and resilient during re-renders.
Chaining and Filtering Locators
Playwright allows expressive locator chaining:
await page
.getByRole('table')
.getByRole('row', { name: 'Order #123' })
.getByRole('button', { name: 'View' })
.click();
This mirrors how a user navigates complex UI components.
Using nth(), first(), and last() Safely
await page.locator('.item').first().click();
Use positional locators only when:
Order is guaranteed
UI is not dynamic
Avoid relying on indexes in frequently changing lists.
Assertions with Locators (The Right Way)
Assertions automatically retry when used with locators:
await expect(page.getByText('Success')).toBeVisible();
This integrates perfectly with Playwright’s auto-waiting.
👉 Suggested Article: Auto-Waiting in Playwright: Why Tests Don’t Flake
Common Locator Anti-Patterns
❌ Using waitForTimeout
await page.waitForTimeout(3000);
Masks bad locators instead of fixing them.
❌ Long CSS or XPath Chains
.page > div > div:nth-child(3) > button
Guaranteed to break.
❌ Reusing Test IDs for Styling
Test IDs should exist only for testing.
Locator Strategy for Large Projects
For scalable frameworks:
Standardize locator priority
Enforce test ID naming conventions
Keep locators close to tests or page objects
Avoid global locator utilities
This keeps maintenance predictable.
👉 Suggested Article: Playwright Page Object Model Best Practices
Playwright Codegen and Locators
Playwright’s codegen can help discover locators:
npx playwright codegen https://example.com
Use it as a learning tool, not as final code. Always refactor generated locators to follow best practices.
When Locators Still Fail
If a locator fails consistently:
Re-evaluate semantics
Check accessibility roles
Add explicit test IDs
Inspect trace viewer
👉 Suggested Article: Playwright Automation Testing: Complete Guide (2026)
Final Thoughts
Playwright locators are not just a technical feature. They represent a philosophy: test what users see and do, not how the DOM happens to be built.
If you adopt semantic locators early, your tests will:
Flake less
Read better
Age gracefully
In Playwright, strong locators are the difference between automation that survives and automation that decays.
📘Tutorials | 🧠AI | 🧪Selenium | 🥇Top 10 | 🛠️Tools | 📋Software Testing