@szc-ft/mcp-szcd-client 0.34.1 → 0.34.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -14,7 +14,7 @@ compatibility:
14
14
  当用户出现以下意图时启动:
15
15
 
16
16
  **用例自动化(用 testCase DSL):**
17
- - "执行 P0 用例" / "批量测试" / "测试用例自动化"
17
+ - "执行测试用例" / "批量测试" / "测试用例自动化"
18
18
  - "创建→审核→上线 全链路" / "生命周期测试"
19
19
  - "表单填写验证" / "深度表单测试"
20
20
 
@@ -58,7 +58,10 @@ echo "SKILL_DIR=$SKILL_DIR"
58
58
  export EXECUTOR="$SKILL_DIR/local-browser-executor.js"
59
59
  ```
60
60
 
61
- 后续所有命令直接 `node "$EXECUTOR" --action ...`。
61
+ > **⚠️ 每条命令必须带 NODE_PATH 前缀!**
62
+ > 各 `run_shell_command` 是独立的 shell 会话,`export` 不会跨会话持久化。
63
+ > 所有命令必须写成:`NODE_PATH=~/.szcd-mcp/deps/node_modules node "$EXECUTOR" --action ...`
64
+ > Windows 若 `~` 不展开,改用完整路径:`NODE_PATH="C:/Users/Lenovo/.szcd-mcp/deps/node_modules"`
62
65
 
63
66
  **执行器 + lib 内置在 skill 目录中,自带 Ant 适配层 + expect 断言 + testCase 引擎。禁止自己 require puppeteer 写脚本**——自写脚本会丢失:
64
67
  - 微前端 frame 探活 + 自动召回
@@ -67,10 +70,6 @@ export EXECUTOR="$SKILL_DIR/local-browser-executor.js"
67
70
  - testCase recovery 重试
68
71
  - API 按 frame scope 过滤
69
72
 
70
- ### 过渡兼容执行器
71
-
72
- `local-browser-executor.cjs`(同目录)是 CommonJS 过渡版,仅支持基础诊断(diagnose/screenshot/list-pages/api-check/plan/snapshot/interact/perf/watch)。**Ant 适配层 / testCase DSL / expect 断言仅 `local-browser-executor.js`(ES Module 版)支持。** 优先用 `.js` 版本。
73
-
74
73
  ## 🎯 三种典型任务(场景驱动)
75
74
 
76
75
  ### 任务 A:P0 用例批量执行(首选)
@@ -99,7 +98,7 @@ cat > /tmp/case.json <<'EOF'
99
98
  "recovery": { "maxRetriesPerStep": 2 }
100
99
  }
101
100
  EOF
102
- node "$EXECUTOR" --action test-case --test-case-file /tmp/case.json --test-report /tmp/report.json
101
+ NODE_PATH=~/.szcd-mcp/deps/node_modules node "$EXECUTOR" --action test-case --test-case-file /tmp/case.json --test-report /tmp/report.json
103
102
  ```
104
103
 
105
104
  dataset 数组每行跑一遍同样的 steps,`{{name}}` / `{{type}}` 自动替换。`setup.findFrame` 一次定位后所有步骤自动走子应用 frame(微前端默认行为)。
@@ -110,7 +109,7 @@ dataset 数组每行跑一遍同样的 steps,`{{name}}` / `{{type}}` 自动替
110
109
 
111
110
  ```bash
112
111
  # 单字段
113
- node "$EXECUTOR" --action act --frame-content-contains "数据集" \
112
+ NODE_PATH=~/.szcd-mcp/deps/node_modules node "$EXECUTOR" --action act --frame-content-contains "数据集" \
114
113
  --selector ant-select=资源类型 --click ... # ❌ 不要这样
115
114
 
116
115
  # ✅ 用 testCase 的 antSelect 步骤(在 testCase.steps 内):
@@ -131,10 +130,10 @@ node "$EXECUTOR" --action act --frame-content-contains "数据集" \
131
130
 
132
131
  ```bash
133
132
  # 一次性看 JS 错误 + API 失败 + iframe 状态
134
- node "$EXECUTOR" --action diagnose --url-contains "platform.aicityos.com" --wait 10000
133
+ NODE_PATH=~/.szcd-mcp/deps/node_modules node "$EXECUTOR" --action diagnose --url-contains "platform.aicityos.com" --wait 10000
135
134
 
136
135
  # 仅看 API
137
- node "$EXECUTOR" --action api-capture --url-contains "platform.aicityos.com" \
136
+ NODE_PATH=~/.szcd-mcp/deps/node_modules node "$EXECUTOR" --action api-capture --url-contains "platform.aicityos.com" \
138
137
  --url-pattern "/api/" --wait 10000 --include-response-body \
139
138
  --output /tmp/api.json
