HTML Report & Failure Triage
Run a small Playwright suite with one deliberately flaky test, open the HTML report, read the trace, and pinpoint the cause — without re-running anything.
1 Goal
In a little under an hour you will build a tiny Playwright suite of three tests against https://www.saucedemo.com and https://example.com, run it with the HTML reporter and trace recording enabled, and then triage the one test that fails intermittently. The outcome you're after is diagnostic: after opening the report, you should be able to say exactly which action failed, what the page looked like at that moment, and why — from the trace alone, without adding console.log or re-running. This is the core loop a test lead runs dozens of times per week when a CI job goes red overnight and the team turns up expecting answers.
2 Install Playwright (free)
Playwright is open source and free. It only needs Node.js 18 or newer, which is also free.
- Install Node.js 18+ from the official site Download the LTS build from nodejs.org. The LTS line (currently 20.x) is free, covers every feature Playwright needs, and is supported on Windows, macOS, and Linux.
-
Verify Node and npm are on your PATH
The commands differ slightly per OS only in how you open the terminal.
PowerShell
node --version npm --version
Terminal (zsh/bash)node --version npm --version
You should seebashnode --version npm --version
v20.x.x(or higher) and10.x.x(or higher). If not, re-open your terminal after installing so the PATH refreshes. - Install the Playwright browsers Playwright ships its own Chromium, Firefox, and WebKit builds so results are consistent across machines. You'll install them in step 3 as part of the project, not globally.
3 Project setup
Create a small project with a single tests/ folder and Playwright's default config. Playwright's init command scaffolds everything, but we'll override the config and tests in step 4 so you see every moving part.
-
Create and enter a project folder
PowerShell
mkdir triage-lab cd triage-lab
bash/zshmkdir triage-lab cd triage-lab
-
Initialise npm and install Playwright
All platformsThe last command downloads the Chromium, Firefox, and WebKit binaries Playwright controls. It only needs to run once per machine.
npm init -y npm install --save-dev @playwright/test@latest npx playwright install
-
Expected folder layout after step 4
Tree
triage-lab/ node_modules/ tests/ smoke.spec.ts playwright.config.ts package.json package-lock.json
4 Write the tests
Two files. Copy each one in full — no placeholders, no trimming.
a) playwright.config.ts — tells Playwright to produce an HTML report and to keep a full trace whenever a test fails on retry:
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
timeout: 30_000,
retries: 1,
reporter: [['html', { open: 'never' }], ['list']],
use: {
actionTimeout: 10_000,
trace: 'retain-on-failure',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
],
});b) tests/smoke.spec.ts — three tests: one that passes on a static page, one that passes on the SauceDemo login, and one that is deliberately flaky because it waits for the wrong locator:
import { test, expect } from '@playwright/test';
test('example.com loads and has the expected heading', async ({ page }) => {
await page.goto('https://example.com');
await expect(page.getByRole('heading', { name: 'Example Domain' })).toBeVisible();
});
test('saucedemo standard user can log in', async ({ page }) => {
await page.goto('https://www.saucedemo.com/');
await page.getByPlaceholder('Username').fill('standard_user');
await page.getByPlaceholder('Password').fill('secret_sauce');
await page.getByRole('button', { name: 'Login' }).click();
await expect(page).toHaveURL(/inventory\.html/);
await expect(page.getByText('Products')).toBeVisible();
});
test('saucedemo add-to-cart badge appears (flaky on purpose)', async ({ page }) => {
await page.goto('https://www.saucedemo.com/');
await page.getByPlaceholder('Username').fill('standard_user');
await page.getByPlaceholder('Password').fill('secret_sauce');
await page.getByRole('button', { name: 'Login' }).click();
await page.getByRole('button', { name: 'Add to cart' }).first().click();
// Bug on purpose: the real selector is '.shopping_cart_badge'.
// This one is wrong, so the test fails and gives us a trace to triage.
await expect(page.locator('.shopping-cart-badge')).toHaveText('1', { timeout: 3_000 });
});retain-on-failure and not on? Tracing every action on every test bloats report size on a large suite. retain-on-failure gives you a full trace only for the runs that matter — the red ones — which is almost always what a lead wants.
5 Run & verify
-
Run the suite
All platformsYou'll see the
npx playwright test
listreporter print three tests. The third one will fail once, retry, and fail again — that's expected. -
Expected result
Two tests pass, one fails. The terminal ends with something like:
Terminal output (abridged)
2 passed (8.1s) 1 failed To open last HTML report run: npx playwright show-report -
Open the HTML report
All platformsA browser opens at
npx playwright show-report
http://localhost:9323showing a tree of tests. The failing test is red; click into it.
6 Triage the failure from the trace
This is the whole point of the exercise. Don't re-run. Don't add console.log. Get the answer from the report.
-
Read the error banner at the top
Playwright shows the exact assertion that failed and the timeout. You should see a message like
Error: Timed out 3000ms waiting for expect(locator).toHaveText(expected)with the locator printed aslocator('.shopping-cart-badge'). - Open the trace Scroll down and click the Trace attachment. This opens the Trace Viewer: a timeline of every action, a DOM snapshot for each one, and the network log. The failing step is highlighted in red.
-
Find the action snapshot
Click the red
expect.toHaveTextstep. The left-hand pane shows the DOM as it was when the assertion ran. Inspect it: the cart badge element is actually<span class="shopping_cart_badge">1</span>— underscores, not hyphens. -
Confirm with the Actions list
The Actions panel on the left lists every step in order, with time taken. The last passing step is the
clickon the Add to cart button; everything after is the hung locator. That's your smoking gun: the click succeeded, the page reached the correct state, and only the assertion was wrong. -
Write down the root cause in one sentence
Example: "The cart-badge assertion used the selector
.shopping-cart-badge, but the real class isshopping_cart_badge. The click worked; the locator did not. Fix the selector, not the test flow." That's the message you'd paste into Slack.
.shopping_cart_badge and run npx playwright test again. All three should pass.
7 Troubleshooting
npx playwright: command not found (or npx unrecognised on Windows)
You installed Node but the terminal was open before it landed on your PATH. Close and reopen the terminal, then run node --version to confirm. On Windows, if that still fails, check System → Advanced → Environment Variables for a C:\Program Files\nodejs entry in Path. Reinstall with the official MSI if it is missing.
All three tests fail with net::ERR_NAME_NOT_RESOLVED or DNS errors
Your machine cannot reach example.com or saucedemo.com. Check corporate proxy settings first. In PowerShell, set $env:HTTPS_PROXY = "http://your-proxy:port" before running the tests; on bash, export HTTPS_PROXY=http://your-proxy:port. For Playwright browsers specifically, you can also add proxy: { server: 'http://your-proxy:port' } to the use block in playwright.config.ts.
Browsers fail to launch with Host system is missing dependencies (Linux)
Playwright's Chromium needs a handful of shared libraries on Linux. Run npx playwright install-deps once — it uses your distro's package manager and installs exactly what's needed. On Ubuntu 22.04 or Debian 12 this fixes it in one shot. Windows and macOS never hit this.
The HTML report opens but every test shows No trace
Two common causes: (1) you set trace: 'off' in the config — switch it back to 'retain-on-failure' or 'on-first-retry'. (2) the test passed on the first attempt — retain-on-failure only keeps traces for runs that actually fail. For the exercise, the flaky test should always fail both attempts because the locator is genuinely wrong.
8 Challenge
Extend the triage loop
Challenge 1 — add a real flaky test (timing, not typos). Write a fourth test that adds two items to the cart in rapid succession and asserts .shopping_cart_badge shows "2". Do it without any explicit waits. Run the suite five times (npx playwright test --repeat-each=5) and see whether it ever goes red. If it does, use the trace to decide whether the fix is a better locator (expect(...).toHaveText('2') with Playwright's auto-waiting) or a structural change.
Challenge 2 — publish the report. Zip the playwright-report/ folder and share it with a teammate. Can they open index.html directly (no server) and still see the trace? If not, check the Playwright docs for the flag that inlines trace data into a portable bundle. Leads who can hand a green-or-red report to a PM without a shared CI link move a lot faster.