@szc-ft/mcp-szcd-client 0.18.0 → 0.19.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.
@@ -0,0 +1,239 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * 本地浏览器测试执行器 - CLI 入口
4
+ *
5
+ * 用法:
6
+ * node local-browser-executor.js --plan '<JSON>' --output /tmp/result.json
7
+ * node local-browser-executor.js --plan-file /path/to/plan.json --output /tmp/result.json
8
+ *
9
+ * 参数:
10
+ * --plan 测试计划 JSON 字符串
11
+ * --plan-file 测试计划 JSON 文件路径(与 --plan 二选一)
12
+ * --output 结果输出文件路径(默认 stdout)
13
+ * --cdp-url Chrome DevTools Protocol URL(默认自动检测 localhost:9222)
14
+ * --page-url 连接已有 Chrome 时的目标页面 URL
15
+ *
16
+ * 退出码:
17
+ * 0 - 全部步骤执行成功
18
+ * 1 - 部分步骤失败
19
+ * 2 - 环境错误(Chrome 未安装 / 依赖缺失 / 无法连接)
20
+ */
21
+
22
+ import fs from "node:fs";
23
+ import path from "node:path";
24
+ import { fileURLToPath } from "node:url";
25
+ import { BrowserEngine } from "./lib/browser-engine.js";
26
+
27
+ const __filename = fileURLToPath(import.meta.url);
28
+ const __dirname = path.dirname(__filename);
29
+
30
+ // ==================== 参数解析 ====================
31
+
32
+ function parseArgs() {
33
+ const args = process.argv.slice(2);
34
+ const parsed = {};
35
+
36
+ for (let i = 0; i < args.length; i++) {
37
+ switch (args[i]) {
38
+ case "--plan":
39
+ parsed.planJson = args[++i];
40
+ break;
41
+ case "--plan-file":
42
+ parsed.planFile = args[++i];
43
+ break;
44
+ case "--output":
45
+ parsed.output = args[++i];
46
+ break;
47
+ case "--cdp-url":
48
+ parsed.cdpUrl = args[++i];
49
+ break;
50
+ case "--page-url":
51
+ parsed.pageUrl = args[++i];
52
+ break;
53
+ case "--help":
54
+ case "-h":
55
+ printHelp();
56
+ process.exit(0);
57
+ }
58
+ }
59
+ return parsed;
60
+ }
61
+
62
+ function printHelp() {
63
+ console.log(`
64
+ 本地浏览器测试执行器
65
+
66
+ 用法:
67
+ node local-browser-executor.js --plan '<JSON>' [options]
68
+ node local-browser-executor.js --plan-file <path> [options]
69
+
70
+ 选项:
71
+ --plan <json> 测试计划 JSON 字符串
72
+ --plan-file <path> 测试计划 JSON 文件路径
73
+ --output <path> 结果输出文件路径(默认 stdout)
74
+ --cdp-url <url> Chrome DevTools Protocol URL(默认 http://localhost:9222)
75
+ --page-url <url> 目标页面 URL(connect 模式时匹配标签页)
76
+ -h, --help 显示帮助
77
+ `);
78
+ }
79
+
80
+ function loadPlan(args) {
81
+ if (args.planJson) {
82
+ return JSON.parse(args.planJson);
83
+ }
84
+ if (args.planFile) {
85
+ const content = fs.readFileSync(args.planFile, "utf8");
86
+ return JSON.parse(content);
87
+ }
88
+ throw new Error("必须提供 --plan 或 --plan-file 参数");
89
+ }
90
+
91
+ // ==================== 执行引擎 ====================
92
+
93
+ async function executePlan(plan, options) {
94
+ const engine = new BrowserEngine();
95
+ const results = {
96
+ planId: plan.planId || "unknown",
97
+ description: plan.description || "",
98
+ mode: null,
99
+ steps: [],
100
+ summary: {
101
+ total: plan.steps.length,
102
+ passed: 0,
103
+ failed: 0,
104
+ fidelity: null,
105
+ },
106
+ startTime: new Date().toISOString(),
107
+ };
108
+
109
+ const outputDir = plan.options?.outputDir || path.join("/tmp", `browser-test-${results.planId}`);
110
+ if (!fs.existsSync(outputDir)) {
111
+ fs.mkdirSync(outputDir, { recursive: true });
112
+ }
113
+
114
+ try {
115
+ // 初始化浏览器
116
+ const initResult = await engine.init({
117
+ cdpUrl: options.cdpUrl,
118
+ pageUrl: options.pageUrl || plan.options?.pageUrl,
119
+ viewport: plan.options?.viewport,
120
+ });
121
+ results.mode = initResult.mode;
122
+ results.browserInfo = initResult;
123
+
124
+ // 如果计划中有 navigate 步骤且当前是 connect 模式,导航到目标页面
125
+ const navigateStep = plan.steps.find((s) => s.type === "navigate");
126
+
127
+ // 逐步执行
128
+ for (let i = 0; i < plan.steps.length; i++) {
129
+ const step = plan.steps[i];
130
+ const stepResult = await engine.executeStep(step, { outputDir });
131
+ stepResult.index = i + 1;
132
+ results.steps.push(stepResult);
133
+
134
+ if (stepResult.status === "PASS") {
135
+ results.summary.passed++;
136
+ } else {
137
+ results.summary.failed++;
138
+ }
139
+
140
+ // 提取 fidelity
141
+ if (step.type === "compare" && stepResult.status === "PASS" && stepResult.fidelity !== undefined) {
142
+ results.summary.fidelity = stepResult.fidelity;
143
+ }
144
+
145
+ // 输出进度
146
+ const status = stepResult.status === "PASS" ? "✓" : "✗";
147
+ process.stderr.write(
148
+ ` ${status} Step ${i + 1}/${plan.steps.length} ${step.type}` +
149
+ (step.selector ? ` (${step.selector})` : "") +
150
+ (stepResult.duration ? ` [${stepResult.duration}ms]` : "") +
151
+ "\n"
152
+ );
153
+ }
154
+ } catch (err) {
155
+ results.error = err.message;
156
+ results.errorCode = err.code || "UNKNOWN";
157
+
158
+ if (err.code === "CHROME_NOT_FOUND" || err.code === "DEPENDENCY_MISSING") {
159
+ results.installGuide = err.message;
160
+ }
161
+ } finally {
162
+ await engine.close();
163
+ }
164
+
165
+ results.endTime = new Date().toISOString();
166
+ results.duration = new Date(results.endTime) - new Date(results.startTime);
167
+ results.outputDir = outputDir;
168
+
169
+ return results;
170
+ }
171
+
172
+ // ==================== 主流程 ====================
173
+
174
+ async function main() {
175
+ const args = parseArgs();
176
+
177
+ let plan;
178
+ try {
179
+ plan = loadPlan(args);
180
+ } catch (err) {
181
+ const errorResult = {
182
+ error: err.message,
183
+ errorCode: "INVALID_PLAN",
184
+ };
185
+ outputResult(errorResult, args.output);
186
+ process.exit(2);
187
+ }
188
+
189
+ process.stderr.write(`\n浏览器测试执行器\n`);
190
+ process.stderr.write(`计划: ${plan.description || plan.planId || "unnamed"}\n`);
191
+ process.stderr.write(`步骤: ${plan.steps?.length || 0}\n\n`);
192
+
193
+ const results = await executePlan(plan, {
194
+ cdpUrl: args.cdpUrl,
195
+ pageUrl: args.pageUrl,
196
+ });
197
+
198
+ // 输出结果
199
+ outputResult(results, args.output);
200
+
201
+ // 输出摘要
202
+ process.stderr.write(`\n执行完成:\n`);
203
+ process.stderr.write(` 模式: ${results.mode || "failed"}\n`);
204
+ process.stderr.write(` 通过: ${results.summary.passed}/${results.summary.total}\n`);
205
+ process.stderr.write(` 失败: ${results.summary.failed}/${results.summary.total}\n`);
206
+ if (results.summary.fidelity !== null) {
207
+ process.stderr.write(` 还原度: ${results.summary.fidelity}%\n`);
208
+ }
209
+ process.stderr.write(` 耗时: ${results.duration}ms\n\n`);
210
+
211
+ // 退出码
212
+ if (results.error) {
213
+ process.exit(2);
214
+ } else if (results.summary.failed > 0) {
215
+ process.exit(1);
216
+ } else {
217
+ process.exit(0);
218
+ }
219
+ }
220
+
221
+ function outputResult(results, outputPath) {
222
+ const json = JSON.stringify(results, null, 2);
223
+
224
+ if (outputPath) {
225
+ const dir = path.dirname(outputPath);
226
+ if (!fs.existsSync(dir)) {
227
+ fs.mkdirSync(dir, { recursive: true });
228
+ }
229
+ fs.writeFileSync(outputPath, json, "utf8");
230
+ process.stderr.write(`结果已写入: ${outputPath}\n`);
231
+ } else {
232
+ process.stdout.write(json + "\n");
233
+ }
234
+ }
235
+
236
+ main().catch((err) => {
237
+ process.stderr.write(`Fatal error: ${err.message}\n`);
238
+ process.exit(2);
239
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@szc-ft/mcp-szcd-client",
3
- "version": "0.18.0",
3
+ "version": "0.19.0",
4
4
  "description": "MCP client for szcd component library - auto-configures AI coding tools with MCP server, skills, agents and commands",
5
5
  "keywords": [
6
6
  "mcp",
@@ -29,6 +29,8 @@
29
29
  },
30
30
  "files": [
31
31
  "mcp-proxy.js",
32
+ "local-browser-executor.js",
33
+ "lib/",
32
34
  "scripts/postinstall.js",
33
35
  "scripts/update-mcp-url.js",
34
36
  "scripts/update-coding-config.js",
@@ -55,6 +57,12 @@
55
57
  "dependencies": {
56
58
  "@modelcontextprotocol/sdk": "^1.29.0"
57
59
  },
60
+ "optionalDependencies": {
61
+ "puppeteer-core": "^22.15.0",
62
+ "pixelmatch": "^5.3.0",
63
+ "pngjs": "^7.0.0",
64
+ "sharp": "^0.33.5"
65
+ },
58
66
  "repository": {
59
67
  "type": "git",
60
68
  "url": "git+https://github.com/szc-ft/mcp-szcd.git"
@@ -113,6 +113,28 @@ szcd 是基于 Ant Design 5.27 封装的企业级 React 组件库,采用分层
113
113
  - "代码已生成,是否需要调整样式或交互细节?"
114
114
  - "是否需要补充表单校验规则?"
115
115
  - "是否需要添加批量操作功能?"
116
+ - "是否需要进行浏览器验证?(在本地浏览器中检查还原度和功能)"
117
+
118
+
119
+ ### 步骤6.5:浏览器验证(条件执行)
120
+
121
+ 当用户在步骤6中选择进行浏览器验证时执行此步骤。
122
+
123
+ 1. **提取代码上下文**:你已经掌握生成的代码结构(组件、selector、API),直接用于构造测试计划
124
+ 2. **询问用户**:目标页面 URL(如果还不知道)和设计稿(如有)
125
+ 3. **构造测试计划 JSON**:按 `local-browser-test` skill 中定义的 Plan Schema,从代码中提取真实 selector 和 API 路径
126
+ 4. **执行测试**:在用户终端执行:
127
+ ```bash
128
+ node {client_path}/local-browser-executor.js --plan '<计划JSON>' --output /tmp/browser-test-result.json
129
+ ```
130
+ 5. **解读结果**:读取 /tmp/browser-test-result.json,分析还原度评分和功能测试通过情况
131
+ 6. **闭环修复**:如有问题,主动修复代码并询问是否重新测试
132
+
133
+ **构造测试计划的要点**:
134
+ - selector 从生成的代码中读取(id/className/data-testid),不要猜测
135
+ - API 路径从代码中的接口调用读取
136
+ - checkElement 的 expect 从代码逻辑推断(columns 数量、数据行数等)
137
+ - 有设计稿时添加 compare 步骤,无设计稿时跳过
116
138
 
117
139
  ### 步骤7:收集用户反馈(必做,质量闭环)
118
140
 
@@ -0,0 +1,57 @@
1
+ ---
2
+ description: 在本地浏览器中执行自动化测试(设计稿还原度对比/功能验证/页面检查)
3
+ argument-hint: <目标页面URL> [可选:设计稿图片路径]
4
+ ---
5
+
6
+ 请帮我在本地浏览器中执行测试。
7
+
8
+ **执行流程**:
9
+
10
+ 1. **收集测试信息**:
11
+
12
+ - **目标页面 URL**(必需):如果用户在命令参数中已提供,直接使用;否则向用户询问要测试的页面地址
13
+ - **设计稿图片**(可选):如果用户在命令参数中提供了图片路径,用于还原度对比;否则跳过视觉对比
14
+ - **测试范围**:向用户提供选项:
15
+ - "设计稿还原度对比"(需提供设计稿)
16
+ - "功能验证"(点击/输入/导航等交互测试)
17
+ - "页面检查"(JS 错误检测 + 页面截图)
18
+ - "全部测试"(以上全部)
19
+
20
+ 2. **读取 `local-browser-test` skill 文档**,了解 Plan Schema 和执行命令格式:
21
+ - 按 skill 中定义的步骤类型构造测试计划 JSON
22
+ - selector 从用户项目代码中读取(`id`/`className`/`data-testid`),不要猜测
23
+ - 根据用户选择的测试范围选择对应场景(A/B/C/D)
24
+
25
+ 3. **构造测试计划 JSON** 并写入临时文件(避免命令行参数过长):
26
+ ```bash
27
+ cat > /tmp/browser-test-plan.json << 'EOF'
28
+ {测试计划 JSON}
29
+ EOF
30
+ ```
31
+
32
+ 4. **执行测试**:
33
+ ```bash
34
+ node {client_install_path}/local-browser-executor.js \
35
+ --plan-file /tmp/browser-test-plan.json \
36
+ --output /tmp/browser-test-result.json
37
+ ```
38
+
39
+ 命令回退(如果上面的路径不可用):
40
+ - `npx szcd-mcp-browser-test --plan-file /tmp/browser-test-plan.json --output /tmp/browser-test-result.json`
41
+ - `node $(npm root -g)/@szc-ft/mcp-szcd-client/local-browser-executor.js --plan-file /tmp/browser-test-plan.json --output /tmp/browser-test-result.json`
42
+ - `node /scity/zengzhijie/mcp/szcd-mcp-client/local-browser-executor.js --plan-file /tmp/browser-test-plan.json --output /tmp/browser-test-result.json`
43
+
44
+ 5. **读取并解读结果**:
45
+ 读取 `/tmp/browser-test-result.json`,按以下标准解读:
46
+ - **还原度**:fidelity >= 95% 优秀 / 90-95% 良好 / < 90% 不足
47
+ - **功能测试**:passed = total 全部通过,否则列出失败步骤及原因
48
+ - **diff 图**:如有 diffImagePath,展示 diff 图路径供用户查看
49
+
50
+ 6. **闭环建议**:
51
+ - 如有问题,给出具体的修复建议
52
+ - 询问用户是否需要重新测试
53
+
54
+ **注意事项**:
55
+ - 首次使用需确保 Chrome 以调试模式启动:`google-chrome --remote-debugging-port=9222`
56
+ - 如执行器报错依赖缺失,引导用户安装:`npm install puppeteer-core pixelmatch pngjs sharp`
57
+ - connect 模式下天然继承用户浏览器的登录态和微前端环境
@@ -0,0 +1,42 @@
1
+ ---
2
+ description: 提交本次会话的代码生成反馈,用于优化 szcd MCP 服务质量
3
+ argument-hint: [可选:评分1-5] [可选:采纳/不采纳]
4
+ ---
5
+
6
+ 请帮我提交本次会话的反馈。
7
+
8
+ **执行流程**:
9
+
10
+ 1. **自动收集上下文**:从当前会话历史中提取以下信息,**不要重复询问用户已经说过的内容**:
11
+ - `userQuery`:用户最初的原始需求描述(从会话开头的消息中提取)
12
+ - `toolsUsed`:本次会话中实际调用过的 MCP 工具名称列表(从工具调用记录中提取)
13
+ - `generatedCodeSummary`:最近一次生成的代码摘要或关键组件列表(从生成的代码中提取前几行或核心结构,不超过 500 字)
14
+ - `toolType`:当前使用的 IDE 类型(根据运行环境判断:Qoder → "qoder",Trae → "trae",Claude → "claude",Qwen → "qwen",Cursor → "cursor",VS Code → "vscode",其他 → "generic")
15
+
16
+ 2. **向用户确认或补充**(仅询问缺失的信息):
17
+ - **准确性评分**:请用户评分 1-5 分(5 分最准确)。如果用户在命令参数中已提供,直接使用
18
+ - **是否采纳**:用户是否计划采纳生成的代码。如果用户在命令参数中已提供("采纳"/"不采纳"),直接使用
19
+ - **如不采纳**:询问具体原因(组件不匹配、API 错误、缺少功能、不符合设计稿等)
20
+
21
+ 3. **调用 MCP 工具提交反馈**:
22
+ 调用 `submit_feedback` 工具,参数如下:
23
+ ```
24
+ sessionId: "<当前会话 ID 或时间戳>"
25
+ toolsUsed: [自动提取的工具列表]
26
+ userQuery: "<自动提取的用户需求>"
27
+ generatedCodeSummary: "<自动提取的代码摘要>"
28
+ accuracyRating: <用户评分 1-5>
29
+ adopted: <true/false>
30
+ rejectionReason: "<不采纳原因,采纳则为空>"
31
+ contextSnapshot: {
32
+ 可选:从会话中提取的组件方案信息
33
+ }
34
+ toolType: "<自动判断的 IDE 类型>"
35
+ ```
36
+
37
+ 4. **输出结果**:告知用户反馈是否提交成功,显示 feedbackId
38
+
39
+ **注意事项**:
40
+ - 如果当前会话中没有代码生成记录,提示用户"当前会话中没有代码生成记录,反馈提交需要有生成代码的上下文"
41
+ - 尽可能从会话历史自动提取信息,减少用户手动输入
42
+ - 反馈提交失败不影响已生成的代码
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "szcd-component-helper",
3
- "version": "0.18.0",
3
+ "version": "0.19.0",
4
4
  "description": "szcd 组件库 MCP 助手 — 查询组件信息、匹配需求、生成代码",
5
5
  "mcpServers": {
6
6
  "szcd-component-helper": {
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: local-api-tool
3
- description: 本地网络 API 工具,专门处理 10.x.x.x 网段的 Swagger/YApi 文档拉取和联调测试。当服务端 api_tool 因 LOCAL_NETWORK_UNREACHABLE 无法访问时,此技能在用户本地环境执行 curl,再将结果交给服务端解析。适用于内网开发环境、VPN 场景。
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
4
  compatibility:
5
5
  tools:
6
6
  - run_command
@@ -20,6 +20,19 @@ compatibility:
20
20
  - API 联调测试目标为 `10.x.x.x` 网段
21
21
  - 用户在 VPN 或内网开发环境中,本地可访问但远程服务器不可达
22
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
+
23
36
  ## 工作流
24
37
 
25
38
  ### 流程 A:拉取 Swagger API 文档(fetch)
@@ -0,0 +1,153 @@
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
+
72
+ ### 场景选择策略
73
+
74
+ 根据上下文自动选择步骤组合:
75
+
76
+ **场景 A:有设计稿 + 功能验证(最完整)**
77
+ `navigate → waitFor → screenshot → compare → 功能步骤`
78
+
79
+ **场景 B:无设计稿 + 功能验证**
80
+ `navigate → waitFor → screenshot → evaluate(JS错误检查) → 功能步骤`
81
+
82
+ **场景 C:仅视觉对比**
83
+ `navigate → waitFor → screenshot → compare`
84
+
85
+ **场景 D:仅页面检查**
86
+ `navigate → waitFor → screenshot → evaluate(JS错误检查)`
87
+
88
+ ### 构造步骤的指导原则
89
+
90
+ 1. **selector 必须从用户代码中读取,不要猜测**
91
+ - 读代码中的 `id`、`className`、`data-testid`
92
+ - szcd 组件的 class 可通过 `get_component_full_profile` 获取
93
+ 2. **API 路径从代码中的接口调用读取**
94
+ 3. **checkElement 的 expect 从代码逻辑推断**
95
+ - columns 定义了 N 列 → `expect: { minCount: N }`(检查 th 数量)
96
+ - dataSource 有数据 → `expect: { minCount: 1 }`(检查行数)
97
+ 4. **compare 的 regions 从设计稿分析结果或组件布局推断**
98
+
99
+ ## 执行命令
100
+
101
+ ```bash
102
+ node {client_install_path}/local-browser-executor.js \
103
+ --plan '<测试计划JSON>' \
104
+ --output /tmp/browser-test-result.json
105
+ ```
106
+
107
+ 也支持从文件读取计划:
108
+ ```bash
109
+ node {client_install_path}/local-browser-executor.js \
110
+ --plan-file /tmp/test-plan.json \
111
+ --output /tmp/browser-test-result.json
112
+ ```
113
+
114
+ ### 可选参数
115
+
116
+ - `--cdp-url http://localhost:9223`:指定非默认的 CDP 端口
117
+ - `--page-url http://xxx/page`:connect 模式下指定目标页面
118
+
119
+ ## 依赖检查
120
+
121
+ 如果执行报错依赖缺失,引导用户安装:
122
+ ```bash
123
+ npm install puppeteer-core pixelmatch pngjs sharp
124
+ ```
125
+
126
+ ## 结果解读
127
+
128
+ ### 还原度评分
129
+ - fidelity >= 95%:还原度优秀
130
+ - fidelity 90-95%:还原度良好,有优化空间
131
+ - fidelity < 90%:还原度不足,需要调整
132
+
133
+ ### 功能测试
134
+ - passed = total:全部通过
135
+ - 有失败步骤:根据 `step` + `error` 字段定位代码问题
136
+
137
+ ### 结果文件结构
138
+ ```json
139
+ {
140
+ "planId": "xxx",
141
+ "mode": "connect",
142
+ "steps": [
143
+ { "index": 1, "step": "navigate", "status": "PASS", "duration": 1200, ... },
144
+ { "index": 2, "step": "screenshot", "status": "PASS", "duration": 500, ... },
145
+ { "index": 3, "step": "compare", "status": "PASS", "fidelity": 94.7, ... }
146
+ ],
147
+ "summary": {
148
+ "total": 5, "passed": 4, "failed": 1,
149
+ "fidelity": 94.7
150
+ },
151
+ "outputDir": "/tmp/browser-test-xxx"
152
+ }
153
+ ```
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: local-api-tool
3
- description: 本地网络 API 工具,专门处理 10.x.x.x 网段的 Swagger/YApi 文档拉取和联调测试。当服务端 api_tool 因 LOCAL_NETWORK_UNREACHABLE 无法访问时,此技能在用户本地环境执行 curl,再将结果交给服务端解析。适用于内网开发环境、VPN 场景。
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
4
  compatibility:
5
5
  tools:
6
6
  - run_command
@@ -20,6 +20,19 @@ compatibility:
20
20
  - API 联调测试目标为 `10.x.x.x` 网段
21
21
  - 用户在 VPN 或内网开发环境中,本地可访问但远程服务器不可达
22
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
+
23
36
  ## 工作流
24
37
 
25
38
  ### 流程 A:拉取 Swagger API 文档(fetch)