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,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
|
+
}
|