@szc-ft/mcp-szcd-client 0.34.2 → 0.38.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 +59 -1
- package/agents/platforms.json +7 -0
- package/agents/szcd-component-expert.qoder.md +2 -0
- package/opencode-extension/opencode.json +9 -0
- package/opencode-extension/skills/local-browser-test/SKILL.md +91 -0
- package/opencode-extension/skills/local-browser-test/config.example.yaml +51 -0
- package/opencode-extension/skills/local-browser-test/lib/api-call-executor.js +251 -0
- package/opencode-extension/skills/local-browser-test/lib/browser-engine.js +95 -21
- package/opencode-extension/skills/local-browser-test/lib/config-loader.js +104 -0
- package/opencode-extension/skills/local-browser-test/lib/console-archiver.js +100 -0
- package/opencode-extension/skills/local-browser-test/lib/global-setup.js +72 -0
- package/opencode-extension/skills/local-browser-test/lib/har-collector.js +124 -0
- package/opencode-extension/skills/local-browser-test/lib/html-reporter.js +248 -0
- package/opencode-extension/skills/local-browser-test/lib/projects.js +46 -0
- package/opencode-extension/skills/local-browser-test/lib/raw-script-helpers.js +93 -0
- package/opencode-extension/skills/local-browser-test/lib/redact.js +94 -0
- package/opencode-extension/skills/local-browser-test/lib/report-retain.js +84 -0
- package/opencode-extension/skills/local-browser-test/lib/retry-policy.js +160 -0
- package/opencode-extension/skills/local-browser-test/lib/storage-state.js +111 -0
- package/opencode-extension/skills/local-browser-test/lib/tag-runner.js +103 -0
- package/opencode-extension/skills/local-browser-test/lib/test-plan.js +2 -0
- package/opencode-extension/skills/local-browser-test/lib/trace-collector.js +94 -0
- package/opencode-extension/skills/local-browser-test/lib/trend-aggregator.js +97 -0
- package/opencode-extension/skills/local-browser-test/lib/workspace-init.js +178 -0
- package/opencode-extension/skills/local-browser-test/local-browser-executor.js +226 -2
- package/opencode-extension/skills/local-browser-test/tests/integration/pr-01.test.js +254 -0
- package/opencode-extension/skills/local-browser-test/tests/integration/pr-02.test.js +263 -0
- package/opencode-extension/skills/local-browser-test/tests/integration/pr-03.test.js +308 -0
- package/opencode-extension/skills/local-browser-test/tests/integration/pr-04.test.js +168 -0
- package/opencode-extension/skills/local-browser-test/tests/integration/pr-05.test.js +183 -0
- package/opencode-extension/skills/local-browser-test/tests/integration/pr-06.test.js +252 -0
- package/opencode-extension/skills/local-browser-test/tests/integration/pr-07.test.js +168 -0
- package/opencode-extension/skills/local-browser-test/tests/integration/pr-08.test.js +293 -0
- package/opencode-extension/skills/shadcn-design-to-code/SKILL.md +352 -0
- package/opencode-extension/skills/shadcn-design-to-code/examples/1.1-overview-page.tsx +382 -0
- package/opencode-extension/skills/shadcn-design-to-code/references/install-pitfalls.md +526 -0
- package/opencode-extension/skills/shadcn-design-to-code/references/layouts.md +551 -0
- package/opencode-extension/skills/shadcn-design-to-code/references/sketch-mapping.md +247 -0
- package/opencode-extension/skills/shadcn-design-to-code/references/tokens.md +534 -0
- package/opencode-extension/skills/szcd-upload-zip/SKILL.md +45 -0
- package/package.json +70 -70
- package/qwen-extension/qwen-extension.json +10 -1
- package/qwen-extension/skills/local-browser-test/SKILL.md +91 -0
- package/qwen-extension/skills/local-browser-test/config.example.yaml +51 -0
- package/qwen-extension/skills/local-browser-test/lib/api-call-executor.js +251 -0
- package/qwen-extension/skills/local-browser-test/lib/browser-engine.js +95 -21
- package/qwen-extension/skills/local-browser-test/lib/config-loader.js +104 -0
- package/qwen-extension/skills/local-browser-test/lib/console-archiver.js +100 -0
- package/qwen-extension/skills/local-browser-test/lib/global-setup.js +72 -0
- package/qwen-extension/skills/local-browser-test/lib/har-collector.js +124 -0
- package/qwen-extension/skills/local-browser-test/lib/html-reporter.js +248 -0
- package/qwen-extension/skills/local-browser-test/lib/projects.js +46 -0
- package/qwen-extension/skills/local-browser-test/lib/raw-script-helpers.js +93 -0
- package/qwen-extension/skills/local-browser-test/lib/redact.js +94 -0
- package/qwen-extension/skills/local-browser-test/lib/report-retain.js +84 -0
- package/qwen-extension/skills/local-browser-test/lib/retry-policy.js +160 -0
- package/qwen-extension/skills/local-browser-test/lib/storage-state.js +111 -0
- package/qwen-extension/skills/local-browser-test/lib/tag-runner.js +103 -0
- package/qwen-extension/skills/local-browser-test/lib/test-plan.js +2 -0
- package/qwen-extension/skills/local-browser-test/lib/trace-collector.js +94 -0
- package/qwen-extension/skills/local-browser-test/lib/trend-aggregator.js +97 -0
- package/qwen-extension/skills/local-browser-test/lib/workspace-init.js +178 -0
- package/qwen-extension/skills/local-browser-test/local-browser-executor.js +226 -2
- package/qwen-extension/skills/local-browser-test/tests/integration/pr-01.test.js +254 -0
- package/qwen-extension/skills/local-browser-test/tests/integration/pr-02.test.js +263 -0
- package/qwen-extension/skills/local-browser-test/tests/integration/pr-03.test.js +308 -0
- package/qwen-extension/skills/local-browser-test/tests/integration/pr-04.test.js +168 -0
- package/qwen-extension/skills/local-browser-test/tests/integration/pr-05.test.js +183 -0
- package/qwen-extension/skills/local-browser-test/tests/integration/pr-06.test.js +252 -0
- package/qwen-extension/skills/local-browser-test/tests/integration/pr-07.test.js +168 -0
- package/qwen-extension/skills/local-browser-test/tests/integration/pr-08.test.js +293 -0
- package/qwen-extension/skills/shadcn-design-to-code/SKILL.md +352 -0
- package/qwen-extension/skills/shadcn-design-to-code/examples/1.1-overview-page.tsx +382 -0
- package/qwen-extension/skills/shadcn-design-to-code/references/install-pitfalls.md +526 -0
- package/qwen-extension/skills/shadcn-design-to-code/references/layouts.md +551 -0
- package/qwen-extension/skills/shadcn-design-to-code/references/sketch-mapping.md +247 -0
- package/qwen-extension/skills/shadcn-design-to-code/references/tokens.md +534 -0
- package/qwen-extension/skills/szcd-upload-zip/SKILL.md +45 -0
- package/scripts/lib/common.js +8 -1
- package/standard-skill/local-browser-test/SKILL.md +91 -0
- package/standard-skill/local-browser-test/config.example.yaml +51 -0
- package/standard-skill/local-browser-test/lib/api-call-executor.js +251 -0
- package/standard-skill/local-browser-test/lib/browser-engine.js +95 -21
- package/standard-skill/local-browser-test/lib/config-loader.js +104 -0
- package/standard-skill/local-browser-test/lib/console-archiver.js +100 -0
- package/standard-skill/local-browser-test/lib/global-setup.js +72 -0
- package/standard-skill/local-browser-test/lib/har-collector.js +124 -0
- package/standard-skill/local-browser-test/lib/html-reporter.js +248 -0
- package/standard-skill/local-browser-test/lib/projects.js +46 -0
- package/standard-skill/local-browser-test/lib/raw-script-helpers.js +93 -0
- package/standard-skill/local-browser-test/lib/redact.js +94 -0
- package/standard-skill/local-browser-test/lib/report-retain.js +84 -0
- package/standard-skill/local-browser-test/lib/retry-policy.js +160 -0
- package/standard-skill/local-browser-test/lib/storage-state.js +111 -0
- package/standard-skill/local-browser-test/lib/tag-runner.js +103 -0
- package/standard-skill/local-browser-test/lib/test-plan.js +2 -0
- package/standard-skill/local-browser-test/lib/trace-collector.js +94 -0
- package/standard-skill/local-browser-test/lib/trend-aggregator.js +97 -0
- package/standard-skill/local-browser-test/lib/workspace-init.js +178 -0
- package/standard-skill/local-browser-test/local-browser-executor.js +226 -2
- package/standard-skill/local-browser-test/tests/integration/pr-01.test.js +254 -0
- package/standard-skill/local-browser-test/tests/integration/pr-02.test.js +263 -0
- package/standard-skill/local-browser-test/tests/integration/pr-03.test.js +308 -0
- package/standard-skill/local-browser-test/tests/integration/pr-04.test.js +168 -0
- package/standard-skill/local-browser-test/tests/integration/pr-05.test.js +183 -0
- package/standard-skill/local-browser-test/tests/integration/pr-06.test.js +252 -0
- package/standard-skill/local-browser-test/tests/integration/pr-07.test.js +168 -0
- package/standard-skill/local-browser-test/tests/integration/pr-08.test.js +293 -0
- package/standard-skill/shadcn-design-to-code/SKILL.md +352 -0
- package/standard-skill/shadcn-design-to-code/examples/1.1-overview-page.tsx +382 -0
- package/standard-skill/shadcn-design-to-code/references/install-pitfalls.md +526 -0
- package/standard-skill/shadcn-design-to-code/references/layouts.md +551 -0
- package/standard-skill/shadcn-design-to-code/references/sketch-mapping.md +247 -0
- package/standard-skill/shadcn-design-to-code/references/tokens.md +534 -0
- package/standard-skill/szcd-upload-zip/SKILL.md +45 -0
- package/opencode-extension/skills/local-browser-test/local-browser-executor.cjs +0 -742
- package/qwen-extension/skills/local-browser-test/local-browser-executor.cjs +0 -742
package/package.json
CHANGED
|
@@ -1,72 +1,72 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
2
|
+
"name": "@szc-ft/mcp-szcd-client",
|
|
3
|
+
"version": "0.38.0",
|
|
4
|
+
"description": "MCP client for szcd component library - auto-configures AI coding tools with MCP server, skills, agents and commands",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"mcp",
|
|
7
|
+
"szcd",
|
|
8
|
+
"component",
|
|
9
|
+
"ai",
|
|
10
|
+
"agent",
|
|
11
|
+
"claude",
|
|
12
|
+
"trae",
|
|
13
|
+
"qoder",
|
|
14
|
+
"qwen",
|
|
15
|
+
"skill",
|
|
16
|
+
"client"
|
|
17
|
+
],
|
|
18
|
+
"author": "szc-ft",
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"type": "module",
|
|
21
|
+
"main": "mcp-proxy.js",
|
|
22
|
+
"bin": {
|
|
23
|
+
"szcd-mcp-proxy": "mcp-proxy.js",
|
|
24
|
+
"szcd-mcp-setup": "scripts/postinstall.js",
|
|
25
|
+
"szcd-mcp-install-shortcuts": "scripts/install-dev-shortcuts.js",
|
|
26
|
+
"szcd-mcp-update-url": "scripts/update-mcp-url.js",
|
|
27
|
+
"szcd-mcp-coding-config": "scripts/update-coding-config.js",
|
|
28
|
+
"szcd-mcp-api-config": "scripts/update-api-config.js",
|
|
29
|
+
"szcd-mcp-auth": "scripts/update-auth.js"
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"mcp-proxy.js",
|
|
33
|
+
"local-browser-executor.js",
|
|
34
|
+
"lib/",
|
|
35
|
+
"scripts/postinstall.js",
|
|
36
|
+
"scripts/install-dev-shortcuts.js",
|
|
37
|
+
"scripts/update-mcp-url.js",
|
|
38
|
+
"scripts/update-coding-config.js",
|
|
39
|
+
"scripts/update-api-config.js",
|
|
40
|
+
"scripts/update-auth.js",
|
|
41
|
+
"scripts/lib/",
|
|
42
|
+
"standard-skill/",
|
|
43
|
+
"agents/",
|
|
44
|
+
"commands/",
|
|
45
|
+
"qwen-extension/",
|
|
46
|
+
"opencode-extension/",
|
|
47
|
+
"AGENTS.md",
|
|
48
|
+
"README.md"
|
|
49
|
+
],
|
|
50
|
+
"engines": {
|
|
51
|
+
"node": ">=18"
|
|
52
|
+
},
|
|
53
|
+
"scripts": {
|
|
54
|
+
"postinstall": "node scripts/postinstall.js",
|
|
55
|
+
"setup": "node scripts/postinstall.js",
|
|
56
|
+
"setup:quick": "node scripts/postinstall.js --quick",
|
|
57
|
+
"build:agents": "node agents/build.js",
|
|
58
|
+
"prepublishOnly": "node agents/build.js"
|
|
59
|
+
},
|
|
60
|
+
"dependencies": {
|
|
61
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
62
|
+
"yaml": "^2.9.0"
|
|
63
|
+
},
|
|
64
|
+
"repository": {
|
|
65
|
+
"type": "git",
|
|
66
|
+
"url": "git+https://github.com/szc-ft/mcp-szcd.git"
|
|
67
|
+
},
|
|
68
|
+
"bugs": {
|
|
69
|
+
"url": "https://github.com/szc-ft/mcp-szcd/issues"
|
|
70
|
+
},
|
|
71
|
+
"homepage": "https://github.com/szc-ft/mcp-szcd#readme"
|
|
72
72
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "szcd-component-helper",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.38.0",
|
|
4
4
|
"description": "szcd 组件库 MCP 助手 — 查询组件信息、匹配需求、生成代码",
|
|
5
5
|
"mcpServers": {
|
|
6
6
|
"szcd-component-helper": {
|
|
@@ -11,6 +11,15 @@
|
|
|
11
11
|
"type": "stdio",
|
|
12
12
|
"command": "sketch-mcp-server",
|
|
13
13
|
"args": []
|
|
14
|
+
},
|
|
15
|
+
"shadcn": {
|
|
16
|
+
"type": "stdio",
|
|
17
|
+
"command": "npx",
|
|
18
|
+
"args": [
|
|
19
|
+
"-y",
|
|
20
|
+
"shadcn@latest",
|
|
21
|
+
"mcp"
|
|
22
|
+
]
|
|
14
23
|
}
|
|
15
24
|
},
|
|
16
25
|
"contextFileName": "QWEN.md",
|
|
@@ -1307,3 +1307,94 @@ NODE_PATH=~/.szcd-mcp/deps/node_modules node "$EXECUTOR" --action test-case --te
|
|
|
1307
1307
|
- ❌ 单元测试 / 组件级测试 → Vitest / Jest / RTL
|
|
1308
1308
|
- ❌ 视觉回归全量对比(无 baseline 设计稿)→ Percy / Chromatic
|
|
1309
1309
|
- ✅ 真实浏览器内的功能验证、API 时序捕获、设计稿单图对比、动态菜单批量扫描
|
|
1310
|
+
|
|
1311
|
+
## rawScript 可用 API(PR #4 v3)
|
|
1312
|
+
|
|
1313
|
+
`rawScript` 步骤支持两种模式:
|
|
1314
|
+
|
|
1315
|
+
### 模式 A:inline code
|
|
1316
|
+
```json
|
|
1317
|
+
{ "type": "rawScript", "code": "return { passed: document.title.includes('登录') }" }
|
|
1318
|
+
```
|
|
1319
|
+
|
|
1320
|
+
### 模式 B:codeFile(PR #4 新增)
|
|
1321
|
+
```json
|
|
1322
|
+
{ "type": "rawScript", "codeFile": ".lbt/scripts/check-title.js" }
|
|
1323
|
+
```
|
|
1324
|
+
- 相对路径相对 `process.cwd()`
|
|
1325
|
+
- 文件不存在 → 明确报错(指出绝对路径)
|
|
1326
|
+
- 文件读失败 → 明确报错
|
|
1327
|
+
|
|
1328
|
+
### rawScript 函数签名
|
|
1329
|
+
|
|
1330
|
+
```js
|
|
1331
|
+
async (page, frame, params, browser) => {
|
|
1332
|
+
// page: puppeteer Page 对象
|
|
1333
|
+
// frame: PuppeteerFrame(iframe 上下文)
|
|
1334
|
+
// params: step.params 对象
|
|
1335
|
+
// browser: puppeteer Browser 对象
|
|
1336
|
+
return { passed: true, ... }; // 或 { passed: false, reason: '...' }
|
|
1337
|
+
}
|
|
1338
|
+
```
|
|
1339
|
+
|
|
1340
|
+
### ✅ 浏览器侧可用 API
|
|
1341
|
+
|
|
1342
|
+
| 类别 | API |
|
|
1343
|
+
|------|-----|
|
|
1344
|
+
| DOM | `document.*` / `window.*` / `frame.document` |
|
|
1345
|
+
| 存储 | `localStorage.*` / `sessionStorage.*` / `cookieStore` |
|
|
1346
|
+
| 网络 | `fetch()` / `XMLHttpRequest` / `WebSocket` |
|
|
1347
|
+
| JS 内置 | `JSON` / `Date` / `Math` / `Array` / `Object` / `Promise` / `async/await` |
|
|
1348
|
+
| 浏览器事件 | `MouseEvent` / `KeyboardEvent` / `CustomEvent` |
|
|
1349
|
+
| puppeteer | `page.*`(evaluate / click / type / waitForSelector 等)|
|
|
1350
|
+
|
|
1351
|
+
### ❌ Node 侧 API 不可用(浏览器无)
|
|
1352
|
+
|
|
1353
|
+
| 错误信息 | 原因 | 浏览器侧替代 |
|
|
1354
|
+
|---------|------|------------|
|
|
1355
|
+
| `TypeError: require is not a function` | 浏览器无 `require` | `await fetch()` / `page.evaluate(() => ...)` |
|
|
1356
|
+
| `ReferenceError: __dirname is not defined` | 浏览器无 Node 全局 | `window.location.origin` / `frame.url()` |
|
|
1357
|
+
| `ReferenceError: process is not defined` | 浏览器无 `process` | `localStorage` / `URL` 替代 |
|
|
1358
|
+
| `ReferenceError: Buffer is not defined` | 浏览器无 Node `Buffer` | `Uint8Array` / `TextEncoder` |
|
|
1359
|
+
| `TypeError: require.main is not a function` | 浏览器无 `require.main` | 不需要 |
|
|
1360
|
+
|
|
1361
|
+
**★ PR #4 修复**:rawScript 执行器会在跑前**静态扫描**代码,发现 Node-only API 时**立即 fail**,错误信息包含"详见 SKILL.md > ## rawScript 可用 API",避免用户踩坑。
|
|
1362
|
+
|
|
1363
|
+
### 常见错误速查
|
|
1364
|
+
|
|
1365
|
+
| 错误 | 修复 |
|
|
1366
|
+
|------|------|
|
|
1367
|
+
| `codeFile 不存在: /path/to/file` | 检查路径或用 inline `code` 替代 |
|
|
1368
|
+
| `rawScript 检测到 Node-only API: require('fs')` | 改用 `page.evaluate(() => ...)` 走浏览器 |
|
|
1369
|
+
| `script compile error: Unexpected token` | 检查 `code` 字段 JSON 转义(`"` → `\"`) |
|
|
1370
|
+
| `script execution error: ...` | 看具体堆栈;可能是 page 已 close 或网络超时 |
|
|
1371
|
+
|
|
1372
|
+
### 示例:检查页面 title
|
|
1373
|
+
|
|
1374
|
+
```js
|
|
1375
|
+
// 模式 A:inline
|
|
1376
|
+
{ "type": "rawScript", "code": "return { passed: document.title.includes('数据集目录'), title: document.title }" }
|
|
1377
|
+
|
|
1378
|
+
// 模式 B:codeFile
|
|
1379
|
+
{ "type": "rawScript", "codeFile": ".lbt/scripts/check-dataset-title.js" }
|
|
1380
|
+
// .lbt/scripts/check-dataset-title.js 内容:
|
|
1381
|
+
// return { passed: document.title.includes('数据集目录'), title: document.title };
|
|
1382
|
+
```
|
|
1383
|
+
|
|
1384
|
+
### 示例:拿所有按钮文本
|
|
1385
|
+
|
|
1386
|
+
```js
|
|
1387
|
+
{
|
|
1388
|
+
"type": "rawScript",
|
|
1389
|
+
"code": "return { passed: true, buttons: [...document.querySelectorAll('button')].map(b => b.textContent.trim()) }"
|
|
1390
|
+
}
|
|
1391
|
+
```
|
|
1392
|
+
|
|
1393
|
+
### 示例:调浏览器 fetch 验证 API
|
|
1394
|
+
|
|
1395
|
+
```js
|
|
1396
|
+
{
|
|
1397
|
+
"type": "rawScript",
|
|
1398
|
+
"code": "const r = await fetch('/api/health'); const j = await r.json(); return { passed: j.status === 'ok', data: j }"
|
|
1399
|
+
}
|
|
1400
|
+
```
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# local-browser-test 配置示例
|
|
2
|
+
# 复制为 .lbt/config.yaml 后填值(config.yaml 已在 .gitignore 内)
|
|
3
|
+
#
|
|
4
|
+
# 占位符 ${ENV_VAR} 走 process.env,CI 推荐用:
|
|
5
|
+
# export LBT_TOKEN=xxx
|
|
6
|
+
# export LBT_PASSWORD=xxx
|
|
7
|
+
|
|
8
|
+
# 应用基础配置
|
|
9
|
+
app:
|
|
10
|
+
baseURL: http://10.2.7.60:30020
|
|
11
|
+
timeout: 30000
|
|
12
|
+
|
|
13
|
+
# 鉴权(强烈建议用环境变量,避免明文)
|
|
14
|
+
auth:
|
|
15
|
+
type: bearer # bearer | cookie | oauth2
|
|
16
|
+
token: ${LBT_TOKEN} # 走环境变量
|
|
17
|
+
# password: ${LBT_PASSWORD}
|
|
18
|
+
# username: ${LBT_USERNAME}
|
|
19
|
+
# verifyCode: ${LBT_VERIFY_CODE}
|
|
20
|
+
storageState: null # 可选:复用 .lbt/auth/<project>.json
|
|
21
|
+
|
|
22
|
+
# 多环境配置(配合 --project <name> 切换)
|
|
23
|
+
projects:
|
|
24
|
+
- name: corpus-test
|
|
25
|
+
baseURL: http://119.0.87.158
|
|
26
|
+
auth:
|
|
27
|
+
type: bearer
|
|
28
|
+
token: ${LBT_TOKEN}
|
|
29
|
+
- name: corpus-stg
|
|
30
|
+
baseURL: http://10.2.7.60:30020
|
|
31
|
+
auth:
|
|
32
|
+
type: bearer
|
|
33
|
+
token: ${LBT_TOKEN}
|
|
34
|
+
- name: corpus-prod
|
|
35
|
+
baseURL: http://172.16.0.172:30010
|
|
36
|
+
auth:
|
|
37
|
+
type: bearer
|
|
38
|
+
token: ${LBT_TOKEN}
|
|
39
|
+
|
|
40
|
+
# 重试策略
|
|
41
|
+
retry:
|
|
42
|
+
maxRetries: 0 # 用例级(CI 跑 2 次)
|
|
43
|
+
maxRetriesPerStep: 2 # 步骤级(验证码过期等)
|
|
44
|
+
flakyThreshold: 5 # 5 次中 1 挂 4 过 → flaky
|
|
45
|
+
noRetryActions: true # 写操作(删除/提交/上线)永不重试
|
|
46
|
+
|
|
47
|
+
# 报告(v4 Playwright 风格)
|
|
48
|
+
report:
|
|
49
|
+
retainRuns: 30 # 保留最近 30 次 run
|
|
50
|
+
retainDays: 7 # 保留最近 7 天
|
|
51
|
+
retainFailedForever: true # 失败 run 永久保留
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* apiCall step 增强执行器(PR #8 v3 走法 P)
|
|
3
|
+
*
|
|
4
|
+
* 关键设计:
|
|
5
|
+
* - API 步骤走 Node 18+ 内置 fetch(**不通过浏览器**,但自动共享 context.cookies())
|
|
6
|
+
* - UI 步骤走 page(真实浏览器,CDP)
|
|
7
|
+
* - 同一 testCase 跑(UI + API 混合),cookie 自动共享
|
|
8
|
+
* - 首次 = 回归 = 真实行为(永远一致)
|
|
9
|
+
* - 不引入 newman 依赖
|
|
10
|
+
* - 不引入 axios 依赖(用 fetch 即可,Node 18+ 内置)
|
|
11
|
+
*
|
|
12
|
+
* 6 个新字段:
|
|
13
|
+
* - assert: L1 status / L2 header / L3 body path / L4 responseTime
|
|
14
|
+
* - captureTrace: HAR-like trace 录制(PR #5 trace-collector 集成)
|
|
15
|
+
* - noRetry: 写操作禁重试(PR #6 retry-policy 集成)
|
|
16
|
+
* - strategy: plan 显式(newman / browser / auto)
|
|
17
|
+
* - headers: 自定义请求头
|
|
18
|
+
* - tags: 用例标签(PR #7)
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { redact } from './redact.js';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* apiCall step 增强 schema
|
|
25
|
+
* @typedef {object} ApiCallStep
|
|
26
|
+
* @property {'apiCall'} type
|
|
27
|
+
* @property {string} method
|
|
28
|
+
* @property {string} url
|
|
29
|
+
* @property {object} [headers]
|
|
30
|
+
* @property {object} [body]
|
|
31
|
+
* @property {object} [assert] - L1-L4 断言
|
|
32
|
+
* @property {boolean} [captureTrace] - 是否记录 trace
|
|
33
|
+
* @property {boolean} [noRetry] - 写操作禁重试(默认 true)
|
|
34
|
+
* @property {string} [strategy] - plan 显式(newman / browser / auto)
|
|
35
|
+
* @property {string[]} [tags] - 用例标签
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 共享 cookie header(PR #8 关键设计)
|
|
40
|
+
* @param {Array<{name: string, value: string, domain?: string}>} cookies
|
|
41
|
+
* @param {string} [targetOrigin]
|
|
42
|
+
* @returns {string} Cookie 头(空字符串如果无 cookie)
|
|
43
|
+
*/
|
|
44
|
+
export function buildCookieHeader(cookies, targetOrigin) {
|
|
45
|
+
if (!Array.isArray(cookies) || cookies.length === 0) return '';
|
|
46
|
+
const filtered = targetOrigin
|
|
47
|
+
? cookies.filter((c) => c.domain?.endsWith(targetOrigin.replace(/^https?:\/\//, '')))
|
|
48
|
+
: cookies;
|
|
49
|
+
return filtered.map((c) => `${c.name}=${c.value}`).join('; ');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* ★ axios interceptor 工厂:记录 timings(PR #5 + PR #8)
|
|
54
|
+
* @param {object} collector - { timings: Array<{name, url, method, total}> }
|
|
55
|
+
* @returns {{ request: Function, response: Function, error: Function }}
|
|
56
|
+
*/
|
|
57
|
+
export function createTimingsInterceptor(collector) {
|
|
58
|
+
return {
|
|
59
|
+
request(config) {
|
|
60
|
+
config.metadata = { startTime: process.hrtime.bigint() };
|
|
61
|
+
return config;
|
|
62
|
+
},
|
|
63
|
+
response(response) {
|
|
64
|
+
const ns = process.hrtime.bigint() - response.config.metadata.startTime;
|
|
65
|
+
const total = Number(ns / 1000000n); // ms
|
|
66
|
+
collector.timings.push({
|
|
67
|
+
name: response.config.metadata.name ?? '',
|
|
68
|
+
url: response.config.url,
|
|
69
|
+
method: response.config.method?.toUpperCase(),
|
|
70
|
+
total,
|
|
71
|
+
status: response.status
|
|
72
|
+
});
|
|
73
|
+
response.config.metadata.total = total;
|
|
74
|
+
return response;
|
|
75
|
+
},
|
|
76
|
+
error(error) {
|
|
77
|
+
if (error.config?.metadata) {
|
|
78
|
+
const ns = process.hrtime.bigint() - error.config.metadata.startTime;
|
|
79
|
+
error.config.metadata.total = Number(ns / 1000000n);
|
|
80
|
+
}
|
|
81
|
+
return Promise.reject(error);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* L1-L4 断言引擎
|
|
88
|
+
* @param {object} response - axios response
|
|
89
|
+
* @param {object} assertSpec - 断言规格
|
|
90
|
+
* @returns {{passed: boolean, checks: Array<{name: string, level: number, passed: boolean, actual: any, expected: any, error?: string}>}}
|
|
91
|
+
*/
|
|
92
|
+
export function assertApiResponse(response, assertSpec) {
|
|
93
|
+
const checks = [];
|
|
94
|
+
let allPassed = true;
|
|
95
|
+
|
|
96
|
+
// L1: status code
|
|
97
|
+
if (assertSpec.status !== undefined) {
|
|
98
|
+
const actual = response.status;
|
|
99
|
+
const expected = assertSpec.status;
|
|
100
|
+
const passed = actual === expected;
|
|
101
|
+
checks.push({ name: `Status is ${expected}`, level: 1, actual, expected, passed });
|
|
102
|
+
if (!passed) allPassed = false;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// L2: header
|
|
106
|
+
if (assertSpec.headers) {
|
|
107
|
+
for (const [key, expected] of Object.entries(assertSpec.headers)) {
|
|
108
|
+
const actual = response.headers?.[key.toLowerCase()];
|
|
109
|
+
const passed = actual === expected;
|
|
110
|
+
checks.push({ name: `Header ${key}=${expected}`, level: 2, actual, expected, passed });
|
|
111
|
+
if (!passed) allPassed = false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// L3: body path
|
|
116
|
+
if (assertSpec.body) {
|
|
117
|
+
for (const [path, expected] of Object.entries(assertSpec.body)) {
|
|
118
|
+
const actual = getByPath(response.data, path);
|
|
119
|
+
const passed = matchValue(actual, expected);
|
|
120
|
+
checks.push({ name: `Body ${path} = ${JSON.stringify(expected)}`, level: 3, actual, expected, passed });
|
|
121
|
+
if (!passed) allPassed = false;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// L4: response time
|
|
126
|
+
if (assertSpec.responseTime !== undefined) {
|
|
127
|
+
const total = response.config?.metadata?.total ?? 0;
|
|
128
|
+
const expected = assertSpec.responseTime;
|
|
129
|
+
const passed = total < expected;
|
|
130
|
+
checks.push({ name: `Response time < ${expected}ms`, level: 4, actual: total, expected, passed });
|
|
131
|
+
if (!passed) allPassed = false;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return { passed: allPassed, checks };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* 路径取值(点号访问嵌套对象)
|
|
139
|
+
*/
|
|
140
|
+
function getByPath(obj, path) {
|
|
141
|
+
return String(path).split('.').reduce((o, k) => o?.[k], obj);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* 操作符匹配
|
|
146
|
+
* @param {any} actual
|
|
147
|
+
* @param {any} expected - 可以是值 / { $gt: n } / { $lt: n } / { $contains: 'x' } / { $regex: 'p' }
|
|
148
|
+
*/
|
|
149
|
+
function matchValue(actual, expected) {
|
|
150
|
+
if (expected && typeof expected === 'object' && !Array.isArray(expected)) {
|
|
151
|
+
if ('$gt' in expected && actual > expected.$gt) return true;
|
|
152
|
+
if ('$lt' in expected && actual < expected.$lt) return true;
|
|
153
|
+
if ('$gte' in expected && actual >= expected.$gte) return true;
|
|
154
|
+
if ('$lte' in expected && actual <= expected.$lte) return true;
|
|
155
|
+
if ('$contains' in expected && String(actual ?? '').includes(expected.$contains)) return true;
|
|
156
|
+
if ('$regex' in expected && new RegExp(expected.$regex).test(String(actual ?? ''))) return true;
|
|
157
|
+
if ('$in' in expected && Array.isArray(expected.$in) && expected.$in.includes(actual)) return true;
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
return actual === expected;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* 执行 apiCall step(v3 走法 P:不通过浏览器,走 axios)
|
|
165
|
+
* @param {ApiCallStep} step
|
|
166
|
+
* @param {object} ctx - { baseURL, cookies, traceCollector? }
|
|
167
|
+
* @returns {Promise<{
|
|
168
|
+
* name: string, status: 'passed'|'failed', duration: number,
|
|
169
|
+
* request: object, response: object,
|
|
170
|
+
* assertions: Array, trace: object|null, error: object|null
|
|
171
|
+
* }>}
|
|
172
|
+
*/
|
|
173
|
+
export async function executeApiCallStep(step, ctx = {}) {
|
|
174
|
+
const startTime = Date.now();
|
|
175
|
+
const method = (step.method || 'POST').toUpperCase();
|
|
176
|
+
const timings = [];
|
|
177
|
+
const collector = { timings };
|
|
178
|
+
|
|
179
|
+
// 1. 构造 axios request:自动共享 context.cookies()
|
|
180
|
+
const cookieHeader = buildCookieHeader(ctx.cookies ?? [], ctx.baseURL);
|
|
181
|
+
const request = axios.create({
|
|
182
|
+
baseURL: ctx.baseURL,
|
|
183
|
+
headers: {
|
|
184
|
+
Cookie: cookieHeader,
|
|
185
|
+
'Content-Type': 'application/json',
|
|
186
|
+
...step.headers
|
|
187
|
+
},
|
|
188
|
+
// 让 axios 接受任意状态码(不抛错)
|
|
189
|
+
validateStatus: () => true
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// 2. ★ axios interceptor(PR #5 + PR #8)
|
|
193
|
+
const interceptor = createTimingsInterceptor(collector);
|
|
194
|
+
request.interceptors.request.use(interceptor.request);
|
|
195
|
+
request.interceptors.response.use(interceptor.response, interceptor.error);
|
|
196
|
+
|
|
197
|
+
// 3. 发请求
|
|
198
|
+
let response;
|
|
199
|
+
let error = null;
|
|
200
|
+
try {
|
|
201
|
+
response = await request({
|
|
202
|
+
method,
|
|
203
|
+
url: step.url,
|
|
204
|
+
data: step.body
|
|
205
|
+
});
|
|
206
|
+
} catch (e) {
|
|
207
|
+
error = {
|
|
208
|
+
message: e.message ?? String(e),
|
|
209
|
+
code: e.code ?? null
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const duration = Date.now() - startTime;
|
|
214
|
+
const status = response?.status ?? 0;
|
|
215
|
+
|
|
216
|
+
// 4. ★ 断言(PR #8 L1-L4)
|
|
217
|
+
let assertionResults = [];
|
|
218
|
+
let passed = error == null && status >= 200 && status < 300; // 默认通过 = 2xx
|
|
219
|
+
if (step.assert && response) {
|
|
220
|
+
const assertResult = assertApiResponse(response, step.assert);
|
|
221
|
+
assertionResults = assertResult.checks;
|
|
222
|
+
if (!assertResult.passed) {
|
|
223
|
+
passed = false;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// 5. ★ 构造返回值(脱敏 + 统一 schema)
|
|
228
|
+
const safeResponse = response ? {
|
|
229
|
+
status: response.status,
|
|
230
|
+
statusText: response.statusText,
|
|
231
|
+
duration: response.config?.metadata?.total ?? 0,
|
|
232
|
+
headers: redact(response.headers ?? {}),
|
|
233
|
+
body: redact(response.data)
|
|
234
|
+
} : null;
|
|
235
|
+
|
|
236
|
+
return {
|
|
237
|
+
name: step.name ?? `${method} ${step.url}`,
|
|
238
|
+
status: passed ? 'passed' : 'failed',
|
|
239
|
+
duration,
|
|
240
|
+
request: {
|
|
241
|
+
method,
|
|
242
|
+
url: step.url,
|
|
243
|
+
headers: redact(step.headers ?? {}),
|
|
244
|
+
body: redact(step.body)
|
|
245
|
+
},
|
|
246
|
+
response: safeResponse,
|
|
247
|
+
assertions: assertionResults,
|
|
248
|
+
trace: step.captureTrace ? { timings: collector.timings } : null,
|
|
249
|
+
error
|
|
250
|
+
};
|
|
251
|
+
}
|