140
139
  ```
@@ -169,8 +168,6 @@ Agent / SKILL.md
169
168
  - API 按 frame scope 过滤
170
169
  - 浏览器凭据自动解密(使用内置 `decrypt-creds.mjs`)
171
170
 
172
- `local-browser-executor.cjs`(同目录)是 CommonJS 过渡版,仅支持基础诊断。**Ant 适配层 / testCase DSL / expect 断言仅 `local-browser-executor.js`(ES Module 版)支持。**
173
-
174
171
  ## 浏览器模式
175
172
 
176
173
  支持两种模式,执行器自动检测优先使用 connect:
@@ -270,6 +267,18 @@ AI 直接构造测试计划 JSON:
270
267
 
271
268
  > 微前端场景端口可变,优先用 `findPage` + `findFrame` 发现页面,而非硬编码 URL 导航。
272
269
 
270
+ **场景 F:登录/登出(探索后匹配内置脚本)**
271
+ `observe/evaluate(获取表单结构)→ 判断匹配 cdpScript login → 直接执行`
272
+
273
+ 探索获取到"用户名+密码+验证码+登录按钮"的元信息后,直接用 cdpScript login,
274
+ 不需要手动处理验证码、截图、问用户。内置脚本自带验证码重试。
275
+
276
+ **场景 G:执行失败后智能重构**
277
+ `执行 → failedSteps > 0 + autoRecovery.recovered = false → 读取 diagnosticHint → 推理原因 → 重构 JSON → 重新执行`
278
+
279
+ 执行器内置 L1/L2/L3 自动恢复(机械重试),当自动恢复仍失败时,LLM 读取错误信息,
280
+ 推理失败原因,重构 JSON 后重新执行(最多 3 次)。
281
+
273
282
  ### 构造步骤的指导原则
274
283
 
275
284
  **⚠️ 强制流程:构造 test-case JSON 前必须先执行 DOM 预检查**
@@ -285,20 +294,84 @@ AI 直接构造测试计划 JSON:
285
294
  - 表单字段 label(如“数据资源名称”vs“数据集名称”)
286
295
  - 表格列头(确认列数和列名)
287
296
 
288
- 3. **根据 DOM 检查结果构造 test-case JSON**
297
+ 3. **决策推理:根据元信息匹配实现层级**
298
+
299
+ 先判断是否匹配内置 cdpScript(最高优先级,无需手动处理交互细节):
300
+
301
+ | DOM 元信息信号 | 匹配的内置 cdpScript | 说明 |
302
+ |---------------|---------------------|------|
303
+ | URL 含 login + 表单含 password + 验证码 | `login` | 内置验证码重试,无需手动处理 |
304
+ | 已登录状态 + 用户要求退出 | `logout` | 自动保留 appId/viewId |
305
+ | 存在 `.ant-drawer` 且需选择/搜索/确认 | `drawer-action` | 指定 action + radioIndex |
306
+ | 存在 `.ant-modal` / Popconfirm 需确认 | `modal-action` | 指定 action + buttonText |
307
+ | 多标签页场景需切换 | `switch-tab` | 按 url/title/index |
308
+ | 需等待某 API 响应后继续 | `wait-api` | 指定 urlPattern |
309
+ | 需提取表格数据 | `get-table-data` | 指定 selector + maxRows |
310
+ | 抽屉式导航(.operate + .page-left-menu)需点菜单项导航 | `drawer-menu-nav` | 开抽屉→定位→原生点击→验证路由,解决 React 事件委托 |
311
+ | 多模块系统,需探测导航结构 | `detect-menu` | 报告 sidebar + 抽屉模块列表 + 选择器,为遍历或切模块提供元信息 |
312
+ | 需切换到指定模块(抽屉式模块切换器) | `switch-module` | 开抽屉→点模块/快捷项→等 sidebar 刷新→返回 newSidebar |
313
+ | 需全量遍历所有模块的菜单路由 | `traverse-all-menus` | 自动逐模块切换 + URL 去重,一次拿完整菜单树+截图 |
314
+ | 需对单页做只读深度探索(表格/按钮/Tab/接口) | `explore-page` | 自动扫描交互元素、遍历 Tab、点详情,识别但不点击写按钮 |
315
+
316
+ 不匹配内置 cdpScript 时,再判断是否能用 antXxx 步骤组合:
317
+ - 存在 `.ant-select` → antSelect
318
+ - 存在 `.ant-tree-select` → antTreeSelect
319
+ - 存在 `.ant-input` + placeholder → antInput
320
+ - 存在 `.ant-radio` → antRadio
321
+ - 多字段表单 → formFill + validateAfter
322
+
323
+ 都不匹配时,用 rawScript/registerCdpScript 自定义:
324
+ - 跨 frame 操作 → rawScript(完整 puppeteer API)
325
+ - 可复用的自定义交互 → registerCdpScript 注册后用 cdpScript 调用
326
+
327
+ 4. **根据决策结果构造 plan/test-case JSON**
328
+ - 匹配内置 cdpScript → 直接用,不需要手动处理验证码/选项渲染/浮层等细节
329
+ - 匹配 antXxx → 从 observe/evaluate 获取 selector 和文字,构造 testCase
330
+ - 自定义 → 从 DOM 元信息推导交互逻辑,写 rawScript
289
331
  - selector 从 observe 获取(不猜测)
290
332
  - 选项文字从 evaluate 获取(不猜测)
291
333
  - 按钮文字从 evaluate 获取(注意空格)
292
334
  - 无项目代码时,DOM 检查替代代码读取
293
335
 
294
- 4. **act 文字匹配**
295
- - `text=新建` → 模糊匹配(includes),可能误匹配“新建目录”
296
- - `text==新建` → 精确匹配(===),只匹配文字恰好为“新建”的元素
297
- - 按钮文字可能有空格(如“提 交”),evaluate 检查后再选择匹配方式
336
+ 5. **act 文字匹配**
337
+ - `text=新建` → 模糊匹配(includes),可能误匹配"新建目录"
338
+ - `text==新建` → 精确匹配(===),只匹配文字恰好为"新建"的元素
339
+ - 按钮文字可能有空格(如"提 交"),evaluate 检查后再选择匹配方式
340
+
341
+ 6. **API 路径从代码中的接口调用读取,或从 API 文档获取**
342
+ 7. **checkElement 的 expect 从代码逻辑推断**
343
+ 8. **compare 的 regions 从设计稿分析结果或组件布局推断**
344
+
345
+ 9. **执行反馈循环:LLM 层面的智能重构**
346
+
347
+ 执行器内置 L1/L2/L3 自动恢复(机械重试),当自动恢复仍失败时,LLM 介入:
348
+
349
+ **触发条件**:执行结果中 `failedSteps > 0` 且 `autoRecovery.recovered = false`
298
350
 
299
- 5. **API 路径从代码中的接口调用读取,或从 API 文档获取**
300
- 6. **checkElement 的 expect 从代码逻辑推断**
301
- 7. **compare regions 从设计稿分析结果或组件布局推断**
351
+ **LLM 读取执行结果,推理失败原因,重构 JSON 后重新执行**:
352
+
353
+ | 失败信号 | 推理方向 | 重构动作 |
354
+ |---------|---------|---------|
355
+ | option not found | 选项文字不匹配 | observe 重新获取选项列表,修正 option |
356
+ | not visible / timeout | 元素未渲染 | 增加 waitFor 或延长 timeout |
357
+ | 401 | 登录态过期 | steps 前插入 cdpScript login |
358
+ | 403 | 权限不足 | 换账号或跳过 |
359
+ | 步骤间时序问题 | 渲染未完成 | 增加 waitAfterEach |
360
+ | 内置脚本不匹配 | 场景未被覆盖 | 降级为 antXxx 或 rawScript |
361
+
362
+ **重构循环**:
363
+ - 最大重构次数:3 次
364
+ - 每次重构前可重新 observe(DOM 可能已变化)
365
+ - 每次重构后重新执行整个 plan/test-case
366
+ - 超过最大次数 → 报告用户失败原因 + diagnosticHint
367
+
368
+ **完整四环流程**:
369
+ ```
370
+ 环 1:DOM 探索 → 获取元信息
371
+ 环 2:决策推理 → 匹配实现层级(内置 cdpScript / antXxx / rawScript)
372
+ 环 3:构造 JSON
373
+ 环 4:执行 → 失败 → LLM 推理重构 → 重新执行(loop,最多 3 次)
374
+ ```
302
375
 
303
376
  ## 微前端测试指南(实战经验)
304
377
 
@@ -389,7 +462,7 @@ wujie 微前端子应用渲染在 blob URL iframe 中,**坐标点击无法到
389
462
 
390
463
  ### 结构化观察(主链路起点)
391
464
  ```bash
