惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

量子位
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
雷峰网
雷峰网
酷 壳 – CoolShell
酷 壳 – CoolShell
博客园 - 司徒正美
N
News | PayPal Newsroom
WordPress大学
WordPress大学
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
The Cloudflare Blog
S
Secure Thoughts
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Security Archives - TechRepublic
Security Archives - TechRepublic
博客园 - 【当耐特】
博客园 - 聂微东
S
Securelist
宝玉的分享
宝玉的分享
爱范儿
爱范儿
IT之家
IT之家
T
The Exploit Database - CXSecurity.com
S
SegmentFault 最新的问题
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
AI
AI
Security Latest
Security Latest
博客园 - 叶小钗
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
The Last Watchdog
The Last Watchdog
月光博客
月光博客
D
Darknet – Hacking Tools, Hacker News & Cyber Security
S
Schneier on Security
人人都是产品经理
人人都是产品经理
Webroot Blog
Webroot Blog
Jina AI
Jina AI
阮一峰的网络日志
阮一峰的网络日志
J
Java Code Geeks
N
News and Events Feed by Topic
Recent Commits to openclaw:main
Recent Commits to openclaw:main
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Last Week in AI
Last Week in AI
S
Security Affairs
美团技术团队
Hugging Face - Blog
Hugging Face - Blog
V
V2EX
罗磊的独立博客
Spread Privacy
Spread Privacy
Help Net Security
Help Net Security
T
Tailwind CSS Blog
C
Cybersecurity and Infrastructure Security Agency CISA
博客园_首页
Apple Machine Learning Research
Apple Machine Learning Research

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
The Playwright Playbook — Part 6: Debugging Like a Pro — Trace Viewer, Inspector & VS Code
Faizal · 2026-06-20 · via DEV Community

The Playwright Playbook — Part 6: Debugging Like a Pro — Trace Viewer, Inspector & VS Code

"A failing test at 2am in CI tells you what broke. Trace Viewer tells you why."

In Part 1, we wrote solid tests. In Parts 2 through 5, we built layer after layer — network interception, multi-user flows, API testing, visual regression.

Now something fails.

It always does.

The question isn't whether your tests will fail — it's how fast you can understand why.

Most engineers debug Playwright like this:

// 🔴 The classic debugging approach
console.log('got here 1');
await page.click(someLocator);
console.log('got here 2');

Or worse — they add waitForTimeout(5000) and hope the problem goes away.

There's a better way. Playwright ships with a debugging toolkit so powerful that once you start using it, you'll never go back to console.log again.

Trace Viewer. Playwright Inspector. VS Code integration. page.pause().

These tools show you every click, every network call, every DOM snapshot, every console error — in a visual timeline that reconstructs exactly what happened, step by step.

Let's build the debugging layer into our framework. 🎯


🏗️ Where We Left Off

After Part 5, our full project structure is:

playwright-playbook/
├── tests/
│   ├── auth/
│   │   └── login.spec.ts                        ✅ Part 1
│   ├── tasks/
│   │   └── task-management.spec.ts              ✅ Part 1
│   ├── network/                                 ✅ Part 2
│   │   ├── api-mocking.spec.ts
│   │   ├── error-simulation.spec.ts
│   │   └── network-assertions.spec.ts
│   ├── multi-user/                              ✅ Part 3
│   │   ├── role-permissions.spec.ts
│   │   └── realtime-collaboration.spec.ts
│   ├── multi-tab/                               ✅ Part 3
│   │   └── multi-tab-flows.spec.ts
│   ├── api/                                     ✅ Part 4
│   │   ├── tasks-api.spec.ts
│   │   ├── auth-api.spec.ts
│   │   ├── graphql-api.spec.ts
│   │   └── api-ui-chain.spec.ts
│   └── visual/                                  ✅ Part 5
│       ├── dashboard-visual.spec.ts
│       ├── task-visual.spec.ts
│       └── responsive-visual.spec.ts
├── pages/
│   ├── LoginPage.ts                             ✅ Part 1
│   ├── TaskPage.ts                              ✅ Part 1
│   └── DashboardPage.ts                         ✅ Part 3
├── api/
│   ├── TaskApiClient.ts                         ✅ Part 4
│   └── AuthApiClient.ts                         ✅ Part 4
├── fixtures/
│   ├── auth.fixture.ts                          ✅ Part 1
│   ├── tasks.json                               ✅ Part 2
│   ├── empty-tasks.json                         ✅ Part 2
│   ├── tasks-har.har                            ✅ Part 2
│   ├── multi-user.fixture.ts                    ✅ Part 3
│   └── api.fixture.ts                           ✅ Part 4
├── scripts/
│   └── record-har.ts                            ✅ Part 2
├── utils/
│   ├── schema-validator.ts                      ✅ Part 4
│   └── visual-helpers.ts                        ✅ Part 5
├── snapshots/                                   ✅ Part 5
├── .auth/
├── global-setup.ts                              ✅ Part 1
├── playwright.config.ts                         ✅ Part 1 (updated Parts 3, 4 & 5)
└── .env

