MCP Hub
Back to servers

MCP Playwright Server

A comprehensive MCP server enabling AI agents to perform full browser automation using Playwright, featuring tools for navigation, interaction, web-first assertions, and accessibility audits.

Tools
47
Updated
Dec 9, 2025

MCP Playwright Server

TypeScriptPlaywrightMCPNode.jsLicense

A comprehensive Model Context Protocol (MCP) server for browser automation using Playwright. This server enables AI assistants to control browsers via the MCP protocol, providing powerful tools for web testing, accessibility audits, and browser automation.

Features

  • 🌐 Multi-Browser Support - Chromium, Firefox, and WebKit
  • 🔧 50+ Browser Automation Tools - Navigation, interaction, assertions, screenshots
  • Accessibility Testing - Built-in axe-core integration with WCAG compliance checks
  • 🔒 Session Management - Isolated browser contexts with rate limiting
  • 📸 Visual Testing - Screenshots, video recording, and tracing
  • 🧪 AI Test Agents - Automated test planning, generation, and healing

Technology Stack

CategoryTechnologies
RuntimeNode.js 18+
LanguageTypeScript 5.8
Browser AutomationPlaywright 1.52+
ProtocolModel Context Protocol (MCP) SDK
ValidationZod
Accessibility@axe-core/playwright
LoggingWinston
Testing@playwright/test
LintingESLint 9, Prettier

Architecture

AI Client → MCP Protocol → Tool Handler → BrowserManager → Action Module → Playwright API
                                ↑
                         SessionManager (lifecycle, rate limiting)

Layer Responsibilities

LayerLocationPurpose
Entrysrc/index.tsBootstrap, graceful shutdown
MCP Serversrc/server/mcp-server.tsTool/resource registration, session cleanup
Handlerssrc/server/handlers/Tool definitions grouped by category
Browser Managersrc/playwright/browser-manager.tsOrchestrates action modules
Actionssrc/playwright/actions/Domain-specific Playwright operations
Session Managersrc/playwright/session-manager.tsSession/page lifecycle, rate limiting

Getting Started

Prerequisites

  • Node.js 18 or higher
  • npm or yarn

Installation

# Clone the repository
git clone https://github.com/j0hanz/playwright-mcp.git
cd playwright-mcp

# Install dependencies
npm install

# Install Playwright browsers
npm run install:browsers

Configuration

Create a .env file in the project root (optional):

LOG_LEVEL=info              # debug, info, warn, error
DEFAULT_BROWSER=chromium    # chromium, firefox, webkit
HEADLESS=true               # Run headless mode
MAX_SESSIONS=5              # Concurrent sessions (1-20)
SESSION_TIMEOUT=1800000     # Session expiry in ms (30 min)
TIMEOUT_ACTION=20000        # Element action timeout in ms
TIMEOUT_NAVIGATION=30000    # Page navigation timeout in ms

Running the Server

# Development mode (with hot reload)
npm run dev

# Production build and run
npm run build
npm start

Project Structure

├── src/
│   ├── index.ts                    # Application entry point
│   ├── config/
│   │   ├── server-config.ts        # Environment configuration
│   │   └── types.ts                # TypeScript type definitions
│   ├── server/
│   │   ├── mcp-server.ts           # MCP server implementation
│   │   └── handlers/               # Tool handler categories
│   │       ├── browser-tools.ts    # Browser lifecycle tools
│   │       ├── navigation-tools.ts # Navigation tools
│   │       ├── interaction-tools.ts# Click, fill, hover tools
│   │       ├── assertion-tools.ts  # Web-first assertions
│   │       ├── page-tools.ts       # Screenshots, content, a11y
│   │       ├── test-tools.ts       # Test file management
│   │       ├── advanced-tools.ts   # Network, tracing, dialogs
│   │       └── schemas.ts          # Zod validation schemas
│   ├── playwright/
│   │   ├── browser-manager.ts      # Central browser orchestration
│   │   ├── session-manager.ts      # Session lifecycle
│   │   ├── browser-launcher.ts     # Browser launch logic
│   │   └── actions/                # Domain-specific actions
│   │       ├── assertion-actions.ts
│   │       ├── interaction-actions.ts
│   │       ├── navigation-actions.ts
│   │       └── ...
│   └── utils/
│       ├── error-handler.ts        # Centralized error handling
│       └── logger.ts               # Winston logger
├── tests/                          # Playwright test files
├── specs/                          # Human-readable test plans
├── .github/
│   ├── agents/                     # AI agent definitions
│   ├── prompts/                    # Agent prompts
│   └── copilot-instructions.md     # Development guidelines
└── playwright.config.ts            # Playwright test configuration

