MCP Playwright 自动化调试实战指南

本文最后更新于 2026年4月6日 凌晨

MCP Playwright 自动化调试实战指南

作者: LWMS2 开发团队
日期: 2026-04-06
版本: v1.0
适用对象: 前端开发者、测试工程师、全栈开发者


📖 目录


什么是 MCP Playwright

MCP (Model Context Protocol) 是一种标准化的协议,允许 AI 助手与外部工具进行交互。Playwright 是微软开发的现代化浏览器自动化工具。

MCP Playwright 将两者结合,让 AI 助手能够:

  • 🌐 控制真实浏览器(Chrome、Edge、Firefox)
  • 🔍 捕获页面元素、截图、录制视频
  • 📊 监控网络请求和控制台日志
  • ⚡ 执行 JavaScript 代码
  • 🎯 模拟用户交互(点击、输入、滚动等)

与传统调试方式的对比

特性 手动调试 传统自动化脚本 MCP Playwright
启动速度 慢(手动操作) 中等 快(AI 驱动)
灵活性 低(需预编写) 高(动态生成)
学习成本
问题定位 依赖经验 依赖脚本质量 AI 智能分析
文档化 手动记录 部分自动 完全自动化

为什么选择 MCP Playwright

1. 智能化调试

AI 可以:

  • 自动分析错误信息
  • 智能选择最优的选择器
  • 动态调整调试策略
  • 自动生成调试报告

2. 完整的上下文感知

1
2
3
4
5
6
# AI 可以看到:
- 当前页面 DOM 结构
- 控制台日志(console.log, error, warning)
- 网络请求和响应
- 页面截图和视频
- localStorage/sessionStorage 状态

3. 高效的迭代循环

1
2
3
发现问题 → AI 分析 → 生成修复方案 → 自动验证 → 输出报告
↓ ↑
└──────────── 快速迭代,无需手动干预 ──────────┘

4. 可复用的调试资产

每次调试都会生成:

  • 📸 关键步骤截图
  • 📹 完整操作视频
  • 📄 详细的 JSON 日志
  • 📝 结构化的问题报告

环境准备

1. 安装 Playwright

1
2
3
4
5
6
7
# 安装 Playwright Python 库
pip install playwright

# 安装浏览器驱动
playwright install chromium
playwright install chrome # 可选:使用系统 Chrome
playwright install msedge # 可选:使用系统 Edge

2. 配置 MCP 服务器

在项目根目录创建 .lingma/mcps/playwright/ 目录,确保包含以下文件:

1
2
3
4
5
6
7
8
├── SERVER_METADATA.json    # 服务器元数据
└── tools/
├── browser_navigate.json
├── browser_click.json
├── browser_type.json
├── browser_snapshot.json
├── browser_take_screenshot.json
└── ... (其他工具定义)

3. 验证安装

1
2
3
4
5
6
7
8
from playwright.sync_api import sync_playwright

with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page()
page.goto("https://example.com")
print(page.title())
browser.close()

预期输出:Example Domain


核心功能详解

1. 浏览器导航

1
2
3
4
5
6
7
8
# 基本导航
page.goto("http://localhost:5173/login")

# 等待页面完全加载
page.wait_for_load_state("networkidle")

# 等待特定元素出现
page.wait_for_selector("#username", timeout=5000)

最佳实践:

  • ✅ 始终使用 wait_for_load_state() 确保页面加载完成
  • ✅ 设置合理的超时时间(默认 30 秒)
  • ✅ 对于 SPA 应用,等待关键元素而非网络空闲

2. 元素定位与交互

多种选择器策略

1
2
3
4
5
6
7
8
9
10
11
12
13
# CSS 选择器(推荐)
page.locator('input[name="username"]')
page.locator('.btn-primary')
page.locator('#login-form > button[type="submit"]')

# XPath(复杂场景)
page.locator('//button[contains(text(), "登录")]')

# 文本选择器
page.locator('text=忘记密码')

# 组合选择器(提高准确性)
page.locator('input[name="username"], input[id="username"]').first

常见交互操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 填写表单
page.fill('input[name="username"]', 'admin')
page.fill('input[name="password"]', 'admin123')

# 点击按钮
page.click('button[type="submit"]')

# 下拉框选择
page.select_option('select#warehouse', 'WH001')

# 上传文件
page.set_input_files('input[type="file"]', 'test.pdf')

# 键盘操作
page.press('input#search', 'Enter')
page.press('body', 'Control+A') # 全选

