Featured

Deploy OpenClaw in 60 seconds — 20% off logoDeploy OpenClaw in 60 seconds — 20% off

Launch OpenClaw on Hostinger in about 60 seconds and keep your agent live 24/7. Our referral link gives you 20% off, no coupon code needed.

Launch on Hostinger
Run your Hermes agent on Hostinger, fully managed logoRun your Hermes agent on Hostinger, fully managed

Launch Hermes on Hostinger in one click, fully managed, no VPS knowledge needed. Use code ZACAARON10 for 10% off.

Launch on Hostinger
Crawl and scrape any site into clean data, 10% off logoCrawl and scrape any site into clean data, 10% off

Firecrawl crawls and scrapes any site into clean markdown for your agent. Get 1,000 free credits, and new users get 10% off their first purchase.

Try Firecrawl free
6,000+ web scrapers for your AI agent, start free logo6,000+ web scrapers for your AI agent, start free

Apify gives your agent live web data: 6,000+ prebuilt scrapers and actors, MCP-ready. Sign up free with $5 in usage credits.

Try Apify free
One API to scrape, enrich, and extract the internet. logoOne API to scrape, enrich, and extract the internet.

Context.dev gives your agents a single API to scrape, enrich, and extract live web data — no proxies, no parsers, no maintenance.

Start building free
SetupClaw: done-for-you OpenClaw for founders & exec teams logoSetupClaw: done-for-you OpenClaw for founders & exec teams

White-glove OpenClaw for founders and exec teams (4–50+ employees): we install, harden, integrate your tools, and maintain it — secured from day one.

Get it set up for you
SEO data APIs for your agent, $1 free credit logoSEO data APIs for your agent, $1 free credit

DataForSEO gives your agent live access to SERP results, keyword data, backlinks, and on-page SEO data through one API. New accounts get a $1 credit, good for up to 20,000 keyword or backlink lookups.

Try DataForSEO free
Reach 48,000+ AI builders

A flat monthly placement in front of developers actively installing AI tools. No lock-in, cancel anytime.

Advertise here

Works with

Claude CodeClaude DesktopCursorVS CodeClineCodex CLIOpenClaw+ any MCP client

Install to Claude Code

This server doesn't publish a one-line install command. Follow the setup in the source repository.

Summary

Enables browser automation through the MCP protocol, allowing AI agents to control a real browser using accessibility snapshots and natural language commands.

README.md

Playwright MCP Server — POC with 100+ Test Cases

VentureDive QA Engineering Prepared by: Raheel Imran Date: May 2026

---

Overview

This Proof of Concept (POC) demonstrates the Playwright MCP Server (@playwright/mcp) used as the browser automation engine for a complete test suite. It includes:

  • A fully functional demo web application — TaskMaster Pro
  • 166 Playwright test cases across 8 feature areas using reusable MCP-style commands
  • A cmd fixture that maps directly to @playwright/mcp tool names
  • A live MCP server demo that connects via @modelcontextprotocol/sdk and drives the browser using real MCP protocol — no external API calls
  • MCP server configuration for Claude Desktop and Claude Code
  • An interactive HTML test report

---

Architecture

┌──────────────────────────────────────────────────────────────┐
│                   Playwright MCP POC                         │
│                                                              │
│  ┌───────────────────┐   MCP Protocol    ┌────────────────┐  │
│  │  MCP Client       │◄─────────────────►│ @playwright/mcp│  │
│  │  (run-demo.ts)    │   stdio transport │  Server        │  │
│  └───────────────────┘                   └───────┬────────┘  │
│                                                  │           │
│                                         Playwright Browser   │
│                                                  │           │
│                                         ┌────────▼─────────┐ │
│                                         │ TaskMaster Pro   │ │
│                                         │ localhost:3000   │ │
│                                         └──────────────────┘ │
│                                                              │
│  Playwright Test Runner (166 tests)                          │
│  └── tests/fixtures/browser-commands.ts  ← reusable cmd      │
│      ├── auth.spec.ts          (25 tests)                    │
│      ├── navigation.spec.ts    (20 tests)                    │
│      ├── dashboard.spec.ts     (12 tests)                    │
│      ├── tasks.spec.ts         (45 tests)                    │
│      ├── categories.spec.ts    (14 tests)                    │
│      ├── profile.spec.ts       (17 tests)                    │
│      ├── settings.spec.ts      (17 tests)                    │
│      └── ui-components.spec.ts (16 tests)                    │
└──────────────────────────────────────────────────────────────┘

