If you learn only one locator, learn getByRole. It covers 70-80% of all elements you will ever need to find. It works by matching the ARIA role of an element and optionally its accessible name. What does that mean in plain language? Every HTML element has a role -- a button is a "button," a link is a "link," an input is a "textbox." And each one has a name -- usually the visible text or label associated with it.
import { test, expect } from '@playwright/test';
test('getByRole examples', async ({ page }) => {
await page.goto('/banking');
// Find a button by its visible text
await page.getByRole('button', { name: 'Login' }).click();
// Find a link by its text
await page.getByRole('link', { name: 'Forgot Password' }).click();
// Find a heading
await expect(page.getByRole('heading', { name: 'Welcome' })).toBeVisible();
// Find a text input (textbox role)
await page.getByRole('textbox', { name: 'Username' }).fill('john');
// Find a checkbox
await page.getByRole('checkbox', { name: 'Remember me' }).check();
});| Role | HTML Elements | Example Locator |
|---|---|---|
| button | <button>, <input type="submit">, <input type="button"> | getByRole('button', { name: 'Save' }) |
| link | <a href="..."> | getByRole('link', { name: 'Home' }) |
| textbox | <input type="text">, <textarea>, <input type="email"> | getByRole('textbox', { name: 'Email' }) |
| heading | <h1>, <h2>, <h3>, <h4>, <h5>, <h6> | getByRole('heading', { name: 'Dashboard' }) |
| checkbox | <input type="checkbox"> | getByRole('checkbox', { name: 'Agree' }) |
| radio | <input type="radio"> | getByRole('radio', { name: 'Male' }) |
| combobox | <select>, <input role="combobox"> | getByRole('combobox', { name: 'Country' }) |
| listitem | <li> | getByRole('listitem') |
| row | <tr> | getByRole('row') |
| cell | <td> | getByRole('cell', { name: '$500' }) |
| navigation | <nav> | getByRole('navigation') |
| dialog | <dialog>, role="dialog" | getByRole('dialog') |
| img | <img alt="..."> | getByRole('img', { name: 'Profile photo' }) |
| alert | role="alert" | getByRole('alert') |
The name option matches the accessible name of the element. Where does the accessible name come from? It depends on the element type.
// Exact match (default)
page.getByRole('button', { name: 'Submit Order' });
// Substring match with regex
page.getByRole('button', { name: /submit/i });
// exact: false -- case-insensitive substring match
page.getByRole('button', { name: 'submit', exact: false });
// Heading level filter
page.getByRole('heading', { name: 'Dashboard', level: 1 });
page.getByRole('heading', { name: 'Recent Activity', level: 2 });
// Checkbox state -- checked or unchecked
page.getByRole('checkbox', { name: 'Newsletter', checked: true });
page.getByRole('checkbox', { name: 'Newsletter', checked: false });By default, name matching is case-sensitive and matches the full string. "Login" will NOT match a button with text "Login to Account." Use a regex like /login/i for flexible matching, or { exact: false } for substring match.
Not sure what role an element has? Open the browser DevTools, select the element, and look at the Accessibility tab. It shows the computed role and accessible name. Or use Playwright Inspector's Pick Locator -- it always suggests getByRole first.
Q: What is getByRole and why is it the preferred locator in Playwright?
A: getByRole finds elements by their ARIA role and accessible name. A button is role "button," a link is role "link," an input is role "textbox." The name option matches the visible text or associated label. It is preferred because it mirrors how assistive technologies and real users perceive the page. If a button says "Login," getByRole('button', { name: 'Login' }) finds it regardless of its CSS class, ID, or position in the DOM. It also validates accessibility -- if getByRole cannot find your element, your HTML may not be accessible.
Key Point: getByRole matches ARIA roles and accessible names. It covers buttons, links, textboxes, headings, checkboxes, and more. Learn the roles table -- it is your most-used reference.