automation_model 1.0.638-dev → 1.0.638-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/README.md +16 -16
- package/lib/analyze_helper.js.map +1 -1
- package/lib/api.d.ts +0 -1
- package/lib/api.js +35 -21
- package/lib/api.js.map +1 -1
- package/lib/auto_page.d.ts +1 -1
- package/lib/auto_page.js +96 -26
- package/lib/auto_page.js.map +1 -1
- package/lib/browser_manager.js +33 -9
- package/lib/browser_manager.js.map +1 -1
- package/lib/bruno.d.ts +2 -0
- package/lib/bruno.js +381 -0
- package/lib/bruno.js.map +1 -0
- package/lib/command_common.d.ts +4 -4
- package/lib/command_common.js +36 -16
- package/lib/command_common.js.map +1 -1
- package/lib/date_time.js.map +1 -1
- package/lib/drawRect.js.map +1 -1
- package/lib/environment.d.ts +1 -0
- package/lib/environment.js +1 -0
- package/lib/environment.js.map +1 -1
- package/lib/error-messages.js.map +1 -1
- package/lib/file_checker.d.ts +1 -0
- package/lib/file_checker.js +61 -0
- package/lib/file_checker.js.map +1 -0
- package/lib/find_function.js.map +1 -1
- package/lib/index.d.ts +2 -0
- package/lib/index.js +2 -0
- package/lib/index.js.map +1 -1
- package/lib/init_browser.d.ts +2 -2
- package/lib/init_browser.js +33 -27
- package/lib/init_browser.js.map +1 -1
- package/lib/locate_element.js +2 -2
- package/lib/locate_element.js.map +1 -1
- package/lib/locator.js +1 -1
- package/lib/locator.js.map +1 -1
- package/lib/locator_log.js.map +1 -1
- package/lib/network.d.ts +1 -1
- package/lib/network.js +5 -5
- package/lib/network.js.map +1 -1
- package/lib/snapshot_validation.d.ts +37 -0
- package/lib/snapshot_validation.js +357 -0
- package/lib/snapshot_validation.js.map +1 -0
- package/lib/stable_browser.d.ts +51 -24
- package/lib/stable_browser.js +730 -195
- package/lib/stable_browser.js.map +1 -1
- package/lib/table.js.map +1 -1
- package/lib/table_analyze.js.map +1 -1
- package/lib/table_helper.js +15 -0
- package/lib/table_helper.js.map +1 -1
- package/lib/test_context.d.ts +2 -0
- package/lib/test_context.js +2 -0
- package/lib/test_context.js.map +1 -1
- package/lib/utils.d.ts +6 -4
- package/lib/utils.js +131 -20
- package/lib/utils.js.map +1 -1
- package/package.json +11 -6
package/lib/stable_browser.js
CHANGED
|
@@ -15,6 +15,7 @@ import csv from "csv-parser";
|
|
|
15
15
|
import { Readable } from "node:stream";
|
|
16
16
|
import readline from "readline";
|
|
17
17
|
import { getContext, refreshBrowser } from "./init_browser.js";
|
|
18
|
+
import { getTestData } from "./auto_page.js";
|
|
18
19
|
import { locate_element } from "./locate_element.js";
|
|
19
20
|
import { randomUUID } from "crypto";
|
|
20
21
|
import { _commandError, _commandFinally, _preCommand, _validateSelectors, _screenshot, _reportToWorld, } from "./command_common.js";
|
|
@@ -22,23 +23,26 @@ import { registerDownloadEvent, registerNetworkEvents } from "./network.js";
|
|
|
22
23
|
import { LocatorLog } from "./locator_log.js";
|
|
23
24
|
import axios from "axios";
|
|
24
25
|
import { _findCellArea, findElementsInArea } from "./table_helper.js";
|
|
26
|
+
import { snapshotValidation } from "./snapshot_validation.js";
|
|
27
|
+
import { loadBrunoParams } from "./bruno.js";
|
|
25
28
|
export const Types = {
|
|
26
29
|
CLICK: "click_element",
|
|
27
30
|
WAIT_ELEMENT: "wait_element",
|
|
28
|
-
NAVIGATE: "navigate",
|
|
31
|
+
NAVIGATE: "navigate", ///
|
|
29
32
|
FILL: "fill_element",
|
|
30
|
-
EXECUTE: "execute_page_method",
|
|
31
|
-
OPEN: "open_environment",
|
|
33
|
+
EXECUTE: "execute_page_method", //
|
|
34
|
+
OPEN: "open_environment", //
|
|
32
35
|
COMPLETE: "step_complete",
|
|
33
36
|
ASK: "information_needed",
|
|
34
|
-
GET_PAGE_STATUS: "get_page_status",
|
|
35
|
-
CLICK_ROW_ACTION: "click_row_action",
|
|
37
|
+
GET_PAGE_STATUS: "get_page_status", ///
|
|
38
|
+
CLICK_ROW_ACTION: "click_row_action", //
|
|
36
39
|
VERIFY_ELEMENT_CONTAINS_TEXT: "verify_element_contains_text",
|
|
37
40
|
VERIFY_PAGE_CONTAINS_TEXT: "verify_page_contains_text",
|
|
38
41
|
VERIFY_PAGE_CONTAINS_NO_TEXT: "verify_page_contains_no_text",
|
|
39
42
|
ANALYZE_TABLE: "analyze_table",
|
|
40
|
-
SELECT: "select_combobox",
|
|
43
|
+
SELECT: "select_combobox", //
|
|
41
44
|
VERIFY_PAGE_PATH: "verify_page_path",
|
|
45
|
+
VERIFY_PAGE_TITLE: "verify_page_title",
|
|
42
46
|
TYPE_PRESS: "type_press",
|
|
43
47
|
PRESS: "press_key",
|
|
44
48
|
HOVER: "hover_element",
|
|
@@ -55,6 +59,12 @@ export const Types = {
|
|
|
55
59
|
WAIT_FOR_TEXT_TO_DISAPPEAR: "wait_for_text_to_disappear",
|
|
56
60
|
VERIFY_ATTRIBUTE: "verify_element_attribute",
|
|
57
61
|
VERIFY_TEXT_WITH_RELATION: "verify_text_with_relation",
|
|
62
|
+
BRUNO: "bruno",
|
|
63
|
+
VERIFY_FILE_EXISTS: "verify_file_exists",
|
|
64
|
+
SET_INPUT_FILES: "set_input_files",
|
|
65
|
+
SNAPSHOT_VALIDATION: "snapshot_validation",
|
|
66
|
+
REPORT_COMMAND: "report_command",
|
|
67
|
+
STEP_COMPLETE: "step_complete",
|
|
58
68
|
};
|
|
59
69
|
export const apps = {};
|
|
60
70
|
const formatElementName = (elementName) => {
|
|
@@ -180,6 +190,30 @@ class StableBrowser {
|
|
|
180
190
|
await this.waitForPageLoad();
|
|
181
191
|
}
|
|
182
192
|
}
|
|
193
|
+
async switchTab(tabTitleOrIndex) {
|
|
194
|
+
// first check if the tabNameOrIndex is a number
|
|
195
|
+
let index = parseInt(tabTitleOrIndex);
|
|
196
|
+
if (!isNaN(index)) {
|
|
197
|
+
if (index >= 0 && index < this.context.pages.length) {
|
|
198
|
+
this.page = this.context.pages[index];
|
|
199
|
+
this.context.page = this.page;
|
|
200
|
+
await this.page.bringToFront();
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
// if the tabNameOrIndex is a string, find the tab by name
|
|
205
|
+
for (let i = 0; i < this.context.pages.length; i++) {
|
|
206
|
+
let page = this.context.pages[i];
|
|
207
|
+
let title = await page.title();
|
|
208
|
+
if (title.includes(tabTitleOrIndex)) {
|
|
209
|
+
this.page = page;
|
|
210
|
+
this.context.page = this.page;
|
|
211
|
+
await this.page.bringToFront();
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
throw new Error("Tab not found: " + tabTitleOrIndex);
|
|
216
|
+
}
|
|
183
217
|
registerConsoleLogListener(page, context) {
|
|
184
218
|
if (!this.context.webLogger) {
|
|
185
219
|
this.context.webLogger = [];
|
|
@@ -247,6 +281,7 @@ class StableBrowser {
|
|
|
247
281
|
if (!url) {
|
|
248
282
|
throw new Error("url is null, verify that the environment file is correct");
|
|
249
283
|
}
|
|
284
|
+
url = await this._replaceWithLocalData(url, this.world);
|
|
250
285
|
if (!url.startsWith("http")) {
|
|
251
286
|
url = "https://" + url;
|
|
252
287
|
}
|
|
@@ -275,7 +310,7 @@ class StableBrowser {
|
|
|
275
310
|
_commandError(state, error, this);
|
|
276
311
|
}
|
|
277
312
|
finally {
|
|
278
|
-
_commandFinally(state, this);
|
|
313
|
+
await _commandFinally(state, this);
|
|
279
314
|
}
|
|
280
315
|
}
|
|
281
316
|
async _getLocator(locator, scope, _params) {
|
|
@@ -391,7 +426,7 @@ class StableBrowser {
|
|
|
391
426
|
}
|
|
392
427
|
return { elementCount: tagCount, randomToken };
|
|
393
428
|
}
|
|
394
|
-
async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true, allowDisabled = false, element_name = null) {
|
|
429
|
+
async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true, allowDisabled = false, element_name = null, logErrors = false) {
|
|
395
430
|
if (!info) {
|
|
396
431
|
info = {};
|
|
397
432
|
}
|
|
@@ -458,7 +493,7 @@ class StableBrowser {
|
|
|
458
493
|
}
|
|
459
494
|
return;
|
|
460
495
|
}
|
|
461
|
-
if (info.locatorLog && count === 0) {
|
|
496
|
+
if (info.locatorLog && count === 0 && logErrors) {
|
|
462
497
|
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "NOT_FOUND");
|
|
463
498
|
}
|
|
464
499
|
for (let j = 0; j < count; j++) {
|
|
@@ -473,7 +508,7 @@ class StableBrowser {
|
|
|
473
508
|
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
|
|
474
509
|
}
|
|
475
510
|
}
|
|
476
|
-
else {
|
|
511
|
+
else if (logErrors) {
|
|
477
512
|
info.failCause.visible = visible;
|
|
478
513
|
info.failCause.enabled = enabled;
|
|
479
514
|
if (!info.printMessages) {
|
|
@@ -565,15 +600,27 @@ class StableBrowser {
|
|
|
565
600
|
let element = await this._locate_internal(selectors, info, _params, timeout, allowDisabled);
|
|
566
601
|
if (!element.rerun) {
|
|
567
602
|
const randomToken = Math.random().toString(36).substring(7);
|
|
568
|
-
element.evaluate((el, randomToken) => {
|
|
603
|
+
await element.evaluate((el, randomToken) => {
|
|
569
604
|
el.setAttribute("data-blinq-id-" + randomToken, "");
|
|
570
605
|
}, randomToken);
|
|
571
|
-
if (element._frame) {
|
|
572
|
-
|
|
573
|
-
}
|
|
574
|
-
const scope = element.page();
|
|
575
|
-
|
|
576
|
-
|
|
606
|
+
// if (element._frame) {
|
|
607
|
+
// return element;
|
|
608
|
+
// }
|
|
609
|
+
const scope = element._frame ?? element.page();
|
|
610
|
+
let newElementSelector = "[data-blinq-id-" + randomToken + "]";
|
|
611
|
+
let prefixSelector = "";
|
|
612
|
+
const frameControlSelector = " >> internal:control=enter-frame";
|
|
613
|
+
const frameSelectorIndex = element._selector.lastIndexOf(frameControlSelector);
|
|
614
|
+
if (frameSelectorIndex !== -1) {
|
|
615
|
+
// remove everything after the >> internal:control=enter-frame
|
|
616
|
+
const frameSelector = element._selector.substring(0, frameSelectorIndex);
|
|
617
|
+
prefixSelector = frameSelector + " >> internal:control=enter-frame >>";
|
|
618
|
+
}
|
|
619
|
+
// if (element?._frame?._selector) {
|
|
620
|
+
// prefixSelector = element._frame._selector + " >> " + prefixSelector;
|
|
621
|
+
// }
|
|
622
|
+
const newSelector = prefixSelector + newElementSelector;
|
|
623
|
+
return scope.locator(newSelector);
|
|
577
624
|
}
|
|
578
625
|
}
|
|
579
626
|
throw new Error("unable to locate element " + JSON.stringify(selectors));
|
|
@@ -725,14 +772,9 @@ class StableBrowser {
|
|
|
725
772
|
// info.log += "scanning locators in priority 2" + "\n";
|
|
726
773
|
result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
727
774
|
}
|
|
728
|
-
if (result.foundElements.length === 0 && onlyPriority3) {
|
|
775
|
+
if (result.foundElements.length === 0 && (onlyPriority3 || !highPriorityOnly)) {
|
|
729
776
|
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
730
777
|
}
|
|
731
|
-
else {
|
|
732
|
-
if (result.foundElements.length === 0 && !highPriorityOnly) {
|
|
733
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
734
|
-
}
|
|
735
|
-
}
|
|
736
778
|
let foundElements = result.foundElements;
|
|
737
779
|
if (foundElements.length === 1 && foundElements[0].unique) {
|
|
738
780
|
info.box = foundElements[0].box;
|
|
@@ -787,6 +829,11 @@ class StableBrowser {
|
|
|
787
829
|
visibleOnly = false;
|
|
788
830
|
}
|
|
789
831
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
832
|
+
// sheck of more of half of the timeout has passed
|
|
833
|
+
if (Date.now() - startTime > timeout / 2) {
|
|
834
|
+
highPriorityOnly = false;
|
|
835
|
+
visibleOnly = false;
|
|
836
|
+
}
|
|
790
837
|
}
|
|
791
838
|
this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
|
|
792
839
|
// if (info.locatorLog) {
|
|
@@ -802,7 +849,7 @@ class StableBrowser {
|
|
|
802
849
|
}
|
|
803
850
|
throw new Error("failed to locate first element no elements found, " + info.log);
|
|
804
851
|
}
|
|
805
|
-
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false, element_name) {
|
|
852
|
+
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false, element_name, logErrors = false) {
|
|
806
853
|
let foundElements = [];
|
|
807
854
|
const result = {
|
|
808
855
|
foundElements: foundElements,
|
|
@@ -821,7 +868,9 @@ class StableBrowser {
|
|
|
821
868
|
await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
|
|
822
869
|
}
|
|
823
870
|
catch (e) {
|
|
824
|
-
|
|
871
|
+
if (logErrors) {
|
|
872
|
+
this.logger.info("unable to use locator (second try) " + JSON.stringify(locatorsGroup[i]));
|
|
873
|
+
}
|
|
825
874
|
}
|
|
826
875
|
}
|
|
827
876
|
if (foundLocators.length === 1) {
|
|
@@ -833,9 +882,40 @@ class StableBrowser {
|
|
|
833
882
|
result.locatorIndex = i;
|
|
834
883
|
}
|
|
835
884
|
if (foundLocators.length > 1) {
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
885
|
+
// remove elements that consume the same space with 10 pixels tolerance
|
|
886
|
+
const boxes = [];
|
|
887
|
+
for (let j = 0; j < foundLocators.length; j++) {
|
|
888
|
+
boxes.push({ box: await foundLocators[j].boundingBox(), locator: foundLocators[j] });
|
|
889
|
+
}
|
|
890
|
+
for (let j = 0; j < boxes.length; j++) {
|
|
891
|
+
for (let k = 0; k < boxes.length; k++) {
|
|
892
|
+
if (j === k) {
|
|
893
|
+
continue;
|
|
894
|
+
}
|
|
895
|
+
// check if x, y, width, height are the same with 10 pixels tolerance
|
|
896
|
+
if (Math.abs(boxes[j].box.x - boxes[k].box.x) < 10 &&
|
|
897
|
+
Math.abs(boxes[j].box.y - boxes[k].box.y) < 10 &&
|
|
898
|
+
Math.abs(boxes[j].box.width - boxes[k].box.width) < 10 &&
|
|
899
|
+
Math.abs(boxes[j].box.height - boxes[k].box.height) < 10) {
|
|
900
|
+
// as the element is not unique, will remove it
|
|
901
|
+
boxes.splice(k, 1);
|
|
902
|
+
k--;
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
if (boxes.length === 1) {
|
|
907
|
+
result.foundElements.push({
|
|
908
|
+
locator: boxes[0].locator.first(),
|
|
909
|
+
box: boxes[0].box,
|
|
910
|
+
unique: true,
|
|
911
|
+
});
|
|
912
|
+
result.locatorIndex = i;
|
|
913
|
+
}
|
|
914
|
+
else if (logErrors) {
|
|
915
|
+
info.failCause.foundMultiple = true;
|
|
916
|
+
if (info.locatorLog) {
|
|
917
|
+
info.locatorLog.setLocatorSearchStatus(JSON.stringify(locatorsGroup[i]), "FOUND_NOT_UNIQUE");
|
|
918
|
+
}
|
|
839
919
|
}
|
|
840
920
|
}
|
|
841
921
|
}
|
|
@@ -883,7 +963,7 @@ class StableBrowser {
|
|
|
883
963
|
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
884
964
|
}
|
|
885
965
|
finally {
|
|
886
|
-
_commandFinally(state, this);
|
|
966
|
+
await _commandFinally(state, this);
|
|
887
967
|
}
|
|
888
968
|
}
|
|
889
969
|
}
|
|
@@ -932,7 +1012,7 @@ class StableBrowser {
|
|
|
932
1012
|
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
933
1013
|
}
|
|
934
1014
|
finally {
|
|
935
|
-
_commandFinally(state, this);
|
|
1015
|
+
await _commandFinally(state, this);
|
|
936
1016
|
}
|
|
937
1017
|
}
|
|
938
1018
|
}
|
|
@@ -961,7 +1041,7 @@ class StableBrowser {
|
|
|
961
1041
|
await _commandError(state, e, this);
|
|
962
1042
|
}
|
|
963
1043
|
finally {
|
|
964
|
-
_commandFinally(state, this);
|
|
1044
|
+
await _commandFinally(state, this);
|
|
965
1045
|
}
|
|
966
1046
|
}
|
|
967
1047
|
async waitForElement(selectors, _params, options = {}, world = null) {
|
|
@@ -992,7 +1072,7 @@ class StableBrowser {
|
|
|
992
1072
|
// await _commandError(state, e, this);
|
|
993
1073
|
}
|
|
994
1074
|
finally {
|
|
995
|
-
_commandFinally(state, this);
|
|
1075
|
+
await _commandFinally(state, this);
|
|
996
1076
|
}
|
|
997
1077
|
return found;
|
|
998
1078
|
}
|
|
@@ -1016,8 +1096,8 @@ class StableBrowser {
|
|
|
1016
1096
|
try {
|
|
1017
1097
|
// if (world && world.screenshot && !world.screenshotPath) {
|
|
1018
1098
|
// console.log(`Highlighting while running from recorder`);
|
|
1019
|
-
await this._highlightElements(element);
|
|
1020
|
-
await state.element.setChecked(checked);
|
|
1099
|
+
await this._highlightElements(state.element);
|
|
1100
|
+
await state.element.setChecked(checked, { timeout: 2000 });
|
|
1021
1101
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1022
1102
|
// await this._unHighlightElements(element);
|
|
1023
1103
|
// }
|
|
@@ -1029,11 +1109,28 @@ class StableBrowser {
|
|
|
1029
1109
|
this.logger.info("element did not change its state, ignoring...");
|
|
1030
1110
|
}
|
|
1031
1111
|
else {
|
|
1112
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1032
1113
|
//await this.closeUnexpectedPopups();
|
|
1033
1114
|
state.info.log += "setCheck failed, will try again" + "\n";
|
|
1034
|
-
state.
|
|
1035
|
-
|
|
1036
|
-
|
|
1115
|
+
state.element_found = false;
|
|
1116
|
+
try {
|
|
1117
|
+
state.element = await this._locate(selectors, state.info, _params, 100);
|
|
1118
|
+
state.element_found = true;
|
|
1119
|
+
// check the check state
|
|
1120
|
+
}
|
|
1121
|
+
catch (error) {
|
|
1122
|
+
// element dismissed
|
|
1123
|
+
}
|
|
1124
|
+
if (state.element_found) {
|
|
1125
|
+
const isChecked = await state.element.isChecked();
|
|
1126
|
+
if (isChecked !== checked) {
|
|
1127
|
+
// perform click
|
|
1128
|
+
await state.element.click({ timeout: 2000, force: true });
|
|
1129
|
+
}
|
|
1130
|
+
else {
|
|
1131
|
+
this.logger.info(`Element ${selectors.element_name} is already in the desired state (${checked})`);
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1037
1134
|
}
|
|
1038
1135
|
}
|
|
1039
1136
|
await this.waitForPageLoad();
|
|
@@ -1043,7 +1140,7 @@ class StableBrowser {
|
|
|
1043
1140
|
await _commandError(state, e, this);
|
|
1044
1141
|
}
|
|
1045
1142
|
finally {
|
|
1046
|
-
_commandFinally(state, this);
|
|
1143
|
+
await _commandFinally(state, this);
|
|
1047
1144
|
}
|
|
1048
1145
|
}
|
|
1049
1146
|
async hover(selectors, _params, options = {}, world = null) {
|
|
@@ -1069,7 +1166,7 @@ class StableBrowser {
|
|
|
1069
1166
|
await _commandError(state, e, this);
|
|
1070
1167
|
}
|
|
1071
1168
|
finally {
|
|
1072
|
-
_commandFinally(state, this);
|
|
1169
|
+
await _commandFinally(state, this);
|
|
1073
1170
|
}
|
|
1074
1171
|
}
|
|
1075
1172
|
async selectOption(selectors, values, _params = null, options = {}, world = null) {
|
|
@@ -1105,7 +1202,7 @@ class StableBrowser {
|
|
|
1105
1202
|
await _commandError(state, e, this);
|
|
1106
1203
|
}
|
|
1107
1204
|
finally {
|
|
1108
|
-
_commandFinally(state, this);
|
|
1205
|
+
await _commandFinally(state, this);
|
|
1109
1206
|
}
|
|
1110
1207
|
}
|
|
1111
1208
|
async type(_value, _params = null, options = {}, world = null) {
|
|
@@ -1151,7 +1248,7 @@ class StableBrowser {
|
|
|
1151
1248
|
await _commandError(state, e, this);
|
|
1152
1249
|
}
|
|
1153
1250
|
finally {
|
|
1154
|
-
_commandFinally(state, this);
|
|
1251
|
+
await _commandFinally(state, this);
|
|
1155
1252
|
}
|
|
1156
1253
|
}
|
|
1157
1254
|
async setInputValue(selectors, value, _params = null, options = {}, world = null) {
|
|
@@ -1187,7 +1284,7 @@ class StableBrowser {
|
|
|
1187
1284
|
await _commandError(state, e, this);
|
|
1188
1285
|
}
|
|
1189
1286
|
finally {
|
|
1190
|
-
_commandFinally(state, this);
|
|
1287
|
+
await _commandFinally(state, this);
|
|
1191
1288
|
}
|
|
1192
1289
|
}
|
|
1193
1290
|
async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
|
|
@@ -1256,7 +1353,7 @@ class StableBrowser {
|
|
|
1256
1353
|
await _commandError(state, e, this);
|
|
1257
1354
|
}
|
|
1258
1355
|
finally {
|
|
1259
|
-
_commandFinally(state, this);
|
|
1356
|
+
await _commandFinally(state, this);
|
|
1260
1357
|
}
|
|
1261
1358
|
}
|
|
1262
1359
|
async clickType(selectors, _value, enter = false, _params = null, options = {}, world = null) {
|
|
@@ -1275,6 +1372,9 @@ class StableBrowser {
|
|
|
1275
1372
|
operation: "clickType",
|
|
1276
1373
|
log: "***** clickType on " + selectors.element_name + " with value " + maskValue(_value) + "*****\n",
|
|
1277
1374
|
};
|
|
1375
|
+
if (!options) {
|
|
1376
|
+
options = {};
|
|
1377
|
+
}
|
|
1278
1378
|
if (newValue !== _value) {
|
|
1279
1379
|
//this.logger.info(_value + "=" + newValue);
|
|
1280
1380
|
_value = newValue;
|
|
@@ -1282,7 +1382,7 @@ class StableBrowser {
|
|
|
1282
1382
|
try {
|
|
1283
1383
|
await _preCommand(state, this);
|
|
1284
1384
|
state.info.value = _value;
|
|
1285
|
-
if (
|
|
1385
|
+
if (!options.press) {
|
|
1286
1386
|
try {
|
|
1287
1387
|
let currentValue = await state.element.inputValue();
|
|
1288
1388
|
if (currentValue) {
|
|
@@ -1293,10 +1393,7 @@ class StableBrowser {
|
|
|
1293
1393
|
this.logger.info("unable to clear input value");
|
|
1294
1394
|
}
|
|
1295
1395
|
}
|
|
1296
|
-
if (options
|
|
1297
|
-
if (!options) {
|
|
1298
|
-
options = {};
|
|
1299
|
-
}
|
|
1396
|
+
if (options.press) {
|
|
1300
1397
|
options.timeout = 5000;
|
|
1301
1398
|
await performAction("click", state.element, options, this, state, _params);
|
|
1302
1399
|
}
|
|
@@ -1356,7 +1453,7 @@ class StableBrowser {
|
|
|
1356
1453
|
await _commandError(state, e, this);
|
|
1357
1454
|
}
|
|
1358
1455
|
finally {
|
|
1359
|
-
_commandFinally(state, this);
|
|
1456
|
+
await _commandFinally(state, this);
|
|
1360
1457
|
}
|
|
1361
1458
|
}
|
|
1362
1459
|
async fill(selectors, value, enter = false, _params = null, options = {}, world = null) {
|
|
@@ -1386,7 +1483,42 @@ class StableBrowser {
|
|
|
1386
1483
|
await _commandError(state, e, this);
|
|
1387
1484
|
}
|
|
1388
1485
|
finally {
|
|
1389
|
-
_commandFinally(state, this);
|
|
1486
|
+
await _commandFinally(state, this);
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
async setInputFiles(selectors, files, _params = null, options = {}, world = null) {
|
|
1490
|
+
const state = {
|
|
1491
|
+
selectors,
|
|
1492
|
+
_params,
|
|
1493
|
+
files,
|
|
1494
|
+
value: '"' + files.join('", "') + '"',
|
|
1495
|
+
options,
|
|
1496
|
+
world,
|
|
1497
|
+
type: Types.SET_INPUT_FILES,
|
|
1498
|
+
text: `Set input files`,
|
|
1499
|
+
_text: `Set input files on ${selectors.element_name}`,
|
|
1500
|
+
operation: "setInputFiles",
|
|
1501
|
+
log: "***** set input files " + selectors.element_name + " *****\n",
|
|
1502
|
+
};
|
|
1503
|
+
const uploadsFolder = this.configuration.uploadsFolder ?? "data/uploads";
|
|
1504
|
+
try {
|
|
1505
|
+
await _preCommand(state, this);
|
|
1506
|
+
for (let i = 0; i < files.length; i++) {
|
|
1507
|
+
const file = files[i];
|
|
1508
|
+
const filePath = path.join(uploadsFolder, file);
|
|
1509
|
+
if (!fs.existsSync(filePath)) {
|
|
1510
|
+
throw new Error(`File not found: ${filePath}`);
|
|
1511
|
+
}
|
|
1512
|
+
state.files[i] = filePath;
|
|
1513
|
+
}
|
|
1514
|
+
await state.element.setInputFiles(files);
|
|
1515
|
+
return state.info;
|
|
1516
|
+
}
|
|
1517
|
+
catch (e) {
|
|
1518
|
+
await _commandError(state, e, this);
|
|
1519
|
+
}
|
|
1520
|
+
finally {
|
|
1521
|
+
await _commandFinally(state, this);
|
|
1390
1522
|
}
|
|
1391
1523
|
}
|
|
1392
1524
|
async getText(selectors, _params = null, options = {}, info = {}, world = null) {
|
|
@@ -1502,7 +1634,7 @@ class StableBrowser {
|
|
|
1502
1634
|
await _commandError(state, e, this);
|
|
1503
1635
|
}
|
|
1504
1636
|
finally {
|
|
1505
|
-
_commandFinally(state, this);
|
|
1637
|
+
await _commandFinally(state, this);
|
|
1506
1638
|
}
|
|
1507
1639
|
}
|
|
1508
1640
|
async containsText(selectors, text, climb, _params = null, options = {}, world = null) {
|
|
@@ -1537,7 +1669,7 @@ class StableBrowser {
|
|
|
1537
1669
|
while (Date.now() - startTime < timeout) {
|
|
1538
1670
|
try {
|
|
1539
1671
|
await _preCommand(state, this);
|
|
1540
|
-
foundObj = await this._getText(selectors, climb, _params, { timeout:
|
|
1672
|
+
foundObj = await this._getText(selectors, climb, _params, { timeout: 3000 }, state.info, world);
|
|
1541
1673
|
if (foundObj && foundObj.element) {
|
|
1542
1674
|
await this.scrollIfNeeded(foundObj.element, state.info);
|
|
1543
1675
|
}
|
|
@@ -1579,7 +1711,79 @@ class StableBrowser {
|
|
|
1579
1711
|
throw e;
|
|
1580
1712
|
}
|
|
1581
1713
|
finally {
|
|
1582
|
-
_commandFinally(state, this);
|
|
1714
|
+
await _commandFinally(state, this);
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
async snapshotValidation(frameSelectors, referanceSnapshot, _params = null, options = {}, world = null) {
|
|
1718
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1719
|
+
const startTime = Date.now();
|
|
1720
|
+
const state = {
|
|
1721
|
+
_params,
|
|
1722
|
+
value: referanceSnapshot,
|
|
1723
|
+
options,
|
|
1724
|
+
world,
|
|
1725
|
+
locate: false,
|
|
1726
|
+
scroll: false,
|
|
1727
|
+
screenshot: true,
|
|
1728
|
+
highlight: false,
|
|
1729
|
+
type: Types.SNAPSHOT_VALIDATION,
|
|
1730
|
+
text: `verify snapshot: ${referanceSnapshot}`,
|
|
1731
|
+
operation: "snapshotValidation",
|
|
1732
|
+
log: "***** verify snapshot *****\n",
|
|
1733
|
+
};
|
|
1734
|
+
if (!referanceSnapshot) {
|
|
1735
|
+
throw new Error("referanceSnapshot is null");
|
|
1736
|
+
}
|
|
1737
|
+
let text = null;
|
|
1738
|
+
if (fs.existsSync(path.join(this.project_path, "data", "snapshots", this.context.environment.name, referanceSnapshot + ".yml"))) {
|
|
1739
|
+
text = fs.readFileSync(path.join(this.project_path, "data", "snapshots", this.context.environment.name, referanceSnapshot + ".yml"), "utf8");
|
|
1740
|
+
}
|
|
1741
|
+
else if (fs.existsSync(path.join(this.project_path, "data", "snapshots", this.context.environment.name, referanceSnapshot + ".yaml"))) {
|
|
1742
|
+
text = fs.readFileSync(path.join(this.project_path, "data", "snapshots", this.context.environment.name, referanceSnapshot + ".yaml"), "utf8");
|
|
1743
|
+
}
|
|
1744
|
+
else if (referanceSnapshot.startsWith("yaml:")) {
|
|
1745
|
+
text = referanceSnapshot.substring(5);
|
|
1746
|
+
}
|
|
1747
|
+
else {
|
|
1748
|
+
throw new Error("referenceSnapshot file not found: " + referanceSnapshot);
|
|
1749
|
+
}
|
|
1750
|
+
state.text = text;
|
|
1751
|
+
const newValue = await this._replaceWithLocalData(text, world);
|
|
1752
|
+
await _preCommand(state, this);
|
|
1753
|
+
let foundObj = null;
|
|
1754
|
+
try {
|
|
1755
|
+
let matchResult = null;
|
|
1756
|
+
while (Date.now() - startTime < timeout) {
|
|
1757
|
+
try {
|
|
1758
|
+
let scope = null;
|
|
1759
|
+
if (!frameSelectors) {
|
|
1760
|
+
scope = this.page;
|
|
1761
|
+
}
|
|
1762
|
+
else {
|
|
1763
|
+
scope = await this._findFrameScope(frameSelectors, timeout, state.info);
|
|
1764
|
+
}
|
|
1765
|
+
const snapshot = await scope.locator("body").ariaSnapshot({ timeout });
|
|
1766
|
+
matchResult = snapshotValidation(snapshot, newValue, referanceSnapshot);
|
|
1767
|
+
if (matchResult.errorLine !== -1) {
|
|
1768
|
+
throw new Error("Snapshot validation failed at line " + matchResult.errorLineText);
|
|
1769
|
+
}
|
|
1770
|
+
// highlight and screenshot
|
|
1771
|
+
return state.info;
|
|
1772
|
+
}
|
|
1773
|
+
catch (e) {
|
|
1774
|
+
// Log error but continue retrying until timeout is reached
|
|
1775
|
+
//this.logger.warn("Retrying snapshot validation due to: " + e.message);
|
|
1776
|
+
}
|
|
1777
|
+
await new Promise((resolve) => setTimeout(resolve, 2000)); // Wait 1 second before retrying
|
|
1778
|
+
}
|
|
1779
|
+
throw new Error("No snapshot match " + matchResult?.errorLineText);
|
|
1780
|
+
}
|
|
1781
|
+
catch (e) {
|
|
1782
|
+
await _commandError(state, e, this);
|
|
1783
|
+
throw e;
|
|
1784
|
+
}
|
|
1785
|
+
finally {
|
|
1786
|
+
await _commandFinally(state, this);
|
|
1583
1787
|
}
|
|
1584
1788
|
}
|
|
1585
1789
|
async waitForUserInput(message, world = null) {
|
|
@@ -1617,6 +1821,15 @@ class StableBrowser {
|
|
|
1617
1821
|
// save the data to the file
|
|
1618
1822
|
fs.writeFileSync(dataFile, JSON.stringify(data, null, 2));
|
|
1619
1823
|
}
|
|
1824
|
+
overwriteTestData(testData, world = null) {
|
|
1825
|
+
if (!testData) {
|
|
1826
|
+
return;
|
|
1827
|
+
}
|
|
1828
|
+
// if data file exists, load it
|
|
1829
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1830
|
+
// save the data to the file
|
|
1831
|
+
fs.writeFileSync(dataFile, JSON.stringify(testData, null, 2));
|
|
1832
|
+
}
|
|
1620
1833
|
_getDataFilePath(fileName) {
|
|
1621
1834
|
let dataFile = path.join(this.project_path, "data", fileName);
|
|
1622
1835
|
if (fs.existsSync(dataFile)) {
|
|
@@ -1869,7 +2082,7 @@ class StableBrowser {
|
|
|
1869
2082
|
await _commandError(state, e, this);
|
|
1870
2083
|
}
|
|
1871
2084
|
finally {
|
|
1872
|
-
_commandFinally(state, this);
|
|
2085
|
+
await _commandFinally(state, this);
|
|
1873
2086
|
}
|
|
1874
2087
|
}
|
|
1875
2088
|
async extractAttribute(selectors, attribute, variable, _params = null, options = {}, world = null) {
|
|
@@ -1900,10 +2113,31 @@ class StableBrowser {
|
|
|
1900
2113
|
case "value":
|
|
1901
2114
|
state.value = await state.element.inputValue();
|
|
1902
2115
|
break;
|
|
2116
|
+
case "text":
|
|
2117
|
+
state.value = await state.element.textContent();
|
|
2118
|
+
break;
|
|
1903
2119
|
default:
|
|
1904
2120
|
state.value = await state.element.getAttribute(attribute);
|
|
1905
2121
|
break;
|
|
1906
2122
|
}
|
|
2123
|
+
if (options !== null) {
|
|
2124
|
+
if (options.regex && options.regex !== "") {
|
|
2125
|
+
// Construct a regex pattern from the provided string
|
|
2126
|
+
const regex = options.regex.slice(1, -1);
|
|
2127
|
+
const regexPattern = new RegExp(regex, "g");
|
|
2128
|
+
const matches = state.value.match(regexPattern);
|
|
2129
|
+
if (matches) {
|
|
2130
|
+
let newValue = "";
|
|
2131
|
+
for (const match of matches) {
|
|
2132
|
+
newValue += match;
|
|
2133
|
+
}
|
|
2134
|
+
state.value = newValue;
|
|
2135
|
+
}
|
|
2136
|
+
}
|
|
2137
|
+
if (options.trimSpaces && options.trimSpaces === true) {
|
|
2138
|
+
state.value = state.value.trim();
|
|
2139
|
+
}
|
|
2140
|
+
}
|
|
1907
2141
|
state.info.value = state.value;
|
|
1908
2142
|
this.setTestData({ [variable]: state.value }, world);
|
|
1909
2143
|
this.logger.info("set test data: " + variable + "=" + state.value);
|
|
@@ -1914,7 +2148,7 @@ class StableBrowser {
|
|
|
1914
2148
|
await _commandError(state, e, this);
|
|
1915
2149
|
}
|
|
1916
2150
|
finally {
|
|
1917
|
-
_commandFinally(state, this);
|
|
2151
|
+
await _commandFinally(state, this);
|
|
1918
2152
|
}
|
|
1919
2153
|
}
|
|
1920
2154
|
async verifyAttribute(selectors, attribute, value, _params = null, options = {}, world = null) {
|
|
@@ -1939,12 +2173,15 @@ class StableBrowser {
|
|
|
1939
2173
|
let expectedValue;
|
|
1940
2174
|
try {
|
|
1941
2175
|
await _preCommand(state, this);
|
|
1942
|
-
expectedValue = state.value;
|
|
2176
|
+
expectedValue = await replaceWithLocalTestData(state.value, world);
|
|
1943
2177
|
state.info.expectedValue = expectedValue;
|
|
1944
2178
|
switch (attribute) {
|
|
1945
2179
|
case "innerText":
|
|
1946
2180
|
val = String(await state.element.innerText());
|
|
1947
2181
|
break;
|
|
2182
|
+
case "text":
|
|
2183
|
+
val = String(await state.element.textContent());
|
|
2184
|
+
break;
|
|
1948
2185
|
case "value":
|
|
1949
2186
|
val = String(await state.element.inputValue());
|
|
1950
2187
|
break;
|
|
@@ -1966,17 +2203,42 @@ class StableBrowser {
|
|
|
1966
2203
|
let regex;
|
|
1967
2204
|
if (expectedValue.startsWith("/") && expectedValue.endsWith("/")) {
|
|
1968
2205
|
const patternBody = expectedValue.slice(1, -1);
|
|
1969
|
-
|
|
2206
|
+
const processedPattern = patternBody.replace(/\n/g, ".*");
|
|
2207
|
+
regex = new RegExp(processedPattern, "gs");
|
|
2208
|
+
state.info.regex = true;
|
|
1970
2209
|
}
|
|
1971
2210
|
else {
|
|
1972
2211
|
const escapedPattern = expectedValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1973
2212
|
regex = new RegExp(escapedPattern, "g");
|
|
1974
2213
|
}
|
|
1975
|
-
if (
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
2214
|
+
if (attribute === "innerText") {
|
|
2215
|
+
if (state.info.regex) {
|
|
2216
|
+
if (!regex.test(val)) {
|
|
2217
|
+
let errorMessage = `The ${attribute} attribute has a value of "${val}", but the expected value is "${expectedValue}"`;
|
|
2218
|
+
state.info.failCause.assertionFailed = true;
|
|
2219
|
+
state.info.failCause.lastError = errorMessage;
|
|
2220
|
+
throw new Error(errorMessage);
|
|
2221
|
+
}
|
|
2222
|
+
}
|
|
2223
|
+
else {
|
|
2224
|
+
const valLines = val.split("\n");
|
|
2225
|
+
const expectedLines = expectedValue.split("\n");
|
|
2226
|
+
const isPart = expectedLines.every((expectedLine) => valLines.some((valLine) => valLine === expectedLine));
|
|
2227
|
+
if (!isPart) {
|
|
2228
|
+
let errorMessage = `The ${attribute} attribute has a value of "${val}", but the expected value is "${expectedValue}"`;
|
|
2229
|
+
state.info.failCause.assertionFailed = true;
|
|
2230
|
+
state.info.failCause.lastError = errorMessage;
|
|
2231
|
+
throw new Error(errorMessage);
|
|
2232
|
+
}
|
|
2233
|
+
}
|
|
2234
|
+
}
|
|
2235
|
+
else {
|
|
2236
|
+
if (!val.match(regex)) {
|
|
2237
|
+
let errorMessage = `The ${attribute} attribute has a value of "${val}", but the expected value is "${expectedValue}"`;
|
|
2238
|
+
state.info.failCause.assertionFailed = true;
|
|
2239
|
+
state.info.failCause.lastError = errorMessage;
|
|
2240
|
+
throw new Error(errorMessage);
|
|
2241
|
+
}
|
|
1980
2242
|
}
|
|
1981
2243
|
return state.info;
|
|
1982
2244
|
}
|
|
@@ -1984,7 +2246,7 @@ class StableBrowser {
|
|
|
1984
2246
|
await _commandError(state, e, this);
|
|
1985
2247
|
}
|
|
1986
2248
|
finally {
|
|
1987
|
-
_commandFinally(state, this);
|
|
2249
|
+
await _commandFinally(state, this);
|
|
1988
2250
|
}
|
|
1989
2251
|
}
|
|
1990
2252
|
async extractEmailData(emailAddress, options, world) {
|
|
@@ -2144,56 +2406,49 @@ class StableBrowser {
|
|
|
2144
2406
|
console.debug(error);
|
|
2145
2407
|
}
|
|
2146
2408
|
}
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
// });
|
|
2190
|
-
// }
|
|
2191
|
-
// } catch (error) {
|
|
2192
|
-
// // console.debug(error);
|
|
2193
|
-
// }
|
|
2194
|
-
// }
|
|
2409
|
+
_matcher(text) {
|
|
2410
|
+
if (!text) {
|
|
2411
|
+
return { matcher: "contains", queryText: "" };
|
|
2412
|
+
}
|
|
2413
|
+
if (text.length < 2) {
|
|
2414
|
+
return { matcher: "contains", queryText: text };
|
|
2415
|
+
}
|
|
2416
|
+
const split = text.split(":");
|
|
2417
|
+
const matcher = split[0].toLowerCase();
|
|
2418
|
+
const queryText = split.slice(1).join(":").trim();
|
|
2419
|
+
return { matcher, queryText };
|
|
2420
|
+
}
|
|
2421
|
+
_getDomain(url) {
|
|
2422
|
+
if (url.length === 0 || (!url.startsWith("http://") && !url.startsWith("https://"))) {
|
|
2423
|
+
return "";
|
|
2424
|
+
}
|
|
2425
|
+
let hostnameFragments = url.split("/")[2].split(".");
|
|
2426
|
+
if (hostnameFragments.some((fragment) => fragment.includes(":"))) {
|
|
2427
|
+
return hostnameFragments.join("-").split(":").join("-");
|
|
2428
|
+
}
|
|
2429
|
+
let n = hostnameFragments.length;
|
|
2430
|
+
let fragments = [...hostnameFragments];
|
|
2431
|
+
while (n > 0 && hostnameFragments[n - 1].length <= 3) {
|
|
2432
|
+
hostnameFragments.pop();
|
|
2433
|
+
n = hostnameFragments.length;
|
|
2434
|
+
}
|
|
2435
|
+
if (n == 0) {
|
|
2436
|
+
if (fragments[0] === "www")
|
|
2437
|
+
fragments = fragments.slice(1);
|
|
2438
|
+
return fragments.length > 1 ? fragments.slice(0, fragments.length - 1).join("-") : fragments.join("-");
|
|
2439
|
+
}
|
|
2440
|
+
if (hostnameFragments[0] === "www")
|
|
2441
|
+
hostnameFragments = hostnameFragments.slice(1);
|
|
2442
|
+
return hostnameFragments.join(".");
|
|
2443
|
+
}
|
|
2444
|
+
/**
|
|
2445
|
+
* Verify the page path matches the given path.
|
|
2446
|
+
* @param {string} pathPart - The path to verify.
|
|
2447
|
+
* @param {object} options - Options for verification.
|
|
2448
|
+
* @param {object} world - The world context.
|
|
2449
|
+
* @returns {Promise<object>} - The state info after verification.
|
|
2450
|
+
*/
|
|
2195
2451
|
async verifyPagePath(pathPart, options = {}, world = null) {
|
|
2196
|
-
const startTime = Date.now();
|
|
2197
2452
|
let error = null;
|
|
2198
2453
|
let screenshotId = null;
|
|
2199
2454
|
let screenshotPath = null;
|
|
@@ -2207,74 +2462,235 @@ class StableBrowser {
|
|
|
2207
2462
|
pathPart = newValue;
|
|
2208
2463
|
}
|
|
2209
2464
|
info.pathPart = pathPart;
|
|
2465
|
+
const { matcher, queryText } = this._matcher(pathPart);
|
|
2466
|
+
const state = {
|
|
2467
|
+
text_search: queryText,
|
|
2468
|
+
options,
|
|
2469
|
+
world,
|
|
2470
|
+
locate: false,
|
|
2471
|
+
scroll: false,
|
|
2472
|
+
highlight: false,
|
|
2473
|
+
type: Types.VERIFY_PAGE_PATH,
|
|
2474
|
+
text: `Verify the page url is ${queryText}`,
|
|
2475
|
+
_text: `Verify the page url is ${queryText}`,
|
|
2476
|
+
operation: "verifyPagePath",
|
|
2477
|
+
log: "***** verify page url is " + queryText + " *****\n",
|
|
2478
|
+
};
|
|
2210
2479
|
try {
|
|
2480
|
+
await _preCommand(state, this);
|
|
2481
|
+
state.info.text = queryText;
|
|
2211
2482
|
for (let i = 0; i < 30; i++) {
|
|
2212
2483
|
const url = await this.page.url();
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2484
|
+
switch (matcher) {
|
|
2485
|
+
case "exact":
|
|
2486
|
+
if (url !== queryText) {
|
|
2487
|
+
if (i === 29) {
|
|
2488
|
+
throw new Error(`Page URL ${url} is not equal to ${queryText}`);
|
|
2489
|
+
}
|
|
2490
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2491
|
+
continue;
|
|
2492
|
+
}
|
|
2493
|
+
break;
|
|
2494
|
+
case "contains":
|
|
2495
|
+
if (!url.includes(queryText)) {
|
|
2496
|
+
if (i === 29) {
|
|
2497
|
+
throw new Error(`Page URL ${url} doesn't contain ${queryText}`);
|
|
2498
|
+
}
|
|
2499
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2500
|
+
continue;
|
|
2501
|
+
}
|
|
2502
|
+
break;
|
|
2503
|
+
case "starts-with":
|
|
2504
|
+
{
|
|
2505
|
+
const domain = this._getDomain(url);
|
|
2506
|
+
if (domain.length > 0 && domain !== queryText) {
|
|
2507
|
+
if (i === 29) {
|
|
2508
|
+
throw new Error(`Page URL ${url} doesn't start with ${queryText}`);
|
|
2509
|
+
}
|
|
2510
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2511
|
+
continue;
|
|
2512
|
+
}
|
|
2513
|
+
}
|
|
2514
|
+
break;
|
|
2515
|
+
case "ends-with":
|
|
2516
|
+
{
|
|
2517
|
+
const urlObj = new URL(url);
|
|
2518
|
+
let route = "/";
|
|
2519
|
+
if (urlObj.pathname !== "/") {
|
|
2520
|
+
route = urlObj.pathname.split("/").slice(-1)[0].trim();
|
|
2521
|
+
}
|
|
2522
|
+
else {
|
|
2523
|
+
route = "/";
|
|
2524
|
+
}
|
|
2525
|
+
if (route !== queryText) {
|
|
2526
|
+
if (i === 29) {
|
|
2527
|
+
throw new Error(`Page URL ${url} doesn't end with ${queryText}`);
|
|
2528
|
+
}
|
|
2529
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2530
|
+
continue;
|
|
2531
|
+
}
|
|
2532
|
+
}
|
|
2533
|
+
break;
|
|
2534
|
+
case "regex":
|
|
2535
|
+
const regex = new RegExp(queryText.slice(1, -1), "g");
|
|
2536
|
+
if (!regex.test(url)) {
|
|
2537
|
+
if (i === 29) {
|
|
2538
|
+
throw new Error(`Page URL ${url} doesn't match regex ${queryText}`);
|
|
2539
|
+
}
|
|
2540
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2541
|
+
continue;
|
|
2542
|
+
}
|
|
2543
|
+
break;
|
|
2544
|
+
default:
|
|
2545
|
+
console.log("Unknown matching type, defaulting to contains matching");
|
|
2546
|
+
if (!url.includes(pathPart)) {
|
|
2547
|
+
if (i === 29) {
|
|
2548
|
+
throw new Error(`Page URL ${url} does not contain ${pathPart}`);
|
|
2549
|
+
}
|
|
2550
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2551
|
+
continue;
|
|
2552
|
+
}
|
|
2219
2553
|
}
|
|
2220
|
-
|
|
2221
|
-
return info;
|
|
2554
|
+
await _screenshot(state, this);
|
|
2555
|
+
return state.info;
|
|
2222
2556
|
}
|
|
2223
2557
|
}
|
|
2224
2558
|
catch (e) {
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
info.screenshotPath = screenshotPath;
|
|
2229
|
-
Object.assign(e, { info: info });
|
|
2230
|
-
error = e;
|
|
2231
|
-
// throw e;
|
|
2232
|
-
await _commandError({ text: "verifyPagePath", operation: "verifyPagePath", pathPart, info }, e, this);
|
|
2559
|
+
state.info.failCause.lastError = e.message;
|
|
2560
|
+
state.info.failCause.assertionFailed = true;
|
|
2561
|
+
await _commandError(state, e, this);
|
|
2233
2562
|
}
|
|
2234
2563
|
finally {
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2564
|
+
await _commandFinally(state, this);
|
|
2565
|
+
}
|
|
2566
|
+
}
|
|
2567
|
+
/**
|
|
2568
|
+
* Verify the page title matches the given title.
|
|
2569
|
+
* @param {string} title - The title to verify.
|
|
2570
|
+
* @param {object} options - Options for verification.
|
|
2571
|
+
* @param {object} world - The world context.
|
|
2572
|
+
* @returns {Promise<object>} - The state info after verification.
|
|
2573
|
+
*/
|
|
2574
|
+
async verifyPageTitle(title, options = {}, world = null) {
|
|
2575
|
+
let error = null;
|
|
2576
|
+
let screenshotId = null;
|
|
2577
|
+
let screenshotPath = null;
|
|
2578
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2579
|
+
const newValue = await this._replaceWithLocalData(title, world);
|
|
2580
|
+
if (newValue !== title) {
|
|
2581
|
+
this.logger.info(title + "=" + newValue);
|
|
2582
|
+
title = newValue;
|
|
2583
|
+
}
|
|
2584
|
+
const { matcher, queryText } = this._matcher(title);
|
|
2585
|
+
const state = {
|
|
2586
|
+
text_search: queryText,
|
|
2587
|
+
options,
|
|
2588
|
+
world,
|
|
2589
|
+
locate: false,
|
|
2590
|
+
scroll: false,
|
|
2591
|
+
highlight: false,
|
|
2592
|
+
type: Types.VERIFY_PAGE_TITLE,
|
|
2593
|
+
text: `Verify the page title is ${queryText}`,
|
|
2594
|
+
_text: `Verify the page title is ${queryText}`,
|
|
2595
|
+
operation: "verifyPageTitle",
|
|
2596
|
+
log: "***** verify page title is " + queryText + " *****\n",
|
|
2597
|
+
};
|
|
2598
|
+
try {
|
|
2599
|
+
await _preCommand(state, this);
|
|
2600
|
+
state.info.text = queryText;
|
|
2601
|
+
for (let i = 0; i < 30; i++) {
|
|
2602
|
+
const foundTitle = await this.page.title();
|
|
2603
|
+
switch (matcher) {
|
|
2604
|
+
case "exact":
|
|
2605
|
+
if (foundTitle !== queryText) {
|
|
2606
|
+
if (i === 29) {
|
|
2607
|
+
throw new Error(`Page Title ${foundTitle} is not equal to ${queryText}`);
|
|
2608
|
+
}
|
|
2609
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2610
|
+
continue;
|
|
2611
|
+
}
|
|
2612
|
+
break;
|
|
2613
|
+
case "contains":
|
|
2614
|
+
if (!foundTitle.includes(queryText)) {
|
|
2615
|
+
if (i === 29) {
|
|
2616
|
+
throw new Error(`Page Title ${foundTitle} doesn't contain ${queryText}`);
|
|
2617
|
+
}
|
|
2618
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2619
|
+
continue;
|
|
2620
|
+
}
|
|
2621
|
+
break;
|
|
2622
|
+
case "starts-with":
|
|
2623
|
+
if (!foundTitle.startsWith(queryText)) {
|
|
2624
|
+
if (i === 29) {
|
|
2625
|
+
throw new Error(`Page title ${foundTitle} doesn't start with ${queryText}`);
|
|
2626
|
+
}
|
|
2627
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2628
|
+
continue;
|
|
2629
|
+
}
|
|
2630
|
+
break;
|
|
2631
|
+
case "ends-with":
|
|
2632
|
+
if (!foundTitle.endsWith(queryText)) {
|
|
2633
|
+
if (i === 29) {
|
|
2634
|
+
throw new Error(`Page Title ${foundTitle} doesn't end with ${queryText}`);
|
|
2635
|
+
}
|
|
2636
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2637
|
+
continue;
|
|
2638
|
+
}
|
|
2639
|
+
break;
|
|
2640
|
+
case "regex":
|
|
2641
|
+
const regex = new RegExp(queryText.slice(1, -1), "g");
|
|
2642
|
+
if (!regex.test(foundTitle)) {
|
|
2643
|
+
if (i === 29) {
|
|
2644
|
+
throw new Error(`Page Title ${foundTitle} doesn't match regex ${queryText}`);
|
|
2645
|
+
}
|
|
2646
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2647
|
+
continue;
|
|
2648
|
+
}
|
|
2649
|
+
break;
|
|
2650
|
+
default:
|
|
2651
|
+
console.log("Unknown matching type, defaulting to contains matching");
|
|
2652
|
+
if (!foundTitle.includes(title)) {
|
|
2653
|
+
if (i === 29) {
|
|
2654
|
+
throw new Error(`Page Title ${foundTitle} does not contain ${title}`);
|
|
2655
|
+
}
|
|
2656
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2657
|
+
continue;
|
|
2658
|
+
}
|
|
2659
|
+
}
|
|
2660
|
+
await _screenshot(state, this);
|
|
2661
|
+
return state.info;
|
|
2662
|
+
}
|
|
2663
|
+
}
|
|
2664
|
+
catch (e) {
|
|
2665
|
+
state.info.failCause.lastError = e.message;
|
|
2666
|
+
state.info.failCause.assertionFailed = true;
|
|
2667
|
+
await _commandError(state, e, this);
|
|
2668
|
+
}
|
|
2669
|
+
finally {
|
|
2670
|
+
await _commandFinally(state, this);
|
|
2255
2671
|
}
|
|
2256
2672
|
}
|
|
2257
|
-
async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state) {
|
|
2673
|
+
async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state, partial = true, ignoreCase = false) {
|
|
2258
2674
|
const frames = this.page.frames();
|
|
2259
2675
|
let results = [];
|
|
2260
|
-
let ignoreCase = false;
|
|
2676
|
+
// let ignoreCase = false;
|
|
2261
2677
|
for (let i = 0; i < frames.length; i++) {
|
|
2262
2678
|
if (dateAlternatives.date) {
|
|
2263
2679
|
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2264
|
-
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false,
|
|
2680
|
+
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, partial, ignoreCase, {});
|
|
2265
2681
|
result.frame = frames[i];
|
|
2266
2682
|
results.push(result);
|
|
2267
2683
|
}
|
|
2268
2684
|
}
|
|
2269
2685
|
else if (numberAlternatives.number) {
|
|
2270
2686
|
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2271
|
-
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false,
|
|
2687
|
+
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, partial, ignoreCase, {});
|
|
2272
2688
|
result.frame = frames[i];
|
|
2273
2689
|
results.push(result);
|
|
2274
2690
|
}
|
|
2275
2691
|
}
|
|
2276
2692
|
else {
|
|
2277
|
-
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false,
|
|
2693
|
+
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, partial, ignoreCase, {});
|
|
2278
2694
|
result.frame = frames[i];
|
|
2279
2695
|
results.push(result);
|
|
2280
2696
|
}
|
|
@@ -2293,7 +2709,7 @@ class StableBrowser {
|
|
|
2293
2709
|
scroll: false,
|
|
2294
2710
|
highlight: false,
|
|
2295
2711
|
type: Types.VERIFY_PAGE_CONTAINS_TEXT,
|
|
2296
|
-
text: `Verify text exists in page`,
|
|
2712
|
+
text: `Verify the text '${maskValue(text)}' exists in page`,
|
|
2297
2713
|
_text: `Verify the text '${text}' exists in page`,
|
|
2298
2714
|
operation: "verifyTextExistInPage",
|
|
2299
2715
|
log: "***** verify text " + text + " exists in page *****\n",
|
|
@@ -2335,27 +2751,10 @@ class StableBrowser {
|
|
|
2335
2751
|
const frame = resultWithElementsFound[0].frame;
|
|
2336
2752
|
const dataAttribute = `[data-blinq-id-${resultWithElementsFound[0].randomToken}]`;
|
|
2337
2753
|
await this._highlightElements(frame, dataAttribute);
|
|
2338
|
-
// if (world && world.screenshot && !world.screenshotPath) {
|
|
2339
|
-
// console.log(`Highlighting for verify text is found while running from recorder`);
|
|
2340
|
-
// this._highlightElements(frame, dataAttribute).then(async () => {
|
|
2341
|
-
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2342
|
-
// this._unhighlightElements(frame, dataAttribute)
|
|
2343
|
-
// .then(async () => {
|
|
2344
|
-
// console.log(`Unhighlighted frame dataAttribute successfully`);
|
|
2345
|
-
// })
|
|
2346
|
-
// .catch(
|
|
2347
|
-
// (e) => {}
|
|
2348
|
-
// console.error(e)
|
|
2349
|
-
// );
|
|
2350
|
-
// });
|
|
2351
|
-
// }
|
|
2352
2754
|
const element = await frame.locator(dataAttribute).first();
|
|
2353
|
-
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2354
|
-
// await this._unhighlightElements(frame, dataAttribute);
|
|
2355
2755
|
if (element) {
|
|
2356
2756
|
await this.scrollIfNeeded(element, state.info);
|
|
2357
2757
|
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2358
|
-
// await _screenshot(state, this, element);
|
|
2359
2758
|
}
|
|
2360
2759
|
}
|
|
2361
2760
|
await _screenshot(state, this);
|
|
@@ -2365,13 +2764,12 @@ class StableBrowser {
|
|
|
2365
2764
|
console.error(error);
|
|
2366
2765
|
}
|
|
2367
2766
|
}
|
|
2368
|
-
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2369
2767
|
}
|
|
2370
2768
|
catch (e) {
|
|
2371
2769
|
await _commandError(state, e, this);
|
|
2372
2770
|
}
|
|
2373
2771
|
finally {
|
|
2374
|
-
_commandFinally(state, this);
|
|
2772
|
+
await _commandFinally(state, this);
|
|
2375
2773
|
}
|
|
2376
2774
|
}
|
|
2377
2775
|
async waitForTextToDisappear(text, options = {}, world = null) {
|
|
@@ -2384,7 +2782,7 @@ class StableBrowser {
|
|
|
2384
2782
|
scroll: false,
|
|
2385
2783
|
highlight: false,
|
|
2386
2784
|
type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
|
|
2387
|
-
text: `Verify text does not exist in page`,
|
|
2785
|
+
text: `Verify the text '${maskValue(text)}' does not exist in page`,
|
|
2388
2786
|
_text: `Verify the text '${text}' does not exist in page`,
|
|
2389
2787
|
operation: "verifyTextNotExistInPage",
|
|
2390
2788
|
log: "***** verify text " + text + " does not exist in page *****\n",
|
|
@@ -2428,7 +2826,7 @@ class StableBrowser {
|
|
|
2428
2826
|
await _commandError(state, e, this);
|
|
2429
2827
|
}
|
|
2430
2828
|
finally {
|
|
2431
|
-
_commandFinally(state, this);
|
|
2829
|
+
await _commandFinally(state, this);
|
|
2432
2830
|
}
|
|
2433
2831
|
}
|
|
2434
2832
|
async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
|
|
@@ -2470,7 +2868,7 @@ class StableBrowser {
|
|
|
2470
2868
|
};
|
|
2471
2869
|
while (true) {
|
|
2472
2870
|
try {
|
|
2473
|
-
resultWithElementsFound = await this.findTextInAllFrames(
|
|
2871
|
+
resultWithElementsFound = await this.findTextInAllFrames(findDateAlternatives(textAnchor), findNumberAlternatives(textAnchor), textAnchor, state, false);
|
|
2474
2872
|
}
|
|
2475
2873
|
catch (error) {
|
|
2476
2874
|
// ignore
|
|
@@ -2498,7 +2896,7 @@ class StableBrowser {
|
|
|
2498
2896
|
const count = await frame.locator(css).count();
|
|
2499
2897
|
for (let j = 0; j < count; j++) {
|
|
2500
2898
|
const continer = await frame.locator(css).nth(j);
|
|
2501
|
-
const result = await this._locateElementByText(continer, textToVerify, "
|
|
2899
|
+
const result = await this._locateElementByText(continer, textToVerify, "*:not(script, style, head)", false, true, true, {});
|
|
2502
2900
|
if (result.elementCount > 0) {
|
|
2503
2901
|
const dataAttribute = "[data-blinq-id-" + result.randomToken + "]";
|
|
2504
2902
|
await this._highlightElements(frame, dataAttribute);
|
|
@@ -2539,8 +2937,32 @@ class StableBrowser {
|
|
|
2539
2937
|
await _commandError(state, e, this);
|
|
2540
2938
|
}
|
|
2541
2939
|
finally {
|
|
2542
|
-
_commandFinally(state, this);
|
|
2940
|
+
await _commandFinally(state, this);
|
|
2941
|
+
}
|
|
2942
|
+
}
|
|
2943
|
+
async findRelatedTextInAllFrames(textAnchor, climb, textToVerify, params = {}, options = {}, world = null) {
|
|
2944
|
+
const frames = this.page.frames();
|
|
2945
|
+
let results = [];
|
|
2946
|
+
let ignoreCase = false;
|
|
2947
|
+
for (let i = 0; i < frames.length; i++) {
|
|
2948
|
+
const result = await this._locateElementByText(frames[i], textAnchor, "*:not(script, style, head)", false, true, ignoreCase, {});
|
|
2949
|
+
result.frame = frames[i];
|
|
2950
|
+
const climbArray = [];
|
|
2951
|
+
for (let i = 0; i < climb; i++) {
|
|
2952
|
+
climbArray.push("..");
|
|
2953
|
+
}
|
|
2954
|
+
let climbXpath = "xpath=" + climbArray.join("/");
|
|
2955
|
+
const newLocator = `[data-blinq-id-${result.randomToken}] ${climb > 0 ? ">> " + climbXpath : ""} >> internal:text=${testForRegex(textToVerify) ? textToVerify : unEscapeString(textToVerify)}`;
|
|
2956
|
+
const count = await frames[i].locator(newLocator).count();
|
|
2957
|
+
if (count > 0) {
|
|
2958
|
+
result.elementCount = count;
|
|
2959
|
+
result.locator = newLocator;
|
|
2960
|
+
results.push(result);
|
|
2961
|
+
}
|
|
2543
2962
|
}
|
|
2963
|
+
// state.info.results = results;
|
|
2964
|
+
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2965
|
+
return resultWithElementsFound;
|
|
2544
2966
|
}
|
|
2545
2967
|
async visualVerification(text, options = {}, world = null) {
|
|
2546
2968
|
const startTime = Date.now();
|
|
@@ -2858,7 +3280,13 @@ class StableBrowser {
|
|
|
2858
3280
|
}
|
|
2859
3281
|
}
|
|
2860
3282
|
async _replaceWithLocalData(value, world, _decrypt = true, totpWait = true) {
|
|
2861
|
-
|
|
3283
|
+
try {
|
|
3284
|
+
return await replaceWithLocalTestData(value, world, _decrypt, totpWait, this.context, this);
|
|
3285
|
+
}
|
|
3286
|
+
catch (error) {
|
|
3287
|
+
this.logger.debug(error);
|
|
3288
|
+
throw error;
|
|
3289
|
+
}
|
|
2862
3290
|
}
|
|
2863
3291
|
_getLoadTimeout(options) {
|
|
2864
3292
|
let timeout = 15000;
|
|
@@ -2881,6 +3309,7 @@ class StableBrowser {
|
|
|
2881
3309
|
}
|
|
2882
3310
|
async saveStoreState(path = null, world = null) {
|
|
2883
3311
|
const storageState = await this.page.context().storageState();
|
|
3312
|
+
path = await this._replaceWithLocalData(path, this.world);
|
|
2884
3313
|
//const testDataFile = _getDataFile(world, this.context, this);
|
|
2885
3314
|
if (path) {
|
|
2886
3315
|
// save { storageState: storageState } into the path
|
|
@@ -2891,10 +3320,14 @@ class StableBrowser {
|
|
|
2891
3320
|
}
|
|
2892
3321
|
}
|
|
2893
3322
|
async restoreSaveState(path = null, world = null) {
|
|
3323
|
+
path = await this._replaceWithLocalData(path, this.world);
|
|
2894
3324
|
await refreshBrowser(this, path, world);
|
|
2895
3325
|
this.registerEventListeners(this.context);
|
|
2896
3326
|
registerNetworkEvents(this.world, this, this.context, this.page);
|
|
2897
3327
|
registerDownloadEvent(this.page, this.world, this.context);
|
|
3328
|
+
if (this.onRestoreSaveState) {
|
|
3329
|
+
this.onRestoreSaveState(path);
|
|
3330
|
+
}
|
|
2898
3331
|
}
|
|
2899
3332
|
async waitForPageLoad(options = {}, world = null) {
|
|
2900
3333
|
let timeout = this._getLoadTimeout(options);
|
|
@@ -2977,7 +3410,7 @@ class StableBrowser {
|
|
|
2977
3410
|
await _commandError(state, e, this);
|
|
2978
3411
|
}
|
|
2979
3412
|
finally {
|
|
2980
|
-
_commandFinally(state, this);
|
|
3413
|
+
await _commandFinally(state, this);
|
|
2981
3414
|
}
|
|
2982
3415
|
}
|
|
2983
3416
|
async tableCellOperation(headerText, rowText, options, _params, world = null) {
|
|
@@ -3064,7 +3497,7 @@ class StableBrowser {
|
|
|
3064
3497
|
await _commandError(state, e, this);
|
|
3065
3498
|
}
|
|
3066
3499
|
finally {
|
|
3067
|
-
_commandFinally(state, this);
|
|
3500
|
+
await _commandFinally(state, this);
|
|
3068
3501
|
}
|
|
3069
3502
|
}
|
|
3070
3503
|
saveTestDataAsGlobal(options, world) {
|
|
@@ -3169,7 +3602,39 @@ class StableBrowser {
|
|
|
3169
3602
|
console.log("#-#");
|
|
3170
3603
|
}
|
|
3171
3604
|
}
|
|
3605
|
+
async beforeScenario(world, scenario) {
|
|
3606
|
+
this.beforeScenarioCalled = true;
|
|
3607
|
+
if (scenario && scenario.pickle && scenario.pickle.name) {
|
|
3608
|
+
this.scenarioName = scenario.pickle.name;
|
|
3609
|
+
}
|
|
3610
|
+
if (scenario && scenario.gherkinDocument && scenario.gherkinDocument.feature) {
|
|
3611
|
+
this.featureName = scenario.gherkinDocument.feature.name;
|
|
3612
|
+
}
|
|
3613
|
+
if (this.context) {
|
|
3614
|
+
this.context.examplesRow = extractStepExampleParameters(scenario);
|
|
3615
|
+
}
|
|
3616
|
+
if (this.tags === null && scenario && scenario.pickle && scenario.pickle.tags) {
|
|
3617
|
+
this.tags = scenario.pickle.tags.map((tag) => tag.name);
|
|
3618
|
+
// check if @global_test_data tag is present
|
|
3619
|
+
if (this.tags.includes("@global_test_data")) {
|
|
3620
|
+
this.saveTestDataAsGlobal({}, world);
|
|
3621
|
+
}
|
|
3622
|
+
}
|
|
3623
|
+
// update test data based on feature/scenario
|
|
3624
|
+
let envName = null;
|
|
3625
|
+
if (this.context && this.context.environment) {
|
|
3626
|
+
envName = this.context.environment.name;
|
|
3627
|
+
}
|
|
3628
|
+
if (!process.env.TEMP_RUN) {
|
|
3629
|
+
await getTestData(envName, world, undefined, this.featureName, this.scenarioName);
|
|
3630
|
+
}
|
|
3631
|
+
await loadBrunoParams(this.context, this.context.environment.name);
|
|
3632
|
+
}
|
|
3633
|
+
async afterScenario(world, scenario) { }
|
|
3172
3634
|
async beforeStep(world, step) {
|
|
3635
|
+
if (!this.beforeScenarioCalled) {
|
|
3636
|
+
this.beforeScenario(world, step);
|
|
3637
|
+
}
|
|
3173
3638
|
if (this.stepIndex === undefined) {
|
|
3174
3639
|
this.stepIndex = 0;
|
|
3175
3640
|
}
|
|
@@ -3186,21 +3651,11 @@ class StableBrowser {
|
|
|
3186
3651
|
else {
|
|
3187
3652
|
this.stepName = "step " + this.stepIndex;
|
|
3188
3653
|
}
|
|
3189
|
-
if (this.context) {
|
|
3190
|
-
this.context.examplesRow = extractStepExampleParameters(step);
|
|
3191
|
-
}
|
|
3192
3654
|
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
3193
3655
|
if (this.context.browserObject.context) {
|
|
3194
3656
|
await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
|
|
3195
3657
|
}
|
|
3196
3658
|
}
|
|
3197
|
-
if (this.tags === null && step && step.pickle && step.pickle.tags) {
|
|
3198
|
-
this.tags = step.pickle.tags.map((tag) => tag.name);
|
|
3199
|
-
// check if @global_test_data tag is present
|
|
3200
|
-
if (this.tags.includes("@global_test_data")) {
|
|
3201
|
-
this.saveTestDataAsGlobal({}, world);
|
|
3202
|
-
}
|
|
3203
|
-
}
|
|
3204
3659
|
if (this.initSnapshotTaken === false) {
|
|
3205
3660
|
this.initSnapshotTaken = true;
|
|
3206
3661
|
if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
|
|
@@ -3225,18 +3680,68 @@ class StableBrowser {
|
|
|
3225
3680
|
const content = [`- path: ${path}`, `- title: ${title}`];
|
|
3226
3681
|
const timeout = this.configuration.ariaSnapshotTimeout ? this.configuration.ariaSnapshotTimeout : 3000;
|
|
3227
3682
|
for (let i = 0; i < frames.length; i++) {
|
|
3228
|
-
content.push(`- frame: ${i}`);
|
|
3229
3683
|
const frame = frames[i];
|
|
3230
|
-
|
|
3231
|
-
|
|
3684
|
+
try {
|
|
3685
|
+
// Ensure frame is attached and has body
|
|
3686
|
+
const body = frame.locator("body");
|
|
3687
|
+
await body.waitFor({ timeout: 200 }); // wait explicitly
|
|
3688
|
+
const snapshot = await body.ariaSnapshot({ timeout });
|
|
3689
|
+
content.push(`- frame: ${i}`);
|
|
3690
|
+
content.push(snapshot);
|
|
3691
|
+
}
|
|
3692
|
+
catch (innerErr) { }
|
|
3232
3693
|
}
|
|
3233
3694
|
return content.join("\n");
|
|
3234
3695
|
}
|
|
3235
3696
|
catch (e) {
|
|
3236
|
-
console.
|
|
3697
|
+
console.log("Error in getAriaSnapshot");
|
|
3698
|
+
//console.debug(e);
|
|
3237
3699
|
}
|
|
3238
3700
|
return null;
|
|
3239
3701
|
}
|
|
3702
|
+
/**
|
|
3703
|
+
* Sends command with custom payload to report.
|
|
3704
|
+
* @param commandText - Title of the command to be shown in the report.
|
|
3705
|
+
* @param commandStatus - Status of the command (e.g. "PASSED", "FAILED").
|
|
3706
|
+
* @param content - Content of the command to be shown in the report.
|
|
3707
|
+
* @param options - Options for the command. Example: { type: "json", screenshot: true }
|
|
3708
|
+
* @param world - Optional world context.
|
|
3709
|
+
* @public
|
|
3710
|
+
*/
|
|
3711
|
+
async addCommandToReport(commandText, commandStatus, content, options = {}, world = null) {
|
|
3712
|
+
const state = {
|
|
3713
|
+
options,
|
|
3714
|
+
world,
|
|
3715
|
+
locate: false,
|
|
3716
|
+
scroll: false,
|
|
3717
|
+
screenshot: options.screenshot ?? false,
|
|
3718
|
+
highlight: options.highlight ?? false,
|
|
3719
|
+
type: Types.REPORT_COMMAND,
|
|
3720
|
+
text: commandText,
|
|
3721
|
+
_text: commandText,
|
|
3722
|
+
operation: "report_command",
|
|
3723
|
+
log: "***** " + commandText + " *****\n",
|
|
3724
|
+
};
|
|
3725
|
+
try {
|
|
3726
|
+
await _preCommand(state, this);
|
|
3727
|
+
const payload = {
|
|
3728
|
+
type: options.type ?? "text",
|
|
3729
|
+
content: content,
|
|
3730
|
+
screenshotId: null,
|
|
3731
|
+
};
|
|
3732
|
+
state.payload = payload;
|
|
3733
|
+
if (commandStatus === "FAILED") {
|
|
3734
|
+
state.throwError = true;
|
|
3735
|
+
throw new Error("Command failed");
|
|
3736
|
+
}
|
|
3737
|
+
}
|
|
3738
|
+
catch (e) {
|
|
3739
|
+
await _commandError(state, e, this);
|
|
3740
|
+
}
|
|
3741
|
+
finally {
|
|
3742
|
+
await _commandFinally(state, this);
|
|
3743
|
+
}
|
|
3744
|
+
}
|
|
3240
3745
|
async afterStep(world, step) {
|
|
3241
3746
|
this.stepName = null;
|
|
3242
3747
|
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
@@ -3244,6 +3749,13 @@ class StableBrowser {
|
|
|
3244
3749
|
await this.context.browserObject.context.tracing.stopChunk({
|
|
3245
3750
|
path: path.join(this.context.browserObject.traceFolder, `trace-${this.stepIndex}.zip`),
|
|
3246
3751
|
});
|
|
3752
|
+
if (world && world.attach) {
|
|
3753
|
+
await world.attach(JSON.stringify({
|
|
3754
|
+
type: "trace",
|
|
3755
|
+
traceFilePath: `trace-${this.stepIndex}.zip`,
|
|
3756
|
+
}), "application/json+trace");
|
|
3757
|
+
}
|
|
3758
|
+
// console.log("trace file created", `trace-${this.stepIndex}.zip`);
|
|
3247
3759
|
}
|
|
3248
3760
|
}
|
|
3249
3761
|
if (this.context) {
|
|
@@ -3256,6 +3768,29 @@ class StableBrowser {
|
|
|
3256
3768
|
await world.attach(JSON.stringify(snapshot), "application/json+snapshot-after");
|
|
3257
3769
|
}
|
|
3258
3770
|
}
|
|
3771
|
+
if (!process.env.TEMP_RUN) {
|
|
3772
|
+
const state = {
|
|
3773
|
+
world,
|
|
3774
|
+
locate: false,
|
|
3775
|
+
scroll: false,
|
|
3776
|
+
screenshot: true,
|
|
3777
|
+
highlight: true,
|
|
3778
|
+
type: Types.STEP_COMPLETE,
|
|
3779
|
+
text: "end of scenario",
|
|
3780
|
+
_text: "end of scenario",
|
|
3781
|
+
operation: "step_complete",
|
|
3782
|
+
log: "***** " + "end of scenario" + " *****\n",
|
|
3783
|
+
};
|
|
3784
|
+
try {
|
|
3785
|
+
await _preCommand(state, this);
|
|
3786
|
+
}
|
|
3787
|
+
catch (e) {
|
|
3788
|
+
await _commandError(state, e, this);
|
|
3789
|
+
}
|
|
3790
|
+
finally {
|
|
3791
|
+
await _commandFinally(state, this);
|
|
3792
|
+
}
|
|
3793
|
+
}
|
|
3259
3794
|
}
|
|
3260
3795
|
}
|
|
3261
3796
|
function createTimedPromise(promise, label) {
|