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,372 @@
|
|
|
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
|
+
/**
|
|
8
|
+
* Selector Healer class
|
|
9
|
+
*/
|
|
10
|
+
export class SelectorHealer {
|
|
11
|
+
config;
|
|
12
|
+
constructor(config) {
|
|
13
|
+
this.config = config;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Attempt to heal a broken selector
|
|
17
|
+
*/
|
|
18
|
+
async healSelector(options) {
|
|
19
|
+
const candidates = [];
|
|
20
|
+
// Try each enabled strategy
|
|
21
|
+
if (this.config.features.selectorHealing) {
|
|
22
|
+
if (this.config.selectorStrategies.useDataTestIds) {
|
|
23
|
+
candidates.push(...this.tryDataTestIdStrategies(options));
|
|
24
|
+
}
|
|
25
|
+
if (this.config.selectorStrategies.useAriaLabels) {
|
|
26
|
+
candidates.push(...this.tryAriaLabelStrategies(options));
|
|
27
|
+
}
|
|
28
|
+
if (this.config.selectorStrategies.useTextSelectors) {
|
|
29
|
+
candidates.push(...this.tryTextContentStrategies(options));
|
|
30
|
+
}
|
|
31
|
+
if (this.config.selectorStrategies.fuzzyMatching) {
|
|
32
|
+
candidates.push(...this.tryFuzzyMatchStrategies(options));
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
// Sort by confidence and return best candidate
|
|
36
|
+
candidates.sort((a, b) => b.confidence - a.confidence);
|
|
37
|
+
const best = candidates[0];
|
|
38
|
+
if (best && best.confidence >= (this.config.confidenceThreshold || 0.7)) {
|
|
39
|
+
return {
|
|
40
|
+
originalSelector: options.originalSelector,
|
|
41
|
+
healedSelector: best.selector,
|
|
42
|
+
strategy: best.strategy,
|
|
43
|
+
confidence: best.confidence,
|
|
44
|
+
elementCount: 1,
|
|
45
|
+
failureReason: this.inferFailureReason(options.errorType)
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
// No good heal found
|
|
49
|
+
return {
|
|
50
|
+
originalSelector: options.originalSelector,
|
|
51
|
+
healedSelector: options.originalSelector,
|
|
52
|
+
strategy: 'no_heal',
|
|
53
|
+
confidence: 0,
|
|
54
|
+
elementCount: 0,
|
|
55
|
+
failureReason: this.inferFailureReason(options.errorType)
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Try data-testid attribute strategies
|
|
60
|
+
*/
|
|
61
|
+
tryDataTestIdStrategies(options) {
|
|
62
|
+
const candidates = [];
|
|
63
|
+
const { originalSelector, expectedText } = options;
|
|
64
|
+
// Extract potential test ID from original selector
|
|
65
|
+
const idMatch = originalSelector.match(/#(\w+)/);
|
|
66
|
+
const classMatch = originalSelector.match(/\.([\w-]+)/);
|
|
67
|
+
const tagMatch = originalSelector.match(/^(\w+)/);
|
|
68
|
+
// Strategy 1: Convert ID to data-testid
|
|
69
|
+
if (idMatch) {
|
|
70
|
+
const testId = idMatch[1];
|
|
71
|
+
candidates.push({
|
|
72
|
+
selector: `[data-testid="${testId}"]`,
|
|
73
|
+
strategy: 'data_testid',
|
|
74
|
+
confidence: 0.85,
|
|
75
|
+
reason: 'Converted ID to data-testid attribute'
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
// Strategy 2: Convert class to data-testid
|
|
79
|
+
if (classMatch) {
|
|
80
|
+
const testId = classMatch[1].replace(/-/g, '_');
|
|
81
|
+
candidates.push({
|
|
82
|
+
selector: `[data-testid="${testId}"]`,
|
|
83
|
+
strategy: 'data_testid',
|
|
84
|
+
confidence: 0.75,
|
|
85
|
+
reason: 'Converted class name to data-testid attribute'
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
// Strategy 3: Generate data-testid from expected text
|
|
89
|
+
if (expectedText) {
|
|
90
|
+
const testId = expectedText
|
|
91
|
+
.toLowerCase()
|
|
92
|
+
.replace(/[^a-z0-9]+/g, '_')
|
|
93
|
+
.replace(/^_+|_+$/g, '');
|
|
94
|
+
candidates.push({
|
|
95
|
+
selector: `[data-testid="${testId}"]`,
|
|
96
|
+
strategy: 'data_testid',
|
|
97
|
+
confidence: 0.70,
|
|
98
|
+
reason: 'Generated data-testid from expected text'
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
// Strategy 4: Tag-based data-testid
|
|
102
|
+
if (tagMatch) {
|
|
103
|
+
const tag = tagMatch[1];
|
|
104
|
+
candidates.push({
|
|
105
|
+
selector: `${tag}[data-testid]`,
|
|
106
|
+
strategy: 'data_testid',
|
|
107
|
+
confidence: 0.60,
|
|
108
|
+
reason: 'Tag with any data-testid attribute'
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
return candidates;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Try ARIA label strategies
|
|
115
|
+
*/
|
|
116
|
+
tryAriaLabelStrategies(options) {
|
|
117
|
+
const candidates = [];
|
|
118
|
+
const { originalSelector, expectedText } = options;
|
|
119
|
+
// Extract tag if available
|
|
120
|
+
const tagMatch = originalSelector.match(/^(\w+)/);
|
|
121
|
+
const tag = tagMatch ? `${tagMatch[1]}[` : '[';
|
|
122
|
+
// Strategy 1: Use expected text as aria-label
|
|
123
|
+
if (expectedText) {
|
|
124
|
+
const escapedText = this.escapeCssString(expectedText);
|
|
125
|
+
candidates.push({
|
|
126
|
+
selector: `${tag}aria-label="${escapedText}"]`,
|
|
127
|
+
strategy: 'aria_label',
|
|
128
|
+
confidence: 0.80,
|
|
129
|
+
reason: 'Match by aria-label attribute'
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
// Strategy 2: Role-based selectors
|
|
133
|
+
const roleMap = {
|
|
134
|
+
'button': 'button',
|
|
135
|
+
'input': 'textbox',
|
|
136
|
+
'select': 'combobox',
|
|
137
|
+
'a': 'link',
|
|
138
|
+
};
|
|
139
|
+
if (tagMatch) {
|
|
140
|
+
const tagName = tagMatch[1];
|
|
141
|
+
const role = roleMap[tagName];
|
|
142
|
+
if (role) {
|
|
143
|
+
candidates.push({
|
|
144
|
+
selector: `[role="${role}"]`,
|
|
145
|
+
strategy: 'aria_label',
|
|
146
|
+
confidence: 0.60,
|
|
147
|
+
reason: `Match by ARIA role for <${tagName}> element`
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return candidates;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Try text content strategies
|
|
155
|
+
*/
|
|
156
|
+
tryTextContentStrategies(options) {
|
|
157
|
+
const candidates = [];
|
|
158
|
+
const { originalSelector, expectedText } = options;
|
|
159
|
+
if (!expectedText) {
|
|
160
|
+
return candidates;
|
|
161
|
+
}
|
|
162
|
+
// Strategy 1: Playwright text selector
|
|
163
|
+
candidates.push({
|
|
164
|
+
selector: `text="${expectedText}"`,
|
|
165
|
+
strategy: 'text_content',
|
|
166
|
+
confidence: 0.75,
|
|
167
|
+
reason: 'Exact text match'
|
|
168
|
+
});
|
|
169
|
+
// Strategy 2: XPath text selector
|
|
170
|
+
const xpathText = this.escapeXPathString(expectedText);
|
|
171
|
+
candidates.push({
|
|
172
|
+
selector: `xpath=.//*[text()="${xpathText}"]`,
|
|
173
|
+
strategy: 'text_content',
|
|
174
|
+
confidence: 0.72,
|
|
175
|
+
reason: 'XPath text match'
|
|
176
|
+
});
|
|
177
|
+
// Strategy 3: Combine with tag if available
|
|
178
|
+
const tagMatch = originalSelector.match(/^(\w+)/);
|
|
179
|
+
if (tagMatch) {
|
|
180
|
+
const tag = tagMatch[1];
|
|
181
|
+
candidates.push({
|
|
182
|
+
selector: `${tag}:has-text("${expectedText}")`,
|
|
183
|
+
strategy: 'text_content',
|
|
184
|
+
confidence: 0.70,
|
|
185
|
+
reason: `Tag with text content: ${tag}`
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
return candidates;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Try fuzzy matching strategies
|
|
192
|
+
*/
|
|
193
|
+
tryFuzzyMatchStrategies(options) {
|
|
194
|
+
const candidates = [];
|
|
195
|
+
const { originalSelector } = options;
|
|
196
|
+
// Strategy 1: Remove pseudo-classes
|
|
197
|
+
const withoutPseudo = originalSelector.replace(/::?[a-z-]+(\([^)]*\))?/gi, '');
|
|
198
|
+
if (withoutPseudo !== originalSelector) {
|
|
199
|
+
candidates.push({
|
|
200
|
+
selector: withoutPseudo,
|
|
201
|
+
strategy: 'fuzzy_match',
|
|
202
|
+
confidence: 0.65,
|
|
203
|
+
reason: 'Removed pseudo-classes'
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
// Strategy 2: Try nth-child variants
|
|
207
|
+
const nthMatch = originalSelector.match(/:nth-child\((\d+)\)/);
|
|
208
|
+
if (nthMatch) {
|
|
209
|
+
const index = parseInt(nthMatch[1], 10);
|
|
210
|
+
const baseSelector = originalSelector.replace(/:nth-child\(\d+\)/, '');
|
|
211
|
+
// Try nth-of-type
|
|
212
|
+
candidates.push({
|
|
213
|
+
selector: `${baseSelector}:nth-of-type(${index})`,
|
|
214
|
+
strategy: 'fuzzy_match',
|
|
215
|
+
confidence: 0.55,
|
|
216
|
+
reason: 'Changed nth-child to nth-of-type'
|
|
217
|
+
});
|
|
218
|
+
// Try first/last
|
|
219
|
+
if (index === 1) {
|
|
220
|
+
candidates.push({
|
|
221
|
+
selector: `${baseSelector}:first-child`,
|
|
222
|
+
strategy: 'fuzzy_match',
|
|
223
|
+
confidence: 0.60,
|
|
224
|
+
reason: 'Changed nth-child(1) to first-child'
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
// Strategy 3: Try removing one selector component at a time
|
|
229
|
+
const parts = originalSelector.split(/\s+/);
|
|
230
|
+
if (parts.length > 1) {
|
|
231
|
+
// Try without the last part
|
|
232
|
+
candidates.push({
|
|
233
|
+
selector: parts.slice(0, -1).join(' '),
|
|
234
|
+
strategy: 'fuzzy_match',
|
|
235
|
+
confidence: 0.45,
|
|
236
|
+
reason: 'Removed last selector component'
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
return candidates;
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Infer failure reason from error type
|
|
243
|
+
*/
|
|
244
|
+
inferFailureReason(errorType) {
|
|
245
|
+
const reasons = {
|
|
246
|
+
timeout: 'Element not found within timeout period',
|
|
247
|
+
not_found: 'No matching elements found in DOM',
|
|
248
|
+
ambiguous: 'Multiple elements matched - selector not specific enough',
|
|
249
|
+
detached: 'Element was detached from DOM',
|
|
250
|
+
invisible: 'Element exists but not visible or interactable',
|
|
251
|
+
stale: 'Element reference became stale'
|
|
252
|
+
};
|
|
253
|
+
return errorType ? reasons[errorType] : 'Unknown selector failure';
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Escape string for CSS selectors
|
|
257
|
+
*/
|
|
258
|
+
escapeCssString(str) {
|
|
259
|
+
return str.replace(/(["\\])/g, '\\$1');
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Escape string for XPath
|
|
263
|
+
*/
|
|
264
|
+
escapeXPathString(str) {
|
|
265
|
+
if (str.includes('"')) {
|
|
266
|
+
if (str.includes("'")) {
|
|
267
|
+
// Use concat for strings with both quotes
|
|
268
|
+
return str
|
|
269
|
+
.split(/(")/)
|
|
270
|
+
.map(part => {
|
|
271
|
+
if (part === '"')
|
|
272
|
+
return '\'"\''; // '"'"' concat hack
|
|
273
|
+
if (part === "'")
|
|
274
|
+
return "'";
|
|
275
|
+
return `"${part}"`;
|
|
276
|
+
})
|
|
277
|
+
.join(', ');
|
|
278
|
+
}
|
|
279
|
+
return `'${str}'`;
|
|
280
|
+
}
|
|
281
|
+
return `"${str}"`;
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Parse selector to understand its structure
|
|
285
|
+
*/
|
|
286
|
+
parseSelector(selector) {
|
|
287
|
+
const result = {
|
|
288
|
+
classes: [],
|
|
289
|
+
attributes: {},
|
|
290
|
+
pseudoClasses: [],
|
|
291
|
+
combinators: []
|
|
292
|
+
};
|
|
293
|
+
// Extract tag
|
|
294
|
+
const tagMatch = selector.match(/^([\w-]+)/);
|
|
295
|
+
if (tagMatch) {
|
|
296
|
+
result.tag = tagMatch[1];
|
|
297
|
+
}
|
|
298
|
+
// Extract ID
|
|
299
|
+
const idMatch = selector.match(/#([\w-]+)/);
|
|
300
|
+
if (idMatch) {
|
|
301
|
+
result.id = idMatch[1];
|
|
302
|
+
}
|
|
303
|
+
// Extract classes
|
|
304
|
+
const classMatches = selector.matchAll(/\.([\w-]+)/g);
|
|
305
|
+
for (const match of classMatches) {
|
|
306
|
+
result.classes.push(match[1]);
|
|
307
|
+
}
|
|
308
|
+
// Extract attributes
|
|
309
|
+
const attrMatches = selector.matchAll(/\[([\w-]+)(=["']([^"']*)["'])?\]/g);
|
|
310
|
+
for (const match of attrMatches) {
|
|
311
|
+
result.attributes[match[1]] = match[3] || '';
|
|
312
|
+
}
|
|
313
|
+
return result;
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Generate multiple healing candidates for a selector
|
|
317
|
+
*/
|
|
318
|
+
async generateHealingCandidates(selector, context) {
|
|
319
|
+
const allCandidates = [];
|
|
320
|
+
const options = {
|
|
321
|
+
originalSelector: selector,
|
|
322
|
+
expectedText: context?.expectedText,
|
|
323
|
+
errorType: context?.errorType
|
|
324
|
+
};
|
|
325
|
+
// Generate candidates from all strategies
|
|
326
|
+
allCandidates.push(...this.tryDataTestIdStrategies(options));
|
|
327
|
+
allCandidates.push(...this.tryAriaLabelStrategies(options));
|
|
328
|
+
allCandidates.push(...this.tryTextContentStrategies(options));
|
|
329
|
+
allCandidates.push(...this.tryFuzzyMatchStrategies(options));
|
|
330
|
+
// Remove duplicates and sort by confidence
|
|
331
|
+
const unique = this.deduplicateCandidates(allCandidates);
|
|
332
|
+
unique.sort((a, b) => b.confidence - a.confidence);
|
|
333
|
+
return unique;
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Remove duplicate candidates
|
|
337
|
+
*/
|
|
338
|
+
deduplicateCandidates(candidates) {
|
|
339
|
+
const seen = new Set();
|
|
340
|
+
return candidates.filter(c => {
|
|
341
|
+
if (seen.has(c.selector))
|
|
342
|
+
return false;
|
|
343
|
+
seen.add(c.selector);
|
|
344
|
+
return true;
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Create a default self-healing config
|
|
350
|
+
*/
|
|
351
|
+
export function createDefaultSelfHealingConfig() {
|
|
352
|
+
return {
|
|
353
|
+
enabled: true,
|
|
354
|
+
maxAttempts: 3,
|
|
355
|
+
confidenceThreshold: 0.7,
|
|
356
|
+
autoApply: false,
|
|
357
|
+
backup: true,
|
|
358
|
+
features: {
|
|
359
|
+
selectorHealing: true,
|
|
360
|
+
assertionUpdates: true,
|
|
361
|
+
apiHealing: true,
|
|
362
|
+
timeoutAdjustment: true,
|
|
363
|
+
aiAssisted: true
|
|
364
|
+
},
|
|
365
|
+
selectorStrategies: {
|
|
366
|
+
useDataTestIds: true,
|
|
367
|
+
useAriaLabels: true,
|
|
368
|
+
useTextSelectors: true,
|
|
369
|
+
fuzzyMatching: true
|
|
370
|
+
}
|
|
371
|
+
};
|
|
372
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QA360 Self-Healing Module
|
|
3
|
+
*
|
|
4
|
+
* Types for self-healing functionality.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Self-healing configuration
|
|
8
|
+
*/
|
|
9
|
+
export interface SelfHealingConfig {
|
|
10
|
+
enabled: boolean;
|
|
11
|
+
maxAttempts: number;
|
|
12
|
+
confidenceThreshold: number;
|
|
13
|
+
autoApply: boolean;
|
|
14
|
+
backup: boolean;
|
|
15
|
+
features: {
|
|
16
|
+
selectorHealing: boolean;
|
|
17
|
+
assertionUpdates: boolean;
|
|
18
|
+
apiHealing: boolean;
|
|
19
|
+
timeoutAdjustment: boolean;
|
|
20
|
+
aiAssisted: boolean;
|
|
21
|
+
};
|
|
22
|
+
selectorStrategies: {
|
|
23
|
+
useDataTestIds: boolean;
|
|
24
|
+
useAriaLabels: boolean;
|
|
25
|
+
useTextSelectors: boolean;
|
|
26
|
+
fuzzyMatching: boolean;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Selector error types
|
|
31
|
+
*/
|
|
32
|
+
export type SelectorErrorType = 'timeout' | 'not_found' | 'ambiguous' | 'detached' | 'invisible' | 'stale';
|
|
33
|
+
/**
|
|
34
|
+
* Selector healing strategy types
|
|
35
|
+
*/
|
|
36
|
+
export type SelectorHealingStrategy = 'data_testid' | 'aria_label' | 'text_content' | 'fuzzy_match' | 'sibling_traversal' | 'css_variant' | 'ai_suggested' | 'no_heal';
|
|
37
|
+
/**
|
|
38
|
+
* Selector healing result
|
|
39
|
+
*/
|
|
40
|
+
export interface SelectorHealingResult {
|
|
41
|
+
originalSelector: string;
|
|
42
|
+
healedSelector: string;
|
|
43
|
+
strategy: SelectorHealingStrategy;
|
|
44
|
+
confidence: number;
|
|
45
|
+
elementCount: number;
|
|
46
|
+
failureReason: string;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Assertion types
|
|
50
|
+
*/
|
|
51
|
+
export type AssertionType = 'equality' | 'containment' | 'truthiness' | 'count' | 'status_code' | 'response_body' | 'ui_element_exists' | 'ui_text_matches';
|
|
52
|
+
/**
|
|
53
|
+
* Assertion update result
|
|
54
|
+
*/
|
|
55
|
+
export interface AssertionUpdateResult {
|
|
56
|
+
testFile: string;
|
|
57
|
+
lineNumber: number;
|
|
58
|
+
originalAssertion: string;
|
|
59
|
+
updatedAssertion: string;
|
|
60
|
+
expected: string;
|
|
61
|
+
actual: string;
|
|
62
|
+
confidence: number;
|
|
63
|
+
assertionType: AssertionType;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* API change types
|
|
67
|
+
*/
|
|
68
|
+
export type ApiChangeType = 'path_changed' | 'method_changed' | 'status_changed' | 'response_structure' | 'auth_required' | 'deprecated' | 'moved_permanently';
|
|
69
|
+
/**
|
|
70
|
+
* API healing result
|
|
71
|
+
*/
|
|
72
|
+
export interface ApiHealingResult {
|
|
73
|
+
originalSpec: string;
|
|
74
|
+
healedSpec: string;
|
|
75
|
+
changeType: ApiChangeType;
|
|
76
|
+
suggestion: string;
|
|
77
|
+
confidence: number;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Self-healing session result
|
|
81
|
+
*/
|
|
82
|
+
export interface SelfHealingSession {
|
|
83
|
+
sessionId: string;
|
|
84
|
+
startTime: number;
|
|
85
|
+
endTime: number;
|
|
86
|
+
duration: number;
|
|
87
|
+
config: SelfHealingConfig;
|
|
88
|
+
selectorHeals: SelectorHealingResult[];
|
|
89
|
+
assertionUpdates: AssertionUpdateResult[];
|
|
90
|
+
apiHeals: ApiHealingResult[];
|
|
91
|
+
success: boolean;
|
|
92
|
+
unhealed: number;
|
|
93
|
+
summary: SelfHealingSummary;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Self-healing summary statistics
|
|
97
|
+
*/
|
|
98
|
+
export interface SelfHealingSummary {
|
|
99
|
+
totalIssues: number;
|
|
100
|
+
healed: number;
|
|
101
|
+
failed: number;
|
|
102
|
+
highConfidence: number;
|
|
103
|
+
mediumConfidence: number;
|
|
104
|
+
lowConfidence: number;
|
|
105
|
+
byType: {
|
|
106
|
+
selector: number;
|
|
107
|
+
assertion: number;
|
|
108
|
+
api: number;
|
|
109
|
+
timeout: number;
|
|
110
|
+
};
|
|
111
|
+
successRate: number;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Test failure context for healing
|
|
115
|
+
*/
|
|
116
|
+
export interface TestFailureContext {
|
|
117
|
+
testType: 'api' | 'ui' | 'a11y' | 'perf' | 'unknown';
|
|
118
|
+
gate: string;
|
|
119
|
+
testSource: string;
|
|
120
|
+
errorMessage: string;
|
|
121
|
+
stackTrace?: string;
|
|
122
|
+
screenshot?: string;
|
|
123
|
+
pageUrl?: string;
|
|
124
|
+
domSnapshot?: {
|
|
125
|
+
title: string;
|
|
126
|
+
url: string;
|
|
127
|
+
html?: string;
|
|
128
|
+
elements: Record<string, number>;
|
|
129
|
+
};
|
|
130
|
+
requestDetails?: {
|
|
131
|
+
url: string;
|
|
132
|
+
method: string;
|
|
133
|
+
status?: number;
|
|
134
|
+
responseTime?: number;
|
|
135
|
+
responseBody?: unknown;
|
|
136
|
+
};
|
|
137
|
+
metadata?: Record<string, unknown>;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* AI healing suggestion
|
|
141
|
+
*/
|
|
142
|
+
export interface AiHealingSuggestion {
|
|
143
|
+
code: string;
|
|
144
|
+
explanation: string;
|
|
145
|
+
filePath: string;
|
|
146
|
+
lineNumber: number;
|
|
147
|
+
confidence: number;
|
|
148
|
+
effort: 'low' | 'medium' | 'high';
|
|
149
|
+
healingType: 'selector' | 'assertion' | 'api' | 'timeout' | 'other';
|
|
150
|
+
applied?: boolean;
|
|
151
|
+
verified?: boolean;
|
|
152
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SLO/SLI Default Configuration
|
|
3
|
+
*
|
|
4
|
+
* Predefined SLOs and SLIs for common quality metrics.
|
|
5
|
+
* Factory functions for creating standard configurations.
|
|
6
|
+
*/
|
|
7
|
+
import type { SLO, SLI, SLOConfig } from './types.js';
|
|
8
|
+
/**
|
|
9
|
+
* Default time windows in milliseconds
|
|
10
|
+
*/
|
|
11
|
+
export declare const TimeWindows: {
|
|
12
|
+
HOUR: number;
|
|
13
|
+
DAY: number;
|
|
14
|
+
WEEK: number;
|
|
15
|
+
MONTH: number;
|
|
16
|
+
QUARTER: number;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Create default SLO configuration
|
|
20
|
+
*/
|
|
21
|
+
export declare function createDefaultSLOConfig(): SLOConfig;
|
|
22
|
+
/**
|
|
23
|
+
* Create strict SLO configuration (enterprise/production)
|
|
24
|
+
*/
|
|
25
|
+
export declare function createStrictSLOConfig(): SLOConfig;
|
|
26
|
+
/**
|
|
27
|
+
* Create relaxed SLO configuration (development)
|
|
28
|
+
*/
|
|
29
|
+
export declare function createRelaxedSLOConfig(): SLOConfig;
|
|
30
|
+
/**
|
|
31
|
+
* Default: Test Quality SLO (95% pass rate over 30 days)
|
|
32
|
+
*/
|
|
33
|
+
export declare function createDefaultTestQualitySLO(): SLO;
|
|
34
|
+
/**
|
|
35
|
+
* Default: API Success Rate SLO (99.9% over 30 days)
|
|
36
|
+
*/
|
|
37
|
+
export declare function createDefaultAPISuccessSLO(): SLO;
|
|
38
|
+
/**
|
|
39
|
+
* Default: Performance SLO (p95 latency < 500ms)
|
|
40
|
+
*/
|
|
41
|
+
export declare function createDefaultPerformanceSLO(): SLO;
|
|
42
|
+
/**
|
|
43
|
+
* Strict: Test Quality SLO (98% pass rate)
|
|
44
|
+
*/
|
|
45
|
+
export declare function createStrictTestQualitySLO(): SLO;
|
|
46
|
+
/**
|
|
47
|
+
* Strict: API Success Rate SLO (99.95%)
|
|
48
|
+
*/
|
|
49
|
+
export declare function createStrictAPISuccessSLO(): SLO;
|
|
50
|
+
/**
|
|
51
|
+
* Strict: Performance SLO (p95 latency < 200ms)
|
|
52
|
+
*/
|
|
53
|
+
export declare function createStrictPerformanceSLO(): SLO;
|
|
54
|
+
/**
|
|
55
|
+
* Relaxed: Test Quality SLO (90% pass rate)
|
|
56
|
+
*/
|
|
57
|
+
export declare function createRelaxedTestQualitySLO(): SLO;
|
|
58
|
+
/**
|
|
59
|
+
* Relaxed: API Success Rate SLO (99%)
|
|
60
|
+
*/
|
|
61
|
+
export declare function createRelaxedAPISuccessSLO(): SLO;
|
|
62
|
+
/**
|
|
63
|
+
* Relaxed: Performance SLO (p95 latency < 1000ms)
|
|
64
|
+
*/
|
|
65
|
+
export declare function createRelaxedPerformanceSLO(): SLO;
|
|
66
|
+
/**
|
|
67
|
+
* SLI: Test Pass Rate
|
|
68
|
+
*/
|
|
69
|
+
export declare function createTestPassRateSLI(): SLI;
|
|
70
|
+
/**
|
|
71
|
+
* SLI: API Success Rate
|
|
72
|
+
*/
|
|
73
|
+
export declare function createAPISuccessRateSLI(): SLI;
|
|
74
|
+
/**
|
|
75
|
+
* SLI: Latency (p95)
|
|
76
|
+
*/
|
|
77
|
+
export declare function createLatencySLI(): SLI;
|
|
78
|
+
/**
|
|
79
|
+
* Create custom SLO from parameters
|
|
80
|
+
*/
|
|
81
|
+
export declare function createCustomSLO(params: {
|
|
82
|
+
name: string;
|
|
83
|
+
description?: string;
|
|
84
|
+
target: number;
|
|
85
|
+
windowMs?: number;
|
|
86
|
+
sliIds: string[];
|
|
87
|
+
tags?: string[];
|
|
88
|
+
owner?: string;
|
|
89
|
+
}): SLO;
|
|
90
|
+
/**
|
|
91
|
+
* Create custom SLI from parameters
|
|
92
|
+
*/
|
|
93
|
+
export declare function createCustomSLI(params: {
|
|
94
|
+
name: string;
|
|
95
|
+
description?: string;
|
|
96
|
+
type: SLI['type'];
|
|
97
|
+
source: SLI['source'];
|
|
98
|
+
queryString: string;
|
|
99
|
+
aggregation: SLI['aggregation'];
|
|
100
|
+
unit: SLI['unit'];
|
|
101
|
+
threshold: {
|
|
102
|
+
operator: SLI['threshold']['operator'];
|
|
103
|
+
value: number;
|
|
104
|
+
};
|
|
105
|
+
category?: SLI['category'];
|
|
106
|
+
tags?: string[];
|
|
107
|
+
}): SLI;
|