@szc-ft/mcp-szcd-client 0.28.1 → 0.29.0

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.
Files changed (36) hide show
  1. package/agents/szcd-component-expert.qoder.md +1 -0
  2. package/opencode-extension/skills/local-browser-test/SKILL.md +189 -53
  3. package/opencode-extension/skills/local-browser-test/lib/ant-adapter.js +735 -0
  4. package/opencode-extension/skills/local-browser-test/lib/browser-engine.js +2427 -0
  5. package/opencode-extension/skills/local-browser-test/lib/chrome-finder.js +125 -0
  6. package/opencode-extension/skills/local-browser-test/lib/expect.js +392 -0
  7. package/opencode-extension/skills/local-browser-test/lib/shared-deps.js +137 -0
  8. package/opencode-extension/skills/local-browser-test/lib/test-plan.js +48 -0
  9. package/opencode-extension/skills/local-browser-test/lib/visual-compare.js +192 -0
  10. package/opencode-extension/skills/local-browser-test/local-browser-executor.js +735 -0
  11. package/opencode-extension/skills/local-browser-test/package.json +1 -0
  12. package/opencode-extension/skills/szcd-upload-zip/SKILL.md +122 -0
  13. package/package.json +1 -1
  14. package/qwen-extension/qwen-extension.json +1 -1
  15. package/qwen-extension/skills/local-browser-test/SKILL.md +189 -53
  16. package/qwen-extension/skills/local-browser-test/lib/ant-adapter.js +735 -0
  17. package/qwen-extension/skills/local-browser-test/lib/browser-engine.js +2427 -0
  18. package/qwen-extension/skills/local-browser-test/lib/chrome-finder.js +125 -0
  19. package/qwen-extension/skills/local-browser-test/lib/expect.js +392 -0
  20. package/qwen-extension/skills/local-browser-test/lib/shared-deps.js +137 -0
  21. package/qwen-extension/skills/local-browser-test/lib/test-plan.js +48 -0
  22. package/qwen-extension/skills/local-browser-test/lib/visual-compare.js +192 -0
  23. package/qwen-extension/skills/local-browser-test/local-browser-executor.js +735 -0
  24. package/qwen-extension/skills/local-browser-test/package.json +1 -0
  25. package/qwen-extension/skills/szcd-upload-zip/SKILL.md +122 -0
  26. package/standard-skill/local-browser-test/SKILL.md +189 -53
  27. package/standard-skill/local-browser-test/lib/ant-adapter.js +735 -0
  28. package/standard-skill/local-browser-test/lib/browser-engine.js +2427 -0
  29. package/standard-skill/local-browser-test/lib/chrome-finder.js +125 -0
  30. package/standard-skill/local-browser-test/lib/expect.js +392 -0
  31. package/standard-skill/local-browser-test/lib/shared-deps.js +137 -0
  32. package/standard-skill/local-browser-test/lib/test-plan.js +48 -0
  33. package/standard-skill/local-browser-test/lib/visual-compare.js +192 -0
  34. package/standard-skill/local-browser-test/local-browser-executor.js +735 -0
  35. package/standard-skill/local-browser-test/package.json +1 -0
  36. package/standard-skill/szcd-upload-zip/SKILL.md +122 -0
@@ -17,6 +17,7 @@ skills:
17
17
  - local-browser-test
18
18
  - szcd-component-helper
19
19
  - szcd-design-to-code
20
+ - szcd-upload-zip
20
21
  mcpServers:
21
22
  - szcd-component-helper
22
23
  - sketch-mcp-server
@@ -27,16 +27,46 @@ compatibility:
27
27
 
28
28
  ## ⚡ 路径解析(必读,避免 ad-hoc 脚本)
29
29
 
30
- **所有命令使用 `$CLIENT_PATH` 变量,会话开始时先解析一次:**
30
+ **执行器内置在 skill 目录中(自包含),会话开始时先解析 skill 路径:**
31
31
 
