|

Day 2: Locator Strategies — Why getByRole Wins and XPath Loses

This is Day 2 of the 21-Day Playwright with TypeScript Challenge. One lesson per day. Zero to production-ready in 3 weeks.

🎭 Want to master this with real projects? Join the Playwright Automation Mastery course at The Testing Academy.


Locators determine how your tests find elements. Wrong strategy = tests break every sprint. Right strategy = tests survive UI redesigns. Playwright offers 8+ locator types. Here is which to use and when.

Contents

Locator Resilience Ranking

TierLocatorResilienceExample
1 (Best)getByRoleSurvives all UI changespage.getByRole('button', {name: 'Submit'})
2getByLabelSurvives class/ID changespage.getByLabel('Email')
3getByTestIdExplicit contractpage.getByTestId('checkout-btn')
4getByTextBreaks on text change onlypage.getByText('Add to cart')
5getByPlaceholderModeratepage.getByPlaceholder('Search')
6 (Worst)CSS/XPathBreaks on any DOM changepage.locator('.btn-primary')

Each Locator Explained

// 1. getByRole — ALWAYS try first
await page.getByRole('button', { name: 'Sign in' }).click();
await page.getByRole('link', { name: 'Documentation' }).click();
await page.getByRole('heading', { name: 'Welcome' });
await page.getByRole('textbox', { name: 'Email' }).fill('test@test.com');

// 2. getByLabel — for labeled form fields
await page.getByLabel('Password').fill('secret123');
await page.getByLabel('Remember me').check();

// 3. getByTestId — when semantic locators insufficient
await page.getByTestId('submit-button').click();
// Requires: data-testid="submit-button" in HTML

// 4. getByText — for visible text content
await page.getByText('Welcome back').click();
await page.getByText(/total: \$/i); // Regex support

// 5. getByPlaceholder — for placeholder text
await page.getByPlaceholder('Enter your email').fill('x@y.com');

// 6. CSS selector — last resort
await page.locator('.nav-item.active').click();
await page.locator('#main-content').isVisible();

🚀 Level Up Your Playwright

From locators to CI pipelines — build a production-grade Playwright + TypeScript framework step by step.

Chaining and Filtering

// filter() — narrow within results
await page.getByRole('listitem')
  .filter({ hasText: 'Active' })
  .click();

// .and() — both conditions must match
const btn = page.getByRole('button')
  .and(page.getByText('Submit'));

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

// Chaining parent > child
await page.getByTestId('product-card')
  .filter({ hasText: 'Laptop' })
  .getByRole('button', { name: 'Add to cart' })
  .click();

Decision Tree

  1. Has visible role? → getByRole()
  2. Labeled form field? → getByLabel()
  3. Has data-testid? → getByTestId()
  4. Unique visible text? → getByText()
  5. None above? → CSS selector (never XPath)

Tomorrow (Day 3): Assertions that actually catch bugs — expect() deep dive.

🎓 Master Playwright End to End

Join hundreds of SDETs building real automation frameworks. Lifetime access, hands-on projects, and a job-ready portfolio.

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.