3. 截图与录制

1
2
3
4
5
6
7
8
9
10
11
12
# 全屏截图
page.screenshot(path='full_page.png', full_page=True)

# 元素截图
element = page.locator('#error-message')
element.screenshot(path='error_element.png')

# 录制视频(在 context 级别配置)
context = browser.new_context(
record_video_dir='videos/',
record_video_size={'width': 1920, 'height': 1080}
)

截图命名规范:

1
2
3
4
5
debug_01_initial.png      # 初始状态
debug_02_filled.png # 填写完成后
debug_03_after_click.png # 点击后
debug_04_error.png # 错误状态
debug_05_success.png # 成功状态

4. 日志捕获

控制台日志

1
2
3
4
5
6
7
8
9
10
11
console_messages = []

def handle_console(msg):
console_messages.append({
'type': msg.type, # log, error, warning, info
'text': msg.text,
'location': f"{msg.location.get('url', '')}:{msg.location.get('lineNumber', '')}"
})
print(f"[Console {msg.type}] {msg.text}")

page.on("console", handle_console)

网络请求监控

1
2
3
4
5
6
7
8
9
10
11
12
network_requests = []

def handle_response(response):
network_requests.append({
'url': response.url,
'method': response.request.method,
'status': response.status,
'headers': dict(response.headers),
'timestamp': time.time()
})

page.on("response", handle_response)

5. JavaScript 执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 获取 localStorage
auth_data = page.evaluate("""() => {
return {
token: localStorage.getItem('token'),
user: localStorage.getItem('user')
};
}""")

# 修改页面状态
page.evaluate("""() => {
document.body.style.backgroundColor = 'red';
}""")

# 调用页面函数
result = page.evaluate("""(arg1, arg2) => {
return window.myFunction(arg1, arg2);
}""", "value1", "value2")

实战案例:登录问题排查

问题背景

用户报告无法登录系统,提示”登录失败,请检查用户名和密码”。

调试流程

Step 1: 创建调试脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
"""
登录功能调试脚本 - debug_login.py
"""
from playwright.sync_api import sync_playwright
import time
import json

def debug_login():
print("="*80)
print("🔍 开始调试登录功能")
print("="*80)

with sync_playwright() as p:
# 1. 启动浏览器
browser = p.chromium.launch(
headless=False,
channel="chrome",
slow_mo=500,
args=[
'--no-sandbox',
'--disable-infobars',
'--disable-extensions'
]
)

context = browser.new_context(viewport={'width': 1920, 'height': 1080})
page = context.new_page()

# 2. 启用日志捕获
console_messages = []
network_requests = []

page.on("console", lambda msg: console_messages.append({
'type': msg.type,
'text': msg.text
}))

page.on("response", lambda res: network_requests.append({
'url': res.url,
'status': res.status,
'method': res.request.method
}))

# 3. 导航到登录页
print("\n→ 导航到登录页面...")
page.goto('http://localhost:5173/login')
page.wait_for_load_state('networkidle')
time.sleep(2)

# 4. 截图:初始状态
page.screenshot(path='debug_01_initial.png', full_page=True)
print("📸 已保存初始状态截图")

# 5. 检查页面元素
print("\n🔍 检查页面元素...")
username_input = page.locator('input[name="username"]').first
password_input = page.locator('input[name="password"]').first
login_button = page.locator('button[type="submit"]').first

assert username_input.count() > 0, "❌ 未找到用户名输入框"
assert password_input.count() > 0, "❌ 未找到密码输入框"
assert login_button.count() > 0, "❌ 未找到登录按钮"
print("✓ 所有必需元素都存在")

# 6. 填写表单
print("\n→ 填写用户名: admin")
username_input.fill('admin')

print("→ 填写密码: admin123")
password_input.fill('admin123')
time.sleep(1)

# 7. 截图:填写完成
page.screenshot(path='debug_02_filled.png', full_page=True)

# 8. 点击登录
print("\n→ 点击登录按钮...")
login_button.click()
time.sleep(3)

# 9. 截图:点击后
page.screenshot(path='debug_03_after_click.png', full_page=True)

# 10. 检查结果
current_url = page.url
print(f"\n→ 当前 URL: {current_url}")

if '/login' in current_url:
print("⚠️ 仍在登录页面,登录可能失败")

