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,327 @@
1
+ /**
2
+ * QA360 SLO/SLI Command
3
+ *
4
+ * CLI command for SLO/SLI management and reporting.
5
+ */
6
+ import { Command } from 'commander';
7
+ import { SLOTracker, createDefaultSLOConfig, createStrictSLOConfig, createRelaxedSLOConfig, TimeWindows } from '../core/index.js';
8
+ export const sloCommand = new Command('slo');
9
+ sloCommand.description('Service Level Objectives and Indicators management');
10
+ // List all SLOs
11
+ sloCommand
12
+ .command('list')
13
+ .description('List all SLOs')
14
+ .option('--json', 'Output as JSON')
15
+ .action(async (options) => {
16
+ const config = createDefaultSLOConfig();
17
+ const tracker = new SLOTracker();
18
+ tracker.registerSLOs(config.slos);
19
+ const slos = tracker.getAllSLOs();
20
+ if (options.json) {
21
+ console.log(JSON.stringify(slos, null, 2));
22
+ return;
23
+ }
24
+ console.log('\n📊 Service Level Objectives\n');
25
+ if (slos.length === 0) {
26
+ console.log('No SLOs configured.\n');
27
+ return;
28
+ }
29
+ for (const slo of slos) {
30
+ const status = slo.status === 'healthy' ? '✓' : slo.status === 'warning' ? '⚠' : '✗';
31
+ const currentValue = slo.currentValue?.toFixed(2) || 'N/A';
32
+ console.log(` ${status} ${slo.name}`);
33
+ console.log(` Target: ${slo.target}% | Current: ${currentValue}%`);
34
+ console.log(` Window: ${formatDuration(slo.windowMs)}`);
35
+ console.log(` Error Budget: ${slo.errorBudget.remainingBudget.toFixed(2)}% remaining`);
36
+ console.log('');
37
+ }
38
+ });
39
+ // List all SLIs
40
+ sloCommand
41
+ .command('sli')
42
+ .description('List all Service Level Indicators')
43
+ .option('--json', 'Output as JSON')
44
+ .action(async (options) => {
45
+ const config = createDefaultSLOConfig();
46
+ const tracker = new SLOTracker();
47
+ tracker.registerSLIs(config.slis);
48
+ const slis = tracker.getAllSLIs();
49
+ if (options.json) {
50
+ console.log(JSON.stringify(slis, null, 2));
51
+ return;
52
+ }
53
+ console.log('\n📈 Service Level Indicators\n');
54
+ if (slis.length === 0) {
55
+ console.log('No SLIs configured.\n');
56
+ return;
57
+ }
58
+ for (const sli of slis) {
59
+ console.log(` • ${sli.name}`);
60
+ console.log(` Type: ${sli.type}`);
61
+ console.log(` Source: ${sli.source}`);
62
+ console.log(` Aggregation: ${sli.aggregation}`);
63
+ console.log(` Unit: ${sli.unit}`);
64
+ console.log(` Threshold: ${sli.threshold.operator} ${sli.threshold.value}${sli.unit === 'percentage' ? '%' : sli.unit}`);
65
+ console.log('');
66
+ }
67
+ });
68
+ // Show SLO status
69
+ sloCommand
70
+ .command('status')
71
+ .description('Show current SLO status')
72
+ .option('--id <id>', 'SLO ID (optional, shows all if not specified)')
73
+ .option('--json', 'Output as JSON')
74
+ .action(async (options) => {
75
+ const config = createDefaultSLOConfig();
76
+ const tracker = new SLOTracker();
77
+ tracker.registerSLOs(config.slos);
78
+ tracker.registerSLIs(config.slis);
79
+ // Simulate some measurements for demo
80
+ const demoSli = config.slis[0];
81
+ if (demoSli) {
82
+ tracker.recordMeasurement({
83
+ sliId: demoSli.id,
84
+ timestamp: Date.now(),
85
+ value: 92,
86
+ total: 100,
87
+ good: 92,
88
+ bad: 8,
89
+ unit: demoSli.unit,
90
+ windowStart: Date.now() - TimeWindows.DAY,
91
+ windowEnd: Date.now()
92
+ });
93
+ }
94
+ const results = options.id
95
+ ? [tracker.calculateSLOResult(options.id)]
96
+ : tracker.calculateAllResults();
97
+ if (options.json) {
98
+ console.log(JSON.stringify(results.filter(r => r), null, 2));
99
+ return;
100
+ }
101
+ console.log('\n📊 SLO Status Report\n');
102
+ console.log(`Generated: ${new Date().toISOString()}\n`);
103
+ for (const result of results.filter(r => r)) {
104
+ if (!result)
105
+ continue;
106
+ const status = result.status === 'healthy' ? '✅ Healthy' : result.status === 'warning' ? '⚠️ Warning' : '❌ Breached';
107
+ const budgetBar = createBudgetBar(result.errorBudget.remaining);
108
+ console.log(` ${result.name}`);
109
+ console.log(` Status: ${status}`);
110
+ console.log(` Target: ${result.target}% | Current: ${result.current.toFixed(2)}%`);
111
+ console.log(` Error Budget: ${budgetBar} ${result.errorBudget.remaining.toFixed(2)}%`);
112
+ console.log(` Burn Rate: ${result.errorBudget.burnRate.toFixed(2)}x`);
113
+ console.log('');
114
+ }
115
+ });
116
+ // Show error budget
117
+ sloCommand
118
+ .command('budget')
119
+ .description('Show error budget status')
120
+ .option('--id <id>', 'SLO ID')
121
+ .option('--json', 'Output as JSON')
122
+ .action(async (options) => {
123
+ const config = createDefaultSLOConfig();
124
+ const tracker = new SLOTracker();
125
+ tracker.registerSLOs(config.slos);
126
+ const slos = options.id
127
+ ? ((slo => slo ? [slo] : [])(tracker.getSLO(options.id)))
128
+ : tracker.getAllSLOs();
129
+ if (options.json) {
130
+ console.log(JSON.stringify(slos.map(s => ({
131
+ id: s.id,
132
+ name: s.name,
133
+ errorBudget: s.errorBudget
134
+ })), null, 2));
135
+ return;
136
+ }
137
+ console.log('\n💰 Error Budget Status\n');
138
+ for (const slo of slos) {
139
+ if (!slo)
140
+ continue;
141
+ const budget = slo.errorBudget;
142
+ const budgetBar = createBudgetBar(budget.remainingBudget);
143
+ const status = budget.remainingBudget > 0 ? '✅' : '❌';
144
+ console.log(` ${status} ${slo.name}`);
145
+ console.log(` Initial Budget: ${budget.initialBudget}%`);
146
+ console.log(` Remaining: ${budgetBar} ${budget.remainingBudget.toFixed(2)}%`);
147
+ console.log(` Burned: ${(budget.initialBudget - budget.remainingBudget).toFixed(2)}%`);
148
+ console.log('');
149
+ }
150
+ });
151
+ // Create a new SLO
152
+ sloCommand
153
+ .command('create')
154
+ .description('Create a new SLO')
155
+ .option('--name <name>', 'SLO name')
156
+ .option('--target <percentage>', 'Target percentage (0-100)', parseFloat)
157
+ .option('--window <duration>', 'Window duration (e.g., 7d, 30d)', '30d')
158
+ .option('--sli <ids...>', 'SLI IDs (comma-separated)')
159
+ .option('--tags <tags...>', 'Tags (comma-separated)')
160
+ .action(async (options) => {
161
+ if (!options.name) {
162
+ console.error('Error: --name is required');
163
+ process.exit(1);
164
+ }
165
+ if (options.target === undefined || options.target < 0 || options.target > 100) {
166
+ console.error('Error: --target must be between 0 and 100');
167
+ process.exit(1);
168
+ }
169
+ const windowMs = parseDuration(options.window);
170
+ const sliIds = options.sli ? options.sli.split(',') : [];
171
+ const tags = options.tags ? options.tags.split(',') : [];
172
+ const newSLO = {
173
+ id: `slo_${Date.now()}`,
174
+ name: options.name,
175
+ sliIds,
176
+ target: options.target,
177
+ windowMs,
178
+ errorBudget: {
179
+ initialBudget: 100 - options.target,
180
+ remainingBudget: 100 - options.target,
181
+ alertThresholds: { warning: 2, critical: 5 },
182
+ onExhaustion: 'alert_only'
183
+ },
184
+ status: 'unknown',
185
+ tags,
186
+ createdAt: Date.now(),
187
+ updatedAt: Date.now()
188
+ };
189
+ console.log('\n✅ SLO Created\n');
190
+ console.log(` ID: ${newSLO.id}`);
191
+ console.log(` Name: ${newSLO.name}`);
192
+ console.log(` Target: ${newSLO.target}%`);
193
+ console.log(` Window: ${formatDuration(windowMs)}`);
194
+ console.log(` SLIs: ${sliIds.join(', ') || 'None'}`);
195
+ console.log(` Tags: ${tags.join(', ') || 'None'}`);
196
+ console.log('');
197
+ });
198
+ // Validate configuration
199
+ sloCommand
200
+ .command('validate')
201
+ .description('Validate SLO configuration')
202
+ .option('--mode <mode>', 'Configuration mode: default, strict, relaxed', 'default')
203
+ .action(async (options) => {
204
+ let config;
205
+ switch (options.mode) {
206
+ case 'strict':
207
+ config = createStrictSLOConfig();
208
+ break;
209
+ case 'relaxed':
210
+ config = createRelaxedSLOConfig();
211
+ break;
212
+ default:
213
+ config = createDefaultSLOConfig();
214
+ }
215
+ console.log(`\n✅ SLO Configuration Validated (${options.mode} mode)\n`);
216
+ console.log(` SLOs defined: ${config.slos.length}`);
217
+ console.log(` SLIs defined: ${config.slis.length}`);
218
+ console.log(` Default window: ${formatDuration(config.defaultWindowMs)}`);
219
+ console.log(` On exhaustion: ${config.defaultOnExhaustion}`);
220
+ console.log('');
221
+ for (const slo of config.slos) {
222
+ console.log(` • ${slo.name} (${slo.target}% target)`);
223
+ }
224
+ console.log('');
225
+ });
226
+ // Generate SLO report
227
+ sloCommand
228
+ .command('report')
229
+ .description('Generate SLO report')
230
+ .option('--period <duration>', 'Report period (e.g., 7d, 30d)', '30d')
231
+ .option('--json', 'Output as JSON')
232
+ .action(async (options) => {
233
+ const config = createDefaultSLOConfig();
234
+ const tracker = new SLOTracker();
235
+ tracker.registerSLOs(config.slos);
236
+ tracker.registerSLIs(config.slis);
237
+ // Add demo measurements
238
+ for (const sli of config.slis) {
239
+ tracker.recordMeasurement({
240
+ sliId: sli.id,
241
+ timestamp: Date.now(),
242
+ value: 92 + Math.random() * 8,
243
+ total: 100,
244
+ good: 92 + Math.floor(Math.random() * 8),
245
+ bad: Math.floor(Math.random() * 8),
246
+ unit: sli.unit,
247
+ windowStart: Date.now() - parseDuration(options.period),
248
+ windowEnd: Date.now()
249
+ });
250
+ }
251
+ const report = tracker.generateReport({
252
+ start: Date.now() - parseDuration(options.period),
253
+ end: Date.now()
254
+ });
255
+ if (options.json) {
256
+ console.log(JSON.stringify(report, null, 2));
257
+ return;
258
+ }
259
+ console.log('\n📊 SLO Report\n');
260
+ console.log(`Period: Last ${options.period}`);
261
+ console.log(`Generated: ${new Date(report.timestamp).toISOString()}`);
262
+ console.log(`Overall Status: ${formatStatus(report.status)}\n`);
263
+ console.log('Summary:');
264
+ console.log(` Total SLOs: ${report.summary.total}`);
265
+ console.log(` ✅ Healthy: ${report.summary.healthy}`);
266
+ console.log(` ⚠️ Warning: ${report.summary.warning}`);
267
+ console.log(` ❌ Breached: ${report.summary.breached}\n`);
268
+ if (report.slos.length > 0) {
269
+ console.log('SLO Details:\n');
270
+ for (const slo of report.slos) {
271
+ const status = slo.status === 'healthy' ? '✅' : slo.status === 'warning' ? '⚠️' : '❌';
272
+ console.log(` ${status} ${slo.name}`);
273
+ console.log(` Target: ${slo.target}% | Current: ${slo.current.toFixed(2)}%`);
274
+ console.log(` Error Budget: ${slo.errorBudget.remaining.toFixed(2)}% remaining`);
275
+ console.log('');
276
+ }
277
+ }
278
+ });
279
+ // Helper functions
280
+ function formatDuration(ms) {
281
+ const days = Math.floor(ms / (24 * 60 * 60 * 1000));
282
+ const hours = Math.floor((ms % (24 * 60 * 60 * 1000)) / (60 * 60 * 1000));
283
+ if (days > 0) {
284
+ return `${days} day${days > 1 ? 's' : ''}`;
285
+ }
286
+ if (hours > 0) {
287
+ return `${hours} hour${hours > 1 ? 's' : ''}`;
288
+ }
289
+ return `${ms}ms`;
290
+ }
291
+ function parseDuration(str) {
292
+ const match = str.match(/^(\d+)([dh])$/);
293
+ if (!match) {
294
+ return TimeWindows.MONTH; // Default
295
+ }
296
+ const value = parseInt(match[1], 10);
297
+ const unit = match[2];
298
+ switch (unit) {
299
+ case 'd':
300
+ return value * 24 * 60 * 60 * 1000;
301
+ case 'h':
302
+ return value * 60 * 60 * 1000;
303
+ default:
304
+ return TimeWindows.MONTH;
305
+ }
306
+ }
307
+ function createBudgetBar(remaining) {
308
+ const width = 20;
309
+ const filled = Math.round((remaining / 100) * width);
310
+ const bar = '█'.repeat(Math.max(0, filled)) + '░'.repeat(Math.max(0, width - filled));
311
+ const color = remaining > 50 ? '\x1b[32m' : remaining > 20 ? '\x1b[33m' : '\x1b[31m';
312
+ const reset = '\x1b[0m';
313
+ return `${color}[${bar}]${reset}`;
314
+ }
315
+ function formatStatus(status) {
316
+ switch (status) {
317
+ case 'healthy':
318
+ return '✅ Healthy';
319
+ case 'warning':
320
+ return '⚠️ Warning';
321
+ case 'breached':
322
+ return '❌ Breached';
323
+ default:
324
+ return '❓ Unknown';
325
+ }
326
+ }
327
+ export default sloCommand;
@@ -0,0 +1,183 @@
1
+ /**
2
+ * QA360 Playwright Native API Adapter
3
+ *
4
+ * Uses Playwright's native APIRequestContext for HTTP testing.
5
+ * No browser launch required - lighter and faster than browser-based approach.
6
+ *
7
+ * @see https://playwright.dev/docs/api/class-apirequestcontext
8
+ *
9
+ * Benefits over browser-based approach:
10
+ * - No browser overhead (faster startup, lower memory)
11
+ * - Direct HTTP requests
12
+ * - Same API testing capabilities
13
+ * - Better for CI/CD environments
14
+ */
15
+ import { ApiTarget, PackBudgets } from '../types/pack-v1.js';
16
+ import { AuthCredentials } from '../auth/index.js';
17
+ export interface NativeApiTestConfig {
18
+ target: ApiTarget;
19
+ budgets?: PackBudgets;
20
+ timeout?: number;
21
+ retries?: number;
22
+ auth?: AuthCredentials;
23
+ /**
24
+ * Basic auth credentials (username/password)
25
+ */
26
+ basicAuth?: {
27
+ username: string;
28
+ password: string;
29
+ };
30
+ /**
31
+ * Request body for POST/PUT/PATCH
32
+ */
33
+ body?: Record<string, unknown> | string;
34
+ /**
35
+ * Storage state to persist cookies/auth between requests
36
+ */
37
+ storageState?: Record<string, unknown> | string;
38
+ /**
39
+ * Cache configuration for HTTP responses
40
+ */
41
+ cache?: {
42
+ /** Enable caching (default: false) */
43
+ enabled?: boolean;
44
+ /** TTL in milliseconds (default: 300000 = 5 minutes) */
45
+ ttl?: number;
46
+ /** Maximum cache size in bytes (default: 10MB) */
47
+ maxSizeBytes?: number;
48
+ /** Maximum number of entries (default: 100) */
49
+ maxSize?: number;
50
+ };
51
+ }
52
+ export interface ApiTestResult {
53
+ endpoint: string;
54
+ method: string;
55
+ status: number;
56
+ responseTime: number;
57
+ success: boolean;
58
+ error?: string;
59
+ headers?: Record<string, string>;
60
+ body?: any;
61
+ attempts?: number;
62
+ }
63
+ export interface NativeApiSmokeResult {
64
+ success: boolean;
65
+ results: ApiTestResult[];
66
+ summary: {
67
+ total: number;
68
+ passed: number;
69
+ failed: number;
70
+ avgResponseTime: number;
71
+ totalAttempts: number;
72
+ };
73
+ junit?: string;
74
+ error?: string;
75
+ }
76
+ /**
77
+ * Playwright Native API Adapter
78
+ *
79
+ * Uses playwright.request.newContext() for direct HTTP requests
80
+ * without launching a browser.
81
+ */
82
+ export declare class PlaywrightNativeApiAdapter {
83
+ private requestContext?;
84
+ private redactor;
85
+ private auth?;
86
+ private cache?;
87
+ constructor();
88
+ /**
89
+ * Set authentication credentials for requests
90
+ */
91
+ setAuth(credentials?: AuthCredentials): void;
92
+ /**
93
+ * Execute API smoke tests using Playwright's native API
94
+ */
95
+ runSmokeTests(config: NativeApiTestConfig): Promise<NativeApiSmokeResult>;
96
+ /**
97
+ * Setup Playwright APIRequestContext for HTTP requests
98
+ */
99
+ private setupRequestContext;
100
+ /**
101
+ * Execute single API test with retry logic using native API
102
+ */
103
+ private executeApiTest;
104
+ /**
105
+ * Parse test specification string
106
+ * Format: "GET /path -> 200" or "POST /api/users -> 201"
107
+ */
108
+ private parseTestSpec;
109
+ /**
110
+ * Check if error is retryable
111
+ */
112
+ private isRetryableError;
113
+ /**
114
+ * Calculate test summary
115
+ */
116
+ private calculateSummary;
117
+ /**
118
+ * Generate JUnit XML fragment
119
+ */
120
+ private generateJUnit;
121
+ /**
122
+ * Escape XML special characters
123
+ */
124
+ private escapeXml;
125
+ /**
126
+ * Cleanup request context
127
+ */
128
+ private cleanup;
129
+ /**
130
+ * Validate API target configuration
131
+ */
132
+ static validateConfig(target: ApiTarget): {
133
+ valid: boolean;
134
+ errors: string[];
135
+ };
136
+ /**
137
+ * Health check using native API
138
+ * Quick check if the API is accessible without running full tests
139
+ */
140
+ healthCheck(baseUrl: string, path?: string, timeout?: number): Promise<boolean>;
141
+ /**
142
+ * Get API info (useful for debugging)
143
+ * Returns version and capabilities
144
+ */
145
+ static getCapabilities(): {
146
+ adapter: string;
147
+ browserless: boolean;
148
+ features: string[];
149
+ };
150
+ /**
151
+ * Get cache statistics if cache is enabled
152
+ */
153
+ getCacheStats(): {
154
+ enabled: boolean;
155
+ stats?: {
156
+ hits: number;
157
+ misses: number;
158
+ additions: number;
159
+ evictions: number;
160
+ size: number;
161
+ sizeBytes: number;
162
+ hitRate: number;
163
+ avgEntrySize: number;
164
+ };
165
+ };
166
+ /**
167
+ * Clear the cache if enabled
168
+ */
169
+ clearCache(): void;
170
+ }
171
+ /**
172
+ * Factory function to create a native API adapter
173
+ */
174
+ export declare function createPlaywrightNativeApiAdapter(): PlaywrightNativeApiAdapter;
175
+ /**
176
+ * Error class for native API adapter
177
+ */
178
+ export declare class PlaywrightNativeApiError extends Error {
179
+ code: string;
180
+ endpoint?: string;
181
+ method?: string;
182
+ constructor(message: string, endpoint?: string, method?: string);
183
+ }