7.1 KiB
name, description, disable-model-invocation, allowed-tools
| name | description | disable-model-invocation | allowed-tools | |||||||
|---|---|---|---|---|---|---|---|---|---|---|
| e2e-test-create | 分析 React 组件源代码以理解 UI 结构, 然后按照 Metabase 惯例生成符合习惯的 Cypress E2E 测试。 仅在代码阅读和截图调试不足以解决问题时,才回退使用 Playwright MCP 浏览器探索。 | true |
|
代码阅读优先 → 生成 Cypress 测试(Metabase)
你正在为 Metabase 代码库编写 Cypress E2E 测试。 在生成任何测试代码之前,你必须分析 React 组件源代码, 以理解 DOM 结构、选择器和用户流程。
阶段 0 — 调研
- 在编写任何代码之前,先阅读现有辅助函数:
e2e/support/helpers/— 所有共享辅助函数(restore、signInAs、openOrdersTable 等)e2e/support/cypress_sample_database.ts— 表/字段模式常量(ORDERS、PRODUCTS 等)e2e/support/cypress_sample_instance_data.ts— 实例特定 ID(ORDERS_DASHBOARD_ID、NORMAL_USER_ID 等)
- 使用 Glob 搜索
e2e/test/scenarios/,找到与待测试区域最接近的已有 spec 文件。 研究其模式——严格匹配它们。 - 使用 Glob 搜索
frontend/src/metabase/,找到该功能区域对应的 React 组件。
阶段 1 — 代码分析
阅读 React 组件源代码以理解 DOM 结构。不需要浏览器——源代码包含所有信息。
- 查找相关组件:使用 Glob 和 grep 搜索
frontend/src/metabase/中该功能区域的代码。 - 提取选择器:在相关组件中 grep 搜索
data-testid。 - 记录可见文本:阅读组件的 JSX,获取按钮标签、标题、占位符文本。
- 记录 aria 属性:在相关组件中 grep 搜索
aria-label。 - 理解用户流程:阅读事件处理函数(onClick、onSubmit、onChange)以理解交互过程。
- 查找 API 调用:grep 搜索
Api.use、fetch、useQuery、端点定义,识别需要拦截的 API 调用。 - 与已有 spec 交叉对照:找到同一区域内的已有 spec,重用其经过验证的选择器和
cy.intercept模式。
阶段 2 — 启动后端
默认使用 MB_EDITION=oss。仅当用户明确要求编写企业版测试时,才使用 MB_EDITION=ee。
使用 run_in_background: true(而不是 &)启动后端。
bin/e2e-backend 会自动检测后端是否已在运行并复用。
MB_EDITION=oss bin/e2e-backend
不要通过运行不相关的测试 spec 来手动生成快照。
bun test-cypress 运行器默认设置 GENERATE_SNAPSHOTS: true,并在运行任何 spec 之前
自动生成快照。通过 /e2e-test skill 在阶段 4 运行测试时,
如果快照尚不存在,将在首次运行时生成。
恢复干净的测试数据:
curl -sf -X POST http://localhost:4000/api/testing/restore/default
阶段 3 — 生成 Cypress Spec
遵循 Metabase Cypress 惯例:
@./../_shared/cypress-conventions.md
在代码分析中识别出 API 调用后,使用上面展示的拦截模式对它们进行桩代码替换或等待。
阶段 4 — 验证
生成 spec 后:
- 检查所有导入的辅助函数是否存在(在
e2e/support/helpers/中 grep 搜索)。 - 你必须使用
/e2e-testskill 来运行测试——不要直接运行bun test-cypress。/e2e-testskill 处理版本选择、快照管理和正确的环境变量设置。/e2e-test GREP="should do the thing" --spec e2e/test/scenarios/<path>如果你创建了多个it()块,请逐一运行每个块以隔离失败。
阶段 5 — 修复失败(最多尝试 2 次)
测试失败时,首先尝试根据 Cypress 输出修复:
- 查看失败截图(路径打印在
(Screenshots)下方)。 - 从控制台输出中查看错误消息和代码帧。
- 修复测试并重新运行(回到阶段 4,步骤 2)。
如果在 2 次尝试后仍无法诊断问题,则进入阶段 6。
阶段 6 — Playwright 回退
仅在阶段 5 中经过 2 次修复尝试失败后才能进入此阶段。后端已在运行。
恢复干净的测试数据:
curl -sf -X POST http://localhost:4000/api/testing/restore/default
绕过 CSP 头(在导航之前执行——Metabase 提供了严格的 CSP,会阻止开发服务器脚本)。
使用 browser_run_code 进行设置:
async (page) => {
// 移除 CSP 头以便页面加载(镜像 Cypress 的 chromeWebSecurity: false)
await page.context().route('**/*', async (route) => {
const response = await route.fetch();
const headers = { ...response.headers() };
delete headers['content-security-policy'];
delete headers['content-security-policy-report-only'];
await route.fulfill({ response, headers });
});
// 通过 API 登录
const response = await page.request.post('http://localhost:4000/api/session', {
data: { username: 'admin@metabase.test', password: '12341234' }
});
const session = await response.json();
await page.context().addCookies([{
name: 'metabase.DEVICE',
value: session.id,
domain: 'localhost',
path: '/'
}]);
await page.goto('http://localhost:4000');
await page.waitForLoadState('networkidle');
return 'signed in';
}
逐步维护观察日志。 每次进行重要的 Playwright 交互后, 立即将观察到的内容追加到临时文件中,然后再进行下一次交互:
cat >> /tmp/e2e-observations.md << 'OBSERVATION'
## [页面/流程名称]
- URL: /question/notebook#...
- 点击: "Box plot" 按钮 → 可见文本 "Box plot",角色: radio
- 选择器: data-testid="viz-type-button",findByText("Box plot")
- API 调用: POST /api/dataset(在可视化类型变更时触发)
- 关键状态: 选择可视化类型后,摘要侧边栏显示指标选择器
OBSERVATION
对于每个页面/流程:
- 获取无障碍快照(
browser_snapshot)。 - 点击交互式元素、填写表单、触发弹窗。
- 每执行一步后立即追加到观察日志:URL、可见文本、aria 标签、
data-testid属性、API 调用。 - 对关键状态进行截图。
探索完成后:
- 回读观察日志:
cat /tmp/e2e-observations.md - 使用观察到的选择器和行为修复测试。
- 重新运行测试(回到阶段 4)。
- 清理:
rm -f /tmp/e2e-observations.md
阶段 7 — 清理
所有测试通过后(或放弃修复失败后),始终杀死端口 4000 上的后端:
lsof -ti:4000 | xargs kill 2>/dev/null || true
不要使用宽泛的 pkill 模式——可能还有其他 Metabase 实例运行在不同的端口上。
阶段 2 启动的后端进程在 Claude 会话结束时不会被自动终止。
让其持续运行会浪费资源,并可能干扰将来的会话。务必清理。
禁止事项(工作流程)
- 不要将 Playwright 作为第一步——始终先分析源代码。
- 不要在阶段之间杀死后端——它应在整个过程中保持运行。
- 不要编造你在源代码中未找到或在浏览器中未观察到的选择器。
关于惯例层面的「禁止事项」(选择器、等待、辅助函数等),请参见阶段 3 中引用的惯例文件。