The Playwright HTML reporter doesn’t just show you what happened in your tests; it actively reconstructs the user experience you were trying to emulate, making it a powerful debugging tool disguised as a reporting mechanism.

Let’s see it in action. Imagine you have a simple test file, example.spec.ts:

import { test, expect } from '@playwright/test';

test('should navigate to Google and search', async ({ page }) => {
  await page.goto('https://www.google.com/');
  await expect(page).toHaveTitle(/Google/);

  await page.locator('textarea[name="q"]').fill('Playwright');
  await page.keyboard.press('Enter');

  await expect(page).toHaveURL(/q=Playwright/);
  await expect(page.locator('div.g')).toHaveCount(10); // Expecting at least 10 search results
});

test('should fail on purpose', async ({ page }) => {
  await page.goto('https://www.example.com');
  await expect(page).toHaveTitle(/NonExistentTitle/);
});

To generate the HTML report, you’d run Playwright with the --reporter=html flag:

npx playwright test --reporter=html

This command will execute your tests and, upon completion, create a playwright-report directory in your project’s root. Inside, you’ll find an index.html file. Open this file in your browser.

You’ll be greeted with a dashboard. On the left, a list of your test files and suites. Clicking on example.spec.ts expands it to show the two tests. The "should navigate to Google and search" test will likely show as passed. The "should fail on purpose" test will be marked with a red failure icon.

Clicking on the failed test reveals a detailed breakdown. You see the steps executed: navigating to example.com, the assertion toHaveTitle(/NonExistentTitle/). Playwright doesn’t just say "it failed"; it shows you a screenshot of the page at the moment of failure. The actual title was "Example Domain," and the assertion expected "NonExistentTitle." The difference is stark and immediately obvious.

But the real power is in the passed test. Clicking on "should navigate to Google and search" allows you to replay every action. You can see the initial Google page, the search bar being filled, the Enter key press, and the subsequent search results page. Crucially, you can hover over each step. For assertions like toHaveCount(10), you’ll see the actual count of the matched elements (e.g., div.g) highlighted on the page within the report’s embedded browser view. This is not a static screenshot; it’s an interactive reconstruction of the test execution.

The HTML reporter addresses the fundamental problem of understanding why a test failed or why it passed in a complex, dynamic web environment. Traditional test runners often just give you stack traces, which are notoriously difficult to map to a visual, user-facing interaction. The HTML reporter bridges this gap by providing a visual, step-by-step replay of the user’s journey, complete with DOM snapshots and network activity (if enabled).

Internally, when Playwright runs tests with the HTML reporter, it’s not just executing commands. It’s capturing a rich stream of events: page navigation, element interactions, assertions, network requests, and screenshots. These events are serialized and stored. The index.html file is a sophisticated client-side application that reads these serialized events and reconstructs the test execution timeline, rendering it in an interactive, user-friendly interface.

The key levers you control are Playwright’s configuration options. You can customize the output directory:

npx playwright test --reporter=html --reporter-options output=./test-results

You can also add other reporters alongside the HTML reporter. For instance, to also get the standard list reporter:

npx playwright test --reporter=list --reporter=html

You can even configure what information is captured. By default, Playwright captures screenshots on failure and video for each test file if video: 'on' is set in your playwright.config.ts. These artifacts are linked within the HTML report, allowing you to dive deeper into specific moments.

The most surprising thing about the HTML reporter is how it handles network requests. By default, it logs all network requests made during the test execution. When you click on a specific step in the report, you can see the network requests that occurred during that step. This is invaluable for debugging issues related to API calls, resource loading, or slow responses. You can see the request URL, method, status code, and even the request/response payloads if they are captured, all directly correlated with the UI action that triggered them.

The next concept you’ll want to explore is how to integrate these reports into your CI/CD pipeline for automated analysis and artifact archiving.

Want structured learning?

Take the full Playwright course →