qa360 1.4.5 ā 2.0.1
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 +1 -1
- package/dist/commands/ai.d.ts +41 -0
- package/dist/commands/ai.js +499 -0
- package/dist/commands/ask.js +12 -12
- package/dist/commands/coverage.d.ts +8 -0
- package/dist/commands/coverage.js +252 -0
- package/dist/commands/explain.d.ts +27 -0
- package/dist/commands/explain.js +630 -0
- package/dist/commands/flakiness.d.ts +73 -0
- package/dist/commands/flakiness.js +435 -0
- package/dist/commands/generate.d.ts +66 -0
- package/dist/commands/generate.js +438 -0
- package/dist/commands/init.d.ts +56 -9
- package/dist/commands/init.js +217 -10
- package/dist/commands/monitor.d.ts +27 -0
- package/dist/commands/monitor.js +225 -0
- package/dist/commands/ollama.d.ts +40 -0
- package/dist/commands/ollama.js +301 -0
- package/dist/commands/pack.d.ts +37 -9
- package/dist/commands/pack.js +240 -141
- package/dist/commands/regression.d.ts +8 -0
- package/dist/commands/regression.js +340 -0
- package/dist/commands/repair.d.ts +26 -0
- package/dist/commands/repair.js +307 -0
- package/dist/commands/retry.d.ts +43 -0
- package/dist/commands/retry.js +275 -0
- package/dist/commands/run.d.ts +8 -3
- package/dist/commands/run.js +45 -31
- package/dist/commands/slo.d.ts +8 -0
- package/dist/commands/slo.js +327 -0
- package/dist/core/adapters/playwright-native-api.d.ts +183 -0
- package/dist/core/adapters/playwright-native-api.js +461 -0
- package/dist/core/adapters/playwright-ui.d.ts +7 -0
- package/dist/core/adapters/playwright-ui.js +29 -1
- package/dist/core/ai/anthropic-provider.d.ts +50 -0
- package/dist/core/ai/anthropic-provider.js +211 -0
- package/dist/core/ai/deepseek-provider.d.ts +81 -0
- package/dist/core/ai/deepseek-provider.js +254 -0
- package/dist/core/ai/index.d.ts +60 -0
- package/dist/core/ai/index.js +18 -0
- package/dist/core/ai/llm-client.d.ts +45 -0
- package/dist/core/ai/llm-client.js +7 -0
- package/dist/core/ai/mock-provider.d.ts +49 -0
- package/dist/core/ai/mock-provider.js +121 -0
- package/dist/core/ai/ollama-provider.d.ts +78 -0
- package/dist/core/ai/ollama-provider.js +192 -0
- package/dist/core/ai/openai-provider.d.ts +48 -0
- package/dist/core/ai/openai-provider.js +188 -0
- package/dist/core/ai/provider-factory.d.ts +160 -0
- package/dist/core/ai/provider-factory.js +269 -0
- package/dist/core/auth/api-key-provider.d.ts +16 -0
- package/dist/core/auth/api-key-provider.js +63 -0
- package/dist/core/auth/aws-iam-provider.d.ts +35 -0
- package/dist/core/auth/aws-iam-provider.js +177 -0
- package/dist/core/auth/azure-ad-provider.d.ts +15 -0
- package/dist/core/auth/azure-ad-provider.js +99 -0
- package/dist/core/auth/basic-auth-provider.d.ts +26 -0
- package/dist/core/auth/basic-auth-provider.js +111 -0
- package/dist/core/auth/gcp-adc-provider.d.ts +27 -0
- package/dist/core/auth/gcp-adc-provider.js +126 -0
- package/dist/core/auth/index.d.ts +238 -0
- package/dist/core/auth/index.js +82 -0
- package/dist/core/auth/jwt-provider.d.ts +19 -0
- package/dist/core/auth/jwt-provider.js +160 -0
- package/dist/core/auth/manager.d.ts +84 -0
- package/dist/core/auth/manager.js +230 -0
- package/dist/core/auth/oauth2-provider.d.ts +17 -0
- package/dist/core/auth/oauth2-provider.js +114 -0
- package/dist/core/auth/totp-provider.d.ts +31 -0
- package/dist/core/auth/totp-provider.js +134 -0
- package/dist/core/auth/ui-login-provider.d.ts +26 -0
- package/dist/core/auth/ui-login-provider.js +198 -0
- package/dist/core/cache/index.d.ts +7 -0
- package/dist/core/cache/index.js +6 -0
- package/dist/core/cache/lru-cache.d.ts +203 -0
- package/dist/core/cache/lru-cache.js +397 -0
- package/dist/core/coverage/analyzer.d.ts +101 -0
- package/dist/core/coverage/analyzer.js +415 -0
- package/dist/core/coverage/collector.d.ts +74 -0
- package/dist/core/coverage/collector.js +459 -0
- package/dist/core/coverage/config.d.ts +37 -0
- package/dist/core/coverage/config.js +156 -0
- package/dist/core/coverage/index.d.ts +11 -0
- package/dist/core/coverage/index.js +15 -0
- package/dist/core/coverage/types.d.ts +267 -0
- package/dist/core/coverage/types.js +6 -0
- package/dist/core/coverage/vault.d.ts +95 -0
- package/dist/core/coverage/vault.js +405 -0
- package/dist/core/dashboard/assets.d.ts +6 -0
- package/dist/core/dashboard/assets.js +690 -0
- package/dist/core/dashboard/index.d.ts +6 -0
- package/dist/core/dashboard/index.js +5 -0
- package/dist/core/dashboard/server.d.ts +72 -0
- package/dist/core/dashboard/server.js +354 -0
- package/dist/core/dashboard/types.d.ts +70 -0
- package/dist/core/dashboard/types.js +5 -0
- package/dist/core/discoverer/index.d.ts +115 -0
- package/dist/core/discoverer/index.js +250 -0
- package/dist/core/flakiness/index.d.ts +228 -0
- package/dist/core/flakiness/index.js +384 -0
- package/dist/core/generation/code-formatter.d.ts +111 -0
- package/dist/core/generation/code-formatter.js +307 -0
- package/dist/core/generation/code-generator.d.ts +144 -0
- package/dist/core/generation/code-generator.js +293 -0
- package/dist/core/generation/generator.d.ts +40 -0
- package/dist/core/generation/generator.js +76 -0
- package/dist/core/generation/index.d.ts +30 -0
- package/dist/core/generation/index.js +28 -0
- package/dist/core/generation/pack-generator.d.ts +107 -0
- package/dist/core/generation/pack-generator.js +416 -0
- package/dist/core/generation/prompt-builder.d.ts +132 -0
- package/dist/core/generation/prompt-builder.js +672 -0
- package/dist/core/generation/source-analyzer.d.ts +213 -0
- package/dist/core/generation/source-analyzer.js +657 -0
- package/dist/core/generation/test-optimizer.d.ts +117 -0
- package/dist/core/generation/test-optimizer.js +328 -0
- package/dist/core/generation/types.d.ts +214 -0
- package/dist/core/generation/types.js +4 -0
- package/dist/core/index.d.ts +23 -1
- package/dist/core/index.js +39 -0
- package/dist/core/pack/validator.js +31 -1
- package/dist/core/pack-v2/index.d.ts +9 -0
- package/dist/core/pack-v2/index.js +8 -0
- package/dist/core/pack-v2/loader.d.ts +62 -0
- package/dist/core/pack-v2/loader.js +231 -0
- package/dist/core/pack-v2/migrator.d.ts +56 -0
- package/dist/core/pack-v2/migrator.js +455 -0
- package/dist/core/pack-v2/validator.d.ts +61 -0
- package/dist/core/pack-v2/validator.js +577 -0
- package/dist/core/regression/detector.d.ts +107 -0
- package/dist/core/regression/detector.js +497 -0
- package/dist/core/regression/index.d.ts +9 -0
- package/dist/core/regression/index.js +11 -0
- package/dist/core/regression/trend-analyzer.d.ts +102 -0
- package/dist/core/regression/trend-analyzer.js +345 -0
- package/dist/core/regression/types.d.ts +222 -0
- package/dist/core/regression/types.js +7 -0
- package/dist/core/regression/vault.d.ts +87 -0
- package/dist/core/regression/vault.js +289 -0
- package/dist/core/repair/engine/fixer.d.ts +24 -0
- package/dist/core/repair/engine/fixer.js +226 -0
- package/dist/core/repair/engine/suggestion-engine.d.ts +18 -0
- package/dist/core/repair/engine/suggestion-engine.js +187 -0
- package/dist/core/repair/index.d.ts +10 -0
- package/dist/core/repair/index.js +13 -0
- package/dist/core/repair/repairer.d.ts +90 -0
- package/dist/core/repair/repairer.js +284 -0
- package/dist/core/repair/types.d.ts +91 -0
- package/dist/core/repair/types.js +6 -0
- package/dist/core/repair/utils/error-analyzer.d.ts +28 -0
- package/dist/core/repair/utils/error-analyzer.js +264 -0
- package/dist/core/retry/flakiness-integration.d.ts +60 -0
- package/dist/core/retry/flakiness-integration.js +228 -0
- package/dist/core/retry/index.d.ts +14 -0
- package/dist/core/retry/index.js +16 -0
- package/dist/core/retry/retry-engine.d.ts +80 -0
- package/dist/core/retry/retry-engine.js +296 -0
- package/dist/core/retry/types.d.ts +178 -0
- package/dist/core/retry/types.js +52 -0
- package/dist/core/retry/vault.d.ts +77 -0
- package/dist/core/retry/vault.js +304 -0
- package/dist/core/runner/e2e-helpers.d.ts +102 -0
- package/dist/core/runner/e2e-helpers.js +153 -0
- package/dist/core/runner/phase3-runner.d.ts +101 -2
- package/dist/core/runner/phase3-runner.js +559 -24
- package/dist/core/self-healing/assertion-healer.d.ts +97 -0
- package/dist/core/self-healing/assertion-healer.js +371 -0
- package/dist/core/self-healing/engine.d.ts +122 -0
- package/dist/core/self-healing/engine.js +538 -0
- package/dist/core/self-healing/index.d.ts +10 -0
- package/dist/core/self-healing/index.js +11 -0
- package/dist/core/self-healing/selector-healer.d.ts +103 -0
- package/dist/core/self-healing/selector-healer.js +372 -0
- package/dist/core/self-healing/types.d.ts +152 -0
- package/dist/core/self-healing/types.js +6 -0
- package/dist/core/slo/config.d.ts +107 -0
- package/dist/core/slo/config.js +360 -0
- package/dist/core/slo/index.d.ts +11 -0
- package/dist/core/slo/index.js +15 -0
- package/dist/core/slo/sli-calculator.d.ts +92 -0
- package/dist/core/slo/sli-calculator.js +364 -0
- package/dist/core/slo/slo-tracker.d.ts +148 -0
- package/dist/core/slo/slo-tracker.js +379 -0
- package/dist/core/slo/types.d.ts +281 -0
- package/dist/core/slo/types.js +7 -0
- package/dist/core/slo/vault.d.ts +102 -0
- package/dist/core/slo/vault.js +427 -0
- package/dist/core/tui/index.d.ts +7 -0
- package/dist/core/tui/index.js +6 -0
- package/dist/core/tui/monitor.d.ts +92 -0
- package/dist/core/tui/monitor.js +271 -0
- package/dist/core/tui/renderer.d.ts +33 -0
- package/dist/core/tui/renderer.js +218 -0
- package/dist/core/tui/types.d.ts +63 -0
- package/dist/core/tui/types.js +5 -0
- package/dist/core/types/pack-v2.d.ts +425 -0
- package/dist/core/types/pack-v2.js +8 -0
- package/dist/core/vault/index.d.ts +116 -0
- package/dist/core/vault/index.js +400 -5
- package/dist/core/watch/index.d.ts +7 -0
- package/dist/core/watch/index.js +6 -0
- package/dist/core/watch/watch-mode.d.ts +213 -0
- package/dist/core/watch/watch-mode.js +389 -0
- package/dist/index.js +68 -68
- package/dist/utils/config.d.ts +5 -0
- package/dist/utils/config.js +136 -0
- package/package.json +5 -1
- package/dist/core/adapters/playwright-api.d.ts +0 -82
- package/dist/core/adapters/playwright-api.js +0 -264
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QA360 Watch Mode
|
|
3
|
+
*
|
|
4
|
+
* Continuous testing mode that monitors file changes and re-runs tests.
|
|
5
|
+
* Features:
|
|
6
|
+
* - File watching with debounce
|
|
7
|
+
* - Smart test selection based on changed files
|
|
8
|
+
* - Benchmark mode for performance comparison
|
|
9
|
+
* - Statistics aggregation across runs
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* const watcher = new WatchMode({
|
|
14
|
+
* workingDir: process.cwd(),
|
|
15
|
+
* debounceMs: 300,
|
|
16
|
+
* onRunStart: () => console.log('Running tests...'),
|
|
17
|
+
* onRunComplete: (result) => console.log('Done:', result)
|
|
18
|
+
* });
|
|
19
|
+
*
|
|
20
|
+
* await watcher.start();
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
import { watch } from 'fs';
|
|
24
|
+
import { relative, join } from 'path';
|
|
25
|
+
import { Phase3Runner } from '../runner/phase3-runner.js';
|
|
26
|
+
import { readFile } from 'fs/promises';
|
|
27
|
+
/**
|
|
28
|
+
* Watch Mode Manager
|
|
29
|
+
*
|
|
30
|
+
* Monitors file changes and runs tests automatically.
|
|
31
|
+
*/
|
|
32
|
+
export class WatchMode {
|
|
33
|
+
options;
|
|
34
|
+
watchers = [];
|
|
35
|
+
runNumber = 0;
|
|
36
|
+
isRunning = false;
|
|
37
|
+
debounceTimer;
|
|
38
|
+
pendingChanges = new Set();
|
|
39
|
+
runResults = [];
|
|
40
|
+
stats = {
|
|
41
|
+
totalRuns: 0,
|
|
42
|
+
successfulRuns: 0,
|
|
43
|
+
failedRuns: 0,
|
|
44
|
+
avgDuration: 0,
|
|
45
|
+
fastestRun: Infinity,
|
|
46
|
+
slowestRun: 0,
|
|
47
|
+
totalDuration: 0,
|
|
48
|
+
avgTrustScore: 0,
|
|
49
|
+
totalTests: 0,
|
|
50
|
+
totalPassed: 0,
|
|
51
|
+
totalFailed: 0
|
|
52
|
+
};
|
|
53
|
+
packConfig;
|
|
54
|
+
constructor(options) {
|
|
55
|
+
this.options = {
|
|
56
|
+
workingDir: options.workingDir,
|
|
57
|
+
packPath: options.packPath ?? join(options.workingDir, 'pack.yml'),
|
|
58
|
+
debounceMs: options.debounceMs ?? 300,
|
|
59
|
+
ignore: options.ignore ?? ['**/node_modules/**', '**/.git/**', '**/dist/**', '**/.qa360/**', '**/build/**'],
|
|
60
|
+
runOnStart: options.runOnStart ?? true,
|
|
61
|
+
clearScreen: options.clearScreen ?? true,
|
|
62
|
+
onRunStart: options.onRunStart ?? (() => { }),
|
|
63
|
+
onRunComplete: options.onRunComplete ?? (() => { }),
|
|
64
|
+
onFileChange: options.onFileChange ?? (() => { }),
|
|
65
|
+
onError: options.onError ?? ((err) => console.error('Watch error:', err))
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Start watching files and running tests
|
|
70
|
+
*/
|
|
71
|
+
async start() {
|
|
72
|
+
if (this.isRunning) {
|
|
73
|
+
throw new Error('Watch mode is already running');
|
|
74
|
+
}
|
|
75
|
+
this.isRunning = true;
|
|
76
|
+
try {
|
|
77
|
+
// Load pack configuration
|
|
78
|
+
await this.loadPackConfig();
|
|
79
|
+
// Setup file watcher
|
|
80
|
+
this.setupWatcher();
|
|
81
|
+
// Run initial test if configured
|
|
82
|
+
if (this.options.runOnStart) {
|
|
83
|
+
await this.runTests([]);
|
|
84
|
+
}
|
|
85
|
+
this.log('Watch mode started. Press Ctrl+C to stop.');
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
this.isRunning = false;
|
|
89
|
+
throw error;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Stop watching files
|
|
94
|
+
*/
|
|
95
|
+
async stop() {
|
|
96
|
+
if (!this.isRunning) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
this.isRunning = false;
|
|
100
|
+
if (this.debounceTimer) {
|
|
101
|
+
clearTimeout(this.debounceTimer);
|
|
102
|
+
}
|
|
103
|
+
// Close all file watchers
|
|
104
|
+
for (const watcher of this.watchers) {
|
|
105
|
+
watcher.close();
|
|
106
|
+
}
|
|
107
|
+
this.watchers = [];
|
|
108
|
+
this.log('Watch mode stopped.');
|
|
109
|
+
this.log(this.formatStats());
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Get current statistics
|
|
113
|
+
*/
|
|
114
|
+
getStats() {
|
|
115
|
+
return { ...this.stats };
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Get run history
|
|
119
|
+
*/
|
|
120
|
+
getRunHistory() {
|
|
121
|
+
return [...this.runResults];
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Run benchmark tests
|
|
125
|
+
*/
|
|
126
|
+
async benchmark(config) {
|
|
127
|
+
this.log(`Starting benchmark: ${config.iterations} iterations (${config.warmupIterations} warmup)`);
|
|
128
|
+
const durations = [];
|
|
129
|
+
let successful = 0;
|
|
130
|
+
// Warmup runs
|
|
131
|
+
this.log('Warming up...');
|
|
132
|
+
for (let i = 0; i < config.warmupIterations; i++) {
|
|
133
|
+
await this.runTests([], { silent: true });
|
|
134
|
+
}
|
|
135
|
+
// Benchmark runs
|
|
136
|
+
this.log('Running benchmark...');
|
|
137
|
+
for (let i = 0; i < config.iterations; i++) {
|
|
138
|
+
const result = await this.runTests([], { silent: true });
|
|
139
|
+
durations.push(result.duration);
|
|
140
|
+
if (result.success) {
|
|
141
|
+
successful++;
|
|
142
|
+
}
|
|
143
|
+
// Progress indicator
|
|
144
|
+
if ((i + 1) % Math.max(1, Math.floor(config.iterations / 10)) === 0) {
|
|
145
|
+
this.log(` Progress: ${i + 1}/${config.iterations}`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return this.calculateBenchmarkStats(durations, successful, config.iterations);
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Load pack configuration
|
|
152
|
+
*/
|
|
153
|
+
async loadPackConfig() {
|
|
154
|
+
try {
|
|
155
|
+
const content = await readFile(this.options.packPath, 'utf-8');
|
|
156
|
+
// Parse YAML (simple version, assumes YAML is preprocessed or uses JSON-compatible format)
|
|
157
|
+
// For full YAML support, you'd need a YAML parser
|
|
158
|
+
this.packConfig = JSON.parse(content);
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
// If YAML parsing fails, try to use a simple config
|
|
162
|
+
this.packConfig = {
|
|
163
|
+
version: 2,
|
|
164
|
+
name: 'watch-tests',
|
|
165
|
+
gates: {},
|
|
166
|
+
execution: { on_failure: 'continue' }
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Setup file watcher using native Node.js fs.watch
|
|
172
|
+
*/
|
|
173
|
+
setupWatcher() {
|
|
174
|
+
const watchPath = this.options.workingDir;
|
|
175
|
+
try {
|
|
176
|
+
const watcher = watch(watchPath, { recursive: true }, (eventType, filename) => {
|
|
177
|
+
if (filename) {
|
|
178
|
+
this.handleFileChange(join(watchPath, filename));
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
watcher.on('error', (error) => this.options.onError(error));
|
|
182
|
+
this.watchers.push(watcher);
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
this.options.onError(error);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Check if a path should be ignored
|
|
190
|
+
*/
|
|
191
|
+
shouldIgnore(path) {
|
|
192
|
+
const normalizedPath = path.replace(/\\/g, '/');
|
|
193
|
+
for (const pattern of this.options.ignore) {
|
|
194
|
+
const regexPattern = pattern
|
|
195
|
+
.replace(/\*\*/g, '.*')
|
|
196
|
+
.replace(/\*/g, '[^/]*')
|
|
197
|
+
.replace(/\//g, '\\/');
|
|
198
|
+
try {
|
|
199
|
+
const regex = new RegExp(regexPattern);
|
|
200
|
+
if (regex.test(normalizedPath)) {
|
|
201
|
+
return true;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
catch {
|
|
205
|
+
// Invalid regex pattern, skip
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Handle file change with debouncing
|
|
212
|
+
*/
|
|
213
|
+
handleFileChange(path) {
|
|
214
|
+
if (this.shouldIgnore(path)) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
this.pendingChanges.add(path);
|
|
218
|
+
this.options.onFileChange(path);
|
|
219
|
+
if (this.debounceTimer) {
|
|
220
|
+
clearTimeout(this.debounceTimer);
|
|
221
|
+
}
|
|
222
|
+
this.debounceTimer = setTimeout(() => {
|
|
223
|
+
const changes = Array.from(this.pendingChanges);
|
|
224
|
+
this.pendingChanges.clear();
|
|
225
|
+
this.runTests(changes);
|
|
226
|
+
}, this.options.debounceMs);
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Run tests and update statistics
|
|
230
|
+
*/
|
|
231
|
+
async runTests(changedFiles, options = {}) {
|
|
232
|
+
const runNumber = ++this.runNumber;
|
|
233
|
+
const startedAt = new Date();
|
|
234
|
+
if (!options.silent) {
|
|
235
|
+
if (this.options.clearScreen) {
|
|
236
|
+
console.clear();
|
|
237
|
+
}
|
|
238
|
+
this.options.onRunStart(runNumber);
|
|
239
|
+
}
|
|
240
|
+
let result;
|
|
241
|
+
try {
|
|
242
|
+
const runner = new Phase3Runner({
|
|
243
|
+
workingDir: this.options.workingDir,
|
|
244
|
+
pack: this.packConfig
|
|
245
|
+
});
|
|
246
|
+
const phase3Result = await runner.run();
|
|
247
|
+
const completedAt = new Date();
|
|
248
|
+
const duration = completedAt.getTime() - startedAt.getTime();
|
|
249
|
+
const success = phase3Result.success;
|
|
250
|
+
result = {
|
|
251
|
+
runNumber,
|
|
252
|
+
startedAt,
|
|
253
|
+
completedAt,
|
|
254
|
+
duration,
|
|
255
|
+
result: phase3Result,
|
|
256
|
+
changedFiles,
|
|
257
|
+
success
|
|
258
|
+
};
|
|
259
|
+
this.runResults.push(result);
|
|
260
|
+
this.updateStats(result);
|
|
261
|
+
if (!options.silent) {
|
|
262
|
+
this.logRunResult(result);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
catch (error) {
|
|
266
|
+
const completedAt = new Date();
|
|
267
|
+
const duration = completedAt.getTime() - startedAt.getTime();
|
|
268
|
+
result = {
|
|
269
|
+
runNumber,
|
|
270
|
+
startedAt,
|
|
271
|
+
completedAt,
|
|
272
|
+
duration,
|
|
273
|
+
result: {
|
|
274
|
+
success: false,
|
|
275
|
+
pack: this.packConfig,
|
|
276
|
+
duration: 0,
|
|
277
|
+
gates: [],
|
|
278
|
+
hooks: { beforeAll: [], beforeEach: [], afterEach: [], afterAll: [] },
|
|
279
|
+
summary: { total: 0, passed: 0, failed: 1, trustScore: 0 },
|
|
280
|
+
error: error instanceof Error ? error.message : String(error)
|
|
281
|
+
},
|
|
282
|
+
changedFiles,
|
|
283
|
+
success: false
|
|
284
|
+
};
|
|
285
|
+
this.runResults.push(result);
|
|
286
|
+
this.updateStats(result);
|
|
287
|
+
}
|
|
288
|
+
this.options.onRunComplete(result);
|
|
289
|
+
return result;
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Update aggregated statistics
|
|
293
|
+
*/
|
|
294
|
+
updateStats(result) {
|
|
295
|
+
this.stats.totalRuns++;
|
|
296
|
+
this.stats.totalDuration += result.duration;
|
|
297
|
+
if (result.success) {
|
|
298
|
+
this.stats.successfulRuns++;
|
|
299
|
+
this.stats.avgTrustScore = ((this.stats.avgTrustScore * (this.stats.successfulRuns - 1) + result.result.summary.trustScore) /
|
|
300
|
+
this.stats.successfulRuns);
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
this.stats.failedRuns++;
|
|
304
|
+
}
|
|
305
|
+
this.stats.avgDuration = this.stats.totalDuration / this.stats.totalRuns;
|
|
306
|
+
this.stats.fastestRun = Math.min(this.stats.fastestRun, result.duration);
|
|
307
|
+
this.stats.slowestRun = Math.max(this.stats.slowestRun, result.duration);
|
|
308
|
+
this.stats.totalTests += result.result.summary.total;
|
|
309
|
+
this.stats.totalPassed += result.result.summary.passed;
|
|
310
|
+
this.stats.totalFailed += result.result.summary.failed;
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Calculate benchmark statistics
|
|
314
|
+
*/
|
|
315
|
+
calculateBenchmarkStats(durations, successful, total) {
|
|
316
|
+
const sorted = [...durations].sort((a, b) => a - b);
|
|
317
|
+
const sum = durations.reduce((a, b) => a + b, 0);
|
|
318
|
+
const avg = sum / durations.length;
|
|
319
|
+
const variance = durations.reduce((acc, val) => acc + Math.pow(val - avg, 2), 0) / durations.length;
|
|
320
|
+
const stdDev = Math.sqrt(variance);
|
|
321
|
+
return {
|
|
322
|
+
avgDuration: avg,
|
|
323
|
+
minDuration: sorted[0] ?? 0,
|
|
324
|
+
maxDuration: sorted[sorted.length - 1] ?? 0,
|
|
325
|
+
stdDev,
|
|
326
|
+
median: sorted[Math.floor(sorted.length / 2)] ?? 0,
|
|
327
|
+
p95: sorted[Math.floor(sorted.length * 0.95)] ?? 0,
|
|
328
|
+
p99: sorted[Math.floor(sorted.length * 0.99)] ?? 0,
|
|
329
|
+
successfulRuns: successful,
|
|
330
|
+
totalIterations: total,
|
|
331
|
+
throughput: 1000 / avg // runs per second
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Log formatted run result
|
|
336
|
+
*/
|
|
337
|
+
logRunResult(result) {
|
|
338
|
+
const { result: r, changedFiles, duration } = result;
|
|
339
|
+
console.log(`\n${result.success ? 'ā
' : 'ā'} Run #${result.runNumber} (${duration}ms)`);
|
|
340
|
+
if (changedFiles.length > 0) {
|
|
341
|
+
console.log(` Changed files:`);
|
|
342
|
+
for (const file of changedFiles) {
|
|
343
|
+
console.log(` - ${relative(this.options.workingDir, file)}`);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
console.log(` Tests: ${r.summary.passed}/${r.summary.total} passed`);
|
|
347
|
+
console.log(` Trust Score: ${r.summary.trustScore}`);
|
|
348
|
+
console.log(` Gates: ${r.gates.length}`);
|
|
349
|
+
if (r.gates.length > 0) {
|
|
350
|
+
for (const gate of r.gates) {
|
|
351
|
+
console.log(` ${gate.success ? 'ā
' : 'ā'} ${gate.gate}`);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
console.log(`\n${this.formatStats()}`);
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Format statistics for display
|
|
358
|
+
*/
|
|
359
|
+
formatStats() {
|
|
360
|
+
const s = this.stats;
|
|
361
|
+
if (s.totalRuns === 0) {
|
|
362
|
+
return 'No runs yet';
|
|
363
|
+
}
|
|
364
|
+
const successRate = ((s.successfulRuns / s.totalRuns) * 100).toFixed(1);
|
|
365
|
+
return [
|
|
366
|
+
'',
|
|
367
|
+
'š Statistics:',
|
|
368
|
+
` Runs: ${s.totalRuns} (${s.successfulRuns} passed, ${s.failedRuns} failed)`,
|
|
369
|
+
` Success Rate: ${successRate}%`,
|
|
370
|
+
` Avg Duration: ${Math.round(s.avgDuration)}ms`,
|
|
371
|
+
` Fastest: ${s.fastestRun === Infinity ? 0 : s.fastestRun}ms | Slowest: ${s.slowestRun}ms`,
|
|
372
|
+
` Avg Trust Score: ${Math.round(s.avgTrustScore)}`,
|
|
373
|
+
` Total Tests: ${s.totalTests} (${s.totalPassed} passed, ${s.totalFailed} failed)`,
|
|
374
|
+
''
|
|
375
|
+
].join('\n');
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Log message
|
|
379
|
+
*/
|
|
380
|
+
log(message) {
|
|
381
|
+
console.log(`[Watch] ${message}`);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Factory function to create a watch mode instance
|
|
386
|
+
*/
|
|
387
|
+
export function createWatchMode(options) {
|
|
388
|
+
return new WatchMode(options);
|
|
389
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -21,9 +21,21 @@ import { examplesListCommand, examplesCopyCommand, examplesShowCommand } from '.
|
|
|
21
21
|
import { runCommand } from './commands/run.js';
|
|
22
22
|
import { reportCommand } from './commands/report.js';
|
|
23
23
|
import { verifyCommand } from './commands/verify.js';
|
|
24
|
-
import {
|
|
24
|
+
import { explainCommand } from './commands/explain.js';
|
|
25
|
+
// import { packValidateCommand, packLintCommand, packMigrateCommand } from './commands/pack.js'; // TODO: fix pack v2 imports
|
|
25
26
|
import { secretsAddCommand, secretsListCommand, secretsRemoveCommand, secretsDoctorCommand, secretsExportCommand } from './commands/secrets.js';
|
|
26
27
|
import { createHistoryCommands } from './commands/history.js';
|
|
28
|
+
import { createAICommands } from './commands/ai.js';
|
|
29
|
+
import { createFlakinessCommands } from './commands/flakiness.js';
|
|
30
|
+
import { createDashboardCommand } from './commands/monitor.js';
|
|
31
|
+
import { coverageCommand } from './commands/coverage.js';
|
|
32
|
+
import { sloCommand } from './commands/slo.js';
|
|
33
|
+
import { regressionCommand } from './commands/regression.js';
|
|
34
|
+
import { createRetryCommands } from './commands/retry.js';
|
|
35
|
+
// import { createMonitorCommands } from './commands/monitor.js'; // TODO: Re-enable monitor imports
|
|
36
|
+
// import { createOllamaCommands } from './commands/ollama.js'; // TODO: Re-enable when Ollama exports from core are fixed
|
|
37
|
+
// import { createGenerateCommands } from './commands/generate.js'; // TODO: fix generation imports
|
|
38
|
+
// import { createRepairCommand } from './commands/repair.js'; // TODO: fix repair imports
|
|
27
39
|
const program = new Command();
|
|
28
40
|
program
|
|
29
41
|
.name('qa360')
|
|
@@ -46,6 +58,7 @@ program
|
|
|
46
58
|
.option('--output <file>', 'Output file path', 'qa360.yml')
|
|
47
59
|
.option('--force', 'Overwrite existing file')
|
|
48
60
|
.option('-y, --yes', 'Skip prompts and use defaults')
|
|
61
|
+
.option('--beginner', 'Guided mode for first-time users with step-by-step explanations')
|
|
49
62
|
.action(async (options) => {
|
|
50
63
|
await initCommand(options);
|
|
51
64
|
});
|
|
@@ -114,80 +127,47 @@ program
|
|
|
114
127
|
await verifyCommand(proof, options);
|
|
115
128
|
});
|
|
116
129
|
program
|
|
117
|
-
.command('explain
|
|
118
|
-
.description('Explain
|
|
119
|
-
.
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
},
|
|
127
|
-
'QX002': {
|
|
128
|
-
title: 'QA360 Directory Structure Missing',
|
|
129
|
-
cause: 'Required .qa360 directories not found',
|
|
130
|
-
solution: 'Run "qa360 doctor --fix" to create missing directories'
|
|
131
|
-
},
|
|
132
|
-
'QX003': {
|
|
133
|
-
title: 'Playwright Not Available',
|
|
134
|
-
cause: 'Playwright is not installed or not in PATH',
|
|
135
|
-
solution: 'Install: "npx playwright install" or use --docker flag'
|
|
136
|
-
},
|
|
137
|
-
'QX004': {
|
|
138
|
-
title: 'Puppeteer Not Available',
|
|
139
|
-
cause: 'Puppeteer is not installed (required for PDF generation)',
|
|
140
|
-
solution: 'Install: "npm install puppeteer" or check dependencies'
|
|
141
|
-
},
|
|
142
|
-
'QX005': {
|
|
143
|
-
title: 'Network Connectivity Issues',
|
|
144
|
-
cause: 'Cannot reach external services (proxy/firewall)',
|
|
145
|
-
solution: 'Check network settings, configure proxy, or use offline mode'
|
|
146
|
-
},
|
|
147
|
-
'QX006': {
|
|
148
|
-
title: 'Secrets Configuration Missing',
|
|
149
|
-
cause: 'No secrets.json file configured',
|
|
150
|
-
solution: 'Run "qa360 doctor --fix" to create default secrets file'
|
|
151
|
-
},
|
|
152
|
-
'QX007': {
|
|
153
|
-
title: 'Cryptographic Keys Missing',
|
|
154
|
-
cause: 'Ed25519 key pair not found for signing',
|
|
155
|
-
solution: 'Run "qa360 doctor --fix" to generate new key pair'
|
|
156
|
-
}
|
|
157
|
-
};
|
|
158
|
-
const explanation = explanations[code];
|
|
159
|
-
if (explanation) {
|
|
160
|
-
console.log(chalk.red(`\nā ${explanation.title}`));
|
|
161
|
-
console.log(chalk.yellow(`\nš Cause: ${explanation.cause}`));
|
|
162
|
-
console.log(chalk.green(`\nš” Solution: ${explanation.solution}`));
|
|
163
|
-
}
|
|
164
|
-
else {
|
|
165
|
-
console.log(chalk.red(`\nā Code d'erreur inconnu: ${code}`));
|
|
166
|
-
console.log(chalk.blue('\nš Codes disponibles: QX001-QX007'));
|
|
167
|
-
}
|
|
130
|
+
.command('explain [input]')
|
|
131
|
+
.description('Explain errors, failures, and test results with AI')
|
|
132
|
+
.option('--ai', 'Use AI-powered explanation')
|
|
133
|
+
.option('-P, --provider <provider>', 'AI provider to use (deepseek, ollama, openai, anthropic)')
|
|
134
|
+
.option('--run <id>', 'Explain a specific test run')
|
|
135
|
+
.option('--gate <name>', 'Explain gate-specific failures')
|
|
136
|
+
.option('--json', 'Output as JSON')
|
|
137
|
+
.action(async (input, options) => {
|
|
138
|
+
await explainCommand(input, options);
|
|
168
139
|
});
|
|
169
140
|
// Pack commands (Phase 2)
|
|
141
|
+
/*
|
|
142
|
+
// TODO: Re-enable after fixing pack v2 imports
|
|
170
143
|
const packCommand = program
|
|
171
|
-
|
|
172
|
-
|
|
144
|
+
.command('pack')
|
|
145
|
+
.description('Pack configuration management');
|
|
146
|
+
|
|
173
147
|
packCommand
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
148
|
+
.command('validate [pack]')
|
|
149
|
+
.description('Validate pack.yml (auto-detects v1 or v2)')
|
|
150
|
+
.option('--check-files', 'Verify that referenced test files exist')
|
|
151
|
+
.action(async (pack, options) => {
|
|
152
|
+
await packValidateCommand(pack, options);
|
|
153
|
+
});
|
|
154
|
+
|
|
179
155
|
packCommand
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
156
|
+
.command('lint [pack]')
|
|
157
|
+
.description('Auto-fix pack.yml formatting and structure')
|
|
158
|
+
.action(async (pack) => {
|
|
183
159
|
await packLintCommand(pack);
|
|
184
|
-
});
|
|
160
|
+
});
|
|
161
|
+
|
|
185
162
|
packCommand
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
163
|
+
.command('migrate [pack]')
|
|
164
|
+
.description('Migrate pack v1 to v2 format')
|
|
165
|
+
.option('--output <file>', 'Output file path')
|
|
166
|
+
.option('--dry-run', 'Preview changes without writing files')
|
|
167
|
+
.action(async (pack, options) => {
|
|
168
|
+
await packMigrateCommand(pack, options);
|
|
169
|
+
});
|
|
170
|
+
*/
|
|
191
171
|
// Secrets commands (Phase 2)
|
|
192
172
|
const secretsCommand = program
|
|
193
173
|
.command('secrets')
|
|
@@ -232,6 +212,26 @@ program
|
|
|
232
212
|
});
|
|
233
213
|
// History commands (Evidence Vault)
|
|
234
214
|
program.addCommand(createHistoryCommands());
|
|
215
|
+
// Flakiness commands (Test reliability tracking)
|
|
216
|
+
program.addCommand(createFlakinessCommands());
|
|
217
|
+
// AI commands (DeepSeek, Ollama, OpenAI, Anthropic)
|
|
218
|
+
program.addCommand(createAICommands());
|
|
219
|
+
// program.addCommand(createOllamaCommands()); // TODO: Re-enable when Ollama exports from core are fixed
|
|
220
|
+
// Generate AI commands (Phase 4)
|
|
221
|
+
// program.addCommand(createGenerateCommands()); // TODO: Re-enable after fixing imports
|
|
222
|
+
// Repair Commands (Phase 7 - Auto-Repair)
|
|
223
|
+
// program.addCommand(createRepairCommand()); // TODO: Re-enable after fixing imports
|
|
224
|
+
// Monitor Commands (Phase 8 - TUI & Dashboard)
|
|
225
|
+
// program.addCommand(createMonitorCommands()); // TODO: Re-enable after fixing imports
|
|
226
|
+
program.addCommand(createDashboardCommand());
|
|
227
|
+
// Coverage Commands (Vision 2.0 - Phase 2 - F11)
|
|
228
|
+
program.addCommand(coverageCommand);
|
|
229
|
+
// SLO/SLI Commands (Vision 2.0 - Phase 2)
|
|
230
|
+
program.addCommand(sloCommand);
|
|
231
|
+
// Regression Commands (Vision 2.0 - Phase 2 - F12)
|
|
232
|
+
program.addCommand(regressionCommand);
|
|
233
|
+
// Retry Commands (Vision 2.0 - Phase 2 - F8 Smart Retry)
|
|
234
|
+
program.addCommand(createRetryCommands());
|
|
235
235
|
// Show banner
|
|
236
236
|
console.log(chalk.bold.blue('QA360 Core v' + version));
|
|
237
237
|
console.log(chalk.gray('Transform software testing into verifiable, signed, and traceable proofs\n'));
|