From 4f24a49b295f40d8b9445230469155879cfb2849 Mon Sep 17 00:00:00 2001 From: wehub-skill-sync Date: Mon, 13 Jul 2026 21:35:45 +0800 Subject: [PATCH] chore: import zh skill e2e-test-create --- README.wehub.md | 9 +++ SKILL.md | 176 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 185 insertions(+) create mode 100644 README.wehub.md create mode 100644 SKILL.md diff --git a/README.wehub.md b/README.wehub.md new file mode 100644 index 0000000..d95be7d --- /dev/null +++ b/README.wehub.md @@ -0,0 +1,9 @@ +# WeHub 来源说明 + +- Skill 名称:`e2e-test-create` +- 中文类目:由 Web UI 源码生成可运行 E2E 测试代码 +- 上游仓库:`metabase__metabase` +- 上游路径:`.claude/skills/e2e-test-create/SKILL.md` +- 上游链接:https://github.com/metabase/metabase/blob/HEAD/.claude/skills/e2e-test-create/SKILL.md +- 本仓库为 WeHub 中文 Skill 汉化包,基于 skill 市场筛选 Top200 清单整理 +- 原作者、版权和许可证信息以上游仓库为准 diff --git a/SKILL.md b/SKILL.md new file mode 100644 index 0000000..d71f994 --- /dev/null +++ b/SKILL.md @@ -0,0 +1,176 @@ +--- +name: e2e-test-create +description: > + 分析 React 组件源代码以理解 UI 结构, + 然后按照 Metabase 惯例生成符合习惯的 Cypress E2E 测试。 + 仅在代码阅读和截图调试不足以解决问题时,才回退使用 Playwright MCP 浏览器探索。 +disable-model-invocation: true +allowed-tools: + - Bash + - Read + - Write + - Grep + - Glob + - Skill + - "mcp__playwright__*" +--- + +# 代码阅读优先 → 生成 Cypress 测试(Metabase) + +你正在为 **Metabase** 代码库编写 Cypress E2E 测试。 +在生成任何测试代码之前,你**必须**分析 React 组件源代码, +以理解 DOM 结构、选择器和用户流程。 + +## 阶段 0 — 调研 + +1. 在编写任何代码之前,先阅读现有辅助函数: + - `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 等) +2. 使用 Glob 搜索 `e2e/test/scenarios/`,找到与待测试区域最接近的已有 spec 文件。 + 研究其模式——严格匹配它们。 +3. 使用 Glob 搜索 `frontend/src/metabase/`,找到该功能区域对应的 React 组件。 + +## 阶段 1 — 代码分析 + +阅读 React 组件源代码以理解 DOM 结构。不需要浏览器——源代码包含所有信息。 + +1. **查找相关组件**:使用 Glob 和 grep 搜索 `frontend/src/metabase/` 中该功能区域的代码。 +2. **提取选择器**:在相关组件中 grep 搜索 `data-testid`。 +3. **记录可见文本**:阅读组件的 JSX,获取按钮标签、标题、占位符文本。 +4. **记录 aria 属性**:在相关组件中 grep 搜索 `aria-label`。 +5. **理解用户流程**:阅读事件处理函数(onClick、onSubmit、onChange)以理解交互过程。 +6. **查找 API 调用**:grep 搜索 `Api.use`、`fetch`、`useQuery`、端点定义,识别需要拦截的 API 调用。 +7. **与已有 spec 交叉对照**:找到同一区域内的已有 spec,重用其经过验证的选择器和 `cy.intercept` 模式。 + +## 阶段 2 — 启动后端 + +默认使用 `MB_EDITION=oss`。仅当用户明确要求编写企业版测试时,才使用 `MB_EDITION=ee`。 + +使用 `run_in_background: true`(而不是 `&`)启动后端。 +`bin/e2e-backend` 会自动检测后端是否已在运行并复用。 +```bash +MB_EDITION=oss bin/e2e-backend +``` + +不要通过运行不相关的测试 spec 来手动生成快照。 +`bun test-cypress` 运行器默认设置 `GENERATE_SNAPSHOTS: true`,并在运行任何 spec 之前 +自动生成快照。通过 `/e2e-test` skill 在阶段 4 运行测试时, +如果快照尚不存在,将在首次运行时生成。 + +恢复干净的测试数据: +```bash +curl -sf -X POST http://localhost:4000/api/testing/restore/default +``` + +## 阶段 3 — 生成 Cypress Spec + +遵循 Metabase Cypress 惯例: + +@./../_shared/cypress-conventions.md + +在代码分析中识别出 API 调用后,使用上面展示的拦截模式对它们进行桩代码替换或等待。 + +## 阶段 4 — 验证 + +生成 spec 后: +1. 检查所有导入的辅助函数是否存在(在 `e2e/support/helpers/` 中 grep 搜索)。 +2. **你必须使用 `/e2e-test` skill** 来运行测试——不要直接运行 `bun test-cypress`。 + `/e2e-test` skill 处理版本选择、快照管理和正确的环境变量设置。 + `/e2e-test GREP="should do the thing" --spec e2e/test/scenarios/` + 如果你创建了多个 `it()` 块,请逐一运行每个块以隔离失败。 + +## 阶段 5 — 修复失败(最多尝试 2 次) + +测试失败时,**首先尝试根据 Cypress 输出修复**: +1. 查看失败截图(路径打印在 `(Screenshots)` 下方)。 +2. 从控制台输出中查看错误消息和代码帧。 +3. 修复测试并重新运行(回到阶段 4,步骤 2)。 + +如果在 2 次尝试后仍无法诊断问题,则进入阶段 6。 + +## 阶段 6 — Playwright 回退 + +仅在阶段 5 中经过 2 次修复尝试失败后才能进入此阶段。后端已在运行。 + +恢复干净的测试数据: +```bash +curl -sf -X POST http://localhost:4000/api/testing/restore/default +``` + +**绕过 CSP** 头(在导航之前执行——Metabase 提供了严格的 CSP,会阻止开发服务器脚本)。 +使用 `browser_run_code` 进行设置: + +```js +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 交互后, +立即将观察到的内容追加到临时文件中,然后再进行下一次交互: + +```bash +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 调用。 +- 对关键状态进行截图。 + +探索完成后: +1. 回读观察日志:`cat /tmp/e2e-observations.md` +2. 使用观察到的选择器和行为修复测试。 +3. 重新运行测试(回到阶段 4)。 +4. 清理:`rm -f /tmp/e2e-observations.md` + +## 阶段 7 — 清理 + +所有测试通过后(或放弃修复失败后),**始终杀死端口 4000 上的后端**: +```bash +lsof -ti:4000 | xargs kill 2>/dev/null || true +``` + +不要使用宽泛的 `pkill` 模式——可能还有其他 Metabase 实例运行在不同的端口上。 +阶段 2 启动的后端进程在 Claude 会话结束时**不会**被自动终止。 +让其持续运行会浪费资源,并可能干扰将来的会话。务必清理。 + +## 禁止事项(工作流程) + +- 不要将 Playwright 作为第一步——始终先分析源代码。 +- 不要在阶段之间杀死后端——它应在整个过程中保持运行。 +- 不要编造你在源代码中未找到或在浏览器中未观察到的选择器。 + +关于惯例层面的「禁止事项」(选择器、等待、辅助函数等),请参见阶段 3 中引用的惯例文件。