lecture 14 of 170 completed

Forensic debugging with traces

Playwright records enough to diagnose a failure you never saw. Most engineers use a fraction of it, so this is the method.

A test failed once, three days ago, on a machine that no longer exists. You have a trace file. That is enough, and the method below is how a senior engineer gets from that file to a cause in minutes rather than an afternoon.

Configure it so the evidence exists.

use: {
  trace: 'on-first-retry',
  screenshot: 'only-on-failure',
  video: 'retain-on-failure',
},

on-first-retry is the right default: full recording when something fails, no cost on passing runs. On a suite where failures are rare and expensive to reproduce, retain-on-failure is worth the extra overhead.

Then upload the artifacts from CI. Traces written to a build machine that gets destroyed are traces you did not collect. This one line of pipeline configuration is the difference between a diagnosable failure and a shrug.

What a trace actually holds. More than people assume, which is why most engineers underuse it: a screenshot filmstrip, every action with its duration, the full DOM snapshot before and after each action, all network requests with status and timing, console output, and the source line for each step.

The DOM snapshots are the part people miss. You can open the page as it was at the moment of failure and inspect elements in it, as though the browser were still running.

The method, in order.

1. Read the error and the failing action. Start with what Playwright says. "Locator resolved to 2 elements" and "element is not visible" lead to entirely different investigations.

2. Look at the DOM snapshot at that step, not the screenshot. The screenshot shows what was painted. The snapshot shows what was there. An element that is present but zero-height looks identical to absent in a screenshot and is obvious in the snapshot.

3. Step backwards, not forwards. The failing step is usually a symptom. Walk back through the filmstrip asking where the page stopped being what you expected. That is where the cause is.

4. Check the network panel around that point. A 500, a request that never fired, or one that took nine seconds explains most "element never appeared" failures. This is frequently where the answer is, and it is the panel people forget exists.

5. Compare durations against a passing run. A step that normally takes 200ms and took 6 seconds is your lead. Timing comparison is the fastest way to find contention and slow endpoints.

Classify before you dig, because the class determines the method:

Fails alone in CI, passes locally. Environment. Viewport, timezone, data, latency, or the app not being ready when the run started.

Passes alone, fails in the suite. Cross-test interference. Do not read the failing test first. Bisect the group to find the test that breaks it, then look at what that test leaves behind.

Fails intermittently everywhere. A genuine race. Reproduce by exaggerating: slow the relevant response with a route handler and the one-in-fifty becomes reliable.

Started failing at a point in time. Read what landed between the last pass and the first failure. Fastest of the four and most often skipped.

Reading traces for flaky tests specifically. With retries on, you get a trace from the failed attempt and a passing run to compare against. Open both. The difference is almost always visible in the timing column, and it is almost always something arriving later than the test assumed.

One habit that compounds. When you diagnose a hard failure, write two sentences in the pull request: what the cause was and which trace step showed it. Over a year this teaches the team to read traces properly, which is worth more than fixing any individual test.

Remember this: classify first, read DOM snapshots rather than screenshots, walk backwards, and check the network panel.

Reference: Trace viewer.

now prove it

Reading is the setup. Solve these problems in the editor. We break the app on purpose to check your test would catch it.