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.
Files changed (209) hide show
  1. package/README.md +1 -1
  2. package/dist/commands/ai.d.ts +41 -0
  3. package/dist/commands/ai.js +499 -0
  4. package/dist/commands/ask.js +12 -12
  5. package/dist/commands/coverage.d.ts +8 -0
  6. package/dist/commands/coverage.js +252 -0
  7. package/dist/commands/explain.d.ts +27 -0
  8. package/dist/commands/explain.js +630 -0
  9. package/dist/commands/flakiness.d.ts +73 -0
  10. package/dist/commands/flakiness.js +435 -0
  11. package/dist/commands/generate.d.ts +66 -0
  12. package/dist/commands/generate.js +438 -0
  13. package/dist/commands/init.d.ts +56 -9
  14. package/dist/commands/init.js +217 -10
  15. package/dist/commands/monitor.d.ts +27 -0
  16. package/dist/commands/monitor.js +225 -0
  17. package/dist/commands/ollama.d.ts +40 -0
  18. package/dist/commands/ollama.js +301 -0
  19. package/dist/commands/pack.d.ts +37 -9
  20. package/dist/commands/pack.js +240 -141
  21. package/dist/commands/regression.d.ts +8 -0
  22. package/dist/commands/regression.js +340 -0
  23. package/dist/commands/repair.d.ts +26 -0
  24. package/dist/commands/repair.js +307 -0
  25. package/dist/commands/retry.d.ts +43 -0
  26. package/dist/commands/retry.js +275 -0
  27. package/dist/commands/run.d.ts +8 -3
  28. package/dist/commands/run.js +45 -31
  29. package/dist/commands/slo.d.ts +8 -0
  30. package/dist/commands/slo.js +327 -0
  31. package/dist/core/adapters/playwright-native-api.d.ts +183 -0
  32. package/dist/core/adapters/playwright-native-api.js +461 -0
  33. package/dist/core/adapters/playwright-ui.d.ts +7 -0
  34. package/dist/core/adapters/playwright-ui.js +29 -1
  35. package/dist/core/ai/anthropic-provider.d.ts +50 -0
  36. package/dist/core/ai/anthropic-provider.js +211 -0
  37. package/dist/core/ai/deepseek-provider.d.ts +81 -0
  38. package/dist/core/ai/deepseek-provider.js +254 -0
  39. package/dist/core/ai/index.d.ts +60 -0
  40. package/dist/core/ai/index.js +18 -0
  41. package/dist/core/ai/llm-client.d.ts +45 -0
  42. package/dist/core/ai/llm-client.js +7 -0
  43. package/dist/core/ai/mock-provider.d.ts +49 -0
  44. package/dist/core/ai/mock-provider.js +121 -0
  45. package/dist/core/ai/ollama-provider.d.ts +78 -0
  46. package/dist/core/ai/ollama-provider.js +192 -0
  47. package/dist/core/ai/openai-provider.d.ts +48 -0
  48. package/dist/core/ai/openai-provider.js +188 -0
  49. package/dist/core/ai/provider-factory.d.ts +160 -0
  50. package/dist/core/ai/provider-factory.js +269 -0
  51. package/dist/core/auth/api-key-provider.d.ts +16 -0
  52. package/dist/core/auth/api-key-provider.js +63 -0
  53. package/dist/core/auth/aws-iam-provider.d.ts +35 -0
  54. package/dist/core/auth/aws-iam-provider.js +177 -0
  55. package/dist/core/auth/azure-ad-provider.d.ts +15 -0
  56. package/dist/core/auth/azure-ad-provider.js +99 -0
  57. package/dist/core/auth/basic-auth-provider.d.ts +26 -0
  58. package/dist/core/auth/basic-auth-provider.js +111 -0
  59. package/dist/core/auth/gcp-adc-provider.d.ts +27 -0
  60. package/dist/core/auth/gcp-adc-provider.js +126 -0
  61. package/dist/core/auth/index.d.ts +238 -0
  62. package/dist/core/auth/index.js +82 -0
  63. package/dist/core/auth/jwt-provider.d.ts +19 -0
  64. package/dist/core/auth/jwt-provider.js +160 -0
  65. package/dist/core/auth/manager.d.ts +84 -0
  66. package/dist/core/auth/manager.js +230 -0
  67. package/dist/core/auth/oauth2-provider.d.ts +17 -0
  68. package/dist/core/auth/oauth2-provider.js +114 -0
  69. package/dist/core/auth/totp-provider.d.ts +31 -0
  70. package/dist/core/auth/totp-provider.js +134 -0
  71. package/dist/core/auth/ui-login-provider.d.ts +26 -0
  72. package/dist/core/auth/ui-login-provider.js +198 -0
  73. package/dist/core/cache/index.d.ts +7 -0
  74. package/dist/core/cache/index.js +6 -0
  75. package/dist/core/cache/lru-cache.d.ts +203 -0
  76. package/dist/core/cache/lru-cache.js +397 -0
  77. package/dist/core/coverage/analyzer.d.ts +101 -0
  78. package/dist/core/coverage/analyzer.js +415 -0
  79. package/dist/core/coverage/collector.d.ts +74 -0
  80. package/dist/core/coverage/collector.js +459 -0
  81. package/dist/core/coverage/config.d.ts +37 -0
  82. package/dist/core/coverage/config.js +156 -0
  83. package/dist/core/coverage/index.d.ts +11 -0
  84. package/dist/core/coverage/index.js +15 -0
  85. package/dist/core/coverage/types.d.ts +267 -0
  86. package/dist/core/coverage/types.js +6 -0
  87. package/dist/core/coverage/vault.d.ts +95 -0
  88. package/dist/core/coverage/vault.js +405 -0
  89. package/dist/core/dashboard/assets.d.ts +6 -0
  90. package/dist/core/dashboard/assets.js +690 -0
  91. package/dist/core/dashboard/index.d.ts +6 -0
  92. package/dist/core/dashboard/index.js +5 -0
  93. package/dist/core/dashboard/server.d.ts +72 -0
  94. package/dist/core/dashboard/server.js +354 -0
  95. package/dist/core/dashboard/types.d.ts +70 -0
  96. package/dist/core/dashboard/types.js +5 -0
  97. package/dist/core/discoverer/index.d.ts +115 -0
  98. package/dist/core/discoverer/index.js +250 -0
  99. package/dist/core/flakiness/index.d.ts +228 -0
  100. package/dist/core/flakiness/index.js +384 -0
  101. package/dist/core/generation/code-formatter.d.ts +111 -0
  102. package/dist/core/generation/code-formatter.js +307 -0
  103. package/dist/core/generation/code-generator.d.ts +144 -0
  104. package/dist/core/generation/code-generator.js +293 -0
  105. package/dist/core/generation/generator.d.ts +40 -0
  106. package/dist/core/generation/generator.js +76 -0
  107. package/dist/core/generation/index.d.ts +30 -0
  108. package/dist/core/generation/index.js +28 -0
  109. package/dist/core/generation/pack-generator.d.ts +107 -0
  110. package/dist/core/generation/pack-generator.js +416 -0
  111. package/dist/core/generation/prompt-builder.d.ts +132 -0
  112. package/dist/core/generation/prompt-builder.js +672 -0
  113. package/dist/core/generation/source-analyzer.d.ts +213 -0
  114. package/dist/core/generation/source-analyzer.js +657 -0
  115. package/dist/core/generation/test-optimizer.d.ts +117 -0
  116. package/dist/core/generation/test-optimizer.js +328 -0
  117. package/dist/core/generation/types.d.ts +214 -0
  118. package/dist/core/generation/types.js +4 -0
  119. package/dist/core/index.d.ts +23 -1
  120. package/dist/core/index.js +39 -0
  121. package/dist/core/pack/validator.js +31 -1
  122. package/dist/core/pack-v2/index.d.ts +9 -0
  123. package/dist/core/pack-v2/index.js +8 -0
  124. package/dist/core/pack-v2/loader.d.ts +62 -0
  125. package/dist/core/pack-v2/loader.js +231 -0
  126. package/dist/core/pack-v2/migrator.d.ts +56 -0
  127. package/dist/core/pack-v2/migrator.js +455 -0
  128. package/dist/core/pack-v2/validator.d.ts +61 -0
  129. package/dist/core/pack-v2/validator.js +577 -0
  130. package/dist/core/regression/detector.d.ts +107 -0
  131. package/dist/core/regression/detector.js +497 -0
  132. package/dist/core/regression/index.d.ts +9 -0
  133. package/dist/core/regression/index.js +11 -0
  134. package/dist/core/regression/trend-analyzer.d.ts +102 -0
  135. package/dist/core/regression/trend-analyzer.js +345 -0
  136. package/dist/core/regression/types.d.ts +222 -0
  137. package/dist/core/regression/types.js +7 -0
  138. package/dist/core/regression/vault.d.ts +87 -0
  139. package/dist/core/regression/vault.js +289 -0
  140. package/dist/core/repair/engine/fixer.d.ts +24 -0
  141. package/dist/core/repair/engine/fixer.js +226 -0
  142. package/dist/core/repair/engine/suggestion-engine.d.ts +18 -0
  143. package/dist/core/repair/engine/suggestion-engine.js +187 -0
  144. package/dist/core/repair/index.d.ts +10 -0
  145. package/dist/core/repair/index.js +13 -0
  146. package/dist/core/repair/repairer.d.ts +90 -0
  147. package/dist/core/repair/repairer.js +284 -0
  148. package/dist/core/repair/types.d.ts +91 -0
  149. package/dist/core/repair/types.js +6 -0
  150. package/dist/core/repair/utils/error-analyzer.d.ts +28 -0
  151. package/dist/core/repair/utils/error-analyzer.js +264 -0
  152. package/dist/core/retry/flakiness-integration.d.ts +60 -0
  153. package/dist/core/retry/flakiness-integration.js +228 -0
  154. package/dist/core/retry/index.d.ts +14 -0
  155. package/dist/core/retry/index.js +16 -0
  156. package/dist/core/retry/retry-engine.d.ts +80 -0
  157. package/dist/core/retry/retry-engine.js +296 -0
  158. package/dist/core/retry/types.d.ts +178 -0
  159. package/dist/core/retry/types.js +52 -0
  160. package/dist/core/retry/vault.d.ts +77 -0
  161. package/dist/core/retry/vault.js +304 -0
  162. package/dist/core/runner/e2e-helpers.d.ts +102 -0
  163. package/dist/core/runner/e2e-helpers.js +153 -0
  164. package/dist/core/runner/phase3-runner.d.ts +101 -2
  165. package/dist/core/runner/phase3-runner.js +559 -24
  166. package/dist/core/self-healing/assertion-healer.d.ts +97 -0
  167. package/dist/core/self-healing/assertion-healer.js +371 -0
  168. package/dist/core/self-healing/engine.d.ts +122 -0
  169. package/dist/core/self-healing/engine.js +538 -0
  170. package/dist/core/self-healing/index.d.ts +10 -0
  171. package/dist/core/self-healing/index.js +11 -0
  172. package/dist/core/self-healing/selector-healer.d.ts +103 -0
  173. package/dist/core/self-healing/selector-healer.js +372 -0
  174. package/dist/core/self-healing/types.d.ts +152 -0
  175. package/dist/core/self-healing/types.js +6 -0
  176. package/dist/core/slo/config.d.ts +107 -0
  177. package/dist/core/slo/config.js +360 -0
  178. package/dist/core/slo/index.d.ts +11 -0
  179. package/dist/core/slo/index.js +15 -0
  180. package/dist/core/slo/sli-calculator.d.ts +92 -0
  181. package/dist/core/slo/sli-calculator.js +364 -0
  182. package/dist/core/slo/slo-tracker.d.ts +148 -0
  183. package/dist/core/slo/slo-tracker.js +379 -0
  184. package/dist/core/slo/types.d.ts +281 -0
  185. package/dist/core/slo/types.js +7 -0
  186. package/dist/core/slo/vault.d.ts +102 -0
  187. package/dist/core/slo/vault.js +427 -0
  188. package/dist/core/tui/index.d.ts +7 -0
  189. package/dist/core/tui/index.js +6 -0
  190. package/dist/core/tui/monitor.d.ts +92 -0
  191. package/dist/core/tui/monitor.js +271 -0
  192. package/dist/core/tui/renderer.d.ts +33 -0
  193. package/dist/core/tui/renderer.js +218 -0
  194. package/dist/core/tui/types.d.ts +63 -0
  195. package/dist/core/tui/types.js +5 -0
  196. package/dist/core/types/pack-v2.d.ts +425 -0
  197. package/dist/core/types/pack-v2.js +8 -0
  198. package/dist/core/vault/index.d.ts +116 -0
  199. package/dist/core/vault/index.js +400 -5
  200. package/dist/core/watch/index.d.ts +7 -0
  201. package/dist/core/watch/index.js +6 -0
  202. package/dist/core/watch/watch-mode.d.ts +213 -0
  203. package/dist/core/watch/watch-mode.js +389 -0
  204. package/dist/index.js +68 -68
  205. package/dist/utils/config.d.ts +5 -0
  206. package/dist/utils/config.js +136 -0
  207. package/package.json +5 -1
  208. package/dist/core/adapters/playwright-api.d.ts +0 -82
  209. package/dist/core/adapters/playwright-api.js +0 -264
