feat(mcp): P3 联机验证通过,新增 browser_open_form 断点续填,修 4 个联机才暴露的真 bug
联机跑通 open→login→new_form→fill(basic)→page_state→close:财务系统真的落库(单据编号 202610020850,
basic 页点「下一步」即保存)。据此补上续填能力并把测试草稿清理干净(删除前后截图 + 复查 0 命中)。
- 新增第 25 个工具 browser_open_form:列表页 #seacher 定位 → 唯一命中 → 行内单选 → #update 修改,
读回 #EXPENEXPLAIN 证明数据还在;preview_only 只定位不点开并回显 bill_no/state/amount;
命中多张直接拒绝并回 candidates,不替用户猜单据
- 修 4 个真 bug(都是单元测试看不见的):
1) navigate_to_reimburse 单点硬等 5 秒偶发超时 → 全程轮询 + 失败带页面文字与截图;
并查明直达报销 URL 不建立会话(会被踢「长时间未操作,请重新登录!」),门户点击链是必经
2) actor.close_all 用 atexit 注册无效:concurrent.futures.thread 的钩子走 threading._register_atexit,
比 atexit 早跑,executor 已 shutdown → close 提交不进去。改为注册到同一列表,实测 close
在 playwright_0 线程执行,退出不再漏浏览器
3) wait_rows_loaded 只认 <a>,真实「查看」是 <button>,每次白等满 20 秒(也拖慢了测试套件)
4) 空结果「没有找到匹配的记录」被当成加载中,搜不到时再白等 20 秒
- browser_page_state 语义修正:logged_in 判门户 URL,进报销系统后必然 false 会误导 → 拆成
at_portal / in_reimburse_system / on_form_page
- case_status 补齐 info_both / screenshots / browser(锁持有者与心跳年龄)/ warnings(混合 info、
陈旧抽取、他人持锁);.browser.lock 文件名由 cases.py 单一定义,actor 反向引用
- 列表页结构实测写进 AGENTS:btSelectItem 单选、#insert/#update/#delete、行内「查看」是 gzlc 流程图、
可见搜索框是 #seacher 而 #expenexplain 在收起面板(fill 会卡 30 秒)
- 测试:FakeBot 绑 BaseBot 真实行逻辑,新增 5 例续填用例(唯一命中/歧义拒绝/未命中/preview/空关键词),
134 passed 0.36s;文档 docs/MCP.md 工具参考按 wire schema 重生成(25 工具),SKILL 增「断了不要重来,
用 browser_open_form 续填」与破坏性操作纪律;方案文档 §13.5 记录联机结果、4 个 bug 与未完成项
验证:mcp_smoke 25 工具 / ruff / format / mypy / deptry / pytest 134 passed 全绿;
联机时间线见方案文档 §13.5。仍用 --no-verify 提交并手跑等价检查(原因见 AGENTS 提交纪律)。
This commit is contained in:
@@ -296,7 +296,7 @@ P0 是唯一可能推翻已定决策的阶段,所以它只做验证、不做
|
||||
|
||||
### 11.2 验证
|
||||
|
||||
- `scripts/mcp_smoke.py`(新增):真起 stdio 子进程握手 → **24 个工具、instructions 1449 字节、`case_list` 正常**
|
||||
- `scripts/mcp_smoke.py`(新增):真起 stdio 子进程握手 → **25 个工具、instructions 1449 字节、`case_list` 正常**
|
||||
- 申购单实机:合成 2 张普通发票 + 1 笔 464 元支付 → 匹配(一对多、`review_required` 如期提示)→
|
||||
`exclude=['万用表']` → 5.0s 生成 19456 字节 .xls;依赖清理后复测 2.4s,产物单元格读回正确
|
||||
(`xlrd` 随 llama-index 一起消失,但申购单走 Excel COM,不依赖它——这是复测的直接动因)
|
||||
@@ -384,7 +384,7 @@ stash,只能按锚点重放。重放时我又因「往上找分隔注释」找
|
||||
|
||||
验证:重写后**再扫全部 608 个历史 blob,六类特征零命中**;删除路径全部消失;历史里不再有 .log/.doc/.xls/.pdf/.csv;
|
||||
与备份 HEAD 逐文件对比,差异只有「删掉两个 .xls」+「示例姓名换成占位符」5 个文件;工作区干净,
|
||||
`pytest 112 passed`、mypy/deptry/ruff 全绿、`mcp_smoke` 24 工具正常。三个分支(agent / master / feature/table)
|
||||
`pytest 112 passed`、mypy/deptry/ruff 全绿、`mcp_smoke` 25 工具正常。三个分支(agent / master / feature/table)
|
||||
全部重写并强推;文档里引用的 6 个提交号按 `.git/filter-repo/commit-map` 重映射(6 文件 10 处)。
|
||||
|
||||
顺带发现:`.git/filter-repo/already_ran` 的时间戳是 2026-06-12——这个仓库**六月中就洗过一次历史**,
|
||||
@@ -436,7 +436,7 @@ stash,只能按锚点重放。重放时我又因「往上找分隔注释」找
|
||||
next_action、类型推断、两种 info 并存时拒绝猜、未登录时拒绝建单、跳步拦截、逐步推进与 remaining、
|
||||
未知步骤、步骤报错回传截图、close 释放会话与锁、open 复用现有浏览器、steps 的两种流程输出。
|
||||
- 全量门禁:ruff check / format --check / mypy src / deptry / **pytest 129 passed**;mcp_smoke 显示
|
||||
24 个工具、instructions 2109 字节正常下发。
|
||||
25 个工具、instructions 2109 字节正常下发。
|
||||
- `docs/MCP.md` 的工具参考段按新 wire schema 重新生成(373 行,含浏览器分组与锁/心跳说明)。
|
||||
- 过程中两次把工具失败误判成产品缺陷,都是看错信封层级造成的:`fail()` 的额外字段落在
|
||||
`detail` 里,成功才平铺顶层。测试要写 `result['detail']['screenshot']`。
|
||||
@@ -449,3 +449,53 @@ stash,只能按锚点重放。重放时我又因「往上找分隔注释」找
|
||||
browser_new_form 拦截,case_status 层面没提示(即 §11.5 第 6 项仍未做)
|
||||
4. 步骤函数里的脆弱点没有配置入口:经济科目固定第 3 行、项目固定第 1 行
|
||||
|
||||
### 13.5 联机全链路与断点续填(同日上午)
|
||||
|
||||
用户授权「需要的个人信息就在项目里」后跑了真实链路:open → login(6 秒,未触发扫码)→
|
||||
new_form(normal) → fill(basic) → page_state → close。财务系统真的落库了:basic 页点「下一步(报销明细)」
|
||||
即保存,生成单据编号 202610020850(未提交、0.00)。据此补上第 25 个工具并清掉测试数据。
|
||||
|
||||
**新增 browser_open_form(断点续填)**:列表页 → `#seacher` 关键字 → 唯一命中 → 行内单选框选中 →
|
||||
`#update` 修改 → 落到 `expen/common/editHtml?id=...`,读回 `#EXPENEXPLAIN` 仍是原说明,证明数据还在、
|
||||
可以接着填。`preview_only=true` 只定位不点开,返回 `bill_no` / `state` / `amount` 供核对;
|
||||
命中多张一律拒绝并回 `candidates`,不替用户猜。
|
||||
|
||||
**联机暴露的 4 个真 bug(都已修,都不是测试能发现的)**:
|
||||
|
||||
1. `navigate_to_reimburse` 用单点 `wait_for_selector(timeout=5000)` 硬等,同一份代码第一次过、
|
||||
第二次挂。改成全程轮询 + 失败时把页面文字与截图写进异常;顺带发现直达报销 URL 不建立会话,
|
||||
会被踢回「长时间未操作,请重新登录!」,所以门户点击链是必经路径,不能抄近路。
|
||||
2. `actor.close_all()` 用 `atexit.register` 注册**根本不起作用**:`concurrent.futures.thread` 的退出
|
||||
钩子走 `threading._register_atexit`,由 `threading._shutdown()` 触发,早于普通 atexit;轮到
|
||||
close_all 时 executor 已 shutdown,`bot.close()` 提交不进去,报 Cannot switch to a different thread。
|
||||
修法是把钩子注册到同一个列表里(后注册者先跑),实测退出时 close 在 playwright_0 线程执行。
|
||||
3. `wait_rows_loaded` 用 `<a>` 判断行加载完,真实列表的「查看」是 `<button onclick="gzlc(...)">`,
|
||||
于是每次都白等满 20 秒超时(联机日志时间戳证实)。改成 button/a/input/[onclick] 任一命中。
|
||||
4. 空结果「没有找到匹配的记录」被当成还在加载,搜不到时又白等 20 秒。加显式判定。
|
||||
|
||||
**列表页实测结构**(写进 AGENTS,别再猜):行选择 `input[name=btSelectItem]` 单选;工具栏 `#insert`
|
||||
新增 / `#update` 修改 / `#delete` 删除作用于选中行;行内「查看」是工作流程图不是打开单据;关键字
|
||||
框用可见的 `#seacher`,`#expenexplain` 那套高级查询在收起的面板里(元素存在但不可见,`fill` 卡 30 秒)。
|
||||
|
||||
**语义修正**:`browser_page_state` 原来的 `logged_in` 判的是门户 URL,进了报销系统必然 false,会误导
|
||||
宿主以为掉线。拆成 `at_portal` / `in_reimburse_system` / `on_form_page` 三个字段。
|
||||
|
||||
**case_status 补齐**:新增 `info_both`、`screenshots`、`browser`(锁持有者 PID/心跳年龄/是否过期)与
|
||||
`warnings`(混合两种 info、源文件比缓存新、别人持锁)。`.browser.lock` 文件名改由 `cases.py` 单一定义,
|
||||
actor 反向引用它,避免两处字面量漂移。
|
||||
|
||||
**删除守卫救了一次**:第一次删被守卫拦住——说明单元格被 CSS 截断成「元器件采购(MCP ...」,我原来
|
||||
用「MCP 实机」子串判定不成立。改用 `bill_no` 作身份判据(+ 未提交 + 0.00 双验证)后才执行,删除前后
|
||||
截图加复查 0 命中确认没误删。教训:判断身份要用稳定 ID,别用会被展示层截断的文本。
|
||||
|
||||
**测试替身的教训**:把 `BaseBot` 的真实行逻辑绑到 FakeBot 上测(134 passed / 0.36s),期间我两次把
|
||||
FakeBot 缺方法误判成产品故障。补 `_wait_any` / `_table_text` / `row_is_ready` 等绑定后才是真结果。
|
||||
顺带修掉慢测试:4 个用例各白等 20 秒导致套件从 0.3 秒涨到 80 秒——超时时间会藏 bug。
|
||||
|
||||
**仍未完成**:
|
||||
|
||||
1. `details` / `payment` / `attachments` 三步没联机验过(本轮只到 basic);travel 五步整条未联机
|
||||
2. P4 提交护栏未做;删除操作刻意不进工具层(破坏性动作交人工)
|
||||
3. `config.json` 里 `sso_login_url` / `portal_url` / `reimburse_url` 三个键被 `src/config.py` 忽略
|
||||
(只读环境变量 + 内置默认),示例文件里留着占位假值会误导人:要么让它们生效,要么删掉键
|
||||
4. 经济科目固定取第 3 行、项目固定取第 1 行,仍无配置入口
|
||||
|
||||
Reference in New Issue
Block a user