debug-browser

star 28

Debug or reproduce a component issue in a live browser using the Chrome DevTools MCP server. Use to get a live repro, inspect the DOM, watch console errors, analyze network requests, take screenshots, or evaluate scripts against a running grid. Covers launching dev servers and navigating to pages/stories. (For performance traces and hot-path profiling, use the debug-perf skill.)

OysteinAmundsen By OysteinAmundsen schedule Updated 6/3/2026

name: debug-browser description: Debug or reproduce a component issue in a live browser using the Chrome DevTools MCP server. Use to get a live repro, inspect the DOM, watch console errors, analyze network requests, take screenshots, or evaluate scripts against a running grid. Covers launching dev servers and navigating to pages/stories. (For performance traces and hot-path profiling, use the debug-perf skill.) argument-hint:

Browser Debugging via Chrome DevTools MCP

Guide for debugging grid components in a live browser using the Chrome DevTools MCP server. This enables inspecting DOM, evaluating scripts, capturing screenshots, monitoring console/network, and more — all from within the AI coding assistant.

Performance-specific debugging? See the debug-perf skill for hot path analysis, render scheduler profiling, and performance budget verification. That skill includes a section on live browser profiling via MCP.

Prerequisites

The workspace has Chrome DevTools MCP pre-configured in .vscode/mcp.json:

{
  "servers": {
    "chrome-devtools": {
      "command": "npx",
      "args": ["-y", "chrome-devtools-mcp@latest"],
    },
  },
}

The MCP server launches its own Chromium instance. This is separate from any VS Code debug launch configuration — they are independent workflows:

  • MCP browser: For AI-driven inspection, script evaluation, screenshots
  • VS Code debugger: For breakpoints, step-through, variable inspection

Troubleshooting MCP server launch / connection failures

If an MCP tool call returns an error such as "server not running", "failed to launch", or "connection refused", work through this checklist before continuing:

  • Confirm .vscode/mcp.json matches the configuration block above (correct command, args, no typos in the server name chrome-devtools).
  • Open the MCP: List Servers command from the VS Code command palette and verify chrome-devtools is listed and shows status "Started". Use MCP: Restart Server if it is in an error state.
  • Open the server's output log via MCP: Show Output for chrome-devtools and read the most recent error — common causes are missing npx/Node, a corporate proxy blocking the chrome-devtools-mcp@latest download, or a previous Chromium process still holding the debug port.
  • If a stale Chromium is suspected, kill any running chrome-devtools-mcp / orphan Chromium processes and retry.
  • As a last resort, manually run npx -y chrome-devtools-mcp@latest in a terminal and report the exact stderr output back to the user; do not silently retry the tool call.