392
- node "$EXECUTOR" \
465
+ NODE_PATH=~/.szcd-mcp/deps/node_modules node "$EXECUTOR" \
393
466
  --action observe \
394
467
  --url-contains "platform.aicityos.com" \
395
468
  --frame-content-contains "数据集目录" \
@@ -401,7 +474,7 @@ node "$EXECUTOR" \
401
474
 
402
475
  ### 观察后按 index 交互(主链路闭环)
403
476
  ```bash
404
- node "$EXECUTOR" \
477
+ NODE_PATH=~/.szcd-mcp/deps/node_modules node "$EXECUTOR" \
405
478
  --action act \
406
479
  --url-contains "platform.aicityos.com" \
407
480
  --frame-content-contains "数据集目录" \
@@ -412,7 +485,7 @@ node "$EXECUTOR" \
412
485
 
413
486
  ### 左侧菜单路径稳定点击
414
487
  ```bash
415
- node "$EXECUTOR" \
488
+ NODE_PATH=~/.szcd-mcp/deps/node_modules node "$EXECUTOR" \
416
489
  --action act \
417
490
  --url-contains "platform.aicityos.com" \
418
491
  --menu-path "目录管理/数据集目录" \
@@ -422,9 +495,124 @@ node "$EXECUTOR" \
422
495
 
423
496
  `--menu-path` 会按路径逐级匹配菜单项,非末级优先执行展开,末级执行点击;匹配优先考虑 `role=menuitem`、`.ant-menu-item`、`.ant-menu-submenu-title` 和文本精确/包含匹配。
424
497
 
