Playwright Locators - getByRole, Test IDs, fill() & Dropdowns

A locator tells Playwright how to find an element — and, unlike a one-off element handle, it's re-evaluated every time you use it and waits automatically until the element is ready. Picking the right kind of locator is the biggest single factor in how stable a Playwright suite is. This guide covers the built-in locators in priority order, filtering and chaining, strict mode, and the actions you'll use with them.

Advertisement
Priority Locator Finds by Example
1 getByRole() ARIA role + accessible name getByRole('button', { name: 'Submit transfer' })
2 getByLabel() The form field's label getByLabel('Amount')
3 getByPlaceholder() Placeholder text getByPlaceholder('Search payees')
4 getByText() Visible text (non-interactive elements) getByText('Transfer successful')
5 getByAltText() / getByTitle() Image alt text / title attribute getByAltText('Company logo')
6 getByTestId() data-testid (configurable) getByTestId('submit-transfer')
7 locator() CSS or XPath locator('#amt')

Why this order? Role, label and text locators find elements the way a user (or a screen reader) does, so they keep working through CSS and layout refactors — and a failing role locator often reveals a real accessibility problem. Test IDs are the most stable of all but invisible to users; many teams use them whenever user-facing attributes are ambiguous. CSS and XPath are the fallback, because they depend on page structure.

All of Them on One Form

import { test, expect } from '@playwright/test';

test('locator strategies on one form', async ({ page }) => {
  await page.setContent(`
    <h1>Transfer money</h1>
    <label for="amt">Amount</label><input id="amt" value="100">
    <input placeholder="Search payees">
    <label><input type="checkbox"> Save as template</label>
    <select id="country" aria-label="Country">
      <option value="in">India</option><option value="us">United States</option>
    </select>
    <ul class="payees">
      <li><span>Asha</span><button>Pay</button></li>
      <li><span>Ravi</span><button>Pay</button></li>
    </ul>
    <button data-testid="submit-transfer">Submit transfer</button>`);

  await expect(page.getByRole('heading', { name: 'Transfer money' })).toBeVisible();

  await page.getByLabel('Amount').fill('250');                       // replaces the existing "100"
  await page.getByPlaceholder('Search payees').fill('Ravi');
  await page.getByRole('checkbox', { name: 'Save as template' }).check();
  await page.getByLabel('Country').selectOption({ label: 'United States' });
  await expect(page.getByLabel('Country')).toHaveValue('us');

  // Two "Pay" buttons — narrow down to Ravi's row
  await page.getByRole('listitem').filter({ hasText: 'Ravi' })
            .getByRole('button', { name: 'Pay' }).click();

  await page.getByTestId('submit-transfer').click();
});

The HTML is inline, so you can paste this test into any Playwright project and run it.

getByRole in Detail

page.getByRole('button', { name: 'Login' })
page.getByRole('link', { name: 'Forgot Password?' })
page.getByRole('checkbox', { name: 'Remember me' })
page.getByRole('textbox', { name: 'Email' })
page.getByRole('heading', { name: 'Dashboard', level: 1 })
page.getByRole('row', { name: /Invoice 1043/ })          // regex for partial names
page.getByRole('button', { name: 'Save', exact: true })  // exact match (default is case-insensitive substring)

The name is the accessible name — usually the visible text, the label, or aria-label. Not sure what an element's role and name are? Use the locator picker in UI mode or npx playwright codegen, which suggests role locators automatically.

Filtering and Chaining ⭐

Real pages repeat things — a "Pay" button per payee, an "Edit" link per row. Narrow down instead of reaching for XPath indexes:

const row = page.getByRole('row').filter({ hasText: 'Invoice 1043' });
await row.getByRole('button', { name: 'Edit' }).click();                 // chain inside the row

page.getByRole('listitem').filter({ has: page.getByText('Out of stock') });  // contains an element
page.getByRole('listitem').filter({ hasNotText: 'Archived' });               // exclude

