@dev-blinq/cucumber_client 1.0.1472-dev → 1.0.1472-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 +73 -73
- package/bin/assets/scripts/recorder.js +87 -49
- package/bin/assets/scripts/snapshot_capturer.js +10 -17
- package/bin/assets/scripts/unique_locators.js +168 -40
- package/bin/assets/templates/_hooks_template.txt +6 -2
- package/bin/assets/templates/utils_template.txt +16 -16
- package/bin/client/code_cleanup/utils.js +16 -7
- package/bin/client/code_gen/code_inversion.js +125 -1
- package/bin/client/code_gen/duplication_analysis.js +2 -1
- package/bin/client/code_gen/function_signature.js +8 -0
- package/bin/client/code_gen/index.js +4 -0
- package/bin/client/code_gen/page_reflection.js +90 -9
- package/bin/client/code_gen/playwright_codeget.js +173 -77
- package/bin/client/codemod/find_harcoded_locators.js +173 -0
- package/bin/client/codemod/fix_hardcoded_locators.js +247 -0
- package/bin/client/codemod/index.js +8 -0
- package/bin/client/codemod/locators_array/find_misstructured_elements.js +148 -0
- package/bin/client/codemod/locators_array/fix_misstructured_elements.js +144 -0
- package/bin/client/codemod/locators_array/index.js +114 -0
- package/bin/client/codemod/types.js +1 -0
- package/bin/client/cucumber/feature.js +4 -17
- package/bin/client/cucumber/steps_definitions.js +17 -12
- package/bin/client/recorderv3/bvt_init.js +310 -0
- package/bin/client/recorderv3/bvt_recorder.js +1560 -1183
- package/bin/client/recorderv3/constants.js +45 -0
- package/bin/client/recorderv3/implemented_steps.js +2 -0
- package/bin/client/recorderv3/index.js +3 -293
- package/bin/client/recorderv3/mixpanel.js +41 -0
- package/bin/client/recorderv3/services.js +839 -142
- package/bin/client/recorderv3/step_runner.js +36 -7
- package/bin/client/recorderv3/step_utils.js +300 -98
- package/bin/client/recorderv3/update_feature.js +87 -39
- package/bin/client/recorderv3/utils.js +74 -0
- package/bin/client/recorderv3/wbr_entry.js +61 -0
- package/bin/client/recording.js +1 -0
- package/bin/client/types/locators.js +2 -0
- package/bin/client/upload-service.js +2 -0
- package/bin/client/utils/app_dir.js +21 -0
- package/bin/client/utils/socket_logger.js +100 -125
- package/bin/index.js +5 -0
- package/package.json +21 -6
- package/bin/client/recorderv3/app_dir.js +0 -23
- package/bin/client/recorderv3/network.js +0 -299
- package/bin/client/recorderv3/scriptTest.js +0 -5
- package/bin/client/recorderv3/ws_server.js +0 -72
|
@@ -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
|
|
|
@@ -390,6 +390,7 @@ class BVTRecorder {
|
|
|
390
390
|
}
|
|
391
391
|
case "input": {
|
|
392
392
|
const target = this.eventUtils.getNearestInteractiveElement(this.eventUtils.deepEventTarget(event));
|
|
393
|
+
|
|
393
394
|
if (target.nodeName === "INPUT" && target.type.toLowerCase() === "file") {
|
|
394
395
|
return {
|
|
395
396
|
details: {
|
|
@@ -434,6 +435,36 @@ class BVTRecorder {
|
|
|
434
435
|
return;
|
|
435
436
|
}
|
|
436
437
|
case "keydown": {
|
|
438
|
+
// override event.preventDefault to capture the value
|
|
439
|
+
|
|
440
|
+
const oldPreventDefault = event.preventDefault.bind(event);
|
|
441
|
+
event.preventDefault = () => {
|
|
442
|
+
if (event.key.length >= 1 && !event.ctrlKey && !event.metaKey && !event.altKey) {
|
|
443
|
+
const target = this.eventUtils.getNearestInteractiveElement(this.eventUtils.deepEventTarget(event));
|
|
444
|
+
setTimeout(() => {
|
|
445
|
+
if (event.__bvt_recorded !== undefined) return;
|
|
446
|
+
const valueBefore = target.value;
|
|
447
|
+
let newValue = valueBefore;
|
|
448
|
+
|
|
449
|
+
this.recordEvent(
|
|
450
|
+
{
|
|
451
|
+
details: {
|
|
452
|
+
name: "fill",
|
|
453
|
+
text: newValue,
|
|
454
|
+
},
|
|
455
|
+
element: target,
|
|
456
|
+
},
|
|
457
|
+
target,
|
|
458
|
+
"input",
|
|
459
|
+
event
|
|
460
|
+
);
|
|
461
|
+
|
|
462
|
+
event.__bvt_recorded = true;
|
|
463
|
+
}, 20);
|
|
464
|
+
}
|
|
465
|
+
oldPreventDefault();
|
|
466
|
+
};
|
|
467
|
+
|
|
437
468
|
if (!this.eventUtils.shouldGenerateKeyPressFor(event)) return;
|
|
438
469
|
// if (this._actionInProgress(event)) {
|
|
439
470
|
// this._expectProgrammaticKeyUp = true;
|
|
@@ -676,7 +707,8 @@ class BVTRecorder {
|
|
|
676
707
|
|
|
677
708
|
lastInputId = el.dataset.inputId;
|
|
678
709
|
|
|
679
|
-
el.__locators = this.getLocatorsObject(el);
|
|
710
|
+
el.__locators = this.getLocatorsObject(el, { maxLocators: 1 });
|
|
711
|
+
|
|
680
712
|
}
|
|
681
713
|
const role = this.PW.roleUtils.getAriaRole(el);
|
|
682
714
|
const label =
|
|
@@ -684,13 +716,15 @@ class BVTRecorder {
|
|
|
684
716
|
this.PW.roleUtils.getElementAccessibleName(el, true) ||
|
|
685
717
|
"";
|
|
686
718
|
const result = this.getElementProperties(el);
|
|
719
|
+
const elText = this.PW.selectorUtils.elementText(new Map(), el);
|
|
687
720
|
return {
|
|
688
721
|
role,
|
|
689
722
|
label,
|
|
690
723
|
inputID: el.dataset.inputId,
|
|
691
724
|
tagName: el.tagName,
|
|
692
725
|
type: el.type,
|
|
693
|
-
text:
|
|
726
|
+
text: elText.full.trim(),
|
|
727
|
+
textNormalized: elText.normalized.trim(),
|
|
694
728
|
parent: `tagname: ${el.parentElement?.tagName}\ninnerText: ${el.parentElement?.innerText}`,
|
|
695
729
|
attrs: {
|
|
696
730
|
placeholder: el.getAttribute("placeholder"),
|
|
@@ -787,30 +821,31 @@ class BVTRecorder {
|
|
|
787
821
|
return el.matches(closeBtnSelector);
|
|
788
822
|
});
|
|
789
823
|
}
|
|
790
|
-
|
|
791
|
-
|
|
824
|
+
docHasShadowDOM() {
|
|
825
|
+
for (const el of document.all) {
|
|
826
|
+
if (el.tagName.includes("-")) {
|
|
827
|
+
if (["x-bvt-toolbar", "x-bvt-tool"].includes(el.tagName.toLowerCase())) continue;
|
|
828
|
+
if (el.shadowRoot) {
|
|
829
|
+
return true;
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
return false;
|
|
834
|
+
}
|
|
835
|
+
getLocatorsObject(el, options = {}) {
|
|
836
|
+
if (!this.docHasShadowDOM()) {
|
|
837
|
+
console.log("No custom elements detected, skipping locator generation");
|
|
838
|
+
return;
|
|
839
|
+
}
|
|
840
|
+
console.log("Custom elements detected locator generation");
|
|
792
841
|
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
|
-
});
|
|
806
|
-
result.allStrategyLocators.context = result.locators;
|
|
807
|
-
result.allStrategyLocators.strategy = "context";
|
|
808
|
-
result.allStrategyLocators.no_text = [];
|
|
842
|
+
const result = this.locatorGenerator.toContextLocators(el, this.contextElement, options);
|
|
809
843
|
return result;
|
|
810
844
|
}
|
|
811
845
|
const isRecordingText = this.#mode === "recordingText";
|
|
812
846
|
return this.locatorGenerator.getElementLocators(el, {
|
|
813
847
|
excludeText: isRecordingText,
|
|
848
|
+
...options,
|
|
814
849
|
});
|
|
815
850
|
}
|
|
816
851
|
addListeners() {
|
|
@@ -847,31 +882,7 @@ class BVTRecorder {
|
|
|
847
882
|
}
|
|
848
883
|
|
|
849
884
|
performance.mark("command-send");
|
|
850
|
-
|
|
851
|
-
mode: this.#mode,
|
|
852
|
-
action: action.details,
|
|
853
|
-
element: this.getElementDetails(actionElement, eventName),
|
|
854
|
-
isPopupCloseClick: this.isPopupCloseEvent(e),
|
|
855
|
-
// ...this.getLocatorsObject(actionElement),
|
|
856
|
-
...(actionElement.__locators ?? this.getLocatorsObject(actionElement)),
|
|
857
|
-
frame: this.getFrameDetails(),
|
|
858
|
-
statistics: {
|
|
859
|
-
time: `${performance.measure("command-received", "command-send").duration.toFixed(2)} ms`,
|
|
860
|
-
},
|
|
861
|
-
};
|
|
862
|
-
const snapshotDetails = {
|
|
863
|
-
id: actionElement.getAttribute("data-blinq-id"),
|
|
864
|
-
contextId: this.contextElement?.getAttribute("data-blinq-context-id"),
|
|
865
|
-
doc: this.snapshotCapturer.createSnapshot({
|
|
866
|
-
excludeSelectors: ["x-bvt-toolbar", "script", "style", "link[rel=stylesheet]"],
|
|
867
|
-
}),
|
|
868
|
-
};
|
|
869
|
-
cmd.snapshotDetails = snapshotDetails;
|
|
870
|
-
// eventQueue.enqueue(async () => {
|
|
871
|
-
// await bvtRecorderBindings.validateLocators(snapshotDetails);
|
|
872
|
-
// });
|
|
873
|
-
// console.log(cmd);
|
|
874
|
-
await bvtRecorderBindings.recordCommand(cmd);
|
|
885
|
+
this.recordEvent(action, actionElement, eventName, e);
|
|
875
886
|
this.handleStateTransition(action.element);
|
|
876
887
|
},
|
|
877
888
|
{ capture: true }
|
|
@@ -879,6 +890,33 @@ class BVTRecorder {
|
|
|
879
890
|
});
|
|
880
891
|
}
|
|
881
892
|
|
|
893
|
+
recordEvent(action, actionElement, eventName, e) {
|
|
894
|
+
const cmd = {
|
|
895
|
+
mode: this.#mode,
|
|
896
|
+
action: action.details,
|
|
897
|
+
element: this.getElementDetails(actionElement, eventName),
|
|
898
|
+
isPopupCloseClick: this.isPopupCloseEvent(e),
|
|
899
|
+
...(actionElement.__locators ?? this.getLocatorsObject(actionElement, { maxLocators: 1 })),
|
|
900
|
+
frame: this.getFrameDetails(),
|
|
901
|
+
statistics: {
|
|
902
|
+
time: `${performance.measure("command-received", "command-send").duration.toFixed(2)} ms`,
|
|
903
|
+
},
|
|
904
|
+
};
|
|
905
|
+
const snapshotDetails = {
|
|
906
|
+
id: actionElement.getAttribute("data-blinq-id"),
|
|
907
|
+
contextId: this.contextElement?.getAttribute("data-blinq-context-id"),
|
|
908
|
+
doc: this.snapshotCapturer.createSnapshot({
|
|
909
|
+
excludeSelectors: ["x-bvt-toolbar", "script", "style", "link[rel=stylesheet]"],
|
|
910
|
+
}),
|
|
911
|
+
};
|
|
912
|
+
cmd.snapshotDetails = snapshotDetails;
|
|
913
|
+
// eventQueue.enqueue(async () => {
|
|
914
|
+
// await bvtRecorderBindings.validateLocators(snapshotDetails);
|
|
915
|
+
// });
|
|
916
|
+
// console.log(cmd);
|
|
917
|
+
bvtRecorderBindings.recordCommand(cmd);
|
|
918
|
+
}
|
|
919
|
+
|
|
882
920
|
// TODO: implement the corresponding logic for the below methods
|
|
883
921
|
setPopupHandlers(_popopHandlers) {
|
|
884
922
|
this.popupHandlers = _popopHandlers;
|
|
@@ -897,7 +935,7 @@ class BVTRecorder {
|
|
|
897
935
|
this.interestedElements.clear();
|
|
898
936
|
}
|
|
899
937
|
processAriaSnapshot(snapshot) {
|
|
900
|
-
const matchedElements = this.findMatchingElements(snapshot, this.snapshotElements);
|
|
938
|
+
const matchedElements = this.snapshotUtils.findMatchingElements(snapshot, this.snapshotElements);
|
|
901
939
|
for (const el of matchedElements.values()) {
|
|
902
940
|
const element = el;
|
|
903
941
|
if (element) {
|
|
@@ -29,25 +29,18 @@ class SnapshotCapturer {
|
|
|
29
29
|
processStyles(document) {
|
|
30
30
|
if (!this.inlineStyles) return;
|
|
31
31
|
|
|
32
|
-
const stylesheets = Array.from(document.styleSheets);
|
|
32
|
+
const stylesheets = Array.from(window.document.styleSheets);
|
|
33
33
|
for (const sheet of stylesheets) {
|
|
34
34
|
try {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
|
35
|
+
const el = sheet.ownerNode.cloneNode(true);
|
|
36
|
+
if (!el.href) {
|
|
37
|
+
document.head.appendChild(el);
|
|
38
|
+
} else {
|
|
39
|
+
const rules = Array.from(sheet.cssRules)
|
|
40
|
+
.map((rule) => rule.cssText)
|
|
41
|
+
.join("\n");
|
|
42
|
+
const styleEl = document.createElement("style");
|
|
43
|
+
styleEl.textContent = rules;
|
|
51
44
|
document.head.appendChild(styleEl);
|
|
52
45
|
}
|
|
53
46
|
} catch (error) {
|
|
@@ -217,6 +217,59 @@ class LocatorGenerator {
|
|
|
217
217
|
}
|
|
218
218
|
return result;
|
|
219
219
|
}
|
|
220
|
+
getMixedLocators(element, options) {
|
|
221
|
+
const noTextLocators = this.getNoTextLocators(element, options);
|
|
222
|
+
const textLocators = this.getTextLocators(element, options);
|
|
223
|
+
const customLocators = this.getCustomLocators(element, options);
|
|
224
|
+
return [...customLocators, ...textLocators, ...noTextLocators];
|
|
225
|
+
}
|
|
226
|
+
toContextLocators(element, contextElement, options = {}) {
|
|
227
|
+
const commonParent = this.dom_Parent.findLowestCommonAncestor([contextElement, element]);
|
|
228
|
+
const climb = this.dom_Parent.getClimbCountToParent(contextElement, commonParent);
|
|
229
|
+
const text = contextElement.innerText.trim();
|
|
230
|
+
|
|
231
|
+
const prefix = `internal:text="${text}" >> ${this.getXPathSelector(climb)}`;
|
|
232
|
+
const result = this.getElementLocators(element, {
|
|
233
|
+
root: commonParent,
|
|
234
|
+
strategies: {
|
|
235
|
+
[this.locatorStrategies.custom]: true,
|
|
236
|
+
[this.locatorStrategies.text]: true,
|
|
237
|
+
[this.locatorStrategies.no_text]: true,
|
|
238
|
+
},
|
|
239
|
+
prefix,
|
|
240
|
+
...options,
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
const attachContextToLocators = (locs) => {
|
|
244
|
+
locs.forEach((loc) => {
|
|
245
|
+
loc.climb = climb;
|
|
246
|
+
loc.text = text;
|
|
247
|
+
});
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
const allStrategyLocators = result.allStrategyLocators;
|
|
251
|
+
const locators = result.locators;
|
|
252
|
+
if (allStrategyLocators) {
|
|
253
|
+
const allLocators = [];
|
|
254
|
+
for (const strategy in allStrategyLocators) {
|
|
255
|
+
if (strategy === "strategy") continue;
|
|
256
|
+
const locators = allStrategyLocators[strategy];
|
|
257
|
+
if (locators.length === 0) continue;
|
|
258
|
+
allLocators.push(...locators);
|
|
259
|
+
allStrategyLocators[strategy] = [];
|
|
260
|
+
}
|
|
261
|
+
attachContextToLocators(allLocators);
|
|
262
|
+
allStrategyLocators[this.locatorStrategies.context] = allLocators;
|
|
263
|
+
allStrategyLocators.strategy = this.locatorStrategies.context;
|
|
264
|
+
result.locators = allLocators;
|
|
265
|
+
return result;
|
|
266
|
+
}
|
|
267
|
+
if (locators) {
|
|
268
|
+
attachContextToLocators(locators);
|
|
269
|
+
return result;
|
|
270
|
+
}
|
|
271
|
+
return result;
|
|
272
|
+
}
|
|
220
273
|
getContextLocators(element, locators) {
|
|
221
274
|
if (!locators || !Array.isArray(locators)) {
|
|
222
275
|
console.error("Locators must be an array");
|
|
@@ -441,12 +494,16 @@ class LocatorGenerator {
|
|
|
441
494
|
categorizeLocators(element, locators, options) {
|
|
442
495
|
const unique = [];
|
|
443
496
|
const nonUnique = [];
|
|
497
|
+
const visible = options?.visible ?? true;
|
|
444
498
|
try {
|
|
445
499
|
for (const locator of locators) {
|
|
446
500
|
if (!locator || !locator.css || typeof locator.css !== "string") {
|
|
447
501
|
console.error("Locator must have a valid css selector found: ", locator);
|
|
448
502
|
continue;
|
|
449
503
|
}
|
|
504
|
+
if (visible === false) {
|
|
505
|
+
locator.visible = false;
|
|
506
|
+
}
|
|
450
507
|
const elements = this.getMatchingElements(locator.css, options);
|
|
451
508
|
if (elements.length === 0) {
|
|
452
509
|
console.warn(`No elements found for locator: ${locator.css}`);
|
|
@@ -571,7 +628,7 @@ class LocatorGenerator {
|
|
|
571
628
|
result.push(_locator);
|
|
572
629
|
} else {
|
|
573
630
|
const index = _elements.indexOf(element);
|
|
574
|
-
if (index !== -1 && index <
|
|
631
|
+
if (index !== -1 && index < 10) {
|
|
575
632
|
// _locator.selector = fullSelector;
|
|
576
633
|
_locator.css = fullSelector;
|
|
577
634
|
_locator.index = index;
|
|
@@ -583,7 +640,7 @@ class LocatorGenerator {
|
|
|
583
640
|
}
|
|
584
641
|
} else {
|
|
585
642
|
const index = elements.indexOf(element);
|
|
586
|
-
if (index !== -1 && index <
|
|
643
|
+
if (index !== -1 && index < 10) {
|
|
587
644
|
locator.index = index;
|
|
588
645
|
locator.priority = 2; // non-unique locators have lower priority
|
|
589
646
|
result.push(locator);
|
|
@@ -591,7 +648,7 @@ class LocatorGenerator {
|
|
|
591
648
|
}
|
|
592
649
|
} else {
|
|
593
650
|
const index = elements.indexOf(element);
|
|
594
|
-
if (index !== -1 && index <
|
|
651
|
+
if (index !== -1 && index < 10) {
|
|
595
652
|
locator.index = index;
|
|
596
653
|
locator.priority = 2; // non-unique locators have lower priority
|
|
597
654
|
result.push(locator);
|
|
@@ -625,7 +682,7 @@ class LocatorGenerator {
|
|
|
625
682
|
|
|
626
683
|
getUniqueLocators2(element, locatorGenerator = this.getNoTextLocators, options = {}) {
|
|
627
684
|
try {
|
|
628
|
-
const { maxLocators = 5, root = window.document.body } = options;
|
|
685
|
+
const { maxLocators = 5, root = window.document.body, prefix } = options;
|
|
629
686
|
|
|
630
687
|
if (!element) {
|
|
631
688
|
return [];
|
|
@@ -672,12 +729,13 @@ class LocatorGenerator {
|
|
|
672
729
|
|
|
673
730
|
const elementsCache = new Map();
|
|
674
731
|
|
|
675
|
-
const allAncestors = this.dom_Parent.getFullAncestorChainToRoot(element, root);
|
|
732
|
+
const allAncestors = prefix ? [element] : this.dom_Parent.getFullAncestorChainToRoot(element, root);
|
|
676
733
|
allAncestors.shift(); // remove the element itself from the ancestors
|
|
677
734
|
|
|
678
735
|
const cache = new Map();
|
|
679
736
|
const textToIgnore = this.PW.selectorUtils.elementText(cache, element).full.trim();
|
|
680
737
|
const ancestorLocators = [];
|
|
738
|
+
let uniqueAncestor = null;
|
|
681
739
|
for (const ancestor of allAncestors) {
|
|
682
740
|
const _locators = locatorGenerator(ancestor, { ...options, textToIgnore });
|
|
683
741
|
if (!_locators || !Array.isArray(_locators) || _locators.length === 0) {
|
|
@@ -690,11 +748,15 @@ class LocatorGenerator {
|
|
|
690
748
|
});
|
|
691
749
|
elementsCache.set(ancestor, _categorized);
|
|
692
750
|
if (_categorized.unique.length > 0) {
|
|
751
|
+
uniqueAncestor = {
|
|
752
|
+
element: ancestor,
|
|
753
|
+
locators: _categorized,
|
|
754
|
+
};
|
|
693
755
|
break;
|
|
694
756
|
}
|
|
695
757
|
}
|
|
696
758
|
|
|
697
|
-
const uniqueAncestor = ancestorLocators[ancestorLocators.length - 1];
|
|
759
|
+
// const uniqueAncestor = ancestorLocators[ancestorLocators.length - 1];
|
|
698
760
|
|
|
699
761
|
for (const locator of nonUnique) {
|
|
700
762
|
const selector = locator.css ?? locator.selector;
|
|
@@ -703,6 +765,27 @@ class LocatorGenerator {
|
|
|
703
765
|
console.warn(`No elements found for locator: ${selector}`);
|
|
704
766
|
continue;
|
|
705
767
|
}
|
|
768
|
+
if (!uniqueAncestor) {
|
|
769
|
+
const elements = this.getMatchingElements(selector, options);
|
|
770
|
+
if (elements.length === 1 && elements[0] === element) {
|
|
771
|
+
result.push({
|
|
772
|
+
css: selector,
|
|
773
|
+
score: locator.score,
|
|
774
|
+
priority: 1,
|
|
775
|
+
});
|
|
776
|
+
} else {
|
|
777
|
+
const index = elements.indexOf(element);
|
|
778
|
+
if (index !== -1) {
|
|
779
|
+
result.push({
|
|
780
|
+
css: selector,
|
|
781
|
+
index,
|
|
782
|
+
score: locator.score + 200,
|
|
783
|
+
priority: 2,
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
continue;
|
|
788
|
+
}
|
|
706
789
|
|
|
707
790
|
for (const unique_locator of uniqueAncestor.locators.unique) {
|
|
708
791
|
const fullSelector = `${unique_locator.css} >> ${selector}`;
|
|
@@ -718,7 +801,7 @@ class LocatorGenerator {
|
|
|
718
801
|
result.push(newLocator);
|
|
719
802
|
} else {
|
|
720
803
|
const index = elements.indexOf(element);
|
|
721
|
-
if (index !== -1 && index <
|
|
804
|
+
if (index !== -1 && index < 10) {
|
|
722
805
|
const effectiveScore = (unique_locator.score + locator.score) / 2;
|
|
723
806
|
const newLocator = {
|
|
724
807
|
...unique_locator,
|
|
@@ -741,9 +824,32 @@ class LocatorGenerator {
|
|
|
741
824
|
return [];
|
|
742
825
|
}
|
|
743
826
|
}
|
|
827
|
+
isElementVisible(element) {
|
|
828
|
+
if (!(element instanceof Element)) return false;
|
|
829
|
+
const style = window.getComputedStyle(element);
|
|
830
|
+
if (style.display === "none" || style.visibility === "hidden" || style.opacity === "0") return false;
|
|
831
|
+
const rect = element.getBoundingClientRect();
|
|
832
|
+
if (rect.width === 0 || rect.height === 0) return false;
|
|
833
|
+
return true;
|
|
834
|
+
}
|
|
744
835
|
getElementLocators(element, options = {}) {
|
|
836
|
+
const isVisible = this.PW.domUtils.isElementVisible(element);
|
|
837
|
+
if (isVisible === false) {
|
|
838
|
+
console.warn("Element is not visible: ", element);
|
|
839
|
+
options.visible = isVisible;
|
|
840
|
+
}
|
|
745
841
|
try {
|
|
746
|
-
const {
|
|
842
|
+
const {
|
|
843
|
+
excludeText = false,
|
|
844
|
+
strategies = {
|
|
845
|
+
[this.locatorStrategies.custom]: true,
|
|
846
|
+
[this.locatorStrategies.context]: true,
|
|
847
|
+
[this.locatorStrategies.text]: true,
|
|
848
|
+
[this.locatorStrategies.text_with_index]: true,
|
|
849
|
+
[this.locatorStrategies.digitIgnore]: true,
|
|
850
|
+
[this.locatorStrategies.no_text]: true,
|
|
851
|
+
},
|
|
852
|
+
} = options;
|
|
747
853
|
|
|
748
854
|
const allStrategyLocators = {
|
|
749
855
|
[this.locatorStrategies.custom]: [],
|
|
@@ -753,7 +859,12 @@ class LocatorGenerator {
|
|
|
753
859
|
[this.locatorStrategies.digitIgnore]: [],
|
|
754
860
|
[this.locatorStrategies.no_text]: [],
|
|
755
861
|
};
|
|
756
|
-
if (
|
|
862
|
+
if (
|
|
863
|
+
strategies[this.locatorStrategies.custom] &&
|
|
864
|
+
this.options?.customAttributes &&
|
|
865
|
+
Array.isArray(this.options.customAttributes) &&
|
|
866
|
+
this.options.customAttributes.length > 0
|
|
867
|
+
) {
|
|
757
868
|
console.groupCollapsed("Generating Custom locators for element:", element);
|
|
758
869
|
const customLocators = this.getUniqueLocators(element, this.getCustomLocators.bind(this), options);
|
|
759
870
|
if (customLocators.length > 0) {
|
|
@@ -762,43 +873,60 @@ class LocatorGenerator {
|
|
|
762
873
|
}
|
|
763
874
|
console.groupEnd();
|
|
764
875
|
if (!excludeText) {
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
console.groupEnd();
|
|
768
|
-
if (basicLocators.length > 0) {
|
|
769
|
-
allStrategyLocators[this.locatorStrategies.text] = basicLocators;
|
|
770
|
-
}
|
|
876
|
+
if (strategies[this.locatorStrategies.text]) {
|
|
877
|
+
console.groupCollapsed("Generating Text locators for element:", element);
|
|
771
878
|
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
879
|
+
const basicLocators = this.getUniqueLocators(element, this.getTextLocators.bind(this), options);
|
|
880
|
+
console.groupEnd();
|
|
881
|
+
if (basicLocators.length > 0) {
|
|
882
|
+
allStrategyLocators[this.locatorStrategies.text] = basicLocators;
|
|
883
|
+
}
|
|
884
|
+
if (strategies[this.locatorStrategies.text_with_index]) {
|
|
885
|
+
const textWithIndexLocators = this.getTextwithIndexLocators(basicLocators);
|
|
886
|
+
if (textWithIndexLocators.length > 0) {
|
|
887
|
+
allStrategyLocators[this.locatorStrategies.text_with_index] = textWithIndexLocators;
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
if (strategies[this.locatorStrategies.digitIgnore]) {
|
|
891
|
+
const digitIgnoreLocators = this.getDigitIgnoreLocators(element, basicLocators);
|
|
892
|
+
if (digitIgnoreLocators.length > 0) {
|
|
893
|
+
allStrategyLocators[this.locatorStrategies.digitIgnore] = digitIgnoreLocators;
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
if (strategies[this.locatorStrategies.context]) {
|
|
897
|
+
const contextLocators = this.getContextLocators(element, basicLocators);
|
|
898
|
+
if (contextLocators.length > 0) {
|
|
899
|
+
allStrategyLocators[this.locatorStrategies.context] = contextLocators;
|
|
900
|
+
}
|
|
901
|
+
}
|
|
783
902
|
}
|
|
784
903
|
}
|
|
785
|
-
|
|
786
|
-
|
|
904
|
+
if (strategies[this.locatorStrategies.no_text]) {
|
|
905
|
+
console.groupCollapsed("Generating No Text locators for element:", element);
|
|
906
|
+
const noTextLocators = this.getUniqueLocators(element, this.getNoTextLocators.bind(this), options);
|
|
787
907
|
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
908
|
+
if (noTextLocators.length > 0) {
|
|
909
|
+
allStrategyLocators[this.locatorStrategies.no_text] = noTextLocators;
|
|
910
|
+
} else {
|
|
911
|
+
|
|
912
|
+
// try mixed locator strategy as last resort
|
|
913
|
+
const mixedLocators = this.getUniqueLocators(element, this.getMixedLocators.bind(this), options);
|
|
914
|
+
if (mixedLocators.length > 0) {
|
|
915
|
+
allStrategyLocators[this.locatorStrategies.no_text] = mixedLocators;
|
|
916
|
+
} else {
|
|
917
|
+
const _locators = [];
|
|
918
|
+
_locators.push({
|
|
919
|
+
css: this.generateUniqueCSSSelector(element, options),
|
|
920
|
+
score: 500,
|
|
921
|
+
priority: 3,
|
|
922
|
+
});
|
|
923
|
+
if (_locators.length > 0) {
|
|
924
|
+
allStrategyLocators[this.locatorStrategies.no_text] = _locators;
|
|
925
|
+
}
|
|
926
|
+
}
|
|
799
927
|
}
|
|
928
|
+
console.groupEnd();
|
|
800
929
|
}
|
|
801
|
-
console.groupEnd();
|
|
802
930
|
|
|
803
931
|
let bestStrategy = this.getBestStrategy(allStrategyLocators);
|
|
804
932
|
if (!bestStrategy) {
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import dotenv from "dotenv";
|
|
2
|
+
// Load .env into process.env
|
|
3
|
+
dotenv.config();
|
|
4
|
+
|
|
1
5
|
import {
|
|
2
6
|
After,
|
|
3
7
|
setDefaultTimeout,
|
|
@@ -30,8 +34,8 @@ BeforeStep(async function (step) {
|
|
|
30
34
|
}
|
|
31
35
|
});
|
|
32
36
|
|
|
33
|
-
AfterStep(async function (
|
|
37
|
+
AfterStep(async function ({ result, pickleStep }) {
|
|
34
38
|
if (context) {
|
|
35
|
-
await context.web.afterStep(this,
|
|
39
|
+
await context.web.afterStep(this, pickleStep, result);
|
|
36
40
|
}
|
|
37
41
|
});
|