498
+ ### 菜单的两种设计(探索时必须先区分)
499
+
500
+ 导航菜单有两种设计,**探索阶段先调 `cdpScript detect-menu` 探测导航拓扑**,再据结果编排遍历:
501
+
502
+ ```json
503
+ { "type": "cdpScript", "script": "detect-menu", "frameContentContains": "<页面特征文字>" }
504
+ ```
505
+
506
+ 返回关键字段:
507
+ - `currentSidebar`:当前模块的左侧菜单(**权威完整菜单**)
508
+ - `hasDrawer` / `triggerSelector` / `drawerSelector`:是否有抽屉切换器及其选择器
509
+ - `drawerModules`:抽屉里可切换的模块入口(含 `moduleTitle` + `shortcutSample`)
510
+ - `navModel`:遍历策略说明
511
+
512
+ **⚠️ 核心模型:抽屉是「模块切换器」,不是完整菜单**
513
+ - 抽屉(如 `.page-left-menu`,触发器 `.operate`)只用来在大模块间切换,是用户自定义的快捷入口,**可能不全**
514
+ - 点抽屉里任一项 → 跳转该页 + **左侧 sidebar 刷新成那个模块的完整菜单**
515
+ - 每个模块的**权威完整菜单是切进去后的左侧 sidebar**,不能拿抽屉里的项当模块页面清单
516
+
517
+ | 场景 | 点击方式 |
518
+ |------|---------|
519
+ | 目标已在当前 `currentSidebar` | 直接 `act --menu-path "一级/二级"`(无需开抽屉) |
520
+ | 目标在其它模块 | `cdpScript switch-module` 切过去 → 读返回的 `newSidebar` → 再 `act --menu-path` 点二级 |
521
+ | 纯 sidebar(无抽屉) | 直接 `act --menu-path` 遍历 `currentSidebar` |
522
+
523
+ **完整遍历全部菜单的正确流程**:
524
+
525
+ **首选:一步拿到全量菜单树**(`traverse-all-menus` 已封装"逐个切模块+URL去重+读sidebar"):
526
+ ```json
527
+ { "type": "cdpScript", "script": "traverse-all-menus", "frameContentContains": "<页面特征>",
528
+ "params": { "triggerSelector": ".operate", "drawerSelector": ".page-left-menu" } }
529
+ // 返回 modules[],每项 { moduleTitle, url, urlKey, sidebar[] };撞 urlKey 的自动标 skipped
530
+ ```
531
+
532
+ **手动编排(理解底层 / traverse 不适用时)**:
533
+ ```
534
+ 1. detect-menu → 拿到 currentSidebar + drawerModules
535
+ 2. 先遍历当前模块:对 currentSidebar 每项 act --menu-path
536
+ 3. 对 drawerModules 里每个未探索的模块:
537
+ switch-module(用模块下任一快捷项文字切过去)
538
+ → 拿到 newSidebar(该模块完整菜单)
539
+ → 对 newSidebar 每项 act --menu-path 逐项探索
540
+ 4. 用「URL 前缀(urlKey)」做已探索缓存,命中则跳过,保证不重复、不遗漏
541
+ ```
542
+
543
+ > **去重锚点用 URL 而非模块名**:抽屉模块名可能重复/拆分,但落地 URL 唯一。实测中第8个模块"基础支撑中心"用首个快捷项切换时落到了已访问的 data-security,按 urlKey 去重才能正确跳过——遍历的去重锚点是"实际到达的页面",不是"以为点了哪个模块"。
544
+
545
+ > ⚠️ `traverse-all-menus` 只做**导航广度**覆盖(拿到所有页面入口 URL + sidebar),不展开页面内部的 tab/详情/接口/写操作。页面内深度探索由 `explore-page` 负责。
546
+
547
+ ### 完整探索:两阶段(导航广度 + 页面深度)
548
+
549
+ ```
550
+ 阶段1 traverse-all-menus → 全量页面 URL 清单(广度)
551
+ 阶段2 对每个 URL:导航过去 → explore-page → 页面内深度探索
552
+ ```
553
+
554
+ `explore-page` 产出(只读,绝不触发写操作):
555
+ - `loadApis`:页面加载时触发的接口 `[{method,url,status}]`
556
+ - `tabs[]`:每个 tab + 切换后捕获的接口
557
+ - `detailEntries[]`:查看/详情入口点进去捕获的接口 + URL + 详情内 tab(递归最多 3 层)
558
+ - `writeEntries[]`:新增/编辑/删除/提交等写按钮——**只识别记录文本,绝不点击**
559
+
560
+ **写操作是独立下一步**:explore-page 拿到 `writeEntries` 后,由 LLM **向用户确认**要触发哪些,再单独用 `act`/`antInput`/`formFill` + 原生点击执行(表单自动填默认值)。这样 explore-page 本身永远只读、安全、可重入。
561
+
562
+ ```json
563
+ { "type": "cdpScript", "script": "explore-page", "frameContentContains": "<页面特征>",
564
+ "params": { "maxDepth": 3, "apiPattern": "/api/" } }
565
+ ```
566
+
567
+ **⚠️ wujie 微前端 shadow DOM**:业务内容渲染在**主 frame 的 `wujie-app.shadowRoot`** 内(子应用 frame 反而可能是空的),普通 `document.querySelectorAll` 探不到。explore-page 已内置三重适配:
568
+ 1. **frame 自动选择**:遍历所有 frame(含 shadow 穿透)按可点元素数选最丰富的,避免 `_getTargetFrame` 选到空的子应用 frame(实测主 frame btns=3/pointers=67,子应用 frame 全 0)
569
+ 2. **shadow 穿透**:遍历 `wujie-app` 的 shadowRoot 作为额外查询根
570
+ 3. **坐标点击**:`frame.$` 选不到 shadow 元素,改用 evaluate 取 rect + `page.mouse.click`
571
+
572
+ 手写探索 shadow 页面时必须同样三重适配。
573
+
574
+ > **explore-page 验证状态**:框架 + frame 自动选择 + shadow 穿透 + 坐标点击 + rawClickables 兜底快照均已实现验证。**接口捕获实测有效**:① 查询接口 `dp-corpus/dataset/converge/page`;② 详情页 4 个接口(`queryDatasetConvergeDetail`/`getDataUsage`/`datasetFileList`);③ **tab 遍历**——智能模型仓库 7 个 tab 全部切换并各捕获到 `dp-label/model/*` 接口(7/7)。详情入口词表已扩充(查看/详情/明细/管理/检测/进入/预览)。
575
+
576
+ > **apiPattern 默认不过滤**:默认空 = 捕获所有 XHR/Fetch。**切勿默认按 `/api/` 过滤**——实测不同模块接口前缀不同(语料汇聚 `dp-corpus`、模型仓库 `dp-label`),默认过滤会漏抓。只在确实要收窄到某类接口时才传 `apiPattern`。
577
+
578
+ > **explore-page 已知约束**:
579
+ > - **不保证从干净列表态开始**:若进来时已有打开的弹窗/抽屉/已展开的内嵌面板,识别的是当前态而非列表。批量探索前建议先关闭残留弹窗(点 `.ant-drawer-close`/`.ant-modal-close`/取消按钮)。
580
+ > - **详情多为弹窗/抽屉式**(URL 不变、内容更新):用 `urlChanged:false + apis 非空` 判断详情已加载,不能只看 URL。
581
+ > - **行操作识别盲区**:纯 `<a>`/图标按钮/hover 才显示的操作可能漏识别 → 看 `rawClickables` 由 LLM 推理补充。
582
+
583
+ ### explore-page 不满足时:LLM 观测 DOM 推理补充
584
+
585
+ `explore-page` 是**固化的通用启发式**,只覆盖标准模式(`.ant-table` / `.ant-tabs-tab` / 常见中文按钮词 / 标准 drawer-modal)。它是「打底」,不是全部。遇到下列情况,**不要因为它返回空就判定页面没内容**,应由 LLM 用 `observe`/`evaluate` 看 DOM 现场推理补充:
586
+
587
+ | explore-page 覆盖不到的场景 | LLM 兜底做法 |
588
+ |---------------------------|-------------|
589
+ | 卡片式/自定义布局(无 ant-table,如"点击进入"卡片) | `evaluate` 穿透 shadow 列出可点元素(`cursor:pointer` / `onclick` / 自定义类名),按文本/位置推理入口 |
590
+ | 非标准 tab(非 `.ant-tabs-tab`,用 div+active 类自实现) | `evaluate` 找出"一组同级、点击切换 active 态"的元素,推断为 tab |
591
+ | 按钮文字不在内置写/读词表(如"派发""归档""调拨") | `evaluate` 列出所有按钮文字,LLM 判断语义归类(读/写/危险) |
592
+ | 详情不是 drawer/modal 而是路由跳转或内嵌面板 | `observe` 比对点击前后 DOM/URL,推断详情承载方式 |
593
+ | 接口 pattern 非 `/api/`(如 `/gateway/`、GraphQL) | explore-page 传 `apiPattern` 调整,或用 `api-capture` 单独抓 |
594
+
595
+ > **实测语料平台接口前缀是 `/dp-corpus/`**(非 `/api/`)。explore-page 默认 `apiPattern:'/api/'` 会漏抓——对该平台传 `apiPattern: "dp-corpus"`。不确定前缀时先用 `api-capture` 不加 pattern 抓一次看真实路径。
596
+
597
+ **协作模型**:
598
+ ```
599
+ 通用模式 → explore-page 一把梭(快、稳、可重入)
600
+ 非标准 → explore-page 返回空/不全 → LLM observe+evaluate 看现场 → 推理补充 → 必要时 rawScript 自定义交互
601
+ ```
602
+
603
+ 这与整个 skill 的「内置脚本打底 + LLM 推理兜底」一致:固化脚本省去重复踩坑,LLM 推理处理长尾。explore-page 的空结果是「这里没有标准模式」的信号,不是「这里没东西」的结论。
604
+
605
+ > `switch-module` 的 `moduleText` 用**抽屉里的叶子文本精确匹配**(如模块下快捷项"数据集目录")。
606
+ > 模块标题常被拆成"数据资产-主"+"语料资产中心"两个叶子,传不完整的"资产中心"会匹配失败——用快捷项文字更可靠。
607
+
608
+ **抽屉式菜单的三个坑(switch-module / drawer-menu-nav 已内置处理,手写时注意)**:
609
+ - **开关判断用可见性**:抽屉常驻 DOM,`querySelector` 永远命中,必须按 `getBoundingClientRect` 宽高 + display/visibility/opacity 判断是否真打开
610
+ - **菜单项是裸 span**:`.ant-menu-item` 选择器命不中,需按叶子文本精确匹配(避免命中含整块文字的父容器)
611
+ - **必须用原生鼠标点击**:React 事件委托菜单用 `el.click()` 不触发路由,必须 puppeteer `page.mouse.click(坐标)` 发真实 mousedown/mouseup/click
612
+
425
613
  ### 轻量 DOM 断言