---

Quick Start

1. Install dependencies

npm install
npx playwright install chromium

2. Start the demo app

npm run dev
# → http://localhost:3000

3. Run all tests and generate HTML report

npm test
npm run test:report    # opens playwright-report/index.html

4. Run the MCP server demo (browser driven via MCP protocol)

npm run demo
# Starts @playwright/mcp server, connects as MCP client, runs 10 scenarios

5. Interactive test runner

npm run test:ui

---

npm Scripts

| Script | Description | |--------|-------------| | npm run dev | Start the TaskMaster Pro demo app on port 3000 | | npm test | Run all 100+ tests (Chromium + Firefox) | | npm run test:ui | Open Playwright interactive UI mode | | npm run test:report | Open generated HTML report | | npm run test:headed | Run tests in headed browser | | npm run test:debug | Run tests with Playwright debugger | | npm run demo | Run the MCP server demo (real MCP protocol) | | npm run mcp:start | Start @playwright/mcp standalone server |

---

Reusable Browser Commands

All tests import from tests/fixtures/browser-commands.ts instead of @playwright/test directly. Command names match @playwright/mcp tool names exactly, making the test code portable to any MCP client.

import { test, expect } from './fixtures/browser-commands';

test('example', async ({ cmd }) => {
  await cmd.browser_navigate('/login.html');
  await cmd.browser_type('email-input', 'testuser@test.com');
  await cmd.browser_type('password-input', 'Test@123');
  await cmd.browser_click('login-btn');
  await cmd.browser_expect_url(/dashboard/);
  await cmd.browser_take_screenshot('login-success.png');
});

Full command reference

| Command | MCP Tool | Description | |---------|----------|-------------| | cmd.browser_navigate(url) | browser_navigate | Navigate to URL | | cmd.browser_navigate_back() | browser_navigate_back | Go back in history | | cmd.browser_click(testId) | browser_click | Click element by data-testid | | cmd.browser_type(testId, text) | browser_type | Fill input field | | cmd.browser_clear_and_type(testId, text) | browser_type | Clear then fill | | cmd.browser_select_option(testId, value) | browser_select_option | Select dropdown option | | cmd.browser_check(testId) | browser_click | Check a checkbox | | cmd.browser_uncheck(testId) | browser_click | Uncheck a checkbox | | cmd.browser_take_screenshot(filename?) | browser_take_screenshot | Capture screenshot | | cmd.browser_get_url() | — | Get current URL | | cmd.browser_get_text(testId) | — | Get element text | | cmd.browser_get_value(testId) | — | Get input value | | cmd.browser_is_visible(testId) | browser_snapshot | Returns boolean | | cmd.browser_is_checked(testId) | browser_snapshot | Returns boolean | | cmd.browser_count(testId) | browser_snapshot | Count elements | | cmd.browser_wait_for(testId, state?) | browser_wait_for | Wait for element | | cmd.browser_wait_for_url(pattern) | — | Wait for URL | | cmd.browser_set_storage(key, value) | — (fixture-only) | Set localStorage via page.evaluate | | cmd.browser_evaluate(fn) | — (fixture-only) | Run JS via page.evaluate | | cmd.browser_expect_url(pattern) | — | Assert current URL | | cmd.browser_expect_visible(testId) | — | Assert visible | | cmd.browser_expect_hidden(testId) | — | Assert hidden | | cmd.browser_expect_text(testId, text) | — | Assert text content | | cmd.browser_expect_count(testId, n) | — | Assert element count | | cmd.browser_expect_value(testId, val) | — | Assert input value | | cmd.browser_expect_checked(testId) | — | Assert checkbox state | | cmd.browser_expect_disabled(testId) | — | Assert disabled | | cmd.browser_expect_attr(testId, attr, val) | — | Assert attribute |

---

Demo App — TaskMaster Pro