By the end of Part 6, we add:

playwright-playbook/
├── tests/
│   └── debug/                                   ← NEW
│       └── trace-examples.spec.ts
├── utils/
│   └── debug-helpers.ts                         ← NEW
├── .vscode/                                     ← NEW
│   ├── extensions.json
│   └── launch.json

Every file gets fully built below. 👇


🧠 The Debugging Toolkit — Overview

Playwright gives you five distinct debugging tools. Each is for a different situation:

Tool                  When to use
─────────────────     ──────────────────────────────────────────────
Trace Viewer          Post-mortem — understanding a CI failure
Playwright Inspector  Live — step through a test interactively
page.pause()          Breakpoint — pause mid-test, inspect live DOM
VS Code Extension     IDE — run/debug individual tests without CLI
Console + Network     During development — lightweight live logging

We'll build proper support for all five into the framework. Let's start with the most important one. 👇


🎬 Trace Viewer — The Most Powerful Debugging Tool in Playwright

Trace Viewer is a full timeline recording of your test — every action, every network request, every DOM snapshot, every console log — captured and viewable in a browser-based UI.

When a test fails in CI, you download the trace file and open it locally. You see exactly what the browser saw at every step. No guessing. No re-running the test hoping to reproduce it.

Step 1 — Configure traces in playwright.config.ts

// playwright.config.ts — updated use block
use: {
  baseURL: process.env.BASE_URL || 'http://localhost:3000',

  // Screenshot on every failure — attached to HTML report
  screenshot: 'only-on-failure',

  // Video on first retry — see exactly what happened
  video: 'retain-on-failure',

  // Trace on first retry in CI, always on locally for debugging
  trace: process.env.CI ? 'on-first-retry' : 'on',

  extraHTTPHeaders: {
    'Accept': 'application/json',
    'Content-Type': 'application/json',
  },
},

Trace modes explained:

'off'             — No traces recorded (fastest)
'on'              — Trace recorded for every test (slowest, most info)
'retain-on-failure' — Trace only kept when test fails (good balance)
'on-first-retry'  — Trace recorded on first retry (recommended for CI)

on-first-retry is the sweet spot for CI — it only records when something is already failing, so you don't pay the overhead on every green run. 🎯

Step 2 — Open a trace locally

After a test failure, Playwright saves a .zip trace file in test-results/. Open it:

# Open the trace viewer in your browser
npx playwright show-trace test-results/my-test/trace.zip

# Or open the full HTML report which embeds trace links
npx playwright show-report

Step 3 — What you see in Trace Viewer

The Trace Viewer UI has five panels:

┌─────────────────────────────────────────────────────────┐
│  Timeline ────────────────────────────────────────────  │
│  [action][action][action][network][action][FAIL]        │
├─────────────────┬───────────────────────────────────────┤
│                 │  Before snapshot    After snapshot     │
│  Action list    │  ┌─────────────┐  ┌─────────────────┐ │
│                 │  │             │  │                 │ │
│  ▶ goto /tasks  │  │   DOM before│  │   DOM after     │ │
│  ▶ click button │  │   the action│  │   the action    │ │
│  ▶ fill input   │  │             │  │                 │ │
│  ❌ expect fails│  └─────────────┘  └─────────────────┘ │
├─────────────────┴───────────────────────────────────────┤
│  Network  |  Console  |  Source                         │
└─────────────────────────────────────────────────────────┘

Every action shows:

  • The DOM before the action
  • The DOM after the action
  • All network calls that fired during the action
  • Console logs and errors at that moment
  • The exact line of test code that triggered it

This is how you debug a test that fails once in 50 runs in CI. You don't reproduce it — you read the trace. 🔥


