playwright-mcp-server

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Playwright MCP Server

Playwright MCP Server

Skill by ara.so — MCP Skills collection.
The Playwright MCP server provides browser automation capabilities through the Model Context Protocol. It enables LLMs to interact with web pages using Playwright's accessibility tree instead of screenshots, making it fast, lightweight, and deterministic.
ara.so开发的Skill — MCP Skills集合。
Playwright MCP服务器通过Model Context Protocol(模型上下文协议)提供浏览器自动化能力。它让LLM能够借助Playwright的无障碍树与网页交互,而非依赖截图,兼具快速、轻量和确定性的特点。

What It Does

功能介绍

  • Structured interaction: Uses accessibility snapshots instead of vision models
  • Browser automation: Navigate, click, fill forms, extract data from web pages
  • Multi-browser support: Chromium, Firefox, and WebKit
  • LLM-optimized: Returns structured data that's easy for language models to parse
  • 结构化交互:使用无障碍快照而非视觉模型
  • 浏览器自动化:实现网页导航、点击、表单填写、数据提取
  • 多浏览器支持:支持Chromium、Firefox和WebKit
  • LLM优化:返回易于大语言模型解析的结构化数据

Installation

安装步骤

Add to your MCP client configuration:
json
{
  "mcpServers": {
    "playwright": {
      "command": "npx",
      "args": ["@playwright/mcp@latest"]
    }
  }
}
将以下配置添加到你的MCP客户端配置文件中:
json
{
  "mcpServers": {
    "playwright": {
      "command": "npx",
      "args": ["@playwright/mcp@latest"]
    }
  }
}

Configuration Options

配置选项

Configure via
args
in your MCP config:
json
{
  "mcpServers": {
    "playwright": {
      "command": "npx",
      "args": [
        "@playwright/mcp@latest",
        "--allowed-hosts", "example.com,*.trusted-domain.com",
        "--browser", "chromium",
        "--headless", "false"
      ]
    }
  }
}
Common options:
  • --allowed-hosts <hosts...>
    : Comma-separated hosts (or
    *
    to disable check). Env:
    PLAYWRIGHT_MCP_ALLOWED_HOSTS
  • --browser <browser>
    : Choose
    chromium
    ,
    firefox
    , or
    webkit
  • --headless <true|false>
    : Run browser in headless mode
  • --timeout <ms>
    : Default timeout for operations
可通过MCP配置中的
args
参数进行配置:
json
{
  "mcpServers": {
    "playwright": {
      "command": "npx",
      "args": [
        "@playwright/mcp@latest",
        "--allowed-hosts", "example.com,*.trusted-domain.com",
        "--browser", "chromium",
        "--headless", "false"
      ]
    }
  }
}
常用选项:
  • --allowed-hosts <hosts...>
    :逗号分隔的允许访问的主机(或使用
    *
    禁用检查)。环境变量:
    PLAYWRIGHT_MCP_ALLOWED_HOSTS
  • --browser <browser>
    :选择
    chromium
    firefox
    webkit
    浏览器
  • --headless <true|false>
    :以无头模式运行浏览器
  • --timeout <ms>
    :操作的默认超时时间

Available Tools

可用工具

The MCP server exposes these tools to LLM agents:
该MCP服务器为LLM Agent提供以下工具:

1.
playwright_navigate

1.
playwright_navigate

Navigate to a URL and get accessibility snapshot.
Parameters:
  • url
    (string, required): URL to navigate to
Example usage:
typescript
// Agent will call this tool
{
  "url": "https://example.com"
}
Returns: Accessibility tree snapshot of the page
导航至指定URL并获取无障碍快照。
参数:
  • url
    (字符串,必填):要导航的URL
使用示例:
typescript
// Agent将调用此工具
{
  "url": "https://example.com"
}
返回结果: 页面的无障碍树快照

2.
playwright_click

2.
playwright_click

Click an element identified by its accessibility role and name.
Parameters:
  • selector
    (string, required): Element selector (role, text, or CSS)
  • button
    (string, optional):
    left
    ,
    right
    , or
    middle
    (default:
    left
    )
