Skip to content

Chapter 19 · Debugging Techniques and Flaky Test Resolution ​

Chapter goals: Master Playwright's official debug toolchain (Inspector, headed mode, Trace Viewer), build a root-cause analysis process for flaky tests, and make the test suite trustworthy again.

19.1 Why E2E Tests Become "Randomly Failing" ​

Flaky tests — the same code, the same command, sometimes pass sometimes fail — are E2E testing's biggest enemy. Once a team gets used to "red means re-run", the entire suite loses credibility. Common root causes fall into four categories:

Root Cause CategoryTypical SymptomDirection of Fix
Animations / transitionsElement "invisible" or position shiftedWait for animation to end, or disable via CSS
Network race conditionsAssertion fires before data rendersUse Web-First assertions with auto-retry (Chapter 5)
Time dependencyCases only fail at midnight / month-endInject a fixed clock; never depend on real time
Shared dataFails in parallel, passes seriallyTest-level data isolation (Chapters 6/13)

First principle

When encountering flakiness, the first response should not be "add more retries" — it should be root-cause analysis first. Retries are painkillers: they reduce the failure probability but the root cause remains, and it will eventually explode in a more隐蔽 way.

19.2 PWDEBUG and Inspector: The Official Debug Entry ​

Playwright's officially recommended debugging method is setting the environment variable PWDEBUG=1. It simultaneously enables three tools:

  1. Inspector window: visual step-by-step test execution;
  2. Headed browser (headed mode): see the page change in real time;
  3. Timeout zeroed: all auto-waits no longer time out, letting you observe at your own pace.
bash
# macOS / Linux: run in debug mode (with -s to see print output)
PWDEBUG=1 pytest -s test_login.py::test_login_success

# Windows PowerShell
$env:PWDEBUG=1; pytest -s test_login.py::test_login_success

The Inspector toolbar supports play / pause / step: each action (click, fill) highlights the corresponding code line and the target element on the page simultaneously — you can watch frame by frame what the test actually does.

19.3 page.pause(): Start Debugging from a Breakpoint ​

If the test is long and step-by-step is too slow, insert a hard breakpoint where you care:

python
def test_checkout_flow(page):
    page.goto("https://shop.example.com/cart")
    page.get_by_role("button", name="Checkout").click()

    # Pause from here; earlier steps run through directly
    page.pause()

    page.get_by_label("Shipping address").fill("1 XX Road, Chaoyang, Beijing")
    page.get_by_role("button", name="Submit order").click()
    expect(page.get_by_text("Order created")).to_be_visible()

page.pause() only takes effect when PWDEBUG=1 (ignored in normal runs), so it's safe to leave in code for temporary debugging. Two high-frequency features when Inspector pauses at the breakpoint:

  • Pick Locator: hover any element on the page; Playwright auto-generates a resilient locator using role/text/testid;
  • Live editing: modify the expression in the Inspector's locator input box directly; matched elements highlight in real time — tweak until satisfied, then copy back to code.

19.4 Trace Viewer: Best Evidence for Post-Mortem ​

Inspector is for "debug on the spot"; Trace is for "post-failure review". We configured --tracing retain-on-failure in Chapter 11; now let's use it to review an intermittent failure:

bash
# Failed cases leave a trace zip in test-results/
playwright show-trace test-results/test-cart-test-checkout-chromium.zip

Each tab in Trace Viewer is a different information dimension:

  • Actions: timing and result of every action; red = failure point; hover to see before/after DOM snapshots;
  • Log: action-level detailed logs (waiting for elements, scrolling, injecting events…) — see exactly what Playwright retried before timeout;
  • Network: all requests and responses at that moment; spot "API didn't return" race conditions at a glance;
  • Console / Errors: frontend JS errors are often the real cause of test failures.

Debugging routine

Look at Actions to find the failing action → switch to Log to see what it waited for → go to Network/Console to find "what didn't arrive". 90% of flaky cases can be traced along this path.

19.5 Waiting Strategy Correction: Stop Using sleep as a Crutch ​

Recurring time.sleep() or page.wait_for_timeout() is the strongest signal that a test is masking a race condition rather than solving it. The correct hierarchy:

  1. Auto-wait (built into every action) — sufficient for 95% of cases;
  2. Web-First assertions (expect().to_be_visible()) — for conditional state changes;
  3. Explicit waits (wait_for_url, expect_response) — for event-driven changes;
  4. wait_for_timeout — last resort, only when you know exactly why and for how long.

If you find yourself needing wait_for_timeout, ask: "what condition am I actually waiting for?" — the answer is almost always "the element to appear", which expect already handles.

19.6 Chapter Summary ​

  • Flaky roots: animation, network race, time dependency, shared data — categorize before treating;
  • PWDEBUG=1 opens Inspector + headed + zero-timeout simultaneously;
  • page.pause() inserts a breakpoint that only fires in debug mode;
  • Trace Viewer is the ultimate post-mortem tool: Actions → Log → Network → Console;
  • Never use sleep or wait_for_timeout as a crutch — fix the waiting strategy instead.

🧪 随堂测验

点击你认为正确的选项。答错时会展示正确答案与原因解析。

1. What is the first principle when a test turns flaky?

2. What does PWDEBUG=1 enable all at once?

3. When should wait_for_timeout be used?

4. In Trace Viewer, what is the recommended investigation order?

🛠️ Hands-On Exercises ​

  1. Write a test that intentionally fails due to an animation race; run with PWDEBUG=1 and use Inspector to step through and observe the auto-wait behavior.
  2. Add page.pause() mid-test; run with PWDEBUG=1 pytest -s; use Pick Locator and Live Editing in Inspector to refine a flaky selector.
  3. Trigger a failure with --tracing retain-on-failure; open the trace with playwright show-trace and follow the Actions → Log → Network path to identify the root cause.

Proceed to Chapter 20: Accessibility Testing and Aria Snapshot.