npx skills add https://github.com/dididy/e2e-test-skills --skill e2e-test-debugger从报告文件中诊断 Playwright 测试失败。对根本原因进行分类并提供具体修复方案。
请勿直接运行 playwright test 并读取其标准输出 — 输出可能被令牌优化代理(例如 rtk)截断。请改用:
npx playwright test --reporter=json 2>/dev/null > playwright-report/results.json
然后在第一阶段解析报告文件。
# 如果未指定路径,则查找报告
find . -name "results.json" -path "*/playwright-report/*" | head -5
# 提取失败的测试(使用 jq)
cat playwright-report/results.json | jq '[
.. | objects |
select(.status == "failed" or .status == "timedOut") |
{title: .title, status: .status, error: .error.message, file: .location.file, duration: .duration}
] | unique'
# 提取失败的测试(使用 node 作为备选方案)
node -e "
const r = require('./playwright-report/results.json');
const flat = (s) => [s, ...(s.suites||[]).flatMap(flat), ...(s.specs||[]).flatMap(sp => sp.tests||[])];
flat(r).filter(t => t.status === 'failed' || t.status === 'timedOut')
.forEach(t => console.log(t.status, t.title, t.error?.message?.slice(0,120)))
"
使用第一阶段的输出(错误信息 + 持续时间 + 文件)进行分类。大多数失败在此处即可识别 — 只有在仍不清楚时才进入第三阶段。
Diagnose Playwright test failures from report files. Classifies root causes and provides concrete fixes.
Do NOT runplaywright test directly and read its stdout — output may be truncated by token-optimizing proxies (e.g. rtk). Instead:
npx playwright test --reporter=json 2>/dev/null > playwright-report/results.json
Then parse the report file in Phase 1.
# Find report if path not specified
find . -name "results.json" -path "*/playwright-report/*" | head -5
# Extract failed tests (jq)
cat playwright-report/results.json | jq '[
.. | objects |
select(.status == "failed" or .status == "timedOut") |
{title: .title, status: .status, error: .error.message, file: .location.file, duration: .duration}
] | unique'
# Extract failed tests (node fallback)
node -e "
const r = require('./playwright-report/results.json');
const flat = (s) => [s, ...(s.suites||[]).flatMap(flat), ...(s.specs||[]).flatMap(sp => sp.tests||[])];
flat(r).filter(t => t.status === 'failed' || t.status === 'timedOut')
.forEach(t => console.log(t.status, t.title, t.error?.message?.slice(0,120)))
"
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
---|---|---|---
F1 | 不稳定 / 时序问题 | TimeoutError,持续时间接近 maxTimeout,重试通过 | #13a, #13c
F2 | 选择器损坏 | locator not found, strict mode violation, 元素数量不匹配 | #7, #14
F3 | 网络依赖问题 | net::ERR_*, 意外的 API 响应, 404/500 | #13b
F4 | 断言不匹配 | Expected X to equal Y, 主语倒置, 检查范围过宽 | #4, #11, #11b
F5 | 缺少 Then 步骤 | 操作已完成但错误状态仍然存在 | #2
F6 | 条件分支缺失 | 元素有条件地存在,断言始终运行 | #6
F7 | 测试隔离失败 | 单独运行通过,在套件中失败;状态泄漏 | —
F8 | 环境不匹配 | 仅 CI 与本地环境;视口、操作系统、时区 | —
F9 | 数据依赖问题 | 缺少种子数据,硬编码的 ID | —
F10 | 认证 / 会话问题 | 会话过期,基于角色的 UI 未渲染 | —
F11 | 异步顺序假设错误 | Promise.all 顺序,并行竞争 | —
F12 | POM / 定位器漂移 | DOM 已更改,POM 定位器未更新 | #14
F13 | 错误被吞没 | .catch(() => {}) 隐藏了失败,测试静默通过 | #3
F14 | 动画竞争条件 | 元素可见但内容尚未渲染 | #13c
分类步骤:
duration 接近超时时间 → F1 或 F3trace.zip 结构:
trace.trace — 换行符分隔的 JSON(操作、快照、控制台)
trace.network — 换行符分隔的 JSON(网络请求)
resources/ — JPEG 截图
find playwright-report -name "*.zip" | head -10
渐进式披露 — 一旦根本原因明确就停止:
# 1. 哪一步失败了?
unzip -p trace.zip trace.trace | node -e "
process.stdin.resume(); let d='';
process.stdin.on('data',c=>d+=c);
process.stdin.on('end',()=>d.trim().split('\n').map(l=>JSON.parse(l))
.filter(e=>e.type==='after'&&e.error)
.forEach(e=>console.log(e.apiName, e.error.message)));"
# 2. 所有操作及其通过/失败状态
unzip -p trace.zip trace.trace | node -e "
process.stdin.resume(); let d='';
process.stdin.on('data',c=>d+=c);
process.stdin.on('end',()=>d.trim().split('\n').map(l=>JSON.parse(l))
.filter(e=>e.type==='after')
.forEach((e,i)=>console.log(i, e.apiName, e.error?'❌ '+e.error.message.slice(0,80):'✓')));"
# 3a. 选择器问题 — 失败步骤时的 DOM
# 将 SNAPSHOT_NAME 替换为步骤 2 中的 beforeSnapshot 值(例如 "snapshot@call@123")
unzip -p trace.zip trace.trace | node -e "
process.stdin.resume(); let d='';
process.stdin.on('data',c=>d+=c);
process.stdin.on('end',()=>d.trim().split('\n').map(l=>JSON.parse(l))
.filter(e=>e.type==='frame-snapshot'&&e.snapshot?.name==='SNAPSHOT_NAME')
.forEach(e=>console.log(JSON.stringify(e.snapshot.html).slice(0,3000))));"
# 3b. 网络问题 — 失败的请求
unzip -p trace.zip trace.network | node -e "
process.stdin.resume(); let d='';
process.stdin.on('data',c=>d+=c);
process.stdin.on('end',()=>d.trim().split('\n').map(l=>JSON.parse(l))
.filter(e=>e.type==='resource-snapshot'&&e.response?.status>=400)
.forEach(e=>console.log(e.response.status, e.request.url)));"
# 3c. JS 错误
unzip -p trace.zip trace.trace | node -e "
process.stdin.resume(); let d='';
process.stdin.on('data',c=>d+=c);
process.stdin.on('end',()=>d.trim().split('\n').map(l=>JSON.parse(l))
.filter(e=>e.type==='console'&&e.messageType==='error')
.forEach(e=>console.log(e.text)));"
# 3d. 仍不清楚 — 添加临时截图,重新运行,通过浏览器代理检查
# await page.screenshot({ path: 'debug/before.png' });
# await someAction();
# await page.screenshot({ path: 'debug/after.png' });
# → 调度浏览器代理打开并比较。调试后移除。
## [P0/P1/P2] `测试名称`
- **类别:** F2 — 选择器损坏 (#14 POM 漂移)
- **错误:** `locator('.submit-btn') strict mode violation, 3 elements found`
- **根本原因:** DOM 重构后按钮选择器过于宽泛
- **修复:**
```typescript
// 修复前
await page.locator('.submit-btn').click();
// 修复后
await page.locator('form[data-testid="login-form"] button[type="submit"]').click();
**严重性:**
- **P0:** 功能损坏时测试静默通过 (F6, F13)
- **P1:** 间歇性或误导性失败 (F1, F2, F3, F7, F11, F14)
- **P2:** 一致的失败,修复直接 (F4, F5, F8, F9, F10, F12)
## 输出格式
```markdown
## 失败摘要
- 总计: N 个失败 (M 个不稳定, K 个损坏, J 个环境问题)
## [P0] `测试名称` — F13 错误被吞没
...
## 审查摘要
| 严重性 | 数量 | 主要类别 | 文件 |
|-----|-------|-------------|-------|
| P0 | 1 | 错误被吞没 | auth.spec.ts |
| P1 | 3 | 不稳定 / 时序问题 | dashboard.spec.ts |
| P2 | 2 | POM 漂移 | settings.spec.ts |
首先修复 P0。运行 `npx playwright test --retries=2` 以确认不稳定的测试。
每周安装次数
1
代码仓库
首次出现
今日
安全审计
安装于
amp1
cline1
opencode1
cursor1
kimi-cli1
codex1
Use Phase 1 output (error message + duration + file) to classify. Most failures are identifiable here — only go to Phase 3 if still unclear.
---|---|---|---
F1 | Flaky / Timing | TimeoutError, duration near maxTimeout, passes on retry | #13a, #13c
F2 | Selector Broken | locator not found, strict mode violation, element count mismatch | #7, #14
F3 | Network Dependency | net::ERR_*, unexpected API response, 404/500 | #13b
F4 | Assertion Mismatch | Expected X to equal Y, subject-inversion, over-broad check | #4, #11, #11b
F5 | Missing Then | Action completed but wrong state remains | #2
F6 | Condition Branch Missing | Element conditionally present, assertion always runs | #6
F7 | Test Isolation Failure | Passes alone, fails in suite; leaked state | —
F8 | Environment Mismatch | CI vs local only; viewport, OS, timezone | —
F9 | Data Dependency | Missing seed data, hardcoded IDs | —
F10 | Auth / Session | Session expired, role-based UI not rendered | —
F11 | Async Order Assumption | Promise.all order, parallel race | —
F12 | POM / Locator Drift | DOM changed, POM locator not updated | #14
F13 | Error Swallowing | .catch(() => {}) hiding failure, test passes silently | #3
F14 | Animation Race | Element visible but content not yet rendered | #13c
Classification steps:
duration near timeout → F1 or F3trace.zip structure:
trace.trace — newline-delimited JSON (actions, snapshots, console)
trace.network — newline-delimited JSON (network requests)
resources/ — JPEG screenshots
find playwright-report -name "*.zip" | head -10
Progressive disclosure — stop as soon as root cause is clear:
# 1. Which step failed?
unzip -p trace.zip trace.trace | node -e "
process.stdin.resume(); let d='';
process.stdin.on('data',c=>d+=c);
process.stdin.on('end',()=>d.trim().split('\n').map(l=>JSON.parse(l))
.filter(e=>e.type==='after'&&e.error)
.forEach(e=>console.log(e.apiName, e.error.message)));"
# 2. All actions with pass/fail
unzip -p trace.zip trace.trace | node -e "
process.stdin.resume(); let d='';
process.stdin.on('data',c=>d+=c);
process.stdin.on('end',()=>d.trim().split('\n').map(l=>JSON.parse(l))
.filter(e=>e.type==='after')
.forEach((e,i)=>console.log(i, e.apiName, e.error?'❌ '+e.error.message.slice(0,80):'✓')));"
# 3a. Selector issue — DOM at failed step
# replace SNAPSHOT_NAME with beforeSnapshot value from step 2 (e.g. "snapshot@call@123")
unzip -p trace.zip trace.trace | node -e "
process.stdin.resume(); let d='';
process.stdin.on('data',c=>d+=c);
process.stdin.on('end',()=>d.trim().split('\n').map(l=>JSON.parse(l))
.filter(e=>e.type==='frame-snapshot'&&e.snapshot?.name==='SNAPSHOT_NAME')
.forEach(e=>console.log(JSON.stringify(e.snapshot.html).slice(0,3000))));"
# 3b. Network issue — failed requests
unzip -p trace.zip trace.network | node -e "
process.stdin.resume(); let d='';
process.stdin.on('data',c=>d+=c);
process.stdin.on('end',()=>d.trim().split('\n').map(l=>JSON.parse(l))
.filter(e=>e.type==='resource-snapshot'&&e.response?.status>=400)
.forEach(e=>console.log(e.response.status, e.request.url)));"
# 3c. JS errors
unzip -p trace.zip trace.trace | node -e "
process.stdin.resume(); let d='';
process.stdin.on('data',c=>d+=c);
process.stdin.on('end',()=>d.trim().split('\n').map(l=>JSON.parse(l))
.filter(e=>e.type==='console'&&e.messageType==='error')
.forEach(e=>console.log(e.text)));"
# 3d. Still unclear — add temporary screenshots, re-run, inspect via browser agent
# await page.screenshot({ path: 'debug/before.png' });
# await someAction();
# await page.screenshot({ path: 'debug/after.png' });
# → dispatch browser agent to open and compare. Remove after debugging.
## [P0/P1/P2] `test name`
- **Category:** F2 — Selector Broken (#14 POM Drift)
- **Error:** `locator('.submit-btn') strict mode violation, 3 elements found`
- **Root Cause:** Button selector too broad after DOM refactor
- **Fix:**
```typescript
// before
await page.locator('.submit-btn').click();
// after
await page.locator('form[data-testid="login-form"] button[type="submit"]').click();
**Severity:**
- **P0:** Test passes silently when feature is broken (F6, F13)
- **P1:** Intermittent or misleading failures (F1, F2, F3, F7, F11, F14)
- **P2:** Consistent failures, straightforward fix (F4, F5, F8, F9, F10, F12)
## Output Format
```markdown
## Failure Summary
- Total: N failed (M flaky, K broken, J environment)
## [P0] `test name` — F13 Error Swallowing
...
## Review Summary
| Sev | Count | Top Category | Files |
|-----|-------|-------------|-------|
| P0 | 1 | Error Swallowing | auth.spec.ts |
| P1 | 3 | Flaky / Timing | dashboard.spec.ts |
| P2 | 2 | POM Drift | settings.spec.ts |
Fix P0 first. Run `npx playwright test --retries=2` to confirm flaky tests.
Weekly Installs
1
Repository
First Seen
Today
Security Audits
Installed on
amp1
cline1
opencode1
cursor1
kimi-cli1
codex1
通过 LiteLLM 代理让 Claude Code 对接 GitHub Copilot 运行 | 高级变通方案指南
31,600 周安装