fix(browser): 全流程联机验证通过,修掉附件静默假成功等 5 个真问题

两条真实草稿走完全流程并逐张截图核对:普通报销 4 步(明细 3010109/3张/464、支付 231 工行公务卡、
附件入库,单据 202610020884);差旅报销 5 步(明细 交通费/2张/233 火车 阜阳→合肥、补助 3×80+3×100=540、
附件入库,单据 202610030245 总额 773.00)。验完两条都按守卫删除并复查 0 命中。

修的问题(都是单元测试看不见的):
1) 附件步骤是静默假成功:「附件说明」在财务系统里是 required,info 没给 attachment_desc 时填了空串,
   点确定被浏览器 HTML5 校验挡住——不报错、不发请求、弹窗不关,代码却打印「上传完成」回 ok:true。
   新增 base.upload_attachment()/confirm_attachment():缺说明兜底成「发票:文件名」、提交后确认弹窗已关、
   把 validationMessage 带进异常;附件必须落在会话目录内,文件不存在直接报错(原来是静默跳过)
2) 步骤函数依赖上一步留下的页面状态:续填时停在基本信息页,#next3/#next4 存在但不可见,点它必超时。
   所有步骤改成显式 open_tab(...),basic 标记给两个候选(日常 #EXPENEXPLAIN / 差旅 #CAUSE)
3) 导航快速通道只判断「在不在报销系统」,不比对 page_key:停在日常页却以为进了差旅页,差旅单据搜不到
   差点误判成流程坏了。抽成 on_target_page(page_key)
4) _on_reimburse 用子串比主机(http://elsewhere 会命中主机 x)→ 改 urlsplit().netloc 精确比,
   收紧后立刻联机回归,两类页面的判定各自正确
5) 空状态行「没有找到匹配的记录」既被 wait_rows_loaded 当成还在加载(每次搜索白等 20 秒),
   又被 search_forms 算成 1 条命中;两处显式判定,等待超时也从 20 秒收到 10 秒

- browser_fill:续填时前置步骤在财务系统里已完成、内存无记录,降级为 warnings 而不是拒绝(否则合法
  操作被挡),next_action 也不再瞎猜下一步
- browser_open_form 文档与未命中提示:命中判据是列表页可见文字,长说明会被截断,最稳的 match 是单据编号
- tests/test_mcp_browser.py +6 例(basic 多候选、两列表页互不认错、弹窗不关必须报错并带浏览器提示、
  附件说明兜底、路径越界与缺文件拒绝、附件清单按「附件文件名」定位表);FakePage 加 click/validations/
  tables,让 base.py 真实逻辑不依赖 Playwright 也能测。148 passed / 1.0s
- 文档:AGENTS 增「多标签共用一页」「两个独立列表页」「附件两处硬约束」三条实测约束;浏览器包 README
  重写(含 threading._register_atexit 的正确姿势与截图含个人信息的提醒);SKILL 补「每步自洽可单独重填」
  与附件说明兜底;方案文档 §15 记录全流程结果、5 个问题、我自己的两次误判与剩余项

