mcpField
T
triage-ci-flake

triage-ci-flake

github.com/payloadcms测试与质量45.6k304 次安装今天更新
代码仓库

关于 triage-ci-flake

用于诊断和修复 CI 端到端测试失败的工作流技能,尤其针对间歇性 Playwright 故障。它要求先复现具体失败,并在必要时检查开发代码与打包后的生产代码,再提出修复方案。

主要功能

  1. 01PR 检查与日志审阅
  2. 02针对单个测试复现
  3. 03开发与生产环境检查
  4. 04间歇性测试问题指引
  5. 05修复验证流程

适用场景

  1. 01调查已合并 PR 的 CI 失败
  2. 02复现仅在 CI 出现的 Playwright 超时
  3. 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-viewfailed[link]
plugin-import-exportfailed[link]

对于每个失败的检查,context 字段包含测试套件名称(例如 admin__e2e__list-view)。

步骤 3:从每个检查中提取失败详情

对于每个失败的检查:

  1. 访问 targetUrl 以获取详细的失败日志
  2. 提取具体的测试名称和错误消息
  3. 添加到汇总的失败列表中

呈现一份整合后的摘要:

## 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:继续进行排查

对于每个唯一的失败,遵循下方标准的复现工作流。

强制性的首要步骤

你必须执行这些命令。阅读代码或分析日志不算作复现。

  1. 提取 CI 日志中的套件名称、测试名称和错误
  2. 执行:杀掉端口 3000 以避免冲突
  3. 执行:pnpm dev $SUITE_NAME(使用 run_in_background=true)
  4. 执行:等待服务器就绪(使用 curl 或 sleep 检查)
  5. 执行:直接用 Playwright 运行具体的失败测试(npx playwright test test/TEST_SUITE_NAME/e2e.spec.ts:31:3 --headed -g "TEST_DESCRIPTION_TARGET_GOES_HERE")
  6. 如果测试通过,执行:pnpm prepare-run-test-against-prod
  7. 执行: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. 使用开发代码复现

关键:始终按名称运行具体的测试,而不是整个套件。

服务器管理规则:

  1. 在启动新服务器之前,始终杀掉所有服务器
  2. 永远不要假设端口是空闲的
  3. 在运行测试之前,始终等待服务器就绪确认
# ========================================
# 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 内置的断言和等待

关键原则

  1. 先复现再修复:绝不在未复现问题的情况下提出修复方案
  2. 有针对性地测试:运行确切失败的测试,而不是完整测试套件
  3. 先开发代码,后生产代码:在打包代码之前先检查开发代码
  4. 遵循工作流:没有捷径 - 这些步骤的存在是为了节省时间
  5. 验证稳定性:多次运行测试以确认修复有效

完成:创建 PR

在你完成以下事项之后:

  1. ✅ 复现了问题
  2. ✅ 实现了修复
  3. ✅ 验证了修复在本地通过(多次运行)
  4. ✅ 使用生产构建进行了测试(如适用)

你必须提示用户创建 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 还是先做其他更改

这确保用户对提交审查的内容有可见性和控制权。

评论

支持 Markdown