# 检查错误提示
error_element = page.locator('.bg-red-50').first
if error_element.count() > 0:
error_text = error_element.inner_text()
print(f"❌ 错误提示: {error_text}")
page.screenshot(path='debug_04_error.png', full_page=True)
else:
print("✅ 已跳转到首页,登录成功")
page.screenshot(path='debug_05_success.png', full_page=True)

# 11. 保存调试信息
debug_info = {
'console_messages': console_messages[-20:],
'network_requests': [r for r in network_requests if 'api' in r['url']],
'current_url': current_url,
'timestamp': time.time()
}

with open('debug_login_info.json', 'w', encoding='utf-8') as f:
json.dump(debug_info, f, ensure_ascii=False, indent=2)

print("\n📄 调试信息已保存到 debug_login_info.json")
print("\n按 Enter 关闭浏览器...")
input()

browser.close()

if __name__ == '__main__':
debug_login()

Step 2: 运行调试脚本

1
2
cd backend
python scripts/debug_login.py

Step 3: 分析输出

发现的关键信息:

1
2
3
4
5
6
[Console error] Access to XMLHttpRequest at 'http://localhost:8000/api/v1/auth/login' 
from origin 'http://localhost:5173' has been blocked by CORS policy

[Console error] Failed to load resource: net::ERR_FAILED

❌ 错误提示: 登录失败,请检查用户名和密码

后端日志显示:

1
2
3
ValueError: password cannot be longer than 72 bytes
File "app/core/security.py", line 26, in verify_password
return pwd_context.verify(plain_password, hashed_password)

Step 4: 定位根本原因

通过综合分析:

  1. CORS 错误 - 实际是表象,请求被后端拒绝
  2. bcrypt 兼容性问题 - passlib 与新版 bcrypt 不兼容
  3. 根本原因 - 密码验证逻辑崩溃导致返回错误响应

Step 5: 实施修复

修改 backend/app/core/security.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 修改前:使用 passlib
from passlib.context import CryptContext
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

def verify_password(plain_password: str, hashed_password: str) -> bool:
return pwd_context.verify(plain_password, hashed_password)

# 修改后:直接使用 bcrypt
import bcrypt

def verify_password(plain_password: str, hashed_password: str) -> bool:
try:
return bcrypt.checkpw(
plain_password.encode('utf-8'),
hashed_password.encode('utf-8')
)
except Exception:
return False

重置密码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import bcrypt
from app.db.session import SessionLocal
from app.models.user import User

db = SessionLocal()
user = db.query(User).filter(User.username == "admin").first()

if user:
password = b'admin123'
salt = bcrypt.gensalt()
hashed = bcrypt.hashpw(password, salt)
user.password_hash = hashed.decode('utf-8')
db.commit()
print("✅ 密码更新成功")

Step 6: 验证修复

重新运行调试脚本,确认:

  • ✅ 无 CORS 错误
  • ✅ 无控制台错误
  • ✅ 成功跳转到首页
  • ✅ localStorage 中有 token

最佳实践

1. 浏览器配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# ✅ 推荐配置
browser = p.chromium.launch(
headless=False, # 调试时设为 False,便于观察
channel="chrome", # 使用系统 Chrome(更稳定)
slow_mo=500, # 放慢操作,便于调试
args=[
'--no-sandbox',
'--disable-infobars',
'--disable-extensions',
'--disable-popup-blocking'
]
)

# ❌ 避免的配置
browser = p.chromium.launch(
headless=True, # 调试时看不到页面
slow_mo=0, # 太快,难以观察
args=[] # 缺少必要的禁用参数
)

2. 元素定位策略

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# ✅ 优先级顺序
# 1. 使用 name/id 属性(最稳定)
page.locator('input[name="username"]')
page.locator('#login-button')

# 2. 使用 data-testid(专为测试设计)
page.locator('[data-testid="submit-btn"]')

# 3. 使用 CSS 类名(注意可能的变化)
page.locator('.btn.btn-primary')

# 4. 使用文本内容(多语言场景需注意)
page.locator('text=登录')

# 5. 使用 XPath(最后手段)
page.locator('//div[@class="form"]/input[1]')

# ❌ 避免的做法
page.locator('div > div > div > button') # 脆弱的层级选择器
page.locator(':nth-child(3)') # 位置依赖,易失效

3. 等待策略

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# ✅ 推荐的等待方式
# 1. 等待网络空闲
page.wait_for_load_state('networkidle')

# 2. 等待元素可见
page.wait_for_selector('#element', state='visible')

# 3. 等待元素可点击
page.wait_for_selector('#button', state='enabled')

