@dev-blinq/cucumber_client 1.0.1325-dev → 1.0.1325-stage
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/bin/assets/bundled_scripts/recorder.js +108 -108
- package/bin/assets/preload/css_gen.js +10 -10
- package/bin/assets/preload/recorderv3.js +3 -1
- package/bin/assets/preload/toolbar.js +27 -29
- package/bin/assets/preload/unique_locators.js +1 -1
- package/bin/assets/preload/yaml.js +288 -275
- package/bin/assets/scripts/aria_snapshot.js +223 -220
- package/bin/assets/scripts/dom_attr.js +329 -329
- package/bin/assets/scripts/dom_parent.js +169 -174
- package/bin/assets/scripts/event_utils.js +94 -94
- package/bin/assets/scripts/pw.js +2050 -1949
- package/bin/assets/scripts/recorder.js +5 -17
- package/bin/assets/scripts/snapshot_capturer.js +153 -146
- package/bin/assets/scripts/unique_locators.js +156 -48
- package/bin/assets/scripts/yaml.js +796 -783
- package/bin/assets/templates/_hooks_template.txt +41 -0
- package/bin/assets/templates/utils_template.txt +1 -44
- package/bin/client/apiTest/apiTest.js +6 -0
- package/bin/client/cli_helpers.js +11 -13
- package/bin/client/code_cleanup/utils.js +5 -1
- package/bin/client/code_gen/api_codegen.js +2 -2
- package/bin/client/code_gen/code_inversion.js +53 -4
- package/bin/client/code_gen/page_reflection.js +839 -906
- package/bin/client/code_gen/playwright_codeget.js +26 -18
- package/bin/client/cucumber/feature.js +89 -27
- package/bin/client/cucumber/feature_data.js +2 -2
- package/bin/client/cucumber/project_to_document.js +9 -3
- package/bin/client/cucumber/steps_definitions.js +6 -3
- package/bin/client/cucumber_selector.js +17 -1
- package/bin/client/local_agent.js +6 -5
- package/bin/client/parse_feature_file.js +23 -26
- package/bin/client/playground/projects/env.json +2 -2
- package/bin/client/project.js +186 -196
- package/bin/client/recorderv3/bvt_recorder.js +182 -79
- package/bin/client/recorderv3/implemented_steps.js +24 -14
- package/bin/client/recorderv3/index.js +68 -54
- package/bin/client/recorderv3/network.js +22 -5
- package/bin/client/recorderv3/scriptTest.js +1 -1
- package/bin/client/recorderv3/services.js +4 -16
- package/bin/client/recorderv3/step_runner.js +303 -228
- package/bin/client/recorderv3/step_utils.js +484 -7
- package/bin/client/recorderv3/update_feature.js +32 -30
- package/bin/client/run_cucumber.js +5 -1
- package/bin/client/scenario_report.js +0 -5
- package/bin/client/test_scenario.js +0 -1
- package/bin/client/upload-service.js +3 -2
- package/bin/client/utils/socket_logger.js +132 -0
- package/bin/index.js +1 -0
- package/bin/logger.js +3 -2
- package/bin/min/consoleApi.min.cjs +2 -3
- package/bin/min/injectedScript.min.cjs +16 -16
- package/package.json +21 -12
|
@@ -144,7 +144,7 @@ class BVTRecorder {
|
|
|
144
144
|
getAction: (e) => {
|
|
145
145
|
this.eventUtils.consumeEvent(e);
|
|
146
146
|
},
|
|
147
|
-
getInterestedElement: () => {
|
|
147
|
+
getInterestedElement: () => {},
|
|
148
148
|
hoverOutlineStyle: "",
|
|
149
149
|
});
|
|
150
150
|
|
|
@@ -159,7 +159,7 @@ class BVTRecorder {
|
|
|
159
159
|
};
|
|
160
160
|
},
|
|
161
161
|
getAction: () => null,
|
|
162
|
-
getInterestedElement: () => {
|
|
162
|
+
getInterestedElement: () => {},
|
|
163
163
|
hoverOutlineStyle: "",
|
|
164
164
|
});
|
|
165
165
|
|
|
@@ -174,7 +174,7 @@ class BVTRecorder {
|
|
|
174
174
|
};
|
|
175
175
|
},
|
|
176
176
|
getAction: () => null,
|
|
177
|
-
getInterestedElement: () => {
|
|
177
|
+
getInterestedElement: () => {},
|
|
178
178
|
hoverOutlineStyle: "",
|
|
179
179
|
});
|
|
180
180
|
|
|
@@ -790,19 +790,7 @@ class BVTRecorder {
|
|
|
790
790
|
|
|
791
791
|
getLocatorsObject(el) {
|
|
792
792
|
if (this.contextElement) {
|
|
793
|
-
const
|
|
794
|
-
const contextEl = this.contextElement;
|
|
795
|
-
// const { climb, commonParent } = window.getCommonParent(contextEl, el);
|
|
796
|
-
const commonParent = this.locatorGenerator.dom_Parent.findLowestCommonAncestor([contextEl, el]);
|
|
797
|
-
const climb = this.locatorGenerator.dom_Parent.getClimbCountToParent(contextEl, commonParent);
|
|
798
|
-
const result = this.locatorGenerator.getElementLocators(el, {
|
|
799
|
-
excludeText: true,
|
|
800
|
-
root: commonParent,
|
|
801
|
-
});
|
|
802
|
-
result.locators.forEach((locator) => {
|
|
803
|
-
locator.text = text;
|
|
804
|
-
locator.climb = climb;
|
|
805
|
-
});
|
|
793
|
+
const result = this.locatorGenerator.toContextLocators(el, this.contextElement);
|
|
806
794
|
return result;
|
|
807
795
|
}
|
|
808
796
|
const isRecordingText = this.#mode === "recordingText";
|
|
@@ -894,7 +882,7 @@ class BVTRecorder {
|
|
|
894
882
|
this.interestedElements.clear();
|
|
895
883
|
}
|
|
896
884
|
processAriaSnapshot(snapshot) {
|
|
897
|
-
const matchedElements = this.findMatchingElements(snapshot, this.snapshotElements);
|
|
885
|
+
const matchedElements = this.snapshotUtils.findMatchingElements(snapshot, this.snapshotElements);
|
|
898
886
|
for (const el of matchedElements.values()) {
|
|
899
887
|
const element = el;
|
|
900
888
|
if (element) {
|
|
@@ -1,155 +1,162 @@
|
|
|
1
1
|
class SnapshotCapturer {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
2
|
+
constructor(options = {}) {
|
|
3
|
+
const { inlineImages = true, inlineStyles = true, excludeSelectors = [] } = options;
|
|
4
|
+
// this.options = {
|
|
5
|
+
// inlineImages,
|
|
6
|
+
// inlineStyles,
|
|
7
|
+
// excludeSelectors
|
|
8
|
+
// };
|
|
9
|
+
this.inlineImages = inlineImages;
|
|
10
|
+
this.inlineStyles = inlineStyles;
|
|
11
|
+
this.excludeSelectors = excludeSelectors;
|
|
12
|
+
}
|
|
13
|
+
imageToDataURL(img, document) {
|
|
14
|
+
try {
|
|
15
|
+
// Create canvas to draw the image
|
|
16
|
+
const canvas = document.createElement("canvas");
|
|
17
|
+
canvas.width = img.naturalWidth || img.width;
|
|
18
|
+
canvas.height = img.naturalHeight || img.height;
|
|
19
|
+
const ctx = canvas.getContext("2d");
|
|
20
|
+
|
|
21
|
+
// Draw image to canvas and convert to data URL
|
|
22
|
+
ctx.drawImage(img, 0, 0);
|
|
23
|
+
return canvas.toDataURL("image/png");
|
|
24
|
+
} catch (error) {
|
|
25
|
+
console.warn(`Failed to inline image: ${img.src}`, error);
|
|
26
|
+
return img.src; // Fall back to original source
|
|
16
27
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
}
|
|
29
|
+
processStyles(document) {
|
|
30
|
+
if (!this.inlineStyles) return;
|
|
31
|
+
|
|
32
|
+
const stylesheets = Array.from(document.styleSheets);
|
|
33
|
+
for (const sheet of stylesheets) {
|
|
34
|
+
try {
|
|
35
|
+
if (!sheet.href) continue; // Skip inline styles
|
|
36
|
+
const styleEl = document.createElement("style");
|
|
37
|
+
let text = "";
|
|
38
|
+
const cssRules = Array.from(sheet.cssRules || []);
|
|
39
|
+
for (const rule of cssRules) {
|
|
40
|
+
if (rule.cssText) {
|
|
41
|
+
text += rule.cssText + "\n";
|
|
42
|
+
}
|
|
31
43
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const styleEl = document.createElement('style');
|
|
41
|
-
let text = ""
|
|
42
|
-
const cssRules = Array.from(sheet.cssRules || []);
|
|
43
|
-
for (const rule of cssRules) {
|
|
44
|
-
if (rule.cssText) {
|
|
45
|
-
text += rule.cssText + '\n';
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
styleEl.textContent = text;
|
|
49
|
-
|
|
50
|
-
// Replace the link with our new style element
|
|
51
|
-
if (sheet.ownerNode && sheet.ownerNode.parentNode) {
|
|
52
|
-
sheet.ownerNode.parentNode.replaceChild(styleEl, sheet.ownerNode);
|
|
53
|
-
} else if (sheet.ownerNode) {
|
|
54
|
-
// If the owner node is not in the document, just append it
|
|
55
|
-
document.head.appendChild(styleEl);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
} catch (error) {
|
|
59
|
-
console.warn(`Error processing stylesheet: ${sheet.href}`, error);
|
|
60
|
-
}
|
|
44
|
+
styleEl.textContent = text;
|
|
45
|
+
|
|
46
|
+
// Replace the link with our new style element
|
|
47
|
+
if (sheet.ownerNode && sheet.ownerNode.parentNode) {
|
|
48
|
+
sheet.ownerNode.parentNode.replaceChild(styleEl, sheet.ownerNode);
|
|
49
|
+
} else if (sheet.ownerNode) {
|
|
50
|
+
// If the owner node is not in the document, just append it
|
|
51
|
+
document.head.appendChild(styleEl);
|
|
61
52
|
}
|
|
53
|
+
} catch (error) {
|
|
54
|
+
console.warn(`Error processing stylesheet: ${sheet.href}`, error);
|
|
55
|
+
}
|
|
62
56
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const images = document.querySelectorAll('img');
|
|
67
|
-
images.forEach(img => {
|
|
68
|
-
// Skip SVGs and already data URLs
|
|
69
|
-
if (img.src.startsWith('data:') || img.src.endsWith('.svg')) return;
|
|
70
|
-
|
|
71
|
-
// Skip if the image is excluded
|
|
72
|
-
if (this.excludeSelectors.some(selector => img.matches(selector))) return;
|
|
73
|
-
|
|
74
|
-
// Only inline complete images
|
|
75
|
-
if (img.complete && img.naturalWidth !== 0) {
|
|
76
|
-
try {
|
|
77
|
-
img.setAttribute('src', this.imageToDataURL(img, document));
|
|
78
|
-
} catch (e) {
|
|
79
|
-
console.warn(`Failed to process image: ${img.src}`, e);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
removeExcludedElements(document) {
|
|
85
|
-
this.excludeSelectors.forEach(selector => {
|
|
86
|
-
const elements = document.querySelectorAll(selector);
|
|
87
|
-
elements.forEach(el => {
|
|
88
|
-
el.parentNode?.removeChild(el);
|
|
89
|
-
});
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
processComputedStyles(document) {
|
|
93
|
-
const elements = document.querySelectorAll('*');
|
|
94
|
-
|
|
95
|
-
elements.forEach(el => {
|
|
96
|
-
// Skip excluded elements
|
|
97
|
-
if (this.excludeSelectors.some(selector => el.matches(selector))) return;
|
|
98
|
-
|
|
99
|
-
// Get computed style
|
|
100
|
-
const style = window.getComputedStyle(el);
|
|
101
|
-
|
|
102
|
-
// Copy important styles to inline style
|
|
103
|
-
const importantStyles = [
|
|
104
|
-
'display', 'position', 'width', 'height', 'margin', 'padding',
|
|
105
|
-
'color', 'background-color', 'font-family', 'font-size',
|
|
106
|
-
'text-align', 'line-height', 'border', 'box-shadow', 'opacity'
|
|
107
|
-
// Add more styles as needed
|
|
108
|
-
];
|
|
109
|
-
|
|
110
|
-
const inlineStyles = [];
|
|
111
|
-
|
|
112
|
-
importantStyles.forEach(prop => {
|
|
113
|
-
const value = style.getPropertyValue(prop);
|
|
114
|
-
if (value) {
|
|
115
|
-
inlineStyles.push(`${prop}: ${value}`);
|
|
116
|
-
}
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
// Set inline style
|
|
120
|
-
if (inlineStyles.length > 0) {
|
|
121
|
-
const currentStyle = el.getAttribute('style') || '';
|
|
122
|
-
el.setAttribute('style', currentStyle + inlineStyles.join('; ') + ';');
|
|
123
|
-
}
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
}
|
|
127
|
-
createSnapshot() {
|
|
128
|
-
// Clone the document to avoid modifying the original
|
|
129
|
-
const docClone = window.document.cloneNode(true);
|
|
130
|
-
|
|
131
|
-
// Store the original document
|
|
132
|
-
const originalDoc = window.document;
|
|
57
|
+
}
|
|
58
|
+
processImages(document) {
|
|
59
|
+
if (!this.inlineImages) return;
|
|
133
60
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
61
|
+
const images = document.querySelectorAll("img");
|
|
62
|
+
images.forEach((img) => {
|
|
63
|
+
// Skip SVGs and already data URLs
|
|
64
|
+
if (img.src.startsWith("data:") || img.src.endsWith(".svg")) return;
|
|
137
65
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
this.processImages(doc);
|
|
141
|
-
this.removeExcludedElements(doc);
|
|
142
|
-
this.processComputedStyles(doc);
|
|
66
|
+
// Skip if the image is excluded
|
|
67
|
+
if (this.excludeSelectors.some((selector) => img.matches(selector))) return;
|
|
143
68
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
69
|
+
// Only inline complete images
|
|
70
|
+
if (img.complete && img.naturalWidth !== 0) {
|
|
71
|
+
try {
|
|
72
|
+
img.setAttribute("src", this.imageToDataURL(img, document));
|
|
73
|
+
} catch (e) {
|
|
74
|
+
console.warn(`Failed to process image: ${img.src}`, e);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
removeExcludedElements(document) {
|
|
80
|
+
this.excludeSelectors.forEach((selector) => {
|
|
81
|
+
const elements = document.querySelectorAll(selector);
|
|
82
|
+
elements.forEach((el) => {
|
|
83
|
+
el.parentNode?.removeChild(el);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
processComputedStyles(document) {
|
|
88
|
+
const elements = document.querySelectorAll("*");
|
|
89
|
+
|
|
90
|
+
elements.forEach((el) => {
|
|
91
|
+
// Skip excluded elements
|
|
92
|
+
if (this.excludeSelectors.some((selector) => el.matches(selector))) return;
|
|
93
|
+
|
|
94
|
+
// Get computed style
|
|
95
|
+
const style = window.getComputedStyle(el);
|
|
96
|
+
|
|
97
|
+
// Copy important styles to inline style
|
|
98
|
+
const importantStyles = [
|
|
99
|
+
"display",
|
|
100
|
+
"position",
|
|
101
|
+
"width",
|
|
102
|
+
"height",
|
|
103
|
+
"margin",
|
|
104
|
+
"padding",
|
|
105
|
+
"color",
|
|
106
|
+
"background-color",
|
|
107
|
+
"font-family",
|
|
108
|
+
"font-size",
|
|
109
|
+
"text-align",
|
|
110
|
+
"line-height",
|
|
111
|
+
"border",
|
|
112
|
+
"box-shadow",
|
|
113
|
+
"opacity",
|
|
114
|
+
// Add more styles as needed
|
|
115
|
+
];
|
|
116
|
+
|
|
117
|
+
const inlineStyles = [];
|
|
118
|
+
|
|
119
|
+
importantStyles.forEach((prop) => {
|
|
120
|
+
const value = style.getPropertyValue(prop);
|
|
121
|
+
if (value) {
|
|
122
|
+
inlineStyles.push(`${prop}: ${value}`);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// Set inline style
|
|
127
|
+
if (inlineStyles.length > 0) {
|
|
128
|
+
const currentStyle = el.getAttribute("style") || "";
|
|
129
|
+
el.setAttribute("style", currentStyle + inlineStyles.join("; ") + ";");
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
createSnapshot() {
|
|
134
|
+
// Clone the document to avoid modifying the original
|
|
135
|
+
const docClone = window.document.cloneNode(true);
|
|
136
|
+
|
|
137
|
+
// Store the original document
|
|
138
|
+
const originalDoc = window.document;
|
|
139
|
+
|
|
140
|
+
// Temporarily "swap" the document for processing
|
|
141
|
+
// (We're just using this as a convention - it doesn't actually replace the global document)
|
|
142
|
+
const doc = docClone;
|
|
143
|
+
|
|
144
|
+
// Process the clone
|
|
145
|
+
this.processStyles(doc);
|
|
146
|
+
this.processImages(doc);
|
|
147
|
+
this.removeExcludedElements(doc);
|
|
148
|
+
this.processComputedStyles(doc);
|
|
149
|
+
|
|
150
|
+
// Generate HTML with doctype
|
|
151
|
+
const doctype = originalDoc.doctype
|
|
152
|
+
? new XMLSerializer().serializeToString(originalDoc.doctype)
|
|
153
|
+
: "<!DOCTYPE html>";
|
|
154
|
+
|
|
155
|
+
// Get the HTML content
|
|
156
|
+
const htmlContent = doc.documentElement.outerHTML;
|
|
157
|
+
|
|
158
|
+
// Combine doctype and HTML content
|
|
159
|
+
return `${doctype}${htmlContent}`;
|
|
160
|
+
}
|
|
154
161
|
}
|
|
155
|
-
export default SnapshotCapturer;
|
|
162
|
+
export default SnapshotCapturer;
|
|
@@ -150,6 +150,7 @@ class LocatorGenerator {
|
|
|
150
150
|
}
|
|
151
151
|
getTextLocators(element, options) {
|
|
152
152
|
const injectedScript = this.injectedScript;
|
|
153
|
+
const { textToIgnore = null } = options;
|
|
153
154
|
const selectorPartLists = this.PW.selectorGenerator.buildTextCandidates(injectedScript, element, options);
|
|
154
155
|
const result = [];
|
|
155
156
|
for (const selectorPartList of selectorPartLists) {
|
|
@@ -157,6 +158,9 @@ class LocatorGenerator {
|
|
|
157
158
|
const tSelectorList = [];
|
|
158
159
|
for (const selectorPart of selectorPartList) {
|
|
159
160
|
const { engine, selector } = selectorPart;
|
|
161
|
+
if (textToIgnore && selector.includes(textToIgnore)) {
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
160
164
|
if (engine === "css") {
|
|
161
165
|
tSelectorList.push(selector);
|
|
162
166
|
} else {
|
|
@@ -213,6 +217,52 @@ class LocatorGenerator {
|
|
|
213
217
|
}
|
|
214
218
|
return result;
|
|
215
219
|
}
|
|
220
|
+
toContextLocators(element, contextElement) {
|
|
221
|
+
const commonParent = this.dom_Parent.findLowestCommonAncestor([contextElement, element]);
|
|
222
|
+
const climb = this.dom_Parent.getClimbCountToParent(contextElement, commonParent);
|
|
223
|
+
const text = contextElement.innerText.trim();
|
|
224
|
+
|
|
225
|
+
const prefix = `internal:text="${text}" >> ${this.getXPathSelector(climb)}`;
|
|
226
|
+
const result = this.getElementLocators(element, {
|
|
227
|
+
root: commonParent,
|
|
228
|
+
strategies: {
|
|
229
|
+
[this.locatorStrategies.custom]: true,
|
|
230
|
+
[this.locatorStrategies.text]: true,
|
|
231
|
+
[this.locatorStrategies.no_text]: true,
|
|
232
|
+
},
|
|
233
|
+
prefix,
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
const attachContextToLocators = (locs) => {
|
|
237
|
+
locs.forEach((loc) => {
|
|
238
|
+
loc.climb = climb;
|
|
239
|
+
loc.text = text;
|
|
240
|
+
});
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
const allStrategyLocators = result.allStrategyLocators;
|
|
244
|
+
const locators = result.locators;
|
|
245
|
+
if (allStrategyLocators) {
|
|
246
|
+
const allLocators = [];
|
|
247
|
+
for (const strategy in allStrategyLocators) {
|
|
248
|
+
if (strategy === "strategy") continue;
|
|
249
|
+
const locators = allStrategyLocators[strategy];
|
|
250
|
+
if (locators.length === 0) continue;
|
|
251
|
+
allLocators.push(...locators);
|
|
252
|
+
allStrategyLocators[strategy] = [];
|
|
253
|
+
}
|
|
254
|
+
attachContextToLocators(allLocators);
|
|
255
|
+
allStrategyLocators[this.locatorStrategies.context] = allLocators;
|
|
256
|
+
allStrategyLocators.strategy = this.locatorStrategies.context;
|
|
257
|
+
result.locators = allLocators;
|
|
258
|
+
return result;
|
|
259
|
+
}
|
|
260
|
+
if (locators) {
|
|
261
|
+
attachContextToLocators(locators);
|
|
262
|
+
return result;
|
|
263
|
+
}
|
|
264
|
+
return result;
|
|
265
|
+
}
|
|
216
266
|
getContextLocators(element, locators) {
|
|
217
267
|
if (!locators || !Array.isArray(locators)) {
|
|
218
268
|
console.error("Locators must be an array");
|
|
@@ -283,7 +333,7 @@ class LocatorGenerator {
|
|
|
283
333
|
}
|
|
284
334
|
const textElement = elements[0];
|
|
285
335
|
// const text = this.PW.selectorUtils.elementText(textElement);
|
|
286
|
-
const text = this.injectedScript.utils.elementText(new Map(), textElement).full;
|
|
336
|
+
const text = this.injectedScript.utils.elementText(new Map(), textElement).full.trim();
|
|
287
337
|
|
|
288
338
|
const fullSelector = `${textLocator} >> xpath=${xpath} >> ${restOfSelector}`;
|
|
289
339
|
const fullElements = this.getMatchingElements(fullSelector, {});
|
|
@@ -361,7 +411,7 @@ class LocatorGenerator {
|
|
|
361
411
|
hasDigitsInText = digitsRegex.test(text);
|
|
362
412
|
|
|
363
413
|
let pattern = this.PW.stringUtils.escapeRegExp(text.substring(1, text.length - 2));
|
|
364
|
-
const re = new RegExp(
|
|
414
|
+
const re = new RegExp(pattern);
|
|
365
415
|
|
|
366
416
|
finalSelector += `internal:text=${escapeRegexForSelector(re).replace(digitsRegex, "\\d+")} >> `;
|
|
367
417
|
}
|
|
@@ -439,6 +489,10 @@ class LocatorGenerator {
|
|
|
439
489
|
const nonUnique = [];
|
|
440
490
|
try {
|
|
441
491
|
for (const locator of locators) {
|
|
492
|
+
if (!locator || !locator.css || typeof locator.css !== "string") {
|
|
493
|
+
console.error("Locator must have a valid css selector found: ", locator);
|
|
494
|
+
continue;
|
|
495
|
+
}
|
|
442
496
|
const elements = this.getMatchingElements(locator.css, options);
|
|
443
497
|
if (elements.length === 0) {
|
|
444
498
|
console.warn(`No elements found for locator: ${locator.css}`);
|
|
@@ -617,7 +671,7 @@ class LocatorGenerator {
|
|
|
617
671
|
|
|
618
672
|
getUniqueLocators2(element, locatorGenerator = this.getNoTextLocators, options = {}) {
|
|
619
673
|
try {
|
|
620
|
-
const { maxLocators = 5, root = window.document.body } = options;
|
|
674
|
+
const { maxLocators = 5, root = window.document.body, prefix } = options;
|
|
621
675
|
|
|
622
676
|
if (!element) {
|
|
623
677
|
return [];
|
|
@@ -637,13 +691,7 @@ class LocatorGenerator {
|
|
|
637
691
|
},
|
|
638
692
|
];
|
|
639
693
|
} else {
|
|
640
|
-
return [
|
|
641
|
-
{
|
|
642
|
-
css: ":root",
|
|
643
|
-
score: 1,
|
|
644
|
-
priority: 1,
|
|
645
|
-
},
|
|
646
|
-
];
|
|
694
|
+
return [];
|
|
647
695
|
}
|
|
648
696
|
}
|
|
649
697
|
|
|
@@ -670,12 +718,15 @@ class LocatorGenerator {
|
|
|
670
718
|
|
|
671
719
|
const elementsCache = new Map();
|
|
672
720
|
|
|
673
|
-
const allAncestors = this.dom_Parent.getFullAncestorChainToRoot(element, root);
|
|
721
|
+
const allAncestors = prefix ? [element] : this.dom_Parent.getFullAncestorChainToRoot(element, root);
|
|
674
722
|
allAncestors.shift(); // remove the element itself from the ancestors
|
|
675
723
|
|
|
724
|
+
const cache = new Map();
|
|
725
|
+
const textToIgnore = this.PW.selectorUtils.elementText(cache, element).full.trim();
|
|
676
726
|
const ancestorLocators = [];
|
|
727
|
+
let uniqueAncestor = null;
|
|
677
728
|
for (const ancestor of allAncestors) {
|
|
678
|
-
const _locators = locatorGenerator(ancestor, options);
|
|
729
|
+
const _locators = locatorGenerator(ancestor, { ...options, textToIgnore });
|
|
679
730
|
if (!_locators || !Array.isArray(_locators) || _locators.length === 0) {
|
|
680
731
|
continue;
|
|
681
732
|
}
|
|
@@ -686,11 +737,15 @@ class LocatorGenerator {
|
|
|
686
737
|
});
|
|
687
738
|
elementsCache.set(ancestor, _categorized);
|
|
688
739
|
if (_categorized.unique.length > 0) {
|
|
740
|
+
uniqueAncestor = {
|
|
741
|
+
element: ancestor,
|
|
742
|
+
locators: _categorized,
|
|
743
|
+
};
|
|
689
744
|
break;
|
|
690
745
|
}
|
|
691
746
|
}
|
|
692
747
|
|
|
693
|
-
const uniqueAncestor = ancestorLocators[ancestorLocators.length - 1];
|
|
748
|
+
// const uniqueAncestor = ancestorLocators[ancestorLocators.length - 1];
|
|
694
749
|
|
|
695
750
|
for (const locator of nonUnique) {
|
|
696
751
|
const selector = locator.css ?? locator.selector;
|
|
@@ -699,6 +754,27 @@ class LocatorGenerator {
|
|
|
699
754
|
console.warn(`No elements found for locator: ${selector}`);
|
|
700
755
|
continue;
|
|
701
756
|
}
|
|
757
|
+
if (!uniqueAncestor) {
|
|
758
|
+
const elements = this.getMatchingElements(selector, options);
|
|
759
|
+
if (elements.length === 1 && elements[0] === element) {
|
|
760
|
+
result.push({
|
|
761
|
+
css: selector,
|
|
762
|
+
score: locator.score,
|
|
763
|
+
priority: 1,
|
|
764
|
+
});
|
|
765
|
+
} else {
|
|
766
|
+
const index = elements.indexOf(element);
|
|
767
|
+
if (index !== -1) {
|
|
768
|
+
result.push({
|
|
769
|
+
css: selector,
|
|
770
|
+
index,
|
|
771
|
+
score: locator.score + 200,
|
|
772
|
+
priority: 2,
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
continue;
|
|
777
|
+
}
|
|
702
778
|
|
|
703
779
|
for (const unique_locator of uniqueAncestor.locators.unique) {
|
|
704
780
|
const fullSelector = `${unique_locator.css} >> ${selector}`;
|
|
@@ -739,10 +815,32 @@ class LocatorGenerator {
|
|
|
739
815
|
}
|
|
740
816
|
getElementLocators(element, options = {}) {
|
|
741
817
|
try {
|
|
742
|
-
const {
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
818
|
+
const {
|
|
819
|
+
excludeText = false,
|
|
820
|
+
strategies = {
|
|
821
|
+
[this.locatorStrategies.custom]: true,
|
|
822
|
+
[this.locatorStrategies.context]: true,
|
|
823
|
+
[this.locatorStrategies.text]: true,
|
|
824
|
+
[this.locatorStrategies.text_with_index]: true,
|
|
825
|
+
[this.locatorStrategies.digitIgnore]: true,
|
|
826
|
+
[this.locatorStrategies.no_text]: true,
|
|
827
|
+
},
|
|
828
|
+
} = options;
|
|
829
|
+
|
|
830
|
+
const allStrategyLocators = {
|
|
831
|
+
[this.locatorStrategies.custom]: [],
|
|
832
|
+
[this.locatorStrategies.context]: [],
|
|
833
|
+
[this.locatorStrategies.text]: [],
|
|
834
|
+
[this.locatorStrategies.text_with_index]: [],
|
|
835
|
+
[this.locatorStrategies.digitIgnore]: [],
|
|
836
|
+
[this.locatorStrategies.no_text]: [],
|
|
837
|
+
};
|
|
838
|
+
if (
|
|
839
|
+
strategies[this.locatorStrategies.custom] &&
|
|
840
|
+
this.options?.customAttributes &&
|
|
841
|
+
Array.isArray(this.options.customAttributes) &&
|
|
842
|
+
this.options.customAttributes.length > 0
|
|
843
|
+
) {
|
|
746
844
|
console.groupCollapsed("Generating Custom locators for element:", element);
|
|
747
845
|
const customLocators = this.getUniqueLocators(element, this.getCustomLocators.bind(this), options);
|
|
748
846
|
if (customLocators.length > 0) {
|
|
@@ -751,43 +849,53 @@ class LocatorGenerator {
|
|
|
751
849
|
}
|
|
752
850
|
console.groupEnd();
|
|
753
851
|
if (!excludeText) {
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
console.groupEnd();
|
|
757
|
-
if (basicLocators.length > 0) {
|
|
758
|
-
allStrategyLocators[this.locatorStrategies.text] = basicLocators;
|
|
759
|
-
}
|
|
852
|
+
if (strategies[this.locatorStrategies.text]) {
|
|
853
|
+
console.groupCollapsed("Generating Text locators for element:", element);
|
|
760
854
|
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
855
|
+
const basicLocators = this.getUniqueLocators(element, this.getTextLocators.bind(this), options);
|
|
856
|
+
console.groupEnd();
|
|
857
|
+
if (basicLocators.length > 0) {
|
|
858
|
+
allStrategyLocators[this.locatorStrategies.text] = basicLocators;
|
|
859
|
+
}
|
|
860
|
+
if (strategies[this.locatorStrategies.text_with_index]) {
|
|
861
|
+
const textWithIndexLocators = this.getTextwithIndexLocators(basicLocators);
|
|
862
|
+
if (textWithIndexLocators.length > 0) {
|
|
863
|
+
allStrategyLocators[this.locatorStrategies.text_with_index] = textWithIndexLocators;
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
if (strategies[this.locatorStrategies.digitIgnore]) {
|
|
867
|
+
const digitIgnoreLocators = this.getDigitIgnoreLocators(element, basicLocators);
|
|
868
|
+
if (digitIgnoreLocators.length > 0) {
|
|
869
|
+
allStrategyLocators[this.locatorStrategies.digitIgnore] = digitIgnoreLocators;
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
if (strategies[this.locatorStrategies.context]) {
|
|
873
|
+
const contextLocators = this.getContextLocators(element, basicLocators);
|
|
874
|
+
if (contextLocators.length > 0) {
|
|
875
|
+
allStrategyLocators[this.locatorStrategies.context] = contextLocators;
|
|
876
|
+
}
|
|
877
|
+
}
|
|
772
878
|
}
|
|
773
879
|
}
|
|
774
|
-
|
|
775
|
-
|
|
880
|
+
if (strategies[this.locatorStrategies.no_text]) {
|
|
881
|
+
console.groupCollapsed("Generating No Text locators for element:", element);
|
|
882
|
+
const noTextLocators = this.getUniqueLocators(element, this.getNoTextLocators.bind(this), options);
|
|
776
883
|
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
884
|
+
if (noTextLocators.length > 0) {
|
|
885
|
+
allStrategyLocators[this.locatorStrategies.no_text] = noTextLocators;
|
|
886
|
+
} else {
|
|
887
|
+
const _locators = [];
|
|
888
|
+
_locators.push({
|
|
889
|
+
css: this.generateUniqueCSSSelector(element, options),
|
|
890
|
+
score: 500,
|
|
891
|
+
priority: 3,
|
|
892
|
+
});
|
|
893
|
+
if (_locators.length > 0) {
|
|
894
|
+
allStrategyLocators[this.locatorStrategies.no_text] = _locators;
|
|
895
|
+
}
|
|
788
896
|
}
|
|
897
|
+
console.groupEnd();
|
|
789
898
|
}
|
|
790
|
-
console.groupEnd();
|
|
791
899
|
|
|
792
900
|
let bestStrategy = this.getBestStrategy(allStrategyLocators);
|
|
793
901
|
if (!bestStrategy) {
|