@kkeve/suite 0.0.9 → 0.0.11
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/commands/run.js
CHANGED
|
@@ -264,24 +264,28 @@ async function run(options) {
|
|
|
264
264
|
console.log(chalk_1.default.gray(` Passed: ${passed}, Failed: ${failed}, Skipped: ${skipped}`));
|
|
265
265
|
(0, config_1.completeRound)(taskDir, round, { passed, failed, skipped });
|
|
266
266
|
stripBase64FromTestResults(resultsFile, resultDir);
|
|
267
|
-
// ──
|
|
267
|
+
// ── 生成 report-data.json:在 Playwright 进程结束后统一生成,避免 keve-reporter 的 onEnd 与 JSON reporter 的 race condition ──
|
|
268
|
+
// keve-reporter.ts 的 onEnd 在 Playwright 进程内部执行,此时 JSON reporter 可能尚未完全刷新 test-results.json,
|
|
269
|
+
// 导致 generateReportData 读取到旧数据(specs 为空)→ 所有 case 被标记为 missing。
|
|
270
|
+
// 因此在 run() 外部(Playwright 进程结束后)统一重新生成,确保 test-results.json 已完全落盘。
|
|
268
271
|
const reportDataPath = path.join(resultDir, 'report-data.json');
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
272
|
+
try {
|
|
273
|
+
const confidencePath = path.join(resultDir, 'confidence-data.jsonl');
|
|
274
|
+
const casesPath = path.join(taskDir, 'cases', 'test-cases.yaml');
|
|
275
|
+
const reportData = await (0, reportData_1.generateReportData)({
|
|
276
|
+
projectRoot: taskDir,
|
|
277
|
+
resultsPath: resultsFile,
|
|
278
|
+
confidencePath: fs.existsSync(confidencePath) ? confidencePath : undefined,
|
|
279
|
+
casesPath: fs.existsSync(casesPath) ? casesPath : undefined,
|
|
280
|
+
});
|
|
281
|
+
fs.writeFileSync(reportDataPath, JSON.stringify(reportData, null, 2));
|
|
282
|
+
console.log(chalk_1.default.green(` ✓ report-data.json generated at: ${reportDataPath}`));
|
|
283
|
+
}
|
|
284
|
+
catch (err) {
|
|
285
|
+
console.error(chalk_1.default.red(` ✗ report-data.json generation failed: ${err.message}`));
|
|
286
|
+
// 主生成失败时,若 report-data.json 仍不存在,尝试生成极简 fallback
|
|
287
|
+
if (!fs.existsSync(reportDataPath)) {
|
|
288
|
+
try {
|
|
285
289
|
const fallbackReport = {
|
|
286
290
|
round,
|
|
287
291
|
taskDir,
|
|
@@ -289,7 +293,6 @@ async function run(options) {
|
|
|
289
293
|
cases: [],
|
|
290
294
|
warning: 'report-data.json generated by fallback — some test data may be incomplete',
|
|
291
295
|
};
|
|
292
|
-
// 尝试从 test-results.json 提取基础统计
|
|
293
296
|
if (fs.existsSync(resultsFile)) {
|
|
294
297
|
try {
|
|
295
298
|
const results = JSON.parse(fs.readFileSync(resultsFile, 'utf-8'));
|
|
@@ -306,27 +309,27 @@ async function run(options) {
|
|
|
306
309
|
fs.writeFileSync(reportDataPath, JSON.stringify(fallbackReport, null, 2));
|
|
307
310
|
console.log(chalk_1.default.yellow(` ⚠ Minimal fallback report-data.json generated (missing confidence data)`));
|
|
308
311
|
}
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
if (latestDir !== resultDir) {
|
|
312
|
-
try {
|
|
313
|
-
if (fs.existsSync(latestDir)) {
|
|
314
|
-
const stat = fs.lstatSync(latestDir);
|
|
315
|
-
if (stat.isSymbolicLink()) {
|
|
316
|
-
fs.unlinkSync(latestDir);
|
|
317
|
-
}
|
|
318
|
-
else {
|
|
319
|
-
fs.rmSync(latestDir, { recursive: true, force: true });
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
fs.symlinkSync(path.basename(resultDir), latestDir, 'junction');
|
|
323
|
-
}
|
|
324
|
-
catch { /* ignore latest symlink error */ }
|
|
312
|
+
catch (fallbackErr) {
|
|
313
|
+
console.error(chalk_1.default.red(` ✗ Fallback report generation failed: ${fallbackErr.message}`));
|
|
325
314
|
}
|
|
326
315
|
}
|
|
327
|
-
|
|
328
|
-
|
|
316
|
+
}
|
|
317
|
+
// 同步到 latest 目录 → symlink 指向最新 round(避免重复拷贝 34MB+)
|
|
318
|
+
const latestDir = path.join(taskDir, 'test-artifacts', 'latest');
|
|
319
|
+
if (latestDir !== resultDir) {
|
|
320
|
+
try {
|
|
321
|
+
if (fs.existsSync(latestDir)) {
|
|
322
|
+
const stat = fs.lstatSync(latestDir);
|
|
323
|
+
if (stat.isSymbolicLink()) {
|
|
324
|
+
fs.unlinkSync(latestDir);
|
|
325
|
+
}
|
|
326
|
+
else {
|
|
327
|
+
fs.rmSync(latestDir, { recursive: true, force: true });
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
fs.symlinkSync(path.basename(resultDir), latestDir, 'junction');
|
|
329
331
|
}
|
|
332
|
+
catch { /* ignore latest symlink error */ }
|
|
330
333
|
}
|
|
331
334
|
return { exitCode, resultsFile, resultDir, taskDir, round, passed, failed, skipped };
|
|
332
335
|
}
|
|
@@ -68,10 +68,14 @@ function resolveOptions(opts) {
|
|
|
68
68
|
return { PROJECT_ROOT, TEST_RESULTS_JSON, CONFIDENCE_DATA_JSONL, TEST_CASES_YAML, SCREENSHOTS_DIR, REPORTS_DIR };
|
|
69
69
|
}
|
|
70
70
|
// ─── Helpers ─────────────────────────────────────────────────────
|
|
71
|
+
// 支持两种 caseId 格式:
|
|
72
|
+
// 1. LLM 生成的 AG-01 / WW-01 / REG-01 等:「字母-数字」用 dash 分隔
|
|
73
|
+
// 2. ktest 原始的 TCDPQA100195 / TCDPQA100189 等:「大写字母+数字」连续无分隔
|
|
74
|
+
// (ktest 命名规则:TC=TestCase DPQA=数据平台QA 100195=序列号)
|
|
71
75
|
function parseCaseIdFromTitle(title) {
|
|
72
|
-
const colonMatch = title.match(/^([A-Z]+(?:-[A-Z]+)*-\d+):/);
|
|
73
|
-
const bracketMatch = title.match(/\[([A-Z]+(?:-[A-Z]+)*-\d+)\]/);
|
|
74
|
-
const spaceMatch = title.match(/^([A-Z]+(?:-[A-Z]+)*-\d+)\s/);
|
|
76
|
+
const colonMatch = title.match(/^([A-Z]+(?:-[A-Z]+)*-\d+|[A-Z]+\d+):/);
|
|
77
|
+
const bracketMatch = title.match(/\[([A-Z]+(?:-[A-Z]+)*-\d+|[A-Z]+\d+)\]/);
|
|
78
|
+
const spaceMatch = title.match(/^([A-Z]+(?:-[A-Z]+)*-\d+|[A-Z]+\d+)\s/);
|
|
75
79
|
return colonMatch?.[1] || bracketMatch?.[1] || spaceMatch?.[1] || null;
|
|
76
80
|
}
|
|
77
81
|
/**
|
|
@@ -398,7 +402,8 @@ function buildCaseResultMap(specs, taskDir) {
|
|
|
398
402
|
const suitePathParts = spec.suitePath.split(' > ');
|
|
399
403
|
const pwModuleTitle = suitePathParts.length >= 2 ? suitePathParts[suitePathParts.length - 1].trim() : suitePathParts[0].trim();
|
|
400
404
|
// @keveModel title format: "${id}: ${description}" — extract module ID and description
|
|
401
|
-
|
|
405
|
+
// 模块 ID 可能是字母格式(如 AG-01)或 ktest 纯数字格式(如 7473423)
|
|
406
|
+
const moduleIdMatch = pwModuleTitle.match(/^([A-Z]+(?:-[A-Z]+)*|\d+):\s*(.+)$/);
|
|
402
407
|
const pwModuleId = moduleIdMatch?.[1] || '';
|
|
403
408
|
const pwModuleDescription = moduleIdMatch?.[2] || pwModuleTitle;
|
|
404
409
|
map[caseId] = {
|
|
@@ -551,7 +556,7 @@ async function generateReportData(opts) {
|
|
|
551
556
|
// YAML enrichment (optional): priority, type, precondition, notes, steps
|
|
552
557
|
const yamlCase = yamlCaseLookup[caseId];
|
|
553
558
|
// Title: use result.title (Playwright spec.title) but strip ID prefix for display
|
|
554
|
-
const displayTitle = result.title.replace(/^[A-Z]+-\d+:\s*/, '').replace(/^\[[A-Z]+-\d+\]\s*/, '');
|
|
559
|
+
const displayTitle = result.title.replace(/^[A-Z]+-\d+:\s*|^[A-Z]+\d+:\s*/, '').replace(/^\[[A-Z]+-\d+\]\s*|^\[[A-Z]+\d+\]\s*/, '');
|
|
555
560
|
const steps = aiEntry?.steps
|
|
556
561
|
? aiEntry.steps.map((s) => {
|
|
557
562
|
// 截图路径保持原始相对路径(相对于 taskDir),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kkeve/suite",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.11",
|
|
4
4
|
"description": "AI-powered full-stack test agent CLI - static analysis, unit test, component test, E2E test, execute, evaluate, and report",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"bin": {
|
|
@@ -63,6 +63,7 @@
|
|
|
63
63
|
},
|
|
64
64
|
"dependencies": {
|
|
65
65
|
"@kkeve/core": "^0.0.4",
|
|
66
|
+
"@playwright/test": "*",
|
|
66
67
|
"@vitest/coverage-v8": "^4.1.9",
|
|
67
68
|
"@vue/test-utils": "^2.4.6",
|
|
68
69
|
"chalk": "^4.1.0",
|
|
@@ -73,7 +74,6 @@
|
|
|
73
74
|
"zod": "^4.4.3"
|
|
74
75
|
},
|
|
75
76
|
"devDependencies": {
|
|
76
|
-
"@playwright/test": "^1.60.0",
|
|
77
77
|
"@types/node": "^25.6.0",
|
|
78
78
|
"tsx": "^4.19.0",
|
|
79
79
|
"typescript": "^6.0.2"
|