mcpField
C
chrome-devtools-mcp

chrome-devtools-mcp

github.com/gsmlg-devTesting & quality040 installsUpdated 3w ago
Repository

About chrome-devtools-mcp

chrome-devtools-mcp is an MCP server for controlling and inspecting Chrome through Chrome DevTools, aimed at browser automation and testing. It offers page snapshots, interaction, script evaluation, screenshots, and performance and accessibility checks, with optional extension and memory tools.

Key features

  1. 01Navigate and wait for pages to load
  2. 02Inspect page structure and interact with elements
  3. 03Evaluate scripts to check page state
  4. 04Capture viewport screenshots
  5. 05Check performance, layout, and accessibility
  6. 06Enable optional extension and memory tools

Use cases

  1. 01Automate browser interactions and data retrieval
  2. 02Inspect page errors and network failures
  3. 03Test responsive layouts at different viewport sizes
  4. 04Verify extension behavior on web pages

MCP support

It acts as an MCP server that exposes Chrome browser control and inspection tools to MCP clients. Clients can target pages by page ID, with optional extension or memory tools enabled through configuration flags.

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/gsmlg-dev/code-agent --skill chrome-devtools-mcp

After installing, type/chrome-devtools-mcpin Claude Code to confirm it loaded.

Details

From the project README

Core Concepts

Browser lifecycle: Browser starts automatically on first tool call using a persistent Chrome profile. Configure via CLI args in the MCP server configuration: npx chrome-devtools-mcp@latest --help.

Additional tooling can be enabled by providing the following flags:

  • For extension tooling, use the --categoryExtensions flag.
  • For memory tooling, use the --memoryDebugging flag.

Page targeting: Page-scoped tools require a pageId parameter to target a specific page. Use list_pages to see available pages and their IDs (e.g. pageId: 1), or use the ID returned when creating a page with new_page. Note: For evaluate_script, pageId is required when targeting pages. However, when --categoryExtensions is enabled, pageId is optional so you can pass serviceWorkerId instead to evaluate inside an extension background service worker.

Element interaction: Use take_snapshot to get page structure with element uids. Each element has a unique uid for interaction. If an element isn't found, take a fresh snapshot - the element may have been removed or the page changed.

Workflow Patterns

Before interacting with a page
  1. Navigate: navigate_page or new_page
  2. Wait: wait_for to ensure content is loaded if you know what you look for.
  3. Snapshot: take_snapshot with pageId to understand page structure
  4. Interact: Use element uids from snapshot for click, fill, etc., passing the corresponding pageId.
Efficient data retrieval
  • Use filePath parameter for large outputs (screenshots, snapshots, traces)
  • Use pagination (pageIdx, pageSize) and filtering (types) to minimize data
  • Set includeSnapshot: false on input actions unless you need updated page state
Tool selection
  • Automation/interaction: take_snapshot (text-based, faster, better for automation)
  • Visual inspection: take_screenshot (when user needs to see visual state)
  • Additional details: evaluate_script for data not in accessibility tree
Parallel execution

You can send multiple tool calls in parallel, but maintain correct order: navigate → wait → snapshot → interact.

Testing an extension

Extension tools (install_extension, list_extensions, etc.) are available only when the MCP server is started with the --categoryExtensions flag. If these tools are unavailable, stop and ask the user to update their MCP server configuration and restart the MCP server (or AI client).

  1. Install: Use install_extension with the path to the unpacked extension.
  2. Identify: Get the extension ID from the response or by calling list_extensions.
  3. Trigger Action: Use trigger_extension_action to open the popup or side panel if applicable.
  4. Verify Service Worker: Use evaluate_script with serviceWorkerId (omitting pageId and args) to check extension state or trigger background actions. When evaluating in a page, pass pageId (omitting serviceWorkerId).
  5. Verify Page Behavior: Navigate to a page where the extension operates and use take_snapshot to check if content scripts injected elements or modified the page correctly.

Troubleshooting

If chrome-devtools-mcp is insufficient, guide users to use Chrome DevTools UI:

If there are errors launching chrome-devtools-mcp or Chrome, refer to https://github.com/ChromeDevTools/chrome-devtools-mcp/blob/main/docs/troubleshooting.md.