A fully self-contained web app using localStorage for all data persistence.

| Page | URL | Features | |------|-----|----------| | Login | /login.html | Form validation, password toggle, remember-me | | Register | /register.html | Password strength, validation | | Dashboard | /dashboard.html | Stats, recent tasks, quick-add | | Tasks | /tasks.html | CRUD, filter, search, sort, bulk actions | | Categories | /tasks.html#categories | Create, edit, delete categories | | Profile | /profile.html | Edit name, avatar, change password | | Settings | /settings.html | Theme, notifications, language, export | | 404 | /404.html | Error page |

Default test credentials:

| Email | Password | Role | |-------|----------|------| | testuser@test.com | Test@123 | Test User | | admin@test.com | Admin@123 | Admin User |

Pre-seeded data: 5 tasks (3 active, 2 completed) and 4 categories.

MCP Tools provided by @playwright/mcp

See the full categorized reference in MCP Server Setup → Available MCP tools below. Key tools used in this demo app:

| Tool | Description | |------|-------------| | browser_navigate | Navigate to a URL | | browser_snapshot | Get accessibility tree — returns ARIA refs for click/type targets | | browser_click | Click an element by ARIA ref | | browser_type | Type text into a focused field | | browser_fill_form | Fill multiple form fields in one call | | browser_select_option | Select a dropdown value | | browser_take_screenshot | Capture the current page as an image | | browser_localstorage_set | Write a localStorage key (used for fast auth seeding) | | browser_evaluate | Evaluate JavaScript on the page | | browser_wait_for | Wait for an element or condition | | browser_network_requests | Inspect captured network traffic | | browser_route | Mock API responses for isolated testing | | browser_tabs | List, open, close, or switch tabs | | browser_close | Close the browser |

---

MCP Server Setup

@playwright/mcp exposes browser automation as MCP tools so any MCP-compatible client (Claude Code, Claude Desktop, VS Code, Cursor, Windsurf) can drive a real browser with no screenshots or vision models — just accessibility snapshots.

Claude Code (project-level)

A .mcp.json file is included at the project root. Claude Code picks it up automatically when you open this directory:

{
  "mcpServers": {
    "playwright": {
      "command": "npx",
      "args": ["@playwright/mcp@latest"]
    }
  }
}

Or install it manually via the CLI:

claude mcp add playwright npx @playwright/mcp@latest

Claude Desktop

Add the following to ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows):

{
  "mcpServers": {
    "playwright": {
      "command": "npx",
      "args": ["@playwright/mcp@latest"]
    }
  }
}

VS Code

One-liner via the VS Code CLI:

code --add-mcp '{"name":"playwright","command":"npx","args":["@playwright/mcp@latest"]}'

Or use the included .vscode/mcp.json — VS Code with the Copilot extension picks it up automatically:

{
  "servers": {
    "playwright": {
      "type": "stdio",
      "command": "npx",
      "args": ["@playwright/mcp@latest"]
    }
  }
}

Cursor / Windsurf

Add the same block under mcpServers in their respective settings files.

Configuration options

Append flags to the args array to customise the server:

| Flag | Effect | |------|--------| | --headless | Run browser without a visible window (default: headed) | | --browser <browser> | Browser to use: chrome, firefox, webkit, msedge (default: Chromium) | | --isolated | Keep browser profile in memory; do not persist to disk | | --port <n> | Expose the server over HTTP/SSE instead of stdio | | --host <host> | Host to bind to (default: localhost; use 0.0.0.0 for all interfaces) | | --vision | Use screenshots instead of accessibility snapshots (requires vision model) | | --caps <caps> | Comma-separated capability subset: tabs,pdf,history,wait,files,install | | --device <device> | Emulate a device, e.g. "iPhone 15" | | --viewport-size <size> | Set viewport, e.g. "1280, 720" | | --user-agent <ua> | Override the browser user-agent string | | --storage-state <path> | Load saved auth state (cookies/localStorage) from a JSON file | | --save-trace | Save a Playwright Trace of the session to --output-dir | | --output-dir <path> | Directory for screenshots, PDFs, and traces | | --proxy-server <proxy> | Route traffic through a proxy, e.g. http://myproxy:3128 | | --no-sandbox | Disable the browser sandbox (useful in some CI environments) | | --extension | Connect to an existing browser tab instead of launching a new browser | | --config <path> | Load advanced settings from a JSON config file |

