@autobest-ui/agent 1.0.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/README.md +182 -0
- package/bin/sync-assets.mjs +126 -0
- package/bin/sync-assets.test.mjs +64 -0
- package/mcp/azurepr-mcp-bridge/README.md +37 -0
- package/mcp/azurepr-mcp-bridge/azure-devops.js +327 -0
- package/mcp/azurepr-mcp-bridge/config.toml.example +7 -0
- package/mcp/azurepr-mcp-bridge/index.js +65 -0
- package/mcp/azurepr-mcp-bridge/index.test.js +116 -0
- package/mcp/azurepr-mcp-bridge/package.json +22 -0
- package/mcp/rag-mcp-bridge/README.md +42 -0
- package/mcp/rag-mcp-bridge/codex-system-prompt.md +20 -0
- package/mcp/rag-mcp-bridge/config.toml.example +12 -0
- package/mcp/rag-mcp-bridge/index.js +361 -0
- package/mcp/rag-mcp-bridge/index.test.js +56 -0
- package/mcp/rag-mcp-bridge/package.json +21 -0
- package/package.json +44 -0
- package/plugins/autobest-delivery/.codex-plugin/plugin.json +25 -0
- package/plugins/autobest-delivery/.mcp.json +11 -0
- package/plugins/autobest-delivery/README.md +164 -0
- package/plugins/autobest-delivery/assets/delivery-report-template.xlsx +0 -0
- package/plugins/autobest-delivery/mcp-server/npm-shrinkwrap.json +3511 -0
- package/plugins/autobest-delivery/mcp-server/package.json +23 -0
- package/plugins/autobest-delivery/mcp-server/src/paths.mjs +43 -0
- package/plugins/autobest-delivery/mcp-server/src/report.mjs +605 -0
- package/plugins/autobest-delivery/mcp-server/src/runner.mjs +489 -0
- package/plugins/autobest-delivery/mcp-server/src/server.mjs +199 -0
- package/plugins/autobest-delivery/mcp-server/tests/fixture-server.mjs +36 -0
- package/plugins/autobest-delivery/mcp-server/tests/fixtures/basic.feature.mjs +68 -0
- package/plugins/autobest-delivery/mcp-server/tests/mcp-smoke.test.mjs +83 -0
- package/plugins/autobest-delivery/mcp-server/tests/report.test.mjs +254 -0
- package/plugins/autobest-delivery/mcp-server/tests/runner.test.mjs +354 -0
- package/plugins/autobest-delivery/scripts/export-delivery-report.mjs +41 -0
- package/plugins/autobest-delivery/scripts/setup.mjs +295 -0
- package/plugins/autobest-delivery/scripts/setup.test.mjs +145 -0
- package/plugins/autobest-delivery/scripts/start-mcp.mjs +7 -0
- package/plugins/autobest-delivery/skills/code-audit/SKILL.md +24 -0
- package/plugins/autobest-delivery/skills/code-audit/agents/openai.yaml +7 -0
- package/plugins/autobest-delivery/skills/code-craft/SKILL.md +27 -0
- package/plugins/autobest-delivery/skills/code-craft/agents/openai.yaml +7 -0
- package/plugins/autobest-delivery/skills/delivery-loop/SKILL.md +43 -0
- package/plugins/autobest-delivery/skills/delivery-loop/agents/openai.yaml +7 -0
- package/plugins/autobest-delivery/skills/delivery-loop/references/delivery-contract.md +235 -0
- package/plugins/autobest-delivery/skills/e2e-gen-spec/SKILL.md +35 -0
- package/plugins/autobest-delivery/skills/e2e-gen-spec/agents/openai.yaml +7 -0
- package/plugins/autobest-delivery/skills/e2e-ui-checker/SKILL.md +30 -0
- package/plugins/autobest-delivery/skills/e2e-ui-checker/agents/openai.yaml +7 -0
- package/plugins/autobest-delivery/skills/export-report/SKILL.md +66 -0
- package/plugins/autobest-delivery/skills/export-report/agents/openai.yaml +8 -0
- package/plugins/autobest-delivery/skills/ui-structure-guard/SKILL.md +24 -0
- package/plugins/autobest-delivery/skills/ui-structure-guard/agents/openai.yaml +7 -0
- package/skills/README.md +38 -0
- package/skills/common/figma-ui-capture/SKILL.md +197 -0
- package/skills/common/figma-ui-capture/agents/openai.yaml +4 -0
- package/skills/common/ui-prd-scope/SKILL.md +67 -0
- package/skills/common/ui-prd-scope/agents/openai.yaml +4 -0
- package/skills/common/ui-prd-scope/references/scope-schema.md +158 -0
- package/skills/common/ui-prd-scope/scripts/validate-scope-bundle.mjs +302 -0
- package/skills/react/react-code-standards/SKILL.md +78 -0
- package/skills/react/react-code-standards/agents/openai.yaml +4 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import http from 'node:http';
|
|
2
|
+
|
|
3
|
+
const html = `<!doctype html>
|
|
4
|
+
<html lang="zh-CN">
|
|
5
|
+
<head><meta charset="utf-8"><title>Autobest 交付测试页</title></head>
|
|
6
|
+
<body>
|
|
7
|
+
<main>
|
|
8
|
+
<h1>交付测试页</h1>
|
|
9
|
+
<button type="button" id="reveal">显示结果</button>
|
|
10
|
+
<p id="result" aria-live="polite"></p>
|
|
11
|
+
</main>
|
|
12
|
+
<script>
|
|
13
|
+
document.querySelector('#reveal').addEventListener('click', function () {
|
|
14
|
+
document.querySelector('#result').textContent = '运行器运行正常';
|
|
15
|
+
});
|
|
16
|
+
</script>
|
|
17
|
+
</body>
|
|
18
|
+
</html>`;
|
|
19
|
+
|
|
20
|
+
export function startFixtureServer() {
|
|
21
|
+
const server = http.createServer((request, response) => {
|
|
22
|
+
response.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
23
|
+
response.end(html);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
return new Promise((resolve, reject) => {
|
|
27
|
+
server.once('error', reject);
|
|
28
|
+
server.listen(0, '127.0.0.1', () => {
|
|
29
|
+
const address = server.address();
|
|
30
|
+
resolve({
|
|
31
|
+
baseUrl: `http://127.0.0.1:${address.port}`,
|
|
32
|
+
close: () => new Promise(done => server.close(done))
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
export const metadata = {
|
|
2
|
+
reportSchemaVersion: 1,
|
|
3
|
+
scenes: ['initial', 'revealed'],
|
|
4
|
+
viewports: [{ name: 'desktop', width: 1400, height: 900 }],
|
|
5
|
+
visualMappings: [
|
|
6
|
+
{
|
|
7
|
+
scene: 'revealed',
|
|
8
|
+
capture: 'fixture-main.png',
|
|
9
|
+
locator: 'main',
|
|
10
|
+
reportModule: '运行器测试页',
|
|
11
|
+
reportGroup: 'fixture-result',
|
|
12
|
+
reportTitle: '运行结果',
|
|
13
|
+
reportDevice: 'desktop'
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export default async function run({
|
|
19
|
+
page,
|
|
20
|
+
expect,
|
|
21
|
+
check,
|
|
22
|
+
capture,
|
|
23
|
+
artifact,
|
|
24
|
+
baseUrl
|
|
25
|
+
}) {
|
|
26
|
+
await page.setViewportSize({ width: 1400, height: 900 });
|
|
27
|
+
await page.goto(baseUrl, { waitUntil: 'domcontentloaded' });
|
|
28
|
+
|
|
29
|
+
await check(
|
|
30
|
+
{
|
|
31
|
+
id: 'fixture-heading',
|
|
32
|
+
specSnippet: '测试页显示标题。',
|
|
33
|
+
scene: 'initial',
|
|
34
|
+
reportModule: '运行器测试页',
|
|
35
|
+
reportGroup: 'fixture-heading',
|
|
36
|
+
reportTitle: '页面标题',
|
|
37
|
+
reportMethod: '检查页面标题是否正确显示。',
|
|
38
|
+
errorType: '功能缺陷',
|
|
39
|
+
expect: '交付测试页标题可见'
|
|
40
|
+
},
|
|
41
|
+
async () => {
|
|
42
|
+
await expect(page.getByRole('heading', { name: '交付测试页' })).toBeVisible();
|
|
43
|
+
return '交付测试页标题可见';
|
|
44
|
+
}
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
await page.getByRole('button', { name: '显示结果' }).click();
|
|
48
|
+
await check(
|
|
49
|
+
{
|
|
50
|
+
id: 'fixture-result',
|
|
51
|
+
specSnippet: '点击后测试页显示运行结果。',
|
|
52
|
+
scene: 'revealed',
|
|
53
|
+
reportModule: '运行器测试页',
|
|
54
|
+
reportGroup: 'fixture-result',
|
|
55
|
+
reportTitle: '运行结果',
|
|
56
|
+
reportMethod: '检查操作后的运行结果是否正确显示。',
|
|
57
|
+
errorType: '功能缺陷',
|
|
58
|
+
expect: '运行器运行正常提示可见'
|
|
59
|
+
},
|
|
60
|
+
async () => {
|
|
61
|
+
await expect(page.getByText('运行器运行正常')).toBeVisible();
|
|
62
|
+
return '运行器运行正常提示可见';
|
|
63
|
+
}
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
const evidencePath = await capture(page.locator('main'), 'fixture-main.png');
|
|
67
|
+
await artifact('fixture-evidence.json', { evidencePath });
|
|
68
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import test from 'node:test';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
8
|
+
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
9
|
+
import { startFixtureServer } from './fixture-server.mjs';
|
|
10
|
+
|
|
11
|
+
const testsRoot = path.dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
const pluginRoot = path.resolve(testsRoot, '..', '..');
|
|
13
|
+
const fixtureScenario = path.join(testsRoot, 'fixtures', 'basic.feature.mjs');
|
|
14
|
+
|
|
15
|
+
test('stdio MCP 能暴露环境信息并执行功能场景', async t => {
|
|
16
|
+
const fixture = await startFixtureServer();
|
|
17
|
+
t.after(fixture.close);
|
|
18
|
+
const workspaceRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'autobest-mcp-'));
|
|
19
|
+
t.after(() => fs.rm(workspaceRoot, { recursive: true, force: true }));
|
|
20
|
+
const scenarioPath = path.join(workspaceRoot, 'feature', 'e2e', 'e2e.feature.mjs');
|
|
21
|
+
await fs.mkdir(path.dirname(scenarioPath), { recursive: true });
|
|
22
|
+
await fs.copyFile(fixtureScenario, scenarioPath);
|
|
23
|
+
|
|
24
|
+
const pluginConfig = JSON.parse(
|
|
25
|
+
await fs.readFile(path.join(pluginRoot, '.mcp.json'), 'utf8')
|
|
26
|
+
);
|
|
27
|
+
const serverConfig = pluginConfig.mcpServers['autobest-delivery'];
|
|
28
|
+
const transport = new StdioClientTransport({
|
|
29
|
+
command: serverConfig.command,
|
|
30
|
+
args: serverConfig.args,
|
|
31
|
+
cwd: pluginRoot,
|
|
32
|
+
stderr: 'pipe'
|
|
33
|
+
});
|
|
34
|
+
const client = new Client({ name: 'autobest-delivery-test', version: '0.1.0' });
|
|
35
|
+
await client.connect(transport);
|
|
36
|
+
t.after(() => client.close());
|
|
37
|
+
|
|
38
|
+
const environment = await client.callTool({
|
|
39
|
+
name: 'check_environment',
|
|
40
|
+
arguments: {}
|
|
41
|
+
});
|
|
42
|
+
assert.equal(environment.structuredContent.ready, true);
|
|
43
|
+
assert.equal(environment.structuredContent.browserMode, 'headless');
|
|
44
|
+
|
|
45
|
+
const execution = await client.callTool({
|
|
46
|
+
name: 'run_feature_e2e',
|
|
47
|
+
arguments: {
|
|
48
|
+
workspaceRoot,
|
|
49
|
+
scenarioPath: path.relative(workspaceRoot, scenarioPath),
|
|
50
|
+
baseUrl: fixture.baseUrl,
|
|
51
|
+
outputDir: 'feature/e2e/runs/iteration-01',
|
|
52
|
+
iteration: 1,
|
|
53
|
+
timeoutMs: 15000
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
assert.equal(execution.structuredContent.status, 'passed');
|
|
58
|
+
assert.equal(execution.structuredContent.checks.length, 2);
|
|
59
|
+
|
|
60
|
+
const rejectedPath = await client.callTool({
|
|
61
|
+
name: 'run_feature_e2e',
|
|
62
|
+
arguments: {
|
|
63
|
+
workspaceRoot,
|
|
64
|
+
scenarioPath: path.relative(workspaceRoot, scenarioPath),
|
|
65
|
+
baseUrl: fixture.baseUrl,
|
|
66
|
+
outputDir: '../outside',
|
|
67
|
+
iteration: 2,
|
|
68
|
+
timeoutMs: 15000
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
assert.equal(rejectedPath.structuredContent.status, 'blocked');
|
|
72
|
+
assert.match(rejectedPath.structuredContent.blockers[0].detail, /位于 workspaceRoot 内/);
|
|
73
|
+
|
|
74
|
+
const missingSession = await client.callTool({
|
|
75
|
+
name: 'resolve_blocked_run',
|
|
76
|
+
arguments: {
|
|
77
|
+
sessionId: '00000000-0000-4000-8000-000000000000',
|
|
78
|
+
decision: 'retry',
|
|
79
|
+
note: 'MCP schema smoke test'
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
assert.equal(missingSession.structuredContent.status, 'not-found');
|
|
83
|
+
});
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { mkdtemp, mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import test from 'node:test';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
|
+
|
|
9
|
+
import ExcelJS from 'exceljs';
|
|
10
|
+
|
|
11
|
+
import { exportDeliveryReport } from '../src/report.mjs';
|
|
12
|
+
|
|
13
|
+
const pluginRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
14
|
+
const templatePath = path.join(pluginRoot, 'assets', 'delivery-report-template.xlsx');
|
|
15
|
+
const png = Buffer.from(
|
|
16
|
+
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=',
|
|
17
|
+
'base64'
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
async function createFixture() {
|
|
21
|
+
const workspaceRoot = await mkdtemp(path.join(os.tmpdir(), 'autobest-report-'));
|
|
22
|
+
await mkdir(path.join(workspaceRoot, '.git'));
|
|
23
|
+
const featureDir = path.join(workspaceRoot, '.scratch', 'feature');
|
|
24
|
+
const runDir = path.join(featureDir, 'e2e', 'runs', 'iteration-02');
|
|
25
|
+
const capturesDir = path.join(runDir, 'captures');
|
|
26
|
+
await mkdir(path.join(featureDir, 'checker'), { recursive: true });
|
|
27
|
+
await mkdir(capturesDir, { recursive: true });
|
|
28
|
+
await writeFile(
|
|
29
|
+
path.join(featureDir, 'spec.md'),
|
|
30
|
+
'# 订单规格\n\n## 用户故事\n\n1. 作为访客,我希望订单页显示标题。\n2. 作为访客,我希望订单页标题样式正确。\n3. 作为访客,我希望支付操作可用。\n4. 作为访客,我希望错误状态可见。\n\n## 实现决策\n'
|
|
31
|
+
);
|
|
32
|
+
const relativeRun = '.scratch/feature/e2e/runs/iteration-02/runner-result.json';
|
|
33
|
+
const desktop = '.scratch/feature/e2e/runs/iteration-02/captures/order-desktop-payment.png';
|
|
34
|
+
const mobile = '.scratch/feature/e2e/runs/iteration-02/captures/order-mobile-payment.png';
|
|
35
|
+
await writeFile(path.join(workspaceRoot, desktop), png);
|
|
36
|
+
await writeFile(path.join(workspaceRoot, mobile), png);
|
|
37
|
+
await writeFile(
|
|
38
|
+
path.join(featureDir, 'checker', 'checker-result.json'),
|
|
39
|
+
JSON.stringify({
|
|
40
|
+
status: 'failed',
|
|
41
|
+
iteration: 2,
|
|
42
|
+
scenarioSha256: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
|
|
43
|
+
runnerResult: relativeRun,
|
|
44
|
+
checks: [
|
|
45
|
+
{
|
|
46
|
+
id: 'story-02',
|
|
47
|
+
scene: 'order-desktop',
|
|
48
|
+
expect: '标题样式正确',
|
|
49
|
+
actual: '标题样式错误',
|
|
50
|
+
isPass: false
|
|
51
|
+
}
|
|
52
|
+
]
|
|
53
|
+
})
|
|
54
|
+
);
|
|
55
|
+
await writeFile(
|
|
56
|
+
path.join(runDir, 'runner-result.json'),
|
|
57
|
+
JSON.stringify({
|
|
58
|
+
status: 'failed',
|
|
59
|
+
iteration: 2,
|
|
60
|
+
scenarioSha256: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
|
|
61
|
+
metadata: {
|
|
62
|
+
visualMappings: [
|
|
63
|
+
{
|
|
64
|
+
capture: path.basename(desktop),
|
|
65
|
+
scene: 'order-desktop',
|
|
66
|
+
specSnippet: '订单支付操作',
|
|
67
|
+
reportModule: '订单页',
|
|
68
|
+
reportGroup: 'payment-action',
|
|
69
|
+
reportTitle: '支付操作',
|
|
70
|
+
reportDevice: 'desktop'
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
capture: path.basename(mobile),
|
|
74
|
+
scene: 'order-mobile',
|
|
75
|
+
specSnippet: '订单支付操作',
|
|
76
|
+
reportModule: '订单页',
|
|
77
|
+
reportGroup: 'payment-action',
|
|
78
|
+
reportTitle: '支付操作',
|
|
79
|
+
reportDevice: 'mobile'
|
|
80
|
+
}
|
|
81
|
+
]
|
|
82
|
+
},
|
|
83
|
+
checks: [
|
|
84
|
+
{
|
|
85
|
+
id: 'story-01',
|
|
86
|
+
scene: 'order-desktop',
|
|
87
|
+
reportModule: '订单页',
|
|
88
|
+
reportGroup: 'order-heading',
|
|
89
|
+
reportTitle: '订单标题',
|
|
90
|
+
reportMethod: '检查订单页标题的文案、层级和视觉样式。',
|
|
91
|
+
expect: '标题可见',
|
|
92
|
+
actual: '标题可见',
|
|
93
|
+
isPass: true
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
id: 'story-02',
|
|
97
|
+
scene: 'order-desktop',
|
|
98
|
+
reportModule: '订单页',
|
|
99
|
+
reportGroup: 'order-heading',
|
|
100
|
+
reportTitle: '订单标题',
|
|
101
|
+
reportMethod: '检查订单页标题的文案、层级和视觉样式。',
|
|
102
|
+
expect: '标题样式正确',
|
|
103
|
+
actual: '标题样式正确',
|
|
104
|
+
isPass: true
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
id: 'story-03',
|
|
108
|
+
scene: 'order-desktop',
|
|
109
|
+
reportModule: '订单页',
|
|
110
|
+
reportGroup: 'payment-action',
|
|
111
|
+
reportTitle: '支付操作',
|
|
112
|
+
reportMethod: '检查支付操作的可用性和提交结果。',
|
|
113
|
+
expect: '支付操作可以提交',
|
|
114
|
+
actual: '提交成功',
|
|
115
|
+
isPass: true
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
id: 'story-04',
|
|
119
|
+
scene: 'order-error',
|
|
120
|
+
reportModule: '订单页',
|
|
121
|
+
reportGroup: 'order-error',
|
|
122
|
+
reportTitle: '订单错误状态',
|
|
123
|
+
reportMethod: '检查订单请求失败时的错误反馈。',
|
|
124
|
+
expect: '错误状态可见',
|
|
125
|
+
actual: '本轮未执行到该检查点'
|
|
126
|
+
}
|
|
127
|
+
],
|
|
128
|
+
captures: [desktop, mobile]
|
|
129
|
+
})
|
|
130
|
+
);
|
|
131
|
+
return { workspaceRoot, featureDir, runDir, desktop, mobile };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
test('同一组件或功能合并为单 Sheet 一行,不输出原子明细', async () => {
|
|
135
|
+
const { workspaceRoot, featureDir } = await createFixture();
|
|
136
|
+
const inputBefore = await readFile(path.join(featureDir, 'checker', 'checker-result.json'));
|
|
137
|
+
const result = await exportDeliveryReport({ featureDir, workspaceRoot, templatePath });
|
|
138
|
+
assert.equal(result.rows, 3);
|
|
139
|
+
assert.deepEqual(result.statuses, { '通过': 1, '不通过': 1, Blocked: 1 });
|
|
140
|
+
assert.equal(result.embeddedScreenshots, 2);
|
|
141
|
+
assert.equal(result.screenshotPlacements, 2);
|
|
142
|
+
assert.equal(result.groupingSource, 'metadata');
|
|
143
|
+
|
|
144
|
+
const workbook = new ExcelJS.Workbook();
|
|
145
|
+
await workbook.xlsx.readFile(result.outputPath);
|
|
146
|
+
assert.deepEqual(workbook.worksheets.map(worksheet => worksheet.name), ['自测报告']);
|
|
147
|
+
const worksheet = workbook.getWorksheet('自测报告');
|
|
148
|
+
assert.deepEqual(
|
|
149
|
+
Array.from({ length: 6 }, (_, index) => worksheet.getCell(1, index + 1).value),
|
|
150
|
+
['页面/模块', '自测点', '自测方式', '通过', '截图1', '截图2']
|
|
151
|
+
);
|
|
152
|
+
assert.equal(worksheet.getCell('A2').value, '订单页');
|
|
153
|
+
assert.equal(worksheet.getCell('B2').value, '订单标题');
|
|
154
|
+
assert.equal(worksheet.getCell('D2').value, '不通过');
|
|
155
|
+
assert.match(worksheet.getCell('C2').value, /^检查订单页标题的文案、层级和视觉样式。/);
|
|
156
|
+
assert.doesNotMatch(worksheet.getCell('C2').value, /story-/);
|
|
157
|
+
assert.equal(worksheet.getCell('B3').value, '支付操作');
|
|
158
|
+
assert.equal(worksheet.getCell('D3').value, '通过');
|
|
159
|
+
assert.equal(worksheet.getCell('D4').value, 'Blocked');
|
|
160
|
+
assert.equal(workbook.model.media.length, 2);
|
|
161
|
+
assert.deepEqual(
|
|
162
|
+
await readFile(path.join(featureDir, 'checker', 'checker-result.json')),
|
|
163
|
+
inputBefore
|
|
164
|
+
);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test('旧证据必须提供完整语义分组清单,不能按 scene 猜测功能', async () => {
|
|
168
|
+
const { workspaceRoot, featureDir, runDir, desktop, mobile } = await createFixture();
|
|
169
|
+
const runnerPath = path.join(runDir, 'runner-result.json');
|
|
170
|
+
const runner = JSON.parse(await readFile(runnerPath, 'utf8'));
|
|
171
|
+
for (const check of runner.checks) {
|
|
172
|
+
delete check.reportModule;
|
|
173
|
+
delete check.reportGroup;
|
|
174
|
+
delete check.reportTitle;
|
|
175
|
+
delete check.reportMethod;
|
|
176
|
+
}
|
|
177
|
+
for (const mapping of runner.metadata.visualMappings) {
|
|
178
|
+
delete mapping.reportModule;
|
|
179
|
+
delete mapping.reportGroup;
|
|
180
|
+
delete mapping.reportTitle;
|
|
181
|
+
delete mapping.reportDevice;
|
|
182
|
+
}
|
|
183
|
+
await writeFile(runnerPath, JSON.stringify(runner));
|
|
184
|
+
|
|
185
|
+
await assert.rejects(
|
|
186
|
+
exportDeliveryReport({ featureDir, workspaceRoot, templatePath }),
|
|
187
|
+
/旧证据缺少语义分组清单/
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
const specText = await readFile(path.join(featureDir, 'spec.md'), 'utf8');
|
|
191
|
+
const groupingPath = path.join(featureDir, 'report', 'report-groups.json');
|
|
192
|
+
await mkdir(path.dirname(groupingPath), { recursive: true });
|
|
193
|
+
await writeFile(groupingPath, JSON.stringify({
|
|
194
|
+
version: 1,
|
|
195
|
+
source: {
|
|
196
|
+
specSha256: createHash('sha256').update(specText).digest('hex'),
|
|
197
|
+
iteration: 2,
|
|
198
|
+
scenarioSha256: runner.scenarioSha256
|
|
199
|
+
},
|
|
200
|
+
groups: [
|
|
201
|
+
{
|
|
202
|
+
id: 'order-heading',
|
|
203
|
+
module: '订单页',
|
|
204
|
+
title: '订单标题',
|
|
205
|
+
method: '检查订单页标题的文案、层级和视觉样式。',
|
|
206
|
+
storyIds: ['story-01', 'story-02']
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
id: 'payment-action',
|
|
210
|
+
module: '订单页',
|
|
211
|
+
title: '支付操作',
|
|
212
|
+
method: '检查支付操作的可用性和提交结果。',
|
|
213
|
+
storyIds: ['story-03'],
|
|
214
|
+
desktopCapture: path.basename(desktop),
|
|
215
|
+
mobileCapture: path.basename(mobile)
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
id: 'order-error',
|
|
219
|
+
module: '订单页',
|
|
220
|
+
title: '订单错误状态',
|
|
221
|
+
method: '检查订单请求失败时的错误反馈。',
|
|
222
|
+
storyIds: ['story-04']
|
|
223
|
+
}
|
|
224
|
+
]
|
|
225
|
+
}));
|
|
226
|
+
|
|
227
|
+
const result = await exportDeliveryReport({
|
|
228
|
+
featureDir,
|
|
229
|
+
workspaceRoot,
|
|
230
|
+
templatePath,
|
|
231
|
+
groupingPath
|
|
232
|
+
});
|
|
233
|
+
assert.equal(result.rows, 3);
|
|
234
|
+
assert.equal(result.groupingSource, 'manifest');
|
|
235
|
+
assert.equal(result.groupingPath, groupingPath);
|
|
236
|
+
|
|
237
|
+
const workbook = new ExcelJS.Workbook();
|
|
238
|
+
await workbook.xlsx.readFile(result.outputPath);
|
|
239
|
+
assert.deepEqual(workbook.worksheets.map(worksheet => worksheet.name), ['自测报告']);
|
|
240
|
+
assert.equal(workbook.getWorksheet('自测报告').getCell('A2').value, '订单页');
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
test('拒绝功能目录之外的输出路径', async () => {
|
|
244
|
+
const { workspaceRoot, featureDir } = await createFixture();
|
|
245
|
+
await assert.rejects(
|
|
246
|
+
exportDeliveryReport({
|
|
247
|
+
featureDir,
|
|
248
|
+
workspaceRoot,
|
|
249
|
+
templatePath,
|
|
250
|
+
outputPath: path.join(workspaceRoot, 'outside.xlsx')
|
|
251
|
+
}),
|
|
252
|
+
/输出路径必须位于功能目录内/
|
|
253
|
+
);
|
|
254
|
+
});
|