SoftwareTestPilot
Module 04 · Lab 1
Intermediate
9 min read

Playwright Assertions & Matchers — expect, toHaveText, toBeVisible

Every Playwright matcher you'll ever need — auto-retrying assertions, soft assertions, and negative matchers with real examples.

1. expect() auto-retries

Playwright's expect() assertions auto-retry. expect(locator).toBeVisible() polls the page until the assertion passes or the timeout (default 5s) fires — no manual waitFor needed.

await expect(page.getByRole('alert')).toBeVisible();
// Polls every ~100ms for up to 5s until the alert appears.

2. Common matchers

The matchers you'll use 90% of the time:

  • toBeVisible — element is rendered and visible
  • toHaveText — exact text match
  • toContainText — substring match
  • toHaveValue — input value
  • toHaveURL — current page URL
  • toHaveCount — number of matched elements
  • toHaveAttribute — DOM attribute value
await expect(page.getByRole('heading')).toHaveText('Welcome');
await expect(page.getByLabel('Email')).toHaveValue('qa@example.com');
await expect(page).toHaveURL(/dashboard/);
await expect(page.getByRole('listitem')).toHaveCount(5);
await expect(page.getByRole('link', { name: 'Docs' })).toHaveAttribute('href', '/docs');

3. Negative matchers

Chain .not to assert the opposite. Negative matchers also auto-retry until the condition becomes false (or the timeout fires).

await expect(page.getByRole('progressbar')).not.toBeVisible();
await expect(page.getByRole('status')).not.toHaveText('Error');

4. Soft assertions

Use expect.soft() when you want the test to keep running after a failure and report every problem at the end — perfect for verifying a results page with many fields.

await expect.soft(page.getByTestId('order-id')).toHaveText('ORD-1001');
await expect.soft(page.getByTestId('total')).toHaveText('$42.00');
await expect.soft(page.getByTestId('status')).toHaveText('Confirmed');
// Test continues even if one fails; all failures reported at the end.

5. Custom matchers

Extend expect with domain-specific matchers to keep tests readable.

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

expect.extend({
  toBeWithinRange(received: number, floor: number, ceiling: number) {
    const pass = received >= floor && received <= ceiling;
    return {
      pass,
      message: () =>
        `expected ${received} to be within ${floor}..${ceiling}`,
    };
  },
});

// usage
expect(99.5).toBeWithinRange(0, 100);

6. Hands-on task

Take 5 manual checks from an existing test and convert them into auto-retrying assertions. Drop every waitForTimeout and if (await locator.isVisible()) pattern in favour of expect().

// Before
await page.waitForTimeout(2000);
if (await page.locator('.toast').isVisible()) {
  console.log('saved');
}

// After
await expect(page.getByRole('status')).toHaveText('Saved');

7. What's next

Next, move into Module 05: Page Object Model to structure your tests for a real product.

Up next in the learning path

Module 05: Page Object Model