426
614
  ```bash
427
- node "$EXECUTOR" \
615
+ NODE_PATH=~/.szcd-mcp/deps/node_modules node "$EXECUTOR" \
428
616
  --action assert \
429
617
  --url-contains "platform.aicityos.com" \
430
618
  --selector ".ant-table" \
@@ -435,7 +623,7 @@ node "$EXECUTOR" \
435
623
 
436
624
  ### API 请求/响应捕获(真实浏览器 + 微前端)
437
625
  ```bash
438
- node "$EXECUTOR" \
626
+ NODE_PATH=~/.szcd-mcp/deps/node_modules node "$EXECUTOR" \
439
627
  --action api-capture \
440
628
  --url-contains "platform.aicityos.com" \
441
629
  --frame-content-contains "数据集目录" \
@@ -450,7 +638,7 @@ node "$EXECUTOR" \
450
638
 
451
639
  ### API 捕获结果断言
452
640
  ```bash
453
- node "$EXECUTOR" \
641
+ NODE_PATH=~/.szcd-mcp/deps/node_modules node "$EXECUTOR" \
454
642
  --action assert-api \
455
643
  --capture-file /tmp/browser-api-capture.json \
456
644
  --max-failed 0 \
@@ -463,7 +651,7 @@ node "$EXECUTOR" \
463
651
 
464
652
  ### 浏览器端 JS 执行(构造 testCase 前提取表单元数据)
465
653
  ```bash
466
- node "$EXECUTOR" \
654
+ NODE_PATH=~/.szcd-mcp/deps/node_modules node "$EXECUTOR" \
467
655
  --action evaluate \
468
656
  --url-contains "platform.aicityos.com" \
469
657
  --frame-index 1 \
@@ -485,7 +673,7 @@ node "$EXECUTOR" \
485
673
 
486
674
  ### 执行测试计划 JSON(ES Module 版)
487
675
  ```bash
488
- node "$EXECUTOR" \
676
+ NODE_PATH=~/.szcd-mcp/deps/node_modules node "$EXECUTOR" \
489
677
  --action plan \
490
678
  --plan-file /tmp/test-plan.json \
491
679
  --output /tmp/browser-test-result.json
@@ -493,9 +681,9 @@ node "$EXECUTOR" \
493
681
 
494
682
  `--action plan` 使用 ES Module 执行器运行测试计划 JSON,支持全部步骤类型(包括 evaluate、antSelect 等)。与 `--action test-case` 的区别:plan 不走 dataset 循环,适合单次执行。
495
683
 
496
- ### 快速诊断(过渡兼容)
684
+ ### 快速诊断(JS错误 + 网络 + 资源 + iframe)
497
685
  ```bash
498
- NODE_PATH=~/.szcd-mcp/deps/node_modules node "$SKILL_DIR/local-browser-executor.cjs" \
686
+ NODE_PATH=~/.szcd-mcp/deps/node_modules node "$EXECUTOR" \
499
687
  --action diagnose \
500
688
  --url-contains "platform.aicityos.com" \
501
689
  --wait 15000 \
@@ -504,97 +692,28 @@ NODE_PATH=~/.szcd-mcp/deps/node_modules node "$SKILL_DIR/local-browser-executor.
504
692
 
505
693
  ### 列出所有标签页
