12 KiB
12 KiB
追踪与调试
使用场景:调试失败的操作、分析性能瓶颈、捕获执行证据、理解交互未生效的原因,或在自动化过程中检查网络及控制台活动。 前置条件:core-commands.md —— 掌握基本 CLI 用法
快速参考
# 追踪会话
playwright-cli tracing-start
playwright-cli open https://example.com
playwright-cli click e4
playwright-cli fill e7 "test"
playwright-cli tracing-stop
# 查看控制台消息
playwright-cli console # 所有级别
playwright-cli console error # 仅错误
playwright-cli console warning # 仅警告
# 查看网络活动
playwright-cli network
追踪
追踪会捕获一切 —— 包括 DOM 快照、截图、网络活动、控制台日志以及时间信息,覆盖会话中的每一个操作。
基本用法
# 在要调试的流程之前开始录制
playwright-cli tracing-start
# 执行操作
playwright-cli open https://example.com
playwright-cli snapshot
playwright-cli click e4
playwright-cli fill e7 "test data"
playwright-cli click e9
# 停止并保存追踪
playwright-cli tracing-stop
追踪捕获的内容
| 类别 | 详情 |
|---|---|
| 操作 | 每次点击、填写、悬停、键盘输入、导航 —— 附带时间信息 |
| DOM 快照 | 每个操作前后的完整 DOM 状态 |
| 截图 | 每一步的视觉状态 |
| 网络 | 所有 HTTP 请求、响应、请求头、响应体、时间信息 |
| 控制台 | 所有 console.log、console.warn、console.error 消息 |
| 时间信息 | 每个操作的具体耗时 |
| 来源 | 每个操作由哪个命令触发 |
追踪输出文件
当追踪激活时,Playwright 会创建一个 traces/ 目录:
trace-{时间戳}.trace —— 主追踪文件,包含:
- 附带 DOM 快照的操作日志
- 每一步的截图
- 时间信息
- 控制台消息
trace-{时间戳}.network —— 网络活动:
- 所有 HTTP 请求与响应
- 请求/响应请求头与响应体
- DNS、连接、TLS、TTFB、下载时间
- 资源大小及失败的请求
resources/ —— 用于追踪回放的缓存资源:
- 图片、字体、样式表、脚本
- 用于离线回放的响应体
查看追踪
在 Playwright 的 Trace Viewer 中打开追踪 —— 这是一个用于逐步调试的丰富 GUI:
# 在 Trace Viewer Web 应用中打开追踪
npx playwright show-trace traces/trace-123456.trace
# 或使用在线 Trace Viewer
# 将追踪文件上传至:https://trace.playwright.dev
Trace Viewer 会显示:
- 时间轴:带截图的逐步操作
- DOM 快照:检查每一步中的元素(类似 DevTools)
- 网络:所有请求的水fall图
- 控制台:带时间戳的日志消息
- 来源:触发每个操作的代码
控制台监控
查看页面的 JavaScript 控制台输出 —— 对捕获错误至关重要:
# 显示所有控制台消息
playwright-cli console
# 按严重级别筛选
playwright-cli console error # 仅错误
playwright-cli console warning # 仅警告
playwright-cli console info # 仅信息消息
playwright-cli console log # 仅日志消息
常见调试模式
playwright-cli open https://app.example.com
playwright-cli snapshot
playwright-cli click e5 # 发生了意外情况
# 检查是否有 JavaScript 错误
playwright-cli console error
# 输出可能显示:
# [error] TypeError: Cannot read properties of undefined (reading 'map')
# [error] Uncaught ReferenceError: processData is not defined
交互期间查看控制台
使用 run-code 设置持续的控制台监控:
playwright-cli run-code "async page => {
page.on('console', msg => {
if (msg.type() === 'error' || msg.type() === 'warning') {
console.log(\`[\${msg.type()}] \${msg.text()}\`);
}
});
}"
# 现在开始交互 —— 错误和警告会实时打印
playwright-cli click e5
playwright-cli fill e3 "test"
捕获未捕获的异常
playwright-cli run-code "async page => {
page.on('pageerror', error => {
console.log('Uncaught exception:', error.message);
});
}"
网络监控
查看页面发出的所有网络请求:
playwright-cli network
实时查看网络
playwright-cli run-code "async page => {
page.on('request', request => {
console.log(\`>> \${request.method()} \${request.url()}\`);
});
page.on('response', response => {
console.log(\`<< \${response.status()} \${response.url()}\`);
});
}"
# 现在开始交互 —— 请求会实时打印
playwright-cli click e5
监控失败的请求
playwright-cli run-code "async page => {
page.on('requestfailed', request => {
console.log(\`FAILED: \${request.method()} \${request.url()} - \${request.failure()?.errorText}\`);
});
}"
检查特定响应
playwright-cli run-code "async page => {
const response = await page.waitForResponse('**/api/users');
return {
status: response.status(),
statusText: response.statusText(),
headers: response.headers(),
body: await response.json()
};
}"
调试策略
策略 1:快照前后对比
最简单的调试方法 —— 查看发生了什么变化:
playwright-cli snapshot --filename=before.yaml
playwright-cli click e5
playwright-cli snapshot --filename=after.yaml
# 比较两个快照,了解变化内容
策略 2:追踪失败流程
在失败步骤之前开始追踪:
playwright-cli tracing-start
# 重现问题
playwright-cli goto https://app.example.com/checkout
playwright-cli fill e1 "Jane Doe"
playwright-cli click e5 # 此步骤失败
playwright-cli tracing-stop
# 打开追踪,查看尝试点击时的 DOM 状态
策略 3:检查元素状态
当交互失败时,检查元素的实际状态:
# 元素是否可见?
playwright-cli eval "el => window.getComputedStyle(el).display" e5
playwright-cli eval "el => window.getComputedStyle(el).visibility" e5
playwright-cli eval "el => el.getBoundingClientRect()" e5
# 是否被其他元素遮挡?
playwright-cli eval "el => {
const rect = el.getBoundingClientRect();
const topEl = document.elementFromPoint(rect.x + rect.width/2, rect.y + rect.height/2);
return topEl === el ? 'Element is on top' : 'Covered by: ' + topEl?.tagName + '.' + topEl?.className;
}" e5
# 是否被禁用?
playwright-cli eval "el => el.disabled" e5
playwright-cli eval "el => el.getAttribute('aria-disabled')" e5
策略 4:控制台 + 网络组合
# 同时监控控制台和网络
playwright-cli run-code "async page => {
page.on('console', msg => {
if (msg.type() === 'error') console.log('[CONSOLE]', msg.text());
});
page.on('requestfailed', req => {
console.log('[NETWORK]', req.method(), req.url(), req.failure()?.errorText);
});
page.on('response', resp => {
if (resp.status() >= 400) {
console.log('[HTTP ERROR]', resp.status(), resp.url());
}
});
}"
# 现在开始交互并查看错误
playwright-cli click e5
playwright-cli fill e3 "test"
策略 5:等待与重试
如果操作因时序问题失败,可添加显式等待:
# 等待元素变为可操作状态
playwright-cli run-code "async page => {
await page.locator('#dynamic-button').waitFor({ state: 'visible', timeout: 10000 });
}"
playwright-cli snapshot
playwright-cli click e5
# 等待页面稳定
playwright-cli run-code "async page => {
await page.waitForLoadState('networkidle');
}"
playwright-cli snapshot
性能分析
测量页面加载时间
playwright-cli run-code "async page => {
const timing = await page.evaluate(() => {
const t = performance.timing;
return {
dns: t.domainLookupEnd - t.domainLookupStart,
tcp: t.connectEnd - t.connectStart,
ttfb: t.responseStart - t.requestStart,
download: t.responseEnd - t.responseStart,
domParsing: t.domInteractive - t.domLoading,
domComplete: t.domComplete - t.domLoading,
total: t.loadEventEnd - t.navigationStart
};
});
return timing;
}"
使用 Navigation Timing API 分析
playwright-cli run-code "async page => {
const entries = await page.evaluate(() => {
return performance.getEntriesByType('navigation').map(e => ({
type: e.type,
redirectTime: e.redirectEnd - e.redirectStart,
dnsTime: e.domainLookupEnd - e.domainLookupStart,
connectTime: e.connectEnd - e.connectStart,
tlsTime: e.secureConnectionStart > 0 ? e.connectEnd - e.secureConnectionStart : 0,
requestTime: e.responseStart - e.requestStart,
responseTime: e.responseEnd - e.responseStart,
domProcessing: e.domComplete - e.domInteractive,
loadTime: e.loadEventEnd - e.loadEventStart,
totalTime: e.loadEventEnd - e.startTime
}));
});
return entries;
}"
列出慢资源
playwright-cli run-code "async page => {
const resources = await page.evaluate(() => {
return performance.getEntriesByType('resource')
.map(r => ({ name: r.name.split('/').pop(), duration: Math.round(r.duration), size: r.transferSize }))
.sort((a, b) => b.duration - a.duration)
.slice(0, 10);
});
return resources;
}"
追踪 vs 视频 vs 截图
选择合适的捕获方式:
| 功能 | 追踪 | 视频 | 截图 |
|---|---|---|---|
| 格式 | .trace 文件 |
.webm 视频 |
.png 图片 |
| DOM 检查 | 是 | 否 | 否 |
| 网络详情 | 是 | 否 | 否 |
| 逐步回放 | 是 | 连续播放 | 单帧 |
| 控制台日志 | 是 | 否 | 否 |
| 文件大小 | 中等 | 大 | 小 |
| 最佳用途 | 调试 | 演示、文档 | 快速捕获 |
选择指南
- 操作失败且不明原因 → 追踪
- 需要向他人展示流程 → 视频
- 需要验证视觉状态 → 截图
- 需要分析性能 → 追踪(包含网络水fall图)
- CI 中失败测试的产物 → 追踪 + 截图
最佳实践
1. 在问题发生前开始追踪
追踪导致失败的整个流程,而不仅仅是失败的步骤:
playwright-cli tracing-start
# 从头重现完整流程
playwright-cli open https://app.example.com
playwright-cli fill e1 "user@example.com"
playwright-cli click e3
# ... 直至失败的所有步骤 ...
playwright-cli tracing-stop
2. 主动使用控制台监控
在任何调试会话开始时即启动控制台监控:
playwright-cli run-code "async page => {
page.on('console', msg => console.log(\`[\${msg.type()}] \${msg.text()}\`));
page.on('pageerror', err => console.log('[EXCEPTION]', err.message));
}"
3. 清理旧追踪
追踪文件会占用大量磁盘空间:
# 删除 7 天前的追踪文件
find .playwright-cli/traces -mtime +7 -delete
4. 组合多种技术
最有效的调试往往结合多种方法:
playwright-cli tracing-start # 捕获一切
playwright-cli console error # 监控 JS 错误
playwright-cli network # 监控失败的请求
playwright-cli snapshot # 查看当前状态
# ... 交互并调试 ...
playwright-cli tracing-stop # 保存追踪用于详细分析