{
  "$type": "site.standard.document",
  "bskyPostRef": {
    "cid": "bafyreiavqogt66ssymd7qyda7xdsakkrnbeuun4jmhgalkvgd3zteudgta",
    "uri": "at://did:plc:25rdn5elo5izoxrmtis34zuk/app.bsky.feed.post/3mooq3777lf22"
  },
  "coverImage": {
    "$type": "blob",
    "ref": {
      "$link": "bafkreiesv254l24f7if45ysycpbkbk75pzdtrcfbljfnq5ipbeqew4u334"
    },
    "mimeType": "image/webp",
    "size": 401608
  },
  "path": "/sshhfaiz/the-playwright-playbook-part-6-debugging-like-a-pro-trace-viewer-inspector-vs-code-32li",
  "publishedAt": "2026-06-20T00:59:29.000Z",
  "site": "https://dev.to",
  "tags": [
    "playwright",
    "testing",
    "typescript",
    "automation",
    "LinkedIn",
    "@playwright",
    "@smoke"
  ],
  "textContent": "##  The Playwright Playbook β€” Part 6: Debugging Like a Pro β€” Trace Viewer, Inspector & VS Code\n\n> _\"A failing test at 2am in CI tells you what broke. Trace Viewer tells you why.\"_\n\nIn 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.\n\nNow something fails.\n\nIt always does.\n\nThe question isn't whether your tests will fail β€” it's how fast you can understand why.\n\nMost engineers debug Playwright like this:\n\n\n\n    // πŸ”΄ The classic debugging approach\n    console.log('got here 1');\n    await page.click(someLocator);\n    console.log('got here 2');\n\n\nOr worse β€” they add `waitForTimeout(5000)` and hope the problem goes away.\n\nThere'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.\n\n**Trace Viewer. Playwright Inspector. VS Code integration.`page.pause()`.**\n\nThese 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.\n\nLet's build the debugging layer into our framework. 🎯\n\n##  πŸ—οΈ Where We Left Off\n\nAfter Part 5, our full project structure is:\n\n\n\n    playwright-playbook/\n    β”œβ”€β”€ tests/\n    β”‚   β”œβ”€β”€ auth/\n    β”‚   β”‚   └── login.spec.ts                        βœ… Part 1\n    β”‚   β”œβ”€β”€ tasks/\n    β”‚   β”‚   └── task-management.spec.ts              βœ… Part 1\n    β”‚   β”œβ”€β”€ network/                                 βœ… Part 2\n    β”‚   β”‚   β”œβ”€β”€ api-mocking.spec.ts\n    β”‚   β”‚   β”œβ”€β”€ error-simulation.spec.ts\n    β”‚   β”‚   └── network-assertions.spec.ts\n    β”‚   β”œβ”€β”€ multi-user/                              βœ… Part 3\n    β”‚   β”‚   β”œβ”€β”€ role-permissions.spec.ts\n    β”‚   β”‚   └── realtime-collaboration.spec.ts\n    β”‚   β”œβ”€β”€ multi-tab/                               βœ… Part 3\n    β”‚   β”‚   └── multi-tab-flows.spec.ts\n    β”‚   β”œβ”€β”€ api/                                     βœ… Part 4\n    β”‚   β”‚   β”œβ”€β”€ tasks-api.spec.ts\n    β”‚   β”‚   β”œβ”€β”€ auth-api.spec.ts\n    β”‚   β”‚   β”œβ”€β”€ graphql-api.spec.ts\n    β”‚   β”‚   └── api-ui-chain.spec.ts\n    β”‚   └── visual/                                  βœ… Part 5\n    β”‚       β”œβ”€β”€ dashboard-visual.spec.ts\n    β”‚       β”œβ”€β”€ task-visual.spec.ts\n    β”‚       └── responsive-visual.spec.ts\n    β”œβ”€β”€ pages/\n    β”‚   β”œβ”€β”€ LoginPage.ts                             βœ… Part 1\n    β”‚   β”œβ”€β”€ TaskPage.ts                              βœ… Part 1\n    β”‚   └── DashboardPage.ts                         βœ… Part 3\n    β”œβ”€β”€ api/\n    β”‚   β”œβ”€β”€ TaskApiClient.ts                         βœ… Part 4\n    β”‚   └── AuthApiClient.ts                         βœ… Part 4\n    β”œβ”€β”€ fixtures/\n    β”‚   β”œβ”€β”€ auth.fixture.ts                          βœ… Part 1\n    β”‚   β”œβ”€β”€ tasks.json                               βœ… Part 2\n    β”‚   β”œβ”€β”€ empty-tasks.json                         βœ… Part 2\n    β”‚   β”œβ”€β”€ tasks-har.har                            βœ… Part 2\n    β”‚   β”œβ”€β”€ multi-user.fixture.ts                    βœ… Part 3\n    β”‚   └── api.fixture.ts                           βœ… Part 4\n    β”œβ”€β”€ scripts/\n    β”‚   └── record-har.ts                            βœ… Part 2\n    β”œβ”€β”€ utils/\n    β”‚   β”œβ”€β”€ schema-validator.ts                      βœ… Part 4\n    β”‚   └── visual-helpers.ts                        βœ… Part 5\n    β”œβ”€β”€ snapshots/                                   βœ… Part 5\n    β”œβ”€β”€ .auth/\n    β”œβ”€β”€ global-setup.ts                              βœ… Part 1\n    β”œβ”€β”€ playwright.config.ts                         βœ… Part 1 (updated Parts 3, 4 & 5)\n    └── .env\n\n\nBy the end of Part 6, we add:\n\n\n\n    playwright-playbook/\n    β”œβ”€β”€ tests/\n    β”‚   └── debug/                                   ← NEW\n    β”‚       └── trace-examples.spec.ts\n    β”œβ”€β”€ utils/\n    β”‚   └── debug-helpers.ts                         ← NEW\n    β”œβ”€β”€ .vscode/                                     ← NEW\n    β”‚   β”œβ”€β”€ extensions.json\n    β”‚   └── launch.json\n\n\nEvery file gets fully built below. πŸ‘‡\n\n##  🧠 The Debugging Toolkit β€” Overview\n\nPlaywright gives you five distinct debugging tools. Each is for a different situation:\n\n\n\n    Tool                  When to use\n    ─────────────────     ──────────────────────────────────────────────\n    Trace Viewer          Post-mortem β€” understanding a CI failure\n    Playwright Inspector  Live β€” step through a test interactively\n    page.pause()          Breakpoint β€” pause mid-test, inspect live DOM\n    VS Code Extension     IDE β€” run/debug individual tests without CLI\n    Console + Network     During development β€” lightweight live logging\n\n\nWe'll build proper support for all five into the framework. Let's start with the most important one. πŸ‘‡\n\n##  🎬 Trace Viewer β€” The Most Powerful Debugging Tool in Playwright\n\nTrace 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.\n\nWhen 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.\n\n###  Step 1 β€” Configure traces in `playwright.config.ts`\n\n\n    // playwright.config.ts β€” updated use block\n    use: {\n      baseURL: process.env.BASE_URL || 'http://localhost:3000',\n\n      // Screenshot on every failure β€” attached to HTML report\n      screenshot: 'only-on-failure',\n\n      // Video on first retry β€” see exactly what happened\n      video: 'retain-on-failure',\n\n      // Trace on first retry in CI, always on locally for debugging\n      trace: process.env.CI ? 'on-first-retry' : 'on',\n\n      extraHTTPHeaders: {\n        'Accept': 'application/json',\n        'Content-Type': 'application/json',\n      },\n    },\n\n\n**Trace modes explained:**\n\n\n\n    'off'             β€” No traces recorded (fastest)\n    'on'              β€” Trace recorded for every test (slowest, most info)\n    'retain-on-failure' β€” Trace only kept when test fails (good balance)\n    'on-first-retry'  β€” Trace recorded on first retry (recommended for CI)\n\n\n`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. 🎯\n\n###  Step 2 β€” Open a trace locally\n\nAfter a test failure, Playwright saves a `.zip` trace file in `test-results/`. Open it:\n\n\n\n    # Open the trace viewer in your browser\n    npx playwright show-trace test-results/my-test/trace.zip\n\n    # Or open the full HTML report which embeds trace links\n    npx playwright show-report\n\n\n###  Step 3 β€” What you see in Trace Viewer\n\nThe Trace Viewer UI has five panels:\n\n\n\n    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”\n    β”‚  Timeline ────────────────────────────────────────────  β”‚\n    β”‚  [action][action][action][network][action][FAIL]        β”‚\n    β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€\n    β”‚                 β”‚  Before snapshot    After snapshot     β”‚\n    β”‚  Action list    β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚\n    β”‚                 β”‚  β”‚             β”‚  β”‚                 β”‚ β”‚\n    β”‚  β–Ά goto /tasks  β”‚  β”‚   DOM beforeβ”‚  β”‚   DOM after     β”‚ β”‚\n    β”‚  β–Ά click button β”‚  β”‚   the actionβ”‚  β”‚   the action    β”‚ β”‚\n    β”‚  β–Ά fill input   β”‚  β”‚             β”‚  β”‚                 β”‚ β”‚\n    β”‚  ❌ expect failsβ”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚\n    β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€\n    β”‚  Network  |  Console  |  Source                         β”‚\n    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜\n\n\nEvery action shows:\n\n  * The DOM **before** the action\n  * The DOM **after** the action\n  * All network calls that fired during the action\n  * Console logs and errors at that moment\n  * The exact line of test code that triggered it\n\n\n\nThis is how you debug a test that fails once in 50 runs in CI. You don't reproduce it β€” you read the trace. πŸ”₯\n\n##  πŸ” Playwright Inspector β€” Interactive Step-Through Debugging\n\nInspector 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.\n\nIt opens a live browser alongside an inspector panel β€” you step through your test action by action, watching what happens in real time.\n\n###  Launch modes\n\n\n    # Run a specific test in debug mode β€” opens Inspector\n    npx playwright test tests/tasks/task-management.spec.ts --debug\n\n    # Run in headed mode only β€” see the browser, no Inspector panel\n    npx playwright test --headed\n\n    # Run with slow motion β€” slows each action by 1 second (great for watching)\n    npx playwright test --headed --slow-mo=1000\n\n    # Debug a specific test by name\n    npx playwright test --debug -g \"user can create a new task\"\n\n\n###  Using Inspector to find the right locator\n\nThis is the killer use case. When you're not sure what locator to use:\n\n  1. Run your test with `--debug`\n  2. Inspector opens β€” click **Record** or **Explore**\n  3. Hover over elements in the browser β€” Inspector shows you the recommended locator\n  4. Click the element β€” Inspector generates the `getByRole`, `getByLabel`, or `getByTestId` code\n  5. Copy it into your test\n\n\n\nNo more guessing at selectors. No more `page.$('div > div:nth-child(2) > span')`. 🎯\n\n###  The Inspector toolbar\n\n\n    β–Ά  Resume     β€” Run to the next breakpoint or end of test\n    ⏭  Step over  β€” Execute the current action and pause on the next\n    πŸ” Pick       β€” Hover over elements to see their locators\n    ⏺  Record    β€” Record new actions by clicking in the browser\n\n\n##  ⏸️ `page.pause()` β€” Your Interactive Breakpoint\n\n`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.\n\n\n\n    test('debugging a flaky task creation flow', async ({ page }) => {\n      const taskPage = new TaskPage(page);\n      await taskPage.goto();\n\n      // Test runs normally up to here\n      await taskPage.newTaskButton.click();\n\n      // ⏸️ Test freezes here β€” Inspector opens\n      // You can now interact with the page, inspect the DOM,\n      // check network calls, try locators β€” all live\n      await page.pause();\n\n      // When you click Resume in Inspector, the test continues\n      await taskPage.taskTitleInput.fill('Debugging this task');\n      await taskPage.saveTaskButton.click();\n    });\n\n\n**Important:** Remove `page.pause()` before committing. If it runs in CI with no terminal, the test will hang indefinitely.\n\nA safer pattern β€” only pause in local development:\n\n\n\n    // Only pause when running locally β€” never in CI\n    if (!process.env.CI) {\n      await page.pause();\n    }\n\n\n##  πŸ› οΈ Building the Debug Helpers Utility\n\nThese helpers centralise common debugging patterns β€” collecting console errors, logging performance timing, and capturing network activity during a test.\n\n\n\n    // utils/debug-helpers.ts\n    import { Page, ConsoleMessage } from '@playwright/test';\n\n    export interface ConsoleError {\n      type: string;\n      text: string;\n      location: string;\n    }\n\n    export interface NetworkCall {\n      method: string;\n      url: string;\n      status: number;\n      duration: number;\n    }\n\n    /**\n     * Attach a console error collector to the page.\n     * Returns a function to get all collected errors at any point.\n     *\n     * Usage:\n     *   const getErrors = collectConsoleErrors(page);\n     *   // ... run your test ...\n     *   const errors = getErrors();\n     *   expect(errors).toHaveLength(0);\n     */\n    export function collectConsoleErrors(page: Page): () => ConsoleError[] {\n      const errors: ConsoleError[] = [];\n\n      page.on('console', (msg: ConsoleMessage) => {\n        if (msg.type() === 'error') {\n          errors.push({\n            type: msg.type(),\n            text: msg.text(),\n            location: msg.location().url,\n          });\n        }\n      });\n\n      // Also capture uncaught page errors (runtime JS errors)\n      page.on('pageerror', (error: Error) => {\n        errors.push({\n          type: 'pageerror',\n          text: error.message,\n          location: error.stack ?? '',\n        });\n      });\n\n      return () => [...errors];\n    }\n\n    /**\n     * Attach a network call logger to the page.\n     * Returns a function to get all captured network calls.\n     *\n     * Usage:\n     *   const getCalls = collectNetworkCalls(page, '/api/');\n     *   // ... run your test ...\n     *   const calls = getCalls();\n     *   console.log(calls); // see every API call made\n     */\n    export function collectNetworkCalls(\n      page: Page,\n      urlFilter: string = ''\n    ): () => NetworkCall[] {\n      const calls: NetworkCall[] = [];\n\n      page.on('requestfinished', async request => {\n        if (urlFilter && !request.url().includes(urlFilter)) return;\n\n        const response = await request.response();\n        const timing = request.timing();\n\n        calls.push({\n          method: request.method(),\n          url: request.url(),\n          status: response?.status() ?? 0,\n          duration: timing.responseEnd - timing.requestStart,\n        });\n      });\n\n      return () => [...calls];\n    }\n\n    /**\n     * Log all page console output during a test.\n     * Useful during development β€” remove before committing.\n     */\n    export function attachConsoleLogger(page: Page): void {\n      page.on('console', msg => {\n        const type = msg.type().padEnd(7);\n        console.log(`[PAGE ${type}] ${msg.text()}`);\n      });\n\n      page.on('pageerror', error => {\n        console.error(`[PAGE ERROR] ${error.message}`);\n      });\n    }\n\n    /**\n     * Capture a named checkpoint screenshot during a test.\n     * Useful for documenting test steps without full VRT.\n     *\n     * Usage:\n     *   await checkpoint(page, 'after-task-created');\n     */\n    export async function checkpoint(page: Page, name: string): Promise<void> {\n      const timestamp = new Date().toISOString().replace(/[:.]/g, '-');\n      await page.screenshot({\n        path: `test-results/checkpoints/${name}-${timestamp}.png`,\n        fullPage: false,\n      });\n      console.log(`πŸ“Έ Checkpoint: ${name}`);\n    }\n\n    /**\n     * Assert no console errors occurred during the test.\n     * Add this at the end of any test to catch silent JS errors.\n     */\n    export function assertNoConsoleErrors(getErrors: () => ConsoleError[]): void {\n      const errors = getErrors();\n      if (errors.length > 0) {\n        const errorMessages = errors.map(e => `  [${e.type}] ${e.text}`).join('\\n');\n        throw new Error(`Console errors detected during test:\\n${errorMessages}`);\n      }\n    }\n\n    /**\n     * Log a summary of all network calls made during the test.\n     * Helpful for understanding what API traffic a user action triggers.\n     */\n    export function logNetworkSummary(getCalls: () => NetworkCall[]): void {\n      const calls = getCalls();\n      if (calls.length === 0) {\n        console.log('No API network calls recorded.');\n        return;\n      }\n\n      console.log('\\nπŸ“‘ Network calls during test:');\n      calls.forEach(call => {\n        const status = call.status >= 400 ? `❌ ${call.status}` : `βœ… ${call.status}`;\n        console.log(`  ${status} ${call.method.padEnd(6)} ${call.url} (${Math.round(call.duration)}ms)`);\n      });\n    }\n\n\n##  πŸ§ͺ Trace Examples β€” Tests That Demonstrate the Debug Toolkit\n\nThese tests serve a dual purpose: they're real tests AND they demonstrate how to use each debugging tool in a real scenario.\n\n\n\n    // tests/debug/trace-examples.spec.ts\n    import { test, expect } from '@playwright/test';\n    import { TaskPage } from '../../pages/TaskPage';\n    import { DashboardPage } from '../../pages/DashboardPage';\n    import {\n      collectConsoleErrors,\n      collectNetworkCalls,\n      assertNoConsoleErrors,\n      logNetworkSummary,\n      checkpoint,\n    } from '../../utils/debug-helpers';\n\n    test.describe('Debug Helpers β€” Console Error Detection', () => {\n      test('task page loads with no console errors', async ({ page }) => {\n        // Attach error collector BEFORE navigating\n        const getErrors = collectConsoleErrors(page);\n\n        const taskPage = new TaskPage(page);\n        await taskPage.goto();\n\n        // Wait for page to fully settle\n        await page.waitForLoadState('networkidle');\n\n        // Assert no JS errors occurred during page load\n        assertNoConsoleErrors(getErrors);\n      });\n\n      test('dashboard loads with no console errors', async ({ page }) => {\n        const getErrors = collectConsoleErrors(page);\n\n        const dashboard = new DashboardPage(page);\n        await dashboard.goto();\n        await page.waitForLoadState('networkidle');\n\n        assertNoConsoleErrors(getErrors);\n      });\n\n      test('task creation flow produces no console errors', async ({ page }) => {\n        const getErrors = collectConsoleErrors(page);\n\n        const taskPage = new TaskPage(page);\n        await taskPage.goto();\n        await taskPage.createTask('Console error test task');\n\n        await expect(taskPage.getTaskLocator('Console error test task')).toBeVisible();\n\n        // Clean up\n        await taskPage.deleteTask('Console error test task');\n\n        // Assert no errors occurred during the entire flow\n        assertNoConsoleErrors(getErrors);\n      });\n    });\n\n    test.describe('Debug Helpers β€” Network Call Auditing', () => {\n      test('task page load triggers expected API calls', async ({ page }) => {\n        const getCalls = collectNetworkCalls(page, '/api/');\n\n        const taskPage = new TaskPage(page);\n        await taskPage.goto();\n        await page.waitForLoadState('networkidle');\n\n        const calls = getCalls();\n        logNetworkSummary(getCalls);\n\n        // Assert the expected API calls were made\n        const tasksFetch = calls.find(\n          c => c.url.includes('/api/tasks') && c.method === 'GET'\n        );\n        expect(tasksFetch).toBeDefined();\n        expect(tasksFetch!.status).toBe(200);\n\n        // Assert no unexpected 4xx or 5xx calls\n        const failedCalls = calls.filter(c => c.status >= 400);\n        expect(failedCalls).toHaveLength(0);\n      });\n\n      test('task creation triggers exactly one POST call', async ({ page }) => {\n        const taskPage = new TaskPage(page);\n        await taskPage.goto();\n\n        // Start collecting AFTER initial page load β€” only capture creation calls\n        const getCalls = collectNetworkCalls(page, '/api/tasks');\n\n        await taskPage.createTask('Network audit test task');\n        await expect(taskPage.getTaskLocator('Network audit test task')).toBeVisible();\n\n        const calls = getCalls();\n        const postCalls = calls.filter(c => c.method === 'POST');\n\n        // Should be exactly one POST β€” not two, not zero\n        expect(postCalls).toHaveLength(1);\n        expect(postCalls[0].status).toBe(201);\n\n        // Cleanup\n        await taskPage.deleteTask('Network audit test task');\n      });\n    });\n\n    test.describe('Debug Helpers β€” Checkpoint Screenshots', () => {\n      test('document task creation flow with checkpoints', async ({ page }) => {\n        const taskPage = new TaskPage(page);\n        await taskPage.goto();\n\n        // Checkpoint 1 β€” initial state\n        await checkpoint(page, '01-task-page-initial');\n\n        // Open new task modal\n        await taskPage.newTaskButton.click();\n        await checkpoint(page, '02-new-task-modal-open');\n\n        // Fill in the task\n        await taskPage.taskTitleInput.fill('Checkpoint documented task');\n        await checkpoint(page, '03-task-title-filled');\n\n        // Save\n        await taskPage.saveTaskButton.click();\n        await expect(taskPage.getTaskLocator('Checkpoint documented task')).toBeVisible();\n        await checkpoint(page, '04-task-created-success');\n\n        // Cleanup\n        await taskPage.deleteTask('Checkpoint documented task');\n        await checkpoint(page, '05-task-deleted');\n\n        // Checkpoint PNGs are saved to test-results/checkpoints/\n        // Useful for documenting a flow or building a test report with screenshots\n      });\n    });\n\n    test.describe('Flaky Test Patterns β€” How to Debug Them', () => {\n      test('wait for real condition β€” not arbitrary timeout', async ({ page }) => {\n        const taskPage = new TaskPage(page);\n        await taskPage.goto();\n\n        // βœ… Correct pattern β€” wait for the network response AND the action together\n        const [response] = await Promise.all([\n          page.waitForResponse(\n            resp => resp.url().includes('/api/tasks') && resp.request().method() === 'POST'\n          ),\n          taskPage.createTask('Condition-based wait task'),\n        ]);\n\n        // Assert on the actual condition that confirms completion\n        expect(response.status()).toBe(201);\n        await expect(taskPage.getTaskLocator('Condition-based wait task')).toBeVisible();\n\n        await taskPage.deleteTask('Condition-based wait task');\n      });\n\n      test('identify what a \"random\" failure actually looks like', async ({ page }) => {\n        const getErrors = collectConsoleErrors(page);\n        const getCalls = collectNetworkCalls(page, '/api/');\n\n        const taskPage = new TaskPage(page);\n        await taskPage.goto();\n\n        // Simulate a slow/unreliable API response\n        await page.route('**/api/tasks', async route => {\n          // Add a random delay between 0 and 500ms β€” simulates real-world jitter\n          const delay = Math.random() * 500;\n          await new Promise(resolve => setTimeout(resolve, delay));\n          await route.continue();\n        });\n\n        await taskPage.createTask('Jitter test task');\n\n        // This should STILL pass β€” because we're waiting on conditions, not timeouts\n        await expect(taskPage.getTaskLocator('Jitter test task')).toBeVisible();\n\n        // Log what actually happened\n        logNetworkSummary(getCalls);\n        assertNoConsoleErrors(getErrors);\n\n        await taskPage.deleteTask('Jitter test task');\n      });\n    });\n\n\n##  πŸ’» VS Code Integration β€” Run & Debug Tests From Your IDE\n\nIf you use VS Code, the official Playwright extension is the fastest way to run and debug individual tests without touching the terminal.\n\n###  Step 1 β€” Install the extension\n\n\n    // .vscode/extensions.json\n    {\n      \"recommendations\": [\n        \"ms-playwright.playwright\",\n        \"dbaeumer.vscode-eslint\",\n        \"esbenp.prettier-vscode\"\n      ]\n    }\n\n\nCommit this file β€” when teammates open the repo, VS Code prompts them to install the recommended extensions automatically.\n\n###  Step 2 β€” VS Code debug launch config\n\n\n    // .vscode/launch.json\n    {\n      \"version\": \"0.2.0\",\n      \"configurations\": [\n        {\n          \"name\": \"Debug Playwright Test\",\n          \"type\": \"node\",\n          \"request\": \"launch\",\n          \"program\": \"${workspaceFolder}/node_modules/.bin/playwright\",\n          \"args\": [\"test\", \"--debug\", \"${file}\"],\n          \"cwd\": \"${workspaceFolder}\",\n          \"env\": {\n            \"PWDEBUG\": \"1\"\n          },\n          \"console\": \"integratedTerminal\"\n        },\n        {\n          \"name\": \"Debug Current Test File\",\n          \"type\": \"node\",\n          \"request\": \"launch\",\n          \"program\": \"${workspaceFolder}/node_modules/.bin/playwright\",\n          \"args\": [\"test\", \"${file}\", \"--headed\", \"--project=admin\"],\n          \"cwd\": \"${workspaceFolder}\",\n          \"env\": {},\n          \"console\": \"integratedTerminal\"\n        },\n        {\n          \"name\": \"Debug Specific Test by Name\",\n          \"type\": \"node\",\n          \"request\": \"launch\",\n          \"program\": \"${workspaceFolder}/node_modules/.bin/playwright\",\n          \"args\": [\n            \"test\",\n            \"--debug\",\n            \"--grep\",\n            \"${input:testName}\"\n          ],\n          \"cwd\": \"${workspaceFolder}\",\n          \"console\": \"integratedTerminal\"\n        }\n      ],\n      \"inputs\": [\n        {\n          \"id\": \"testName\",\n          \"type\": \"promptString\",\n          \"description\": \"Enter the test name or partial match\"\n        }\n      ]\n    }\n\n\n###  What the VS Code extension gives you\n\nOnce installed and configured, you get:\n\n\n\n    Testing sidebar (beaker icon):\n    β”œβ”€β”€ See all tests in a tree view\n    β”œβ”€β”€ β–Ά Run individual test with one click\n    β”œβ”€β”€ πŸ› Debug individual test with one click\n    β”œβ”€β”€ πŸ“ Set breakpoints in test code\n    └── πŸ”΄ Failing tests highlighted inline in the editor\n\n    Editor gutter:\n    β”œβ”€β”€ β–Ά Run this test (appears next to each test() block)\n    └── πŸ› Debug this test\n\n\nYou can click the β–Ά icon next to any `test()` in your editor and it runs immediately β€” no terminal, no command to remember. 🎯\n\n##  πŸ“‹ Reading CI Failures Like a Pro\n\nWhen a test fails in CI, you get three artifacts attached to the pipeline run:\n\n\n\n    test-results/\n    β”œβ”€β”€ my-failing-test/\n    β”‚   β”œβ”€β”€ trace.zip           ← Full trace β€” every action, network call, DOM snapshot\n    β”‚   β”œβ”€β”€ test-failed-1.png   ← Screenshot at the moment of failure\n    β”‚   └── video.webm          ← Video of the entire test run\n\n\n###  The debugging workflow for CI failures\n\n\n    # Step 1 β€” Download trace.zip from CI artifacts\n\n    # Step 2 β€” Open it locally\n    npx playwright show-trace path/to/trace.zip\n\n    # Step 3 β€” In Trace Viewer:\n    #   Click through the action timeline to find where it went wrong\n    #   Check the \"Before\" vs \"After\" DOM snapshots\n    #   Look at the network tab β€” was there a 500? A missing response?\n    #   Check the console tab β€” was there a JS error?\n\n    # Step 4 β€” Reproduce locally if needed\n    npx playwright test --debug -g \"the failing test name\"\n\n\n###  Reading the trace timeline β€” what to look for\n\n\n    Common failure patterns and where to find them in the trace:\n\n    Element not found:\n      β†’ Action shows \"waiting for locator\" β†’ times out\n      β†’ Check \"Before\" snapshot β€” is the element there? Is the selector wrong?\n      β†’ Check network tab β€” did the API call that renders it fail?\n\n    Wrong element clicked:\n      β†’ \"Before\" snapshot shows multiple matching elements\n      β†’ Your locator was too broad β€” make it more specific\n\n    Flaky timing failure:\n      β†’ Network tab shows slow response (800ms+)\n      β†’ Action fired before the data loaded\n      β†’ Fix: wait for the response, not a timeout\n\n    Authentication failure:\n      β†’ First network call returns 401\n      β†’ storageState expired or wasn't loaded\n      β†’ Fix: check global-setup.ts ran correctly\n\n    Console error on failure:\n      β†’ Console tab shows red JS error\n      β†’ Often unrelated to your test β€” but worth checking\n      β†’ Add collectConsoleErrors() to catch these proactively\n\n\n##  πŸ”§ Useful Debug Commands β€” Quick Reference\n\n\n    # Run a single test file in debug mode\n    npx playwright test tests/tasks/task-management.spec.ts --debug\n\n    # Run a test by name pattern\n    npx playwright test --debug -g \"user can create a new task\"\n\n    # Run headed (see the browser) without Inspector\n    npx playwright test --headed\n\n    # Run with slow motion β€” 1 second between each action\n    npx playwright test --headed --slow-mo=1000\n\n    # Generate and open the HTML report after a run\n    npx playwright test && npx playwright show-report\n\n    # Open a specific trace file\n    npx playwright show-trace test-results/my-test/trace.zip\n\n    # Update visual snapshots after intentional UI change\n    npx playwright test --project=visual --update-snapshots\n\n    # Run only tests matching a tag\n    npx playwright test --grep @smoke\n\n    # List all tests without running them\n    npx playwright test --list\n\n    # Run with maximum verbosity\n    npx playwright test --reporter=list --verbose\n\n\n##  πŸ“ Final Project Structure After Part 6\n\nEvery file listed below has been fully built across Parts 1 through 6:\n\n\n\n    playwright-playbook/\n    β”œβ”€β”€ tests/\n    β”‚   β”œβ”€β”€ auth/\n    β”‚   β”‚   └── login.spec.ts                        βœ… Part 1\n    β”‚   β”œβ”€β”€ tasks/\n    β”‚   β”‚   └── task-management.spec.ts              βœ… Part 1\n    β”‚   β”œβ”€β”€ network/                                 βœ… Part 2\n    β”‚   β”‚   β”œβ”€β”€ api-mocking.spec.ts\n    β”‚   β”‚   β”œβ”€β”€ error-simulation.spec.ts\n    β”‚   β”‚   └── network-assertions.spec.ts\n    β”‚   β”œβ”€β”€ multi-user/                              βœ… Part 3\n    β”‚   β”‚   β”œβ”€β”€ role-permissions.spec.ts\n    β”‚   β”‚   └── realtime-collaboration.spec.ts\n    β”‚   β”œβ”€β”€ multi-tab/                               βœ… Part 3\n    β”‚   β”‚   └── multi-tab-flows.spec.ts\n    β”‚   β”œβ”€β”€ api/                                     βœ… Part 4\n    β”‚   β”‚   β”œβ”€β”€ tasks-api.spec.ts\n    β”‚   β”‚   β”œβ”€β”€ auth-api.spec.ts\n    β”‚   β”‚   β”œβ”€β”€ graphql-api.spec.ts\n    β”‚   β”‚   └── api-ui-chain.spec.ts\n    β”‚   β”œβ”€β”€ visual/                                  βœ… Part 5\n    β”‚   β”‚   β”œβ”€β”€ dashboard-visual.spec.ts\n    β”‚   β”‚   β”œβ”€β”€ task-visual.spec.ts\n    β”‚   β”‚   └── responsive-visual.spec.ts\n    β”‚   └── debug/                                   βœ… Part 6\n    β”‚       └── trace-examples.spec.ts\n    β”œβ”€β”€ pages/\n    β”‚   β”œβ”€β”€ LoginPage.ts                             βœ… Part 1\n    β”‚   β”œβ”€β”€ TaskPage.ts                              βœ… Part 1\n    β”‚   └── DashboardPage.ts                         βœ… Part 3\n    β”œβ”€β”€ api/\n    β”‚   β”œβ”€β”€ TaskApiClient.ts                         βœ… Part 4\n    β”‚   └── AuthApiClient.ts                         βœ… Part 4\n    β”œβ”€β”€ fixtures/\n    β”‚   β”œβ”€β”€ auth.fixture.ts                          βœ… Part 1\n    β”‚   β”œβ”€β”€ tasks.json                               βœ… Part 2\n    β”‚   β”œβ”€β”€ empty-tasks.json                         βœ… Part 2\n    β”‚   β”œβ”€β”€ tasks-har.har                            βœ… Part 2\n    β”‚   β”œβ”€β”€ multi-user.fixture.ts                    βœ… Part 3\n    β”‚   └── api.fixture.ts                           βœ… Part 4\n    β”œβ”€β”€ scripts/\n    β”‚   └── record-har.ts                            βœ… Part 2\n    β”œβ”€β”€ utils/\n    β”‚   β”œβ”€β”€ schema-validator.ts                      βœ… Part 4\n    β”‚   β”œβ”€β”€ visual-helpers.ts                        βœ… Part 5\n    β”‚   └── debug-helpers.ts                         βœ… Part 6\n    β”œβ”€β”€ snapshots/                                   βœ… Part 5\n    β”œβ”€β”€ .vscode/                                     βœ… Part 6\n    β”‚   β”œβ”€β”€ extensions.json\n    β”‚   └── launch.json\n    β”œβ”€β”€ .auth/                                       ← git-ignored\n    β”‚   β”œβ”€β”€ admin.json\n    β”‚   └── user.json\n    β”œβ”€β”€ global-setup.ts                              βœ… Part 1\n    β”œβ”€β”€ playwright.config.ts                         βœ… Part 1 (updated Parts 3, 4, 5 & 6)\n    β”œβ”€β”€ .env                                         ← git-ignored\n    └── package.json\n\n\n##  πŸ—ΊοΈ What's Coming in This Series\n\n\n    Part 1 β€” Stop Writing Tests Like a Beginner              βœ… Done\n    Part 2 β€” Network Interception: The Complete Guide        βœ… Done\n    Part 3 β€” Multi-User, Multi-Tab & Context Testing         βœ… Done\n    Part 4 β€” API Testing (The Underrated Superpower)         βœ… Done\n    Part 5 β€” Visual Regression Testing                       βœ… Done\n    Part 6 β€” Debugging Like a Pro: Trace Viewer & Inspector  ← You are here\n    Part 7 β€” The CI/CD Setup Nobody Shows You\n    Part 8 β€” Playwright Meets AI: Agents, MCP & Self-Healing Tests\n\n\nIn **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.\n\n##  πŸ”– Before You Go\n\nSix parts in. The framework is deep.\n\nAnd now when something breaks β€” which it will β€” you're not flying blind.\n\nYou 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.\n\nThe best QA engineers aren't the ones who write tests that never fail. They're the ones who understand failures the fastest. 🎯\n\n**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.\n\nDrop a comment below πŸ‘‡\n\n  * What's your current debugging workflow when a Playwright test fails?\n  * Have you used Trace Viewer before β€” or is that new?\n  * What's the most mysterious flaky test you've ever had to debug?\n\n\n\nLet's talk in the comments. πŸ™Œ\n\n_Faizal Shaikh | Senior Automation Engineer | Playwright & AI Testing_\n_Connect with me on LinkedIn_",
  "title": "The Playwright Playbook β€” Part 6: Debugging Like a Pro β€” Trace Viewer, Inspector & VS Code"
}