📖 The Story: The Heisenbug

A SaaS company had a test named "Checkout Flow" that failed exactly once every 50 runs. It was a classic "Heisenbug"—when the QA team tried to observe it locally using console.log or headed mode, it passed perfectly. The bug disappeared when observed.

The QA lead, Sarah, configured the suite with trace: 'on-first-retry'. On the 50th run, the test failed in CI, and a trace.zip was generated. Sarah downloaded it and opened the Trace Viewer.

Instead of looking at the final error, she hovered her mouse over the Timeline at the exact millisecond before the failure. She watched the DOM snapshot shift. She saw a promotional pop-up appear for exactly 200 milliseconds, intercept the "Submit" button click, and then disappear before the final assertion ran.

The pop-up was triggered by a race condition in a third-party analytics script. It only manifested when the CI server's CPU was under high load. Sarah added a line to dismiss the pop-up. The test passed 1,000 times in a row. The Heisenbug was dead.

Logs are for events. Traces are for time. If you don't scrub the timeline, you are blind to race conditions.

🎯 Why Advanced Tracing?

Timeline Scrubbing

Hover over the timeline to see the exact DOM state at any millisecond.

🌐

Network Waterfall

Inspect API request headers, payloads, and response bodies in context.

💻

Console & Errors

Catch silent JavaScript errors and warnings that break the UI later.

📜

Source Mapping

Click any action to see the exact line of TypeScript code that executed it.

🌍 Real World Example

A test fails because a "Success" message didn't appear. In the Trace Viewer Network tab, you see the POST request was made, but the response took 6 seconds. In the Console tab, you see a warning: "JWT token expired." The backend rejected the delayed request. The trace reveals it wasn't a UI bug, but a session timeout issue.

🧒 Explain Like I'm 10

Imagine a magic video camera that records your house. A normal camera just shows you a movie. But this magic camera also has an "X-ray" mode (to see inside walls), a "wiretap" mode (to hear phone calls), and a "time-scrub" dial that lets you freeze time and look at exactly what was on the table at 3:02 PM.

The Playwright Trace Viewer is that magic camera for your web app. It records everything—what the screen looked like, what the network was doing, and what the code was doing—so you can freeze time and investigate.

🎓 Professional Explanation

The Trace Viewer is a local Progressive Web App (PWA) that renders a serialized timeline of test events. Playwright instruments the browser via the Chrome DevTools Protocol (CDP) to capture DOM snapshots (using mutation diffing), network traffic, and console output. The viewer stitches these together, allowing you to "scrub" through time. As you hover over the timeline, the viewer reconstructs the DOM state at that exact millisecond, overlaying action metadata and source code mappings.

📊 Trace UI Anatomy

The Trace Viewer Interface Layout

Timeline (Hover to scrub DOM state)
Actions
1. page.goto()
2. click('#submit')
Snapshots (DOM)
Visual rendering of the page at the selected time.
Source
spec.ts:42
await page.click()
Console: [Error] Failed to load resource: 401 Unauthorized

⏳ Timeline Scrubbing (The Secret Weapon)

Most engineers only look at the final snapshot when a test fails. This is a mistake. The root cause is almost always in the before state.

trace viewer usage
// How to hunt a race condition:
1. Open trace.zip
2. Find the failed action (red bar on timeline)
3. Slowly drag your mouse LEFT across the timeline
4. Watch the "Snapshots" panel (center)
5. Look for elements appearing, disappearing, or shifting
6. Notice the exact moment an overlay or spinner intercepts your target

🌐 Network Waterfall

Click the "Network" tab at the bottom of the Trace Viewer. It displays a Chrome DevTools-style waterfall of all requests.

trace viewer usage
// What to look for in the Network tab:
- Look for 500 status codes (Server errors)
- Look for requests taking > 3000ms (Latency)
- Click a request to inspect Request Headers & Payload
- Check the Response Body to see if the backend sent bad data

💻 Console & Errors

The frontend can look perfect while a silent JavaScript error destroys the app's state. The Console tab captures all console.log, console.error, and unhandled Promise rejections.

🚨 The Silent Killer