Chrome DevTools MCP Usage

Critical Constraint: Screenshot Size Limit

API submissions have a hard limit of 8000px on any dimension. Full-page screenshots frequently exceed this.

Solution: Always use viewport-only screenshots by default
// ✓ CORRECT: Viewport-only (default)
take_screenshot({ fullPage: false })

// ✓ CORRECT: Save to filesystem for large pages
take_screenshot({ fullPage: false, path: './screenshots/page.png' })

// ✗ AVOID: Full-page without size check
take_screenshot({ fullPage: true })
When full-page screenshots are needed

Check page height first:

const height = execute_script('return document.documentElement.scrollHeight')
if (height > 8000) {
  take_screenshot({ fullPage: false, path: './screenshot.png' })
} else {
  take_screenshot({ fullPage: true })
}

Alternative: Capture multiple viewport screenshots by scrolling:

take_screenshot({ fullPage: false, path: './top.png' })
execute_script('window.scrollBy(0, window.innerHeight)')
take_screenshot({ fullPage: false, path: './middle.png' })

Navigation and Timing

Always wait after navigation to allow JS execution and rendering:

navigate('https://example.com')
wait(1000) // Minimum recommended wait

// For dynamic apps, wait for specific elements
navigate('https://example.com')
execute_script(`
  return new Promise(resolve => {
    const check = () => {
      if (document.querySelector('#app')) resolve();
      else setTimeout(check, 100);
    };
    check();
  });
`)

Error Detection Patterns

Console Errors
const errors = execute_script(`
  return performance.getEntriesByType('navigation')[0].type === 'reload'
    ? []
    : (window.__consoleErrors || []);
`)
Network Failures
const failed = execute_script(`
  return performance.getEntriesByType('resource')
    .filter(r => r.transferSize === 0 && r.duration > 0)
    .map(r => ({ url: r.name, duration: r.duration }));
`)
Layout Issues
const issues = execute_script(`
  const issues = [];

  // Horizontal overflow
  if (document.documentElement.scrollWidth > window.innerWidth) {
    issues.push({ type: 'horizontal-overflow', width: document.documentElement.scrollWidth });
  }

  // Elements outside viewport
  document.querySelectorAll('*').forEach(el => {
    const rect = el.getBoundingClientRect();
    if (rect.right > window.innerWidth) {
      issues.push({ type: 'overflow-right', element: el.tagName });
    }
  });

  return issues;
`)

Responsive Testing

Test multiple viewports efficiently:

const viewports = [
  { width: 375, height: 667, name: 'mobile' },
  { width: 768, height: 1024, name: 'tablet' },
  { width: 1440, height: 900, name: 'desktop' }
];

for (const vp of viewports) {
  set_viewport(vp.width, vp.height);
  wait(500); // Allow reflow
  take_screenshot({
    fullPage: false,
    path: `./screenshots/${vp.name}.png`
  });
}

Component State Testing

Test interactive states without user interaction:

// Hover state
execute_script(`
  const el = document.querySelector('#button');
  el.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }));
`);
take_screenshot({ fullPage: false, path: './hover.png' });

// Focus state
execute_script(`
  document.querySelector('#input').focus();
`);
take_screenshot({ fullPage: false, path: './focus.png' });

// Active/pressed state
execute_script(`
  const el = document.querySelector('#button');
  el.dispatchEvent(new MouseEvent('mousedown', { bubbles: true }));
`);
take_screenshot({ fullPage: false, path: './active.png' });

Performance Metrics

Collect web vitals and performance data:

const metrics = execute_script(`
  return {
    fcp: performance.getEntriesByName('first-contentful-paint')[0]?.startTime,
    lcp: performance.getEntriesByType('largest-contentful-paint')[0]?.startTime,
    cls: performance.getEntriesByType('layout-shift')
      .reduce((sum, entry) => sum + (entry.hadRecentInput ? 0 : entry.value), 0),
    resources: performance.getEntriesByType('resource').length,
    totalSize: performance.getEntriesByType('resource')
      .reduce((sum, r) => sum + r.transferSize, 0)
  };
`);