Step 1: Reach the Dev Server (assume it's already running)

Do NOT start a dev server first. In a debugging session the user almost always already has the relevant dev server running. Starting another one wastes time and can fail on a port-in-use clash. Check first, start only as a last resort.

about:blank is NOT "no server". list_pages (or a freshly attached MCP browser) reports about:blank because that's the default empty tab — it tells you nothing about whether the dev server is up. Never infer "the server isn't running, I must start one" from an about:blank page. The only way to test the server is to navigate_page to the real URL and see whether it loads. Always navigate first.

Work through these in order and stop at the first that succeeds:

  1. Just navigate via Chrome MCP. Point navigate_page straight at the target URL (do not be misled by an about:blank tab — navigate to confirm). If the page loads, you're done — no terminal command needed.

    navigate_page → url: http://localhost:4400/grid/plugins/editing/
    

    If it errors with a connection refused / ERR_CONNECTION_REFUSED, the server for that port isn't up — fall through to step 2.

  2. Probe the port from the terminal (cheap, non-blocking) to confirm whether something is listening before launching anything:

    curl -sSf -o /dev/null http://localhost:4400/ && echo "up" || echo "down"
    

    If it prints up, retry the navigate_page from step 1 (the server is there). If down, proceed to step 3.

  3. Only now start the dev server for the port you need, then navigate once it's listening:

    # Docs site (port 4400)
    bun nx serve docs
    
    # Vanilla demo (port 4000)
    bun nx serve demo-vanilla
    
    # Angular demo (port 4200)
    bun nx serve demo-angular
    
    # React demo (port 4300)
    bun nx serve demo-react
    
    # Vue demo (port 4100)
    bun nx serve demo-vue
    
    # All demos at once
    bun run demo
    

Server reference:

App Port Command Probe
Docs Site 4400 bun nx serve docs curl -sSf -o /dev/null localhost:4400/
Demo Vanilla 4000 bun nx serve demo-vanilla curl -sSf -o /dev/null localhost:4000/
Demo Angular 4200 bun nx serve demo-angular curl -sSf -o /dev/null localhost:4200/
Demo React 4300 bun nx serve demo-react curl -sSf -o /dev/null localhost:4300/
Demo Vue 4100 bun nx serve demo-vue curl -sSf -o /dev/null localhost:4100/

Step 2: Navigate to the Target

Navigate to a demo app

navigate_page → url: http://localhost:4200/

Navigate to a specific docs page

The docs site runs at http://localhost:4400. Navigate to specific pages by path:

navigate_page → url: http://localhost:4400/grid/plugins/editing/
navigate_page → url: http://localhost:4400/grid/guides/getting-started/

Reload a page (after code changes with HMR)

navigate_page → type: reload, ignoreCache: true

List open pages

list_pages

Use select_page to switch between tabs if multiple are open.

Step 3: Inspect and Debug

DOM Inspection

Take a DOM snapshot to see the full HTML structure:

take_snapshot → selector: tbw-grid

This returns a structured DOM tree with element attributes, classes, and text content. Very useful for understanding the current grid state.

Evaluate a script to query the DOM programmatically:

// Example: Count visible rows, editing cells, check column count
() => {
  const grid = document.querySelector('tbw-grid');
  return {
    rows: grid?._bodyEl?.querySelectorAll('.data-grid-row').length,
    editingCells: grid?.querySelectorAll('.cell.editing').length,
    columns: grid?._visibleColumns?.length,
  };
};

Console Monitoring

List console messages to catch warnings/errors:

list_console_messages

Get a specific console message by index for full details:

get_console_message → index: 0

Useful for catching grid validation warnings (e.g., "EditingPlugin required", config validation errors).

Network Monitoring

List network requests to debug API calls or asset loading:

list_network_requests

Get details of a specific request (headers, response body):

get_network_request → index: 0

Screenshots

Take a screenshot for visual verification:

take_screenshot

Take a screenshot of a specific element:

take_screenshot → selector: tbw-grid

Useful for visual regression debugging — compare before/after states.

Interaction

Click on an element:

click → selector: .cell[data-col="3"][data-row="0"]

Fill an input:

fill → selector: .cell.editing input → value: new text

Press a key:

press_key → key: Enter
press_key → key: Escape
press_key → key: Tab

Hover over an element (e.g., to trigger tooltips):

hover → selector: .cell[data-col="0"][data-row="0"]

Wait for Conditions

Wait for an element to appear:

wait_for → selector: .cell.editing
wait_for → selector: tbw-grid .data-grid-row

Wait for network idle (after navigation or data loading):

wait_for → type: networkIdle

Step 4: Script Evaluation Patterns

The evaluate_script tool is the most powerful debugging tool. It runs arbitrary JavaScript in the page context.

Grid Internals Access

The grid's internal state is accessible through the DOM element:

() => {
  const grid = document.querySelector('tbw-grid');
  return {
    // Configuration
    effectiveConfig: Object.keys(grid.effectiveConfig || {}),
    columnCount: grid._visibleColumns?.length,
    rowCount: grid._rows?.length,

    // Virtualization window
    virtStart: grid._virtualization?.start,
    virtEnd: grid._virtualization?.end,
    rowHeight: grid._virtualization?.rowHeight,

    // Edit state
    activeEditRow: grid._activeEditRows,
    isGridEditMode: grid._isGridEditMode,
    editingCells: grid.querySelectorAll('.cell.editing').length,

    // Plugin state
    plugins: grid._pluginManager?.getPlugins().map((p) => p.constructor.name),
  };
};

Framework Adapter Inspection

// Angular adapter state
() => {
  const grid = document.querySelector('tbw-grid');
  const adapter = grid?.__frameworkAdapter;
  return {
    adapterType: adapter?.constructor.name,
    viewRefs: adapter?.viewRefs?.length,
    componentRefs: adapter?.componentRefs?.length,
    editorViewRefs: adapter?.editorViewRefs?.length,
    editorComponentRefs: adapter?.editorComponentRefs?.length,
  };
};

Monkey-Patching for Tracing

Inject tracing code to track specific operations:

// Track how many times releaseCell is called and what triggers it
() => {
  const grid = document.querySelector('tbw-grid');
  const adapter = grid?.__frameworkAdapter;
  if (!adapter?.releaseCell) return { error: 'No adapter or releaseCell' };

  const original = adapter.releaseCell.bind(adapter);
  window.__releaseLogs = [];
  adapter.releaseCell = (cellEl) => {
    window.__releaseLogs.push({
      col: cellEl.getAttribute('data-col'),
      row: cellEl.getAttribute('data-row'),
      isEditing: cellEl.classList.contains('editing'),
      stack: new Error().stack?.split('\n').slice(1, 4).join('\n'),
    });
    return original(cellEl);
  };
  return { patched: true };
};

Then trigger the action and read results:

() => ({
  logCount: window.__releaseLogs?.length || 0,
  logs: window.__releaseLogs?.slice(0, 10) || [],
});

Async Operations

For operations that need to wait for render cycles:

() => {
  const grid = document.querySelector('tbw-grid');
  // Trigger something
  grid.rows = [...grid.rows];
  // Wait for render to complete
  return new Promise((resolve) => {
    requestAnimationFrame(() => {
      requestAnimationFrame(() => {
        resolve({ rendered: true, rowCount: grid._rows?.length });
      });
    });
  });
};

Step 5: Common Debugging Workflows

"Component renders incorrectly"

  1. navigate_page to the page showing the issue
  2. take_screenshot for visual context
  3. take_snapshot on tbw-grid for DOM structure
  4. evaluate_script to read grid state (config, columns, rows)
  5. Identify the discrepancy between expected and actual state

"Editor is destroyed during editing"

  1. Navigate to the page with the editor
  2. evaluate_script: Monkey-patch adapter.releaseCell to log destructions
  3. Enter edit mode via click + press_key Enter
  4. Trigger the suspected cause (e.g., rows replacement)
  5. evaluate_script: Read the logs to see if/how destruction happened

"Event not firing or has wrong payload"

  1. evaluate_script: Add event listener with logging
    () => {
      window.__eventLogs = [];
      const grid = document.querySelector('tbw-grid');
      grid.on('cell-commit', (detail) => {
        window.__eventLogs.push({ type: 'cell-commit', detail });
      });
      return { listening: true };
    };
    
  2. Trigger the action
  3. evaluate_script: Read window.__eventLogs

"Styles not applied correctly"

  1. take_screenshot for visual state
  2. evaluate_script: Read computed styles
    () => {
      const cell = document.querySelector('.cell[data-col="0"]');
      const styles = getComputedStyle(cell);
      return {
        width: styles.width,
        color: styles.color,
        background: styles.backgroundColor,
        display: styles.display,
      };
    };
    
  3. take_snapshot with selector for the specific element's DOM

"Docs page fails to render"

  1. navigate_page to http://localhost:4400/grid/plugins/<plugin>/
  2. list_console_messages to catch any errors
  3. take_screenshot for visual state
  4. take_snapshot to see what DOM was actually rendered

MCP Tool Reference

Tools are grouped by category. Pick a category first based on what you are trying to learn, then choose a tool within it.

Navigation & tab management

Use when you need to load a URL, switch between tabs, or open/close pages.

Tool Purpose
navigate_page Go to URL, reload, go back/forward
list_pages List all open browser tabs
select_page Switch to a different tab
new_page Open a new tab
close_page Close a tab

Visual debugging (screenshots & DOM snapshots)

Use when you need to see what the page looks like or what its DOM structure is.

Tool Purpose
take_screenshot Capture full page or element screenshot
take_snapshot Get DOM tree structure

Script evaluation

Use when you need to read internal state, monkey-patch a method, or run arbitrary JS in the page context.

Tool Purpose
evaluate_script Run JavaScript in page context

User interaction simulation

Use when you need to simulate a user action (click, type, drag, key press) to reproduce or trigger a bug.

Tool Purpose
click Click an element by selector
fill Type text into an input
fill_form Fill multiple form fields at once
press_key Send keyboard events
hover Hover over an element
drag Drag from one element to another
upload_file Upload a file to a file input
handle_dialog Accept/dismiss browser dialogs

Synchronization & viewport

Use when you need to wait for the page to settle, or to change the viewport / device emulation.

Tool Purpose
wait_for Wait for element, URL, timeout, or network
emulate Set viewport size or device emulation
resize_page Resize the browser viewport

Console monitoring

Use when you need to catch warnings, errors, or console.log output emitted by the page.

Tool Purpose
list_console_messages Get all console messages
get_console_message Get specific console message details

Network monitoring

Use when you need to debug API calls, asset loading, or response payloads.

Tool Purpose
list_network_requests List all network requests
get_network_request Get specific request/response details

Performance tracing

Use when you need to profile rendering, scripting, or layout work. See the debug-perf skill for full workflow.

Tool Purpose
performance_start_trace Start a performance trace (see debug-perf)
performance_stop_trace Stop trace and get results
performance_analyze_insight Get AI-analyzed performance insights
Install via CLI
npx skills add https://github.com/OysteinAmundsen/toolbox --skill debug-browser
Repository Details
star Stars 28
call_split Forks 2
navigation Branch main
article Path SKILL.md
More from Creator
OysteinAmundsen
OysteinAmundsen Explore all skills →