Example — headless Firefox in isolated mode:

{
  "mcpServers": {
    "playwright": {
      "command": "npx",
      "args": ["@playwright/mcp@latest", "--headless", "--browser=firefox", "--isolated"]
    }
  }
}

Standalone server (HTTP transport)

For headless/CI environments where stdio is not suitable:

npx @playwright/mcp@latest --port 8931

Then point your MCP client at http://localhost:8931/mcp using the url form:

{
  "mcpServers": {
    "playwright": {
      "url": "http://localhost:8931/mcp"
    }
  }
}

Available MCP tools

Navigation

| Tool | Description | |------|-------------| | browser_navigate | Navigate to a URL | | browser_navigate_back / browser_navigate_forward | Browser history navigation | | browser_reload | Reload the current page |

Interaction

| Tool | Description | |------|-------------| | browser_click | Click an element by ARIA ref | | browser_check / browser_uncheck | Check or uncheck a checkbox | | browser_type | Type text into a focused field | | browser_press_sequentially | Type text one character at a time (for masked fields) | | browser_fill_form | Fill multiple form fields by their labels in one call | | browser_select_option | Select a dropdown value | | browser_hover | Hover over an element | | browser_drag / browser_drop | Drag-and-drop between elements | | browser_press_key / browser_keydown / browser_keyup | Keyboard key events | | browser_handle_dialog | Accept or dismiss browser dialogs | | browser_file_upload | Upload a file to a file input | | browser_mouse_click_xy / browser_mouse_move_xy / browser_mouse_drag_xy | Pixel-precise mouse control | | browser_mouse_down / browser_mouse_up / browser_mouse_wheel | Low-level mouse events |

Inspection & Assertions

| Tool | Description | |------|-------------| | browser_snapshot | Get the page accessibility tree — primary way to read elements and obtain ARIA refs | | browser_take_screenshot | Capture the current page as an image | | browser_annotate | Capture a screenshot with annotated highlights | | browser_highlight / browser_hide_highlight | Visually highlight an element on the page | | browser_generate_locator | Generate a Playwright locator for a given element | | browser_verify_element_visible | Assert an element is visible | | browser_verify_text_visible | Assert specific text is visible on the page | | browser_verify_list_visible | Assert a list of items is visible | | browser_verify_value | Assert an element's value | | browser_wait_for | Wait for an element or network condition |

Storage

| Tool | Description | |------|-------------| | browser_evaluate | Evaluate a JavaScript expression on the page | | browser_localstorage_get / browser_localstorage_set / browser_localstorage_list | Read/write localStorage | | browser_localstorage_clear / browser_localstorage_delete | Remove localStorage items | | browser_sessionstorage_get / browser_sessionstorage_set / browser_sessionstorage_list | sessionStorage CRUD | | browser_sessionstorage_clear / browser_sessionstorage_delete | Remove sessionStorage items | | browser_cookie_get / browser_cookie_set / browser_cookie_list | Read/write cookies | | browser_cookie_clear / browser_cookie_delete | Remove cookies | | browser_storage_state | Export current cookies and storage to a file | | browser_set_storage_state | Import previously saved storage state |

Network

| Tool | Description | |------|-------------| | browser_network_requests | List all captured network requests | | browser_network_request | Get details for a single network request | | browser_network_clear | Clear the captured request log | | browser_network_state_set | Simulate online/offline/throttled network conditions | | browser_route | Mock requests matching a URL pattern with a custom response | | browser_route_list | List active route mocks | | browser_unroute | Remove a route mock |

Tabs

| Tool | Description | |------|-------------| | browser_tabs | List, create (new), close, or switch to a tab by index |

Console

| Tool | Description | |------|-------------| | browser_console_messages | Get browser console output | | browser_console_clear | Clear the console message log |

Recording & Tracing

| Tool | Description | |------|-------------| | browser_start_tracing / browser_stop_tracing | Record a Playwright Trace | | browser_start_video / browser_stop_video / browser_video_chapter | Record session video | | browser_pdf_save | Save the current page as a PDF |