506
694
  ```bash
507
- NODE_PATH=~/.szcd-mcp/deps/node_modules node "$SKILL_DIR/local-browser-executor.cjs" \
508
- --action list-pages
509
- ```
510
-
511
- ### 检查 API 请求状态
512
- ```bash
513
- NODE_PATH=~/.szcd-mcp/deps/node_modules node "$SKILL_DIR/local-browser-executor.cjs" \
514
- --action api-check \
515
- --url-contains "platform.aicityos.com" \
516
- --wait 15000
695
+ NODE_PATH=~/.szcd-mcp/deps/node_modules node "$EXECUTOR" --action list-pages
517
696
  ```
518
697
 
519
698
  ### 截图
520
699
  ```bash
521
- NODE_PATH=~/.szcd-mcp/deps/node_modules node "$SKILL_DIR/local-browser-executor.cjs" \
700
+ NODE_PATH=~/.szcd-mcp/deps/node_modules node "$EXECUTOR" \
522
701
  --action screenshot \
523
702
  --url-contains "platform.aicityos.com" \
524
703
  --filename current-page.png \
525
704
  --output-dir /tmp/browser-test
526
705
  ```
527
706
 
528
- ### 执行测试计划 JSON
529
- ```bash
530
- NODE_PATH=~/.szcd-mcp/deps/node_modules node "$SKILL_DIR/local-browser-executor.cjs" \
531
- --action plan \
532
- --plan-file /tmp/test-plan.json \
533
- --output /tmp/browser-test-result.json
534
- ```
535
-
536
- ### DOM 快照(结构化页面树,类似 browser-use take_snapshot)
537
- ```bash
538
- NODE_PATH=~/.szcd-mcp/deps/node_modules node "$SKILL_DIR/local-browser-executor.cjs" \
539
- --action snapshot \
540
- --url-contains "platform.aicityos.com" \
541
- --depth 4 \
542
- --selector ".page-container"
543
- ```
544
- 输出示例:
545
- ```
546
- <div.page-container>
547
- <section.page-layout>
548
- <header.page-header>
549
- <div.page-header-left> <div.logo>
550
- <section.h-box>
551
- <aside.ant-layout-sider> 🔗
552
- <main.ant-layout-content>
553
- <div.layout-wrap>
554
- ```
555
-
556
- ### 智能交互(支持 text=、role=、~ 选择器,类似 Playwright)
557
- ```bash
558
- # 点击包含“构建”文本的按钮
559
- NODE_PATH=~/.szcd-mcp/deps/node_modules node "$SKILL_DIR/local-browser-executor.cjs" \
560
- --action interact \
561
- --url-contains "jenkins" \
562
- --click "text=Build"
563
-
564
- # 在输入框中输入文本
565
- NODE_PATH=~/.szcd-mcp/deps/node_modules node "$SKILL_DIR/local-browser-executor.cjs" \
566
- --action interact \
567
- --click "#search-input" \
568
- --type "hello world"
569
-
570
- # 悬停 + 截图
571
- NODE_PATH=~/.szcd-mcp/deps/node_modules node "$SKILL_DIR/local-browser-executor.cjs" \
572
- --action interact \
573
- --hover "~ant-menu-item" \
574
- --screenshot-after
575
-
576
- # 滚动页面
577
- NODE_PATH=~/.szcd-mcp/deps/node_modules node "$SKILL_DIR/local-browser-executor.cjs" \
578
- --action interact \
579
- --scroll down --scroll-amount 500
580
- ```
581
- 选择器语法:
582
- - `text=Build` → 按文本内容模糊查找,多个匹配时选 innerText 最短的元素(叶子节点优先,避免命中父容器)
583
- - `text==Build` → 精确文本匹配(Playwright 风格),只匹配 innerText 完全相等的元素
584
- - `role=button` → 按 ARIA role 查找
585
- - `~ant-menu-item` → CSS class 模糊匹配(`[class*="ant-menu-item"]`)
586
- - `.my-class` → 精确 CSS 选择器
587
-
588
- ### 性能指标采集(FCP/TTFB/资源统计)
707
+ ### 性能指标采集(FCP/TTFB/资源统计/Chrome内部指标)
589
708
  ```bash
590
- NODE_PATH=~/.szcd-mcp/deps/node_modules node "$SKILL_DIR/local-browser-executor.cjs" \
709
+ NODE_PATH=~/.szcd-mcp/deps/node_modules node "$EXECUTOR" \
591
710
  --action perf \
592
711
  --url-contains "platform.aicityos.com"
593
712
  ```
594
713
 
595
714
  ### 持续监控模式(定时截图 + 错误检测)
596
715
  ```bash
597
- NODE_PATH=~/.szcd-mcp/deps/node_modules node "$SKILL_DIR/local-browser-executor.cjs" \
716
+ NODE_PATH=~/.szcd-mcp/deps/node_modules node "$EXECUTOR" \
598
717
  --action watch \
599
718
  --url-contains "platform.aicityos.com" \
600
719
  --interval 5000 \
@@ -602,6 +721,8 @@ NODE_PATH=~/.szcd-mcp/deps/node_modules node "$SKILL_DIR/local-browser-executor.
602
721
  --output-dir /tmp/browser-watch
603
722
  ```
604
723
 
724
+ > snapshot / interact / api-check 已由 `observe` + `act` + `api-capture` 完全覆盖,不再单独提供。
725
+
605
726
  ### 可选参数
606
727
 
607
728
  - `--cdp-url http://localhost:9222`:CDP 地址(默认 9222)
@@ -656,7 +777,32 @@ NODE_PATH=~/.szcd-mcp/deps/node_modules node "$SKILL_DIR/local-browser-executor.
656
777
  - 主链路(`observe/act/assert/diagnose/screenshot` + 凭据解密)安装 `puppeteer-core` + `sql.js` 到 `~/.szcd-mcp/deps/`
657
778
  - 只有执行设计稿像素对比 `compare` 时才安装 `pixelmatch/pngjs/sharp`
658
779
  - 后续所有项目共享复用,无需重复安装