🔍 Playwright Inspector — Interactive Step-Through Debugging

Inspector is for when you're writing a new test and want to figure out the right locator or understand why an action isn't working.

It opens a live browser alongside an inspector panel — you step through your test action by action, watching what happens in real time.

Launch modes

# Run a specific test in debug mode — opens Inspector
npx playwright test tests/tasks/task-management.spec.ts --debug

# Run in headed mode only — see the browser, no Inspector panel
npx playwright test --headed

# Run with slow motion — slows each action by 1 second (great for watching)
npx playwright test --headed --slow-mo=1000

# Debug a specific test by name
npx playwright test --debug -g "user can create a new task"

Using Inspector to find the right locator

This is the killer use case. When you're not sure what locator to use:

  1. Run your test with --debug
  2. Inspector opens — click Record or Explore
  3. Hover over elements in the browser — Inspector shows you the recommended locator
  4. Click the element — Inspector generates the getByRole, getByLabel, or getByTestId code
  5. Copy it into your test

No more guessing at selectors. No more page.$('div > div:nth-child(2) > span'). 🎯

The Inspector toolbar

▶  Resume     — Run to the next breakpoint or end of test
⏭  Step over  — Execute the current action and pause on the next
🔍 Pick       — Hover over elements to see their locators
⏺  Record    — Record new actions by clicking in the browser


⏸️ page.pause() — Your Interactive Breakpoint

page.pause() is the equivalent of a debugger breakpoint inside your test. The test runs normally until it hits page.pause() — then it freezes and opens Inspector.

test('debugging a flaky task creation flow', async ({ page }) => {
  const taskPage = new TaskPage(page);
  await taskPage.goto();

  // Test runs normally up to here
  await taskPage.newTaskButton.click();

  // ⏸️ Test freezes here — Inspector opens
  // You can now interact with the page, inspect the DOM,
  // check network calls, try locators — all live
  await page.pause();

  // When you click Resume in Inspector, the test continues
  await taskPage.taskTitleInput.fill('Debugging this task');
  await taskPage.saveTaskButton.click();
});

Important: Remove page.pause() before committing. If it runs in CI with no terminal, the test will hang indefinitely.

A safer pattern — only pause in local development:

// Only pause when running locally — never in CI
if (!process.env.CI) {
  await page.pause();
}


🛠️ Building the Debug Helpers Utility

These helpers centralise common debugging patterns — collecting console errors, logging performance timing, and capturing network activity during a test.

// utils/debug-helpers.ts
import { Page, ConsoleMessage } from '@playwright/test';

export interface ConsoleError {
  type: string;
  text: string;
  location: string;
}

export interface NetworkCall {
  method: string;
  url: string;
  status: number;
  duration: number;
}

/**
 * Attach a console error collector to the page.
 * Returns a function to get all collected errors at any point.
 *
 * Usage:
 *   const getErrors = collectConsoleErrors(page);
 *   // ... run your test ...
 *   const errors = getErrors();
 *   expect(errors).toHaveLength(0);
 */
export function collectConsoleErrors(page: Page): () => ConsoleError[] {
  const errors: ConsoleError[] = [];

  page.on('console', (msg: ConsoleMessage) => {
    if (msg.type() === 'error') {
      errors.push({
        type: msg.type(),
        text: msg.text(),
        location: msg.location().url,
      });
    }
  });

  // Also capture uncaught page errors (runtime JS errors)
  page.on('pageerror', (error: Error) => {
    errors.push({
      type: 'pageerror',
      text: error.message,
      location: error.stack ?? '',
    });
  });

  return () => [...errors];
}

/**
 * Attach a network call logger to the page.
 * Returns a function to get all captured network calls.
 *
 * Usage:
 *   const getCalls = collectNetworkCalls(page, '/api/');
 *   // ... run your test ...
 *   const calls = getCalls();
 *   console.log(calls); // see every API call made
 */
export function collectNetworkCalls(
  page: Page,
  urlFilter: string = ''
): () => NetworkCall[] {
  const calls: NetworkCall[] = [];

  page.on('requestfinished', async request => {
    if (urlFilter && !request.url().includes(urlFilter)) return;

    const response = await request.response();
    const timing = request.timing();

    calls.push({
      method: request.method(),
      url: request.url(),
      status: response?.status() ?? 0,
      duration: timing.responseEnd - timing.requestStart,
    });
  });

  return () => [...calls];
}

