@szc-ft/mcp-szcd-client 0.27.1 → 0.27.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.
- package/commands/szcd-mcp-browser-test.md +1 -1
- package/lib/browser-engine.js +434 -4
- package/lib/shared-deps.js +80 -40
- package/lib/visual-compare.js +3 -3
- package/local-browser-executor.js +194 -7
- package/opencode-extension/commands/szcd-mcp-browser-test.md +1 -1
- package/opencode-extension/skills/local-browser-test/SKILL.md +157 -15
- package/opencode-extension/skills/local-browser-test/local-browser-executor-old.cjs +395 -0
- package/opencode-extension/skills/local-browser-test/local-browser-executor.cjs +348 -16
- package/package.json +1 -1
- package/qwen-extension/commands/szcd-mcp-browser-test.md +1 -1
- package/qwen-extension/qwen-extension.json +1 -1
- package/qwen-extension/skills/local-browser-test/SKILL.md +157 -15
- package/qwen-extension/skills/local-browser-test/local-browser-executor-old.cjs +395 -0
- package/qwen-extension/skills/local-browser-test/local-browser-executor.cjs +348 -16
- package/standard-skill/local-browser-test/SKILL.md +157 -15
- package/standard-skill/local-browser-test/local-browser-executor-old.cjs +395 -0
- package/standard-skill/local-browser-test/local-browser-executor.cjs +348 -16
|
@@ -3,10 +3,13 @@
|
|
|
3
3
|
* 本地浏览器测试执行器 - CLI 入口
|
|
4
4
|
*
|
|
5
5
|
* 用法:
|
|
6
|
+
* node local-browser-executor.js --action observe --output /tmp/observe.json
|
|
7
|
+
* node local-browser-executor.js --action act --index 1 --observe-after
|
|
6
8
|
* node local-browser-executor.js --plan '<JSON>' --output /tmp/result.json
|
|
7
9
|
* node local-browser-executor.js --plan-file /path/to/plan.json --output /tmp/result.json
|
|
8
10
|
*
|
|
9
11
|
* 参数:
|
|
12
|
+
* --action 直接执行动作,当前支持 observe/act/assert
|
|
10
13
|
* --plan 测试计划 JSON 字符串
|
|
11
14
|
* --plan-file 测试计划 JSON 文件路径(与 --plan 二选一)
|
|
12
15
|
* --output 结果输出文件路径(默认 stdout)
|
|
@@ -36,6 +39,9 @@ function parseArgs() {
|
|
|
36
39
|
|
|
37
40
|
for (let i = 0; i < args.length; i++) {
|
|
38
41
|
switch (args[i]) {
|
|
42
|
+
case "--action":
|
|
43
|
+
parsed.action = args[++i];
|
|
44
|
+
break;
|
|
39
45
|
case "--plan":
|
|
40
46
|
parsed.planJson = args[++i];
|
|
41
47
|
break;
|
|
@@ -51,9 +57,66 @@ function parseArgs() {
|
|
|
51
57
|
case "--page-url":
|
|
52
58
|
parsed.pageUrl = args[++i];
|
|
53
59
|
break;
|
|
60
|
+
case "--url-contains":
|
|
61
|
+
parsed.urlContains = args[++i];
|
|
62
|
+
break;
|
|
63
|
+
case "--frame-url-contains":
|
|
64
|
+
parsed.frameUrlContains = args[++i];
|
|
65
|
+
break;
|
|
66
|
+
case "--frame-content-contains":
|
|
67
|
+
parsed.frameContentContains = args[++i];
|
|
68
|
+
break;
|
|
69
|
+
case "--frame-index":
|
|
70
|
+
parsed.frameIndex = args[++i];
|
|
71
|
+
break;
|
|
72
|
+
case "--selector":
|
|
73
|
+
parsed.selector = args[++i];
|
|
74
|
+
break;
|
|
75
|
+
case "--depth":
|
|
76
|
+
parsed.depth = args[++i];
|
|
77
|
+
break;
|
|
78
|
+
case "--max-interactive-elements":
|
|
79
|
+
parsed.maxInteractiveElements = args[++i];
|
|
80
|
+
break;
|
|
81
|
+
case "--index":
|
|
82
|
+
parsed.index = args[++i];
|
|
83
|
+
break;
|
|
84
|
+
case "--click":
|
|
85
|
+
parsed.click = args[++i];
|
|
86
|
+
break;
|
|
87
|
+
case "--type":
|
|
88
|
+
parsed.typeText = args[++i];
|
|
89
|
+
break;
|
|
90
|
+
case "--clear":
|
|
91
|
+
parsed.clear = true;
|
|
92
|
+
break;
|
|
93
|
+
case "--hover":
|
|
94
|
+
parsed.hover = args[++i];
|
|
95
|
+
break;
|
|
96
|
+
case "--observe-after":
|
|
97
|
+
parsed.observeAfter = true;
|
|
98
|
+
break;
|
|
99
|
+
case "--url-contains-assert":
|
|
100
|
+
parsed.urlContainsAssert = args[++i];
|
|
101
|
+
break;
|
|
102
|
+
case "--text-contains":
|
|
103
|
+
parsed.textContains = args[++i];
|
|
104
|
+
break;
|
|
105
|
+
case "--expect-visible":
|
|
106
|
+
parsed.expectVisible = true;
|
|
107
|
+
break;
|
|
108
|
+
case "--min-count":
|
|
109
|
+
parsed.minCount = args[++i];
|
|
110
|
+
break;
|
|
111
|
+
case "--has-text":
|
|
112
|
+
parsed.hasText = args[++i];
|
|
113
|
+
break;
|
|
54
114
|
case "--base-url":
|
|
55
115
|
parsed.baseUrl = args[++i];
|
|
56
116
|
break;
|
|
117
|
+
case "--allow-launch":
|
|
118
|
+
parsed.allowLaunch = true;
|
|
119
|
+
break;
|
|
57
120
|
case "--help":
|
|
58
121
|
case "-h":
|
|
59
122
|
printHelp();
|
|
@@ -68,17 +131,40 @@ function printHelp() {
|
|
|
68
131
|
本地浏览器测试执行器
|
|
69
132
|
|
|
70
133
|
用法:
|
|
134
|
+
node local-browser-executor.js --action observe [options]
|
|
135
|
+
node local-browser-executor.js --action act --index 1 --observe-after [options]
|
|
136
|
+
node local-browser-executor.js --action assert --selector ".ant-table" --expect-visible [options]
|
|
71
137
|
node local-browser-executor.js --plan '<JSON>' [options]
|
|
72
138
|
node local-browser-executor.js --plan-file <path> [options]
|
|
73
139
|
|
|
74
140
|
选项:
|
|
75
|
-
--
|
|
76
|
-
--plan
|
|
77
|
-
--
|
|
78
|
-
--
|
|
79
|
-
--
|
|
80
|
-
--
|
|
81
|
-
-
|
|
141
|
+
--action <name> 直接执行动作,当前支持 observe/act/assert
|
|
142
|
+
--plan <json> 测试计划 JSON 字符串
|
|
143
|
+
--plan-file <path> 测试计划 JSON 文件路径
|
|
144
|
+
--output <path> 结果输出文件路径(默认 stdout)
|
|
145
|
+
--cdp-url <url> Chrome DevTools Protocol URL(默认 http://localhost:9222)
|
|
146
|
+
--page-url <url> 目标页面 URL(connect 模式时匹配标签页)
|
|
147
|
+
--url-contains <text> 按 URL 片段匹配已打开标签页
|
|
148
|
+
--frame-url-contains <text> 按 URL 片段匹配 iframe
|
|
149
|
+
--frame-content-contains <text> 按文本内容匹配 iframe
|
|
150
|
+
--frame-index <n> 按 frame 下标选择 iframe
|
|
151
|
+
--selector <css> observe/assert 根节点或目标元素(observe 默认 body)
|
|
152
|
+
--depth <n> observe 的 DOM 树深度(默认 5)
|
|
153
|
+
--max-interactive-elements <n> observe 的交互元素上限(默认 200)
|
|
154
|
+
--index <n> act 时按 observe 的 interactiveElements[index] 操作
|
|
155
|
+
--click <selector> act 时点击 selector/text=/role=/~ 目标
|
|
156
|
+
--type <text> act 时输入文本
|
|
157
|
+
--clear act 输入前清空目标
|
|
158
|
+
--hover <selector> act 时悬停 selector/text=/role=/~ 目标
|
|
159
|
+
--observe-after act 后再次 observe,返回 before/act/after 闭环结果
|
|
160
|
+
--url-contains-assert <text> assert 当前 URL 包含文本
|
|
161
|
+
--text-contains <text> assert 页面文本包含内容
|
|
162
|
+
--expect-visible assert selector 可见
|
|
163
|
+
--min-count <n> assert selector 数量下限
|
|
164
|
+
--has-text <text> assert selector 文本包含内容
|
|
165
|
+
--allow-launch action 模式允许无 CDP 时启动 headless Chrome
|
|
166
|
+
--base-url <url> 基础 URL,替换计划中的 {{baseUrl}} 占位符
|
|
167
|
+
-h, --help 显示帮助
|
|
82
168
|
`);
|
|
83
169
|
}
|
|
84
170
|
|
|
@@ -183,9 +269,110 @@ async function executePlan(plan, options) {
|
|
|
183
269
|
|
|
184
270
|
// ==================== 主流程 ====================
|
|
185
271
|
|
|
272
|
+
async function executeAction(args) {
|
|
273
|
+
const engine = new BrowserEngine();
|
|
274
|
+
const startedAt = new Date().toISOString();
|
|
275
|
+
try {
|
|
276
|
+
const cdpUrl = args.cdpUrl || "http://localhost:9222";
|
|
277
|
+
const debuggerAvailable = await engine.isDebuggerAvailable(cdpUrl);
|
|
278
|
+
if (!debuggerAvailable && !args.allowLaunch) {
|
|
279
|
+
return {
|
|
280
|
+
error: `无法连接到 Chrome 调试端口: ${cdpUrl}`,
|
|
281
|
+
errorCode: "CDP_UNAVAILABLE",
|
|
282
|
+
guide: "请先以 --remote-debugging-port=9222 启动用户真实浏览器;如确需启动 headless 回退,追加 --allow-launch。",
|
|
283
|
+
startTime: startedAt,
|
|
284
|
+
endTime: new Date().toISOString(),
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const initResult = await engine.init({
|
|
289
|
+
cdpUrl,
|
|
290
|
+
pageUrl: args.pageUrl || args.urlContains,
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
switch (args.action) {
|
|
294
|
+
case "observe": {
|
|
295
|
+
const result = await engine.observe({
|
|
296
|
+
selector: args.selector,
|
|
297
|
+
depth: args.depth,
|
|
298
|
+
frameUrlContains: args.frameUrlContains,
|
|
299
|
+
frameContentContains: args.frameContentContains,
|
|
300
|
+
frameIndex: args.frameIndex,
|
|
301
|
+
maxInteractiveElements: args.maxInteractiveElements,
|
|
302
|
+
});
|
|
303
|
+
return { ...result, browserInfo: initResult, startTime: startedAt, endTime: new Date().toISOString() };
|
|
304
|
+
}
|
|
305
|
+
case "act": {
|
|
306
|
+
const actResult = await engine.act({
|
|
307
|
+
index: args.index,
|
|
308
|
+
selector: args.selector,
|
|
309
|
+
click: args.click,
|
|
310
|
+
hover: args.hover,
|
|
311
|
+
typeText: args.typeText,
|
|
312
|
+
clear: args.clear,
|
|
313
|
+
frameUrlContains: args.frameUrlContains,
|
|
314
|
+
frameContentContains: args.frameContentContains,
|
|
315
|
+
frameIndex: args.frameIndex,
|
|
316
|
+
});
|
|
317
|
+
if (!args.observeAfter) {
|
|
318
|
+
return { type: "act", ...actResult, browserInfo: initResult, startTime: startedAt, endTime: new Date().toISOString() };
|
|
319
|
+
}
|
|
320
|
+
const after = await engine.observe({
|
|
321
|
+
depth: args.depth,
|
|
322
|
+
frameUrlContains: args.frameUrlContains,
|
|
323
|
+
frameContentContains: args.frameContentContains,
|
|
324
|
+
frameIndex: args.frameIndex,
|
|
325
|
+
maxInteractiveElements: args.maxInteractiveElements,
|
|
326
|
+
});
|
|
327
|
+
return { type: "actObserve", act: actResult, after, browserInfo: initResult, startTime: startedAt, endTime: new Date().toISOString() };
|
|
328
|
+
}
|
|
329
|
+
case "assert": {
|
|
330
|
+
const expect = {};
|
|
331
|
+
if (args.expectVisible) expect.visible = true;
|
|
332
|
+
if (args.minCount !== undefined) expect.minCount = Number.parseInt(args.minCount, 10);
|
|
333
|
+
if (args.hasText !== undefined) expect.hasText = args.hasText;
|
|
334
|
+
const result = await engine.assert({
|
|
335
|
+
selector: args.selector,
|
|
336
|
+
expect,
|
|
337
|
+
urlContains: args.urlContainsAssert,
|
|
338
|
+
textContains: args.textContains,
|
|
339
|
+
frameUrlContains: args.frameUrlContains,
|
|
340
|
+
frameContentContains: args.frameContentContains,
|
|
341
|
+
frameIndex: args.frameIndex,
|
|
342
|
+
});
|
|
343
|
+
return { type: "assert", ...result, browserInfo: initResult, startTime: startedAt, endTime: new Date().toISOString() };
|
|
344
|
+
}
|
|
345
|
+
default:
|
|
346
|
+
return {
|
|
347
|
+
error: `未知 action: ${args.action}`,
|
|
348
|
+
errorCode: "UNKNOWN_ACTION",
|
|
349
|
+
supportedActions: ["observe", "act", "assert"],
|
|
350
|
+
startTime: startedAt,
|
|
351
|
+
endTime: new Date().toISOString(),
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
} catch (err) {
|
|
355
|
+
return {
|
|
356
|
+
error: err.message,
|
|
357
|
+
errorCode: err.code || "ACTION_FAILED",
|
|
358
|
+
installGuide: err.code === "CHROME_NOT_FOUND" || err.code === "DEPENDENCY_MISSING" ? err.message : undefined,
|
|
359
|
+
startTime: startedAt,
|
|
360
|
+
endTime: new Date().toISOString(),
|
|
361
|
+
};
|
|
362
|
+
} finally {
|
|
363
|
+
await engine.close();
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
186
367
|
async function main() {
|
|
187
368
|
const args = parseArgs();
|
|
188
369
|
|
|
370
|
+
if (args.action) {
|
|
371
|
+
const results = await executeAction(args);
|
|
372
|
+
outputResult(results, args.output);
|
|
373
|
+
process.exit(results.error ? 2 : 0);
|
|
374
|
+
}
|
|
375
|
+
|
|
189
376
|
let plan;
|
|
190
377
|
try {
|
|
191
378
|
plan = loadPlan(args);
|
|
@@ -53,5 +53,5 @@ argument-hint: <目标页面URL> [可选:设计稿图片路径]
|
|
|
53
53
|
|
|
54
54
|
**注意事项**:
|
|
55
55
|
- 首次使用需确保 Chrome 以调试模式启动:`google-chrome --remote-debugging-port=9222`
|
|
56
|
-
-
|
|
56
|
+
- 如执行器报错依赖缺失,优先按提示安装核心依赖;仅设计稿像素对比需要额外安装 `pixelmatch pngjs sharp`
|
|
57
57
|
- connect 模式下天然继承用户浏览器的登录态和微前端环境
|
|
@@ -17,6 +17,24 @@ compatibility:
|
|
|
17
17
|
- "功能测试"、"能不能正常用"
|
|
18
18
|
- "检查页面"、"页面渲染"
|
|
19
19
|
|
|
20
|
+
## 主链路定位
|
|
21
|
+
|
|
22
|
+
local-browser-test 的长期主链路是:
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
Agent / SKILL.md
|
|
26
|
+
→ szcd-mcp-client/local-browser-executor.js
|
|
27
|
+
→ lib/browser-engine.js
|
|
28
|
+
→ puppeteer-core + CDP
|
|
29
|
+
→ 用户真实 Chrome / Edge
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
目标是在用户真实登录态/真实微前端环境中形成 browser-use + Playwright 结合体:
|
|
33
|
+
- browser-use 侧:`observe → act → observe`,通过结构化页面观察和交互元素 index 支持自主任务执行。
|
|
34
|
+
- Playwright 侧:locator、actionability、assert、plan 报告逐步收敛到 `BrowserEngine`。
|
|
35
|
+
|
|
36
|
+
`standard-skill/local-browser-test/local-browser-executor.cjs` 是过渡兼容执行器;新增稳定能力优先进入 `szcd-mcp-client/lib/browser-engine.js` 和根目录 `local-browser-executor.js`。
|
|
37
|
+
|
|
20
38
|
## 浏览器模式
|
|
21
39
|
|
|
22
40
|
支持两种模式,执行器自动检测优先使用 connect:
|
|
@@ -60,14 +78,17 @@ AI 直接构造测试计划 JSON:
|
|
|
60
78
|
|
|
61
79
|
| type | 功能 | 参数 | 返回 |
|
|
62
80
|
|------|------|------|------|
|
|
81
|
+
| observe | 结构化观察 | `selector`, `depth`, `frameContentContains`, `maxInteractiveElements` | `{ frames, tree, interactiveElements[] }` |
|
|
82
|
+
| act | 按 index/selector 交互 | `index` 或 `selector/click/hover`, `typeText`, `clear`, `frameContentContains` | `{ acted, action, element }` |
|
|
83
|
+
| assert | 轻量断言 | `selector`, `expect`, `urlContains`, `textContains` | `{ passed, checks }` |
|
|
63
84
|
| navigate | 导航到 URL | `url`, `waitUntil` | `{ url, title, loadTime }` |
|
|
64
85
|
| screenshot | 页面截图 | `fullPage`, `clip`, `filename` | `{ filepath, width, height }` |
|
|
65
86
|
| compare | 设计稿对比 | `designImagePath`, `threshold`, `regions[]` | `{ fidelity, diffPixels, diffImagePath, regions[] }` |
|
|
66
|
-
| click |
|
|
67
|
-
| type |
|
|
87
|
+
| click | 点击元素(兼容) | `selector`, `waitForNavigation` | `{ clicked, tagName }` |
|
|
88
|
+
| type | 输入文本(兼容) | `selector`, `text`, `clear` | `{ typed, value }` |
|
|
68
89
|
| waitFor | 等待条件 | `selector` 或 `url`, `timeout` | `{ matched, waited }` |
|
|
69
90
|
| evaluate | 执行 JS | `expression` | `{ result }` |
|
|
70
|
-
| checkElement |
|
|
91
|
+
| checkElement | 元素断言(兼容) | `selector`, `expect: {visible, minCount, maxCount, hasText}` | `{ passed, checks, elementCount }` |
|
|
71
92
|
| findPage | 查找已打开的标签页 | `urlIncludes` 或 `urlRegex` 或 `titleIncludes` | `{ found, url, title, totalPages }` |
|
|
72
93
|
| findFrame | 查找微前端 iframe | `urlIncludes` 或 `urlRegex` 或 `titleIncludes` 或 `contentIncludes` 或 `frameIndex` | `{ found, frameUrl, matchedBy, totalFrames }` |
|
|
73
94
|
| loginWait | 等待 SSO 登录完成 | `loginSelector`, `timeout`, `interval` | `{ loggedIn, elapsed, currentUrl }` |
|
|
@@ -141,15 +162,15 @@ AI 直接构造测试计划 JSON:
|
|
|
141
162
|
wujie 微前端子应用渲染在 blob URL iframe 中,**坐标点击无法到达 iframe 内容**。
|
|
142
163
|
|
|
143
164
|
```json
|
|
144
|
-
{ "type": "
|
|
145
|
-
{ "type": "
|
|
146
|
-
{ "type": "
|
|
165
|
+
{ "type": "observe", "frameContentContains": "知识采编", "depth": 5, "maxInteractiveElements": 80 }
|
|
166
|
+
{ "type": "act", "index": 3, "frameContentContains": "知识采编" }
|
|
167
|
+
{ "type": "assert", "selector": "~.editKnowledge", "expect": { "minCount": 1 } }
|
|
147
168
|
```
|
|
148
169
|
|
|
149
|
-
- `
|
|
150
|
-
-
|
|
151
|
-
- `
|
|
152
|
-
- iframe 内的
|
|
170
|
+
- 优先用 `observe` 发现 iframe 内的 `interactiveElements[index]`,再用 `act.index` 操作,不要重新猜 selector
|
|
171
|
+
- `observe/act` 支持 `frameUrlContains`、`frameContentContains`、`frameIndex` 多种发现方式
|
|
172
|
+
- 优先用 `frameContentContains`(按页面文字内容查找),不依赖 URL/端口
|
|
173
|
+
- iframe 内的 act 使用 DOM 事件触发,不依赖坐标
|
|
153
174
|
|
|
154
175
|
### CSS Modules 哈希类名
|
|
155
176
|
项目使用 CSS Modules,类名会被哈希化(如 `editKnowledge___ynQgI`)。
|
|
@@ -192,7 +213,41 @@ wujie 微前端子应用渲染在 blob URL iframe 中,**坐标点击无法到
|
|
|
192
213
|
|
|
193
214
|
## 执行命令
|
|
194
215
|
|
|
195
|
-
###
|
|
216
|
+
### 结构化观察(主链路起点)
|
|
217
|
+
```bash
|
|
218
|
+
node {client_path}/local-browser-executor.js \
|
|
219
|
+
--action observe \
|
|
220
|
+
--url-contains "platform.aicityos.com" \
|
|
221
|
+
--frame-content-contains "数据集目录" \
|
|
222
|
+
--depth 5 \
|
|
223
|
+
--output /tmp/browser-observe.json
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
`observe` 输出 `frames`、`tree`、`interactiveElements[index]`、`selectorCandidates`、`bbox`、`state`。Agent 后续应优先使用 `index` 调用 `act`,不要从 DOM 树重新猜 selector。直接 `--action observe` 默认只连接用户已启动的真实浏览器 CDP;如确需无 CDP 时启动 headless 回退,追加 `--allow-launch`。
|
|
227
|
+
|
|
228
|
+
### 观察后按 index 交互(主链路闭环)
|
|
229
|
+
```bash
|
|
230
|
+
node {client_path}/local-browser-executor.js \
|
|
231
|
+
--action act \
|
|
232
|
+
--url-contains "platform.aicityos.com" \
|
|
233
|
+
--frame-content-contains "数据集目录" \
|
|
234
|
+
--index 3 \
|
|
235
|
+
--observe-after \
|
|
236
|
+
--output /tmp/browser-act-observe.json
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### 轻量 DOM 断言
|
|
240
|
+
```bash
|
|
241
|
+
node {client_path}/local-browser-executor.js \
|
|
242
|
+
--action assert \
|
|
243
|
+
--url-contains "platform.aicityos.com" \
|
|
244
|
+
--selector ".ant-table" \
|
|
245
|
+
--expect-visible \
|
|
246
|
+
--min-count 1 \
|
|
247
|
+
--output /tmp/browser-assert.json
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### 快速诊断(过渡兼容)
|
|
196
251
|
```bash
|
|
197
252
|
NODE_PATH=~/.szcd-mcp/deps/node_modules node {skill_path}/local-browser-executor.cjs \
|
|
198
253
|
--action diagnose \
|
|
@@ -232,16 +287,98 @@ NODE_PATH=~/.szcd-mcp/deps/node_modules node {skill_path}/local-browser-executor
|
|
|
232
287
|
--output /tmp/browser-test-result.json
|
|
233
288
|
```
|
|
234
289
|
|
|
290
|
+
### DOM 快照(结构化页面树,类似 browser-use take_snapshot)
|
|
291
|
+
```bash
|
|
292
|
+
NODE_PATH=~/.szcd-mcp/deps/node_modules node {skill_path}/local-browser-executor.cjs \
|
|
293
|
+
--action snapshot \
|
|
294
|
+
--url-contains "platform.aicityos.com" \
|
|
295
|
+
--depth 4 \
|
|
296
|
+
--selector ".page-container"
|
|
297
|
+
```
|
|
298
|
+
输出示例:
|
|
299
|
+
```
|
|
300
|
+
<div.page-container>
|
|
301
|
+
<section.page-layout>
|
|
302
|
+
<header.page-header>
|
|
303
|
+
<div.page-header-left> <div.logo>
|
|
304
|
+
<section.h-box>
|
|
305
|
+
<aside.ant-layout-sider> 🔗
|
|
306
|
+
<main.ant-layout-content>
|
|
307
|
+
<div.layout-wrap>
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
### 智能交互(支持 text=、role=、~ 选择器,类似 Playwright)
|
|
311
|
+
```bash
|
|
312
|
+
# 点击包含“构建”文本的按钮
|
|
313
|
+
NODE_PATH=~/.szcd-mcp/deps/node_modules node {skill_path}/local-browser-executor.cjs \
|
|
314
|
+
--action interact \
|
|
315
|
+
--url-contains "jenkins" \
|
|
316
|
+
--click "text=Build"
|
|
317
|
+
|
|
318
|
+
# 在输入框中输入文本
|
|
319
|
+
NODE_PATH=~/.szcd-mcp/deps/node_modules node {skill_path}/local-browser-executor.cjs \
|
|
320
|
+
--action interact \
|
|
321
|
+
--click "#search-input" \
|
|
322
|
+
--type "hello world"
|
|
323
|
+
|
|
324
|
+
# 悬停 + 截图
|
|
325
|
+
NODE_PATH=~/.szcd-mcp/deps/node_modules node {skill_path}/local-browser-executor.cjs \
|
|
326
|
+
--action interact \
|
|
327
|
+
--hover "~ant-menu-item" \
|
|
328
|
+
--screenshot-after
|
|
329
|
+
|
|
330
|
+
# 滚动页面
|
|
331
|
+
NODE_PATH=~/.szcd-mcp/deps/node_modules node {skill_path}/local-browser-executor.cjs \
|
|
332
|
+
--action interact \
|
|
333
|
+
--scroll down --scroll-amount 500
|
|
334
|
+
```
|
|
335
|
+
选择器语法:
|
|
336
|
+
- `text=Build` → 按文本内容查找(类似 Playwright text selector)
|
|
337
|
+
- `role=button` → 按 ARIA role 查找
|
|
338
|
+
- `~ant-menu-item` → CSS class 模糊匹配(`[class*="ant-menu-item"]`)
|
|
339
|
+
- `.my-class` → 精确 CSS 选择器
|
|
340
|
+
|
|
341
|
+
### 性能指标采集(FCP/TTFB/资源统计)
|
|
342
|
+
```bash
|
|
343
|
+
NODE_PATH=~/.szcd-mcp/deps/node_modules node {skill_path}/local-browser-executor.cjs \
|
|
344
|
+
--action perf \
|
|
345
|
+
--url-contains "platform.aicityos.com"
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
### 持续监控模式(定时截图 + 错误检测)
|
|
349
|
+
```bash
|
|
350
|
+
NODE_PATH=~/.szcd-mcp/deps/node_modules node {skill_path}/local-browser-executor.cjs \
|
|
351
|
+
--action watch \
|
|
352
|
+
--url-contains "platform.aicityos.com" \
|
|
353
|
+
--interval 5000 \
|
|
354
|
+
--max-rounds 60 \
|
|
355
|
+
--output-dir /tmp/browser-watch
|
|
356
|
+
```
|
|
357
|
+
|
|
235
358
|
### 可选参数
|
|
236
359
|
|
|
237
360
|
- `--cdp-url http://localhost:9222`:CDP 地址(默认 9222)
|
|
238
361
|
- `--url <url>`:目标页面 URL(不存在则导航)
|
|
239
362
|
- `--url-contains <keyword>`:按关键词查找已打开的页面
|
|
363
|
+
- `--frame-url-contains <keyword>`:observe 时按 iframe URL 片段匹配目标 frame
|
|
364
|
+
- `--frame-content-contains <keyword>`:observe 时按 iframe 文本内容匹配目标 frame
|
|
365
|
+
- `--frame-index <n>`:observe 时按 frame 下标选择目标 frame
|
|
240
366
|
- `--wait <ms>`:额外等待时间(默认 5000ms,微前端建议 15000)
|
|
241
367
|
- `--output <path>`:JSON 结果输出路径
|
|
242
368
|
- `--output-dir <dir>`:截图/产物目录
|
|
243
369
|
- `--filename <name>`:截图文件名
|
|
244
370
|
- `--full-page`:全页截图
|
|
371
|
+
- `--selector <css>`:快照/observe 根元素(默认 body)
|
|
372
|
+
- `--depth <n>`:DOM 深度(默认 5)
|
|
373
|
+
- `--click <selector>`:点击元素(支持 text=/role=/~)
|
|
374
|
+
- `--type <text>`:输入文本
|
|
375
|
+
- `--hover <selector>`:悬停元素
|
|
376
|
+
- `--scroll <up|down|left|right>`:滚动方向
|
|
377
|
+
- `--scroll-amount <px>`:滚动距离(默认 300)
|
|
378
|
+
- `--screenshot-after`:交互后自动截图
|
|
379
|
+
- `--interval <ms>`:watch 模式间隔(默认 5000)
|
|
380
|
+
- `--max-rounds <n>`:watch 最大轮数(默认 60)
|
|
381
|
+
- `--allow-launch`:根执行器 action 模式允许无 CDP 时启动 headless Chrome;默认不启用,优先保障真实用户浏览器链路
|
|
245
382
|
|
|
246
383
|
## AI 语义断言(aiAssert)
|
|
247
384
|
|
|
@@ -268,12 +405,17 @@ NODE_PATH=~/.szcd-mcp/deps/node_modules node {skill_path}/local-browser-executor
|
|
|
268
405
|
|
|
269
406
|
## 依赖管理
|
|
270
407
|
|
|
271
|
-
|
|
272
|
-
-
|
|
408
|
+
浏览器测试依赖采用**共享缓存 + 按需安装**方案:
|
|
409
|
+
- `observe/act/assert/diagnose/screenshot` 主链路只安装 `puppeteer-core` 到 `~/.szcd-mcp/deps/`
|
|
410
|
+
- 只有执行设计稿像素对比 `compare` 时才安装 `pixelmatch/pngjs/sharp`
|
|
273
411
|
- 后续所有项目共享复用,无需重复安装
|
|
274
|
-
-
|
|
412
|
+
- 如果核心依赖自动安装失败,引导用户手动执行:
|
|
413
|
+
```bash
|
|
414
|
+
cd ~/.szcd-mcp/deps && npm install --registry=https://registry.npmmirror.com puppeteer-core
|
|
415
|
+
```
|
|
416
|
+
- 如果视觉对比依赖自动安装失败,引导用户手动执行:
|
|
275
417
|
```bash
|
|
276
|
-
cd ~/.szcd-mcp/deps && npm install
|
|
418
|
+
cd ~/.szcd-mcp/deps && npm install --registry=https://registry.npmmirror.com --@img:registry=https://registry.npmmirror.com --prefer-offline --no-audit --no-fund --omit=dev --package-lock=false --sharp_binary_host=https://npmmirror.com/mirrors/sharp --sharp_libvips_binary_host=https://npmmirror.com/mirrors/sharp-libvips pixelmatch pngjs sharp
|
|
277
419
|
```
|
|
278
420
|
|
|
279
421
|
## 结果解读
|