Accessibility Checks

Basic accessibility validation:

const a11y = execute_script(`
  const issues = [];

  // Missing alt text
  document.querySelectorAll('img:not([alt])').forEach(img => {
    issues.push({ type: 'missing-alt', src: img.src });
  });

  // Missing form labels
  document.querySelectorAll('input:not([aria-label]):not([id])').forEach(input => {
    if (!input.closest('label')) {
      issues.push({ type: 'missing-label', name: input.name });
    }
  });

  // Empty links
  document.querySelectorAll('a').forEach(link => {
    if (!link.textContent.trim() && !link.getAttribute('aria-label')) {
      issues.push({ type: 'empty-link', href: link.href });
    }
  });

  return issues;
`);

Standard Audit Workflow

Use this pattern for systematic page audits:

function audit_page(url, name) {
  // Navigate
  navigate(url);
  wait(1000);

  // Screenshot (viewport only to avoid size errors)
  const screenshot_path = `./screenshots/${name}.png`;
  take_screenshot({ fullPage: false, path: screenshot_path });

  // Page metrics
  const height = execute_script('return document.documentElement.scrollHeight');
  const viewport = execute_script('return window.innerHeight');

  // Error detection
  const console_errors = execute_script('return window.__errors || []');
  const network_failures = execute_script(`
    return performance.getEntriesByType('resource')
      .filter(r => r.transferSize === 0 && r.duration > 0)
      .map(r => r.name);
  `);
  const layout_issues = execute_script(`
    return document.documentElement.scrollWidth > window.innerWidth
      ? ['horizontal-overflow']
      : [];
  `);

  return {
    name,
    url,
    screenshot_path,
    page_height: height,
    viewport_height: viewport,
    needs_scroll: height > viewport,
    console_errors,
    network_failures,
    layout_issues,
    has_issues: console_errors.length > 0 ||
                network_failures.length > 0 ||
                layout_issues.length > 0
  };
}

Error Handling

Screenshot Dimension Error

If you encounter dimension errors despite using fullPage: false, the page may be using unusual viewport settings. Save to file instead:

try {
  take_screenshot({ fullPage: false });
} catch (error) {
  if (error.includes('8000 pixels')) {
    take_screenshot({ fullPage: false, path: './fallback.png' });
  }
}
Navigation Timeout
try {
  navigate(url);
  wait(5000);
} catch (error) {
  // Log failure and continue
  console.log(`Failed to load: ${url}`);
}
Missing Elements
const exists = execute_script(`return !!document.querySelector('#target')`);
if (!exists) {
  take_screenshot({ fullPage: false, path: './missing-element.png' });
  // Handle accordingly
}

Key Reminders

  1. Always default to fullPage: false to avoid dimension errors
  2. Save screenshots to filesystem when possible instead of API submission
  3. Wait after navigation to allow JS execution (minimum 1000ms)
  4. Use execute_script for DOM queries instead of brittle selector-based approaches
  5. Test multiple viewports for responsive applications
  6. Capture evidence early - take screenshots before elements change state
  7. Check page dimensions before deciding on screenshot strategy

Additional Tooling Categories

Addional tooling can be enabled by providing the following flags:

  • For extension tooling, use the --categoryExtensions flag.
  • For memory tooling, use the --memoryDebugging flag.
Testing an extension

Before proceeding: Extension tools (install_extension, list_extensions, etc.) are only available when the MCP server is started with the --categoryExtensions flag. If these tools are not in your tool list, stop and ask the user to update their MCP server configuration:

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

After updating, the user must restart the MCP server (or their AI client) for the change to take effect.

  1. Install: Use install_extension with the path to the unpacked extension.
  2. Identify: Get the extension ID from the response or by calling list_extensions.
  3. Trigger Action: Use trigger_extension_action to open the popup or side panel if applicable.
  4. Verify Service Worker: Use evaluate_script with serviceWorkerId to check extension state or trigger background actions.
  5. Verify Page Behavior: Navigate to a page where the extension operates and use take_snapshot to check if content scripts injected elements or modified the page correctly.

Comments

Markdown supported