/**
 * Log all page console output during a test.
 * Useful during development — remove before committing.
 */
export function attachConsoleLogger(page: Page): void {
  page.on('console', msg => {
    const type = msg.type().padEnd(7);
    console.log(`[PAGE ${type}] ${msg.text()}`);
  });

  page.on('pageerror', error => {
    console.error(`[PAGE ERROR] ${error.message}`);
  });
}

/**
 * Capture a named checkpoint screenshot during a test.
 * Useful for documenting test steps without full VRT.
 *
 * Usage:
 *   await checkpoint(page, 'after-task-created');
 */
export async function checkpoint(page: Page, name: string): Promise<void> {
  const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
  await page.screenshot({
    path: `test-results/checkpoints/${name}-${timestamp}.png`,
    fullPage: false,
  });
  console.log(`📸 Checkpoint: ${name}`);
}

/**
 * Assert no console errors occurred during the test.
 * Add this at the end of any test to catch silent JS errors.
 */
export function assertNoConsoleErrors(getErrors: () => ConsoleError[]): void {
  const errors = getErrors();
  if (errors.length > 0) {
    const errorMessages = errors.map(e => `  [${e.type}] ${e.text}`).join('\n');
    throw new Error(`Console errors detected during test:\n${errorMessages}`);
  }
}

/**
 * Log a summary of all network calls made during the test.
 * Helpful for understanding what API traffic a user action triggers.
 */
export function logNetworkSummary(getCalls: () => NetworkCall[]): void {
  const calls = getCalls();
  if (calls.length === 0) {
    console.log('No API network calls recorded.');
    return;
  }

  console.log('\n📡 Network calls during test:');
  calls.forEach(call => {
    const status = call.status >= 400 ? `❌ ${call.status}` : `✅ ${call.status}`;
    console.log(`  ${status} ${call.method.padEnd(6)} ${call.url} (${Math.round(call.duration)}ms)`);
  });
}


🧪 Trace Examples — Tests That Demonstrate the Debug Toolkit

These tests serve a dual purpose: they're real tests AND they demonstrate how to use each debugging tool in a real scenario.

// tests/debug/trace-examples.spec.ts
import { test, expect } from '@playwright/test';
import { TaskPage } from '../../pages/TaskPage';
import { DashboardPage } from '../../pages/DashboardPage';
import {
  collectConsoleErrors,
  collectNetworkCalls,
  assertNoConsoleErrors,
  logNetworkSummary,
  checkpoint,
} from '../../utils/debug-helpers';

test.describe('Debug Helpers — Console Error Detection', () => {
  test('task page loads with no console errors', async ({ page }) => {
    // Attach error collector BEFORE navigating
    const getErrors = collectConsoleErrors(page);

    const taskPage = new TaskPage(page);
    await taskPage.goto();

    // Wait for page to fully settle
    await page.waitForLoadState('networkidle');

    // Assert no JS errors occurred during page load
    assertNoConsoleErrors(getErrors);
  });

  test('dashboard loads with no console errors', async ({ page }) => {
    const getErrors = collectConsoleErrors(page);

    const dashboard = new DashboardPage(page);
    await dashboard.goto();
    await page.waitForLoadState('networkidle');

    assertNoConsoleErrors(getErrors);
  });

  test('task creation flow produces no console errors', async ({ page }) => {
    const getErrors = collectConsoleErrors(page);

    const taskPage = new TaskPage(page);
    await taskPage.goto();
    await taskPage.createTask('Console error test task');

    await expect(taskPage.getTaskLocator('Console error test task')).toBeVisible();

    // Clean up
    await taskPage.deleteTask('Console error test task');

    // Assert no errors occurred during the entire flow
    assertNoConsoleErrors(getErrors);
  });
});

