Chapter 14 – Assertions and Validations in Playwright

Assertions are the heart of every automated test, they determine whether your application behaves as expected. Without them, tests can interact with the UI endlessly but never verify if the result is correct.

Playwright provides a powerful, built-in assertion library that integrates seamlessly with its test runner. These assertions are smart, auto-retrying, and wait-aware, which means they automatically wait for the condition to be true before failing, reducing the need for manual waits or sleep statements.

In this chapter, we’ll go from basic assertions like checking text or visibility, to advanced validations such as API response checks, soft assertions, and custom matchers.

1. What are assertions?

An assertion is a statement that verifies whether a condition in your test is true or false. If the condition fails, the test fails.

Example:

await expect(page).toHaveTitle('Playwright');

This verifies the page title. If it doesn’t match, Playwright retries for a few seconds before failing the test.

2. The expect API

Playwright’s expect API is imported from @playwright/test:

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

It supports a wide range of assertions on pages, locators, responses, and values.

3. Basic assertions

3.1 Checking visibility

await expect(page.locator('#login-button')).toBeVisible();

Playwright waits until the element becomes visible.

3.2 Checking text content

await expect(page.locator('.message')).toHaveText('Login successful');

You can also check partial matches:

await expect(page.locator('.message')).toContainText('Login');

3.3 Checking attributes

await expect(page.locator('input#email')).toHaveAttribute('type', 'email');

3.4 Checking URLs

await expect(page).toHaveURL(/dashboard/);

3.5 Checking page titles

await expect(page).toHaveTitle(/Playwright/);

4. Assertions with Locators

Locators are Playwright’s most reliable way to identify elements. You can apply assertions directly to them.

Example:

const username = page.locator('#username');
await expect(username).toBeEditable();
await expect(username).not.toBeEmpty();

Other useful locator assertions:

AssertionDescription
toBeChecked()Checkbox/radio button is selected
toBeDisabled()Element is disabled
toBeEnabled()Element is enabled
toBeHidden()Element is hidden
toBeFocused()Element is focused
toHaveCount(n)Number of matching elements
toHaveValue(value)Input field has a specific value

5. Numeric and value assertions

You can perform direct value comparisons using expect():

const price = 100;
expect(price).toBeGreaterThan(50);
expect(price).toBeLessThanOrEqual(200);
expect(price).not.toBe(0);

These work like Jest-style matchers, ideal for validating calculations or API results.

6. Assertions with retry and auto-wait

Playwright automatically retries an assertion until it passes or times out (default: 5 seconds).

Example:

await expect(page.locator('#status')).toHaveText('Completed');

If the text appears after 3 seconds, the test still passes, no manual wait() needed.

You can customize the timeout:

await expect(page.locator('#status')).toHaveText('Completed', { timeout: 10000 });

7. Soft assertions (non-blocking)

Sometimes you want the test to continue even if an assertion fails, for example, when verifying multiple elements at once. That’s where soft assertions come in.

Example:

test('soft assertions', async ({ page, soft }) => {
  await page.goto('https://example.com');
  await soft.expect(page.locator('#header')).toBeVisible();
  await soft.expect(page.locator('#footer')).toBeVisible();
});

Soft assertions collect failures but don’t stop execution immediately.

8. Assertions on network responses

Playwright can assert directly on API responses fetched during tests.

Example:

const [response] = await Promise.all([
  page.waitForResponse('**/api/user'),
  page.click('#get-user')
]);

await expect(response.status()).toBe(200);
const data = await response.json();
expect(data.name).toBe('John Doe');

This is powerful for validating backend data consistency during end-to-end tests.

9. Combining multiple assertions

You can combine multiple assertions for a single test scenario:

const button = page.locator('#submit');
await expect(button).toBeVisible();
await expect(button).toBeEnabled();
await expect(button).toHaveText('Submit');

Combine assertions for better clarity and validation coverage.

10. Custom assertions

You can extend Playwright’s expect with your own matchers for domain-specific checks.

Example:

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

expect(75).toBeWithinRange(50, 100);

Custom assertions make tests expressive and reusable.

11. Assertions with retries for unstable elements

Sometimes, dynamic UIs can cause brief inconsistencies. Playwright’s retries can help.

Example:

await expect(async () => {
  const text = await page.locator('#status').innerText();
  expect(text).toMatch(/Success|Done/);
}).toPass();

This retries the entire block until it passes, ideal for dynamic content or animations.

12. Assertions for API and JSON data

You can assert on JSON structures directly:

const response = await page.request.get('https://jsonplaceholder.typicode.com/users/1');
const user = await response.json();
expect(user).toMatchObject({ id: 1, username: 'Bret' });

Tip: Use toMatchObject() for partial structure validation, it’s more flexible than full equality.

13. Negative assertions

Playwright supports inverse conditions via .not.

Example:

await expect(page.locator('#error')).not.toBeVisible();
await expect(page.locator('#loading')).not.toHaveText('Loading...');

14. Best practices for assertions

  1. Use locator-based assertions instead of page.evaluate() for reliability.
  2. Avoid waitForTimeout(), Playwright auto-waits for you.
  3. Use specific matchers (toHaveText, toBeVisible) over generic ones.
  4. Prefer soft assertions when testing multiple independent UI parts.
  5. Group related assertions for better readability.
  6. Always assert after every major user action, not just at the end.

15. Debugging assertion failures

When an assertion fails, Playwright provides detailed error messages, including expected vs. actual values.

Use the following tools to debug further:

  • Trace Viewer: npx playwright show-trace trace.zip
  • Debug mode: npx playwright test --debug
  • Screenshot on failure: Enable in config:use: { screenshot: 'only-on-failure' }

Exercise

Task 1: Write a test that checks the page title and URL.
Task 2: Verify an element is visible and enabled before clicking.
Task 3: Assert that an API returns the expected JSON data.
Task 4: Use soft assertions to check multiple UI sections.
Bonus: Write a custom assertion that checks whether a value is even.

Cheat Sheet

TypeAssertionExample
VisibilitytoBeVisible()await expect(locator).toBeVisible()
TexttoHaveText()await expect(locator).toHaveText('Done')
AttributetoHaveAttribute()await expect(locator).toHaveAttribute('type', 'email')
URLtoHaveURL()await expect(page).toHaveURL(/dashboard/)
TitletoHaveTitle()await expect(page).toHaveTitle(/Playwright/)
CounttoHaveCount(n)await expect(locator).toHaveCount(3)
ValuetoHaveValue()await expect(locator).toHaveValue('test@example.com')
Negative.notawait expect(locator).not.toBeVisible()
Softsoft.expect()Continue after failed assertions
RetrytoPass()Retries assertion until it passes

Summary

In this chapter, you learned how to:

  • Use Playwright’s expect API for reliable, auto-retrying assertions.
  • Validate visibility, text, attributes, and URLs.
  • Work with soft assertions and retries.
  • Assert API and JSON data.
  • Create custom assertions for flexible validation.

Assertions turn your Playwright tests from mere interactions into powerful, trustworthy verifications. Mastering them ensures your tests provide real confidence that your app works correctly.