Playwright Locators and Assertions
Playwright’s user-facing locators and retrying assertions are the key to tests that are both readable and stable.
On this page · 5 min · Intermediate
On this page
- Learning objectives
- Explanation
- Code example
- Step by step
- Real-world use
- Common mistakes
- Best practices
- Remember this
- Practice exercise
- Mini quiz
- Related topics
- My notes
- Reading time
- 5 min
- Difficulty
- Intermediate
- Updated
- Oct 29, 2026
Related tutorials
Learning objectives#
- Use role, label, text and test-id locators
- Narrow locators with filter and chaining
- Write web-first assertions
- Assert on URLs, text, counts and attributes
Explanation#
Prefer user-facing locators: getByRole (with accessible name), getByLabel, getByPlaceholder, getByText; and getByTestId for elements without good semantics.
Chain and filter: page.getByRole("row").filter({ hasText: "Laptop" }).getByRole("button", { name: "Edit" }).
Web-first assertions (await expect(locator).toBeVisible()) retry until they pass or time out — no manual waits needed.
Role-based locators also nudge developers toward accessible markup.
Code example: Editing a row in an admin table#
test('admin edits a product price', async ({ page }) => {
await page.goto('/admin/products');
const row = page.getByRole('row').filter({ hasText: 'Laptop' });
await row.getByRole('button', { name: 'Edit' }).click();
await page.getByLabel('Price').fill('52000');
await page.getByRole('button', { name: 'Save' }).click();
await expect(page).toHaveURL(/\/admin\/products$/);
await expect(page.getByRole('status')).toHaveText('Saved Laptop.');
await expect(row.getByRole('cell').nth(1)).toHaveText('52,000');
await expect(page.getByRole('row')).toHaveCount(11); // header + 10 products
});
Every assertion retries until the UI updates, so the test is stable even when saving takes a moment.
This example is illustrative. Copy or download it and run it in your own environment; the expected output is shown above.
Step-by-step explanation#
- filter finds the row containing “Laptop”.
- Chained locators find the Edit button inside that row.
- getByLabel targets the form field by its label.
- Assertions check URL, status message, cell text and row count.
Real-world use case#
Admin CRUD screens tested with locators that survive layout changes.
Common mistakes#
This checks once without retrying. Use await expect(locator).toBeVisible().
Prefer role and label locators that reflect what users see.
Best practices#
- getByRole first
- Filter and chain instead of nth where possible
- Web-first assertions only
- Add data-testid for non-semantic elements
Remember this#
getByRole / getByLabel / getByTestId.
filter({ hasText }) narrows lists.
await expect(locator)… retries.
Practice exercise#
Your task: Assert that a “Delete” button inside the Mouse row is disabled.
Show solution
await expect(page.getByRole('row').filter({ hasText: 'Mouse' }).getByRole('button', { name: 'Delete' })).toBeDisabled();
Mini quiz#
Which assertion retries automatically?
Want more? Practise all Automation Testing MCQs · Automation Testing interview questions
My notes#
Saved in this browser.