Skip to main content

Assertions & Debugging

Writing selectors and actions gets you halfway. Assertions verify that your application behaves correctly, and debugging tools help you understand why tests fail. Playwright provides both in abundance.

Expect Matchers

Playwright extends the expect function with web-specific matchers that auto-retry until the condition is met or the timeout expires. This is different from Jest — Playwright assertions are async and will wait for the expected state.

Page-Level Assertions

// Check the page title
await expect(page).toHaveTitle('Dashboard — My App');
await expect(page).toHaveTitle(/Dashboard/);

// Check the current URL
await expect(page).toHaveURL('/dashboard');
await expect(page).toHaveURL(/\/dashboard\?tab=.+/);

Element Visibility and Text

const heading = page.getByRole('heading', { name: 'Welcome' });

// Is the element visible?
await expect(heading).toBeVisible();
await expect(heading).not.toBeVisible();

// Check text content
await expect(heading).toHaveText('Welcome Back');
await expect(heading).toContainText('Welcome');

Form State Assertions

const emailInput = page.getByLabel('Email');
const submitButton = page.getByRole('button', { name: 'Submit' });

// Check input values
await expect(emailInput).toHaveValue('user@example.com');
await expect(emailInput).toBeEmpty();

// Check enabled/disabled state
await expect(submitButton).toBeEnabled();
await expect(submitButton).toBeDisabled();

// Check checkbox state
await expect(page.getByLabel('Remember me')).toBeChecked();

CSS and Attribute Assertions

// Check CSS classes
await expect(page.locator('.alert')).toHaveClass(/alert-success/);

// Check element count
await expect(page.getByRole('listitem')).toHaveCount(5);

// Check attributes
await expect(page.getByAltText('Logo')).toHaveAttribute('src', /logo\.png/);

Soft Assertions

By default, a failed assertion stops the test immediately. Soft assertions let you collect multiple failures before stopping:

await expect.soft(page.getByTestId('status')).toHaveText('Active');
await expect.soft(page.getByTestId('role')).toHaveText('Admin');
await expect.soft(page.getByTestId('email')).toHaveText('a@b.com');
// Test continues even if the first assertion fails
// All failures are reported at the end

Debugging Failing Tests

When a test fails, you need to understand what the page looked like at the moment of failure. Playwright offers several debugging tools.

Playwright Inspector

The inspector is an interactive debugger that lets you step through your test line by line:

npx playwright test --debug

This opens a browser window alongside an inspector panel. You can pause execution, inspect the DOM, try selectors in real time, and step through each action. It is the fastest way to diagnose why a locator is not finding an element.

Trace Viewer

The trace viewer is a post-mortem tool that records everything during test execution — DOM snapshots, network requests, console logs, and action timelines:

// In playwright.config.ts
export default defineConfig({
  use: {
    trace: 'on-first-retry', // Record trace on retry
  },
});

After a failed test, open the trace:

npx playwright show-trace trace.zip

The trace viewer shows a timeline of every action, a DOM snapshot at each step, network requests, and console output. You can scrub through the timeline to see exactly what the page looked like when the assertion failed.

Screenshots on Failure

Configure Playwright to capture a screenshot when any test fails:

export default defineConfig({
  use: {
    screenshot: 'only-on-failure',
  },
});

Screenshots are saved to the test results directory. You can also take screenshots manually within a test:

await page.screenshot({ path: 'debug-checkout.png' });
await page.screenshot({ path: 'full-page.png', fullPage: true });

Console Logs

Capture browser console messages in your test to catch runtime errors:

page.on('console', (msg) => {
  if (msg.type() === 'error') {
    console.log('Browser error:', msg.text());
  }
});

Key Takeaways

  • Playwright assertions auto-retry, so you do not need manual waits.
  • Use toBeVisible(), toHaveText(), and toHaveURL() for common checks.
  • Use soft assertions when you want to collect multiple failures in one test.
  • The --debug flag opens the interactive inspector for step-by-step debugging.
  • Configure trace: 'on-first-retry' and screenshot: 'only-on-failure' for automated diagnostics.