@kkeve/suite 0.0.15 → 0.0.18
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/dist/bin/keve.js +2 -1
- package/dist/commands/clean.js +2 -2
- package/dist/commands/config.js +7 -7
- package/dist/commands/init.js +2 -2
- package/dist/commands/report.js +7 -7
- package/dist/commands/run.js +12 -11
- package/dist/commands/test.js +7 -7
- package/dist/config.js +7 -7
- package/dist/core/action-log-writer.js +4 -1
- package/dist/core/decorator/keve-decorators.d.ts +128 -0
- package/dist/core/decorator/keve-decorators.js +254 -0
- package/dist/core/decorator/keve-host.d.ts +40 -0
- package/dist/core/decorator/keve-host.js +33 -0
- package/dist/core/{keve-registry.d.ts → decorator/keve-registry.d.ts} +19 -0
- package/dist/core/{keve-registry.js → decorator/keve-registry.js} +13 -1
- package/dist/core/generateReport/report.d.ts +1 -1
- package/dist/core/generateReport/report.js +2 -2
- package/dist/core/generateReport/reportData.d.ts +3 -0
- package/dist/core/generateReport/reportData.js +49 -15
- package/dist/core/judge/confidence.d.ts +119 -0
- package/dist/core/judge/confidence.js +295 -0
- package/dist/core/judge/goal-heuristics.d.ts +32 -0
- package/dist/core/judge/goal-heuristics.js +128 -0
- package/dist/core/judge/pngCodec.d.ts +23 -0
- package/dist/core/judge/pngCodec.js +215 -0
- package/dist/core/judge/screenshotDiff.d.ts +112 -0
- package/dist/core/judge/screenshotDiff.js +420 -0
- package/dist/core/page-object/definePage.d.ts +95 -0
- package/dist/core/page-object/definePage.js +62 -0
- package/dist/core/page-object/elementResolver.d.ts +32 -0
- package/dist/core/page-object/elementResolver.js +236 -0
- package/dist/core/page-object/engine-types.d.ts +140 -0
- package/dist/core/page-object/engine-types.js +34 -0
- package/dist/core/sso-cookie.d.ts +18 -0
- package/dist/core/sso-cookie.js +10 -0
- package/dist/dsl/index.d.ts +15 -0
- package/dist/dsl/index.js +29 -0
- package/dist/dsl/locatorSource.d.ts +25 -0
- package/dist/dsl/locatorSource.js +323 -0
- package/dist/dsl/ops.d.ts +46 -0
- package/dist/dsl/ops.js +95 -0
- package/dist/dsl/toDecorator.d.ts +98 -0
- package/dist/dsl/toDecorator.js +747 -0
- package/dist/engine-cypress/adapter.d.ts +108 -0
- package/dist/engine-cypress/adapter.js +514 -0
- package/dist/engine-cypress/agent-page.d.ts +159 -0
- package/dist/engine-cypress/agent-page.js +566 -0
- package/dist/engine-cypress/agentDom.d.ts +22 -0
- package/dist/engine-cypress/agentDom.js +590 -0
- package/dist/engine-cypress/bridge.d.ts +39 -0
- package/dist/engine-cypress/bridge.js +598 -0
- package/dist/engine-cypress/cdp.d.ts +78 -0
- package/dist/engine-cypress/cdp.js +339 -0
- package/dist/engine-cypress/host.d.ts +22 -0
- package/dist/engine-cypress/host.js +480 -0
- package/dist/engine-cypress/index.d.ts +26 -0
- package/dist/engine-cypress/index.js +58 -0
- package/dist/engine-cypress/keveGoal.d.ts +43 -0
- package/dist/engine-cypress/keveGoal.js +265 -0
- package/dist/engine-cypress/pageOps.d.ts +41 -0
- package/dist/engine-cypress/pageOps.js +439 -0
- package/dist/engine-cypress/reporter.d.ts +20 -0
- package/dist/engine-cypress/reporter.js +128 -0
- package/dist/engine-cypress/setup.d.ts +21 -0
- package/dist/engine-cypress/setup.js +27 -0
- package/dist/engine-cypress/shotBuffer.d.ts +40 -0
- package/dist/engine-cypress/shotBuffer.js +41 -0
- package/dist/engine-cypress/state.d.ts +56 -0
- package/dist/engine-cypress/state.js +81 -0
- package/dist/engine-playwright/engineAdapter.d.ts +78 -0
- package/dist/engine-playwright/engineAdapter.js +371 -0
- package/dist/engine-playwright/global-setup.d.ts +28 -0
- package/dist/engine-playwright/global-setup.js +142 -0
- package/dist/{core → engine-playwright}/keve-report.d.ts +0 -1
- package/dist/engine-playwright/keve-report.js +137 -0
- package/dist/{core → engine-playwright}/keve-test.d.ts +10 -8
- package/dist/{core → engine-playwright}/keve-test.js +204 -200
- package/dist/{core → engine-playwright}/pw-config.js +8 -7
- package/dist/index.d.ts +13 -8
- package/dist/index.js +43 -28
- package/dist/page-agent/agent.d.ts +82 -7
- package/dist/page-agent/agent.js +252 -88
- package/dist/page-agent/diagnostics.d.ts +184 -0
- package/dist/page-agent/diagnostics.js +377 -0
- package/dist/page-agent/hooks.d.ts +3 -2
- package/dist/page-agent/hooks.js +4 -3
- package/dist/page-agent/page-like.d.ts +116 -0
- package/dist/page-agent/page-like.js +14 -0
- package/dist/page-agent/playwright-page.d.ts +20 -0
- package/dist/page-agent/playwright-page.js +98 -0
- package/dist/page-agent/system-prompt.d.ts +7 -0
- package/dist/page-agent/system-prompt.js +10 -0
- package/dist/page-agent/tools.d.ts +1 -1
- package/dist/page-agent/tools.js +144 -8
- package/package.json +24 -11
- package/scripts/gen-system-prompt.mjs +36 -0
- package/scripts/run-dsl-task.mjs +652 -0
- package/scripts/runtime/cypress-preprocessor.mjs +44 -0
- package/scripts/runtime/cypress.config.mjs +35 -0
- package/scripts/runtime/playwright.config.mjs +41 -0
- package/scripts/verify-shot-diff.mjs +243 -0
- package/dist/commands/auth-update.d.ts +0 -16
- package/dist/commands/auth-update.js +0 -88
- package/dist/core/auth.d.ts +0 -17
- package/dist/core/auth.js +0 -224
- package/dist/core/cdp-resolver.d.ts +0 -5
- package/dist/core/cdp-resolver.js +0 -93
- package/dist/core/global-setup.d.ts +0 -6
- package/dist/core/global-setup.js +0 -123
- package/dist/core/keve-decorators.d.ts +0 -51
- package/dist/core/keve-decorators.js +0 -95
- package/dist/core/keve-report.js +0 -376
- /package/dist/core/{keve-aspect.d.ts → decorator/keve-aspect.d.ts} +0 -0
- /package/dist/core/{keve-aspect.js → decorator/keve-aspect.js} +0 -0
- /package/dist/{core → engine-playwright}/pw-config.d.ts +0 -0
package/dist/core/keve-report.js
DELETED
|
@@ -1,376 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* keve-report: Playwright Reporter that collects test execution data
|
|
4
|
-
*
|
|
5
|
-
* After EACH test completes (onTestEnd), records test result metadata
|
|
6
|
-
* including step results, error category, AI exploration steps, and diagnostics.
|
|
7
|
-
* Results are appended to confidence-data.jsonl (one line per test).
|
|
8
|
-
*
|
|
9
|
-
* After ALL tests complete (onEnd), generates report-data.json directly,
|
|
10
|
-
* eliminating the need for a separate `keve report` command.
|
|
11
|
-
*
|
|
12
|
-
* Usage in playwright config:
|
|
13
|
-
* reporter: [
|
|
14
|
-
* ['list'],
|
|
15
|
-
* ['@kkeve/suite/keve-report'],
|
|
16
|
-
* ]
|
|
17
|
-
*/
|
|
18
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
19
|
-
if (k2 === undefined) k2 = k;
|
|
20
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
21
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
22
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
23
|
-
}
|
|
24
|
-
Object.defineProperty(o, k2, desc);
|
|
25
|
-
}) : (function(o, m, k, k2) {
|
|
26
|
-
if (k2 === undefined) k2 = k;
|
|
27
|
-
o[k2] = m[k];
|
|
28
|
-
}));
|
|
29
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
30
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
31
|
-
}) : function(o, v) {
|
|
32
|
-
o["default"] = v;
|
|
33
|
-
});
|
|
34
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
35
|
-
var ownKeys = function(o) {
|
|
36
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
37
|
-
var ar = [];
|
|
38
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
39
|
-
return ar;
|
|
40
|
-
};
|
|
41
|
-
return ownKeys(o);
|
|
42
|
-
};
|
|
43
|
-
return function (mod) {
|
|
44
|
-
if (mod && mod.__esModule) return mod;
|
|
45
|
-
var result = {};
|
|
46
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
47
|
-
__setModuleDefault(result, mod);
|
|
48
|
-
return result;
|
|
49
|
-
};
|
|
50
|
-
})();
|
|
51
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
52
|
-
const fs = __importStar(require("fs"));
|
|
53
|
-
const path = __importStar(require("path"));
|
|
54
|
-
const reportData_1 = require("./generateReport/reportData");
|
|
55
|
-
const reportData_2 = require("./generateReport/reportData");
|
|
56
|
-
// Error classification patterns (duplicated from keve-decorators.ts for onTestEnd primary classification)
|
|
57
|
-
const ENV_ERROR_PATTERNS = /timeout|timed out|locator.*not found|navigation.*intercepted|net::|ERR_|authenticate|redirect|ECONNREFUSED|socket hang up|Cannot navigate to invalid URL|Protocol error.*Page\.navigate/i;
|
|
58
|
-
class KeveReporter {
|
|
59
|
-
outputPath = '';
|
|
60
|
-
resultDir = '';
|
|
61
|
-
onBegin(config, suite) {
|
|
62
|
-
// Resolve resultDir: taskDir/test-artifacts/round-N (matches Playwright config)
|
|
63
|
-
const taskDir = process.env.KEVE_TASK_DIR || '.keve';
|
|
64
|
-
const round = process.env.KEVE_ROUND || 'latest';
|
|
65
|
-
this.resultDir = path.join(taskDir, 'test-artifacts', `round-${round}`);
|
|
66
|
-
this.outputPath = path.join(this.resultDir, 'confidence-data.jsonl');
|
|
67
|
-
if (!fs.existsSync(this.resultDir)) {
|
|
68
|
-
fs.mkdirSync(this.resultDir, { recursive: true });
|
|
69
|
-
}
|
|
70
|
-
console.log(`[keve-reporter] Confidence evaluation will be saved to: ${this.outputPath}`);
|
|
71
|
-
}
|
|
72
|
-
// Confidence values by errorCategory — gives meaningful scores instead of always 0
|
|
73
|
-
static CONFIDENCE_MAP = {
|
|
74
|
-
pass: 100, // 测试通过,确定性最高
|
|
75
|
-
script: 95, // 脚本代码错误,结果确定性高
|
|
76
|
-
visual: 80, // 视觉断言已评估,置信度较高
|
|
77
|
-
'text-mismatch': 70, // UI文案格式不匹配(功能逻辑正确,文案措辞变了)
|
|
78
|
-
assert: 50, // Playwright 断言失败,需人工确认
|
|
79
|
-
incomplete: 0, // 执行被中断(超时/action 耗尽),无法得出结论
|
|
80
|
-
env: 0, // 环境问题,测试结果不可信
|
|
81
|
-
unknown: 0, // 未知异常,无参考价值
|
|
82
|
-
};
|
|
83
|
-
async onTestEnd(test, result) {
|
|
84
|
-
// ── Step 0: Extract Agent conclusion from keveGoalResult attachments ──
|
|
85
|
-
const steps = parseSteps(result.attachments);
|
|
86
|
-
const agentConclusion = extractAgentConclusion(steps);
|
|
87
|
-
// Error classification: Agent conclusion takes priority over fnError
|
|
88
|
-
let effectiveCategory = 'unknown';
|
|
89
|
-
let skipAI = false;
|
|
90
|
-
let classifiedMessage = '';
|
|
91
|
-
if (result.status === 'skipped') {
|
|
92
|
-
// 主动跳过(testInfo.skip,如"非自动执行")— 独立处理,data='跳过'
|
|
93
|
-
this.appendRecord({
|
|
94
|
-
title: test.title,
|
|
95
|
-
data: '跳过',
|
|
96
|
-
confidence: 0,
|
|
97
|
-
thought: '用例被跳过(非自动执行)',
|
|
98
|
-
errorCategory: 'unknown',
|
|
99
|
-
steps,
|
|
100
|
-
diagnosticHint: undefined,
|
|
101
|
-
});
|
|
102
|
-
console.log(`[keve-reporter] ${test.title}: SKIPPED`);
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
else if (result.status === 'passed') {
|
|
106
|
-
effectiveCategory = 'pass';
|
|
107
|
-
}
|
|
108
|
-
else if (agentConclusion) {
|
|
109
|
-
// ── Agent provided a conclusion — use it directly (most reliable) ──
|
|
110
|
-
classifiedMessage = agentConclusion.text.substring(0, 500);
|
|
111
|
-
if (agentConclusion.result === 'pass') {
|
|
112
|
-
// Agent said pass but test still failed → fn script issue (not env)
|
|
113
|
-
effectiveCategory = 'pass';
|
|
114
|
-
skipAI = true;
|
|
115
|
-
}
|
|
116
|
-
else if (agentConclusion.result === 'blocked') {
|
|
117
|
-
effectiveCategory = 'env';
|
|
118
|
-
skipAI = true;
|
|
119
|
-
}
|
|
120
|
-
else if (agentConclusion.result === 'incomplete') {
|
|
121
|
-
// Agent 执行被中断(超时/action 耗尽),无法得出结论
|
|
122
|
-
effectiveCategory = 'incomplete';
|
|
123
|
-
skipAI = true;
|
|
124
|
-
}
|
|
125
|
-
else {
|
|
126
|
-
// Agent said fail → assert/text-mismatch depending on diagnostic hints
|
|
127
|
-
const hint = buildDiagnosticHint(result.attachments);
|
|
128
|
-
if (hint && (hint.includes('实际显示') || hint.includes('页面实际') || hint.includes('而非预期'))) {
|
|
129
|
-
effectiveCategory = 'text-mismatch';
|
|
130
|
-
skipAI = false;
|
|
131
|
-
}
|
|
132
|
-
else {
|
|
133
|
-
effectiveCategory = 'assert';
|
|
134
|
-
skipAI = false;
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
else if (result.error) {
|
|
139
|
-
// ── No Agent conclusion — fall back to fnError classification ──
|
|
140
|
-
const rawError = result.error.message || String(result.error);
|
|
141
|
-
classifiedMessage = rawError.substring(0, 500);
|
|
142
|
-
// 1. 脚本错误:确定性短路
|
|
143
|
-
const err = result.error;
|
|
144
|
-
if (err instanceof TypeError || err instanceof ReferenceError || err instanceof SyntaxError) {
|
|
145
|
-
effectiveCategory = 'script';
|
|
146
|
-
skipAI = true;
|
|
147
|
-
}
|
|
148
|
-
// 2. 视觉断言失败(keveAssert 抛出的特定错误)
|
|
149
|
-
else if (rawError.includes('视觉断言失败')) {
|
|
150
|
-
effectiveCategory = 'visual';
|
|
151
|
-
skipAI = true;
|
|
152
|
-
}
|
|
153
|
-
// 3. Playwright expect 断言失败
|
|
154
|
-
else if (rawError.includes('expect(') || err?.constructor?.name === 'AssertionError' || err?.name === 'AssertionError') {
|
|
155
|
-
effectiveCategory = 'assert';
|
|
156
|
-
skipAI = false;
|
|
157
|
-
}
|
|
158
|
-
// 4. 环境/基础设施错误
|
|
159
|
-
else if (ENV_ERROR_PATTERNS.test(rawError.substring(0, 300))) {
|
|
160
|
-
effectiveCategory = 'env';
|
|
161
|
-
skipAI = true;
|
|
162
|
-
}
|
|
163
|
-
// 5. Re-Act loop 失败:执行被中断(action 耗尽 / 未达预期)→ incomplete
|
|
164
|
-
else if (rawError.includes('Re-Act loop did not achieve') || rawError.includes('Expected not achieved after agent explore')) {
|
|
165
|
-
effectiveCategory = 'incomplete';
|
|
166
|
-
skipAI = true;
|
|
167
|
-
}
|
|
168
|
-
// 5.5 文案不匹配
|
|
169
|
-
else if (rawError.includes('Expected not achieved')) {
|
|
170
|
-
const hint = buildDiagnosticHint(result.attachments);
|
|
171
|
-
if (hint && (hint.includes('实际显示') || hint.includes('页面实际'))) {
|
|
172
|
-
effectiveCategory = 'text-mismatch';
|
|
173
|
-
skipAI = false;
|
|
174
|
-
}
|
|
175
|
-
else {
|
|
176
|
-
effectiveCategory = 'incomplete';
|
|
177
|
-
skipAI = true;
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
// 6. 兜底
|
|
181
|
-
else {
|
|
182
|
-
effectiveCategory = 'unknown';
|
|
183
|
-
skipAI = true;
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
// Script/visual/env error short-circuit — skip AI evaluation
|
|
187
|
-
if (skipAI) {
|
|
188
|
-
const thought = effectiveCategory === 'script'
|
|
189
|
-
? `脚本本身存在代码错误(${classifiedMessage}),测试结果无效`
|
|
190
|
-
: effectiveCategory === 'env'
|
|
191
|
-
? `环境/基础设施异常(${classifiedMessage}),非应用功能问题`
|
|
192
|
-
: effectiveCategory === 'visual'
|
|
193
|
-
? `视觉断言失败(${classifiedMessage}),已由keveAssert评估`
|
|
194
|
-
: effectiveCategory === 'unknown'
|
|
195
|
-
? `无法分类的异常(${classifiedMessage}),跳过AI评估`
|
|
196
|
-
: effectiveCategory === 'pass'
|
|
197
|
-
? `Agent判定通过但fn脚本执行失败(${classifiedMessage}),脚本需修复`
|
|
198
|
-
: effectiveCategory === 'incomplete'
|
|
199
|
-
? `执行被中断(${classifiedMessage}),Agent未完成目标,建议重跑`
|
|
200
|
-
: `无法分类的异常(${classifiedMessage})`;
|
|
201
|
-
// AI评估列输出:
|
|
202
|
-
// - assert/text-mismatch → "不通过"(有明确失败结论,skipAI=false)
|
|
203
|
-
// - 其余所有无法得出结论的 → "阻塞"(包括 incomplete/env/script/visual/pass/unknown)
|
|
204
|
-
const aiData = (effectiveCategory === 'assert' || effectiveCategory === 'text-mismatch') ? '不通过' : '阻塞';
|
|
205
|
-
const keveScreenshots = (0, reportData_1.parseKeveAsserts)(result.attachments).map(ka => ka.label);
|
|
206
|
-
this.appendRecord({
|
|
207
|
-
title: test.title,
|
|
208
|
-
data: aiData,
|
|
209
|
-
confidence: KeveReporter.CONFIDENCE_MAP[effectiveCategory] ?? 0,
|
|
210
|
-
thought,
|
|
211
|
-
errorCategory: effectiveCategory,
|
|
212
|
-
...(keveScreenshots.length > 0 ? { keveScreenshots } : {}), // 仅非空时写入
|
|
213
|
-
steps,
|
|
214
|
-
diagnosticHint: buildDiagnosticHint(result.attachments),
|
|
215
|
-
});
|
|
216
|
-
console.log(`[keve-reporter] ${test.title}: SHORT-CIRCUIT skipAI (category=${effectiveCategory})`);
|
|
217
|
-
return;
|
|
218
|
-
}
|
|
219
|
-
// Record result without AI confidence evaluation
|
|
220
|
-
const keveScreenshots = (0, reportData_1.parseKeveAsserts)(result.attachments).map(ka => ka.label);
|
|
221
|
-
this.appendRecord({
|
|
222
|
-
title: test.title,
|
|
223
|
-
data: result.status === 'passed' ? '通过' : '不通过',
|
|
224
|
-
confidence: KeveReporter.CONFIDENCE_MAP[effectiveCategory] ?? 0,
|
|
225
|
-
thought: '',
|
|
226
|
-
errorCategory: effectiveCategory,
|
|
227
|
-
...(keveScreenshots.length > 0 ? { keveScreenshots } : {}),
|
|
228
|
-
steps,
|
|
229
|
-
diagnosticHint: buildDiagnosticHint(result.attachments),
|
|
230
|
-
});
|
|
231
|
-
console.log(`[keve-reporter] ${test.title}: ${result.status} (category=${effectiveCategory})`);
|
|
232
|
-
}
|
|
233
|
-
async onEnd(result) {
|
|
234
|
-
console.log(`[keve-reporter] All tests completed. Generating report-data.json...`);
|
|
235
|
-
// ── 直接生成 report-data.json(不再需要 keve report 命令) ──
|
|
236
|
-
try {
|
|
237
|
-
const taskDir = process.env.KEVE_TASK_DIR || '.keve';
|
|
238
|
-
const projectRoot = taskDir;
|
|
239
|
-
// 找到 test-results.json(Playwright JSON Reporter 写入)
|
|
240
|
-
const testResultsPath = path.join(this.resultDir, 'test-results.json');
|
|
241
|
-
const confidencePath = this.outputPath;
|
|
242
|
-
if (fs.existsSync(testResultsPath) && fs.existsSync(confidencePath)) {
|
|
243
|
-
// YAML 用例文件在 {taskDir}/cases/test-cases.yaml
|
|
244
|
-
const casesYamlPath = path.join(taskDir, 'cases', 'test-cases.yaml');
|
|
245
|
-
const reportData = await (0, reportData_2.generateReportData)({
|
|
246
|
-
projectRoot,
|
|
247
|
-
resultsPath: testResultsPath,
|
|
248
|
-
confidencePath,
|
|
249
|
-
casesPath: fs.existsSync(casesYamlPath) ? casesYamlPath : undefined,
|
|
250
|
-
});
|
|
251
|
-
// 写入 report-data.json
|
|
252
|
-
const reportDataPath = path.join(this.resultDir, 'report-data.json');
|
|
253
|
-
fs.writeFileSync(reportDataPath, JSON.stringify(reportData, null, 2));
|
|
254
|
-
// 更新 latest 目录 → symlink 指向最新 round(避免重复拷贝 34MB+)
|
|
255
|
-
const latestDir = path.join(taskDir, 'test-artifacts', 'latest');
|
|
256
|
-
if (latestDir !== this.resultDir) {
|
|
257
|
-
try {
|
|
258
|
-
if (fs.existsSync(latestDir)) {
|
|
259
|
-
const stat = fs.lstatSync(latestDir);
|
|
260
|
-
if (stat.isSymbolicLink()) {
|
|
261
|
-
fs.unlinkSync(latestDir);
|
|
262
|
-
}
|
|
263
|
-
else {
|
|
264
|
-
fs.rmSync(latestDir, { recursive: true, force: true });
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
fs.symlinkSync(path.basename(this.resultDir), latestDir, 'junction');
|
|
268
|
-
}
|
|
269
|
-
catch { /* ignore latest symlink error */ }
|
|
270
|
-
}
|
|
271
|
-
console.log(`[keve-reporter] report-data.json generated at: ${reportDataPath}`);
|
|
272
|
-
}
|
|
273
|
-
else {
|
|
274
|
-
console.log(`[keve-reporter] Skipped report-data.json generation (missing test-results.json or confidence-data.jsonl)`);
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
catch (err) {
|
|
278
|
-
console.error(`[keve-reporter] Failed to generate report-data.json: ${err.message}`);
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
appendRecord(record) {
|
|
282
|
-
// 清洗 steps:移除 screenshotBase64(截图已有文件路径保存)
|
|
283
|
-
// 移除 snapshotPreview / finalSnapshot(冗余大字段,截图路径可获取相同信息)
|
|
284
|
-
if (record.steps) {
|
|
285
|
-
record.steps = record.steps.map((step) => {
|
|
286
|
-
const { screenshotBase64, finalSnapshot, ...stepRest } = step;
|
|
287
|
-
if (stepRest.actions) {
|
|
288
|
-
stepRest.actions = stepRest.actions.map((a) => {
|
|
289
|
-
const { snapshotPreview, ...actionRest } = a;
|
|
290
|
-
return actionRest;
|
|
291
|
-
});
|
|
292
|
-
}
|
|
293
|
-
return stepRest;
|
|
294
|
-
});
|
|
295
|
-
}
|
|
296
|
-
fs.appendFileSync(this.outputPath, JSON.stringify(record) + '\n');
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
// ── 解析 keveGoalResult attachment(合并后统一步骤数据) ──────────────
|
|
300
|
-
/** Parse all keveGoalResult attachments into a unified steps array */
|
|
301
|
-
function parseSteps(attachments) {
|
|
302
|
-
const results = [];
|
|
303
|
-
for (const att of attachments) {
|
|
304
|
-
if (att.name !== 'keveGoalResult' || !att.body)
|
|
305
|
-
continue;
|
|
306
|
-
try {
|
|
307
|
-
let jsonStr;
|
|
308
|
-
if (typeof att.body === 'string') {
|
|
309
|
-
// Playwright JSON Reporter may base64-encode Buffer bodies
|
|
310
|
-
try {
|
|
311
|
-
jsonStr = Buffer.from(att.body, 'base64').toString('utf-8');
|
|
312
|
-
JSON.parse(jsonStr); // validate it's JSON
|
|
313
|
-
}
|
|
314
|
-
catch {
|
|
315
|
-
jsonStr = att.body; // fallback: treat as raw JSON string
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
else {
|
|
319
|
-
jsonStr = att.body.toString('utf-8');
|
|
320
|
-
}
|
|
321
|
-
results.push(JSON.parse(jsonStr));
|
|
322
|
-
}
|
|
323
|
-
catch { /* skip malformed attachment */ }
|
|
324
|
-
}
|
|
325
|
-
return results;
|
|
326
|
-
}
|
|
327
|
-
/** Extract Agent conclusion (result + text) from keveGoalResult steps */
|
|
328
|
-
function extractAgentConclusion(steps) {
|
|
329
|
-
if (steps.length === 0)
|
|
330
|
-
return undefined;
|
|
331
|
-
// keveGoal 串行执行:取最后一步的结论作为用例整体结论
|
|
332
|
-
const lastStep = steps[steps.length - 1];
|
|
333
|
-
// 最后一步有明确结论(Agent 调用了 done)→ 直接使用
|
|
334
|
-
if (lastStep.conclusion) {
|
|
335
|
-
const doneAction = lastStep.actions?.find(a => a.tool === 'done');
|
|
336
|
-
return { result: lastStep.conclusion, text: doneAction?.text || '' };
|
|
337
|
-
}
|
|
338
|
-
// Action 级别结论兜底(来自 done action 的 result 字段)
|
|
339
|
-
if (lastStep.actions) {
|
|
340
|
-
for (let j = lastStep.actions.length - 1; j >= 0; j--) {
|
|
341
|
-
const action = lastStep.actions[j];
|
|
342
|
-
if (action?.tool === 'done' && action?.conclusion) {
|
|
343
|
-
return { result: action.conclusion, text: action.text || '' };
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
// 最后一步无任何结论 → 执行被中断(超时 / action 耗尽)
|
|
348
|
-
return { result: 'incomplete', text: '执行被中断,Agent未完成目标' };
|
|
349
|
-
}
|
|
350
|
-
/** Build diagnosticHint from keveGoalResult attachments (primary) + fallback to keveDiagnosticHint */
|
|
351
|
-
function buildDiagnosticHint(attachments) {
|
|
352
|
-
// 优先从 keveGoalResult 中提取 diagnosticHints
|
|
353
|
-
const goalResults = parseSteps(attachments);
|
|
354
|
-
const allHints = [];
|
|
355
|
-
for (const gr of goalResults) {
|
|
356
|
-
if (gr.success)
|
|
357
|
-
continue;
|
|
358
|
-
if (gr.diagnosticHints?.length) {
|
|
359
|
-
allHints.push(...gr.diagnosticHints);
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
if (allHints.length)
|
|
363
|
-
return allHints.join(';');
|
|
364
|
-
// 兜底:从旧的 keveDiagnosticHint attachment 提取
|
|
365
|
-
const hint = attachments.find(a => a.name === 'keveDiagnosticHint');
|
|
366
|
-
if (!hint || !hint.body)
|
|
367
|
-
return undefined;
|
|
368
|
-
try {
|
|
369
|
-
const data = typeof hint.body === 'string' ? JSON.parse(hint.body) : JSON.parse(hint.body.toString('utf-8'));
|
|
370
|
-
return data.hints?.join(';') || undefined;
|
|
371
|
-
}
|
|
372
|
-
catch {
|
|
373
|
-
return undefined;
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
exports.default = KeveReporter;
|
|
File without changes
|
|
File without changes
|
|
File without changes
|