Playwright MCP Server
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
```bash
npm install
npx playwright install chromium
```
### 2. Start the demo app
```bash
npm run dev
# → http://localhost:3000
```
### 3. Run all tests and generate HTML report
```bash
npm test
npm run test:report # opens playwright-report/index.html
```
### 4. Run the MCP server demo (browser driven via MCP protocol)
```bash
npm run demo
# Starts @playwright/mcp server, connects as MCP client, runs 10 scenarios
```
### 5. Interactive test runner
```bash
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.
```typescript
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](#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:
```json
{
"mcpServers": {
"playwright": {
"command": "npx",
"args": ["@playwright/mcp@latest"]
}
}
}
```
Or install it manually via the CLI:
```bash
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):
```json
{
"mcpServers": {
"playwright": {
"command": "npx",
"args": ["@playwright/mcp@latest"]
}
}
}
```
### VS Code
One-liner via the VS Code CLI:
```bash
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:
```json
{
"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:
```json
{
"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:
```bash
npx @playwright/mcp@latest --port 8931
```
Then point your MCP client at `http://localhost:8931/mcp` using the `url` form:
```json
{
"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
```bash
# 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 |
---