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,296 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Smart Retry Engine
|
|
3
|
+
*
|
|
4
|
+
* Core retry logic with multiple strategies and adaptive behavior.
|
|
5
|
+
*/
|
|
6
|
+
import { DEFAULT_RETRY_CONFIG, RetryStrategy } from './types.js';
|
|
7
|
+
/**
|
|
8
|
+
* Smart Retry Engine
|
|
9
|
+
*/
|
|
10
|
+
export class SmartRetryEngine {
|
|
11
|
+
config;
|
|
12
|
+
constructor(config = {}) {
|
|
13
|
+
this.config = { ...DEFAULT_RETRY_CONFIG, ...config };
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Execute a test with retry logic
|
|
17
|
+
*/
|
|
18
|
+
async executeWithRetry(testId, executor, context) {
|
|
19
|
+
const startTime = Date.now();
|
|
20
|
+
const attempts = [];
|
|
21
|
+
let lastError;
|
|
22
|
+
let stopReason = 'max_retries';
|
|
23
|
+
// Adjust strategy based on flakiness if adaptive
|
|
24
|
+
const effectiveStrategy = this.getEffectiveStrategy(this.config.strategy, context?.flakinessScore, context?.patternType);
|
|
25
|
+
for (let attempt = 1; attempt <= this.config.maxRetries + 1; attempt++) {
|
|
26
|
+
// Calculate delay before this attempt
|
|
27
|
+
const delayMs = attempt === 1 ? 0 : this.calculateDelay(attempt, effectiveStrategy);
|
|
28
|
+
// Apply delay if not first attempt
|
|
29
|
+
if (delayMs > 0) {
|
|
30
|
+
await this.delay(delayMs);
|
|
31
|
+
}
|
|
32
|
+
const attemptStartTime = Date.now();
|
|
33
|
+
let attemptSuccess = false;
|
|
34
|
+
let errorType;
|
|
35
|
+
let errorMessage;
|
|
36
|
+
let shouldStop = false;
|
|
37
|
+
try {
|
|
38
|
+
await executor();
|
|
39
|
+
attemptSuccess = true;
|
|
40
|
+
stopReason = 'success';
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
const err = error;
|
|
44
|
+
errorType = err.constructor?.name || 'Error';
|
|
45
|
+
errorMessage = err.message;
|
|
46
|
+
// Check if error is non-retriable
|
|
47
|
+
if (this.isNonRetriableError(errorType, errorMessage)) {
|
|
48
|
+
stopReason = 'unrecoverable';
|
|
49
|
+
lastError = err;
|
|
50
|
+
shouldStop = true;
|
|
51
|
+
}
|
|
52
|
+
// Check if we should retry on assertion failure
|
|
53
|
+
if (!shouldStop && !this.config.retryOnAssertionFailure && this.isAssertionError(errorType, errorMessage)) {
|
|
54
|
+
stopReason = 'unrecoverable';
|
|
55
|
+
lastError = err;
|
|
56
|
+
shouldStop = true;
|
|
57
|
+
}
|
|
58
|
+
// Check if we should retry on timeout
|
|
59
|
+
if (!shouldStop && !this.config.retryOnTimeout && this.isTimeoutError(errorType, errorMessage)) {
|
|
60
|
+
stopReason = 'unrecoverable';
|
|
61
|
+
lastError = err;
|
|
62
|
+
shouldStop = true;
|
|
63
|
+
}
|
|
64
|
+
if (!shouldStop) {
|
|
65
|
+
lastError = err;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const attemptDuration = Date.now() - attemptStartTime;
|
|
69
|
+
// Always record the attempt
|
|
70
|
+
attempts.push({
|
|
71
|
+
attemptNumber: attempt,
|
|
72
|
+
success: attemptSuccess,
|
|
73
|
+
durationMs: attemptDuration,
|
|
74
|
+
errorType,
|
|
75
|
+
errorMessage,
|
|
76
|
+
delayMs,
|
|
77
|
+
timestamp: attemptStartTime
|
|
78
|
+
});
|
|
79
|
+
if (attemptSuccess || shouldStop) {
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
// Check overall timeout
|
|
83
|
+
if (this.config.overallTimeoutMs) {
|
|
84
|
+
const elapsed = Date.now() - startTime;
|
|
85
|
+
if (elapsed + this.config.maxDelayMs > this.config.overallTimeoutMs) {
|
|
86
|
+
stopReason = 'timeout';
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
const totalDuration = Date.now() - startTime;
|
|
92
|
+
const success = attempts.some(a => a.success);
|
|
93
|
+
const successfulAttempt = attempts.find(a => a.success)?.attemptNumber;
|
|
94
|
+
return {
|
|
95
|
+
testId,
|
|
96
|
+
success,
|
|
97
|
+
totalAttempts: attempts.length,
|
|
98
|
+
successfulAttempt,
|
|
99
|
+
attempts,
|
|
100
|
+
totalDurationMs: totalDuration,
|
|
101
|
+
strategy: effectiveStrategy,
|
|
102
|
+
stopReason
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Get retry recommendation based on flakiness and pattern
|
|
107
|
+
*/
|
|
108
|
+
getRetryRecommendation(params) {
|
|
109
|
+
const { flakinessScore = 100, patternType, errorType, errorCount = 1, recentAttempts = 0 } = params;
|
|
110
|
+
// Non-retriable errors should not be retried
|
|
111
|
+
if (errorType && this.isNonRetriableError(errorType)) {
|
|
112
|
+
return {
|
|
113
|
+
shouldRetry: false,
|
|
114
|
+
strategy: RetryStrategy.NONE,
|
|
115
|
+
maxRetries: 0,
|
|
116
|
+
initialDelayMs: 0,
|
|
117
|
+
confidence: 0.95,
|
|
118
|
+
reason: `Error type "${errorType}" is not retriable`
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
// High flakiness score (reliable tests) -> fewer retries
|
|
122
|
+
if (flakinessScore >= 90) {
|
|
123
|
+
return {
|
|
124
|
+
shouldRetry: false,
|
|
125
|
+
strategy: RetryStrategy.NONE,
|
|
126
|
+
maxRetries: 0,
|
|
127
|
+
initialDelayMs: 0,
|
|
128
|
+
confidence: 0.9,
|
|
129
|
+
reason: 'Test is highly reliable (Legendary/Solid), retry not recommended'
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
// Medium flakiness -> conservative retry
|
|
133
|
+
if (flakinessScore >= 75) {
|
|
134
|
+
return {
|
|
135
|
+
shouldRetry: true,
|
|
136
|
+
strategy: RetryStrategy.FIXED,
|
|
137
|
+
maxRetries: 2,
|
|
138
|
+
initialDelayMs: 1000,
|
|
139
|
+
confidence: 0.75,
|
|
140
|
+
reason: 'Test is occasionally flaky (Good), conservative retry recommended'
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
// Low flakiness -> adaptive retry
|
|
144
|
+
if (flakinessScore >= 50) {
|
|
145
|
+
return {
|
|
146
|
+
shouldRetry: true,
|
|
147
|
+
strategy: RetryStrategy.ADAPTIVE,
|
|
148
|
+
maxRetries: 3,
|
|
149
|
+
initialDelayMs: 1000,
|
|
150
|
+
confidence: 0.85,
|
|
151
|
+
reason: 'Test is often flaky (Shaky), adaptive retry recommended',
|
|
152
|
+
pattern: patternType
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
// Very low flakiness -> intelligent retry
|
|
156
|
+
return {
|
|
157
|
+
shouldRetry: true,
|
|
158
|
+
strategy: RetryStrategy.INTELLIGENT,
|
|
159
|
+
maxRetries: 5,
|
|
160
|
+
initialDelayMs: 2000,
|
|
161
|
+
confidence: 0.9,
|
|
162
|
+
reason: 'Test is very flaky (Unstable), aggressive retry with pattern analysis',
|
|
163
|
+
pattern: patternType
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Calculate delay before retry attempt
|
|
168
|
+
*/
|
|
169
|
+
calculateDelay(attemptNumber, strategy) {
|
|
170
|
+
const baseDelay = this.config.initialDelayMs;
|
|
171
|
+
switch (strategy) {
|
|
172
|
+
case RetryStrategy.FIXED:
|
|
173
|
+
return this.applyJitter(baseDelay);
|
|
174
|
+
case RetryStrategy.LINEAR:
|
|
175
|
+
const linearDelay = baseDelay * (attemptNumber - 1) * this.config.backoffMultiplier;
|
|
176
|
+
return this.applyJitter(Math.min(linearDelay, this.config.maxDelayMs));
|
|
177
|
+
case RetryStrategy.EXPONENTIAL:
|
|
178
|
+
const expDelay = baseDelay * Math.pow(this.config.backoffMultiplier, attemptNumber - 1);
|
|
179
|
+
return this.applyJitter(Math.min(expDelay, this.config.maxDelayMs));
|
|
180
|
+
case RetryStrategy.ADAPTIVE:
|
|
181
|
+
// Start with exponential, but reduce if attempts are high
|
|
182
|
+
const adaptiveDelay = baseDelay * Math.pow(this.config.backoffMultiplier, Math.min(attemptNumber - 1, 3));
|
|
183
|
+
return this.applyJitter(Math.min(adaptiveDelay, this.config.maxDelayMs));
|
|
184
|
+
case RetryStrategy.INTELLIGENT:
|
|
185
|
+
// Use pattern-based delay calculation
|
|
186
|
+
return this.getIntelligentDelay(attemptNumber);
|
|
187
|
+
case RetryStrategy.NONE:
|
|
188
|
+
default:
|
|
189
|
+
return 0;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Get effective strategy based on context
|
|
194
|
+
*/
|
|
195
|
+
getEffectiveStrategy(configuredStrategy, flakinessScore, patternType) {
|
|
196
|
+
// If explicitly set to none, respect it
|
|
197
|
+
if (configuredStrategy === RetryStrategy.NONE) {
|
|
198
|
+
return RetryStrategy.NONE;
|
|
199
|
+
}
|
|
200
|
+
// Adaptive strategy adjusts based on flakiness
|
|
201
|
+
if (configuredStrategy === RetryStrategy.ADAPTIVE && flakinessScore !== undefined) {
|
|
202
|
+
if (flakinessScore >= 90)
|
|
203
|
+
return RetryStrategy.NONE;
|
|
204
|
+
if (flakinessScore >= 75)
|
|
205
|
+
return RetryStrategy.FIXED;
|
|
206
|
+
if (flakinessScore >= 50)
|
|
207
|
+
return RetryStrategy.LINEAR;
|
|
208
|
+
return RetryStrategy.EXPONENTIAL;
|
|
209
|
+
}
|
|
210
|
+
// Intelligent strategy considers patterns
|
|
211
|
+
if (configuredStrategy === RetryStrategy.INTELLIGENT && patternType) {
|
|
212
|
+
// Timing issues -> exponential backoff
|
|
213
|
+
if (patternType.includes('timing'))
|
|
214
|
+
return RetryStrategy.EXPONENTIAL;
|
|
215
|
+
// External dependency -> linear with longer delays
|
|
216
|
+
if (patternType.includes('external'))
|
|
217
|
+
return RetryStrategy.LINEAR;
|
|
218
|
+
// Selector issues -> fixed delay (usually resolves quickly)
|
|
219
|
+
if (patternType.includes('selector'))
|
|
220
|
+
return RetryStrategy.FIXED;
|
|
221
|
+
// Race condition -> exponential with jitter
|
|
222
|
+
if (patternType.includes('race'))
|
|
223
|
+
return RetryStrategy.EXPONENTIAL;
|
|
224
|
+
}
|
|
225
|
+
return configuredStrategy;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Calculate intelligent delay based on common patterns
|
|
229
|
+
*/
|
|
230
|
+
getIntelligentDelay(attemptNumber) {
|
|
231
|
+
// Timing issues: exponential backoff starting at 2s
|
|
232
|
+
if (attemptNumber === 1)
|
|
233
|
+
return 2000;
|
|
234
|
+
if (attemptNumber === 2)
|
|
235
|
+
return 3000;
|
|
236
|
+
if (attemptNumber === 3)
|
|
237
|
+
return 5000;
|
|
238
|
+
return 8000;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Apply jitter to delay to avoid thundering herd
|
|
242
|
+
*/
|
|
243
|
+
applyJitter(delayMs) {
|
|
244
|
+
if (this.config.jitterFactor === 0)
|
|
245
|
+
return delayMs;
|
|
246
|
+
const jitter = delayMs * this.config.jitterFactor * (Math.random() * 2 - 1);
|
|
247
|
+
return Math.max(0, Math.round(delayMs + jitter));
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Check if error type is non-retriable
|
|
251
|
+
*/
|
|
252
|
+
isNonRetriableError(errorType, errorMessage) {
|
|
253
|
+
return this.config.nonRetriableErrors.some(nonRetriable => errorType.includes(nonRetriable) ||
|
|
254
|
+
(errorMessage && errorMessage.includes(nonRetriable)));
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Check if error is an assertion failure
|
|
258
|
+
*/
|
|
259
|
+
isAssertionError(errorType, errorMessage) {
|
|
260
|
+
return errorType.toLowerCase().includes('assertion') ||
|
|
261
|
+
errorType === 'AssertionError' ||
|
|
262
|
+
errorType === 'ExpectFailError' ||
|
|
263
|
+
Boolean(errorMessage && errorMessage.toLowerCase().includes('assertion'));
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Check if error is a timeout
|
|
267
|
+
*/
|
|
268
|
+
isTimeoutError(errorType, errorMessage) {
|
|
269
|
+
return errorType.toLowerCase().includes('timeout') ||
|
|
270
|
+
Boolean(errorMessage && errorMessage.toLowerCase().includes('timeout'));
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Delay for specified milliseconds
|
|
274
|
+
*/
|
|
275
|
+
delay(ms) {
|
|
276
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Update retry configuration
|
|
280
|
+
*/
|
|
281
|
+
updateConfig(config) {
|
|
282
|
+
this.config = { ...this.config, ...config };
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Get current configuration
|
|
286
|
+
*/
|
|
287
|
+
getConfig() {
|
|
288
|
+
return { ...this.config };
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Create a smart retry engine
|
|
293
|
+
*/
|
|
294
|
+
export function createSmartRetryEngine(config) {
|
|
295
|
+
return new SmartRetryEngine(config);
|
|
296
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QA360 Smart Retry Module (F8)
|
|
3
|
+
*
|
|
4
|
+
* Intelligent retry strategies based on flakiness scoring and pattern detection.
|
|
5
|
+
* Completes the flakiness system: Detection → Scoring → Quarantine → Smart Retry
|
|
6
|
+
*
|
|
7
|
+
* @module retry
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Retry strategy types
|
|
11
|
+
*/
|
|
12
|
+
export declare enum RetryStrategy {
|
|
13
|
+
/** No retry - fail immediately */
|
|
14
|
+
NONE = "none",
|
|
15
|
+
/** Fixed delay between retries */
|
|
16
|
+
FIXED = "fixed",
|
|
17
|
+
/** Linear backoff (delay increases linearly) */
|
|
18
|
+
LINEAR = "linear",
|
|
19
|
+
/** Exponential backoff (delay doubles each retry) */
|
|
20
|
+
EXPONENTIAL = "exponential",
|
|
21
|
+
/** Adaptive retry based on flakiness score */
|
|
22
|
+
ADAPTIVE = "adaptive",
|
|
23
|
+
/** Intelligent retry based on pattern detection */
|
|
24
|
+
INTELLIGENT = "intelligent"
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Retry attempt result
|
|
28
|
+
*/
|
|
29
|
+
export interface RetryAttempt {
|
|
30
|
+
/** Attempt number (1-indexed) */
|
|
31
|
+
attemptNumber: number;
|
|
32
|
+
/** Whether the attempt succeeded */
|
|
33
|
+
success: boolean;
|
|
34
|
+
/** Duration in milliseconds */
|
|
35
|
+
durationMs: number;
|
|
36
|
+
/** Error type if failed */
|
|
37
|
+
errorType?: string;
|
|
38
|
+
/** Error message if failed */
|
|
39
|
+
errorMessage?: string;
|
|
40
|
+
/** Delay before this attempt in ms */
|
|
41
|
+
delayMs: number;
|
|
42
|
+
/** Timestamp of attempt */
|
|
43
|
+
timestamp: number;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Retry result
|
|
47
|
+
*/
|
|
48
|
+
export interface RetryResult {
|
|
49
|
+
/** Test identifier */
|
|
50
|
+
testId: string;
|
|
51
|
+
/** Whether any retry attempt succeeded */
|
|
52
|
+
success: boolean;
|
|
53
|
+
/** Total number of attempts */
|
|
54
|
+
totalAttempts: number;
|
|
55
|
+
/** Successful attempt number (0 if none) */
|
|
56
|
+
successfulAttempt?: number;
|
|
57
|
+
/** All retry attempts */
|
|
58
|
+
attempts: RetryAttempt[];
|
|
59
|
+
/** Total duration including delays */
|
|
60
|
+
totalDurationMs: number;
|
|
61
|
+
/** Strategy used */
|
|
62
|
+
strategy: RetryStrategy;
|
|
63
|
+
/** Reason for stopping retries */
|
|
64
|
+
stopReason: 'success' | 'max_retries' | 'timeout' | 'unrecoverable';
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Retry configuration
|
|
68
|
+
*/
|
|
69
|
+
export interface RetryConfig {
|
|
70
|
+
/** Maximum number of retry attempts */
|
|
71
|
+
maxRetries: number;
|
|
72
|
+
/** Initial delay in milliseconds */
|
|
73
|
+
initialDelayMs: number;
|
|
74
|
+
/** Maximum delay in milliseconds */
|
|
75
|
+
maxDelayMs: number;
|
|
76
|
+
/** Timeout for each attempt */
|
|
77
|
+
attemptTimeoutMs: number;
|
|
78
|
+
/** Overall timeout including retries */
|
|
79
|
+
overallTimeoutMs?: number;
|
|
80
|
+
/** Retry strategy */
|
|
81
|
+
strategy: RetryStrategy;
|
|
82
|
+
/** Multiplier for exponential/linear backoff */
|
|
83
|
+
backoffMultiplier: number;
|
|
84
|
+
/** Jitter for backoff (0-1) to avoid thundering herd */
|
|
85
|
+
jitterFactor: number;
|
|
86
|
+
/** Whether to retry on timeout */
|
|
87
|
+
retryOnTimeout: boolean;
|
|
88
|
+
/** Whether to retry on assertion failures */
|
|
89
|
+
retryOnAssertionFailure: boolean;
|
|
90
|
+
/** Error types that should not be retried */
|
|
91
|
+
nonRetriableErrors: string[];
|
|
92
|
+
/** Adaptive retry: minimum flakiness score to enable retries */
|
|
93
|
+
adaptiveMinFlakinessScore?: number;
|
|
94
|
+
/** Intelligent retry: enable pattern-based retry */
|
|
95
|
+
enablePatternBasedRetry?: boolean;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Default retry configuration
|
|
99
|
+
*/
|
|
100
|
+
export declare const DEFAULT_RETRY_CONFIG: Required<Omit<RetryConfig, 'nonRetriableErrors' | 'adaptiveMinFlakinessScore' | 'enablePatternBasedRetry'>> & {
|
|
101
|
+
nonRetriableErrors: string[];
|
|
102
|
+
adaptiveMinFlakinessScore?: number;
|
|
103
|
+
enablePatternBasedRetry?: boolean;
|
|
104
|
+
};
|
|
105
|
+
/**
|
|
106
|
+
* Flakiness-based retry recommendation
|
|
107
|
+
*/
|
|
108
|
+
export interface RetryRecommendation {
|
|
109
|
+
/** Whether retry is recommended */
|
|
110
|
+
shouldRetry: boolean;
|
|
111
|
+
/** Suggested retry strategy */
|
|
112
|
+
strategy: RetryStrategy;
|
|
113
|
+
/** Suggested max retries */
|
|
114
|
+
maxRetries: number;
|
|
115
|
+
/** Suggested initial delay */
|
|
116
|
+
initialDelayMs: number;
|
|
117
|
+
/** Confidence in recommendation (0-1) */
|
|
118
|
+
confidence: number;
|
|
119
|
+
/** Reason for recommendation */
|
|
120
|
+
reason: string;
|
|
121
|
+
/** Specific pattern detected that informs retry */
|
|
122
|
+
pattern?: string;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Retry history record for vault storage
|
|
126
|
+
*/
|
|
127
|
+
export interface RetryHistoryRecord {
|
|
128
|
+
id: number;
|
|
129
|
+
run_id: string;
|
|
130
|
+
test_id: string;
|
|
131
|
+
test_name: string;
|
|
132
|
+
gate: string;
|
|
133
|
+
strategy: RetryStrategy;
|
|
134
|
+
max_retries: number;
|
|
135
|
+
actual_attempts: number;
|
|
136
|
+
success: boolean;
|
|
137
|
+
successful_attempt: number | null;
|
|
138
|
+
total_duration_ms: number;
|
|
139
|
+
stop_reason: string;
|
|
140
|
+
error_type: string | null;
|
|
141
|
+
error_message: string | null;
|
|
142
|
+
flakiness_score: number | null;
|
|
143
|
+
pattern_type: string | null;
|
|
144
|
+
timestamp: number;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Retry statistics
|
|
148
|
+
*/
|
|
149
|
+
export interface RetryStatistics {
|
|
150
|
+
/** Total retries attempted */
|
|
151
|
+
totalRetries: number;
|
|
152
|
+
/** Total tests that were retried */
|
|
153
|
+
testsRetried: number;
|
|
154
|
+
/** Tests that passed after retry */
|
|
155
|
+
recoveredTests: number;
|
|
156
|
+
/** Tests that failed all retries */
|
|
157
|
+
failedTests: number;
|
|
158
|
+
/** Recovery rate (percentage) */
|
|
159
|
+
recoveryRate: number;
|
|
160
|
+
/** Average attempts per retried test */
|
|
161
|
+
avgAttemptsPerTest: number;
|
|
162
|
+
/** Time spent retrying (ms) */
|
|
163
|
+
totalRetryTimeMs: number;
|
|
164
|
+
/** Breakdown by strategy */
|
|
165
|
+
byStrategy: Record<RetryStrategy, {
|
|
166
|
+
attempts: number;
|
|
167
|
+
successes: number;
|
|
168
|
+
avgDurationMs: number;
|
|
169
|
+
}>;
|
|
170
|
+
/** Breakdown by error type */
|
|
171
|
+
byErrorType: Record<string, number>;
|
|
172
|
+
/** Breakdown by flakiness score */
|
|
173
|
+
byFlakinessScore: {
|
|
174
|
+
low: number;
|
|
175
|
+
medium: number;
|
|
176
|
+
high: number;
|
|
177
|
+
};
|
|
178
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QA360 Smart Retry Module (F8)
|
|
3
|
+
*
|
|
4
|
+
* Intelligent retry strategies based on flakiness scoring and pattern detection.
|
|
5
|
+
* Completes the flakiness system: Detection → Scoring → Quarantine → Smart Retry
|
|
6
|
+
*
|
|
7
|
+
* @module retry
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Retry strategy types
|
|
11
|
+
*/
|
|
12
|
+
export var RetryStrategy;
|
|
13
|
+
(function (RetryStrategy) {
|
|
14
|
+
/** No retry - fail immediately */
|
|
15
|
+
RetryStrategy["NONE"] = "none";
|
|
16
|
+
/** Fixed delay between retries */
|
|
17
|
+
RetryStrategy["FIXED"] = "fixed";
|
|
18
|
+
/** Linear backoff (delay increases linearly) */
|
|
19
|
+
RetryStrategy["LINEAR"] = "linear";
|
|
20
|
+
/** Exponential backoff (delay doubles each retry) */
|
|
21
|
+
RetryStrategy["EXPONENTIAL"] = "exponential";
|
|
22
|
+
/** Adaptive retry based on flakiness score */
|
|
23
|
+
RetryStrategy["ADAPTIVE"] = "adaptive";
|
|
24
|
+
/** Intelligent retry based on pattern detection */
|
|
25
|
+
RetryStrategy["INTELLIGENT"] = "intelligent";
|
|
26
|
+
})(RetryStrategy || (RetryStrategy = {}));
|
|
27
|
+
/**
|
|
28
|
+
* Default retry configuration
|
|
29
|
+
*/
|
|
30
|
+
export const DEFAULT_RETRY_CONFIG = {
|
|
31
|
+
maxRetries: 3,
|
|
32
|
+
initialDelayMs: 1000,
|
|
33
|
+
maxDelayMs: 30000,
|
|
34
|
+
attemptTimeoutMs: 30000,
|
|
35
|
+
overallTimeoutMs: 120000,
|
|
36
|
+
strategy: RetryStrategy.ADAPTIVE,
|
|
37
|
+
backoffMultiplier: 2,
|
|
38
|
+
jitterFactor: 0.1,
|
|
39
|
+
retryOnTimeout: false,
|
|
40
|
+
retryOnAssertionFailure: false,
|
|
41
|
+
nonRetriableErrors: [
|
|
42
|
+
'OutOfMemoryError',
|
|
43
|
+
'StackOverflowError',
|
|
44
|
+
'SecurityError',
|
|
45
|
+
'PermissionDenied',
|
|
46
|
+
'InvalidArgument',
|
|
47
|
+
'ValidationError',
|
|
48
|
+
'SyntaxError'
|
|
49
|
+
],
|
|
50
|
+
adaptiveMinFlakinessScore: 75,
|
|
51
|
+
enablePatternBasedRetry: true
|
|
52
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retry Vault Integration
|
|
3
|
+
*
|
|
4
|
+
* Stores retry history and statistics in the Evidence Vault.
|
|
5
|
+
*/
|
|
6
|
+
import type { Database } from 'sqlite3';
|
|
7
|
+
import type { RetryResult, RetryHistoryRecord, RetryStatistics } from './types.js';
|
|
8
|
+
import { RetryStrategy } from './types.js';
|
|
9
|
+
/**
|
|
10
|
+
* Retry Vault class
|
|
11
|
+
*/
|
|
12
|
+
export declare class RetryVault {
|
|
13
|
+
private db;
|
|
14
|
+
private dbRun;
|
|
15
|
+
private dbAll;
|
|
16
|
+
private dbGet;
|
|
17
|
+
constructor(db: Database);
|
|
18
|
+
/**
|
|
19
|
+
* Initialize retry tables
|
|
20
|
+
*/
|
|
21
|
+
initialize(): Promise<void>;
|
|
22
|
+
/**
|
|
23
|
+
* Store retry result
|
|
24
|
+
*/
|
|
25
|
+
storeRetryResult(runId: string, result: RetryResult, context?: {
|
|
26
|
+
testName?: string;
|
|
27
|
+
gate?: string;
|
|
28
|
+
flakinessScore?: number;
|
|
29
|
+
patternType?: string;
|
|
30
|
+
}): Promise<number>;
|
|
31
|
+
/**
|
|
32
|
+
* Get retry history for a test
|
|
33
|
+
*/
|
|
34
|
+
getRetryHistory(testId: string, limit?: number): Promise<RetryHistoryRecord[]>;
|
|
35
|
+
/**
|
|
36
|
+
* Get retry history for a run
|
|
37
|
+
*/
|
|
38
|
+
getRetryHistoryForRun(runId: string): Promise<RetryHistoryRecord[]>;
|
|
39
|
+
/**
|
|
40
|
+
* Get retry statistics
|
|
41
|
+
*/
|
|
42
|
+
getStatistics(days?: number): Promise<RetryStatistics>;
|
|
43
|
+
/**
|
|
44
|
+
* Get strategy performance
|
|
45
|
+
*/
|
|
46
|
+
getStrategyPerformance(days?: number): Promise<Array<{
|
|
47
|
+
strategy: RetryStrategy;
|
|
48
|
+
date: string;
|
|
49
|
+
attempts: number;
|
|
50
|
+
successes: number;
|
|
51
|
+
successRate: number;
|
|
52
|
+
avgDurationMs: number;
|
|
53
|
+
}>>;
|
|
54
|
+
/**
|
|
55
|
+
* Get most retried tests
|
|
56
|
+
*/
|
|
57
|
+
getMostRetriedTests(limit?: number, days?: number): Promise<Array<{
|
|
58
|
+
testId: string;
|
|
59
|
+
testName: string;
|
|
60
|
+
gate: string;
|
|
61
|
+
retryCount: number;
|
|
62
|
+
recoveryRate: number;
|
|
63
|
+
avgAttempts: number;
|
|
64
|
+
}>>;
|
|
65
|
+
/**
|
|
66
|
+
* Update daily statistics
|
|
67
|
+
*/
|
|
68
|
+
private updateStatistics;
|
|
69
|
+
/**
|
|
70
|
+
* Map database rows to RetryHistoryRecord objects
|
|
71
|
+
*/
|
|
72
|
+
private mapRowsToRecords;
|
|
73
|
+
/**
|
|
74
|
+
* Delete old retry history
|
|
75
|
+
*/
|
|
76
|
+
deleteOldHistory(olderThanMs: number): Promise<number>;
|
|
77
|
+
}
|