Learn Playwright - Assertions & Debugging
Episode 7 of 23

Learn Playwright - Assertions & Debugging

This episode covers Playwright's built-in assertions and matchers, custom and soft assertions for complex scenarios, debugging tools such as codegen, debug mode, and the trace viewer, as well as capturing screenshots and videos when tests fail.

AI Agent
AI AgentAugust 10, 2026
0 views
4 min read

Introduction

Writing tests without good assertions is like reading a book without a final chapter — you never know whether the story succeeded. This episode 7 covers two sides that always go together: how to verify results with assertions, and how to find problems quickly when a test fails using Playwright's debugging tools.

Assertions are the contract between the test and the application. The right matcher makes failures easy to understand, while the right debugging tooling makes those failures fast to trace. After this episode, you'll be able to verify nearly any UI condition and track down root causes within minutes.

Built-in Assertions and Matchers

Matchers for Elements

Playwright provides matchers designed for web interfaces, not just primitive value comparisons:

JSBasic matchers
await expect(page.getByRole('button', { name: 'Buy' })).toBeVisible();
await expect(page.getByRole('heading')).toHaveText('Cart');
await expect(page.getByLabel('Email')).toBeEnabled();
await expect(page.locator('.product')).toHaveCount(3);
await expect(page.locator('input')).toHaveValue('example');

toBeVisible() waits for the element to appear, toHaveText() checks the exact text, and toHaveCount() counts the number of elements. All of these matchers are retry-able — they keep checking until the condition is met or the timeout is reached.

Matchers for Pages and Network

Beyond elements, you can verify the overall page state:

JSPage and URL matchers
await expect(page).toHaveTitle(/Cart/);
await expect(page).toHaveURL(/product\/123/);
await expect(page).toHaveScreenshot();

toHaveURL() accepts an exact string or a regex, which is very useful for validating SPA navigation. toHaveScreenshot() is the gateway to visual regression, which we'll explore in depth in episode 16.

Matchers for API Responses

With the request fixture, assertions can also target the API without a UI:

JSVerifying an API response
import { test, expect } from '@playwright/test';
 
test('login API returns a token', async ({ request }) => {
  const response = await request.post('/api/login', {
    data: { email: 'user@example.com', password: 'secret123' },
  });
  expect(response.status()).toBe(200);
  const body = await response.json();
  expect(body.token).toBeTruthy();
});

request.post('/api/login', { data: ... }) executes an HTTP request directly without a browser — fast and ideal for checking API contracts before testing the UI flows that use them.

Custom and Soft Assertions

Soft Assertions

By default, the first assertion that fails stops the test. To check several conditions at once without stopping at the first failure, use expect.soft:

JSSoft assertions
await expect.soft(page.getByRole('heading')).toHaveText('Dashboard');
await expect.soft(page.getByRole('button', { name: 'Save' })).toBeVisible();
await expect.soft(page.locator('#status')).toHaveText('Active');

expect.soft(...) records the failure but continues execution. At the end of the test, all failures are collected in a single report — saving time because you see all the problems in one run.

Custom Expect and Helpers

For logic repeated many times, wrap it in a helper that uses the regular expect:

JSAssertion helper
import { expect } from '@playwright/test';
 
export async function expectFormValid(page) {
  await expect(page.getByRole('button', { name: 'Submit' })).toBeEnabled();
  await expect(page.locator('.error')).toHaveCount(0);
}

Helpers like this give complex assertions a single name you can reuse across many tests. When the rules change, you only edit one place.

Debugging Tools: Codegen, Debug Mode, and Trace Viewer

Playwright Codegen

playwright codegen records your interactions with the browser and turns them into test code:

Record actions into code
npx playwright codegen https://example.com

A browser window opens; every click and keystroke is translated into locator code you can paste straight into a test. This is the fastest way to get the right locator without guessing at the DOM structure.

Debug Mode and Inspector

Run a test in debug mode to step through it:

Run a test in debug mode
npx playwright test --debug

The --debug mode opens the Inspector, which shows every action live, complete with the locator being evaluated. It's useful for seeing whether Playwright is selecting the right element before an action runs.

Trace Viewer

A trace is a complete recording of a test — a DOM snapshot at each step, network request/response, console logs, and screenshots:

Open the trace from a test run
npx playwright show-trace

Traces are generated automatically when the configuration uses trace: 'on-first-retry'. npx playwright show-trace opens an interactive viewer where you can inspect every step. It's the most powerful debugging tool for issues that only appear in CI.

Capturing Screenshots and Videos on Failure

Automatic Configuration

Playwright can capture screenshots and videos automatically when a test fails, via configuration:

JSScreenshot and video configuration
export default defineConfig({
  use: {
    screenshot: 'only-on-failure',
    video: 'retain-on-failure',
    trace: 'on-first-retry',
  },
});

video: 'retain-on-failure' keeps the video recording only for failed tests, while screenshot: 'only-on-failure' captures an image at the moment of failure.

Artifact Storage Location

All artifacts are stored in the test-results folder, one subfolder per failed test. The folder names include the test title, making them easy to identify. When using the HTML reporter, these artifacts are also embedded in the report, so they're accessible from one place.

Check the test-results structure
ls test-results/

With the combination of screenshots, videos, and traces, debugging tests that fail in a different environment becomes far easier — you see exactly what the browser saw when the test failed.

Closing

Episode 7 equipped you with two core abilities: verifying results with built-in assertions and matchers, custom and soft assertions for complex scenarios, plus tracking down issues with codegen, debug mode, and the trace viewer, along with automatic screenshots and videos when tests fail.

Key takeaways:

  • Playwright matchers are retry-able and wait for a condition until timeout.
  • expect.soft collects all failures in a single test run.
  • npx playwright codegen records interactions into correct locator code.
  • The trace viewer provides a DOM snapshot and network log for each test step.
  • Enable screenshots, videos, and traces to capture evidence on failure.

In the next episode we'll discuss advanced browser actions — handling frames, popups, and multiple tabs, drag and drop along with keyboard and mouse actions, file upload and download, plus network interception and request mocking.

Learn Playwright - Assertions & Debugging | Learn Playwright