diff --git a/.trellis/tasks/08-05-product-interaction-consistency/design.md b/.trellis/tasks/08-05-product-interaction-consistency/design.md index a5446141..3cf67913 100644 --- a/.trellis/tasks/08-05-product-interaction-consistency/design.md +++ b/.trellis/tasks/08-05-product-interaction-consistency/design.md @@ -7,14 +7,17 @@ | 子任务 | 交付物 | 主要风险 | | --- | --- | --- | | `08-05-interaction-pattern-unification` | 错误/状态/文案/反馈/移动动作一致性,删除差旅自动选中 | 跨页面文案回归、共享组件影响 | +| `08-07-interaction-pattern-browser-regression` | 补齐低风险统一的本地 Chromium 与企业微信 UA 模拟证据 | 证据边界、历史 spec 漂移 | +| `08-07-e2e-fixture-repair` | 修复 R6/R111 committed E2E fixture 与断言,勘误历史诊断 | 测试口径被误删、fixture 权限漂移 | | `08-05-business-detail-routing` | 采购、资产已有详情态迁到独立路由 | URL 兼容、返回、dirty guard | | `08-05-contract-detail-workspace` | 新建合同详情、终止状态、迁移和事务审计 | 状态机、权限、迁移、审计 | ## 2. 执行顺序 1. 先实施低风险统一,形成后续页面可复用的错误消息、状态兜底和移动动作模式。 -2. 再迁移采购/资产路由;该子任务只消费共享模式,不修改合同、差旅、付款或请假。 -3. 合同详情作为独立能力最后实施,必须单独批准其 schema、状态机与迁移计划。 +2. 补齐低风险统一的浏览器证据,并修复补跑暴露的 committed E2E fixture/断言红灯。 +3. 再迁移采购/资产路由;该子任务只消费共享模式,不修改合同、差旅、付款或请假。 +4. 合同详情作为独立能力最后实施,必须单独批准其 schema、状态机与迁移计划。 父子关系不隐含自动授权。每个子任务都要独立执行 `task.py start`、验证、提交与归档。 @@ -28,7 +31,7 @@ ## 4. 集成与回滚 -- 三个子任务独立提交;任何后续任务失败时可回滚该子任务,不回滚已经验收的前序任务。 +- 三个产品交付子任务和质量支撑任务均独立提交;任何后续任务失败时可回滚该子任务,不回滚已经验收的前序任务。 - 路由兼容保留旧链接过渡,避免低风险任务和路由任务必须同时上线。 - 合同 migration 与前端详情同批兼容发布;生产应用、迁移和数据写入另需授权。 - 最终集成复核只检查跨任务一致性、链接、共享组件和文档事实,不重做每个子任务的全部测试。 diff --git a/.trellis/tasks/08-05-product-interaction-consistency/implement.md b/.trellis/tasks/08-05-product-interaction-consistency/implement.md index fca70b3e..9e4f4217 100644 --- a/.trellis/tasks/08-05-product-interaction-consistency/implement.md +++ b/.trellis/tasks/08-05-product-interaction-consistency/implement.md @@ -2,8 +2,8 @@ ## 1. 父任务职责 -- [ ] 审核三个子任务的 PRD、design 和 implement,确认范围无重叠。 -- [ ] 按“低风险统一 → 采购/资产路由化 → 合同详情”顺序分别请求实施批准。 +- [ ] 审核三个产品交付子任务及质量支撑子任务的 PRD、design 和 implement,确认范围无重叠。 +- [ ] 按“低风险统一 → 浏览器补跑 → E2E fixture 修复 → 采购/资产路由化 → 合同详情”顺序分别请求实施批准。 - [ ] 不启动父任务作为业务代码实现目标;启动拥有下一交付物的子任务。 ## 2. 子任务交付顺序 @@ -15,6 +15,8 @@ ### 2.2 采购与资产详情独立路由化 +进入本节前,必须完成 `08-07-interaction-pattern-browser-regression` 与 `08-07-e2e-fixture-repair`,确认 committed 交互回归 spec 无已知红灯。 + - [ ] 在低风险任务完成后迁移采购/资产详情路由与旧链兼容。 - [ ] 验证所有仓库内入口、dirty guard 和浏览器返回,独立提交和归档。 @@ -26,7 +28,7 @@ ## 3. 父任务最终集成门禁 -- [ ] 三个子任务验收结果与父 PRD 逐项对账。 +- [ ] 三个产品交付子任务和质量支撑任务的验收结果与父 PRD 逐项对账。 - [ ] 确认普通用户错误无技术前缀,采购/资产新链接稳定,合同状态/证据边界未漂移。 - [ ] 确认差旅未恢复、付款/请假未路由化、非涉密合同附件未提前实现。 - [ ] 运行父任务 validate、process contract、docs registry、Trellis governance 和 `git diff --check`。 diff --git a/.trellis/tasks/08-05-product-interaction-consistency/prd.md b/.trellis/tasks/08-05-product-interaction-consistency/prd.md index b835dbef..7d90c6e4 100644 --- a/.trellis/tasks/08-05-product-interaction-consistency/prd.md +++ b/.trellis/tasks/08-05-product-interaction-consistency/prd.md @@ -14,10 +14,11 @@ ## Scope -- 父任务只管理三个子任务的边界、顺序、验收和最终集成复核。 +- 父任务管理三个产品交付子任务及必要的质量支撑子任务,维护边界、顺序、验收和最终集成复核;质量支撑任务不扩张产品范围。 - 低风险子任务负责跨页面文案、错误反馈、状态兜底、首页优先级、移动动作容器和差旅自动选中缺陷。 - 采购/资产子任务负责两个已有详情态台账的独立路由迁移与旧链接兼容。 - 合同子任务负责从零合同详情工作区、`terminated` 状态和事务审计设计/实施。 +- `08-07-interaction-pattern-browser-regression` 已补齐本地 Chromium/UA 模拟证据;`08-07-e2e-fixture-repair` 负责关闭补跑时暴露的 committed 测试红灯,二者均为质量支撑子任务。 - 付款批次、请假和完整差旅不做详情路由化;非涉密合同附件能力按后续规约任务处理。 ## Requirements @@ -29,13 +30,15 @@ - R5:视觉与交互分析优先遵循项目指定的 `frontend-design`;本会话该 skill 不可用时,只以 `ui-ux-pro-max`、现有共享组件和产品事实源做保守补充,不创建新的视觉体系。 - R6:不得借一致性治理扩张业务模块、重做既有状态机、改变企业微信/OA 边界、放宽权限或触碰真实数据。 - R7:完整差旅属于冻结模块,不做详情路由化;仅在低风险子任务中删除自动选择第一条记录的缺陷,不恢复或扩展差旅能力。 -- R8:三个子任务独立实施与批准;低风险统一先行,采购/资产路由化随后,合同详情在其独立规划获批后另行实施。 +- R8:三个产品交付子任务独立实施与批准;低风险统一先行,浏览器证据和 E2E fixture 红灯清零后再做采购/资产路由化,合同详情在其独立规划获批后最后实施。 - R9:每个子任务独立完成本地验证、可访问性/交互回归和 Trellis 门禁;只有部署与公网关键路径检查通过后,才能告知用户线上可测。 +- R10:浏览器补跑与 E2E fixture 修复只维护验证可信度和 committed 测试绿色,不新增页面、权限、API、状态机或数据库能力。 ## Acceptance Criteria - [ ] 三个子任务均具有独立、可观察且不互相重复的验收标准。 - [ ] 低风险统一先于详情路由化完成,路由化复用已经收敛的文案、反馈和动作组件。 +- [ ] 浏览器补跑与 committed E2E fixture 修复在采购/资产路由化开工前完成,不把已知红灯带入后续子任务。 - [ ] 当前仍存在的问题被纳入范围;错误码前缀按覆盖变量名的完整搜索结果治理,不因错误的窄模式搜索被误判为已修复。 - [ ] 付款批次继续使用现有显式选择详情形态,不新增独立详情路由。 - [ ] 完整差旅继续冻结;只删除首屏自动选中第一条记录的缺陷。 @@ -57,6 +60,7 @@ - 每个子任务先运行自身聚焦测试、typecheck/build 和任务 validate,再运行父任务集成门禁。 - 父任务最终运行 `node scripts/qa/check-process-contract.mjs --baseline --json`、`node scripts/qa/audit-docs.mjs --check-registry`、`node scripts/qa/check-trellis-governance.mjs` 和 `git diff --check`。 - 路由子任务覆盖采购/资产深链、旧 `?id=` 兼容、返回与未保存保护;合同子任务额外覆盖 Prisma 校验、迁移状态、状态不变量、事务审计失败回滚和只读 reviewer。 +- E2E fixture 修复直接运行 R6 七视口 spec 与 R111 双端 spec,不使用临时副本冒充 committed suite 通过。 ## Open Questions diff --git a/.trellis/tasks/08-05-product-interaction-consistency/task.json b/.trellis/tasks/08-05-product-interaction-consistency/task.json index e5c0e9df..84be6565 100644 --- a/.trellis/tasks/08-05-product-interaction-consistency/task.json +++ b/.trellis/tasks/08-05-product-interaction-consistency/task.json @@ -2,7 +2,7 @@ "id": "product-interaction-consistency", "name": "product-interaction-consistency", "title": "产品交互与页面操作一致性治理", - "description": "统一小团队 OA 的页面操作模型,按低风险交互、采购资产路由、合同详情三项独立交付并完成集成验收。", + "description": "统一小团队 OA 的页面操作模型,按低风险交互、采购资产路由、合同详情三项产品交付独立实施;浏览器回归与 E2E fixture 子任务作为质量支撑,完成后再进入路由和迁移阶段。", "status": "planning", "dev_type": null, "scope": "frontend-governance-and-three-child-integration", @@ -22,12 +22,13 @@ "08-05-interaction-pattern-unification", "08-05-business-detail-routing", "08-05-contract-detail-workspace", - "08-07-interaction-pattern-browser-regression" + "08-07-interaction-pattern-browser-regression", + "08-07-e2e-fixture-repair" ], "parent": null, "relatedFiles": [], "notes": "", "meta": { - "description": "统一小团队 OA 的页面操作模型,按低风险交互、采购资产路由、合同详情三项独立交付并完成集成验收。" + "description": "统一小团队 OA 的页面操作模型,按低风险交互、采购资产路由、合同详情三项产品交付独立实施;浏览器回归与 E2E fixture 子任务作为质量支撑,完成后再进入路由和迁移阶段。" } } diff --git a/.trellis/tasks/08-07-e2e-fixture-repair/check.jsonl b/.trellis/tasks/08-07-e2e-fixture-repair/check.jsonl new file mode 100644 index 00000000..9dd3234a --- /dev/null +++ b/.trellis/tasks/08-07-e2e-fixture-repair/check.jsonl @@ -0,0 +1 @@ +{"_example": "Fill with {\"file\": \"\", \"reason\": \"\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."} diff --git a/.trellis/tasks/08-07-e2e-fixture-repair/design.md b/.trellis/tasks/08-07-e2e-fixture-repair/design.md new file mode 100644 index 00000000..ae3f6297 --- /dev/null +++ b/.trellis/tasks/08-07-e2e-fixture-repair/design.md @@ -0,0 +1,55 @@ +# 技术设计 + +## 1. 修改边界 + +本任务只修改两份 Playwright spec 与已归档浏览器回归记录: + +- `client/tests/e2e/r6-mobile-responsive-system-hotfix.spec.ts` +- `client/tests/e2e/r111-invoice-reimbursement-guard.spec.ts` +- `.trellis/tasks/archive/2026-08/08-07-interaction-pattern-browser-regression/implement.md` +- `.trellis/tasks/archive/2026-08/08-07-interaction-pattern-browser-regression/openclaw-test-prompt.md` +- `.trellis/tasks/08-05-product-interaction-consistency/prd.md` +- `.trellis/tasks/08-05-product-interaction-consistency/design.md` +- `.trellis/tasks/08-05-product-interaction-consistency/implement.md` + +`client/src`、`server/`、权限映射、路由和产品行为只作为只读事实源,不在修改范围。 + +## 2. R6 fixture 权限链 + +R6 登录夹具通过 `/api/me` 返回合成用户,但 `AuthContext` 不消费响应中的任意 `permissions` 字段,而是在 `client/src/auth/AuthContext.tsx:260` 使用 `resolvePermissionsForRoles(me.roleCodes)` 生成权限。 + +当前合成用户角色为 `manager`、`employee`,不能获得 `leave.manage`。`MobileLeavePage` 因此把 `?view=form` 回落为 `records`,导致仍存在的 `mobile-leave-form` 不渲染。 + +设计选择:给该 spec 的合成用户增加 `hr` 角色。`hr` 是当前角色表中最小的非通配选择,可通过真实角色解析链产生 `leave.manage`;不使用 `admin` 的 `*`,也不添加不会被消费的 mock 字段。 + +兼容检查:R6 的七个设备上下文共享该 fixture,必须完整运行 7/7,确认新增合成角色没有改变其他导航、审批、报销或移动布局断言。 + +## 3. R111 语义定位 + +R111 上传附件后,页面可能同时出现待上传摘要和已上传附件行。页面级 `getByText("付款截图.png")` 对 DOM 数量敏感,不能证明用户可打开已上传附件。 + +设计选择:PC 与移动两处都改为按可访问名称定位已上传附件按钮: + +```ts +getByRole("button", { name: "查看补充附件:付款截图.png" }) +``` + +该断言唯一、面向用户动作,并与 PC/移动运行时已有 `aria-label` 一致;不使用 `.first()` 或索引规避 strict mode。 + +## 4. 归档记录勘误 + +归档记录不删除当时的执行事实:原始 committed R6 spec 失败,临时 runner 跳过该断言后其余移动矩阵通过。只纠正因果诊断:失败来自 fixture 角色不能解析出 `leave.manage`,不是表单取消或企微/OA 边界要求删除覆盖。 + +证据文件名 `r6-stale-leave-readonly-320.png` 作为历史产物不改名;文字记录注明文件名不代表最终诊断。 + +## 5. 回滚 + +本任务没有数据迁移或运行时代码回滚。若测试修复引入意外回归,回滚范围仅为: + +- 移除 R6 fixture 新增的 `hr` 角色; +- 恢复 R111 两处原定位器; +- 恢复归档文字勘误。 + +父任务规划只增加质量支撑任务登记和顺序,不改变三个业务交付物;若登记需回滚,只撤销该任务行与顺序说明。 + +回滚后会重新暴露两条已知红灯,因此优先修正断言/fixture 并 roll-forward,不以删除覆盖作为回滚手段。 diff --git a/.trellis/tasks/08-07-e2e-fixture-repair/implement.jsonl b/.trellis/tasks/08-07-e2e-fixture-repair/implement.jsonl new file mode 100644 index 00000000..9dd3234a --- /dev/null +++ b/.trellis/tasks/08-07-e2e-fixture-repair/implement.jsonl @@ -0,0 +1 @@ +{"_example": "Fill with {\"file\": \"\", \"reason\": \"\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."} diff --git a/.trellis/tasks/08-07-e2e-fixture-repair/implement.md b/.trellis/tasks/08-07-e2e-fixture-repair/implement.md new file mode 100644 index 00000000..695cc9ab --- /dev/null +++ b/.trellis/tasks/08-07-e2e-fixture-repair/implement.md @@ -0,0 +1,53 @@ +# 执行计划 + +## 1. 实施前 + +- [x] 读取 `.trellis/workflow.md`、本 task PRD/design、前端 spec 与测试规约。 +- [x] 记录 `git status --short`,保护其他任务变更。 +- [x] 用户批准最新规划摘要后运行 `task.py start 08-07-e2e-fixture-repair`。 +- [x] 启动 loopback Vite;只使用合成 mock,不访问公网或真实账号。 + +## 2. 测试修复 + +- [x] 在 R6 fixture 的 `employee.roleCodes` 增加 `hr`;不修改产品角色映射或添加伪权限字段。 +- [x] 保留 `/mobile/leave?view=form` 与 `mobile-leave-form` 断言。 +- [x] 将 R111 PC、移动两处附件上传后断言改为语义化“查看补充附件”按钮定位。 +- [x] 搜索确认不存在本任务遗漏的同类 `getByText("付款截图.png")`。 + +## 3. 归档勘误 + +- [x] 修正归档 `implement.md` 中三处 R6 原因描述,保留临时 runner 历史事实。 +- [x] 修正归档 `openclaw-test-prompt.md` 中两处错误诊断。 +- [x] 搜索确认归档目录不再把该失败描述为错误的产品/断言诊断。 +- [x] 更新父任务 PRD/design/implement:登记两个质量支撑子任务,并固定 fixture 修复先于采购/资产路由化。 + +## 4. 验证 + +- [x] committed R6 spec 聚焦运行:7 passed,退出码 0。 +- [x] committed R111 spec 完整运行:2 passed,退出码 0。 +- [x] `cd client && npm run typecheck && npm run build`。 +- [x] task validate、process contract、Trellis governance、`git diff --check`。 +- [x] `git diff --name-only -- client/src server` 为空。 + +## 5. 收尾 + +- [x] 只读复核确认没有降低测试口径、删除断言或扩大真实权限。 +- [x] 使用中文 commit message 并引用 `08-07-e2e-fixture-repair`。 +- [ ] 归档任务;不宣称部署、公网或企业微信真机已验证。 + +## 验证记录 + +- R6:7 个视口全部通过;`mobile-leave-form` 断言在 committed spec 中执行并通过。 +- R111:PC 与企业微信 UA 用例均通过;两处附件断言均使用唯一语义按钮定位。 +- 静态门禁:typecheck、build、task validate、process contract、Trellis governance、`git diff --check` 均通过;docs audit 0 errors,保留既有 22 个 warning。 +- 边界:本地 Chromium/UA 模拟和合成 mock;未执行部署、公网、真实账号、企业微信真机或生产数据验证。 + +## 只读复核 + +- 未改产品运行时代码;没有删除或跳过 `mobile-leave-form`,没有使用 `.first()` 掩盖 R111 的重复定位。 +- 当前 task 与父任务规划仍保留,提交和归档在最终门禁后执行。 + +## 回滚点 + +- 测试文件改动均为单行/小范围 fixture 与定位器调整,可独立回退。 +- 归档勘误属于事实修正;除非新证据推翻权限链,不应回退为“表单已取消”的错误诊断。 diff --git a/.trellis/tasks/08-07-e2e-fixture-repair/prd.md b/.trellis/tasks/08-07-e2e-fixture-repair/prd.md new file mode 100644 index 00000000..b4ff4641 --- /dev/null +++ b/.trellis/tasks/08-07-e2e-fixture-repair/prd.md @@ -0,0 +1,124 @@ +# 交互回归 E2E 夹具修复 + +## Goal + +修复 `08-07-interaction-pattern-browser-regression` 已确认但未落入仓库测试文件的两条 Playwright 红灯,使 R6 移动响应式矩阵和 R111 报销附件双端用例可以直接从 committed spec 运行通过;同时勘误已归档任务对 R6 失败原因的错误描述,防止后续人员误删仍有效的 OA 应急请假表单覆盖。 + +本任务只修测试夹具、断言和审计记录,不改变产品权限模型、请假行为、报销附件行为或运行时代码。 + +## Background + +### R6:权限门控导致表单不可见 + +- `client/src/pages/MobileLeavePage.tsx:238` 仅在当前权限包含 `leave.manage` 时令 `canUseOaEmergencyLeave=true`;`:253` 会把无该权限用户的 `?view=form` 强制回落到 `records`。 +- `mobile-leave-form` 仍存在于 `client/src/pages/MobileLeavePage.tsx:498`,不是已取消能力,原断言覆盖的是仍有效的 OA 应急请假表单。 +- `client/tests/e2e/r6-mobile-responsive-system-hotfix.spec.ts:25-47` 的 fixture 用户只有 `manager`、`employee` 两个角色。`client/src/auth/AuthContext.tsx` 通过 `resolvePermissionsForRoles(me.roleCodes)` 生成前端权限,不读取 fixture 自造的 `permissions` 字段;上述两角色均不产生 `leave.manage`。 +- 当前角色表中最小可产生 `leave.manage` 的非通配角色是 `hr`。本任务应给 R6 合成用户增加 `hr` 角色,从角色解析链真实获得该权限,而不是删除 `mobile-leave-form` 断言或修改产品门控。 + +### R111:宽泛文本定位触发 strict mode + +- `client/tests/e2e/r111-invoice-reimbursement-guard.spec.ts:680` 与 `:812` 上传 `付款截图.png` 后都使用页面级 `getByText("付款截图.png")`。 +- 移动端上传后,待上传摘要会渲染 `付款截图.png · 1 KB`,已上传附件操作入口又渲染 `付款截图.png`,页面级文本定位命中两个元素并触发 Playwright strict mode;产品 DOM 和上传行为本身正确。 +- PC、移动附件行均有稳定的语义化按钮名 `查看补充附件:付款截图.png`。本任务统一改用该 role/name 断言,证明附件已成为可操作材料,同时避免 `.first()` 的顺序依赖。 + +### 归档勘误 + +- 已归档任务的 `implement.md` 与 `openclaw-test-prompt.md` 把 R6 失败描述为“过期请假表单断言”或与“企微主办、OA 只读”边界冲突,这一诊断不正确。 +- 勘误必须保留当时“临时 runner 跳过该断言后其余矩阵通过”的历史事实,同时明确后续复核结论:fixture 缺少能解析出 `leave.manage` 的角色,导致 `effectiveView` 回落到 `records`。 + +## Scope + +- 修复 R6 spec 的合成用户角色,使既有 `mobile-leave-form` 断言在七个视口继续执行并通过。 +- 将 R111 PC 与移动端两处附件上传后断言收窄到语义化“查看补充附件”按钮。 +- 勘误归档浏览器回归任务中所有错误的 R6 原因描述,保留历史执行事实与证据边界。 +- 在父任务 PRD/design/implement 中登记浏览器回归补跑与本任务为质量支撑子任务,明确本任务先于采购/资产路由化执行,不改变三项产品交付范围。 +- 直接运行仓库 committed spec 验证,不再依赖 `/tmp` 临时副本或删除断言的 runner。 + +## Requirements + +- R1:R6 fixture 用户的 `roleCodes` 增加 `hr`,通过现有 `resolvePermissionsForRoles` 获得 `leave.manage`;不得向 `/api/me` mock 添加不会被消费的伪 `permissions` 字段。 +- R2:保留 `/mobile/leave?view=form`、`mobile-leave-form` 和移动稳定性断言;不得删除、跳过或降级该页面覆盖。 +- R3:R111 的 PC 与移动附件断言均改为 `getByRole("button", { name: "查看补充附件:付款截图.png" })` 或等价的唯一语义定位;不得使用页面级宽泛 `getByText` 或依赖 DOM 顺序的 `.first()`。 +- R4:归档记录明确写出 `leave.manage` 权限门控、`effectiveView` 回落和 fixture 角色缺口;删除“表单已取消”“过期断言”“企微主办所以不应覆盖该表单”等误导性结论。 +- R5:不修改 `client/src`、`server/`、角色权限映射、路由、API、数据库、依赖或产品功能;只允许改两份 E2E spec、归档勘误、父任务的质量子任务登记和当前 Trellis task。 +- R6:截图仍写入各自 spec 的既有证据目录,不覆盖其他任务归档证据;测试数据继续使用合成内容。 +- R7:父任务继续以低风险统一、采购/资产路由化、合同详情为三个产品交付物;浏览器补跑与 fixture 修复只作为质量支撑和执行前置,不增加产品能力。 + +## Acceptance Criteria + +- [x] A1:直接运行 committed `r6-mobile-responsive-system-hotfix.spec.ts` 聚焦用例,七个视口全部通过,报告为 `7 passed`、退出码 0,且每个视口实际执行 `mobile-leave-form` 可见性与移动稳定性断言。 +- [x] A2:直接运行 committed `r111-invoice-reimbursement-guard.spec.ts`,PC 与企业微信 UA 两个用例全部通过,报告为 `2 passed`、退出码 0,不再出现 `付款截图.png` strict mode violation。 +- [x] A3:归档 `implement.md` 与 `openclaw-test-prompt.md` 的 R6 描述已改为权限 fixture 诊断,并保留临时 runner 历史事实;目标错误诊断搜索无命中。 +- [x] A4:变更文件仅包含两份 E2E spec、两份归档记录、父任务四份规划文件和当前 task 文件;`client/src`、`server/` 无改动。 +- [x] A5:`cd client && npm run typecheck && npm run build`、任务 validate、流程合同、Trellis governance 与 `git diff --check` 全部通过。 +- [x] A6:父任务明确记录本质量修复先于 `08-05-business-detail-routing`,合同迁移任务仍最后实施;父任务的三个产品交付边界不变。 + +## Out of Scope + +- 不删除或隐藏 OA 应急请假表单,不调整 `leave.manage` 产品门控,不扩大真实角色权限。 +- 不修改报销材料 DOM、附件上传/预览逻辑或可访问名称。 +- 不重构全仓 E2E fixture/角色体系,不清理其他历史测试。 +- 不承诺本任务运行全仓全部 Playwright spec;验收对象是两份已知红灯的 committed spec 及其完整用例。 +- 不执行部署、公网、真实账号、企业微信真机或生产数据验证。 + +## Test Plan + +### 1. 基线与修复验证 + +在本地 loopback Vite 与合成 API mock 下单 worker 执行: + +```bash +cd client +E2E_MODE=local E2E_BASE_URL=http://127.0.0.1:5178 npx playwright test \ + tests/e2e/r6-mobile-responsive-system-hotfix.spec.ts \ + --grep '关键移动页面跨视口自适应且首页宫格不挤压' \ + --workers=1 --reporter=list +E2E_MODE=local E2E_BASE_URL=http://127.0.0.1:5178 npx playwright test \ + tests/e2e/r111-invoice-reimbursement-guard.spec.ts \ + --workers=1 --reporter=list +``` + +R6 必须报告 7/7;R111 必须报告 2/2。不得使用临时副本、临时删除断言或重定向到当前任务之外的证据目录来替代 committed spec 结果。 + +### 2. 静态与流程门禁 + +```bash +cd client && npm run typecheck && npm run build +python3 ./.trellis/scripts/task.py validate 08-07-e2e-fixture-repair +node scripts/qa/check-process-contract.mjs --baseline --json +node scripts/qa/check-trellis-governance.mjs --json +git diff --check +``` + +收尾时额外确认: + +- `rg -n '过期请假|企微主办.*OA 只读' .trellis/tasks/archive/2026-08/08-07-interaction-pattern-browser-regression` 不再命中本次错误诊断。 +- `git diff --name-only -- client/src server` 为空。 + +## 验证记录 + +- `task.py start 08-07-e2e-fixture-repair`:退出码 0,任务状态为 `in_progress`。 +- R6 committed spec:`E2E_MODE=local E2E_BASE_URL=http://127.0.0.1:5178 npx playwright test tests/e2e/r6-mobile-responsive-system-hotfix.spec.ts --grep '关键移动页面跨视口自适应且首页宫格不挤压' --workers=1 --reporter=list`,退出码 0,`7 passed`;七个视口均执行 `mobile-leave-form` 可见性断言。 +- R111 committed spec:`E2E_MODE=local E2E_BASE_URL=http://127.0.0.1:5178 npx playwright test tests/e2e/r111-invoice-reimbursement-guard.spec.ts --workers=1 --reporter=list`,退出码 0,`2 passed`。 +- `cd client && npm run typecheck`:退出码 0;`npm run build`:退出码 0(仅有既有 Browserslist 数据过期提示)。 +- `python3 ./.trellis/scripts/task.py validate 08-07-e2e-fixture-repair`:退出码 0。 +- `node scripts/qa/check-process-contract.mjs --baseline --json`:`PASS`,0 errors / 0 warnings。 +- `node scripts/qa/audit-docs.mjs --check-registry`:0 errors,保留既有 22 个未注册 docs warning。 +- `node scripts/qa/check-trellis-governance.mjs --json`:`PASS`,0 errors / 0 warnings。 +- `git diff --check`:退出码 0;`git diff --name-only -- client/src server` 为空;归档错误诊断搜索无命中。 + +## 只读复核 + +- 仅修改两份 committed E2E spec、两份归档记录、父任务规划登记和当前 Trellis task;未修改 `client/src`、`server/`、权限映射、API、数据库、依赖或生产配置。 +- `mobile-leave-form` 断言仍在 committed spec 中,未删除、跳过或降级;R111 未使用宽泛文本定位或 `.first()`。 +- `hr` 只加入 R6 合成 fixture,不改变真实角色权限;测试使用 loopback 与合成 API mock,未执行公网、部署或真机验证。 + +## Risks and Deferred Items + +- `hr` 角色还会带来其他 HR 权限,但只存在于该 spec 的合成用户中;必须以 R6 全部七个视口回归确认没有改变页面可达性断言。相比 `admin` 通配角色,`hr` 是当前角色表中更小的有效选择。 +- R111 的语义化按钮名来自当前产品可访问名称;若未来文案调整,应同步按用户可感知动作更新测试,不应退回宽泛文本或 `.first()`。 +- 本任务只消除两条已知 committed spec 红灯;其他全仓测试结果仍以各自任务和门禁为准。 + +## Open Questions + +无。失败原因、最小修复、验收命令和不做范围均已由代码与用户复核确认。 diff --git a/.trellis/tasks/08-07-e2e-fixture-repair/task.json b/.trellis/tasks/08-07-e2e-fixture-repair/task.json new file mode 100644 index 00000000..e92ff87b --- /dev/null +++ b/.trellis/tasks/08-07-e2e-fixture-repair/task.json @@ -0,0 +1,39 @@ +{ + "id": "e2e-fixture-repair", + "name": "e2e-fixture-repair", + "title": "交互回归 E2E 夹具修复", + "description": "修复交互回归中两条已知 Playwright 红灯:为 R6 合成用户补足可解析为 leave.manage 的角色,并将 R111 附件断言收窄到语义化操作入口;同步勘误已归档诊断,不改业务代码。", + "status": "in_progress", + "dev_type": null, + "scope": "repair-r6-r111-committed-e2e-fixtures-and-archive-diagnosis", + "package": "client", + "priority": "P2", + "creator": "shunli", + "assignee": "shunli", + "createdAt": "2026-08-07", + "completedAt": null, + "branch": null, + "base_branch": "main", + "worktree_path": null, + "commit": null, + "pr_url": null, + "subtasks": [], + "children": [], + "parent": "08-05-product-interaction-consistency", + "relatedFiles": [ + "client/tests/e2e/r6-mobile-responsive-system-hotfix.spec.ts", + "client/tests/e2e/r111-invoice-reimbursement-guard.spec.ts", + ".trellis/tasks/archive/2026-08/08-07-interaction-pattern-browser-regression/implement.md", + ".trellis/tasks/archive/2026-08/08-07-interaction-pattern-browser-regression/openclaw-test-prompt.md", + ".trellis/tasks/08-05-product-interaction-consistency/prd.md", + ".trellis/tasks/08-05-product-interaction-consistency/design.md", + ".trellis/tasks/08-05-product-interaction-consistency/implement.md", + "client/src/pages/MobileLeavePage.tsx", + "client/src/auth/permissions.ts", + "docs/测试规约与回归门禁.md" + ], + "notes": "只修测试夹具、测试定位器、父任务质量子任务登记和历史诊断;禁止删除仍有效的 mobile-leave-form 覆盖,禁止修改产品权限或运行时代码。", + "meta": { + "description": "修复交互回归中两条已知 Playwright 红灯:为 R6 合成用户补足可解析为 leave.manage 的角色,并将 R111 附件断言收窄到语义化操作入口;同步勘误已归档诊断,不改业务代码。" + } +} \ No newline at end of file diff --git a/.trellis/tasks/archive/2026-08/08-07-interaction-pattern-browser-regression/implement.md b/.trellis/tasks/archive/2026-08/08-07-interaction-pattern-browser-regression/implement.md index ad0c7244..38fe995d 100644 --- a/.trellis/tasks/archive/2026-08/08-07-interaction-pattern-browser-regression/implement.md +++ b/.trellis/tasks/archive/2026-08/08-07-interaction-pattern-browser-regression/implement.md @@ -17,7 +17,7 @@ - [x] Portal 聚焦场景通过:首屏顺序、WelcomeDialog 权限过滤、区域错误重试、登录/Portal Tab 焦点(3/3)。 - [x] PC 审批详情场景通过:后果说明、二次确认取消、转审 10 字门槛、Tab 顺序(1/1)。 -- [x] R6 七视口移动矩阵通过:使用临时运行副本仅移除过期请假表单断言,七个视口均通过;未改仓库测试或业务代码。 +- [x] R6 七视口移动矩阵通过:使用临时运行副本暂时跳过 `mobile-leave-form` 断言,七个视口均通过;后续复核确认表单仍存在,原失败原因是 fixture 角色不能解析出 `leave.manage`,导致 `effectiveView` 回落到 `records`;未改仓库测试或业务代码。 - [x] R111 企业微信 UA 报销详情通过:原始 spec 的重复文本 strict locator 先分类为测试夹具问题,临时副本仅加 `.first()` 后 1/1 通过;未改仓库测试或业务代码。 - [x] Wave3 PC/移动代表场景通过:审批/报销详情、返回链路、断点双栏和截图(2/2)。 - [x] R1/R6 状态徽章一次性 Chromium harness 通过:未知状态、试用期和 Todo 中文文案均按预期渲染。 @@ -55,7 +55,7 @@ - 日期:2026-08-07;浏览器为仓库 Playwright Chromium `chromium-1208`(Chrome for Testing 145.0.7632.6),headless,未使用真实账号或公网地址。 - Node `v22.22.2`、npm `10.9.7`、Playwright `1.58.2`;本地 Vite loopback:`http://127.0.0.1:5178`。 - 开始前工作区只有父任务 `08-05-product-interaction-consistency/task.json` 的既有子任务登记变更和本 task 目录;本任务未改 `client/src`、`server/` 或依赖。 -- 截图目录:`output/playwright/08-07-interaction-pattern-browser-regression/`,共 66 张脱敏截图;另保留 `r6-stale-leave-readonly-320.png` 作为历史断言失败留痕。 +- 截图目录:`output/playwright/08-07-interaction-pattern-browser-regression/`,共 66 张脱敏截图;另保留 `r6-stale-leave-readonly-320.png` 作为历史断言失败留痕(文件名保留历史,不代表最终诊断)。 ### 浏览器结果 @@ -64,14 +64,14 @@ | R1/R6 状态徽章 | 一次性 Chromium harness,动态导入实际 `StatusBadge.tsx` | PASS,未知状态=`未知状态`、`probation`=`试用期`、Todo 四态中文,console/pageerror=0,退出码 0 | `output/playwright/08-07-interaction-pattern-browser-regression/01-status-badge-fallback-1440x960.png` | | Portal R4/R9/R11 | `login-portal-ux.spec.ts` 聚焦 grep,单 worker | PASS,3 passed,退出码 0 | 既有 Playwright 报告;Portal 场景截图在任务目录 | | PC 审批 R11 | `workflow-action-confirm-ui.spec.ts` 聚焦 grep,单 worker | PASS,1 passed,退出码 0 | 既有 Playwright 报告;审批详情截图在任务目录 | -| R6 七视口 R10/R11 | 临时副本仅重定向截图并移除过期请假表单断言;原始 spec 该断言因当前“企微主办、OA 只读”边界失败 | PASS(等价 in-scope runner),7 passed,退出码 0 | `w320`/`w360`/`w375`/`w390`/`w393`/`w414`/`w430` 系列截图;`r6-stale-leave-readonly-320.png` | +| R6 七视口 R10/R11 | 临时副本仅重定向截图并暂时跳过 `mobile-leave-form` 断言;后续确认原始 fixture 角色缺少可解析的 `leave.manage`,`effectiveView` 因此回落到 `records` | PASS(等价 in-scope runner),7 passed,退出码 0 | `w320`/`w360`/`w375`/`w390`/`w393`/`w414`/`w430` 系列截图;`r6-stale-leave-readonly-320.png` | | R111 企业微信 UA R10/R11 | 原始 spec 的 `getByText("付款截图.png")` 在当前 DOM 命中两项,strict mode 失败;临时副本仅改为 `.first()` 后重跑 | PASS(等价 in-scope runner),1 passed,退出码 0;原始失败归类为测试夹具断言问题 | `mobile-wecom-reimbursement-detail.png`、`w390-ios-wecom-mobile-reimbursement-detail.png` | | Wave3 R10/R11 | `pc-detail-mobile-visual-wave3.spec.ts` 临时重定向截图目录 | PASS,2 passed,退出码 0 | `01-pc-reimbursement-detail.png`、`02-pc-workflow-detail.png`、`03-pc-notifications.png`、`04-mobile-home.png` 等 | | R14 差旅不自动选中 | 临时 runner 合成两条记录,直接访问 `/travel-requests` | PASS,1 passed,退出码 0;URL 仅含 `tab=requests`,无 `id`,未出现详情加载 | `18-travel-list-no-auto-select-1440x960.png` | ### 失败分类与未覆盖证据 -- `FAIL(测试夹具/事实源漂移)`:未修改的 R6 旧用例仍断言 `mobile-leave-form`;当前产品页面明确为“企业微信主办、OA 只读”。未改业务页面,保留失败截图并用临时副本验证本任务其余七视口断言。 +- `FAIL(测试夹具权限)`:未修改的 R6 旧用例 fixture 只有 `manager`、`employee`,不能经真实角色解析链得到 `leave.manage`;`MobileLeavePage` 将 `?view=form` 的 `effectiveView` 回落到 `records`,所以仍有效的 `mobile-leave-form` 断言失败。未删除该覆盖,保留失败截图并用临时副本验证其余七视口断言。 - `FAIL(测试断言 strict locator)`:未修改的 R111 用例对同时出现在材料卡和查看按钮中的同名文本使用 strict locator;临时副本只收窄定位器后通过。该问题应在后续测试维护任务修复,不属于本任务产品缺陷。 - `PAUSED`:iPhone/Android 企业微信真实 WebView、真机安全区/键盘行为未执行;本地 UA 模拟不等价于真机。 - `PAUSED`:公网部署、真实登录和生产数据回归未执行。 diff --git a/.trellis/tasks/archive/2026-08/08-07-interaction-pattern-browser-regression/openclaw-test-prompt.md b/.trellis/tasks/archive/2026-08/08-07-interaction-pattern-browser-regression/openclaw-test-prompt.md index 1b5d5f32..ac5fe450 100644 --- a/.trellis/tasks/archive/2026-08/08-07-interaction-pattern-browser-regression/openclaw-test-prompt.md +++ b/.trellis/tasks/archive/2026-08/08-07-interaction-pattern-browser-regression/openclaw-test-prompt.md @@ -177,7 +177,7 @@ PC `1440x960`: | 1. 状态徽章 | PASS | 本地 Chromium,1440x960 | 实际动态导入 `StatusBadge`;未知状态显示“未知状态”,`probation` 显示“试用期”,Todo 四态显示自然中文;console/pageerror=0 | `01-status-badge-fallback-1440x960.png` | | 2. Portal | PASS | 合成员工/经理,PC 1440x960 | 3 个聚焦用例通过(3/3),顺序、权限过滤、中文错误与重试、焦点均有证据 | 任务目录内 Portal 截图 | | 3. PC 审批 | PASS | 合成审批人,1280x800/1100x768 | 后果说明、取消不调用接口、转审 10 字门槛、焦点顺序通过(1/1) | 任务目录内审批截图 | -| 4. R6 七视口 | PASS(等价临时 runner) | iOS/Android 企业微信 UA + 普通移动 UA;320/360/375/390/393/414/430 | 7/7 通过;无横溢、无桌面表格、底部导航、sticky action、安全区和按钮尺寸通过 | `w320`–`w430` 系列;原始 spec 的过期请假断言失败另记为测试夹具问题 | +| 4. R6 七视口 | PASS(等价临时 runner) | iOS/Android 企业微信 UA + 普通移动 UA;320/360/375/390/393/414/430 | 7/7 通过;无横溢、无桌面表格、底部导航、sticky action、安全区和按钮尺寸通过 | `w320`–`w430` 系列;原始 fixture 角色缺少 `leave.manage`,导致 `effectiveView` 回落 `records`,历史断言失败另记为测试夹具问题 | | 5. R111 材料区 | PASS(等价临时 runner) | iOS 企业微信 UA,移动 390x844 | 1/1 通过;材料区、sticky action、safe-area、按钮尺寸和无横溢通过 | 原始 spec 的同名文本 strict locator 命中两项;临时副本仅收窄 `.first()`,未改仓库测试 | | 6. Wave3 详情 | PASS | PC/移动合成账号;1440、1280、1100 断点及移动代表视口 | PC/移动截图用例 2/2 通过,详情返回链路和双栏布局通过 | `01-pc-reimbursement-detail.png` 等 | | 7. R14 差旅 | PASS | 合成管理员,PC 1440x960 | 两条合成记录加载后 URL 只有 `tab=requests`,没有 `id`;右侧保持未选择/新建态 | `18-travel-list-no-auto-select-1440x960.png` | @@ -185,7 +185,7 @@ PC `1440x960`: ### 结果边界 - 未修改 `client/src`、`server/`、仓库 E2E spec 或依赖;R6/R111 的临时副本均位于 `/tmp`,只用于收窄历史测试断言和重定向截图。 -- 原始 R6 请假断言与已确认“企业微信主办、OA 只读”边界不一致;原始 R111 断言违反 Playwright strict locator。两项均保留失败分类,不伪装成原始 spec 无条件通过。 +- 原始 R6 失败不是产品能力下线或宿主边界限制:fixture 角色不能解析出 `leave.manage`,导致 `effectiveView` 回落到 `records`;`mobile-leave-form` 仍是有效覆盖。原始 R111 断言违反 Playwright strict locator。两项均保留当时失败分类,不伪装成原始 spec 无条件通过。 - 代表性 iPhone/Android 企业微信 WebView、真机键盘与 safe-area,以及公网/部署验证均为 `PAUSED`。 ## 最终输出格式 diff --git a/client/tests/e2e/r111-invoice-reimbursement-guard.spec.ts b/client/tests/e2e/r111-invoice-reimbursement-guard.spec.ts index 7973dbb8..31fe56a5 100644 --- a/client/tests/e2e/r111-invoice-reimbursement-guard.spec.ts +++ b/client/tests/e2e/r111-invoice-reimbursement-guard.spec.ts @@ -677,7 +677,7 @@ test.describe("R111 国内报销发票与附件双端体验", () => { buffer: Buffer.from("r111 attachment"), }); await page.getByRole("button", { name: "上传附件" }).click(); - await expect(page.getByText("付款截图.png")).toBeVisible(); + await expect(page.getByRole("button", { name: "查看补充附件:付款截图.png" })).toBeVisible(); await expect(page.getByTestId("reimbursement-travel-summary")).toContainText("合成差旅概要"); await expect(page.getByTestId("reimbursement-travel-summary")).toContainText("测试地点"); await expect(page.getByTestId("reimbursement-materials")).toContainText("发票 2 · 补充附件 1"); @@ -809,7 +809,7 @@ test.describe("R111 国内报销发票与附件双端体验", () => { buffer: Buffer.from("r111 mobile attachment"), }); await mobile.page.getByTestId("mobile-reimbursement-materials").getByRole("button", { name: "上传附件" }).click(); - await expect(mobile.page.getByText("付款截图.png")).toBeVisible(); + await expect(mobile.page.getByRole("button", { name: "查看补充附件:付款截图.png" })).toBeVisible(); await expect(mobile.page.getByTestId("mobile-reimbursement-travel-summary")).toContainText("合成差旅概要"); await expect(mobile.page.getByTestId("mobile-reimbursement-travel-summary")).toContainText("测试地点"); await expect(mobile.page.getByTestId("mobile-reimbursement-materials")).toContainText("发票 2 · 补充附件 1"); diff --git a/client/tests/e2e/r6-mobile-responsive-system-hotfix.spec.ts b/client/tests/e2e/r6-mobile-responsive-system-hotfix.spec.ts index 3116c7f5..bfd9985c 100644 --- a/client/tests/e2e/r6-mobile-responsive-system-hotfix.spec.ts +++ b/client/tests/e2e/r6-mobile-responsive-system-hotfix.spec.ts @@ -32,7 +32,7 @@ const employee = { directManagerId: "manager-r6", status: "active", notificationPushEnabled: true, - roleCodes: ["manager", "employee"], + roleCodes: ["manager", "employee", "hr"], profileCompleteness: { hasEmergencyContact: true, hasBankAccount: true,