Skip to content
SDETRole
SDETRole

Playwright interview question

What is your locator strategy in Playwright, and why prefer getByRole?

beginner 0–2 yrs #locators#getbyrole#accessibility#selectors

Short answer

Prefer user-facing locators: getByRole with an accessible name first, then getByLabel, getByPlaceholder or getByText, and getByTestId for elements with no stable accessible identity. Avoid CSS or XPath tied to DOM structure. Locators are lazy, strict, and re-resolved on every action, which makes them resilient.

Answer

I locate elements the way a user or assistive technology perceives them. In order of preference: getByRole with an accessible name, then getByLabel for form fields, getByPlaceholder, getByText for non-interactive content, getByAltText and getByTitle, and getByTestId when there is no stable user-facing identity. CSS and XPath are a last resort. This is also the order the Playwright documentation recommends.

Why getByRole first

await page.getByRole('button', { name: 'Place order' }).click();
await page.getByRole('link', { name: 'Pricing' }).click();
await page.getByRole('checkbox', { name: 'Accept terms' }).check();
await page.getByRole('heading', { level: 1 }).textContent();
  • Resilient: survives refactors of class names, wrappers and DOM nesting. If the button still looks and reads like “Place order”, the test still works.
  • Meaningful failures: if the locator breaks because the button lost its accessible name, that is often a real accessibility bug.
  • Readable: the test describes what the user does.

By default name matching is case-insensitive and matches a substring; pass exact: true for an exact match, or a regular expression.

Form fields and text

await page.getByLabel('Email').fill('qa@example.com');
await page.getByPlaceholder('Search products').fill('lamp');
await expect(page.getByText('Your cart is empty')).toBeVisible();

getByLabel works with <label for>, wrapping labels and aria-label/aria-labelledby.

Test IDs: the pragmatic fallback

For icon-only controls, canvas widgets, or repeated cards with no unique text, a test ID is honest and stable:

await page.getByTestId('cart-badge');   // matches data-testid="cart-badge" by default

The attribute is configurable (use: { testIdAttribute: 'data-qa' }). Agreeing on test IDs with developers is part of building testable UI.

Narrowing: chaining and filtering

const row = page.getByRole('row').filter({ hasText: 'Order #1042' });
await row.getByRole('button', { name: 'Refund' }).click();

const card = page.getByTestId('product-card').filter({
  has: page.getByRole('heading', { name: 'Desk lamp' }),
});
await card.getByRole('button', { name: 'Add to cart' }).click();

// either variant of a dialog
const confirm = page.getByRole('button', { name: 'Confirm' }).or(page.getByRole('button', { name: 'OK' }));

Scoping to a container is far better than nth() or positional indexes, which break when order changes.

Strictness and laziness

  • Lazy: creating a locator does not query the page. It is resolved fresh every time an action or assertion runs, so it never goes stale like a Selenium WebElement.
  • Strict: if an action’s locator matches more than one element, Playwright throws a strictness violation instead of silently clicking the first one. Fix it by making the locator more specific, not by reflexively adding .first().

What to avoid

page.locator('#root > div:nth-child(3) > div > button.btn-primary'); // structure-dependent
page.locator('//div[2]/span[1]/a');                                     // same, in XPath

These break on harmless layout changes. When CSS is genuinely necessary, keep it short and based on stable attributes.

Tooling helps: npx playwright codegen and the locator picker in UI mode and the VS Code extension suggest role-based locators.

What interviewers look for

  • A clear priority order with getByRole first and reasons.
  • Sensible use of test IDs rather than dogma.
  • Chaining and filter instead of indexes.
  • Understanding of strictness and lazy re-resolution.

A strong answer covers

  • Prioritises user-facing locators, getByRole with name first
  • Uses getByLabel, getByText, getByPlaceholder and getByTestId appropriately
  • Avoids brittle CSS/XPath tied to DOM structure
  • Explains strictness and lazy evaluation of locators
  • Narrows with chaining, filter, and/or
Practise answering under time pressure

Type to search across SDETRole.

Full-text search by Pagefind · runs in your browser Open search page →