Claude Code is Anthropic's terminal-native AI assistant — and when you pair it with the Playwright MCP Server, you get something remarkably powerful: an AI agent that can open a real browser, navigate your application, read the live DOM, and write production-ready Playwright tests — all without leaving your terminal.
This guide covers the complete workflow from first install to advanced custom tools. By the end, you'll have Claude Code generating, running, and debugging Playwright tests against your live application.
What Is the Playwright MCP Server?
The Model Context Protocol (MCP) is an open standard created by Anthropic that lets AI models interact with external tools. Think of it as a USB port for AI — any tool that implements the MCP protocol can plug into Claude and extend what it can do.
The Playwright MCP Server is an official tool built by the Playwright team that exposes browser automation capabilities through this protocol. When connected to Claude Code, it gives the AI direct access to:
- Browser navigation — go to any URL, click links, fill forms
- DOM inspection — read the accessibility tree, find elements, check text content
- Screenshots — capture what the page actually looks like
- Interaction — click buttons, type into inputs, select dropdowns, hover over menus
- Page state — read console logs, network requests, cookies, localStorage
Without MCP, Claude Code can write Playwright tests based on your descriptions — but it's guessing at selectors and page structure. With MCP, Claude sees your actual application and writes tests that match the real DOM, real selectors, and real user flows.
Key distinction: MCP is the protocol. Playwright MCP Server is one specific implementation of that protocol. You can have multiple MCP servers connected to Claude Code simultaneously — for example, Playwright for browser access and a custom server for database operations.
Prerequisites
Before starting, make sure you have these three things installed:
- Node.js 18 or later — the Playwright MCP Server runs on Node. Check with
node --version. - Claude Code CLI — Anthropic's terminal client. Install it with
npm install -g @anthropic-ai/claude-codeand authenticate with your API key or Claude subscription. - A Playwright project — if you don't have one yet, run
npm init playwright@latestto scaffold a new project with TypeScript, test examples, and config.
# Node.js 18+ required node --version # v20.15.0 ✓ # Claude Code CLI claude --version # claude-code 1.x.x ✓ # Playwright installed in your project npx playwright --version # 1.48.0 ✓
If any of these commands fail, install the missing dependency before continuing. The rest of this guide assumes all three are working.
Installing the Playwright MCP Server
The Playwright MCP Server is distributed as the @playwright/mcp npm package. You have three installation approaches:
| Method | Command | Best for |
|---|---|---|
| On-demand (npx) | npx @playwright/mcp@latest |
Default. Always runs the latest version, no install step. |
| Global install | npm install -g @playwright/mcp |
Faster startup (~2s saved). Good if you use MCP daily. |
| Project dependency | npm install --save-dev @playwright/mcp |
Pin a version for team consistency. Committed to package.json. |
For most developers, the npx approach is the right default. It pulls the latest version automatically and requires no setup. If the 2–3 second startup difference matters to your flow, install globally.
Verify the package works by running it directly:
# Start the MCP server standalone (for verification only) npx @playwright/mcp@latest # Expected output: # Playwright MCP Server listening on stdio # Press Ctrl+C to stop
If you see "listening on stdio", the server works. Press Ctrl+C to stop it — Claude Code will manage the server lifecycle automatically.
Configuring Claude Code to Use the MCP Server
Claude Code reads MCP server definitions from a JSON settings file. You can configure it at the project level (recommended) or globally.
Option A: Project-level configuration (recommended)
This scopes the MCP server to your Playwright project. Create or edit .claude/settings.json in your project root:
{
"mcpServers": {
"playwright": {
"command": "npx",
"args": ["@playwright/mcp@latest"],
"env": {}
}
}
}
Option B: Global configuration
If you want Playwright MCP available in every Claude Code session regardless of project, add it to your global settings:
{
"mcpServers": {
"playwright": {
"command": "npx",
"args": ["@playwright/mcp@latest"]
}
}
}
Verify the connection
Start a new Claude Code session and check that the MCP server is recognized:
# Start Claude Code in your project directory cd /path/to/your/playwright-project claude # Inside Claude Code, check connected MCP servers /mcp # Expected: "playwright" listed as a connected server with available tools
If Playwright appears in the /mcp output with tools like browser_navigate, browser_snapshot, and browser_click, your setup is complete.
Common mistake: Editing the settings file while Claude Code is running. Always start a new session after changing MCP configuration. Claude Code reads the config at startup.
Your First AI-Generated Test
With MCP connected, let's generate a real test. This walkthrough uses the Playwright docs site as a target, but substitute your own application URL.
Describe the test in plain English
Tell Claude Code what you want to test. Be specific about the page, the user action, and the expected result.
Claude navigates and inspects the page
Claude uses the MCP Server to open the URL in a real browser, read the accessibility tree, and understand the page structure before writing any code.
Claude writes the test with real selectors
Because Claude can see the actual DOM, it uses getByRole, getByLabel, and getByText selectors that match your real UI — not guesses.
Run the test to verify
Claude can run npx playwright test directly and confirm the test passes. If it fails, it debugs immediately.
Here's what the conversation looks like in practice:
# Your prompt: Navigate to https://playwright.dev and write a Playwright test that verifies the "Get Started" button in the hero section links to the /docs/intro page. Use TypeScript and save it to tests/homepage.spec.ts. # Claude's workflow (visible in the session): # 1. browser_navigate → https://playwright.dev # 2. browser_snapshot → reads the accessibility tree # 3. Identifies the "Get started" link with role="link" # 4. Writes the test file # 5. Runs: npx playwright test tests/homepage.spec.ts # 6. Reports: 1 passed
The generated test will look something like this:
import { test, expect } from '@playwright/test'; test('Get Started button links to intro docs', async ({ page }) => { await page.goto('https://playwright.dev'); const getStarted = page.getByRole('link', { name: 'Get started' }); await expect(getStarted).toBeVisible(); await getStarted.click(); await expect(page).toHaveURL(/\/docs\/intro/); });
Notice the selector quality. Claude used getByRole('link', { name: 'Get started' }) because it read the actual accessibility tree via MCP. Without MCP, it might have guessed .hero-button or a[href="/docs/intro"] — selectors that break when the CSS or HTML changes.
Browser Context: How Claude Sees Your App
Understanding what Claude can and cannot see through MCP is critical for writing effective prompts.
When Claude calls browser_snapshot, it receives the page's accessibility tree — a structured representation of every interactive element, heading, text block, image (with alt text), form field, and ARIA landmark on the page. This is the same tree that screen readers use.
# Simplified accessibility tree output
- navigation "Main"
- link "Home"
- link "Docs"
- link "API"
- link "Community"
- link "Get started"
- heading "Playwright" [level=1]
- text "Reliable end-to-end testing for modern web apps"
- link "Get started" [focused]
- link "Star on GitHub"
- heading "Any browser. Any platform." [level=2]
...
Claude can also:
- Take screenshots —
browser_screenshotcaptures a PNG of the visible viewport, useful for visual verification - Read console output — see JavaScript errors, warnings, and logs
- Inspect network requests — verify API calls, check response status codes
- Execute JavaScript — run arbitrary JS in the page context for advanced inspection
This means Claude's test generation is grounded in reality. It knows exactly which elements exist, what their accessible names are, and how the page is structured — before it writes a single line of test code.
Generating Tests from Live Pages
The most powerful MCP workflow is having Claude navigate your running application and generate tests from the actual UI. Here's a realistic example:
Navigate to http://localhost:3000/login and generate a complete test suite for the login page. Cover: 1. Successful login with valid credentials 2. Error message for wrong password 3. Required field validation (empty submit) 4. Password visibility toggle Save to tests/auth/login.spec.ts using Page Object Model.
Claude will navigate to the login page, snapshot the DOM, identify the email input, password input, submit button, error message container, and visibility toggle. Then it generates a full test suite and a Page Object Model class:
import { type Page, type Locator } from '@playwright/test'; export class LoginPage { readonly emailInput: Locator; readonly passwordInput: Locator; readonly submitButton: Locator; readonly errorMessage: Locator; readonly togglePassword: Locator; constructor(private page: Page) { this.emailInput = page.getByLabel('Email'); this.passwordInput = page.getByLabel('Password'); this.submitButton = page.getByRole('button', { name: 'Sign in' }); this.errorMessage = page.getByRole('alert'); this.togglePassword = page.getByLabel('Show password'); } async goto() { await this.page.goto('/login'); } async login(email: string, password: string) { await this.emailInput.fill(email); await this.passwordInput.fill(password); await this.submitButton.click(); } }
import { test, expect } from '@playwright/test'; import { LoginPage } from '../../pages/LoginPage'; test.describe('Login page', () => { let loginPage: LoginPage; test.beforeEach(async ({ page }) => { loginPage = new LoginPage(page); await loginPage.goto(); }); test('successful login redirects to dashboard', async ({ page }) => { await loginPage.login('user@example.com', 'validPassword123'); await expect(page).toHaveURL('/dashboard'); }); test('wrong password shows error message', async () => { await loginPage.login('user@example.com', 'wrongPassword'); await expect(loginPage.errorMessage).toBeVisible(); await expect(loginPage.errorMessage).toContainText('Invalid credentials'); }); test('empty form submission shows validation errors', async () => { await loginPage.submitButton.click(); await expect(loginPage.emailInput).toHaveAttribute('aria-invalid', 'true'); }); test('password toggle reveals password text', async () => { await loginPage.passwordInput.fill('secret123'); await loginPage.togglePassword.click(); await expect(loginPage.passwordInput).toHaveAttribute('type', 'text'); }); });
Every selector in this code was derived from the live DOM — not guessed from a description. The getByLabel('Email'), getByRole('button', { name: 'Sign in' }), and getByRole('alert') selectors match what actually exists on the page.
Debugging Failures with MCP
This is where the MCP + Claude Code combination truly shines. Instead of staring at a stack trace and manually re-running tests, you hand the failure to Claude and let it investigate with a real browser.
The workflow
Paste the failing test and error
Copy the test file and the error output from npx playwright test. Give Claude the full context.
Claude navigates to the page
Claude opens the same URL in MCP's browser, snapshots the DOM, and compares the expected selectors against what's actually on the page.
Claude identifies the root cause
Maybe the button text changed from "Submit" to "Sign in". Maybe a modal now blocks the element. Maybe the page redirects before the element loads. Claude can see all of this.
Claude rewrites and re-runs the test
The fix is applied, the test is executed, and Claude confirms it passes — all in one conversation.
Example prompt for debugging:
This test is failing. Navigate to http://localhost:3000/checkout and figure out why. Here's the error: Error: locator.click: Error: strict mode violation: getByRole('button', { name: 'Place order' }) resolved to 2 elements Fix the test in tests/checkout.spec.ts so it targets the correct button.
Claude will navigate to the checkout page, snapshot the DOM, find both "Place order" buttons (perhaps one is in a summary sidebar and one is in a sticky footer), and update the selector to target the correct one — for example, by scoping it to a specific section:
await page.getByRole('button', { name: 'Place order' }).click();
await page.getByRole('region', { name: 'Order summary' }) .getByRole('button', { name: 'Place order' }).click();
Advanced: Custom MCP Tools for QA
The Playwright MCP Server handles browser automation, but real QA workflows need more — test data setup, database seeding, API mocking, environment resets. You can build custom MCP servers that expose these operations as tools Claude can call.
Why custom tools matter
Imagine telling Claude: "Create a test user with admin permissions, seed 50 products, then test the admin dashboard filtering." Without custom tools, you'd need to set this up manually before running Claude. With a custom MCP server, Claude does it all.
Example: a test data MCP server
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; const server = new McpServer({ name: 'test-data', version: '1.0.0' }); server.tool( 'seed_test_user', 'Create a test user with specified role', { email: { type: 'string' }, role: { type: 'string' } }, async ({ email, role }) => { // Insert user into test database const user = await db.createUser({ email, role, password: 'TestPass123!' }); return { content: [{ type: 'text', text: `Created user ${user.id} (${email}, ${role})` }] }; } ); server.tool( 'reset_database', 'Reset the test database to a clean state', {}, async () => { await db.reset(); return { content: [{ type: 'text', text: 'Database reset complete' }] }; } ); const transport = new StdioServerTransport(); await server.connect(transport);
Register both servers in your Claude Code settings:
{
"mcpServers": {
"playwright": {
"command": "npx",
"args": ["@playwright/mcp@latest"]
},
"test-data": {
"command": "npx",
"args": ["tsx", "mcp-servers/test-data-server.ts"]
}
}
}
Now Claude Code can seed data, navigate the browser, and validate the UI — all in a single conversation. This is the foundation of a fully autonomous QA agent.
MCP Server vs Claude Code vs Claude.ai — When to Use Which
These three tools overlap but serve different purposes. Here's when to reach for each:
| Playwright MCP Server | Claude Code (CLI) | Claude.ai (Web) | |
|---|---|---|---|
| What it is | Browser automation bridge | Terminal AI assistant | Web chat interface |
| Browser access | Yes — real Chromium/Firefox/WebKit | Only via MCP Server | No |
| File system access | No | Yes — read/write project files | No |
| Run tests | No (it's a tool, not a runner) | Yes — runs npx playwright test |
No |
| Best for | Giving AI eyes on your app | Full test generation + debugging workflow | Quick questions, learning concepts |
| Ideal workflow | Connected to Claude Code as a tool | Primary interface for QA automation | Planning, code review, documentation |
The recommended setup for 2026: Use Claude Code as your primary interface with the Playwright MCP Server connected. This gives you browser access + file system access + terminal execution in a single workflow. Use Claude.ai for planning, conceptual discussions, and code reviews where you don't need live browser interaction.
Pro tip: You can also connect the Playwright MCP Server to Claude Desktop (the GUI app) for a more visual experience. See our MCP Server setup guide for Claude Desktop configuration.
Troubleshooting Common Issues
If your setup isn't working, find your symptom below. These are the most frequently reported issues with the Playwright MCP + Claude Code combination.
1. MCP server not appearing in /mcp output
Cause: JSON syntax error in .claude/settings.json, or the file is in the wrong location.
Fix: Validate your JSON at jsonlint.com. The file must be at <project-root>/.claude/settings.json for project-level or ~/.claude/settings.json for global. Start a new Claude Code session after any config change.
2. "spawn npx ENOENT" error
Cause: Node.js / npx is not on the PATH that Claude Code inherits.
Fix: On macOS/Linux, make sure Node is in your shell profile (~/.zshrc or ~/.bashrc). On Windows, use the full path: "command": "C:\\Program Files\\nodejs\\npx.cmd".
{
"mcpServers": {
"playwright": {
"command": "C:\\Program Files\\nodejs\\npx.cmd",
"args": ["@playwright/mcp@latest"]
}
}
}
3. Timeout when Claude tries to navigate
Cause: The target URL is unreachable — your dev server isn't running, or there's a network/proxy issue.
Fix: Start your dev server first (npm run dev). Verify you can open the URL in a regular browser. If you're behind a corporate proxy, add proxy environment variables to the MCP config:
{
"mcpServers": {
"playwright": {
"command": "npx",
"args": ["@playwright/mcp@latest"],
"env": {
"HTTP_PROXY": "http://proxy.company.com:8080",
"HTTPS_PROXY": "http://proxy.company.com:8080"
}
}
}
}
4. "Browser executable not found"
Cause: Playwright browsers aren't installed on this machine.
Fix: Run npx playwright install chromium (or npx playwright install for all browsers). On Linux, also run npx playwright install --with-deps to install system-level dependencies.
5. "Permission denied" on macOS
Cause: macOS Gatekeeper blocking the Chromium binary that Playwright downloads.
Fix: Run xattr -cr ~/Library/Caches/ms-playwright to remove the quarantine flag from all Playwright browser binaries. Alternatively, open System Settings → Privacy & Security and approve the blocked application.
# Remove quarantine flag from Playwright browsers xattr -cr ~/Library/Caches/ms-playwright # Verify Chromium launches npx playwright open https://example.com
6. MCP server crashes mid-session
Cause: Memory exhaustion (common with many open tabs/contexts) or a version incompatibility.
Fix: Update to the latest version: npx @playwright/mcp@latest. If you installed globally, run npm update -g @playwright/mcp. Start a new Claude Code session — MCP servers are restarted with each session.
7. Claude says "I don't have browser access"
Cause: The MCP server is configured but Claude doesn't know it can use browser tools.
Fix: Run /mcp to confirm the server is connected. If it is, explicitly ask Claude to use it: "Use the Playwright MCP Server to navigate to [URL]." Claude sometimes needs a nudge to invoke MCP tools versus writing code directly.
Frequently Asked Questions
Can Claude Code generate Playwright tests without MCP Server?
Yes, but the quality is lower. Without MCP, Claude generates tests based solely on your descriptions and any code you paste. With MCP, Claude navigates your live app, reads the actual DOM, and writes tests with selectors that match real elements — producing significantly more reliable tests.
Is Playwright MCP Server free to use with Claude Code?
The Playwright MCP Server (@playwright/mcp) is completely free and open-source. Claude Code requires an Anthropic API key or a Claude Pro/Team subscription. There is no additional charge for using MCP Server features.
How do I debug a failing test with Claude Code and MCP?
Paste the failing test file and the error output into Claude Code. Ask Claude to navigate to the same page via MCP, inspect the DOM, and find the root cause. Claude will identify the issue (wrong selector, timing, changed UI) and rewrite the test — then run it to confirm the fix.
Does MCP Server work with Claude Code in VS Code terminal?
Yes. Claude Code runs in any terminal, including VS Code's integrated terminal. Add MCP Server config to your project's .claude/settings.json and it will be available in every Claude Code session started from that project directory.
Can I create custom MCP tools for my QA workflow?
Yes. Build custom MCP servers using the @modelcontextprotocol/sdk package to expose project-specific tools — database seeding, test user creation, API mocking, environment resets. Register them alongside Playwright in your .claude/settings.json and Claude can use all tools in a single session.
Asim Noaman
Senior QA Automation Engineer & AI Testing Specialist
With years of hands-on experience building test automation frameworks for production applications, Asim specializes in combining traditional QA methodologies with cutting-edge AI tools. He has helped teams adopt Playwright and AI-driven testing workflows to ship faster with fewer bugs.