Browser tests

Locate elements by role

Select controls through accessible roles, names, labels, and visible behavior instead of fragile CSS implementation details.

8 minute lesson

~~~

A locator should resemble how a user or assistive technology finds the element.

Prefer getByRole, getByLabel, and getByText. Use a test ID only when there is no meaningful user-facing selector. Role-based locators also reveal missing labels and ambiguous control names.

await page.getByLabel('Title').fill('Dune')
await page.getByLabel('Author').fill('Frank Herbert')
await page.getByRole('button', { name: 'Add book' }).click()
await expect(page.getByRole('listitem')).toContainText('Dune')

The accessible name matters as much as the role. A button may get its name from its text, an aria-label, or another labelled element. getByRole('button', { name: 'Add book' }) therefore follows the same semantic surface that keyboard and assistive-technology users depend on.

Locators are strict for actions: if two buttons match, Playwright reports the ambiguity instead of guessing. Treat that as evidence. Narrow the locator to the relevant form or give the controls distinct accessible names. Reaching for .first() can create a false positive by clicking whichever duplicate happens to render first.

CSS classes such as .btn-primary:nth-child(2) describe implementation details. A redesign can break the test while the user journey still works. A very broad text match has the opposite risk: it can find “Dune” in a hidden template, navigation item, or unrelated notification and pass before the saved book appears. Scope the assertion to the book list and include the author when that identifies the created record.

Test IDs are appropriate for behavior with no user-facing semantics, but they should be a deliberate stable contract. Do not add inaccessible markup merely to make the test easy.

Test the add-book journey using labels, roles, and visible text. Duplicate the “Add book” button on purpose, read the strictness error, then fix the interface names or locator scope without using .first().

Lesson completed

Take this course offline

Get every free book and course as PDF and EPUB files.

Get the download library →