Available Tools

Browser Lifecycle

ToolDescription
browser_launchLaunch browser (Chromium, Firefox, WebKit) with optional auth state
browser_closeClose browser session
browser_tabsList, create, close, or select browser tabs
sessions_listList all active browser sessions
save_storage_stateSave cookies/localStorage for auth reuse
session_reset_stateClear session data for test isolation

Navigation

ToolDescription
browser_navigateNavigate to URL
browser_historyGo back/forward in history
browser_reloadReload current page
handle_dialogAccept/dismiss browser dialogs

Interaction

ToolDescription
element_clickClick by role, text, testid, or selector
element_fillFill inputs by label, placeholder, or selector
element_hoverHover over elements
select_optionSelect dropdown options
keyboard_pressPress keys (Enter, Tab, shortcuts)
keyboard_typeType text character by character
checkbox_setCheck/uncheck checkboxes
file_uploadUpload files
drag_and_dropDrag and drop elements

Assertions

ToolDescription
assert_elementAssert state (visible, hidden, enabled, disabled)
assert_textAssert element text content
assert_valueAssert input value
assert_urlAssert page URL
assert_titleAssert page title
assert_attributeAssert element attribute
assert_cssAssert CSS property
assert_checkedAssert checkbox state
assert_countAssert element count

Page Operations

ToolDescription
page_screenshotCapture screenshots (full page, element, region)
page_pdfGenerate PDF from page (Chromium only)
page_contentGet HTML and text content
page_evaluateExecute JavaScript (read-only)
wait_for_selectorWait for elements
page_wait_for_load_stateWait for page load
accessibility_scanRun axe-core accessibility audit
browser_snapshotGet accessibility tree snapshot

Cookie Management

ToolDescription
cookies_getRetrieve cookies from browser context
cookies_setAdd cookies (auth tokens, sessions)
cookies_clearClear all or specific cookies

Advanced

ToolDescription
network_mockMock network responses
network_unrouteRemove network mocks
tracing_start / tracing_stopRecord execution traces
console_captureCapture console messages
har_record_startRecord HTTP archive
clock_installControl time in tests
video_pathGet video recording path

Best Practices for Stable Tests

Following these practices will ensure your tests are resilient, maintainable, and less prone to flakiness. See the full Best Practices Guide for detailed examples.

Core Principles

  1. Use Semantic, User-Facing Locators

    • Role-based locators are most reliable: getByRole('button', { name: 'Submit' })
    • Avoid CSS selectors and XPath — these break when styling changes
    • Priority: Role → Label → Placeholder → Text → TestId → CSS (last resort)
  2. Use Locator Chaining and Filtering

    • Chain locators to narrow searches: page.getByRole('listitem').filter({ hasText: 'Product 2' })
    • Filter by text or other locators for dynamic content
    • This reduces strict mode violations and increases clarity
  3. Always Use Web-First Assertions

    • Use expect() assertions which auto-wait: await expect(page.getByText('Success')).toBeVisible()
    • Don't use direct checks like isVisible() without expect
    • Assertions wait up to 5 seconds (configurable) before failing
  4. Avoid Common Pitfalls

    • waitForTimeout() — use specific waits instead
    • waitForLoadState('networkidle') — use 'domcontentloaded' or wait for elements
    • ❌ CSS class selectors — use role/label/text locators
    • ❌ Screenshots as selectors — use browser_snapshot for finding elements
    • test.only() or test.skip() — remove before committing

Example: Good Test Structure

test('Add todo and verify', async ({ page }) => {
  // Navigate
  await page.goto('/');

  // Get accessibility snapshot to understand page structure
  const snapshot = await page.accessibility.snapshot();

  // Interact using semantic locators (role > label > text)
  await page.getByPlaceholder('What needs to be done?').fill('Buy groceries');
  await page.getByRole('button', { name: 'Add' }).click();

  // Verify using web-first assertions (auto-wait)
  await expect(page.getByText('Buy groceries')).toBeVisible();
  await expect(page.getByRole('listitem')).toHaveCount(1);
});

