automation_model 1.0.636-dev → 1.0.636-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 +99 -28
- package/lib/auto_page.js.map +1 -1
- package/lib/browser_manager.js +38 -9
- package/lib/browser_manager.js.map +1 -1
- package/lib/bruno.d.ts +2 -0
- package/lib/bruno.js +381 -0
- package/lib/bruno.js.map +1 -0
- package/lib/command_common.d.ts +4 -4
- package/lib/command_common.js +36 -16
- package/lib/command_common.js.map +1 -1
- package/lib/date_time.js.map +1 -1
- package/lib/drawRect.js.map +1 -1
- package/lib/environment.d.ts +1 -0
- package/lib/environment.js +1 -0
- package/lib/environment.js.map +1 -1
- package/lib/error-messages.js.map +1 -1
- package/lib/file_checker.d.ts +1 -0
- package/lib/file_checker.js +61 -0
- package/lib/file_checker.js.map +1 -0
- package/lib/find_function.js.map +1 -1
- package/lib/index.d.ts +2 -0
- package/lib/index.js +2 -0
- package/lib/index.js.map +1 -1
- package/lib/init_browser.d.ts +2 -2
- package/lib/init_browser.js +33 -27
- package/lib/init_browser.js.map +1 -1
- package/lib/locate_element.js +2 -2
- package/lib/locate_element.js.map +1 -1
- package/lib/locator.js +1 -1
- package/lib/locator.js.map +1 -1
- package/lib/locator_log.js.map +1 -1
- package/lib/network.d.ts +1 -1
- package/lib/network.js +5 -5
- package/lib/network.js.map +1 -1
- package/lib/snapshot_validation.d.ts +37 -0
- package/lib/snapshot_validation.js +357 -0
- package/lib/snapshot_validation.js.map +1 -0
- package/lib/stable_browser.d.ts +31 -24
- package/lib/stable_browser.js +547 -176
- 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.d.ts +1 -1
- package/lib/table_helper.js +26 -5
- 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 +7 -4
- package/lib/utils.js +205 -20
- package/lib/utils.js.map +1 -1
- package/package.json +11 -6
package/lib/stable_browser.js
CHANGED
|
@@ -10,11 +10,12 @@ import { getDateTimeValue } from "./date_time.js";
|
|
|
10
10
|
import drawRectangle from "./drawRect.js";
|
|
11
11
|
//import { closeUnexpectedPopups } from "./popups.js";
|
|
12
12
|
import { getTableCells, getTableData } from "./table_analyze.js";
|
|
13
|
-
import { _convertToRegexQuery, _copyContext, _fixLocatorUsingParams, _fixUsingParams, _getServerUrl, extractStepExampleParameters, KEYBOARD_EVENTS, maskValue, replaceWithLocalTestData, scrollPageToLoadLazyElements, unEscapeString, _getDataFile, testForRegex, } from "./utils.js";
|
|
13
|
+
import { _convertToRegexQuery, _copyContext, _fixLocatorUsingParams, _fixUsingParams, _getServerUrl, extractStepExampleParameters, KEYBOARD_EVENTS, maskValue, replaceWithLocalTestData, scrollPageToLoadLazyElements, unEscapeString, _getDataFile, testForRegex, performAction, } from "./utils.js";
|
|
14
14
|
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,22 +23,24 @@ import { registerDownloadEvent, registerNetworkEvents } from "./network.js";
|
|
|
22
23
|
import { LocatorLog } from "./locator_log.js";
|
|
23
24
|
import axios from "axios";
|
|
24
25
|
import { _findCellArea, findElementsInArea } from "./table_helper.js";
|
|
26
|
+
import { snapshotValidation } from "./snapshot_validation.js";
|
|
27
|
+
import { loadBrunoParams } from "./bruno.js";
|
|
25
28
|
export const Types = {
|
|
26
29
|
CLICK: "click_element",
|
|
27
30
|
WAIT_ELEMENT: "wait_element",
|
|
28
|
-
NAVIGATE: "navigate",
|
|
31
|
+
NAVIGATE: "navigate", ///
|
|
29
32
|
FILL: "fill_element",
|
|
30
|
-
EXECUTE: "execute_page_method",
|
|
31
|
-
OPEN: "open_environment",
|
|
33
|
+
EXECUTE: "execute_page_method", //
|
|
34
|
+
OPEN: "open_environment", //
|
|
32
35
|
COMPLETE: "step_complete",
|
|
33
36
|
ASK: "information_needed",
|
|
34
|
-
GET_PAGE_STATUS: "get_page_status",
|
|
35
|
-
CLICK_ROW_ACTION: "click_row_action",
|
|
37
|
+
GET_PAGE_STATUS: "get_page_status", ///
|
|
38
|
+
CLICK_ROW_ACTION: "click_row_action", //
|
|
36
39
|
VERIFY_ELEMENT_CONTAINS_TEXT: "verify_element_contains_text",
|
|
37
40
|
VERIFY_PAGE_CONTAINS_TEXT: "verify_page_contains_text",
|
|
38
41
|
VERIFY_PAGE_CONTAINS_NO_TEXT: "verify_page_contains_no_text",
|
|
39
42
|
ANALYZE_TABLE: "analyze_table",
|
|
40
|
-
SELECT: "select_combobox",
|
|
43
|
+
SELECT: "select_combobox", //
|
|
41
44
|
VERIFY_PAGE_PATH: "verify_page_path",
|
|
42
45
|
TYPE_PRESS: "type_press",
|
|
43
46
|
PRESS: "press_key",
|
|
@@ -55,6 +58,12 @@ export const Types = {
|
|
|
55
58
|
WAIT_FOR_TEXT_TO_DISAPPEAR: "wait_for_text_to_disappear",
|
|
56
59
|
VERIFY_ATTRIBUTE: "verify_element_attribute",
|
|
57
60
|
VERIFY_TEXT_WITH_RELATION: "verify_text_with_relation",
|
|
61
|
+
BRUNO: "bruno",
|
|
62
|
+
VERIFY_FILE_EXISTS: "verify_file_exists",
|
|
63
|
+
SET_INPUT_FILES: "set_input_files",
|
|
64
|
+
SNAPSHOT_VALIDATION: "snapshot_validation",
|
|
65
|
+
REPORT_COMMAND: "report_command",
|
|
66
|
+
STEP_COMPLETE: "step_complete",
|
|
58
67
|
};
|
|
59
68
|
export const apps = {};
|
|
60
69
|
const formatElementName = (elementName) => {
|
|
@@ -180,6 +189,30 @@ class StableBrowser {
|
|
|
180
189
|
await this.waitForPageLoad();
|
|
181
190
|
}
|
|
182
191
|
}
|
|
192
|
+
async switchTab(tabTitleOrIndex) {
|
|
193
|
+
// first check if the tabNameOrIndex is a number
|
|
194
|
+
let index = parseInt(tabTitleOrIndex);
|
|
195
|
+
if (!isNaN(index)) {
|
|
196
|
+
if (index >= 0 && index < this.context.pages.length) {
|
|
197
|
+
this.page = this.context.pages[index];
|
|
198
|
+
this.context.page = this.page;
|
|
199
|
+
await this.page.bringToFront();
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
// if the tabNameOrIndex is a string, find the tab by name
|
|
204
|
+
for (let i = 0; i < this.context.pages.length; i++) {
|
|
205
|
+
let page = this.context.pages[i];
|
|
206
|
+
let title = await page.title();
|
|
207
|
+
if (title.includes(tabTitleOrIndex)) {
|
|
208
|
+
this.page = page;
|
|
209
|
+
this.context.page = this.page;
|
|
210
|
+
await this.page.bringToFront();
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
throw new Error("Tab not found: " + tabTitleOrIndex);
|
|
215
|
+
}
|
|
183
216
|
registerConsoleLogListener(page, context) {
|
|
184
217
|
if (!this.context.webLogger) {
|
|
185
218
|
this.context.webLogger = [];
|
|
@@ -247,6 +280,7 @@ class StableBrowser {
|
|
|
247
280
|
if (!url) {
|
|
248
281
|
throw new Error("url is null, verify that the environment file is correct");
|
|
249
282
|
}
|
|
283
|
+
url = await this._replaceWithLocalData(url, this.world);
|
|
250
284
|
if (!url.startsWith("http")) {
|
|
251
285
|
url = "https://" + url;
|
|
252
286
|
}
|
|
@@ -275,7 +309,7 @@ class StableBrowser {
|
|
|
275
309
|
_commandError(state, error, this);
|
|
276
310
|
}
|
|
277
311
|
finally {
|
|
278
|
-
_commandFinally(state, this);
|
|
312
|
+
await _commandFinally(state, this);
|
|
279
313
|
}
|
|
280
314
|
}
|
|
281
315
|
async _getLocator(locator, scope, _params) {
|
|
@@ -391,7 +425,7 @@ class StableBrowser {
|
|
|
391
425
|
}
|
|
392
426
|
return { elementCount: tagCount, randomToken };
|
|
393
427
|
}
|
|
394
|
-
async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true, allowDisabled = false, element_name = null) {
|
|
428
|
+
async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true, allowDisabled = false, element_name = null, logErrors = false) {
|
|
395
429
|
if (!info) {
|
|
396
430
|
info = {};
|
|
397
431
|
}
|
|
@@ -458,7 +492,7 @@ class StableBrowser {
|
|
|
458
492
|
}
|
|
459
493
|
return;
|
|
460
494
|
}
|
|
461
|
-
if (info.locatorLog && count === 0) {
|
|
495
|
+
if (info.locatorLog && count === 0 && logErrors) {
|
|
462
496
|
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "NOT_FOUND");
|
|
463
497
|
}
|
|
464
498
|
for (let j = 0; j < count; j++) {
|
|
@@ -473,7 +507,7 @@ class StableBrowser {
|
|
|
473
507
|
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
|
|
474
508
|
}
|
|
475
509
|
}
|
|
476
|
-
else {
|
|
510
|
+
else if (logErrors) {
|
|
477
511
|
info.failCause.visible = visible;
|
|
478
512
|
info.failCause.enabled = enabled;
|
|
479
513
|
if (!info.printMessages) {
|
|
@@ -565,15 +599,27 @@ class StableBrowser {
|
|
|
565
599
|
let element = await this._locate_internal(selectors, info, _params, timeout, allowDisabled);
|
|
566
600
|
if (!element.rerun) {
|
|
567
601
|
const randomToken = Math.random().toString(36).substring(7);
|
|
568
|
-
element.evaluate((el, randomToken) => {
|
|
602
|
+
await element.evaluate((el, randomToken) => {
|
|
569
603
|
el.setAttribute("data-blinq-id-" + randomToken, "");
|
|
570
604
|
}, randomToken);
|
|
571
|
-
if (element._frame) {
|
|
572
|
-
|
|
573
|
-
}
|
|
574
|
-
const scope = element.page();
|
|
575
|
-
|
|
576
|
-
|
|
605
|
+
// if (element._frame) {
|
|
606
|
+
// return element;
|
|
607
|
+
// }
|
|
608
|
+
const scope = element._frame ?? element.page();
|
|
609
|
+
let newElementSelector = "[data-blinq-id-" + randomToken + "]";
|
|
610
|
+
let prefixSelector = "";
|
|
611
|
+
const frameControlSelector = " >> internal:control=enter-frame";
|
|
612
|
+
const frameSelectorIndex = element._selector.lastIndexOf(frameControlSelector);
|
|
613
|
+
if (frameSelectorIndex !== -1) {
|
|
614
|
+
// remove everything after the >> internal:control=enter-frame
|
|
615
|
+
const frameSelector = element._selector.substring(0, frameSelectorIndex);
|
|
616
|
+
prefixSelector = frameSelector + " >> internal:control=enter-frame >>";
|
|
617
|
+
}
|
|
618
|
+
// if (element?._frame?._selector) {
|
|
619
|
+
// prefixSelector = element._frame._selector + " >> " + prefixSelector;
|
|
620
|
+
// }
|
|
621
|
+
const newSelector = prefixSelector + newElementSelector;
|
|
622
|
+
return scope.locator(newSelector);
|
|
577
623
|
}
|
|
578
624
|
}
|
|
579
625
|
throw new Error("unable to locate element " + JSON.stringify(selectors));
|
|
@@ -725,14 +771,9 @@ class StableBrowser {
|
|
|
725
771
|
// info.log += "scanning locators in priority 2" + "\n";
|
|
726
772
|
result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
727
773
|
}
|
|
728
|
-
if (result.foundElements.length === 0 && onlyPriority3) {
|
|
774
|
+
if (result.foundElements.length === 0 && (onlyPriority3 || !highPriorityOnly)) {
|
|
729
775
|
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
730
776
|
}
|
|
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
777
|
let foundElements = result.foundElements;
|
|
737
778
|
if (foundElements.length === 1 && foundElements[0].unique) {
|
|
738
779
|
info.box = foundElements[0].box;
|
|
@@ -787,6 +828,11 @@ class StableBrowser {
|
|
|
787
828
|
visibleOnly = false;
|
|
788
829
|
}
|
|
789
830
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
831
|
+
// sheck of more of half of the timeout has passed
|
|
832
|
+
if (Date.now() - startTime > timeout / 2) {
|
|
833
|
+
highPriorityOnly = false;
|
|
834
|
+
visibleOnly = false;
|
|
835
|
+
}
|
|
790
836
|
}
|
|
791
837
|
this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
|
|
792
838
|
// if (info.locatorLog) {
|
|
@@ -802,7 +848,7 @@ class StableBrowser {
|
|
|
802
848
|
}
|
|
803
849
|
throw new Error("failed to locate first element no elements found, " + info.log);
|
|
804
850
|
}
|
|
805
|
-
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false, element_name) {
|
|
851
|
+
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false, element_name, logErrors = false) {
|
|
806
852
|
let foundElements = [];
|
|
807
853
|
const result = {
|
|
808
854
|
foundElements: foundElements,
|
|
@@ -821,7 +867,9 @@ class StableBrowser {
|
|
|
821
867
|
await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
|
|
822
868
|
}
|
|
823
869
|
catch (e) {
|
|
824
|
-
|
|
870
|
+
if (logErrors) {
|
|
871
|
+
this.logger.info("unable to use locator (second try) " + JSON.stringify(locatorsGroup[i]));
|
|
872
|
+
}
|
|
825
873
|
}
|
|
826
874
|
}
|
|
827
875
|
if (foundLocators.length === 1) {
|
|
@@ -833,9 +881,40 @@ class StableBrowser {
|
|
|
833
881
|
result.locatorIndex = i;
|
|
834
882
|
}
|
|
835
883
|
if (foundLocators.length > 1) {
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
884
|
+
// remove elements that consume the same space with 10 pixels tolerance
|
|
885
|
+
const boxes = [];
|
|
886
|
+
for (let j = 0; j < foundLocators.length; j++) {
|
|
887
|
+
boxes.push({ box: await foundLocators[j].boundingBox(), locator: foundLocators[j] });
|
|
888
|
+
}
|
|
889
|
+
for (let j = 0; j < boxes.length; j++) {
|
|
890
|
+
for (let k = 0; k < boxes.length; k++) {
|
|
891
|
+
if (j === k) {
|
|
892
|
+
continue;
|
|
893
|
+
}
|
|
894
|
+
// check if x, y, width, height are the same with 10 pixels tolerance
|
|
895
|
+
if (Math.abs(boxes[j].box.x - boxes[k].box.x) < 10 &&
|
|
896
|
+
Math.abs(boxes[j].box.y - boxes[k].box.y) < 10 &&
|
|
897
|
+
Math.abs(boxes[j].box.width - boxes[k].box.width) < 10 &&
|
|
898
|
+
Math.abs(boxes[j].box.height - boxes[k].box.height) < 10) {
|
|
899
|
+
// as the element is not unique, will remove it
|
|
900
|
+
boxes.splice(k, 1);
|
|
901
|
+
k--;
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
if (boxes.length === 1) {
|
|
906
|
+
result.foundElements.push({
|
|
907
|
+
locator: boxes[0].locator.first(),
|
|
908
|
+
box: boxes[0].box,
|
|
909
|
+
unique: true,
|
|
910
|
+
});
|
|
911
|
+
result.locatorIndex = i;
|
|
912
|
+
}
|
|
913
|
+
else if (logErrors) {
|
|
914
|
+
info.failCause.foundMultiple = true;
|
|
915
|
+
if (info.locatorLog) {
|
|
916
|
+
info.locatorLog.setLocatorSearchStatus(JSON.stringify(locatorsGroup[i]), "FOUND_NOT_UNIQUE");
|
|
917
|
+
}
|
|
839
918
|
}
|
|
840
919
|
}
|
|
841
920
|
}
|
|
@@ -883,7 +962,7 @@ class StableBrowser {
|
|
|
883
962
|
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
884
963
|
}
|
|
885
964
|
finally {
|
|
886
|
-
_commandFinally(state, this);
|
|
965
|
+
await _commandFinally(state, this);
|
|
887
966
|
}
|
|
888
967
|
}
|
|
889
968
|
}
|
|
@@ -932,7 +1011,7 @@ class StableBrowser {
|
|
|
932
1011
|
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
933
1012
|
}
|
|
934
1013
|
finally {
|
|
935
|
-
_commandFinally(state, this);
|
|
1014
|
+
await _commandFinally(state, this);
|
|
936
1015
|
}
|
|
937
1016
|
}
|
|
938
1017
|
}
|
|
@@ -953,19 +1032,7 @@ class StableBrowser {
|
|
|
953
1032
|
};
|
|
954
1033
|
try {
|
|
955
1034
|
await _preCommand(state, this);
|
|
956
|
-
|
|
957
|
-
// state.selectors.locators[0].text = state.options.context;
|
|
958
|
-
// }
|
|
959
|
-
try {
|
|
960
|
-
await state.element.click();
|
|
961
|
-
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
962
|
-
}
|
|
963
|
-
catch (e) {
|
|
964
|
-
// await this.closeUnexpectedPopups();
|
|
965
|
-
state.element = await this._locate(selectors, state.info, _params);
|
|
966
|
-
await state.element.dispatchEvent("click");
|
|
967
|
-
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
968
|
-
}
|
|
1035
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
969
1036
|
await this.waitForPageLoad();
|
|
970
1037
|
return state.info;
|
|
971
1038
|
}
|
|
@@ -973,7 +1040,7 @@ class StableBrowser {
|
|
|
973
1040
|
await _commandError(state, e, this);
|
|
974
1041
|
}
|
|
975
1042
|
finally {
|
|
976
|
-
_commandFinally(state, this);
|
|
1043
|
+
await _commandFinally(state, this);
|
|
977
1044
|
}
|
|
978
1045
|
}
|
|
979
1046
|
async waitForElement(selectors, _params, options = {}, world = null) {
|
|
@@ -1004,7 +1071,7 @@ class StableBrowser {
|
|
|
1004
1071
|
// await _commandError(state, e, this);
|
|
1005
1072
|
}
|
|
1006
1073
|
finally {
|
|
1007
|
-
_commandFinally(state, this);
|
|
1074
|
+
await _commandFinally(state, this);
|
|
1008
1075
|
}
|
|
1009
1076
|
return found;
|
|
1010
1077
|
}
|
|
@@ -1028,7 +1095,7 @@ class StableBrowser {
|
|
|
1028
1095
|
try {
|
|
1029
1096
|
// if (world && world.screenshot && !world.screenshotPath) {
|
|
1030
1097
|
// console.log(`Highlighting while running from recorder`);
|
|
1031
|
-
await this._highlightElements(element);
|
|
1098
|
+
await this._highlightElements(state.element);
|
|
1032
1099
|
await state.element.setChecked(checked);
|
|
1033
1100
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1034
1101
|
// await this._unHighlightElements(element);
|
|
@@ -1055,7 +1122,7 @@ class StableBrowser {
|
|
|
1055
1122
|
await _commandError(state, e, this);
|
|
1056
1123
|
}
|
|
1057
1124
|
finally {
|
|
1058
|
-
_commandFinally(state, this);
|
|
1125
|
+
await _commandFinally(state, this);
|
|
1059
1126
|
}
|
|
1060
1127
|
}
|
|
1061
1128
|
async hover(selectors, _params, options = {}, world = null) {
|
|
@@ -1072,19 +1139,7 @@ class StableBrowser {
|
|
|
1072
1139
|
};
|
|
1073
1140
|
try {
|
|
1074
1141
|
await _preCommand(state, this);
|
|
1075
|
-
|
|
1076
|
-
await state.element.hover();
|
|
1077
|
-
// await _screenshot(state, this);
|
|
1078
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1079
|
-
}
|
|
1080
|
-
catch (e) {
|
|
1081
|
-
//await this.closeUnexpectedPopups();
|
|
1082
|
-
state.info.log += "hover failed, will try again" + "\n";
|
|
1083
|
-
state.element = await this._locate(selectors, state.info, _params);
|
|
1084
|
-
await state.element.hover({ timeout: 10000 });
|
|
1085
|
-
// await _screenshot(state, this);
|
|
1086
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1087
|
-
}
|
|
1142
|
+
await performAction("hover", state.element, options, this, state, _params);
|
|
1088
1143
|
await _screenshot(state, this);
|
|
1089
1144
|
await this.waitForPageLoad();
|
|
1090
1145
|
return state.info;
|
|
@@ -1093,7 +1148,7 @@ class StableBrowser {
|
|
|
1093
1148
|
await _commandError(state, e, this);
|
|
1094
1149
|
}
|
|
1095
1150
|
finally {
|
|
1096
|
-
_commandFinally(state, this);
|
|
1151
|
+
await _commandFinally(state, this);
|
|
1097
1152
|
}
|
|
1098
1153
|
}
|
|
1099
1154
|
async selectOption(selectors, values, _params = null, options = {}, world = null) {
|
|
@@ -1129,7 +1184,7 @@ class StableBrowser {
|
|
|
1129
1184
|
await _commandError(state, e, this);
|
|
1130
1185
|
}
|
|
1131
1186
|
finally {
|
|
1132
|
-
_commandFinally(state, this);
|
|
1187
|
+
await _commandFinally(state, this);
|
|
1133
1188
|
}
|
|
1134
1189
|
}
|
|
1135
1190
|
async type(_value, _params = null, options = {}, world = null) {
|
|
@@ -1175,7 +1230,7 @@ class StableBrowser {
|
|
|
1175
1230
|
await _commandError(state, e, this);
|
|
1176
1231
|
}
|
|
1177
1232
|
finally {
|
|
1178
|
-
_commandFinally(state, this);
|
|
1233
|
+
await _commandFinally(state, this);
|
|
1179
1234
|
}
|
|
1180
1235
|
}
|
|
1181
1236
|
async setInputValue(selectors, value, _params = null, options = {}, world = null) {
|
|
@@ -1211,7 +1266,7 @@ class StableBrowser {
|
|
|
1211
1266
|
await _commandError(state, e, this);
|
|
1212
1267
|
}
|
|
1213
1268
|
finally {
|
|
1214
|
-
_commandFinally(state, this);
|
|
1269
|
+
await _commandFinally(state, this);
|
|
1215
1270
|
}
|
|
1216
1271
|
}
|
|
1217
1272
|
async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
|
|
@@ -1231,7 +1286,7 @@ class StableBrowser {
|
|
|
1231
1286
|
try {
|
|
1232
1287
|
await _preCommand(state, this);
|
|
1233
1288
|
try {
|
|
1234
|
-
await state.element
|
|
1289
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
1235
1290
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1236
1291
|
if (format) {
|
|
1237
1292
|
state.value = dayjs(state.value).format(format);
|
|
@@ -1280,7 +1335,7 @@ class StableBrowser {
|
|
|
1280
1335
|
await _commandError(state, e, this);
|
|
1281
1336
|
}
|
|
1282
1337
|
finally {
|
|
1283
|
-
_commandFinally(state, this);
|
|
1338
|
+
await _commandFinally(state, this);
|
|
1284
1339
|
}
|
|
1285
1340
|
}
|
|
1286
1341
|
async clickType(selectors, _value, enter = false, _params = null, options = {}, world = null) {
|
|
@@ -1299,6 +1354,9 @@ class StableBrowser {
|
|
|
1299
1354
|
operation: "clickType",
|
|
1300
1355
|
log: "***** clickType on " + selectors.element_name + " with value " + maskValue(_value) + "*****\n",
|
|
1301
1356
|
};
|
|
1357
|
+
if (!options) {
|
|
1358
|
+
options = {};
|
|
1359
|
+
}
|
|
1302
1360
|
if (newValue !== _value) {
|
|
1303
1361
|
//this.logger.info(_value + "=" + newValue);
|
|
1304
1362
|
_value = newValue;
|
|
@@ -1306,7 +1364,7 @@ class StableBrowser {
|
|
|
1306
1364
|
try {
|
|
1307
1365
|
await _preCommand(state, this);
|
|
1308
1366
|
state.info.value = _value;
|
|
1309
|
-
if (
|
|
1367
|
+
if (!options.press) {
|
|
1310
1368
|
try {
|
|
1311
1369
|
let currentValue = await state.element.inputValue();
|
|
1312
1370
|
if (currentValue) {
|
|
@@ -1317,13 +1375,9 @@ class StableBrowser {
|
|
|
1317
1375
|
this.logger.info("unable to clear input value");
|
|
1318
1376
|
}
|
|
1319
1377
|
}
|
|
1320
|
-
if (options
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
}
|
|
1324
|
-
catch (e) {
|
|
1325
|
-
await state.element.dispatchEvent("click");
|
|
1326
|
-
}
|
|
1378
|
+
if (options.press) {
|
|
1379
|
+
options.timeout = 5000;
|
|
1380
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
1327
1381
|
}
|
|
1328
1382
|
else {
|
|
1329
1383
|
try {
|
|
@@ -1381,7 +1435,7 @@ class StableBrowser {
|
|
|
1381
1435
|
await _commandError(state, e, this);
|
|
1382
1436
|
}
|
|
1383
1437
|
finally {
|
|
1384
|
-
_commandFinally(state, this);
|
|
1438
|
+
await _commandFinally(state, this);
|
|
1385
1439
|
}
|
|
1386
1440
|
}
|
|
1387
1441
|
async fill(selectors, value, enter = false, _params = null, options = {}, world = null) {
|
|
@@ -1411,7 +1465,42 @@ class StableBrowser {
|
|
|
1411
1465
|
await _commandError(state, e, this);
|
|
1412
1466
|
}
|
|
1413
1467
|
finally {
|
|
1414
|
-
_commandFinally(state, this);
|
|
1468
|
+
await _commandFinally(state, this);
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
async setInputFiles(selectors, files, _params = null, options = {}, world = null) {
|
|
1472
|
+
const state = {
|
|
1473
|
+
selectors,
|
|
1474
|
+
_params,
|
|
1475
|
+
files,
|
|
1476
|
+
value: '"' + files.join('", "') + '"',
|
|
1477
|
+
options,
|
|
1478
|
+
world,
|
|
1479
|
+
type: Types.SET_INPUT_FILES,
|
|
1480
|
+
text: `Set input files`,
|
|
1481
|
+
_text: `Set input files on ${selectors.element_name}`,
|
|
1482
|
+
operation: "setInputFiles",
|
|
1483
|
+
log: "***** set input files " + selectors.element_name + " *****\n",
|
|
1484
|
+
};
|
|
1485
|
+
const uploadsFolder = this.configuration.uploadsFolder ?? "data/uploads";
|
|
1486
|
+
try {
|
|
1487
|
+
await _preCommand(state, this);
|
|
1488
|
+
for (let i = 0; i < files.length; i++) {
|
|
1489
|
+
const file = files[i];
|
|
1490
|
+
const filePath = path.join(uploadsFolder, file);
|
|
1491
|
+
if (!fs.existsSync(filePath)) {
|
|
1492
|
+
throw new Error(`File not found: ${filePath}`);
|
|
1493
|
+
}
|
|
1494
|
+
state.files[i] = filePath;
|
|
1495
|
+
}
|
|
1496
|
+
await state.element.setInputFiles(files);
|
|
1497
|
+
return state.info;
|
|
1498
|
+
}
|
|
1499
|
+
catch (e) {
|
|
1500
|
+
await _commandError(state, e, this);
|
|
1501
|
+
}
|
|
1502
|
+
finally {
|
|
1503
|
+
await _commandFinally(state, this);
|
|
1415
1504
|
}
|
|
1416
1505
|
}
|
|
1417
1506
|
async getText(selectors, _params = null, options = {}, info = {}, world = null) {
|
|
@@ -1527,7 +1616,7 @@ class StableBrowser {
|
|
|
1527
1616
|
await _commandError(state, e, this);
|
|
1528
1617
|
}
|
|
1529
1618
|
finally {
|
|
1530
|
-
_commandFinally(state, this);
|
|
1619
|
+
await _commandFinally(state, this);
|
|
1531
1620
|
}
|
|
1532
1621
|
}
|
|
1533
1622
|
async containsText(selectors, text, climb, _params = null, options = {}, world = null) {
|
|
@@ -1562,7 +1651,7 @@ class StableBrowser {
|
|
|
1562
1651
|
while (Date.now() - startTime < timeout) {
|
|
1563
1652
|
try {
|
|
1564
1653
|
await _preCommand(state, this);
|
|
1565
|
-
foundObj = await this._getText(selectors, climb, _params, { timeout:
|
|
1654
|
+
foundObj = await this._getText(selectors, climb, _params, { timeout: 3000 }, state.info, world);
|
|
1566
1655
|
if (foundObj && foundObj.element) {
|
|
1567
1656
|
await this.scrollIfNeeded(foundObj.element, state.info);
|
|
1568
1657
|
}
|
|
@@ -1604,7 +1693,79 @@ class StableBrowser {
|
|
|
1604
1693
|
throw e;
|
|
1605
1694
|
}
|
|
1606
1695
|
finally {
|
|
1607
|
-
_commandFinally(state, this);
|
|
1696
|
+
await _commandFinally(state, this);
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
async snapshotValidation(frameSelectors, referanceSnapshot, _params = null, options = {}, world = null) {
|
|
1700
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1701
|
+
const startTime = Date.now();
|
|
1702
|
+
const state = {
|
|
1703
|
+
_params,
|
|
1704
|
+
value: referanceSnapshot,
|
|
1705
|
+
options,
|
|
1706
|
+
world,
|
|
1707
|
+
locate: false,
|
|
1708
|
+
scroll: false,
|
|
1709
|
+
screenshot: true,
|
|
1710
|
+
highlight: false,
|
|
1711
|
+
type: Types.SNAPSHOT_VALIDATION,
|
|
1712
|
+
text: `verify snapshot: ${referanceSnapshot}`,
|
|
1713
|
+
operation: "snapshotValidation",
|
|
1714
|
+
log: "***** verify snapshot *****\n",
|
|
1715
|
+
};
|
|
1716
|
+
if (!referanceSnapshot) {
|
|
1717
|
+
throw new Error("referanceSnapshot is null");
|
|
1718
|
+
}
|
|
1719
|
+
let text = null;
|
|
1720
|
+
if (fs.existsSync(path.join(this.project_path, "data", "snapshots", this.context.environment.name, referanceSnapshot + ".yml"))) {
|
|
1721
|
+
text = fs.readFileSync(path.join(this.project_path, "data", "snapshots", this.context.environment.name, referanceSnapshot + ".yml"), "utf8");
|
|
1722
|
+
}
|
|
1723
|
+
else if (fs.existsSync(path.join(this.project_path, "data", "snapshots", this.context.environment.name, referanceSnapshot + ".yaml"))) {
|
|
1724
|
+
text = fs.readFileSync(path.join(this.project_path, "data", "snapshots", this.context.environment.name, referanceSnapshot + ".yaml"), "utf8");
|
|
1725
|
+
}
|
|
1726
|
+
else if (referanceSnapshot.startsWith("yaml:")) {
|
|
1727
|
+
text = referanceSnapshot.substring(5);
|
|
1728
|
+
}
|
|
1729
|
+
else {
|
|
1730
|
+
throw new Error("referenceSnapshot file not found: " + referanceSnapshot);
|
|
1731
|
+
}
|
|
1732
|
+
state.text = text;
|
|
1733
|
+
const newValue = await this._replaceWithLocalData(text, world);
|
|
1734
|
+
await _preCommand(state, this);
|
|
1735
|
+
let foundObj = null;
|
|
1736
|
+
try {
|
|
1737
|
+
let matchResult = null;
|
|
1738
|
+
while (Date.now() - startTime < timeout) {
|
|
1739
|
+
try {
|
|
1740
|
+
let scope = null;
|
|
1741
|
+
if (!frameSelectors) {
|
|
1742
|
+
scope = this.page;
|
|
1743
|
+
}
|
|
1744
|
+
else {
|
|
1745
|
+
scope = await this._findFrameScope(frameSelectors, timeout, state.info);
|
|
1746
|
+
}
|
|
1747
|
+
const snapshot = await scope.locator("body").ariaSnapshot({ timeout });
|
|
1748
|
+
matchResult = snapshotValidation(snapshot, newValue, referanceSnapshot);
|
|
1749
|
+
if (matchResult.errorLine !== -1) {
|
|
1750
|
+
throw new Error("Snapshot validation failed at line " + matchResult.errorLineText);
|
|
1751
|
+
}
|
|
1752
|
+
// highlight and screenshot
|
|
1753
|
+
return state.info;
|
|
1754
|
+
}
|
|
1755
|
+
catch (e) {
|
|
1756
|
+
// Log error but continue retrying until timeout is reached
|
|
1757
|
+
//this.logger.warn("Retrying snapshot validation due to: " + e.message);
|
|
1758
|
+
}
|
|
1759
|
+
await new Promise((resolve) => setTimeout(resolve, 2000)); // Wait 1 second before retrying
|
|
1760
|
+
}
|
|
1761
|
+
throw new Error("No snapshot match " + matchResult?.errorLineText);
|
|
1762
|
+
}
|
|
1763
|
+
catch (e) {
|
|
1764
|
+
await _commandError(state, e, this);
|
|
1765
|
+
throw e;
|
|
1766
|
+
}
|
|
1767
|
+
finally {
|
|
1768
|
+
await _commandFinally(state, this);
|
|
1608
1769
|
}
|
|
1609
1770
|
}
|
|
1610
1771
|
async waitForUserInput(message, world = null) {
|
|
@@ -1642,6 +1803,15 @@ class StableBrowser {
|
|
|
1642
1803
|
// save the data to the file
|
|
1643
1804
|
fs.writeFileSync(dataFile, JSON.stringify(data, null, 2));
|
|
1644
1805
|
}
|
|
1806
|
+
overwriteTestData(testData, world = null) {
|
|
1807
|
+
if (!testData) {
|
|
1808
|
+
return;
|
|
1809
|
+
}
|
|
1810
|
+
// if data file exists, load it
|
|
1811
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1812
|
+
// save the data to the file
|
|
1813
|
+
fs.writeFileSync(dataFile, JSON.stringify(testData, null, 2));
|
|
1814
|
+
}
|
|
1645
1815
|
_getDataFilePath(fileName) {
|
|
1646
1816
|
let dataFile = path.join(this.project_path, "data", fileName);
|
|
1647
1817
|
if (fs.existsSync(dataFile)) {
|
|
@@ -1894,7 +2064,7 @@ class StableBrowser {
|
|
|
1894
2064
|
await _commandError(state, e, this);
|
|
1895
2065
|
}
|
|
1896
2066
|
finally {
|
|
1897
|
-
_commandFinally(state, this);
|
|
2067
|
+
await _commandFinally(state, this);
|
|
1898
2068
|
}
|
|
1899
2069
|
}
|
|
1900
2070
|
async extractAttribute(selectors, attribute, variable, _params = null, options = {}, world = null) {
|
|
@@ -1925,10 +2095,31 @@ class StableBrowser {
|
|
|
1925
2095
|
case "value":
|
|
1926
2096
|
state.value = await state.element.inputValue();
|
|
1927
2097
|
break;
|
|
2098
|
+
case "text":
|
|
2099
|
+
state.value = await state.element.textContent();
|
|
2100
|
+
break;
|
|
1928
2101
|
default:
|
|
1929
2102
|
state.value = await state.element.getAttribute(attribute);
|
|
1930
2103
|
break;
|
|
1931
2104
|
}
|
|
2105
|
+
if (options !== null) {
|
|
2106
|
+
if (options.regex && options.regex !== "") {
|
|
2107
|
+
// Construct a regex pattern from the provided string
|
|
2108
|
+
const regex = options.regex.slice(1, -1);
|
|
2109
|
+
const regexPattern = new RegExp(regex, "g");
|
|
2110
|
+
const matches = state.value.match(regexPattern);
|
|
2111
|
+
if (matches) {
|
|
2112
|
+
let newValue = "";
|
|
2113
|
+
for (const match of matches) {
|
|
2114
|
+
newValue += match;
|
|
2115
|
+
}
|
|
2116
|
+
state.value = newValue;
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
if (options.trimSpaces && options.trimSpaces === true) {
|
|
2120
|
+
state.value = state.value.trim();
|
|
2121
|
+
}
|
|
2122
|
+
}
|
|
1932
2123
|
state.info.value = state.value;
|
|
1933
2124
|
this.setTestData({ [variable]: state.value }, world);
|
|
1934
2125
|
this.logger.info("set test data: " + variable + "=" + state.value);
|
|
@@ -1939,7 +2130,7 @@ class StableBrowser {
|
|
|
1939
2130
|
await _commandError(state, e, this);
|
|
1940
2131
|
}
|
|
1941
2132
|
finally {
|
|
1942
|
-
_commandFinally(state, this);
|
|
2133
|
+
await _commandFinally(state, this);
|
|
1943
2134
|
}
|
|
1944
2135
|
}
|
|
1945
2136
|
async verifyAttribute(selectors, attribute, value, _params = null, options = {}, world = null) {
|
|
@@ -1964,12 +2155,15 @@ class StableBrowser {
|
|
|
1964
2155
|
let expectedValue;
|
|
1965
2156
|
try {
|
|
1966
2157
|
await _preCommand(state, this);
|
|
1967
|
-
expectedValue = state.value;
|
|
2158
|
+
expectedValue = await replaceWithLocalTestData(state.value, world);
|
|
1968
2159
|
state.info.expectedValue = expectedValue;
|
|
1969
2160
|
switch (attribute) {
|
|
1970
2161
|
case "innerText":
|
|
1971
2162
|
val = String(await state.element.innerText());
|
|
1972
2163
|
break;
|
|
2164
|
+
case "text":
|
|
2165
|
+
val = String(await state.element.textContent());
|
|
2166
|
+
break;
|
|
1973
2167
|
case "value":
|
|
1974
2168
|
val = String(await state.element.inputValue());
|
|
1975
2169
|
break;
|
|
@@ -1991,17 +2185,42 @@ class StableBrowser {
|
|
|
1991
2185
|
let regex;
|
|
1992
2186
|
if (expectedValue.startsWith("/") && expectedValue.endsWith("/")) {
|
|
1993
2187
|
const patternBody = expectedValue.slice(1, -1);
|
|
1994
|
-
|
|
2188
|
+
const processedPattern = patternBody.replace(/\n/g, ".*");
|
|
2189
|
+
regex = new RegExp(processedPattern, "gs");
|
|
2190
|
+
state.info.regex = true;
|
|
1995
2191
|
}
|
|
1996
2192
|
else {
|
|
1997
2193
|
const escapedPattern = expectedValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1998
2194
|
regex = new RegExp(escapedPattern, "g");
|
|
1999
2195
|
}
|
|
2000
|
-
if (
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2196
|
+
if (attribute === "innerText") {
|
|
2197
|
+
if (state.info.regex) {
|
|
2198
|
+
if (!regex.test(val)) {
|
|
2199
|
+
let errorMessage = `The ${attribute} attribute has a value of "${val}", but the expected value is "${expectedValue}"`;
|
|
2200
|
+
state.info.failCause.assertionFailed = true;
|
|
2201
|
+
state.info.failCause.lastError = errorMessage;
|
|
2202
|
+
throw new Error(errorMessage);
|
|
2203
|
+
}
|
|
2204
|
+
}
|
|
2205
|
+
else {
|
|
2206
|
+
const valLines = val.split("\n");
|
|
2207
|
+
const expectedLines = expectedValue.split("\n");
|
|
2208
|
+
const isPart = expectedLines.every((expectedLine) => valLines.some((valLine) => valLine === expectedLine));
|
|
2209
|
+
if (!isPart) {
|
|
2210
|
+
let errorMessage = `The ${attribute} attribute has a value of "${val}", but the expected value is "${expectedValue}"`;
|
|
2211
|
+
state.info.failCause.assertionFailed = true;
|
|
2212
|
+
state.info.failCause.lastError = errorMessage;
|
|
2213
|
+
throw new Error(errorMessage);
|
|
2214
|
+
}
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
2217
|
+
else {
|
|
2218
|
+
if (!val.match(regex)) {
|
|
2219
|
+
let errorMessage = `The ${attribute} attribute has a value of "${val}", but the expected value is "${expectedValue}"`;
|
|
2220
|
+
state.info.failCause.assertionFailed = true;
|
|
2221
|
+
state.info.failCause.lastError = errorMessage;
|
|
2222
|
+
throw new Error(errorMessage);
|
|
2223
|
+
}
|
|
2005
2224
|
}
|
|
2006
2225
|
return state.info;
|
|
2007
2226
|
}
|
|
@@ -2009,7 +2228,7 @@ class StableBrowser {
|
|
|
2009
2228
|
await _commandError(state, e, this);
|
|
2010
2229
|
}
|
|
2011
2230
|
finally {
|
|
2012
|
-
_commandFinally(state, this);
|
|
2231
|
+
await _commandFinally(state, this);
|
|
2013
2232
|
}
|
|
2014
2233
|
}
|
|
2015
2234
|
async extractEmailData(emailAddress, options, world) {
|
|
@@ -2169,54 +2388,6 @@ class StableBrowser {
|
|
|
2169
2388
|
console.debug(error);
|
|
2170
2389
|
}
|
|
2171
2390
|
}
|
|
2172
|
-
// async _unhighlightElements(scope, css) {
|
|
2173
|
-
// try {
|
|
2174
|
-
// if (!scope) {
|
|
2175
|
-
// return;
|
|
2176
|
-
// }
|
|
2177
|
-
// if (!css) {
|
|
2178
|
-
// scope
|
|
2179
|
-
// .evaluate((node) => {
|
|
2180
|
-
// if (node && node.style) {
|
|
2181
|
-
// if (!node.__previousOutline) {
|
|
2182
|
-
// node.style.outline = "";
|
|
2183
|
-
// } else {
|
|
2184
|
-
// node.style.outline = node.__previousOutline;
|
|
2185
|
-
// }
|
|
2186
|
-
// }
|
|
2187
|
-
// })
|
|
2188
|
-
// .then(() => {})
|
|
2189
|
-
// .catch((e) => {
|
|
2190
|
-
// // console.log(`Error while unhighlighting node ${JSON.stringify(scope)}: ${e}`);
|
|
2191
|
-
// });
|
|
2192
|
-
// } else {
|
|
2193
|
-
// scope
|
|
2194
|
-
// .evaluate(([css]) => {
|
|
2195
|
-
// if (!css) {
|
|
2196
|
-
// return;
|
|
2197
|
-
// }
|
|
2198
|
-
// let elements = Array.from(document.querySelectorAll(css));
|
|
2199
|
-
// for (i = 0; i < elements.length; i++) {
|
|
2200
|
-
// let element = elements[i];
|
|
2201
|
-
// if (!element.style) {
|
|
2202
|
-
// return;
|
|
2203
|
-
// }
|
|
2204
|
-
// if (!element.__previousOutline) {
|
|
2205
|
-
// element.style.outline = "";
|
|
2206
|
-
// } else {
|
|
2207
|
-
// element.style.outline = element.__previousOutline;
|
|
2208
|
-
// }
|
|
2209
|
-
// }
|
|
2210
|
-
// })
|
|
2211
|
-
// .then(() => {})
|
|
2212
|
-
// .catch((e) => {
|
|
2213
|
-
// // console.error(`Error while unhighlighting element in css: ${e}`);
|
|
2214
|
-
// });
|
|
2215
|
-
// }
|
|
2216
|
-
// } catch (error) {
|
|
2217
|
-
// // console.debug(error);
|
|
2218
|
-
// }
|
|
2219
|
-
// }
|
|
2220
2391
|
async verifyPagePath(pathPart, options = {}, world = null) {
|
|
2221
2392
|
const startTime = Date.now();
|
|
2222
2393
|
let error = null;
|
|
@@ -2254,7 +2425,7 @@ class StableBrowser {
|
|
|
2254
2425
|
Object.assign(e, { info: info });
|
|
2255
2426
|
error = e;
|
|
2256
2427
|
// throw e;
|
|
2257
|
-
await _commandError({ text: "verifyPagePath", operation: "verifyPagePath", pathPart, info }, e, this);
|
|
2428
|
+
await _commandError({ text: "verifyPagePath", operation: "verifyPagePath", pathPart, info, throwError: true }, e, this);
|
|
2258
2429
|
}
|
|
2259
2430
|
finally {
|
|
2260
2431
|
const endTime = Date.now();
|
|
@@ -2279,27 +2450,89 @@ class StableBrowser {
|
|
|
2279
2450
|
});
|
|
2280
2451
|
}
|
|
2281
2452
|
}
|
|
2282
|
-
async
|
|
2453
|
+
async verifyPageTitle(title, options = {}, world = null) {
|
|
2454
|
+
const startTime = Date.now();
|
|
2455
|
+
let error = null;
|
|
2456
|
+
let screenshotId = null;
|
|
2457
|
+
let screenshotPath = null;
|
|
2458
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2459
|
+
const info = {};
|
|
2460
|
+
info.log = "***** verify page title " + title + " *****\n";
|
|
2461
|
+
info.operation = "verifyPageTitle";
|
|
2462
|
+
const newValue = await this._replaceWithLocalData(title, world);
|
|
2463
|
+
if (newValue !== title) {
|
|
2464
|
+
this.logger.info(title + "=" + newValue);
|
|
2465
|
+
title = newValue;
|
|
2466
|
+
}
|
|
2467
|
+
info.title = title;
|
|
2468
|
+
try {
|
|
2469
|
+
for (let i = 0; i < 30; i++) {
|
|
2470
|
+
const foundTitle = await this.page.title();
|
|
2471
|
+
if (!foundTitle.includes(title)) {
|
|
2472
|
+
if (i === 29) {
|
|
2473
|
+
throw new Error(`url ${foundTitle} doesn't contain ${title}`);
|
|
2474
|
+
}
|
|
2475
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2476
|
+
continue;
|
|
2477
|
+
}
|
|
2478
|
+
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2479
|
+
return info;
|
|
2480
|
+
}
|
|
2481
|
+
}
|
|
2482
|
+
catch (e) {
|
|
2483
|
+
//await this.closeUnexpectedPopups();
|
|
2484
|
+
this.logger.error("verify page title failed " + info.log);
|
|
2485
|
+
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2486
|
+
info.screenshotPath = screenshotPath;
|
|
2487
|
+
Object.assign(e, { info: info });
|
|
2488
|
+
error = e;
|
|
2489
|
+
// throw e;
|
|
2490
|
+
await _commandError({ text: "verifyPageTitle", operation: "verifyPageTitle", title, info, throwError: true }, e, this);
|
|
2491
|
+
}
|
|
2492
|
+
finally {
|
|
2493
|
+
const endTime = Date.now();
|
|
2494
|
+
_reportToWorld(world, {
|
|
2495
|
+
type: Types.VERIFY_PAGE_PATH,
|
|
2496
|
+
text: "Verify page title",
|
|
2497
|
+
_text: "Verify the page title contains " + title,
|
|
2498
|
+
screenshotId,
|
|
2499
|
+
result: error
|
|
2500
|
+
? {
|
|
2501
|
+
status: "FAILED",
|
|
2502
|
+
startTime,
|
|
2503
|
+
endTime,
|
|
2504
|
+
message: error?.message,
|
|
2505
|
+
}
|
|
2506
|
+
: {
|
|
2507
|
+
status: "PASSED",
|
|
2508
|
+
startTime,
|
|
2509
|
+
endTime,
|
|
2510
|
+
},
|
|
2511
|
+
info: info,
|
|
2512
|
+
});
|
|
2513
|
+
}
|
|
2514
|
+
}
|
|
2515
|
+
async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state, partial = true, ignoreCase = false) {
|
|
2283
2516
|
const frames = this.page.frames();
|
|
2284
2517
|
let results = [];
|
|
2285
|
-
let ignoreCase = false;
|
|
2518
|
+
// let ignoreCase = false;
|
|
2286
2519
|
for (let i = 0; i < frames.length; i++) {
|
|
2287
2520
|
if (dateAlternatives.date) {
|
|
2288
2521
|
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2289
|
-
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false,
|
|
2522
|
+
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, partial, ignoreCase, {});
|
|
2290
2523
|
result.frame = frames[i];
|
|
2291
2524
|
results.push(result);
|
|
2292
2525
|
}
|
|
2293
2526
|
}
|
|
2294
2527
|
else if (numberAlternatives.number) {
|
|
2295
2528
|
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2296
|
-
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false,
|
|
2529
|
+
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, partial, ignoreCase, {});
|
|
2297
2530
|
result.frame = frames[i];
|
|
2298
2531
|
results.push(result);
|
|
2299
2532
|
}
|
|
2300
2533
|
}
|
|
2301
2534
|
else {
|
|
2302
|
-
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false,
|
|
2535
|
+
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, partial, ignoreCase, {});
|
|
2303
2536
|
result.frame = frames[i];
|
|
2304
2537
|
results.push(result);
|
|
2305
2538
|
}
|
|
@@ -2318,13 +2551,13 @@ class StableBrowser {
|
|
|
2318
2551
|
scroll: false,
|
|
2319
2552
|
highlight: false,
|
|
2320
2553
|
type: Types.VERIFY_PAGE_CONTAINS_TEXT,
|
|
2321
|
-
text: `Verify text exists in page`,
|
|
2554
|
+
text: `Verify the text '${maskValue(text)}' exists in page`,
|
|
2322
2555
|
_text: `Verify the text '${text}' exists in page`,
|
|
2323
2556
|
operation: "verifyTextExistInPage",
|
|
2324
2557
|
log: "***** verify text " + text + " exists in page *****\n",
|
|
2325
2558
|
};
|
|
2326
2559
|
if (testForRegex(text)) {
|
|
2327
|
-
text = text.replace(
|
|
2560
|
+
text = text.replace(/\\"/g, '"');
|
|
2328
2561
|
}
|
|
2329
2562
|
const timeout = this._getFindElementTimeout(options);
|
|
2330
2563
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
@@ -2396,7 +2629,7 @@ class StableBrowser {
|
|
|
2396
2629
|
await _commandError(state, e, this);
|
|
2397
2630
|
}
|
|
2398
2631
|
finally {
|
|
2399
|
-
_commandFinally(state, this);
|
|
2632
|
+
await _commandFinally(state, this);
|
|
2400
2633
|
}
|
|
2401
2634
|
}
|
|
2402
2635
|
async waitForTextToDisappear(text, options = {}, world = null) {
|
|
@@ -2409,11 +2642,14 @@ class StableBrowser {
|
|
|
2409
2642
|
scroll: false,
|
|
2410
2643
|
highlight: false,
|
|
2411
2644
|
type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
|
|
2412
|
-
text: `Verify text does not exist in page`,
|
|
2645
|
+
text: `Verify the text '${maskValue(text)}' does not exist in page`,
|
|
2413
2646
|
_text: `Verify the text '${text}' does not exist in page`,
|
|
2414
2647
|
operation: "verifyTextNotExistInPage",
|
|
2415
2648
|
log: "***** verify text " + text + " does not exist in page *****\n",
|
|
2416
2649
|
};
|
|
2650
|
+
if (testForRegex(text)) {
|
|
2651
|
+
text = text.replace(/\\"/g, '"');
|
|
2652
|
+
}
|
|
2417
2653
|
const timeout = this._getFindElementTimeout(options);
|
|
2418
2654
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2419
2655
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
@@ -2450,7 +2686,7 @@ class StableBrowser {
|
|
|
2450
2686
|
await _commandError(state, e, this);
|
|
2451
2687
|
}
|
|
2452
2688
|
finally {
|
|
2453
|
-
_commandFinally(state, this);
|
|
2689
|
+
await _commandFinally(state, this);
|
|
2454
2690
|
}
|
|
2455
2691
|
}
|
|
2456
2692
|
async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
|
|
@@ -2492,7 +2728,7 @@ class StableBrowser {
|
|
|
2492
2728
|
};
|
|
2493
2729
|
while (true) {
|
|
2494
2730
|
try {
|
|
2495
|
-
resultWithElementsFound = await this.findTextInAllFrames(
|
|
2731
|
+
resultWithElementsFound = await this.findTextInAllFrames(findDateAlternatives(textAnchor), findNumberAlternatives(textAnchor), textAnchor, state, false);
|
|
2496
2732
|
}
|
|
2497
2733
|
catch (error) {
|
|
2498
2734
|
// ignore
|
|
@@ -2520,7 +2756,7 @@ class StableBrowser {
|
|
|
2520
2756
|
const count = await frame.locator(css).count();
|
|
2521
2757
|
for (let j = 0; j < count; j++) {
|
|
2522
2758
|
const continer = await frame.locator(css).nth(j);
|
|
2523
|
-
const result = await this._locateElementByText(continer, textToVerify, "
|
|
2759
|
+
const result = await this._locateElementByText(continer, textToVerify, "*:not(script, style, head)", false, true, true, {});
|
|
2524
2760
|
if (result.elementCount > 0) {
|
|
2525
2761
|
const dataAttribute = "[data-blinq-id-" + result.randomToken + "]";
|
|
2526
2762
|
await this._highlightElements(frame, dataAttribute);
|
|
@@ -2561,8 +2797,32 @@ class StableBrowser {
|
|
|
2561
2797
|
await _commandError(state, e, this);
|
|
2562
2798
|
}
|
|
2563
2799
|
finally {
|
|
2564
|
-
_commandFinally(state, this);
|
|
2800
|
+
await _commandFinally(state, this);
|
|
2801
|
+
}
|
|
2802
|
+
}
|
|
2803
|
+
async findRelatedTextInAllFrames(textAnchor, climb, textToVerify, params = {}, options = {}, world = null) {
|
|
2804
|
+
const frames = this.page.frames();
|
|
2805
|
+
let results = [];
|
|
2806
|
+
let ignoreCase = false;
|
|
2807
|
+
for (let i = 0; i < frames.length; i++) {
|
|
2808
|
+
const result = await this._locateElementByText(frames[i], textAnchor, "*:not(script, style, head)", false, true, ignoreCase, {});
|
|
2809
|
+
result.frame = frames[i];
|
|
2810
|
+
const climbArray = [];
|
|
2811
|
+
for (let i = 0; i < climb; i++) {
|
|
2812
|
+
climbArray.push("..");
|
|
2813
|
+
}
|
|
2814
|
+
let climbXpath = "xpath=" + climbArray.join("/");
|
|
2815
|
+
const newLocator = `[data-blinq-id-${result.randomToken}] ${climb > 0 ? ">> " + climbXpath : ""} >> internal:text=${testForRegex(textToVerify) ? textToVerify : unEscapeString(textToVerify)}`;
|
|
2816
|
+
const count = await frames[i].locator(newLocator).count();
|
|
2817
|
+
if (count > 0) {
|
|
2818
|
+
result.elementCount = count;
|
|
2819
|
+
result.locator = newLocator;
|
|
2820
|
+
results.push(result);
|
|
2821
|
+
}
|
|
2565
2822
|
}
|
|
2823
|
+
// state.info.results = results;
|
|
2824
|
+
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2825
|
+
return resultWithElementsFound;
|
|
2566
2826
|
}
|
|
2567
2827
|
async visualVerification(text, options = {}, world = null) {
|
|
2568
2828
|
const startTime = Date.now();
|
|
@@ -2880,7 +3140,13 @@ class StableBrowser {
|
|
|
2880
3140
|
}
|
|
2881
3141
|
}
|
|
2882
3142
|
async _replaceWithLocalData(value, world, _decrypt = true, totpWait = true) {
|
|
2883
|
-
|
|
3143
|
+
try {
|
|
3144
|
+
return await replaceWithLocalTestData(value, world, _decrypt, totpWait, this.context, this);
|
|
3145
|
+
}
|
|
3146
|
+
catch (error) {
|
|
3147
|
+
this.logger.debug(error);
|
|
3148
|
+
throw error;
|
|
3149
|
+
}
|
|
2884
3150
|
}
|
|
2885
3151
|
_getLoadTimeout(options) {
|
|
2886
3152
|
let timeout = 15000;
|
|
@@ -2903,6 +3169,7 @@ class StableBrowser {
|
|
|
2903
3169
|
}
|
|
2904
3170
|
async saveStoreState(path = null, world = null) {
|
|
2905
3171
|
const storageState = await this.page.context().storageState();
|
|
3172
|
+
path = await this._replaceWithLocalData(path, this.world);
|
|
2906
3173
|
//const testDataFile = _getDataFile(world, this.context, this);
|
|
2907
3174
|
if (path) {
|
|
2908
3175
|
// save { storageState: storageState } into the path
|
|
@@ -2913,10 +3180,14 @@ class StableBrowser {
|
|
|
2913
3180
|
}
|
|
2914
3181
|
}
|
|
2915
3182
|
async restoreSaveState(path = null, world = null) {
|
|
3183
|
+
path = await this._replaceWithLocalData(path, this.world);
|
|
2916
3184
|
await refreshBrowser(this, path, world);
|
|
2917
3185
|
this.registerEventListeners(this.context);
|
|
2918
3186
|
registerNetworkEvents(this.world, this, this.context, this.page);
|
|
2919
3187
|
registerDownloadEvent(this.page, this.world, this.context);
|
|
3188
|
+
if (this.onRestoreSaveState) {
|
|
3189
|
+
this.onRestoreSaveState(path);
|
|
3190
|
+
}
|
|
2920
3191
|
}
|
|
2921
3192
|
async waitForPageLoad(options = {}, world = null) {
|
|
2922
3193
|
let timeout = this._getLoadTimeout(options);
|
|
@@ -2999,10 +3270,10 @@ class StableBrowser {
|
|
|
2999
3270
|
await _commandError(state, e, this);
|
|
3000
3271
|
}
|
|
3001
3272
|
finally {
|
|
3002
|
-
_commandFinally(state, this);
|
|
3273
|
+
await _commandFinally(state, this);
|
|
3003
3274
|
}
|
|
3004
3275
|
}
|
|
3005
|
-
async tableCellOperation(headerText, rowText, options, world = null) {
|
|
3276
|
+
async tableCellOperation(headerText, rowText, options, _params, world = null) {
|
|
3006
3277
|
let operation = null;
|
|
3007
3278
|
if (!options || !options.operation) {
|
|
3008
3279
|
throw new Error("operation is not defined");
|
|
@@ -3059,26 +3330,24 @@ class StableBrowser {
|
|
|
3059
3330
|
await this.page.mouse.click(cellArea.x + cellArea.width / 2 + xOffset, cellArea.y + cellArea.height / 2 + yOffset);
|
|
3060
3331
|
}
|
|
3061
3332
|
else {
|
|
3062
|
-
const results = await findElementsInArea(options.css, cellArea, this
|
|
3333
|
+
const results = await findElementsInArea(options.css, cellArea, this, options);
|
|
3063
3334
|
if (results.length === 0) {
|
|
3064
3335
|
throw new Error(`Element not found in cell area`);
|
|
3065
3336
|
}
|
|
3066
3337
|
state.element = results[0];
|
|
3067
|
-
await
|
|
3338
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
3068
3339
|
}
|
|
3069
3340
|
break;
|
|
3070
3341
|
case "hover+click":
|
|
3071
3342
|
if (!options.css) {
|
|
3072
3343
|
throw new Error("css is not defined");
|
|
3073
3344
|
}
|
|
3074
|
-
const results = await findElementsInArea(options.css, cellArea, this
|
|
3345
|
+
const results = await findElementsInArea(options.css, cellArea, this, options);
|
|
3075
3346
|
if (results.length === 0) {
|
|
3076
3347
|
throw new Error(`Element not found in cell area`);
|
|
3077
3348
|
}
|
|
3078
3349
|
state.element = results[0];
|
|
3079
|
-
await
|
|
3080
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
3081
|
-
await results[0].click();
|
|
3350
|
+
await performAction("hover+click", state.element, options, this, state, _params);
|
|
3082
3351
|
break;
|
|
3083
3352
|
default:
|
|
3084
3353
|
throw new Error("operation is not supported");
|
|
@@ -3088,7 +3357,7 @@ class StableBrowser {
|
|
|
3088
3357
|
await _commandError(state, e, this);
|
|
3089
3358
|
}
|
|
3090
3359
|
finally {
|
|
3091
|
-
_commandFinally(state, this);
|
|
3360
|
+
await _commandFinally(state, this);
|
|
3092
3361
|
}
|
|
3093
3362
|
}
|
|
3094
3363
|
saveTestDataAsGlobal(options, world) {
|
|
@@ -3193,7 +3462,39 @@ class StableBrowser {
|
|
|
3193
3462
|
console.log("#-#");
|
|
3194
3463
|
}
|
|
3195
3464
|
}
|
|
3465
|
+
async beforeScenario(world, scenario) {
|
|
3466
|
+
this.beforeScenarioCalled = true;
|
|
3467
|
+
if (scenario && scenario.pickle && scenario.pickle.name) {
|
|
3468
|
+
this.scenarioName = scenario.pickle.name;
|
|
3469
|
+
}
|
|
3470
|
+
if (scenario && scenario.gherkinDocument && scenario.gherkinDocument.feature) {
|
|
3471
|
+
this.featureName = scenario.gherkinDocument.feature.name;
|
|
3472
|
+
}
|
|
3473
|
+
if (this.context) {
|
|
3474
|
+
this.context.examplesRow = extractStepExampleParameters(scenario);
|
|
3475
|
+
}
|
|
3476
|
+
if (this.tags === null && scenario && scenario.pickle && scenario.pickle.tags) {
|
|
3477
|
+
this.tags = scenario.pickle.tags.map((tag) => tag.name);
|
|
3478
|
+
// check if @global_test_data tag is present
|
|
3479
|
+
if (this.tags.includes("@global_test_data")) {
|
|
3480
|
+
this.saveTestDataAsGlobal({}, world);
|
|
3481
|
+
}
|
|
3482
|
+
}
|
|
3483
|
+
// update test data based on feature/scenario
|
|
3484
|
+
let envName = null;
|
|
3485
|
+
if (this.context && this.context.environment) {
|
|
3486
|
+
envName = this.context.environment.name;
|
|
3487
|
+
}
|
|
3488
|
+
if (!process.env.TEMP_RUN) {
|
|
3489
|
+
await getTestData(envName, world, undefined, this.featureName, this.scenarioName);
|
|
3490
|
+
}
|
|
3491
|
+
await loadBrunoParams(this.context, this.context.environment.name);
|
|
3492
|
+
}
|
|
3493
|
+
async afterScenario(world, scenario) { }
|
|
3196
3494
|
async beforeStep(world, step) {
|
|
3495
|
+
if (!this.beforeScenarioCalled) {
|
|
3496
|
+
this.beforeScenario(world, step);
|
|
3497
|
+
}
|
|
3197
3498
|
if (this.stepIndex === undefined) {
|
|
3198
3499
|
this.stepIndex = 0;
|
|
3199
3500
|
}
|
|
@@ -3210,21 +3511,11 @@ class StableBrowser {
|
|
|
3210
3511
|
else {
|
|
3211
3512
|
this.stepName = "step " + this.stepIndex;
|
|
3212
3513
|
}
|
|
3213
|
-
if (this.context) {
|
|
3214
|
-
this.context.examplesRow = extractStepExampleParameters(step);
|
|
3215
|
-
}
|
|
3216
3514
|
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
3217
3515
|
if (this.context.browserObject.context) {
|
|
3218
3516
|
await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
|
|
3219
3517
|
}
|
|
3220
3518
|
}
|
|
3221
|
-
if (this.tags === null && step && step.pickle && step.pickle.tags) {
|
|
3222
|
-
this.tags = step.pickle.tags.map((tag) => tag.name);
|
|
3223
|
-
// check if @global_test_data tag is present
|
|
3224
|
-
if (this.tags.includes("@global_test_data")) {
|
|
3225
|
-
this.saveTestDataAsGlobal({}, world);
|
|
3226
|
-
}
|
|
3227
|
-
}
|
|
3228
3519
|
if (this.initSnapshotTaken === false) {
|
|
3229
3520
|
this.initSnapshotTaken = true;
|
|
3230
3521
|
if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
|
|
@@ -3249,18 +3540,68 @@ class StableBrowser {
|
|
|
3249
3540
|
const content = [`- path: ${path}`, `- title: ${title}`];
|
|
3250
3541
|
const timeout = this.configuration.ariaSnapshotTimeout ? this.configuration.ariaSnapshotTimeout : 3000;
|
|
3251
3542
|
for (let i = 0; i < frames.length; i++) {
|
|
3252
|
-
content.push(`- frame: ${i}`);
|
|
3253
3543
|
const frame = frames[i];
|
|
3254
|
-
|
|
3255
|
-
|
|
3544
|
+
try {
|
|
3545
|
+
// Ensure frame is attached and has body
|
|
3546
|
+
const body = frame.locator("body");
|
|
3547
|
+
await body.waitFor({ timeout: 200 }); // wait explicitly
|
|
3548
|
+
const snapshot = await body.ariaSnapshot({ timeout });
|
|
3549
|
+
content.push(`- frame: ${i}`);
|
|
3550
|
+
content.push(snapshot);
|
|
3551
|
+
}
|
|
3552
|
+
catch (innerErr) { }
|
|
3256
3553
|
}
|
|
3257
3554
|
return content.join("\n");
|
|
3258
3555
|
}
|
|
3259
3556
|
catch (e) {
|
|
3260
|
-
console.
|
|
3557
|
+
console.log("Error in getAriaSnapshot");
|
|
3558
|
+
//console.debug(e);
|
|
3261
3559
|
}
|
|
3262
3560
|
return null;
|
|
3263
3561
|
}
|
|
3562
|
+
/**
|
|
3563
|
+
* Sends command with custom payload to report.
|
|
3564
|
+
* @param commandText - Title of the command to be shown in the report.
|
|
3565
|
+
* @param commandStatus - Status of the command (e.g. "PASSED", "FAILED").
|
|
3566
|
+
* @param content - Content of the command to be shown in the report.
|
|
3567
|
+
* @param options - Options for the command. Example: { type: "json", screenshot: true }
|
|
3568
|
+
* @param world - Optional world context.
|
|
3569
|
+
* @public
|
|
3570
|
+
*/
|
|
3571
|
+
async addCommandToReport(commandText, commandStatus, content, options = {}, world = null) {
|
|
3572
|
+
const state = {
|
|
3573
|
+
options,
|
|
3574
|
+
world,
|
|
3575
|
+
locate: false,
|
|
3576
|
+
scroll: false,
|
|
3577
|
+
screenshot: options.screenshot ?? false,
|
|
3578
|
+
highlight: options.highlight ?? false,
|
|
3579
|
+
type: Types.REPORT_COMMAND,
|
|
3580
|
+
text: commandText,
|
|
3581
|
+
_text: commandText,
|
|
3582
|
+
operation: "report_command",
|
|
3583
|
+
log: "***** " + commandText + " *****\n",
|
|
3584
|
+
};
|
|
3585
|
+
try {
|
|
3586
|
+
await _preCommand(state, this);
|
|
3587
|
+
const payload = {
|
|
3588
|
+
type: options.type ?? "text",
|
|
3589
|
+
content: content,
|
|
3590
|
+
screenshotId: null,
|
|
3591
|
+
};
|
|
3592
|
+
state.payload = payload;
|
|
3593
|
+
if (commandStatus === "FAILED") {
|
|
3594
|
+
state.throwError = true;
|
|
3595
|
+
throw new Error("Command failed");
|
|
3596
|
+
}
|
|
3597
|
+
}
|
|
3598
|
+
catch (e) {
|
|
3599
|
+
await _commandError(state, e, this);
|
|
3600
|
+
}
|
|
3601
|
+
finally {
|
|
3602
|
+
await _commandFinally(state, this);
|
|
3603
|
+
}
|
|
3604
|
+
}
|
|
3264
3605
|
async afterStep(world, step) {
|
|
3265
3606
|
this.stepName = null;
|
|
3266
3607
|
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
@@ -3268,6 +3609,13 @@ class StableBrowser {
|
|
|
3268
3609
|
await this.context.browserObject.context.tracing.stopChunk({
|
|
3269
3610
|
path: path.join(this.context.browserObject.traceFolder, `trace-${this.stepIndex}.zip`),
|
|
3270
3611
|
});
|
|
3612
|
+
if (world && world.attach) {
|
|
3613
|
+
await world.attach(JSON.stringify({
|
|
3614
|
+
type: "trace",
|
|
3615
|
+
traceFilePath: `trace-${this.stepIndex}.zip`,
|
|
3616
|
+
}), "application/json+trace");
|
|
3617
|
+
}
|
|
3618
|
+
// console.log("trace file created", `trace-${this.stepIndex}.zip`);
|
|
3271
3619
|
}
|
|
3272
3620
|
}
|
|
3273
3621
|
if (this.context) {
|
|
@@ -3280,6 +3628,29 @@ class StableBrowser {
|
|
|
3280
3628
|
await world.attach(JSON.stringify(snapshot), "application/json+snapshot-after");
|
|
3281
3629
|
}
|
|
3282
3630
|
}
|
|
3631
|
+
if (!process.env.TEMP_RUN) {
|
|
3632
|
+
const state = {
|
|
3633
|
+
world,
|
|
3634
|
+
locate: false,
|
|
3635
|
+
scroll: false,
|
|
3636
|
+
screenshot: true,
|
|
3637
|
+
highlight: true,
|
|
3638
|
+
type: Types.STEP_COMPLETE,
|
|
3639
|
+
text: "end of scenario",
|
|
3640
|
+
_text: "end of scenario",
|
|
3641
|
+
operation: "step_complete",
|
|
3642
|
+
log: "***** " + "end of scenario" + " *****\n",
|
|
3643
|
+
};
|
|
3644
|
+
try {
|
|
3645
|
+
await _preCommand(state, this);
|
|
3646
|
+
}
|
|
3647
|
+
catch (e) {
|
|
3648
|
+
await _commandError(state, e, this);
|
|
3649
|
+
}
|
|
3650
|
+
finally {
|
|
3651
|
+
await _commandFinally(state, this);
|
|
3652
|
+
}
|
|
3653
|
+
}
|
|
3283
3654
|
}
|
|
3284
3655
|
}
|
|
3285
3656
|
function createTimedPromise(promise, label) {
|