32
32
  ```bash
33
- # 解析客户端安装路径(成功后所有命令复用此变量)
34
- export CLIENT_PATH=$(node -p "require.resolve('@szc-ft/mcp-szcd-client/local-browser-executor.js')" 2>/dev/null \
35
- || node -p "require('path').dirname(require.resolve('@szc-ft/mcp-szcd-client/package.json')) + '/local-browser-executor.js'")
36
- echo "CLIENT_PATH=$CLIENT_PATH"
37
- ```
33
+ # 解析 skill 目录路径(执行器 + lib 都在 skill 目录内)
34
+ export SKILL_DIR=$(node -p "
35
+ const path = require('path');
36
+ // 尝试从 npm 包解析
37
+ try { return path.dirname(require.resolve('@szc-ft/mcp-szcd-client/standard-skill/local-browser-test/SKILL.md')); } catch {}
38
+ // 尝试从 IDE skill 目录查找
39
+ const os = require('os');
40
+ const candidates = [
41
+ path.join(os.homedir(), '.qwen/extensions/szcd-component-helper/skills/local-browser-test'),
42
+ path.join(os.homedir(), '.claude/skills/local-browser-test'),
43
+ path.join(os.homedir(), '.config/opencode/skills/local-browser-test'),
44
+ path.join(os.homedir(), '.trae-cn/skills/local-browser-test'),
45
+ path.join(os.homedir(), '.qoder/skills/local-browser-test'),
46
+ ];
47
+ for (const c of candidates) {
48
+ if (require('fs').existsSync(path.join(c, 'local-browser-executor.js'))) return c;
49
+ }
50
+ throw new Error('local-browser-test skill not found');
51
+ ")
52
+ echo "SKILL_DIR=$SKILL_DIR"
53
+
54
+ # 执行器路径
55
+ export EXECUTOR="$SKILL_DIR/local-browser-executor.js"
56
+ ```
57
+
58
+ 后续所有命令直接 `node "$EXECUTOR" --action ...`。
59
+
60
+ **执行器 + lib 内置在 skill 目录中,自带 Ant 适配层 + expect 断言 + testCase 引擎。禁止自己 require puppeteer 写脚本**——自写脚本会丢失:
61
+ - 微前端 frame 探活 + 自动召回
62
+ - `closeAllDropdowns` 跨 frame 清理
63
+ - Ant Select mousedown + VERIFY 回写
64
+ - testCase recovery 重试
65
+ - API 按 frame scope 过滤
38
66
 
39
- 后续所有命令直接 `node "$CLIENT_PATH" --action ...`,**不要自己 require puppeteer 写脚本**——执行器已封装好 antSelect/formFill/expect/testCase 等步骤,自己写脚本会丢失微前端 frame 探活、closeAllDropdowns、verify 回写等保护。
67
+ ### 过渡兼容执行器
68
+
69
+ `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` 版本。
40
70
 
41
71
  ## 🎯 三种典型任务(场景驱动)
42
72
 
@@ -66,7 +96,7 @@ cat > /tmp/case.json <<'EOF'
66
96
  "recovery": { "maxRetriesPerStep": 2 }
67
97
  }
68
98
  EOF
69
- node "$CLIENT_PATH" --action test-case --test-case-file /tmp/case.json --test-report /tmp/report.json
99
+ node "$EXECUTOR" --action test-case --test-case-file /tmp/case.json --test-report /tmp/report.json
70
100
  ```
71
101
 
72
102
  dataset 数组每行跑一遍同样的 steps,`{{name}}` / `{{type}}` 自动替换。`setup.findFrame` 一次定位后所有步骤自动走子应用 frame(微前端默认行为)。
@@ -77,7 +107,7 @@ dataset 数组每行跑一遍同样的 steps,`{{name}}` / `{{type}}` 自动替
77
107
 
78
108
  ```bash
79
109
  # 单字段
80
- node "$CLIENT_PATH" --action act --frame-content-contains "数据集" \
110
+ node "$EXECUTOR" --action act --frame-content-contains "数据集" \
81
111
  --selector ant-select=资源类型 --click ... # ❌ 不要这样
82
112
 
83
113
  # ✅ 用 testCase 的 antSelect 步骤(在 testCase.steps 内):
@@ -98,10 +128,10 @@ node "$CLIENT_PATH" --action act --frame-content-contains "数据集" \
98
128
 
99
129
  ```bash
100
130
  # 一次性看 JS 错误 + API 失败 + iframe 状态
101
- node "$CLIENT_PATH" --action diagnose --url-contains "platform.aicityos.com" --wait 10000
131
+ node "$EXECUTOR" --action diagnose --url-contains "platform.aicityos.com" --wait 10000
102
132
 
103
133
  # 仅看 API
104
- node "$CLIENT_PATH" --action api-capture --url-contains "platform.aicityos.com" \
134
+ node "$EXECUTOR" --action api-capture --url-contains "platform.aicityos.com" \
105
135
  --url-pattern "/api/" --wait 10000 --include-response-body \
106
136
  --output /tmp/api.json
107
137
  ```
@@ -113,21 +143,24 @@ node "$CLIENT_PATH" --action api-capture --url-contains "platform.aicityos.com"
113
143
  ```
114
144
  Agent / SKILL.md
115
145
  → testCase JSON ← P0 用例首选入口
116
- szcd-mcp-client/local-browser-executor.js ($CLIENT_PATH)
117
- → lib/browser-engine.js (executeTestCase + Ant 适配 + expect)
118
- → lib/ant-adapter.js (Select/TreeSelect/Input/Radio/...)
119
- → lib/expect.js (validation/locator/url/api + poll)
146
+ $SKILL_DIR/local-browser-executor.js ($EXECUTOR)
147
+ $SKILL_DIR/lib/browser-engine.js (executeTestCase + Ant 适配 + expect)
148
+ $SKILL_DIR/lib/ant-adapter.js (Select/TreeSelect/Input/Radio/...)
149
+ $SKILL_DIR/lib/expect.js (validation/locator/url/api + poll)
120
150
  → puppeteer-core + CDP
121
151
  → 用户真实 Chrome / Edge
122
152
  ```
123
153
 
124
- 新增稳定能力优先进入 `lib/browser-engine.js` `lib/ant-adapter.js`,**禁止 AI 在用户机器上写 ad-hoc puppeteer 脚本**——会丢失:
154
+ 执行器 + lib 内置在 skill 目录中(自包含),`postinstall` 会把整个 skill 目录复制到各 IDE。
155
+ **禁止 AI 在用户机器上写 ad-hoc puppeteer 脚本**——会丢失:
125
156
  - 微前端 frame 探活 + 自动召回
126
157
  - `closeAllDropdowns` 跨 frame 清理
127
158
  - Ant Select VERIFY 回写
128
159
  - testCase recovery 重试
129
160
  - API 按 frame scope 过滤
130
161
 
162
+ `local-browser-executor.cjs`(同目录)是 CommonJS 过渡版,仅支持基础诊断。**Ant 适配层 / testCase DSL / expect 断言仅 `local-browser-executor.js`(ES Module 版)支持。**
163
+
131
164
  ## 浏览器模式
132
165
 
133
166
  支持两种模式,执行器自动检测优先使用 connect:
@@ -182,13 +215,18 @@ AI 直接构造测试计划 JSON:
182
215
  | click | 点击元素(兼容) | `selector`, `waitForNavigation` | `{ clicked, tagName }` |
183
216
  | type | 输入文本(兼容) | `selector`, `text`, `clear` | `{ typed, value }` |
184
217
  | waitFor | 等待条件 | `selector` 或 `url`, `timeout` | `{ matched, waited }` |
185
- | evaluate | 执行 JS | `expression` | `{ result }` |
218
+ | evaluate | 执行 JS(浏览器端) | `expression`, `expectResult?`, `expectResultContains?`, `failOnFalsy?` | `{ result, passed, reason }` |
186
219
  | checkElement | 元素断言(兼容) | `selector`, `expect: {visible, minCount, maxCount, hasText}` | `{ passed, checks, elementCount }` |
187
220
  | findPage | 查找已打开的标签页 | `urlIncludes` 或 `urlRegex` 或 `titleIncludes` | `{ found, url, title, totalPages }` |
188
- | findFrame | 查找微前端 iframe | `urlIncludes` 或 `urlRegex` 或 `titleIncludes` 或 `contentIncludes` 或 `frameIndex` | `{ found, frameUrl, matchedBy, totalFrames }` |
221
+ | findFrame | 查找微前端 iframe | `frameSelector`(CSS选择器) 或 `frameUrlContains` 或 `frameContentContains` 或 `frameIndex` | `{ found, frameUrl, matchedBy, totalFrames }` |
189
222
  | loginWait | 等待 SSO 登录完成 | `loginSelector`, `timeout`, `interval` | `{ loggedIn, elapsed, currentUrl }` |
190
223
  | aiAssert | 语义断言(截图+审查) | `assertion`, `filename` | `{ screenshotPath, assertion, needsVisualReview }` |
191
224
  | runTask | 高层任务 runner(批量动态菜单扫描) | `task: "menu-api-scan"`, `menuApiPattern`, `menuLimit`, `menuPathPrefix[]`, `menuFilter[]`, `drawerTriggerSelector`, `useNativeHover`, `apiCaptureOptions`, `continueOnFail` | `{ task, discovered, attempted, byOutcome, results[] }` |
225
+ | **apiCall** | **API 直调(绕过 UI)** | `method`, `url`, `body?`, `headers?` | `{ status, data, passed }` |
226
+ | **switchPage** | **多标签页切换** | `urlContains?`/`titleIncludes?`/`pageIndex?` | `{ passed, url, title }` |
227
+ | **cdpScript** | **调用内置/已注册脚本** | `script`, `params?` | `{ acted, passed, ...result }` |
228
+ | **rawScript** | **Node.js 端执行任意 async** | `code`, `params?` | `{ acted, passed, ...result }` |
229
+ | **registerCdpScript** | **注册可复用脚本** | `name`, `code` | `{ registered, passed }` |
192
230
 
193
231
  ### 场景选择策略
194
232
 
@@ -213,14 +251,33 @@ AI 直接构造测试计划 JSON:
213
251
 
214
252
  ### 构造步骤的指导原则
215
253
 
216
- 1. **selector 必须从用户代码中读取,不要猜测**
217
- - 读代码中的 `id`、`className`、`data-testid`
218
- - szcd 组件的 class 可通过 `get_component_full_profile` 获取
219
- 2. **API 路径从代码中的接口调用读取**
220
- 3. **checkElement 的 expect 从代码逻辑推断**
221
- - columns 定义了 N 列 → `expect: { minCount: N }`(检查 th 数量)
222
- - dataSource 有数据 → `expect: { minCount: 1 }`(检查行数)
223
- 4. **compare 的 regions 从设计稿分析结果或组件布局推断**
254
+ **⚠️ 强制流程:构造 test-case JSON 前必须先执行 DOM 预检查**
255
+
256
+ 1. **observe 检查目标页面 DOM 结构**
257
+ - 确认 frame 结构(主页面 + 子应用 iframe)
258
+ - 确认表格/表单/按钮的 selector
259
+ - 确认 setup.findFrame 的匹配方式(frameSelector/frameUrlContains)
260
+
261
+ 2. **evaluate 检查关键元素文字**
262
+ - 按钮文字(如“提 交”中间有空格,不能用“提交”匹配)
263
+ - Select 选项文字(如“AI知识主题库”可能与 UI 显示不一致)
264
+ - 表单字段 label(如“数据资源名称”vs“数据集名称”)
265
+ - 表格列头(确认列数和列名)
266
+
267
+ 3. **根据 DOM 检查结果构造 test-case JSON**
268
+ - selector 从 observe 获取(不猜测)
269
+ - 选项文字从 evaluate 获取(不猜测)
270
+ - 按钮文字从 evaluate 获取(注意空格)
271
+ - 无项目代码时,DOM 检查替代代码读取
272
+
273
+ 4. **act 文字匹配**
274
+ - `text=新建` → 模糊匹配(includes),可能误匹配“新建目录”
275
+ - `text==新建` → 精确匹配(===),只匹配文字恰好为“新建”的元素
276
+ - 按钮文字可能有空格(如“提 交”),evaluate 检查后再选择匹配方式
277
+
278
+ 5. **API 路径从代码中的接口调用读取,或从 API 文档获取**
279
+ 6. **checkElement 的 expect 从代码逻辑推断**
280
+ 7. **compare 的 regions 从设计稿分析结果或组件布局推断**
224
281
 
225
282
  ## 微前端测试指南(实战经验)
226
283
 
@@ -311,7 +368,7 @@ wujie 微前端子应用渲染在 blob URL iframe 中,**坐标点击无法到
311
368
 
312
369
  ### 结构化观察(主链路起点)
313
370
  ```bash
314
- node {client_path}/local-browser-executor.js \
371
+ node "$EXECUTOR" \
315
372
  --action observe \
316
373
  --url-contains "platform.aicityos.com" \
317
374
  --frame-content-contains "数据集目录" \
@@ -323,7 +380,7 @@ node {client_path}/local-browser-executor.js \
323
380
 
324
381
  ### 观察后按 index 交互(主链路闭环)
325
382
  ```bash
326
- node {client_path}/local-browser-executor.js \
383
+ node "$EXECUTOR" \
327
384
  --action act \
328
385
  --url-contains "platform.aicityos.com" \
329
386
  --frame-content-contains "数据集目录" \
@@ -334,7 +391,7 @@ node {client_path}/local-browser-executor.js \
334
391
 
335
392
  ### 左侧菜单路径稳定点击
336
393
  ```bash
337
- node {client_path}/local-browser-executor.js \
394
+ node "$EXECUTOR" \
338
395
  --action act \
339
396
  --url-contains "platform.aicityos.com" \
340
397
  --menu-path "目录管理/数据集目录" \
@@ -346,7 +403,7 @@ node {client_path}/local-browser-executor.js \
346
403
 
347
404
  ### 轻量 DOM 断言
348
405
  ```bash
349
- node {client_path}/local-browser-executor.js \
406
+ node "$EXECUTOR" \
350
407
  --action assert \
351
408
  --url-contains "platform.aicityos.com" \
352
409
  --selector ".ant-table" \
@@ -357,7 +414,7 @@ node {client_path}/local-browser-executor.js \
357
414
 
358
415
  ### API 请求/响应捕获(真实浏览器 + 微前端)
359
416
  ```bash
360
- node {client_path}/local-browser-executor.js \
417
+ node "$EXECUTOR" \
361
418
  --action api-capture \
362
419
  --url-contains "platform.aicityos.com" \
363
420
  --frame-content-contains "数据集目录" \
@@ -372,7 +429,7 @@ node {client_path}/local-browser-executor.js \
372
429
 
373
430
  ### API 捕获结果断言
374
431
  ```bash
375
- node {client_path}/local-browser-executor.js \
432
+ node "$EXECUTOR" \
376
433
  --action assert-api \
377
434
  --capture-file /tmp/browser-api-capture.json \
378
435
  --max-failed 0 \
@@ -385,7 +442,7 @@ node {client_path}/local-browser-executor.js \
385
442
 
386
443
  ### 快速诊断(过渡兼容)
387
444
  ```bash
388
- NODE_PATH=~/.szcd-mcp/deps/node_modules node {skill_path}/local-browser-executor.cjs \
445
+ NODE_PATH=~/.szcd-mcp/deps/node_modules node "$SKILL_DIR/local-browser-executor.cjs" \
389
446
  --action diagnose \
390
447
  --url-contains "platform.aicityos.com" \
391
448
  --wait 15000 \
@@ -394,13 +451,13 @@ NODE_PATH=~/.szcd-mcp/deps/node_modules node {skill_path}/local-browser-executor
394
451
 
395
452
  ### 列出所有标签页
396
453
  ```bash
397
- NODE_PATH=~/.szcd-mcp/deps/node_modules node {skill_path}/local-browser-executor.cjs \
454
+ NODE_PATH=~/.szcd-mcp/deps/node_modules node "$SKILL_DIR/local-browser-executor.cjs" \
398
455
  --action list-pages
399
456
  ```
400
457
 
401
458
  ### 检查 API 请求状态
402
459
  ```bash
403
- NODE_PATH=~/.szcd-mcp/deps/node_modules node {skill_path}/local-browser-executor.cjs \
460
+ NODE_PATH=~/.szcd-mcp/deps/node_modules node "$SKILL_DIR/local-browser-executor.cjs" \
404
461
  --action api-check \
405
462
  --url-contains "platform.aicityos.com" \
406
463
  --wait 15000
@@ -408,7 +465,7 @@ NODE_PATH=~/.szcd-mcp/deps/node_modules node {skill_path}/local-browser-executor
408
465
 
409
466
  ### 截图
410
467
  ```bash
411
- NODE_PATH=~/.szcd-mcp/deps/node_modules node {skill_path}/local-browser-executor.cjs \
468
+ NODE_PATH=~/.szcd-mcp/deps/node_modules node "$SKILL_DIR/local-browser-executor.cjs" \
412
469
  --action screenshot \
413
470
  --url-contains "platform.aicityos.com" \
414
471
  --filename current-page.png \
@@ -417,7 +474,7 @@ NODE_PATH=~/.szcd-mcp/deps/node_modules node {skill_path}/local-browser-executor
417
474
 
418
475
  ### 执行测试计划 JSON
419
476
  ```bash
420
- NODE_PATH=~/.szcd-mcp/deps/node_modules node {skill_path}/local-browser-executor.cjs \
477
+ NODE_PATH=~/.szcd-mcp/deps/node_modules node "$SKILL_DIR/local-browser-executor.cjs" \
421
478
  --action plan \
422
479
  --plan-file /tmp/test-plan.json \
423
480
  --output /tmp/browser-test-result.json
@@ -425,7 +482,7 @@ NODE_PATH=~/.szcd-mcp/deps/node_modules node {skill_path}/local-browser-executor
425
482
 
426
483
  ### DOM 快照(结构化页面树,类似 browser-use take_snapshot)
427
484
  ```bash
428
- NODE_PATH=~/.szcd-mcp/deps/node_modules node {skill_path}/local-browser-executor.cjs \
485
+ NODE_PATH=~/.szcd-mcp/deps/node_modules node "$SKILL_DIR/local-browser-executor.cjs" \
429
486
  --action snapshot \
430
487
  --url-contains "platform.aicityos.com" \
431
488
  --depth 4 \
@@ -446,25 +503,25 @@ NODE_PATH=~/.szcd-mcp/deps/node_modules node {skill_path}/local-browser-executor
446
503
  ### 智能交互(支持 text=、role=、~ 选择器,类似 Playwright)
447
504
  ```bash
448
505
  # 点击包含“构建”文本的按钮
449
- NODE_PATH=~/.szcd-mcp/deps/node_modules node {skill_path}/local-browser-executor.cjs \
506
+ NODE_PATH=~/.szcd-mcp/deps/node_modules node "$SKILL_DIR/local-browser-executor.cjs" \
450
507
  --action interact \
451
508
  --url-contains "jenkins" \
452
509
  --click "text=Build"
453
510
 
454
511
  # 在输入框中输入文本
455
- NODE_PATH=~/.szcd-mcp/deps/node_modules node {skill_path}/local-browser-executor.cjs \
512
+ NODE_PATH=~/.szcd-mcp/deps/node_modules node "$SKILL_DIR/local-browser-executor.cjs" \
456
513
  --action interact \
457
514
  --click "#search-input" \
458
515
  --type "hello world"
459
516
 
460
517
  # 悬停 + 截图
461
- NODE_PATH=~/.szcd-mcp/deps/node_modules node {skill_path}/local-browser-executor.cjs \
518
+ NODE_PATH=~/.szcd-mcp/deps/node_modules node "$SKILL_DIR/local-browser-executor.cjs" \
462
519
  --action interact \
463
520
  --hover "~ant-menu-item" \
464
521
  --screenshot-after
465
522
 
466
523
  # 滚动页面
467
- NODE_PATH=~/.szcd-mcp/deps/node_modules node {skill_path}/local-browser-executor.cjs \
524
+ NODE_PATH=~/.szcd-mcp/deps/node_modules node "$SKILL_DIR/local-browser-executor.cjs" \
468
525
  --action interact \
469
526
  --scroll down --scroll-amount 500
470
527
  ```
@@ -476,14 +533,14 @@ NODE_PATH=~/.szcd-mcp/deps/node_modules node {skill_path}/local-browser-executor
476
533
 
477
534
  ### 性能指标采集(FCP/TTFB/资源统计)
478
535
  ```bash
479
- NODE_PATH=~/.szcd-mcp/deps/node_modules node {skill_path}/local-browser-executor.cjs \
536
+ NODE_PATH=~/.szcd-mcp/deps/node_modules node "$SKILL_DIR/local-browser-executor.cjs" \
480
537
  --action perf \
481
538
  --url-contains "platform.aicityos.com"
482
539
  ```
483
540
 
484
541
  ### 持续监控模式(定时截图 + 错误检测)
485
542
  ```bash
486
- NODE_PATH=~/.szcd-mcp/deps/node_modules node {skill_path}/local-browser-executor.cjs \
543
+ NODE_PATH=~/.szcd-mcp/deps/node_modules node "$SKILL_DIR/local-browser-executor.cjs" \
487
544
  --action watch \
488
545
  --url-contains "platform.aicityos.com" \
489
546
  --interval 5000 \
@@ -659,11 +716,15 @@ cd ~/.szcd-mcp/deps && npm install --registry=https://registry.npmmirror.com --@
659
716
  | `formFill` | 批量填充 + 可选 `validateAfter` | `fields[]` |
660
717
  | `closeAllDropdowns` | 关闭所有下拉浮层 | - |
661
718
 
662
- **关键约束**:
663
- - Ant Select 必须 `mousedown` 触发(不是 click)
664
- - 选项异步渲染,内置 15×200ms 轮询
665
- - 每次操作前后自动 `closeAllDropdowns`,`act` 入口检测残留并预清理
666
- - 操作后 VERIFY `.ant-select-selection-item[title]` 回写
719
+ **关键约束(6/22 反馈后更新)**:
720
+ - Ant Select 必须 `mousedown` + `click` 触发(click 触发 React onChange
721
+ - 选项异步渲染,内置 poll 轮询(timeout 默认 3s,不限制 1.5s)
722
+ - `closeAllDropdowns` 去掉 `body >` 限制(适配微前端浮层)+ 增加 300ms sleep
723
+ - 选项点击后 mousedown + mouseup + **click**(三步组合)
724
+ - VERIFY 全局兜底:marker 丢失时全局搜索 `.ant-select-selection-item`
725
+ - VERIFY 检查 `title` + `innerText` + `textContent`(三重兜底)
726
+ - `popupResolved` 变量提升到 for 循环外(避免作用域 bug)
727
+ - 浮层选择器去掉 `body >` 限制(`.ant-select-dropdown` 而非 `body > .ant-select-dropdown`)
667
728
 
668
729
  ```json
669
730
  { "type": "antSelect", "field": "数据集名称", "option": "测试集A" }
@@ -699,6 +760,75 @@ node local-browser-executor.js --action test-case --test-case-file /tmp/case.jso
699
760
  }
