@kkeve/suite 0.0.1 → 0.0.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/README.md +53 -0
- package/dist/bin/keve.d.ts +13 -0
- package/dist/bin/keve.js +164 -0
- package/dist/commands/auth-update.d.ts +16 -0
- package/dist/commands/auth-update.js +88 -0
- package/dist/commands/clean.d.ts +10 -0
- package/dist/commands/clean.js +78 -0
- package/dist/commands/config.d.ts +17 -0
- package/dist/commands/config.js +99 -0
- package/dist/commands/init.d.ts +15 -0
- package/dist/commands/init.js +292 -0
- package/dist/commands/report.d.ts +12 -0
- package/dist/commands/report.js +159 -0
- package/dist/commands/run.d.ts +29 -0
- package/dist/commands/run.js +417 -0
- package/dist/commands/test.d.ts +12 -0
- package/dist/commands/test.js +102 -0
- package/dist/commands/vitest.d.ts +14 -0
- package/dist/commands/vitest.js +166 -0
- package/dist/config.d.ts +109 -0
- package/dist/config.js +291 -0
- package/dist/core/action-log-writer.d.ts +47 -0
- package/dist/{tools/write-file.js → core/action-log-writer.js} +42 -26
- package/dist/core/auth.d.ts +17 -0
- package/dist/core/auth.js +224 -0
- package/dist/core/cdp-resolver.d.ts +5 -0
- package/dist/core/cdp-resolver.js +93 -0
- package/dist/core/generateReport/report.d.ts +19 -0
- package/dist/core/generateReport/report.js +91 -0
- package/dist/core/generateReport/reportData.d.ts +215 -0
- package/dist/core/generateReport/reportData.js +735 -0
- package/dist/core/global-setup.d.ts +6 -0
- package/dist/core/global-setup.js +113 -0
- package/dist/core/keve-aspect.d.ts +54 -0
- package/dist/core/keve-aspect.js +52 -0
- package/dist/core/keve-decorators.d.ts +51 -0
- package/dist/core/keve-decorators.js +95 -0
- package/dist/core/keve-registry.d.ts +38 -0
- package/dist/core/keve-registry.js +25 -0
- package/dist/core/keve-report.d.ts +27 -0
- package/dist/core/keve-report.js +357 -0
- package/dist/core/keve-test.d.ts +36 -0
- package/dist/core/keve-test.js +364 -0
- package/dist/core/learned-actions.d.ts +24 -0
- package/dist/core/learned-actions.js +65 -0
- package/dist/core/pw-config.d.ts +19 -0
- package/dist/core/pw-config.js +180 -0
- package/dist/index.d.ts +8 -23
- package/dist/index.js +36 -43
- package/dist/page-agent/agent.d.ts +129 -0
- package/dist/page-agent/agent.js +585 -0
- package/dist/page-agent/hooks.d.ts +13 -0
- package/dist/page-agent/hooks.js +218 -0
- package/dist/page-agent/tools.d.ts +29 -0
- package/dist/page-agent/tools.js +297 -0
- package/doc/README.md +124 -0
- package/doc/agents/keve-dev-test-agent.md +140 -0
- package/doc/agents/keve-test-agent.md +178 -0
- package/doc/asserts//346/265/213/350/257/225/345/220/204/351/230/266/346/256/265/350/257/264/346/230/216.png +0 -0
- package/doc/keve-agent-demo.html +370 -0
- package/doc/skills/keve-bddcase-gen/SKILL.md +332 -0
- package/doc/skills/keve-bddcase-gen/references/test-case-rules.md +91 -0
- package/doc/skills/keve-bddcase-gen/references/test-cases-example.md +103 -0
- package/doc/skills/keve-component-gen/SKILL.md +255 -0
- package/doc/skills/keve-component-gen/references/component-mock-strategy.md +74 -0
- package/doc/skills/keve-component-gen/references/component-model-template.md +98 -0
- package/doc/skills/keve-component-gen/references/component-test-template.md +100 -0
- package/doc/skills/keve-component-gen/references/vue-test-utils-guide.md +91 -0
- package/doc/skills/keve-e2e-gen/SKILL.md +179 -0
- package/doc/skills/keve-e2e-gen/references/code-analysis-guide.md +208 -0
- package/doc/skills/keve-e2e-gen/references/decisions.md +36 -0
- package/doc/skills/keve-e2e-gen/references/pom-generation-guide.md +140 -0
- package/doc/skills/keve-e2e-gen/references/script-generation-guide.md +236 -0
- package/doc/skills/keve-e2e-gen/references/test-auditor-guide.md +189 -0
- package/doc/skills/keve-e2e-gen/scripts/extract-components.sh +85 -0
- package/doc/skills/keve-e2e-gen/scripts/inject-testids.sh +95 -0
- package/doc/skills/keve-static-analysis/SKILL.md +362 -0
- package/doc/skills/keve-static-analysis/references/codegraph-analysis-guide.md +108 -0
- package/doc/skills/keve-static-analysis/references/git-impact-analysis.md +81 -0
- package/doc/skills/keve-static-analysis/references/lint-fix-guide.md +69 -0
- package/doc/skills/keve-static-analysis/references/runtime-risk-patterns.md +92 -0
- package/doc/skills/keve-static-analysis/references/security-scan-guide.md +32 -0
- package/doc/skills/keve-static-analysis/references/ts-error-patterns.md +98 -0
- package/doc/skills/keve-static-analysis/references/vue-security-guide.md +72 -0
- package/doc/skills/keve-test-eval/SKILL.md +78 -0
- package/doc/skills/keve-test-eval/references/common-fix-guide.md +166 -0
- package/doc/skills/keve-test-eval/references/confidence-data-guide.md +78 -0
- package/doc/skills/keve-test-eval/references/fix-loop-guide.md +67 -0
- package/doc/skills/keve-unit-gen/SKILL.md +203 -0
- package/doc/skills/keve-unit-gen/references/boundary-conditions.md +78 -0
- package/doc/skills/keve-unit-gen/references/unit-mock-strategy.md +71 -0
- package/doc/skills/keve-unit-gen/references/unit-test-template.md +105 -0
- package/package.json +47 -25
- package/dist/agent/factory.d.ts +0 -25
- package/dist/agent/factory.js +0 -99
- package/dist/agent/index.d.ts +0 -3
- package/dist/agent/index.js +0 -7
- package/dist/agent/loader.d.ts +0 -22
- package/dist/agent/loader.js +0 -117
- package/dist/agent/types.d.ts +0 -9
- package/dist/agent/types.js +0 -2
- package/dist/agent-loop/complete-task.d.ts +0 -28
- package/dist/agent-loop/complete-task.js +0 -70
- package/dist/agent-loop/index.d.ts +0 -16
- package/dist/agent-loop/index.js +0 -26
- package/dist/agent-loop/loop.d.ts +0 -42
- package/dist/agent-loop/loop.js +0 -282
- package/dist/agent-loop/runtime.d.ts +0 -40
- package/dist/agent-loop/runtime.js +0 -163
- package/dist/agent-loop/types.d.ts +0 -161
- package/dist/agent-loop/types.js +0 -105
- package/dist/context/index.d.ts +0 -8
- package/dist/context/index.js +0 -29
- package/dist/context/tokenCalculation.d.ts +0 -55
- package/dist/context/tokenCalculation.js +0 -95
- package/dist/context/truncation.d.ts +0 -67
- package/dist/context/truncation.js +0 -142
- package/dist/context/types.d.ts +0 -7
- package/dist/context/types.js +0 -27
- package/dist/llm/OpenAIClient.d.ts +0 -31
- package/dist/llm/OpenAIClient.js +0 -455
- package/dist/llm/constants.d.ts +0 -2
- package/dist/llm/constants.js +0 -6
- package/dist/llm/errors.d.ts +0 -25
- package/dist/llm/errors.js +0 -51
- package/dist/llm/index.d.ts +0 -49
- package/dist/llm/index.js +0 -158
- package/dist/llm/types.d.ts +0 -124
- package/dist/llm/types.js +0 -2
- package/dist/llm/utils.d.ts +0 -29
- package/dist/llm/utils.js +0 -284
- package/dist/skill/frontmatter-parser.d.ts +0 -50
- package/dist/skill/frontmatter-parser.js +0 -152
- package/dist/skill/index.d.ts +0 -12
- package/dist/skill/index.js +0 -17
- package/dist/skill/loader.d.ts +0 -96
- package/dist/skill/loader.js +0 -256
- package/dist/tools/ask-user.d.ts +0 -8
- package/dist/tools/ask-user.js +0 -47
- package/dist/tools/bash.d.ts +0 -47
- package/dist/tools/bash.js +0 -213
- package/dist/tools/edit-file.d.ts +0 -9
- package/dist/tools/edit-file.js +0 -76
- package/dist/tools/glob.d.ts +0 -8
- package/dist/tools/glob.js +0 -101
- package/dist/tools/grep.d.ts +0 -10
- package/dist/tools/grep.js +0 -76
- package/dist/tools/index.d.ts +0 -24
- package/dist/tools/index.js +0 -47
- package/dist/tools/read-files.d.ts +0 -8
- package/dist/tools/read-files.js +0 -81
- package/dist/tools/write-file.d.ts +0 -8
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* keve run - Execute Playwright tests
|
|
4
|
+
*
|
|
5
|
+
* Wraps `npx playwright test` with round management and output organization.
|
|
6
|
+
* Does NOT hardcode reporter or resultDir — the Playwright config handles those.
|
|
7
|
+
* Only adds: round number management, retry-from/cases filtering, and post-run report generation.
|
|
8
|
+
*/
|
|
9
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
12
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
13
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
14
|
+
}
|
|
15
|
+
Object.defineProperty(o, k2, desc);
|
|
16
|
+
}) : (function(o, m, k, k2) {
|
|
17
|
+
if (k2 === undefined) k2 = k;
|
|
18
|
+
o[k2] = m[k];
|
|
19
|
+
}));
|
|
20
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
21
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
22
|
+
}) : function(o, v) {
|
|
23
|
+
o["default"] = v;
|
|
24
|
+
});
|
|
25
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
26
|
+
var ownKeys = function(o) {
|
|
27
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
28
|
+
var ar = [];
|
|
29
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
30
|
+
return ar;
|
|
31
|
+
};
|
|
32
|
+
return ownKeys(o);
|
|
33
|
+
};
|
|
34
|
+
return function (mod) {
|
|
35
|
+
if (mod && mod.__esModule) return mod;
|
|
36
|
+
var result = {};
|
|
37
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
38
|
+
__setModuleDefault(result, mod);
|
|
39
|
+
return result;
|
|
40
|
+
};
|
|
41
|
+
})();
|
|
42
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
43
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
44
|
+
};
|
|
45
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
46
|
+
exports.KeveRunError = void 0;
|
|
47
|
+
exports.run = run;
|
|
48
|
+
const fs = __importStar(require("node:fs"));
|
|
49
|
+
const path = __importStar(require("node:path"));
|
|
50
|
+
const os = __importStar(require("node:os"));
|
|
51
|
+
const node_child_process_1 = require("node:child_process");
|
|
52
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
53
|
+
const config_1 = require("../config");
|
|
54
|
+
const report_1 = require("../core/generateReport/report");
|
|
55
|
+
const reportData_1 = require("../core/generateReport/reportData");
|
|
56
|
+
function isPlaywrightBrowserInstalled() {
|
|
57
|
+
const cacheDir = os.platform() === 'darwin'
|
|
58
|
+
? path.join(os.homedir(), 'Library/Caches/ms-playwright')
|
|
59
|
+
: os.platform() === 'win32'
|
|
60
|
+
? path.join(process.env.LOCALAPPDATA || '', 'ms-playwright')
|
|
61
|
+
: path.join(os.homedir(), '.cache/ms-playwright');
|
|
62
|
+
if (!fs.existsSync(cacheDir))
|
|
63
|
+
return false;
|
|
64
|
+
try {
|
|
65
|
+
const entries = fs.readdirSync(cacheDir);
|
|
66
|
+
return entries.some(e => e.startsWith('chromium-'));
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async function ensurePlaywrightBrowsers() {
|
|
73
|
+
if (isPlaywrightBrowserInstalled())
|
|
74
|
+
return;
|
|
75
|
+
console.log(chalk_1.default.yellow(' ⚠ Playwright browsers not found. Installing Chromium...'));
|
|
76
|
+
console.log(chalk_1.default.gray(' This is a one-time setup (~200MB download).'));
|
|
77
|
+
try {
|
|
78
|
+
(0, node_child_process_1.execSync)('npx playwright install chromium', {
|
|
79
|
+
stdio: 'inherit',
|
|
80
|
+
timeout: 300_000,
|
|
81
|
+
});
|
|
82
|
+
console.log(chalk_1.default.green(' ✓ Playwright browsers installed.'));
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
console.error(chalk_1.default.red(' ✗ Failed to install Playwright browsers.'));
|
|
86
|
+
console.error(chalk_1.default.gray(' Please run manually: npx playwright install chromium'));
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function findPlaywrightConfig(keveDir, optionsConfig) {
|
|
91
|
+
if (optionsConfig) {
|
|
92
|
+
const resolved = path.resolve(optionsConfig);
|
|
93
|
+
if (fs.existsSync(resolved))
|
|
94
|
+
return resolved;
|
|
95
|
+
console.error(chalk_1.default.red(` ✗ Config not found: ${optionsConfig}`));
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
const taskDir = (0, config_1.resolveTaskDir)(keveDir);
|
|
99
|
+
const searchPaths = [
|
|
100
|
+
taskDir ? path.join(taskDir, 'keve-test.config.mjs') : null,
|
|
101
|
+
taskDir ? path.join(taskDir, 'keve-test.config.ts') : null,
|
|
102
|
+
keveDir ? path.join(keveDir, 'keve-test.config.mjs') : null,
|
|
103
|
+
keveDir ? path.join(keveDir, 'keve-test.config.ts') : null,
|
|
104
|
+
path.join(process.cwd(), 'keve_test_spec', 'keve-test.config.mjs'),
|
|
105
|
+
path.join(process.cwd(), 'keve_test_spec', 'keve-test.config.ts'),
|
|
106
|
+
].filter(Boolean);
|
|
107
|
+
for (const p of searchPaths) {
|
|
108
|
+
if (fs.existsSync(p))
|
|
109
|
+
return p;
|
|
110
|
+
}
|
|
111
|
+
console.error(chalk_1.default.red(' ✗ Playwright config not found'));
|
|
112
|
+
console.error(chalk_1.default.gray(' Searched:'));
|
|
113
|
+
for (const p of searchPaths) {
|
|
114
|
+
console.error(chalk_1.default.gray(` ${p}`));
|
|
115
|
+
}
|
|
116
|
+
console.error(chalk_1.default.gray(' Run: keve config to create one'));
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
function collectFailedTitles(suite, titles) {
|
|
120
|
+
for (const spec of suite.specs || []) {
|
|
121
|
+
for (const test of spec.tests || []) {
|
|
122
|
+
if (test.status === 'failed' || test.status === 'timedOut') {
|
|
123
|
+
titles.push(spec.title || spec.name || '');
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
for (const child of suite.suites || []) {
|
|
128
|
+
collectFailedTitles(child, titles);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
function escapeGrepPattern(str) {
|
|
132
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, String.raw `\$&`);
|
|
133
|
+
}
|
|
134
|
+
class KeveRunError extends Error {
|
|
135
|
+
constructor(message) {
|
|
136
|
+
super(message);
|
|
137
|
+
this.name = 'KeveRunError';
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
exports.KeveRunError = KeveRunError;
|
|
141
|
+
async function run(options) {
|
|
142
|
+
const keveDir = (0, config_1.findKeveDir)();
|
|
143
|
+
const cwd = process.cwd();
|
|
144
|
+
const configPath = findPlaywrightConfig(keveDir || '', options.config);
|
|
145
|
+
if (!configPath) {
|
|
146
|
+
throw new KeveRunError('Playwright config not found');
|
|
147
|
+
}
|
|
148
|
+
let pwConfig;
|
|
149
|
+
try {
|
|
150
|
+
pwConfig = await new Function('return import(' + JSON.stringify(configPath) + ')')();
|
|
151
|
+
}
|
|
152
|
+
catch (e) {
|
|
153
|
+
throw new KeveRunError(`Failed to import Playwright config: ${e.message}`);
|
|
154
|
+
}
|
|
155
|
+
const configDefault = pwConfig?.default || pwConfig;
|
|
156
|
+
const taskDir = process.env.KEVE_TASK_DIR
|
|
157
|
+
? path.resolve(process.env.KEVE_TASK_DIR)
|
|
158
|
+
: (0, config_1.resolveTaskDir)(keveDir) || cwd;
|
|
159
|
+
let resultDir = '';
|
|
160
|
+
let resultsFile = '';
|
|
161
|
+
if (configDefault?.reporter) {
|
|
162
|
+
for (const r of configDefault.reporter) {
|
|
163
|
+
if (Array.isArray(r) && r[0] === 'json' && r[1]?.outputFile) {
|
|
164
|
+
resultsFile = path.resolve(r[1].outputFile);
|
|
165
|
+
resultDir = path.dirname(resultsFile);
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
console.log(chalk_1.default.cyan('\n🚀 keve run\n'));
|
|
171
|
+
let round;
|
|
172
|
+
if (options.round) {
|
|
173
|
+
round = parseInt(options.round, 10);
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
round = (0, config_1.getNextRoundFromState)(taskDir);
|
|
177
|
+
}
|
|
178
|
+
resultDir = path.join(taskDir, 'test-artifacts', `round-${round}`);
|
|
179
|
+
resultsFile = path.join(resultDir, 'test-results.json');
|
|
180
|
+
const pwArgs = ['playwright', 'test', '--config', configPath];
|
|
181
|
+
let effectiveGrep = options.grep || '';
|
|
182
|
+
if (options.retryFrom) {
|
|
183
|
+
const sourceRound = parseInt(options.retryFrom, 10);
|
|
184
|
+
const sourceResultDir = path.join(taskDir, 'test-artifacts', `round-${sourceRound}`);
|
|
185
|
+
const sourceResultsFile = path.join(sourceResultDir, 'test-results.json');
|
|
186
|
+
if (!fs.existsSync(sourceResultsFile)) {
|
|
187
|
+
throw new KeveRunError(`Round ${sourceRound} results not found: ${sourceResultsFile}`);
|
|
188
|
+
}
|
|
189
|
+
const sourceResults = JSON.parse(fs.readFileSync(sourceResultsFile, 'utf-8'));
|
|
190
|
+
const failedTitles = [];
|
|
191
|
+
for (const suite of sourceResults.suites || []) {
|
|
192
|
+
collectFailedTitles(suite, failedTitles);
|
|
193
|
+
}
|
|
194
|
+
if (failedTitles.length === 0) {
|
|
195
|
+
console.log(chalk_1.default.green(` ✓ Round ${sourceRound} had no failures — nothing to retry`));
|
|
196
|
+
return {
|
|
197
|
+
exitCode: 0, resultsFile: sourceResultsFile, resultDir: sourceResultDir,
|
|
198
|
+
taskDir, round, passed: 0, failed: 0, skipped: 0,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
const retryGrep = failedTitles.map(t => escapeGrepPattern(t)).join('|');
|
|
202
|
+
effectiveGrep = effectiveGrep ? `${effectiveGrep}|${retryGrep}` : retryGrep;
|
|
203
|
+
console.log(chalk_1.default.gray(` Retrying ${failedTitles.length} failed cases from round ${sourceRound}`));
|
|
204
|
+
}
|
|
205
|
+
if (options.cases) {
|
|
206
|
+
const caseNames = options.cases.split(',').map(c => c.trim());
|
|
207
|
+
const casesGrep = caseNames.map(c => escapeGrepPattern(c)).join('|');
|
|
208
|
+
effectiveGrep = effectiveGrep ? `${effectiveGrep}|${casesGrep}` : casesGrep;
|
|
209
|
+
console.log(chalk_1.default.gray(` Running specific cases: ${caseNames.join(', ')}`));
|
|
210
|
+
}
|
|
211
|
+
if (effectiveGrep) {
|
|
212
|
+
pwArgs.push('--grep', effectiveGrep);
|
|
213
|
+
}
|
|
214
|
+
const env = {
|
|
215
|
+
...process.env,
|
|
216
|
+
KEVE_TASK_DIR: taskDir,
|
|
217
|
+
KEVE_ROUND: String(round),
|
|
218
|
+
};
|
|
219
|
+
if (!env.BASE_URL && !env.PWGEN_TARGET_URL) {
|
|
220
|
+
console.log(chalk_1.default.gray(' Note: BASE_URL not set, Playwright config baseURL will be used for relative paths'));
|
|
221
|
+
}
|
|
222
|
+
fs.mkdirSync(resultDir, { recursive: true });
|
|
223
|
+
if (options.verbose) {
|
|
224
|
+
console.log(chalk_1.default.gray(` Round: ${round}`));
|
|
225
|
+
console.log(chalk_1.default.gray(` Config: ${configPath}`));
|
|
226
|
+
console.log(chalk_1.default.gray(` ResultDir: ${resultDir}`));
|
|
227
|
+
console.log(chalk_1.default.gray(` Grep: ${effectiveGrep || '(all)'}`));
|
|
228
|
+
console.log();
|
|
229
|
+
}
|
|
230
|
+
console.log(chalk_1.default.blue(` ▶ Round ${round} — Executing Playwright tests...\n`));
|
|
231
|
+
await ensurePlaywrightBrowsers();
|
|
232
|
+
if (!env.KEVE_CDP_ENDPOINT && !env.KEVE_CDP_WS_ENDPOINT) {
|
|
233
|
+
env.KEVE_CDP_ENDPOINT = 'http://127.0.0.1:9222';
|
|
234
|
+
}
|
|
235
|
+
const exitCode = await new Promise((resolve) => {
|
|
236
|
+
const child = (0, node_child_process_1.spawn)('npx', pwArgs, {
|
|
237
|
+
cwd,
|
|
238
|
+
env,
|
|
239
|
+
stdio: ['inherit', 'pipe', 'pipe'],
|
|
240
|
+
});
|
|
241
|
+
child.stdout.on('data', (data) => process.stdout.write(data));
|
|
242
|
+
child.stderr.on('data', (data) => process.stderr.write(data));
|
|
243
|
+
child.on('close', (code) => resolve(code ?? 1));
|
|
244
|
+
child.on('error', () => resolve(1));
|
|
245
|
+
});
|
|
246
|
+
if (exitCode !== 0 && options.verbose) {
|
|
247
|
+
console.log(chalk_1.default.gray(' Playwright exited with errors (tests may have failed)'));
|
|
248
|
+
}
|
|
249
|
+
if (!fs.existsSync(resultsFile)) {
|
|
250
|
+
console.error(chalk_1.default.red(' ✗ test-results.json not found after execution'));
|
|
251
|
+
console.error(chalk_1.default.gray(' Expected at: ' + resultsFile));
|
|
252
|
+
console.error(chalk_1.default.gray(' Check that config has json reporter with outputFile pointing to resultDir'));
|
|
253
|
+
return {
|
|
254
|
+
exitCode: 1, resultsFile, resultDir, taskDir, round, passed: 0, failed: 0, skipped: 0,
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
const results = JSON.parse(fs.readFileSync(resultsFile, 'utf-8'));
|
|
258
|
+
const stats = results.stats || {};
|
|
259
|
+
const passed = stats.expected || (stats.passed || 0);
|
|
260
|
+
const failed = stats.unexpected || (stats.failed || 0);
|
|
261
|
+
const skipped = stats.skipped || 0;
|
|
262
|
+
console.log(chalk_1.default.green(`\n ✓ Round ${round} complete`));
|
|
263
|
+
console.log(chalk_1.default.gray(` Tests: ${stats.expected || 0} expected, ${stats.actual || 0} actual`));
|
|
264
|
+
console.log(chalk_1.default.gray(` Passed: ${passed}, Failed: ${failed}, Skipped: ${skipped}`));
|
|
265
|
+
(0, config_1.completeRound)(taskDir, round, { passed, failed, skipped });
|
|
266
|
+
stripBase64FromTestResults(resultsFile, resultDir);
|
|
267
|
+
// ── 兜底:确保 report-data.json 始终存在 ──
|
|
268
|
+
const reportDataPath = path.join(resultDir, 'report-data.json');
|
|
269
|
+
if (!fs.existsSync(reportDataPath)) {
|
|
270
|
+
console.log(chalk_1.default.yellow(' ⚠ report-data.json missing — generating fallback report'));
|
|
271
|
+
try {
|
|
272
|
+
const confidencePath = path.join(resultDir, 'confidence-data.jsonl');
|
|
273
|
+
if (fs.existsSync(resultsFile) && fs.existsSync(confidencePath)) {
|
|
274
|
+
// 正常兜底:双文件都存在,调用 generateReportData 生成
|
|
275
|
+
const reportData = await (0, reportData_1.generateReportData)({
|
|
276
|
+
projectRoot: taskDir,
|
|
277
|
+
resultsPath: resultsFile,
|
|
278
|
+
confidencePath,
|
|
279
|
+
});
|
|
280
|
+
fs.writeFileSync(reportDataPath, JSON.stringify(reportData, null, 2));
|
|
281
|
+
console.log(chalk_1.default.green(` ✓ Fallback report-data.json generated at: ${reportDataPath}`));
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
// 极端兜底:缺少 confidence-data.jsonl(Playwright crash 场景)
|
|
285
|
+
const fallbackReport = {
|
|
286
|
+
round,
|
|
287
|
+
taskDir,
|
|
288
|
+
generatedBy: 'fallback',
|
|
289
|
+
cases: [],
|
|
290
|
+
warning: 'report-data.json generated by fallback — some test data may be incomplete',
|
|
291
|
+
};
|
|
292
|
+
// 尝试从 test-results.json 提取基础统计
|
|
293
|
+
if (fs.existsSync(resultsFile)) {
|
|
294
|
+
try {
|
|
295
|
+
const results = JSON.parse(fs.readFileSync(resultsFile, 'utf-8'));
|
|
296
|
+
const stats = results.stats || {};
|
|
297
|
+
fallbackReport.summary = {
|
|
298
|
+
total: (stats.expected || 0) + (stats.unexpected || 0) + (stats.skipped || 0),
|
|
299
|
+
passed: stats.expected || 0,
|
|
300
|
+
failed: stats.unexpected || 0,
|
|
301
|
+
skipped: stats.skipped || 0,
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
catch { /* ignore parse error in fallback */ }
|
|
305
|
+
}
|
|
306
|
+
fs.writeFileSync(reportDataPath, JSON.stringify(fallbackReport, null, 2));
|
|
307
|
+
console.log(chalk_1.default.yellow(` ⚠ Minimal fallback report-data.json generated (missing confidence data)`));
|
|
308
|
+
}
|
|
309
|
+
// 同步到 latest 目录 → symlink 指向最新 round(避免重复拷贝 34MB+)
|
|
310
|
+
const latestDir = path.join(taskDir, 'test-artifacts', 'latest');
|
|
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 */ }
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
catch (err) {
|
|
328
|
+
console.error(chalk_1.default.red(` ✗ Fallback report generation failed: ${err.message}`));
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
return { exitCode, resultsFile, resultDir, taskDir, round, passed, failed, skipped };
|
|
332
|
+
}
|
|
333
|
+
function stripBase64FromTestResults(resultsFile, resultDir) {
|
|
334
|
+
if (!fs.existsSync(resultsFile))
|
|
335
|
+
return;
|
|
336
|
+
try {
|
|
337
|
+
const data = JSON.parse(fs.readFileSync(resultsFile, 'utf-8'));
|
|
338
|
+
const screenshotsDir = path.join(resultDir, 'screenshots');
|
|
339
|
+
let extractedCount = 0;
|
|
340
|
+
function stripSuite(suite) {
|
|
341
|
+
for (const spec of suite.specs || []) {
|
|
342
|
+
for (const test of spec.tests || []) {
|
|
343
|
+
for (const result of test.results || []) {
|
|
344
|
+
for (const att of result.attachments || []) {
|
|
345
|
+
if (att.body && typeof att.body === 'string' && att.contentType !== 'application/json') {
|
|
346
|
+
const fileName = `${att.name || 'screenshot'}.png`;
|
|
347
|
+
const filePath = path.join(screenshotsDir, fileName);
|
|
348
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
349
|
+
fs.writeFileSync(filePath, Buffer.from(att.body, 'base64'));
|
|
350
|
+
delete att.body;
|
|
351
|
+
att.path = path.relative(resultDir, filePath);
|
|
352
|
+
extractedCount++;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
for (const child of suite.suites || []) {
|
|
359
|
+
stripSuite(child);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
for (const suite of data.suites || []) {
|
|
363
|
+
stripSuite(suite);
|
|
364
|
+
}
|
|
365
|
+
fs.writeFileSync(resultsFile, JSON.stringify(data, null, 2));
|
|
366
|
+
if (extractedCount > 0) {
|
|
367
|
+
console.log(chalk_1.default.gray(` Stripped ${extractedCount} base64 attachments from test-results.json`));
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
catch (err) {
|
|
371
|
+
console.log(chalk_1.default.gray(` Note: Could not strip base64 from test-results.json: ${err.message}`));
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
async function runGenerateReport(resultDir, resultsFile, keveDir, taskDir) {
|
|
375
|
+
let casesPath = '';
|
|
376
|
+
const casesCandidates = [
|
|
377
|
+
path.join(process.cwd(), '.keve', 'test-cases.yaml'),
|
|
378
|
+
keveDir ? path.join(keveDir, 'test-cases.yaml') : '',
|
|
379
|
+
path.join(process.cwd(), 'keve_test_spec', 'test-cases.yaml'),
|
|
380
|
+
];
|
|
381
|
+
for (const candidate of casesCandidates) {
|
|
382
|
+
if (candidate && fs.existsSync(candidate)) {
|
|
383
|
+
casesPath = candidate;
|
|
384
|
+
break;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
if (!casesPath && taskDir) {
|
|
388
|
+
const taskCase = path.join(taskDir, 'test-cases.yaml');
|
|
389
|
+
if (fs.existsSync(taskCase))
|
|
390
|
+
casesPath = taskCase;
|
|
391
|
+
}
|
|
392
|
+
const projectRoot = process.cwd();
|
|
393
|
+
const confidenceFile = path.join(resultDir, 'confidence-data.jsonl');
|
|
394
|
+
console.log(chalk_1.default.gray(' Step 1: Generating report data...'));
|
|
395
|
+
try {
|
|
396
|
+
const { reportData } = await (0, report_1.generateReport)({
|
|
397
|
+
projectRoot,
|
|
398
|
+
resultsPath: resultsFile,
|
|
399
|
+
confidencePath: fs.existsSync(confidenceFile) ? confidenceFile : undefined,
|
|
400
|
+
casesPath: casesPath || undefined,
|
|
401
|
+
});
|
|
402
|
+
const roundDir = reportData.roundDir;
|
|
403
|
+
const latestDir = reportData.latestDir;
|
|
404
|
+
try {
|
|
405
|
+
if (fs.existsSync(latestDir))
|
|
406
|
+
fs.unlinkSync(latestDir);
|
|
407
|
+
fs.symlinkSync(path.basename(roundDir), latestDir, 'junction');
|
|
408
|
+
}
|
|
409
|
+
catch { /* non-critical */ }
|
|
410
|
+
console.log(chalk_1.default.green(` ✓ Report: ${path.relative(process.cwd(), path.join(roundDir, 'report-data.json'))}`));
|
|
411
|
+
}
|
|
412
|
+
catch (err) {
|
|
413
|
+
console.log(chalk_1.default.yellow(` ⚠ Report generation failed: ${err.message}`));
|
|
414
|
+
if (process.env.KEVE_VERBOSE)
|
|
415
|
+
console.error(err);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* keve test - Full pipeline: gen → run → evaluate → report
|
|
3
|
+
* Orchestrates the complete KEVE test loop with fix cycles.
|
|
4
|
+
*/
|
|
5
|
+
export interface TestOptions {
|
|
6
|
+
config?: string;
|
|
7
|
+
maxRounds?: string;
|
|
8
|
+
grep?: string;
|
|
9
|
+
verbose?: boolean;
|
|
10
|
+
inlineImages?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare function test(options: TestOptions): Promise<void>;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* keve test - Full pipeline: gen → run → evaluate → report
|
|
4
|
+
* Orchestrates the complete KEVE test loop with fix cycles.
|
|
5
|
+
*/
|
|
6
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
+
if (k2 === undefined) k2 = k;
|
|
8
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
+
}
|
|
12
|
+
Object.defineProperty(o, k2, desc);
|
|
13
|
+
}) : (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
o[k2] = m[k];
|
|
16
|
+
}));
|
|
17
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
18
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
19
|
+
}) : function(o, v) {
|
|
20
|
+
o["default"] = v;
|
|
21
|
+
});
|
|
22
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
23
|
+
var ownKeys = function(o) {
|
|
24
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
25
|
+
var ar = [];
|
|
26
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
27
|
+
return ar;
|
|
28
|
+
};
|
|
29
|
+
return ownKeys(o);
|
|
30
|
+
};
|
|
31
|
+
return function (mod) {
|
|
32
|
+
if (mod && mod.__esModule) return mod;
|
|
33
|
+
var result = {};
|
|
34
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
35
|
+
__setModuleDefault(result, mod);
|
|
36
|
+
return result;
|
|
37
|
+
};
|
|
38
|
+
})();
|
|
39
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
40
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
41
|
+
};
|
|
42
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
|
+
exports.test = test;
|
|
44
|
+
const fs = __importStar(require("node:fs"));
|
|
45
|
+
const path = __importStar(require("node:path"));
|
|
46
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
47
|
+
const config_1 = require("../config");
|
|
48
|
+
const run_1 = require("./run");
|
|
49
|
+
const report_1 = require("./report");
|
|
50
|
+
async function test(options) {
|
|
51
|
+
const maxRounds = parseInt(options.maxRounds || '3', 10);
|
|
52
|
+
const keveDir = (0, config_1.findKeveDir)();
|
|
53
|
+
console.log(chalk_1.default.cyan('\n🔄 keve test — Full pipeline\n'));
|
|
54
|
+
console.log(chalk_1.default.gray(` Max rounds: ${maxRounds}`));
|
|
55
|
+
for (let round = 1; round <= maxRounds; round++) {
|
|
56
|
+
console.log(chalk_1.default.blue(`\n━━━ Round ${round}/${maxRounds} ━━━\n`));
|
|
57
|
+
// Step 1: Run tests
|
|
58
|
+
console.log(chalk_1.default.yellow(' [1/2] Running tests...'));
|
|
59
|
+
try {
|
|
60
|
+
await (0, run_1.run)({
|
|
61
|
+
config: options.config,
|
|
62
|
+
grep: options.grep,
|
|
63
|
+
verbose: options.verbose,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
catch (e) {
|
|
67
|
+
if (e instanceof run_1.KeveRunError) {
|
|
68
|
+
console.error(chalk_1.default.red(` ✗ Run failed: ${e.message}`));
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
throw e;
|
|
72
|
+
}
|
|
73
|
+
// Step 2: Generate report
|
|
74
|
+
console.log(chalk_1.default.yellow(' [2/2] Generating report...'));
|
|
75
|
+
await (0, report_1.report)({});
|
|
76
|
+
// Check if all passed — if so, we're done
|
|
77
|
+
if (keveDir) {
|
|
78
|
+
const summaryPath = path.join(keveDir, 'reports', 'latest', 'execution-summary.json');
|
|
79
|
+
if (fs.existsSync(summaryPath)) {
|
|
80
|
+
try {
|
|
81
|
+
const summary = JSON.parse(fs.readFileSync(summaryPath, 'utf-8'));
|
|
82
|
+
if (summary.summary?.failed === 0) {
|
|
83
|
+
console.log(chalk_1.default.green(`\n ✓ All tests passed in round ${round}!`));
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
console.log(chalk_1.default.yellow(` ⚠ ${summary.summary.failed} failed, ${summary.summary.passed} passed`));
|
|
87
|
+
// Check for D-class failures that could be auto-fixed
|
|
88
|
+
const dClassFailures = (summary.failedCases || []).filter((c) => c.type?.startsWith('D'));
|
|
89
|
+
if (dClassFailures.length === 0 || round >= maxRounds) {
|
|
90
|
+
if (round >= maxRounds) {
|
|
91
|
+
console.log(chalk_1.default.yellow(`\n ⚠ Reached max rounds (${maxRounds})`));
|
|
92
|
+
}
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
console.log(chalk_1.default.gray(` → ${dClassFailures.length} D-class failures, will retry...`));
|
|
96
|
+
}
|
|
97
|
+
catch { /* ignore parse errors */ }
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
console.log(chalk_1.default.green('\n ✓ Pipeline complete\n'));
|
|
102
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* keve vitest - Execute Vitest unit/component tests
|
|
3
|
+
*
|
|
4
|
+
* Wraps `npx vitest` with keve-specific config and coverage support.
|
|
5
|
+
* Uses vitest.config.ts from keve_test_spec/ by default.
|
|
6
|
+
*/
|
|
7
|
+
export interface VitestOptions {
|
|
8
|
+
config?: string;
|
|
9
|
+
watch?: boolean;
|
|
10
|
+
coverage?: boolean;
|
|
11
|
+
filter?: string;
|
|
12
|
+
verbose?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export declare function vitest(options: VitestOptions): Promise<void>;
|