diff --git a/.claude/skills/d8d-dev/auto-fix-engine.md b/.claude/skills/d8d-dev/auto-fix-engine.md new file mode 100644 index 0000000..6717c6f --- /dev/null +++ b/.claude/skills/d8d-dev/auto-fix-engine.md @@ -0,0 +1,611 @@ +# 统一修复引擎(Auto-Fix Engine) + +> **⛔ 所有模式共享**:完整模式、回归验证模式、旅程验证模式均引用此文件。任何模式不得另起炉灶重新定义修复机制。 + +--- + +## 1. 步骤完成判定标准(⛔ 每个 step 执行后强制判定) + +**PASS**: `browser_snapshot` 显示的内容 **完全包含** 旅程/验证定义中该步骤"用户看到 X"的预期状态。 +- 例: 预期"看到已登录状态(头像、昵称)" → snapshot 中有实际昵称文字(非占位符、非"加载中...") → PASS + +**FAIL**: 以下任一 = FAIL +1. snapshot 内容与预期不符(如预期"已登录"但看到"加载中...") +2. `browser_click` / `browser_type` 无法找到目标元素(元素不存在或不可交互) +3. `browser_wait_for` 超时(页面未按预期响应) +4. snapshot 显示错误状态(错误提示、空白页、404、500) +5. 数据断言不通过(操作前后数据状态无变化或变化不符预期) + +**⛔ 禁止判定为 PASS 的情况**: +- `"加载中..."` / `"请稍候"` / 空白页 +- 元素存在但内容为空 / 占位符 +- `"暂未开放"` / `"开发中"` / `"功能开发中"` 提示 +- 不做 snapshot 直接假设操作成功 +- 用 `browser_evaluate` 绕过不可交互的元素后声称"操作完成" + +--- + +## 2. FAIL 分类标准(⛔ 所有模式共享) + +| # | FAIL 特征 | 分类 | 修复类型 | 示例 | +|---|-----------|------|---------|------| +| 1 | API 返回 404 且路由文件不存在 | 未实现 | AUTO-IMPLEMENT | 路由缺失 | +| 2 | API 返回 500 / 错误状态码 | Bug | AUTO-FIX | 空 body 返回 500 | +| 3 | 前端组件/按钮未渲染 | 未实现 | AUTO-IMPLEMENT | 登录入口按钮缺失 | +| 4 | 页面跳转到错误位置 | Bug | AUTO-FIX | 导航逻辑错误 | +| 5 | 页面停留在非功能态("加载中"/空白/错误态/有内容但零交互元素) | Bug | AUTO-FIX | hydration 失败 / 渲染 Bug / fetchProfile 空 catch / 响应结构不匹配 | +| 6 | 可交互元素无法通过 Playwright 操作 | Bug | AUTO-FIX | uni-input 编译后 browser_type 失败 | +| 7 | 功能提示"暂未开放"/"开发中"/"暂未实现" | 未实现 | AUTO-IMPLEMENT | 手机号登录只有占位符 | +| 8 | 测试数据过期/状态不对 | 数据问题 | AUTO-DATA-FIX | 收益冻结期不可提现 | +| 9 | 已有数据阻止测试(重复评价等) | 数据问题 | AUTO-DATA-FIX | 删除旧评价或换测试用户 | +| 10 | 正常业务规则正确拒绝无效操作 | 可接受 | 仅记录 | 最低提现金额限制 | +| 11 | 旅程定义或源码中发现 Mock 登录且手机号登录未实现 | 未实现 | AUTO-IMPLEMENT | Mock 登录不是真实用户流程,必须实现手机号+验证码登录 | +| 12 | 操作被前端表单校验拦截(Toast/提示阻止提交)| 环境限制/校验 | ENV-ADAPT | 按 Toast 内容分流:缺少必填→补充填写;文件上传→browser_file_upload;环境限制→代码添加 dev 替代 | +| 13 | 表单校验失败但页面无任何提示(无 Toast/无错误文字)| Bug(UX 缺陷)| AUTO-FIX | 用户不知道为什么操作无效 → 添加 Toast 或内联错误提示 | +| 14 | 即时使用数据(验证码等)仅通过 Toast 显示,无 console.log 或自动填充,下一步骤无法获取 | Bug(数据丢失风险)| AUTO-FIX | sendCode() 用 Toast 显示验证码但无 console.log,流程卡死 | +| 15 | UI 直接输出英文枚举值(如 APPROVED/REJECTED/PENDING/PAID/CANCELLED 等)而非本地化中文标签 | Bug(UI 本地化缺失)| AUTO-FIX | 任何前端页面(管理后台/小程序/H5)状态字段显示英文枚举而非中文 | +| 16 | 约束声明与代码行为不一致(Constraint Mismatch): 旅程描述/UI 文案说"可选/可跳过/选填"但代码实际必填,或 UI 标记"选填"但接口必填 | 约束不一致 | JOURNEY-FIX / AUTO-FIX | 旅程说"可跳过上传证件"但代码 `if (!idCardFront)` Toast "请上传所有证件照片" | +| 17 | 前端代码 CSS/style 含 `url("data:image/svg+xml")` 或 `url('data:image/svg+xml')`(SVG data URI 充当图标/背景/装饰) | Bug(平台不兼容) | AUTO-IMPLEMENT | 微信小程序不支持 SVG data URI。⛔ 必须按 platform-uni-app.md §1 完整安装 Iconify + tailwindcss-icons + weapp-tailwindcss,将所有 SVG data URI 替换为 Iconify 类名。**禁止用另一种 SVG data URI 替代原来的** | +| 18 | 页面同时有系统导航栏(pages.json navigationBarTitleText 非 custom)和自定义 fixed header,导致双层标题栏 | Bug(双重导航栏) | AUTO-FIX | 截图中顶部出现两层标题(系统导航+自定义头部),参见 ./reference/platform-uni-app.md §3 | +| 19 | Tab 页面下拉刷新方式不一致(scroll-view refresher / onPullDownRefresh / 无 混用),或使用列表级 scroll-view refresher | Bug(UX 不一致) | AUTO-FIX | 首页用 scroll-view refresher,其他 Tab 无刷新,参见 ./reference/platform-uni-app.md §7 | +| 20 | API route handler 无 try-catch 包裹,导致未处理异常直接返回原始 500 且无日志 | Bug(健壮性) | AUTO-FIX | Admin 提现审批 PUT 返回 500 但状态仍更新(异常被吞) | +| 21 | uni-app 页面使用 `onMounted` 获取数据,但该页面是 `uni.navigateBack()` 的返回目标,导致回退时数据不刷新 | Bug(数据陈旧) | AUTO-FIX | 提现后返回收益页余额未刷新(onMounted 不再触发) | +| **99** | **以上都不匹配的任何 FAIL** | **默认: Bug** | **AUTO-FIX** | **兜底规则,确保无遗漏** | +| **V1** | 页面主题/配色与原型不一致(深色→浅色、主色调偏差) | 视觉差异 | VISUAL-FIX | 原型深色主题但实际浅色;按钮颜色不一致 | +| **V2** | 页面布局结构与原型不同(顶部导航↔侧边栏、网格↔列表、卡片↔表格) | 视觉差异 | VISUAL-FIX | 原型顶部水平导航但实际左侧边栏;原型卡片网格但实际列表 | +| **V3** | 品牌标识与原型不同(Logo 图标、标题文字、副标题) | 视觉差异 | VISUAL-FIX | 原型闪电图标+"VividSpark"但实际 "V" 字母+"Welcome back" | +| **V4** | 原型存在的 UI 组件在实际页面中缺失(Remember me、FAQ、过滤器、标签页等) | 视觉差异 | VISUAL-FIX | 原型有 FAQ 区域但实际缺失;原型有 Remember me 但实际无 | +| **V5** | 按钮/输入框/卡片样式与原型不一致(padding、圆角、边框、字号等) | 视觉差异 | VISUAL-FIX | 原型圆角 16px 但实际 8px;原型按钮 padding 16/32 但实际 12/24 | + +### "可接受"严格限定(⛔ 仅以下 2 种,其余一律必须修复) + +1. **系统按设计正确拒绝了无效操作**(如未选协议点登录 → "请先同意用户协议"),且不影响后续步骤 +2. **业务规则正确阻止了当前数据状态下的操作**(如余额不足不可提现),且该规则不应为测试目的修改 + +**除以上 2 种外,任何 FAIL 不得标记为"可接受"。功能缺失、代码错误、交互故障均必须修复。** + +### ⛔ "已知问题不豁免"规则(⛔ 强制 — 跨轮次生效) + +**核心原则**: "已知" ≠ "已修复"。任何在之前的验证轮次中被发现但未实际修复的问题,在后续轮次再次检测到时,**必须**立即触发修复,不得以任何理由跳过。 + +**判定流程**: +``` +检测到 FAIL → 查分类表确定修复类型 + ↓ +读取 pipeline-state.yaml 前一次验证结果 + ↓ +该问题是否在上一轮已被记录(pending_fixes / verification_detail 中存在)? + ├── 是(跨轮次未修复)→ ⛔ 提升优先级,立即执行修复(不得再次"仅记录") + └── 否(新问题)→ 按正常分类触发修复 +``` + +**⛔ 禁止行为**: +- ❌ 以"已知/遗留/历史/V3遗留"为由跳过修复 +- ❌ 只记录到 `pending_fixes` 而不执行实际修复动作 +- ❌ 将修复推迟到"旅程完成后"或"下次验证" +- ❌ 标记"已记录"作为修复动作("已记录"不是修复) +- ❌ 以"非当前 EPIC/旅程范围"为由跳过 pending_fixes 中的问题(范围外 ≠ 豁免) + +**跨轮次问题升级规则**: +- 第 1 轮检测到 → 正常修复流程 +- 第 2 轮仍未修复 → **强制修复**(不得延迟,不得降级为 WARN) +- 第 3+ 轮仍未修复 → **阻断当前旅程**(标记 FAIL + 输出修复失败报告) + +### 关键判断流程 + +- API 404 → `grep` 检查路由文件是否存在 → 存在 = Bug(#2) → 不存在 = 未实现(#1) +- 页面显示"暂未开放"/"开发中"/"暂未实现" → 一律未实现(#7) → AUTO-IMPLEMENT +- 元素可见但不可操作(`browser_type` 失败) → Bug(#6) → ⛔ **先诊断**(`browser_evaluate` 检查 DOM/CSS) → 按诊断结果分流修复(禁止直接用 `browser_run_code` 绕过) +- 页面卡在"加载中..."/空白 → Bug(#5) → AUTO-FIX(调查根因:空 catch、响应结构不匹配、状态管理错误) +- 页面有静态内容但零交互元素(有文字但无 button/input/a/form)→ `browser_evaluate` 执行 FAIL #5 诊断 → 按诊断结果分流(见 FAIL #5 诊断子步骤) +- 操作后页面无变化(无跳转/无反馈)→ browser_snapshot 检查 Toast → 按 Toast 内容分流(#12) → ENV-ADAPT + - 有 Toast/错误提示 → 读取内容 → 按 #12 分流(补充填写 / browser_file_upload / ENV-ADAPT) + - ⛔ 无任何提示 → Bug(UX 缺陷:校验失败但用户看不到原因)→ AUTO-FIX(添加 Toast/错误提示) +- 即时使用数据(验证码)仅通过 Toast 显示且源码无 console.log → Bug(#14) → AUTO-FIX(添加 console.log + 自动填充) +- UI 显示英文枚举值(APPROVED/REJECTED/PENDING/PAID/CANCELLED 等)→ Bug(#15) → AUTO-FIX(添加中文映射函数,适用于所有前端:管理后台 statusLabel / 小程序 computed 映射) +- 前端代码含 `data:image/svg+xml`(SVG data URI)→ `grep -rn "data:image/svg+xml" app/mini-program/src/` 扫描所有 `.vue` 文件 → 发现 → 平台不兼容(#17) → AUTO-IMPLEMENT(⛔ 必须按 platform-uni-app.md §1 完整安装 Iconify + 替换所有 SVG data URI,**禁止用另一种 SVG data URI 替代原来的**) +- Tab 页面下拉刷新方式不一致(scroll-view refresher / onPullDownRefresh / 无 混用)→ Bug(#19) → AUTO-FIX(参见 ./reference/platform-uni-app.md §7 统一为 onPullDownRefresh) +- 无法归类的 FAIL → 默认 Bug(#99) → AUTO-FIX +- API 返回 500 + `Read` 源码发现 handler 无 try-catch → Bug(#20) → AUTO-FIX(添加 try-catch 包裹 + console.error + JSON 500 响应) +- `uni.navigateBack()` 返回后 `browser_snapshot` 数据未刷新 + 源码用 `onMounted` 非 `onShow` → Bug(#21) → AUTO-FIX(替换 `onMounted` 为 `onShow`) + +--- + +## 3. 真实用户完成规则(⛔ 旅程验证模式强制) + +**核心原则**: 旅程中的每个用户步骤,必须是一个**仅使用鼠标/键盘/触摸屏的真实用户**能够完成的方式。 + +**具体规则**: +- `browser_type` 在可见 input 上失败 → Bug(#6) → AUTO-FIX +- `browser_click` 在可见按钮上失败 → Bug(#6) → AUTO-FIX +- 禁止用 `browser_evaluate` / `browser_run_code` 绕过不可交互的元素 +- "需要 evaluate 才能操作" 本身就是 Bug → AUTO-FIX + +**`browser_run_code` 仅允许场景**: +- 检测布局溢出等非用户交互操作 +- 读取 localStorage / cookie 等数据状态验证 +- 复杂的拖拽操作(`browser_drag` 不可用时) + +**⛔ 如果 `browser_type` 在可见 input 上失败**: +1. 标记该步骤 FAIL +2. 分类: Bug(#6 前端交互问题) +3. ⛔ **诊断**: `browser_evaluate` 检查 DOM 结构和计算样式(见下方 FAIL #6 诊断子步骤) +4. 根据诊断结果选择修复策略(CSS Bug / 可见性 Bug / 事件 Bug / 框架编译 Bug) +5. 触发 AUTO-FIX: 修复具体根因 +6. 修复后重试 `browser_type` +7. 仍失败 → 标记 journey-step-fail,继续下一步骤 + +**⛔ FAIL #5 诊断子步骤(页面有内容但零交互元素时强制执行)**: + +**触发条件**: `browser_snapshot` 显示页面有静态内容(标题/文字),但没有 button、input、a、form 等可交互元素。 + +1. `browser_evaluate` 检查页面状态: + ```javascript + () => { + const interactive = document.querySelectorAll('button, input, a, form, [role="button"], [role="link"]'); + const hasContent = document.body.innerText.trim().length > 0; + // 检测框架挂载状态 + const hasNextRoot = !!document.getElementById('__next'); + const hasReactRoot = !!document.querySelector('[data-reactroot]'); + const hasVueApp = !!document.querySelector('[data-v-app]') || !!document.querySelector('#app')?.__vue_app__; + const frameworkMounted = hasNextRoot || hasReactRoot || hasVueApp; + // 检测 JS 是否加载 + const scriptTags = document.querySelectorAll('script[src]').length; + return { + interactiveCount: interactive.length, + hasContent, + frameworkMounted, + hasNextRoot, hasReactRoot, hasVueApp, + scriptTags, + bodyHTMLLength: document.body.innerHTML.length, + bodyText: document.body.innerText.substring(0, 300) + }; + } + ``` + +2. 根据诊断结果分流: + - `interactiveCount === 0 && frameworkMounted === true` → **框架已挂载,当前页面源码就是无交互的展示页** + → `grep/Read` 检查当前页面源码:源码无交互组件 → **功能缺失** → AUTO-IMPLEMENT(添加 redirect/导航逻辑) + → 源码有交互组件但未渲染 → **渲染 Bug(#5)** → AUTO-FIX + - `interactiveCount === 0 && frameworkMounted === false && scriptTags > 0` → **JS 加载但框架未挂载** → **Hydration 失败 Bug(#5)** → AUTO-FIX(检查控制台错误、检查构建产物) + - `interactiveCount === 0 && !hasContent` → **空白页** → Bug(#5) → AUTO-FIX(检查加载逻辑) + +3. 修复后重测: + - AUTO-IMPLEMENT 添加 redirect 后 → `browser_navigate` 重试 → 应自动跳转到功能页 → `browser_snapshot` 确认有交互元素 + - AUTO-FIX 修复 hydration/渲染后 → `browser_navigate` 重试 → `browser_snapshot` 确认交互元素已渲染 + +**⛔ FAIL #6 诊断子步骤(`browser_type`/`browser_click` 失败时强制执行)**: + +> uni-app/小程序编译特性诊断(如 uni-input height:0px)详见 **./reference/platform-uni-app.md 第 3.1 节**。 + +1. `browser_evaluate` 检查目标元素的 DOM 结构和计算样式: + ```javascript + (el) => { + const tag = el.tagName?.toLowerCase(); + const computed = getComputedStyle(el); + const info = { + tag, height: computed.height, width: computed.width, + opacity: computed.opacity, display: computed.display, + innerInputs: [] + }; + el.querySelectorAll('input').forEach(i => { + const c = getComputedStyle(i); + info.innerInputs.push({ + height: c.height, width: c.width, + opacity: c.opacity, pointerEvents: c.pointerEvents + }); + }); + return info; + } + ``` + +2. 根据诊断结果分流: + - `innerInputs[0].height === '0px'` → **框架编译 CSS Bug** → AUTO-FIX: 给 input 或包裹容器添加显式 `height` + - `opacity === '0'` 或 `pointerEvents === 'none'` → **可见性 Bug** → AUTO-FIX: 修复 CSS 可见性 + - 无 `innerInputs`(非框架编译)→ **原生交互 Bug** → AUTO-FIX: 修复事件绑定或 DOM 结构 + - 诊断结果正常但 `browser_type` 仍失败 → **Playwright 适配** → 允许 `browser_run_code` + `force: true`(仅限此场景,且须记录适配原因) + +--- + +## 4. 修复流程 + +### AUTO-FIX(代码 Bug,max 2 次) + +**适用**: API 错误、逻辑错误、配置错误、渲染异常、交互故障、空 catch 块、响应结构不匹配 + +**流程**: +1. 读取失败原因,定位源文件和行号(`grep` / `Read`) +2. ⛔ 如果是 FAIL #6(交互失败)→ 先执行诊断子步骤(见上方),按诊断结果选择修复策略 +3. `Edit` 最小改动修复 +4. `Bash` 重启受影响服务(如有必要) +5. 重测失败步骤 → PASS 继续 / FAIL 重试 + +**示例**: +- 退款 API 无 body 返回 500 → `Edit` 添加 body 解析容错 +- `fetchProfile` 空 catch 吞噬错误 → `Edit` 添加错误日志 + null 处理 +- API 响应结构与前端期望不匹配 → `Edit` 修正数据提取逻辑 +- input 元素不可交互 → `browser_evaluate` 诊断 → 按根因修复(见 FAIL #6 诊断子步骤) +- uni-app input `height: 0px` → `Edit` 给 `.input-field` 添加显式 `height: 44px` +- 管理后台首页缺少 redirect → 源码检查发现 page.tsx 是纯展示 → AUTO-IMPLEMENT: 添加 `redirect("/login")` +- React hydration 失败 → `browser_evaluate` 确认框架未挂载 → AUTO-FIX: 检查构建产物和 `use client` 指令 +- 任何前端页面状态字段显示英文枚举 → `Edit` 添加中文映射函数(管理后台: statusLabel / 小程序: computed 属性)→ 替换直接输出为映射函数调用 + +### AUTO-IMPLEMENT(功能缺失,max 2 次) + +**适用**: 路由缺失、组件未渲染、功能占位符("暂未开放")、后端接口未实现 + +**流程**: +1. 读取 `phase-03-implement.md` 对应方案(如存在) +2. `Write` 新文件 / `Edit` 补充实现 +3. `Bash` 安装依赖(如需)+ 重启服务 +4. 重测失败步骤 → PASS 继续 / FAIL 重试 + +**示例**: +- 个人页无登录入口 → `Edit` 添加登录按钮 +- 手机号登录"暂未开放" → `AUTO-IMPLEMENT` 实现完整手机号+验证码登录流程 +- 后端路由缺失 → `Write` 新 `route.ts` + `handler` +- 图标方案未实施 → 按方案安装 Tailwind + Iconify + +**Emoji → Iconify 替换方案(检测到 emoji 充当功能图标时使用 ⛔ 强制)**: + +⛔ **完整安装配置、使用方式、禁止方案列表见 ./reference/platform-uni-app.md 第 1 节。禁止自行发明替代方案。** + +适用条件: icon/btn 元素内含 emoji 字符(如 🔍🔔💬🪪💳📷🏠📱) + +执行步骤: +1. **Read fully and follow: ./reference/platform-uni-app.md 第 1 节**(⛔ 不可跳过 — 含 Admin/Mini-program 两条路径完整安装步骤) +2. **替换源码**: 将所有 emoji 字符替换为 Iconify 类名(如 🔍 → `class="i-lucide-search"`) +3. **Bash** 安装依赖 + 重新构建 +4. **重测** 验证图标渲染正常 + +⛔ **禁止的替代方案**(完整列表见 ./reference/platform-uni-app.md 第 1 节"禁止的替代方案") + +**手机号+验证码登录实现方案(#11 触发时使用)**: + +当旅程验证发现 Mock 登录(非真实用户流程)且手机号登录未实现时,按以下方案 AUTO-IMPLEMENT: + +1. **后端: POST /api/auth/send-code** + - 接收 `{phone}` + - `NODE_ENV=development`: 生成 6 位随机验证码,存 Redis `sms:${phone}` TTL 300s,**直接返回** `{code: "123456"}` — 这是标准开发实践,不是 Mock + - `NODE_ENV=production`: 调用短信服务商 API 发送验证码,返回 `{success: true}` +2. **后端: POST /api/auth/login** — 增强已有接口 + - 接收 `{phone, code}` + - 从 Redis 取出验证码比对 + - 匹配 → 查找/创建用户 → 返回 JWT(复用已有登录逻辑) +3. **前端: 启用 login/index.vue 已有的手机号表单**(HTML 已存在,仅需实现 JS) + - `sendCode()`: 调用 `POST /api/auth/send-code`,开发环境将返回的 code 自动填入输入框 + - `handlePhoneLogin()`: 调用 `POST /api/auth/login {phone, code}`,成功后跳转首页 +4. **清理**: 删除 Mock 登录按钮(`v-if="isDev" class="mock-btn"`)和 `handleMockLogin()` 函数 + +### AUTO-FIX #14: 即时使用数据持久化(max 1 次) + +**适用**: FAIL #14 — 即时使用数据(验证码等)仅通过 Toast 显示,无 console.log,下一步骤无法获取 + +**为什么这是 Bug**: 下一步骤需要的数据(如验证码)仅通过 Toast 传递,3秒后消失。Playwright 无法回溯,流程卡死。 +注意:仅适用于**下一步骤立即需要**的数据。订单号、支付结果等后续页面自然显示的不在此列。 + +**流程**: +1. 读取失败页面源码,定位 `uni.showToast` 传递即时使用数据的位置 +2. 确认数据变量名(如 `res.code`) +3. 在 `uni.showToast` 调用**前**添加 `console.log('[DEV] 验证码:', res.code)` +4. 对验证码场景:添加自动填充 `if (res.code) { code.value = res.code }` +5. Toast 保留作为辅助反馈,不删除 +6. 重测失败步骤 → PASS 继续 / FAIL 标记 journey-step-fail + +**修复模板**: +```javascript +// 修复前: +const msg = res.code ? `验证码已发送:${res.code}(开发环境)` : "验证码已发送"; +uni.showToast({ title: msg, icon: "none" }); + +// 修复后: +console.log('[DEV] 验证码:', res.code); +if (res.code) { code.value = res.code; } +uni.showToast({ title: "验证码已发送", icon: "success" }); +``` + +### AUTO-FIX #18: 双重导航栏修复(max 1 次) + +**适用**: FAIL #18 — 页面在 pages.json 有 `navigationBarTitleText`(非 custom)且页面源码有 `position:fixed` 的自定义 header + +**为什么这是 Bug**: 系统导航栏与自定义头部同时显示,用户看到两层标题(如"首页"+"NEON PULSE"),浪费屏幕空间且视觉混乱。uni-app 小程序项目应统一使用 `navigationStyle: "custom"`。 + +**流程**: +1. `Read` pages.json → 找到该页面的 style 配置 +2. `Edit` pages.json → 将该页面 style 改为 `{ "navigationStyle": "custom" }`(移除或保留 navigationBarTitleText 均可,custom 模式下不显示) +3. `Read` 页面源码 → 找到自定义 header 的 CSS(通常为 `position: fixed; top: 0` 的元素) +4. `Edit` 页面源码 CSS: + - header 添加 `padding-top: env(safe-area-inset-top, 20px)` + - header height 改为 `calc(原高度 + env(safe-area-inset-top, 20px))` + - content margin-top 与新 header height 一致(含 safe-area) +5. 重测 → 截图确认只有一层标题 + +**修复示例**: +```css +/* 修复前(首页 header 与系统导航栏重叠): */ +.header { position: fixed; top: 0; left: 0; right: 0; height: 64px; } +.content { margin-top: 64px; } + +/* 修复后(统一使用自定义导航栏): */ +.header { + position: fixed; top: 0; left: 0; right: 0; + height: calc(64px + env(safe-area-inset-top, 20px)); + padding-top: env(safe-area-inset-top, 20px); +} +.content { + margin-top: calc(64px + env(safe-area-inset-top, 20px)); +} +``` + +```jsonc +// pages.json 修复: +// 修复前: +{ "path": "pages/home/index", "style": { "navigationBarTitleText": "首页" } } +// 修复后: +{ "path": "pages/home/index", "style": { "navigationStyle": "custom" } } +``` + +### AUTO-FIX #19: 下拉刷新方式统一(max 1 次) + +**适用**: FAIL #19 — Tab 页面下拉刷新方式不一致(scroll-view refresher / onPullDownRefresh / 无 混用) + +**⛔ 详细修复步骤见 ./reference/platform-uni-app.md §7** + +**流程**: +1. `Edit` pages.json → globalStyle 添加 `"enablePullDownRefresh": true` +2. 对 LIST_LEVEL 页面(scroll-view refresher): + - 移除 scroll-view 的 `refresher-enabled` / `:refresher-triggered` / `@refresherrefresh` + - 移除 `refreshing` ref 和 `onRefresh` 函数 + - 添加 `onPullDownRefresh` 生命周期(from `@dcloudio/uni-app`) + - 数据加载 `finally` 中调用 `uni.stopPullDownRefresh()` +3. 对 NONE 页面:添加 `onPullDownRefresh` + 对应数据刷新逻辑 + `uni.stopPullDownRefresh()` +4. 重测 → 所有 Tab 页面整页下拉刷新效果一致 + +### AUTO-FIX #20: API route try-catch 包裹(max 1 次) + +**适用**: API endpoint 返回 500,`Read` 源码发现 handler 函数无 try-catch 包裹 + +**为什么这是 Bug**: 未处理异常直接返回原始 500 且无日志,无法诊断根因。常见的二次操作(如审批后创建通知)失败会吞噬前面的成功状态。 + +**检测方法**: API 返回 500 时,`Read` 对应 route.ts 源码,检查 `export async function POST/PUT/DELETE/GET` 内是否有 try-catch + +**修复流程**: +1. `Read` 失败的 route.ts 文件 +2. 确认 handler 无 try-catch 包裹 +3. 在 requireAdmin/鉴权检查之后,将剩余逻辑包裹在 try-catch 中 +4. catch 块:`console.error("Xxx error:", error)` + `NextResponse.json({error: "处理失败"}, {status: 500})` +5. 参考模式:同项目中已有 try-catch 的 route(如 admin auth login) + +**修复模板**: +```typescript +// 修复前: +export async function PUT(req, { params }) { + const result = requireAdmin(req); + if ("error" in result) return result.error; + const body = await req.json(); // ← 无 try-catch,异常直接变 500 + // ... 业务逻辑 ... +} + +// 修复后: +export async function PUT(req, { params }) { + const result = requireAdmin(req); + if ("error" in result) return result.error; + try { + const body = await req.json(); + // ... 业务逻辑不变 ... + } catch (error) { + console.error("Xxx error:", error); + return NextResponse.json({ error: "处理失败" }, { status: 500 }); + } +} +``` + +### AUTO-FIX #21: uni-app 页面 onMounted→onShow 替换(max 1 次) + +**适用**: 旅程中 `uni.navigateBack()` 返回目标页面后,页面数据未刷新 + +**为什么这是 Bug**: `onMounted` 仅在组件首次创建时触发。当用户从子页面 `navigateBack()` 返回时,父页面组件仍在导航栈中未被销毁,`onMounted` 不会再次触发。uni-app 的 `onShow` 在每次页面显示时都会触发,是正确的数据刷新时机。 + +**触发条件**(三条件全部满足): +- 条件 A: 旅程中使用 `uni.navigateBack()` 返回到该页面 +- 条件 B: 返回后 `browser_snapshot` 显示数据未更新(如余额、列表数量未变化) +- 条件 C: `Read` 页面源码确认数据获取在 `onMounted` 而非 `onShow` 中 + +**修复流程**: +1. `Read` 页面 `.vue` 文件 +2. 修改 import:将 `import { ref, onMounted } from "vue"` 中 `onMounted` 移除(若 vue 导入不再需要 onMounted) +3. 添加 `import { onShow } from "@dcloudio/uni-app"`(新 import 行) +4. 将 `onMounted(() => { fetchXxx() })` 替换为 `onShow(() => { fetchXxx() })` +5. 参考模式:项目中已有正确使用 `onShow` 的页面(如 profile/index.vue) + +**修复模板**: +```typescript +// 修复前: +import { ref, onMounted } from "vue"; +// ... +onMounted(() => { fetchEarnings(); }); + +// 修复后: +import { ref } from "vue"; +import { onShow } from "@dcloudio/uni-app"; +// ... +onShow(() => { fetchEarnings(); }); +``` + +### AUTO-FIX/JOURNEY-FIX #16: 约束一致性修复(max 1 次) + +**适用**: FAIL #16 — 旅程描述/UI 文案说"可选"但代码实际必填,或 UI 标记"选填"但接口必填 + +**检测触发条件**(双条件同时满足): +- 条件 1:声明方说"可选"(旅程描述含"可跳过/可选/选填/skip/optional/使用占位图" 或 页面文本含"(选填)/(可选)") +- 条件 2:代码实际"必填"(Toast 含"请上传/请填写/请选择/不能为空" 或 源码有 `if (!field)` 验证) + +**为什么这是问题**: 用户被"可选"承诺误导,但操作时被强制校验拦截。真实用户体验受损。 + +**流程**: +1. `Read` 对应页面源码 → 定位必填验证逻辑(`if (!field)` / `required` / `validate`) +2. 判断正确设计意图: + - PRD/UX 规范说可选 → AUTO-FIX 代码(移除必填验证或改为可选提交) + - PRD/UX 规范说必填 → JOURNEY-FIX 旅程定义(更新描述为必填)+ AUTO-FIX UI 文案(移除"选填"标记) + - 无明确 PRD 依据 → JOURNEY-FIX(旅程定义改为反映代码行为)+ 记录 `constraint_mismatch` 到 pending_fixes +3. 修复后重测该步骤 + +**代码修复示例**(设计意图=可选时): +```javascript +// 修复前(强制必填): +if (!form.idCardFront || !form.idCardBack || !form.idCardHolding) { + uni.showToast({ title: "请上传所有证件照片", icon: "none" }); + return; +} + +// 修复后(改为可选): +// 移除强制校验,允许不上传照片直接提交 +``` + +**旅程修复示例**(设计意图=必填时): +```markdown +// 修复前: +9. 用户第二步上传身份证照片(可跳过或使用占位图)→ 点击"下一步" + +// 修复后: +9. 用户第二步上传身份证照片(必填:身份证正面、反面、手持照片)→ 点击"下一步" +``` + +### AUTO-DATA-FIX(数据状态,max 1 次) + +**适用**: 测试数据过期、冻结期收益不可用、关联数据缺失 + +**流程**: +1. `Bash` 直接操作数据库或 API 修复数据状态 +2. 重测失败步骤 + +**示例**: +- 收益冻结期不可提现 → `Bash`: `psql UPDATE earnings SET status='AVAILABLE'` +- 组局状态不正确 → API: `PUT /api/admin/events/[id]` 修正状态 +- 测试用户数据缺失 → API: `POST` 创建缺失的关联记录 + +### ENV-ADAPT(环境限制,max 1 次) + +**适用**: 操作被前端表单校验拦截(#12),Toast 提示了具体原因 + +**流程**: +1. `browser_snapshot` 读取页面 Toast/错误提示内容 +2. 根据 Toast 内容分流: + - "请输入/请填写/不能为空" → 找到对应字段 → `browser_type` 补充填写 → 重试操作 + - "请上传/请选择图片/请选择文件" → `browser_click` 触发文件选择 → `browser_file_upload` 上传测试文件 → 重试操作 + - "暂未开放/开发中" → 分类为 AUTO-IMPLEMENT → 走 auto-implement 流程 +3. 重试仍失败 → 标记 journey-step-fail + Toast 内容 + +**⛔ 重要:表单校验失败但页面无任何提示 → Bug(#13) → AUTO-FIX(添加 Toast/错误提示)** + +**示例**: +- 局头申请要求上传证件照 → Toast "请上传所有证件照片" → `browser_file_upload` 上传测试图片 → 重试提交 +- 注册表单缺少必填项 → Toast "请输入手机号" → `browser_type` 填写手机号 → 重试提交 +- 表单提交无反应且无 Toast → Bug(#13) → AUTO-FIX: 添加 Toast 提示 + +### 修复失败处理 + +- 重试达到上限仍 FAIL → 标记该步骤 `journey-step-fail` +- 记录: `{journey, step, fail_reason, fix_type, fix_attempted, fix_result}` +- 继续执行下一个步骤(不阻断整条旅程) +- 旅程完成后统计: `steps_passed / steps_total` → 决定整体 PASS/FAIL + +--- + +## 5. GATE-CHECK(⛔ 修复完成后、记录阶段之前执行) + +``` +GATE-CHECK: +├── 所有交互使用真实用户操作(无 browser_run_code 绕过 input/button) ← 真实用户完成规则 +├── browser_take_screenshot 已执行 ← 证据检查 +├── ⛔ analyze_image 已执行(所有截图都有对应 AI 视觉分析结果) ← 视觉验证检查 +│ 验证:pipeline-state 中 analyze_image_executed === true +│ 未执行 → 不得标记完成,必须回退执行视觉验证(见 ./visual-verification.md) +├── browser_network_requests 已执行 ← 网络检查 +├── failed_steps 数量 = 0 ← 断言检查 +├── 即时使用数据有 console.log 或自动填充(无 #14 违规) ← 数据可追溯检查 +├── auto_fix 对所有 FAIL 已触发 ← 修复检查 +├── ⛔ verification_detail 中每个 FAIL 项都有对应的 auto_fix 记录 ← FAIL-修复交叉验证 +│ 逐一比对 verification_detail 与 auto_fix 记录: +│ english_enum_check.found = FOUND → auto_fix 必须包含 #15 修复记录 +│ icons_check.emoji_as_icons = FOUND → auto_fix 必须包含 Iconify 替换修复记录 +│ icons_check.tabbar_icons_visible = FAIL → auto_fix 必须包含 TabBar 修复记录 +│ 缺少对应记录 → 不得标记完成,必须回退执行修复 +└── ⛔ 视觉验证 FAIL 的修复在内联关卡已执行(非推迟到步骤 4) ← 内联修复检查 + +结果判定: +├── 全部 PASS → 继续记录阶段,status: pass +├── FAIL + 修复成功 → 继续记录阶段,status: pass(含修复记录) +└── FAIL + 修复失败/未触发 → status: fail(⛔ 不得记录为 pass) +``` + +### ⛔ 严重违规行为 + +- 检测到 FAIL 但没有执行分类判定 +- 分类判定为 Bug/未实现但没有触发修复 +- 修复失败但没有记录 `auto_fix_result` +- `status: pass` 但 `failed_steps > 0` 且未全部修复成功 +- 用 `browser_evaluate` 绕过不可交互的 input/button 后标记步骤 PASS +- 把"加载中..."/"暂未开放"判定为 PASS +- 任何 FAIL 标记为"可接受"但不符合上述严格限定的 2 种情况 +- **用 SVG data URI / CSS background-image 替代 Iconify 图标方案**(微信小程序不支持 SVG data URI,必须用 @egoist/tailwindcss-icons + Iconify) + +--- + +## 5.5 步骤级状态更新(⛔ AUTO-FIX/AUTO-IMPLEMENT 完成后强制执行) + +**修复完成后,必须同步更新步骤级跟踪文件,确保修复证据完整可追溯。** + +``` +AUTO-FIX/AUTO-IMPLEMENT 执行后: + 1. 更新 verification-checklist.yaml: + - 找到当前 step → status 改为 "fixed"(修复后待重测)或 "pass"(重测通过后) + - 如果重测通过 → status 改为 "pass" + + 2. 更新 pipeline-state.yaml step_results: + - 在当前步骤的 auto_fixes 数组中追加修复记录: + ```yaml + auto_fixes: + - fix_id: "FIX-{NNN}" + issue_id: "ISSUE-{NNN}" # 关联触发修复的问题 + fix_type: "AUTO-FIX|AUTO-IMPLEMENT|AUTO-DATA-FIX" + description: "{修复描述}" + files_changed: [ "{文件路径}" ] + retry_count: {N} + result: "pass|fail" + fixed_at: "{ISO timestamp}" + screenshot_after_fix: "{修复后重测截图路径}" + ``` + + 3. 更新步骤级 GATE: + - 重新检查 G5: all_issues_resolved + - 修复通过 → step_gate 改为 "pass" + - 修复失败且重试耗尽 → step_gate 改为 "fail",步骤标记 failed + + 4. 如重测需要重新截图和 analyze_image: + - browser_take_screenshot → 保存为新截图(原截图保留) + - analyze_image → 更新 analyze_image_result + - 更新 step_results 的 screenshot_path 和 analyze_image_result +``` + +**⛔ 禁止行为**: +- ❌ 修复后不更新 verification-checklist.yaml 的 step status +- ❌ 修复后不追加 auto_fix 记录到 step_results +- ❌ 修复后不重新检查步骤级 GATE +- ❌ 修复失败但 step_gate 仍标记为 "pass" + +--- + +## 6. 各模式引用方式 + +以下文件必须引用本引擎,删除各自的重复定义: + +| 文件 | 引用位置 | +|------|---------| +| `journey-mode.md` | 执行模板步骤 4(修复阶段)→ `Read fully and follow: ./auto-fix-engine.md` | +| `validation-mode.md` | AUTO-FIX/AUTO-IMPLEMENT 分支 → `Read fully and follow: ./auto-fix-engine.md` | +| `phase-04-test.md` | Phase 4.5 AUTO-FIX 循环 → 引用第 4 节修复流程 | +| `phase-05-review.md` | 质量关卡 FAIL 处理 → 引用第 2 节分类标准 | +| `visual-diff-mode.md` | Phase VD-3 逐页修复 → VISUAL-FIX (V1-V5) 分类引用 | +| `e2e-testing.md` | 步骤 9.5 判定规则 → 引用第 2 节分类标准 | +| `iron-laws.md` | 检测即阻断规则 → 引用第 2 节分类标准 + 第 5 节 GATE-CHECK | diff --git a/.claude/skills/d8d-dev/e2e-testing.md b/.claude/skills/d8d-dev/e2e-testing.md new file mode 100644 index 0000000..c0c170c --- /dev/null +++ b/.claude/skills/d8d-dev/e2e-testing.md @@ -0,0 +1,329 @@ +# E2E 测试 + +> 参见 ./auto-fix-engine.md 了解统一修复引擎(FAIL分类标准 + 修复流程 + GATE-CHECK)。 + +## E2E 测试覆盖矩阵 + +| 目标 | 启动方式 | Playwright MCP 测试方法 | +|------|---------|----------------------| +| 管理后台(Next.js) | `pm2 start ecosystem.config.js` → admin 进程 | browser_navigate → snapshot → click/type → verify | +| 小程序用户端(uni-app) | `pm2 start ecosystem.config.js` → mini-h5 进程 | browser_navigate → snapshot → click/type → verify | +| API 路由 | `pm2 start ecosystem.config.js` → api 进程 | browser_navigate → evaluate(JSON 验证) | + +### Playwright MCP 测试序列模式 + +**此序列为完整模式,所有步骤都是强制的**(除非页面确实没有表单/按钮)。 + +``` +0. browser_resize → 设置正确视口(管理后台 1440x900 / 小程序 375x812) +1. browser_navigate → 目标 URL +2. browser_snapshot → 验证页面/组件加载(确认非 404/空白) +3. browser_type → 填写表单(页面有表单则必须填写,不能跳过) +4. browser_click → 提交/操作按钮(页面有按钮则必须点击,不能跳过) +5. browser_wait_for → 等待响应/数据加载(timeout: 5s) +6. browser_snapshot → 验证操作结果(数据已创建/更新/删除) +7. browser_take_screenshot → 保存截图到 tests/e2e/screenshots/story-x-y.png(强制) +8. analyze_image → AI 视觉验证截图(强制,见下方"增强 analyze_image 提示词") +8.5. browser_evaluate → 布局溢出检测(小程序 Story 强制,见下方"步骤 8.5") +9. browser_network_requests → API 调用目标检测(步骤 8 之后,前端 Story 强制) +``` + +**强制规则**: +- 步骤 0(resize)不能省略,即使"上一个 Story 刚 resize 过" +- 步骤 3-5(交互)在页面有表单/按钮时**绝对不能省略** +- 步骤 7(截图)是强制证据,不是可选装饰 +- 步骤 8(视觉验证)**不调用等于验证未完成**(⛔ 详细规则和标准提示词见 ./visual-verification.md) +- 步骤 9(API 目标检测)**前端 Story 必须执行**,检测页面 JS 发起的 API 请求是否指向 localhost 而非外网域名 + +### API 调用目标检测(步骤 9 详解) + +**目的**:检测前端页面的 JavaScript 发出的 API 请求是否指向正确的服务器地址。如果前端代码 fallback 到 `http://localhost:3001`,在 Playwright 测试环境下可以成功(因为 Playwright 运行在 localhost),但真实外网用户会失败。 + +**检测方法**: + +``` +在步骤 8(analyze_image)之后执行: +browser_network_requests: + filter: "/api/" + requestHeaders: false + requestBody: false + static: false +``` + +**判定规则**: + +1. 读取返回的网络请求列表中所有请求的 URL +2. 检查是否有任何 URL 包含 `localhost` 或 `127.0.0.1` +3. 结合 `pipeline-context.md` 中的 `URL_MODE` 判断: + - `URL_MODE` 为 "外网域名" 且发现 localhost API 调用 → **FAIL** + - `URL_MODE` 为 "localhost(fallback)" 且发现 localhost API 调用 → **PASS**(符合预期) + +**结果记录**: +```yaml +verification_detail: + api_target_verified: true/false + api_target_violations: ["http://localhost:3001/api/xxx"] # 仅失败时 +``` + +**失败处理**: +1. 检查 `.env.local` 是否存在:`cat app/mini-program/.env.local` / `cat app/admin/.env.local` +2. 检查文件内容是否包含正确的外网域名 +3. 如果文件正确但仍失败 → 重启服务 `pm2 restart admin && pm2 restart mini-h5`,清缓存后重试 + +**注意**: +- 此检测必须在页面有 API 交互后执行(先完成 browser_click 等操作触发 API 调用) +- 如果页面刚打开还没有 API 请求,需要先触发一个会产生 API 调用的操作 +- 此检测是对 phase-00-setup.md 步骤 4.7 的运行时验证 + +### 步骤 9.5: 源码三重检查(所有前端 Story 强制) + +**目的**:检测前端源码中的三类问题 — localhost fallback、图标方案未合规、emoji 充当图标。 +**适用范围**: app/ 下所有前端代码(admin、mini-program 等),不仅限于 admin。 + +**检测方法**(对所有涉及前端页面的 Story 强制执行): + +```bash +# 检查 1: localhost fallback 值 +LOCALHOST_FALLBACK=$(grep -rn "localhost:[0-9]" app/*/src/ --include="*.ts" --include="*.tsx" --include="*.vue" | grep -v node_modules) +LOCALHOST_COUNT=$(echo "$LOCALHOST_FALLBACK" | grep -c ":" || echo 0) +echo "localhost_fallback=$LOCALHOST_COUNT" +[ "$LOCALHOST_COUNT" -gt 0 ] && echo "LOCALHOST_FILES:" && echo "$LOCALHOST_FALLBACK" + +# 检查 2: 内联 API 声明(非集中式模块) +INLINE_FILES=$(grep -rn "const.*API_BASE\s*=" app/*/src/ --include="*.ts" --include="*.tsx" --include="*.vue" | grep -v node_modules | grep -v "lib/api\|config/api\|lib/config") +INLINE_COUNT=$(echo "$INLINE_FILES" | grep -c ":" || echo 0) +echo "inline_api=$INLINE_COUNT" +[ "$INLINE_COUNT" -gt 0 ] && echo "INLINE_FILES:" && echo "$INLINE_FILES" + +# 检查 3: 图标方案合规 + Emoji 充当图标 +# 完整检测命令见 ./reference/platform-uni-app.md 第 6 节 +``` + +**判定规则**: + +| 条件 | 分类 | 结果 | 动作 | +|------|------|------|------| +| `LOCALHOST_COUNT > 0` | Bug | **FAIL** | AUTO-FIX: 替换为外网域名 | +| `INLINE_COUNT > 0` 且集中模块存在 | Bug | **FAIL** | AUTO-FIX: 替换为 import | +| `INLINE_COUNT > 0` 且无集中模块 | 未实现 | **FAIL** | AUTO-IMPLEMENT: 创建集中模块 | +| `tabbar_iconpath_empty=true` | 未实现 | **FAIL** | AUTO-IMPLEMENT: 实现自定义 TabBar | +| `EMOJI_COUNT > 0` | 未实现 | **FAIL** | AUTO-IMPLEMENT: Iconify 方案替换 | +| 全部为 0 | — | **PASS** | — | + +**AUTO-FIX(Bug 类型)**: +1. localhost fallback → 从 pipeline-context.md 读取外网域名,Edit 替换 +2. 内联 API(集中模块存在)→ Edit 替换为 import 引用 +3. 重启前端服务 → 重新执行步骤 9.5 验证 + +**AUTO-IMPLEMENT(未实现类型)**: +按照 `./reference/platform-uni-app.md` 第 1 节(图标方案)和第 2 节(TabBar 方案)执行完整实现: +1. 安装依赖(Tailwind + Iconify + weapp-tailwindcss) +2. 创建配置文件(tailwind.config.js, vite.config.ts) +3. 创建组件(自定义 TabBar) +4. 修改引用(替换 emoji、更新 pages.json) +5. 重启服务 → 重新执行步骤 9.5 验证 + +> 完整的 AUTO-FIX/IMPLEMENT/DATA-FIX 分类标准和修复流程见 ./auto-fix-engine.md + +### 增强 analyze_image 提示词(步骤 8 详解) + +**目的**:当前 analyze_image 只检查项目设计系统颜色,需增加多个维度,每个维度必须明确 PASS/FAIL。 + +**提示词模板**: + +``` +分析此截图,按以下维度逐一给出 PASS/FAIL 判定: + +1. **设计规范**(从项目 _bmad-output/04-ui-ux-design.md 提取): + - {执行时:Read _bmad-output/04-ui-ux-design.md → 提取设计系统规则 → 填入} + - 回退:文件不存在 → grep 项目 CSS :root 变量 + → PASS/FAIL + 具体问题 + +2. **图标/图片完整性**: + - TabBar 图标是否全部可见(非空白/缺失) + - 是否有 broken image(加载失败的图片占位符) + - 是否有 emoji 充当功能图标(如 🏠🏠📱 等) + - 可缺失的图片(如头像)是否有 CSS 渐变色占位符 + - TabBar 图标:如果有底部导航栏,检查图标是实际图片/SVG 还是纯文本标签? + 如果只有文字(如"首页/广场/消息/我的")而没有对应的图标 → FAIL + 检查方法:图标区域是否有 或 icon class,还是只有 + → PASS/FAIL + 具体问题 + +3. **功能元素完整性**: + - 预期的按钮/表单/列表是否全部渲染 + - 按钮文案是否可读(非乱码/空白) + - 列表项是否有数据(非空列表且无"暂无数据"提示) + → PASS/FAIL + 具体问题 + +4. **布局质量**(⛔ 逐一检查以下每一项,不得跳过): + a. 溢出检测:内容在视口范围内,无水平溢出或裁剪 + b. 文字可读:文字不被截断,不显示省略号,对比度足够 + c. 重叠检测:元素无重叠(标题栏与自定义头部不碰撞,导航栏不遮盖内容) + d. 间距合理性:无异常大间距(如导航栏与内容之间的过大空白),无紧贴元素(相邻交互元素应有明确间距) + e. 元素变形检测:按钮/输入框/卡片未被挤压变形(宽高比异常),文字未被截断 + f. 对齐检测:同行元素水平对齐,同列元素垂直对齐 + g. TabBar:底部 TabBar 完整可见(非 TabBar 页面此项 PASS) + → PASS/FAIL + 具体问题 + +总结:列出所有 FAIL 项,如全部 PASS 则输出"全部 PASS"。 +``` + +**结果记录**: +```yaml +verification_detail: + design_check: { design_compliance: PASS/FAIL, design_spec_source: "04-ui-ux-design.md", details: "..." } + icons_check: { tabbar_icons_visible: PASS/FAIL, broken_images: PASS/FAIL, emoji_as_icons: PASS/FAIL } + functional_check: { elements_rendered: PASS/FAIL, text_readable: PASS/FAIL } + layout_check: { overflow: PASS/FAIL, spacing_anomaly: NONE/FOUND, element_deformation: NONE/FOUND, alignment: PASS/FAIL, tabbar_visible: PASS/FAIL } +``` + +### 步骤 8.5: 布局溢出检测(小程序强制) + +**目的**:小程序页面在真实移动设备上经常出现宽度/高度溢出视口、底部 TabBar 被遮挡的问题。此步骤通过 JavaScript 检测这类布局问题。 + +**检测方法**: +> 完整的检测 JS 代码和 TabBar CSS 选择器见 **./reference/platform-uni-app.md 第 5.2 节**。 +> 防溢出规则见 **./reference/platform-uni-app.md 第 5.1 节**。 + +在步骤 8(analyze_image)之后、步骤 9(network_requests)之前执行。 + +**判定规则**: + +| 检查项 | FAIL 条件 | 说明 | +|--------|----------|------| +| 水平溢出 | `horizontal_overflow === true` | 页面宽度超出视口,会导致横向滚动条 | +| TabBar 不可见 | `tabbar_found && !tabbar_visible` | TabBar 存在但被遮挡,用户需要滚动才能看到 | + +**⛔ 阻断规则(检测到 FAIL 时必须修复后才能继续)**: +- 水平溢出: `horizontal_overflow === true` → Story 标记 FAIL,进入 AUTO-FIX +- TabBar 不可见: `tabbar_found && !tabbar_visible` → Story 标记 FAIL,进入 AUTO-FIX +- 修复方向: + 1. 检查页面根元素是否有超过 `100vw` 的宽度设置 + 2. 检查固定定位元素的 `bottom` 值是否考虑了 TabBar 高度 + 3. 检查可滚动内容高度是否减去了 TabBar + Header 高度 + 4. 修复后重新执行步骤 8.5 验证 + +**结果记录**: +```yaml +verification_detail: + layout_check: + horizontal_overflow: true/false + document_width: 375 + viewport_width: 375 + tabbar_found: true/false + tabbar_visible: true/false/null # null = 页面无 TabBar +``` + +### 用户旅程测试(Epic 级集成验证) + +**⛔ 工具选择强制规则**:执行旅程测试时,每个用户步骤必须遵循上述 Playwright MCP 测试序列(步骤 0-9)。**禁止使用 curl 替代 browser_click / browser_type / browser_snapshot / browser_navigate**。curl 仅用于数据状态的前后对比验证(补充,非替代用户交互)。详见 `journey-mode.md` 的"工具选择守卫"部分。 + +**核心理念**:技能文档只定义"如何做用户旅程测试"的方法论和步骤,**不定死具体旅程**。具体旅程由 AI 根据项目自身的 PRD/需求文档/Epic+Story 定义自主识别。 + +### ⛔ 用户旅程真实性原则(强制遵守) + +**用户旅程 ≠ 单元测试。必须模拟真实用户从自然入口开始的行为路径。** + +**规则 1:起始页必须是自然入口** +- 小程序:起始页 = 用户打开网址看到的第一个页面(参见 ./reference/platform-uni-app.md §4 入口页面) +- 管理后台:起始页 = 根 URL `/`(通常重定向到登录或 Dashboard) +- ❌ `browser_navigate("/pages/login/index")` — 用户不会直接输入登录页 URL +- ✅ `browser_navigate("http://mini-xxx.dev.d8d.fun/")` → 用户看到首页 → 自然触发登录流程 + +**规则 2:操作路径必须可发现** +- 每一步必须是用户"看得见、点得到"的 +- ❌ "用户进入局头申请页"(怎么进去的?没写) +- ✅ "用户点'我的'Tab → 看到'申请成为局头'按钮 → 点击进入申请页" + +**规则 3:前置条件必须明确且可验证** +- 每个旅程必须写明操作前的系统/用户状态 +- ❌ "用户提交申请" +- ✅ "前置:用户已登录(有 token),且 organizer=null(无局头身份)" + +**规则 4:数据断言必须具体** +- ❌ "登录成功返回 JWT token"(太宽泛) +- ✅ "新用户登录后返回的 userId ≠ 已有用户的 userId" +- ❌ "申请提交成功" +- ✅ "申请前 organizer=null,申请后 organizer.status=PENDING" + +**规则 5:跨越页面必须连续导航** +- 页面跳转必须通过用户操作触发(点击/提交),不得直接 `browser_navigate` +- ❌ `browser_navigate("/pages/organizer-apply/index")` +- ✅ 用户点击"申请成为局头"按钮 → 页面跳转到申请页 + +**违反以上任何规则的旅程测试视为无效,不得标记为 pass。** + +**旅程来源(兼容模式)**: +- **优先**:读取 Phase-01 预定义的 `{implementation_artifacts}/epic-N-journeys.md` 文件(验证模式由 Phase 0V 步骤 4.5 预定义/恢复) +- **回退**:如果旅程文件不存在,从 PRD/需求文档 + 已实现源码自主识别跨页面业务流程 +- 验证模式的 Epic 旅程测试遵循此完整模式流程 + +**执行时机**:每个 Epic 所有 Story 验证完成后(Phase 6: EPIC-DONE 中触发)。 + +**步骤定义**: + +1. **读取旅程定义** + - **首选**:读取 `{implementation_artifacts}/epic-N-journeys.md`(Phase-01 或 Phase 0V 步骤 4.5 预定义) + - **回退**:读取 `_bmad-output/` 下的 PRD 文件、Epic/Story 定义文件,从需求文档自主提取跨页面业务流程 + - 关注:用户故事描述、业务流程图、页面跳转逻辑 + +2. **识别测试旅程** + - 从 PRD 中提取涉及当前 Epic 功能的用户场景 + - 旅程定义格式:`起始页 → 操作步骤1 → 页面2 → 操作步骤2 → ... → 预期最终状态` + - 每个旅程必须跨越至少 2 个页面 + - 每个旅程必须包含至少 1 个数据状态变化(创建/更新/删除) + +3. **执行旅程测试** + ``` + 对每个旅程: + browser_resize → 正确视口 + browser_navigate → 起始页 + browser_snapshot → 验证起始状态 + (循环) + browser_type / browser_click → 执行操作 + browser_wait_for → 等待页面跳转/数据更新 + browser_snapshot → 验证中间状态 + (结束循环) + browser_take_screenshot → 最终状态截图 + browser_evaluate → 验证最终数据状态(如 API 返回值、localStorage 等) + ``` + +4. **验证标准** + - 每步页面渲染正常(无空白/404/错误提示) + - 每个数据操作有正确响应(创建成功/更新成功/状态变化) + - 页面跳转正确(到达预期页面) + - 最终状态符合 PRD 预期 + +5. **记录结果** + ```yaml + user_journeys: + epic_N: + journey_1: + name: "{从 PRD 提取的旅程名称}" + steps: ["页面A → 操作X → 页面B → 操作Y → 结果"] + status: pass/fail + screenshot: "tests/e2e/screenshots/journey-epicN-1.png" + details: "简短描述" + ``` + +**旅程来源优先级**: +1. 项目 PRD 文档中的用户流程/场景描述(最高优先级) +2. Epic/Story 规格文件中的验收标准组合 +3. 已实现的页面路由和导航逻辑 + +**最低覆盖要求**:每个 Epic 至少覆盖 1 条涉及该 Epic 功能的用户旅程。 + +**与 Story 级验证的区别**: +- Story 级验证:测试单个功能点(如"创建按钮可点击") +- 旅程级验证:测试完整业务流程(如"登录 → 浏览 → 创建 → 发布 → 验证列表更新") + +| 服务 | Mock 方式 | +|------|----------| +| 微信 OAuth | Mock API 返回测试 OpenID | +| 微信支付 | 沙箱环境 + Mock 回调 | +| 企业付款到零钱 | Mock 转账 API,验证请求结构 | +| 对象存储 | **容器内置 MinIO**(`localhost:9000`,兼容 S3 API) | +| PostgreSQL | **容器内置 PG 17**(`localhost:5432`,用户 `postgres`) | +| Redis | **容器内置 Redis 7**(`localhost:6379`) | +| 短信通知 | 输出到控制台,验证通知记录入库 | + diff --git a/.claude/skills/d8d-dev/iron-laws.md b/.claude/skills/d8d-dev/iron-laws.md new file mode 100644 index 0000000..a3519b0 --- /dev/null +++ b/.claude/skills/d8d-dev/iron-laws.md @@ -0,0 +1,299 @@ +# 铁律 + +> 参见 ./auto-fix-engine.md 了解统一修复引擎(FAIL分类标准 + 修复流程 + GATE-CHECK)。 + +## 铁律:Epic 间零暂停自动继续 + +**这是最重要的执行规则,必须严格遵守:** + +``` +Epic N 完成(所有 Story done + 分类提交 + push) + ↓ +立即开始 Epic N+1(无任何暂停、无状态报告、无用户确认) + ↓ +Epic N+1 完成 + ↓ +立即开始 Epic N+2 + ↓ +... 直到所有 Epic 完成 +``` + +**规则:** +1. **Epic 间不停**:Epic 完成后立即进入下一个 Epic,不输出进度汇总 +2. **用户未取消即继续**:只要用户没有发送停止/取消消息,就一直自动执行 +3. **进度信息内联**:每完成一个 Story,用一行 `✅ Story X-Y done` 标记,不展开说明 +4. **只在结束时汇报**:所有 Epic 完成后才输出最终 pipeline-report +5. **错误不中断**:单个 Story 失败记录到 error_log,跳过继续下一个 + +**违反此规则的行为:** +- ❌ Epic 完成后问用户"是否继续?" +- ❌ Epic 完成后输出长篇进度汇总然后停下 +- ❌ 等待用户确认才开始下一个 Epic +- ❌ 在 Epic 之间做多余的验证或状态汇报 + +## 铁律二:禁止跳过 Story 级质量关卡 + +**与铁律一(Epic 间不停)同样重要。铁律一说的是速度,铁律二说的是质量。** + +``` +❌ 错误做法(批量跳步): + 把 39 个 Story 合并为 3 批 → 批量写 API → 批量写页面 → 批量提交 + 结果:跳过所有 Spec/Test/Review,留下未发现的 bug + +✅ 正确做法(逐 Story 流水线): + Story 1: CREATE-SPEC → IMPLEMENT → TEST → REVIEW → MARK-DONE + Story 2: CREATE-SPEC → IMPLEMENT → TEST → REVIEW → MARK-DONE + ... + Story 39: CREATE-SPEC → IMPLEMENT → TEST → REVIEW → MARK-DONE +``` + +**每个 Story 必须经过的关卡(一个都不能少)**: + +| 关卡 | 作用 | 跳过后果 | +|------|------|---------| +| CREATE-SPEC | 分析依赖、边界条件、验收标准 | 遗漏非显而易见的需求细节 | +| TEST | 单元测试(Jest) + E2E 测试(Playwright MCP) 验证实现正确性 | bug 滞留到后期,修复成本 10x;缺少 E2E = 前端/API 行为未验证 | +| REVIEW | 并行审查子代理(盲猎人/边界猎人/验收审计) | 类型错误、关系不匹配等问题逃逸 | +| AUTO-FIX | 自动修复失败测试 | 失去流水线核心自愈能力 | + +**允许的效率优化(不跳步骤)**: +- 同 Epic 内连续 Story 的 Spec 可以精简,但不能跳过 +- IMPLEMENT 阶段可以在一次 Agent 调用中创建多个文件,但必须按 Story 粒度跟踪状态 +- Epic 内所有 Story 完成后统一分类提交(按类型分 commit),而不是每个 Story 单独提交 + +**违反此规则的行为**: +- ❌ 把多个 Story 合并为一批直接实现 +- ❌ 跳过 CREATE-SPEC 直接写代码 +- ❌ 跳过 TEST 直接提交 +- ❌ 跳过 REVIEW 直接标记 done +- ❌ 用"效率"作为借口绕过质量关卡 +- ❌ 创建 Epic 级 Task 或用一次 Agent 调用批量处理多个 Story + +## 铁律三:禁止 Agent 批量处理多 Story + +**铁律一说速度,铁律二说质量,铁律三说粒度。** + +``` +❌ 错误做法(Agent 批量合并): + 一次 Agent 调用验证 Epic 2-4 的所有 API + 一次 Bash curl 测试多个 Story 的端点 + 一次 Playwright 会话验证多个页面 + Agent prompt 列出 5 个 Story 的验收标准 + +✅ 正确做法(逐 Story 执行): + Agent 调用 1: 只处理 Story 2-1 + Agent 调用 2: 只处理 Story 2-2 + ... + 每个 Agent = 一个 Story = 一个 TaskCreate +``` + +**规则**: + +| 规则 | 说明 | +|------|------| +| 规则 1 | 一次 Agent 调用只处理一个 Story | +| 规则 2 | 禁止并行 Agent 分别验证不同 Epic 的 Story | +| 规则 3 | 39 个 Story = 39 个 TaskCreate,禁止合并 | + +**违反行为(全部禁止)**: +- ❌ 用一次 Bash curl 测试多个 Story 的 API 端点 +- ❌ Playwright 一次会话验证多个页面(跨 Story) +- ❌ Agent prompt 列出多个 Story 的验收标准 +- ❌ 创建 `subject: "Epic 2: 组局管理"` 这样的粗粒度 Task + +**唯一并行例外**:同 Epic 内无依赖的 Story 可并行实现,但**每个 Agent 仍只处理一个 Story**。 + +## 铁律四:验证模式禁止表层验证 + +**铁律一说速度,铁律二说质量,铁律三说粒度,铁律四说深度。** + +表层验证 = 只看页面"能不能打开",不验证"能不能用"。这是验证模式最常见的失败模式。 + +``` +❌ 表层验证(禁止): + browser_navigate → browser_snapshot → "OK 渲染正确" → 下一个 + 结果:表单提交不了、按钮没绑定事件、样式与设计稿不一致,全部漏掉 + +✅ 深层验证(必须): + browser_resize → browser_navigate → browser_snapshot + → browser_type → browser_click → browser_wait_for → browser_snapshot + → browser_take_screenshot → analyze_image + → "表单提交成功、数据已更新、视觉符合项目设计系统" → 下一个 +``` + +### 按Story类型的验证要求矩阵 + +**根据 sprint-status.yaml 的 assignee 字段判断 Story 类型,确定验证要求**: + +| Story 类型 | assignee | 渲染验证 | 交互验证 | 视觉验证(analyze_image) | 截图存档 | API curl | +|------|---------|---------|---------|---------|---------|---------| +| **纯后端** | `backend` | — | — | — | — | **必须** | +| **API+前端** | `frontend+backend` | **必须** | **必须** | **必须** | **必须** | **必须** | +| **API+管理后台** | `backend+admin` | **必须** | **必须** | **必须** | **必须** | **必须** | +| **纯前端(小程序)** | `frontend` | **必须** | **必须** | **必须** | **必须** | 可跳过 | +| **纯管理后台** | `admin` | **必须** | **必须** | **必须** | **必须** | 可跳过 | +| **纯配置/Schema** | — | — | — | — | — | — | + +**判断规则**: +- assignee 含 `backend` → 需要测试 API +- assignee 含 `frontend` 或 `admin` → 需要测试前端页面 +- 两者都有 → API + 前端都需要测试 +- 纯配置/Schema Story → 仅验证配置正确性,记录 `e2e_skipped_reason` + +### 六维验证检查清单(每个前端 Story 必须全部通过) + +| 维度 | 验证方法 | 通过标准 | 常见遗漏 | +|------|---------|---------|---------| +| **渲染验证** | browser_snapshot | 组件树包含预期元素 | 只做了这一步就跳过 | +| **交互验证** | browser_type + browser_click | 表单可填写、按钮可点击、操作有结果响应 | 管理后台不填表单、小程序不测试流程 | +| **视觉验证** | analyze_image(对每步截图) | 项目设计系统(04-ui-ux-design.md) + 图标/图片加载完整 + 无 emoji 占位符 + 无 broken image | 只在最后做一次,中间步骤视觉问题被遗漏 | +| **截图存档** | browser_take_screenshot(每步) | 每步保存到 tests/e2e/screenshots/(含步骤编号) | 只在最后保存一张截图 | +| **API 目标验证** | browser_network_requests | 前端 JS 发起的 API 请求不指向 localhost(外网域名模式下) | 完全不检测,导致外网用户无法使用 | +| **布局溢出验证** | browser_evaluate(JS 检测) | scrollWidth <= viewportWidth、TabBar 可见(小程序页面) | 完全不检测布局溢出 | + +### Epic 0 专用验证检查清单 + +**Epic 0(项目初始化)的验证不涉及前端页面,使用以下独立检查项**: + +| 检查项 | 验证命令 | 通过标准 | +|--------|---------|---------| +| 数据库连接 | `psql -U postgres -c "SELECT 1"` | 返回结果 | +| 数据库表创建 | `psql -U postgres -d barparty -c "\dt"` | 17 张表存在 | +| 数据库迁移 | `cd api && npx prisma migrate status` | 所有迁移已应用 | +| Redis 连接 | `redis-cli ping` | PONG | +| 种子数据 | `psql -U postgres -d barparty -c "SELECT count(*) FROM \"Admin\""` | 3 条 admin 记录 | +| pm2 进程 | `pm2 list` | api/admin/mini-h5 均 online | +| API 健康检查 | `curl -sf http://localhost:3001/api/health` | 返回 200 | +| Admin 页面 | `curl -sf http://localhost:3002` | 返回 HTML | +| Mini-H5 页面 | `curl -sf http://localhost:5173` | 返回 HTML | + +### 管理后台交互验证(强制操作列表) + +**每个管理后台页面必须至少完成以下交互**(如果页面包含对应元素): + +1. **表单页面**:填写所有必填字段 → 点击提交 → 等待成功消息 → 验证数据已创建/更新 +2. **列表页面**:验证数据行存在 → 点击筛选标签 → 验证筛选生效 → 翻页 → 验证分页 +3. **详情页面**:从列表点击进入 → 验证详情字段完整 → 点击编辑 → 修改并保存 + +### 小程序交互验证(强制操作列表) + +**每个小程序页面必须至少完成以下交互**(如果页面包含对应元素): + +1. **表单页面**:填写字段 → 点击提交 → 等待响应 → 验证成功提示 +2. **列表页面**:验证列表项渲染 → 点击进入详情 → 验证详情页 +3. **流程类页面**:必须走完完整流程(如登录 → 首页 → 详情 → 报名 → 订单确认) + +### 视口切换规则(每个前端 Story 强制执行) + +``` +验证管理后台页面之前: + browser_resize → width: 1440, height: 900 + (即使上一个 Story 也是管理后台,也要确认当前视口正确) + +验证小程序页面之前: + browser_resize → width: 390, height: 844, deviceScaleFactor: 2 + (即使上一个 Story 也是小程序,也要确认当前视口正确) + - 390x844 = 现代 iPhone (12/13/14/15) 标准视口 + - deviceScaleFactor: 2 → 像素密度 2x,匹配 Pencil 设计稿导出 scale=2 + - 旧款 iPhone 是 375x812,现代设计稿普遍使用 390 宽度 +``` + +### 禁止行为列表 + +- ❌ 只执行 browser_navigate + browser_snapshot 就标记验证通过 +- ❌ 不填写表单就说"管理后台页面验证通过" +- ❌ 不点击按钮就说"交互功能正常" +- ❌ 不调用 analyze_image 就说"视觉验证通过" +- ❌ 不保存截图就继续下一个 Story +- ❌ 只在最后阶段截图和 analyze_image,中间步骤无视觉证据 +- ❌ 不执行 browser_network_requests 检测就说"API 调用正确" +- ❌ 前端页面 JS 调用 localhost:3001 但仍标记 Story 为 done +- ❌ 混用视口(管理后台用 375 宽度或小程序用 1440 宽度) + +### 检测即阻断规则(⛔ 铁律四补充) + +**任何检测到的问题必须阻断 Story 完成,不允许"记录后继续":** + +| 检测项 | FAIL 条件 | 分类 | 修复动作 | +|--------|----------|------|---------| +| 布局溢出 | `horizontal_overflow === true` | Bug | AUTO-FIX: CSS 修复 | +| TabBar 图标缺失 | 只有文本无图标 | 未实现 | AUTO-IMPLEMENT: 实现自定义 TabBar(参见 ./reference/platform-uni-app.md §2) | +| API 内联声明 | 任何前端代码有独立 `const API_BASE =` | Bug/未实现 | 集中模块存在→AUTO-FIX,否则→AUTO-IMPLEMENT | +| API 目标错误 | 外网模式下发现 localhost | Bug | AUTO-FIX: 替换 fallback 值 | +| 源码 localhost fallback | grep 发现 `localhost:[port]` | Bug | AUTO-FIX: 替换为外网域名 | +| TabBar iconPath 为空 | `pages.json` iconPath 是空字符串 | 未实现 | AUTO-IMPLEMENT: 自定义 TabBar + Iconify(参见 ./reference/platform-uni-app.md §1+§2) | +| Emoji 充当功能图标 | icon/btn 元素内含 emoji 字符 | 未实现 | AUTO-IMPLEMENT: **⛔ 必须按 ./reference/platform-uni-app.md §1 完整安装 Iconify + tailwindcss-icons + weapp-tailwindcss(小程序),禁止 SVG data URI 等不支持的替代方案** | +| SVG data URI 充当图标/装饰 | `grep -rn "data:image/svg+xml" app/mini-program/src/` 在 CSS/style 中发现 `url("data:image/svg+xml...")` 或 `url('data:image/svg+xml...')` | Bug(平台不兼容) | AUTO-IMPLEMENT: **⛔ 必须按 ./reference/platform-uni-app.md §1 完整安装 Iconify + tailwindcss-icons + weapp-tailwindcss/vite,将所有 SVG data URI 替换为 Iconify 类名,⛔ 禁止用另一种 SVG data URI 替代原来的** | +| Tailwind/Iconify 未配置 | mini-program 无 tailwindcss-icons | 未实现 | AUTO-IMPLEMENT: 完整安装配置(参见 ./reference/platform-uni-app.md §1) | +| Phase 衔接跳步 | Phase 0V 未完成就进入 Story 验证 | 阻断 | 不得开始 Story 验证直到 Phase 0V COMPLETE GATE 通过 | +| 三写不一致 | pipeline-state 和 pipeline-context 的 done 计数不匹配 | 阻断 | 不得标记 Story 完成,必须定位漏写的文件 | +| 用户旅程未执行 | `pipeline-state` user_journeys 为空 | 阻断 | 不得标记 complete,必须回退执行 | +| 未实现功能占位符 | 前端代码含"暂未开放"/"功能开发中"/"暂未实现"等占位文本 | 未实现 | AUTO-IMPLEMENT: 实现该功能 | +| 旅程步骤使用 curl 替代 Playwright | 旅程中用户操作步骤(点击/填写/导航/查看页面)使用了 curl 而非 browser_click/type/navigate/snapshot | 阻断 | 旅程标记 FAIL,必须使用 Playwright 重做 | +| 旅程 FAIL 未触发修复 | FAIL 被记录但未执行 AUTO-FIX/AUTO-IMPLEMENT/AUTO-DATA-FIX 分类判定和修复流程 | 阻断 | 必须执行分类判定并触发对应修复流程 | +| 即时使用数据仅 Toast 传递 | 验证码等下一步骤需要的数据仅通过 Toast 显示且源码无 console.log 且无自动填充 | Bug | AUTO-FIX(#14): 添加 console.log + 自动填充 | +| 框架编译 input 不可交互 | `browser_type` 失败后 `browser_evaluate` 诊断发现内部 input `height: 0px`(如 uni-app `uni-input` 在 flex 容器中无显式 height) | Bug | AUTO-FIX(#6): 修复 CSS 添加显式 height/width | +| 页面有内容但零交互元素 | `browser_evaluate` 确认 `interactiveCount===0` 且 `hasContent=true`(用户打开页面看到标题但无任何 button/input/a/form 可操作) | Bug(#5) 或 未实现(#3) | 诊断分流:框架已挂载+源码无交互→AUTO-IMPLEMENT(添加 redirect/导航);框架未挂载→AUTO-FIX(hydration) | +| UI 硬编码英文枚举值 | `browser_evaluate` 检测页面可见文本含 APPROVED/REJECTED/PENDING/PAID/CANCELLED/REFUNDED/DRAFT/ONGOING/COMPLETED 等英文大写枚举(适用于所有前端页面,不限于管理后台) | Bug(#15) | AUTO-FIX: 添加中文映射函数(管理后台 statusLabel / 小程序 computed) | +| 约束声明与代码行为不一致 | 旅程描述含"可选/可跳过/选填"或页面 UI 含"(选填)/(可选)"标记,但代码实际有必填验证(Toast 含"请上传/请填写/不能为空"或源码 `if (!field)` 拦截) | 约束不一致(#16) | JOURNEY-FIX: 修复旅程定义 / AUTO-FIX: 修复代码或 UI 文案,使声明与行为一致 | +| analyze_image 视觉验证未执行 | pipeline-state 无 `analyze_image_called: true` 或 verification_detail.design_check 缺失 | 阻断 | 必须回退执行 analyze_image 后才能标记完成(见 ./visual-verification.md) | +| 双重导航栏 | 页面在 pages.json 中有 `navigationBarTitleText`(非 custom)且页面源码含 `position:fixed` 的自定义 header 元素,导致系统导航栏与自定义头部同时显示(截图中可见两层标题) | Bug(#18) | AUTO-FIX: pages.json 该页面添加 `navigationStyle: "custom"` + 页面 header CSS 添加 safe-area padding(参见 ./reference/platform-uni-app.md §3) | +| API route 无 try-catch | API 返回 500 + `Read` 源码发现 handler 函数无 try-catch 包裹(异常直接返回原始 500 且无日志) | Bug(#20) | AUTO-FIX: 添加 try-catch 包裹 + console.error + JSON 500 响应(参见 ./auto-fix-engine.md #20) | +| 页面 navigateBack 后数据陈旧 | `uni.navigateBack()` 返回目标页后 `browser_snapshot` 数据未更新 + 源码用 `onMounted` 非 `onShow` | Bug(#21) | AUTO-FIX: 替换 `onMounted` 为 `onShow`(from `@dcloudio/uni-app`,参见 ./auto-fix-engine.md #21) | + +> 完整的 FAIL 分类标准(含兜底规则 #99)和修复流程见 ./auto-fix-engine.md 第 2-4 节 + +**违反此规则的行为**: +- ❌ 检测到溢出后写"需注意"然后标记 done +- ❌ analyze_image 报告 FAIL 但 Story 仍标记 verified +- ❌ 跳过用户旅程测试直接标记 Epic 完成 +- ❌ 发现 localhost fallback 但标记"已通过 env 覆盖"然后 done +- ❌ 发现 emoji 当图标但写"视觉可接受"然后 done +- ❌ **将视觉问题标记为任何非检测即阻断表中定义的分类**(模式性禁止) + - 禁止的分类包括但不限于:"已知遗留项"、"技术债"、"后续优化"、"暂时保留"、"非本次引入" + - 唯一允许的分类:检测即阻断表中的分类(Bug/未实现/阻断) +- ❌ **将修复从 AUTO-FIX/AUTO-IMPLEMENT 降级为"仅记录"或"WARN"** +- ❌ **推迟视觉验证修复到"旅程完成后"或"步骤 4 修复阶段"**(必须在关卡内立即执行) +- ❌ 发现 pages.json iconPath 为空但标记"TabBar 可见"然后 done +- ❌ 页面有静态标题但无交互元素(用户无法操作)时标记"渲染通过" +- ❌ 遇到零交互元素但不诊断原因直接判定为 hydration Bug(可能是缺少 redirect) +- ❌ 任何前端页面直接显示英文枚举值(如 APPROVED/REJECTED/PENDING)但标记"显示正常" +- ❌ 旅程说"可跳过/可选/选填"但代码必填验证拦截后仅静默补填继续(不标记 Constraint Mismatch) +- ❌ 页面 UI 含"(选填)/(可选)"标记但后端/前端实际必填(不标记 UX Mismatch) +- ❌ API 返回 500 但未检查源码 try-catch 就标记"环境问题"或"已知问题" +- ❌ 页面 navigateBack 后数据未刷新但标记"已通过 API 验证"(必须检查 onShow) + +## 铁律六:页面变更必须同步原型 + +**铁律一说速度,铁律二说质量,铁律三说粒度,铁律四说深度,铁律五说自愈,铁律六说原型同步。** + +任何涉及新 UI 页面或页面修改的 Story,完成后必须同步更新原型系统。原型是 UI 的唯一参考来源,代码实现与原型不同步 = 规格与实现不一致。 + +``` +❌ 错误做法(实现后不管原型): + Story 7-4 实现了批量任务面板 UI → 原型仍只有 Epic 1-6 的页面 + 结果:新页面在原型中无法预览,文档中心缺少新页面卡片 + +✅ 正确做法(实现后同步原型): + Story 7-4 实现了批量任务面板 UI + → 原型系统更新:添加批量任务面板页面 + → 文档中心更新:docs-index.html 添加新文档卡片 + → 统一入口更新:index.html 更新页数和快速导航 +``` + +**判断规则**: + +| Story 类型 | 原型同步 | 文档中心更新 | 入口页更新 | +|------------|---------|------------|-----------| +| 新 UI 页面 | **必须** | **必须** | **必须** | +| 修改现有 UI 布局/组件 | **必须** | 可选 | 可选 | +| 纯后端/API | 跳过 | 可选 | 跳过 | +| 纯配置/Schema | 跳过 | 跳过 | 跳过 | + +**违反此规则的行为**: +- ❌ 实现了新 UI 页面但原型中无对应页面 +- ❌ d8d-iterate feature 类型涉及新页面但跳过 Phase 3 +- ❌ 原型页面更新了但文档中心未更新 +- ❌ 统一入口页 (index.html) 页数未更新或快速导航缺少新页面 +- ❌ 用"遵循现有 UI 模式"作为跳过原型的理由 + diff --git a/.claude/skills/d8d-dev/journey-authenticity-rules.md b/.claude/skills/d8d-dev/journey-authenticity-rules.md new file mode 100644 index 0000000..4f56588 --- /dev/null +++ b/.claude/skills/d8d-dev/journey-authenticity-rules.md @@ -0,0 +1,453 @@ +# 旅程真实性规则(⛔ 统一规则集 — 定义和验证共享) + +> **Read fully and follow** when referenced by journey-mode.md. +> +> 被以下流程引用: +> - 子模式 1 步骤 3.2(旅程生成后自检) +> - 子模式 2 步骤 1.5(执行前校验与自动修复) +> - 其他任何需要校验旅程定义的流程 + +--- + +## 规则 R1:用户步骤禁止引用开发专用元素 + +### 检测方法 + +1. 扫描旅程文件的"用户视角步骤"部分 +2. 匹配禁止模式: + +| 禁止模式 | 匹配规则 | 违反示例 | +|---------|---------|---------| +| Mock 开发桩 | 包含 "Mock 登录" "Mock 支付" "Mock 按钮" "[开发]" | ❌ "用户点击 Mock 登录按钮" | +| 测试固定数据 | "13900001111" "test-user" "13900000000" 在用户步骤中 | ❌ "用户输入手机号 13900001111" | +| 环境判断代码 | "v-if" "isDev" "DEV" "NODE_ENV" 在用户步骤中 | ❌ "如果 isDev 则点击 Mock 按钮" | + +**通过标准**:用户视角步骤中 0 条匹配。 + +### 修复策略 + +``` +遇到"Mock 登录" → + 读取 login/index.vue 源码: + ├── 手机号+验证码已实现 → 替换为"用户输入手机号 → 获取验证码 → 输入验证码 → 点击登录" + │ 技术验证注明"⚠️ 开发环境:POST /api/auth/send-code 直接返回验证码(非 Mock,是标准开发实践)" + └── 手机号+验证码未实现 → 触发 AUTO-IMPLEMENT: + 1. 后端: POST /api/auth/send-code {phone} → 开发环境返回 {code}, 生产环境发送短信 + 2. 后端: POST /api/auth/login {phone, code} → 验证码校验 + JWT + 3. 前端: 启用 login/index.vue 已有的手机号表单 + 实现 sendCode/handlePhoneLogin + 4. 删除 Mock 登录按钮及相关代码 + +遇到"Mock 支付" → + 替换为"用户确认支付"(后端沙箱/Mock 对用户透明) + 技术验证步骤可注明"开发环境支付自动成功" + +遇到测试固定数据 → + 替换为泛化描述:"用户输入手机号"(具体号码移到技术验证步骤或前置条件) +``` + +--- + +## 规则 R2:功能可用性必须与源码一致 + +### 检测方法 + +1. 对旅程中涉及的关键功能,读取对应页面/组件源码 +2. 检查功能是否标记为"暂未开放" / "开发中" / "暂未实现" + +```bash +# 自动检测命令 +grep -rn "暂未开放\|开发中\|暂未实现\|功能暂未" app/mini-program/src/pages/ app/admin/src/app/ +``` + +### 判定 + +- 功能已实现 → 正常写入旅程 ✅ +- 功能标记"暂未开放" → 旅程中不能作为用户步骤 ❌ + - 修复:移除该步骤,或标注为"前置条件:需先实现该功能" + - 在验证模式中触发 AUTO-IMPLEMENT + +--- + +## 规则 R3:环境适配 — 旅程必须适配当前测试环境 + +### 检测方法 + +```bash +# 检测条件编译 +grep -rn "#ifdef H5\|#ifdef MP-WEIXIN\|#ifdef APP-PLUS" app/mini-program/src/pages/ + +# 检测环境判断 +grep -rn "import.meta.env.DEV\|process.env.NODE_ENV\|isDev" app/mini-program/src/pages/ +``` + +### 环境适配决策树 + +``` +读取 pipeline-context.md → 确定测试环境 + ↓ +对每个旅程步骤检查:该操作在当前环境是否可用? + ├── 可用 → 正常步骤 + ├── 不可用但有替代方案 → 改用替代方案(标注) + └── 不可用且无替代 → 标注"⚠️ 环境限制" + 使用可用的开发桩 +``` + +### 环境适配表(动态生成 — AI 读取源码后填入) + +执行校验时,AI 读取相关源码后自动生成此表: + +| 用户操作 | 微信小程序 | H5 | 当前环境适配 | +|---------|-----------|-----|------------| +| 微信一键登录 | ✅ 可用 | ❌ Toast 提示不可用 | H5: 不可用(已知,不影响旅程) | +| 手机号+验证码 | ✅ 可用 | ✅ 可用(开发环境验证码直接返回) | 正常 — 旅程验证的标准登录方式 | +| 微信支付 | ✅ 可用 | ❌ N/A | H5: 开发环境自动成功 | +| Admin 登录 | N/A | ✅ 可用 | 正常 | + +--- + +## 规则 R4:凭据隔离 + +### 规则 + +- ✅ 前置条件:`Admin 账号可用(凭据见 pipeline-context.md)` +- ✅ 技术验证步骤:`POST /api/admin/auth/login body={username, password}`(凭据值可出现) +- ❌ 用户视角步骤:`Admin 输入用户名 superadmin、密码 admin123` +- ✅ 用户视角步骤:`Admin 输入账号密码 → 点击"登录"按钮` + +### 修复 + +将凭据从用户步骤移到前置条件或技术验证步骤。 + +--- + +## 规则 R5:API 标注与用户行为对应 + +### 规则 + +用户操作名称 → 技术验证中的 API 名称必须一致: + +| 用户视角操作 | 正确的技术验证标注 | 错误标注 | +|-------------|------------------|---------| +| 用户点击"微信一键登录" | `POST /api/auth/login {code}` | "Mock 登录 API" | +| 用户确认支付 | `POST /api/orders/[id]/pay` | "Mock 支付 API" | +| 用户申请退款 | `POST /api/orders/[id]/refund` | — | + +技术验证步骤可注明"⚠️ 开发环境说明:后端自动 Mock 微信 OAuth 返回",但不能改变 API 名称或标注为 "Mock"。 + +--- + +## 规则 R6:用户操作可行性兜底原则 + +> **⛔ 通用规则,不需要为每种操作类型单独定义子规则。** + +### 核心原则 + +旅程中的每个用户操作,如果执行失败(Playwright 无法完成),必须判断: +- 是 **Bug**(真实用户也无法完成)→ AUTO-FIX +- 还是 **环境限制**(真实用户可以完成但 Playwright 不能)→ 找 Playwright 替代方案 + +### 判定流程(通用兜底) + +``` +用户操作执行失败(browser_type/click/file_upload 等) + ↓ +browser_snapshot 检查页面 → 验证真实用户是否也会失败 + ↓ +├── 有 Toast/错误提示 → 读提示内容 → 按提示处理 +├── 无任何提示 + 操作无反应 → 验证真实用户体验: +│ ├── 真实用户也无法完成 → Bug → AUTO-FIX +│ └── 真实用户可以完成 → Playwright 适配(优先用 Playwright 工具) +└── 需要特定环境能力 → 环境限制 → ENV-ADAPT +``` + +### 表单校验提示可见性规则 + +**⛔ 表单校验失败时,错误提示必须持久可见(不能仅用 Toast)** + +理由:Toast 在几秒内消失,Playwright snapshot 时可能已消失,无法读取失败原因。 +更重要的是:对真实用户来说,持久可见的内联错误提示也是更好的用户体验。 + +**修复策略(校验失败但看不到提示 → Bug → AUTO-FIX)**: + +``` +校验失败 → browser_snapshot 检查: +├── 看到内联错误文字 → 读取 → 按提示处理 +├── 看到 Toast → 读取 → 按提示处理(⚠️ Toast 会消失,应同时修复为内联提示) +├── 无任何提示 + browser_console_messages 有错误 → Bug(提示仅 console 无 UI) +│ → AUTO-FIX: 将 Toast 改为内联错误提示(显示在对应字段旁) +├── 无任何提示 + console 也无错误 → Bug(UX 缺陷:校验拦截但用户看不到原因) +│ → AUTO-FIX: 添加 console.error + 内联错误提示 +└── 校验通过但操作仍无反应 → 走通用判定流程 +``` + +**AUTO-FIX 修复标准**: +- 表单校验错误应显示在**对应字段下方/旁边**(内联提示),不用 Toast +- 同时在 `console.error` 中打印校验失败原因(方便 Playwright 读取) +- Toast 可作为**辅助**保留,但不能是唯一的错误提示方式 + +### 场景 B: 即时使用数据仅通过 Toast 传递 + +操作成功后产生的数据,如果**下一步骤立即需要使用**(如验证码需输入到表单), +且该数据**仅通过 Toast 传递**(无 console.log、无自动填充、无页面持久化), +则 Toast 消失后旅程执行无法获取该数据 → 流程卡死。 + +**三条件判定(必须同时满足)**: + +| 条件 | 说明 | +|------|------| +| 1. 下一步骤需要该数据 | 验证码、临时 token 等即时使用数据 | +| 2. 仅通过 Toast 传递 | 无 console.log / 无自动填充 / 无页面持久化 | +| 3. Toast 消失后无法回溯 | 后续页面不显示、API 不返回历史记录 | + +**不适用场景(以下不是问题)**: +- 订单号、支付结果 → 后续页面自然显示,不需要从 Toast 提取 +- 创建成功提示 → ID 在后续页面可查 +- 普通操作反馈("已收藏"、"已取消")→ 不涉及后续步骤需要的数据 + +**检测方法**: + +``` +1. 源码扫描:检查 uni.showToast 是否传递了下一步骤需要的数据 + grep -rn "uni\.showToast" app/ | grep -i "code\|验证码\|token" + +2. console.log 检查:确认是否有对应的 console 记录 + grep -rn "console\.log" app/ | grep -i "code\|验证码" + +3. 自动填充检查:确认数据是否被写入 ref/reactive +``` + +**修复策略(AUTO-FIX)**: + +``` +发现即时使用数据仅通过 Toast 传递且无 console.log: + ↓ +1. 添加 console.log(开发环境): + console.log('[DEV] 验证码:', res.code) +2. 自动填充(验证码场景): + code.value = res.code || '' +3. Toast 保留作为辅助,不作为唯一数据载体 + +修复示例(sendCode): +// 修复前: +uni.showToast({ title: `验证码已发送:${res.code}`, icon: "none" }) + +// 修复后: +console.log('[DEV] 验证码:', res.code) +if (res.code) { code.value = res.code } +uni.showToast({ title: "验证码已发送", icon: "success" }) +``` + +### 通过标准 + +- 操作失败时必须先 `browser_snapshot` 确认页面状态 +- 表单校验错误必须有持久可见的 UI 提示(非仅 Toast) +- 即时使用数据必须有 console.log 或自动填充,不能仅依赖 Toast +- 框架编译导致的元素不可交互必须通过 `browser_evaluate` 诊断 CSS 根因 +- 不为每种操作类型单独定义规则,用此通用流程兜底 + +### 场景 C: 框架编译导致元素不可交互 + +`browser_type` 或 `browser_click` 失败时,如果 `browser_evaluate` 诊断发现: +- 元素是框架编译产物(如 `` 包裹原生 `