700
761
  ```
701
762
 
763
+ ## 脚本执行能力分层
764
+
765
+ | 层级 | 步骤类型 | 执行环境 | 能力 | 适用场景 |
766
+ |------|---------|---------|------|---------|
767
+ | 1 | evaluate | 浏览器端 | DOM 查询/操作 | 简单检查、点击元素 |
768
+ | 2 | rawScript | Node.js 端 | 完整 puppeteer API | 跨 frame、等待、多标签页 |
769
+ | 3 | cdpScript | Node.js 端 | 调用已注册脚本 | 复用通用交互模式 |
770
+ | 4 | 内置 cdpScript | Node.js 端 | 预定义通用 UI 交互 | Drawer/Modal/表格等 |
771
+
772
+ **evaluate**:浏览器端执行 JS,只能操作 DOM,不能等待/跨 frame/调用 puppeteer
773
+ **rawScript**:Node.js 端执行 async 函数,接收 `(page, frame, params, browser)`,可调用所有 puppeteer API
774
+ **registerCdpScript**:注册可复用脚本,后续通过 cdpScript 步骤按 name 调用
775
+ **cdpScript**:调用 registerCdpScript 注册的脚本或内置脚本
776
+
777
+ ### 内置 cdpScript(通用 UI 交互模式)
778
+
779
+ | script | 功能 | params |
780
+ |--------|------|--------|
781
+ | `drawer-action` | Drawer 操作 | `action: 'search'\|'select'\|'confirm'\|'cancel'`, `radioIndex?`, `searchText?`, `searchPlaceholder?` |
782
+ | `modal-action` | Modal/Popconfirm 操作 | `action: 'confirm'\|'cancel'`, `buttonText?` |
783
+ | `switch-tab` | 多标签页切换 | `urlContains?`, `titleIncludes?`, `pageIndex?` |
784
+ | `wait-api` | 等待 API 响应 | `urlPattern`, `timeout?`, `method?` |
785
+ | `get-table-data` | 提取表格数据 | `selector?`, `maxRows?` |
786
+
787
+ **设计原则**:cdpScript 内置通用 UI 交互模式(跨项目复用),业务流程由 LLM 用 rawScript 动态生成
788
+
789
+ ```json
790
+ // 内置 cdpScript 示例
791
+ { "type": "cdpScript", "script": "get-table-data", "params": { "maxRows": 5 } }
792
+ { "type": "cdpScript", "script": "drawer-action", "params": { "action": "select", "radioIndex": 0 } }
793
+ { "type": "cdpScript", "script": "drawer-action", "params": { "action": "confirm" } }
794
+ { "type": "cdpScript", "script": "modal-action", "params": { "action": "confirm" } }
795
+ { "type": "cdpScript", "script": "switch-tab", "params": { "urlContains": "localhost:8088" } }
796
+
797
+ // rawScript 示例(LLM 动态生成业务逻辑)
798
+ { "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' } }" }
799
+
800
+ // registerCdpScript + cdpScript(先注册再调用)
801
+ { "type": "registerCdpScript", "name": "my-check", "code": "async (page, frame, params) => { ... }" }
802
+ { "type": "cdpScript", "script": "my-check", "params": { "id": "123" } }
803
+ ```
804
+
805
+ ### evaluate 增强
806
+
807
+ ```json
808
+ // expectResult:精确匹配返回值
809
+ { "type": "evaluate", "expression": "...", "expectResult": "found" }
810
+
811
+ // expectResultContains:包含匹配返回值
812
+ { "type": "evaluate", "expression": "...", "expectResultContains": "数据资源" }
813
+
814
+ // failOnFalsy:返回 falsy 时标记 FAIL
815
+ { "type": "evaluate", "expression": "...", "failOnFalsy": true }
816
+ ```
817
+
818
+ ### apiCall(API 直调)
819
+
820
+ ```json
821
+ { "type": "apiCall", "method": "POST", "url": "http://localhost:8088/api/submit", "body": { "id": "123" } }
822
+ // → { status: 200, data: { code: "200" }, passed: true }
823
+ ```
824
+
825
+ ### switchPage(多标签页切换)
826
+
827
+ ```json
828
+ { "type": "switchPage", "urlContains": "localhost:8088" }
829
+ // → { passed: true, url: "http://localhost:8088/...", title: "..." }
830
+ ```
831
+
702
832
  ## 语义断言(expect)
703
833
 
704
834
  | assertion | 功能 |
@@ -729,20 +859,26 @@ node local-browser-executor.js --action test-case --test-case-file /tmp/case.jso
729
859
  {
730
860
  "title": "数据集创建 P0",
731
861
  "setup": {
732
- "findFrame": { "contentContains": "数据集目录" }
862
+ "findFrame": { "frameUrlContains": "#/catalog/" }
733
863
  },
734
864
  "dataset": [{ "name": "A" }, { "name": "B" }],
735
865
  "steps": [
736
- { "type": "apiCapture", "urlPattern": "/api/dataset", "wait": 5000, "includeAllTargets": true },
866
+ { "type": "navigate", "url": "http://localhost:8088/#/szc-ai-data/catalog/catalog-manage", "waitUntil": "domcontentloaded" },
867
+ { "type": "expect", "assertion": "locator.toBeVisible", "selector": ".ant-table", "timeout": 20000 },
737
868
  { "type": "antInput", "field": "名称", "value": "{{name}}" },
738
869
  { "type": "antSelect", "field": "类型", "option": "图像" },
739
- { "type": "act", "click": "text=提交" },
870
+ { "type": "act", "click": "text==提 交" },
740
871
  { "type": "expect", "assertion": "validation.toPass" },
741
- { "type": "expect", "assertion": "api.toAllPass", "scope": "current-frame" }
872
+ { "type": "expect", "assertion": "locator.toBeVisible", "selector": ".ant-table", "timeout": 15000 }
742
873
  ]
743
874
  }
744
875
  ```
745
876
 
877
+ **findFrame 三种方案(按稳定性排序)**:
878
+ - `frameSelector: "iframe"` — 基于 DOM 选择器(最稳定,推荐)
879
+ - `frameUrlContains: "#/catalog/"` — 基于 URL 子串(navigate 后可能失效)
880
+ - `frameContentContains: "数据集目录"` — 基于内容文字(页面未加载时不可用)
881
+
746
882
  **关键参数:**
747
883
 
748
884
  - `setup.findFrame.contentContains` — 一次定位子应用 frame