Example usage:
typescript
{
  "selector": "button[name='Submit']",
  "button": "left"
}
通过无障碍角色和名称定位元素并点击。
参数:
  • selector
    (字符串,必填):元素选择器(角色、文本或CSS选择器)
  • button
    (字符串,可选):点击按钮类型,可选
    left
    right
    middle
    (默认:
    left
使用示例:
typescript
{
  "selector": "button[name='Submit']",
  "button": "left"
}

3.
playwright_fill

3.
playwright_fill

Fill an input field with text.
Parameters:
  • selector
    (string, required): Input field selector
  • value
    (string, required): Text to fill
Example usage:
typescript
{
  "selector": "input[name='email']",
  "value": "user@example.com"
}
在输入框中填充文本。
参数:
  • selector
    (字符串,必填):输入框选择器
  • value
    (字符串,必填):要填充的文本
使用示例:
typescript
{
  "selector": "input[name='email']",
  "value": "user@example.com"
}

4.
playwright_screenshot

4.
playwright_screenshot

Take a screenshot of the page or element.
Parameters:
  • selector
    (string, optional): Element to screenshot (defaults to full page)
  • path
    (string, optional): File path to save screenshot
Example usage:
typescript
{
  "selector": "div.main-content",
  "path": "./screenshots/content.png"
}
截取页面或指定元素的截图。
参数:
  • selector
    (字符串,可选):要截图的元素(默认截取整个页面)
  • path
    (字符串,可选):保存截图的文件路径
使用示例:
typescript
{
  "selector": "div.main-content",
  "path": "./screenshots/content.png"
}

5.
playwright_evaluate

5.
playwright_evaluate

Execute JavaScript in the page context.
Parameters:
  • expression
    (string, required): JavaScript to execute
Example usage:
typescript
{
  "expression": "document.title"
}
在页面上下文中执行JavaScript代码。
参数:
  • expression
    (字符串,必填):要执行的JavaScript代码
使用示例:
typescript
{
  "expression": "document.title"
}

6.
playwright_snapshot

6.
playwright_snapshot

Get current accessibility snapshot without navigation.
Example usage:
typescript
{}
无需导航即可获取当前页面的无障碍快照。
使用示例:
typescript
{}

Common Patterns

常见使用场景

Form Automation

表单自动化

typescript
// Navigate to login page
await playwright_navigate({ url: "https://app.example.com/login" });

// Fill credentials
await playwright_fill({
  selector: "input[name='username']",
  value: "user@example.com"
});

await playwright_fill({
  selector: "input[type='password']",
  value: process.env.USER_PASSWORD  // Use env vars for secrets
});

// Submit form
await playwright_click({
  selector: "button[type='submit']"
});

// Verify login
const snapshot = await playwright_snapshot({});
// Parse snapshot to confirm successful login
typescript
// 导航至登录页面
await playwright_navigate({ url: "https://app.example.com/login" });

// 填写凭证
await playwright_fill({
  selector: "input[name='username']",
  value: "user@example.com"
});

await playwright_fill({
  selector: "input[type='password']",
  value: process.env.USER_PASSWORD  // 使用环境变量存储敏感信息
});

// 提交表单
await playwright_click({
  selector: "button[type='submit']"
});

// 验证登录状态
const snapshot = await playwright_snapshot({});
// 解析快照以确认登录成功

Data Extraction

数据提取

typescript
// Navigate to data page
await playwright_navigate({ url: "https://example.com/products" });

// Extract product information
const products = await playwright_evaluate({
  expression: `
    Array.from(document.querySelectorAll('.product')).map(p => ({
      name: p.querySelector('.name')?.textContent,
      price: p.querySelector('.price')?.textContent
    }))
  `
});
typescript
// 导航至数据页面
await playwright_navigate({ url: "https://example.com/products" });

// 提取产品信息
const products = await playwright_evaluate({
  expression: `
    Array.from(document.querySelectorAll('.product')).map(p => ({
      name: p.querySelector('.name')?.textContent,
      price: p.querySelector('.price')?.textContent
    }))
  `
});

Multi-Step Workflow

多步骤工作流

typescript
// Search flow
await playwright_navigate({ url: "https://example.com" });

await playwright_fill({
  selector: "input[placeholder='Search']",
  value: "playwright automation"
});

await playwright_click({
  selector: "button[aria-label='Search']"
});

// Wait for results by getting snapshot
const results = await playwright_snapshot({});

// Click first result
await playwright_click({
  selector: "a.result-item:first-child"
});

// Capture final page
await playwright_screenshot({
  path: "./evidence/result-page.png"
});
typescript
// 搜索流程
await playwright_navigate({ url: "https://example.com" });

await playwright_fill({
  selector: "input[placeholder='Search']",
  value: "playwright automation"
});

await playwright_click({
  selector: "button[aria-label='Search']"
});

// 通过获取快照等待结果加载
const results = await playwright_snapshot({});

// 点击第一个搜索结果
await playwright_click({
  selector: "a.result-item:first-child"
});

// 捕获最终页面
await playwright_screenshot({
  path: "./evidence/result-page.png"
});

Testing Accessibility

无障碍测试

typescript
// Navigate to page under test
await playwright_navigate({ url: "https://myapp.com/dashboard" });

// Get accessibility tree
const snapshot = await playwright_snapshot({});

// The snapshot will show:
// - Missing ARIA labels
// - Elements without proper roles
// - Navigation structure
// Parse snapshot to validate accessibility
typescript
// 导航至待测试页面
await playwright_navigate({ url: "https://myapp.com/dashboard" });

// 获取无障碍树
const snapshot = await playwright_snapshot({});

// 快照将展示:
// - 缺失的ARIA标签
// - 未设置正确角色的元素
// - 导航结构
// 解析快照以验证无障碍性

Accessibility Selectors

无障碍选择器

Playwright MCP uses accessible selectors. Prefer:
typescript
// Good: Role-based selectors
"button[name='Submit']"
"link[name='Documentation']"
"textbox[name='Email']"

// Good: ARIA attributes
"[aria-label='Close dialog']"
"[role='navigation']"

// Okay: Text content
"text=Click here"

// Last resort: CSS selectors
"div.modal > button.close"
Playwright MCP使用无障碍选择器,优先选择以下类型:
typescript
// 推荐:基于角色的选择器
"button[name='Submit']"
"link[name='Documentation']"
"textbox[name='Email']"

// 推荐:ARIA属性选择器
"[aria-label='Close dialog']"
"[role='navigation']"

// 可用:文本内容选择器
"text=Click here"

// 最后选择:CSS选择器
"div.modal > button.close"

Working with Snapshots

快照使用指南

Accessibility snapshots are structured representations of the page:
typescript
// Example snapshot structure
{
  "role": "WebArea",
  "name": "Example Page",
  "children": [
    {
      "role": "button",
      "name": "Submit",
      "focusable": true
    },
    {
      "role": "textbox",
      "name": "Email",
      "value": "user@example.com"
    }
  ]
}
Use snapshots to:
  • Understand page structure
  • Identify interactive elements
  • Validate content presence
  • Find navigation paths
无障碍快照是页面的结构化表示:
typescript
// 示例快照结构
{
  "role": "WebArea",
  "name": "Example Page",
  "children": [
    {
      "role": "button",
      "name": "Submit",
      "focusable": true
    },
    {
      "role": "textbox",
      "name": "Email",
      "value": "user@example.com"
    }
  ]
}
可使用快照完成以下操作:
  • 理解页面结构
  • 识别交互元素
  • 验证内容存在性
  • 查找导航路径

Troubleshooting

问题排查

Host Not Allowed

主机未被允许

Error:
Host not allowed: example.com
Solution: Add to allowed hosts:
json
{
  "args": [
    "@playwright/mcp@latest",
    "--allowed-hosts", "example.com,*.example.com"
  ]
}
Or disable checks (development only):
json
{
  "args": ["@playwright/mcp@latest", "--allowed-hosts", "*"]
}
错误信息:
Host not allowed: example.com
解决方案: 将主机添加到允许列表:
json
{
  "args": [
    "@playwright/mcp@latest",
    "--allowed-hosts", "example.com,*.example.com"
  ]
}
或禁用检查(仅开发环境使用):
json
{
  "args": ["@playwright/mcp@latest", "--allowed-hosts", "*"]
}

Element Not Found

元素未找到

Error:
Element not found: button[name='Submit']
Solutions:
  1. Get current snapshot to see available elements:
    typescript
    const snapshot = await playwright_snapshot({});
  2. Use more flexible selectors:
    typescript
    // Instead of exact match
    "button[name*='submit']"  // Contains 'submit'
    "text=/submit/i"           // Case-insensitive regex
  3. Wait for element by evaluating:
    typescript
    await playwright_evaluate({
      expression: `
        new Promise(resolve => {
          const check = () => {
            if (document.querySelector('button[name="Submit"]')) {
              resolve(true);
            } else {
              setTimeout(check, 100);
            }
          };
          check();
        })
      `
    });
错误信息:
Element not found: button[name='Submit']
解决方案:
  1. 获取当前快照查看可用元素:
    typescript
    const snapshot = await playwright_snapshot({});
  2. 使用更灵活的选择器:
    typescript
    // 替代精确匹配
    "button[name*='submit']"  // 包含'submit'的名称
    "text=/submit/i"           // 不区分大小写的正则匹配
  3. 通过执行代码等待元素加载:
    typescript
    await playwright_evaluate({
      expression: `
        new Promise(resolve => {
          const check = () => {
            if (document.querySelector('button[name="Submit"]')) {
              resolve(true);
            } else {
              setTimeout(check, 100);
            }
          };
          check();
        })
      `
    });

Timeout Issues

超时问题

Error:
Timeout waiting for element
Solution: Increase timeout:
json
{
  "args": ["@playwright/mcp@latest", "--timeout", "60000"]
}
Or wait explicitly:
typescript
await playwright_evaluate({
  expression: "new Promise(r => setTimeout(r, 2000))"
});
错误信息:
Timeout waiting for element
解决方案: 增加超时时间:
json
{
  "args": ["@playwright/mcp@latest", "--timeout", "60000"]
}
或显式等待:
typescript
await playwright_evaluate({
  expression: "new Promise(r => setTimeout(r, 2000))"
});

Browser Not Launching

浏览器无法启动

Error:
Failed to launch browser
Solutions:
  1. Check browser installation:
    bash
    npx playwright install chromium
  2. Try different browser:
    json
    {
      "args": ["@playwright/mcp@latest", "--browser", "firefox"]
    }
  3. Run in headed mode for debugging:
    json
    {
      "args": ["@playwright/mcp@latest", "--headless", "false"]
    }
错误信息:
Failed to launch browser
解决方案:
  1. 检查浏览器安装情况:
    bash
    npx playwright install chromium
  2. 尝试其他浏览器:
    json
    {
      "args": ["@playwright/mcp@latest", "--browser", "firefox"]
    }
  3. 以有头模式运行以便调试:
    json
    {
      "args": ["@playwright/mcp@latest", "--headless", "false"]
    }

Best Practices

最佳实践

  1. Use environment variables for secrets:
    typescript
    // Never hardcode credentials
    await playwright_fill({
      selector: "input[type='password']",
      value: process.env.PASSWORD
    });
  2. Take snapshots for debugging:
    typescript
    // Before and after critical actions
    const beforeSnapshot = await playwright_snapshot({});
    await playwright_click({ selector: "button[name='Delete']" });
    const afterSnapshot = await playwright_snapshot({});
  3. Prefer accessibility selectors:
    typescript
    // Robust to UI changes
    "button[name='Save']"
    // vs fragile CSS
    "div.container > div:nth-child(2) > button.primary"
  4. Handle navigation timing:
    typescript
    await playwright_navigate({ url: "https://example.com" });
    // Snapshot after navigate includes wait for load
    const snapshot = await playwright_snapshot({});
  5. Combine tools for complex workflows:
    typescript
    // Navigate → Inspect → Act → Verify
    await playwright_navigate({ url: "..." });
    const structure = await playwright_snapshot({});
    await playwright_fill({ ... });
    await playwright_click({ ... });
    const result = await playwright_snapshot({});
  1. 使用环境变量存储敏感信息:
    typescript
    // 切勿硬编码凭证
    await playwright_fill({
      selector: "input[type='password']",
      value: process.env.PASSWORD
    });
  2. 通过快照进行调试:
    typescript
    // 在关键操作前后获取快照
    const beforeSnapshot = await playwright_snapshot({});
    await playwright_click({ selector: "button[name='Delete']" });
    const afterSnapshot = await playwright_snapshot({});
  3. 优先使用无障碍选择器:
    typescript
    // 对UI变更更鲁棒
    "button[name='Save']"
    // 对比脆弱的CSS选择器
    "div.container > div:nth-child(2) > button.primary"
  4. 处理导航时序:
    typescript
    await playwright_navigate({ url: "https://example.com" });
    // 导航后的快照会等待页面加载完成
    const snapshot = await playwright_snapshot({});
  5. 组合工具完成复杂工作流:
    typescript
    // 导航 → 检查 → 操作 → 验证
    await playwright_navigate({ url: "..." });
    const structure = await playwright_snapshot({});
    await playwright_fill({ ... });
    await playwright_click({ ... });
    const result = await playwright_snapshot({});

vs Playwright CLI

与Playwright CLI对比

Use Playwright MCP when:
  • Building chat-based automation agents
  • Need persistent browser state across tool calls
  • Iterative reasoning over page structure
  • Long-running autonomous workflows
  • Working with coding agents that favor CLI tools
  • Token efficiency is critical
  • Managing large codebases alongside automation
  • Need concise, purpose-built commands
选择Playwright MCP的场景:
  • 构建基于聊天的自动化Agent
  • 需要在工具调用间保持浏览器持久状态
  • 需要对页面结构进行迭代推理
  • 长运行的自主工作流
选择**Playwright CLI + SKILLS**的场景:
  • 与偏好CLI工具的编码Agent配合使用
  • 对Token效率要求较高
  • 在自动化同时管理大型代码库
  • 需要简洁、针对性的命令