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,340 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QA360 Regression Command
|
|
3
|
+
*
|
|
4
|
+
* CLI command for regression detection and analysis.
|
|
5
|
+
*/
|
|
6
|
+
import { Command } from 'commander';
|
|
7
|
+
import { createRegressionDetector, createRegressionTrendAnalyzer } from '../core/index.js';
|
|
8
|
+
export const regressionCommand = new Command('regression');
|
|
9
|
+
regressionCommand.description('Regression detection and analysis');
|
|
10
|
+
// Detect regressions
|
|
11
|
+
regressionCommand
|
|
12
|
+
.command('detect')
|
|
13
|
+
.description('Detect regressions by comparing current metrics to baseline')
|
|
14
|
+
.option('--baseline <file>', 'Baseline metrics JSON file')
|
|
15
|
+
.option('--current <file>', 'Current metrics JSON file')
|
|
16
|
+
.option('--run-id <id>', 'Current run ID')
|
|
17
|
+
.option('--gate <name>', 'Gate name')
|
|
18
|
+
.option('--json', 'Output as JSON')
|
|
19
|
+
.action(async (options) => {
|
|
20
|
+
const detector = createRegressionDetector();
|
|
21
|
+
// Demo: Create baseline from simulated data
|
|
22
|
+
const baselineData = [];
|
|
23
|
+
for (let i = 0; i < 30; i++) {
|
|
24
|
+
baselineData.push({
|
|
25
|
+
timestamp: Date.now() - (30 - i) * 3600000,
|
|
26
|
+
value: 100 + Math.random() * 10,
|
|
27
|
+
runId: `run_${i}`
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
detector.updateBaseline('api_latency_p95', baselineData);
|
|
31
|
+
detector.updateBaseline('pass_rate', baselineData.map(d => ({
|
|
32
|
+
...d,
|
|
33
|
+
value: 95 + Math.random() * 3
|
|
34
|
+
})));
|
|
35
|
+
detector.updateBaseline('line_coverage', baselineData.map(d => ({
|
|
36
|
+
...d,
|
|
37
|
+
value: 80 + Math.random() * 2
|
|
38
|
+
})));
|
|
39
|
+
// Simulate current metrics with one regression
|
|
40
|
+
const currentMetrics = {
|
|
41
|
+
api_latency_p95: 145, // 45% increase - regression!
|
|
42
|
+
pass_rate: 94,
|
|
43
|
+
line_coverage: 79.5
|
|
44
|
+
};
|
|
45
|
+
const regressions = detector.detectRegressions(currentMetrics, options.runId || 'current', options.gate || 'api_smoke');
|
|
46
|
+
if (options.json) {
|
|
47
|
+
console.log(JSON.stringify(regressions, null, 2));
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
console.log('\n🔍 Regression Detection Results\n');
|
|
51
|
+
console.log(`Run ID: ${options.runId || 'current'}`);
|
|
52
|
+
console.log(`Gate: ${options.gate || 'api_smoke'}\n`);
|
|
53
|
+
if (regressions.length === 0) {
|
|
54
|
+
console.log('✅ No regressions detected!\n');
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
console.log(`Detected ${regressions.length} regression(s):\n`);
|
|
58
|
+
for (const reg of regressions) {
|
|
59
|
+
const icon = getSeverityIcon(reg.severity);
|
|
60
|
+
const arrow = reg.direction === 'worse' ? '↑' : '↓';
|
|
61
|
+
const changeStr = reg.percentChange > 0 ? '+' : '';
|
|
62
|
+
console.log(` ${icon} ${reg.metricName}`);
|
|
63
|
+
console.log(` Type: ${reg.type}`);
|
|
64
|
+
console.log(` Severity: ${reg.severity.toUpperCase()}`);
|
|
65
|
+
console.log(` Baseline: ${reg.baselineValue.toFixed(2)} → Current: ${reg.currentValue.toFixed(2)}`);
|
|
66
|
+
console.log(` Change: ${arrow} ${changeStr}${reg.percentChange.toFixed(1)}% (${reg.absoluteChange.toFixed(2)})`);
|
|
67
|
+
console.log(` Confidence: ${(reg.confidence * 100).toFixed(1)}%`);
|
|
68
|
+
console.log(` Component: ${reg.affectedComponent}`);
|
|
69
|
+
console.log(` Suggestions:`);
|
|
70
|
+
for (const suggestion of reg.suggestions) {
|
|
71
|
+
console.log(` - ${suggestion}`);
|
|
72
|
+
}
|
|
73
|
+
console.log('');
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
// Analyze trends
|
|
77
|
+
regressionCommand
|
|
78
|
+
.command('trend')
|
|
79
|
+
.description('Analyze metric trends for potential future regressions')
|
|
80
|
+
.option('--metric <name>', 'Metric name to analyze')
|
|
81
|
+
.option('--points <n>', 'Number of data points to use', '30')
|
|
82
|
+
.option('--json', 'Output as JSON')
|
|
83
|
+
.action(async (options) => {
|
|
84
|
+
const analyzer = createRegressionTrendAnalyzer();
|
|
85
|
+
const metricName = options.metric || 'api_latency_p95';
|
|
86
|
+
// Generate demo trend data
|
|
87
|
+
const data = [];
|
|
88
|
+
for (let i = 0; i < parseInt(options.points); i++) {
|
|
89
|
+
// Slight upward trend (degrading)
|
|
90
|
+
const trend = i * 0.5;
|
|
91
|
+
const noise = (Math.random() - 0.5) * 5;
|
|
92
|
+
data.push({
|
|
93
|
+
timestamp: Date.now() - (parseInt(options.points) - i) * 3600000,
|
|
94
|
+
value: 100 + trend + noise,
|
|
95
|
+
runId: `run_${i}`,
|
|
96
|
+
metadata: { metric: metricName }
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
const trend = analyzer.analyzeTrend(data);
|
|
100
|
+
if (options.json) {
|
|
101
|
+
console.log(JSON.stringify(trend, null, 2));
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
console.log('\n📈 Trend Analysis\n');
|
|
105
|
+
console.log(`Metric: ${metricName}`);
|
|
106
|
+
console.log(`Data Points: ${data.length}\n`);
|
|
107
|
+
if (!trend) {
|
|
108
|
+
console.log('⚠️ Insufficient data for trend analysis\n');
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const directionIcon = trend.direction === 'degrading' ? '📉' : trend.direction === 'improving' ? '📈' : '➡️';
|
|
112
|
+
const strengthPercent = Math.round(trend.strength * 100);
|
|
113
|
+
console.log(` ${directionIcon} Direction: ${trend.direction.toUpperCase()}`);
|
|
114
|
+
console.log(` Strength: ${strengthPercent}% confidence`);
|
|
115
|
+
if (trend.projected !== undefined) {
|
|
116
|
+
console.log(` Projected (7 days): ${trend.projected.toFixed(2)}`);
|
|
117
|
+
}
|
|
118
|
+
console.log('');
|
|
119
|
+
});
|
|
120
|
+
// List recent regressions
|
|
121
|
+
regressionCommand
|
|
122
|
+
.command('list')
|
|
123
|
+
.description('List recent regressions')
|
|
124
|
+
.option('--severity <level>', 'Filter by severity')
|
|
125
|
+
.option('--type <type>', 'Filter by type')
|
|
126
|
+
.option('--limit <n>', 'Max results', '20')
|
|
127
|
+
.option('--json', 'Output as JSON')
|
|
128
|
+
.action(async (options) => {
|
|
129
|
+
// Demo: Generate some regressions
|
|
130
|
+
const regressions = [
|
|
131
|
+
{
|
|
132
|
+
id: 'reg_1',
|
|
133
|
+
runId: 'run_123',
|
|
134
|
+
baselineRunId: 'run_100',
|
|
135
|
+
type: 'performance',
|
|
136
|
+
severity: 'major',
|
|
137
|
+
status: 'detected',
|
|
138
|
+
metricName: 'api_latency_p95',
|
|
139
|
+
baselineValue: 100,
|
|
140
|
+
currentValue: 145,
|
|
141
|
+
absoluteChange: 45,
|
|
142
|
+
percentChange: 45,
|
|
143
|
+
significance: 0.001,
|
|
144
|
+
confidence: 0.999,
|
|
145
|
+
direction: 'worse',
|
|
146
|
+
affectedComponent: '/api/users',
|
|
147
|
+
gate: 'api_smoke',
|
|
148
|
+
suggestions: ['Investigate recent API changes', 'Profile the endpoint'],
|
|
149
|
+
timestamp: Date.now() - 3600000
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
id: 'reg_2',
|
|
153
|
+
runId: 'run_123',
|
|
154
|
+
baselineRunId: 'run_100',
|
|
155
|
+
type: 'coverage',
|
|
156
|
+
severity: 'minor',
|
|
157
|
+
status: 'detected',
|
|
158
|
+
metricName: 'line_coverage',
|
|
159
|
+
baselineValue: 82,
|
|
160
|
+
currentValue: 79.5,
|
|
161
|
+
absoluteChange: -2.5,
|
|
162
|
+
percentChange: -3.05,
|
|
163
|
+
significance: 0.03,
|
|
164
|
+
confidence: 0.97,
|
|
165
|
+
direction: 'worse',
|
|
166
|
+
affectedComponent: 'src/components/',
|
|
167
|
+
gate: 'ui',
|
|
168
|
+
suggestions: ['Review deleted test cases'],
|
|
169
|
+
timestamp: Date.now() - 3600000
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
id: 'reg_3',
|
|
173
|
+
runId: 'run_122',
|
|
174
|
+
baselineRunId: 'run_99',
|
|
175
|
+
type: 'quality',
|
|
176
|
+
severity: 'critical',
|
|
177
|
+
status: 'resolved',
|
|
178
|
+
metricName: 'pass_rate',
|
|
179
|
+
baselineValue: 98,
|
|
180
|
+
currentValue: 85,
|
|
181
|
+
absoluteChange: -13,
|
|
182
|
+
percentChange: -13.27,
|
|
183
|
+
significance: 0.0001,
|
|
184
|
+
confidence: 0.9999,
|
|
185
|
+
direction: 'worse',
|
|
186
|
+
affectedComponent: 'test/auth',
|
|
187
|
+
gate: 'unit',
|
|
188
|
+
suggestions: ['Fix authentication tests'],
|
|
189
|
+
timestamp: Date.now() - 86400000
|
|
190
|
+
}
|
|
191
|
+
];
|
|
192
|
+
// Filter
|
|
193
|
+
let filtered = regressions;
|
|
194
|
+
if (options.severity) {
|
|
195
|
+
filtered = filtered.filter(r => r.severity === options.severity);
|
|
196
|
+
}
|
|
197
|
+
if (options.type) {
|
|
198
|
+
filtered = filtered.filter(r => r.type === options.type);
|
|
199
|
+
}
|
|
200
|
+
filtered = filtered.slice(0, parseInt(options.limit));
|
|
201
|
+
if (options.json) {
|
|
202
|
+
console.log(JSON.stringify(filtered, null, 2));
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
console.log('\n🔍 Recent Regressions\n');
|
|
206
|
+
if (filtered.length === 0) {
|
|
207
|
+
console.log('No regressions found.\n');
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
for (const reg of filtered) {
|
|
211
|
+
const icon = getSeverityIcon(reg.severity);
|
|
212
|
+
const statusIcon = reg.status === 'resolved' ? '✅' : reg.status === 'investigating' ? '🔬' : '❗';
|
|
213
|
+
console.log(` ${icon} ${statusIcon} ${reg.metricName}`);
|
|
214
|
+
console.log(` Severity: ${reg.severity.toUpperCase()} | Status: ${reg.status}`);
|
|
215
|
+
console.log(` Type: ${reg.type} | Gate: ${reg.gate}`);
|
|
216
|
+
console.log(` ${new Date(reg.timestamp).toISOString()}`);
|
|
217
|
+
console.log('');
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
// Compare two runs
|
|
221
|
+
regressionCommand
|
|
222
|
+
.command('compare')
|
|
223
|
+
.description('Compare two runs for regressions')
|
|
224
|
+
.option('--baseline <runId>', 'Baseline run ID')
|
|
225
|
+
.option('--current <runId>', 'Current run ID')
|
|
226
|
+
.option('--metric <name>', 'Metric to compare')
|
|
227
|
+
.option('--json', 'Output as JSON')
|
|
228
|
+
.action(async (options) => {
|
|
229
|
+
const analyzer = createRegressionTrendAnalyzer();
|
|
230
|
+
const metricName = options.metric || 'api_latency';
|
|
231
|
+
// Generate demo data
|
|
232
|
+
const baseline = [];
|
|
233
|
+
const current = [];
|
|
234
|
+
for (let i = 0; i < 10; i++) {
|
|
235
|
+
baseline.push({
|
|
236
|
+
timestamp: Date.now() - (20 - i) * 3600000,
|
|
237
|
+
value: 100 + Math.random() * 5,
|
|
238
|
+
runId: options.baseline || `baseline_${i}`
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
for (let i = 0; i < 10; i++) {
|
|
242
|
+
current.push({
|
|
243
|
+
timestamp: Date.now() - (10 - i) * 3600000,
|
|
244
|
+
value: 120 + Math.random() * 5, // 20% higher
|
|
245
|
+
runId: options.current || `current_${i}`
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
const regression = analyzer.comparePeriods(baseline, current, metricName);
|
|
249
|
+
if (options.json) {
|
|
250
|
+
console.log(JSON.stringify(regression, null, 2));
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
console.log('\n🔍 Regression Comparison\n');
|
|
254
|
+
console.log(`Metric: ${metricName}`);
|
|
255
|
+
console.log(`Baseline: ${options.baseline || 'baseline_*'}`);
|
|
256
|
+
console.log(`Current: ${options.current || 'current_*'}\n`);
|
|
257
|
+
if (!regression) {
|
|
258
|
+
console.log('✅ No regression detected!\n');
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
const icon = getSeverityIcon(regression.severity);
|
|
262
|
+
console.log(` ${icon} Regression Detected`);
|
|
263
|
+
console.log(` Type: ${regression.type}`);
|
|
264
|
+
console.log(` Severity: ${regression.severity.toUpperCase()}`);
|
|
265
|
+
console.log(` Baseline: ${regression.baselineValue.toFixed(2)} → Current: ${regression.currentValue.toFixed(2)}`);
|
|
266
|
+
console.log(` Change: ${regression.percentChange > 0 ? '+' : ''}${regression.percentChange.toFixed(1)}%`);
|
|
267
|
+
console.log(` Confidence: ${(regression.confidence * 100).toFixed(1)}%\n`);
|
|
268
|
+
});
|
|
269
|
+
// Show regression statistics
|
|
270
|
+
regressionCommand
|
|
271
|
+
.command('stats')
|
|
272
|
+
.description('Show regression statistics')
|
|
273
|
+
.option('--json', 'Output as JSON')
|
|
274
|
+
.action(async (_options) => {
|
|
275
|
+
// Demo statistics
|
|
276
|
+
const stats = {
|
|
277
|
+
total: 15,
|
|
278
|
+
bySeverity: {
|
|
279
|
+
critical: 2,
|
|
280
|
+
major: 3,
|
|
281
|
+
moderate: 5,
|
|
282
|
+
minor: 3,
|
|
283
|
+
info: 2
|
|
284
|
+
},
|
|
285
|
+
byType: {
|
|
286
|
+
performance: 5,
|
|
287
|
+
quality: 4,
|
|
288
|
+
coverage: 2,
|
|
289
|
+
reliability: 2,
|
|
290
|
+
security: 1,
|
|
291
|
+
api_breaking: 1
|
|
292
|
+
},
|
|
293
|
+
byStatus: {
|
|
294
|
+
detected: 5,
|
|
295
|
+
investigating: 3,
|
|
296
|
+
confirmed: 2,
|
|
297
|
+
resolved: 4,
|
|
298
|
+
ignored: 1
|
|
299
|
+
},
|
|
300
|
+
recent: 3 // Last 7 days
|
|
301
|
+
};
|
|
302
|
+
console.log('\n📊 Regression Statistics\n');
|
|
303
|
+
console.log(`Total Regressions: ${stats.total}`);
|
|
304
|
+
console.log(`Recent (7 days): ${stats.recent}\n`);
|
|
305
|
+
console.log('By Severity:');
|
|
306
|
+
for (const [severity, count] of Object.entries(stats.bySeverity)) {
|
|
307
|
+
if (count > 0) {
|
|
308
|
+
const icon = getSeverityIcon(severity);
|
|
309
|
+
console.log(` ${icon} ${severity.toUpperCase()}: ${count}`);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
console.log('\nBy Type:');
|
|
313
|
+
for (const [type, count] of Object.entries(stats.byType)) {
|
|
314
|
+
if (count > 0) {
|
|
315
|
+
console.log(` • ${type}: ${count}`);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
console.log('\nBy Status:');
|
|
319
|
+
for (const [status, count] of Object.entries(stats.byStatus)) {
|
|
320
|
+
if (count > 0) {
|
|
321
|
+
console.log(` • ${status}: ${count}`);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
console.log('');
|
|
325
|
+
});
|
|
326
|
+
function getSeverityIcon(severity) {
|
|
327
|
+
switch (severity) {
|
|
328
|
+
case 'critical':
|
|
329
|
+
return '🔴';
|
|
330
|
+
case 'major':
|
|
331
|
+
return '🟠';
|
|
332
|
+
case 'moderate':
|
|
333
|
+
return '🟡';
|
|
334
|
+
case 'minor':
|
|
335
|
+
return '🟢';
|
|
336
|
+
default:
|
|
337
|
+
return '⚪';
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
export default regressionCommand;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Repair Commands
|
|
3
|
+
*
|
|
4
|
+
* CLI commands for automatic test repair.
|
|
5
|
+
*/
|
|
6
|
+
import { Command } from 'commander';
|
|
7
|
+
/**
|
|
8
|
+
* Repair analyze command
|
|
9
|
+
*/
|
|
10
|
+
export declare function createRepairAnalyzeCommand(): Command;
|
|
11
|
+
/**
|
|
12
|
+
* Repair suggest command
|
|
13
|
+
*/
|
|
14
|
+
export declare function createRepairSuggestCommand(): Command;
|
|
15
|
+
/**
|
|
16
|
+
* Repair fix command
|
|
17
|
+
*/
|
|
18
|
+
export declare function createRepairFixCommand(): Command;
|
|
19
|
+
/**
|
|
20
|
+
* Repair interactive command
|
|
21
|
+
*/
|
|
22
|
+
export declare function createRepairInteractiveCommand(): Command;
|
|
23
|
+
/**
|
|
24
|
+
* Create main repair command group
|
|
25
|
+
*/
|
|
26
|
+
export declare function createRepairCommand(): Command;
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Repair Commands
|
|
3
|
+
*
|
|
4
|
+
* CLI commands for automatic test repair.
|
|
5
|
+
*/
|
|
6
|
+
import { Command } from 'commander';
|
|
7
|
+
import { readFile } from 'fs/promises';
|
|
8
|
+
import { existsSync } from 'fs';
|
|
9
|
+
import { resolve } from 'path';
|
|
10
|
+
import { TestRepairer, analyzeTestErrors, getQuickFixes } from '../core/index.js';
|
|
11
|
+
/**
|
|
12
|
+
* Parse test output to extract errors
|
|
13
|
+
*/
|
|
14
|
+
async function parseTestOutput(outputPath) {
|
|
15
|
+
if (!existsSync(outputPath)) {
|
|
16
|
+
return { errors: [] };
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
const content = await readFile(outputPath, 'utf-8');
|
|
20
|
+
const errors = await analyzeTestErrors('', content);
|
|
21
|
+
return { errors };
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return { errors: [] };
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Simulate running tests and extracting errors
|
|
29
|
+
*/
|
|
30
|
+
async function extractErrorsFromFile(testFile) {
|
|
31
|
+
if (!existsSync(testFile)) {
|
|
32
|
+
return [];
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
const content = await readFile(testFile, 'utf-8');
|
|
36
|
+
// Look for common error patterns in test files
|
|
37
|
+
const errors = [];
|
|
38
|
+
// This is a simplified extraction - in production would parse actual test output
|
|
39
|
+
if (content.includes('expect(') && content.includes('.toBe(')) {
|
|
40
|
+
// Find assertions that might fail
|
|
41
|
+
const lines = content.split('\n');
|
|
42
|
+
lines.forEach((line, index) => {
|
|
43
|
+
if (line.includes('expect(') && line.includes('.toBe(')) {
|
|
44
|
+
// Create a simulated error for demonstration
|
|
45
|
+
const match = line.match(/expect\(([^)]+)\)\.toBe\(([^)]+)\)/);
|
|
46
|
+
if (match) {
|
|
47
|
+
errors.push({
|
|
48
|
+
testFile,
|
|
49
|
+
testName: `Test at line ${index + 1}`,
|
|
50
|
+
line: index + 1,
|
|
51
|
+
type: 'assertion_error',
|
|
52
|
+
message: `Expected ${match[2]} but received different value`,
|
|
53
|
+
context: 'Assertion failed'
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
return errors;
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return [];
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Repair analyze command
|
|
67
|
+
*/
|
|
68
|
+
export function createRepairAnalyzeCommand() {
|
|
69
|
+
const cmd = new Command('analyze');
|
|
70
|
+
cmd
|
|
71
|
+
.description('Analyze test failures and generate repair suggestions')
|
|
72
|
+
.argument('<test-file>', 'Path to the test file')
|
|
73
|
+
.option('-o, --output <path>', 'Path to test output file')
|
|
74
|
+
.option('-p, --provider <provider>', 'LLM provider (ollama, openai, anthropic, mock)', 'ollama')
|
|
75
|
+
.option('-j, --json', 'Output in JSON format')
|
|
76
|
+
.option('-v, --verbose', 'Verbose output')
|
|
77
|
+
.action(async (testFile, options) => {
|
|
78
|
+
const filePath = resolve(testFile);
|
|
79
|
+
if (!existsSync(filePath)) {
|
|
80
|
+
console.error(`Error: Test file not found: ${filePath}`);
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
// Extract errors from test output or simulate
|
|
85
|
+
let errors = [];
|
|
86
|
+
if (options.output) {
|
|
87
|
+
const result = await parseTestOutput(options.output);
|
|
88
|
+
errors = result.errors;
|
|
89
|
+
}
|
|
90
|
+
if (errors.length === 0) {
|
|
91
|
+
errors = await extractErrorsFromFile(filePath);
|
|
92
|
+
}
|
|
93
|
+
if (errors.length === 0) {
|
|
94
|
+
console.log('No test errors found to analyze.');
|
|
95
|
+
console.log('Tip: Provide test output with --output option or run tests first.');
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
// Create repairer
|
|
99
|
+
const repairer = new TestRepairer({
|
|
100
|
+
llmProvider: options.provider,
|
|
101
|
+
verbose: options.verbose
|
|
102
|
+
});
|
|
103
|
+
// Analyze
|
|
104
|
+
const testRunResult = {
|
|
105
|
+
passed: 0,
|
|
106
|
+
failed: errors.length,
|
|
107
|
+
errors,
|
|
108
|
+
duration: 0
|
|
109
|
+
};
|
|
110
|
+
const analysis = await repairer.analyze(filePath, testRunResult);
|
|
111
|
+
if (options.json) {
|
|
112
|
+
console.log(JSON.stringify({
|
|
113
|
+
testFile: filePath,
|
|
114
|
+
errorsFound: errors.length,
|
|
115
|
+
autoFixable: analysis.autoFixable,
|
|
116
|
+
suggestions: analysis.suggestions,
|
|
117
|
+
priority: analysis.priority,
|
|
118
|
+
estimatedTime: analysis.estimatedTime
|
|
119
|
+
}, null, 2));
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
console.log(`\n=== Test Repair Analysis ===`);
|
|
123
|
+
console.log(`File: ${filePath}`);
|
|
124
|
+
console.log(`Errors found: ${errors.length}`);
|
|
125
|
+
console.log(`Auto-fixable: ${analysis.autoFixable}`);
|
|
126
|
+
console.log(`Priority: ${analysis.priority}`);
|
|
127
|
+
console.log(`Estimated repair time: ${analysis.estimatedTime}s`);
|
|
128
|
+
console.log(`\nSuggestions:`);
|
|
129
|
+
for (const suggestion of analysis.suggestions) {
|
|
130
|
+
console.log(`\n ${suggestion.description}`);
|
|
131
|
+
console.log(` Type: ${suggestion.type}`);
|
|
132
|
+
console.log(` Confidence: ${(suggestion.confidence * 100).toFixed(0)}%`);
|
|
133
|
+
console.log(` Effort: ${suggestion.estimatedEffort}`);
|
|
134
|
+
console.log(` Reason: ${suggestion.reason}`);
|
|
135
|
+
if (options.verbose && suggestion.code) {
|
|
136
|
+
console.log(` Code:\n ${suggestion.code}`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
console.error(`Analysis failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
143
|
+
process.exit(1);
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
return cmd;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Repair suggest command
|
|
150
|
+
*/
|
|
151
|
+
export function createRepairSuggestCommand() {
|
|
152
|
+
const cmd = new Command('suggest');
|
|
153
|
+
cmd
|
|
154
|
+
.description('Get quick fix suggestions for a test error')
|
|
155
|
+
.argument('<test-file>', 'Path to the test file')
|
|
156
|
+
.option('-m, --message <message>', 'Error message')
|
|
157
|
+
.option('-t, --type <type>', 'Error type (assertion_error, timeout_error, etc.)')
|
|
158
|
+
.option('-l, --line <number>', 'Line number', '0')
|
|
159
|
+
.option('-j, --json', 'Output in JSON format')
|
|
160
|
+
.action(async (testFile, options) => {
|
|
161
|
+
const filePath = resolve(testFile);
|
|
162
|
+
if (!options.message) {
|
|
163
|
+
console.error('Error: --message is required');
|
|
164
|
+
process.exit(1);
|
|
165
|
+
}
|
|
166
|
+
try {
|
|
167
|
+
const error = {
|
|
168
|
+
testFile: filePath,
|
|
169
|
+
testName: 'unknown',
|
|
170
|
+
line: parseInt(options.line, 10),
|
|
171
|
+
type: options.type || 'unknown',
|
|
172
|
+
message: options.message
|
|
173
|
+
};
|
|
174
|
+
const suggestions = await getQuickFixes(error);
|
|
175
|
+
if (options.json) {
|
|
176
|
+
console.log(JSON.stringify(suggestions, null, 2));
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
console.log(`\n=== Quick Fix Suggestions ===`);
|
|
180
|
+
console.log(`Error: ${error.message}`);
|
|
181
|
+
console.log(`Type: ${error.type}\n`);
|
|
182
|
+
if (suggestions.length === 0) {
|
|
183
|
+
console.log('No quick fixes available for this error type.');
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
for (const suggestion of suggestions) {
|
|
187
|
+
console.log(`\n${suggestion.description}`);
|
|
188
|
+
console.log(` Type: ${suggestion.type}`);
|
|
189
|
+
console.log(` Confidence: ${(suggestion.confidence * 100).toFixed(0)}%`);
|
|
190
|
+
console.log(` Code: ${suggestion.code}`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
console.error(`Failed to generate suggestions: ${error instanceof Error ? error.message : String(error)}`);
|
|
197
|
+
process.exit(1);
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
return cmd;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Repair fix command
|
|
204
|
+
*/
|
|
205
|
+
export function createRepairFixCommand() {
|
|
206
|
+
const cmd = new Command('fix');
|
|
207
|
+
cmd
|
|
208
|
+
.description('Apply automatic fixes to failing tests')
|
|
209
|
+
.argument('<test-file>', 'Path to the test file')
|
|
210
|
+
.option('-o, --output <path>', 'Path to test output file')
|
|
211
|
+
.option('-p, --provider <provider>', 'LLM provider (ollama, openai, anthropic, mock)', 'ollama')
|
|
212
|
+
.option('--apply', 'Actually apply fixes (default: dry-run)')
|
|
213
|
+
.option('--no-backup', 'Do not create backup before fixing')
|
|
214
|
+
.option('-j, --json', 'Output in JSON format')
|
|
215
|
+
.option('-v, --verbose', 'Verbose output')
|
|
216
|
+
.action(async (testFile, options) => {
|
|
217
|
+
const filePath = resolve(testFile);
|
|
218
|
+
if (!existsSync(filePath)) {
|
|
219
|
+
console.error(`Error: Test file not found: ${filePath}`);
|
|
220
|
+
process.exit(1);
|
|
221
|
+
}
|
|
222
|
+
try {
|
|
223
|
+
// Extract errors
|
|
224
|
+
let errors = [];
|
|
225
|
+
if (options.output) {
|
|
226
|
+
const result = await parseTestOutput(options.output);
|
|
227
|
+
errors = result.errors;
|
|
228
|
+
}
|
|
229
|
+
if (errors.length === 0) {
|
|
230
|
+
errors = await extractErrorsFromFile(filePath);
|
|
231
|
+
}
|
|
232
|
+
if (errors.length === 0) {
|
|
233
|
+
console.log('No test errors found to fix.');
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
// Create repairer
|
|
237
|
+
const repairer = new TestRepairer({
|
|
238
|
+
llmProvider: options.provider,
|
|
239
|
+
autoApply: options.apply,
|
|
240
|
+
backup: options.backup !== false,
|
|
241
|
+
verbose: options.verbose
|
|
242
|
+
});
|
|
243
|
+
const testRunResult = {
|
|
244
|
+
passed: 0,
|
|
245
|
+
failed: errors.length,
|
|
246
|
+
errors,
|
|
247
|
+
duration: 0
|
|
248
|
+
};
|
|
249
|
+
console.log(`\n${options.apply ? 'Applying' : 'Previewing'} fixes for ${errors.length} error(s)...`);
|
|
250
|
+
const report = await repairer.repair(filePath, testRunResult);
|
|
251
|
+
if (options.json) {
|
|
252
|
+
console.log(JSON.stringify({
|
|
253
|
+
duration: report.duration,
|
|
254
|
+
success: report.success,
|
|
255
|
+
originalErrors: report.result.originalErrors.length,
|
|
256
|
+
remainingErrors: report.result.remainingErrors.length,
|
|
257
|
+
fixesApplied: report.result.fixes.length,
|
|
258
|
+
iterations: report.result.iterations
|
|
259
|
+
}, null, 2));
|
|
260
|
+
}
|
|
261
|
+
else {
|
|
262
|
+
console.log(repairer.generateReport(report));
|
|
263
|
+
if (!options.apply) {
|
|
264
|
+
console.log('\n[Dry run mode] Use --apply to actually apply fixes.');
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
catch (error) {
|
|
269
|
+
console.error(`Fix failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
270
|
+
process.exit(1);
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
return cmd;
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Repair interactive command
|
|
277
|
+
*/
|
|
278
|
+
export function createRepairInteractiveCommand() {
|
|
279
|
+
const cmd = new Command('interactive');
|
|
280
|
+
cmd
|
|
281
|
+
.description('Interactive repair mode')
|
|
282
|
+
.argument('[test-file]', 'Path to the test file (optional)')
|
|
283
|
+
.option('-p, --provider <provider>', 'LLM provider (ollama, openai, anthropic, mock)', 'ollama')
|
|
284
|
+
.action(async (testFile, options) => {
|
|
285
|
+
console.log('\n=== Interactive Repair Mode ===');
|
|
286
|
+
console.log('This feature will be available in a future version.');
|
|
287
|
+
console.log('For now, use: qa360 repair analyze <file> && qa360 repair fix <file> --apply');
|
|
288
|
+
console.log('\nAvailable commands:');
|
|
289
|
+
console.log(' qa360 repair analyze <file> - Analyze test failures');
|
|
290
|
+
console.log(' qa360 repair suggest <file> - Get quick fix suggestions');
|
|
291
|
+
console.log(' qa360 repair fix <file> - Apply automatic fixes');
|
|
292
|
+
});
|
|
293
|
+
return cmd;
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Create main repair command group
|
|
297
|
+
*/
|
|
298
|
+
export function createRepairCommand() {
|
|
299
|
+
const cmd = new Command('repair');
|
|
300
|
+
cmd
|
|
301
|
+
.description('Automatic test repair commands (Phase 7)');
|
|
302
|
+
cmd.addCommand(createRepairAnalyzeCommand());
|
|
303
|
+
cmd.addCommand(createRepairSuggestCommand());
|
|
304
|
+
cmd.addCommand(createRepairFixCommand());
|
|
305
|
+
cmd.addCommand(createRepairInteractiveCommand());
|
|
306
|
+
return cmd;
|
|
307
|
+
}
|