# 4. 等待 API 响应
with page.expect_response("**/api/login") as response_info:
page.click('#login-btn')
response = response_info.value
assert response.status == 200

# ❌ 避免的做法
time.sleep(5) # 硬编码等待,不可靠且浪费时间

4. 错误处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# ✅ 完善的错误处理
try:
page.goto('http://localhost:5173/login', timeout=10000)
except TimeoutError:
print("❌ 页面加载超时")
page.screenshot(path='error_timeout.png')
raise
except Exception as e:
print(f"❌ 未知错误: {e}")
page.screenshot(path='error_unknown.png')
raise

# ✅ 断言验证
username_input = page.locator('input[name="username"]')
assert username_input.count() > 0, "用户名输入框不存在"
assert username_input.is_visible(), "用户名输入框不可见"
assert username_input.is_enabled(), "用户名输入框被禁用"

5. 资源清理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# ✅ 确保资源释放
def run_test():
browser = None
try:
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
# ... 测试逻辑
finally:
if browser:
browser.close()
print("✅ 浏览器已关闭")

# 或使用 context manager
with sync_playwright() as p:
browser = p.chromium.launch()
try:
# ... 测试逻辑
finally:
browser.close()

常见问题与解决方案

Q1: 浏览器启动失败

症状:

1
playwright._impl._errors.Error: BrowserType.launch: Executable doesn't exist

解决:

1
2
3
# 重新安装浏览器
playwright install chromium
playwright install chrome

Q2: 元素找不到

症状:

1
TimeoutError: locator.click: Timeout 30000ms exceeded

解决:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 1. 检查选择器是否正确
print(page.content()) # 打印页面 HTML

# 2. 截图查看当前状态
page.screenshot(path='debug_current.png')

# 3. 尝试多种选择器
element = page.locator('input[name="username"], input#username, .username-input').first

# 4. 增加等待时间
page.wait_for_selector('input[name="username"]', timeout=10000)

# 5. 检查是否在 iframe 中
frame = page.frame_locator('iframe#content-frame')
frame.locator('input[name="username"]').fill('admin')

Q3: CORS 跨域错误

症状:

1
Access to XMLHttpRequest has been blocked by CORS policy

解决:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 方法1:检查后端 CORS 配置
# backend/app/core/config.py
BACKEND_CORS_ORIGINS = [
"http://localhost:5173",
"http://127.0.0.1:5173"
]

# 方法2:使用 Playwright 绕过 CORS(仅用于调试)
context = browser.new_context(
bypass_csp=True, # 绕过 Content Security Policy
ignore_https_errors=True
)

# 方法3:检查后端是否正常运行
import requests
response = requests.get('http://localhost:8000/health')
print(response.status_code) # 应该是 200

Q4: 异步操作不同步

症状:

1
Element is not attached to the DOM

解决:

1
2
3
4
5
6
7
8
9
10
# ✅ 等待元素稳定后再操作
element = page.locator('#dynamic-element')
element.wait_for(state='attached')
element.wait_for(state='visible')
element.click()

# ✅ 使用 expect 断言(自动重试)
from playwright.sync_api import expect
expect(element).to_be_visible(timeout=5000)
element.click()

Q5: 内存泄漏

症状:

1
浏览器占用内存持续增长

解决:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# ✅ 定期清理上下文
for test_case in test_cases:
context = browser.new_context()
page = context.new_page()
try:
# 执行测试
run_test(page)
finally:
context.close() # 关闭上下文释放资源

# ✅ 限制并发数量
from concurrent.futures import ThreadPoolExecutor

with ThreadPoolExecutor(max_workers=3) as executor:
futures = [executor.submit(run_test, url) for url in urls]
for future in futures:
future.result()

高级技巧

1. 自定义认证状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 保存登录状态
context = browser.new_context()
page = context.new_page()
page.goto('http://localhost:5173/login')
page.fill('input[name="username"]', 'admin')
page.fill('input[name="password"]', 'admin123')
page.click('button[type="submit"]')
page.wait_for_url('**/')

# 保存状态
context.storage_state(path='auth_state.json')

# 在其他测试中复用
new_context = browser.new_context(storage_state='auth_state.json')
new_page = new_context.new_page()
new_page.goto('http://localhost:5173/dashboard')
# 已经处于登录状态

2. 网络拦截与 Mock

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 拦截并修改 API 响应
page.route('**/api/users', lambda route: route.fulfill(
status=200,
content_type='application/json',
body=json.dumps([
{'id':1, 'name': 'Mock User'}
])
))