If a React component throws an error inside a try/catch block, the UI might not crash immediately, but state updates will stop. The only trace of this will be a red line in the Trace Viewer's Console tab. Always check the console!

📜 Source Mapping

The "Source" panel (usually right side) maps the selected action back to your TypeScript code. If an action took 5 seconds to complete, you can see exactly which line of code triggered it, helping you identify slow waitForResponse or page.evaluate calls.

📌 Custom Annotations

You can inject your own notes directly into the Trace timeline using test.info().annotate(). This is invaluable for marking business logic steps.

typescript
test('checkout flow', async ({ page }) => {
  await page.goto('/cart');
  
  // Add a custom annotation to the trace timeline
  await test.info().annotate('User has 3 items in cart, starting checkout');

  await page.click('Checkout');
  await test.info().annotate(`Applying discount code: SAVE20`);
  
  await page.fill('#promo', 'SAVE20');
});

When you open the trace, these annotations appear as markers on the timeline, making it easy to navigate complex test flows.

⚠️ Common Mistakes

Mistake 1: Only Looking at the End

Engineers often open a trace, click the last action, and look at the final screenshot. If a pop-up appeared and disappeared earlier, it won't be visible in the final state. You must scrub the timeline.

Mistake 2: Ignoring the Console

A test fails to find a button. The engineer assumes the selector is wrong. In reality, a JavaScript error earlier in the test prevented the button from rendering. The Console tab would have shown the error instantly.

✅ Best Practices

  1. Always check the Console tab first. Look for red errors before inspecting the DOM.
  2. Scrub slowly. Drag your mouse across the timeline to catch fleeting overlays.
  3. Use annotations. Tag complex business steps with test.info().annotate().
  4. Check Network timing. A 200 OK response that takes 8 seconds is just as bad as a 500 error.

🏗️ Senior Engineer Deep Dive

DOM Mutation Diffing

How does the Trace Viewer show the DOM at any millisecond without recording a full HTML snapshot every frame (which would be gigabytes)? Playwright uses DOM mutation observers. It takes one initial snapshot, then records only the differences (added/removed nodes, changed attributes) as events. When you scrub the timeline, the viewer reconstructs the DOM by replaying the diff patch. This is why trace files are typically only 1-5 MB.

Local vs CI Trace Viewing

You can view traces locally via the CLI (npx playwright show-trace), but Playwright also hosts a public version at trace.playwright.dev. It is a client-side PWA; dragging and dropping your .zip file there processes everything in your browser. No data is uploaded to Microsoft's servers, making it secure for enterprise use.

💼 Interview Questions

Beginner
How do you debug a test that only fails in CI?
Show Answer
I configure trace: 'retain-on-failure' in the Playwright config. When the test fails in CI, the trace.zip is uploaded as an artifact. I download it, run npx playwright show-trace trace.zip, and inspect the timeline, network, and console tabs to find the root cause.
Intermediate
What is the most effective way to find a race condition using the Trace Viewer?
Show Answer
I open the trace and go to the timeline at the top. I slowly hover my mouse backwards from the point of failure. As I hover, the DOM snapshot updates. I look for elements (like overlays, spinners, or modals) that appear momentarily and might intercept the click or block visibility.
Advanced
How can you mark specific business logic steps in a complex trace?
Show Answer
I use test.info().annotate('Step description') inside the test. This injects a custom marker directly into the trace timeline, making it easy to navigate long, complex test flows without guessing which action corresponds to which business step.
Scenario
A test fails because an element isn't visible, but it works locally. How do you use the Trace Viewer to investigate?
Show Answer
First, I check the Console tab for any JavaScript errors that might have halted rendering. Then, I check the Network tab to ensure the API call that provides the data for the element succeeded. Finally, I scrub the timeline to see if a loading spinner or overlay was stuck over the element.

🏋️ Practical Exercise

🎯 Hands-On Practice Medium

Scrub a Trace

  1. Run a test locally with --trace=on.
  2. Open the generated trace file.
  3. Find the page.goto() action in the Actions list.
  4. Hover your mouse over the Timeline at the top. Watch how the DOM snapshot changes as the page loads.
  5. Click the Network tab and find the main document request.

🚀 Mini Project

