In the previous chapter, you learned how to perform user interactions like clicking, typing, selecting, and navigating. Now it’s time to learn how to verify that your web application behaves as expected.
This is where assertions and expectations come in. Assertions are the checkpoints in your automation, they confirm that something is correct (like a title, a message, or a URL). Without them, automation is just a series of actions without meaning.
In this chapter, we’ll use the Playwright Test Runner to make our tests smart, reliable, and meaningful.
What is the Playwright Test Runner?
Playwright includes a built-in testing framework called the Playwright Test Runner. You don’t need to install Jest or Mocha, it’s already part of Playwright.
It provides:
- A simple structure for writing tests.
- Built-in assertions (via
expect). - Test fixtures (like
page,browser,context). - Parallel execution.
- HTML reports and trace debugging.
Here’s a simple example:
import { test, expect } from '@playwright/test';
test('homepage has title', async ({ page }) => {
await page.goto('https://playwright.dev/');
await expect(page).toHaveTitle(/Playwright/);
});
This test opens the Playwright website and checks that the title contains the word “Playwright.”
Structure of a Playwright test
A typical Playwright test file has three main parts:
import { test, expect } from '@playwright/test';
test('description', async ({ page }) => {
// 1. Setup or navigation
await page.goto('https://example.com');
// 2. Action
await page.click('text=Login');
// 3. Assertion
await expect(page).toHaveURL(/login/);
});
Let’s break it down:
| Section | Purpose |
|---|---|
test() | Defines a test case. |
{ page } | Fixture automatically provided by Playwright (represents a browser tab). |
expect() | Built-in assertion library. |
async/await | Ensures each step completes before the next one. |
Common Playwright assertions
Playwright provides many built-in assertions for verifying the page state.
1. Checking the title
await expect(page).toHaveTitle(/Dashboard/);
Checks that the page title matches a string or regex.
2. Checking the current URL
await expect(page).toHaveURL('https://example.com/dashboard');
3. Checking element visibility
const welcomeText = page.locator('h1');
await expect(welcomeText).toBeVisible();
4. Checking element text
await expect(page.locator('.message')).toHaveText('Welcome back!');
You can also check partial text with regex:
await expect(page.locator('.message')).toHaveText(/Welcome/);
5. Checking input values
await expect(page.locator('#username')).toHaveValue('john_doe');
6. Checking element attributes
await expect(page.locator('#submit')).toHaveAttribute('type', 'submit');
7. Checking element count
await expect(page.locator('ul > li')).toHaveCount(5);
8. Checking if an element is enabled or disabled
await expect(page.locator('#submit')).toBeEnabled();
await expect(page.locator('#cancel')).toBeDisabled();
9. Checking screenshots (visual regression)
You can even compare screenshots to detect visual changes:
await expect(page).toHaveScreenshot('homepage.png');
How Playwright handles retries automatically
Assertions in Playwright are smart. They automatically retry until the condition is true or a timeout is reached.
For example:
await expect(page.locator('#status')).toHaveText('Success');
If the text takes 2 seconds to appear, Playwright waits by default, no need for explicit wait() calls.
Default timeout for assertions is 5 seconds, but you can customize it:
await expect(page.locator('#status')).toHaveText('Success', { timeout: 10000 });
Grouping tests
You can group related tests using test.describe():
test.describe('Login tests', () => {
test('valid login', async ({ page }) => {
await page.goto('https://example.com/login');
await page.fill('#username', 'user1');
await page.fill('#password', 'pass123');
await page.click('button[type="submit"]');
await expect(page).toHaveURL(/dashboard/);
});
test('invalid login shows error', async ({ page }) => {
await page.goto('https://example.com/login');
await page.fill('#username', 'wrong');
await page.fill('#password', 'wrongpass');
await page.click('button[type="submit"]');
await expect(page.locator('.error')).toHaveText('Invalid credentials');
});
});
Grouping helps organize tests logically (like login, signup, checkout, etc.).
Test hooks: beforeEach and afterEach
Playwright supports setup and teardown hooks to prepare and clean up before or after tests.
test.beforeEach(async ({ page }) => {
await page.goto('https://example.com');
});
test.afterEach(async ({ page }) => {
console.log('Test completed!');
});
Expect inside loops or conditionals
Playwright’s expect works seamlessly inside loops:
for (const item of await page.locator('ul > li').all()) {
await expect(item).toBeVisible();
}
Handling flaky assertions
Sometimes, tests may fail intermittently (due to animations, delays, etc.). Here are tips to reduce flakiness:
- Use
toBeVisible()before clicking elements. - Avoid arbitrary waits like
waitForTimeout(), use expectations instead. - Increase assertion timeout only when necessary.
- Use stable selectors like
data-testid.
Full example: login assertion test
Here’s a full example that combines everything:
import { test, expect } from '@playwright/test';
test.describe('Login functionality', () => {
test('should login successfully and show dashboard', async ({ page }) => {
await page.goto('https://www.saucedemo.com/');
await page.fill('#user-name', 'standard_user');
await page.fill('#password', 'secret_sauce');
await page.click('#login-button');
await expect(page).toHaveURL(/inventory/);
await expect(page.locator('.title')).toHaveText('Products');
});
test('should show error for invalid credentials', async ({ page }) => {
await page.goto('https://www.saucedemo.com/');
await page.fill('#user-name', 'wrong_user');
await page.fill('#password', 'wrong_pass');
await page.click('#login-button');
const errorMessage = page.locator('[data-test="error"]');
await expect(errorMessage).toBeVisible();
await expect(errorMessage).toHaveText(/Username and password do not match/);
});
});
Exercise
Task 1: Write a test that opens a website and asserts that the title, URL, and a header element are correct.
Task 2: Add another test that verifies an element’s visibility and text.
Bonus: Try adding a screenshot assertion using toHaveScreenshot().
Cheat Sheet
| Assertion | Example |
| Check title | await expect(page).toHaveTitle(/Home/) |
| Check URL | await expect(page).toHaveURL(/dashboard/) |
| Check visibility | await expect(locator).toBeVisible() |
| Check text | await expect(locator).toHaveText('Success') |
| Check count | await expect(locator).toHaveCount(5) |
| Check attribute | await expect(locator).toHaveAttribute('href', '/home') |
| Check enabled | await expect(locator).toBeEnabled() |
| Screenshot | await expect(page).toHaveScreenshot('page.png') |
Summary
In this chapter, you learned how to:
- Use the Playwright Test Runner and
expectfor assertions. - Verify page titles, URLs, text, visibility, and attributes.
- Write grouped and structured tests.
- Handle flaky conditions with built-in retries.
Assertions turn actions into meaningful automation. They tell you not just what happened, but whether it happened correctly.
