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,384 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QA360 Flakiness Detection Engine
|
|
3
|
+
*
|
|
4
|
+
* Detects and scores test flakiness using consecutive runs and pattern analysis.
|
|
5
|
+
* This is the core differentiator of QA360 - measuring test reliability.
|
|
6
|
+
*
|
|
7
|
+
* Scoring Categories:
|
|
8
|
+
* - Legendary (100): Never flaky - 100% reliable
|
|
9
|
+
* - Solid (90-99): Rarely flaky
|
|
10
|
+
* - Good (75-89): Occasionally flaky
|
|
11
|
+
* - Shaky (50-74): Often flaky - attention needed
|
|
12
|
+
* - Unstable (0-49): Very flaky - quarantine recommended
|
|
13
|
+
*
|
|
14
|
+
* @module flakiness
|
|
15
|
+
*/
|
|
16
|
+
import { createHash } from 'crypto';
|
|
17
|
+
/**
|
|
18
|
+
* Flakiness score category
|
|
19
|
+
*/
|
|
20
|
+
export var FlakinessCategory;
|
|
21
|
+
(function (FlakinessCategory) {
|
|
22
|
+
FlakinessCategory["LEGENDARY"] = "legendary";
|
|
23
|
+
FlakinessCategory["SOLID"] = "solid";
|
|
24
|
+
FlakinessCategory["GOOD"] = "good";
|
|
25
|
+
FlakinessCategory["SHAKY"] = "shaky";
|
|
26
|
+
FlakinessCategory["UNSTABLE"] = "unstable"; // 0-49
|
|
27
|
+
})(FlakinessCategory || (FlakinessCategory = {}));
|
|
28
|
+
/**
|
|
29
|
+
* Flakiness category metadata
|
|
30
|
+
*/
|
|
31
|
+
export const FLAKINESS_CATEGORIES = {
|
|
32
|
+
[FlakinessCategory.LEGENDARY]: {
|
|
33
|
+
min: 100,
|
|
34
|
+
max: 100,
|
|
35
|
+
label: 'LEGENDARY',
|
|
36
|
+
emoji: '🟢',
|
|
37
|
+
color: '#10B981',
|
|
38
|
+
description: 'Jamais flaky - 100% fiable',
|
|
39
|
+
action: 'None - keep doing what you\'re doing'
|
|
40
|
+
},
|
|
41
|
+
[FlakinessCategory.SOLID]: {
|
|
42
|
+
min: 90,
|
|
43
|
+
max: 99,
|
|
44
|
+
label: 'SOLID',
|
|
45
|
+
emoji: '🟢',
|
|
46
|
+
color: '#34D399',
|
|
47
|
+
description: 'Rarement flaky',
|
|
48
|
+
action: 'Monitor - occasional hiccups'
|
|
49
|
+
},
|
|
50
|
+
[FlakinessCategory.GOOD]: {
|
|
51
|
+
min: 75,
|
|
52
|
+
max: 89,
|
|
53
|
+
label: 'GOOD',
|
|
54
|
+
emoji: '🟡',
|
|
55
|
+
color: '#FBBF24',
|
|
56
|
+
description: 'Occasionnellement flaky',
|
|
57
|
+
action: 'Watch - may need attention'
|
|
58
|
+
},
|
|
59
|
+
[FlakinessCategory.SHAKY]: {
|
|
60
|
+
min: 50,
|
|
61
|
+
max: 74,
|
|
62
|
+
label: 'SHAKY',
|
|
63
|
+
emoji: '🟠',
|
|
64
|
+
color: '#F97316',
|
|
65
|
+
description: 'Souvent flaky - attention requise',
|
|
66
|
+
action: 'Investigate - fix recommended'
|
|
67
|
+
},
|
|
68
|
+
[FlakinessCategory.UNSTABLE]: {
|
|
69
|
+
min: 0,
|
|
70
|
+
max: 49,
|
|
71
|
+
label: 'UNSTABLE',
|
|
72
|
+
emoji: '🔴',
|
|
73
|
+
color: '#EF4444',
|
|
74
|
+
description: 'Très flaky - quarantaine recommandée',
|
|
75
|
+
action: 'Quarantine - unblock the team'
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* Default flakiness detection options
|
|
80
|
+
*/
|
|
81
|
+
export const DEFAULT_FLAKINESS_OPTIONS = {
|
|
82
|
+
minRuns: 3,
|
|
83
|
+
consecutiveRuns: 3,
|
|
84
|
+
timeWindowDays: 30,
|
|
85
|
+
enablePatternDetection: true
|
|
86
|
+
};
|
|
87
|
+
/**
|
|
88
|
+
* Flakiness pattern types
|
|
89
|
+
*/
|
|
90
|
+
export var FlakinessPattern;
|
|
91
|
+
(function (FlakinessPattern) {
|
|
92
|
+
FlakinessPattern["TIMING"] = "timing";
|
|
93
|
+
FlakinessPattern["RACE_CONDITION"] = "race_condition";
|
|
94
|
+
FlakinessPattern["EXTERNAL_DEPENDENCY"] = "external_dependency";
|
|
95
|
+
FlakinessPattern["ENVIRONMENT_SPECIFIC"] = "environment_specific";
|
|
96
|
+
FlakinessPattern["SELECTOR_ISSUE"] = "selector_issue";
|
|
97
|
+
FlakinessPattern["UNKNOWN"] = "unknown";
|
|
98
|
+
})(FlakinessPattern || (FlakinessPattern = {}));
|
|
99
|
+
/**
|
|
100
|
+
* Calculate flakiness score from test results
|
|
101
|
+
* @param results Array of test results
|
|
102
|
+
* @returns Flakiness score (0-100)
|
|
103
|
+
*/
|
|
104
|
+
export function calculateFlakinessScore(results) {
|
|
105
|
+
if (results.length === 0) {
|
|
106
|
+
return 100; // No failures means 100% by default
|
|
107
|
+
}
|
|
108
|
+
const successCount = results.filter(r => r.success).length;
|
|
109
|
+
const score = Math.round((successCount / results.length) * 100);
|
|
110
|
+
return Math.max(0, Math.min(100, score));
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Get flakiness category from score
|
|
114
|
+
* @param score Flakiness score (0-100)
|
|
115
|
+
* @returns Flakiness category
|
|
116
|
+
*/
|
|
117
|
+
export function getFlakinessCategory(score) {
|
|
118
|
+
if (score === 100)
|
|
119
|
+
return FlakinessCategory.LEGENDARY;
|
|
120
|
+
if (score >= 90)
|
|
121
|
+
return FlakinessCategory.SOLID;
|
|
122
|
+
if (score >= 75)
|
|
123
|
+
return FlakinessCategory.GOOD;
|
|
124
|
+
if (score >= 50)
|
|
125
|
+
return FlakinessCategory.SHAKY;
|
|
126
|
+
return FlakinessCategory.UNSTABLE;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Generate a unique test ID
|
|
130
|
+
* @param testName Test name
|
|
131
|
+
* @param filePath File path
|
|
132
|
+
* @returns Unique test ID
|
|
133
|
+
*/
|
|
134
|
+
export function generateTestId(testName, filePath) {
|
|
135
|
+
const hash = createHash('sha256')
|
|
136
|
+
.update(`${filePath}:${testName}`)
|
|
137
|
+
.digest('hex')
|
|
138
|
+
.substring(0, 16);
|
|
139
|
+
return hash;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Analyze test results for flakiness patterns
|
|
143
|
+
* @param results Array of test results for the same test
|
|
144
|
+
* @returns Pattern detection result or undefined
|
|
145
|
+
*/
|
|
146
|
+
export function detectFlakinessPattern(results) {
|
|
147
|
+
if (results.length < 3) {
|
|
148
|
+
return undefined;
|
|
149
|
+
}
|
|
150
|
+
const failures = results.filter(r => !r.success);
|
|
151
|
+
if (failures.length === 0) {
|
|
152
|
+
return undefined; // No failures to analyze
|
|
153
|
+
}
|
|
154
|
+
// Collect error messages as lowercase strings
|
|
155
|
+
const errorMessages = failures.map(f => (f.errorMessage || '').toLowerCase());
|
|
156
|
+
// Helper to check if any error contains a keyword
|
|
157
|
+
const anyErrorContains = (keywords) => {
|
|
158
|
+
return errorMessages.some(err => keywords.some(kw => err.includes(kw)));
|
|
159
|
+
};
|
|
160
|
+
// Check for timing issues
|
|
161
|
+
if (anyErrorContains(['timeout', 'timed out', 'waiting for', 'element not found', 'waiting for selector'])) {
|
|
162
|
+
return {
|
|
163
|
+
patternType: FlakinessPattern.TIMING,
|
|
164
|
+
description: 'Timing issues - test is too fast for the application',
|
|
165
|
+
suggestedFix: 'Increase timeout or add explicit wait (waitForSelector, waitForLoadState)',
|
|
166
|
+
confidence: 0.85,
|
|
167
|
+
autoFixPossible: true
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
// Check for selector issues
|
|
171
|
+
if (anyErrorContains(['selector', 'detached', 'stale element', 'element not found'])) {
|
|
172
|
+
return {
|
|
173
|
+
patternType: FlakinessPattern.SELECTOR_ISSUE,
|
|
174
|
+
description: 'Selector issues - element not consistently found',
|
|
175
|
+
suggestedFix: 'Use data-testid selectors instead of CSS selectors for better reliability',
|
|
176
|
+
confidence: 0.80,
|
|
177
|
+
autoFixPossible: true
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
// Check for external dependency issues
|
|
181
|
+
if (anyErrorContains(['econnrefused', 'connection refused', 'connection reset', 'network error', 'network unreachable', 'api timeout'])) {
|
|
182
|
+
return {
|
|
183
|
+
patternType: FlakinessPattern.EXTERNAL_DEPENDENCY,
|
|
184
|
+
description: 'External dependency issues - network or service unavailable',
|
|
185
|
+
suggestedFix: 'Add retry logic with exponential backoff or mock the external dependency',
|
|
186
|
+
confidence: 0.90,
|
|
187
|
+
autoFixPossible: true
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
// Check for race conditions
|
|
191
|
+
if (anyErrorContains(['race condition', 'state changed', 'unexpected state', 'assertion failed'])) {
|
|
192
|
+
return {
|
|
193
|
+
patternType: FlakinessPattern.RACE_CONDITION,
|
|
194
|
+
description: 'Possible race condition - state changes unexpectedly',
|
|
195
|
+
suggestedFix: 'Add proper synchronization/locks or use explicit waits',
|
|
196
|
+
confidence: 0.70,
|
|
197
|
+
autoFixPossible: false
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
// Check for environment-specific issues
|
|
201
|
+
const envs = new Set(results.map(r => r.environment));
|
|
202
|
+
if (envs.size > 1) {
|
|
203
|
+
const failEnvs = new Set(failures.map(r => r.environment));
|
|
204
|
+
const passEnvs = new Set(results.filter(r => r.success).map(r => r.environment));
|
|
205
|
+
// Check if failures are concentrated in one environment
|
|
206
|
+
for (const env of failEnvs) {
|
|
207
|
+
if (!passEnvs.has(env)) {
|
|
208
|
+
return {
|
|
209
|
+
patternType: FlakinessPattern.ENVIRONMENT_SPECIFIC,
|
|
210
|
+
description: `Environment-specific issue - fails on ${env} only`,
|
|
211
|
+
suggestedFix: 'Check environment-specific configuration (OS, timezone, locale)',
|
|
212
|
+
confidence: 0.75,
|
|
213
|
+
autoFixPossible: false
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return {
|
|
219
|
+
patternType: FlakinessPattern.UNKNOWN,
|
|
220
|
+
description: 'Flaky behavior detected but pattern unclear',
|
|
221
|
+
suggestedFix: 'Manual investigation recommended - check logs for specific failure patterns',
|
|
222
|
+
confidence: 0.50,
|
|
223
|
+
autoFixPossible: false
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Calculate consecutive streak (passes or failures)
|
|
228
|
+
* @param results Array of test results sorted by timestamp
|
|
229
|
+
* @returns Current streak info
|
|
230
|
+
*/
|
|
231
|
+
export function calculateStreak(results) {
|
|
232
|
+
if (results.length === 0) {
|
|
233
|
+
return { currentStreak: 0, streakType: 'pass' };
|
|
234
|
+
}
|
|
235
|
+
// Sort by timestamp (newest first)
|
|
236
|
+
const sorted = [...results].sort((a, b) => b.timestamp - a.timestamp);
|
|
237
|
+
let streak = 0;
|
|
238
|
+
let streakType = sorted[0].success ? 'pass' : 'fail';
|
|
239
|
+
for (const result of sorted) {
|
|
240
|
+
if (result.success === (streakType === 'pass')) {
|
|
241
|
+
streak++;
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return { currentStreak: streak, streakType };
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Format flakiness score for display
|
|
251
|
+
* @param score Flakiness score (0-100)
|
|
252
|
+
* @returns Formatted string with emoji
|
|
253
|
+
*/
|
|
254
|
+
export function formatFlakinessScore(score) {
|
|
255
|
+
const category = getFlakinessCategory(score);
|
|
256
|
+
const meta = FLAKINESS_CATEGORIES[category];
|
|
257
|
+
return `${meta.emoji} ${category.toUpperCase()} (${score}%)`;
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Flakiness Detection Engine
|
|
261
|
+
*
|
|
262
|
+
* Main class for detecting and scoring test flakiness
|
|
263
|
+
*/
|
|
264
|
+
export class FlakinessDetector {
|
|
265
|
+
options;
|
|
266
|
+
constructor(options = {}) {
|
|
267
|
+
this.options = { ...DEFAULT_FLAKINESS_OPTIONS, ...options };
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Analyze a single test's flakiness from its run history
|
|
271
|
+
* @param testId Test identifier
|
|
272
|
+
* @param results Array of test results (must be from the same test)
|
|
273
|
+
* @returns Flakiness result
|
|
274
|
+
*/
|
|
275
|
+
analyzeTest(testId, results) {
|
|
276
|
+
// Filter by time window
|
|
277
|
+
const timeWindow = this.options.timeWindowDays * 24 * 60 * 60 * 1000;
|
|
278
|
+
const now = Date.now();
|
|
279
|
+
const cutoff = now - timeWindow;
|
|
280
|
+
const filteredResults = results.filter(r => r.timestamp >= cutoff);
|
|
281
|
+
// Need minimum runs to calculate score
|
|
282
|
+
if (filteredResults.length < this.options.minRuns) {
|
|
283
|
+
return {
|
|
284
|
+
testId,
|
|
285
|
+
testName: results[0]?.testName || 'Unknown',
|
|
286
|
+
filePath: results[0]?.filePath || 'Unknown',
|
|
287
|
+
gate: results[0]?.gate || 'Unknown',
|
|
288
|
+
score: 100, // Default to 100 if not enough data
|
|
289
|
+
category: FlakinessCategory.LEGENDARY,
|
|
290
|
+
totalRuns: filteredResults.length,
|
|
291
|
+
successfulRuns: filteredResults.filter(r => r.success).length,
|
|
292
|
+
avgDurationMs: this.averageDuration(filteredResults)
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
// Calculate score
|
|
296
|
+
const score = calculateFlakinessScore(filteredResults);
|
|
297
|
+
const category = getFlakinessCategory(score);
|
|
298
|
+
// Calculate streak
|
|
299
|
+
const { currentStreak, streakType } = calculateStreak(filteredResults);
|
|
300
|
+
// Detect patterns if enabled
|
|
301
|
+
let patternType;
|
|
302
|
+
let suggestedFix;
|
|
303
|
+
let confidence;
|
|
304
|
+
if (this.options.enablePatternDetection) {
|
|
305
|
+
const pattern = detectFlakinessPattern(filteredResults);
|
|
306
|
+
if (pattern) {
|
|
307
|
+
patternType = pattern.patternType;
|
|
308
|
+
suggestedFix = pattern.suggestedFix;
|
|
309
|
+
confidence = pattern.confidence;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
return {
|
|
313
|
+
testId,
|
|
314
|
+
testName: results[0].testName,
|
|
315
|
+
filePath: results[0].filePath,
|
|
316
|
+
gate: results[0].gate,
|
|
317
|
+
score,
|
|
318
|
+
category,
|
|
319
|
+
totalRuns: filteredResults.length,
|
|
320
|
+
successfulRuns: filteredResults.filter(r => r.success).length,
|
|
321
|
+
avgDurationMs: this.averageDuration(filteredResults),
|
|
322
|
+
patternType,
|
|
323
|
+
suggestedFix,
|
|
324
|
+
confidence,
|
|
325
|
+
firstSeen: Math.min(...filteredResults.map(r => r.timestamp)),
|
|
326
|
+
lastSeen: Math.max(...filteredResults.map(r => r.timestamp)),
|
|
327
|
+
currentStreak,
|
|
328
|
+
streakType
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Analyze multiple tests and return flakiness results
|
|
333
|
+
* @param testResults Map of testId to array of results
|
|
334
|
+
* @returns Array of flakiness results
|
|
335
|
+
*/
|
|
336
|
+
analyzeAll(testResults) {
|
|
337
|
+
const flakinessResults = [];
|
|
338
|
+
for (const [testId, testRuns] of testResults.entries()) {
|
|
339
|
+
if (testRuns.length > 0) {
|
|
340
|
+
flakinessResults.push(this.analyzeTest(testId, testRuns));
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
// Sort by score (lowest first) - most flaky first
|
|
344
|
+
flakinessResults.sort((a, b) => a.score - b.score);
|
|
345
|
+
return flakinessResults;
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Check if a test should be quarantined based on flakiness
|
|
349
|
+
* @param result Flakiness result
|
|
350
|
+
* @returns Whether to quarantine
|
|
351
|
+
*/
|
|
352
|
+
shouldQuarantine(result) {
|
|
353
|
+
// Quarantine if:
|
|
354
|
+
// 1. Score is below 50 (Unstable)
|
|
355
|
+
// 2. Score is below 75 (Shaky) AND has more than 5 runs
|
|
356
|
+
return (result.category === FlakinessCategory.UNSTABLE ||
|
|
357
|
+
(result.category === FlakinessCategory.SHAKY && result.totalRuns >= 5));
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Get quarantine recommendation message
|
|
361
|
+
* @param result Flakiness result
|
|
362
|
+
* @returns Recommendation message
|
|
363
|
+
*/
|
|
364
|
+
getQuarantineRecommendation(result) {
|
|
365
|
+
if (!this.shouldQuarantine(result)) {
|
|
366
|
+
return `Test is ${FLAKINESS_CATEGORIES[result.category].emoji} ${result.category} - no quarantine needed`;
|
|
367
|
+
}
|
|
368
|
+
return [
|
|
369
|
+
`❌ Test "${result.testName}" is ${FLAKINESS_CATEGORIES[result.category].emoji} ${FLAKINESS_CATEGORIES[result.category].label} (${result.score}% flaky)`,
|
|
370
|
+
` Passed ${result.successfulRuns}/${result.totalRuns} times`,
|
|
371
|
+
result.suggestedFix ? ` 💡 Suggested: ${result.suggestedFix}` : '',
|
|
372
|
+
` 📋 Action: ${FLAKINESS_CATEGORIES[result.category].action}`
|
|
373
|
+
].filter(Boolean).join('\n');
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Calculate average duration
|
|
377
|
+
*/
|
|
378
|
+
averageDuration(results) {
|
|
379
|
+
if (results.length === 0)
|
|
380
|
+
return 0;
|
|
381
|
+
const total = results.reduce((sum, r) => sum + r.durationMs, 0);
|
|
382
|
+
return Math.round(total / results.length);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QA360 Code Formatter
|
|
3
|
+
*
|
|
4
|
+
* Formats and validates generated test code.
|
|
5
|
+
* Supports TypeScript, JavaScript, Go, and Python.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Formatter configuration
|
|
9
|
+
*/
|
|
10
|
+
export interface FormatterConfig {
|
|
11
|
+
/**
|
|
12
|
+
* Indentation style
|
|
13
|
+
*/
|
|
14
|
+
indentStyle?: 'spaces' | 'tabs';
|
|
15
|
+
/**
|
|
16
|
+
* Indentation size
|
|
17
|
+
*/
|
|
18
|
+
indentSize?: number;
|
|
19
|
+
/**
|
|
20
|
+
* Line width
|
|
21
|
+
*/
|
|
22
|
+
lineWidth?: number;
|
|
23
|
+
/**
|
|
24
|
+
* Semicolons (for JS/TS)
|
|
25
|
+
*/
|
|
26
|
+
semicolons?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Quotes style
|
|
29
|
+
*/
|
|
30
|
+
quotes?: 'single' | 'double';
|
|
31
|
+
/**
|
|
32
|
+
* Trailing commas
|
|
33
|
+
*/
|
|
34
|
+
trailingCommas?: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Sort imports
|
|
37
|
+
*/
|
|
38
|
+
sortImports?: boolean;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Formatting result
|
|
42
|
+
*/
|
|
43
|
+
export interface FormatResult {
|
|
44
|
+
formatted: string;
|
|
45
|
+
errors: string[];
|
|
46
|
+
warnings: string[];
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Code Formatter class
|
|
50
|
+
*/
|
|
51
|
+
export declare class CodeFormatter {
|
|
52
|
+
private readonly config;
|
|
53
|
+
private readonly defaultConfig;
|
|
54
|
+
constructor(config?: FormatterConfig);
|
|
55
|
+
/**
|
|
56
|
+
* Format code based on language
|
|
57
|
+
*/
|
|
58
|
+
format(code: string, language: string): string;
|
|
59
|
+
/**
|
|
60
|
+
* Format TypeScript code
|
|
61
|
+
*/
|
|
62
|
+
formatTypeScript(code: string): string;
|
|
63
|
+
/**
|
|
64
|
+
* Format JavaScript code
|
|
65
|
+
*/
|
|
66
|
+
formatJavaScript(code: string): string;
|
|
67
|
+
/**
|
|
68
|
+
* Format Go code
|
|
69
|
+
*/
|
|
70
|
+
formatGo(code: string): string;
|
|
71
|
+
/**
|
|
72
|
+
* Format Python code
|
|
73
|
+
*/
|
|
74
|
+
formatPython(code: string): string;
|
|
75
|
+
/**
|
|
76
|
+
* Generic formatting
|
|
77
|
+
*/
|
|
78
|
+
formatGeneric(code: string): string;
|
|
79
|
+
/**
|
|
80
|
+
* Validate code syntax
|
|
81
|
+
*/
|
|
82
|
+
validate(code: string, language: string): FormatResult;
|
|
83
|
+
/**
|
|
84
|
+
* Sort imports
|
|
85
|
+
*/
|
|
86
|
+
private sortImports;
|
|
87
|
+
/**
|
|
88
|
+
* Sort import lines
|
|
89
|
+
*/
|
|
90
|
+
private sortImportLines;
|
|
91
|
+
/**
|
|
92
|
+
* Normalize quotes
|
|
93
|
+
*/
|
|
94
|
+
private normalizeQuotes;
|
|
95
|
+
/**
|
|
96
|
+
* Normalize braces spacing
|
|
97
|
+
*/
|
|
98
|
+
private normalizeBraces;
|
|
99
|
+
/**
|
|
100
|
+
* Fix common formatting issues
|
|
101
|
+
*/
|
|
102
|
+
private fixCommonIssues;
|
|
103
|
+
/**
|
|
104
|
+
* Normalize Python indentation
|
|
105
|
+
*/
|
|
106
|
+
private normalizePythonIndentation;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Create a formatter with default config
|
|
110
|
+
*/
|
|
111
|
+
export declare function createFormatter(config?: FormatterConfig): CodeFormatter;
|