@@ -0,0 +1,304 @@
1
+ /**
2
+ * Retry Vault Integration
3
+ *
4
+ * Stores retry history and statistics in the Evidence Vault.
5
+ */
6
+ import { promisify } from 'util';
7
+ import { RetryStrategy } from './types.js';
8
+ /**
9
+ * Retry Vault class
10
+ */
11
+ export class RetryVault {
12
+ db;
13
+ dbRun;
14
+ dbAll;
15
+ dbGet;
16
+ constructor(db) {
17
+ this.db = db;
18
+ this.dbRun = promisify(db.run.bind(db));
19
+ this.dbAll = promisify(db.all.bind(db));
20
+ this.dbGet = promisify(db.get.bind(db));
21
+ }
22
+ /**
23
+ * Initialize retry tables
24
+ */
25
+ async initialize() {
26
+ const schema = `
27
+ -- Retry history table
28
+ CREATE TABLE IF NOT EXISTS retry_history (
29
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
30
+ run_id TEXT NOT NULL REFERENCES runs(id) ON DELETE CASCADE,
31
+ test_id TEXT NOT NULL,
32
+ test_name TEXT NOT NULL,
33
+ gate TEXT NOT NULL,
34
+ strategy TEXT NOT NULL,
35
+ max_retries INTEGER NOT NULL,
36
+ actual_attempts INTEGER NOT NULL,
37
+ success BOOLEAN NOT NULL,
38
+ successful_attempt INTEGER,
39
+ total_duration_ms INTEGER NOT NULL,
40
+ stop_reason TEXT NOT NULL,
41
+ error_type TEXT,
42
+ error_message TEXT,
43
+ flakiness_score REAL,
44
+ pattern_type TEXT,
45
+ timestamp INTEGER NOT NULL
46
+ );
47
+
48
+ CREATE INDEX IF NOT EXISTS idx_retry_run_id ON retry_history(run_id);
49
+ CREATE INDEX IF NOT EXISTS idx_retry_test_id ON retry_history(test_id);
50
+ CREATE INDEX IF NOT EXISTS idx_retry_strategy ON retry_history(strategy);
51
+ CREATE INDEX IF NOT EXISTS idx_retry_success ON retry_history(success);
52
+ CREATE INDEX IF NOT EXISTS idx_retry_timestamp ON retry_history(timestamp);
53
+
54
+ -- Retry statistics summary table
55
+ CREATE TABLE IF NOT EXISTS retry_stats (
56
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
57
+ date TEXT NOT NULL,
58
+ total_retries INTEGER NOT NULL DEFAULT 0,
59
+ tests_retried INTEGER NOT NULL DEFAULT 0,
60
+ recovered_tests INTEGER NOT NULL DEFAULT 0,
61
+ failed_tests INTEGER NOT NULL DEFAULT 0,
62
+ recovery_rate REAL NOT NULL DEFAULT 0,
63
+ avg_attempts_per_test REAL NOT NULL DEFAULT 0,
64
+ total_retry_time_ms INTEGER NOT NULL DEFAULT 0,
65
+ UNIQUE(date)
66
+ );
67
+
68
+ CREATE INDEX IF NOT EXISTS idx_retry_stats_date ON retry_stats(date);
69
+
70
+ -- Strategy performance table
71
+ CREATE TABLE IF NOT EXISTS retry_strategy_performance (
72
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
73
+ strategy TEXT NOT NULL,
74
+ date TEXT NOT NULL,
75
+ attempts INTEGER NOT NULL DEFAULT 0,
76
+ successes INTEGER NOT NULL DEFAULT 0,
77
+ avg_duration_ms REAL NOT NULL DEFAULT 0,
78
+ UNIQUE(strategy, date)
79
+ );
80
+
81
+ CREATE INDEX IF NOT EXISTS idx_retry_strategy_perf_date ON retry_strategy_performance(date);
82
+ `;
83
+ const statements = schema
84
+ .split(';')
85
+ .map(s => s.trim())
86
+ .filter(s => s.length > 0);
87
+ for (const statement of statements) {
88
+ await this.dbRun(statement, []);
89
+ }
90
+ }
91
+ /**
92
+ * Store retry result
93
+ */
94
+ async storeRetryResult(runId, result, context) {
95
+ const lastAttempt = result.attempts[result.attempts.length - 1];
96
+ const errorType = lastAttempt?.errorType;
97
+ const errorMessage = lastAttempt?.errorMessage;
98
+ const sql = `
99
+ INSERT INTO retry_history (
100
+ run_id, test_id, test_name, gate, strategy, max_retries,
101
+ actual_attempts, success, successful_attempt, total_duration_ms,
102
+ stop_reason, error_type, error_message, flakiness_score, pattern_type, timestamp
103
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
104
+ `;
105
+ const result2 = await this.dbRun(sql, [
106
+ runId,
107
+ result.testId,
108
+ context?.testName || result.testId,
109
+ context?.gate || 'unknown',
110
+ result.strategy,
111
+ result.totalAttempts - 1, // max_retries = attempts - 1 (first attempt is not a retry)
112
+ result.totalAttempts,
113
+ result.success ? 1 : 0,
114
+ result.successfulAttempt || null,
115
+ result.totalDurationMs,
116
+ result.stopReason,
117
+ errorType || null,
118
+ errorMessage || null,
119
+ context?.flakinessScore || null,
120
+ context?.patternType || null,
121
+ Date.now()
122
+ ]);
123
+ // Update statistics
124
+ await this.updateStatistics(result);
125
+ return result2.lastID;
126
+ }
127
+ /**
128
+ * Get retry history for a test
129
+ */
130
+ async getRetryHistory(testId, limit = 50) {
131
+ const rows = await this.dbAll(`SELECT * FROM retry_history WHERE test_id = ? ORDER BY timestamp DESC LIMIT ?`, [testId, limit]);
132
+ return this.mapRowsToRecords(rows);
133
+ }
134
+ /**
135
+ * Get retry history for a run
136
+ */
137
+ async getRetryHistoryForRun(runId) {
138
+ const rows = await this.dbAll(`SELECT * FROM retry_history WHERE run_id = ? ORDER BY timestamp DESC`, [runId]);
139
+ return this.mapRowsToRecords(rows);
140
+ }
141
+ /**
142
+ * Get retry statistics
143
+ */
144
+ async getStatistics(days = 30) {
145
+ const cutoff = Date.now() - days * 24 * 60 * 60 * 1000;
146
+ const total = await this.dbGet(`SELECT COUNT(*) as count, SUM(actual_attempts) as attempts, SUM(total_duration_ms) as duration
147
+ FROM retry_history WHERE timestamp >= ?`, [cutoff]);
148
+ const testsRetried = await this.dbGet(`SELECT COUNT(DISTINCT test_id) as count FROM retry_history WHERE timestamp >= ?`, [cutoff]);
149
+ const recovered = await this.dbGet(`SELECT COUNT(*) as count FROM retry_history WHERE success = 1 AND timestamp >= ?`, [cutoff]);
150
+ const failed = await this.dbGet(`SELECT COUNT(*) as count FROM retry_history WHERE success = 0 AND timestamp >= ?`, [cutoff]);
151
+ const byStrategy = await this.dbAll(`SELECT strategy, COUNT(*) as attempts, SUM(success) as successes, AVG(total_duration_ms) as avg_duration
152
+ FROM retry_history WHERE timestamp >= ?
153
+ GROUP BY strategy`, [cutoff]);
154
+ const byErrorType = await this.dbAll(`SELECT error_type, COUNT(*) as count FROM retry_history
155
+ WHERE timestamp >= ? AND error_type IS NOT NULL
156
+ GROUP BY error_type
157
+ ORDER BY count DESC`, [cutoff]);
158
+ const byFlakiness = await this.dbGet(`SELECT
159
+ SUM(CASE WHEN flakiness_score < 50 THEN 1 ELSE 0 END) as low,
160
+ SUM(CASE WHEN flakiness_score >= 50 AND flakiness_score < 75 THEN 1 ELSE 0 END) as medium,
161
+ SUM(CASE WHEN flakiness_score >= 75 THEN 1 ELSE 0 END) as high
162
+ FROM retry_history WHERE timestamp >= ?`, [cutoff]);
163
+ const totalRetries = total?.attempts || 0;
164
+ const totalRecovered = recovered?.count || 0;
165
+ const totalFailed = failed?.count || 0;
166
+ const totalTests = testsRetried?.count || 0;
167
+ const byStrategyMap = {
168
+ [RetryStrategy.NONE]: { attempts: 0, successes: 0, avgDurationMs: 0 },
169
+ [RetryStrategy.FIXED]: { attempts: 0, successes: 0, avgDurationMs: 0 },
170
+ [RetryStrategy.LINEAR]: { attempts: 0, successes: 0, avgDurationMs: 0 },
171
+ [RetryStrategy.EXPONENTIAL]: { attempts: 0, successes: 0, avgDurationMs: 0 },
172
+ [RetryStrategy.ADAPTIVE]: { attempts: 0, successes: 0, avgDurationMs: 0 },
173
+ [RetryStrategy.INTELLIGENT]: { attempts: 0, successes: 0, avgDurationMs: 0 }
174
+ };
175
+ for (const row of byStrategy) {
176
+ byStrategyMap[row.strategy] = {
177
+ attempts: row.attempts,
178
+ successes: row.successes,
179
+ avgDurationMs: Math.round(row.avg_duration)
180
+ };
181
+ }
182
+ const byErrorTypeMap = {};
183
+ for (const row of byErrorType) {
184
+ byErrorTypeMap[row.error_type] = row.count;
185
+ }
186
+ return {
187
+ totalRetries,
188
+ testsRetried: totalTests,
189
+ recoveredTests: totalRecovered,
190
+ failedTests: totalFailed,
191
+ recoveryRate: totalRetries > 0 ? Math.round((totalRecovered / (totalRecovered + totalFailed)) * 100) : 0,
192
+ avgAttemptsPerTest: totalTests > 0 ? Math.round(totalRetries / totalTests) : 0,
193
+ totalRetryTimeMs: total?.duration || 0,
194
+ byStrategy: byStrategyMap,
195
+ byErrorType: byErrorTypeMap,
196
+ byFlakinessScore: {
197
+ low: byFlakiness?.low || 0,
198
+ medium: byFlakiness?.medium || 0,
199
+ high: byFlakiness?.high || 0
200
+ }
201
+ };
202
+ }
203
+ /**
204
+ * Get strategy performance
205
+ */
206
+ async getStrategyPerformance(days = 30) {
207
+ const cutoff = Date.now() - days * 24 * 60 * 60 * 1000;
208
+ const rows = await this.dbAll(`SELECT strategy, date, attempts, successes, avg_duration_ms
209
+ FROM retry_strategy_performance
210
+ WHERE date >= datetime(?, 'unixepoch')
211
+ ORDER BY date DESC, strategy`, [Math.floor(cutoff / 1000)]);
212
+ return rows.map((r) => ({
213
+ strategy: r.strategy,
214
+ date: r.date,
215
+ attempts: r.attempts,
216
+ successes: r.successes,
217
+ successRate: r.attempts > 0 ? Math.round((r.successes / r.attempts) * 100) : 0,
218
+ avgDurationMs: Math.round(r.avg_duration_ms)
219
+ }));
220
+ }
221
+ /**
222
+ * Get most retried tests
223
+ */
224
+ async getMostRetriedTests(limit = 10, days = 30) {
225
+ const cutoff = Date.now() - days * 24 * 60 * 60 * 1000;
226
+ const rows = await this.dbAll(`SELECT
227
+ test_id,
228
+ test_name,
229
+ gate,
230
+ COUNT(*) as retry_count,
231
+ SUM(success) as recovered,
232
+ AVG(actual_attempts) as avg_attempts
233
+ FROM retry_history
234
+ WHERE timestamp >= ?
235
+ GROUP BY test_id, test_name, gate
236
+ ORDER BY retry_count DESC
237
+ LIMIT ?`, [cutoff, limit]);
238
+ return rows.map((r) => ({
239
+ testId: r.test_id,
240
+ testName: r.test_name,
241
+ gate: r.gate,
242
+ retryCount: r.retry_count,
243
+ recoveryRate: r.retry_count > 0 ? Math.round((r.recovered / r.retry_count) * 100) : 0,
244
+ avgAttempts: Math.round(r.avg_attempts)
245
+ }));
246
+ }
247
+ /**
248
+ * Update daily statistics
249
+ */
250
+ async updateStatistics(result) {
251
+ const today = new Date().toISOString().split('T')[0];
252
+ // Update main stats
253
+ await this.dbRun(`
254
+ INSERT INTO retry_stats (date, total_retries, tests_retried, recovered_tests, failed_tests)
255
+ VALUES (?, 1, 1, ?, ?)
256
+ ON CONFLICT(date) DO UPDATE SET
257
+ total_retries = total_retries + 1,
258
+ tests_retried = tests_retried + 1,
259
+ recovered_tests = recovered_tests + ?,
260
+ failed_tests = failed_tests + ?
261
+ `, [today, result.success ? 1 : 0, result.success ? 0 : 1, result.success ? 1 : 0, result.success ? 0 : 1]);
262
+ // Update strategy performance
263
+ await this.dbRun(`
264
+ INSERT INTO retry_strategy_performance (strategy, date, attempts, successes, avg_duration_ms)
265
+ VALUES (?, ?, 1, ?, ?)
266
+ ON CONFLICT(strategy, date) DO UPDATE SET
267
+ attempts = attempts + 1,
268
+ successes = successes + ?,
269
+ avg_duration_ms = (avg_duration_ms * (attempts - 1) + ?) / attempts
270
+ `, [result.strategy, today, result.success ? 1 : 0, result.totalDurationMs, result.success ? 1 : 0, result.totalDurationMs]);
271
+ }
272
+ /**
273
+ * Map database rows to RetryHistoryRecord objects
274
+ */
275
+ mapRowsToRecords(rows) {
276
+ return rows.map((r) => ({
277
+ id: r.id,
278
+ run_id: r.run_id,
279
+ test_id: r.test_id,
280
+ test_name: r.test_name,
281
+ gate: r.gate,
282
+ strategy: r.strategy,
283
+ max_retries: r.max_retries,
284
+ actual_attempts: r.actual_attempts,
285
+ success: r.success === 1,
286
+ successful_attempt: r.successful_attempt,
287
+ total_duration_ms: r.total_duration_ms,
288
+ stop_reason: r.stop_reason,
289
+ error_type: r.error_type,
290
+ error_message: r.error_message,
291
+ flakiness_score: r.flakiness_score,
292
+ pattern_type: r.pattern_type,
293
+ timestamp: r.timestamp
294
+ }));
295
+ }
296
+ /**
297
+ * Delete old retry history
298
+ */
299
+ async deleteOldHistory(olderThanMs) {
300
+ const cutoff = Date.now() - olderThanMs;
301
+ const result = await this.dbRun(`DELETE FROM retry_history WHERE timestamp < ?`, [cutoff]);
302
+ return result.changes || 0;
303
+ }
304
+ }
@@ -0,0 +1,102 @@
1
+ /**
2
+ * E2E Test Helpers
3
+ *
4
+ * Utility functions for E2E testing including health checks,
5
+ * site availability verification, and graceful test skipping.
6
+ */
7
+ /**
8
+ * Health check result for a target site
9
+ */
10
+ export interface HealthCheckResult {
11
+ url: string;
12
+ available: boolean;
13
+ status?: number;
14
+ latency?: number;
15
+ error?: string;
16
+ }
17
+ /**
18
+ * Health check options
19
+ */
20
+ export interface HealthCheckOptions {
21
+ timeout?: number;
22
+ method?: 'GET' | 'HEAD' | 'OPTIONS';
23
+ expectedStatus?: number[];
24
+ }
25
+ /**
26
+ * Perform a health check on a URL
27
+ *
28
+ * @param url - URL to check
29
+ * @param options - Health check options
30
+ * @returns Health check result
31
+ */
32
+ export declare function healthCheck(url: string, options?: HealthCheckOptions): Promise<HealthCheckResult>;
33
+ /**
34
+ * Check if a site is healthy before running E2E tests
35
+ *
36
+ * @param url - URL to check
37
+ * @param options - Health check options
38
+ * @returns true if site is healthy, false otherwise
39
+ */
40
+ export declare function isSiteHealthy(url: string, options?: HealthCheckOptions): Promise<boolean>;
41
+ /**
42
+ * Perform health checks on multiple URLs
43
+ *
44
+ * @param urls - URLs to check
45
+ * @param options - Health check options
46
+ * @returns Array of health check results
47
+ */
48
+ export declare function healthCheckMany(urls: string[], options?: HealthCheckOptions): Promise<HealthCheckResult[]>;
49
+ /**
50
+ * Skip test if site is unhealthy with a clear message
51
+ *
52
+ * Usage in beforeEach:
53
+ * await skipIfUnhealthy('https://example.com');
54
+ *
55
+ * @param url - URL to check
56
+ * @param options - Health check options
57
+ */
58
+ export declare function skipIfUnhealthy(url: string, options?: HealthCheckOptions): Promise<void>;
59
+ /**
60
+ * Health check configuration for known E2E targets
61
+ */
62
+ export declare const E2E_TARGETS: {
63
+ readonly b2bshop: {
64
+ readonly url: "https://b2bshop.lovable.app";
65
+ readonly timeout: 10000;
66
+ readonly description: "B2BShop E-commerce platform";
67
+ };
68
+ readonly reqres: {
69
+ readonly url: "https://reqres.in/api";
70
+ readonly timeout: 5000;
71
+ readonly description: "Reqres.in fake REST API";
72
+ };
73
+ };
74
+ /**
75
+ * Get health check config for a known target
76
+ *
77
+ * @param target - Target name
78
+ * @returns Health check options for the target
79
+ */
80
+ export declare function getTargetConfig(target: keyof typeof E2E_TARGETS): HealthCheckOptions & {
81
+ url: string;
82
+ description: string;
83
+ };
84
+ /**
85
+ * Perform health check on a known E2E target
86
+ *
87
+ * @param target - Target name
88
+ * @returns Health check result
89
+ */
90
+ export declare function healthCheckE2ETarget(target: keyof typeof E2E_TARGETS): Promise<HealthCheckResult>;
91
+ /**
92
+ * Skip test if known E2E target is unhealthy
93
+ *
94
+ * @param target - Target name
95
+ */
96
+ export declare function skipIfE2ETargetUnhealthy(target: keyof typeof E2E_TARGETS): Promise<void>;
97
+ /**
98
+ * Log health status of all known E2E targets
99
+ *
100
+ * Useful for debugging or CI output
101
+ */
102
+ export declare function logE2ETargetsStatus(): Promise<void>;
@@ -0,0 +1,153 @@
1
+ /**
2
+ * E2E Test Helpers
3
+ *
4
+ * Utility functions for E2E testing including health checks,
5
+ * site availability verification, and graceful test skipping.
6
+ */
7
+ /**
8
+ * Perform a health check on a URL
9
+ *
10
+ * @param url - URL to check
11
+ * @param options - Health check options
12
+ * @returns Health check result
13
+ */
14
+ export async function healthCheck(url, options = {}) {
15
+ const { timeout = 5000, method = 'HEAD', expectedStatus = [200, 301, 302, 304] } = options;
16
+ const startTime = Date.now();
17
+ try {
18
+ const controller = new AbortController();
19
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
20
+ const response = await fetch(url, {
21
+ method,
22
+ signal: controller.signal,
23
+ // Don't follow redirects for health check
24
+ redirect: 'manual'
25
+ });
26
+ clearTimeout(timeoutId);
27
+ const latency = Date.now() - startTime;
28
+ const isAvailable = expectedStatus.includes(response.status);
29
+ return {
30
+ url,
31
+ available: isAvailable,
32
+ status: response.status,
33
+ latency
34
+ };
35
+ }
36
+ catch (error) {
37
+ const latency = Date.now() - startTime;
38
+ return {
39
+ url,
40
+ available: false,
41
+ latency,
42
+ error: error instanceof Error ? error.message : String(error)
43
+ };
44
+ }
45
+ }
46
+ /**
47
+ * Check if a site is healthy before running E2E tests
48
+ *
49
+ * @param url - URL to check
50
+ * @param options - Health check options
51
+ * @returns true if site is healthy, false otherwise
52
+ */
53
+ export async function isSiteHealthy(url, options) {
54
+ const result = await healthCheck(url, options);
55
+ return result.available;
56
+ }
57
+ /**
58
+ * Perform health checks on multiple URLs
59
+ *
60
+ * @param urls - URLs to check
61
+ * @param options - Health check options
62
+ * @returns Array of health check results
63
+ */
64
+ export async function healthCheckMany(urls, options) {
65
+ return Promise.all(urls.map(url => healthCheck(url, options)));
66
+ }
67
+ /**
68
+ * Skip test if site is unhealthy with a clear message
69
+ *
70
+ * Usage in beforeEach:
71
+ * await skipIfUnhealthy('https://example.com');
72
+ *
73
+ * @param url - URL to check
74
+ * @param options - Health check options
75
+ */
76
+ export async function skipIfUnhealthy(url, options) {
77
+ const result = await healthCheck(url, options);
78
+ if (!result.available) {
79
+ const message = `Skipping E2E tests: ${url} is not available` +
80
+ (result.status ? ` (status: ${result.status})` : '') +
81
+ (result.error ? ` - ${result.error}` : '');
82
+ console.warn(`\n⚠️ ${message}`);
83
+ // Throw a special error that test frameworks should handle as skip
84
+ const skipError = new Error(message);
85
+ skipError.skip = true;
86
+ throw skipError;
87
+ }
88
+ }
89
+ /**
90
+ * Health check configuration for known E2E targets
91
+ */
92
+ export const E2E_TARGETS = {
93
+ b2bshop: {
94
+ url: 'https://b2bshop.lovable.app',
95
+ timeout: 10000,
96
+ description: 'B2BShop E-commerce platform'
97
+ },
98
+ reqres: {
99
+ url: 'https://reqres.in/api',
100
+ timeout: 5000,
101
+ description: 'Reqres.in fake REST API'
102
+ }
103
+ };
104
+ /**
105
+ * Get health check config for a known target
106
+ *
107
+ * @param target - Target name
108
+ * @returns Health check options for the target
109
+ */
110
+ export function getTargetConfig(target) {
111
+ const config = E2E_TARGETS[target];
112
+ return {
113
+ url: config.url,
114
+ timeout: config.timeout,
115
+ description: config.description
116
+ };
117
+ }
118
+ /**
119
+ * Perform health check on a known E2E target
120
+ *
121
+ * @param target - Target name
122
+ * @returns Health check result
123
+ */
124
+ export async function healthCheckE2ETarget(target) {
125
+ const config = E2E_TARGETS[target];
126
+ return healthCheck(config.url, { timeout: config.timeout });
127
+ }
128
+ /**
129
+ * Skip test if known E2E target is unhealthy
130
+ *
131
+ * @param target - Target name
132
+ */
133
+ export async function skipIfE2ETargetUnhealthy(target) {
134
+ const config = E2E_TARGETS[target];
135
+ await skipIfUnhealthy(config.url, { timeout: config.timeout });
136
+ }
137
+ /**
138
+ * Log health status of all known E2E targets
139
+ *
140
+ * Useful for debugging or CI output
141
+ */
142
+ export async function logE2ETargetsStatus() {
143
+ console.log('\n🔍 E2E Targets Health Check:');
144
+ console.log('━'.repeat(50));
145
+ for (const [name, config] of Object.entries(E2E_TARGETS)) {
146
+ const result = await healthCheck(config.url, { timeout: config.timeout });
147
+ const status = result.available ? '✅' : '❌';
148
+ const latency = result.latency ? `${result.latency}ms` : 'N/A';
149
+ const statusText = result.status ? `(${result.status})` : '';
150
+ console.log(`${status} ${name.padEnd(10)} ${config.url.padEnd(35)} ${latency.padStart(8)} ${statusText}`);
151
+ }
152
+ console.log('━'.repeat(50));
153
+ }