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,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Flakiness CLI Commands
|
|
3
|
+
*
|
|
4
|
+
* Provides commands for managing test flakiness detection and quarantine.
|
|
5
|
+
*/
|
|
6
|
+
import { Command } from 'commander';
|
|
7
|
+
/**
|
|
8
|
+
* List flakiness history for a test
|
|
9
|
+
*/
|
|
10
|
+
export declare function flakinessHistoryAction(testId: string, options: {
|
|
11
|
+
limit?: number;
|
|
12
|
+
json?: boolean;
|
|
13
|
+
workingDir?: string;
|
|
14
|
+
}): Promise<void>;
|
|
15
|
+
/**
|
|
16
|
+
* Show flakiness trends for a test
|
|
17
|
+
*/
|
|
18
|
+
export declare function flakinessTrendsAction(testId: string, options: {
|
|
19
|
+
days?: number;
|
|
20
|
+
json?: boolean;
|
|
21
|
+
workingDir?: string;
|
|
22
|
+
}): Promise<void>;
|
|
23
|
+
/**
|
|
24
|
+
* List all flaky tests from recent runs
|
|
25
|
+
*/
|
|
26
|
+
export declare function flakinessListAction(options: {
|
|
27
|
+
runId?: string;
|
|
28
|
+
threshold?: number;
|
|
29
|
+
json?: boolean;
|
|
30
|
+
workingDir?: string;
|
|
31
|
+
}): Promise<void>;
|
|
32
|
+
/**
|
|
33
|
+
* List quarantined tests
|
|
34
|
+
*/
|
|
35
|
+
export declare function flakinessQuarantineListAction(options: {
|
|
36
|
+
all?: boolean;
|
|
37
|
+
json?: boolean;
|
|
38
|
+
workingDir?: string;
|
|
39
|
+
}): Promise<void>;
|
|
40
|
+
/**
|
|
41
|
+
* Add a test to quarantine
|
|
42
|
+
*/
|
|
43
|
+
export declare function flakinessQuarantineAddAction(testId: string, options: {
|
|
44
|
+
reason?: string;
|
|
45
|
+
score?: number;
|
|
46
|
+
category?: string;
|
|
47
|
+
workingDir?: string;
|
|
48
|
+
}): Promise<void>;
|
|
49
|
+
/**
|
|
50
|
+
* Remove a test from quarantine
|
|
51
|
+
*/
|
|
52
|
+
export declare function flakinessQuarantineRemoveAction(testId: string, options: {
|
|
53
|
+
notes?: string;
|
|
54
|
+
workingDir?: string;
|
|
55
|
+
}): Promise<void>;
|
|
56
|
+
/**
|
|
57
|
+
* Show patterns for a test
|
|
58
|
+
*/
|
|
59
|
+
export declare function flakinessPatternsAction(testId: string, options: {
|
|
60
|
+
json?: boolean;
|
|
61
|
+
workingDir?: string;
|
|
62
|
+
}): Promise<void>;
|
|
63
|
+
/**
|
|
64
|
+
* Analyze flakiness for a specific run
|
|
65
|
+
*/
|
|
66
|
+
export declare function flakinessAnalyzeAction(runId: string, options: {
|
|
67
|
+
json?: boolean;
|
|
68
|
+
workingDir?: string;
|
|
69
|
+
}): Promise<void>;
|
|
70
|
+
/**
|
|
71
|
+
* Create all flakiness commands
|
|
72
|
+
*/
|
|
73
|
+
export declare function createFlakinessCommands(): Command;
|
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Flakiness CLI Commands
|
|
3
|
+
*
|
|
4
|
+
* Provides commands for managing test flakiness detection and quarantine.
|
|
5
|
+
*/
|
|
6
|
+
import { Command } from 'commander';
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
import Table from 'cli-table3';
|
|
9
|
+
import { join } from 'path';
|
|
10
|
+
import { existsSync } from 'fs';
|
|
11
|
+
import { EvidenceVault, FLAKINESS_CATEGORIES } from '../core/index.js';
|
|
12
|
+
/**
|
|
13
|
+
* Get the vault directory path
|
|
14
|
+
*/
|
|
15
|
+
function getVaultPath(workingDir = process.cwd()) {
|
|
16
|
+
return join(workingDir, '.qa360');
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Open the vault or exit with error
|
|
20
|
+
*/
|
|
21
|
+
async function openVault(workingDir) {
|
|
22
|
+
const vaultPath = getVaultPath(workingDir);
|
|
23
|
+
const vaultDbPath = join(vaultPath, 'vault.db');
|
|
24
|
+
if (!existsSync(vaultDbPath)) {
|
|
25
|
+
console.error(chalk.red(`❌ Vault not found at ${vaultDbPath}`));
|
|
26
|
+
console.error(chalk.yellow('💡 Run "qa360 run" first to create a vault'));
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
return await EvidenceVault.open(vaultPath);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Format flakiness score with emoji
|
|
33
|
+
*/
|
|
34
|
+
function formatScore(score, category) {
|
|
35
|
+
const meta = FLAKINESS_CATEGORIES[category];
|
|
36
|
+
const color = score >= 75 ? 'green' : score >= 50 ? 'yellow' : 'red';
|
|
37
|
+
return chalk[color](`${meta.emoji} ${score}%`);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Format category with color
|
|
41
|
+
*/
|
|
42
|
+
function formatCategory(category) {
|
|
43
|
+
const meta = FLAKINESS_CATEGORIES[category];
|
|
44
|
+
const color = category === 'legendary' || category === 'solid' ? 'green' :
|
|
45
|
+
category === 'good' ? 'yellow' :
|
|
46
|
+
category === 'shaky' ? 'yellow' : 'red';
|
|
47
|
+
return chalk[color](meta.label);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* List flakiness history for a test
|
|
51
|
+
*/
|
|
52
|
+
export async function flakinessHistoryAction(testId, options) {
|
|
53
|
+
const vault = await openVault(options.workingDir);
|
|
54
|
+
const history = await vault.getFlakinessHistory(testId, options.limit || 50);
|
|
55
|
+
if (options.json) {
|
|
56
|
+
console.log(JSON.stringify(history, null, 2));
|
|
57
|
+
await vault.close();
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (history.length === 0) {
|
|
61
|
+
console.log(chalk.yellow(`\n⚠️ No flakiness history found for test: ${testId}`));
|
|
62
|
+
await vault.close();
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
console.log(chalk.blue(`\n📊 Flakiness History: ${history[0].test_name}`));
|
|
66
|
+
console.log(chalk.gray(`Test ID: ${testId}`));
|
|
67
|
+
console.log(chalk.gray(`File: ${history[0].file_path}\n`));
|
|
68
|
+
const table = new Table({
|
|
69
|
+
head: [chalk.gray('Score'), chalk.gray('Category'), chalk.gray('Runs'), chalk.gray('Duration'), chalk.gray('Date')],
|
|
70
|
+
colWidths: [12, 15, 10, 12, 20],
|
|
71
|
+
style: { head: [], border: ['gray'] }
|
|
72
|
+
});
|
|
73
|
+
for (const record of history) {
|
|
74
|
+
const date = new Date(record.created_at).toLocaleDateString();
|
|
75
|
+
table.push([
|
|
76
|
+
formatScore(record.score, record.category),
|
|
77
|
+
formatCategory(record.category),
|
|
78
|
+
`${record.successful_runs}/${record.total_runs}`,
|
|
79
|
+
`${record.avg_duration_ms}ms`,
|
|
80
|
+
date
|
|
81
|
+
]);
|
|
82
|
+
}
|
|
83
|
+
console.log(table.toString());
|
|
84
|
+
await vault.close();
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Show flakiness trends for a test
|
|
88
|
+
*/
|
|
89
|
+
export async function flakinessTrendsAction(testId, options) {
|
|
90
|
+
const vault = await openVault(options.workingDir);
|
|
91
|
+
const trends = await vault.getFlakinessTrends(testId, options.days || 30);
|
|
92
|
+
if (options.json) {
|
|
93
|
+
console.log(JSON.stringify(trends, null, 2));
|
|
94
|
+
await vault.close();
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
// Get test name from history
|
|
98
|
+
const history = await vault.getFlakinessHistory(testId, 1);
|
|
99
|
+
const testName = history[0]?.test_name || testId;
|
|
100
|
+
console.log(chalk.blue(`\n📈 Flakiness Trends: ${testName}`));
|
|
101
|
+
console.log(chalk.gray(`Test ID: ${testId}`));
|
|
102
|
+
console.log(chalk.gray(`Time Window: Last ${options.days || 30} days\n`));
|
|
103
|
+
const trendEmoji = trends.trend === 'improving' ? '📈' : trends.trend === 'degrading' ? '📉' : '➡️';
|
|
104
|
+
const trendColor = trends.trend === 'improving' ? 'green' : trends.trend === 'degrading' ? 'red' : 'gray';
|
|
105
|
+
const trendText = trends.trend === 'improving' ? 'Improving' : trends.trend === 'degrading' ? 'Degrading' : 'Stable';
|
|
106
|
+
const currentCategory = trends.currentScore >= 75 ? 'good' : trends.currentScore >= 50 ? 'shaky' : 'unstable';
|
|
107
|
+
console.log(`${chalk.gray('Current Score:')} ${formatScore(trends.currentScore, currentCategory)}`);
|
|
108
|
+
console.log(`${chalk.gray('Average Score:')} ${formatScore(Math.round(trends.averageScore), currentCategory)}`);
|
|
109
|
+
console.log(`${chalk.gray('Trend:')} ${chalk[trendColor](`${trendEmoji} ${trendText}`)}`);
|
|
110
|
+
console.log(`${chalk.gray('Data Points:')} ${trends.dataPoints.length}\n`);
|
|
111
|
+
await vault.close();
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* List all flaky tests from recent runs
|
|
115
|
+
*/
|
|
116
|
+
export async function flakinessListAction(options) {
|
|
117
|
+
const vault = await openVault(options.workingDir);
|
|
118
|
+
let flakinessRecords = [];
|
|
119
|
+
if (options.runId) {
|
|
120
|
+
flakinessRecords = await vault.getRunFlakiness(options.runId);
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
// Get recent runs and their flakiness
|
|
124
|
+
const runs = await vault.listRuns({ limit: 10 });
|
|
125
|
+
for (const run of runs) {
|
|
126
|
+
const runFlakiness = await vault.getRunFlakiness(run.id);
|
|
127
|
+
flakinessRecords.push(...runFlakiness);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
// Filter by threshold if specified
|
|
131
|
+
if (options.threshold !== undefined) {
|
|
132
|
+
flakinessRecords = flakinessRecords.filter(r => r.score < options.threshold);
|
|
133
|
+
}
|
|
134
|
+
// Sort by score (most flaky first)
|
|
135
|
+
flakinessRecords.sort((a, b) => a.score - b.score);
|
|
136
|
+
if (options.json) {
|
|
137
|
+
console.log(JSON.stringify(flakinessRecords, null, 2));
|
|
138
|
+
await vault.close();
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
if (flakinessRecords.length === 0) {
|
|
142
|
+
console.log(chalk.yellow('\n✅ No flaky tests found!'));
|
|
143
|
+
await vault.close();
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
console.log(chalk.blue(`\n🎲 Flakiness Report (${flakinessRecords.length} tests)\n`));
|
|
147
|
+
const table = new Table({
|
|
148
|
+
head: [chalk.gray('Test'), chalk.gray('Gate'), chalk.gray('Score'), chalk.gray('Category'), chalk.gray('Runs'), chalk.gray('Pattern')],
|
|
149
|
+
colWidths: [30, 12, 10, 15, 8, 20],
|
|
150
|
+
wordWrap: true,
|
|
151
|
+
style: { head: [], border: ['gray'] }
|
|
152
|
+
});
|
|
153
|
+
for (const record of flakinessRecords) {
|
|
154
|
+
table.push([
|
|
155
|
+
record.test_name,
|
|
156
|
+
record.gate,
|
|
157
|
+
formatScore(record.score, record.category),
|
|
158
|
+
formatCategory(record.category),
|
|
159
|
+
`${record.successful_runs}/${record.total_runs}`,
|
|
160
|
+
record.pattern_type || '-'
|
|
161
|
+
]);
|
|
162
|
+
}
|
|
163
|
+
console.log(table.toString());
|
|
164
|
+
await vault.close();
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* List quarantined tests
|
|
168
|
+
*/
|
|
169
|
+
export async function flakinessQuarantineListAction(options) {
|
|
170
|
+
const vault = await openVault(options.workingDir);
|
|
171
|
+
const quarantined = await vault.getQuarantinedTests(options.all !== false);
|
|
172
|
+
if (options.json) {
|
|
173
|
+
console.log(JSON.stringify(quarantined, null, 2));
|
|
174
|
+
await vault.close();
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
if (quarantined.length === 0) {
|
|
178
|
+
console.log(chalk.yellow('\n✅ No tests in quarantine!'));
|
|
179
|
+
await vault.close();
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
console.log(chalk.blue(`\n🚫 Quarantined Tests (${quarantined.length} total)\n`));
|
|
183
|
+
const table = new Table({
|
|
184
|
+
head: [chalk.gray('Test'), chalk.gray('Gate'), chalk.gray('Category'), chalk.gray('Score'), chalk.gray('Reason'), chalk.gray('Status')],
|
|
185
|
+
colWidths: [25, 10, 12, 8, 30, 12],
|
|
186
|
+
wordWrap: true,
|
|
187
|
+
style: { head: [], border: ['gray'] }
|
|
188
|
+
});
|
|
189
|
+
for (const test of quarantined) {
|
|
190
|
+
const status = test.resolved_at ?
|
|
191
|
+
chalk.green('✅ Resolved') :
|
|
192
|
+
chalk.red('🚫 Active');
|
|
193
|
+
table.push([
|
|
194
|
+
test.test_name,
|
|
195
|
+
test.gate,
|
|
196
|
+
formatCategory(test.category),
|
|
197
|
+
`${test.score}%`,
|
|
198
|
+
test.reason,
|
|
199
|
+
status
|
|
200
|
+
]);
|
|
201
|
+
}
|
|
202
|
+
console.log(table.toString());
|
|
203
|
+
await vault.close();
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Add a test to quarantine
|
|
207
|
+
*/
|
|
208
|
+
export async function flakinessQuarantineAddAction(testId, options) {
|
|
209
|
+
const vault = await openVault(options.workingDir);
|
|
210
|
+
// Check if already in quarantine
|
|
211
|
+
const existing = await vault.getQuarantine(testId);
|
|
212
|
+
const alreadyQuarantined = existing && !existing.resolved_at;
|
|
213
|
+
if (alreadyQuarantined) {
|
|
214
|
+
await vault.close();
|
|
215
|
+
console.log(chalk.yellow(`\n⚠️ Test "${existing.test_name}" is already in quarantine\n`));
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
// Try to get test info from history
|
|
219
|
+
const history = await vault.getFlakinessHistory(testId, 1);
|
|
220
|
+
const testName = history[0]?.test_name || testId;
|
|
221
|
+
const gate = history[0]?.gate || 'unknown';
|
|
222
|
+
const filePath = history[0]?.file_path || 'unknown';
|
|
223
|
+
const score = options.score || history[0]?.score || 0;
|
|
224
|
+
const category = (options.category || history[0]?.category || 'unstable');
|
|
225
|
+
const reason = options.reason || `Manually quarantined by user`;
|
|
226
|
+
const quarantineId = await vault.addToQuarantine({
|
|
227
|
+
test_id: testId,
|
|
228
|
+
test_name: testName,
|
|
229
|
+
gate,
|
|
230
|
+
file_path: filePath,
|
|
231
|
+
reason,
|
|
232
|
+
score,
|
|
233
|
+
category,
|
|
234
|
+
quarantined_at: Date.now(),
|
|
235
|
+
quarantined_by: 'user'
|
|
236
|
+
});
|
|
237
|
+
console.log(chalk.green(`\n✅ Test "${testName}" added to quarantine (ID: ${quarantineId})`));
|
|
238
|
+
console.log(chalk.gray(` Reason: ${reason}`));
|
|
239
|
+
console.log(chalk.gray(` Category: ${formatCategory(category)} (${score}%)\n`));
|
|
240
|
+
await vault.close();
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Remove a test from quarantine
|
|
244
|
+
*/
|
|
245
|
+
export async function flakinessQuarantineRemoveAction(testId, options) {
|
|
246
|
+
const vault = await openVault(options.workingDir);
|
|
247
|
+
const quarantine = await vault.getQuarantine(testId);
|
|
248
|
+
if (!quarantine) {
|
|
249
|
+
console.log(chalk.yellow(`\n⚠️ Test "${testId}" is not in quarantine\n`));
|
|
250
|
+
await vault.close();
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
if (quarantine.resolved_at) {
|
|
254
|
+
console.log(chalk.yellow(`\n⚠️ Test "${testId}" was already resolved on ${new Date(quarantine.resolved_at).toLocaleString()}\n`));
|
|
255
|
+
await vault.close();
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
const notes = options.notes || 'Resolved by user';
|
|
259
|
+
await vault.removeFromQuarantine(testId, 'user', notes);
|
|
260
|
+
console.log(chalk.green(`\n✅ Test "${quarantine.test_name}" removed from quarantine`));
|
|
261
|
+
console.log(chalk.gray(` Notes: ${notes}\n`));
|
|
262
|
+
await vault.close();
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Show patterns for a test
|
|
266
|
+
*/
|
|
267
|
+
export async function flakinessPatternsAction(testId, options) {
|
|
268
|
+
const vault = await openVault(options.workingDir);
|
|
269
|
+
// Get all pattern types and check each
|
|
270
|
+
const patternTypes = ['timing', 'race_condition', 'external_dependency', 'environment_specific', 'selector_issue', 'unknown'];
|
|
271
|
+
const patterns = [];
|
|
272
|
+
for (const type of patternTypes) {
|
|
273
|
+
const pattern = await vault.getFlakinessPattern(testId, type);
|
|
274
|
+
if (pattern) {
|
|
275
|
+
patterns.push(pattern);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
if (options.json) {
|
|
279
|
+
console.log(JSON.stringify(patterns, null, 2));
|
|
280
|
+
await vault.close();
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
if (patterns.length === 0) {
|
|
284
|
+
console.log(chalk.yellow(`\n⚠️ No patterns detected for test: ${testId}\n`));
|
|
285
|
+
await vault.close();
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
console.log(chalk.blue(`\n🔍 Detected Patterns: ${patterns[0].test_name || testId}\n`));
|
|
289
|
+
const table = new Table({
|
|
290
|
+
head: [chalk.gray('Pattern'), chalk.gray('Description'), chalk.gray('Confidence'), chalk.gray('Fix'), chalk.gray('Detections')],
|
|
291
|
+
colWidths: [18, 30, 12, 30, 10],
|
|
292
|
+
wordWrap: true,
|
|
293
|
+
style: { head: [], border: ['gray'] }
|
|
294
|
+
});
|
|
295
|
+
for (const pattern of patterns) {
|
|
296
|
+
table.push([
|
|
297
|
+
chalk.cyan(pattern.pattern_type),
|
|
298
|
+
pattern.description,
|
|
299
|
+
`${Math.round(pattern.confidence * 100)}%`,
|
|
300
|
+
pattern.suggested_fix,
|
|
301
|
+
pattern.detection_count.toString()
|
|
302
|
+
]);
|
|
303
|
+
}
|
|
304
|
+
console.log(table.toString());
|
|
305
|
+
await vault.close();
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Analyze flakiness for a specific run
|
|
309
|
+
*/
|
|
310
|
+
export async function flakinessAnalyzeAction(runId, options) {
|
|
311
|
+
const vault = await openVault(options.workingDir);
|
|
312
|
+
const flakiness = await vault.getRunFlakiness(runId);
|
|
313
|
+
if (options.json) {
|
|
314
|
+
console.log(JSON.stringify(flakiness, null, 2));
|
|
315
|
+
await vault.close();
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
if (flakiness.length === 0) {
|
|
319
|
+
console.log(chalk.yellow(`\n⚠️ No flakiness data found for run: ${runId}\n`));
|
|
320
|
+
await vault.close();
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
// Calculate stats
|
|
324
|
+
const legendary = flakiness.filter((f) => f.category === 'legendary').length;
|
|
325
|
+
const solid = flakiness.filter((f) => f.category === 'solid').length;
|
|
326
|
+
const good = flakiness.filter((f) => f.category === 'good').length;
|
|
327
|
+
const shaky = flakiness.filter((f) => f.category === 'shaky').length;
|
|
328
|
+
const unstable = flakiness.filter((f) => f.category === 'unstable').length;
|
|
329
|
+
const avgScore = Math.round(flakiness.reduce((sum, f) => sum + f.score, 0) / flakiness.length);
|
|
330
|
+
console.log(chalk.blue(`\n📊 Flakiness Analysis for Run: ${runId}\n`));
|
|
331
|
+
console.log(chalk.gray(`Total Tests: ${flakiness.length}`));
|
|
332
|
+
console.log(chalk.gray(`Average Score: ${formatScore(avgScore, avgScore >= 75 ? 'good' : avgScore >= 50 ? 'shaky' : 'unstable')}\n`));
|
|
333
|
+
console.log(chalk.gray('Distribution:'));
|
|
334
|
+
console.log(` ${chalk.green('🟢')} Legendary: ${legendary}`);
|
|
335
|
+
console.log(` ${chalk.green('🟢')} Solid: ${solid}`);
|
|
336
|
+
console.log(` ${chalk.yellow('🟡')} Good: ${good}`);
|
|
337
|
+
console.log(` ${chalk.yellow('🟠')} Shaky: ${shaky}`);
|
|
338
|
+
console.log(` ${chalk.red('🔴')} Unstable: ${unstable}\n`);
|
|
339
|
+
// Show tests needing attention
|
|
340
|
+
const needsAttention = flakiness.filter((f) => f.score < 75);
|
|
341
|
+
if (needsAttention.length > 0) {
|
|
342
|
+
console.log(chalk.yellow(`⚠️ ${needsAttention.length} test(s) need attention:\n`));
|
|
343
|
+
const table = new Table({
|
|
344
|
+
head: [chalk.gray('Test'), chalk.gray('Score'), chalk.gray('Pattern'), chalk.gray('Suggested Fix')],
|
|
345
|
+
colWidths: [25, 10, 15, 40],
|
|
346
|
+
wordWrap: true,
|
|
347
|
+
style: { head: [], border: ['gray'] }
|
|
348
|
+
});
|
|
349
|
+
for (const test of needsAttention.slice(0, 10)) {
|
|
350
|
+
table.push([
|
|
351
|
+
test.test_name,
|
|
352
|
+
formatScore(test.score, test.category),
|
|
353
|
+
test.pattern_type || '-',
|
|
354
|
+
test.suggested_fix || '-'
|
|
355
|
+
]);
|
|
356
|
+
}
|
|
357
|
+
console.log(table.toString());
|
|
358
|
+
if (needsAttention.length > 10) {
|
|
359
|
+
console.log(chalk.gray(`\n... and ${needsAttention.length - 10} more`));
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
await vault.close();
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Create all flakiness commands
|
|
366
|
+
*/
|
|
367
|
+
export function createFlakinessCommands() {
|
|
368
|
+
const flakinessCommand = new Command('flakiness')
|
|
369
|
+
.description('Test flakiness detection and quarantine management');
|
|
370
|
+
// History command
|
|
371
|
+
flakinessCommand
|
|
372
|
+
.command('history <testId>')
|
|
373
|
+
.description('Show flakiness history for a test')
|
|
374
|
+
.option('-l, --limit <number>', 'Number of records to show', '50')
|
|
375
|
+
.option('--json', 'Output as JSON')
|
|
376
|
+
.option('-w, --working-dir <path>', 'Working directory (default: current directory)')
|
|
377
|
+
.action(flakinessHistoryAction);
|
|
378
|
+
// Trends command
|
|
379
|
+
flakinessCommand
|
|
380
|
+
.command('trends <testId>')
|
|
381
|
+
.description('Show flakiness trends over time')
|
|
382
|
+
.option('-d, --days <number>', 'Time window in days', '30')
|
|
383
|
+
.option('--json', 'Output as JSON')
|
|
384
|
+
.option('-w, --working-dir <path>', 'Working directory')
|
|
385
|
+
.action(flakinessTrendsAction);
|
|
386
|
+
// List command
|
|
387
|
+
flakinessCommand
|
|
388
|
+
.command('list')
|
|
389
|
+
.description('List all flaky tests from recent runs')
|
|
390
|
+
.option('-r, --run-id <id>', 'Specific run ID')
|
|
391
|
+
.option('-t, --threshold <number>', 'Only show tests below this score')
|
|
392
|
+
.option('--json', 'Output as JSON')
|
|
393
|
+
.option('-w, --working-dir <path>', 'Working directory')
|
|
394
|
+
.action(flakinessListAction);
|
|
395
|
+
// Analyze command
|
|
396
|
+
flakinessCommand
|
|
397
|
+
.command('analyze <runId>')
|
|
398
|
+
.description('Analyze flakiness for a specific run')
|
|
399
|
+
.option('--json', 'Output as JSON')
|
|
400
|
+
.option('-w, --working-dir <path>', 'Working directory')
|
|
401
|
+
.action(flakinessAnalyzeAction);
|
|
402
|
+
// Patterns command
|
|
403
|
+
flakinessCommand
|
|
404
|
+
.command('patterns <testId>')
|
|
405
|
+
.description('Show detected patterns for a test')
|
|
406
|
+
.option('--json', 'Output as JSON')
|
|
407
|
+
.option('-w, --working-dir <path>', 'Working directory')
|
|
408
|
+
.action(flakinessPatternsAction);
|
|
409
|
+
// Quarantine subcommand
|
|
410
|
+
const quarantineCommand = flakinessCommand
|
|
411
|
+
.command('quarantine')
|
|
412
|
+
.description('Manage quarantined tests');
|
|
413
|
+
quarantineCommand
|
|
414
|
+
.command('list')
|
|
415
|
+
.description('List quarantined tests')
|
|
416
|
+
.option('-a, --all', 'Include resolved quarantines', false)
|
|
417
|
+
.option('--json', 'Output as JSON')
|
|
418
|
+
.option('-w, --working-dir <path>', 'Working directory')
|
|
419
|
+
.action(flakinessQuarantineListAction);
|
|
420
|
+
quarantineCommand
|
|
421
|
+
.command('add <testId>')
|
|
422
|
+
.description('Add a test to quarantine')
|
|
423
|
+
.option('-r, --reason <text>', 'Reason for quarantine')
|
|
424
|
+
.option('-s, --score <number>', 'Flakiness score (0-100)')
|
|
425
|
+
.option('-c, --category <category>', 'Category: shaky, unstable')
|
|
426
|
+
.option('-w, --working-dir <path>', 'Working directory')
|
|
427
|
+
.action(flakinessQuarantineAddAction);
|
|
428
|
+
quarantineCommand
|
|
429
|
+
.command('remove <testId>')
|
|
430
|
+
.description('Remove a test from quarantine')
|
|
431
|
+
.option('-n, --notes <text>', 'Resolution notes')
|
|
432
|
+
.option('-w, --working-dir <path>', 'Working directory')
|
|
433
|
+
.action(flakinessQuarantineRemoveAction);
|
|
434
|
+
return flakinessCommand;
|
|
435
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QA360 Generate Command
|
|
3
|
+
*
|
|
4
|
+
* AI-powered test generation using Ollama.
|
|
5
|
+
* Generates tests from OpenAPI specs, HAR files, URLs, or code.
|
|
6
|
+
*
|
|
7
|
+
* Phase 4 - AI Test Generation
|
|
8
|
+
*/
|
|
9
|
+
import { Command } from 'commander';
|
|
10
|
+
/**
|
|
11
|
+
* Create generate commands group
|
|
12
|
+
*/
|
|
13
|
+
export declare function createGenerateCommands(): Command;
|
|
14
|
+
/**
|
|
15
|
+
* Generate tests command handler
|
|
16
|
+
*/
|
|
17
|
+
export declare function generateCommand(source: string, options?: {
|
|
18
|
+
type?: 'openapi' | 'har' | 'url' | 'code';
|
|
19
|
+
output?: string;
|
|
20
|
+
framework?: 'playwright' | 'k6' | 'vitest' | 'jest';
|
|
21
|
+
optimize?: boolean;
|
|
22
|
+
model?: string;
|
|
23
|
+
format?: boolean;
|
|
24
|
+
}): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Generate API tests from OpenAPI spec
|
|
27
|
+
*/
|
|
28
|
+
export declare function generateApiCommand(specPath: string, options?: {
|
|
29
|
+
output?: string;
|
|
30
|
+
model?: string;
|
|
31
|
+
optimize?: boolean;
|
|
32
|
+
}): Promise<void>;
|
|
33
|
+
/**
|
|
34
|
+
* Generate UI tests from URL
|
|
35
|
+
*/
|
|
36
|
+
export declare function generateUiCommand(url: string, options?: {
|
|
37
|
+
output?: string;
|
|
38
|
+
model?: string;
|
|
39
|
+
optimize?: boolean;
|
|
40
|
+
}): Promise<void>;
|
|
41
|
+
/**
|
|
42
|
+
* Generate performance tests
|
|
43
|
+
*/
|
|
44
|
+
export declare function generatePerfCommand(specPath: string, options?: {
|
|
45
|
+
output?: string;
|
|
46
|
+
model?: string;
|
|
47
|
+
duration?: string;
|
|
48
|
+
vus?: number;
|
|
49
|
+
}): Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* Generate unit tests from code
|
|
52
|
+
*/
|
|
53
|
+
export declare function generateUnitCommand(filePath: string, options?: {
|
|
54
|
+
language?: string;
|
|
55
|
+
output?: string;
|
|
56
|
+
model?: string;
|
|
57
|
+
optimize?: boolean;
|
|
58
|
+
}): Promise<void>;
|
|
59
|
+
/**
|
|
60
|
+
* Build system prompt for test type
|
|
61
|
+
*/
|
|
62
|
+
export declare function buildSystemPrompt(type: string): string;
|
|
63
|
+
/**
|
|
64
|
+
* Check if generation is available
|
|
65
|
+
*/
|
|
66
|
+
export declare function checkCommand(): Promise<void>;
|