659
- - 如果核心依赖自动安装失败,引导用户手动执行:
780
+
781
+ ### 依赖预检(首次执行前必做)
782
+
783
+ ```bash
784
+ # 检查依赖是否已安装(不报错则已就绪)
785
+ node -e "require.resolve('puppeteer-core', { paths: [process.env.HOME + '/.szcd-mcp/deps/node_modules'] }); console.log('puppeteer-core ✓')" 2>/dev/null || echo "puppeteer-core ✗ 需要安装"
786
+ node -e "require.resolve('sql.js', { paths: [process.env.HOME + '/.szcd-mcp/deps/node_modules'] }); console.log('sql.js ✓')" 2>/dev/null || echo "sql.js ✗ 需要安装"
787
+ ```
788
+
789
+ > **Windows (Git Bash)** 将 `$HOME` 替换为完整路径如 `C:/Users/Lenovo`。
790
+
791
+ ### 排障:"Cannot find module" 错误
792
+
793
+ | 报错信息 | 原因 | 解决 |
794
+ |---------|------|------|
795
+ | `Cannot find module 'puppeteer-core'` | NODE_PATH 未设置或路径错误 | 命令前加 `NODE_PATH=~/.szcd-mcp/deps/node_modules` |
796
+ | `Cannot find module 'sql.js'` | 同上(sql.js 与 puppeteer-core 在同一目录) | 同上 |
797
+ | `ENOENT: no such file or directory` (Windows) | `~` 未正确展开 | 改用完整路径 `C:/Users/xxx/.szcd-mcp/deps/node_modules` |
798
+ | 依赖确实未安装 | 首次使用或被清理 | 执行下方安装命令 |
799
+
800
+ > **⚠️ 遇到 "Cannot find module" 时,先执行依赖预检确认是否真的缺失,不要直接 npm install。**
801
+ > 大多数情况是 NODE_PATH 未设置,依赖实际已安装在 `~/.szcd-mcp/deps/node_modules/`。
802
+
803
+ ### 安装命令
804
+
805
+ 如果核心依赖自动安装失败,引导用户手动执行:
660
806
  ```bash
661
807
  cd ~/.szcd-mcp/deps && npm install --registry=https://registry.npmmirror.com puppeteer-core sql.js
662
808
  ```
@@ -702,7 +848,7 @@ cd ~/.szcd-mcp/deps && npm install --registry=https://registry.npmmirror.com --@
702
848
 
703
849
  1. **第一步:先抓菜单接口**(一次即可,作为整张菜单树的来源)
704
850
  ```bash
705
- node local-browser-executor.js --action api-capture \
851
+ NODE_PATH=~/.szcd-mcp/deps/node_modules node "$EXECUTOR" --action api-capture \
706
852
  --url-pattern "listUserViewMenus" \
707
853
  --include-response-body \
708
854
  --include-all-targets \
@@ -712,7 +858,7 @@ cd ~/.szcd-mcp/deps && npm install --registry=https://registry.npmmirror.com --@
712
858
 
713
859
  2. **第二步:把菜单树喂给 runTask**
714
860
  ```bash
715
- node local-browser-executor.js --action run-task \
861
+ NODE_PATH=~/.szcd-mcp/deps/node_modules node "$EXECUTOR" --action run-task \
716
862
  --task menu-api-scan \
717
863
  --capture-input-file /tmp/menu-tree.json \
718
864
  --menu-path-prefix /catalog \
@@ -820,7 +966,7 @@ cat > /tmp/login.json <<EOF
820
966
  EOF
821
967
 
822
968
  # 3. 执行登录
823
- node "$EXECUTOR" --action plan --plan-file /tmp/login.json --url-contains "platform.aicityos.com" --wait 8000
969
+ NODE_PATH=~/.szcd-mcp/deps/node_modules node "$EXECUTOR" --action plan --plan-file /tmp/login.json --url-contains "platform.aicityos.com" --wait 8000
824
970
  ```
825
971
 
826
972
  **LLM 处理流程:**
@@ -875,6 +1021,8 @@ node "$EXECUTOR" --action plan --plan-file /tmp/login.json --url-contains "platf
875
1021
  | `setRequestInterception(true)` 抓接口 | 仅 CDP `Network.responseReceived` + `getResponseBody` | request interception 会拦下 wujie iframe 的 RPC 帧造成挂起 |
876
1022
  | `page.click("text=保存")` 不加可见性过滤 | 默认走 `act` 的 `text=`,已自动只匹配 `visible+enabled` 的元素 | 隐藏在收起菜单/未渲染抽屉里的同名文本会被错误命中 |
877
1023
  | 多级菜单只用一次 `dispatchEvent("mouseover")` | `useNativeHover: true`(默认) + `drawerTriggerSelector` 每级前重开 | Vue 的 v-on:mouseenter 对 dispatchEvent 不触发;抽屉式菜单点完一次会自动收起 |
1024
+ | 抽屉菜单项用 `el.click()` 点击 | 抽屉式导航用 `cdpScript drawer-menu-nav`,内部走 `page.mouse.click(坐标)` | React 事件委托型菜单(无类名裸 span)`el.click()` 不触发合成事件→路由不切换,必须原生鼠标事件 |
1025
+ | 按 `querySelector` 是否命中判断抽屉开关 | 按可见性判断(rect 宽高 + display/visibility/opacity) | `.page-left-menu` 类抽屉常驻 DOM、靠 CSS 隐藏,存在性判断会误判 already open |
878
1026
  | `setTimeout(awaitPage.bringToFront(), 30000)` 之外不做兜底 | 调用前自查"目标页是否还活着"(`findPage` / `_findFrame`),失败立刻退而求其次操作主帧 | bringToFront 在 OS 抢焦点失败时会无限挂起,没超时 |
879
1027
  | iframe attach 失败就报错退出 | `attachRetries` 默认 3 次、间隔 1.5s 重抓 `browser.targets()` | wujie 的 blob iframe target 在主页面 ready 后才注册,时序敏感 |
880
1028
  | 大批量探索每条都 throw | `_actMenuPath` 失败时返回 `acted:false + candidates`,配合 `runTask --continue-on-fail` | 探索性扫描遇到一条点不开就全部中断,没法发现整体面 |
@@ -991,7 +1139,7 @@ testCase 执行
991
1139
  把多个用例统一为参数化 testCase JSON:
992
1140
 
993
1141
  ```bash
