@dev-blinq/cucumber_client 1.0.1475-dev → 1.0.1475-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 +49 -49
- package/bin/assets/scripts/recorder.js +87 -34
- package/bin/assets/scripts/snapshot_capturer.js +10 -17
- package/bin/assets/scripts/unique_locators.js +78 -28
- package/bin/assets/templates/_hooks_template.txt +6 -2
- package/bin/assets/templates/utils_template.txt +16 -16
- package/bin/client/code_cleanup/codemod/find_harcoded_locators.js +173 -0
- package/bin/client/code_cleanup/codemod/fix_hardcoded_locators.js +247 -0
- 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 +39 -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 +316 -98
- package/bin/client/recorderv3/update_feature.js +85 -37
- package/bin/client/recorderv3/utils.js +80 -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,15 +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 result = this.locatorGenerator.toContextLocators(el, this.contextElement);
|
|
842
|
+
const result = this.locatorGenerator.toContextLocators(el, this.contextElement, options);
|
|
794
843
|
return result;
|
|
795
844
|
}
|
|
796
845
|
const isRecordingText = this.#mode === "recordingText";
|
|
797
846
|
return this.locatorGenerator.getElementLocators(el, {
|
|
798
847
|
excludeText: isRecordingText,
|
|
848
|
+
...options,
|
|
799
849
|
});
|
|
800
850
|
}
|
|
801
851
|
addListeners() {
|
|
@@ -832,31 +882,7 @@ class BVTRecorder {
|
|
|
832
882
|
}
|
|
833
883
|
|
|
834
884
|
performance.mark("command-send");
|
|
835
|
-
|
|
836
|
-
mode: this.#mode,
|
|
837
|
-
action: action.details,
|
|
838
|
-
element: this.getElementDetails(actionElement, eventName),
|
|
839
|
-
isPopupCloseClick: this.isPopupCloseEvent(e),
|
|
840
|
-
// ...this.getLocatorsObject(actionElement),
|
|
841
|
-
...(actionElement.__locators ?? this.getLocatorsObject(actionElement)),
|
|
842
|
-
frame: this.getFrameDetails(),
|
|
843
|
-
statistics: {
|
|
844
|
-
time: `${performance.measure("command-received", "command-send").duration.toFixed(2)} ms`,
|
|
845
|
-
},
|
|
846
|
-
};
|
|
847
|
-
const snapshotDetails = {
|
|
848
|
-
id: actionElement.getAttribute("data-blinq-id"),
|
|
849
|
-
contextId: this.contextElement?.getAttribute("data-blinq-context-id"),
|
|
850
|
-
doc: this.snapshotCapturer.createSnapshot({
|
|
851
|
-
excludeSelectors: ["x-bvt-toolbar", "script", "style", "link[rel=stylesheet]"],
|
|
852
|
-
}),
|
|
853
|
-
};
|
|
854
|
-
cmd.snapshotDetails = snapshotDetails;
|
|
855
|
-
// eventQueue.enqueue(async () => {
|
|
856
|
-
// await bvtRecorderBindings.validateLocators(snapshotDetails);
|
|
857
|
-
// });
|
|
858
|
-
// console.log(cmd);
|
|
859
|
-
await bvtRecorderBindings.recordCommand(cmd);
|
|
885
|
+
this.recordEvent(action, actionElement, eventName, e);
|
|
860
886
|
this.handleStateTransition(action.element);
|
|
861
887
|
},
|
|
862
888
|
{ capture: true }
|
|
@@ -864,6 +890,33 @@ class BVTRecorder {
|
|
|
864
890
|
});
|
|
865
891
|
}
|
|
866
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
|
+
|
|
867
920
|
// TODO: implement the corresponding logic for the below methods
|
|
868
921
|
setPopupHandlers(_popopHandlers) {
|
|
869
922
|
this.popupHandlers = _popopHandlers;
|
|
@@ -882,7 +935,7 @@ class BVTRecorder {
|
|
|
882
935
|
this.interestedElements.clear();
|
|
883
936
|
}
|
|
884
937
|
processAriaSnapshot(snapshot) {
|
|
885
|
-
const matchedElements = this.findMatchingElements(snapshot, this.snapshotElements);
|
|
938
|
+
const matchedElements = this.snapshotUtils.findMatchingElements(snapshot, this.snapshotElements);
|
|
886
939
|
for (const el of matchedElements.values()) {
|
|
887
940
|
const element = el;
|
|
888
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,10 +217,18 @@ class LocatorGenerator {
|
|
|
217
217
|
}
|
|
218
218
|
return result;
|
|
219
219
|
}
|
|
220
|
-
|
|
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 = {}) {
|
|
221
227
|
const commonParent = this.dom_Parent.findLowestCommonAncestor([contextElement, element]);
|
|
222
228
|
const climb = this.dom_Parent.getClimbCountToParent(contextElement, commonParent);
|
|
223
229
|
const text = contextElement.innerText.trim();
|
|
230
|
+
|
|
231
|
+
const prefix = `internal:text="${text}" >> ${this.getXPathSelector(climb)}`;
|
|
224
232
|
const result = this.getElementLocators(element, {
|
|
225
233
|
root: commonParent,
|
|
226
234
|
strategies: {
|
|
@@ -228,26 +236,17 @@ class LocatorGenerator {
|
|
|
228
236
|
[this.locatorStrategies.text]: true,
|
|
229
237
|
[this.locatorStrategies.no_text]: true,
|
|
230
238
|
},
|
|
239
|
+
prefix,
|
|
240
|
+
...options,
|
|
231
241
|
});
|
|
232
242
|
|
|
233
|
-
const randomToken = Math.random().toString(36).substring(2, 8);
|
|
234
|
-
commonParent.setAttribute("data-blinq--" + randomToken, "true");
|
|
235
|
-
// const contextLocators = [];
|
|
236
|
-
|
|
237
243
|
const attachContextToLocators = (locs) => {
|
|
238
244
|
locs.forEach((loc) => {
|
|
239
245
|
loc.climb = climb;
|
|
240
246
|
loc.text = text;
|
|
241
|
-
const _selector = `[data-blinq--${randomToken}]` + " >> " + this.getXPathSelector(climb) + " >> " + loc.css;
|
|
242
|
-
const elements = this.getMatchingElements(_selector, { root: window.document });
|
|
243
|
-
const index = elements.indexOf(element);
|
|
244
|
-
if (index !== -1) {
|
|
245
|
-
loc.index = index;
|
|
246
|
-
} else {
|
|
247
|
-
console.error("Invalid context locator, element not found in context locator", loc, element, contextElement);
|
|
248
|
-
}
|
|
249
247
|
});
|
|
250
248
|
};
|
|
249
|
+
|
|
251
250
|
const allStrategyLocators = result.allStrategyLocators;
|
|
252
251
|
const locators = result.locators;
|
|
253
252
|
if (allStrategyLocators) {
|
|
@@ -262,6 +261,7 @@ class LocatorGenerator {
|
|
|
262
261
|
attachContextToLocators(allLocators);
|
|
263
262
|
allStrategyLocators[this.locatorStrategies.context] = allLocators;
|
|
264
263
|
allStrategyLocators.strategy = this.locatorStrategies.context;
|
|
264
|
+
result.locators = allLocators;
|
|
265
265
|
return result;
|
|
266
266
|
}
|
|
267
267
|
if (locators) {
|
|
@@ -494,12 +494,16 @@ class LocatorGenerator {
|
|
|
494
494
|
categorizeLocators(element, locators, options) {
|
|
495
495
|
const unique = [];
|
|
496
496
|
const nonUnique = [];
|
|
497
|
+
const visible = options?.visible ?? true;
|
|
497
498
|
try {
|
|
498
499
|
for (const locator of locators) {
|
|
499
500
|
if (!locator || !locator.css || typeof locator.css !== "string") {
|
|
500
501
|
console.error("Locator must have a valid css selector found: ", locator);
|
|
501
502
|
continue;
|
|
502
503
|
}
|
|
504
|
+
if (visible === false) {
|
|
505
|
+
locator.visible = false;
|
|
506
|
+
}
|
|
503
507
|
const elements = this.getMatchingElements(locator.css, options);
|
|
504
508
|
if (elements.length === 0) {
|
|
505
509
|
console.warn(`No elements found for locator: ${locator.css}`);
|
|
@@ -624,7 +628,7 @@ class LocatorGenerator {
|
|
|
624
628
|
result.push(_locator);
|
|
625
629
|
} else {
|
|
626
630
|
const index = _elements.indexOf(element);
|
|
627
|
-
if (index !== -1 && index <
|
|
631
|
+
if (index !== -1 && index < 10) {
|
|
628
632
|
// _locator.selector = fullSelector;
|
|
629
633
|
_locator.css = fullSelector;
|
|
630
634
|
_locator.index = index;
|
|
@@ -636,7 +640,7 @@ class LocatorGenerator {
|
|
|
636
640
|
}
|
|
637
641
|
} else {
|
|
638
642
|
const index = elements.indexOf(element);
|
|
639
|
-
if (index !== -1 && index <
|
|
643
|
+
if (index !== -1 && index < 10) {
|
|
640
644
|
locator.index = index;
|
|
641
645
|
locator.priority = 2; // non-unique locators have lower priority
|
|
642
646
|
result.push(locator);
|
|
@@ -644,7 +648,7 @@ class LocatorGenerator {
|
|
|
644
648
|
}
|
|
645
649
|
} else {
|
|
646
650
|
const index = elements.indexOf(element);
|
|
647
|
-
if (index !== -1 && index <
|
|
651
|
+
if (index !== -1 && index < 10) {
|
|
648
652
|
locator.index = index;
|
|
649
653
|
locator.priority = 2; // non-unique locators have lower priority
|
|
650
654
|
result.push(locator);
|
|
@@ -678,7 +682,7 @@ class LocatorGenerator {
|
|
|
678
682
|
|
|
679
683
|
getUniqueLocators2(element, locatorGenerator = this.getNoTextLocators, options = {}) {
|
|
680
684
|
try {
|
|
681
|
-
const { maxLocators = 5, root = window.document.body } = options;
|
|
685
|
+
const { maxLocators = 5, root = window.document.body, prefix } = options;
|
|
682
686
|
|
|
683
687
|
if (!element) {
|
|
684
688
|
return [];
|
|
@@ -725,12 +729,13 @@ class LocatorGenerator {
|
|
|
725
729
|
|
|
726
730
|
const elementsCache = new Map();
|
|
727
731
|
|
|
728
|
-
const allAncestors = this.dom_Parent.getFullAncestorChainToRoot(element, root);
|
|
732
|
+
const allAncestors = prefix ? [element] : this.dom_Parent.getFullAncestorChainToRoot(element, root);
|
|
729
733
|
allAncestors.shift(); // remove the element itself from the ancestors
|
|
730
734
|
|
|
731
735
|
const cache = new Map();
|
|
732
736
|
const textToIgnore = this.PW.selectorUtils.elementText(cache, element).full.trim();
|
|
733
737
|
const ancestorLocators = [];
|
|
738
|
+
let uniqueAncestor = null;
|
|
734
739
|
for (const ancestor of allAncestors) {
|
|
735
740
|
const _locators = locatorGenerator(ancestor, { ...options, textToIgnore });
|
|
736
741
|
if (!_locators || !Array.isArray(_locators) || _locators.length === 0) {
|
|
@@ -743,11 +748,15 @@ class LocatorGenerator {
|
|
|
743
748
|
});
|
|
744
749
|
elementsCache.set(ancestor, _categorized);
|
|
745
750
|
if (_categorized.unique.length > 0) {
|
|
751
|
+
uniqueAncestor = {
|
|
752
|
+
element: ancestor,
|
|
753
|
+
locators: _categorized,
|
|
754
|
+
};
|
|
746
755
|
break;
|
|
747
756
|
}
|
|
748
757
|
}
|
|
749
758
|
|
|
750
|
-
const uniqueAncestor = ancestorLocators[ancestorLocators.length - 1];
|
|
759
|
+
// const uniqueAncestor = ancestorLocators[ancestorLocators.length - 1];
|
|
751
760
|
|
|
752
761
|
for (const locator of nonUnique) {
|
|
753
762
|
const selector = locator.css ?? locator.selector;
|
|
@@ -756,6 +765,27 @@ class LocatorGenerator {
|
|
|
756
765
|
console.warn(`No elements found for locator: ${selector}`);
|
|
757
766
|
continue;
|
|
758
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
|
+
}
|
|
759
789
|
|
|
760
790
|
for (const unique_locator of uniqueAncestor.locators.unique) {
|
|
761
791
|
const fullSelector = `${unique_locator.css} >> ${selector}`;
|
|
@@ -771,7 +801,7 @@ class LocatorGenerator {
|
|
|
771
801
|
result.push(newLocator);
|
|
772
802
|
} else {
|
|
773
803
|
const index = elements.indexOf(element);
|
|
774
|
-
if (index !== -1 && index <
|
|
804
|
+
if (index !== -1 && index < 10) {
|
|
775
805
|
const effectiveScore = (unique_locator.score + locator.score) / 2;
|
|
776
806
|
const newLocator = {
|
|
777
807
|
...unique_locator,
|
|
@@ -794,7 +824,20 @@ class LocatorGenerator {
|
|
|
794
824
|
return [];
|
|
795
825
|
}
|
|
796
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
|
+
}
|
|
797
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
|
+
}
|
|
798
841
|
try {
|
|
799
842
|
const {
|
|
800
843
|
excludeText = false,
|
|
@@ -865,14 +908,21 @@ class LocatorGenerator {
|
|
|
865
908
|
if (noTextLocators.length > 0) {
|
|
866
909
|
allStrategyLocators[this.locatorStrategies.no_text] = noTextLocators;
|
|
867
910
|
} else {
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
}
|
|
874
|
-
|
|
875
|
-
|
|
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
|
+
}
|
|
876
926
|
}
|
|
877
927
|
}
|
|
878
928
|
console.groupEnd();
|
|
@@ -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
|
});
|
|
@@ -12,7 +12,7 @@ import path from "path";
|
|
|
12
12
|
* @param {string} text the text to verify exists in page
|
|
13
13
|
* @protect
|
|
14
14
|
*/
|
|
15
|
-
async function verifyTextExistsInPage(text) {
|
|
15
|
+
export async function verifyTextExistsInPage(text) {
|
|
16
16
|
await context.web.verifyTextExistInPage(text, null, this);
|
|
17
17
|
}
|
|
18
18
|
Then("Verify the text {string} can be found in the page", verifyTextExistsInPage);
|
|
@@ -22,7 +22,7 @@ Then("Verify the text {string} can be found in the page", verifyTextExistsInPage
|
|
|
22
22
|
* @param {string} elementDescription element description
|
|
23
23
|
* @protect
|
|
24
24
|
*/
|
|
25
|
-
async function clickOnElement(elementDescription) {
|
|
25
|
+
export async function clickOnElement(elementDescription) {
|
|
26
26
|
await context.web.simpleClick(elementDescription, null, null, this);
|
|
27
27
|
}
|
|
28
28
|
When("click on {string}", clickOnElement);
|
|
@@ -36,7 +36,7 @@ When("Click {string}", clickOnElement);
|
|
|
36
36
|
* @param {string} value value to fill the element with
|
|
37
37
|
* @protect
|
|
38
38
|
*/
|
|
39
|
-
async function fillElement(elementDescription, value) {
|
|
39
|
+
export async function fillElement(elementDescription, value) {
|
|
40
40
|
await context.web.simpleClickType(elementDescription, value, null, null, this);
|
|
41
41
|
}
|
|
42
42
|
When("fill {string} with {string}", fillElement);
|
|
@@ -47,7 +47,7 @@ When("Fill {string} with {string}", fillElement);
|
|
|
47
47
|
* @param {string} text the text to verify does not exist in page
|
|
48
48
|
* @protect
|
|
49
49
|
*/
|
|
50
|
-
async function verifyTextNotExistsInPage(text) {
|
|
50
|
+
export async function verifyTextNotExistsInPage(text) {
|
|
51
51
|
await context.web.waitForTextToDisappear(text, null, this);
|
|
52
52
|
}
|
|
53
53
|
Then("Verify the text {string} cannot be found in the page", verifyTextNotExistsInPage);
|
|
@@ -57,7 +57,7 @@ Then("Verify the text {string} cannot be found in the page", verifyTextNotExists
|
|
|
57
57
|
* @param {string} url URL to navigate
|
|
58
58
|
* @protect
|
|
59
59
|
*/
|
|
60
|
-
async function navigateTo(url) {
|
|
60
|
+
export async function navigateTo(url) {
|
|
61
61
|
await context.web.goto(url, this);
|
|
62
62
|
}
|
|
63
63
|
When("Navigate to {string}", navigateTo);
|
|
@@ -66,7 +66,7 @@ When("Navigate to {string}", navigateTo);
|
|
|
66
66
|
* Navigate to the current page
|
|
67
67
|
* @protect
|
|
68
68
|
*/
|
|
69
|
-
async function browserNavigateBack() {
|
|
69
|
+
export async function browserNavigateBack() {
|
|
70
70
|
await context.web.goBack({}, this);
|
|
71
71
|
}
|
|
72
72
|
Then("Browser navigate back", browserNavigateBack);
|
|
@@ -75,7 +75,7 @@ Then("Browser navigate back", browserNavigateBack);
|
|
|
75
75
|
* Navigate forward in browser history
|
|
76
76
|
* @protect
|
|
77
77
|
*/
|
|
78
|
-
async function browserNavigateForward() {
|
|
78
|
+
export async function browserNavigateForward() {
|
|
79
79
|
await context.web.goForward({}, this);
|
|
80
80
|
}
|
|
81
81
|
Then("Browser navigate forward", browserNavigateForward);
|
|
@@ -85,7 +85,7 @@ Then("Browser navigate forward", browserNavigateForward);
|
|
|
85
85
|
* @param {string} filePath the file path or empty to store in the test data file
|
|
86
86
|
* @protect
|
|
87
87
|
*/
|
|
88
|
-
async function storeBrowserSession(filePath) {
|
|
88
|
+
export async function storeBrowserSession(filePath) {
|
|
89
89
|
await context.web.saveStoreState(filePath, this);
|
|
90
90
|
}
|
|
91
91
|
When("Store browser session {string}", storeBrowserSession);
|
|
@@ -95,7 +95,7 @@ When("Store browser session {string}", storeBrowserSession);
|
|
|
95
95
|
* @param {string} filePath the file path or empty
|
|
96
96
|
* @protect
|
|
97
97
|
*/
|
|
98
|
-
async function resetBrowserSession(filePath) {
|
|
98
|
+
export async function resetBrowserSession(filePath) {
|
|
99
99
|
await context.web.restoreSaveState(filePath, this);
|
|
100
100
|
}
|
|
101
101
|
When("Reset browser session {string}", resetBrowserSession);
|
|
@@ -107,7 +107,7 @@ When("Reset browser session {string}", resetBrowserSession);
|
|
|
107
107
|
* @param {string} textToVerify the target text to verify
|
|
108
108
|
* @protect
|
|
109
109
|
*/
|
|
110
|
-
async function verifyTextRelatedToText(textAnchor, climb, textToVerify) {
|
|
110
|
+
export async function verifyTextRelatedToText(textAnchor, climb, textToVerify) {
|
|
111
111
|
await context.web.verifyTextRelatedToText(textAnchor, climb, textToVerify, null, this);
|
|
112
112
|
}
|
|
113
113
|
Then(
|
|
@@ -120,7 +120,7 @@ Then(
|
|
|
120
120
|
* @requestName the name of the bruno request file
|
|
121
121
|
* @protect
|
|
122
122
|
*/
|
|
123
|
-
async function runBrunoRequest(requestName) {
|
|
123
|
+
export async function runBrunoRequest(requestName) {
|
|
124
124
|
await executeBrunoRequest(requestName, {}, context, this);
|
|
125
125
|
}
|
|
126
126
|
When("Bruno - {string}", runBrunoRequest);
|
|
@@ -131,7 +131,7 @@ When("bruno - {string}", runBrunoRequest);
|
|
|
131
131
|
* @param {string} fileName the downloaded file to verify
|
|
132
132
|
* @protect
|
|
133
133
|
*/
|
|
134
|
-
async function verify_the_downloaded_file_exists(fileName) {
|
|
134
|
+
export async function verify_the_downloaded_file_exists(fileName) {
|
|
135
135
|
const downloadFolder = path.join(context.reportFolder, "downloads");
|
|
136
136
|
const downloadFile = path.join(downloadFolder, fileName);
|
|
137
137
|
await verifyFileExists(downloadFile, {}, context, this);
|
|
@@ -148,7 +148,7 @@ When("Noop", async function () {});
|
|
|
148
148
|
* @param {string} url URL to be verified against current URL
|
|
149
149
|
* @protect
|
|
150
150
|
*/
|
|
151
|
-
async function verify_page_url(url) {
|
|
151
|
+
export async function verify_page_url(url) {
|
|
152
152
|
await context.web.verifyPagePath(url, {}, this);
|
|
153
153
|
}
|
|
154
154
|
Then("Verify the page url is {string}", verify_page_url);
|
|
@@ -158,7 +158,7 @@ Then("Verify the page url is {string}", verify_page_url);
|
|
|
158
158
|
* @param {string} title Title to be verified against current Title
|
|
159
159
|
* @protect
|
|
160
160
|
*/
|
|
161
|
-
async function verify_page_title(title) {
|
|
161
|
+
export async function verify_page_title(title) {
|
|
162
162
|
await context.web.verifyPageTitle(title, {}, this);
|
|
163
163
|
}
|
|
164
164
|
Then("Verify the page title is {string}", verify_page_title);
|
|
@@ -170,7 +170,7 @@ Then("Verify the page title is {string}", verify_page_title);
|
|
|
170
170
|
* @param {world} - Optional world context
|
|
171
171
|
* @returns Promise that resolves after the specified duration
|
|
172
172
|
*/
|
|
173
|
-
async function sleep(duration) {
|
|
174
|
-
await context.web.sleep(duration, {},
|
|
173
|
+
export async function sleep(duration) {
|
|
174
|
+
await context.web.sleep(duration, {}, this);
|
|
175
175
|
}
|
|
176
176
|
Then("Sleep for {string} ms", { timeout: -1 }, sleep);
|