test.describe('Debug Helpers — Network Call Auditing', () => {
  test('task page load triggers expected API calls', async ({ page }) => {
    const getCalls = collectNetworkCalls(page, '/api/');

    const taskPage = new TaskPage(page);
    await taskPage.goto();
    await page.waitForLoadState('networkidle');

    const calls = getCalls();
    logNetworkSummary(getCalls);

    // Assert the expected API calls were made
    const tasksFetch = calls.find(
      c => c.url.includes('/api/tasks') && c.method === 'GET'
    );
    expect(tasksFetch).toBeDefined();
    expect(tasksFetch!.status).toBe(200);

    // Assert no unexpected 4xx or 5xx calls
    const failedCalls = calls.filter(c => c.status >= 400);
    expect(failedCalls).toHaveLength(0);
  });

  test('task creation triggers exactly one POST call', async ({ page }) => {
    const taskPage = new TaskPage(page);
    await taskPage.goto();

    // Start collecting AFTER initial page load — only capture creation calls
    const getCalls = collectNetworkCalls(page, '/api/tasks');

    await taskPage.createTask('Network audit test task');
    await expect(taskPage.getTaskLocator('Network audit test task')).toBeVisible();

    const calls = getCalls();
    const postCalls = calls.filter(c => c.method === 'POST');

    // Should be exactly one POST — not two, not zero
    expect(postCalls).toHaveLength(1);
    expect(postCalls[0].status).toBe(201);

    // Cleanup
    await taskPage.deleteTask('Network audit test task');
  });
});

test.describe('Debug Helpers — Checkpoint Screenshots', () => {
  test('document task creation flow with checkpoints', async ({ page }) => {
    const taskPage = new TaskPage(page);
    await taskPage.goto();

    // Checkpoint 1 — initial state
    await checkpoint(page, '01-task-page-initial');

    // Open new task modal
    await taskPage.newTaskButton.click();
    await checkpoint(page, '02-new-task-modal-open');

    // Fill in the task
    await taskPage.taskTitleInput.fill('Checkpoint documented task');
    await checkpoint(page, '03-task-title-filled');

    // Save
    await taskPage.saveTaskButton.click();
    await expect(taskPage.getTaskLocator('Checkpoint documented task')).toBeVisible();
    await checkpoint(page, '04-task-created-success');

    // Cleanup
    await taskPage.deleteTask('Checkpoint documented task');
    await checkpoint(page, '05-task-deleted');

    // Checkpoint PNGs are saved to test-results/checkpoints/
    // Useful for documenting a flow or building a test report with screenshots
  });
});

test.describe('Flaky Test Patterns — How to Debug Them', () => {
  test('wait for real condition — not arbitrary timeout', async ({ page }) => {
    const taskPage = new TaskPage(page);
    await taskPage.goto();

    // ✅ Correct pattern — wait for the network response AND the action together
    const [response] = await Promise.all([
      page.waitForResponse(
        resp => resp.url().includes('/api/tasks') && resp.request().method() === 'POST'
      ),
      taskPage.createTask('Condition-based wait task'),
    ]);

    // Assert on the actual condition that confirms completion
    expect(response.status()).toBe(201);
    await expect(taskPage.getTaskLocator('Condition-based wait task')).toBeVisible();

    await taskPage.deleteTask('Condition-based wait task');
  });

  test('identify what a "random" failure actually looks like', async ({ page }) => {
    const getErrors = collectConsoleErrors(page);
    const getCalls = collectNetworkCalls(page, '/api/');

    const taskPage = new TaskPage(page);
    await taskPage.goto();

    // Simulate a slow/unreliable API response
    await page.route('**/api/tasks', async route => {
      // Add a random delay between 0 and 500ms — simulates real-world jitter
      const delay = Math.random() * 500;
      await new Promise(resolve => setTimeout(resolve, delay));
      await route.continue();
    });

    await taskPage.createTask('Jitter test task');

    // This should STILL pass — because we're waiting on conditions, not timeouts
    await expect(taskPage.getTaskLocator('Jitter test task')).toBeVisible();

    // Log what actually happened
    logNetworkSummary(getCalls);
    assertNoConsoleErrors(getErrors);

    await taskPage.deleteTask('Jitter test task');
  });
});


💻 VS Code Integration — Run & Debug Tests From Your IDE

If you use VS Code, the official Playwright extension is the fastest way to run and debug individual tests without touching the terminal.

Step 1 — Install the extension

// .vscode/extensions.json
{
  "recommendations": [
    "ms-playwright.playwright",
    "dbaeumer.vscode-eslint",
    "esbenp.prettier-vscode"
  ]
}

Commit this file — when teammates open the repo, VS Code prompts them to install the recommended extensions automatically.

Step 2 — VS Code debug launch config

