@szc-ft/mcp-szcd-client 0.20.0 → 0.22.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.
- package/agents/build.js +152 -130
- package/agents/opencode-extension/agents/szcd-component-expert.md +96 -12
- package/agents/platforms.json +17 -7
- package/agents/qwen-extension/agents/szcd-component-expert.md +95 -12
- package/agents/src/szcd-component-expert.md +169 -6
- package/agents/src/tools.json +10 -5
- package/agents/szcd-component-expert.md +97 -14
- package/agents/szcd-component-expert.qoder.md +185 -15
- package/agents/szcd-component-expert.trae.md +175 -13
- package/opencode-extension/agents/szcd-component-expert.md +308 -0
- package/opencode-extension/commands/szcd-mcp-api-config.md +48 -0
- package/opencode-extension/commands/szcd-mcp-auth.md +39 -0
- package/opencode-extension/commands/szcd-mcp-browser-test.md +57 -0
- package/opencode-extension/commands/szcd-mcp-coding-config.md +40 -0
- package/opencode-extension/commands/szcd-mcp-feedback.md +42 -0
- package/opencode-extension/commands/szcd-mcp-url.md +37 -0
- package/opencode-extension/opencode.json +15 -0
- package/opencode-extension/skills/local-api-tool/SKILL.md +246 -0
- package/opencode-extension/skills/local-browser-test/SKILL.md +249 -0
- package/opencode-extension/skills/szcd-component-helper/SKILL.md +523 -0
- package/opencode-extension/skills/szcd-design-to-code/SKILL.md +168 -0
- package/package.json +4 -2
- package/qwen-extension/QWEN.md +29 -8
- package/qwen-extension/agents/szcd-component-expert.md +95 -12
- package/qwen-extension/qwen-extension.json +6 -1
- package/qwen-extension/skills/szcd-component-helper/SKILL.md +81 -1
- package/qwen-extension/skills/szcd-design-to-code/SKILL.md +168 -0
- package/scripts/lib/claude-code.js +45 -1
- package/scripts/lib/common.js +17 -0
- package/scripts/lib/opencode.js +108 -12
- package/scripts/lib/qoder.js +42 -1
- package/scripts/lib/trae-cli.js +39 -1
- package/scripts/lib/trae-ide.js +40 -1
- package/scripts/postinstall.js +28 -0
- package/standard-skill/szcd-component-helper/SKILL.md +81 -1
- package/standard-skill/szcd-design-to-code/SKILL.md +168 -0
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: local-api-tool
|
|
3
|
+
description: 本地网络 API 工具,专门处理 10.x.x.x 网段的 Swagger/YApi 文档拉取和联调测试。当服务端 api_tool 因 LOCAL_NETWORK_UNREACHABLE 无法访问时,此技能在用户本地环境执行 curl,再将结果交给服务端解析。适用于内网开发环境、VPN 场景。**仅限 10.x.x.x 网段**,172.16-31.x.x 和 192.168.x.x 服务端可直连,不要使用本技能。
|
|
4
|
+
compatibility:
|
|
5
|
+
tools:
|
|
6
|
+
- run_command
|
|
7
|
+
- web_search
|
|
8
|
+
- web_fetch
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# 本地网络 API 工具(local-api-tool)
|
|
12
|
+
|
|
13
|
+
## 概述
|
|
14
|
+
|
|
15
|
+
当 MCP 服务端 `api_tool` 遇到 `10.x.x.x` 网段地址时,因服务器无法直连用户内网,会返回 `reason: "LOCAL_NETWORK_UNREACHABLE"` 及 `action` 字段指引。此技能指导 AI 自动在用户本地终端通过 `run_command` 执行 curl 命令获取数据,再调用服务端的 `parse_swagger_json` 进行解析,全程无需用户手动操作。
|
|
16
|
+
|
|
17
|
+
## 适用场景
|
|
18
|
+
|
|
19
|
+
- Swagger URL 主机为 `10.x.x.x` 网段(如 `http://10.2.7.60:30194/ikmp-knowledge/swagger-ui/index.html`)
|
|
20
|
+
- API 联调测试目标为 `10.x.x.x` 网段
|
|
21
|
+
- 用户在 VPN 或内网开发环境中,本地可访问但远程服务器不可达
|
|
22
|
+
|
|
23
|
+
## ⚠️ 网段区分(重要)
|
|
24
|
+
|
|
25
|
+
**仅 `10.x.x.x` 网段需要走本地解析,其他私有网段服务端均可直连,切勿误用本技能:**
|
|
26
|
+
|
|
27
|
+
| 网段 | 是否需要本地解析 | 原因 | 处理方式 |
|
|
28
|
+
|------|:---:|------|------|
|
|
29
|
+
| `10.x.x.x` | **是** | 用户本地可达,服务端不可达 | 使用本技能在本地 curl |
|
|
30
|
+
| `172.16-31.x.x` | **否** | 服务端可直接访问 | 直接调用 api_tool,无需本技能 |
|
|
31
|
+
| `192.168.x.x` | **否** | 服务端通过弹性 IP 映射访问 | 直接调用 api_tool,无需本技能 |
|
|
32
|
+
| 公网 IP | **否** | 服务端可直接访问 | 直接调用 api_tool,无需本技能 |
|
|
33
|
+
|
|
34
|
+
**禁止对 172.16-31.x.x 和 192.168.x.x 使用本技能!** 这些地址虽然也是 RFC 1918 私有地址,但服务端网络环境可以直达,不需要在用户本地执行 curl。
|
|
35
|
+
|
|
36
|
+
## 工作流
|
|
37
|
+
|
|
38
|
+
### 流程 A:拉取 Swagger API 文档(fetch)
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
用户请求 fetch Swagger URL (10.x.x.x)
|
|
42
|
+
│
|
|
43
|
+
▼
|
|
44
|
+
调用 api_tool(action="fetch", url=...)
|
|
45
|
+
│
|
|
46
|
+
▼
|
|
47
|
+
服务端返回 { reason: "LOCAL_NETWORK_UNREACHABLE", action: "请使用 local-api-tool ...", swaggerEndpoints: {...} }
|
|
48
|
+
│
|
|
49
|
+
▼
|
|
50
|
+
AI 识别 LOCAL_NETWORK_UNREACHABLE,读取 swaggerEndpoints 和 authInfo
|
|
51
|
+
│
|
|
52
|
+
▼
|
|
53
|
+
AI 通过 run_command 在本地执行 curl:
|
|
54
|
+
步骤1: 鉴权获取 Cookie(如 authInfo.hasPassword 为 true)
|
|
55
|
+
步骤2: 获取完整 API 文档 JSON
|
|
56
|
+
│
|
|
57
|
+
▼
|
|
58
|
+
AI 调用 api_tool(action="parse_swagger_json", swaggerJson=..., swaggerSourceUrl=...)
|
|
59
|
+
│
|
|
60
|
+
▼
|
|
61
|
+
返回解析后的 API 文档给用户
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### 流程 B:联调测试(test)
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
用户请求 test API (10.x.x.x)
|
|
68
|
+
│
|
|
69
|
+
▼
|
|
70
|
+
调用 api_tool(action="test", url=..., method=..., data=..., headers=...)
|
|
71
|
+
│
|
|
72
|
+
▼
|
|
73
|
+
服务端返回 { reason: "LOCAL_NETWORK_UNREACHABLE", action: "请使用 local-api-tool ...", method, data, headers }
|
|
74
|
+
│
|
|
75
|
+
▼
|
|
76
|
+
AI 识别 LOCAL_NETWORK_UNREACHABLE,读取 method/data/headers
|
|
77
|
+
│
|
|
78
|
+
▼
|
|
79
|
+
AI 通过 run_command 在本地执行对应 curl 命令
|
|
80
|
+
│
|
|
81
|
+
▼
|
|
82
|
+
将响应结果返回给用户
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## 详细执行步骤
|
|
86
|
+
|
|
87
|
+
### 一、拉取 Swagger 文档(action=fetch)
|
|
88
|
+
|
|
89
|
+
**步骤 1:调用服务端 api_tool**
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
api_tool(action="fetch", url="http://10.2.7.60:30194/ikmp-knowledge/swagger-ui/index.html", username="admin", password="xxx")
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**步骤 2:检测 LOCAL_NETWORK_UNREACHABLE**
|
|
96
|
+
|
|
97
|
+
如果返回结果中包含 `reason: "LOCAL_NETWORK_UNREACHABLE"`,则自动进入本地执行流程。
|
|
98
|
+
|
|
99
|
+
返回结构示例:
|
|
100
|
+
```json
|
|
101
|
+
{
|
|
102
|
+
"reason": "LOCAL_NETWORK_UNREACHABLE",
|
|
103
|
+
"hostname": "10.2.7.60",
|
|
104
|
+
"swaggerUrl": "http://10.2.7.60:30194/ikmp-knowledge/swagger-ui/index.html",
|
|
105
|
+
"authInfo": { "username": "admin", "hasPassword": true },
|
|
106
|
+
"swaggerEndpoints": {
|
|
107
|
+
"basePath": "http://10.2.7.60:30194/ikmp-knowledge",
|
|
108
|
+
"apiDocsUrl": "http://10.2.7.60:30194/ikmp-knowledge/v2/api-docs"
|
|
109
|
+
},
|
|
110
|
+
"action": "请使用 local-api-tool 技能处理此请求..."
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**步骤 3:AI 通过 run_command 在本地执行 curl**
|
|
115
|
+
|
|
116
|
+
根据返回数据中的 `authInfo` 和 `swaggerEndpoints`,AI 自行构造 curl 命令并通过 `run_command` 在本地执行:
|
|
117
|
+
|
|
118
|
+
3a. **鉴权(如 authInfo.hasPassword 为 true)**:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
curl -s -D - -X GET 'http://10.2.7.60:30194/ikmp-knowledge/swagger-resources/configuration/ui' \
|
|
122
|
+
-H 'Authorization: Basic <base64(username:password)>' \
|
|
123
|
+
-H 'Accept: application/json'
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
从输出的 `Set-Cookie:` 行提取 Cookie 值(分号前的部分)。
|
|
127
|
+
|
|
128
|
+
3b. **获取 API 文档 JSON**:直接使用 `swaggerEndpoints.apiDocsUrl`:
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
curl -s -X GET 'http://10.2.7.60:30194/ikmp-knowledge/v2/api-docs' \
|
|
132
|
+
-H 'Accept: application/json' \
|
|
133
|
+
-H 'Cookie: <步骤3a获取的Cookie>'
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
如果 authInfo.hasPassword 为 false,省略 Cookie 头。
|
|
137
|
+
|
|
138
|
+
**步骤 4:调用服务端解析**
|
|
139
|
+
|
|
140
|
+
```
|
|
141
|
+
api_tool(action="parse_swagger_json", swaggerJson="<步骤3b获取的JSON字符串>", swaggerSourceUrl="http://10.2.7.60:30194/ikmp-knowledge/swagger-ui/index.html")
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**步骤 5:返回解析结果**
|
|
145
|
+
|
|
146
|
+
将 `parse_swagger_json` 返回的解析结果直接返回给用户,格式与正常 fetch 完全一致。
|
|
147
|
+
|
|
148
|
+
### 二、联调测试(action=test)
|
|
149
|
+
|
|
150
|
+
**步骤 1:调用服务端 api_tool**
|
|
151
|
+
|
|
152
|
+
```
|
|
153
|
+
api_tool(action="test", url="http://10.2.7.60:30194/ikmp-knowledge/api/xxx", method="POST", data={...}, headers={...})
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
**步骤 2:检测 LOCAL_NETWORK_UNREACHABLE**
|
|
157
|
+
|
|
158
|
+
如果返回结果中包含 `reason: "LOCAL_NETWORK_UNREACHABLE"`,则自动进入本地执行流程。
|
|
159
|
+
|
|
160
|
+
返回结构示例:
|
|
161
|
+
```json
|
|
162
|
+
{
|
|
163
|
+
"success": false,
|
|
164
|
+
"reason": "LOCAL_NETWORK_UNREACHABLE",
|
|
165
|
+
"hostname": "10.2.7.60",
|
|
166
|
+
"targetUrl": "http://10.2.7.60:30194/ikmp-knowledge/api/xxx",
|
|
167
|
+
"method": "POST",
|
|
168
|
+
"data": { "key": "value" },
|
|
169
|
+
"headers": {},
|
|
170
|
+
"action": "请使用 local-api-tool 技能处理此请求...",
|
|
171
|
+
"timestamp": "..."
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
**步骤 3:AI 通过 run_command 在本地执行 curl**
|
|
176
|
+
|
|
177
|
+
AI 根据返回数据中的 `method`、`data`、`headers`、`targetUrl` 自行构造 curl 命令:
|
|
178
|
+
|
|
179
|
+
GET 请求示例:
|
|
180
|
+
```bash
|
|
181
|
+
curl -s -X GET 'http://10.2.7.60:30194/ikmp-knowledge/api/xxx?key=value' \
|
|
182
|
+
-H 'Accept: application/json'
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
POST 请求示例:
|
|
186
|
+
```bash
|
|
187
|
+
curl -s -X POST 'http://10.2.7.60:30194/ikmp-knowledge/api/xxx' \
|
|
188
|
+
-H 'Accept: application/json' \
|
|
189
|
+
-H 'Content-Type: application/json' \
|
|
190
|
+
-d '{"key":"value"}'
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
如有自定义 headers,添加对应的 `-H` 参数。
|
|
194
|
+
|
|
195
|
+
**步骤 4:返回测试结果**
|
|
196
|
+
|
|
197
|
+
将 curl 的响应内容返回给用户。如果是 JSON 格式,尽量格式化输出。
|
|
198
|
+
|
|
199
|
+
## 快捷方式:AI 直接识别 10 段地址
|
|
200
|
+
|
|
201
|
+
如果 AI 能识别出用户请求的 URL 中包含 `10.x.x.x` 主机地址,可以**直接跳过首次 api_tool 调用**,在本地执行 curl,再调用 `parse_swagger_json`,减少一次无用的服务端请求:
|
|
202
|
+
|
|
203
|
+
1. 从 Swagger URL 推导鉴权地址、文档地址
|
|
204
|
+
2. 通过 `run_command` 在本地依次 curl
|
|
205
|
+
3. 调用 `api_tool(action="parse_swagger_json")` 解析
|
|
206
|
+
|
|
207
|
+
### URL 推导规则
|
|
208
|
+
|
|
209
|
+
对于 Swagger URL 如 `http://10.2.7.60:30194/ikmp-knowledge/swagger-ui/index.html`:
|
|
210
|
+
|
|
211
|
+
| 推导项 | 规则 | 示例 |
|
|
212
|
+
|--------|------|------|
|
|
213
|
+
| basePath | `protocol + host + 第一个路径段` | `http://10.2.7.60:30194/ikmp-knowledge` |
|
|
214
|
+
| authUrl | `basePath + /swagger-resources/configuration/ui` | `http://10.2.7.60:30194/ikmp-knowledge/swagger-resources/configuration/ui` |
|
|
215
|
+
| apiDocsUrl | `basePath + /v2/api-docs` | `http://10.2.7.60:30194/ikmp-knowledge/v2/api-docs` |
|
|
216
|
+
|
|
217
|
+
### curl 执行模板
|
|
218
|
+
|
|
219
|
+
**鉴权(带密码时):**
|
|
220
|
+
```bash
|
|
221
|
+
curl -s -D - -X GET '<authUrl>' \
|
|
222
|
+
-H 'Authorization: Basic <base64(username:password)>' \
|
|
223
|
+
-H 'Accept: application/json'
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
**获取文档(带 Cookie 时):**
|
|
227
|
+
```bash
|
|
228
|
+
curl -s -X GET '<apiDocsUrl>' \
|
|
229
|
+
-H 'Accept: application/json' \
|
|
230
|
+
-H 'Cookie: <鉴权获取的Cookie>'
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
**获取文档(无密码时):**
|
|
234
|
+
```bash
|
|
235
|
+
curl -s -X GET '<apiDocsUrl>' \
|
|
236
|
+
-H 'Accept: application/json'
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
## 注意事项
|
|
240
|
+
|
|
241
|
+
1. **Cookie 传递**:鉴权步骤获取的 Cookie 需要在后续步骤中通过 `-H 'Cookie: xxx'` 传递
|
|
242
|
+
2. **无密码场景**:如果用户未提供密码,跳过鉴权步骤,直接获取文档
|
|
243
|
+
3. **JSON 格式**:curl 获取的 API 文档必须是有效的 Swagger JSON,否则 `parse_swagger_json` 会报错
|
|
244
|
+
4. **超时处理**:本地 curl 可能因网络原因超时,建议添加 `--connect-timeout 10 --max-time 30` 参数
|
|
245
|
+
5. **错误处理**:如果某步骤 curl 失败,将错误信息返回给用户,不要继续后续步骤
|
|
246
|
+
6. **自动执行**:AI 识别 `reason: "LOCAL_NETWORK_UNREACHABLE"` 后应**自动**通过 `run_command` 执行 curl,无需让用户手动复制粘贴命令
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: local-browser-test
|
|
3
|
+
description: 本地浏览器测试工具,在用户本地 Chrome 中执行自动化测试。支持设计稿还原度对比(像素级评分)、功能验证(点击/输入/导航)、DOM 结构检查、JS 错误检测。通过 puppeteer-core 连接用户真实浏览器,天然继承登录态和微前端环境。
|
|
4
|
+
compatibility:
|
|
5
|
+
tools:
|
|
6
|
+
- run_command
|
|
7
|
+
- read_file
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# 本地浏览器测试工具(local-browser-test)
|
|
11
|
+
|
|
12
|
+
## 触发条件
|
|
13
|
+
|
|
14
|
+
当用户说出以下关键词时启动浏览器测试:
|
|
15
|
+
- "测试一下"、"帮我验证"、"浏览器测试"
|
|
16
|
+
- "对比设计稿"、"还原度"、"看看效果"
|
|
17
|
+
- "功能测试"、"能不能正常用"
|
|
18
|
+
- "检查页面"、"页面渲染"
|
|
19
|
+
|
|
20
|
+
## 浏览器模式
|
|
21
|
+
|
|
22
|
+
支持两种模式,执行器自动检测优先使用 connect:
|
|
23
|
+
|
|
24
|
+
### connect 模式(推荐)
|
|
25
|
+
连接用户已启动的调试 Chrome,天然继承登录态/微前端环境。
|
|
26
|
+
|
|
27
|
+
用户一次性配置:
|
|
28
|
+
```bash
|
|
29
|
+
# macOS
|
|
30
|
+
open -a "Google Chrome" --args --remote-debugging-port=9222
|
|
31
|
+
# Linux
|
|
32
|
+
google-chrome --remote-debugging-port=9222
|
|
33
|
+
# Windows
|
|
34
|
+
"C:\Program Files\Google\Chrome\Application\chrome.exe" --remote-debugging-port=9222
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### launch 模式(回退)
|
|
38
|
+
启动新的 headless Chrome,需要用户提供可直接访问的 URL。
|
|
39
|
+
|
|
40
|
+
执行器会自动检测 localhost:9222 是否可用,有则 connect,无则 launch。
|
|
41
|
+
|
|
42
|
+
## 测试计划 Schema
|
|
43
|
+
|
|
44
|
+
AI 直接构造测试计划 JSON:
|
|
45
|
+
|
|
46
|
+
```json
|
|
47
|
+
{
|
|
48
|
+
"planId": "<uuid>",
|
|
49
|
+
"description": "<测试描述>",
|
|
50
|
+
"steps": [ ... ],
|
|
51
|
+
"options": {
|
|
52
|
+
"viewport": { "width": 1440, "height": 900 },
|
|
53
|
+
"timeout": 120000,
|
|
54
|
+
"outputDir": "/tmp/browser-test-<planId>"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### 支持的步骤类型
|
|
60
|
+
|
|
61
|
+
| type | 功能 | 参数 | 返回 |
|
|
62
|
+
|------|------|------|------|
|
|
63
|
+
| navigate | 导航到 URL | `url`, `waitUntil` | `{ url, title, loadTime }` |
|
|
64
|
+
| screenshot | 页面截图 | `fullPage`, `clip`, `filename` | `{ filepath, width, height }` |
|
|
65
|
+
| compare | 设计稿对比 | `designImagePath`, `threshold`, `regions[]` | `{ fidelity, diffPixels, diffImagePath, regions[] }` |
|
|
66
|
+
| click | 点击元素 | `selector`, `waitForNavigation` | `{ clicked, tagName }` |
|
|
67
|
+
| type | 输入文本 | `selector`, `text`, `clear` | `{ typed, value }` |
|
|
68
|
+
| waitFor | 等待条件 | `selector` 或 `url`, `timeout` | `{ matched, waited }` |
|
|
69
|
+
| evaluate | 执行 JS | `expression` | `{ result }` |
|
|
70
|
+
| checkElement | 元素断言 | `selector`, `expect: {visible, minCount, maxCount, hasText}` | `{ passed, checks, elementCount }` |
|
|
71
|
+
| findPage | 查找已打开的标签页 | `urlIncludes` 或 `urlRegex` 或 `titleIncludes` | `{ found, url, title, totalPages }` |
|
|
72
|
+
| findFrame | 查找微前端 iframe | `urlIncludes` 或 `urlRegex` 或 `titleIncludes` 或 `contentIncludes` 或 `frameIndex` | `{ found, frameUrl, matchedBy, totalFrames }` |
|
|
73
|
+
| loginWait | 等待 SSO 登录完成 | `loginSelector`, `timeout`, `interval` | `{ loggedIn, elapsed, currentUrl }` |
|
|
74
|
+
| aiAssert | 语义断言(截图+审查) | `assertion`, `filename` | `{ screenshotPath, assertion, needsVisualReview }` |
|
|
75
|
+
|
|
76
|
+
### 场景选择策略
|
|
77
|
+
|
|
78
|
+
根据上下文自动选择步骤组合:
|
|
79
|
+
|
|
80
|
+
**场景 A:有设计稿 + 功能验证(最完整)**
|
|
81
|
+
`navigate → waitFor → screenshot → compare → 功能步骤`
|
|
82
|
+
|
|
83
|
+
**场景 B:无设计稿 + 功能验证**
|
|
84
|
+
`navigate → waitFor → screenshot → evaluate(JS错误检查) → 功能步骤`
|
|
85
|
+
|
|
86
|
+
**场景 C:仅视觉对比**
|
|
87
|
+
`navigate → waitFor → screenshot → compare`
|
|
88
|
+
|
|
89
|
+
**场景 D:仅页面检查**
|
|
90
|
+
`navigate → waitFor → screenshot → evaluate(JS错误检查)`
|
|
91
|
+
|
|
92
|
+
**场景 E:微前端 + SSO 登录(最常见生产场景)**
|
|
93
|
+
`findPage(按路径发现标签页) → findFrame(按内容发现iframe) → 在 iframe 内执行功能步骤`
|
|
94
|
+
|
|
95
|
+
> 微前端场景端口可变,优先用 `findPage` + `findFrame` 发现页面,而非硬编码 URL 导航。
|
|
96
|
+
|
|
97
|
+
### 构造步骤的指导原则
|
|
98
|
+
|
|
99
|
+
1. **selector 必须从用户代码中读取,不要猜测**
|
|
100
|
+
- 读代码中的 `id`、`className`、`data-testid`
|
|
101
|
+
- szcd 组件的 class 可通过 `get_component_full_profile` 获取
|
|
102
|
+
2. **API 路径从代码中的接口调用读取**
|
|
103
|
+
3. **checkElement 的 expect 从代码逻辑推断**
|
|
104
|
+
- columns 定义了 N 列 → `expect: { minCount: N }`(检查 th 数量)
|
|
105
|
+
- dataSource 有数据 → `expect: { minCount: 1 }`(检查行数)
|
|
106
|
+
4. **compare 的 regions 从设计稿分析结果或组件布局推断**
|
|
107
|
+
|
|
108
|
+
## 微前端测试指南(实战经验)
|
|
109
|
+
|
|
110
|
+
项目多使用 wujie/qiankun 微前端,测试时需注意:
|
|
111
|
+
|
|
112
|
+
### 端口可变问题(重点)
|
|
113
|
+
微前端主应用和子应用的端口会变化,**不要在计划中硬编码 URL**。两种方案:
|
|
114
|
+
|
|
115
|
+
**方案 A:`findPage` 发现已打开的标签页(推荐,connect 模式)**
|
|
116
|
+
用户已在浏览器中打开了目标页面,按路径或标题发现:
|
|
117
|
+
```json
|
|
118
|
+
{ "type": "findPage", "urlIncludes": "knowledge-main" }
|
|
119
|
+
{ "type": "findPage", "titleIncludes": "知识库" }
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
**方案 B:URL 模板变量 + `--base-url` 运行时传入**
|
|
123
|
+
计划中使用占位符,运行时替换:
|
|
124
|
+
```json
|
|
125
|
+
{ "type": "navigate", "url": "{{baseUrl}}/knowledge-main/", "allowRedirects": true }
|
|
126
|
+
```
|
|
127
|
+
执行时传入:`--base-url http://localhost:9090`
|
|
128
|
+
|
|
129
|
+
### SSO 登录重定向链
|
|
130
|
+
主应用访问时会重定向到登录服务,登录后再跳回。
|
|
131
|
+
|
|
132
|
+
```json
|
|
133
|
+
{ "type": "navigate", "url": "{{baseUrl}}/app/", "allowRedirects": true, "redirectWait": 5000 }
|
|
134
|
+
{ "type": "loginWait", "timeout": 120000, "loginSelector": "input[type='password']" }
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
- `allowRedirects: true` 使用 `domcontentloaded` 避免重定向链导致 page 对象销毁
|
|
138
|
+
- `loginWait` 轮询检测登录页是否消失(用户手动登录)
|
|
139
|
+
|
|
140
|
+
### iframe 内操作
|
|
141
|
+
wujie 微前端子应用渲染在 blob URL iframe 中,**坐标点击无法到达 iframe 内容**。
|
|
142
|
+
|
|
143
|
+
```json
|
|
144
|
+
{ "type": "findFrame", "contentIncludes": "知识采编" }
|
|
145
|
+
{ "type": "click", "selector": "a.edit-btn" }
|
|
146
|
+
{ "type": "checkElement", "selector": "~.editKnowledge", "expect": { "minCount": 1 } }
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
- `findFrame` 支持 `urlIncludes`、`titleIncludes`、`contentIncludes` 多种发现方式
|
|
150
|
+
- 优先用 `contentIncludes`(按页面文字内容查找),不依赖 URL/端口
|
|
151
|
+
- `findFrame` 后,后续所有步骤自动在 iframe 内执行
|
|
152
|
+
- iframe 内的 click 使用 `evaluate()` 触发 DOM click,不依赖坐标
|
|
153
|
+
|
|
154
|
+
### CSS Modules 哈希类名
|
|
155
|
+
项目使用 CSS Modules,类名会被哈希化(如 `editKnowledge___ynQgI`)。
|
|
156
|
+
|
|
157
|
+
选择器语法:
|
|
158
|
+
- `~.editKnowledge` → 自动转为 `[class*="editKnowledge"]`(模糊匹配)
|
|
159
|
+
- `~#myId` → 自动转为 `[id*="myId"]`
|
|
160
|
+
- 普通选择器(如 `.my-class`)保持精确匹配
|
|
161
|
+
|
|
162
|
+
### puppeteer-core v22 注意事项
|
|
163
|
+
- `page.$x()` 已移除,用 `evaluate` + `querySelectorAll` 替代
|
|
164
|
+
- SVG 元素的 `className` 是 `SVGAnimatedString` 对象而非字符串,需先检查 `typeof`
|
|
165
|
+
- 所有 `evaluate` 调用内部使用 `safeEval` 包装,自动处理 context destroyed 错误
|
|
166
|
+
|
|
167
|
+
## 执行命令
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
node {client_install_path}/local-browser-executor.js \
|
|
171
|
+
--plan '<测试计划JSON>' \
|
|
172
|
+
--output /tmp/browser-test-result.json \
|
|
173
|
+
--base-url http://localhost:8088
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
也支持从文件读取计划:
|
|
177
|
+
```bash
|
|
178
|
+
node {client_install_path}/local-browser-executor.js \
|
|
179
|
+
--plan-file /tmp/test-plan.json \
|
|
180
|
+
--output /tmp/browser-test-result.json \
|
|
181
|
+
--base-url http://localhost:8088
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### 可选参数
|
|
185
|
+
|
|
186
|
+
- `--cdp-url http://localhost:9223`:指定非默认的 CDP 端口
|
|
187
|
+
- `--page-url http://xxx/page`:connect 模式下指定目标页面
|
|
188
|
+
|
|
189
|
+
## AI 语义断言(aiAssert)
|
|
190
|
+
|
|
191
|
+
`aiAssert` 截图后需要视觉审查,三级降级路由:
|
|
192
|
+
|
|
193
|
+
### Level 1:你能读取图片
|
|
194
|
+
直接读取截图文件,用你的视觉能力判断是否满足断言。
|
|
195
|
+
|
|
196
|
+
### Level 2:你无法读图,但 MCP 服务器可用
|
|
197
|
+
1. 调用 `get_upload_endpoint` 获取上传地址
|
|
198
|
+
2. 用 curl 上传截图
|
|
199
|
+
3. 调用 `assert_page_screenshot` 工具,传入 `upload_id` + 断言描述
|
|
200
|
+
4. 工具返回 `{ passed, confidence, reasoning, details }`
|
|
201
|
+
|
|
202
|
+
### Level 3:都没有
|
|
203
|
+
降级为 `checkElement` 步骤,用 DOM 选择器做替代验证。
|
|
204
|
+
|
|
205
|
+
**构造 aiAssert 步骤示例**:
|
|
206
|
+
```json
|
|
207
|
+
{ "type": "aiAssert", "assertion": "页面显示三栏布局,右侧有滚动条", "filename": "assert-layout.png" }
|
|
208
|
+
{ "type": "aiAssert", "assertion": "表格至少有 5 行数据,包含姓名、手机号列" }
|
|
209
|
+
{ "type": "aiAssert", "assertion": "表单包含必填标记和提交按钮" }
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## 依赖管理
|
|
213
|
+
|
|
214
|
+
浏览器测试依赖(puppeteer-core, pixelmatch, pngjs, sharp)采用**共享缓存**方案:
|
|
215
|
+
- 首次使用时自动安装到 `~/.szcd-mcp/deps/`
|
|
216
|
+
- 后续所有项目共享复用,无需重复安装
|
|
217
|
+
- 如果自动安装失败,引导用户手动执行:
|
|
218
|
+
```bash
|
|
219
|
+
cd ~/.szcd-mcp/deps && npm install puppeteer-core pixelmatch pngjs sharp --registry=https://npmmirror.com
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
## 结果解读
|
|
223
|
+
|
|
224
|
+
### 还原度评分
|
|
225
|
+
- fidelity >= 95%:还原度优秀
|
|
226
|
+
- fidelity 90-95%:还原度良好,有优化空间
|
|
227
|
+
- fidelity < 90%:还原度不足,需要调整
|
|
228
|
+
|
|
229
|
+
### 功能测试
|
|
230
|
+
- passed = total:全部通过
|
|
231
|
+
- 有失败步骤:根据 `step` + `error` 字段定位代码问题
|
|
232
|
+
|
|
233
|
+
### 结果文件结构
|
|
234
|
+
```json
|
|
235
|
+
{
|
|
236
|
+
"planId": "xxx",
|
|
237
|
+
"mode": "connect",
|
|
238
|
+
"steps": [
|
|
239
|
+
{ "index": 1, "step": "navigate", "status": "PASS", "duration": 1200, ... },
|
|
240
|
+
{ "index": 2, "step": "screenshot", "status": "PASS", "duration": 500, ... },
|
|
241
|
+
{ "index": 3, "step": "compare", "status": "PASS", "fidelity": 94.7, ... }
|
|
242
|
+
],
|
|
243
|
+
"summary": {
|
|
244
|
+
"total": 5, "passed": 4, "failed": 1,
|
|
245
|
+
"fidelity": 94.7
|
|
246
|
+
},
|
|
247
|
+
"outputDir": "/tmp/browser-test-xxx"
|
|
248
|
+
}
|
|
249
|
+
```
|