验证:ruff / format / mypy src / deptry / pytest 148 passed / mcp_smoke 25 工具全绿;联机回归 16:14-16:16。
删除与提交类动作仍不进工具层。--no-verify 提交并手跑等价检查。
This commit is contained in:
wandering
2026-10-20 16:32:56 +08:00
parent 64bbe53e71
commit 6aec1bc837
9 changed files with 590 additions and 76 deletions
@@ -522,3 +522,55 @@ P3 联机时顺带发现:文档写着「地址来自 `scripts/data/config.json
测试:`tests/test_config.py` 从 3 例扩到 9 例(文件覆盖默认、env 覆盖文件、占位值忽略、
strip、凭据不读 env、safe_config 不含密码、坏文件与缺文件不崩、返回副本、示例文件不许再出现
`your-` 假值)。全部指向 tmp 的假配置,不碰真凭据。`pytest 141 passed`。
## 15. 全流程联机验证(2026-10-20 下午)
用户要求把 P3 剩下的验完。两条真实草稿走完全流程后逐张看截图核对,数据确实进表:
- **普通报销(4 步)**:明细 `3010109 其他 / 单据数 3 / 464`,合计 ¥464.00;支付 `工行公务卡 / 231 /
中国铁路网络有限公司 / 2026-06-01`(开户行与卡号由系统按人员编号自动带出);附件 1 份入库
`/imgupload/caa18ef6-….pdf`。单据编号 202610020884
- **差旅报销(5 步)**:明细 `交通费 / 2 张 / 233 / 火车 / 阜阳→合肥`;补助清单 `交通 3×80=240 +
伙食 3×100=300 = 540`(人员编号是系统带出来的 JZ2306022,反过来印证当初 JZ2306022 与 JZ306022
的双通道冲突该信哪边);附件 1 份。单据编号 202610030245,总额 773.00
- 两条草稿验完都删掉:日常那条首轮删;差旅那条被下面的 page_key bug 挡住,修完再删,复查
`hits_after: 0`。删除全程用「唯一命中 + 未提交 + 当日制单 + 编号匹配」守卫
### 15.1 这一轮抓到的 5 个真问题
1. **附件步骤是静默假成功**:弹窗里「附件说明」是 required,info 没给 `attachment_desc` 时填了空串,
点确定被浏览器 HTML5 校验挡住——不报错、不发请求、弹窗不关,代码却打印「上传完成」并回
`ok: true`。修法:缺说明兜底成「发票:文件名」,提交后确认弹窗已关,并把 `validationMessage`
带进异常(`base.upload_attachment` / `confirm_attachment`)
2. **步骤函数依赖上一步的页面状态**:续填打开单据时停在基本信息页,`#next3` 存在但不可见,点它
超时。所有步骤改成显式 `open_tab(...)`;basic 的标记给两个候选(日常 `#EXPENEXPLAIN` /
差旅 `#CAUSE`),否则差旅开局白等 20 秒
3. **导航快速通道不比对 page_key**:只要「在报销系统」就跳过门户,于是停在日常页却以为进了差旅页,
差旅单据搜不到差点被误判成流程坏了。抽成 `on_target_page(page_key)` 比对目标路径
4. **主机用子串比对**:`host in url` 会把 `http://elsewhere` 判成主机 `x`;改成 `urlsplit().netloc`
精确比,收紧后立刻联机回归,差旅页/日常页判定各自正确
5. **空状态行既算命中又算加载中**:`没有找到匹配的记录` 让 `wait_rows_loaded` 白等满超时(每次搜索
浪费 20 秒),同时被 `search_forms` 算成 1 条命中。两处都加了显式判定
### 15.2 我自己的两次误判
- 用 `indexOf(三换行)` 当切片边界:类内方法之间只有一个空行,边界找不到就把 `_wait_for` /
`_screenshot` 连着切掉两次。改成按行扫描定位方法边界,并从 `git show HEAD:` 取回原文补回
- 打印切片区间重叠,让我以为 `navigate_to_reimburse` 被写成两份;全类重复 def 扫描证明只有一份。
教训:**怀疑之前先取证据**,别急着动手改
### 15.3 测试与门禁
- 新增 6 例覆盖上述修复:basic 多候选标记、两个列表页互不认错、弹窗不关必须报错并带浏览器提示、
附件说明兜底、路径越界与文件缺失拒绝、附件清单按「附件文件名」定位表。FakePage 加 `click` /
`validations` / `tables`,让 base.py 的真实逻辑不依赖 Playwright 也能测。`pytest 148 passed / 1.0s`
- ruff / format / mypy / deptry 全绿,`mcp_smoke` 25 工具
- 联机时间线:普通 15:43–15:45、附件复验 15:53–15:55、差旅 15:59–16:00、清理 16:03–16:08、回归 16:14–16:16
### 15.4 剩余
1. P4 提交护栏(`submit_preview` / `submit_execute`)——填报链路已全通,只差提交
2. 经济科目固定取第 3 行(这次落到「其他 3010109」)、项目固定取第 1 行,无配置入口。
真实报销要按事由选科目,这是**业务正确性**问题,不是稳定性问题,建议下一个做
3. 补助清单靠「教师工号弹窗 + 查询 + 选第一行」,同名会选错,应改为按编号唯一匹配