关于 triage-ci-flake
用于诊断和修复 CI 端到端测试失败的工作流技能,尤其针对间歇性 Playwright 故障。它要求先复现具体失败,并在必要时检查开发代码与打包后的生产代码,再提出修复方案。
主要功能
- 01PR 检查与日志审阅
- 02针对单个测试复现
- 03开发与生产环境检查
- 04间歇性测试问题指引
- 05修复验证流程
适用场景
- 01调查已合并 PR 的 CI 失败
- 02复现仅在 CI 出现的 Playwright 超时
- 03检查打包是否影响测试
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ payloadcms/ payload --skill triage-ci-flake安装后在 Claude Code 中输入/triage-ci-flake确认已加载。
详细介绍
排查 CI 失败
概述
专为在 CI 中排查和修复端到端(e2e)测试失败而设计的系统化工作流,尤其适用于那些在本地通过但在 CI 中失败的 Playwright 不稳定测试。合并到 main 的 E2E 测试通常由于时序、打包或环境差异而不稳定。
关键规则:在提出任何修复之前,你必须先运行复现工作流。没有例外。
何时使用
- PR 合并后 CI 测试在
main分支上失败 - 测试在本地通过但在 CI 中失败
- 测试失败被标记为"不稳定"或间歇性
- 仅在 CI 中超时的 E2E 测试
- 用户提供了带有失败检查的 PR URL/编号
基于 PR 的工作流(当提供 PR URL/编号时)
当用户提供 PR URL(例如 https://github.com/payloadcms/payload/pull/16701)或 PR 编号时:
步骤 1:获取 PR 状态检查
首先,使用 tool_search 并查询 "github pull request status checks" 来加载 GitHub 工具。
然后使用 github-pull-request_pullRequestStatusChecks 工具获取所有失败的检查:
Tool: github-pull-request_pullRequestStatusChecks
Parameters:
pullRequestNumber: <extracted from URL or provided>
repo: { owner: "payloadcms", name: "payload" }
步骤 2:汇总失败的测试
解析状态检查响应并创建汇总表:
| Suite | 检查状态 | 目标 URL |
|---|---|---|
| admine2elist-view | failed | [link] |
| plugin-import-export | failed | [link] |
对于每个失败的检查,context 字段包含测试套件名称(例如 admin__e2e__list-view)。
步骤 3:从每个检查中提取失败详情
对于每个失败的检查:
- 访问
targetUrl以获取详细的失败日志 - 提取具体的测试名称和错误消息
- 添加到汇总的失败列表中
呈现一份整合后的摘要:
## Failing Tests Summary
### 1. admin**e2e**list-view (3/4)
- **Test**: "should use custom pagination limit"
- **Error**: Locator `.per-page__button` not found
- **File**: test/admin/e2e/list-view/e2e.spec.ts:1495
### 2. plugin-import-export
- **Test**: "should inherit limit from list view URL"
- **Error**: Locator `.per-page__button` not found
- **File**: test/plugin-import-export/e2e.spec.ts:150
步骤 4:识别常见模式
在失败中寻找模式:
- 相同的选择器错误 → 可能是组件变更,需要更新测试
- 相同的测试文件 → 局部性问题
- 同一套件中的不同错误 → 可能是测试污染
步骤 5:继续进行排查
对于每个唯一的失败,遵循下方标准的复现工作流。
强制性的首要步骤
你必须执行这些命令。阅读代码或分析日志不算作复现。
- 提取 CI 日志中的套件名称、测试名称和错误
- 执行:杀掉端口 3000 以避免冲突
- 执行:
pnpm dev $SUITE_NAME(使用 run_in_background=true) - 执行:等待服务器就绪(使用 curl 或 sleep 检查)
- 执行:直接用 Playwright 运行具体的失败测试(npx playwright test test/TEST_SUITE_NAME/e2e.spec.ts:31:3 --headed -g "TEST_DESCRIPTION_TARGET_GOES_HERE")
- 如果测试通过,执行:
pnpm prepare-run-test-against-prod - 执行:
pnpm dev:prod $SUITE_NAME并再次运行测试
只有在执行这些命令并看到其输出之后,你才能继续进行分析和修复。
"根据日志分析"不是复现。你必须运行这些命令。
核心工作流
digraph triage_ci {
"CI failure reported" [shape=box];
"Extract details from CI logs" [shape=box];
"Identify suite and test name" [shape=box];
"Run dev server: pnpm dev $SUITE" [shape=box];
"Run specific test by name" [shape=box];
"Did test fail?" [shape=diamond];
"Debug with dev code" [shape=box];
"Run prepare-run-test-against-prod" [shape=box];
"Run: pnpm dev:prod $SUITE" [shape=box];
"Run specific test again" [shape=box];
"Did test fail now?" [shape=diamond];
"Debug bundling issue" [shape=box];
"Unable to reproduce - check logs" [shape=box];
"Fix and verify" [shape=box];
"CI failure reported" -> "Extract details from CI logs";
"Extract details from CI logs" -> "Identify suite and test name";
"Identify suite and test name" -> "Run dev server: pnpm dev $SUITE";
"Run dev server: pnpm dev $SUITE" -> "Run specific test by name";
"Run specific test by name" -> "Did test fail?";
"Did test fail?" -> "Debug with dev code" [label="yes"];
"Did test fail?" -> "Run prepare-run-test-against-prod" [label="no"];
"Run prepare-run-test-against-prod" -> "Run: pnpm dev:prod $SUITE";
"Run: pnpm dev:prod $SUITE" -> "Run specific test again";
"Run specific test again" -> "Did test fail now?";
"Did test fail now?" -> "Debug bundling issue" [label="yes"];
"Did test fail now?" -> "Unable to reproduce - check logs" [label="no"];
"Debug with dev code" -> "Fix and verify";
"Debug bundling issue" -> "Fix and verify";
}
分步流程
1. 提取 CI 详情
从 CI 日志或 GitHub Actions URL 中识别:
- 套件名称:目录名称(例如
i18n、fields、lexical) - 测试文件:完整路径(例如
test/i18n/e2e.spec.ts) - 测试名称:确切的测试描述
- 错误消息:完整的堆栈跟踪
- 测试类型:E2E(Playwright)
2. 使用开发代码复现
关键:始终按名称运行具体的测试,而不是整个套件。
服务器管理规则:
- 在启动新服务器之前,始终杀掉所有服务器
- 永远不要假设端口是空闲的
- 在运行测试之前,始终等待服务器就绪确认
# ========================================
# STEP 2A: STOP ALL SERVERS
# ========================================
lsof -ti:3000 | xargs kill -9 2>/dev/null || echo "Port 3000 clear"
# ========================================
# STEP 2B: START DEV SERVER
# ========================================
# Start dev server with the suite (in background with run_in_background=true)
pnpm dev $SUITE_NAME
# ========================================
# STEP 2C: WAIT FOR SERVER READY
# ========================================
# Wait for server to be ready (REQUIRED - do not skip)
until curl -s http://localhost:3000/admin > /dev/null 2>&1; do sleep 1; done && echo "Server ready"
# ========================================
# STEP 2D: RUN SPECIFIC TEST
# ========================================
# Run ONLY the specific failing test using Playwright directly
# For E2E tests (DO NOT use pnpm test:e2e as it spawns its own server):
pnpm exec playwright test test/$SUITE_NAME/e2e.spec.ts -g "exact test name"
测试失败了吗?
- ✅ 是:你复现了!继续使用开发代码进行调试。
- ❌ 否:继续步骤 3(打包代码测试)。
3. 使用打包后的代码复现
如果开发代码下测试通过,问题很可能出在打包/生产代码中。
重要:在启动生产服务器之前,你必须先停止开发服务器。
# ========================================
# STEP 3A: STOP ALL SERVERS (INCLUDING DEV SERVER FROM STEP 2)
# ========================================
lsof -ti:3000 | xargs kill -9 2>/dev/null || echo "Port 3000 clear"
# ========================================
# STEP 3B: BUILD AND PACK FOR PROD
# ========================================
# Build all packages and pack them (this takes time - be patient)
pnpm prepare-run-test-against-prod
# ========================================
# STEP 3C: START PROD SERVER
# ========================================
# Start prod dev server (in background with run_in_background=true)
pnpm dev:prod $SUITE_NAME
# ========================================
# STEP 3D: WAIT FOR SERVER READY
# ========================================
# Wait for server to be ready (REQUIRED - do not skip)
until curl -s http://localhost:3000/admin > /dev/null 2>&1; do sleep 1; done && echo "Server ready"
# ========================================
# STEP 3E: RUN SPECIFIC TEST
# ========================================
# Run the specific test again using Playwright directly
pnpm exec playwright test test/$SUITE_NAME/e2e.spec.ts -g "exact test name"
这次测试失败了吗?
- ✅ 是:打包或生产构建问题。请检查:
- package.json 中缺失的导出
- 构建配置问题
- 打包后行为不同的代码
- ❌ 否:无法在本地复现。继续执行第 4 步。
4. 无法复现
如果两次尝试后仍无法在本地复现:
- 更仔细地查看 CI 日志,寻找环境差异
- 检查是否存在竞态条件(多次运行测试:
for i in {1..10}; do pnpm test:e2e...; done) - 查找 CI 特有的限制(内存、CPU、时序)
- 考虑它是否是一个高度依赖时序的真正竞态条件
常见的脆弱测试模式
竞态条件
- 断言运行时页面仍在导航
- 断言前网络请求尚未稳定
- 状态更新尚未完成
修复模式:
- 使用 Playwright 的 web-first 断言(
toBeVisible()、toHaveText()) - 等待特定条件,而不是任意超时
- 使用带条件检查的
waitForFunction()
测试污染
- 测试在数据库中遗留数据
- 测试之间共享状态
afterEach中缺少清理
修复模式:
- 跟踪创建的 ID,并在
afterEach中清理 - 使用隔离的测试数据
- 不要使用会影响其他测试的
deleteAll
时序问题
- 使用
setTimeout/sleep而不是基于条件的等待 - 未等待页面稳定
- 动画/过渡尚未完成
修复模式:
- 使用
waitForPageStability()辅助函数 - 等待特定的 DOM 状态
- 使用 Playwright 内置的等待机制
Linting 注意事项
修复 e2e 测试时,请注意以下 eslint 规则:
playwright/no-networkidle- 避免waitForLoadState('networkidle')(改用基于条件的等待)payload/no-wait-function- 避免自定义wait()函数(改用 Playwright 内置的等待)payload/no-flaky-assertions- 避免不可重试的断言playwright/prefer-web-first-assertions- 使用 Playwright 内置断言
现有代码可能违反这些规则 - 添加新代码时,即使现有代码不遵守,也要遵循这些规则。
验证
修复后:
# Ensure dev server is running on port 3000
# Run test multiple times to confirm stability
for i in {1..10}; do
pnpm exec playwright test test/$SUITE_NAME/e2e.spec.ts -g "exact test name" || break
done
# Run full suite
pnpm exec playwright test test/$SUITE_NAME/e2e.spec.ts
# If you modified bundled code, test with prod build
lsof -ti:3000 | xargs kill -9 2>/dev/null
pnpm prepare-run-test-against-prod
pnpm dev:prod $SUITE_NAME
until curl -s http://localhost:3000/admin > /dev/null; do sleep 1; done
pnpm exec playwright test test/$SUITE_NAME/e2e.spec.ts
铁律
没有复现就没有修复
如果你在完成工作流第 1-3 步之前就提出修复方案,你就违反了这个技能。
这同样适用于以下情况:
- 从日志看修复似乎显而易见
- 你以前见过这个错误
- 团队施加时间压力
- 你对根因很有把握
- 日志显示了清晰的堆栈跟踪
没有例外。先运行复现工作流。
合理化对照表
每一个跳过复现的借口,以及它为什么是错的:
| 合理化借口 | 现实 |
|---|---|
| "日志显示了确切的错误" | 日志显示的是症状,不是根因。去复现。 |
| "我能从代码中看出问题" | 你在猜测。去复现以确认。 |
| "这显然是一个竞态条件" | 也许吧。去复现以确定。 |
| "我以前见过这个错误" | 这次可能不同。去复现。 |
| "堆栈跟踪很清晰" | 堆栈跟踪显示的是位置,不是原因。去复现。 |
| "时间紧张 - 需要快速修复" | 复现本身就是快速的。猜测只会浪费时间。 |
| "测试文件显示了问题" | 阅读 ≠ 运行。执行命令。 |
| "我先分析代码" | 代码分析在复现之后。 |
| "让我调查根因" | 复现就是调查。 |
| "我需要理解这个错误" | 理解来自于亲眼看到它失败。 |
危险信号 - 立即停止
这些想法意味着你即将违反该技能:
- "让我直接修复这个明显的问题" → 停止。先执行复现。
- "我会增加超时时间" → 停止。通过复现找到根本原因。
- "从日志看这像是 X" → 停止。运行测试。
- "我会运行完整测试套件" → 停止。先运行特定的测试。
- "让我检查一下代码" → 停止。先运行复现。
- "我从堆栈跟踪中就能看出问题" → 停止。通过复现来确认。
- "日志已经足够清楚了" → 停止。执行命令。
- "让我分析一下……" → 停止。分析要在复现之后进行。
所有这些都意味着:立即停止。执行复现命令。没有执行就没有分析。
示例会话
正确的做法 - 先执行命令:
User: CI failed on i18n suite - https://github.com/.../actions/runs/XXX
Agent:
1. Extracted details from CI logs:
- Suite: i18n
- Test: "ensure translations update correctly when switching language"
- File: test/i18n/e2e.spec.ts:124
- Error: "Error loading language: en" TypeError: network error
2. Executing reproduction workflow:
[Uses Bash tool to kill ports]
$ lsof -ti:3000 | xargs kill -9 2>/dev/null || echo "Port 3000 clear"
[Uses Bash tool with run_in_background=true]
$ pnpm dev i18n
[Waits for server to start with curl check]
$ until curl -s http://localhost:3000/admin > /dev/null; do sleep 1; done && echo "Server ready"
[Uses Bash tool to run Playwright directly]
$ pnpm exec playwright test test/i18n/e2e.spec.ts -g "ensure translations update correctly when switching language"
Output shows: Test PASSED locally
3. Test passed with dev code, trying bundled code:
[Kills ports again]
$ lsof -ti:3000 | xargs kill -9 2>/dev/null
[Uses Bash tool]
$ pnpm prepare-run-test-against-prod
[Uses Bash tool with run_in_background=true]
$ pnpm dev:prod i18n
[Waits for server]
$ until curl -s http://localhost:3000/admin > /dev/null; do sleep 1; done
[Uses Bash tool]
$ pnpm exec playwright test test/i18n/e2e.spec.ts -g "ensure translations update correctly when switching language"
Output shows: Test FAILED - reproduced!
4. Now that I've reproduced it, analyzing the failure...
[Code analysis, root cause identification]
5. Implementing fix: [specific changes]
6. Verification:
[Uses Bash tool]
$ for i in {1..10}; do pnpm test:e2e i18n -g "exact test"; done
All 10 runs passed!
不正确的做法 - 不执行就分析:
User: CI failed on i18n suite
Agent: Looking at the error logs, I can see this is a race condition...
[Analyzes code, proposes fix without running any commands]
❌ WRONG - This violates the skill. Must execute reproduction commands first.
常见错误
| 错误 | 修复方法 |
|---|---|
| 首先运行完整测试套件 | 按名称运行特定测试 |
| 跳过开发代码复现 | 始终先尝试开发代码 |
| 未使用打包代码进行测试 | 如果开发代码通过,使用 prepare-run-test-against-prod 进行测试 |
| 未复现就提出修复方案 | 遵循工作流 - 先复现 |
在新代码中使用 networkidle | 使用基于条件的等待,配合 waitForFunction() |
添加任意的 wait() 调用 | 使用 Playwright 内置的断言和等待 |
关键原则
- 先复现再修复:绝不在未复现问题的情况下提出修复方案
- 有针对性地测试:运行确切失败的测试,而不是完整测试套件
- 先开发代码,后生产代码:在打包代码之前先检查开发代码
- 遵循工作流:没有捷径 - 这些步骤的存在是为了节省时间
- 验证稳定性:多次运行测试以确认修复有效
完成:创建 PR
在你完成以下事项之后:
- ✅ 复现了问题
- ✅ 实现了修复
- ✅ 验证了修复在本地通过(多次运行)
- ✅ 使用生产构建进行了测试(如适用)
你必须提示用户创建 PR:
The fix has been verified and is ready for review. Would you like me to create a PR with these changes?
Summary of changes:
- [List files modified]
- [Brief description of the fix]
- [Verification results]
重要:
- 不要自动创建 PR - 始终先询问用户
- 提供清晰的摘要,说明更改了什么以及为什么
- 包含验证结果(测试运行次数、通过率)
- 让用户决定是立即创建 PR 还是先做其他更改
这确保用户对提交审查的内容有可见性和控制权。