automation_model 1.0.644-dev → 1.0.644-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 +145 -59
- 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 +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 +29 -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 +46 -23
- package/lib/stable_browser.js +683 -187
- 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 +176 -59
- 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) => {
|
|
@@ -118,6 +128,9 @@ class StableBrowser {
|
|
|
118
128
|
if (!context.pageLoading) {
|
|
119
129
|
context.pageLoading = { status: false };
|
|
120
130
|
}
|
|
131
|
+
if (this.configuration && this.configuration.acceptDialog && this.page) {
|
|
132
|
+
this.page.on("dialog", (dialog) => dialog.accept());
|
|
133
|
+
}
|
|
121
134
|
context.playContext.on("page", async function (page) {
|
|
122
135
|
if (this.configuration && this.configuration.closePopups === true) {
|
|
123
136
|
console.log("close unexpected popups");
|
|
@@ -126,6 +139,14 @@ class StableBrowser {
|
|
|
126
139
|
}
|
|
127
140
|
context.pageLoading.status = true;
|
|
128
141
|
this.page = page;
|
|
142
|
+
try {
|
|
143
|
+
if (this.configuration && this.configuration.acceptDialog) {
|
|
144
|
+
await page.on("dialog", (dialog) => dialog.accept());
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
console.error("Error on dialog accept registration", error);
|
|
149
|
+
}
|
|
129
150
|
context.page = page;
|
|
130
151
|
context.pages.push(page);
|
|
131
152
|
registerNetworkEvents(this.world, this, context, this.page);
|
|
@@ -180,6 +201,30 @@ class StableBrowser {
|
|
|
180
201
|
await this.waitForPageLoad();
|
|
181
202
|
}
|
|
182
203
|
}
|
|
204
|
+
async switchTab(tabTitleOrIndex) {
|
|
205
|
+
// first check if the tabNameOrIndex is a number
|
|
206
|
+
let index = parseInt(tabTitleOrIndex);
|
|
207
|
+
if (!isNaN(index)) {
|
|
208
|
+
if (index >= 0 && index < this.context.pages.length) {
|
|
209
|
+
this.page = this.context.pages[index];
|
|
210
|
+
this.context.page = this.page;
|
|
211
|
+
await this.page.bringToFront();
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
// if the tabNameOrIndex is a string, find the tab by name
|
|
216
|
+
for (let i = 0; i < this.context.pages.length; i++) {
|
|
217
|
+
let page = this.context.pages[i];
|
|
218
|
+
let title = await page.title();
|
|
219
|
+
if (title.includes(tabTitleOrIndex)) {
|
|
220
|
+
this.page = page;
|
|
221
|
+
this.context.page = this.page;
|
|
222
|
+
await this.page.bringToFront();
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
throw new Error("Tab not found: " + tabTitleOrIndex);
|
|
227
|
+
}
|
|
183
228
|
registerConsoleLogListener(page, context) {
|
|
184
229
|
if (!this.context.webLogger) {
|
|
185
230
|
this.context.webLogger = [];
|
|
@@ -247,6 +292,7 @@ class StableBrowser {
|
|
|
247
292
|
if (!url) {
|
|
248
293
|
throw new Error("url is null, verify that the environment file is correct");
|
|
249
294
|
}
|
|
295
|
+
url = await this._replaceWithLocalData(url, this.world);
|
|
250
296
|
if (!url.startsWith("http")) {
|
|
251
297
|
url = "https://" + url;
|
|
252
298
|
}
|
|
@@ -275,7 +321,7 @@ class StableBrowser {
|
|
|
275
321
|
_commandError(state, error, this);
|
|
276
322
|
}
|
|
277
323
|
finally {
|
|
278
|
-
_commandFinally(state, this);
|
|
324
|
+
await _commandFinally(state, this);
|
|
279
325
|
}
|
|
280
326
|
}
|
|
281
327
|
async _getLocator(locator, scope, _params) {
|
|
@@ -391,7 +437,7 @@ class StableBrowser {
|
|
|
391
437
|
}
|
|
392
438
|
return { elementCount: tagCount, randomToken };
|
|
393
439
|
}
|
|
394
|
-
async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true, allowDisabled = false, element_name = null) {
|
|
440
|
+
async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true, allowDisabled = false, element_name = null, logErrors = false) {
|
|
395
441
|
if (!info) {
|
|
396
442
|
info = {};
|
|
397
443
|
}
|
|
@@ -458,7 +504,7 @@ class StableBrowser {
|
|
|
458
504
|
}
|
|
459
505
|
return;
|
|
460
506
|
}
|
|
461
|
-
if (info.locatorLog && count === 0) {
|
|
507
|
+
if (info.locatorLog && count === 0 && logErrors) {
|
|
462
508
|
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "NOT_FOUND");
|
|
463
509
|
}
|
|
464
510
|
for (let j = 0; j < count; j++) {
|
|
@@ -473,7 +519,7 @@ class StableBrowser {
|
|
|
473
519
|
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
|
|
474
520
|
}
|
|
475
521
|
}
|
|
476
|
-
else {
|
|
522
|
+
else if (logErrors) {
|
|
477
523
|
info.failCause.visible = visible;
|
|
478
524
|
info.failCause.enabled = enabled;
|
|
479
525
|
if (!info.printMessages) {
|
|
@@ -565,15 +611,27 @@ class StableBrowser {
|
|
|
565
611
|
let element = await this._locate_internal(selectors, info, _params, timeout, allowDisabled);
|
|
566
612
|
if (!element.rerun) {
|
|
567
613
|
const randomToken = Math.random().toString(36).substring(7);
|
|
568
|
-
element.evaluate((el, randomToken) => {
|
|
614
|
+
await element.evaluate((el, randomToken) => {
|
|
569
615
|
el.setAttribute("data-blinq-id-" + randomToken, "");
|
|
570
616
|
}, randomToken);
|
|
571
|
-
if (element._frame) {
|
|
572
|
-
|
|
573
|
-
}
|
|
574
|
-
const scope = element.page();
|
|
575
|
-
|
|
576
|
-
|
|
617
|
+
// if (element._frame) {
|
|
618
|
+
// return element;
|
|
619
|
+
// }
|
|
620
|
+
const scope = element._frame ?? element.page();
|
|
621
|
+
let newElementSelector = "[data-blinq-id-" + randomToken + "]";
|
|
622
|
+
let prefixSelector = "";
|
|
623
|
+
const frameControlSelector = " >> internal:control=enter-frame";
|
|
624
|
+
const frameSelectorIndex = element._selector.lastIndexOf(frameControlSelector);
|
|
625
|
+
if (frameSelectorIndex !== -1) {
|
|
626
|
+
// remove everything after the >> internal:control=enter-frame
|
|
627
|
+
const frameSelector = element._selector.substring(0, frameSelectorIndex);
|
|
628
|
+
prefixSelector = frameSelector + " >> internal:control=enter-frame >>";
|
|
629
|
+
}
|
|
630
|
+
// if (element?._frame?._selector) {
|
|
631
|
+
// prefixSelector = element._frame._selector + " >> " + prefixSelector;
|
|
632
|
+
// }
|
|
633
|
+
const newSelector = prefixSelector + newElementSelector;
|
|
634
|
+
return scope.locator(newSelector);
|
|
577
635
|
}
|
|
578
636
|
}
|
|
579
637
|
throw new Error("unable to locate element " + JSON.stringify(selectors));
|
|
@@ -725,14 +783,9 @@ class StableBrowser {
|
|
|
725
783
|
// info.log += "scanning locators in priority 2" + "\n";
|
|
726
784
|
result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
727
785
|
}
|
|
728
|
-
if (result.foundElements.length === 0 && onlyPriority3) {
|
|
786
|
+
if (result.foundElements.length === 0 && (onlyPriority3 || !highPriorityOnly)) {
|
|
729
787
|
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
730
788
|
}
|
|
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
789
|
let foundElements = result.foundElements;
|
|
737
790
|
if (foundElements.length === 1 && foundElements[0].unique) {
|
|
738
791
|
info.box = foundElements[0].box;
|
|
@@ -787,6 +840,11 @@ class StableBrowser {
|
|
|
787
840
|
visibleOnly = false;
|
|
788
841
|
}
|
|
789
842
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
843
|
+
// sheck of more of half of the timeout has passed
|
|
844
|
+
if (Date.now() - startTime > timeout / 2) {
|
|
845
|
+
highPriorityOnly = false;
|
|
846
|
+
visibleOnly = false;
|
|
847
|
+
}
|
|
790
848
|
}
|
|
791
849
|
this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
|
|
792
850
|
// if (info.locatorLog) {
|
|
@@ -802,7 +860,7 @@ class StableBrowser {
|
|
|
802
860
|
}
|
|
803
861
|
throw new Error("failed to locate first element no elements found, " + info.log);
|
|
804
862
|
}
|
|
805
|
-
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false, element_name) {
|
|
863
|
+
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false, element_name, logErrors = false) {
|
|
806
864
|
let foundElements = [];
|
|
807
865
|
const result = {
|
|
808
866
|
foundElements: foundElements,
|
|
@@ -821,7 +879,9 @@ class StableBrowser {
|
|
|
821
879
|
await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
|
|
822
880
|
}
|
|
823
881
|
catch (e) {
|
|
824
|
-
|
|
882
|
+
if (logErrors) {
|
|
883
|
+
this.logger.info("unable to use locator (second try) " + JSON.stringify(locatorsGroup[i]));
|
|
884
|
+
}
|
|
825
885
|
}
|
|
826
886
|
}
|
|
827
887
|
if (foundLocators.length === 1) {
|
|
@@ -862,7 +922,7 @@ class StableBrowser {
|
|
|
862
922
|
});
|
|
863
923
|
result.locatorIndex = i;
|
|
864
924
|
}
|
|
865
|
-
else {
|
|
925
|
+
else if (logErrors) {
|
|
866
926
|
info.failCause.foundMultiple = true;
|
|
867
927
|
if (info.locatorLog) {
|
|
868
928
|
info.locatorLog.setLocatorSearchStatus(JSON.stringify(locatorsGroup[i]), "FOUND_NOT_UNIQUE");
|
|
@@ -914,7 +974,7 @@ class StableBrowser {
|
|
|
914
974
|
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
915
975
|
}
|
|
916
976
|
finally {
|
|
917
|
-
_commandFinally(state, this);
|
|
977
|
+
await _commandFinally(state, this);
|
|
918
978
|
}
|
|
919
979
|
}
|
|
920
980
|
}
|
|
@@ -963,7 +1023,7 @@ class StableBrowser {
|
|
|
963
1023
|
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
964
1024
|
}
|
|
965
1025
|
finally {
|
|
966
|
-
_commandFinally(state, this);
|
|
1026
|
+
await _commandFinally(state, this);
|
|
967
1027
|
}
|
|
968
1028
|
}
|
|
969
1029
|
}
|
|
@@ -992,7 +1052,7 @@ class StableBrowser {
|
|
|
992
1052
|
await _commandError(state, e, this);
|
|
993
1053
|
}
|
|
994
1054
|
finally {
|
|
995
|
-
_commandFinally(state, this);
|
|
1055
|
+
await _commandFinally(state, this);
|
|
996
1056
|
}
|
|
997
1057
|
}
|
|
998
1058
|
async waitForElement(selectors, _params, options = {}, world = null) {
|
|
@@ -1023,7 +1083,7 @@ class StableBrowser {
|
|
|
1023
1083
|
// await _commandError(state, e, this);
|
|
1024
1084
|
}
|
|
1025
1085
|
finally {
|
|
1026
|
-
_commandFinally(state, this);
|
|
1086
|
+
await _commandFinally(state, this);
|
|
1027
1087
|
}
|
|
1028
1088
|
return found;
|
|
1029
1089
|
}
|
|
@@ -1047,8 +1107,8 @@ class StableBrowser {
|
|
|
1047
1107
|
try {
|
|
1048
1108
|
// if (world && world.screenshot && !world.screenshotPath) {
|
|
1049
1109
|
// console.log(`Highlighting while running from recorder`);
|
|
1050
|
-
await this._highlightElements(element);
|
|
1051
|
-
await state.element.setChecked(checked);
|
|
1110
|
+
await this._highlightElements(state.element);
|
|
1111
|
+
await state.element.setChecked(checked, { timeout: 2000 });
|
|
1052
1112
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1053
1113
|
// await this._unHighlightElements(element);
|
|
1054
1114
|
// }
|
|
@@ -1060,11 +1120,28 @@ class StableBrowser {
|
|
|
1060
1120
|
this.logger.info("element did not change its state, ignoring...");
|
|
1061
1121
|
}
|
|
1062
1122
|
else {
|
|
1123
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1063
1124
|
//await this.closeUnexpectedPopups();
|
|
1064
1125
|
state.info.log += "setCheck failed, will try again" + "\n";
|
|
1065
|
-
state.
|
|
1066
|
-
|
|
1067
|
-
|
|
1126
|
+
state.element_found = false;
|
|
1127
|
+
try {
|
|
1128
|
+
state.element = await this._locate(selectors, state.info, _params, 100);
|
|
1129
|
+
state.element_found = true;
|
|
1130
|
+
// check the check state
|
|
1131
|
+
}
|
|
1132
|
+
catch (error) {
|
|
1133
|
+
// element dismissed
|
|
1134
|
+
}
|
|
1135
|
+
if (state.element_found) {
|
|
1136
|
+
const isChecked = await state.element.isChecked();
|
|
1137
|
+
if (isChecked !== checked) {
|
|
1138
|
+
// perform click
|
|
1139
|
+
await state.element.click({ timeout: 2000, force: true });
|
|
1140
|
+
}
|
|
1141
|
+
else {
|
|
1142
|
+
this.logger.info(`Element ${selectors.element_name} is already in the desired state (${checked})`);
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1068
1145
|
}
|
|
1069
1146
|
}
|
|
1070
1147
|
await this.waitForPageLoad();
|
|
@@ -1074,7 +1151,7 @@ class StableBrowser {
|
|
|
1074
1151
|
await _commandError(state, e, this);
|
|
1075
1152
|
}
|
|
1076
1153
|
finally {
|
|
1077
|
-
_commandFinally(state, this);
|
|
1154
|
+
await _commandFinally(state, this);
|
|
1078
1155
|
}
|
|
1079
1156
|
}
|
|
1080
1157
|
async hover(selectors, _params, options = {}, world = null) {
|
|
@@ -1100,7 +1177,7 @@ class StableBrowser {
|
|
|
1100
1177
|
await _commandError(state, e, this);
|
|
1101
1178
|
}
|
|
1102
1179
|
finally {
|
|
1103
|
-
_commandFinally(state, this);
|
|
1180
|
+
await _commandFinally(state, this);
|
|
1104
1181
|
}
|
|
1105
1182
|
}
|
|
1106
1183
|
async selectOption(selectors, values, _params = null, options = {}, world = null) {
|
|
@@ -1136,7 +1213,7 @@ class StableBrowser {
|
|
|
1136
1213
|
await _commandError(state, e, this);
|
|
1137
1214
|
}
|
|
1138
1215
|
finally {
|
|
1139
|
-
_commandFinally(state, this);
|
|
1216
|
+
await _commandFinally(state, this);
|
|
1140
1217
|
}
|
|
1141
1218
|
}
|
|
1142
1219
|
async type(_value, _params = null, options = {}, world = null) {
|
|
@@ -1182,7 +1259,7 @@ class StableBrowser {
|
|
|
1182
1259
|
await _commandError(state, e, this);
|
|
1183
1260
|
}
|
|
1184
1261
|
finally {
|
|
1185
|
-
_commandFinally(state, this);
|
|
1262
|
+
await _commandFinally(state, this);
|
|
1186
1263
|
}
|
|
1187
1264
|
}
|
|
1188
1265
|
async setInputValue(selectors, value, _params = null, options = {}, world = null) {
|
|
@@ -1218,7 +1295,7 @@ class StableBrowser {
|
|
|
1218
1295
|
await _commandError(state, e, this);
|
|
1219
1296
|
}
|
|
1220
1297
|
finally {
|
|
1221
|
-
_commandFinally(state, this);
|
|
1298
|
+
await _commandFinally(state, this);
|
|
1222
1299
|
}
|
|
1223
1300
|
}
|
|
1224
1301
|
async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
|
|
@@ -1287,7 +1364,7 @@ class StableBrowser {
|
|
|
1287
1364
|
await _commandError(state, e, this);
|
|
1288
1365
|
}
|
|
1289
1366
|
finally {
|
|
1290
|
-
_commandFinally(state, this);
|
|
1367
|
+
await _commandFinally(state, this);
|
|
1291
1368
|
}
|
|
1292
1369
|
}
|
|
1293
1370
|
async clickType(selectors, _value, enter = false, _params = null, options = {}, world = null) {
|
|
@@ -1306,6 +1383,9 @@ class StableBrowser {
|
|
|
1306
1383
|
operation: "clickType",
|
|
1307
1384
|
log: "***** clickType on " + selectors.element_name + " with value " + maskValue(_value) + "*****\n",
|
|
1308
1385
|
};
|
|
1386
|
+
if (!options) {
|
|
1387
|
+
options = {};
|
|
1388
|
+
}
|
|
1309
1389
|
if (newValue !== _value) {
|
|
1310
1390
|
//this.logger.info(_value + "=" + newValue);
|
|
1311
1391
|
_value = newValue;
|
|
@@ -1313,7 +1393,7 @@ class StableBrowser {
|
|
|
1313
1393
|
try {
|
|
1314
1394
|
await _preCommand(state, this);
|
|
1315
1395
|
state.info.value = _value;
|
|
1316
|
-
if (
|
|
1396
|
+
if (!options.press) {
|
|
1317
1397
|
try {
|
|
1318
1398
|
let currentValue = await state.element.inputValue();
|
|
1319
1399
|
if (currentValue) {
|
|
@@ -1324,10 +1404,7 @@ class StableBrowser {
|
|
|
1324
1404
|
this.logger.info("unable to clear input value");
|
|
1325
1405
|
}
|
|
1326
1406
|
}
|
|
1327
|
-
if (options
|
|
1328
|
-
if (!options) {
|
|
1329
|
-
options = {};
|
|
1330
|
-
}
|
|
1407
|
+
if (options.press) {
|
|
1331
1408
|
options.timeout = 5000;
|
|
1332
1409
|
await performAction("click", state.element, options, this, state, _params);
|
|
1333
1410
|
}
|
|
@@ -1387,7 +1464,7 @@ class StableBrowser {
|
|
|
1387
1464
|
await _commandError(state, e, this);
|
|
1388
1465
|
}
|
|
1389
1466
|
finally {
|
|
1390
|
-
_commandFinally(state, this);
|
|
1467
|
+
await _commandFinally(state, this);
|
|
1391
1468
|
}
|
|
1392
1469
|
}
|
|
1393
1470
|
async fill(selectors, value, enter = false, _params = null, options = {}, world = null) {
|
|
@@ -1417,7 +1494,42 @@ class StableBrowser {
|
|
|
1417
1494
|
await _commandError(state, e, this);
|
|
1418
1495
|
}
|
|
1419
1496
|
finally {
|
|
1420
|
-
_commandFinally(state, this);
|
|
1497
|
+
await _commandFinally(state, this);
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
async setInputFiles(selectors, files, _params = null, options = {}, world = null) {
|
|
1501
|
+
const state = {
|
|
1502
|
+
selectors,
|
|
1503
|
+
_params,
|
|
1504
|
+
files,
|
|
1505
|
+
value: '"' + files.join('", "') + '"',
|
|
1506
|
+
options,
|
|
1507
|
+
world,
|
|
1508
|
+
type: Types.SET_INPUT_FILES,
|
|
1509
|
+
text: `Set input files`,
|
|
1510
|
+
_text: `Set input files on ${selectors.element_name}`,
|
|
1511
|
+
operation: "setInputFiles",
|
|
1512
|
+
log: "***** set input files " + selectors.element_name + " *****\n",
|
|
1513
|
+
};
|
|
1514
|
+
const uploadsFolder = this.configuration.uploadsFolder ?? "data/uploads";
|
|
1515
|
+
try {
|
|
1516
|
+
await _preCommand(state, this);
|
|
1517
|
+
for (let i = 0; i < files.length; i++) {
|
|
1518
|
+
const file = files[i];
|
|
1519
|
+
const filePath = path.join(uploadsFolder, file);
|
|
1520
|
+
if (!fs.existsSync(filePath)) {
|
|
1521
|
+
throw new Error(`File not found: ${filePath}`);
|
|
1522
|
+
}
|
|
1523
|
+
state.files[i] = filePath;
|
|
1524
|
+
}
|
|
1525
|
+
await state.element.setInputFiles(files);
|
|
1526
|
+
return state.info;
|
|
1527
|
+
}
|
|
1528
|
+
catch (e) {
|
|
1529
|
+
await _commandError(state, e, this);
|
|
1530
|
+
}
|
|
1531
|
+
finally {
|
|
1532
|
+
await _commandFinally(state, this);
|
|
1421
1533
|
}
|
|
1422
1534
|
}
|
|
1423
1535
|
async getText(selectors, _params = null, options = {}, info = {}, world = null) {
|
|
@@ -1533,7 +1645,7 @@ class StableBrowser {
|
|
|
1533
1645
|
await _commandError(state, e, this);
|
|
1534
1646
|
}
|
|
1535
1647
|
finally {
|
|
1536
|
-
_commandFinally(state, this);
|
|
1648
|
+
await _commandFinally(state, this);
|
|
1537
1649
|
}
|
|
1538
1650
|
}
|
|
1539
1651
|
async containsText(selectors, text, climb, _params = null, options = {}, world = null) {
|
|
@@ -1568,7 +1680,7 @@ class StableBrowser {
|
|
|
1568
1680
|
while (Date.now() - startTime < timeout) {
|
|
1569
1681
|
try {
|
|
1570
1682
|
await _preCommand(state, this);
|
|
1571
|
-
foundObj = await this._getText(selectors, climb, _params, { timeout:
|
|
1683
|
+
foundObj = await this._getText(selectors, climb, _params, { timeout: 3000 }, state.info, world);
|
|
1572
1684
|
if (foundObj && foundObj.element) {
|
|
1573
1685
|
await this.scrollIfNeeded(foundObj.element, state.info);
|
|
1574
1686
|
}
|
|
@@ -1610,7 +1722,84 @@ class StableBrowser {
|
|
|
1610
1722
|
throw e;
|
|
1611
1723
|
}
|
|
1612
1724
|
finally {
|
|
1613
|
-
_commandFinally(state, this);
|
|
1725
|
+
await _commandFinally(state, this);
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
async snapshotValidation(frameSelectors, referanceSnapshot, _params = null, options = {}, world = null) {
|
|
1729
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1730
|
+
const startTime = Date.now();
|
|
1731
|
+
const state = {
|
|
1732
|
+
_params,
|
|
1733
|
+
value: referanceSnapshot,
|
|
1734
|
+
options,
|
|
1735
|
+
world,
|
|
1736
|
+
locate: false,
|
|
1737
|
+
scroll: false,
|
|
1738
|
+
screenshot: true,
|
|
1739
|
+
highlight: false,
|
|
1740
|
+
type: Types.SNAPSHOT_VALIDATION,
|
|
1741
|
+
text: `verify snapshot: ${referanceSnapshot}`,
|
|
1742
|
+
operation: "snapshotValidation",
|
|
1743
|
+
log: "***** verify snapshot *****\n",
|
|
1744
|
+
};
|
|
1745
|
+
if (!referanceSnapshot) {
|
|
1746
|
+
throw new Error("referanceSnapshot is null");
|
|
1747
|
+
}
|
|
1748
|
+
let text = null;
|
|
1749
|
+
if (fs.existsSync(path.join(this.project_path, "data", "snapshots", this.context.environment.name, referanceSnapshot + ".yml"))) {
|
|
1750
|
+
text = fs.readFileSync(path.join(this.project_path, "data", "snapshots", this.context.environment.name, referanceSnapshot + ".yml"), "utf8");
|
|
1751
|
+
}
|
|
1752
|
+
else if (fs.existsSync(path.join(this.project_path, "data", "snapshots", this.context.environment.name, referanceSnapshot + ".yaml"))) {
|
|
1753
|
+
text = fs.readFileSync(path.join(this.project_path, "data", "snapshots", this.context.environment.name, referanceSnapshot + ".yaml"), "utf8");
|
|
1754
|
+
}
|
|
1755
|
+
else if (referanceSnapshot.startsWith("yaml:")) {
|
|
1756
|
+
text = referanceSnapshot.substring(5);
|
|
1757
|
+
}
|
|
1758
|
+
else {
|
|
1759
|
+
throw new Error("referenceSnapshot file not found: " + referanceSnapshot);
|
|
1760
|
+
}
|
|
1761
|
+
state.text = text;
|
|
1762
|
+
const newValue = await this._replaceWithLocalData(text, world);
|
|
1763
|
+
await _preCommand(state, this);
|
|
1764
|
+
let foundObj = null;
|
|
1765
|
+
try {
|
|
1766
|
+
let matchResult = null;
|
|
1767
|
+
while (Date.now() - startTime < timeout) {
|
|
1768
|
+
try {
|
|
1769
|
+
let scope = null;
|
|
1770
|
+
if (!frameSelectors) {
|
|
1771
|
+
scope = this.page;
|
|
1772
|
+
}
|
|
1773
|
+
else {
|
|
1774
|
+
scope = await this._findFrameScope(frameSelectors, timeout, state.info);
|
|
1775
|
+
}
|
|
1776
|
+
const snapshot = await scope.locator("body").ariaSnapshot({ timeout });
|
|
1777
|
+
matchResult = snapshotValidation(snapshot, newValue, referanceSnapshot);
|
|
1778
|
+
if (matchResult.errorLine !== -1) {
|
|
1779
|
+
throw new Error("Snapshot validation failed at line " + matchResult.errorLineText);
|
|
1780
|
+
}
|
|
1781
|
+
// highlight and screenshot
|
|
1782
|
+
try {
|
|
1783
|
+
await await highlightSnapshot(newValue, scope);
|
|
1784
|
+
await _screenshot(state, this);
|
|
1785
|
+
}
|
|
1786
|
+
catch (e) { }
|
|
1787
|
+
return state.info;
|
|
1788
|
+
}
|
|
1789
|
+
catch (e) {
|
|
1790
|
+
// Log error but continue retrying until timeout is reached
|
|
1791
|
+
//this.logger.warn("Retrying snapshot validation due to: " + e.message);
|
|
1792
|
+
}
|
|
1793
|
+
await new Promise((resolve) => setTimeout(resolve, 2000)); // Wait 1 second before retrying
|
|
1794
|
+
}
|
|
1795
|
+
throw new Error("No snapshot match " + matchResult?.errorLineText);
|
|
1796
|
+
}
|
|
1797
|
+
catch (e) {
|
|
1798
|
+
await _commandError(state, e, this);
|
|
1799
|
+
throw e;
|
|
1800
|
+
}
|
|
1801
|
+
finally {
|
|
1802
|
+
await _commandFinally(state, this);
|
|
1614
1803
|
}
|
|
1615
1804
|
}
|
|
1616
1805
|
async waitForUserInput(message, world = null) {
|
|
@@ -1648,6 +1837,15 @@ class StableBrowser {
|
|
|
1648
1837
|
// save the data to the file
|
|
1649
1838
|
fs.writeFileSync(dataFile, JSON.stringify(data, null, 2));
|
|
1650
1839
|
}
|
|
1840
|
+
overwriteTestData(testData, world = null) {
|
|
1841
|
+
if (!testData) {
|
|
1842
|
+
return;
|
|
1843
|
+
}
|
|
1844
|
+
// if data file exists, load it
|
|
1845
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1846
|
+
// save the data to the file
|
|
1847
|
+
fs.writeFileSync(dataFile, JSON.stringify(testData, null, 2));
|
|
1848
|
+
}
|
|
1651
1849
|
_getDataFilePath(fileName) {
|
|
1652
1850
|
let dataFile = path.join(this.project_path, "data", fileName);
|
|
1653
1851
|
if (fs.existsSync(dataFile)) {
|
|
@@ -1900,7 +2098,7 @@ class StableBrowser {
|
|
|
1900
2098
|
await _commandError(state, e, this);
|
|
1901
2099
|
}
|
|
1902
2100
|
finally {
|
|
1903
|
-
_commandFinally(state, this);
|
|
2101
|
+
await _commandFinally(state, this);
|
|
1904
2102
|
}
|
|
1905
2103
|
}
|
|
1906
2104
|
async extractAttribute(selectors, attribute, variable, _params = null, options = {}, world = null) {
|
|
@@ -1931,10 +2129,31 @@ class StableBrowser {
|
|
|
1931
2129
|
case "value":
|
|
1932
2130
|
state.value = await state.element.inputValue();
|
|
1933
2131
|
break;
|
|
2132
|
+
case "text":
|
|
2133
|
+
state.value = await state.element.textContent();
|
|
2134
|
+
break;
|
|
1934
2135
|
default:
|
|
1935
2136
|
state.value = await state.element.getAttribute(attribute);
|
|
1936
2137
|
break;
|
|
1937
2138
|
}
|
|
2139
|
+
if (options !== null) {
|
|
2140
|
+
if (options.regex && options.regex !== "") {
|
|
2141
|
+
// Construct a regex pattern from the provided string
|
|
2142
|
+
const regex = options.regex.slice(1, -1);
|
|
2143
|
+
const regexPattern = new RegExp(regex, "g");
|
|
2144
|
+
const matches = state.value.match(regexPattern);
|
|
2145
|
+
if (matches) {
|
|
2146
|
+
let newValue = "";
|
|
2147
|
+
for (const match of matches) {
|
|
2148
|
+
newValue += match;
|
|
2149
|
+
}
|
|
2150
|
+
state.value = newValue;
|
|
2151
|
+
}
|
|
2152
|
+
}
|
|
2153
|
+
if (options.trimSpaces && options.trimSpaces === true) {
|
|
2154
|
+
state.value = state.value.trim();
|
|
2155
|
+
}
|
|
2156
|
+
}
|
|
1938
2157
|
state.info.value = state.value;
|
|
1939
2158
|
this.setTestData({ [variable]: state.value }, world);
|
|
1940
2159
|
this.logger.info("set test data: " + variable + "=" + state.value);
|
|
@@ -1945,7 +2164,7 @@ class StableBrowser {
|
|
|
1945
2164
|
await _commandError(state, e, this);
|
|
1946
2165
|
}
|
|
1947
2166
|
finally {
|
|
1948
|
-
_commandFinally(state, this);
|
|
2167
|
+
await _commandFinally(state, this);
|
|
1949
2168
|
}
|
|
1950
2169
|
}
|
|
1951
2170
|
async verifyAttribute(selectors, attribute, value, _params = null, options = {}, world = null) {
|
|
@@ -1970,12 +2189,15 @@ class StableBrowser {
|
|
|
1970
2189
|
let expectedValue;
|
|
1971
2190
|
try {
|
|
1972
2191
|
await _preCommand(state, this);
|
|
1973
|
-
expectedValue = state.value;
|
|
2192
|
+
expectedValue = await replaceWithLocalTestData(state.value, world);
|
|
1974
2193
|
state.info.expectedValue = expectedValue;
|
|
1975
2194
|
switch (attribute) {
|
|
1976
2195
|
case "innerText":
|
|
1977
2196
|
val = String(await state.element.innerText());
|
|
1978
2197
|
break;
|
|
2198
|
+
case "text":
|
|
2199
|
+
val = String(await state.element.textContent());
|
|
2200
|
+
break;
|
|
1979
2201
|
case "value":
|
|
1980
2202
|
val = String(await state.element.inputValue());
|
|
1981
2203
|
break;
|
|
@@ -1997,17 +2219,42 @@ class StableBrowser {
|
|
|
1997
2219
|
let regex;
|
|
1998
2220
|
if (expectedValue.startsWith("/") && expectedValue.endsWith("/")) {
|
|
1999
2221
|
const patternBody = expectedValue.slice(1, -1);
|
|
2000
|
-
|
|
2222
|
+
const processedPattern = patternBody.replace(/\n/g, ".*");
|
|
2223
|
+
regex = new RegExp(processedPattern, "gs");
|
|
2224
|
+
state.info.regex = true;
|
|
2001
2225
|
}
|
|
2002
2226
|
else {
|
|
2003
2227
|
const escapedPattern = expectedValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2004
2228
|
regex = new RegExp(escapedPattern, "g");
|
|
2005
2229
|
}
|
|
2006
|
-
if (
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2230
|
+
if (attribute === "innerText") {
|
|
2231
|
+
if (state.info.regex) {
|
|
2232
|
+
if (!regex.test(val)) {
|
|
2233
|
+
let errorMessage = `The ${attribute} attribute has a value of "${val}", but the expected value is "${expectedValue}"`;
|
|
2234
|
+
state.info.failCause.assertionFailed = true;
|
|
2235
|
+
state.info.failCause.lastError = errorMessage;
|
|
2236
|
+
throw new Error(errorMessage);
|
|
2237
|
+
}
|
|
2238
|
+
}
|
|
2239
|
+
else {
|
|
2240
|
+
const valLines = val.split("\n");
|
|
2241
|
+
const expectedLines = expectedValue.split("\n");
|
|
2242
|
+
const isPart = expectedLines.every((expectedLine) => valLines.some((valLine) => valLine === expectedLine));
|
|
2243
|
+
if (!isPart) {
|
|
2244
|
+
let errorMessage = `The ${attribute} attribute has a value of "${val}", but the expected value is "${expectedValue}"`;
|
|
2245
|
+
state.info.failCause.assertionFailed = true;
|
|
2246
|
+
state.info.failCause.lastError = errorMessage;
|
|
2247
|
+
throw new Error(errorMessage);
|
|
2248
|
+
}
|
|
2249
|
+
}
|
|
2250
|
+
}
|
|
2251
|
+
else {
|
|
2252
|
+
if (!val.match(regex)) {
|
|
2253
|
+
let errorMessage = `The ${attribute} attribute has a value of "${val}", but the expected value is "${expectedValue}"`;
|
|
2254
|
+
state.info.failCause.assertionFailed = true;
|
|
2255
|
+
state.info.failCause.lastError = errorMessage;
|
|
2256
|
+
throw new Error(errorMessage);
|
|
2257
|
+
}
|
|
2011
2258
|
}
|
|
2012
2259
|
return state.info;
|
|
2013
2260
|
}
|
|
@@ -2015,7 +2262,7 @@ class StableBrowser {
|
|
|
2015
2262
|
await _commandError(state, e, this);
|
|
2016
2263
|
}
|
|
2017
2264
|
finally {
|
|
2018
|
-
_commandFinally(state, this);
|
|
2265
|
+
await _commandFinally(state, this);
|
|
2019
2266
|
}
|
|
2020
2267
|
}
|
|
2021
2268
|
async extractEmailData(emailAddress, options, world) {
|
|
@@ -2175,56 +2422,49 @@ class StableBrowser {
|
|
|
2175
2422
|
console.debug(error);
|
|
2176
2423
|
}
|
|
2177
2424
|
}
|
|
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
|
-
// }
|
|
2425
|
+
_matcher(text) {
|
|
2426
|
+
if (!text) {
|
|
2427
|
+
return { matcher: "contains", queryText: "" };
|
|
2428
|
+
}
|
|
2429
|
+
if (text.length < 2) {
|
|
2430
|
+
return { matcher: "contains", queryText: text };
|
|
2431
|
+
}
|
|
2432
|
+
const split = text.split(":");
|
|
2433
|
+
const matcher = split[0].toLowerCase();
|
|
2434
|
+
const queryText = split.slice(1).join(":").trim();
|
|
2435
|
+
return { matcher, queryText };
|
|
2436
|
+
}
|
|
2437
|
+
_getDomain(url) {
|
|
2438
|
+
if (url.length === 0 || (!url.startsWith("http://") && !url.startsWith("https://"))) {
|
|
2439
|
+
return "";
|
|
2440
|
+
}
|
|
2441
|
+
let hostnameFragments = url.split("/")[2].split(".");
|
|
2442
|
+
if (hostnameFragments.some((fragment) => fragment.includes(":"))) {
|
|
2443
|
+
return hostnameFragments.join("-").split(":").join("-");
|
|
2444
|
+
}
|
|
2445
|
+
let n = hostnameFragments.length;
|
|
2446
|
+
let fragments = [...hostnameFragments];
|
|
2447
|
+
while (n > 0 && hostnameFragments[n - 1].length <= 3) {
|
|
2448
|
+
hostnameFragments.pop();
|
|
2449
|
+
n = hostnameFragments.length;
|
|
2450
|
+
}
|
|
2451
|
+
if (n == 0) {
|
|
2452
|
+
if (fragments[0] === "www")
|
|
2453
|
+
fragments = fragments.slice(1);
|
|
2454
|
+
return fragments.length > 1 ? fragments.slice(0, fragments.length - 1).join("-") : fragments.join("-");
|
|
2455
|
+
}
|
|
2456
|
+
if (hostnameFragments[0] === "www")
|
|
2457
|
+
hostnameFragments = hostnameFragments.slice(1);
|
|
2458
|
+
return hostnameFragments.join(".");
|
|
2459
|
+
}
|
|
2460
|
+
/**
|
|
2461
|
+
* Verify the page path matches the given path.
|
|
2462
|
+
* @param {string} pathPart - The path to verify.
|
|
2463
|
+
* @param {object} options - Options for verification.
|
|
2464
|
+
* @param {object} world - The world context.
|
|
2465
|
+
* @returns {Promise<object>} - The state info after verification.
|
|
2466
|
+
*/
|
|
2226
2467
|
async verifyPagePath(pathPart, options = {}, world = null) {
|
|
2227
|
-
const startTime = Date.now();
|
|
2228
2468
|
let error = null;
|
|
2229
2469
|
let screenshotId = null;
|
|
2230
2470
|
let screenshotPath = null;
|
|
@@ -2238,51 +2478,212 @@ class StableBrowser {
|
|
|
2238
2478
|
pathPart = newValue;
|
|
2239
2479
|
}
|
|
2240
2480
|
info.pathPart = pathPart;
|
|
2481
|
+
const { matcher, queryText } = this._matcher(pathPart);
|
|
2482
|
+
const state = {
|
|
2483
|
+
text_search: queryText,
|
|
2484
|
+
options,
|
|
2485
|
+
world,
|
|
2486
|
+
locate: false,
|
|
2487
|
+
scroll: false,
|
|
2488
|
+
highlight: false,
|
|
2489
|
+
type: Types.VERIFY_PAGE_PATH,
|
|
2490
|
+
text: `Verify the page url is ${queryText}`,
|
|
2491
|
+
_text: `Verify the page url is ${queryText}`,
|
|
2492
|
+
operation: "verifyPagePath",
|
|
2493
|
+
log: "***** verify page url is " + queryText + " *****\n",
|
|
2494
|
+
};
|
|
2241
2495
|
try {
|
|
2496
|
+
await _preCommand(state, this);
|
|
2497
|
+
state.info.text = queryText;
|
|
2242
2498
|
for (let i = 0; i < 30; i++) {
|
|
2243
2499
|
const url = await this.page.url();
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2500
|
+
switch (matcher) {
|
|
2501
|
+
case "exact":
|
|
2502
|
+
if (url !== queryText) {
|
|
2503
|
+
if (i === 29) {
|
|
2504
|
+
throw new Error(`Page URL ${url} is not equal to ${queryText}`);
|
|
2505
|
+
}
|
|
2506
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2507
|
+
continue;
|
|
2508
|
+
}
|
|
2509
|
+
break;
|
|
2510
|
+
case "contains":
|
|
2511
|
+
if (!url.includes(queryText)) {
|
|
2512
|
+
if (i === 29) {
|
|
2513
|
+
throw new Error(`Page URL ${url} doesn't contain ${queryText}`);
|
|
2514
|
+
}
|
|
2515
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2516
|
+
continue;
|
|
2517
|
+
}
|
|
2518
|
+
break;
|
|
2519
|
+
case "starts-with":
|
|
2520
|
+
{
|
|
2521
|
+
const domain = this._getDomain(url);
|
|
2522
|
+
if (domain.length > 0 && domain !== queryText) {
|
|
2523
|
+
if (i === 29) {
|
|
2524
|
+
throw new Error(`Page URL ${url} doesn't start with ${queryText}`);
|
|
2525
|
+
}
|
|
2526
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2527
|
+
continue;
|
|
2528
|
+
}
|
|
2529
|
+
}
|
|
2530
|
+
break;
|
|
2531
|
+
case "ends-with":
|
|
2532
|
+
{
|
|
2533
|
+
const urlObj = new URL(url);
|
|
2534
|
+
let route = "/";
|
|
2535
|
+
if (urlObj.pathname !== "/") {
|
|
2536
|
+
route = urlObj.pathname.split("/").slice(-1)[0].trim();
|
|
2537
|
+
}
|
|
2538
|
+
else {
|
|
2539
|
+
route = "/";
|
|
2540
|
+
}
|
|
2541
|
+
if (route !== queryText) {
|
|
2542
|
+
if (i === 29) {
|
|
2543
|
+
throw new Error(`Page URL ${url} doesn't end with ${queryText}`);
|
|
2544
|
+
}
|
|
2545
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2546
|
+
continue;
|
|
2547
|
+
}
|
|
2548
|
+
}
|
|
2549
|
+
break;
|
|
2550
|
+
case "regex":
|
|
2551
|
+
const regex = new RegExp(queryText.slice(1, -1), "g");
|
|
2552
|
+
if (!regex.test(url)) {
|
|
2553
|
+
if (i === 29) {
|
|
2554
|
+
throw new Error(`Page URL ${url} doesn't match regex ${queryText}`);
|
|
2555
|
+
}
|
|
2556
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2557
|
+
continue;
|
|
2558
|
+
}
|
|
2559
|
+
break;
|
|
2560
|
+
default:
|
|
2561
|
+
console.log("Unknown matching type, defaulting to contains matching");
|
|
2562
|
+
if (!url.includes(pathPart)) {
|
|
2563
|
+
if (i === 29) {
|
|
2564
|
+
throw new Error(`Page URL ${url} does not contain ${pathPart}`);
|
|
2565
|
+
}
|
|
2566
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2567
|
+
continue;
|
|
2568
|
+
}
|
|
2250
2569
|
}
|
|
2251
|
-
|
|
2252
|
-
return info;
|
|
2570
|
+
await _screenshot(state, this);
|
|
2571
|
+
return state.info;
|
|
2253
2572
|
}
|
|
2254
2573
|
}
|
|
2255
2574
|
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);
|
|
2575
|
+
state.info.failCause.lastError = e.message;
|
|
2576
|
+
state.info.failCause.assertionFailed = true;
|
|
2577
|
+
await _commandError(state, e, this);
|
|
2264
2578
|
}
|
|
2265
2579
|
finally {
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2580
|
+
await _commandFinally(state, this);
|
|
2581
|
+
}
|
|
2582
|
+
}
|
|
2583
|
+
/**
|
|
2584
|
+
* Verify the page title matches the given title.
|
|
2585
|
+
* @param {string} title - The title to verify.
|
|
2586
|
+
* @param {object} options - Options for verification.
|
|
2587
|
+
* @param {object} world - The world context.
|
|
2588
|
+
* @returns {Promise<object>} - The state info after verification.
|
|
2589
|
+
*/
|
|
2590
|
+
async verifyPageTitle(title, options = {}, world = null) {
|
|
2591
|
+
let error = null;
|
|
2592
|
+
let screenshotId = null;
|
|
2593
|
+
let screenshotPath = null;
|
|
2594
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2595
|
+
const newValue = await this._replaceWithLocalData(title, world);
|
|
2596
|
+
if (newValue !== title) {
|
|
2597
|
+
this.logger.info(title + "=" + newValue);
|
|
2598
|
+
title = newValue;
|
|
2599
|
+
}
|
|
2600
|
+
const { matcher, queryText } = this._matcher(title);
|
|
2601
|
+
const state = {
|
|
2602
|
+
text_search: queryText,
|
|
2603
|
+
options,
|
|
2604
|
+
world,
|
|
2605
|
+
locate: false,
|
|
2606
|
+
scroll: false,
|
|
2607
|
+
highlight: false,
|
|
2608
|
+
type: Types.VERIFY_PAGE_TITLE,
|
|
2609
|
+
text: `Verify the page title is ${queryText}`,
|
|
2610
|
+
_text: `Verify the page title is ${queryText}`,
|
|
2611
|
+
operation: "verifyPageTitle",
|
|
2612
|
+
log: "***** verify page title is " + queryText + " *****\n",
|
|
2613
|
+
};
|
|
2614
|
+
try {
|
|
2615
|
+
await _preCommand(state, this);
|
|
2616
|
+
state.info.text = queryText;
|
|
2617
|
+
for (let i = 0; i < 30; i++) {
|
|
2618
|
+
const foundTitle = await this.page.title();
|
|
2619
|
+
switch (matcher) {
|
|
2620
|
+
case "exact":
|
|
2621
|
+
if (foundTitle !== queryText) {
|
|
2622
|
+
if (i === 29) {
|
|
2623
|
+
throw new Error(`Page Title ${foundTitle} is not equal to ${queryText}`);
|
|
2624
|
+
}
|
|
2625
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2626
|
+
continue;
|
|
2627
|
+
}
|
|
2628
|
+
break;
|
|
2629
|
+
case "contains":
|
|
2630
|
+
if (!foundTitle.includes(queryText)) {
|
|
2631
|
+
if (i === 29) {
|
|
2632
|
+
throw new Error(`Page Title ${foundTitle} doesn't contain ${queryText}`);
|
|
2633
|
+
}
|
|
2634
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2635
|
+
continue;
|
|
2636
|
+
}
|
|
2637
|
+
break;
|
|
2638
|
+
case "starts-with":
|
|
2639
|
+
if (!foundTitle.startsWith(queryText)) {
|
|
2640
|
+
if (i === 29) {
|
|
2641
|
+
throw new Error(`Page title ${foundTitle} doesn't start with ${queryText}`);
|
|
2642
|
+
}
|
|
2643
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2644
|
+
continue;
|
|
2645
|
+
}
|
|
2646
|
+
break;
|
|
2647
|
+
case "ends-with":
|
|
2648
|
+
if (!foundTitle.endsWith(queryText)) {
|
|
2649
|
+
if (i === 29) {
|
|
2650
|
+
throw new Error(`Page Title ${foundTitle} doesn't end with ${queryText}`);
|
|
2651
|
+
}
|
|
2652
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2653
|
+
continue;
|
|
2654
|
+
}
|
|
2655
|
+
break;
|
|
2656
|
+
case "regex":
|
|
2657
|
+
const regex = new RegExp(queryText.slice(1, -1), "g");
|
|
2658
|
+
if (!regex.test(foundTitle)) {
|
|
2659
|
+
if (i === 29) {
|
|
2660
|
+
throw new Error(`Page Title ${foundTitle} doesn't match regex ${queryText}`);
|
|
2661
|
+
}
|
|
2662
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2663
|
+
continue;
|
|
2664
|
+
}
|
|
2665
|
+
break;
|
|
2666
|
+
default:
|
|
2667
|
+
console.log("Unknown matching type, defaulting to contains matching");
|
|
2668
|
+
if (!foundTitle.includes(title)) {
|
|
2669
|
+
if (i === 29) {
|
|
2670
|
+
throw new Error(`Page Title ${foundTitle} does not contain ${title}`);
|
|
2671
|
+
}
|
|
2672
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2673
|
+
continue;
|
|
2674
|
+
}
|
|
2675
|
+
}
|
|
2676
|
+
await _screenshot(state, this);
|
|
2677
|
+
return state.info;
|
|
2678
|
+
}
|
|
2679
|
+
}
|
|
2680
|
+
catch (e) {
|
|
2681
|
+
state.info.failCause.lastError = e.message;
|
|
2682
|
+
state.info.failCause.assertionFailed = true;
|
|
2683
|
+
await _commandError(state, e, this);
|
|
2684
|
+
}
|
|
2685
|
+
finally {
|
|
2686
|
+
await _commandFinally(state, this);
|
|
2286
2687
|
}
|
|
2287
2688
|
}
|
|
2288
2689
|
async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state, partial = true, ignoreCase = false) {
|
|
@@ -2324,7 +2725,7 @@ class StableBrowser {
|
|
|
2324
2725
|
scroll: false,
|
|
2325
2726
|
highlight: false,
|
|
2326
2727
|
type: Types.VERIFY_PAGE_CONTAINS_TEXT,
|
|
2327
|
-
text: `Verify the text '${text}' exists in page`,
|
|
2728
|
+
text: `Verify the text '${maskValue(text)}' exists in page`,
|
|
2328
2729
|
_text: `Verify the text '${text}' exists in page`,
|
|
2329
2730
|
operation: "verifyTextExistInPage",
|
|
2330
2731
|
log: "***** verify text " + text + " exists in page *****\n",
|
|
@@ -2366,27 +2767,10 @@ class StableBrowser {
|
|
|
2366
2767
|
const frame = resultWithElementsFound[0].frame;
|
|
2367
2768
|
const dataAttribute = `[data-blinq-id-${resultWithElementsFound[0].randomToken}]`;
|
|
2368
2769
|
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
2770
|
const element = await frame.locator(dataAttribute).first();
|
|
2384
|
-
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2385
|
-
// await this._unhighlightElements(frame, dataAttribute);
|
|
2386
2771
|
if (element) {
|
|
2387
2772
|
await this.scrollIfNeeded(element, state.info);
|
|
2388
2773
|
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2389
|
-
// await _screenshot(state, this, element);
|
|
2390
2774
|
}
|
|
2391
2775
|
}
|
|
2392
2776
|
await _screenshot(state, this);
|
|
@@ -2396,13 +2780,12 @@ class StableBrowser {
|
|
|
2396
2780
|
console.error(error);
|
|
2397
2781
|
}
|
|
2398
2782
|
}
|
|
2399
|
-
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2400
2783
|
}
|
|
2401
2784
|
catch (e) {
|
|
2402
2785
|
await _commandError(state, e, this);
|
|
2403
2786
|
}
|
|
2404
2787
|
finally {
|
|
2405
|
-
_commandFinally(state, this);
|
|
2788
|
+
await _commandFinally(state, this);
|
|
2406
2789
|
}
|
|
2407
2790
|
}
|
|
2408
2791
|
async waitForTextToDisappear(text, options = {}, world = null) {
|
|
@@ -2415,7 +2798,7 @@ class StableBrowser {
|
|
|
2415
2798
|
scroll: false,
|
|
2416
2799
|
highlight: false,
|
|
2417
2800
|
type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
|
|
2418
|
-
text: `Verify text does not exist in page`,
|
|
2801
|
+
text: `Verify the text '${maskValue(text)}' does not exist in page`,
|
|
2419
2802
|
_text: `Verify the text '${text}' does not exist in page`,
|
|
2420
2803
|
operation: "verifyTextNotExistInPage",
|
|
2421
2804
|
log: "***** verify text " + text + " does not exist in page *****\n",
|
|
@@ -2459,7 +2842,7 @@ class StableBrowser {
|
|
|
2459
2842
|
await _commandError(state, e, this);
|
|
2460
2843
|
}
|
|
2461
2844
|
finally {
|
|
2462
|
-
_commandFinally(state, this);
|
|
2845
|
+
await _commandFinally(state, this);
|
|
2463
2846
|
}
|
|
2464
2847
|
}
|
|
2465
2848
|
async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
|
|
@@ -2529,7 +2912,7 @@ class StableBrowser {
|
|
|
2529
2912
|
const count = await frame.locator(css).count();
|
|
2530
2913
|
for (let j = 0; j < count; j++) {
|
|
2531
2914
|
const continer = await frame.locator(css).nth(j);
|
|
2532
|
-
const result = await this._locateElementByText(continer, textToVerify, "*:not(script, style, head)", false,
|
|
2915
|
+
const result = await this._locateElementByText(continer, textToVerify, "*:not(script, style, head)", false, true, true, {});
|
|
2533
2916
|
if (result.elementCount > 0) {
|
|
2534
2917
|
const dataAttribute = "[data-blinq-id-" + result.randomToken + "]";
|
|
2535
2918
|
await this._highlightElements(frame, dataAttribute);
|
|
@@ -2570,7 +2953,7 @@ class StableBrowser {
|
|
|
2570
2953
|
await _commandError(state, e, this);
|
|
2571
2954
|
}
|
|
2572
2955
|
finally {
|
|
2573
|
-
_commandFinally(state, this);
|
|
2956
|
+
await _commandFinally(state, this);
|
|
2574
2957
|
}
|
|
2575
2958
|
}
|
|
2576
2959
|
async findRelatedTextInAllFrames(textAnchor, climb, textToVerify, params = {}, options = {}, world = null) {
|
|
@@ -2913,7 +3296,13 @@ class StableBrowser {
|
|
|
2913
3296
|
}
|
|
2914
3297
|
}
|
|
2915
3298
|
async _replaceWithLocalData(value, world, _decrypt = true, totpWait = true) {
|
|
2916
|
-
|
|
3299
|
+
try {
|
|
3300
|
+
return await replaceWithLocalTestData(value, world, _decrypt, totpWait, this.context, this);
|
|
3301
|
+
}
|
|
3302
|
+
catch (error) {
|
|
3303
|
+
this.logger.debug(error);
|
|
3304
|
+
throw error;
|
|
3305
|
+
}
|
|
2917
3306
|
}
|
|
2918
3307
|
_getLoadTimeout(options) {
|
|
2919
3308
|
let timeout = 15000;
|
|
@@ -2936,6 +3325,7 @@ class StableBrowser {
|
|
|
2936
3325
|
}
|
|
2937
3326
|
async saveStoreState(path = null, world = null) {
|
|
2938
3327
|
const storageState = await this.page.context().storageState();
|
|
3328
|
+
path = await this._replaceWithLocalData(path, this.world);
|
|
2939
3329
|
//const testDataFile = _getDataFile(world, this.context, this);
|
|
2940
3330
|
if (path) {
|
|
2941
3331
|
// save { storageState: storageState } into the path
|
|
@@ -2946,10 +3336,14 @@ class StableBrowser {
|
|
|
2946
3336
|
}
|
|
2947
3337
|
}
|
|
2948
3338
|
async restoreSaveState(path = null, world = null) {
|
|
3339
|
+
path = await this._replaceWithLocalData(path, this.world);
|
|
2949
3340
|
await refreshBrowser(this, path, world);
|
|
2950
3341
|
this.registerEventListeners(this.context);
|
|
2951
3342
|
registerNetworkEvents(this.world, this, this.context, this.page);
|
|
2952
3343
|
registerDownloadEvent(this.page, this.world, this.context);
|
|
3344
|
+
if (this.onRestoreSaveState) {
|
|
3345
|
+
this.onRestoreSaveState(path);
|
|
3346
|
+
}
|
|
2953
3347
|
}
|
|
2954
3348
|
async waitForPageLoad(options = {}, world = null) {
|
|
2955
3349
|
let timeout = this._getLoadTimeout(options);
|
|
@@ -3032,7 +3426,7 @@ class StableBrowser {
|
|
|
3032
3426
|
await _commandError(state, e, this);
|
|
3033
3427
|
}
|
|
3034
3428
|
finally {
|
|
3035
|
-
_commandFinally(state, this);
|
|
3429
|
+
await _commandFinally(state, this);
|
|
3036
3430
|
}
|
|
3037
3431
|
}
|
|
3038
3432
|
async tableCellOperation(headerText, rowText, options, _params, world = null) {
|
|
@@ -3119,7 +3513,7 @@ class StableBrowser {
|
|
|
3119
3513
|
await _commandError(state, e, this);
|
|
3120
3514
|
}
|
|
3121
3515
|
finally {
|
|
3122
|
-
_commandFinally(state, this);
|
|
3516
|
+
await _commandFinally(state, this);
|
|
3123
3517
|
}
|
|
3124
3518
|
}
|
|
3125
3519
|
saveTestDataAsGlobal(options, world) {
|
|
@@ -3224,7 +3618,39 @@ class StableBrowser {
|
|
|
3224
3618
|
console.log("#-#");
|
|
3225
3619
|
}
|
|
3226
3620
|
}
|
|
3621
|
+
async beforeScenario(world, scenario) {
|
|
3622
|
+
this.beforeScenarioCalled = true;
|
|
3623
|
+
if (scenario && scenario.pickle && scenario.pickle.name) {
|
|
3624
|
+
this.scenarioName = scenario.pickle.name;
|
|
3625
|
+
}
|
|
3626
|
+
if (scenario && scenario.gherkinDocument && scenario.gherkinDocument.feature) {
|
|
3627
|
+
this.featureName = scenario.gherkinDocument.feature.name;
|
|
3628
|
+
}
|
|
3629
|
+
if (this.context) {
|
|
3630
|
+
this.context.examplesRow = extractStepExampleParameters(scenario);
|
|
3631
|
+
}
|
|
3632
|
+
if (this.tags === null && scenario && scenario.pickle && scenario.pickle.tags) {
|
|
3633
|
+
this.tags = scenario.pickle.tags.map((tag) => tag.name);
|
|
3634
|
+
// check if @global_test_data tag is present
|
|
3635
|
+
if (this.tags.includes("@global_test_data")) {
|
|
3636
|
+
this.saveTestDataAsGlobal({}, world);
|
|
3637
|
+
}
|
|
3638
|
+
}
|
|
3639
|
+
// update test data based on feature/scenario
|
|
3640
|
+
let envName = null;
|
|
3641
|
+
if (this.context && this.context.environment) {
|
|
3642
|
+
envName = this.context.environment.name;
|
|
3643
|
+
}
|
|
3644
|
+
if (!process.env.TEMP_RUN) {
|
|
3645
|
+
await getTestData(envName, world, undefined, this.featureName, this.scenarioName, this.context);
|
|
3646
|
+
}
|
|
3647
|
+
await loadBrunoParams(this.context, this.context.environment.name);
|
|
3648
|
+
}
|
|
3649
|
+
async afterScenario(world, scenario) { }
|
|
3227
3650
|
async beforeStep(world, step) {
|
|
3651
|
+
if (!this.beforeScenarioCalled) {
|
|
3652
|
+
this.beforeScenario(world, step);
|
|
3653
|
+
}
|
|
3228
3654
|
if (this.stepIndex === undefined) {
|
|
3229
3655
|
this.stepIndex = 0;
|
|
3230
3656
|
}
|
|
@@ -3241,21 +3667,11 @@ class StableBrowser {
|
|
|
3241
3667
|
else {
|
|
3242
3668
|
this.stepName = "step " + this.stepIndex;
|
|
3243
3669
|
}
|
|
3244
|
-
if (this.context) {
|
|
3245
|
-
this.context.examplesRow = extractStepExampleParameters(step);
|
|
3246
|
-
}
|
|
3247
3670
|
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
3248
3671
|
if (this.context.browserObject.context) {
|
|
3249
3672
|
await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
|
|
3250
3673
|
}
|
|
3251
3674
|
}
|
|
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
3675
|
if (this.initSnapshotTaken === false) {
|
|
3260
3676
|
this.initSnapshotTaken = true;
|
|
3261
3677
|
if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
|
|
@@ -3280,18 +3696,68 @@ class StableBrowser {
|
|
|
3280
3696
|
const content = [`- path: ${path}`, `- title: ${title}`];
|
|
3281
3697
|
const timeout = this.configuration.ariaSnapshotTimeout ? this.configuration.ariaSnapshotTimeout : 3000;
|
|
3282
3698
|
for (let i = 0; i < frames.length; i++) {
|
|
3283
|
-
content.push(`- frame: ${i}`);
|
|
3284
3699
|
const frame = frames[i];
|
|
3285
|
-
|
|
3286
|
-
|
|
3700
|
+
try {
|
|
3701
|
+
// Ensure frame is attached and has body
|
|
3702
|
+
const body = frame.locator("body");
|
|
3703
|
+
await body.waitFor({ timeout: 200 }); // wait explicitly
|
|
3704
|
+
const snapshot = await body.ariaSnapshot({ timeout });
|
|
3705
|
+
content.push(`- frame: ${i}`);
|
|
3706
|
+
content.push(snapshot);
|
|
3707
|
+
}
|
|
3708
|
+
catch (innerErr) { }
|
|
3287
3709
|
}
|
|
3288
3710
|
return content.join("\n");
|
|
3289
3711
|
}
|
|
3290
3712
|
catch (e) {
|
|
3291
|
-
console.
|
|
3713
|
+
console.log("Error in getAriaSnapshot");
|
|
3714
|
+
//console.debug(e);
|
|
3292
3715
|
}
|
|
3293
3716
|
return null;
|
|
3294
3717
|
}
|
|
3718
|
+
/**
|
|
3719
|
+
* Sends command with custom payload to report.
|
|
3720
|
+
* @param commandText - Title of the command to be shown in the report.
|
|
3721
|
+
* @param commandStatus - Status of the command (e.g. "PASSED", "FAILED").
|
|
3722
|
+
* @param content - Content of the command to be shown in the report.
|
|
3723
|
+
* @param options - Options for the command. Example: { type: "json", screenshot: true }
|
|
3724
|
+
* @param world - Optional world context.
|
|
3725
|
+
* @public
|
|
3726
|
+
*/
|
|
3727
|
+
async addCommandToReport(commandText, commandStatus, content, options = {}, world = null) {
|
|
3728
|
+
const state = {
|
|
3729
|
+
options,
|
|
3730
|
+
world,
|
|
3731
|
+
locate: false,
|
|
3732
|
+
scroll: false,
|
|
3733
|
+
screenshot: options.screenshot ?? false,
|
|
3734
|
+
highlight: options.highlight ?? false,
|
|
3735
|
+
type: Types.REPORT_COMMAND,
|
|
3736
|
+
text: commandText,
|
|
3737
|
+
_text: commandText,
|
|
3738
|
+
operation: "report_command",
|
|
3739
|
+
log: "***** " + commandText + " *****\n",
|
|
3740
|
+
};
|
|
3741
|
+
try {
|
|
3742
|
+
await _preCommand(state, this);
|
|
3743
|
+
const payload = {
|
|
3744
|
+
type: options.type ?? "text",
|
|
3745
|
+
content: content,
|
|
3746
|
+
screenshotId: null,
|
|
3747
|
+
};
|
|
3748
|
+
state.payload = payload;
|
|
3749
|
+
if (commandStatus === "FAILED") {
|
|
3750
|
+
state.throwError = true;
|
|
3751
|
+
throw new Error("Command failed");
|
|
3752
|
+
}
|
|
3753
|
+
}
|
|
3754
|
+
catch (e) {
|
|
3755
|
+
await _commandError(state, e, this);
|
|
3756
|
+
}
|
|
3757
|
+
finally {
|
|
3758
|
+
await _commandFinally(state, this);
|
|
3759
|
+
}
|
|
3760
|
+
}
|
|
3295
3761
|
async afterStep(world, step) {
|
|
3296
3762
|
this.stepName = null;
|
|
3297
3763
|
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
@@ -3299,6 +3765,13 @@ class StableBrowser {
|
|
|
3299
3765
|
await this.context.browserObject.context.tracing.stopChunk({
|
|
3300
3766
|
path: path.join(this.context.browserObject.traceFolder, `trace-${this.stepIndex}.zip`),
|
|
3301
3767
|
});
|
|
3768
|
+
if (world && world.attach) {
|
|
3769
|
+
await world.attach(JSON.stringify({
|
|
3770
|
+
type: "trace",
|
|
3771
|
+
traceFilePath: `trace-${this.stepIndex}.zip`,
|
|
3772
|
+
}), "application/json+trace");
|
|
3773
|
+
}
|
|
3774
|
+
// console.log("trace file created", `trace-${this.stepIndex}.zip`);
|
|
3302
3775
|
}
|
|
3303
3776
|
}
|
|
3304
3777
|
if (this.context) {
|
|
@@ -3311,6 +3784,29 @@ class StableBrowser {
|
|
|
3311
3784
|
await world.attach(JSON.stringify(snapshot), "application/json+snapshot-after");
|
|
3312
3785
|
}
|
|
3313
3786
|
}
|
|
3787
|
+
if (!process.env.TEMP_RUN) {
|
|
3788
|
+
const state = {
|
|
3789
|
+
world,
|
|
3790
|
+
locate: false,
|
|
3791
|
+
scroll: false,
|
|
3792
|
+
screenshot: true,
|
|
3793
|
+
highlight: true,
|
|
3794
|
+
type: Types.STEP_COMPLETE,
|
|
3795
|
+
text: "end of scenario",
|
|
3796
|
+
_text: "end of scenario",
|
|
3797
|
+
operation: "step_complete",
|
|
3798
|
+
log: "***** " + "end of scenario" + " *****\n",
|
|
3799
|
+
};
|
|
3800
|
+
try {
|
|
3801
|
+
await _preCommand(state, this);
|
|
3802
|
+
}
|
|
3803
|
+
catch (e) {
|
|
3804
|
+
await _commandError(state, e, this);
|
|
3805
|
+
}
|
|
3806
|
+
finally {
|
|
3807
|
+
await _commandFinally(state, this);
|
|
3808
|
+
}
|
|
3809
|
+
}
|
|
3314
3810
|
}
|
|
3315
3811
|
}
|
|
3316
3812
|
function createTimedPromise(promise, label) {
|