Automation Testing · Playwright

Playwright Locators and Assertions

Playwright’s user-facing locators and retrying assertions are the key to tests that are both readable and stable.

Intermediate 5 min Lesson 10 of 24
Before you start:Playwright Fundamentals
0/24 in Automation Testing
On this page · 5 min · Intermediate

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#

TypeScriptEditing a row in an admin table Illustrative
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
});
Expected output
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#

  1. filter finds the row containing “Laptop”.
  2. Chained locators find the Edit button inside that row.
  3. getByLabel targets the form field by its label.
  4. 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#

expect(await locator.isVisible()).toBe(true)

This checks once without retrying. Use await expect(locator).toBeVisible().

Long CSS chains

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
TypeScript Illustrative
await expect(page.getByRole('row').filter({ hasText: 'Mouse' }).getByRole('button', { name: 'Delete' })).toBeDisabled();

Mini quiz#

Playwright Locators and Assertions quizMultiple choice Question 1 of 1

Which assertion retries automatically?

Score so far: 0/0

Want more? Practise all Automation Testing MCQs · Automation Testing interview questions

My notes#

Saved in this browser.

Progress is saved in this browser. Sign in to sync.