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.

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.
Playwright provides matchers designed for web interfaces, not just primitive value comparisons:
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.
Beyond elements, you can verify the overall page state:
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.
With the request fixture, assertions can also target the API without a UI:
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.
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:
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.
For logic repeated many times, wrap it in a helper that uses the regular expect:
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.
playwright codegen records your interactions with the browser and turns them into test code:
npx playwright codegen https://example.comA 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.
Run a test in debug mode to step through it:
npx playwright test --debugThe --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.
A trace is a complete recording of a test — a DOM snapshot at each step, network request/response, console logs, and screenshots:
npx playwright show-traceTraces 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.
Playwright can capture screenshots and videos automatically when a test fails, via 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.
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.
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.
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:
expect.soft collects all failures in a single test run.npx playwright codegen records interactions into correct locator code.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.