agentic-qe 3.7.6 → 3.7.7
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/.claude/skills/skills-manifest.json +1 -1
- package/CHANGELOG.md +20 -0
- package/dist/cli/bundle.js +1023 -40
- package/dist/domains/test-execution/services/e2e/adaptive-locator-service.d.ts +71 -0
- package/dist/domains/test-execution/services/e2e/adaptive-locator-service.d.ts.map +1 -0
- package/dist/domains/test-execution/services/e2e/adaptive-locator-service.js +456 -0
- package/dist/domains/test-execution/services/e2e/adaptive-locator-service.js.map +1 -0
- package/dist/domains/test-execution/services/e2e/adaptive-locator-types.d.ts +81 -0
- package/dist/domains/test-execution/services/e2e/adaptive-locator-types.d.ts.map +1 -0
- package/dist/domains/test-execution/services/e2e/adaptive-locator-types.js +20 -0
- package/dist/domains/test-execution/services/e2e/adaptive-locator-types.js.map +1 -0
- package/dist/domains/test-execution/services/e2e/browser-orchestrator.d.ts +19 -0
- package/dist/domains/test-execution/services/e2e/browser-orchestrator.d.ts.map +1 -1
- package/dist/domains/test-execution/services/e2e/browser-orchestrator.js +82 -0
- package/dist/domains/test-execution/services/e2e/browser-orchestrator.js.map +1 -1
- package/dist/domains/test-execution/services/e2e/index.d.ts +2 -0
- package/dist/domains/test-execution/services/e2e/index.d.ts.map +1 -1
- package/dist/domains/test-execution/services/e2e/index.js +5 -0
- package/dist/domains/test-execution/services/e2e/index.js.map +1 -1
- package/dist/domains/test-execution/services/e2e/step-executors.d.ts +6 -0
- package/dist/domains/test-execution/services/e2e/step-executors.d.ts.map +1 -1
- package/dist/domains/test-execution/services/e2e/step-executors.js +17 -2
- package/dist/domains/test-execution/services/e2e/step-executors.js.map +1 -1
- package/dist/domains/test-execution/services/e2e/types.d.ts +18 -1
- package/dist/domains/test-execution/services/e2e/types.d.ts.map +1 -1
- package/dist/domains/test-execution/services/e2e/types.js.map +1 -1
- package/dist/integrations/browser/client-factory.d.ts +6 -1
- package/dist/integrations/browser/client-factory.d.ts.map +1 -1
- package/dist/integrations/browser/client-factory.js +37 -2
- package/dist/integrations/browser/client-factory.js.map +1 -1
- package/dist/integrations/browser/index.d.ts +5 -1
- package/dist/integrations/browser/index.d.ts.map +1 -1
- package/dist/integrations/browser/index.js +8 -1
- package/dist/integrations/browser/index.js.map +1 -1
- package/dist/integrations/browser/page-pool-types.d.ts +70 -0
- package/dist/integrations/browser/page-pool-types.d.ts.map +1 -0
- package/dist/integrations/browser/page-pool-types.js +19 -0
- package/dist/integrations/browser/page-pool-types.js.map +1 -0
- package/dist/integrations/browser/page-pool.d.ts +79 -0
- package/dist/integrations/browser/page-pool.d.ts.map +1 -0
- package/dist/integrations/browser/page-pool.js +288 -0
- package/dist/integrations/browser/page-pool.js.map +1 -0
- package/dist/integrations/browser/resource-blocking.d.ts +47 -0
- package/dist/integrations/browser/resource-blocking.d.ts.map +1 -0
- package/dist/integrations/browser/resource-blocking.js +195 -0
- package/dist/integrations/browser/resource-blocking.js.map +1 -0
- package/dist/integrations/browser/stealth/index.d.ts +8 -0
- package/dist/integrations/browser/stealth/index.d.ts.map +1 -0
- package/dist/integrations/browser/stealth/index.js +7 -0
- package/dist/integrations/browser/stealth/index.js.map +1 -0
- package/dist/integrations/browser/stealth/stealth-client.d.ts +51 -0
- package/dist/integrations/browser/stealth/stealth-client.d.ts.map +1 -0
- package/dist/integrations/browser/stealth/stealth-client.js +359 -0
- package/dist/integrations/browser/stealth/stealth-client.js.map +1 -0
- package/dist/integrations/browser/stealth/stealth-types.d.ts +35 -0
- package/dist/integrations/browser/stealth/stealth-types.d.ts.map +1 -0
- package/dist/integrations/browser/stealth/stealth-types.js +17 -0
- package/dist/integrations/browser/stealth/stealth-types.js.map +1 -0
- package/dist/integrations/browser/types.d.ts +13 -10
- package/dist/integrations/browser/types.d.ts.map +1 -1
- package/dist/integrations/browser/types.js.map +1 -1
- package/dist/mcp/bundle.js +1114 -70
- package/package.json +1 -1
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adaptive Locator Service
|
|
3
|
+
*
|
|
4
|
+
* Inspired by Scrapling's similarity-based element matching.
|
|
5
|
+
* When a primary selector fails, falls back through text, ARIA, and
|
|
6
|
+
* fingerprint similarity matching to find the intended element.
|
|
7
|
+
*
|
|
8
|
+
* @module test-execution/services/e2e/adaptive-locator-service
|
|
9
|
+
*/
|
|
10
|
+
import type { IBrowserClient } from '@integrations/browser';
|
|
11
|
+
import type { ElementFingerprint, AdaptiveLocatorConfig, LocatorResolutionResult } from './adaptive-locator-types';
|
|
12
|
+
/**
|
|
13
|
+
* Compute weighted similarity between a stored fingerprint and a candidate element
|
|
14
|
+
*/
|
|
15
|
+
export declare function computeSimilarity(stored: ElementFingerprint, candidate: Omit<ElementFingerprint, 'matchCount' | 'lastMatchedAt'>): number;
|
|
16
|
+
/**
|
|
17
|
+
* Simple in-memory fingerprint store keyed by `pageUrl::selector`
|
|
18
|
+
*/
|
|
19
|
+
export declare class FingerprintStore {
|
|
20
|
+
private readonly store;
|
|
21
|
+
private readonly maxPerPage;
|
|
22
|
+
constructor(maxPerPage?: number);
|
|
23
|
+
private key;
|
|
24
|
+
get(pageUrl: string, selector: string): ElementFingerprint | undefined;
|
|
25
|
+
set(pageUrl: string, selector: string, fp: ElementFingerprint): void;
|
|
26
|
+
size(): number;
|
|
27
|
+
clear(): void;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Adaptive Locator Service
|
|
31
|
+
*
|
|
32
|
+
* Provides fallback element resolution when primary selectors fail.
|
|
33
|
+
* Maintains a fingerprint database of previously-matched elements
|
|
34
|
+
* and uses weighted similarity to find the best match.
|
|
35
|
+
*/
|
|
36
|
+
export declare class AdaptiveLocatorService {
|
|
37
|
+
private readonly config;
|
|
38
|
+
private readonly fingerprintStore;
|
|
39
|
+
constructor(config?: Partial<AdaptiveLocatorConfig>);
|
|
40
|
+
/**
|
|
41
|
+
* Capture and store a fingerprint for a successfully-interacted element
|
|
42
|
+
*/
|
|
43
|
+
captureFingerprint(selector: string, client: IBrowserClient, pageUrl: string): Promise<ElementFingerprint | null>;
|
|
44
|
+
/**
|
|
45
|
+
* Resolve an element target with adaptive fallback chain.
|
|
46
|
+
*
|
|
47
|
+
* 1. Try primary selector
|
|
48
|
+
* 2. Text-based match
|
|
49
|
+
* 3. ARIA-based match
|
|
50
|
+
* 4. Fingerprint similarity matching
|
|
51
|
+
*/
|
|
52
|
+
resolveWithFallback(selector: string, client: IBrowserClient, pageUrl: string): Promise<LocatorResolutionResult | null>;
|
|
53
|
+
/**
|
|
54
|
+
* Get the fingerprint store size (for diagnostics)
|
|
55
|
+
*/
|
|
56
|
+
getStoreSize(): number;
|
|
57
|
+
/**
|
|
58
|
+
* Clear all stored fingerprints
|
|
59
|
+
*/
|
|
60
|
+
clearFingerprints(): void;
|
|
61
|
+
private tryFallbackMethod;
|
|
62
|
+
private tryTextMatch;
|
|
63
|
+
private tryAriaMatch;
|
|
64
|
+
private tryFingerprintMatch;
|
|
65
|
+
/**
|
|
66
|
+
* Convert a selector string to a CSS selector suitable for capture.
|
|
67
|
+
* Returns null for non-CSS selectors that can't be used with querySelector.
|
|
68
|
+
*/
|
|
69
|
+
private toCssForCapture;
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=adaptive-locator-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adaptive-locator-service.d.ts","sourceRoot":"","sources":["../../../../../src/domains/test-execution/services/e2e/adaptive-locator-service.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAiB,MAAM,uBAAuB,CAAC;AAC3E,OAAO,KAAK,EACV,kBAAkB,EAClB,qBAAqB,EACrB,uBAAuB,EAExB,MAAM,0BAA0B,CAAC;AA4JlC;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,kBAAkB,EAC1B,SAAS,EAAE,IAAI,CAAC,kBAAkB,EAAE,YAAY,GAAG,eAAe,CAAC,GAClE,MAAM,CAuCR;AA8BD;;GAEG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAyC;IAC/D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;gBAExB,UAAU,SAAM;IAI5B,OAAO,CAAC,GAAG;IAIX,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS;IAItE,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,kBAAkB,GAAG,IAAI;IAepE,IAAI,IAAI,MAAM;IAId,KAAK,IAAI,IAAI;CAGd;AAMD;;;;;;GAMG;AACH,qBAAa,sBAAsB;IACjC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAwB;IAC/C,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAmB;gBAExC,MAAM,CAAC,EAAE,OAAO,CAAC,qBAAqB,CAAC;IAKnD;;OAEG;IACG,kBAAkB,CACtB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC;IAwBrC;;;;;;;OAOG;IACG,mBAAmB,CACvB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,uBAAuB,GAAG,IAAI,CAAC;IAc1C;;OAEG;IACH,YAAY,IAAI,MAAM;IAItB;;OAEG;IACH,iBAAiB,IAAI,IAAI;YAQX,iBAAiB;YAiBjB,YAAY;YAwCZ,YAAY;YAkCZ,mBAAmB;IAoCjC;;;OAGG;IACH,OAAO,CAAC,eAAe;CAMxB"}
|
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adaptive Locator Service
|
|
3
|
+
*
|
|
4
|
+
* Inspired by Scrapling's similarity-based element matching.
|
|
5
|
+
* When a primary selector fails, falls back through text, ARIA, and
|
|
6
|
+
* fingerprint similarity matching to find the intended element.
|
|
7
|
+
*
|
|
8
|
+
* @module test-execution/services/e2e/adaptive-locator-service
|
|
9
|
+
*/
|
|
10
|
+
import { DEFAULT_ADAPTIVE_LOCATOR_CONFIG } from './adaptive-locator-types';
|
|
11
|
+
// ============================================================================
|
|
12
|
+
// Fingerprint Capture Script (runs in browser context)
|
|
13
|
+
// ============================================================================
|
|
14
|
+
/**
|
|
15
|
+
* JavaScript to evaluate in the browser to capture an element fingerprint.
|
|
16
|
+
* Returns a serialisable object matching ElementFingerprint.
|
|
17
|
+
*/
|
|
18
|
+
function buildCaptureScript(cssSelector) {
|
|
19
|
+
return `
|
|
20
|
+
(() => {
|
|
21
|
+
const el = document.querySelector(${JSON.stringify(cssSelector)});
|
|
22
|
+
if (!el) return null;
|
|
23
|
+
const parent = el.parentElement;
|
|
24
|
+
const siblings = parent ? parent.children : [];
|
|
25
|
+
let childIndex = 0;
|
|
26
|
+
for (let i = 0; i < siblings.length; i++) {
|
|
27
|
+
if (siblings[i] === el) { childIndex = i; break; }
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
tagName: el.tagName.toLowerCase(),
|
|
31
|
+
classes: Array.from(el.classList),
|
|
32
|
+
ariaRole: el.getAttribute('role') || undefined,
|
|
33
|
+
ariaLabel: el.getAttribute('aria-label') || undefined,
|
|
34
|
+
textContent: (el.textContent || '').trim().slice(0, 200) || undefined,
|
|
35
|
+
attributes: {
|
|
36
|
+
id: el.id || undefined,
|
|
37
|
+
name: el.getAttribute('name') || undefined,
|
|
38
|
+
'data-testid': el.getAttribute('data-testid') || undefined,
|
|
39
|
+
type: el.getAttribute('type') || undefined,
|
|
40
|
+
placeholder: el.getAttribute('placeholder') || undefined,
|
|
41
|
+
href: el.getAttribute('href') || undefined,
|
|
42
|
+
},
|
|
43
|
+
positionHints: {
|
|
44
|
+
parentTag: parent ? parent.tagName.toLowerCase() : undefined,
|
|
45
|
+
childIndex,
|
|
46
|
+
siblingCount: siblings.length,
|
|
47
|
+
},
|
|
48
|
+
matchCount: 0,
|
|
49
|
+
lastMatchedAt: new Date().toISOString(),
|
|
50
|
+
};
|
|
51
|
+
})()
|
|
52
|
+
`;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* JavaScript to find candidate elements on the page and return their fingerprints.
|
|
56
|
+
*/
|
|
57
|
+
function buildCandidatesScript() {
|
|
58
|
+
return `
|
|
59
|
+
(() => {
|
|
60
|
+
const interactiveTags = 'a,button,input,select,textarea,[role],[data-testid],[aria-label]';
|
|
61
|
+
const elements = document.querySelectorAll(interactiveTags);
|
|
62
|
+
const results = [];
|
|
63
|
+
for (let i = 0; i < Math.min(elements.length, 300); i++) {
|
|
64
|
+
const el = elements[i];
|
|
65
|
+
const parent = el.parentElement;
|
|
66
|
+
const siblings = parent ? parent.children : [];
|
|
67
|
+
let childIndex = 0;
|
|
68
|
+
for (let j = 0; j < siblings.length; j++) {
|
|
69
|
+
if (siblings[j] === el) { childIndex = j; break; }
|
|
70
|
+
}
|
|
71
|
+
results.push({
|
|
72
|
+
tagName: el.tagName.toLowerCase(),
|
|
73
|
+
classes: Array.from(el.classList),
|
|
74
|
+
ariaRole: el.getAttribute('role') || undefined,
|
|
75
|
+
ariaLabel: el.getAttribute('aria-label') || undefined,
|
|
76
|
+
textContent: (el.textContent || '').trim().slice(0, 200) || undefined,
|
|
77
|
+
attributes: {
|
|
78
|
+
id: el.id || undefined,
|
|
79
|
+
name: el.getAttribute('name') || undefined,
|
|
80
|
+
'data-testid': el.getAttribute('data-testid') || undefined,
|
|
81
|
+
type: el.getAttribute('type') || undefined,
|
|
82
|
+
placeholder: el.getAttribute('placeholder') || undefined,
|
|
83
|
+
href: el.getAttribute('href') || undefined,
|
|
84
|
+
},
|
|
85
|
+
positionHints: {
|
|
86
|
+
parentTag: parent ? parent.tagName.toLowerCase() : undefined,
|
|
87
|
+
childIndex,
|
|
88
|
+
siblingCount: siblings.length,
|
|
89
|
+
},
|
|
90
|
+
// Build a unique-enough CSS selector for this element
|
|
91
|
+
selector: buildSelector(el),
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function buildSelector(el) {
|
|
96
|
+
if (el.id) return '#' + CSS.escape(el.id);
|
|
97
|
+
if (el.getAttribute('data-testid')) return '[data-testid="' + el.getAttribute('data-testid') + '"]';
|
|
98
|
+
if (el.getAttribute('name')) return el.tagName.toLowerCase() + '[name="' + el.getAttribute('name') + '"]';
|
|
99
|
+
// Fallback: tag + nth-child
|
|
100
|
+
const parent = el.parentElement;
|
|
101
|
+
if (!parent) return el.tagName.toLowerCase();
|
|
102
|
+
const siblings = Array.from(parent.children).filter(s => s.tagName === el.tagName);
|
|
103
|
+
const idx = siblings.indexOf(el) + 1;
|
|
104
|
+
return el.tagName.toLowerCase() + ':nth-of-type(' + idx + ')';
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return results;
|
|
108
|
+
})()
|
|
109
|
+
`;
|
|
110
|
+
}
|
|
111
|
+
// ============================================================================
|
|
112
|
+
// Similarity Computation
|
|
113
|
+
// ============================================================================
|
|
114
|
+
/** Weights for similarity scoring */
|
|
115
|
+
const WEIGHTS = {
|
|
116
|
+
tagName: 0.20,
|
|
117
|
+
ariaRole: 0.15,
|
|
118
|
+
classes: 0.15,
|
|
119
|
+
textContent: 0.20,
|
|
120
|
+
attributes: 0.15,
|
|
121
|
+
positionHints: 0.15,
|
|
122
|
+
};
|
|
123
|
+
/**
|
|
124
|
+
* Compute Jaccard similarity between two string arrays
|
|
125
|
+
*/
|
|
126
|
+
function jaccardSimilarity(a, b) {
|
|
127
|
+
if (a.length === 0 && b.length === 0)
|
|
128
|
+
return 1;
|
|
129
|
+
const setA = new Set(a);
|
|
130
|
+
const setB = new Set(b);
|
|
131
|
+
let intersection = 0;
|
|
132
|
+
for (const item of setA) {
|
|
133
|
+
if (setB.has(item))
|
|
134
|
+
intersection++;
|
|
135
|
+
}
|
|
136
|
+
const union = new Set([...setA, ...setB]).size;
|
|
137
|
+
return union === 0 ? 0 : intersection / union;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Normalised string similarity (simple containment-based)
|
|
141
|
+
*/
|
|
142
|
+
function textSimilarity(a, b) {
|
|
143
|
+
if (!a && !b)
|
|
144
|
+
return 1;
|
|
145
|
+
if (!a || !b)
|
|
146
|
+
return 0;
|
|
147
|
+
const la = a.toLowerCase();
|
|
148
|
+
const lb = b.toLowerCase();
|
|
149
|
+
if (la === lb)
|
|
150
|
+
return 1;
|
|
151
|
+
if (la.includes(lb) || lb.includes(la))
|
|
152
|
+
return 0.8;
|
|
153
|
+
// Character overlap ratio
|
|
154
|
+
const setA = new Set(la);
|
|
155
|
+
const setB = new Set(lb);
|
|
156
|
+
let common = 0;
|
|
157
|
+
for (const c of setA) {
|
|
158
|
+
if (setB.has(c))
|
|
159
|
+
common++;
|
|
160
|
+
}
|
|
161
|
+
const total = new Set([...setA, ...setB]).size;
|
|
162
|
+
return total === 0 ? 0 : common / total * 0.6;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Compute weighted similarity between a stored fingerprint and a candidate element
|
|
166
|
+
*/
|
|
167
|
+
export function computeSimilarity(stored, candidate) {
|
|
168
|
+
let score = 0;
|
|
169
|
+
// Tag name (exact match)
|
|
170
|
+
score += (stored.tagName === candidate.tagName ? 1 : 0) * WEIGHTS.tagName;
|
|
171
|
+
// ARIA role
|
|
172
|
+
if (stored.ariaRole || candidate.ariaRole) {
|
|
173
|
+
score += (stored.ariaRole === candidate.ariaRole ? 1 : 0) * WEIGHTS.ariaRole;
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
score += WEIGHTS.ariaRole; // both undefined = match
|
|
177
|
+
}
|
|
178
|
+
// Classes (Jaccard)
|
|
179
|
+
score += jaccardSimilarity(stored.classes, candidate.classes) * WEIGHTS.classes;
|
|
180
|
+
// Text content
|
|
181
|
+
score += textSimilarity(stored.textContent, candidate.textContent) * WEIGHTS.textContent;
|
|
182
|
+
// Attributes overlap
|
|
183
|
+
const storedAttrs = Object.entries(stored.attributes).filter(([, v]) => v);
|
|
184
|
+
const candidateAttrs = Object.entries(candidate.attributes).filter(([, v]) => v);
|
|
185
|
+
if (storedAttrs.length === 0 && candidateAttrs.length === 0) {
|
|
186
|
+
score += WEIGHTS.attributes;
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
let matches = 0;
|
|
190
|
+
for (const [key, val] of storedAttrs) {
|
|
191
|
+
const candidateVal = candidate.attributes[key];
|
|
192
|
+
if (candidateVal === val)
|
|
193
|
+
matches++;
|
|
194
|
+
}
|
|
195
|
+
const total = Math.max(storedAttrs.length, candidateAttrs.length, 1);
|
|
196
|
+
score += (matches / total) * WEIGHTS.attributes;
|
|
197
|
+
}
|
|
198
|
+
// Position hints
|
|
199
|
+
const posScore = computePositionScore(stored.positionHints, candidate.positionHints);
|
|
200
|
+
score += posScore * WEIGHTS.positionHints;
|
|
201
|
+
return Math.min(1, Math.max(0, score));
|
|
202
|
+
}
|
|
203
|
+
function computePositionScore(a, b) {
|
|
204
|
+
let total = 0;
|
|
205
|
+
let matches = 0;
|
|
206
|
+
// Parent tag
|
|
207
|
+
total++;
|
|
208
|
+
if (a.parentTag === b.parentTag)
|
|
209
|
+
matches++;
|
|
210
|
+
// Child index proximity
|
|
211
|
+
total++;
|
|
212
|
+
const indexDiff = Math.abs(a.childIndex - b.childIndex);
|
|
213
|
+
matches += Math.max(0, 1 - indexDiff / 10);
|
|
214
|
+
// Sibling count proximity
|
|
215
|
+
total++;
|
|
216
|
+
const sibDiff = Math.abs(a.siblingCount - b.siblingCount);
|
|
217
|
+
matches += Math.max(0, 1 - sibDiff / 10);
|
|
218
|
+
return matches / total;
|
|
219
|
+
}
|
|
220
|
+
// ============================================================================
|
|
221
|
+
// In-Memory Fingerprint Store (lightweight, no DB dependency)
|
|
222
|
+
// ============================================================================
|
|
223
|
+
/**
|
|
224
|
+
* Simple in-memory fingerprint store keyed by `pageUrl::selector`
|
|
225
|
+
*/
|
|
226
|
+
export class FingerprintStore {
|
|
227
|
+
store = new Map();
|
|
228
|
+
maxPerPage;
|
|
229
|
+
constructor(maxPerPage = 200) {
|
|
230
|
+
this.maxPerPage = maxPerPage;
|
|
231
|
+
}
|
|
232
|
+
key(pageUrl, selector) {
|
|
233
|
+
return `${pageUrl}::${selector}`;
|
|
234
|
+
}
|
|
235
|
+
get(pageUrl, selector) {
|
|
236
|
+
return this.store.get(this.key(pageUrl, selector));
|
|
237
|
+
}
|
|
238
|
+
set(pageUrl, selector, fp) {
|
|
239
|
+
const k = this.key(pageUrl, selector);
|
|
240
|
+
this.store.set(k, fp);
|
|
241
|
+
// Enforce per-page limit by evicting oldest entries
|
|
242
|
+
const prefix = `${pageUrl}::`;
|
|
243
|
+
const pageKeys = [...this.store.keys()].filter((key) => key.startsWith(prefix));
|
|
244
|
+
if (pageKeys.length > this.maxPerPage) {
|
|
245
|
+
const toRemove = pageKeys.length - this.maxPerPage;
|
|
246
|
+
for (let i = 0; i < toRemove; i++) {
|
|
247
|
+
this.store.delete(pageKeys[i]);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
size() {
|
|
252
|
+
return this.store.size;
|
|
253
|
+
}
|
|
254
|
+
clear() {
|
|
255
|
+
this.store.clear();
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
// ============================================================================
|
|
259
|
+
// Adaptive Locator Service
|
|
260
|
+
// ============================================================================
|
|
261
|
+
/**
|
|
262
|
+
* Adaptive Locator Service
|
|
263
|
+
*
|
|
264
|
+
* Provides fallback element resolution when primary selectors fail.
|
|
265
|
+
* Maintains a fingerprint database of previously-matched elements
|
|
266
|
+
* and uses weighted similarity to find the best match.
|
|
267
|
+
*/
|
|
268
|
+
export class AdaptiveLocatorService {
|
|
269
|
+
config;
|
|
270
|
+
fingerprintStore;
|
|
271
|
+
constructor(config) {
|
|
272
|
+
this.config = { ...DEFAULT_ADAPTIVE_LOCATOR_CONFIG, ...config };
|
|
273
|
+
this.fingerprintStore = new FingerprintStore(this.config.maxFingerprintsPerPage);
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Capture and store a fingerprint for a successfully-interacted element
|
|
277
|
+
*/
|
|
278
|
+
async captureFingerprint(selector, client, pageUrl) {
|
|
279
|
+
if (!this.config.enabled)
|
|
280
|
+
return null;
|
|
281
|
+
try {
|
|
282
|
+
const cssSelector = this.toCssForCapture(selector);
|
|
283
|
+
if (!cssSelector)
|
|
284
|
+
return null;
|
|
285
|
+
const result = await client.evaluate(buildCaptureScript(cssSelector));
|
|
286
|
+
if (!result.success || !result.value)
|
|
287
|
+
return null;
|
|
288
|
+
const fingerprint = result.value;
|
|
289
|
+
fingerprint.matchCount = (this.fingerprintStore.get(pageUrl, selector)?.matchCount ?? 0) + 1;
|
|
290
|
+
fingerprint.lastMatchedAt = new Date().toISOString();
|
|
291
|
+
this.fingerprintStore.set(pageUrl, selector, fingerprint);
|
|
292
|
+
return fingerprint;
|
|
293
|
+
}
|
|
294
|
+
catch {
|
|
295
|
+
return null;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Resolve an element target with adaptive fallback chain.
|
|
300
|
+
*
|
|
301
|
+
* 1. Try primary selector
|
|
302
|
+
* 2. Text-based match
|
|
303
|
+
* 3. ARIA-based match
|
|
304
|
+
* 4. Fingerprint similarity matching
|
|
305
|
+
*/
|
|
306
|
+
async resolveWithFallback(selector, client, pageUrl) {
|
|
307
|
+
if (!this.config.enabled)
|
|
308
|
+
return null;
|
|
309
|
+
const storedFingerprint = this.fingerprintStore.get(pageUrl, selector);
|
|
310
|
+
if (!storedFingerprint)
|
|
311
|
+
return null;
|
|
312
|
+
for (const method of this.config.fallbackChain) {
|
|
313
|
+
const result = await this.tryFallbackMethod(method, storedFingerprint, client);
|
|
314
|
+
if (result)
|
|
315
|
+
return result;
|
|
316
|
+
}
|
|
317
|
+
return null;
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Get the fingerprint store size (for diagnostics)
|
|
321
|
+
*/
|
|
322
|
+
getStoreSize() {
|
|
323
|
+
return this.fingerprintStore.size();
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Clear all stored fingerprints
|
|
327
|
+
*/
|
|
328
|
+
clearFingerprints() {
|
|
329
|
+
this.fingerprintStore.clear();
|
|
330
|
+
}
|
|
331
|
+
// ==========================================================================
|
|
332
|
+
// Private Helpers
|
|
333
|
+
// ==========================================================================
|
|
334
|
+
async tryFallbackMethod(method, stored, client) {
|
|
335
|
+
switch (method) {
|
|
336
|
+
case 'text':
|
|
337
|
+
return this.tryTextMatch(stored, client);
|
|
338
|
+
case 'aria':
|
|
339
|
+
return this.tryAriaMatch(stored, client);
|
|
340
|
+
case 'fingerprint':
|
|
341
|
+
return this.tryFingerprintMatch(stored, client);
|
|
342
|
+
default:
|
|
343
|
+
return null;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
async tryTextMatch(stored, client) {
|
|
347
|
+
if (!stored.textContent)
|
|
348
|
+
return null;
|
|
349
|
+
const safeText = JSON.stringify(stored.textContent);
|
|
350
|
+
const script = `
|
|
351
|
+
(() => {
|
|
352
|
+
const target = ${safeText};
|
|
353
|
+
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_ELEMENT);
|
|
354
|
+
while (walker.nextNode()) {
|
|
355
|
+
const el = walker.currentNode;
|
|
356
|
+
const text = (el.textContent || '').trim().slice(0, 200);
|
|
357
|
+
if (text === target) {
|
|
358
|
+
if (el.id) return '#' + CSS.escape(el.id);
|
|
359
|
+
if (el.getAttribute('data-testid')) return '[data-testid="' + el.getAttribute('data-testid') + '"]';
|
|
360
|
+
return null;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
return null;
|
|
364
|
+
})()
|
|
365
|
+
`;
|
|
366
|
+
try {
|
|
367
|
+
const result = await client.evaluate(script);
|
|
368
|
+
if (result.success && result.value) {
|
|
369
|
+
return {
|
|
370
|
+
resolvedSelector: result.value,
|
|
371
|
+
method: 'text',
|
|
372
|
+
similarityScore: 0.9,
|
|
373
|
+
fingerprintUpdated: false,
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
catch {
|
|
378
|
+
// continue to next method
|
|
379
|
+
}
|
|
380
|
+
return null;
|
|
381
|
+
}
|
|
382
|
+
async tryAriaMatch(stored, client) {
|
|
383
|
+
if (!stored.ariaRole && !stored.ariaLabel)
|
|
384
|
+
return null;
|
|
385
|
+
let ariaSelector = '';
|
|
386
|
+
if (stored.ariaRole && stored.ariaLabel) {
|
|
387
|
+
ariaSelector = `[role="${stored.ariaRole}"][aria-label="${stored.ariaLabel}"]`;
|
|
388
|
+
}
|
|
389
|
+
else if (stored.ariaLabel) {
|
|
390
|
+
ariaSelector = `[aria-label="${stored.ariaLabel}"]`;
|
|
391
|
+
}
|
|
392
|
+
else if (stored.ariaRole) {
|
|
393
|
+
ariaSelector = `[role="${stored.ariaRole}"]`;
|
|
394
|
+
}
|
|
395
|
+
if (!ariaSelector)
|
|
396
|
+
return null;
|
|
397
|
+
try {
|
|
398
|
+
const checkScript = `!!document.querySelector(${JSON.stringify(ariaSelector)})`;
|
|
399
|
+
const result = await client.evaluate(checkScript);
|
|
400
|
+
if (result.success && result.value) {
|
|
401
|
+
return {
|
|
402
|
+
resolvedSelector: ariaSelector,
|
|
403
|
+
method: 'aria',
|
|
404
|
+
similarityScore: 0.85,
|
|
405
|
+
fingerprintUpdated: false,
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
catch {
|
|
410
|
+
// continue to next method
|
|
411
|
+
}
|
|
412
|
+
return null;
|
|
413
|
+
}
|
|
414
|
+
async tryFingerprintMatch(stored, client) {
|
|
415
|
+
try {
|
|
416
|
+
const result = await client.evaluate(buildCandidatesScript());
|
|
417
|
+
if (!result.success || !result.value?.length)
|
|
418
|
+
return null;
|
|
419
|
+
let bestScore = 0;
|
|
420
|
+
let bestSelector = '';
|
|
421
|
+
for (const candidate of result.value) {
|
|
422
|
+
const score = computeSimilarity(stored, candidate);
|
|
423
|
+
if (score > bestScore) {
|
|
424
|
+
bestScore = score;
|
|
425
|
+
bestSelector = candidate.selector;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
if (bestScore >= this.config.similarityThreshold && bestSelector) {
|
|
429
|
+
return {
|
|
430
|
+
resolvedSelector: bestSelector,
|
|
431
|
+
method: 'fingerprint',
|
|
432
|
+
similarityScore: bestScore,
|
|
433
|
+
fingerprintUpdated: false,
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
catch {
|
|
438
|
+
// fingerprint match failed
|
|
439
|
+
}
|
|
440
|
+
return null;
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Convert a selector string to a CSS selector suitable for capture.
|
|
444
|
+
* Returns null for non-CSS selectors that can't be used with querySelector.
|
|
445
|
+
*/
|
|
446
|
+
toCssForCapture(selector) {
|
|
447
|
+
if (selector.startsWith('//') || selector.startsWith('xpath='))
|
|
448
|
+
return null;
|
|
449
|
+
if (/^@?e\d+$/.test(selector))
|
|
450
|
+
return null;
|
|
451
|
+
if (selector.startsWith('text='))
|
|
452
|
+
return null;
|
|
453
|
+
return selector;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
//# sourceMappingURL=adaptive-locator-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adaptive-locator-service.js","sourceRoot":"","sources":["../../../../../src/domains/test-execution/services/e2e/adaptive-locator-service.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AASH,OAAO,EAAE,+BAA+B,EAAE,MAAM,0BAA0B,CAAC;AAE3E,+EAA+E;AAC/E,uDAAuD;AACvD,+EAA+E;AAE/E;;;GAGG;AACH,SAAS,kBAAkB,CAAC,WAAmB;IAC7C,OAAO;;0CAEiC,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BlE,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,SAAS,qBAAqB;IAC5B,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDN,CAAC;AACJ,CAAC;AAED,+EAA+E;AAC/E,yBAAyB;AACzB,+EAA+E;AAE/E,qCAAqC;AACrC,MAAM,OAAO,GAAG;IACd,OAAO,EAAE,IAAI;IACb,QAAQ,EAAE,IAAI;IACd,OAAO,EAAE,IAAI;IACb,WAAW,EAAE,IAAI;IACjB,UAAU,EAAE,IAAI;IAChB,aAAa,EAAE,IAAI;CACX,CAAC;AAEX;;GAEG;AACH,SAAS,iBAAiB,CAAC,CAAW,EAAE,CAAW;IACjD,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IAC/C,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC;IACxB,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC;IACxB,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,KAAK,MAAM,IAAI,IAAI,IAAI,EAAE,CAAC;QACxB,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,YAAY,EAAE,CAAC;IACrC,CAAC;IACD,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,IAAI,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;IAC/C,OAAO,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,YAAY,GAAG,KAAK,CAAC;AAChD,CAAC;AAED;;GAEG;AACH,SAAS,cAAc,CAAC,CAAU,EAAE,CAAU;IAC5C,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC;QAAE,OAAO,CAAC,CAAC;IACvB,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC;QAAE,OAAO,CAAC,CAAC;IACvB,MAAM,EAAE,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC;IAC3B,MAAM,EAAE,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC;IAC3B,IAAI,EAAE,KAAK,EAAE;QAAE,OAAO,CAAC,CAAC;IACxB,IAAI,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC;QAAE,OAAO,GAAG,CAAC;IACnD,0BAA0B;IAC1B,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,EAAE,CAAC,CAAC;IACzB,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,EAAE,CAAC,CAAC;IACzB,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QACrB,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;YAAE,MAAM,EAAE,CAAC;IAC5B,CAAC;IACD,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,IAAI,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;IAC/C,OAAO,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,KAAK,GAAG,GAAG,CAAC;AAChD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAC/B,MAA0B,EAC1B,SAAmE;IAEnE,IAAI,KAAK,GAAG,CAAC,CAAC;IAEd,yBAAyB;IACzB,KAAK,IAAI,CAAC,MAAM,CAAC,OAAO,KAAK,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC;IAE1E,YAAY;IACZ,IAAI,MAAM,CAAC,QAAQ,IAAI,SAAS,CAAC,QAAQ,EAAE,CAAC;QAC1C,KAAK,IAAI,CAAC,MAAM,CAAC,QAAQ,KAAK,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC;IAC/E,CAAC;SAAM,CAAC;QACN,KAAK,IAAI,OAAO,CAAC,QAAQ,CAAC,CAAC,yBAAyB;IACtD,CAAC;IAED,oBAAoB;IACpB,KAAK,IAAI,iBAAiB,CAAC,MAAM,CAAC,OAAO,EAAE,SAAS,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC;IAEhF,eAAe;IACf,KAAK,IAAI,cAAc,CAAC,MAAM,CAAC,WAAW,EAAE,SAAS,CAAC,WAAW,CAAC,GAAG,OAAO,CAAC,WAAW,CAAC;IAEzF,qBAAqB;IACrB,MAAM,WAAW,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;IAC3E,MAAM,cAAc,GAAG,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;IACjF,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,IAAI,cAAc,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC5D,KAAK,IAAI,OAAO,CAAC,UAAU,CAAC;IAC9B,CAAC;SAAM,CAAC;QACN,IAAI,OAAO,GAAG,CAAC,CAAC;QAChB,KAAK,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI,WAAW,EAAE,CAAC;YACrC,MAAM,YAAY,GAAG,SAAS,CAAC,UAAU,CAAC,GAA6C,CAAC,CAAC;YACzF,IAAI,YAAY,KAAK,GAAG;gBAAE,OAAO,EAAE,CAAC;QACtC,CAAC;QACD,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,WAAW,CAAC,MAAM,EAAE,cAAc,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;QACrE,KAAK,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC,GAAG,OAAO,CAAC,UAAU,CAAC;IAClD,CAAC;IAED,iBAAiB;IACjB,MAAM,QAAQ,GAAG,oBAAoB,CAAC,MAAM,CAAC,aAAa,EAAE,SAAS,CAAC,aAAa,CAAC,CAAC;IACrF,KAAK,IAAI,QAAQ,GAAG,OAAO,CAAC,aAAa,CAAC;IAE1C,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC;AACzC,CAAC;AAED,SAAS,oBAAoB,CAC3B,CAAsC,EACtC,CAAsC;IAEtC,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,OAAO,GAAG,CAAC,CAAC;IAEhB,aAAa;IACb,KAAK,EAAE,CAAC;IACR,IAAI,CAAC,CAAC,SAAS,KAAK,CAAC,CAAC,SAAS;QAAE,OAAO,EAAE,CAAC;IAE3C,wBAAwB;IACxB,KAAK,EAAE,CAAC;IACR,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC;IACxD,OAAO,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,SAAS,GAAG,EAAE,CAAC,CAAC;IAE3C,0BAA0B;IAC1B,KAAK,EAAE,CAAC;IACR,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,YAAY,GAAG,CAAC,CAAC,YAAY,CAAC,CAAC;IAC1D,OAAO,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,OAAO,GAAG,EAAE,CAAC,CAAC;IAEzC,OAAO,OAAO,GAAG,KAAK,CAAC;AACzB,CAAC;AAED,+EAA+E;AAC/E,8DAA8D;AAC9D,+EAA+E;AAE/E;;GAEG;AACH,MAAM,OAAO,gBAAgB;IACV,KAAK,GAAG,IAAI,GAAG,EAA8B,CAAC;IAC9C,UAAU,CAAS;IAEpC,YAAY,UAAU,GAAG,GAAG;QAC1B,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;IAC/B,CAAC;IAEO,GAAG,CAAC,OAAe,EAAE,QAAgB;QAC3C,OAAO,GAAG,OAAO,KAAK,QAAQ,EAAE,CAAC;IACnC,CAAC;IAED,GAAG,CAAC,OAAe,EAAE,QAAgB;QACnC,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC;IACrD,CAAC;IAED,GAAG,CAAC,OAAe,EAAE,QAAgB,EAAE,EAAsB;QAC3D,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;QACtC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAEtB,oDAAoD;QACpD,MAAM,MAAM,GAAG,GAAG,OAAO,IAAI,CAAC;QAC9B,MAAM,QAAQ,GAAG,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC;QAChF,IAAI,QAAQ,CAAC,MAAM,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC;YACtC,MAAM,QAAQ,GAAG,QAAQ,CAAC,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC;YACnD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,EAAE,CAAC,EAAE,EAAE,CAAC;gBAClC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;YACjC,CAAC;QACH,CAAC;IACH,CAAC;IAED,IAAI;QACF,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;IACzB,CAAC;IAED,KAAK;QACH,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;IACrB,CAAC;CACF;AAED,+EAA+E;AAC/E,2BAA2B;AAC3B,+EAA+E;AAE/E;;;;;;GAMG;AACH,MAAM,OAAO,sBAAsB;IAChB,MAAM,CAAwB;IAC9B,gBAAgB,CAAmB;IAEpD,YAAY,MAAuC;QACjD,IAAI,CAAC,MAAM,GAAG,EAAE,GAAG,+BAA+B,EAAE,GAAG,MAAM,EAAE,CAAC;QAChE,IAAI,CAAC,gBAAgB,GAAG,IAAI,gBAAgB,CAAC,IAAI,CAAC,MAAM,CAAC,sBAAsB,CAAC,CAAC;IACnF,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,kBAAkB,CACtB,QAAgB,EAChB,MAAsB,EACtB,OAAe;QAEf,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO;YAAE,OAAO,IAAI,CAAC;QAEtC,IAAI,CAAC;YACH,MAAM,WAAW,GAAG,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC;YACnD,IAAI,CAAC,WAAW;gBAAE,OAAO,IAAI,CAAC;YAE9B,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,QAAQ,CAClC,kBAAkB,CAAC,WAAW,CAAC,CAChC,CAAC;YAEF,IAAI,CAAC,MAAM,CAAC,OAAO,IAAI,CAAC,MAAM,CAAC,KAAK;gBAAE,OAAO,IAAI,CAAC;YAElD,MAAM,WAAW,GAAG,MAAM,CAAC,KAAK,CAAC;YACjC,WAAW,CAAC,UAAU,GAAG,CAAC,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,OAAO,EAAE,QAAQ,CAAC,EAAE,UAAU,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;YAC7F,WAAW,CAAC,aAAa,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;YAErD,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,OAAO,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAC;YAC1D,OAAO,WAAW,CAAC;QACrB,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAED;;;;;;;OAOG;IACH,KAAK,CAAC,mBAAmB,CACvB,QAAgB,EAChB,MAAsB,EACtB,OAAe;QAEf,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO;YAAE,OAAO,IAAI,CAAC;QAEtC,MAAM,iBAAiB,GAAG,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;QACvE,IAAI,CAAC,iBAAiB;YAAE,OAAO,IAAI,CAAC;QAEpC,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,MAAM,CAAC,aAAa,EAAE,CAAC;YAC/C,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,iBAAiB,CAAC,MAAM,EAAE,iBAAiB,EAAE,MAAM,CAAC,CAAC;YAC/E,IAAI,MAAM;gBAAE,OAAO,MAAM,CAAC;QAC5B,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;OAEG;IACH,YAAY;QACV,OAAO,IAAI,CAAC,gBAAgB,CAAC,IAAI,EAAE,CAAC;IACtC,CAAC;IAED;;OAEG;IACH,iBAAiB;QACf,IAAI,CAAC,gBAAgB,CAAC,KAAK,EAAE,CAAC;IAChC,CAAC;IAED,6EAA6E;IAC7E,kBAAkB;IAClB,6EAA6E;IAErE,KAAK,CAAC,iBAAiB,CAC7B,MAAuC,EACvC,MAA0B,EAC1B,MAAsB;QAEtB,QAAQ,MAAM,EAAE,CAAC;YACf,KAAK,MAAM;gBACT,OAAO,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;YAC3C,KAAK,MAAM;gBACT,OAAO,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;YAC3C,KAAK,aAAa;gBAChB,OAAO,IAAI,CAAC,mBAAmB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;YAClD;gBACE,OAAO,IAAI,CAAC;QAChB,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,YAAY,CACxB,MAA0B,EAC1B,MAAsB;QAEtB,IAAI,CAAC,MAAM,CAAC,WAAW;YAAE,OAAO,IAAI,CAAC;QAErC,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;QACpD,MAAM,MAAM,GAAG;;yBAEM,QAAQ;;;;;;;;;;;;;KAa5B,CAAC;QAEF,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAgB,MAAM,CAAC,CAAC;YAC5D,IAAI,MAAM,CAAC,OAAO,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;gBACnC,OAAO;oBACL,gBAAgB,EAAE,MAAM,CAAC,KAAK;oBAC9B,MAAM,EAAE,MAAiC;oBACzC,eAAe,EAAE,GAAG;oBACpB,kBAAkB,EAAE,KAAK;iBAC1B,CAAC;YACJ,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,0BAA0B;QAC5B,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAEO,KAAK,CAAC,YAAY,CACxB,MAA0B,EAC1B,MAAsB;QAEtB,IAAI,CAAC,MAAM,CAAC,QAAQ,IAAI,CAAC,MAAM,CAAC,SAAS;YAAE,OAAO,IAAI,CAAC;QAEvD,IAAI,YAAY,GAAG,EAAE,CAAC;QACtB,IAAI,MAAM,CAAC,QAAQ,IAAI,MAAM,CAAC,SAAS,EAAE,CAAC;YACxC,YAAY,GAAG,UAAU,MAAM,CAAC,QAAQ,kBAAkB,MAAM,CAAC,SAAS,IAAI,CAAC;QACjF,CAAC;aAAM,IAAI,MAAM,CAAC,SAAS,EAAE,CAAC;YAC5B,YAAY,GAAG,gBAAgB,MAAM,CAAC,SAAS,IAAI,CAAC;QACtD,CAAC;aAAM,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;YAC3B,YAAY,GAAG,UAAU,MAAM,CAAC,QAAQ,IAAI,CAAC;QAC/C,CAAC;QAED,IAAI,CAAC,YAAY;YAAE,OAAO,IAAI,CAAC;QAE/B,IAAI,CAAC;YACH,MAAM,WAAW,GAAG,4BAA4B,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,GAAG,CAAC;YAChF,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAU,WAAW,CAAC,CAAC;YAC3D,IAAI,MAAM,CAAC,OAAO,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;gBACnC,OAAO;oBACL,gBAAgB,EAAE,YAAY;oBAC9B,MAAM,EAAE,MAAiC;oBACzC,eAAe,EAAE,IAAI;oBACrB,kBAAkB,EAAE,KAAK;iBAC1B,CAAC;YACJ,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,0BAA0B;QAC5B,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAEO,KAAK,CAAC,mBAAmB,CAC/B,MAA0B,EAC1B,MAAsB;QAEtB,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,QAAQ,CAElC,qBAAqB,EAAE,CAAC,CAAC;YAE3B,IAAI,CAAC,MAAM,CAAC,OAAO,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM;gBAAE,OAAO,IAAI,CAAC;YAE1D,IAAI,SAAS,GAAG,CAAC,CAAC;YAClB,IAAI,YAAY,GAAG,EAAE,CAAC;YAEtB,KAAK,MAAM,SAAS,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;gBACrC,MAAM,KAAK,GAAG,iBAAiB,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;gBACnD,IAAI,KAAK,GAAG,SAAS,EAAE,CAAC;oBACtB,SAAS,GAAG,KAAK,CAAC;oBAClB,YAAY,GAAG,SAAS,CAAC,QAAQ,CAAC;gBACpC,CAAC;YACH,CAAC;YAED,IAAI,SAAS,IAAI,IAAI,CAAC,MAAM,CAAC,mBAAmB,IAAI,YAAY,EAAE,CAAC;gBACjE,OAAO;oBACL,gBAAgB,EAAE,YAAY;oBAC9B,MAAM,EAAE,aAAwC;oBAChD,eAAe,EAAE,SAAS;oBAC1B,kBAAkB,EAAE,KAAK;iBAC1B,CAAC;YACJ,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,2BAA2B;QAC7B,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;OAGG;IACK,eAAe,CAAC,QAAgB;QACtC,IAAI,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,QAAQ,CAAC,UAAU,CAAC,QAAQ,CAAC;YAAE,OAAO,IAAI,CAAC;QAC5E,IAAI,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC;YAAE,OAAO,IAAI,CAAC;QAC3C,IAAI,QAAQ,CAAC,UAAU,CAAC,OAAO,CAAC;YAAE,OAAO,IAAI,CAAC;QAC9C,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adaptive Locator Types
|
|
3
|
+
*
|
|
4
|
+
* Inspired by Scrapling's similarity-based element matching.
|
|
5
|
+
* When a CSS/XPath selector fails, falls back to fingerprint-based
|
|
6
|
+
* element matching to reduce E2E flakiness from UI changes.
|
|
7
|
+
*
|
|
8
|
+
* @module test-execution/services/e2e/adaptive-locator-types
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Captured fingerprint of a DOM element for similarity matching.
|
|
12
|
+
* Stored after successful interactions to enable fallback matching.
|
|
13
|
+
*/
|
|
14
|
+
export interface ElementFingerprint {
|
|
15
|
+
/** HTML tag name (e.g., 'button', 'input', 'a') */
|
|
16
|
+
tagName: string;
|
|
17
|
+
/** CSS classes on the element */
|
|
18
|
+
classes: string[];
|
|
19
|
+
/** WAI-ARIA role */
|
|
20
|
+
ariaRole?: string;
|
|
21
|
+
/** WAI-ARIA label */
|
|
22
|
+
ariaLabel?: string;
|
|
23
|
+
/** Visible text content (trimmed, max 200 chars) */
|
|
24
|
+
textContent?: string;
|
|
25
|
+
/** Key attributes for matching */
|
|
26
|
+
attributes: {
|
|
27
|
+
id?: string;
|
|
28
|
+
name?: string;
|
|
29
|
+
'data-testid'?: string;
|
|
30
|
+
type?: string;
|
|
31
|
+
placeholder?: string;
|
|
32
|
+
href?: string;
|
|
33
|
+
};
|
|
34
|
+
/** Structural position hints */
|
|
35
|
+
positionHints: {
|
|
36
|
+
parentTag?: string;
|
|
37
|
+
childIndex: number;
|
|
38
|
+
siblingCount: number;
|
|
39
|
+
};
|
|
40
|
+
/** Number of times this fingerprint matched */
|
|
41
|
+
matchCount: number;
|
|
42
|
+
/** Last time this fingerprint was matched (ISO string) */
|
|
43
|
+
lastMatchedAt: string;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Configuration for the adaptive locator service
|
|
47
|
+
*/
|
|
48
|
+
export interface AdaptiveLocatorConfig {
|
|
49
|
+
/** Enable adaptive locator fallback */
|
|
50
|
+
enabled: boolean;
|
|
51
|
+
/** Minimum similarity score (0-1) to accept a fingerprint match */
|
|
52
|
+
similarityThreshold: number;
|
|
53
|
+
/** Maximum fingerprints to store per page */
|
|
54
|
+
maxFingerprintsPerPage: number;
|
|
55
|
+
/** Memory namespace for fingerprint storage */
|
|
56
|
+
namespace: string;
|
|
57
|
+
/** Fallback chain order */
|
|
58
|
+
fallbackChain: Array<'text' | 'aria' | 'fingerprint'>;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Default adaptive locator configuration
|
|
62
|
+
*/
|
|
63
|
+
export declare const DEFAULT_ADAPTIVE_LOCATOR_CONFIG: AdaptiveLocatorConfig;
|
|
64
|
+
/**
|
|
65
|
+
* Method used to resolve an element target
|
|
66
|
+
*/
|
|
67
|
+
export type LocatorResolutionMethod = 'primary' | 'text' | 'aria' | 'fingerprint';
|
|
68
|
+
/**
|
|
69
|
+
* Result of adaptive locator resolution
|
|
70
|
+
*/
|
|
71
|
+
export interface LocatorResolutionResult {
|
|
72
|
+
/** The resolved element target selector */
|
|
73
|
+
resolvedSelector: string;
|
|
74
|
+
/** How the element was found */
|
|
75
|
+
method: LocatorResolutionMethod;
|
|
76
|
+
/** Similarity score (1.0 for primary, 0-1 for fallbacks) */
|
|
77
|
+
similarityScore: number;
|
|
78
|
+
/** Whether the fingerprint database was updated */
|
|
79
|
+
fingerprintUpdated: boolean;
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=adaptive-locator-types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adaptive-locator-types.d.ts","sourceRoot":"","sources":["../../../../../src/domains/test-execution/services/e2e/adaptive-locator-types.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IACjC,mDAAmD;IACnD,OAAO,EAAE,MAAM,CAAC;IAChB,iCAAiC;IACjC,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,oBAAoB;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,qBAAqB;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oDAAoD;IACpD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kCAAkC;IAClC,UAAU,EAAE;QACV,EAAE,CAAC,EAAE,MAAM,CAAC;QACZ,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,CAAC;IACF,gCAAgC;IAChC,aAAa,EAAE;QACb,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,UAAU,EAAE,MAAM,CAAC;QACnB,YAAY,EAAE,MAAM,CAAC;KACtB,CAAC;IACF,+CAA+C;IAC/C,UAAU,EAAE,MAAM,CAAC;IACnB,0DAA0D;IAC1D,aAAa,EAAE,MAAM,CAAC;CACvB;AAMD;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,uCAAuC;IACvC,OAAO,EAAE,OAAO,CAAC;IACjB,mEAAmE;IACnE,mBAAmB,EAAE,MAAM,CAAC;IAC5B,6CAA6C;IAC7C,sBAAsB,EAAE,MAAM,CAAC;IAC/B,+CAA+C;IAC/C,SAAS,EAAE,MAAM,CAAC;IAClB,2BAA2B;IAC3B,aAAa,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,GAAG,aAAa,CAAC,CAAC;CACvD;AAED;;GAEG;AACH,eAAO,MAAM,+BAA+B,EAAE,qBAM7C,CAAC;AAMF;;GAEG;AACH,MAAM,MAAM,uBAAuB,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,aAAa,CAAC;AAElF;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,2CAA2C;IAC3C,gBAAgB,EAAE,MAAM,CAAC;IACzB,gCAAgC;IAChC,MAAM,EAAE,uBAAuB,CAAC;IAChC,4DAA4D;IAC5D,eAAe,EAAE,MAAM,CAAC;IACxB,mDAAmD;IACnD,kBAAkB,EAAE,OAAO,CAAC;CAC7B"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adaptive Locator Types
|
|
3
|
+
*
|
|
4
|
+
* Inspired by Scrapling's similarity-based element matching.
|
|
5
|
+
* When a CSS/XPath selector fails, falls back to fingerprint-based
|
|
6
|
+
* element matching to reduce E2E flakiness from UI changes.
|
|
7
|
+
*
|
|
8
|
+
* @module test-execution/services/e2e/adaptive-locator-types
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Default adaptive locator configuration
|
|
12
|
+
*/
|
|
13
|
+
export const DEFAULT_ADAPTIVE_LOCATOR_CONFIG = {
|
|
14
|
+
enabled: true,
|
|
15
|
+
similarityThreshold: 0.6,
|
|
16
|
+
maxFingerprintsPerPage: 200,
|
|
17
|
+
namespace: 'aqe/adaptive-locators',
|
|
18
|
+
fallbackChain: ['text', 'aria', 'fingerprint'],
|
|
19
|
+
};
|
|
20
|
+
//# sourceMappingURL=adaptive-locator-types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adaptive-locator-types.js","sourceRoot":"","sources":["../../../../../src/domains/test-execution/services/e2e/adaptive-locator-types.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AA8DH;;GAEG;AACH,MAAM,CAAC,MAAM,+BAA+B,GAA0B;IACpE,OAAO,EAAE,IAAI;IACb,mBAAmB,EAAE,GAAG;IACxB,sBAAsB,EAAE,GAAG;IAC3B,SAAS,EAAE,uBAAuB;IAClC,aAAa,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,aAAa,CAAC;CAC/C,CAAC"}
|