@szc-ft/mcp-szcd-client 0.27.4 → 0.28.1
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.
- package/lib/ant-adapter.js +716 -0
- package/lib/browser-engine.js +471 -0
- package/lib/expect.js +392 -0
- package/lib/test-plan.js +48 -0
- package/local-browser-executor.js +70 -1
- package/opencode-extension/skills/local-browser-test/SKILL.md +221 -15
- package/package.json +1 -1
- package/qwen-extension/qwen-extension.json +1 -1
- package/qwen-extension/skills/local-browser-test/SKILL.md +221 -15
- package/standard-skill/local-browser-test/SKILL.md +221 -15
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: local-browser-test
|
|
3
|
-
description:
|
|
3
|
+
description: 在用户真实 Chrome 中执行自动化测试,覆盖 P0 用例批量执行、深度表单填写、微前端测试、长任务生命周期、设计稿对比、JS 错误诊断。通过 puppeteer-core 连接 CDP,天然继承登录态/微前端环境。
|
|
4
4
|
compatibility:
|
|
5
5
|
tools:
|
|
6
6
|
- run_command
|
|
@@ -11,29 +11,122 @@ compatibility:
|
|
|
11
11
|
|
|
12
12
|
## 触发条件
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
- "测试一下"、"帮我验证"、"浏览器测试"、"接管浏览器"
|
|
16
|
-
- "对比设计稿"、"还原度"、"看看效果"
|
|
17
|
-
- "功能测试"、"能不能正常用"
|
|
18
|
-
- "检查页面"、"页面渲染"
|
|
14
|
+
当用户出现以下意图时启动:
|
|
19
15
|
|
|
20
|
-
|
|
16
|
+
**用例自动化(用 testCase DSL):**
|
|
17
|
+
- "执行 P0 用例" / "批量测试" / "测试用例自动化"
|
|
18
|
+
- "创建→审核→上线 全链路" / "生命周期测试"
|
|
19
|
+
- "表单填写验证" / "深度表单测试"
|
|
20
|
+
|
|
21
|
+
**轻量诊断(用 --action diagnose):**
|
|
22
|
+
- "测试一下" / "检查页面" / "看看效果" / "能不能正常用"
|
|
23
|
+
- "JS 错误" / "页面渲染" / "API 检查"
|
|
24
|
+
|
|
25
|
+
**视觉对比(用 --action compare):**
|
|
26
|
+
- "对比设计稿" / "还原度" / "像素对比"
|
|
27
|
+
|
|
28
|
+
## ⚡ 路径解析(必读,避免 ad-hoc 脚本)
|
|
29
|
+
|
|
30
|
+
**所有命令使用 `$CLIENT_PATH` 变量,会话开始时先解析一次:**
|
|
31
|
+
|
|
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
|
+
```
|
|
38
|
+
|
|
39
|
+
后续所有命令直接 `node "$CLIENT_PATH" --action ...`,**不要自己 require puppeteer 写脚本**——执行器已封装好 antSelect/formFill/expect/testCase 等步骤,自己写脚本会丢失微前端 frame 探活、closeAllDropdowns、verify 回写等保护。
|
|
21
40
|
|
|
22
|
-
|
|
41
|
+
## 🎯 三种典型任务(场景驱动)
|
|
42
|
+
|
|
43
|
+
### 任务 A:P0 用例批量执行(首选)
|
|
44
|
+
|
|
45
|
+
用户说"执行 P0 用例 / 批量测试 / 生命周期测试"时,**不要写 ad-hoc puppeteer 脚本**,构造 testCase JSON:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
cat > /tmp/case.json <<'EOF'
|
|
49
|
+
{
|
|
50
|
+
"title": "数据集创建 P0",
|
|
51
|
+
"setup": { "findFrame": { "contentContains": "数据集目录" } },
|
|
52
|
+
"dataset": [
|
|
53
|
+
{ "name": "测试集A", "type": "图像" },
|
|
54
|
+
{ "name": "测试集B", "type": "文本" }
|
|
55
|
+
],
|
|
56
|
+
"steps": [
|
|
57
|
+
{ "type": "formFill", "fields": [
|
|
58
|
+
{ "label": "名称", "type": "input", "value": "{{name}}" },
|
|
59
|
+
{ "label": "资源类型", "type": "select", "value": "{{type}}" },
|
|
60
|
+
{ "label": "审核结果", "type": "radio", "value": "通过" }
|
|
61
|
+
], "validateAfter": true },
|
|
62
|
+
{ "type": "act", "click": "text=提 交" },
|
|
63
|
+
{ "type": "expect", "assertion": "url.toContain", "value": "/list" },
|
|
64
|
+
{ "type": "expect", "assertion": "api.toAllPass", "scope": "current-frame" }
|
|
65
|
+
],
|
|
66
|
+
"recovery": { "maxRetriesPerStep": 2 }
|
|
67
|
+
}
|
|
68
|
+
EOF
|
|
69
|
+
node "$CLIENT_PATH" --action test-case --test-case-file /tmp/case.json --test-report /tmp/report.json
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
dataset 数组每行跑一遍同样的 steps,`{{name}}` / `{{type}}` 自动替换。`setup.findFrame` 一次定位后所有步骤自动走子应用 frame(微前端默认行为)。
|
|
73
|
+
|
|
74
|
+
### 任务 B:深度表单填写(Ant Design + 微前端)
|
|
75
|
+
|
|
76
|
+
用户描述"填表单 / Select 选项 / TreeSelect / 挂接资源"时,用单步 antXxx 或 formFill:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
# 单字段
|
|
80
|
+
node "$CLIENT_PATH" --action act --frame-content-contains "数据集" \
|
|
81
|
+
--selector ant-select=资源类型 --click ... # ❌ 不要这样
|
|
82
|
+
|
|
83
|
+
# ✅ 用 testCase 的 antSelect 步骤(在 testCase.steps 内):
|
|
84
|
+
# { "type": "antSelect", "field": "资源类型", "option": "图像" }
|
|
85
|
+
# { "type": "antTreeSelect", "field": "分类", "path": ["AI", "视觉"] }
|
|
86
|
+
# { "type": "antInput", "placeholder": "挂接资源", "value": "abc" }
|
|
87
|
+
# { "type": "antRadio", "field": "审核结果", "option": "通过" }
|
|
88
|
+
# { "type": "formFill", "fields": [...], "validateAfter": true }
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**关键约束(执行器已内置,无需手写):**
|
|
92
|
+
- Ant Select 必须 mousedown(不是 click)
|
|
93
|
+
- 浮层异步渲染 15×200ms 轮询
|
|
94
|
+
- 每次操作前后 `closeAllDropdowns` 防串选
|
|
95
|
+
- VERIFY 回写检查 `.ant-select-selection-item`
|
|
96
|
+
|
|
97
|
+
### 任务 C:轻量诊断(看页面是否正常)
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
# 一次性看 JS 错误 + API 失败 + iframe 状态
|
|
101
|
+
node "$CLIENT_PATH" --action diagnose --url-contains "platform.aicityos.com" --wait 10000
|
|
102
|
+
|
|
103
|
+
# 仅看 API
|
|
104
|
+
node "$CLIENT_PATH" --action api-capture --url-contains "platform.aicityos.com" \
|
|
105
|
+
--url-pattern "/api/" --wait 10000 --include-response-body \
|
|
106
|
+
--output /tmp/api.json
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## 主链路定位
|
|
23
112
|
|
|
24
113
|
```
|
|
25
114
|
Agent / SKILL.md
|
|
26
|
-
→
|
|
27
|
-
→
|
|
115
|
+
→ 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)
|
|
28
120
|
→ puppeteer-core + CDP
|
|
29
121
|
→ 用户真实 Chrome / Edge
|
|
30
122
|
```
|
|
31
123
|
|
|
32
|
-
|
|
33
|
-
-
|
|
34
|
-
-
|
|
35
|
-
|
|
36
|
-
|
|
124
|
+
新增稳定能力优先进入 `lib/browser-engine.js` 和 `lib/ant-adapter.js`,**禁止 AI 在用户机器上写 ad-hoc puppeteer 脚本**——会丢失:
|
|
125
|
+
- 微前端 frame 探活 + 自动召回
|
|
126
|
+
- `closeAllDropdowns` 跨 frame 清理
|
|
127
|
+
- Ant Select VERIFY 回写
|
|
128
|
+
- testCase recovery 重试
|
|
129
|
+
- API 按 frame scope 过滤
|
|
37
130
|
|
|
38
131
|
## 浏览器模式
|
|
39
132
|
|
|
@@ -547,6 +640,119 @@ cd ~/.szcd-mcp/deps && npm install --registry=https://registry.npmmirror.com --@
|
|
|
547
640
|
| `setTimeout(awaitPage.bringToFront(), 30000)` 之外不做兜底 | 调用前自查"目标页是否还活着"(`findPage` / `_findFrame`),失败立刻退而求其次操作主帧 | bringToFront 在 OS 抢焦点失败时会无限挂起,没超时 |
|
|
548
641
|
| iframe attach 失败就报错退出 | `attachRetries` 默认 3 次、间隔 1.5s 重抓 `browser.targets()` | wujie 的 blob iframe target 在主页面 ready 后才注册,时序敏感 |
|
|
549
642
|
| 大批量探索每条都 throw | `_actMenuPath` 失败时返回 `acted:false + candidates`,配合 `runTask --continue-on-fail` | 探索性扫描遇到一条点不开就全部中断,没法发现整体面 |
|
|
643
|
+
| 手写 puppeteer 脚本绕过 `act` 操作 Ant Select | 用 `antSelect` 步骤(内置 mousedown + 轮询 + closeAllDropdowns + VERIFY) | Ant Design Select 必须 mousedown 触发,浮层在 body 末尾,选项异步渲染,单步 `act.evaluate` 无法覆盖四步组合 |
|
|
644
|
+
| 每个 Select 后忘记 `closeAllDropdowns` | `act` 自动检测上一次 antSelect/antTreeSelect 并预清理 | 下拉浮层残留会污染下一次 Select 操作,选项串选 |
|
|
645
|
+
| 硬编码 `sleep(15*200)` 等选项渲染 | `antSelect` 内置 `pollEval` 15×200ms 轮询,`expect(poll)` 可自定义条件 | 选项渲染时间受网络/数据量影响,固定 sleep 时机不可靠 |
|
|
646
|
+
| 表单校验用 ad-hoc evaluate 检查 | 用 `expect validation.toPass` 或 `formFill` 的 `validateAfter` | 语义断言自带 poll + 结构化错误信息(field + message),可直接进报告 |
|
|
647
|
+
|
|
648
|
+
## Ant Design 适配层(6/22 反馈落地)
|
|
649
|
+
|
|
650
|
+
把"触发器定位 → 浮层等待 → 选项交互 → VERIFY → 全局清理"固化为单步骤 JSON。
|
|
651
|
+
|
|
652
|
+
| type | 功能 | 关键参数 |
|
|
653
|
+
|------|------|---------|
|
|
654
|
+
| `antSelect` | Ant Select 选项选择 | `field`, `option`, `mode` |
|
|
655
|
+
| `antTreeSelect` | 树形选择 | `field`, `path` |
|
|
656
|
+
| `antInput` | 输入框(支持 `placeholder` 定位) | `field`/`placeholder`, `value` |
|
|
657
|
+
| `antRadio` / `antCheckbox` / `antSwitch` | 选择类 | `field`, `option`/`on` |
|
|
658
|
+
| `antDatePicker` / `antCascader` / `antUpload` | 其他控件 | 见各方法文档 |
|
|
659
|
+
| `formFill` | 批量填充 + 可选 `validateAfter` | `fields[]` |
|
|
660
|
+
| `closeAllDropdowns` | 关闭所有下拉浮层 | - |
|
|
661
|
+
|
|
662
|
+
**关键约束**:
|
|
663
|
+
- Ant Select 必须 `mousedown` 触发(不是 click)
|
|
664
|
+
- 选项异步渲染,内置 15×200ms 轮询
|
|
665
|
+
- 每次操作前后自动 `closeAllDropdowns`,`act` 入口检测残留并预清理
|
|
666
|
+
- 操作后 VERIFY `.ant-select-selection-item[title]` 回写
|
|
667
|
+
|
|
668
|
+
```json
|
|
669
|
+
{ "type": "antSelect", "field": "数据集名称", "option": "测试集A" }
|
|
670
|
+
{ "type": "antInput", "placeholder": "挂接资源", "value": "abc" }
|
|
671
|
+
{ "type": "formFill", "fields": [
|
|
672
|
+
{ "label": "名称", "type": "input", "value": "{{name}}" },
|
|
673
|
+
{ "label": "类型", "type": "select", "value": "{{type}}" }
|
|
674
|
+
], "validateAfter": true }
|
|
675
|
+
```
|
|
676
|
+
|
|
677
|
+
## 用例 DSL(Test Case)
|
|
678
|
+
|
|
679
|
+
把多个用例统一为参数化 testCase JSON:
|
|
680
|
+
|
|
681
|
+
```bash
|
|
682
|
+
node local-browser-executor.js --action test-case --test-case-file /tmp/case.json [--dataset-index 0] [--test-report /tmp/report.json]
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
```json
|
|
686
|
+
{
|
|
687
|
+
"title": "创建数据集 - P0",
|
|
688
|
+
"dataset": [{ "name": "A", "type": "图像" }, { "name": "B", "type": "文本" }],
|
|
689
|
+
"steps": [
|
|
690
|
+
{ "type": "formFill", "fields": [
|
|
691
|
+
{ "label": "名称", "type": "input", "value": "{{name}}" },
|
|
692
|
+
{ "label": "类型", "type": "select", "value": "{{type}}" }
|
|
693
|
+
], "validateAfter": true },
|
|
694
|
+
{ "type": "expect", "assertion": "validation.toPass" },
|
|
695
|
+
{ "type": "act", "click": "text=提交" },
|
|
696
|
+
{ "type": "expect", "assertion": "url.toContain", "value": "/list" }
|
|
697
|
+
],
|
|
698
|
+
"recovery": { "maxRetriesPerStep": 2 }
|
|
699
|
+
}
|
|
700
|
+
```
|
|
701
|
+
|
|
702
|
+
## 语义断言(expect)
|
|
703
|
+
|
|
704
|
+
| assertion | 功能 |
|
|
705
|
+
|-----------|------|
|
|
706
|
+
| `validation.toPass` | 表单校验无错误 |
|
|
707
|
+
| `validation.toHaveError` | 某字段有错误 |
|
|
708
|
+
| `locator.toBeVisible` / `toHaveText` / `toHaveValue` / `toHaveCount` | 元素状态 |
|
|
709
|
+
| `url.toContain` / `url.toMatch` | URL 断言 |
|
|
710
|
+
| `api.toAllPass` / `api.toInclude` | API 断言 |
|
|
711
|
+
|
|
712
|
+
所有断言内置 `poll` 轮询(默认 timeout 5s, interval 200ms)。
|
|
713
|
+
|
|
714
|
+
## 微前端(wujie / qiankun)场景适配
|
|
715
|
+
|
|
716
|
+
**核心问题与解决:**
|
|
717
|
+
|
|
718
|
+
| 微前端坑 | 自动修复 |
|
|
719
|
+
|---------|---------|
|
|
720
|
+
| 子应用业务在 frame[1],每步都要重传 frameContentContains | testCase `setup.findFrame` 设一次,所有步骤默认走 `_activeFrame` |
|
|
721
|
+
| 子应用路由切换销毁 iframe,导致后续 step 都 evaluate 失败 | `_getTargetFrame` 每次执行前探活,失活自动召回(标记 `frameRecovered: true`) |
|
|
722
|
+
| Ant Select 的 `getPopupContainer` 把浮层渲染到父文档 | `antSelect` 触发器/浮层双 frame 协作:trigger 在子 frame mousedown,浮层在父 frame 找选项 |
|
|
723
|
+
| 残留浮层污染下一次操作 | `closeAllDropdowns` 跨 frame 清理(主 + 所有可访问子 frame) |
|
|
724
|
+
| `api.toAllPass` 因主应用埋点 404 误判子应用 P0 测试失败 | `step.scope: "current-frame"` 仅断言 `_activeFrame` 同源的请求 |
|
|
725
|
+
|
|
726
|
+
**testCase 微前端模板:**
|
|
727
|
+
|
|
728
|
+
```json
|
|
729
|
+
{
|
|
730
|
+
"title": "数据集创建 P0",
|
|
731
|
+
"setup": {
|
|
732
|
+
"findFrame": { "contentContains": "数据集目录" }
|
|
733
|
+
},
|
|
734
|
+
"dataset": [{ "name": "A" }, { "name": "B" }],
|
|
735
|
+
"steps": [
|
|
736
|
+
{ "type": "apiCapture", "urlPattern": "/api/dataset", "wait": 5000, "includeAllTargets": true },
|
|
737
|
+
{ "type": "antInput", "field": "名称", "value": "{{name}}" },
|
|
738
|
+
{ "type": "antSelect", "field": "类型", "option": "图像" },
|
|
739
|
+
{ "type": "act", "click": "text=提交" },
|
|
740
|
+
{ "type": "expect", "assertion": "validation.toPass" },
|
|
741
|
+
{ "type": "expect", "assertion": "api.toAllPass", "scope": "current-frame" }
|
|
742
|
+
]
|
|
743
|
+
}
|
|
744
|
+
```
|
|
745
|
+
|
|
746
|
+
**关键参数:**
|
|
747
|
+
|
|
748
|
+
- `setup.findFrame.contentContains` — 一次定位子应用 frame
|
|
749
|
+
- `step.frameContentContains` — 单步覆盖(少用,违反 DRY)
|
|
750
|
+
- `step.scope: "current-frame"` — API 断言仅看 `_activeFrame` 同源请求
|
|
751
|
+
- `apiCapture.includeAllTargets: true` — 跨 page/iframe target 监听网络
|
|
752
|
+
|
|
753
|
+
**调试技巧:**
|
|
754
|
+
|
|
755
|
+
每个 ant 步骤结果含 `triggerFrameUrl` / `popupFrameUrl`,可看到触发器和浮层实际在哪个 frame。若 `_dropdownFrame: "main"` 出现,说明该控件用了 `getPopupContainer` 跨 frame 渲染浮层。
|
|
550
756
|
|
|
551
757
|
## 非目标声明
|
|
552
758
|
|