level 13 / playwright-mcp

Playwright MCP

Use the official Playwright MCP server to give AI agents direct browser control for test generation and debugging.

What Is Playwright MCP?

Playwright MCP (@playwright/mcp) is an official MCP server from Microsoft that exposes Playwright browser control as MCP tools. An LLM (Claude, GPT-4) can navigate websites, click elements, fill forms, and take screenshots — all via tool calls.

Installation & Configuration

# Install
npm install -D @playwright/mcp

# Or use via npx (no install)
npx @playwright/mcp@latest
// .claude/settings.json — add to Claude Code
{
  "mcpServers": {
    "playwright": {
      "command": "npx",
      "args": ["@playwright/mcp@latest", "--headless"],
      "env": {}
    }
  }
}

Available Tools

Playwright MCP exposes these tool groups:

GroupTools
Navigationbrowser_navigate, browser_go_back, browser_go_forward
Interactionbrowser_click, browser_fill, browser_press_key, browser_select_option
Observationbrowser_screenshot, browser_get_text, browser_snapshot
Tab managementbrowser_new_tab, browser_close_tab, browser_list_tabs
Networkbrowser_network_requests, browser_set_extra_headers
Storagebrowser_storage_state

AI-Assisted Test Generation Workflow

With Playwright MCP, Claude can:

  1. Navigate the app under test
  2. Inspect the DOM via browser_snapshot (accessibility tree)
  3. Generate accurate test code with real selectors
  4. Verify the test by running it

Example Claude Code prompt:

Navigate to http://localhost:3000/login, fill in the credentials,
click submit, and generate a Playwright test for the login flow.
Use role-based selectors from the accessibility tree.

Snapshot-Based Selector Extraction

# Claude uses browser_snapshot to get the accessibility tree:
browser_snapshot() →
  role=heading level=1 name="Sign In"
  role=textbox name="Email"
  role=textbox name="Password"
  role=button name="Sign In"
  role=link name="Forgot password?"

# Claude generates from this snapshot:
await page.getByRole('textbox', { name: 'Email' }).fill(email);
await page.getByRole('textbox', { name: 'Password' }).fill(password);
await page.getByRole('button', { name: 'Sign In' }).click();

This produces semantic, stable selectors — not CSS or XPath.

Practical Example: Generate Login Test

// Claude generates this from Playwright MCP inspection:
import { test, expect } from '@playwright/test';

test('user can log in with valid credentials', async ({ page }) => {
  await page.goto('/login');

  await page.getByRole('textbox', { name: 'Email' }).fill('user@example.com');
  await page.getByRole('textbox', { name: 'Password' }).fill('password123');
  await page.getByRole('button', { name: 'Sign In' }).click();

  await expect(page).toHaveURL(/dashboard/);
  await expect(page.getByRole('heading', { name: 'Welcome' })).toBeVisible();
});

Compared to raw codegen output (CSS selectors, no assertions) — MCP output is production-quality.

Debugging Failing Tests with Playwright MCP

User: "This test is failing — can you debug it?"
[shares test file]

Claude:
1. browser_navigate(url) — visits the page
2. browser_snapshot() — inspects accessibility tree
3. Finds: button text changed from "Submit" to "Place Order"
4. Updates: page.getByRole('button', { name: 'Place Order' })
5. browser_screenshot() — confirms fix

Visual Regression with Screenshots

// Claude takes screenshot for visual comparison
// browser_screenshot() → base64 PNG

test('product page visual check via MCP', async ({ page }) => {
  await page.goto('/products/widget-pro');
  // Take screenshot via standard Playwright (same as MCP internally)
  await expect(page).toHaveScreenshot('widget-pro.png', { maxDiffPixels: 100 });
});

Playwright MCP vs Direct Playwright

Playwright MCPDirect Playwright test
Who controls browserAI agent via tool callsTypeScript code
Use caseExploration, generation, debuggingCI test execution
ReproducibilityAd-hocDeterministic
OutputGenerated test codeTest results

Use MCP for creating and debugging tests. Use direct Playwright for running tests in CI.