automation_model 1.0.615-dev → 1.0.615-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 +130 -0
- package/lib/api.js +35 -21
- package/lib/api.js.map +1 -1
- package/lib/auto_page.d.ts +2 -1
- package/lib/auto_page.js +79 -13
- package/lib/auto_page.js.map +1 -1
- package/lib/browser_manager.js +49 -15
- 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 +34 -16
- package/lib/command_common.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/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 -23
- 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/network.d.ts +1 -1
- package/lib/network.js +5 -5
- package/lib/network.js.map +1 -1
- package/lib/stable_browser.d.ts +21 -1
- package/lib/stable_browser.js +584 -121
- package/lib/stable_browser.js.map +1 -1
- package/lib/table_helper.d.ts +19 -0
- package/lib/table_helper.js +116 -0
- package/lib/table_helper.js.map +1 -0
- package/lib/test_context.d.ts +1 -0
- package/lib/test_context.js +1 -0
- package/lib/test_context.js.map +1 -1
- package/lib/utils.d.ts +8 -4
- package/lib/utils.js +220 -19
- package/lib/utils.js.map +1 -1
- package/package.json +9 -8
package/lib/stable_browser.js
CHANGED
|
@@ -10,19 +10,23 @@ 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, } 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";
|
|
21
22
|
import { registerDownloadEvent, registerNetworkEvents } from "./network.js";
|
|
22
23
|
import { LocatorLog } from "./locator_log.js";
|
|
23
24
|
import axios from "axios";
|
|
25
|
+
import { _findCellArea, findElementsInArea } from "./table_helper.js";
|
|
26
|
+
import { loadBrunoParams } from "./bruno.js";
|
|
24
27
|
export const Types = {
|
|
25
28
|
CLICK: "click_element",
|
|
29
|
+
WAIT_ELEMENT: "wait_element",
|
|
26
30
|
NAVIGATE: "navigate",
|
|
27
31
|
FILL: "fill_element",
|
|
28
32
|
EXECUTE: "execute_page_method",
|
|
@@ -44,6 +48,7 @@ export const Types = {
|
|
|
44
48
|
UNCHECK: "uncheck_element",
|
|
45
49
|
EXTRACT: "extract_attribute",
|
|
46
50
|
CLOSE_PAGE: "close_page",
|
|
51
|
+
TABLE_OPERATION: "table_operation",
|
|
47
52
|
SET_DATE_TIME: "set_date_time",
|
|
48
53
|
SET_VIEWPORT: "set_viewport",
|
|
49
54
|
VERIFY_VISUAL: "verify_visual",
|
|
@@ -52,6 +57,9 @@ export const Types = {
|
|
|
52
57
|
WAIT_FOR_TEXT_TO_DISAPPEAR: "wait_for_text_to_disappear",
|
|
53
58
|
VERIFY_ATTRIBUTE: "verify_element_attribute",
|
|
54
59
|
VERIFY_TEXT_WITH_RELATION: "verify_text_with_relation",
|
|
60
|
+
BRUNO: "bruno",
|
|
61
|
+
VERIFY_FILE_EXISTS: "verify_file_exists",
|
|
62
|
+
SET_INPUT_FILES: "set_input_files",
|
|
55
63
|
};
|
|
56
64
|
export const apps = {};
|
|
57
65
|
const formatElementName = (elementName) => {
|
|
@@ -70,6 +78,7 @@ class StableBrowser {
|
|
|
70
78
|
appName = "main";
|
|
71
79
|
tags = null;
|
|
72
80
|
isRecording = false;
|
|
81
|
+
initSnapshotTaken = false;
|
|
73
82
|
constructor(browser, page, logger = null, context = null, world = null) {
|
|
74
83
|
this.browser = browser;
|
|
75
84
|
this.page = page;
|
|
@@ -176,6 +185,30 @@ class StableBrowser {
|
|
|
176
185
|
await this.waitForPageLoad();
|
|
177
186
|
}
|
|
178
187
|
}
|
|
188
|
+
async switchTab(tabTitleOrIndex) {
|
|
189
|
+
// first check if the tabNameOrIndex is a number
|
|
190
|
+
let index = parseInt(tabTitleOrIndex);
|
|
191
|
+
if (!isNaN(index)) {
|
|
192
|
+
if (index >= 0 && index < this.context.pages.length) {
|
|
193
|
+
this.page = this.context.pages[index];
|
|
194
|
+
this.context.page = this.page;
|
|
195
|
+
await this.page.bringToFront();
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
// if the tabNameOrIndex is a string, find the tab by name
|
|
200
|
+
for (let i = 0; i < this.context.pages.length; i++) {
|
|
201
|
+
let page = this.context.pages[i];
|
|
202
|
+
let title = await page.title();
|
|
203
|
+
if (title.includes(tabTitleOrIndex)) {
|
|
204
|
+
this.page = page;
|
|
205
|
+
this.context.page = this.page;
|
|
206
|
+
await this.page.bringToFront();
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
throw new Error("Tab not found: " + tabTitleOrIndex);
|
|
211
|
+
}
|
|
179
212
|
registerConsoleLogListener(page, context) {
|
|
180
213
|
if (!this.context.webLogger) {
|
|
181
214
|
this.context.webLogger = [];
|
|
@@ -271,7 +304,7 @@ class StableBrowser {
|
|
|
271
304
|
_commandError(state, error, this);
|
|
272
305
|
}
|
|
273
306
|
finally {
|
|
274
|
-
_commandFinally(state, this);
|
|
307
|
+
await _commandFinally(state, this);
|
|
275
308
|
}
|
|
276
309
|
}
|
|
277
310
|
async _getLocator(locator, scope, _params) {
|
|
@@ -352,7 +385,7 @@ class StableBrowser {
|
|
|
352
385
|
return resultCss;
|
|
353
386
|
}
|
|
354
387
|
async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, ignoreCase = true, _params) {
|
|
355
|
-
const query = _convertToRegexQuery(text1, regex1, !partial1, ignoreCase)
|
|
388
|
+
const query = `${_convertToRegexQuery(text1, regex1, !partial1, ignoreCase)}`;
|
|
356
389
|
const locator = scope.locator(query);
|
|
357
390
|
const count = await locator.count();
|
|
358
391
|
if (!tag1) {
|
|
@@ -372,6 +405,12 @@ class StableBrowser {
|
|
|
372
405
|
if (!el.setAttribute) {
|
|
373
406
|
el = el.parentElement;
|
|
374
407
|
}
|
|
408
|
+
// remove any attributes start with data-blinq-id
|
|
409
|
+
// for (let i = 0; i < el.attributes.length; i++) {
|
|
410
|
+
// if (el.attributes[i].name.startsWith("data-blinq-id")) {
|
|
411
|
+
// el.removeAttribute(el.attributes[i].name);
|
|
412
|
+
// }
|
|
413
|
+
// }
|
|
375
414
|
el.setAttribute("data-blinq-id-" + randomToken, "");
|
|
376
415
|
return true;
|
|
377
416
|
}, [tag1, randomToken]))) {
|
|
@@ -558,9 +597,24 @@ class StableBrowser {
|
|
|
558
597
|
element.evaluate((el, randomToken) => {
|
|
559
598
|
el.setAttribute("data-blinq-id-" + randomToken, "");
|
|
560
599
|
}, randomToken);
|
|
600
|
+
// if (element._frame) {
|
|
601
|
+
// return element;
|
|
602
|
+
// }
|
|
561
603
|
const scope = element._frame ?? element.page();
|
|
562
|
-
|
|
563
|
-
|
|
604
|
+
let newElementSelector = "[data-blinq-id-" + randomToken + "]";
|
|
605
|
+
let prefixSelector = "";
|
|
606
|
+
const frameControlSelector = " >> internal:control=enter-frame";
|
|
607
|
+
const frameSelectorIndex = element._selector.lastIndexOf(frameControlSelector);
|
|
608
|
+
if (frameSelectorIndex !== -1) {
|
|
609
|
+
// remove everything after the >> internal:control=enter-frame
|
|
610
|
+
const frameSelector = element._selector.substring(0, frameSelectorIndex);
|
|
611
|
+
prefixSelector = frameSelector + " >> internal:control=enter-frame >>";
|
|
612
|
+
}
|
|
613
|
+
// if (element?._frame?._selector) {
|
|
614
|
+
// prefixSelector = element._frame._selector + " >> " + prefixSelector;
|
|
615
|
+
// }
|
|
616
|
+
const newSelector = prefixSelector + newElementSelector;
|
|
617
|
+
return scope.locator(newSelector);
|
|
564
618
|
}
|
|
565
619
|
}
|
|
566
620
|
throw new Error("unable to locate element " + JSON.stringify(selectors));
|
|
@@ -712,14 +766,9 @@ class StableBrowser {
|
|
|
712
766
|
// info.log += "scanning locators in priority 2" + "\n";
|
|
713
767
|
result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
714
768
|
}
|
|
715
|
-
if (result.foundElements.length === 0 && onlyPriority3) {
|
|
769
|
+
if (result.foundElements.length === 0 && (onlyPriority3 || !highPriorityOnly)) {
|
|
716
770
|
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
717
771
|
}
|
|
718
|
-
else {
|
|
719
|
-
if (result.foundElements.length === 0 && !highPriorityOnly) {
|
|
720
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
721
|
-
}
|
|
722
|
-
}
|
|
723
772
|
let foundElements = result.foundElements;
|
|
724
773
|
if (foundElements.length === 1 && foundElements[0].unique) {
|
|
725
774
|
info.box = foundElements[0].box;
|
|
@@ -774,6 +823,11 @@ class StableBrowser {
|
|
|
774
823
|
visibleOnly = false;
|
|
775
824
|
}
|
|
776
825
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
826
|
+
// sheck of more of half of the timeout has passed
|
|
827
|
+
if (Date.now() - startTime > timeout / 2) {
|
|
828
|
+
highPriorityOnly = false;
|
|
829
|
+
visibleOnly = false;
|
|
830
|
+
}
|
|
777
831
|
}
|
|
778
832
|
this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
|
|
779
833
|
// if (info.locatorLog) {
|
|
@@ -820,9 +874,40 @@ class StableBrowser {
|
|
|
820
874
|
result.locatorIndex = i;
|
|
821
875
|
}
|
|
822
876
|
if (foundLocators.length > 1) {
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
877
|
+
// remove elements that consume the same space with 10 pixels tolerance
|
|
878
|
+
const boxes = [];
|
|
879
|
+
for (let j = 0; j < foundLocators.length; j++) {
|
|
880
|
+
boxes.push({ box: await foundLocators[j].boundingBox(), locator: foundLocators[j] });
|
|
881
|
+
}
|
|
882
|
+
for (let j = 0; j < boxes.length; j++) {
|
|
883
|
+
for (let k = 0; k < boxes.length; k++) {
|
|
884
|
+
if (j === k) {
|
|
885
|
+
continue;
|
|
886
|
+
}
|
|
887
|
+
// check if x, y, width, height are the same with 10 pixels tolerance
|
|
888
|
+
if (Math.abs(boxes[j].box.x - boxes[k].box.x) < 10 &&
|
|
889
|
+
Math.abs(boxes[j].box.y - boxes[k].box.y) < 10 &&
|
|
890
|
+
Math.abs(boxes[j].box.width - boxes[k].box.width) < 10 &&
|
|
891
|
+
Math.abs(boxes[j].box.height - boxes[k].box.height) < 10) {
|
|
892
|
+
// as the element is not unique, will remove it
|
|
893
|
+
boxes.splice(k, 1);
|
|
894
|
+
k--;
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
if (boxes.length === 1) {
|
|
899
|
+
result.foundElements.push({
|
|
900
|
+
locator: boxes[0].locator.first(),
|
|
901
|
+
box: boxes[0].box,
|
|
902
|
+
unique: true,
|
|
903
|
+
});
|
|
904
|
+
result.locatorIndex = i;
|
|
905
|
+
}
|
|
906
|
+
else {
|
|
907
|
+
info.failCause.foundMultiple = true;
|
|
908
|
+
if (info.locatorLog) {
|
|
909
|
+
info.locatorLog.setLocatorSearchStatus(JSON.stringify(locatorsGroup[i]), "FOUND_NOT_UNIQUE");
|
|
910
|
+
}
|
|
826
911
|
}
|
|
827
912
|
}
|
|
828
913
|
}
|
|
@@ -870,7 +955,7 @@ class StableBrowser {
|
|
|
870
955
|
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
871
956
|
}
|
|
872
957
|
finally {
|
|
873
|
-
_commandFinally(state, this);
|
|
958
|
+
await _commandFinally(state, this);
|
|
874
959
|
}
|
|
875
960
|
}
|
|
876
961
|
}
|
|
@@ -919,7 +1004,7 @@ class StableBrowser {
|
|
|
919
1004
|
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
920
1005
|
}
|
|
921
1006
|
finally {
|
|
922
|
-
_commandFinally(state, this);
|
|
1007
|
+
await _commandFinally(state, this);
|
|
923
1008
|
}
|
|
924
1009
|
}
|
|
925
1010
|
}
|
|
@@ -933,25 +1018,14 @@ class StableBrowser {
|
|
|
933
1018
|
options,
|
|
934
1019
|
world,
|
|
935
1020
|
text: "Click element",
|
|
1021
|
+
_text: "Click on " + selectors.element_name,
|
|
936
1022
|
type: Types.CLICK,
|
|
937
1023
|
operation: "click",
|
|
938
1024
|
log: "***** click on " + selectors.element_name + " *****\n",
|
|
939
1025
|
};
|
|
940
1026
|
try {
|
|
941
1027
|
await _preCommand(state, this);
|
|
942
|
-
|
|
943
|
-
// state.selectors.locators[0].text = state.options.context;
|
|
944
|
-
// }
|
|
945
|
-
try {
|
|
946
|
-
await state.element.click();
|
|
947
|
-
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
948
|
-
}
|
|
949
|
-
catch (e) {
|
|
950
|
-
// await this.closeUnexpectedPopups();
|
|
951
|
-
state.element = await this._locate(selectors, state.info, _params);
|
|
952
|
-
await state.element.dispatchEvent("click");
|
|
953
|
-
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
954
|
-
}
|
|
1028
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
955
1029
|
await this.waitForPageLoad();
|
|
956
1030
|
return state.info;
|
|
957
1031
|
}
|
|
@@ -959,9 +1033,41 @@ class StableBrowser {
|
|
|
959
1033
|
await _commandError(state, e, this);
|
|
960
1034
|
}
|
|
961
1035
|
finally {
|
|
962
|
-
_commandFinally(state, this);
|
|
1036
|
+
await _commandFinally(state, this);
|
|
963
1037
|
}
|
|
964
1038
|
}
|
|
1039
|
+
async waitForElement(selectors, _params, options = {}, world = null) {
|
|
1040
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1041
|
+
const state = {
|
|
1042
|
+
selectors,
|
|
1043
|
+
_params,
|
|
1044
|
+
options,
|
|
1045
|
+
world,
|
|
1046
|
+
text: "Wait for element",
|
|
1047
|
+
_text: "Wait for " + selectors.element_name,
|
|
1048
|
+
type: Types.WAIT_ELEMENT,
|
|
1049
|
+
operation: "waitForElement",
|
|
1050
|
+
log: "***** wait for " + selectors.element_name + " *****\n",
|
|
1051
|
+
};
|
|
1052
|
+
let found = false;
|
|
1053
|
+
try {
|
|
1054
|
+
await _preCommand(state, this);
|
|
1055
|
+
// if (state.options && state.options.context) {
|
|
1056
|
+
// state.selectors.locators[0].text = state.options.context;
|
|
1057
|
+
// }
|
|
1058
|
+
await state.element.waitFor({ timeout: timeout });
|
|
1059
|
+
found = true;
|
|
1060
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1061
|
+
}
|
|
1062
|
+
catch (e) {
|
|
1063
|
+
console.error("Error on waitForElement", e);
|
|
1064
|
+
// await _commandError(state, e, this);
|
|
1065
|
+
}
|
|
1066
|
+
finally {
|
|
1067
|
+
await _commandFinally(state, this);
|
|
1068
|
+
}
|
|
1069
|
+
return found;
|
|
1070
|
+
}
|
|
965
1071
|
async setCheck(selectors, checked = true, _params, options = {}, world = null) {
|
|
966
1072
|
const state = {
|
|
967
1073
|
selectors,
|
|
@@ -970,6 +1076,7 @@ class StableBrowser {
|
|
|
970
1076
|
world,
|
|
971
1077
|
type: checked ? Types.CHECK : Types.UNCHECK,
|
|
972
1078
|
text: checked ? `Check element` : `Uncheck element`,
|
|
1079
|
+
_text: checked ? `Check ${selectors.element_name}` : `Uncheck ${selectors.element_name}`,
|
|
973
1080
|
operation: "setCheck",
|
|
974
1081
|
log: "***** check " + selectors.element_name + " *****\n",
|
|
975
1082
|
};
|
|
@@ -981,7 +1088,7 @@ class StableBrowser {
|
|
|
981
1088
|
try {
|
|
982
1089
|
// if (world && world.screenshot && !world.screenshotPath) {
|
|
983
1090
|
// console.log(`Highlighting while running from recorder`);
|
|
984
|
-
await this._highlightElements(element);
|
|
1091
|
+
await this._highlightElements(state.element);
|
|
985
1092
|
await state.element.setChecked(checked);
|
|
986
1093
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
987
1094
|
// await this._unHighlightElements(element);
|
|
@@ -1008,7 +1115,7 @@ class StableBrowser {
|
|
|
1008
1115
|
await _commandError(state, e, this);
|
|
1009
1116
|
}
|
|
1010
1117
|
finally {
|
|
1011
|
-
_commandFinally(state, this);
|
|
1118
|
+
await _commandFinally(state, this);
|
|
1012
1119
|
}
|
|
1013
1120
|
}
|
|
1014
1121
|
async hover(selectors, _params, options = {}, world = null) {
|
|
@@ -1019,24 +1126,13 @@ class StableBrowser {
|
|
|
1019
1126
|
world,
|
|
1020
1127
|
type: Types.HOVER,
|
|
1021
1128
|
text: `Hover element`,
|
|
1129
|
+
_text: `Hover on ${selectors.element_name}`,
|
|
1022
1130
|
operation: "hover",
|
|
1023
1131
|
log: "***** hover " + selectors.element_name + " *****\n",
|
|
1024
1132
|
};
|
|
1025
1133
|
try {
|
|
1026
1134
|
await _preCommand(state, this);
|
|
1027
|
-
|
|
1028
|
-
await state.element.hover();
|
|
1029
|
-
// await _screenshot(state, this);
|
|
1030
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1031
|
-
}
|
|
1032
|
-
catch (e) {
|
|
1033
|
-
//await this.closeUnexpectedPopups();
|
|
1034
|
-
state.info.log += "hover failed, will try again" + "\n";
|
|
1035
|
-
state.element = await this._locate(selectors, state.info, _params);
|
|
1036
|
-
await state.element.hover({ timeout: 10000 });
|
|
1037
|
-
// await _screenshot(state, this);
|
|
1038
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1039
|
-
}
|
|
1135
|
+
await performAction("hover", state.element, options, this, state, _params);
|
|
1040
1136
|
await _screenshot(state, this);
|
|
1041
1137
|
await this.waitForPageLoad();
|
|
1042
1138
|
return state.info;
|
|
@@ -1045,7 +1141,7 @@ class StableBrowser {
|
|
|
1045
1141
|
await _commandError(state, e, this);
|
|
1046
1142
|
}
|
|
1047
1143
|
finally {
|
|
1048
|
-
_commandFinally(state, this);
|
|
1144
|
+
await _commandFinally(state, this);
|
|
1049
1145
|
}
|
|
1050
1146
|
}
|
|
1051
1147
|
async selectOption(selectors, values, _params = null, options = {}, world = null) {
|
|
@@ -1060,6 +1156,7 @@ class StableBrowser {
|
|
|
1060
1156
|
value: values.toString(),
|
|
1061
1157
|
type: Types.SELECT,
|
|
1062
1158
|
text: `Select option: ${values}`,
|
|
1159
|
+
_text: `Select option: ${values} on ${selectors.element_name}`,
|
|
1063
1160
|
operation: "selectOption",
|
|
1064
1161
|
log: "***** select option " + selectors.element_name + " *****\n",
|
|
1065
1162
|
};
|
|
@@ -1080,7 +1177,7 @@ class StableBrowser {
|
|
|
1080
1177
|
await _commandError(state, e, this);
|
|
1081
1178
|
}
|
|
1082
1179
|
finally {
|
|
1083
|
-
_commandFinally(state, this);
|
|
1180
|
+
await _commandFinally(state, this);
|
|
1084
1181
|
}
|
|
1085
1182
|
}
|
|
1086
1183
|
async type(_value, _params = null, options = {}, world = null) {
|
|
@@ -1094,6 +1191,7 @@ class StableBrowser {
|
|
|
1094
1191
|
highlight: false,
|
|
1095
1192
|
type: Types.TYPE_PRESS,
|
|
1096
1193
|
text: `Type value: ${_value}`,
|
|
1194
|
+
_text: `Type value: ${_value}`,
|
|
1097
1195
|
operation: "type",
|
|
1098
1196
|
log: "",
|
|
1099
1197
|
};
|
|
@@ -1125,7 +1223,7 @@ class StableBrowser {
|
|
|
1125
1223
|
await _commandError(state, e, this);
|
|
1126
1224
|
}
|
|
1127
1225
|
finally {
|
|
1128
|
-
_commandFinally(state, this);
|
|
1226
|
+
await _commandFinally(state, this);
|
|
1129
1227
|
}
|
|
1130
1228
|
}
|
|
1131
1229
|
async setInputValue(selectors, value, _params = null, options = {}, world = null) {
|
|
@@ -1161,7 +1259,7 @@ class StableBrowser {
|
|
|
1161
1259
|
await _commandError(state, e, this);
|
|
1162
1260
|
}
|
|
1163
1261
|
finally {
|
|
1164
|
-
_commandFinally(state, this);
|
|
1262
|
+
await _commandFinally(state, this);
|
|
1165
1263
|
}
|
|
1166
1264
|
}
|
|
1167
1265
|
async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
|
|
@@ -1173,6 +1271,7 @@ class StableBrowser {
|
|
|
1173
1271
|
world,
|
|
1174
1272
|
type: Types.SET_DATE_TIME,
|
|
1175
1273
|
text: `Set date time value: ${value}`,
|
|
1274
|
+
_text: `Set date time value: ${value} on ${selectors.element_name}`,
|
|
1176
1275
|
operation: "setDateTime",
|
|
1177
1276
|
log: "***** set date time value " + selectors.element_name + " *****\n",
|
|
1178
1277
|
throwError: false,
|
|
@@ -1180,7 +1279,7 @@ class StableBrowser {
|
|
|
1180
1279
|
try {
|
|
1181
1280
|
await _preCommand(state, this);
|
|
1182
1281
|
try {
|
|
1183
|
-
await state.element
|
|
1282
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
1184
1283
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1185
1284
|
if (format) {
|
|
1186
1285
|
state.value = dayjs(state.value).format(format);
|
|
@@ -1229,7 +1328,7 @@ class StableBrowser {
|
|
|
1229
1328
|
await _commandError(state, e, this);
|
|
1230
1329
|
}
|
|
1231
1330
|
finally {
|
|
1232
|
-
_commandFinally(state, this);
|
|
1331
|
+
await _commandFinally(state, this);
|
|
1233
1332
|
}
|
|
1234
1333
|
}
|
|
1235
1334
|
async clickType(selectors, _value, enter = false, _params = null, options = {}, world = null) {
|
|
@@ -1244,9 +1343,13 @@ class StableBrowser {
|
|
|
1244
1343
|
world,
|
|
1245
1344
|
type: Types.FILL,
|
|
1246
1345
|
text: `Click type input with value: ${_value}`,
|
|
1346
|
+
_text: "Fill " + selectors.element_name + " with value " + maskValue(_value),
|
|
1247
1347
|
operation: "clickType",
|
|
1248
1348
|
log: "***** clickType on " + selectors.element_name + " with value " + maskValue(_value) + "*****\n",
|
|
1249
1349
|
};
|
|
1350
|
+
if (!options) {
|
|
1351
|
+
options = {};
|
|
1352
|
+
}
|
|
1250
1353
|
if (newValue !== _value) {
|
|
1251
1354
|
//this.logger.info(_value + "=" + newValue);
|
|
1252
1355
|
_value = newValue;
|
|
@@ -1254,7 +1357,7 @@ class StableBrowser {
|
|
|
1254
1357
|
try {
|
|
1255
1358
|
await _preCommand(state, this);
|
|
1256
1359
|
state.info.value = _value;
|
|
1257
|
-
if (
|
|
1360
|
+
if (!options.press) {
|
|
1258
1361
|
try {
|
|
1259
1362
|
let currentValue = await state.element.inputValue();
|
|
1260
1363
|
if (currentValue) {
|
|
@@ -1265,13 +1368,9 @@ class StableBrowser {
|
|
|
1265
1368
|
this.logger.info("unable to clear input value");
|
|
1266
1369
|
}
|
|
1267
1370
|
}
|
|
1268
|
-
if (options
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
}
|
|
1272
|
-
catch (e) {
|
|
1273
|
-
await state.element.dispatchEvent("click");
|
|
1274
|
-
}
|
|
1371
|
+
if (options.press) {
|
|
1372
|
+
options.timeout = 5000;
|
|
1373
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
1275
1374
|
}
|
|
1276
1375
|
else {
|
|
1277
1376
|
try {
|
|
@@ -1329,7 +1428,7 @@ class StableBrowser {
|
|
|
1329
1428
|
await _commandError(state, e, this);
|
|
1330
1429
|
}
|
|
1331
1430
|
finally {
|
|
1332
|
-
_commandFinally(state, this);
|
|
1431
|
+
await _commandFinally(state, this);
|
|
1333
1432
|
}
|
|
1334
1433
|
}
|
|
1335
1434
|
async fill(selectors, value, enter = false, _params = null, options = {}, world = null) {
|
|
@@ -1359,13 +1458,49 @@ class StableBrowser {
|
|
|
1359
1458
|
await _commandError(state, e, this);
|
|
1360
1459
|
}
|
|
1361
1460
|
finally {
|
|
1362
|
-
_commandFinally(state, this);
|
|
1461
|
+
await _commandFinally(state, this);
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
async setInputFiles(selectors, files, _params = null, options = {}, world = null) {
|
|
1465
|
+
const state = {
|
|
1466
|
+
selectors,
|
|
1467
|
+
_params,
|
|
1468
|
+
files,
|
|
1469
|
+
value: '"' + files.join('", "') + '"',
|
|
1470
|
+
options,
|
|
1471
|
+
world,
|
|
1472
|
+
type: Types.SET_INPUT_FILES,
|
|
1473
|
+
text: `Set input files`,
|
|
1474
|
+
_text: `Set input files on ${selectors.element_name}`,
|
|
1475
|
+
operation: "setInputFiles",
|
|
1476
|
+
log: "***** set input files " + selectors.element_name + " *****\n",
|
|
1477
|
+
};
|
|
1478
|
+
const uploadsFolder = this.configuration.uploadsFolder ?? "data/uploads";
|
|
1479
|
+
try {
|
|
1480
|
+
await _preCommand(state, this);
|
|
1481
|
+
for (let i = 0; i < files.length; i++) {
|
|
1482
|
+
const file = files[i];
|
|
1483
|
+
const filePath = path.join(uploadsFolder, file);
|
|
1484
|
+
if (!fs.existsSync(filePath)) {
|
|
1485
|
+
throw new Error(`File not found: ${filePath}`);
|
|
1486
|
+
}
|
|
1487
|
+
state.files[i] = filePath;
|
|
1488
|
+
}
|
|
1489
|
+
await state.element.setInputFiles(files);
|
|
1490
|
+
return state.info;
|
|
1491
|
+
}
|
|
1492
|
+
catch (e) {
|
|
1493
|
+
await _commandError(state, e, this);
|
|
1494
|
+
}
|
|
1495
|
+
finally {
|
|
1496
|
+
await _commandFinally(state, this);
|
|
1363
1497
|
}
|
|
1364
1498
|
}
|
|
1365
1499
|
async getText(selectors, _params = null, options = {}, info = {}, world = null) {
|
|
1366
1500
|
return await this._getText(selectors, 0, _params, options, info, world);
|
|
1367
1501
|
}
|
|
1368
1502
|
async _getText(selectors, climb, _params = null, options = {}, info = {}, world = null) {
|
|
1503
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1369
1504
|
_validateSelectors(selectors);
|
|
1370
1505
|
let screenshotId = null;
|
|
1371
1506
|
let screenshotPath = null;
|
|
@@ -1375,7 +1510,7 @@ class StableBrowser {
|
|
|
1375
1510
|
}
|
|
1376
1511
|
info.operation = "getText";
|
|
1377
1512
|
info.selectors = selectors;
|
|
1378
|
-
let element = await this._locate(selectors, info, _params);
|
|
1513
|
+
let element = await this._locate(selectors, info, _params, timeout);
|
|
1379
1514
|
if (climb > 0) {
|
|
1380
1515
|
const climbArray = [];
|
|
1381
1516
|
for (let i = 0; i < climb; i++) {
|
|
@@ -1442,6 +1577,7 @@ class StableBrowser {
|
|
|
1442
1577
|
highlight: false,
|
|
1443
1578
|
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1444
1579
|
text: `Verify element contains pattern: ${pattern}`,
|
|
1580
|
+
_text: "Verify element " + selectors.element_name + " contains pattern " + pattern,
|
|
1445
1581
|
operation: "containsPattern",
|
|
1446
1582
|
log: "***** verify element " + selectors.element_name + " contains pattern " + pattern + " *****\n",
|
|
1447
1583
|
};
|
|
@@ -1473,10 +1609,12 @@ class StableBrowser {
|
|
|
1473
1609
|
await _commandError(state, e, this);
|
|
1474
1610
|
}
|
|
1475
1611
|
finally {
|
|
1476
|
-
_commandFinally(state, this);
|
|
1612
|
+
await _commandFinally(state, this);
|
|
1477
1613
|
}
|
|
1478
1614
|
}
|
|
1479
1615
|
async containsText(selectors, text, climb, _params = null, options = {}, world = null) {
|
|
1616
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1617
|
+
const startTime = Date.now();
|
|
1480
1618
|
const state = {
|
|
1481
1619
|
selectors,
|
|
1482
1620
|
_params,
|
|
@@ -1503,44 +1641,52 @@ class StableBrowser {
|
|
|
1503
1641
|
}
|
|
1504
1642
|
let foundObj = null;
|
|
1505
1643
|
try {
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
const dateAlternatives = findDateAlternatives(text);
|
|
1513
|
-
const numberAlternatives = findNumberAlternatives(text);
|
|
1514
|
-
if (dateAlternatives.date) {
|
|
1515
|
-
for (let i = 0; i < dateAlternatives.dates.length; i++) {
|
|
1516
|
-
if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
|
|
1517
|
-
foundObj?.value?.includes(dateAlternatives.dates[i])) {
|
|
1518
|
-
return state.info;
|
|
1644
|
+
while (Date.now() - startTime < timeout) {
|
|
1645
|
+
try {
|
|
1646
|
+
await _preCommand(state, this);
|
|
1647
|
+
foundObj = await this._getText(selectors, climb, _params, { timeout: 3000 }, state.info, world);
|
|
1648
|
+
if (foundObj && foundObj.element) {
|
|
1649
|
+
await this.scrollIfNeeded(foundObj.element, state.info);
|
|
1519
1650
|
}
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1651
|
+
await _screenshot(state, this);
|
|
1652
|
+
const dateAlternatives = findDateAlternatives(text);
|
|
1653
|
+
const numberAlternatives = findNumberAlternatives(text);
|
|
1654
|
+
if (dateAlternatives.date) {
|
|
1655
|
+
for (let i = 0; i < dateAlternatives.dates.length; i++) {
|
|
1656
|
+
if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
|
|
1657
|
+
foundObj?.value?.includes(dateAlternatives.dates[i])) {
|
|
1658
|
+
return state.info;
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
else if (numberAlternatives.number) {
|
|
1663
|
+
for (let i = 0; i < numberAlternatives.numbers.length; i++) {
|
|
1664
|
+
if (foundObj?.text.includes(numberAlternatives.numbers[i]) ||
|
|
1665
|
+
foundObj?.value?.includes(numberAlternatives.numbers[i])) {
|
|
1666
|
+
return state.info;
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
else if (foundObj?.text.includes(text) || foundObj?.value?.includes(text)) {
|
|
1527
1671
|
return state.info;
|
|
1528
1672
|
}
|
|
1529
1673
|
}
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
throw new Error("element doesn't contain text " + text);
|
|
1674
|
+
catch (e) {
|
|
1675
|
+
// Log error but continue retrying until timeout is reached
|
|
1676
|
+
this.logger.warn("Retrying containsText due to: " + e.message);
|
|
1677
|
+
}
|
|
1678
|
+
await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second before retrying
|
|
1536
1679
|
}
|
|
1537
|
-
|
|
1680
|
+
state.info.foundText = foundObj?.text;
|
|
1681
|
+
state.info.value = foundObj?.value;
|
|
1682
|
+
throw new Error("element doesn't contain text " + text);
|
|
1538
1683
|
}
|
|
1539
1684
|
catch (e) {
|
|
1540
1685
|
await _commandError(state, e, this);
|
|
1686
|
+
throw e;
|
|
1541
1687
|
}
|
|
1542
1688
|
finally {
|
|
1543
|
-
_commandFinally(state, this);
|
|
1689
|
+
await _commandFinally(state, this);
|
|
1544
1690
|
}
|
|
1545
1691
|
}
|
|
1546
1692
|
async waitForUserInput(message, world = null) {
|
|
@@ -1578,6 +1724,15 @@ class StableBrowser {
|
|
|
1578
1724
|
// save the data to the file
|
|
1579
1725
|
fs.writeFileSync(dataFile, JSON.stringify(data, null, 2));
|
|
1580
1726
|
}
|
|
1727
|
+
overwriteTestData(testData, world = null) {
|
|
1728
|
+
if (!testData) {
|
|
1729
|
+
return;
|
|
1730
|
+
}
|
|
1731
|
+
// if data file exists, load it
|
|
1732
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1733
|
+
// save the data to the file
|
|
1734
|
+
fs.writeFileSync(dataFile, JSON.stringify(testData, null, 2));
|
|
1735
|
+
}
|
|
1581
1736
|
_getDataFilePath(fileName) {
|
|
1582
1737
|
let dataFile = path.join(this.project_path, "data", fileName);
|
|
1583
1738
|
if (fs.existsSync(dataFile)) {
|
|
@@ -1830,7 +1985,7 @@ class StableBrowser {
|
|
|
1830
1985
|
await _commandError(state, e, this);
|
|
1831
1986
|
}
|
|
1832
1987
|
finally {
|
|
1833
|
-
_commandFinally(state, this);
|
|
1988
|
+
await _commandFinally(state, this);
|
|
1834
1989
|
}
|
|
1835
1990
|
}
|
|
1836
1991
|
async extractAttribute(selectors, attribute, variable, _params = null, options = {}, world = null) {
|
|
@@ -1843,6 +1998,7 @@ class StableBrowser {
|
|
|
1843
1998
|
world,
|
|
1844
1999
|
type: Types.EXTRACT,
|
|
1845
2000
|
text: `Extract attribute from element`,
|
|
2001
|
+
_text: `Extract attribute ${attribute} from ${selectors.element_name}`,
|
|
1846
2002
|
operation: "extractAttribute",
|
|
1847
2003
|
log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1848
2004
|
allowDisabled: true,
|
|
@@ -1860,10 +2016,31 @@ class StableBrowser {
|
|
|
1860
2016
|
case "value":
|
|
1861
2017
|
state.value = await state.element.inputValue();
|
|
1862
2018
|
break;
|
|
2019
|
+
case "text":
|
|
2020
|
+
state.value = await state.element.textContent();
|
|
2021
|
+
break;
|
|
1863
2022
|
default:
|
|
1864
2023
|
state.value = await state.element.getAttribute(attribute);
|
|
1865
2024
|
break;
|
|
1866
2025
|
}
|
|
2026
|
+
if (options !== null) {
|
|
2027
|
+
if (options.regex && options.regex !== "") {
|
|
2028
|
+
// Construct a regex pattern from the provided string
|
|
2029
|
+
const regex = options.regex.slice(1, -1);
|
|
2030
|
+
const regexPattern = new RegExp(regex, "g");
|
|
2031
|
+
const matches = state.value.match(regexPattern);
|
|
2032
|
+
if (matches) {
|
|
2033
|
+
let newValue = "";
|
|
2034
|
+
for (const match of matches) {
|
|
2035
|
+
newValue += match;
|
|
2036
|
+
}
|
|
2037
|
+
state.value = newValue;
|
|
2038
|
+
}
|
|
2039
|
+
}
|
|
2040
|
+
if (options.trimSpaces && options.trimSpaces === true) {
|
|
2041
|
+
state.value = state.value.trim();
|
|
2042
|
+
}
|
|
2043
|
+
}
|
|
1867
2044
|
state.info.value = state.value;
|
|
1868
2045
|
this.setTestData({ [variable]: state.value }, world);
|
|
1869
2046
|
this.logger.info("set test data: " + variable + "=" + state.value);
|
|
@@ -1874,7 +2051,7 @@ class StableBrowser {
|
|
|
1874
2051
|
await _commandError(state, e, this);
|
|
1875
2052
|
}
|
|
1876
2053
|
finally {
|
|
1877
|
-
_commandFinally(state, this);
|
|
2054
|
+
await _commandFinally(state, this);
|
|
1878
2055
|
}
|
|
1879
2056
|
}
|
|
1880
2057
|
async verifyAttribute(selectors, attribute, value, _params = null, options = {}, world = null) {
|
|
@@ -1889,6 +2066,7 @@ class StableBrowser {
|
|
|
1889
2066
|
highlight: true,
|
|
1890
2067
|
screenshot: true,
|
|
1891
2068
|
text: `Verify element attribute`,
|
|
2069
|
+
_text: `Verify attribute ${attribute} from ${selectors.element_name} is ${value}`,
|
|
1892
2070
|
operation: "verifyAttribute",
|
|
1893
2071
|
log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1894
2072
|
allowDisabled: true,
|
|
@@ -1898,12 +2076,15 @@ class StableBrowser {
|
|
|
1898
2076
|
let expectedValue;
|
|
1899
2077
|
try {
|
|
1900
2078
|
await _preCommand(state, this);
|
|
1901
|
-
expectedValue = state.value;
|
|
2079
|
+
expectedValue = await replaceWithLocalTestData(state.value, world);
|
|
1902
2080
|
state.info.expectedValue = expectedValue;
|
|
1903
2081
|
switch (attribute) {
|
|
1904
2082
|
case "innerText":
|
|
1905
2083
|
val = String(await state.element.innerText());
|
|
1906
2084
|
break;
|
|
2085
|
+
case "text":
|
|
2086
|
+
val = String(await state.element.textContent());
|
|
2087
|
+
break;
|
|
1907
2088
|
case "value":
|
|
1908
2089
|
val = String(await state.element.inputValue());
|
|
1909
2090
|
break;
|
|
@@ -1943,7 +2124,7 @@ class StableBrowser {
|
|
|
1943
2124
|
await _commandError(state, e, this);
|
|
1944
2125
|
}
|
|
1945
2126
|
finally {
|
|
1946
|
-
_commandFinally(state, this);
|
|
2127
|
+
await _commandFinally(state, this);
|
|
1947
2128
|
}
|
|
1948
2129
|
}
|
|
1949
2130
|
async extractEmailData(emailAddress, options, world) {
|
|
@@ -2195,6 +2376,7 @@ class StableBrowser {
|
|
|
2195
2376
|
_reportToWorld(world, {
|
|
2196
2377
|
type: Types.VERIFY_PAGE_PATH,
|
|
2197
2378
|
text: "Verify page path",
|
|
2379
|
+
_text: "Verify the page path contains " + pathPart,
|
|
2198
2380
|
screenshotId,
|
|
2199
2381
|
result: error
|
|
2200
2382
|
? {
|
|
@@ -2212,27 +2394,89 @@ class StableBrowser {
|
|
|
2212
2394
|
});
|
|
2213
2395
|
}
|
|
2214
2396
|
}
|
|
2215
|
-
async
|
|
2397
|
+
async verifyPageTitle(title, options = {}, world = null) {
|
|
2398
|
+
const startTime = Date.now();
|
|
2399
|
+
let error = null;
|
|
2400
|
+
let screenshotId = null;
|
|
2401
|
+
let screenshotPath = null;
|
|
2402
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2403
|
+
const info = {};
|
|
2404
|
+
info.log = "***** verify page title " + title + " *****\n";
|
|
2405
|
+
info.operation = "verifyPageTitle";
|
|
2406
|
+
const newValue = await this._replaceWithLocalData(title, world);
|
|
2407
|
+
if (newValue !== title) {
|
|
2408
|
+
this.logger.info(title + "=" + newValue);
|
|
2409
|
+
title = newValue;
|
|
2410
|
+
}
|
|
2411
|
+
info.title = title;
|
|
2412
|
+
try {
|
|
2413
|
+
for (let i = 0; i < 30; i++) {
|
|
2414
|
+
const foundTitle = await this.page.title();
|
|
2415
|
+
if (!foundTitle.includes(title)) {
|
|
2416
|
+
if (i === 29) {
|
|
2417
|
+
throw new Error(`url ${foundTitle} doesn't contain ${title}`);
|
|
2418
|
+
}
|
|
2419
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2420
|
+
continue;
|
|
2421
|
+
}
|
|
2422
|
+
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2423
|
+
return info;
|
|
2424
|
+
}
|
|
2425
|
+
}
|
|
2426
|
+
catch (e) {
|
|
2427
|
+
//await this.closeUnexpectedPopups();
|
|
2428
|
+
this.logger.error("verify page title failed " + info.log);
|
|
2429
|
+
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2430
|
+
info.screenshotPath = screenshotPath;
|
|
2431
|
+
Object.assign(e, { info: info });
|
|
2432
|
+
error = e;
|
|
2433
|
+
// throw e;
|
|
2434
|
+
await _commandError({ text: "verifyPageTitle", operation: "verifyPageTitle", title, info, throwError: true }, e, this);
|
|
2435
|
+
}
|
|
2436
|
+
finally {
|
|
2437
|
+
const endTime = Date.now();
|
|
2438
|
+
_reportToWorld(world, {
|
|
2439
|
+
type: Types.VERIFY_PAGE_PATH,
|
|
2440
|
+
text: "Verify page title",
|
|
2441
|
+
_text: "Verify the page title contains " + title,
|
|
2442
|
+
screenshotId,
|
|
2443
|
+
result: error
|
|
2444
|
+
? {
|
|
2445
|
+
status: "FAILED",
|
|
2446
|
+
startTime,
|
|
2447
|
+
endTime,
|
|
2448
|
+
message: error?.message,
|
|
2449
|
+
}
|
|
2450
|
+
: {
|
|
2451
|
+
status: "PASSED",
|
|
2452
|
+
startTime,
|
|
2453
|
+
endTime,
|
|
2454
|
+
},
|
|
2455
|
+
info: info,
|
|
2456
|
+
});
|
|
2457
|
+
}
|
|
2458
|
+
}
|
|
2459
|
+
async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state, partial = true, ignoreCase = false) {
|
|
2216
2460
|
const frames = this.page.frames();
|
|
2217
2461
|
let results = [];
|
|
2218
|
-
let ignoreCase = false;
|
|
2462
|
+
// let ignoreCase = false;
|
|
2219
2463
|
for (let i = 0; i < frames.length; i++) {
|
|
2220
2464
|
if (dateAlternatives.date) {
|
|
2221
2465
|
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2222
|
-
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false,
|
|
2466
|
+
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, partial, ignoreCase, {});
|
|
2223
2467
|
result.frame = frames[i];
|
|
2224
2468
|
results.push(result);
|
|
2225
2469
|
}
|
|
2226
2470
|
}
|
|
2227
2471
|
else if (numberAlternatives.number) {
|
|
2228
2472
|
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2229
|
-
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false,
|
|
2473
|
+
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, partial, ignoreCase, {});
|
|
2230
2474
|
result.frame = frames[i];
|
|
2231
2475
|
results.push(result);
|
|
2232
2476
|
}
|
|
2233
2477
|
}
|
|
2234
2478
|
else {
|
|
2235
|
-
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false,
|
|
2479
|
+
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, partial, ignoreCase, {});
|
|
2236
2480
|
result.frame = frames[i];
|
|
2237
2481
|
results.push(result);
|
|
2238
2482
|
}
|
|
@@ -2251,11 +2495,15 @@ class StableBrowser {
|
|
|
2251
2495
|
scroll: false,
|
|
2252
2496
|
highlight: false,
|
|
2253
2497
|
type: Types.VERIFY_PAGE_CONTAINS_TEXT,
|
|
2254
|
-
text: `Verify text exists in page`,
|
|
2498
|
+
text: `Verify the text '${maskValue(text)}' exists in page`,
|
|
2499
|
+
_text: `Verify the text '${text}' exists in page`,
|
|
2255
2500
|
operation: "verifyTextExistInPage",
|
|
2256
2501
|
log: "***** verify text " + text + " exists in page *****\n",
|
|
2257
2502
|
};
|
|
2258
|
-
|
|
2503
|
+
if (testForRegex(text)) {
|
|
2504
|
+
text = text.replace(/\\"/g, '"');
|
|
2505
|
+
}
|
|
2506
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2259
2507
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2260
2508
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2261
2509
|
if (newValue !== text) {
|
|
@@ -2325,7 +2573,7 @@ class StableBrowser {
|
|
|
2325
2573
|
await _commandError(state, e, this);
|
|
2326
2574
|
}
|
|
2327
2575
|
finally {
|
|
2328
|
-
_commandFinally(state, this);
|
|
2576
|
+
await _commandFinally(state, this);
|
|
2329
2577
|
}
|
|
2330
2578
|
}
|
|
2331
2579
|
async waitForTextToDisappear(text, options = {}, world = null) {
|
|
@@ -2338,11 +2586,15 @@ class StableBrowser {
|
|
|
2338
2586
|
scroll: false,
|
|
2339
2587
|
highlight: false,
|
|
2340
2588
|
type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
|
|
2341
|
-
text: `Verify text does not exist in page`,
|
|
2589
|
+
text: `Verify the text '${maskValue(text)}' does not exist in page`,
|
|
2590
|
+
_text: `Verify the text '${text}' does not exist in page`,
|
|
2342
2591
|
operation: "verifyTextNotExistInPage",
|
|
2343
2592
|
log: "***** verify text " + text + " does not exist in page *****\n",
|
|
2344
2593
|
};
|
|
2345
|
-
|
|
2594
|
+
if (testForRegex(text)) {
|
|
2595
|
+
text = text.replace(/\\"/g, '"');
|
|
2596
|
+
}
|
|
2597
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2346
2598
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2347
2599
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2348
2600
|
if (newValue !== text) {
|
|
@@ -2378,7 +2630,7 @@ class StableBrowser {
|
|
|
2378
2630
|
await _commandError(state, e, this);
|
|
2379
2631
|
}
|
|
2380
2632
|
finally {
|
|
2381
|
-
_commandFinally(state, this);
|
|
2633
|
+
await _commandFinally(state, this);
|
|
2382
2634
|
}
|
|
2383
2635
|
}
|
|
2384
2636
|
async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
|
|
@@ -2393,10 +2645,11 @@ class StableBrowser {
|
|
|
2393
2645
|
highlight: false,
|
|
2394
2646
|
type: Types.VERIFY_TEXT_WITH_RELATION,
|
|
2395
2647
|
text: `Verify text with relation to another text`,
|
|
2648
|
+
_text: "Search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found",
|
|
2396
2649
|
operation: "verify_text_with_relation",
|
|
2397
2650
|
log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
|
|
2398
2651
|
};
|
|
2399
|
-
const timeout = this.
|
|
2652
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2400
2653
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2401
2654
|
let newValue = await this._replaceWithLocalData(textAnchor, world);
|
|
2402
2655
|
if (newValue !== textAnchor) {
|
|
@@ -2419,7 +2672,7 @@ class StableBrowser {
|
|
|
2419
2672
|
};
|
|
2420
2673
|
while (true) {
|
|
2421
2674
|
try {
|
|
2422
|
-
resultWithElementsFound = await this.findTextInAllFrames(
|
|
2675
|
+
resultWithElementsFound = await this.findTextInAllFrames(findDateAlternatives(textAnchor), findNumberAlternatives(textAnchor), textAnchor, state, false);
|
|
2423
2676
|
}
|
|
2424
2677
|
catch (error) {
|
|
2425
2678
|
// ignore
|
|
@@ -2447,7 +2700,7 @@ class StableBrowser {
|
|
|
2447
2700
|
const count = await frame.locator(css).count();
|
|
2448
2701
|
for (let j = 0; j < count; j++) {
|
|
2449
2702
|
const continer = await frame.locator(css).nth(j);
|
|
2450
|
-
const result = await this._locateElementByText(continer, textToVerify, "
|
|
2703
|
+
const result = await this._locateElementByText(continer, textToVerify, "*:not(script, style, head)", false, true, true, {});
|
|
2451
2704
|
if (result.elementCount > 0) {
|
|
2452
2705
|
const dataAttribute = "[data-blinq-id-" + result.randomToken + "]";
|
|
2453
2706
|
await this._highlightElements(frame, dataAttribute);
|
|
@@ -2488,9 +2741,33 @@ class StableBrowser {
|
|
|
2488
2741
|
await _commandError(state, e, this);
|
|
2489
2742
|
}
|
|
2490
2743
|
finally {
|
|
2491
|
-
_commandFinally(state, this);
|
|
2744
|
+
await _commandFinally(state, this);
|
|
2492
2745
|
}
|
|
2493
2746
|
}
|
|
2747
|
+
async findRelatedTextInAllFrames(textAnchor, climb, textToVerify, params = {}, options = {}, world = null) {
|
|
2748
|
+
const frames = this.page.frames();
|
|
2749
|
+
let results = [];
|
|
2750
|
+
let ignoreCase = false;
|
|
2751
|
+
for (let i = 0; i < frames.length; i++) {
|
|
2752
|
+
const result = await this._locateElementByText(frames[i], textAnchor, "*:not(script, style, head)", false, true, ignoreCase, {});
|
|
2753
|
+
result.frame = frames[i];
|
|
2754
|
+
const climbArray = [];
|
|
2755
|
+
for (let i = 0; i < climb; i++) {
|
|
2756
|
+
climbArray.push("..");
|
|
2757
|
+
}
|
|
2758
|
+
let climbXpath = "xpath=" + climbArray.join("/");
|
|
2759
|
+
const newLocator = `[data-blinq-id-${result.randomToken}] ${climb > 0 ? ">> " + climbXpath : ""} >> internal:text=${testForRegex(textToVerify) ? textToVerify : unEscapeString(textToVerify)}`;
|
|
2760
|
+
const count = await frames[i].locator(newLocator).count();
|
|
2761
|
+
if (count > 0) {
|
|
2762
|
+
result.elementCount = count;
|
|
2763
|
+
result.locator = newLocator;
|
|
2764
|
+
results.push(result);
|
|
2765
|
+
}
|
|
2766
|
+
}
|
|
2767
|
+
// state.info.results = results;
|
|
2768
|
+
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2769
|
+
return resultWithElementsFound;
|
|
2770
|
+
}
|
|
2494
2771
|
async visualVerification(text, options = {}, world = null) {
|
|
2495
2772
|
const startTime = Date.now();
|
|
2496
2773
|
let error = null;
|
|
@@ -2552,6 +2829,7 @@ class StableBrowser {
|
|
|
2552
2829
|
_reportToWorld(world, {
|
|
2553
2830
|
type: Types.VERIFY_VISUAL,
|
|
2554
2831
|
text: "Visual verification",
|
|
2832
|
+
_text: "Visual verification of " + text,
|
|
2555
2833
|
screenshotId,
|
|
2556
2834
|
result: error
|
|
2557
2835
|
? {
|
|
@@ -2806,7 +3084,13 @@ class StableBrowser {
|
|
|
2806
3084
|
}
|
|
2807
3085
|
}
|
|
2808
3086
|
async _replaceWithLocalData(value, world, _decrypt = true, totpWait = true) {
|
|
2809
|
-
|
|
3087
|
+
try {
|
|
3088
|
+
return await replaceWithLocalTestData(value, world, _decrypt, totpWait, this.context, this);
|
|
3089
|
+
}
|
|
3090
|
+
catch (error) {
|
|
3091
|
+
this.logger.debug(error);
|
|
3092
|
+
throw error;
|
|
3093
|
+
}
|
|
2810
3094
|
}
|
|
2811
3095
|
_getLoadTimeout(options) {
|
|
2812
3096
|
let timeout = 15000;
|
|
@@ -2818,6 +3102,15 @@ class StableBrowser {
|
|
|
2818
3102
|
}
|
|
2819
3103
|
return timeout;
|
|
2820
3104
|
}
|
|
3105
|
+
_getFindElementTimeout(options) {
|
|
3106
|
+
if (options && options.timeout) {
|
|
3107
|
+
return options.timeout;
|
|
3108
|
+
}
|
|
3109
|
+
if (this.configuration.find_element_timeout) {
|
|
3110
|
+
return this.configuration.find_element_timeout;
|
|
3111
|
+
}
|
|
3112
|
+
return 30000;
|
|
3113
|
+
}
|
|
2821
3114
|
async saveStoreState(path = null, world = null) {
|
|
2822
3115
|
const storageState = await this.page.context().storageState();
|
|
2823
3116
|
//const testDataFile = _getDataFile(world, this.context, this);
|
|
@@ -2834,6 +3127,9 @@ class StableBrowser {
|
|
|
2834
3127
|
this.registerEventListeners(this.context);
|
|
2835
3128
|
registerNetworkEvents(this.world, this, this.context, this.page);
|
|
2836
3129
|
registerDownloadEvent(this.page, this.world, this.context);
|
|
3130
|
+
if (this.onRestoreSaveState) {
|
|
3131
|
+
this.onRestoreSaveState(path);
|
|
3132
|
+
}
|
|
2837
3133
|
}
|
|
2838
3134
|
async waitForPageLoad(options = {}, world = null) {
|
|
2839
3135
|
let timeout = this._getLoadTimeout(options);
|
|
@@ -2902,6 +3198,7 @@ class StableBrowser {
|
|
|
2902
3198
|
highlight: false,
|
|
2903
3199
|
type: Types.CLOSE_PAGE,
|
|
2904
3200
|
text: `Close page`,
|
|
3201
|
+
_text: `Close the page`,
|
|
2905
3202
|
operation: "closePage",
|
|
2906
3203
|
log: "***** close page *****\n",
|
|
2907
3204
|
throwError: false,
|
|
@@ -2915,11 +3212,98 @@ class StableBrowser {
|
|
|
2915
3212
|
await _commandError(state, e, this);
|
|
2916
3213
|
}
|
|
2917
3214
|
finally {
|
|
2918
|
-
_commandFinally(state, this);
|
|
3215
|
+
await _commandFinally(state, this);
|
|
3216
|
+
}
|
|
3217
|
+
}
|
|
3218
|
+
async tableCellOperation(headerText, rowText, options, _params, world = null) {
|
|
3219
|
+
let operation = null;
|
|
3220
|
+
if (!options || !options.operation) {
|
|
3221
|
+
throw new Error("operation is not defined");
|
|
3222
|
+
}
|
|
3223
|
+
operation = options.operation;
|
|
3224
|
+
// validate operation is one of the supported operations
|
|
3225
|
+
if (operation != "click" && operation != "hover+click") {
|
|
3226
|
+
throw new Error("operation is not supported");
|
|
3227
|
+
}
|
|
3228
|
+
const state = {
|
|
3229
|
+
options,
|
|
3230
|
+
world,
|
|
3231
|
+
locate: false,
|
|
3232
|
+
scroll: false,
|
|
3233
|
+
highlight: false,
|
|
3234
|
+
type: Types.TABLE_OPERATION,
|
|
3235
|
+
text: `Table operation`,
|
|
3236
|
+
_text: `Table ${operation} operation`,
|
|
3237
|
+
operation: operation,
|
|
3238
|
+
log: "***** Table operation *****\n",
|
|
3239
|
+
};
|
|
3240
|
+
const timeout = this._getFindElementTimeout(options);
|
|
3241
|
+
try {
|
|
3242
|
+
await _preCommand(state, this);
|
|
3243
|
+
const start = Date.now();
|
|
3244
|
+
let cellArea = null;
|
|
3245
|
+
while (true) {
|
|
3246
|
+
try {
|
|
3247
|
+
cellArea = await _findCellArea(headerText, rowText, this, state);
|
|
3248
|
+
if (cellArea) {
|
|
3249
|
+
break;
|
|
3250
|
+
}
|
|
3251
|
+
}
|
|
3252
|
+
catch (e) {
|
|
3253
|
+
// ignore
|
|
3254
|
+
}
|
|
3255
|
+
if (Date.now() - start > timeout) {
|
|
3256
|
+
throw new Error(`Cell not found in table`);
|
|
3257
|
+
}
|
|
3258
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
3259
|
+
}
|
|
3260
|
+
switch (operation) {
|
|
3261
|
+
case "click":
|
|
3262
|
+
if (!options.css) {
|
|
3263
|
+
// will click in the center of the cell
|
|
3264
|
+
let xOffset = 0;
|
|
3265
|
+
let yOffset = 0;
|
|
3266
|
+
if (options.xOffset) {
|
|
3267
|
+
xOffset = options.xOffset;
|
|
3268
|
+
}
|
|
3269
|
+
if (options.yOffset) {
|
|
3270
|
+
yOffset = options.yOffset;
|
|
3271
|
+
}
|
|
3272
|
+
await this.page.mouse.click(cellArea.x + cellArea.width / 2 + xOffset, cellArea.y + cellArea.height / 2 + yOffset);
|
|
3273
|
+
}
|
|
3274
|
+
else {
|
|
3275
|
+
const results = await findElementsInArea(options.css, cellArea, this, options);
|
|
3276
|
+
if (results.length === 0) {
|
|
3277
|
+
throw new Error(`Element not found in cell area`);
|
|
3278
|
+
}
|
|
3279
|
+
state.element = results[0];
|
|
3280
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
3281
|
+
}
|
|
3282
|
+
break;
|
|
3283
|
+
case "hover+click":
|
|
3284
|
+
if (!options.css) {
|
|
3285
|
+
throw new Error("css is not defined");
|
|
3286
|
+
}
|
|
3287
|
+
const results = await findElementsInArea(options.css, cellArea, this, options);
|
|
3288
|
+
if (results.length === 0) {
|
|
3289
|
+
throw new Error(`Element not found in cell area`);
|
|
3290
|
+
}
|
|
3291
|
+
state.element = results[0];
|
|
3292
|
+
await performAction("hover+click", state.element, options, this, state, _params);
|
|
3293
|
+
break;
|
|
3294
|
+
default:
|
|
3295
|
+
throw new Error("operation is not supported");
|
|
3296
|
+
}
|
|
3297
|
+
}
|
|
3298
|
+
catch (e) {
|
|
3299
|
+
await _commandError(state, e, this);
|
|
3300
|
+
}
|
|
3301
|
+
finally {
|
|
3302
|
+
await _commandFinally(state, this);
|
|
2919
3303
|
}
|
|
2920
3304
|
}
|
|
2921
3305
|
saveTestDataAsGlobal(options, world) {
|
|
2922
|
-
const dataFile =
|
|
3306
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
2923
3307
|
process.env.GLOBAL_TEST_DATA_FILE = dataFile;
|
|
2924
3308
|
this.logger.info("Save the scenario test data as global for the following scenarios.");
|
|
2925
3309
|
}
|
|
@@ -2949,6 +3333,7 @@ class StableBrowser {
|
|
|
2949
3333
|
_reportToWorld(world, {
|
|
2950
3334
|
type: Types.SET_VIEWPORT,
|
|
2951
3335
|
text: "set viewport size to " + width + "x" + hight,
|
|
3336
|
+
_text: "Set the viewport size to " + width + "x" + hight,
|
|
2952
3337
|
screenshotId,
|
|
2953
3338
|
result: error
|
|
2954
3339
|
? {
|
|
@@ -3019,7 +3404,39 @@ class StableBrowser {
|
|
|
3019
3404
|
console.log("#-#");
|
|
3020
3405
|
}
|
|
3021
3406
|
}
|
|
3407
|
+
async beforeScenario(world, scenario) {
|
|
3408
|
+
this.beforeScenarioCalled = true;
|
|
3409
|
+
if (scenario && scenario.pickle && scenario.pickle.name) {
|
|
3410
|
+
this.scenarioName = scenario.pickle.name;
|
|
3411
|
+
}
|
|
3412
|
+
if (scenario && scenario.gherkinDocument && scenario.gherkinDocument.feature) {
|
|
3413
|
+
this.featureName = scenario.gherkinDocument.feature.name;
|
|
3414
|
+
}
|
|
3415
|
+
if (this.context) {
|
|
3416
|
+
this.context.examplesRow = extractStepExampleParameters(scenario);
|
|
3417
|
+
}
|
|
3418
|
+
if (this.tags === null && scenario && scenario.pickle && scenario.pickle.tags) {
|
|
3419
|
+
this.tags = scenario.pickle.tags.map((tag) => tag.name);
|
|
3420
|
+
// check if @global_test_data tag is present
|
|
3421
|
+
if (this.tags.includes("@global_test_data")) {
|
|
3422
|
+
this.saveTestDataAsGlobal({}, world);
|
|
3423
|
+
}
|
|
3424
|
+
}
|
|
3425
|
+
// update test data based on feature/scenario
|
|
3426
|
+
let envName = null;
|
|
3427
|
+
if (this.context && this.context.environment) {
|
|
3428
|
+
envName = this.context.environment.name;
|
|
3429
|
+
}
|
|
3430
|
+
if (!process.env.TEMP_RUN) {
|
|
3431
|
+
await getTestData(envName, world, undefined, this.featureName, this.scenarioName);
|
|
3432
|
+
}
|
|
3433
|
+
await loadBrunoParams(this.context, this.context.environment.name);
|
|
3434
|
+
}
|
|
3435
|
+
async afterScenario(world, scenario) { }
|
|
3022
3436
|
async beforeStep(world, step) {
|
|
3437
|
+
if (!this.beforeScenarioCalled) {
|
|
3438
|
+
this.beforeScenario(world, step);
|
|
3439
|
+
}
|
|
3023
3440
|
if (this.stepIndex === undefined) {
|
|
3024
3441
|
this.stepIndex = 0;
|
|
3025
3442
|
}
|
|
@@ -3036,21 +3453,53 @@ class StableBrowser {
|
|
|
3036
3453
|
else {
|
|
3037
3454
|
this.stepName = "step " + this.stepIndex;
|
|
3038
3455
|
}
|
|
3039
|
-
if (this.context) {
|
|
3040
|
-
this.context.examplesRow = extractStepExampleParameters(step);
|
|
3041
|
-
}
|
|
3042
3456
|
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
3043
3457
|
if (this.context.browserObject.context) {
|
|
3044
3458
|
await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
|
|
3045
3459
|
}
|
|
3046
3460
|
}
|
|
3047
|
-
if (this.
|
|
3048
|
-
this.
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3461
|
+
if (this.initSnapshotTaken === false) {
|
|
3462
|
+
this.initSnapshotTaken = true;
|
|
3463
|
+
if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
|
|
3464
|
+
const snapshot = await this.getAriaSnapshot();
|
|
3465
|
+
if (snapshot) {
|
|
3466
|
+
await world.attach(JSON.stringify(snapshot), "application/json+snapshot-before");
|
|
3467
|
+
}
|
|
3468
|
+
}
|
|
3469
|
+
}
|
|
3470
|
+
}
|
|
3471
|
+
async getAriaSnapshot() {
|
|
3472
|
+
try {
|
|
3473
|
+
// find the page url
|
|
3474
|
+
const url = await this.page.url();
|
|
3475
|
+
// extract the path from the url
|
|
3476
|
+
const path = new URL(url).pathname;
|
|
3477
|
+
// get the page title
|
|
3478
|
+
const title = await this.page.title();
|
|
3479
|
+
// go over other frams
|
|
3480
|
+
const frames = this.page.frames();
|
|
3481
|
+
const snapshots = [];
|
|
3482
|
+
const content = [`- path: ${path}`, `- title: ${title}`];
|
|
3483
|
+
const timeout = this.configuration.ariaSnapshotTimeout ? this.configuration.ariaSnapshotTimeout : 3000;
|
|
3484
|
+
for (let i = 0; i < frames.length; i++) {
|
|
3485
|
+
const frame = frames[i];
|
|
3486
|
+
try {
|
|
3487
|
+
// Ensure frame is attached and has body
|
|
3488
|
+
const body = frame.locator("body");
|
|
3489
|
+
await body.waitFor({ timeout }); // wait explicitly
|
|
3490
|
+
const snapshot = await body.ariaSnapshot({ timeout });
|
|
3491
|
+
content.push(`- frame: ${i}`);
|
|
3492
|
+
content.push(snapshot);
|
|
3493
|
+
}
|
|
3494
|
+
catch (innerErr) { }
|
|
3052
3495
|
}
|
|
3496
|
+
return content.join("\n");
|
|
3497
|
+
}
|
|
3498
|
+
catch (e) {
|
|
3499
|
+
console.log("Error in getAriaSnapshot");
|
|
3500
|
+
//console.debug(e);
|
|
3053
3501
|
}
|
|
3502
|
+
return null;
|
|
3054
3503
|
}
|
|
3055
3504
|
async afterStep(world, step) {
|
|
3056
3505
|
this.stepName = null;
|
|
@@ -3059,11 +3508,25 @@ class StableBrowser {
|
|
|
3059
3508
|
await this.context.browserObject.context.tracing.stopChunk({
|
|
3060
3509
|
path: path.join(this.context.browserObject.traceFolder, `trace-${this.stepIndex}.zip`),
|
|
3061
3510
|
});
|
|
3511
|
+
if (world && world.attach) {
|
|
3512
|
+
await world.attach(JSON.stringify({
|
|
3513
|
+
type: "trace",
|
|
3514
|
+
traceFilePath: `trace-${this.stepIndex}.zip`,
|
|
3515
|
+
}), "application/json+trace");
|
|
3516
|
+
}
|
|
3517
|
+
// console.log("trace file created", `trace-${this.stepIndex}.zip`);
|
|
3062
3518
|
}
|
|
3063
3519
|
}
|
|
3064
3520
|
if (this.context) {
|
|
3065
3521
|
this.context.examplesRow = null;
|
|
3066
3522
|
}
|
|
3523
|
+
if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
|
|
3524
|
+
const snapshot = await this.getAriaSnapshot();
|
|
3525
|
+
if (snapshot) {
|
|
3526
|
+
const obj = {};
|
|
3527
|
+
await world.attach(JSON.stringify(snapshot), "application/json+snapshot-after");
|
|
3528
|
+
}
|
|
3529
|
+
}
|
|
3067
3530
|
}
|
|
3068
3531
|
}
|
|
3069
3532
|
function createTimedPromise(promise, label) {
|