994
- node local-browser-executor.js --action test-case --test-case-file /tmp/case.json [--dataset-index 0] [--test-report /tmp/report.json]
1142
+ NODE_PATH=~/.szcd-mcp/deps/node_modules node "$EXECUTOR" --action test-case --test-case-file /tmp/case.json [--dataset-index 0] [--test-report /tmp/report.json]
995
1143
  ```
996
1144
 
997
1145
  ```json
@@ -1017,8 +1165,8 @@ node local-browser-executor.js --action test-case --test-case-file /tmp/case.jso
1017
1165
  |------|---------|---------|------|---------|
1018
1166
  | 1 | evaluate | 浏览器端 | DOM 查询/操作 | 简单检查、点击元素 |
1019
1167
  | 2 | rawScript | Node.js 端 | 完整 puppeteer API | 跨 frame、等待、多标签页 |
1020
- | 3 | cdpScript | Node.js 端 | 调用内置或用户注册脚本 | 登录/登出/切换账号/抽屉/弹窗等通用交互 |
1021
- | 4 | 内置 cdpScript | Node.js 端 | 预定义通用 UI 交互 + 凭据管理 | login/logout/getCredentials/drawer-action/modal-action/switch-tab/wait-api/get-table-data |
1168
+ | 3 | cdpScript | Node.js 端 | 调用内置或用户注册脚本 | 登录/登出/抽屉导航/模块切换/页面探索/弹窗/标签切换/API 等待等通用交互 |
1169
+ | 4 | 内置 cdpScript | Node.js 端 | 预定义通用 UI 交互 + 凭据管理 | login/logout/drawer-action/drawer-menu-nav/detect-menu/switch-module/traverse-all-menus/explore-page/modal-action/switch-tab/wait-api/get-table-data |
1022
1170
 
1023
1171
  **evaluate**:浏览器端执行 JS,只能操作 DOM,不能等待/跨 frame/调用 puppeteer
1024
1172
  **rawScript**:Node.js 端执行 async 函数,接收 `(page, frame, params, browser)`,可调用所有 puppeteer API
@@ -1032,6 +1180,11 @@ node local-browser-executor.js --action test-case --test-case-file /tmp/case.jso
1032
1180
  | `login` | **自动登录**(填表+提交) | `username`, `password`, `captcha?`(默认6), `loginUrl?` |
1033
1181
  | `logout` | **登出**(清 cookie+跳转登录页) | `appUrl?`(默认/szc-ai-data/) |
1034
1182
  | `drawer-action` | Drawer 操作 | `action: 'search'\|'select'\|'confirm'\|'cancel'`, `radioIndex?`, `searchText?`, `searchPlaceholder?` |
1183
+ | `detect-menu` | **导航拓扑探测**(当前 sidebar + 抽屉模块 + 选择器,编排遍历前先调) | `triggerHints?`, `drawerHints?` |
1184
+ | `switch-module` | **切模块**(开抽屉→点模块/快捷项→等 sidebar 刷新→返回 newSidebar) | `moduleText`(必填,用抽屉叶子文本), `triggerSelector?`, `drawerSelector?`, `waitAfter?` |
1185
+ | `traverse-all-menus` | **全量菜单路由遍历**(自动逐个切模块+按 URL 去重,一次拿到完整菜单树) | `triggerSelector?`(默认.operate), `drawerSelector?`(默认.page-left-menu), `maxModules?`(默认30), `waitAfter?`(默认1600) |
1186
+ | `explore-page` | **单页深度探索**(只读:捕获加载接口+遍历tab+点详情,识别但不点写按钮) | `maxDepth?`(默认3,最大3), `apiPattern?`(默认空=捕获全部XHR/Fetch,传了才收窄过滤), `settleMs?`(默认1500) |
1187
+ | `drawer-menu-nav` | **抽屉式直达导航**(开抽屉→定位→原生点击→验证路由) | `menuText`(必填), `triggerSelector?`(默认.operate), `openSelector?`(默认.page-left-menu), `waitAfter?`(默认1500) |
1035
1188
  | `modal-action` | Modal/Popconfirm 操作 | `action: 'confirm'\|'cancel'`, `buttonText?` |
1036
1189
  | `switch-tab` | 多标签页切换 | `urlContains?`, `titleIncludes?`, `pageIndex?` |
1037
1190
  | `wait-api` | 等待 API 响应 | `urlPattern`, `timeout?`, `method?` |
@@ -1046,6 +1199,8 @@ node local-browser-executor.js --action test-case --test-case-file /tmp/case.jso
1046
1199
  { "type": "cdpScript", "script": "drawer-action", "params": { "action": "confirm" } }
1047
1200
  { "type": "cdpScript", "script": "modal-action", "params": { "action": "confirm" } }
1048
1201
  { "type": "cdpScript", "script": "switch-tab", "params": { "urlContains": "localhost:8088" } }
1202
+ // 抽屉式导航菜单:开抽屉 + 点菜单项 + 验证路由切换,一步完成(frameContentContains 锁定含菜单的 frame)
1203
+ { "type": "cdpScript", "script": "drawer-menu-nav", "frameContentContains": "语料资产中心", "params": { "menuText": "数据探索", "triggerSelector": ".operate", "openSelector": ".page-left-menu" } }
1049
1204
 
1050
1205
  // rawScript 示例(LLM 动态生成业务逻辑)
1051
1206
  { "type": "rawScript", "code": "async (page, frame, params, browser) => { var count = await frame.evaluate(() => document.querySelectorAll('tr').length); return { acted: true, passed: count > 0, rowCount: count, status: 'PASS' } }" }