Locator Priority

When interacting with elements, prefer user-facing locators (most reliable first):

  1. Role ⭐ - element_click(locatorType: 'role', role: 'button', name: 'Submit')
  2. Label ⭐ - element_fill(locatorType: 'label', value: 'Email', text: '...')
  3. Text - element_click(locatorType: 'text', value: 'Learn more')
  4. Placeholder - element_fill(locatorType: 'placeholder', value: 'Search...')
  5. TestId - element_click(locatorType: 'testid', value: 'submit-btn')
  6. Selector - CSS selector (last resort only)

Development Workflow

# Watch mode with hot reload
npm run dev

# Build TypeScript to dist/
npm run build

# Run ESLint
npm run lint
npm run lint:fix

# Type check without emit
npm run type-check

# Format with Prettier
npm run format

# Run tests
npm test
npm run test:ui      # Interactive UI
npm run test:headed  # Visible browser
npm run test:debug   # Debug mode

Before committing: Run npm run lint && npm run type-check && npm run build

Coding Standards

Tool Registration Pattern

server.registerTool(
  'tool_name',
  {
    title: 'Human Title',
    description: 'What this tool does',
    inputSchema: {
      /* Zod schemas */
    },
    outputSchema: {
      /* Result shape */
    },
  },
  createToolHandler(async (input) => {
    const result = await browserManager.someMethod(input);
    return {
      content: [{ type: 'text', text: 'Human readable' }],
      structuredContent: result, // Machine readable
    };
  }, 'Error prefix message')
);

Action Module Pattern

export class MyActions extends BaseAction {
  async myOperation(sessionId: string, pageId: string, options: Options) {
    return this.executePageOperation(
      sessionId,
      pageId,
      'My operation',
      async (page) => {
        // Playwright operations
        return { success: true, data: '...' };
      }
    );
  }
}

Error Handling

import {
  ErrorCode,
  ErrorHandler,
  validateUUID,
} from '../utils/error-handler.js';

validateUUID(sessionId, 'sessionId'); // Throws on invalid
throw ErrorHandler.sessionNotFound(id); // Factory methods
throw ErrorHandler.handlePlaywrightError(e); // Maps Playwright errors

Testing

Tests use @playwright/test framework. Configuration is in playwright.config.ts.

npm test                 # Run all tests
npm run test:ui          # Interactive test UI
npm run test:headed      # With visible browser
npm run test:debug       # Debug mode with inspector
npm run test:trace       # Record traces
npm run test:report      # Show HTML report

Test Configuration

  • Timeout: 30 seconds per test
  • Retries: 2 on CI, 0 locally
  • Browsers: Chromium, Firefox, WebKit, Mobile Chrome, Mobile Safari
  • Viewport: 1366x900
  • Test ID Attribute: data-testid

AI Test Agents

Three AI agents for automated test workflows:

AgentInputOutput
PlannerApp URL + seed testspecs/*.md test plans
GeneratorTest plantests/*.spec.ts files
HealerFailing testFixed test file

Usage

  1. Planner: Explore app and create test plans in specs/
  2. Generator: Transform plans into Playwright tests
  3. Healer: Debug and fix failing tests

Agent definitions are in .github/agents/ with prompts in .github/prompts/.

Security

  • URL validation: Only http:// and https:// protocols allowed
  • UUID validation: All session/page IDs validated
  • Rate limiting: Configurable MAX_SESSIONS_PER_MINUTE
  • Session isolation: Each browser context is isolated
  • Script restrictions: Only safe, read-only JavaScript evaluation

Contributing

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Follow the coding standards in .github/copilot-instructions.md
  4. Run linting and type checking (npm run lint && npm run type-check)
  5. Ensure tests pass (npm test)
  6. Commit your changes (git commit -m 'Add amazing feature')
  7. Push to the branch (git push origin feature/amazing-feature)
  8. Open a Pull Request

Adding a New Tool

  1. Add method to action class in src/playwright/actions/
  2. Register in handler file in src/server/handlers/
  3. Add schemas to schemas.ts if new input shapes needed
  4. Add tests for the new functionality

License

This project is licensed under the MIT License - see the LICENSE file for details.

Acknowledgments

Reviews

No reviews yet

Sign in to write a review