Misc

| Tool | Description | |------|-------------| | browser_resize | Resize the browser window | | browser_get_config | Get the current server configuration | | browser_run_code_unsafe | Execute an arbitrary Playwright script (RCE-equivalent; trusted clients only) | | browser_close | Close the browser |

How accessibility snapshots work: Instead of returning a screenshot, browser_snapshot returns a structured text tree of all visible elements with their roles, labels, and unique [ref=…] identifiers. LLMs reference these ids when calling browser_click or browser_type — no vision model required.

Verify the server is working

# Start the demo app first
npm run dev

# Then run the MCP demo (connects via stdio and runs 10 browser scenarios)
npm run demo

---

MCP Demo (demo/)

demo/run-demo.ts connects to @playwright/mcp via real MCP stdio protocol using @modelcontextprotocol/sdk. It runs 10 browser scenarios with no external API dependencies.

npm run demo
╔══════════════════════════════════════════════════════════╗
║  Playwright MCP Server — Direct MCP Protocol Demo       ║
╚══════════════════════════════════════════════════════════╝

Target: http://localhost:3000
Transport: stdio (@playwright/mcp subprocess)

Connecting to @playwright/mcp server…
Connected.

Available MCP tools (14):
  • browser_navigate
  • browser_snapshot
  • browser_click
  …

S01  ✓ PASS  Login page loads via MCP browser_navigate
S02  ✓ PASS  Fill login form using browser_type
S03  ✓ PASS  Capture login page screenshot
…

---

Test Coverage

| Area | Tests | TC Range | |------|-------|----------| | Authentication | 25 | TC001–TC025 | | Navigation | 20 | TC026–TC045 | | Dashboard | 12 | TC046–TC057 | | Task Management | 45 | TC058–TC102 | | Categories | 14 | TC103–TC116 | | Profile | 17 | TC117–TC133 | | Settings | 17 | TC134–TC150 | | UI Components | 16 | TC151–TC166 | | Total | 166 | |

---

Project Structure

.
├── demo-app/
│   ├── server.js                  Express static file server (port 3000)
│   └── public/
│       ├── index.html             Entry point (redirect)
│       ├── login.html
│       ├── register.html
│       ├── dashboard.html
│       ├── tasks.html             Tasks + Categories
│       ├── profile.html
│       ├── settings.html
│       ├── 404.html
│       ├── css/styles.css
│       └── js/utils.js            Shared helpers + seed data
├── tests/
│   ├── fixtures/
│   │   └── browser-commands.ts    Reusable MCP-style command fixture
│   ├── helpers/
│   │   └── auth.helper.ts         Fast localStorage-based auth setup
│   ├── auth.spec.ts               TC001–TC025
│   ├── navigation.spec.ts         TC026–TC045
│   ├── dashboard.spec.ts          TC046–TC057
│   ├── tasks.spec.ts              TC058–TC102
│   ├── categories.spec.ts         TC103–TC116
│   ├── profile.spec.ts            TC117–TC133
│   ├── settings.spec.ts           TC134–TC150
│   └── ui-components.spec.ts      TC151–TC166
├── demo/
│   ├── scenarios.ts               10 MCP test scenario definitions
│   └── run-demo.ts                MCP client demo (real MCP protocol)
├── playwright-report/             Generated HTML report (after npm test)
├── playwright.config.ts
├── package.json
└── tsconfig.json

---

Key Design Decisions

| Decision | Rationale | |----------|-----------| | data-testid attributes on all elements | Tests don't break when CSS or text changes | | Reusable browser-commands.ts fixture | Command names match @playwright/mcp tools — tests are portable | | loginViaStorage() helper | Sets session via localStorage.setItem directly — 10× faster than UI login | | Sequential workers (workers: 1) | Prevents localStorage conflicts between parallel tests | | retries: 1 locally, retries: 2 on CI | Handles transient timing issues without masking real failures | | No external API dependencies | MCP demo uses @playwright/mcp directly via stdio protocol |

---

See related servers & alternatives →

Related MCP servers

Browse all →

Related guides

Hand-picked reading to help you choose and use Browser & Scraping servers.