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,538 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Self-Healing Engine
|
|
3
|
+
*
|
|
4
|
+
* Main orchestration for self-healing functionality.
|
|
5
|
+
* Coordinates selector healing, assertion healing, and AI assistance.
|
|
6
|
+
*/
|
|
7
|
+
import { SelectorHealer } from './selector-healer.js';
|
|
8
|
+
import { AssertionHealer } from './assertion-healer.js';
|
|
9
|
+
import { createLLMProvider } from '../ai/index.js';
|
|
10
|
+
/**
|
|
11
|
+
* Self-Healing Engine class
|
|
12
|
+
*/
|
|
13
|
+
export class SelfHealingEngine {
|
|
14
|
+
config;
|
|
15
|
+
selectorHealer;
|
|
16
|
+
assertionHealer;
|
|
17
|
+
currentSession;
|
|
18
|
+
constructor(config) {
|
|
19
|
+
this.config = config;
|
|
20
|
+
this.selectorHealer = new SelectorHealer(config);
|
|
21
|
+
this.assertionHealer = new AssertionHealer(config);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Start a new self-healing session
|
|
25
|
+
*/
|
|
26
|
+
startSession() {
|
|
27
|
+
const sessionId = this.generateSessionId();
|
|
28
|
+
const startTime = Date.now();
|
|
29
|
+
this.currentSession = {
|
|
30
|
+
sessionId,
|
|
31
|
+
startTime,
|
|
32
|
+
endTime: 0,
|
|
33
|
+
duration: 0,
|
|
34
|
+
config: this.config,
|
|
35
|
+
selectorHeals: [],
|
|
36
|
+
assertionUpdates: [],
|
|
37
|
+
apiHeals: [],
|
|
38
|
+
success: false,
|
|
39
|
+
unhealed: 0,
|
|
40
|
+
summary: {
|
|
41
|
+
totalIssues: 0,
|
|
42
|
+
healed: 0,
|
|
43
|
+
failed: 0,
|
|
44
|
+
highConfidence: 0,
|
|
45
|
+
mediumConfidence: 0,
|
|
46
|
+
lowConfidence: 0,
|
|
47
|
+
byType: {
|
|
48
|
+
selector: 0,
|
|
49
|
+
assertion: 0,
|
|
50
|
+
api: 0,
|
|
51
|
+
timeout: 0
|
|
52
|
+
},
|
|
53
|
+
successRate: 0
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
return sessionId;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* End current session and return results
|
|
60
|
+
*/
|
|
61
|
+
endSession() {
|
|
62
|
+
if (!this.currentSession) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
this.currentSession.endTime = Date.now();
|
|
66
|
+
this.currentSession.duration = this.currentSession.endTime - this.currentSession.startTime;
|
|
67
|
+
this.currentSession.summary = this.calculateSummary();
|
|
68
|
+
const session = { ...this.currentSession };
|
|
69
|
+
this.currentSession = undefined;
|
|
70
|
+
return session;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Attempt to heal a test failure
|
|
74
|
+
*/
|
|
75
|
+
async healFailure(context) {
|
|
76
|
+
if (!this.config.enabled || !this.currentSession) {
|
|
77
|
+
return {
|
|
78
|
+
success: false,
|
|
79
|
+
healed: false,
|
|
80
|
+
suggestion: null,
|
|
81
|
+
reason: 'Self-healing is disabled or no active session'
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
this.currentSession.summary.totalIssues++;
|
|
85
|
+
// Determine failure type and attempt healing
|
|
86
|
+
switch (context.testType) {
|
|
87
|
+
case 'ui':
|
|
88
|
+
return await this.healUiFailure(context);
|
|
89
|
+
case 'api':
|
|
90
|
+
return await this.healApiFailure(context);
|
|
91
|
+
default:
|
|
92
|
+
return await this.healGenericFailure(context);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Heal a UI test failure (selector issues)
|
|
97
|
+
*/
|
|
98
|
+
async healUiFailure(context) {
|
|
99
|
+
// Extract selector from error message
|
|
100
|
+
const selector = this.extractSelector(context.errorMessage);
|
|
101
|
+
if (!selector) {
|
|
102
|
+
return {
|
|
103
|
+
success: false,
|
|
104
|
+
healed: false,
|
|
105
|
+
suggestion: null,
|
|
106
|
+
reason: 'Could not extract selector from error message'
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
const errorType = this.inferSelectorErrorType(context.errorMessage);
|
|
110
|
+
const result = await this.selectorHealer.healSelector({
|
|
111
|
+
originalSelector: selector,
|
|
112
|
+
pageUrl: context.pageUrl,
|
|
113
|
+
domSnapshot: context.domSnapshot,
|
|
114
|
+
errorType,
|
|
115
|
+
expectedText: this.extractExpectedText(context.errorMessage),
|
|
116
|
+
confidenceThreshold: this.config.confidenceThreshold
|
|
117
|
+
});
|
|
118
|
+
this.currentSession.selectorHeals.push(result);
|
|
119
|
+
this.currentSession.summary.byType.selector++;
|
|
120
|
+
if (result.confidence >= this.config.confidenceThreshold && result.healedSelector !== result.originalSelector) {
|
|
121
|
+
this.currentSession.summary.healed++;
|
|
122
|
+
this.currentSession.summary.highConfidence++;
|
|
123
|
+
return {
|
|
124
|
+
success: true,
|
|
125
|
+
healed: true,
|
|
126
|
+
suggestion: {
|
|
127
|
+
code: this.formatSelectorHeal(result),
|
|
128
|
+
explanation: `Replace selector "${result.originalSelector}" with "${result.healedSelector}" using ${result.strategy} strategy (confidence: ${(result.confidence * 100).toFixed(0)}%)`,
|
|
129
|
+
filePath: context.testSource,
|
|
130
|
+
lineNumber: this.extractLineNumber(context.stackTrace),
|
|
131
|
+
confidence: result.confidence,
|
|
132
|
+
effort: 'low',
|
|
133
|
+
healingType: 'selector'
|
|
134
|
+
},
|
|
135
|
+
reason: 'Selector healed successfully'
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
this.currentSession.summary.failed++;
|
|
139
|
+
this.currentSession.unhealed++;
|
|
140
|
+
// Try AI-assisted healing if enabled
|
|
141
|
+
if (this.config.features.aiAssisted) {
|
|
142
|
+
const aiSuggestion = await this.tryAiHealing(context);
|
|
143
|
+
if (aiSuggestion) {
|
|
144
|
+
return {
|
|
145
|
+
success: true,
|
|
146
|
+
healed: false,
|
|
147
|
+
suggestion: aiSuggestion,
|
|
148
|
+
reason: 'AI suggestion available'
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return {
|
|
153
|
+
success: false,
|
|
154
|
+
healed: false,
|
|
155
|
+
suggestion: null,
|
|
156
|
+
reason: result.failureReason
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Heal an API test failure
|
|
161
|
+
*/
|
|
162
|
+
async healApiFailure(context) {
|
|
163
|
+
if (!context.requestDetails) {
|
|
164
|
+
return {
|
|
165
|
+
success: false,
|
|
166
|
+
healed: false,
|
|
167
|
+
suggestion: null,
|
|
168
|
+
reason: 'No request details available'
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
const apiHeal = this.analyzeApiFailure(context);
|
|
172
|
+
this.currentSession.apiHeals.push(apiHeal);
|
|
173
|
+
this.currentSession.summary.byType.api++;
|
|
174
|
+
if (apiHeal.confidence >= this.config.confidenceThreshold) {
|
|
175
|
+
this.currentSession.summary.healed++;
|
|
176
|
+
return {
|
|
177
|
+
success: true,
|
|
178
|
+
healed: true,
|
|
179
|
+
suggestion: {
|
|
180
|
+
code: apiHeal.suggestion,
|
|
181
|
+
explanation: `API change detected: ${apiHeal.changeType}. Suggested fix: ${apiHeal.suggestion}`,
|
|
182
|
+
filePath: context.testSource,
|
|
183
|
+
lineNumber: this.extractLineNumber(context.stackTrace),
|
|
184
|
+
confidence: apiHeal.confidence,
|
|
185
|
+
effort: 'medium',
|
|
186
|
+
healingType: 'api'
|
|
187
|
+
},
|
|
188
|
+
reason: 'API failure analyzed'
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
this.currentSession.summary.failed++;
|
|
192
|
+
this.currentSession.unhealed++;
|
|
193
|
+
return {
|
|
194
|
+
success: false,
|
|
195
|
+
healed: false,
|
|
196
|
+
suggestion: null,
|
|
197
|
+
reason: 'API heal confidence below threshold'
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Heal a generic test failure
|
|
202
|
+
*/
|
|
203
|
+
async healGenericFailure(context) {
|
|
204
|
+
// Try AI-assisted healing for generic failures
|
|
205
|
+
if (this.config.features.aiAssisted) {
|
|
206
|
+
const aiSuggestion = await this.tryAiHealing(context);
|
|
207
|
+
if (aiSuggestion) {
|
|
208
|
+
this.currentSession.summary.healed++;
|
|
209
|
+
return {
|
|
210
|
+
success: true,
|
|
211
|
+
healed: false,
|
|
212
|
+
suggestion: aiSuggestion,
|
|
213
|
+
reason: 'AI suggestion available'
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
this.currentSession.summary.failed++;
|
|
218
|
+
this.currentSession.unhealed++;
|
|
219
|
+
return {
|
|
220
|
+
success: false,
|
|
221
|
+
healed: false,
|
|
222
|
+
suggestion: null,
|
|
223
|
+
reason: 'No healing strategy available for this failure type'
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Try AI-assisted healing
|
|
228
|
+
*/
|
|
229
|
+
async tryAiHealing(context) {
|
|
230
|
+
try {
|
|
231
|
+
const aiClient = await createLLMProvider({ preferred: 'deepseek' });
|
|
232
|
+
const prompt = this.buildAiPrompt(context);
|
|
233
|
+
const response = await aiClient.generate({
|
|
234
|
+
prompt,
|
|
235
|
+
systemPrompt: 'You are an expert test debugging assistant. Analyze test failures and provide specific code fixes.',
|
|
236
|
+
maxTokens: 1000,
|
|
237
|
+
temperature: 0.3
|
|
238
|
+
});
|
|
239
|
+
if (!response.content) {
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
return {
|
|
243
|
+
code: this.extractCodeFromAiResponse(response.content),
|
|
244
|
+
explanation: this.extractExplanationFromAiResponse(response.content),
|
|
245
|
+
filePath: context.testSource,
|
|
246
|
+
lineNumber: this.extractLineNumber(context.stackTrace),
|
|
247
|
+
confidence: 0.7, // AI suggestions have medium confidence by default
|
|
248
|
+
effort: 'medium',
|
|
249
|
+
healingType: 'other',
|
|
250
|
+
applied: false,
|
|
251
|
+
verified: false
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
catch (error) {
|
|
255
|
+
// Silently fail on AI errors
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Build AI prompt from failure context
|
|
261
|
+
*/
|
|
262
|
+
buildAiPrompt(context) {
|
|
263
|
+
let prompt = `Test Failure Analysis Request\n\n`;
|
|
264
|
+
prompt += `Test Type: ${context.testType}\n`;
|
|
265
|
+
prompt += `Gate: ${context.gate}\n`;
|
|
266
|
+
prompt += `Test Source: ${context.testSource}\n\n`;
|
|
267
|
+
prompt += `Error Message:\n${context.errorMessage}\n\n`;
|
|
268
|
+
if (context.stackTrace) {
|
|
269
|
+
prompt += `Stack Trace:\n${context.stackTrace}\n\n`;
|
|
270
|
+
}
|
|
271
|
+
if (context.pageUrl) {
|
|
272
|
+
prompt += `Page URL: ${context.pageUrl}\n\n`;
|
|
273
|
+
}
|
|
274
|
+
if (context.requestDetails) {
|
|
275
|
+
prompt += `Request Details:\n`;
|
|
276
|
+
prompt += `- URL: ${context.requestDetails.url}\n`;
|
|
277
|
+
prompt += `- Method: ${context.requestDetails.method}\n`;
|
|
278
|
+
if (context.requestDetails.status) {
|
|
279
|
+
prompt += `- Status: ${context.requestDetails.status}\n`;
|
|
280
|
+
}
|
|
281
|
+
prompt += `\n`;
|
|
282
|
+
}
|
|
283
|
+
prompt += `Please provide:\n`;
|
|
284
|
+
prompt += `1. A specific code fix for this failure\n`;
|
|
285
|
+
prompt += `2. A brief explanation of why this fix works\n`;
|
|
286
|
+
prompt += `3. Estimated effort level (low/medium/high)\n\n`;
|
|
287
|
+
prompt += `Format your response with:\n`;
|
|
288
|
+
prompt += `- CODE: [your fix]\n`;
|
|
289
|
+
prompt += `- EXPLANATION: [your explanation]\n`;
|
|
290
|
+
prompt += `- EFFORT: [low/medium/high]\n`;
|
|
291
|
+
return prompt;
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Extract code from AI response
|
|
295
|
+
*/
|
|
296
|
+
extractCodeFromAiResponse(response) {
|
|
297
|
+
const codeMatch = response.match(/CODE:\s*([\s\S]*?)(?=EXPLANATION:|$)/i);
|
|
298
|
+
return codeMatch ? codeMatch[1].trim() : response;
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Extract explanation from AI response
|
|
302
|
+
*/
|
|
303
|
+
extractExplanationFromAiResponse(response) {
|
|
304
|
+
const expMatch = response.match(/EXPLANATION:\s*([\s\S]*?)(?=EFFORT:|$)/i);
|
|
305
|
+
return expMatch ? expMatch[1].trim() : 'AI-generated fix';
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Analyze API failure
|
|
309
|
+
*/
|
|
310
|
+
analyzeApiFailure(context) {
|
|
311
|
+
const details = context.requestDetails;
|
|
312
|
+
const changeType = this.detectApiChangeType(context);
|
|
313
|
+
const confidences = {
|
|
314
|
+
'path_changed': 0.7,
|
|
315
|
+
'method_changed': 0.6,
|
|
316
|
+
'status_changed': 0.8,
|
|
317
|
+
'response_structure': 0.65,
|
|
318
|
+
'auth_required': 0.75,
|
|
319
|
+
'deprecated': 0.9,
|
|
320
|
+
'moved_permanently': 0.85
|
|
321
|
+
};
|
|
322
|
+
let suggestion = '';
|
|
323
|
+
switch (changeType) {
|
|
324
|
+
case 'status_changed':
|
|
325
|
+
suggestion = `Update expected status from ${context.metadata?.expectedStatus} to ${details.status}`;
|
|
326
|
+
break;
|
|
327
|
+
case 'auth_required':
|
|
328
|
+
suggestion = 'Add authentication headers to the request';
|
|
329
|
+
break;
|
|
330
|
+
case 'path_changed':
|
|
331
|
+
suggestion = 'Update the API endpoint path';
|
|
332
|
+
break;
|
|
333
|
+
case 'method_changed':
|
|
334
|
+
suggestion = `Change request method to ${details.method}`;
|
|
335
|
+
break;
|
|
336
|
+
default:
|
|
337
|
+
suggestion = 'Review API specification for changes';
|
|
338
|
+
}
|
|
339
|
+
return {
|
|
340
|
+
originalSpec: `${details.method} ${details.url}`,
|
|
341
|
+
healedSpec: `${details.method} ${details.url}`,
|
|
342
|
+
changeType,
|
|
343
|
+
suggestion,
|
|
344
|
+
confidence: confidences[changeType] || 0.5
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Detect API change type from context
|
|
349
|
+
*/
|
|
350
|
+
detectApiChangeType(context) {
|
|
351
|
+
const msg = context.errorMessage.toLowerCase();
|
|
352
|
+
const details = context.requestDetails;
|
|
353
|
+
if (msg.includes('401') || msg.includes('403') || msg.includes('unauthorized') || msg.includes('forbidden')) {
|
|
354
|
+
return 'auth_required';
|
|
355
|
+
}
|
|
356
|
+
if (msg.includes('404') || msg.includes('not found')) {
|
|
357
|
+
return 'path_changed';
|
|
358
|
+
}
|
|
359
|
+
if (msg.includes('405') || msg.includes('method not allowed')) {
|
|
360
|
+
return 'method_changed';
|
|
361
|
+
}
|
|
362
|
+
if (msg.includes('410') || msg.includes('gone')) {
|
|
363
|
+
return 'deprecated';
|
|
364
|
+
}
|
|
365
|
+
if (msg.includes('status') || msg.includes('expected 200')) {
|
|
366
|
+
return 'status_changed';
|
|
367
|
+
}
|
|
368
|
+
if (msg.includes('response') || msg.includes('cannot read') || msg.includes('undefined')) {
|
|
369
|
+
return 'response_structure';
|
|
370
|
+
}
|
|
371
|
+
if (msg.includes('301') || msg.includes('302') || msg.includes('moved')) {
|
|
372
|
+
return 'moved_permanently';
|
|
373
|
+
}
|
|
374
|
+
return 'response_structure';
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Extract selector from error message
|
|
378
|
+
*/
|
|
379
|
+
extractSelector(errorMessage) {
|
|
380
|
+
// Common patterns for selector errors
|
|
381
|
+
const patterns = [
|
|
382
|
+
/selector['"]\s*:\s*['"]([^'"]+)['"]/i,
|
|
383
|
+
/['"]([^'"]+\s*selector[^'"]*)['"]/i,
|
|
384
|
+
/found\s+['"]([^'"]+)['"]/i,
|
|
385
|
+
/waiting\s+for\s+['"]([^'"]+)['"]/i
|
|
386
|
+
];
|
|
387
|
+
for (const pattern of patterns) {
|
|
388
|
+
const match = errorMessage.match(pattern);
|
|
389
|
+
if (match && match[1]) {
|
|
390
|
+
return match[1];
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
return null;
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Extract expected text from error message
|
|
397
|
+
*/
|
|
398
|
+
extractExpectedText(errorMessage) {
|
|
399
|
+
const textMatch = errorMessage.match(/text\s+['"]([^'"]+)['"]/i);
|
|
400
|
+
return textMatch ? textMatch[1] : undefined;
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Infer selector error type from message
|
|
404
|
+
*/
|
|
405
|
+
inferSelectorErrorType(errorMessage) {
|
|
406
|
+
const msg = errorMessage.toLowerCase();
|
|
407
|
+
if (msg.includes('timeout') || msg.includes('timed out')) {
|
|
408
|
+
return 'timeout';
|
|
409
|
+
}
|
|
410
|
+
if (msg.includes('not found') || msg.includes('found 0')) {
|
|
411
|
+
return 'not_found';
|
|
412
|
+
}
|
|
413
|
+
if (msg.includes('found multiple') || msg.includes('ambiguous')) {
|
|
414
|
+
return 'ambiguous';
|
|
415
|
+
}
|
|
416
|
+
if (msg.includes('detached') || msg.includes('removed from dom')) {
|
|
417
|
+
return 'detached';
|
|
418
|
+
}
|
|
419
|
+
if (msg.includes('not visible') || msg.includes('hidden')) {
|
|
420
|
+
return 'invisible';
|
|
421
|
+
}
|
|
422
|
+
if (msg.includes('stale') || msg.includes('element reference')) {
|
|
423
|
+
return 'stale';
|
|
424
|
+
}
|
|
425
|
+
return 'not_found';
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Extract line number from stack trace
|
|
429
|
+
*/
|
|
430
|
+
extractLineNumber(stackTrace) {
|
|
431
|
+
if (!stackTrace)
|
|
432
|
+
return 0;
|
|
433
|
+
const lineMatch = stackTrace.match(/:(\d+)(?::\d+)?/);
|
|
434
|
+
return lineMatch ? parseInt(lineMatch[1], 10) : 0;
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* Format selector heal result
|
|
438
|
+
*/
|
|
439
|
+
formatSelectorHeal(result) {
|
|
440
|
+
return `// Replace: ${result.originalSelector}\n// With: ${result.healedSelector}\n// Strategy: ${result.strategy} (confidence: ${(result.confidence * 100).toFixed(0)}%)`;
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Calculate session summary
|
|
444
|
+
*/
|
|
445
|
+
calculateSummary() {
|
|
446
|
+
if (!this.currentSession) {
|
|
447
|
+
return {
|
|
448
|
+
totalIssues: 0,
|
|
449
|
+
healed: 0,
|
|
450
|
+
failed: 0,
|
|
451
|
+
highConfidence: 0,
|
|
452
|
+
mediumConfidence: 0,
|
|
453
|
+
lowConfidence: 0,
|
|
454
|
+
byType: { selector: 0, assertion: 0, api: 0, timeout: 0 },
|
|
455
|
+
successRate: 0
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
const summary = this.currentSession.summary;
|
|
459
|
+
const allHeals = [
|
|
460
|
+
...this.currentSession.selectorHeals,
|
|
461
|
+
...this.currentSession.assertionUpdates,
|
|
462
|
+
...this.currentSession.apiHeals
|
|
463
|
+
];
|
|
464
|
+
for (const heal of allHeals) {
|
|
465
|
+
if (heal.confidence >= 0.8) {
|
|
466
|
+
summary.highConfidence++;
|
|
467
|
+
}
|
|
468
|
+
else if (heal.confidence >= 0.5) {
|
|
469
|
+
summary.mediumConfidence++;
|
|
470
|
+
}
|
|
471
|
+
else {
|
|
472
|
+
summary.lowConfidence++;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
summary.successRate = summary.totalIssues > 0
|
|
476
|
+
? summary.healed / summary.totalIssues
|
|
477
|
+
: 0;
|
|
478
|
+
return summary;
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* Generate unique session ID
|
|
482
|
+
*/
|
|
483
|
+
generateSessionId() {
|
|
484
|
+
return `sh_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Get current session
|
|
488
|
+
*/
|
|
489
|
+
getSession() {
|
|
490
|
+
return this.currentSession;
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* Update configuration
|
|
494
|
+
*/
|
|
495
|
+
updateConfig(updates) {
|
|
496
|
+
this.config = { ...this.config, ...updates };
|
|
497
|
+
this.selectorHealer = new SelectorHealer(this.config);
|
|
498
|
+
this.assertionHealer = new AssertionHealer(this.config);
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* Check if self-healing is enabled
|
|
502
|
+
*/
|
|
503
|
+
isEnabled() {
|
|
504
|
+
return this.config.enabled;
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
* Get confidence threshold
|
|
508
|
+
*/
|
|
509
|
+
getConfidenceThreshold() {
|
|
510
|
+
return this.config.confidenceThreshold;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* Create a self-healing engine with default config
|
|
515
|
+
*/
|
|
516
|
+
export function createSelfHealingEngine(config) {
|
|
517
|
+
const defaultConfig = {
|
|
518
|
+
enabled: true,
|
|
519
|
+
maxAttempts: 3,
|
|
520
|
+
confidenceThreshold: 0.7,
|
|
521
|
+
autoApply: false,
|
|
522
|
+
backup: true,
|
|
523
|
+
features: {
|
|
524
|
+
selectorHealing: true,
|
|
525
|
+
assertionUpdates: true,
|
|
526
|
+
apiHealing: true,
|
|
527
|
+
timeoutAdjustment: true,
|
|
528
|
+
aiAssisted: true
|
|
529
|
+
},
|
|
530
|
+
selectorStrategies: {
|
|
531
|
+
useDataTestIds: true,
|
|
532
|
+
useAriaLabels: true,
|
|
533
|
+
useTextSelectors: true,
|
|
534
|
+
fuzzyMatching: true
|
|
535
|
+
}
|
|
536
|
+
};
|
|
537
|
+
return new SelfHealingEngine({ ...defaultConfig, ...config });
|
|
538
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QA360 Self-Healing Module
|
|
3
|
+
*
|
|
4
|
+
* Exports all self-healing functionality.
|
|
5
|
+
*/
|
|
6
|
+
export type { SelfHealingConfig, SelectorErrorType, SelectorHealingStrategy, SelectorHealingResult, AssertionType, AssertionUpdateResult, ApiChangeType, ApiHealingResult, SelfHealingSession, SelfHealingSummary, TestFailureContext, AiHealingSuggestion } from './types.js';
|
|
7
|
+
export { SelectorHealer, createDefaultSelfHealingConfig } from './selector-healer.js';
|
|
8
|
+
export { AssertionHealer } from './assertion-healer.js';
|
|
9
|
+
export { SelfHealingEngine, createSelfHealingEngine, type SelfHealingResult } from './engine.js';
|
|
10
|
+
export { createDefaultSelfHealingConfig as createSelfHealingConfig } from './selector-healer.js';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QA360 Self-Healing Module
|
|
3
|
+
*
|
|
4
|
+
* Exports all self-healing functionality.
|
|
5
|
+
*/
|
|
6
|
+
// Classes
|
|
7
|
+
export { SelectorHealer, createDefaultSelfHealingConfig } from './selector-healer.js';
|
|
8
|
+
export { AssertionHealer } from './assertion-healer.js';
|
|
9
|
+
export { SelfHealingEngine, createSelfHealingEngine } from './engine.js';
|
|
10
|
+
// Convenience re-exports
|
|
11
|
+
export { createDefaultSelfHealingConfig as createSelfHealingConfig } from './selector-healer.js';
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Selector Healer
|
|
3
|
+
*
|
|
4
|
+
* Intelligently heals broken CSS selectors in UI/E2E tests.
|
|
5
|
+
* Uses multiple strategies to find replacement selectors.
|
|
6
|
+
*/
|
|
7
|
+
import type { SelectorHealingResult, SelectorHealingStrategy, SelectorErrorType, SelfHealingConfig } from './types.js';
|
|
8
|
+
/**
|
|
9
|
+
* Configuration for selector healing
|
|
10
|
+
*/
|
|
11
|
+
interface SelectorHealOptions {
|
|
12
|
+
/** Page URL where selector failed */
|
|
13
|
+
pageUrl?: string;
|
|
14
|
+
/** DOM snapshot at time of failure */
|
|
15
|
+
domSnapshot?: {
|
|
16
|
+
title: string;
|
|
17
|
+
url: string;
|
|
18
|
+
html?: string;
|
|
19
|
+
elements: Record<string, number>;
|
|
20
|
+
};
|
|
21
|
+
/** Error type that occurred */
|
|
22
|
+
errorType?: SelectorErrorType;
|
|
23
|
+
/** Original selector that failed */
|
|
24
|
+
originalSelector: string;
|
|
25
|
+
/** Expected element type (button, input, etc.) */
|
|
26
|
+
expectedType?: string;
|
|
27
|
+
/** Text content the element should have */
|
|
28
|
+
expectedText?: string;
|
|
29
|
+
/** Confidence threshold for healing */
|
|
30
|
+
confidenceThreshold?: number;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Candidate replacement selector
|
|
34
|
+
*/
|
|
35
|
+
interface SelectorCandidate {
|
|
36
|
+
selector: string;
|
|
37
|
+
strategy: SelectorHealingStrategy;
|
|
38
|
+
confidence: number;
|
|
39
|
+
reason: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Selector Healer class
|
|
43
|
+
*/
|
|
44
|
+
export declare class SelectorHealer {
|
|
45
|
+
private config;
|
|
46
|
+
constructor(config: SelfHealingConfig);
|
|
47
|
+
/**
|
|
48
|
+
* Attempt to heal a broken selector
|
|
49
|
+
*/
|
|
50
|
+
healSelector(options: SelectorHealOptions): Promise<SelectorHealingResult>;
|
|
51
|
+
/**
|
|
52
|
+
* Try data-testid attribute strategies
|
|
53
|
+
*/
|
|
54
|
+
private tryDataTestIdStrategies;
|
|
55
|
+
/**
|
|
56
|
+
* Try ARIA label strategies
|
|
57
|
+
*/
|
|
58
|
+
private tryAriaLabelStrategies;
|
|
59
|
+
/**
|
|
60
|
+
* Try text content strategies
|
|
61
|
+
*/
|
|
62
|
+
private tryTextContentStrategies;
|
|
63
|
+
/**
|
|
64
|
+
* Try fuzzy matching strategies
|
|
65
|
+
*/
|
|
66
|
+
private tryFuzzyMatchStrategies;
|
|
67
|
+
/**
|
|
68
|
+
* Infer failure reason from error type
|
|
69
|
+
*/
|
|
70
|
+
private inferFailureReason;
|
|
71
|
+
/**
|
|
72
|
+
* Escape string for CSS selectors
|
|
73
|
+
*/
|
|
74
|
+
private escapeCssString;
|
|
75
|
+
/**
|
|
76
|
+
* Escape string for XPath
|
|
77
|
+
*/
|
|
78
|
+
private escapeXPathString;
|
|
79
|
+
/**
|
|
80
|
+
* Parse selector to understand its structure
|
|
81
|
+
*/
|
|
82
|
+
parseSelector(selector: string): {
|
|
83
|
+
tag?: string;
|
|
84
|
+
id?: string;
|
|
85
|
+
classes: string[];
|
|
86
|
+
attributes: Record<string, string>;
|
|
87
|
+
pseudoClasses: string[];
|
|
88
|
+
combinators: string[];
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* Generate multiple healing candidates for a selector
|
|
92
|
+
*/
|
|
93
|
+
generateHealingCandidates(selector: string, context?: Pick<SelectorHealOptions, 'expectedText' | 'errorType'>): Promise<SelectorCandidate[]>;
|
|
94
|
+
/**
|
|
95
|
+
* Remove duplicate candidates
|
|
96
|
+
*/
|
|
97
|
+
private deduplicateCandidates;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Create a default self-healing config
|
|
101
|
+
*/
|
|
102
|
+
export declare function createDefaultSelfHealingConfig(): SelfHealingConfig;
|
|
103
|
+
export {};
|