// .vscode/launch.json
{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Debug Playwright Test",
      "type": "node",
      "request": "launch",
      "program": "${workspaceFolder}/node_modules/.bin/playwright",
      "args": ["test", "--debug", "${file}"],
      "cwd": "${workspaceFolder}",
      "env": {
        "PWDEBUG": "1"
      },
      "console": "integratedTerminal"
    },
    {
      "name": "Debug Current Test File",
      "type": "node",
      "request": "launch",
      "program": "${workspaceFolder}/node_modules/.bin/playwright",
      "args": ["test", "${file}", "--headed", "--project=admin"],
      "cwd": "${workspaceFolder}",
      "env": {},
      "console": "integratedTerminal"
    },
    {
      "name": "Debug Specific Test by Name",
      "type": "node",
      "request": "launch",
      "program": "${workspaceFolder}/node_modules/.bin/playwright",
      "args": [
        "test",
        "--debug",
        "--grep",
        "${input:testName}"
      ],
      "cwd": "${workspaceFolder}",
      "console": "integratedTerminal"
    }
  ],
  "inputs": [
    {
      "id": "testName",
      "type": "promptString",
      "description": "Enter the test name or partial match"
    }
  ]
}

What the VS Code extension gives you

Once installed and configured, you get:

Testing sidebar (beaker icon):
├── See all tests in a tree view
├── ▶ Run individual test with one click
├── 🐛 Debug individual test with one click
├── 📍 Set breakpoints in test code
└── 🔴 Failing tests highlighted inline in the editor

Editor gutter:
├── ▶ Run this test (appears next to each test() block)
└── 🐛 Debug this test

You can click the ▶ icon next to any test() in your editor and it runs immediately — no terminal, no command to remember. 🎯


📋 Reading CI Failures Like a Pro

When a test fails in CI, you get three artifacts attached to the pipeline run:

test-results/
├── my-failing-test/
│   ├── trace.zip           ← Full trace — every action, network call, DOM snapshot
│   ├── test-failed-1.png   ← Screenshot at the moment of failure
│   └── video.webm          ← Video of the entire test run

The debugging workflow for CI failures

# Step 1 — Download trace.zip from CI artifacts

# Step 2 — Open it locally
npx playwright show-trace path/to/trace.zip

# Step 3 — In Trace Viewer:
#   Click through the action timeline to find where it went wrong
#   Check the "Before" vs "After" DOM snapshots
#   Look at the network tab — was there a 500? A missing response?
#   Check the console tab — was there a JS error?

# Step 4 — Reproduce locally if needed
npx playwright test --debug -g "the failing test name"

Reading the trace timeline — what to look for

Common failure patterns and where to find them in the trace:

Element not found:
  → Action shows "waiting for locator" → times out
  → Check "Before" snapshot — is the element there? Is the selector wrong?
  → Check network tab — did the API call that renders it fail?

Wrong element clicked:
  → "Before" snapshot shows multiple matching elements
  → Your locator was too broad — make it more specific

Flaky timing failure:
  → Network tab shows slow response (800ms+)
  → Action fired before the data loaded
  → Fix: wait for the response, not a timeout

Authentication failure:
  → First network call returns 401
  → storageState expired or wasn't loaded
  → Fix: check global-setup.ts ran correctly

Console error on failure:
  → Console tab shows red JS error
  → Often unrelated to your test — but worth checking
  → Add collectConsoleErrors() to catch these proactively


🔧 Useful Debug Commands — Quick Reference

# Run a single test file in debug mode
npx playwright test tests/tasks/task-management.spec.ts --debug

# Run a test by name pattern
npx playwright test --debug -g "user can create a new task"

# Run headed (see the browser) without Inspector
npx playwright test --headed

# Run with slow motion — 1 second between each action
npx playwright test --headed --slow-mo=1000

# Generate and open the HTML report after a run
npx playwright test && npx playwright show-report

# Open a specific trace file
npx playwright show-trace test-results/my-test/trace.zip

# Update visual snapshots after intentional UI change
npx playwright test --project=visual --update-snapshots

# Run only tests matching a tag
npx playwright test --grep @smoke

# List all tests without running them
npx playwright test --list

# Run with maximum verbosity
npx playwright test --reporter=list --verbose


📁 Final Project Structure After Part 6

Every file listed below has been fully built across Parts 1 through 6:

