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
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* QA360 Phase 3 Runner
|
|
3
3
|
* Orchestrates hooks, adapters, and proof generation
|
|
4
|
+
* Supports both Pack v1 and Pack v2 configurations
|
|
4
5
|
*/
|
|
5
6
|
import { existsSync, writeFileSync, mkdirSync } from 'fs';
|
|
6
7
|
import { join } from 'path';
|
|
7
8
|
import { createHash } from 'crypto';
|
|
8
9
|
import chalk from 'chalk';
|
|
9
10
|
import { HooksRunner } from '../hooks/runner.js';
|
|
10
|
-
import {
|
|
11
|
+
import { PlaywrightNativeApiAdapter } from '../adapters/playwright-native-api.js';
|
|
11
12
|
import { PlaywrightUiAdapter } from '../adapters/playwright-ui.js';
|
|
12
13
|
import { K6PerfAdapter } from '../adapters/k6-perf.js';
|
|
13
14
|
import { SemgrepSastAdapter } from '../adapters/semgrep-sast.js';
|
|
@@ -15,6 +16,8 @@ import { SecurityRedactor } from '../security/redactor.js';
|
|
|
15
16
|
import { initializeKeys, sign } from '../proof/signer.js';
|
|
16
17
|
import { canonicalize } from '../proof/canonicalize.js';
|
|
17
18
|
import { EvidenceVault } from '../vault/index.js';
|
|
19
|
+
import { AuthManager } from '../auth/index.js';
|
|
20
|
+
import { FlakinessDetector, FlakinessCategory, formatFlakinessScore, generateTestId, DEFAULT_FLAKINESS_OPTIONS } from '../flakiness/index.js';
|
|
18
21
|
export class Phase3Runner {
|
|
19
22
|
workingDir;
|
|
20
23
|
pack;
|
|
@@ -23,25 +26,165 @@ export class Phase3Runner {
|
|
|
23
26
|
hooksRunner;
|
|
24
27
|
keyPair;
|
|
25
28
|
vault;
|
|
29
|
+
authManager;
|
|
30
|
+
authCredentialsCache = new Map();
|
|
31
|
+
flakyDetect;
|
|
32
|
+
flakyRuns;
|
|
33
|
+
flakinessDetector;
|
|
26
34
|
constructor(options) {
|
|
27
35
|
this.workingDir = options.workingDir;
|
|
28
36
|
this.pack = options.pack;
|
|
29
37
|
this.outputDir = options.outputDir || join(this.workingDir, '.qa360', 'runs');
|
|
30
38
|
this.redactor = SecurityRedactor.forLogs();
|
|
39
|
+
this.authManager = new AuthManager();
|
|
40
|
+
this.flakyDetect = options.flakyDetect || false;
|
|
41
|
+
this.flakyRuns = options.flakyRuns || DEFAULT_FLAKINESS_OPTIONS.consecutiveRuns;
|
|
42
|
+
this.flakinessDetector = new FlakinessDetector({
|
|
43
|
+
consecutiveRuns: this.flakyRuns,
|
|
44
|
+
minRuns: 2,
|
|
45
|
+
enablePatternDetection: true
|
|
46
|
+
});
|
|
47
|
+
// Initialize hooks runner (convert v2 hooks to v1 format if needed)
|
|
48
|
+
const hooks = this.isPackV2(options.pack)
|
|
49
|
+
? this.convertV2HooksToV1(options.pack.hooks)
|
|
50
|
+
: options.pack.hooks;
|
|
31
51
|
this.hooksRunner = new HooksRunner({
|
|
32
52
|
workingDir: this.workingDir,
|
|
33
|
-
hooks:
|
|
34
|
-
execution: this.pack.execution,
|
|
53
|
+
hooks: hooks || {},
|
|
54
|
+
execution: this.isPackV2(options.pack) ? this.convertV2ExecutionToV1(options.pack.execution) : options.pack.execution,
|
|
35
55
|
redactor: this.redactor
|
|
36
56
|
});
|
|
57
|
+
// Register auth profiles from pack v2
|
|
58
|
+
if (this.isPackV2(options.pack) && options.pack.auth?.profiles) {
|
|
59
|
+
this.registerAuthProfiles(options.pack.auth.profiles);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Type guard to check if pack is v2
|
|
64
|
+
*/
|
|
65
|
+
isPackV2(pack) {
|
|
66
|
+
return pack.version === 2;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Register authentication profiles from pack config
|
|
70
|
+
*/
|
|
71
|
+
registerAuthProfiles(profiles) {
|
|
72
|
+
for (const [name, profile] of Object.entries(profiles)) {
|
|
73
|
+
// Convert v2 auth profile to auth module format
|
|
74
|
+
const authConfig = {
|
|
75
|
+
type: profile.type || 'none',
|
|
76
|
+
...profile.config
|
|
77
|
+
};
|
|
78
|
+
this.authManager.registerProfile(name, authConfig);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Get auth profile name for a specific gate
|
|
83
|
+
*/
|
|
84
|
+
getAuthProfileForGate(gateName) {
|
|
85
|
+
if (!this.isPackV2(this.pack) || !this.pack.auth) {
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
const authConfig = this.pack.auth;
|
|
89
|
+
// Check if gate has specific auth override
|
|
90
|
+
const gateConfig = this.pack.gates[gateName];
|
|
91
|
+
if (gateConfig?.auth) {
|
|
92
|
+
return gateConfig.auth;
|
|
93
|
+
}
|
|
94
|
+
// Use default based on gate type
|
|
95
|
+
if (gateName === 'api_smoke' || gateName === 'api') {
|
|
96
|
+
return authConfig.api;
|
|
97
|
+
}
|
|
98
|
+
if (gateName === 'ui' || gateName === 'a11y') {
|
|
99
|
+
return authConfig.ui;
|
|
100
|
+
}
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Authenticate and get credentials for a gate
|
|
105
|
+
*/
|
|
106
|
+
async getCredentialsForGate(gateName) {
|
|
107
|
+
const profileName = this.getAuthProfileForGate(gateName);
|
|
108
|
+
if (!profileName) {
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
// Check cache first
|
|
112
|
+
if (this.authCredentialsCache.has(profileName)) {
|
|
113
|
+
return this.authCredentialsCache.get(profileName);
|
|
114
|
+
}
|
|
115
|
+
// Authenticate
|
|
116
|
+
const result = await this.authManager.authenticate(profileName);
|
|
117
|
+
if (result.success && result.credentials) {
|
|
118
|
+
this.authCredentialsCache.set(profileName, result.credentials);
|
|
119
|
+
return result.credentials;
|
|
120
|
+
}
|
|
121
|
+
console.log(chalk.yellow(` ⚠️ Auth failed for profile '${profileName}': ${result.error}`));
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Convert v2 hooks to v1 format
|
|
126
|
+
*/
|
|
127
|
+
convertV2HooksToV1(hooks) {
|
|
128
|
+
if (!hooks)
|
|
129
|
+
return undefined;
|
|
130
|
+
// v2 hooks use { type, command, ... } format
|
|
131
|
+
// v1 hooks use { run, timeout } format
|
|
132
|
+
const converted = {
|
|
133
|
+
beforeAll: [],
|
|
134
|
+
afterAll: [],
|
|
135
|
+
beforeEach: [],
|
|
136
|
+
afterEach: []
|
|
137
|
+
};
|
|
138
|
+
for (const [phase, phaseHooks] of Object.entries(hooks)) {
|
|
139
|
+
if (Array.isArray(phaseHooks)) {
|
|
140
|
+
converted[phase] = phaseHooks.map((h) => {
|
|
141
|
+
if (h.type === 'run' || h.type === 'script') {
|
|
142
|
+
return { run: h.command, timeout: h.timeout, cwd: h.cwd, env: h.env };
|
|
143
|
+
}
|
|
144
|
+
if (h.type === 'wait_on') {
|
|
145
|
+
return { run: `npx wait-on ${h.wait_for?.resource || h.command}`, timeout: h.timeout };
|
|
146
|
+
}
|
|
147
|
+
if (h.type === 'docker') {
|
|
148
|
+
return { run: `docker compose ${h.compose?.command || 'up -d'}`, timeout: h.timeout };
|
|
149
|
+
}
|
|
150
|
+
return h;
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return converted;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Convert v2 execution config to v1 format
|
|
158
|
+
*/
|
|
159
|
+
convertV2ExecutionToV1(execution) {
|
|
160
|
+
if (!execution)
|
|
161
|
+
return undefined;
|
|
162
|
+
return {
|
|
163
|
+
timeout: execution.default_timeout || execution.timeout,
|
|
164
|
+
max_retries: execution.default_retries || execution.retries,
|
|
165
|
+
on_failure: execution.on_failure || 'continue'
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Get gates array from pack (handles v1 and v2)
|
|
170
|
+
*/
|
|
171
|
+
getGatesArray() {
|
|
172
|
+
if (this.isPackV2(this.pack)) {
|
|
173
|
+
return Object.keys(this.pack.gates).filter(gateName => {
|
|
174
|
+
const gateConfig = this.pack.gates[gateName];
|
|
175
|
+
return gateConfig?.enabled !== false;
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
return this.pack.gates || [];
|
|
37
179
|
}
|
|
38
180
|
/**
|
|
39
181
|
* Execute complete Phase 3 workflow
|
|
40
182
|
*/
|
|
41
183
|
async run() {
|
|
42
184
|
const startTime = Date.now();
|
|
185
|
+
const gatesArray = this.getGatesArray();
|
|
43
186
|
console.log(chalk.bold.blue(`\n🚀 QA360 Phase 3 Runner - ${this.pack.name}`));
|
|
44
|
-
console.log(chalk.gray(`Gates: ${
|
|
187
|
+
console.log(chalk.gray(`Gates: ${gatesArray.join(', ')}`));
|
|
45
188
|
try {
|
|
46
189
|
// Ensure output directory exists
|
|
47
190
|
this.ensureOutputDir();
|
|
@@ -65,7 +208,7 @@ export class Phase3Runner {
|
|
|
65
208
|
// Execute gates
|
|
66
209
|
console.log(chalk.blue('\n🎯 Phase 2: Quality Gates'));
|
|
67
210
|
const gateResults = [];
|
|
68
|
-
for (const gate of
|
|
211
|
+
for (const gate of gatesArray) {
|
|
69
212
|
const gateResult = await this.executeGate(gate);
|
|
70
213
|
gateResults.push(gateResult);
|
|
71
214
|
// Check if gate failed and should stop
|
|
@@ -74,6 +217,20 @@ export class Phase3Runner {
|
|
|
74
217
|
break;
|
|
75
218
|
}
|
|
76
219
|
}
|
|
220
|
+
// Flakiness Detection Phase (Vision 2.0)
|
|
221
|
+
let flakinessResults;
|
|
222
|
+
if (this.flakyDetect) {
|
|
223
|
+
console.log(chalk.blue(`\n🎲 Phase 2.5: Flakiness Detection (${this.flakyRuns} consecutive runs)`));
|
|
224
|
+
flakinessResults = await this.detectFlakiness(gateResults);
|
|
225
|
+
// Display flakiness summary
|
|
226
|
+
const unstableCount = flakinessResults.filter(f => f.category === FlakinessCategory.UNSTABLE || f.category === FlakinessCategory.SHAKY).length;
|
|
227
|
+
if (unstableCount > 0) {
|
|
228
|
+
console.log(chalk.yellow(` ⚠️ ${unstableCount} test(s) show flaky behavior`));
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
console.log(chalk.green(' ✅ All tests stable - no flakiness detected'));
|
|
232
|
+
}
|
|
233
|
+
}
|
|
77
234
|
// Execute afterAll hooks
|
|
78
235
|
console.log(chalk.blue('\n🔗 Phase 3: Cleanup Hooks'));
|
|
79
236
|
const afterAllResults = await this.hooksRunner.executeHooks('afterAll');
|
|
@@ -98,8 +255,8 @@ export class Phase3Runner {
|
|
|
98
255
|
});
|
|
99
256
|
// Save to Evidence Vault
|
|
100
257
|
console.log(chalk.blue('\n💾 Phase 5: Evidence Storage'));
|
|
101
|
-
const
|
|
102
|
-
await this.saveToVault(
|
|
258
|
+
const proofRunId = proofPath.split('/').pop()?.replace('-proof.json', '') || 'unknown';
|
|
259
|
+
const vaultRunId = await this.saveToVault(proofRunId, {
|
|
103
260
|
success,
|
|
104
261
|
pack: this.pack,
|
|
105
262
|
duration,
|
|
@@ -112,13 +269,31 @@ export class Phase3Runner {
|
|
|
112
269
|
},
|
|
113
270
|
summary,
|
|
114
271
|
proofPath
|
|
115
|
-
}, proofPath);
|
|
272
|
+
}, proofPath, flakinessResults);
|
|
116
273
|
// Final summary
|
|
117
274
|
console.log(chalk.blue('\n📊 Execution Summary'));
|
|
118
275
|
console.log(` Duration: ${duration}ms`);
|
|
119
276
|
console.log(` Gates: ${summary.passed}/${summary.total} passed`);
|
|
120
277
|
console.log(` Trust Score: ${summary.trustScore}%`);
|
|
121
278
|
console.log(` Proof: ${proofPath}`);
|
|
279
|
+
// Display flakiness details if enabled
|
|
280
|
+
if (flakinessResults && flakinessResults.length > 0) {
|
|
281
|
+
console.log(chalk.blue('\n🎲 Flakiness Scores:'));
|
|
282
|
+
for (const f of flakinessResults) {
|
|
283
|
+
const scoreFormatted = formatFlakinessScore(f.score);
|
|
284
|
+
const categoryMeta = {
|
|
285
|
+
[FlakinessCategory.LEGENDARY]: { emoji: '🟢', color: chalk.green },
|
|
286
|
+
[FlakinessCategory.SOLID]: { emoji: '🟢', color: chalk.green },
|
|
287
|
+
[FlakinessCategory.GOOD]: { emoji: '🟡', color: chalk.yellow },
|
|
288
|
+
[FlakinessCategory.SHAKY]: { emoji: '🟠', color: chalk.hex('#F97316') },
|
|
289
|
+
[FlakinessCategory.UNSTABLE]: { emoji: '🔴', color: chalk.red }
|
|
290
|
+
}[f.category];
|
|
291
|
+
console.log(` ${categoryMeta.color(`${f.testName}: ${scoreFormatted}`)} (${f.successfulRuns}/${f.totalRuns} passes)`);
|
|
292
|
+
if (f.suggestedFix) {
|
|
293
|
+
console.log(chalk.gray(` 💡 ${f.suggestedFix}`));
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
122
297
|
if (success) {
|
|
123
298
|
console.log(chalk.green('\n✅ All quality gates passed!'));
|
|
124
299
|
}
|
|
@@ -137,7 +312,9 @@ export class Phase3Runner {
|
|
|
137
312
|
afterAll: afterAllResults
|
|
138
313
|
},
|
|
139
314
|
summary,
|
|
140
|
-
|
|
315
|
+
flakiness: flakinessResults,
|
|
316
|
+
proofPath,
|
|
317
|
+
runId: vaultRunId
|
|
141
318
|
};
|
|
142
319
|
}
|
|
143
320
|
catch (error) {
|
|
@@ -169,9 +346,17 @@ export class Phase3Runner {
|
|
|
169
346
|
try {
|
|
170
347
|
let result;
|
|
171
348
|
let adapter;
|
|
349
|
+
// Check if this is a v2 pack with dynamic gate configuration
|
|
350
|
+
if (this.isPackV2(this.pack)) {
|
|
351
|
+
const gateConfig = this.pack.gates[gate];
|
|
352
|
+
if (gateConfig) {
|
|
353
|
+
return await this.executeDynamicGate(gate, gateConfig);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
// Legacy v1 gates or predefined gates
|
|
172
357
|
switch (gate) {
|
|
173
358
|
case 'api_smoke':
|
|
174
|
-
adapter = 'playwright-api';
|
|
359
|
+
adapter = 'playwright-native-api';
|
|
175
360
|
result = await this.runApiSmokeGate();
|
|
176
361
|
break;
|
|
177
362
|
case 'ui':
|
|
@@ -231,33 +416,184 @@ export class Phase3Runner {
|
|
|
231
416
|
};
|
|
232
417
|
}
|
|
233
418
|
}
|
|
419
|
+
/**
|
|
420
|
+
* Execute a dynamic v2 gate
|
|
421
|
+
*/
|
|
422
|
+
async executeDynamicGate(gateName, gateConfig) {
|
|
423
|
+
const startTime = Date.now();
|
|
424
|
+
const adapterType = gateConfig.adapter || gateConfig.type;
|
|
425
|
+
if (!adapterType) {
|
|
426
|
+
throw new Error(`Gate '${gateName}' must specify an adapter`);
|
|
427
|
+
}
|
|
428
|
+
// Get auth credentials for this gate
|
|
429
|
+
const credentials = await this.getCredentialsForGate(gateName);
|
|
430
|
+
// Map adapter type to implementation
|
|
431
|
+
let result;
|
|
432
|
+
switch (adapterType) {
|
|
433
|
+
case 'playwright-api':
|
|
434
|
+
result = await this.executePlaywrightApiGate(gateName, gateConfig, credentials);
|
|
435
|
+
break;
|
|
436
|
+
case 'playwright-ui':
|
|
437
|
+
result = await this.executePlaywrightUiGate(gateName, gateConfig, credentials);
|
|
438
|
+
break;
|
|
439
|
+
case 'k6':
|
|
440
|
+
case 'k6-perf':
|
|
441
|
+
result = await this.executeK6PerfGate(gateName, gateConfig);
|
|
442
|
+
break;
|
|
443
|
+
case 'semgrep':
|
|
444
|
+
case 'sast':
|
|
445
|
+
result = await this.executeSemgrepSastGate(gateName, gateConfig);
|
|
446
|
+
break;
|
|
447
|
+
default:
|
|
448
|
+
throw new Error(`Unsupported adapter: '${adapterType}' for gate '${gateName}'`);
|
|
449
|
+
}
|
|
450
|
+
// Set duration
|
|
451
|
+
result.duration = Date.now() - startTime;
|
|
452
|
+
// Log result
|
|
453
|
+
if (result.success) {
|
|
454
|
+
console.log(chalk.green(` ✅ ${gateName} passed (${result.duration}ms)`));
|
|
455
|
+
}
|
|
456
|
+
else {
|
|
457
|
+
console.log(chalk.red(` ❌ ${gateName} failed (${result.duration}ms)`));
|
|
458
|
+
if (result.error) {
|
|
459
|
+
console.log(chalk.red(` 🔍 ${result.error}`));
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
return result;
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Execute Playwright API gate with v2 config
|
|
466
|
+
* Uses PlaywrightNativeApiAdapter for zero-overhead HTTP testing
|
|
467
|
+
*/
|
|
468
|
+
async executePlaywrightApiGate(gateName, gateConfig, credentials) {
|
|
469
|
+
const { PlaywrightNativeApiAdapter } = await import('../adapters/playwright-native-api.js');
|
|
470
|
+
const adapter = new PlaywrightNativeApiAdapter();
|
|
471
|
+
// Transform v2 config to adapter format
|
|
472
|
+
const gateConfigData = gateConfig.config || {};
|
|
473
|
+
const config = {
|
|
474
|
+
target: {
|
|
475
|
+
baseUrl: gateConfigData.baseUrl,
|
|
476
|
+
smoke: gateConfigData.smoke
|
|
477
|
+
},
|
|
478
|
+
budgets: gateConfigData.budgets,
|
|
479
|
+
timeout: gateConfigData.timeout,
|
|
480
|
+
retries: gateConfigData.retries,
|
|
481
|
+
auth: credentials
|
|
482
|
+
};
|
|
483
|
+
const result = await adapter.runSmokeTests(config);
|
|
484
|
+
return {
|
|
485
|
+
gate: gateName,
|
|
486
|
+
success: result.success,
|
|
487
|
+
duration: 0, // Will be set by caller
|
|
488
|
+
adapter: 'playwright-native-api',
|
|
489
|
+
results: result,
|
|
490
|
+
junit: result.junit
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Execute Playwright UI gate with v2 config
|
|
495
|
+
*/
|
|
496
|
+
async executePlaywrightUiGate(gateName, gateConfig, credentials) {
|
|
497
|
+
const { PlaywrightUiAdapter } = await import('../adapters/playwright-ui.js');
|
|
498
|
+
const adapter = new PlaywrightUiAdapter();
|
|
499
|
+
// Transform v2 config to adapter format
|
|
500
|
+
// v2: { baseUrl, pages } -> adapter expects: { target: { baseUrl, pages } }
|
|
501
|
+
const gateConfigData = gateConfig.config || {};
|
|
502
|
+
const pages = gateConfigData.pages ||
|
|
503
|
+
(gateConfigData.baseUrl ? [gateConfigData.baseUrl] : undefined);
|
|
504
|
+
const config = {
|
|
505
|
+
target: {
|
|
506
|
+
baseUrl: gateConfigData.baseUrl,
|
|
507
|
+
pages: pages
|
|
508
|
+
},
|
|
509
|
+
budgets: gateConfigData.budgets,
|
|
510
|
+
timeout: gateConfigData.timeout,
|
|
511
|
+
auth: credentials
|
|
512
|
+
};
|
|
513
|
+
const result = await adapter.runSmokeTests(config);
|
|
514
|
+
return {
|
|
515
|
+
gate: gateName,
|
|
516
|
+
success: result.success,
|
|
517
|
+
duration: 0, // Will be set by caller
|
|
518
|
+
adapter: 'playwright-ui',
|
|
519
|
+
results: result,
|
|
520
|
+
junit: result.junit
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* Execute K6 Performance gate with v2 config
|
|
525
|
+
*/
|
|
526
|
+
async executeK6PerfGate(gateName, gateConfig) {
|
|
527
|
+
const { K6PerfAdapter } = await import('../adapters/k6-perf.js');
|
|
528
|
+
const adapter = new K6PerfAdapter(this.workingDir);
|
|
529
|
+
const config = {
|
|
530
|
+
baseUrl: gateConfig.config?.baseUrl,
|
|
531
|
+
...gateConfig.config
|
|
532
|
+
};
|
|
533
|
+
const result = await adapter.runPerfTest(config);
|
|
534
|
+
return {
|
|
535
|
+
gate: gateName,
|
|
536
|
+
success: result.success,
|
|
537
|
+
duration: 0,
|
|
538
|
+
adapter: 'k6',
|
|
539
|
+
results: result,
|
|
540
|
+
junit: result.junit
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* Execute Semgrep SAST gate with v2 config
|
|
545
|
+
*/
|
|
546
|
+
async executeSemgrepSastGate(gateName, gateConfig) {
|
|
547
|
+
const { SemgrepSastAdapter } = await import('../adapters/semgrep-sast.js');
|
|
548
|
+
const adapter = new SemgrepSastAdapter();
|
|
549
|
+
const config = {
|
|
550
|
+
workingDir: this.workingDir,
|
|
551
|
+
...gateConfig.config
|
|
552
|
+
};
|
|
553
|
+
const result = await adapter.runSastScan(config);
|
|
554
|
+
return {
|
|
555
|
+
gate: gateName,
|
|
556
|
+
success: result.success,
|
|
557
|
+
duration: 0,
|
|
558
|
+
adapter: 'semgrep',
|
|
559
|
+
results: result,
|
|
560
|
+
junit: result.junit
|
|
561
|
+
};
|
|
562
|
+
}
|
|
234
563
|
/**
|
|
235
564
|
* Run API smoke gate
|
|
565
|
+
* Uses PlaywrightNativeApiAdapter for zero-overhead HTTP testing
|
|
236
566
|
*/
|
|
237
567
|
async runApiSmokeGate() {
|
|
238
|
-
|
|
239
|
-
|
|
568
|
+
const target = this.getTargetApi();
|
|
569
|
+
if (!target) {
|
|
570
|
+
throw new Error('API smoke gate requires targets.api configuration or gate config with baseUrl');
|
|
240
571
|
}
|
|
241
|
-
const
|
|
572
|
+
const credentials = await this.getCredentialsForGate('api_smoke');
|
|
573
|
+
const adapter = new PlaywrightNativeApiAdapter();
|
|
242
574
|
return await adapter.runSmokeTests({
|
|
243
|
-
target
|
|
244
|
-
budgets: this.
|
|
245
|
-
timeout: this.
|
|
246
|
-
retries: this.
|
|
575
|
+
target,
|
|
576
|
+
budgets: this.getBudgets(),
|
|
577
|
+
timeout: this.getExecutionTimeout() || 10000,
|
|
578
|
+
retries: this.getExecutionRetries() || 1,
|
|
579
|
+
auth: credentials
|
|
247
580
|
});
|
|
248
581
|
}
|
|
249
582
|
/**
|
|
250
583
|
* Run UI gate (includes basic accessibility)
|
|
251
584
|
*/
|
|
252
585
|
async runUiGate() {
|
|
253
|
-
|
|
254
|
-
|
|
586
|
+
const target = this.getTargetWeb();
|
|
587
|
+
if (!target) {
|
|
588
|
+
throw new Error('UI gate requires targets.web configuration or gate config with baseUrl');
|
|
255
589
|
}
|
|
590
|
+
const credentials = await this.getCredentialsForGate('ui');
|
|
256
591
|
const adapter = new PlaywrightUiAdapter();
|
|
257
592
|
return await adapter.runSmokeTests({
|
|
258
|
-
target
|
|
259
|
-
budgets: this.
|
|
260
|
-
timeout: this.
|
|
593
|
+
target,
|
|
594
|
+
budgets: this.getBudgets(),
|
|
595
|
+
timeout: this.getExecutionTimeout() || 30000,
|
|
596
|
+
auth: credentials
|
|
261
597
|
});
|
|
262
598
|
}
|
|
263
599
|
/**
|
|
@@ -415,11 +751,12 @@ export class Phase3Runner {
|
|
|
415
751
|
}
|
|
416
752
|
/**
|
|
417
753
|
* Save run results to Evidence Vault
|
|
754
|
+
* @returns The vault run ID
|
|
418
755
|
*/
|
|
419
|
-
async saveToVault(runId, result, proofPath) {
|
|
756
|
+
async saveToVault(runId, result, proofPath, flakinessResults) {
|
|
420
757
|
if (!this.vault) {
|
|
421
758
|
console.log(chalk.yellow(' ⚠️ Vault not initialized, skipping storage'));
|
|
422
|
-
return;
|
|
759
|
+
return undefined;
|
|
423
760
|
}
|
|
424
761
|
try {
|
|
425
762
|
// Begin run in vault (returns actual runId used)
|
|
@@ -451,11 +788,152 @@ export class Phase3Runner {
|
|
|
451
788
|
});
|
|
452
789
|
}
|
|
453
790
|
}
|
|
791
|
+
// Record flakiness results if available
|
|
792
|
+
if (flakinessResults && flakinessResults.length > 0) {
|
|
793
|
+
for (const flaky of flakinessResults) {
|
|
794
|
+
// Record unstable/shaky tests as findings
|
|
795
|
+
if (flaky.category === FlakinessCategory.UNSTABLE || flaky.category === FlakinessCategory.SHAKY) {
|
|
796
|
+
await this.vault.recordFinding(vaultRunId, {
|
|
797
|
+
gate: flaky.gate,
|
|
798
|
+
severity: flaky.category === FlakinessCategory.UNSTABLE ? 'high' : 'medium',
|
|
799
|
+
rule: 'flaky-test-detected',
|
|
800
|
+
message: `Test "${flaky.testName}" is ${flaky.category}: ${flaky.score}% reliability (${flaky.successfulRuns}/${flaky.totalRuns} passes)`
|
|
801
|
+
});
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
console.log(chalk.green(` 💾 ${flakinessResults.length} flakiness analysis(es) saved`));
|
|
805
|
+
}
|
|
454
806
|
console.log(chalk.green(' 💾 Run saved to Evidence Vault'));
|
|
807
|
+
return vaultRunId;
|
|
455
808
|
}
|
|
456
809
|
catch (error) {
|
|
457
810
|
console.log(chalk.yellow(` ⚠️ Failed to save to vault: ${error}`));
|
|
811
|
+
return undefined;
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
/**
|
|
815
|
+
* Detect flakiness by running tests multiple times consecutively
|
|
816
|
+
* @param gateResults Original gate results from first run
|
|
817
|
+
* @returns Flakiness analysis results
|
|
818
|
+
*/
|
|
819
|
+
async detectFlakiness(gateResults) {
|
|
820
|
+
const flakinessMap = new Map();
|
|
821
|
+
const timestamp = Date.now();
|
|
822
|
+
// First, collect results from the initial run
|
|
823
|
+
for (const gateResult of gateResults) {
|
|
824
|
+
for (const [testId, testResult] of this.extractTestResults(gateResult, timestamp)) {
|
|
825
|
+
if (!flakinessMap.has(testId)) {
|
|
826
|
+
flakinessMap.set(testId, []);
|
|
827
|
+
}
|
|
828
|
+
flakinessMap.get(testId).push(testResult);
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
// Run additional consecutive runs
|
|
832
|
+
for (let run = 1; run < this.flakyRuns; run++) {
|
|
833
|
+
console.log(chalk.gray(` 🔄 Consecutive run ${run + 1}/${this.flakyRuns}...`));
|
|
834
|
+
for (const gateResult of gateResults) {
|
|
835
|
+
// Re-run all gates to detect flakiness
|
|
836
|
+
try {
|
|
837
|
+
const retryResult = await this.executeGate(gateResult.gate);
|
|
838
|
+
// Extract and store test results
|
|
839
|
+
for (const [testId, testResult] of this.extractTestResults(retryResult, timestamp)) {
|
|
840
|
+
if (!flakinessMap.has(testId)) {
|
|
841
|
+
flakinessMap.set(testId, []);
|
|
842
|
+
}
|
|
843
|
+
flakinessMap.get(testId).push(testResult);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
catch (error) {
|
|
847
|
+
console.log(chalk.yellow(` ⚠️ Failed to re-run ${gateResult.gate}: ${error}`));
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
// Analyze all collected results
|
|
852
|
+
return this.flakinessDetector.analyzeAll(flakinessMap);
|
|
853
|
+
}
|
|
854
|
+
/**
|
|
855
|
+
* Extract test results from a gate result
|
|
856
|
+
* Returns a map of testId to TestResult for flakiness tracking
|
|
857
|
+
*/
|
|
858
|
+
extractTestResults(gateResult, timestamp) {
|
|
859
|
+
const results = new Map();
|
|
860
|
+
// Extract test results from adapter output
|
|
861
|
+
const adapterResults = gateResult.results;
|
|
862
|
+
if (adapterResults?.results && Array.isArray(adapterResults.results)) {
|
|
863
|
+
// Check for Playwright Native API adapter format
|
|
864
|
+
const firstResult = adapterResults.results[0];
|
|
865
|
+
if (firstResult && 'endpoint' in firstResult && 'method' in firstResult) {
|
|
866
|
+
// PlaywrightNativeApiAdapter format: { endpoint, method, status, success, error, ... }
|
|
867
|
+
for (const test of adapterResults.results) {
|
|
868
|
+
// Extract just the path from full URL for cleaner test names
|
|
869
|
+
const url = new URL(test.endpoint);
|
|
870
|
+
const path = url.pathname + url.search;
|
|
871
|
+
const testName = `${test.method} ${path} -> ${test.status}`;
|
|
872
|
+
const testId = generateTestId(testName, gateResult.gate);
|
|
873
|
+
results.set(testId, {
|
|
874
|
+
testId,
|
|
875
|
+
testName,
|
|
876
|
+
filePath: gateResult.gate,
|
|
877
|
+
gate: gateResult.gate,
|
|
878
|
+
success: test.success,
|
|
879
|
+
durationMs: test.responseTime || 0,
|
|
880
|
+
errorMessage: test.error,
|
|
881
|
+
timestamp: timestamp + Math.random(), // Small offset for ordering
|
|
882
|
+
environment: process.env.NODE_ENV || 'local'
|
|
883
|
+
});
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
else {
|
|
887
|
+
// Generic adapter format with results array
|
|
888
|
+
for (const test of adapterResults.results) {
|
|
889
|
+
const testName = test.name || gateResult.gate;
|
|
890
|
+
const testId = generateTestId(testName, gateResult.gate);
|
|
891
|
+
results.set(testId, {
|
|
892
|
+
testId,
|
|
893
|
+
testName,
|
|
894
|
+
filePath: gateResult.gate,
|
|
895
|
+
gate: gateResult.gate,
|
|
896
|
+
success: test.passed || test.status === 'passed',
|
|
897
|
+
durationMs: test.duration || 0,
|
|
898
|
+
errorType: test.error?.type,
|
|
899
|
+
errorMessage: test.error?.message,
|
|
900
|
+
timestamp: timestamp + Math.random(),
|
|
901
|
+
environment: process.env.NODE_ENV || 'local'
|
|
902
|
+
});
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
else if (adapterResults?.summary) {
|
|
907
|
+
// Generic adapter with summary only (no detailed results)
|
|
908
|
+
const testId = generateTestId(gateResult.gate, gateResult.gate);
|
|
909
|
+
results.set(testId, {
|
|
910
|
+
testId,
|
|
911
|
+
testName: gateResult.gate,
|
|
912
|
+
filePath: gateResult.gate,
|
|
913
|
+
gate: gateResult.gate,
|
|
914
|
+
success: gateResult.success,
|
|
915
|
+
durationMs: gateResult.duration,
|
|
916
|
+
errorMessage: gateResult.error,
|
|
917
|
+
timestamp,
|
|
918
|
+
environment: process.env.NODE_ENV || 'local'
|
|
919
|
+
});
|
|
458
920
|
}
|
|
921
|
+
else {
|
|
922
|
+
// Fallback: create single test result from gate
|
|
923
|
+
const testId = generateTestId(gateResult.gate, gateResult.gate);
|
|
924
|
+
results.set(testId, {
|
|
925
|
+
testId,
|
|
926
|
+
testName: gateResult.gate,
|
|
927
|
+
filePath: gateResult.gate,
|
|
928
|
+
gate: gateResult.gate,
|
|
929
|
+
success: gateResult.success,
|
|
930
|
+
durationMs: gateResult.duration,
|
|
931
|
+
errorMessage: gateResult.error,
|
|
932
|
+
timestamp,
|
|
933
|
+
environment: process.env.NODE_ENV || 'local'
|
|
934
|
+
});
|
|
935
|
+
}
|
|
936
|
+
return results;
|
|
459
937
|
}
|
|
460
938
|
/**
|
|
461
939
|
* Generate hash of pack configuration
|
|
@@ -466,6 +944,63 @@ export class Phase3Runner {
|
|
|
466
944
|
.digest('hex')
|
|
467
945
|
.substring(0, 16);
|
|
468
946
|
}
|
|
947
|
+
/**
|
|
948
|
+
* Get API target (v1 or v2 format)
|
|
949
|
+
*/
|
|
950
|
+
getTargetApi() {
|
|
951
|
+
if (this.isPackV2(this.pack)) {
|
|
952
|
+
// In v2, target is in the gate config
|
|
953
|
+
const gateConfig = this.pack.gates['api_smoke'] || this.pack.gates['api'];
|
|
954
|
+
if (gateConfig && gateConfig.config?.baseUrl) {
|
|
955
|
+
return gateConfig.config;
|
|
956
|
+
}
|
|
957
|
+
return undefined;
|
|
958
|
+
}
|
|
959
|
+
return this.pack.targets?.api;
|
|
960
|
+
}
|
|
961
|
+
/**
|
|
962
|
+
* Get Web target (v1 or v2 format)
|
|
963
|
+
*/
|
|
964
|
+
getTargetWeb() {
|
|
965
|
+
if (this.isPackV2(this.pack)) {
|
|
966
|
+
// In v2, target is in the gate config
|
|
967
|
+
const gateConfig = this.pack.gates['ui'] || this.pack.gates['a11y'];
|
|
968
|
+
if (gateConfig && gateConfig.config?.baseUrl) {
|
|
969
|
+
return gateConfig.config;
|
|
970
|
+
}
|
|
971
|
+
return undefined;
|
|
972
|
+
}
|
|
973
|
+
return this.pack.targets?.web;
|
|
974
|
+
}
|
|
975
|
+
/**
|
|
976
|
+
* Get budgets (v1 or v2 format)
|
|
977
|
+
*/
|
|
978
|
+
getBudgets() {
|
|
979
|
+
if (this.isPackV2(this.pack)) {
|
|
980
|
+
// In v2, budgets can be in gate config or global
|
|
981
|
+
// For now, return undefined - budgets are gate-specific in v2
|
|
982
|
+
return undefined;
|
|
983
|
+
}
|
|
984
|
+
return this.pack.budgets;
|
|
985
|
+
}
|
|
986
|
+
/**
|
|
987
|
+
* Get execution timeout (v1 or v2 format)
|
|
988
|
+
*/
|
|
989
|
+
getExecutionTimeout() {
|
|
990
|
+
if (this.isPackV2(this.pack)) {
|
|
991
|
+
return this.pack.execution?.default_timeout;
|
|
992
|
+
}
|
|
993
|
+
return this.pack.execution?.timeout;
|
|
994
|
+
}
|
|
995
|
+
/**
|
|
996
|
+
* Get execution retries (v1 or v2 format)
|
|
997
|
+
*/
|
|
998
|
+
getExecutionRetries() {
|
|
999
|
+
if (this.isPackV2(this.pack)) {
|
|
1000
|
+
return this.pack.execution?.default_retries;
|
|
1001
|
+
}
|
|
1002
|
+
return this.pack.execution?.max_retries;
|
|
1003
|
+
}
|
|
469
1004
|
/**
|
|
470
1005
|
* Ensure output directory exists
|
|
471
1006
|
*/
|