🏗️ The Annotated Flow

  1. Write a test for a multi-step form (e.g., shipping -> payment -> review).
  2. Before each major step, add await test.info().annotate('Starting Payment Step').
  3. Run with tracing enabled.
  4. Open the trace and verify your annotations appear as distinct markers on the timeline.

📝 Quiz

Test your understanding. Click an option to check your answer.

1. What is the primary benefit of "Timeline Scrubbing" in the Trace Viewer?
A) It speeds up test execution.
B) It allows you to see the DOM state at any exact millisecond.
C) It automatically fixes flaky tests.
D) It generates HTML reports.
Answer: B — By hovering over the timeline, Playwright reconstructs the DOM snapshot, letting you catch fleeting UI elements.
2. Which API is used to add custom notes to a trace timeline?
A) test.attach()
B) console.annotate()
C) test.info().annotate()
D) page.markTrace()
Answer: C — test.info().annotate() injects custom markers into the trace timeline.
3. Where should you look first in the Trace Viewer if a test fails unexpectedly?
A) The final screenshot
B) The Console tab for JavaScript errors
C) The config file
D) The test title
Answer: B — Silent JavaScript errors often break UI state without crashing the test immediately. Check the console first.
4. What does the "Source" panel in the Trace Viewer show?
A) The website's HTML source code.
B) The exact line of your spec.ts file that executed the action.
C) The Playwright framework internals.
D) The CSS styles applied to the element.
Answer: B — It maps the browser action back to your TypeScript test code.
5. How does Playwright keep trace file sizes small despite recording DOM states?
A) It compresses the zip file heavily.
B) It only records the first and last frames.
C) It records an initial snapshot and then only saves DOM mutation diffs.
D) It deletes network logs after 1 minute.
Answer: C — By using DOM mutation diffing, it avoids saving full-page HTML for every frame.
6. Can you view a Playwright trace in a web browser without installing Node.js?
A) Yes, by dragging the trace.zip into trace.playwright.dev.
B) No, you must have Node.js installed.
C) Yes, but only in Chrome.
D) No, traces are binary and cannot be viewed in a browser.
Answer: A — The hosted app at trace.playwright.dev is a PWA that processes the file locally in your browser.
7. What indicates a failed action on the Trace Viewer timeline?
A) A green bar
B) A blue bar
C) A red bar
D) A yellow bar
Answer: C — Failed actions or errors are marked with red on the timeline and action list.
8. If an API request returns 200 OK but takes 8 seconds, where can you spot this in the Trace Viewer?
A) The Console tab
B) The Network tab (Waterfall)
C) The Source tab
D) The Metadata tab
Answer: B — The Network waterfall visually shows the duration of the request, making 8-second latency obvious.
9. Why is it a mistake to only look at the final snapshot of a failed test?
A) The final snapshot is usually corrupted.
B) The root cause (like a disappearing overlay) might have already vanished by the end.
C) Playwright doesn't take a final snapshot.
D) The final snapshot doesn't include CSS.
Answer: B — Fleeting elements that caused the failure often disappear before the test ends.
10. Are custom annotations visible in the standard HTML report?
A) Yes, they are highlighted in red.
B) No, they appear as markers on the Trace Viewer timeline, not the HTML report.
C) Yes, but only if you use console.log.
D) Yes, they replace the test title.
Answer: B — Annotations are specifically trace timeline markers, though attachments can appear in HTML reports.

❓ FAQ

Q: How long should I retain trace files in CI?

A: 7 to 14 days is standard. Traces are small, but they accumulate over time. Set retention-days: 14 in your CI artifact upload step.

Q: Can I view a trace on my mobile phone?

A: While technically possible via the web app, the Trace Viewer UI is highly optimized for desktop screens with multiple panels. Mobile viewing is not recommended.

📦 Summary

🎯 Key Takeaways

  • Scrub the timeline to catch fleeting DOM elements and race conditions.
  • Check the Console tab for silent JavaScript errors.
  • Inspect the Network waterfall for slow or failed API calls.
  • Use the Source panel to map actions back to your spec.ts code.
  • Inject custom notes using test.info().annotate().
  • Use trace.playwright.dev to view traces securely in any browser.