playwright-playbook/
├── tests/
│   ├── auth/
│   │   └── login.spec.ts                        ✅ Part 1
│   ├── tasks/
│   │   └── task-management.spec.ts              ✅ Part 1
│   ├── network/                                 ✅ Part 2
│   │   ├── api-mocking.spec.ts
│   │   ├── error-simulation.spec.ts
│   │   └── network-assertions.spec.ts
│   ├── multi-user/                              ✅ Part 3
│   │   ├── role-permissions.spec.ts
│   │   └── realtime-collaboration.spec.ts
│   ├── multi-tab/                               ✅ Part 3
│   │   └── multi-tab-flows.spec.ts
│   ├── api/                                     ✅ Part 4
│   │   ├── tasks-api.spec.ts
│   │   ├── auth-api.spec.ts
│   │   ├── graphql-api.spec.ts
│   │   └── api-ui-chain.spec.ts
│   ├── visual/                                  ✅ Part 5
│   │   ├── dashboard-visual.spec.ts
│   │   ├── task-visual.spec.ts
│   │   └── responsive-visual.spec.ts
│   └── debug/                                   ✅ Part 6
│       └── trace-examples.spec.ts
├── pages/
│   ├── LoginPage.ts                             ✅ Part 1
│   ├── TaskPage.ts                              ✅ Part 1
│   └── DashboardPage.ts                         ✅ Part 3
├── api/
│   ├── TaskApiClient.ts                         ✅ Part 4
│   └── AuthApiClient.ts                         ✅ Part 4
├── fixtures/
│   ├── auth.fixture.ts                          ✅ Part 1
│   ├── tasks.json                               ✅ Part 2
│   ├── empty-tasks.json                         ✅ Part 2
│   ├── tasks-har.har                            ✅ Part 2
│   ├── multi-user.fixture.ts                    ✅ Part 3
│   └── api.fixture.ts                           ✅ Part 4
├── scripts/
│   └── record-har.ts                            ✅ Part 2
├── utils/
│   ├── schema-validator.ts                      ✅ Part 4
│   ├── visual-helpers.ts                        ✅ Part 5
│   └── debug-helpers.ts                         ✅ Part 6
├── snapshots/                                   ✅ Part 5
├── .vscode/                                     ✅ Part 6
│   ├── extensions.json
│   └── launch.json
├── .auth/                                       ← git-ignored
│   ├── admin.json
│   └── user.json
├── global-setup.ts                              ✅ Part 1
├── playwright.config.ts                         ✅ Part 1 (updated Parts 3, 4, 5 & 6)
├── .env                                         ← git-ignored
└── package.json


🗺️ What's Coming in This Series

Part 1 — Stop Writing Tests Like a Beginner              ✅ Done
Part 2 — Network Interception: The Complete Guide        ✅ Done
Part 3 — Multi-User, Multi-Tab & Context Testing         ✅ Done
Part 4 — API Testing (The Underrated Superpower)         ✅ Done
Part 5 — Visual Regression Testing                       ✅ Done
Part 6 — Debugging Like a Pro: Trace Viewer & Inspector  ← You are here
Part 7 — The CI/CD Setup Nobody Shows You
Part 8 — Playwright Meets AI: Agents, MCP & Self-Healing Tests

In Part 7, we take everything we've built and put it in a real CI/CD pipeline — GitHub Actions, test sharding across multiple machines, browser matrix, Docker for consistent environments, Slack failure notifications, and the HTML report published as a pipeline artifact.


🔖 Before You Go

Six parts in. The framework is deep.

And now when something breaks — which it will — you're not flying blind.

You have Trace Viewer to reconstruct exactly what happened. Inspector to step through a test live. page.pause() for interactive breakpoints. VS Code integration to debug without leaving your editor. And debug-helpers.ts to collect console errors and network calls automatically.

The best QA engineers aren't the ones who write tests that never fail. They're the ones who understand failures the fastest. 🎯


Follow me so you don't miss Part 7 — where we take this entire framework to CI/CD. Sharding, GitHub Actions, Docker, browser matrix, failure notifications — the setup that makes your suite production-ready.

Drop a comment below 👇

  • What's your current debugging workflow when a Playwright test fails?
  • Have you used Trace Viewer before — or is that new?
  • What's the most mysterious flaky test you've ever had to debug?

Let's talk in the comments. 🙌


Faizal Shaikh | Senior Automation Engineer | Playwright & AI Testing
Connect with me on LinkedIn