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:
| Assertion | Description |
|---|---|
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
- Use locator-based assertions instead of
page.evaluate()for reliability. - Avoid
waitForTimeout(), Playwright auto-waits for you. - Use specific matchers (
toHaveText,toBeVisible) over generic ones. - Prefer soft assertions when testing multiple independent UI parts.
- Group related assertions for better readability.
- 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
| Type | Assertion | Example |
| Visibility | toBeVisible() | await expect(locator).toBeVisible() |
| Text | toHaveText() | await expect(locator).toHaveText('Done') |
| Attribute | toHaveAttribute() | await expect(locator).toHaveAttribute('type', 'email') |
| URL | toHaveURL() | await expect(page).toHaveURL(/dashboard/) |
| Title | toHaveTitle() | await expect(page).toHaveTitle(/Playwright/) |
| Count | toHaveCount(n) | await expect(locator).toHaveCount(3) |
| Value | toHaveValue() | await expect(locator).toHaveValue('test@example.com') |
| Negative | .not | await expect(locator).not.toBeVisible() |
| Soft | soft.expect() | Continue after failed assertions |
| Retry | toPass() | Retries assertion until it passes |
Summary
In this chapter, you learned how to:
- Use Playwright’s
expectAPI 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.