# 阻止某些请求
page.route('**/analytics/**', lambda route: route.abort())

# 记录所有请求
requests_log = []
page.route('**/*', lambda route: (
requests_log.append({
'url': route.request.url,
'method': route.request.method
}),
route.continue_()
))

3. 视觉回归测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 截图并与基准对比
from PIL import Image
import numpy as np

def compare_screenshots(baseline_path, current_path, threshold=0.01):
"""比较两张截图的差异"""
baseline = np.array(Image.open(baseline_path))
current = np.array(Image.open(current_path))

if baseline.shape != current.shape:
return False, "尺寸不匹配"

diff = np.mean(np.abs(baseline.astype(float) - current.astype(float))) / 255
is_similar = diff < threshold

return is_similar, f"差异度: {diff:.4f}"

# 使用
page.screenshot(path='current.png')
is_similar, message = compare_screenshots('baseline.png', 'current.png')
print(message)

4. 性能监控

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 监控页面性能指标
performance_metrics = page.evaluate("""() => {
const timing = performance.getEntriesByType('navigation')[0];
return {
dnsLookup: timing.domainLookupEnd - timing.domainLookupStart,
tcpConnection: timing.connectEnd - timing.connectStart,
ttfb: timing.responseStart - timing.requestStart,
domContentLoaded: timing.domContentLoadedEventEnd - timing.navigationStart,
pageLoad: timing.loadEventEnd - timing.navigationStart
};
}""")

print(f"DNS 查询: {performance_metrics['dnsLookup']}ms")
print(f"TCP 连接: {performance_metrics['tcpConnection']}ms")
print(f"首字节: {performance_metrics['ttfb']}ms")
print(f"DOM 就绪: {performance_metrics['domContentLoaded']}ms")
print(f"页面加载: {performance_metrics['pageLoad']}ms")

5. 并行测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 使用 pytest-playwright 进行并行测试
# conftest.py
def pytest_configure(config):
config.option.numprocesses = 4 # 4 个并行进程

# test_login.py
def test_login_with_different_users(page, username, password):
page.goto('http://localhost:5173/login')
page.fill('input[name="username"]', username)
page.fill('input[name="password"]', password)
page.click('button[type="submit"]')
expect(page).to_have_url('**/dashboard')

# 运行
pytest tests/ -n 4 --browser chromium

总结与展望

核心优势回顾

  1. 智能化 - AI 驱动的自动化调试,减少人工干预
  2. 全面性 - 从 UI 到网络,从截图到日志,全方位监控
  3. 高效性 - 快速定位问题,自动生成报告
  4. 可复用 - 调试脚本和最佳实践可以沉淀为团队资产

未来发展方向

  • 🤖 更智能的元素识别 - 基于 AI 的智能选择器生成
  • 📊 可视化调试面板 - 实时展示调试过程和结果
  • 🔗 CI/CD 集成 - 自动化回归测试和质量门禁
  • 🌍 跨浏览器测试 - 同时测试 Chrome、Firefox、Safari
  • 📱 移动端支持 - 响应式设计和移动端适配测试

行动建议

  1. 立即开始 - 在你的下一个 bug 修复中使用 Playwright 调试
  2. 建立规范 - 制定团队的 Playwright 使用规范
  3. 积累资产 - 将常用的调试脚本整理成工具库
  4. 持续优化 - 定期回顾和改进调试流程

附录

A. 常用命令速查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 安装
pip install playwright
playwright install

# 更新
pip install --upgrade playwright
playwright install --force

# 查看版本
playwright --version

# 生成代码(录制操作)
playwright codegen http://localhost:5173

# 打开 Inspector
playwright inspector

B. 参考资源

C. 工具链推荐

用途 工具 说明
浏览器自动化 Playwright 本文主角
API 测试 Postman / Insomnia 接口调试
性能分析 Lighthouse 页面性能
视觉测试 Percy / Chromatic 视觉回归
测试框架 pytest Python 测试
CI/CD GitHub Actions 自动化流水线

文档维护者: LWMS2 开发团队
最后更新: 2026-04-06
反馈与建议: 欢迎提交 Issue 或 PR


💡 提示: 本文档会随着项目发展和实践经验不断更新,建议定期查看最新版本。


MCP Playwright 自动化调试实战指南
https://www.normdist.com/2026/04/06/ND-20260406-001-mcp-playwright-practical-guide/
作者
小瑞
发布于
2026年4月6日
许可协议