page.getByRole('button', { name: 'Pay' }).first();    // position — use sparingly
page.getByRole('button', { name: 'Pay' }).nth(1);     // 0-based
await expect(page.getByRole('button', { name: 'Pay' })).toHaveCount(2);

Strict Mode

Actions on a locator require it to match exactly one element. If getByRole('button', { name: 'Pay' }) matches two buttons, click() fails with a strict mode violation listing both. That's a feature: it stops the test from silently clicking the wrong one. Fix it by making the locator more specific (filter, chain, exact name) — use first() or nth() only when position really is what you mean.

Actions

Action Use
click(), dblclick() Click after actionability checks
fill('text') Replace a field's value in one step ⭐
clear() or fill('') Empty a field
pressSequentially('text') Type key by key — for fields that react to each keystroke (autocomplete)
press('Enter'), press('Control+A') Keys and shortcuts
check() / uncheck() Set a checkbox or radio state — idempotent, unlike click
selectOption() Native <select> by value, label or index
hover(), focus() Menus, tooltips, blur validation
setInputFiles() File uploads
dragTo() Drag and drop

fill() vs pressSequentially(): fill() sets the value at once and fires an input event — fast and right for most fields. Use pressSequentially() (the replacement for the deprecated type()) only when the page needs real keystrokes, such as a search box that fetches suggestions as you type.

// Native <select>
await page.getByLabel('Country').selectOption('in');                 // by value
await page.getByLabel('Country').selectOption({ label: 'India' });   // by visible text
await page.getByLabel('Country').selectOption({ index: 1 });

// Custom dropdown (div/ul-based, common in React/Angular)
await page.getByRole('combobox', { name: 'State' }).click();
await page.getByRole('option', { name: 'Karnataka' }).click();
await expect(page.getByRole('combobox', { name: 'State' })).toHaveText('Karnataka');

selectOption() only works on real <select> elements. For dependent dropdowns (Country → State loaded by an API), just select and continue: the option locator auto-waits until the new options appear. More: Playwright Auto-Wait.

Configuring Test IDs

// playwright.config.ts — if your app uses data-test or data-qa instead of data-testid
export default defineConfig({ use: { testIdAttribute: 'data-test' } });

Coming From Selenium

Selenium Playwright
driver.findElement(By.id("amt")) page.locator('#amt') — or getByLabel('Amount')
findElements(...).size() expect(locator).toHaveCount(n)
clear() + sendKeys() fill()
new Select(el).selectByVisibleText("India") selectOption({ label: 'India' })
if (!box.isSelected()) box.click() check()
XPath //li[.//span='Ravi']//button getByRole('listitem').filter({ hasText: 'Ravi' }).getByRole('button')

Convert more of your existing locators in the Selenium ⇄ Playwright ⇄ Cypress Translator.

From Real Projects

My own automation experience is with Selenium WebDriver, Java and TestNG in a POM-based hybrid framework, running in batch, parallel and cross-browser mode. The concepts on this page carry straight over to Playwright — page objects, independent tests, parallel execution and reliable synchronisation — which makes it a natural next tool for Selenium testers. The biggest difference you'll notice is how much waiting Playwright handles for you. Testing Testsigma — a platform where users write automated tests in plain English — meant thinking like its users, who are testers themselves. Prefer role- and label-based locators; they read like the user's view of the page.

📚 Official documentation: Playwright docs: Locators · Playwright docs: Actions

FAQs

What are locators in Playwright?

Objects that describe how to find elements. They're re-evaluated on every use and auto-wait for the element before acting, which makes them more reliable than stored element handles.

User-facing locators first — getByRole, getByLabel, getByPlaceholder, getByText — then getByTestId, and CSS/XPath only as a fallback.

What is a strict mode violation?

An action on a locator that matches more than one element. Make the locator more specific with filters, chaining or an exact name.

How do you clear a text field?

locator.clear() or fill(''); fill('new value') replaces the old value directly.

fill() or pressSequentially()?

fill() for almost everything; pressSequentially() when the page reacts to individual keystrokes.

How do you handle a custom dropdown?

Click the combobox, click the option by role and name, then assert the displayed value — selectOption() works only on native selects.