automation_model 1.0.646-dev → 1.0.646-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/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 +143 -57
- package/lib/auto_page.js.map +1 -1
- package/lib/browser_manager.js +28 -8
- 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 +1 -1
- package/lib/command_common.js +24 -4
- 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.js +4 -4
- package/lib/init_browser.js.map +1 -1
- 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.js.map +1 -1
- package/lib/scripts/axe.mini.js +3 -3
- 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 +48 -24
- package/lib/stable_browser.js +696 -186
- 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 +1 -0
- package/lib/test_context.js +1 -0
- package/lib/test_context.js.map +1 -1
- package/lib/utils.d.ts +5 -3
- package/lib/utils.js +171 -54
- 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 { highlightSnapshot, 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) => {
|
|
@@ -66,6 +76,7 @@ class StableBrowser {
|
|
|
66
76
|
logger;
|
|
67
77
|
context;
|
|
68
78
|
world;
|
|
79
|
+
fastMode;
|
|
69
80
|
project_path = null;
|
|
70
81
|
webLogFile = null;
|
|
71
82
|
networkLogger = null;
|
|
@@ -74,12 +85,13 @@ class StableBrowser {
|
|
|
74
85
|
tags = null;
|
|
75
86
|
isRecording = false;
|
|
76
87
|
initSnapshotTaken = false;
|
|
77
|
-
constructor(browser, page, logger = null, context = null, world = null) {
|
|
88
|
+
constructor(browser, page, logger = null, context = null, world = null, fastMode = false) {
|
|
78
89
|
this.browser = browser;
|
|
79
90
|
this.page = page;
|
|
80
91
|
this.logger = logger;
|
|
81
92
|
this.context = context;
|
|
82
93
|
this.world = world;
|
|
94
|
+
this.fastMode = fastMode;
|
|
83
95
|
if (!this.logger) {
|
|
84
96
|
this.logger = console;
|
|
85
97
|
}
|
|
@@ -108,6 +120,12 @@ class StableBrowser {
|
|
|
108
120
|
context.pages = [this.page];
|
|
109
121
|
const logFolder = path.join(this.project_path, "logs", "web");
|
|
110
122
|
this.world = world;
|
|
123
|
+
if (process.env.FAST_MODE === "true") {
|
|
124
|
+
this.fastMode = true;
|
|
125
|
+
}
|
|
126
|
+
if (this.context) {
|
|
127
|
+
this.context.fastMode = this.fastMode;
|
|
128
|
+
}
|
|
111
129
|
this.registerEventListeners(this.context);
|
|
112
130
|
registerNetworkEvents(this.world, this, this.context, this.page);
|
|
113
131
|
registerDownloadEvent(this.page, this.world, this.context);
|
|
@@ -118,6 +136,9 @@ class StableBrowser {
|
|
|
118
136
|
if (!context.pageLoading) {
|
|
119
137
|
context.pageLoading = { status: false };
|
|
120
138
|
}
|
|
139
|
+
if (this.configuration && this.configuration.acceptDialog && this.page) {
|
|
140
|
+
this.page.on("dialog", (dialog) => dialog.accept());
|
|
141
|
+
}
|
|
121
142
|
context.playContext.on("page", async function (page) {
|
|
122
143
|
if (this.configuration && this.configuration.closePopups === true) {
|
|
123
144
|
console.log("close unexpected popups");
|
|
@@ -126,6 +147,14 @@ class StableBrowser {
|
|
|
126
147
|
}
|
|
127
148
|
context.pageLoading.status = true;
|
|
128
149
|
this.page = page;
|
|
150
|
+
try {
|
|
151
|
+
if (this.configuration && this.configuration.acceptDialog) {
|
|
152
|
+
await page.on("dialog", (dialog) => dialog.accept());
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
console.error("Error on dialog accept registration", error);
|
|
157
|
+
}
|
|
129
158
|
context.page = page;
|
|
130
159
|
context.pages.push(page);
|
|
131
160
|
registerNetworkEvents(this.world, this, context, this.page);
|
|
@@ -177,8 +206,34 @@ class StableBrowser {
|
|
|
177
206
|
if (newContextCreated) {
|
|
178
207
|
this.registerEventListeners(this.context);
|
|
179
208
|
await this.goto(this.context.environment.baseUrl);
|
|
180
|
-
|
|
209
|
+
if (!this.fastMode) {
|
|
210
|
+
await this.waitForPageLoad();
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
async switchTab(tabTitleOrIndex) {
|
|
215
|
+
// first check if the tabNameOrIndex is a number
|
|
216
|
+
let index = parseInt(tabTitleOrIndex);
|
|
217
|
+
if (!isNaN(index)) {
|
|
218
|
+
if (index >= 0 && index < this.context.pages.length) {
|
|
219
|
+
this.page = this.context.pages[index];
|
|
220
|
+
this.context.page = this.page;
|
|
221
|
+
await this.page.bringToFront();
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
// if the tabNameOrIndex is a string, find the tab by name
|
|
226
|
+
for (let i = 0; i < this.context.pages.length; i++) {
|
|
227
|
+
let page = this.context.pages[i];
|
|
228
|
+
let title = await page.title();
|
|
229
|
+
if (title.includes(tabTitleOrIndex)) {
|
|
230
|
+
this.page = page;
|
|
231
|
+
this.context.page = this.page;
|
|
232
|
+
await this.page.bringToFront();
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
181
235
|
}
|
|
236
|
+
throw new Error("Tab not found: " + tabTitleOrIndex);
|
|
182
237
|
}
|
|
183
238
|
registerConsoleLogListener(page, context) {
|
|
184
239
|
if (!this.context.webLogger) {
|
|
@@ -247,6 +302,7 @@ class StableBrowser {
|
|
|
247
302
|
if (!url) {
|
|
248
303
|
throw new Error("url is null, verify that the environment file is correct");
|
|
249
304
|
}
|
|
305
|
+
url = await this._replaceWithLocalData(url, this.world);
|
|
250
306
|
if (!url.startsWith("http")) {
|
|
251
307
|
url = "https://" + url;
|
|
252
308
|
}
|
|
@@ -275,7 +331,7 @@ class StableBrowser {
|
|
|
275
331
|
_commandError(state, error, this);
|
|
276
332
|
}
|
|
277
333
|
finally {
|
|
278
|
-
_commandFinally(state, this);
|
|
334
|
+
await _commandFinally(state, this);
|
|
279
335
|
}
|
|
280
336
|
}
|
|
281
337
|
async _getLocator(locator, scope, _params) {
|
|
@@ -391,7 +447,7 @@ class StableBrowser {
|
|
|
391
447
|
}
|
|
392
448
|
return { elementCount: tagCount, randomToken };
|
|
393
449
|
}
|
|
394
|
-
async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true, allowDisabled = false, element_name = null) {
|
|
450
|
+
async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true, allowDisabled = false, element_name = null, logErrors = false) {
|
|
395
451
|
if (!info) {
|
|
396
452
|
info = {};
|
|
397
453
|
}
|
|
@@ -458,7 +514,7 @@ class StableBrowser {
|
|
|
458
514
|
}
|
|
459
515
|
return;
|
|
460
516
|
}
|
|
461
|
-
if (info.locatorLog && count === 0) {
|
|
517
|
+
if (info.locatorLog && count === 0 && logErrors) {
|
|
462
518
|
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "NOT_FOUND");
|
|
463
519
|
}
|
|
464
520
|
for (let j = 0; j < count; j++) {
|
|
@@ -473,7 +529,7 @@ class StableBrowser {
|
|
|
473
529
|
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
|
|
474
530
|
}
|
|
475
531
|
}
|
|
476
|
-
else {
|
|
532
|
+
else if (logErrors) {
|
|
477
533
|
info.failCause.visible = visible;
|
|
478
534
|
info.failCause.enabled = enabled;
|
|
479
535
|
if (!info.printMessages) {
|
|
@@ -565,15 +621,27 @@ class StableBrowser {
|
|
|
565
621
|
let element = await this._locate_internal(selectors, info, _params, timeout, allowDisabled);
|
|
566
622
|
if (!element.rerun) {
|
|
567
623
|
const randomToken = Math.random().toString(36).substring(7);
|
|
568
|
-
element.evaluate((el, randomToken) => {
|
|
624
|
+
await element.evaluate((el, randomToken) => {
|
|
569
625
|
el.setAttribute("data-blinq-id-" + randomToken, "");
|
|
570
626
|
}, randomToken);
|
|
571
|
-
if (element._frame) {
|
|
572
|
-
|
|
573
|
-
}
|
|
574
|
-
const scope = element.page();
|
|
575
|
-
|
|
576
|
-
|
|
627
|
+
// if (element._frame) {
|
|
628
|
+
// return element;
|
|
629
|
+
// }
|
|
630
|
+
const scope = element._frame ?? element.page();
|
|
631
|
+
let newElementSelector = "[data-blinq-id-" + randomToken + "]";
|
|
632
|
+
let prefixSelector = "";
|
|
633
|
+
const frameControlSelector = " >> internal:control=enter-frame";
|
|
634
|
+
const frameSelectorIndex = element._selector.lastIndexOf(frameControlSelector);
|
|
635
|
+
if (frameSelectorIndex !== -1) {
|
|
636
|
+
// remove everything after the >> internal:control=enter-frame
|
|
637
|
+
const frameSelector = element._selector.substring(0, frameSelectorIndex);
|
|
638
|
+
prefixSelector = frameSelector + " >> internal:control=enter-frame >>";
|
|
639
|
+
}
|
|
640
|
+
// if (element?._frame?._selector) {
|
|
641
|
+
// prefixSelector = element._frame._selector + " >> " + prefixSelector;
|
|
642
|
+
// }
|
|
643
|
+
const newSelector = prefixSelector + newElementSelector;
|
|
644
|
+
return scope.locator(newSelector);
|
|
577
645
|
}
|
|
578
646
|
}
|
|
579
647
|
throw new Error("unable to locate element " + JSON.stringify(selectors));
|
|
@@ -725,14 +793,9 @@ class StableBrowser {
|
|
|
725
793
|
// info.log += "scanning locators in priority 2" + "\n";
|
|
726
794
|
result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
727
795
|
}
|
|
728
|
-
if (result.foundElements.length === 0 && onlyPriority3) {
|
|
796
|
+
if (result.foundElements.length === 0 && (onlyPriority3 || !highPriorityOnly)) {
|
|
729
797
|
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
730
798
|
}
|
|
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
799
|
let foundElements = result.foundElements;
|
|
737
800
|
if (foundElements.length === 1 && foundElements[0].unique) {
|
|
738
801
|
info.box = foundElements[0].box;
|
|
@@ -787,6 +850,11 @@ class StableBrowser {
|
|
|
787
850
|
visibleOnly = false;
|
|
788
851
|
}
|
|
789
852
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
853
|
+
// sheck of more of half of the timeout has passed
|
|
854
|
+
if (Date.now() - startTime > timeout / 2) {
|
|
855
|
+
highPriorityOnly = false;
|
|
856
|
+
visibleOnly = false;
|
|
857
|
+
}
|
|
790
858
|
}
|
|
791
859
|
this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
|
|
792
860
|
// if (info.locatorLog) {
|
|
@@ -802,7 +870,7 @@ class StableBrowser {
|
|
|
802
870
|
}
|
|
803
871
|
throw new Error("failed to locate first element no elements found, " + info.log);
|
|
804
872
|
}
|
|
805
|
-
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false, element_name) {
|
|
873
|
+
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false, element_name, logErrors = false) {
|
|
806
874
|
let foundElements = [];
|
|
807
875
|
const result = {
|
|
808
876
|
foundElements: foundElements,
|
|
@@ -821,7 +889,9 @@ class StableBrowser {
|
|
|
821
889
|
await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
|
|
822
890
|
}
|
|
823
891
|
catch (e) {
|
|
824
|
-
|
|
892
|
+
if (logErrors) {
|
|
893
|
+
this.logger.info("unable to use locator (second try) " + JSON.stringify(locatorsGroup[i]));
|
|
894
|
+
}
|
|
825
895
|
}
|
|
826
896
|
}
|
|
827
897
|
if (foundLocators.length === 1) {
|
|
@@ -862,7 +932,7 @@ class StableBrowser {
|
|
|
862
932
|
});
|
|
863
933
|
result.locatorIndex = i;
|
|
864
934
|
}
|
|
865
|
-
else {
|
|
935
|
+
else if (logErrors) {
|
|
866
936
|
info.failCause.foundMultiple = true;
|
|
867
937
|
if (info.locatorLog) {
|
|
868
938
|
info.locatorLog.setLocatorSearchStatus(JSON.stringify(locatorsGroup[i]), "FOUND_NOT_UNIQUE");
|
|
@@ -914,7 +984,7 @@ class StableBrowser {
|
|
|
914
984
|
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
915
985
|
}
|
|
916
986
|
finally {
|
|
917
|
-
_commandFinally(state, this);
|
|
987
|
+
await _commandFinally(state, this);
|
|
918
988
|
}
|
|
919
989
|
}
|
|
920
990
|
}
|
|
@@ -963,7 +1033,7 @@ class StableBrowser {
|
|
|
963
1033
|
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
964
1034
|
}
|
|
965
1035
|
finally {
|
|
966
|
-
_commandFinally(state, this);
|
|
1036
|
+
await _commandFinally(state, this);
|
|
967
1037
|
}
|
|
968
1038
|
}
|
|
969
1039
|
}
|
|
@@ -985,14 +1055,16 @@ class StableBrowser {
|
|
|
985
1055
|
try {
|
|
986
1056
|
await _preCommand(state, this);
|
|
987
1057
|
await performAction("click", state.element, options, this, state, _params);
|
|
988
|
-
|
|
1058
|
+
if (!this.fastMode) {
|
|
1059
|
+
await this.waitForPageLoad();
|
|
1060
|
+
}
|
|
989
1061
|
return state.info;
|
|
990
1062
|
}
|
|
991
1063
|
catch (e) {
|
|
992
1064
|
await _commandError(state, e, this);
|
|
993
1065
|
}
|
|
994
1066
|
finally {
|
|
995
|
-
_commandFinally(state, this);
|
|
1067
|
+
await _commandFinally(state, this);
|
|
996
1068
|
}
|
|
997
1069
|
}
|
|
998
1070
|
async waitForElement(selectors, _params, options = {}, world = null) {
|
|
@@ -1023,7 +1095,7 @@ class StableBrowser {
|
|
|
1023
1095
|
// await _commandError(state, e, this);
|
|
1024
1096
|
}
|
|
1025
1097
|
finally {
|
|
1026
|
-
_commandFinally(state, this);
|
|
1098
|
+
await _commandFinally(state, this);
|
|
1027
1099
|
}
|
|
1028
1100
|
return found;
|
|
1029
1101
|
}
|
|
@@ -1047,8 +1119,8 @@ class StableBrowser {
|
|
|
1047
1119
|
try {
|
|
1048
1120
|
// if (world && world.screenshot && !world.screenshotPath) {
|
|
1049
1121
|
// console.log(`Highlighting while running from recorder`);
|
|
1050
|
-
await this._highlightElements(element);
|
|
1051
|
-
await state.element.setChecked(checked);
|
|
1122
|
+
await this._highlightElements(state.element);
|
|
1123
|
+
await state.element.setChecked(checked, { timeout: 2000 });
|
|
1052
1124
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1053
1125
|
// await this._unHighlightElements(element);
|
|
1054
1126
|
// }
|
|
@@ -1060,11 +1132,28 @@ class StableBrowser {
|
|
|
1060
1132
|
this.logger.info("element did not change its state, ignoring...");
|
|
1061
1133
|
}
|
|
1062
1134
|
else {
|
|
1135
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1063
1136
|
//await this.closeUnexpectedPopups();
|
|
1064
1137
|
state.info.log += "setCheck failed, will try again" + "\n";
|
|
1065
|
-
state.
|
|
1066
|
-
|
|
1067
|
-
|
|
1138
|
+
state.element_found = false;
|
|
1139
|
+
try {
|
|
1140
|
+
state.element = await this._locate(selectors, state.info, _params, 100);
|
|
1141
|
+
state.element_found = true;
|
|
1142
|
+
// check the check state
|
|
1143
|
+
}
|
|
1144
|
+
catch (error) {
|
|
1145
|
+
// element dismissed
|
|
1146
|
+
}
|
|
1147
|
+
if (state.element_found) {
|
|
1148
|
+
const isChecked = await state.element.isChecked();
|
|
1149
|
+
if (isChecked !== checked) {
|
|
1150
|
+
// perform click
|
|
1151
|
+
await state.element.click({ timeout: 2000, force: true });
|
|
1152
|
+
}
|
|
1153
|
+
else {
|
|
1154
|
+
this.logger.info(`Element ${selectors.element_name} is already in the desired state (${checked})`);
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1068
1157
|
}
|
|
1069
1158
|
}
|
|
1070
1159
|
await this.waitForPageLoad();
|
|
@@ -1074,7 +1163,7 @@ class StableBrowser {
|
|
|
1074
1163
|
await _commandError(state, e, this);
|
|
1075
1164
|
}
|
|
1076
1165
|
finally {
|
|
1077
|
-
_commandFinally(state, this);
|
|
1166
|
+
await _commandFinally(state, this);
|
|
1078
1167
|
}
|
|
1079
1168
|
}
|
|
1080
1169
|
async hover(selectors, _params, options = {}, world = null) {
|
|
@@ -1100,7 +1189,7 @@ class StableBrowser {
|
|
|
1100
1189
|
await _commandError(state, e, this);
|
|
1101
1190
|
}
|
|
1102
1191
|
finally {
|
|
1103
|
-
_commandFinally(state, this);
|
|
1192
|
+
await _commandFinally(state, this);
|
|
1104
1193
|
}
|
|
1105
1194
|
}
|
|
1106
1195
|
async selectOption(selectors, values, _params = null, options = {}, world = null) {
|
|
@@ -1136,7 +1225,7 @@ class StableBrowser {
|
|
|
1136
1225
|
await _commandError(state, e, this);
|
|
1137
1226
|
}
|
|
1138
1227
|
finally {
|
|
1139
|
-
_commandFinally(state, this);
|
|
1228
|
+
await _commandFinally(state, this);
|
|
1140
1229
|
}
|
|
1141
1230
|
}
|
|
1142
1231
|
async type(_value, _params = null, options = {}, world = null) {
|
|
@@ -1182,7 +1271,7 @@ class StableBrowser {
|
|
|
1182
1271
|
await _commandError(state, e, this);
|
|
1183
1272
|
}
|
|
1184
1273
|
finally {
|
|
1185
|
-
_commandFinally(state, this);
|
|
1274
|
+
await _commandFinally(state, this);
|
|
1186
1275
|
}
|
|
1187
1276
|
}
|
|
1188
1277
|
async setInputValue(selectors, value, _params = null, options = {}, world = null) {
|
|
@@ -1218,7 +1307,7 @@ class StableBrowser {
|
|
|
1218
1307
|
await _commandError(state, e, this);
|
|
1219
1308
|
}
|
|
1220
1309
|
finally {
|
|
1221
|
-
_commandFinally(state, this);
|
|
1310
|
+
await _commandFinally(state, this);
|
|
1222
1311
|
}
|
|
1223
1312
|
}
|
|
1224
1313
|
async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
|
|
@@ -1287,7 +1376,7 @@ class StableBrowser {
|
|
|
1287
1376
|
await _commandError(state, e, this);
|
|
1288
1377
|
}
|
|
1289
1378
|
finally {
|
|
1290
|
-
_commandFinally(state, this);
|
|
1379
|
+
await _commandFinally(state, this);
|
|
1291
1380
|
}
|
|
1292
1381
|
}
|
|
1293
1382
|
async clickType(selectors, _value, enter = false, _params = null, options = {}, world = null) {
|
|
@@ -1360,7 +1449,9 @@ class StableBrowser {
|
|
|
1360
1449
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1361
1450
|
}
|
|
1362
1451
|
}
|
|
1452
|
+
//if (!this.fastMode) {
|
|
1363
1453
|
await _screenshot(state, this);
|
|
1454
|
+
//}
|
|
1364
1455
|
if (enter === true) {
|
|
1365
1456
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1366
1457
|
await this.page.keyboard.press("Enter");
|
|
@@ -1387,7 +1478,7 @@ class StableBrowser {
|
|
|
1387
1478
|
await _commandError(state, e, this);
|
|
1388
1479
|
}
|
|
1389
1480
|
finally {
|
|
1390
|
-
_commandFinally(state, this);
|
|
1481
|
+
await _commandFinally(state, this);
|
|
1391
1482
|
}
|
|
1392
1483
|
}
|
|
1393
1484
|
async fill(selectors, value, enter = false, _params = null, options = {}, world = null) {
|
|
@@ -1417,7 +1508,42 @@ class StableBrowser {
|
|
|
1417
1508
|
await _commandError(state, e, this);
|
|
1418
1509
|
}
|
|
1419
1510
|
finally {
|
|
1420
|
-
_commandFinally(state, this);
|
|
1511
|
+
await _commandFinally(state, this);
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
async setInputFiles(selectors, files, _params = null, options = {}, world = null) {
|
|
1515
|
+
const state = {
|
|
1516
|
+
selectors,
|
|
1517
|
+
_params,
|
|
1518
|
+
files,
|
|
1519
|
+
value: '"' + files.join('", "') + '"',
|
|
1520
|
+
options,
|
|
1521
|
+
world,
|
|
1522
|
+
type: Types.SET_INPUT_FILES,
|
|
1523
|
+
text: `Set input files`,
|
|
1524
|
+
_text: `Set input files on ${selectors.element_name}`,
|
|
1525
|
+
operation: "setInputFiles",
|
|
1526
|
+
log: "***** set input files " + selectors.element_name + " *****\n",
|
|
1527
|
+
};
|
|
1528
|
+
const uploadsFolder = this.configuration.uploadsFolder ?? "data/uploads";
|
|
1529
|
+
try {
|
|
1530
|
+
await _preCommand(state, this);
|
|
1531
|
+
for (let i = 0; i < files.length; i++) {
|
|
1532
|
+
const file = files[i];
|
|
1533
|
+
const filePath = path.join(uploadsFolder, file);
|
|
1534
|
+
if (!fs.existsSync(filePath)) {
|
|
1535
|
+
throw new Error(`File not found: ${filePath}`);
|
|
1536
|
+
}
|
|
1537
|
+
state.files[i] = filePath;
|
|
1538
|
+
}
|
|
1539
|
+
await state.element.setInputFiles(files);
|
|
1540
|
+
return state.info;
|
|
1541
|
+
}
|
|
1542
|
+
catch (e) {
|
|
1543
|
+
await _commandError(state, e, this);
|
|
1544
|
+
}
|
|
1545
|
+
finally {
|
|
1546
|
+
await _commandFinally(state, this);
|
|
1421
1547
|
}
|
|
1422
1548
|
}
|
|
1423
1549
|
async getText(selectors, _params = null, options = {}, info = {}, world = null) {
|
|
@@ -1533,7 +1659,7 @@ class StableBrowser {
|
|
|
1533
1659
|
await _commandError(state, e, this);
|
|
1534
1660
|
}
|
|
1535
1661
|
finally {
|
|
1536
|
-
_commandFinally(state, this);
|
|
1662
|
+
await _commandFinally(state, this);
|
|
1537
1663
|
}
|
|
1538
1664
|
}
|
|
1539
1665
|
async containsText(selectors, text, climb, _params = null, options = {}, world = null) {
|
|
@@ -1568,7 +1694,7 @@ class StableBrowser {
|
|
|
1568
1694
|
while (Date.now() - startTime < timeout) {
|
|
1569
1695
|
try {
|
|
1570
1696
|
await _preCommand(state, this);
|
|
1571
|
-
foundObj = await this._getText(selectors, climb, _params, { timeout:
|
|
1697
|
+
foundObj = await this._getText(selectors, climb, _params, { timeout: 3000 }, state.info, world);
|
|
1572
1698
|
if (foundObj && foundObj.element) {
|
|
1573
1699
|
await this.scrollIfNeeded(foundObj.element, state.info);
|
|
1574
1700
|
}
|
|
@@ -1610,7 +1736,84 @@ class StableBrowser {
|
|
|
1610
1736
|
throw e;
|
|
1611
1737
|
}
|
|
1612
1738
|
finally {
|
|
1613
|
-
_commandFinally(state, this);
|
|
1739
|
+
await _commandFinally(state, this);
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
async snapshotValidation(frameSelectors, referanceSnapshot, _params = null, options = {}, world = null) {
|
|
1743
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1744
|
+
const startTime = Date.now();
|
|
1745
|
+
const state = {
|
|
1746
|
+
_params,
|
|
1747
|
+
value: referanceSnapshot,
|
|
1748
|
+
options,
|
|
1749
|
+
world,
|
|
1750
|
+
locate: false,
|
|
1751
|
+
scroll: false,
|
|
1752
|
+
screenshot: true,
|
|
1753
|
+
highlight: false,
|
|
1754
|
+
type: Types.SNAPSHOT_VALIDATION,
|
|
1755
|
+
text: `verify snapshot: ${referanceSnapshot}`,
|
|
1756
|
+
operation: "snapshotValidation",
|
|
1757
|
+
log: "***** verify snapshot *****\n",
|
|
1758
|
+
};
|
|
1759
|
+
if (!referanceSnapshot) {
|
|
1760
|
+
throw new Error("referanceSnapshot is null");
|
|
1761
|
+
}
|
|
1762
|
+
let text = null;
|
|
1763
|
+
if (fs.existsSync(path.join(this.project_path, "data", "snapshots", this.context.environment.name, referanceSnapshot + ".yml"))) {
|
|
1764
|
+
text = fs.readFileSync(path.join(this.project_path, "data", "snapshots", this.context.environment.name, referanceSnapshot + ".yml"), "utf8");
|
|
1765
|
+
}
|
|
1766
|
+
else if (fs.existsSync(path.join(this.project_path, "data", "snapshots", this.context.environment.name, referanceSnapshot + ".yaml"))) {
|
|
1767
|
+
text = fs.readFileSync(path.join(this.project_path, "data", "snapshots", this.context.environment.name, referanceSnapshot + ".yaml"), "utf8");
|
|
1768
|
+
}
|
|
1769
|
+
else if (referanceSnapshot.startsWith("yaml:")) {
|
|
1770
|
+
text = referanceSnapshot.substring(5);
|
|
1771
|
+
}
|
|
1772
|
+
else {
|
|
1773
|
+
throw new Error("referenceSnapshot file not found: " + referanceSnapshot);
|
|
1774
|
+
}
|
|
1775
|
+
state.text = text;
|
|
1776
|
+
const newValue = await this._replaceWithLocalData(text, world);
|
|
1777
|
+
await _preCommand(state, this);
|
|
1778
|
+
let foundObj = null;
|
|
1779
|
+
try {
|
|
1780
|
+
let matchResult = null;
|
|
1781
|
+
while (Date.now() - startTime < timeout) {
|
|
1782
|
+
try {
|
|
1783
|
+
let scope = null;
|
|
1784
|
+
if (!frameSelectors) {
|
|
1785
|
+
scope = this.page;
|
|
1786
|
+
}
|
|
1787
|
+
else {
|
|
1788
|
+
scope = await this._findFrameScope(frameSelectors, timeout, state.info);
|
|
1789
|
+
}
|
|
1790
|
+
const snapshot = await scope.locator("body").ariaSnapshot({ timeout });
|
|
1791
|
+
matchResult = snapshotValidation(snapshot, newValue, referanceSnapshot);
|
|
1792
|
+
if (matchResult.errorLine !== -1) {
|
|
1793
|
+
throw new Error("Snapshot validation failed at line " + matchResult.errorLineText);
|
|
1794
|
+
}
|
|
1795
|
+
// highlight and screenshot
|
|
1796
|
+
try {
|
|
1797
|
+
await await highlightSnapshot(newValue, scope);
|
|
1798
|
+
await _screenshot(state, this);
|
|
1799
|
+
}
|
|
1800
|
+
catch (e) { }
|
|
1801
|
+
return state.info;
|
|
1802
|
+
}
|
|
1803
|
+
catch (e) {
|
|
1804
|
+
// Log error but continue retrying until timeout is reached
|
|
1805
|
+
//this.logger.warn("Retrying snapshot validation due to: " + e.message);
|
|
1806
|
+
}
|
|
1807
|
+
await new Promise((resolve) => setTimeout(resolve, 2000)); // Wait 1 second before retrying
|
|
1808
|
+
}
|
|
1809
|
+
throw new Error("No snapshot match " + matchResult?.errorLineText);
|
|
1810
|
+
}
|
|
1811
|
+
catch (e) {
|
|
1812
|
+
await _commandError(state, e, this);
|
|
1813
|
+
throw e;
|
|
1814
|
+
}
|
|
1815
|
+
finally {
|
|
1816
|
+
await _commandFinally(state, this);
|
|
1614
1817
|
}
|
|
1615
1818
|
}
|
|
1616
1819
|
async waitForUserInput(message, world = null) {
|
|
@@ -1648,6 +1851,15 @@ class StableBrowser {
|
|
|
1648
1851
|
// save the data to the file
|
|
1649
1852
|
fs.writeFileSync(dataFile, JSON.stringify(data, null, 2));
|
|
1650
1853
|
}
|
|
1854
|
+
overwriteTestData(testData, world = null) {
|
|
1855
|
+
if (!testData) {
|
|
1856
|
+
return;
|
|
1857
|
+
}
|
|
1858
|
+
// if data file exists, load it
|
|
1859
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1860
|
+
// save the data to the file
|
|
1861
|
+
fs.writeFileSync(dataFile, JSON.stringify(testData, null, 2));
|
|
1862
|
+
}
|
|
1651
1863
|
_getDataFilePath(fileName) {
|
|
1652
1864
|
let dataFile = path.join(this.project_path, "data", fileName);
|
|
1653
1865
|
if (fs.existsSync(dataFile)) {
|
|
@@ -1900,7 +2112,7 @@ class StableBrowser {
|
|
|
1900
2112
|
await _commandError(state, e, this);
|
|
1901
2113
|
}
|
|
1902
2114
|
finally {
|
|
1903
|
-
_commandFinally(state, this);
|
|
2115
|
+
await _commandFinally(state, this);
|
|
1904
2116
|
}
|
|
1905
2117
|
}
|
|
1906
2118
|
async extractAttribute(selectors, attribute, variable, _params = null, options = {}, world = null) {
|
|
@@ -1931,10 +2143,31 @@ class StableBrowser {
|
|
|
1931
2143
|
case "value":
|
|
1932
2144
|
state.value = await state.element.inputValue();
|
|
1933
2145
|
break;
|
|
2146
|
+
case "text":
|
|
2147
|
+
state.value = await state.element.textContent();
|
|
2148
|
+
break;
|
|
1934
2149
|
default:
|
|
1935
2150
|
state.value = await state.element.getAttribute(attribute);
|
|
1936
2151
|
break;
|
|
1937
2152
|
}
|
|
2153
|
+
if (options !== null) {
|
|
2154
|
+
if (options.regex && options.regex !== "") {
|
|
2155
|
+
// Construct a regex pattern from the provided string
|
|
2156
|
+
const regex = options.regex.slice(1, -1);
|
|
2157
|
+
const regexPattern = new RegExp(regex, "g");
|
|
2158
|
+
const matches = state.value.match(regexPattern);
|
|
2159
|
+
if (matches) {
|
|
2160
|
+
let newValue = "";
|
|
2161
|
+
for (const match of matches) {
|
|
2162
|
+
newValue += match;
|
|
2163
|
+
}
|
|
2164
|
+
state.value = newValue;
|
|
2165
|
+
}
|
|
2166
|
+
}
|
|
2167
|
+
if (options.trimSpaces && options.trimSpaces === true) {
|
|
2168
|
+
state.value = state.value.trim();
|
|
2169
|
+
}
|
|
2170
|
+
}
|
|
1938
2171
|
state.info.value = state.value;
|
|
1939
2172
|
this.setTestData({ [variable]: state.value }, world);
|
|
1940
2173
|
this.logger.info("set test data: " + variable + "=" + state.value);
|
|
@@ -1945,7 +2178,7 @@ class StableBrowser {
|
|
|
1945
2178
|
await _commandError(state, e, this);
|
|
1946
2179
|
}
|
|
1947
2180
|
finally {
|
|
1948
|
-
_commandFinally(state, this);
|
|
2181
|
+
await _commandFinally(state, this);
|
|
1949
2182
|
}
|
|
1950
2183
|
}
|
|
1951
2184
|
async verifyAttribute(selectors, attribute, value, _params = null, options = {}, world = null) {
|
|
@@ -1970,12 +2203,15 @@ class StableBrowser {
|
|
|
1970
2203
|
let expectedValue;
|
|
1971
2204
|
try {
|
|
1972
2205
|
await _preCommand(state, this);
|
|
1973
|
-
expectedValue = state.value;
|
|
2206
|
+
expectedValue = await replaceWithLocalTestData(state.value, world);
|
|
1974
2207
|
state.info.expectedValue = expectedValue;
|
|
1975
2208
|
switch (attribute) {
|
|
1976
2209
|
case "innerText":
|
|
1977
2210
|
val = String(await state.element.innerText());
|
|
1978
2211
|
break;
|
|
2212
|
+
case "text":
|
|
2213
|
+
val = String(await state.element.textContent());
|
|
2214
|
+
break;
|
|
1979
2215
|
case "value":
|
|
1980
2216
|
val = String(await state.element.inputValue());
|
|
1981
2217
|
break;
|
|
@@ -1997,17 +2233,42 @@ class StableBrowser {
|
|
|
1997
2233
|
let regex;
|
|
1998
2234
|
if (expectedValue.startsWith("/") && expectedValue.endsWith("/")) {
|
|
1999
2235
|
const patternBody = expectedValue.slice(1, -1);
|
|
2000
|
-
|
|
2236
|
+
const processedPattern = patternBody.replace(/\n/g, ".*");
|
|
2237
|
+
regex = new RegExp(processedPattern, "gs");
|
|
2238
|
+
state.info.regex = true;
|
|
2001
2239
|
}
|
|
2002
2240
|
else {
|
|
2003
2241
|
const escapedPattern = expectedValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2004
2242
|
regex = new RegExp(escapedPattern, "g");
|
|
2005
2243
|
}
|
|
2006
|
-
if (
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2244
|
+
if (attribute === "innerText") {
|
|
2245
|
+
if (state.info.regex) {
|
|
2246
|
+
if (!regex.test(val)) {
|
|
2247
|
+
let errorMessage = `The ${attribute} attribute has a value of "${val}", but the expected value is "${expectedValue}"`;
|
|
2248
|
+
state.info.failCause.assertionFailed = true;
|
|
2249
|
+
state.info.failCause.lastError = errorMessage;
|
|
2250
|
+
throw new Error(errorMessage);
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
2253
|
+
else {
|
|
2254
|
+
const valLines = val.split("\n");
|
|
2255
|
+
const expectedLines = expectedValue.split("\n");
|
|
2256
|
+
const isPart = expectedLines.every((expectedLine) => valLines.some((valLine) => valLine === expectedLine));
|
|
2257
|
+
if (!isPart) {
|
|
2258
|
+
let errorMessage = `The ${attribute} attribute has a value of "${val}", but the expected value is "${expectedValue}"`;
|
|
2259
|
+
state.info.failCause.assertionFailed = true;
|
|
2260
|
+
state.info.failCause.lastError = errorMessage;
|
|
2261
|
+
throw new Error(errorMessage);
|
|
2262
|
+
}
|
|
2263
|
+
}
|
|
2264
|
+
}
|
|
2265
|
+
else {
|
|
2266
|
+
if (!val.match(regex)) {
|
|
2267
|
+
let errorMessage = `The ${attribute} attribute has a value of "${val}", but the expected value is "${expectedValue}"`;
|
|
2268
|
+
state.info.failCause.assertionFailed = true;
|
|
2269
|
+
state.info.failCause.lastError = errorMessage;
|
|
2270
|
+
throw new Error(errorMessage);
|
|
2271
|
+
}
|
|
2011
2272
|
}
|
|
2012
2273
|
return state.info;
|
|
2013
2274
|
}
|
|
@@ -2015,7 +2276,7 @@ class StableBrowser {
|
|
|
2015
2276
|
await _commandError(state, e, this);
|
|
2016
2277
|
}
|
|
2017
2278
|
finally {
|
|
2018
|
-
_commandFinally(state, this);
|
|
2279
|
+
await _commandFinally(state, this);
|
|
2019
2280
|
}
|
|
2020
2281
|
}
|
|
2021
2282
|
async extractEmailData(emailAddress, options, world) {
|
|
@@ -2175,56 +2436,49 @@ class StableBrowser {
|
|
|
2175
2436
|
console.debug(error);
|
|
2176
2437
|
}
|
|
2177
2438
|
}
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
// });
|
|
2221
|
-
// }
|
|
2222
|
-
// } catch (error) {
|
|
2223
|
-
// // console.debug(error);
|
|
2224
|
-
// }
|
|
2225
|
-
// }
|
|
2439
|
+
_matcher(text) {
|
|
2440
|
+
if (!text) {
|
|
2441
|
+
return { matcher: "contains", queryText: "" };
|
|
2442
|
+
}
|
|
2443
|
+
if (text.length < 2) {
|
|
2444
|
+
return { matcher: "contains", queryText: text };
|
|
2445
|
+
}
|
|
2446
|
+
const split = text.split(":");
|
|
2447
|
+
const matcher = split[0].toLowerCase();
|
|
2448
|
+
const queryText = split.slice(1).join(":").trim();
|
|
2449
|
+
return { matcher, queryText };
|
|
2450
|
+
}
|
|
2451
|
+
_getDomain(url) {
|
|
2452
|
+
if (url.length === 0 || (!url.startsWith("http://") && !url.startsWith("https://"))) {
|
|
2453
|
+
return "";
|
|
2454
|
+
}
|
|
2455
|
+
let hostnameFragments = url.split("/")[2].split(".");
|
|
2456
|
+
if (hostnameFragments.some((fragment) => fragment.includes(":"))) {
|
|
2457
|
+
return hostnameFragments.join("-").split(":").join("-");
|
|
2458
|
+
}
|
|
2459
|
+
let n = hostnameFragments.length;
|
|
2460
|
+
let fragments = [...hostnameFragments];
|
|
2461
|
+
while (n > 0 && hostnameFragments[n - 1].length <= 3) {
|
|
2462
|
+
hostnameFragments.pop();
|
|
2463
|
+
n = hostnameFragments.length;
|
|
2464
|
+
}
|
|
2465
|
+
if (n == 0) {
|
|
2466
|
+
if (fragments[0] === "www")
|
|
2467
|
+
fragments = fragments.slice(1);
|
|
2468
|
+
return fragments.length > 1 ? fragments.slice(0, fragments.length - 1).join("-") : fragments.join("-");
|
|
2469
|
+
}
|
|
2470
|
+
if (hostnameFragments[0] === "www")
|
|
2471
|
+
hostnameFragments = hostnameFragments.slice(1);
|
|
2472
|
+
return hostnameFragments.join(".");
|
|
2473
|
+
}
|
|
2474
|
+
/**
|
|
2475
|
+
* Verify the page path matches the given path.
|
|
2476
|
+
* @param {string} pathPart - The path to verify.
|
|
2477
|
+
* @param {object} options - Options for verification.
|
|
2478
|
+
* @param {object} world - The world context.
|
|
2479
|
+
* @returns {Promise<object>} - The state info after verification.
|
|
2480
|
+
*/
|
|
2226
2481
|
async verifyPagePath(pathPart, options = {}, world = null) {
|
|
2227
|
-
const startTime = Date.now();
|
|
2228
2482
|
let error = null;
|
|
2229
2483
|
let screenshotId = null;
|
|
2230
2484
|
let screenshotPath = null;
|
|
@@ -2238,51 +2492,212 @@ class StableBrowser {
|
|
|
2238
2492
|
pathPart = newValue;
|
|
2239
2493
|
}
|
|
2240
2494
|
info.pathPart = pathPart;
|
|
2495
|
+
const { matcher, queryText } = this._matcher(pathPart);
|
|
2496
|
+
const state = {
|
|
2497
|
+
text_search: queryText,
|
|
2498
|
+
options,
|
|
2499
|
+
world,
|
|
2500
|
+
locate: false,
|
|
2501
|
+
scroll: false,
|
|
2502
|
+
highlight: false,
|
|
2503
|
+
type: Types.VERIFY_PAGE_PATH,
|
|
2504
|
+
text: `Verify the page url is ${queryText}`,
|
|
2505
|
+
_text: `Verify the page url is ${queryText}`,
|
|
2506
|
+
operation: "verifyPagePath",
|
|
2507
|
+
log: "***** verify page url is " + queryText + " *****\n",
|
|
2508
|
+
};
|
|
2241
2509
|
try {
|
|
2510
|
+
await _preCommand(state, this);
|
|
2511
|
+
state.info.text = queryText;
|
|
2242
2512
|
for (let i = 0; i < 30; i++) {
|
|
2243
2513
|
const url = await this.page.url();
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2514
|
+
switch (matcher) {
|
|
2515
|
+
case "exact":
|
|
2516
|
+
if (url !== queryText) {
|
|
2517
|
+
if (i === 29) {
|
|
2518
|
+
throw new Error(`Page URL ${url} is not equal to ${queryText}`);
|
|
2519
|
+
}
|
|
2520
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2521
|
+
continue;
|
|
2522
|
+
}
|
|
2523
|
+
break;
|
|
2524
|
+
case "contains":
|
|
2525
|
+
if (!url.includes(queryText)) {
|
|
2526
|
+
if (i === 29) {
|
|
2527
|
+
throw new Error(`Page URL ${url} doesn't contain ${queryText}`);
|
|
2528
|
+
}
|
|
2529
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2530
|
+
continue;
|
|
2531
|
+
}
|
|
2532
|
+
break;
|
|
2533
|
+
case "starts-with":
|
|
2534
|
+
{
|
|
2535
|
+
const domain = this._getDomain(url);
|
|
2536
|
+
if (domain.length > 0 && domain !== queryText) {
|
|
2537
|
+
if (i === 29) {
|
|
2538
|
+
throw new Error(`Page URL ${url} doesn't start with ${queryText}`);
|
|
2539
|
+
}
|
|
2540
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2541
|
+
continue;
|
|
2542
|
+
}
|
|
2543
|
+
}
|
|
2544
|
+
break;
|
|
2545
|
+
case "ends-with":
|
|
2546
|
+
{
|
|
2547
|
+
const urlObj = new URL(url);
|
|
2548
|
+
let route = "/";
|
|
2549
|
+
if (urlObj.pathname !== "/") {
|
|
2550
|
+
route = urlObj.pathname.split("/").slice(-1)[0].trim();
|
|
2551
|
+
}
|
|
2552
|
+
else {
|
|
2553
|
+
route = "/";
|
|
2554
|
+
}
|
|
2555
|
+
if (route !== queryText) {
|
|
2556
|
+
if (i === 29) {
|
|
2557
|
+
throw new Error(`Page URL ${url} doesn't end with ${queryText}`);
|
|
2558
|
+
}
|
|
2559
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2560
|
+
continue;
|
|
2561
|
+
}
|
|
2562
|
+
}
|
|
2563
|
+
break;
|
|
2564
|
+
case "regex":
|
|
2565
|
+
const regex = new RegExp(queryText.slice(1, -1), "g");
|
|
2566
|
+
if (!regex.test(url)) {
|
|
2567
|
+
if (i === 29) {
|
|
2568
|
+
throw new Error(`Page URL ${url} doesn't match regex ${queryText}`);
|
|
2569
|
+
}
|
|
2570
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2571
|
+
continue;
|
|
2572
|
+
}
|
|
2573
|
+
break;
|
|
2574
|
+
default:
|
|
2575
|
+
console.log("Unknown matching type, defaulting to contains matching");
|
|
2576
|
+
if (!url.includes(pathPart)) {
|
|
2577
|
+
if (i === 29) {
|
|
2578
|
+
throw new Error(`Page URL ${url} does not contain ${pathPart}`);
|
|
2579
|
+
}
|
|
2580
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2581
|
+
continue;
|
|
2582
|
+
}
|
|
2250
2583
|
}
|
|
2251
|
-
|
|
2252
|
-
return info;
|
|
2584
|
+
await _screenshot(state, this);
|
|
2585
|
+
return state.info;
|
|
2253
2586
|
}
|
|
2254
2587
|
}
|
|
2255
2588
|
catch (e) {
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
info.screenshotPath = screenshotPath;
|
|
2260
|
-
Object.assign(e, { info: info });
|
|
2261
|
-
error = e;
|
|
2262
|
-
// throw e;
|
|
2263
|
-
await _commandError({ text: "verifyPagePath", operation: "verifyPagePath", pathPart, info }, e, this);
|
|
2589
|
+
state.info.failCause.lastError = e.message;
|
|
2590
|
+
state.info.failCause.assertionFailed = true;
|
|
2591
|
+
await _commandError(state, e, this);
|
|
2264
2592
|
}
|
|
2265
2593
|
finally {
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2594
|
+
await _commandFinally(state, this);
|
|
2595
|
+
}
|
|
2596
|
+
}
|
|
2597
|
+
/**
|
|
2598
|
+
* Verify the page title matches the given title.
|
|
2599
|
+
* @param {string} title - The title to verify.
|
|
2600
|
+
* @param {object} options - Options for verification.
|
|
2601
|
+
* @param {object} world - The world context.
|
|
2602
|
+
* @returns {Promise<object>} - The state info after verification.
|
|
2603
|
+
*/
|
|
2604
|
+
async verifyPageTitle(title, options = {}, world = null) {
|
|
2605
|
+
let error = null;
|
|
2606
|
+
let screenshotId = null;
|
|
2607
|
+
let screenshotPath = null;
|
|
2608
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2609
|
+
const newValue = await this._replaceWithLocalData(title, world);
|
|
2610
|
+
if (newValue !== title) {
|
|
2611
|
+
this.logger.info(title + "=" + newValue);
|
|
2612
|
+
title = newValue;
|
|
2613
|
+
}
|
|
2614
|
+
const { matcher, queryText } = this._matcher(title);
|
|
2615
|
+
const state = {
|
|
2616
|
+
text_search: queryText,
|
|
2617
|
+
options,
|
|
2618
|
+
world,
|
|
2619
|
+
locate: false,
|
|
2620
|
+
scroll: false,
|
|
2621
|
+
highlight: false,
|
|
2622
|
+
type: Types.VERIFY_PAGE_TITLE,
|
|
2623
|
+
text: `Verify the page title is ${queryText}`,
|
|
2624
|
+
_text: `Verify the page title is ${queryText}`,
|
|
2625
|
+
operation: "verifyPageTitle",
|
|
2626
|
+
log: "***** verify page title is " + queryText + " *****\n",
|
|
2627
|
+
};
|
|
2628
|
+
try {
|
|
2629
|
+
await _preCommand(state, this);
|
|
2630
|
+
state.info.text = queryText;
|
|
2631
|
+
for (let i = 0; i < 30; i++) {
|
|
2632
|
+
const foundTitle = await this.page.title();
|
|
2633
|
+
switch (matcher) {
|
|
2634
|
+
case "exact":
|
|
2635
|
+
if (foundTitle !== queryText) {
|
|
2636
|
+
if (i === 29) {
|
|
2637
|
+
throw new Error(`Page Title ${foundTitle} is not equal to ${queryText}`);
|
|
2638
|
+
}
|
|
2639
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2640
|
+
continue;
|
|
2641
|
+
}
|
|
2642
|
+
break;
|
|
2643
|
+
case "contains":
|
|
2644
|
+
if (!foundTitle.includes(queryText)) {
|
|
2645
|
+
if (i === 29) {
|
|
2646
|
+
throw new Error(`Page Title ${foundTitle} doesn't contain ${queryText}`);
|
|
2647
|
+
}
|
|
2648
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2649
|
+
continue;
|
|
2650
|
+
}
|
|
2651
|
+
break;
|
|
2652
|
+
case "starts-with":
|
|
2653
|
+
if (!foundTitle.startsWith(queryText)) {
|
|
2654
|
+
if (i === 29) {
|
|
2655
|
+
throw new Error(`Page title ${foundTitle} doesn't start with ${queryText}`);
|
|
2656
|
+
}
|
|
2657
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2658
|
+
continue;
|
|
2659
|
+
}
|
|
2660
|
+
break;
|
|
2661
|
+
case "ends-with":
|
|
2662
|
+
if (!foundTitle.endsWith(queryText)) {
|
|
2663
|
+
if (i === 29) {
|
|
2664
|
+
throw new Error(`Page Title ${foundTitle} doesn't end with ${queryText}`);
|
|
2665
|
+
}
|
|
2666
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2667
|
+
continue;
|
|
2668
|
+
}
|
|
2669
|
+
break;
|
|
2670
|
+
case "regex":
|
|
2671
|
+
const regex = new RegExp(queryText.slice(1, -1), "g");
|
|
2672
|
+
if (!regex.test(foundTitle)) {
|
|
2673
|
+
if (i === 29) {
|
|
2674
|
+
throw new Error(`Page Title ${foundTitle} doesn't match regex ${queryText}`);
|
|
2675
|
+
}
|
|
2676
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2677
|
+
continue;
|
|
2678
|
+
}
|
|
2679
|
+
break;
|
|
2680
|
+
default:
|
|
2681
|
+
console.log("Unknown matching type, defaulting to contains matching");
|
|
2682
|
+
if (!foundTitle.includes(title)) {
|
|
2683
|
+
if (i === 29) {
|
|
2684
|
+
throw new Error(`Page Title ${foundTitle} does not contain ${title}`);
|
|
2685
|
+
}
|
|
2686
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2687
|
+
continue;
|
|
2688
|
+
}
|
|
2689
|
+
}
|
|
2690
|
+
await _screenshot(state, this);
|
|
2691
|
+
return state.info;
|
|
2692
|
+
}
|
|
2693
|
+
}
|
|
2694
|
+
catch (e) {
|
|
2695
|
+
state.info.failCause.lastError = e.message;
|
|
2696
|
+
state.info.failCause.assertionFailed = true;
|
|
2697
|
+
await _commandError(state, e, this);
|
|
2698
|
+
}
|
|
2699
|
+
finally {
|
|
2700
|
+
await _commandFinally(state, this);
|
|
2286
2701
|
}
|
|
2287
2702
|
}
|
|
2288
2703
|
async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state, partial = true, ignoreCase = false) {
|
|
@@ -2324,7 +2739,7 @@ class StableBrowser {
|
|
|
2324
2739
|
scroll: false,
|
|
2325
2740
|
highlight: false,
|
|
2326
2741
|
type: Types.VERIFY_PAGE_CONTAINS_TEXT,
|
|
2327
|
-
text: `Verify the text '${text}' exists in page`,
|
|
2742
|
+
text: `Verify the text '${maskValue(text)}' exists in page`,
|
|
2328
2743
|
_text: `Verify the text '${text}' exists in page`,
|
|
2329
2744
|
operation: "verifyTextExistInPage",
|
|
2330
2745
|
log: "***** verify text " + text + " exists in page *****\n",
|
|
@@ -2366,27 +2781,10 @@ class StableBrowser {
|
|
|
2366
2781
|
const frame = resultWithElementsFound[0].frame;
|
|
2367
2782
|
const dataAttribute = `[data-blinq-id-${resultWithElementsFound[0].randomToken}]`;
|
|
2368
2783
|
await this._highlightElements(frame, dataAttribute);
|
|
2369
|
-
// if (world && world.screenshot && !world.screenshotPath) {
|
|
2370
|
-
// console.log(`Highlighting for verify text is found while running from recorder`);
|
|
2371
|
-
// this._highlightElements(frame, dataAttribute).then(async () => {
|
|
2372
|
-
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2373
|
-
// this._unhighlightElements(frame, dataAttribute)
|
|
2374
|
-
// .then(async () => {
|
|
2375
|
-
// console.log(`Unhighlighted frame dataAttribute successfully`);
|
|
2376
|
-
// })
|
|
2377
|
-
// .catch(
|
|
2378
|
-
// (e) => {}
|
|
2379
|
-
// console.error(e)
|
|
2380
|
-
// );
|
|
2381
|
-
// });
|
|
2382
|
-
// }
|
|
2383
2784
|
const element = await frame.locator(dataAttribute).first();
|
|
2384
|
-
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2385
|
-
// await this._unhighlightElements(frame, dataAttribute);
|
|
2386
2785
|
if (element) {
|
|
2387
2786
|
await this.scrollIfNeeded(element, state.info);
|
|
2388
2787
|
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2389
|
-
// await _screenshot(state, this, element);
|
|
2390
2788
|
}
|
|
2391
2789
|
}
|
|
2392
2790
|
await _screenshot(state, this);
|
|
@@ -2396,13 +2794,12 @@ class StableBrowser {
|
|
|
2396
2794
|
console.error(error);
|
|
2397
2795
|
}
|
|
2398
2796
|
}
|
|
2399
|
-
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2400
2797
|
}
|
|
2401
2798
|
catch (e) {
|
|
2402
2799
|
await _commandError(state, e, this);
|
|
2403
2800
|
}
|
|
2404
2801
|
finally {
|
|
2405
|
-
_commandFinally(state, this);
|
|
2802
|
+
await _commandFinally(state, this);
|
|
2406
2803
|
}
|
|
2407
2804
|
}
|
|
2408
2805
|
async waitForTextToDisappear(text, options = {}, world = null) {
|
|
@@ -2415,7 +2812,7 @@ class StableBrowser {
|
|
|
2415
2812
|
scroll: false,
|
|
2416
2813
|
highlight: false,
|
|
2417
2814
|
type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
|
|
2418
|
-
text: `Verify text does not exist in page`,
|
|
2815
|
+
text: `Verify the text '${maskValue(text)}' does not exist in page`,
|
|
2419
2816
|
_text: `Verify the text '${text}' does not exist in page`,
|
|
2420
2817
|
operation: "verifyTextNotExistInPage",
|
|
2421
2818
|
log: "***** verify text " + text + " does not exist in page *****\n",
|
|
@@ -2459,7 +2856,7 @@ class StableBrowser {
|
|
|
2459
2856
|
await _commandError(state, e, this);
|
|
2460
2857
|
}
|
|
2461
2858
|
finally {
|
|
2462
|
-
_commandFinally(state, this);
|
|
2859
|
+
await _commandFinally(state, this);
|
|
2463
2860
|
}
|
|
2464
2861
|
}
|
|
2465
2862
|
async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
|
|
@@ -2529,7 +2926,7 @@ class StableBrowser {
|
|
|
2529
2926
|
const count = await frame.locator(css).count();
|
|
2530
2927
|
for (let j = 0; j < count; j++) {
|
|
2531
2928
|
const continer = await frame.locator(css).nth(j);
|
|
2532
|
-
const result = await this._locateElementByText(continer, textToVerify, "*:not(script, style, head)", false,
|
|
2929
|
+
const result = await this._locateElementByText(continer, textToVerify, "*:not(script, style, head)", false, true, true, {});
|
|
2533
2930
|
if (result.elementCount > 0) {
|
|
2534
2931
|
const dataAttribute = "[data-blinq-id-" + result.randomToken + "]";
|
|
2535
2932
|
await this._highlightElements(frame, dataAttribute);
|
|
@@ -2570,7 +2967,7 @@ class StableBrowser {
|
|
|
2570
2967
|
await _commandError(state, e, this);
|
|
2571
2968
|
}
|
|
2572
2969
|
finally {
|
|
2573
|
-
_commandFinally(state, this);
|
|
2970
|
+
await _commandFinally(state, this);
|
|
2574
2971
|
}
|
|
2575
2972
|
}
|
|
2576
2973
|
async findRelatedTextInAllFrames(textAnchor, climb, textToVerify, params = {}, options = {}, world = null) {
|
|
@@ -2913,7 +3310,13 @@ class StableBrowser {
|
|
|
2913
3310
|
}
|
|
2914
3311
|
}
|
|
2915
3312
|
async _replaceWithLocalData(value, world, _decrypt = true, totpWait = true) {
|
|
2916
|
-
|
|
3313
|
+
try {
|
|
3314
|
+
return await replaceWithLocalTestData(value, world, _decrypt, totpWait, this.context, this);
|
|
3315
|
+
}
|
|
3316
|
+
catch (error) {
|
|
3317
|
+
this.logger.debug(error);
|
|
3318
|
+
throw error;
|
|
3319
|
+
}
|
|
2917
3320
|
}
|
|
2918
3321
|
_getLoadTimeout(options) {
|
|
2919
3322
|
let timeout = 15000;
|
|
@@ -2936,6 +3339,7 @@ class StableBrowser {
|
|
|
2936
3339
|
}
|
|
2937
3340
|
async saveStoreState(path = null, world = null) {
|
|
2938
3341
|
const storageState = await this.page.context().storageState();
|
|
3342
|
+
path = await this._replaceWithLocalData(path, this.world);
|
|
2939
3343
|
//const testDataFile = _getDataFile(world, this.context, this);
|
|
2940
3344
|
if (path) {
|
|
2941
3345
|
// save { storageState: storageState } into the path
|
|
@@ -2946,10 +3350,14 @@ class StableBrowser {
|
|
|
2946
3350
|
}
|
|
2947
3351
|
}
|
|
2948
3352
|
async restoreSaveState(path = null, world = null) {
|
|
3353
|
+
path = await this._replaceWithLocalData(path, this.world);
|
|
2949
3354
|
await refreshBrowser(this, path, world);
|
|
2950
3355
|
this.registerEventListeners(this.context);
|
|
2951
3356
|
registerNetworkEvents(this.world, this, this.context, this.page);
|
|
2952
3357
|
registerDownloadEvent(this.page, this.world, this.context);
|
|
3358
|
+
if (this.onRestoreSaveState) {
|
|
3359
|
+
this.onRestoreSaveState(path);
|
|
3360
|
+
}
|
|
2953
3361
|
}
|
|
2954
3362
|
async waitForPageLoad(options = {}, world = null) {
|
|
2955
3363
|
let timeout = this._getLoadTimeout(options);
|
|
@@ -3032,7 +3440,7 @@ class StableBrowser {
|
|
|
3032
3440
|
await _commandError(state, e, this);
|
|
3033
3441
|
}
|
|
3034
3442
|
finally {
|
|
3035
|
-
_commandFinally(state, this);
|
|
3443
|
+
await _commandFinally(state, this);
|
|
3036
3444
|
}
|
|
3037
3445
|
}
|
|
3038
3446
|
async tableCellOperation(headerText, rowText, options, _params, world = null) {
|
|
@@ -3119,7 +3527,7 @@ class StableBrowser {
|
|
|
3119
3527
|
await _commandError(state, e, this);
|
|
3120
3528
|
}
|
|
3121
3529
|
finally {
|
|
3122
|
-
_commandFinally(state, this);
|
|
3530
|
+
await _commandFinally(state, this);
|
|
3123
3531
|
}
|
|
3124
3532
|
}
|
|
3125
3533
|
saveTestDataAsGlobal(options, world) {
|
|
@@ -3224,7 +3632,39 @@ class StableBrowser {
|
|
|
3224
3632
|
console.log("#-#");
|
|
3225
3633
|
}
|
|
3226
3634
|
}
|
|
3635
|
+
async beforeScenario(world, scenario) {
|
|
3636
|
+
this.beforeScenarioCalled = true;
|
|
3637
|
+
if (scenario && scenario.pickle && scenario.pickle.name) {
|
|
3638
|
+
this.scenarioName = scenario.pickle.name;
|
|
3639
|
+
}
|
|
3640
|
+
if (scenario && scenario.gherkinDocument && scenario.gherkinDocument.feature) {
|
|
3641
|
+
this.featureName = scenario.gherkinDocument.feature.name;
|
|
3642
|
+
}
|
|
3643
|
+
if (this.context) {
|
|
3644
|
+
this.context.examplesRow = extractStepExampleParameters(scenario);
|
|
3645
|
+
}
|
|
3646
|
+
if (this.tags === null && scenario && scenario.pickle && scenario.pickle.tags) {
|
|
3647
|
+
this.tags = scenario.pickle.tags.map((tag) => tag.name);
|
|
3648
|
+
// check if @global_test_data tag is present
|
|
3649
|
+
if (this.tags.includes("@global_test_data")) {
|
|
3650
|
+
this.saveTestDataAsGlobal({}, world);
|
|
3651
|
+
}
|
|
3652
|
+
}
|
|
3653
|
+
// update test data based on feature/scenario
|
|
3654
|
+
let envName = null;
|
|
3655
|
+
if (this.context && this.context.environment) {
|
|
3656
|
+
envName = this.context.environment.name;
|
|
3657
|
+
}
|
|
3658
|
+
if (!process.env.TEMP_RUN) {
|
|
3659
|
+
await getTestData(envName, world, undefined, this.featureName, this.scenarioName, this.context);
|
|
3660
|
+
}
|
|
3661
|
+
await loadBrunoParams(this.context, this.context.environment.name);
|
|
3662
|
+
}
|
|
3663
|
+
async afterScenario(world, scenario) { }
|
|
3227
3664
|
async beforeStep(world, step) {
|
|
3665
|
+
if (!this.beforeScenarioCalled) {
|
|
3666
|
+
this.beforeScenario(world, step);
|
|
3667
|
+
}
|
|
3228
3668
|
if (this.stepIndex === undefined) {
|
|
3229
3669
|
this.stepIndex = 0;
|
|
3230
3670
|
}
|
|
@@ -3241,24 +3681,14 @@ class StableBrowser {
|
|
|
3241
3681
|
else {
|
|
3242
3682
|
this.stepName = "step " + this.stepIndex;
|
|
3243
3683
|
}
|
|
3244
|
-
if (this.context) {
|
|
3245
|
-
this.context.examplesRow = extractStepExampleParameters(step);
|
|
3246
|
-
}
|
|
3247
3684
|
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
3248
3685
|
if (this.context.browserObject.context) {
|
|
3249
3686
|
await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
|
|
3250
3687
|
}
|
|
3251
3688
|
}
|
|
3252
|
-
if (this.tags === null && step && step.pickle && step.pickle.tags) {
|
|
3253
|
-
this.tags = step.pickle.tags.map((tag) => tag.name);
|
|
3254
|
-
// check if @global_test_data tag is present
|
|
3255
|
-
if (this.tags.includes("@global_test_data")) {
|
|
3256
|
-
this.saveTestDataAsGlobal({}, world);
|
|
3257
|
-
}
|
|
3258
|
-
}
|
|
3259
3689
|
if (this.initSnapshotTaken === false) {
|
|
3260
3690
|
this.initSnapshotTaken = true;
|
|
3261
|
-
if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
|
|
3691
|
+
if (world && world.attach && !process.env.DISABLE_SNAPSHOT && !this.fastMode) {
|
|
3262
3692
|
const snapshot = await this.getAriaSnapshot();
|
|
3263
3693
|
if (snapshot) {
|
|
3264
3694
|
await world.attach(JSON.stringify(snapshot), "application/json+snapshot-before");
|
|
@@ -3280,18 +3710,68 @@ class StableBrowser {
|
|
|
3280
3710
|
const content = [`- path: ${path}`, `- title: ${title}`];
|
|
3281
3711
|
const timeout = this.configuration.ariaSnapshotTimeout ? this.configuration.ariaSnapshotTimeout : 3000;
|
|
3282
3712
|
for (let i = 0; i < frames.length; i++) {
|
|
3283
|
-
content.push(`- frame: ${i}`);
|
|
3284
3713
|
const frame = frames[i];
|
|
3285
|
-
|
|
3286
|
-
|
|
3714
|
+
try {
|
|
3715
|
+
// Ensure frame is attached and has body
|
|
3716
|
+
const body = frame.locator("body");
|
|
3717
|
+
await body.waitFor({ timeout: 200 }); // wait explicitly
|
|
3718
|
+
const snapshot = await body.ariaSnapshot({ timeout });
|
|
3719
|
+
content.push(`- frame: ${i}`);
|
|
3720
|
+
content.push(snapshot);
|
|
3721
|
+
}
|
|
3722
|
+
catch (innerErr) { }
|
|
3287
3723
|
}
|
|
3288
3724
|
return content.join("\n");
|
|
3289
3725
|
}
|
|
3290
3726
|
catch (e) {
|
|
3291
|
-
console.
|
|
3727
|
+
console.log("Error in getAriaSnapshot");
|
|
3728
|
+
//console.debug(e);
|
|
3292
3729
|
}
|
|
3293
3730
|
return null;
|
|
3294
3731
|
}
|
|
3732
|
+
/**
|
|
3733
|
+
* Sends command with custom payload to report.
|
|
3734
|
+
* @param commandText - Title of the command to be shown in the report.
|
|
3735
|
+
* @param commandStatus - Status of the command (e.g. "PASSED", "FAILED").
|
|
3736
|
+
* @param content - Content of the command to be shown in the report.
|
|
3737
|
+
* @param options - Options for the command. Example: { type: "json", screenshot: true }
|
|
3738
|
+
* @param world - Optional world context.
|
|
3739
|
+
* @public
|
|
3740
|
+
*/
|
|
3741
|
+
async addCommandToReport(commandText, commandStatus, content, options = {}, world = null) {
|
|
3742
|
+
const state = {
|
|
3743
|
+
options,
|
|
3744
|
+
world,
|
|
3745
|
+
locate: false,
|
|
3746
|
+
scroll: false,
|
|
3747
|
+
screenshot: options.screenshot ?? false,
|
|
3748
|
+
highlight: options.highlight ?? false,
|
|
3749
|
+
type: Types.REPORT_COMMAND,
|
|
3750
|
+
text: commandText,
|
|
3751
|
+
_text: commandText,
|
|
3752
|
+
operation: "report_command",
|
|
3753
|
+
log: "***** " + commandText + " *****\n",
|
|
3754
|
+
};
|
|
3755
|
+
try {
|
|
3756
|
+
await _preCommand(state, this);
|
|
3757
|
+
const payload = {
|
|
3758
|
+
type: options.type ?? "text",
|
|
3759
|
+
content: content,
|
|
3760
|
+
screenshotId: null,
|
|
3761
|
+
};
|
|
3762
|
+
state.payload = payload;
|
|
3763
|
+
if (commandStatus === "FAILED") {
|
|
3764
|
+
state.throwError = true;
|
|
3765
|
+
throw new Error("Command failed");
|
|
3766
|
+
}
|
|
3767
|
+
}
|
|
3768
|
+
catch (e) {
|
|
3769
|
+
await _commandError(state, e, this);
|
|
3770
|
+
}
|
|
3771
|
+
finally {
|
|
3772
|
+
await _commandFinally(state, this);
|
|
3773
|
+
}
|
|
3774
|
+
}
|
|
3295
3775
|
async afterStep(world, step) {
|
|
3296
3776
|
this.stepName = null;
|
|
3297
3777
|
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
@@ -3299,6 +3779,13 @@ class StableBrowser {
|
|
|
3299
3779
|
await this.context.browserObject.context.tracing.stopChunk({
|
|
3300
3780
|
path: path.join(this.context.browserObject.traceFolder, `trace-${this.stepIndex}.zip`),
|
|
3301
3781
|
});
|
|
3782
|
+
if (world && world.attach) {
|
|
3783
|
+
await world.attach(JSON.stringify({
|
|
3784
|
+
type: "trace",
|
|
3785
|
+
traceFilePath: `trace-${this.stepIndex}.zip`,
|
|
3786
|
+
}), "application/json+trace");
|
|
3787
|
+
}
|
|
3788
|
+
// console.log("trace file created", `trace-${this.stepIndex}.zip`);
|
|
3302
3789
|
}
|
|
3303
3790
|
}
|
|
3304
3791
|
if (this.context) {
|
|
@@ -3311,6 +3798,29 @@ class StableBrowser {
|
|
|
3311
3798
|
await world.attach(JSON.stringify(snapshot), "application/json+snapshot-after");
|
|
3312
3799
|
}
|
|
3313
3800
|
}
|
|
3801
|
+
if (!process.env.TEMP_RUN) {
|
|
3802
|
+
const state = {
|
|
3803
|
+
world,
|
|
3804
|
+
locate: false,
|
|
3805
|
+
scroll: false,
|
|
3806
|
+
screenshot: true,
|
|
3807
|
+
highlight: true,
|
|
3808
|
+
type: Types.STEP_COMPLETE,
|
|
3809
|
+
text: "end of scenario",
|
|
3810
|
+
_text: "end of scenario",
|
|
3811
|
+
operation: "step_complete",
|
|
3812
|
+
log: "***** " + "end of scenario" + " *****\n",
|
|
3813
|
+
};
|
|
3814
|
+
try {
|
|
3815
|
+
await _preCommand(state, this);
|
|
3816
|
+
}
|
|
3817
|
+
catch (e) {
|
|
3818
|
+
await _commandError(state, e, this);
|
|
3819
|
+
}
|
|
3820
|
+
finally {
|
|
3821
|
+
await _commandFinally(state, this);
|
|
3822
|
+
}
|
|
3823
|
+
}
|
|
3314
3824
|
}
|
|
3315
3825
|
}
|
|
3316
3826
|
function createTimedPromise(promise, label) {
|