automation_model 1.0.616-dev → 1.0.616-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 +107 -26
- package/lib/auto_page.js.map +1 -1
- package/lib/browser_manager.js +70 -22
- 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/environment.d.ts +1 -0
- package/lib/environment.js +1 -0
- package/lib/environment.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 +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/network.d.ts +1 -1
- package/lib/network.js +5 -5
- package/lib/network.js.map +1 -1
- package/lib/stable_browser.d.ts +22 -1
- package/lib/stable_browser.js +655 -123
- 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 +2 -0
- package/lib/test_context.js +2 -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,12 +597,24 @@ class StableBrowser {
|
|
|
558
597
|
element.evaluate((el, randomToken) => {
|
|
559
598
|
el.setAttribute("data-blinq-id-" + randomToken, "");
|
|
560
599
|
}, randomToken);
|
|
561
|
-
if (element._frame) {
|
|
562
|
-
|
|
563
|
-
}
|
|
564
|
-
const scope = element.page();
|
|
565
|
-
|
|
566
|
-
|
|
600
|
+
// if (element._frame) {
|
|
601
|
+
// return element;
|
|
602
|
+
// }
|
|
603
|
+
const scope = element._frame ?? element.page();
|
|
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);
|
|
567
618
|
}
|
|
568
619
|
}
|
|
569
620
|
throw new Error("unable to locate element " + JSON.stringify(selectors));
|
|
@@ -715,14 +766,9 @@ class StableBrowser {
|
|
|
715
766
|
// info.log += "scanning locators in priority 2" + "\n";
|
|
716
767
|
result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
717
768
|
}
|
|
718
|
-
if (result.foundElements.length === 0 && onlyPriority3) {
|
|
769
|
+
if (result.foundElements.length === 0 && (onlyPriority3 || !highPriorityOnly)) {
|
|
719
770
|
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
720
771
|
}
|
|
721
|
-
else {
|
|
722
|
-
if (result.foundElements.length === 0 && !highPriorityOnly) {
|
|
723
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
772
|
let foundElements = result.foundElements;
|
|
727
773
|
if (foundElements.length === 1 && foundElements[0].unique) {
|
|
728
774
|
info.box = foundElements[0].box;
|
|
@@ -777,6 +823,11 @@ class StableBrowser {
|
|
|
777
823
|
visibleOnly = false;
|
|
778
824
|
}
|
|
779
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
|
+
}
|
|
780
831
|
}
|
|
781
832
|
this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
|
|
782
833
|
// if (info.locatorLog) {
|
|
@@ -823,9 +874,40 @@ class StableBrowser {
|
|
|
823
874
|
result.locatorIndex = i;
|
|
824
875
|
}
|
|
825
876
|
if (foundLocators.length > 1) {
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
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
|
+
}
|
|
829
911
|
}
|
|
830
912
|
}
|
|
831
913
|
}
|
|
@@ -873,7 +955,7 @@ class StableBrowser {
|
|
|
873
955
|
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
874
956
|
}
|
|
875
957
|
finally {
|
|
876
|
-
_commandFinally(state, this);
|
|
958
|
+
await _commandFinally(state, this);
|
|
877
959
|
}
|
|
878
960
|
}
|
|
879
961
|
}
|
|
@@ -922,7 +1004,7 @@ class StableBrowser {
|
|
|
922
1004
|
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
923
1005
|
}
|
|
924
1006
|
finally {
|
|
925
|
-
_commandFinally(state, this);
|
|
1007
|
+
await _commandFinally(state, this);
|
|
926
1008
|
}
|
|
927
1009
|
}
|
|
928
1010
|
}
|
|
@@ -936,25 +1018,14 @@ class StableBrowser {
|
|
|
936
1018
|
options,
|
|
937
1019
|
world,
|
|
938
1020
|
text: "Click element",
|
|
1021
|
+
_text: "Click on " + selectors.element_name,
|
|
939
1022
|
type: Types.CLICK,
|
|
940
1023
|
operation: "click",
|
|
941
1024
|
log: "***** click on " + selectors.element_name + " *****\n",
|
|
942
1025
|
};
|
|
943
1026
|
try {
|
|
944
1027
|
await _preCommand(state, this);
|
|
945
|
-
|
|
946
|
-
// state.selectors.locators[0].text = state.options.context;
|
|
947
|
-
// }
|
|
948
|
-
try {
|
|
949
|
-
await state.element.click();
|
|
950
|
-
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
951
|
-
}
|
|
952
|
-
catch (e) {
|
|
953
|
-
// await this.closeUnexpectedPopups();
|
|
954
|
-
state.element = await this._locate(selectors, state.info, _params);
|
|
955
|
-
await state.element.dispatchEvent("click");
|
|
956
|
-
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
957
|
-
}
|
|
1028
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
958
1029
|
await this.waitForPageLoad();
|
|
959
1030
|
return state.info;
|
|
960
1031
|
}
|
|
@@ -962,8 +1033,40 @@ class StableBrowser {
|
|
|
962
1033
|
await _commandError(state, e, this);
|
|
963
1034
|
}
|
|
964
1035
|
finally {
|
|
965
|
-
_commandFinally(state, this);
|
|
1036
|
+
await _commandFinally(state, this);
|
|
1037
|
+
}
|
|
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);
|
|
966
1068
|
}
|
|
1069
|
+
return found;
|
|
967
1070
|
}
|
|
968
1071
|
async setCheck(selectors, checked = true, _params, options = {}, world = null) {
|
|
969
1072
|
const state = {
|
|
@@ -973,6 +1076,7 @@ class StableBrowser {
|
|
|
973
1076
|
world,
|
|
974
1077
|
type: checked ? Types.CHECK : Types.UNCHECK,
|
|
975
1078
|
text: checked ? `Check element` : `Uncheck element`,
|
|
1079
|
+
_text: checked ? `Check ${selectors.element_name}` : `Uncheck ${selectors.element_name}`,
|
|
976
1080
|
operation: "setCheck",
|
|
977
1081
|
log: "***** check " + selectors.element_name + " *****\n",
|
|
978
1082
|
};
|
|
@@ -984,7 +1088,7 @@ class StableBrowser {
|
|
|
984
1088
|
try {
|
|
985
1089
|
// if (world && world.screenshot && !world.screenshotPath) {
|
|
986
1090
|
// console.log(`Highlighting while running from recorder`);
|
|
987
|
-
await this._highlightElements(element);
|
|
1091
|
+
await this._highlightElements(state.element);
|
|
988
1092
|
await state.element.setChecked(checked);
|
|
989
1093
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
990
1094
|
// await this._unHighlightElements(element);
|
|
@@ -1011,7 +1115,7 @@ class StableBrowser {
|
|
|
1011
1115
|
await _commandError(state, e, this);
|
|
1012
1116
|
}
|
|
1013
1117
|
finally {
|
|
1014
|
-
_commandFinally(state, this);
|
|
1118
|
+
await _commandFinally(state, this);
|
|
1015
1119
|
}
|
|
1016
1120
|
}
|
|
1017
1121
|
async hover(selectors, _params, options = {}, world = null) {
|
|
@@ -1022,24 +1126,13 @@ class StableBrowser {
|
|
|
1022
1126
|
world,
|
|
1023
1127
|
type: Types.HOVER,
|
|
1024
1128
|
text: `Hover element`,
|
|
1129
|
+
_text: `Hover on ${selectors.element_name}`,
|
|
1025
1130
|
operation: "hover",
|
|
1026
1131
|
log: "***** hover " + selectors.element_name + " *****\n",
|
|
1027
1132
|
};
|
|
1028
1133
|
try {
|
|
1029
1134
|
await _preCommand(state, this);
|
|
1030
|
-
|
|
1031
|
-
await state.element.hover();
|
|
1032
|
-
// await _screenshot(state, this);
|
|
1033
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1034
|
-
}
|
|
1035
|
-
catch (e) {
|
|
1036
|
-
//await this.closeUnexpectedPopups();
|
|
1037
|
-
state.info.log += "hover failed, will try again" + "\n";
|
|
1038
|
-
state.element = await this._locate(selectors, state.info, _params);
|
|
1039
|
-
await state.element.hover({ timeout: 10000 });
|
|
1040
|
-
// await _screenshot(state, this);
|
|
1041
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1042
|
-
}
|
|
1135
|
+
await performAction("hover", state.element, options, this, state, _params);
|
|
1043
1136
|
await _screenshot(state, this);
|
|
1044
1137
|
await this.waitForPageLoad();
|
|
1045
1138
|
return state.info;
|
|
@@ -1048,7 +1141,7 @@ class StableBrowser {
|
|
|
1048
1141
|
await _commandError(state, e, this);
|
|
1049
1142
|
}
|
|
1050
1143
|
finally {
|
|
1051
|
-
_commandFinally(state, this);
|
|
1144
|
+
await _commandFinally(state, this);
|
|
1052
1145
|
}
|
|
1053
1146
|
}
|
|
1054
1147
|
async selectOption(selectors, values, _params = null, options = {}, world = null) {
|
|
@@ -1063,6 +1156,7 @@ class StableBrowser {
|
|
|
1063
1156
|
value: values.toString(),
|
|
1064
1157
|
type: Types.SELECT,
|
|
1065
1158
|
text: `Select option: ${values}`,
|
|
1159
|
+
_text: `Select option: ${values} on ${selectors.element_name}`,
|
|
1066
1160
|
operation: "selectOption",
|
|
1067
1161
|
log: "***** select option " + selectors.element_name + " *****\n",
|
|
1068
1162
|
};
|
|
@@ -1083,7 +1177,7 @@ class StableBrowser {
|
|
|
1083
1177
|
await _commandError(state, e, this);
|
|
1084
1178
|
}
|
|
1085
1179
|
finally {
|
|
1086
|
-
_commandFinally(state, this);
|
|
1180
|
+
await _commandFinally(state, this);
|
|
1087
1181
|
}
|
|
1088
1182
|
}
|
|
1089
1183
|
async type(_value, _params = null, options = {}, world = null) {
|
|
@@ -1097,6 +1191,7 @@ class StableBrowser {
|
|
|
1097
1191
|
highlight: false,
|
|
1098
1192
|
type: Types.TYPE_PRESS,
|
|
1099
1193
|
text: `Type value: ${_value}`,
|
|
1194
|
+
_text: `Type value: ${_value}`,
|
|
1100
1195
|
operation: "type",
|
|
1101
1196
|
log: "",
|
|
1102
1197
|
};
|
|
@@ -1128,7 +1223,7 @@ class StableBrowser {
|
|
|
1128
1223
|
await _commandError(state, e, this);
|
|
1129
1224
|
}
|
|
1130
1225
|
finally {
|
|
1131
|
-
_commandFinally(state, this);
|
|
1226
|
+
await _commandFinally(state, this);
|
|
1132
1227
|
}
|
|
1133
1228
|
}
|
|
1134
1229
|
async setInputValue(selectors, value, _params = null, options = {}, world = null) {
|
|
@@ -1164,7 +1259,7 @@ class StableBrowser {
|
|
|
1164
1259
|
await _commandError(state, e, this);
|
|
1165
1260
|
}
|
|
1166
1261
|
finally {
|
|
1167
|
-
_commandFinally(state, this);
|
|
1262
|
+
await _commandFinally(state, this);
|
|
1168
1263
|
}
|
|
1169
1264
|
}
|
|
1170
1265
|
async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
|
|
@@ -1176,6 +1271,7 @@ class StableBrowser {
|
|
|
1176
1271
|
world,
|
|
1177
1272
|
type: Types.SET_DATE_TIME,
|
|
1178
1273
|
text: `Set date time value: ${value}`,
|
|
1274
|
+
_text: `Set date time value: ${value} on ${selectors.element_name}`,
|
|
1179
1275
|
operation: "setDateTime",
|
|
1180
1276
|
log: "***** set date time value " + selectors.element_name + " *****\n",
|
|
1181
1277
|
throwError: false,
|
|
@@ -1183,7 +1279,7 @@ class StableBrowser {
|
|
|
1183
1279
|
try {
|
|
1184
1280
|
await _preCommand(state, this);
|
|
1185
1281
|
try {
|
|
1186
|
-
await state.element
|
|
1282
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
1187
1283
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1188
1284
|
if (format) {
|
|
1189
1285
|
state.value = dayjs(state.value).format(format);
|
|
@@ -1232,7 +1328,7 @@ class StableBrowser {
|
|
|
1232
1328
|
await _commandError(state, e, this);
|
|
1233
1329
|
}
|
|
1234
1330
|
finally {
|
|
1235
|
-
_commandFinally(state, this);
|
|
1331
|
+
await _commandFinally(state, this);
|
|
1236
1332
|
}
|
|
1237
1333
|
}
|
|
1238
1334
|
async clickType(selectors, _value, enter = false, _params = null, options = {}, world = null) {
|
|
@@ -1247,9 +1343,13 @@ class StableBrowser {
|
|
|
1247
1343
|
world,
|
|
1248
1344
|
type: Types.FILL,
|
|
1249
1345
|
text: `Click type input with value: ${_value}`,
|
|
1346
|
+
_text: "Fill " + selectors.element_name + " with value " + maskValue(_value),
|
|
1250
1347
|
operation: "clickType",
|
|
1251
1348
|
log: "***** clickType on " + selectors.element_name + " with value " + maskValue(_value) + "*****\n",
|
|
1252
1349
|
};
|
|
1350
|
+
if (!options) {
|
|
1351
|
+
options = {};
|
|
1352
|
+
}
|
|
1253
1353
|
if (newValue !== _value) {
|
|
1254
1354
|
//this.logger.info(_value + "=" + newValue);
|
|
1255
1355
|
_value = newValue;
|
|
@@ -1257,7 +1357,7 @@ class StableBrowser {
|
|
|
1257
1357
|
try {
|
|
1258
1358
|
await _preCommand(state, this);
|
|
1259
1359
|
state.info.value = _value;
|
|
1260
|
-
if (
|
|
1360
|
+
if (!options.press) {
|
|
1261
1361
|
try {
|
|
1262
1362
|
let currentValue = await state.element.inputValue();
|
|
1263
1363
|
if (currentValue) {
|
|
@@ -1268,13 +1368,9 @@ class StableBrowser {
|
|
|
1268
1368
|
this.logger.info("unable to clear input value");
|
|
1269
1369
|
}
|
|
1270
1370
|
}
|
|
1271
|
-
if (options
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
}
|
|
1275
|
-
catch (e) {
|
|
1276
|
-
await state.element.dispatchEvent("click");
|
|
1277
|
-
}
|
|
1371
|
+
if (options.press) {
|
|
1372
|
+
options.timeout = 5000;
|
|
1373
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
1278
1374
|
}
|
|
1279
1375
|
else {
|
|
1280
1376
|
try {
|
|
@@ -1332,7 +1428,7 @@ class StableBrowser {
|
|
|
1332
1428
|
await _commandError(state, e, this);
|
|
1333
1429
|
}
|
|
1334
1430
|
finally {
|
|
1335
|
-
_commandFinally(state, this);
|
|
1431
|
+
await _commandFinally(state, this);
|
|
1336
1432
|
}
|
|
1337
1433
|
}
|
|
1338
1434
|
async fill(selectors, value, enter = false, _params = null, options = {}, world = null) {
|
|
@@ -1362,13 +1458,49 @@ class StableBrowser {
|
|
|
1362
1458
|
await _commandError(state, e, this);
|
|
1363
1459
|
}
|
|
1364
1460
|
finally {
|
|
1365
|
-
_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);
|
|
1366
1497
|
}
|
|
1367
1498
|
}
|
|
1368
1499
|
async getText(selectors, _params = null, options = {}, info = {}, world = null) {
|
|
1369
1500
|
return await this._getText(selectors, 0, _params, options, info, world);
|
|
1370
1501
|
}
|
|
1371
1502
|
async _getText(selectors, climb, _params = null, options = {}, info = {}, world = null) {
|
|
1503
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1372
1504
|
_validateSelectors(selectors);
|
|
1373
1505
|
let screenshotId = null;
|
|
1374
1506
|
let screenshotPath = null;
|
|
@@ -1378,7 +1510,7 @@ class StableBrowser {
|
|
|
1378
1510
|
}
|
|
1379
1511
|
info.operation = "getText";
|
|
1380
1512
|
info.selectors = selectors;
|
|
1381
|
-
let element = await this._locate(selectors, info, _params);
|
|
1513
|
+
let element = await this._locate(selectors, info, _params, timeout);
|
|
1382
1514
|
if (climb > 0) {
|
|
1383
1515
|
const climbArray = [];
|
|
1384
1516
|
for (let i = 0; i < climb; i++) {
|
|
@@ -1445,6 +1577,7 @@ class StableBrowser {
|
|
|
1445
1577
|
highlight: false,
|
|
1446
1578
|
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1447
1579
|
text: `Verify element contains pattern: ${pattern}`,
|
|
1580
|
+
_text: "Verify element " + selectors.element_name + " contains pattern " + pattern,
|
|
1448
1581
|
operation: "containsPattern",
|
|
1449
1582
|
log: "***** verify element " + selectors.element_name + " contains pattern " + pattern + " *****\n",
|
|
1450
1583
|
};
|
|
@@ -1476,10 +1609,12 @@ class StableBrowser {
|
|
|
1476
1609
|
await _commandError(state, e, this);
|
|
1477
1610
|
}
|
|
1478
1611
|
finally {
|
|
1479
|
-
_commandFinally(state, this);
|
|
1612
|
+
await _commandFinally(state, this);
|
|
1480
1613
|
}
|
|
1481
1614
|
}
|
|
1482
1615
|
async containsText(selectors, text, climb, _params = null, options = {}, world = null) {
|
|
1616
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1617
|
+
const startTime = Date.now();
|
|
1483
1618
|
const state = {
|
|
1484
1619
|
selectors,
|
|
1485
1620
|
_params,
|
|
@@ -1506,44 +1641,124 @@ class StableBrowser {
|
|
|
1506
1641
|
}
|
|
1507
1642
|
let foundObj = null;
|
|
1508
1643
|
try {
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
if (
|
|
1520
|
-
|
|
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);
|
|
1650
|
+
}
|
|
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)) {
|
|
1521
1671
|
return state.info;
|
|
1522
1672
|
}
|
|
1523
1673
|
}
|
|
1524
|
-
|
|
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
|
|
1525
1679
|
}
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1680
|
+
state.info.foundText = foundObj?.text;
|
|
1681
|
+
state.info.value = foundObj?.value;
|
|
1682
|
+
throw new Error("element doesn't contain text " + text);
|
|
1683
|
+
}
|
|
1684
|
+
catch (e) {
|
|
1685
|
+
await _commandError(state, e, this);
|
|
1686
|
+
throw e;
|
|
1687
|
+
}
|
|
1688
|
+
finally {
|
|
1689
|
+
await _commandFinally(state, this);
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
async snapshotValidation(frameSelectors, referanceSnapshot, _params = null, options = {}, world = null) {
|
|
1693
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1694
|
+
const startTime = Date.now();
|
|
1695
|
+
const state = {
|
|
1696
|
+
_params,
|
|
1697
|
+
value: referanceSnapshot,
|
|
1698
|
+
options,
|
|
1699
|
+
world,
|
|
1700
|
+
locate: false,
|
|
1701
|
+
scroll: false,
|
|
1702
|
+
screenshot: true,
|
|
1703
|
+
highlight: false,
|
|
1704
|
+
type: Types.SNAPSHOT_VALIDATION,
|
|
1705
|
+
text: `verify snapshot: ${referanceSnapshot}`,
|
|
1706
|
+
operation: "snapshotValidation",
|
|
1707
|
+
log: "***** verify snapshot *****\n",
|
|
1708
|
+
};
|
|
1709
|
+
if (!referanceSnapshot) {
|
|
1710
|
+
throw new Error("referanceSnapshot is null");
|
|
1711
|
+
}
|
|
1712
|
+
let text = null;
|
|
1713
|
+
if (fs.existsSync(path.join(this.project_path, "data", "snapshots", this.context.environment.name, referanceSnapshot + ".yml"))) {
|
|
1714
|
+
text = fs.readFileSync(path.join(this.project_path, "data", "snapshots", this.context.environment.name, referanceSnapshot + ".yml"), "utf8");
|
|
1715
|
+
}
|
|
1716
|
+
else if (fs.existsSync(path.join(this.project_path, "data", "snapshots", this.context.environment.name, referanceSnapshot + ".yaml"))) {
|
|
1717
|
+
text = fs.readFileSync(path.join(this.project_path, "data", "snapshots", this.context.environment.name, referanceSnapshot + ".yaml"), "utf8");
|
|
1718
|
+
}
|
|
1719
|
+
else if (referanceSnapshot.startsWith("yaml:")) {
|
|
1720
|
+
text = referanceSnapshot.substring(5);
|
|
1721
|
+
}
|
|
1722
|
+
else {
|
|
1723
|
+
throw new Error("referenceSnapshot file not found: " + referanceSnapshot);
|
|
1724
|
+
}
|
|
1725
|
+
state.text = text;
|
|
1726
|
+
const newValue = await this._replaceWithLocalData(text, world);
|
|
1727
|
+
await _preCommand(state, this);
|
|
1728
|
+
let foundObj = null;
|
|
1729
|
+
try {
|
|
1730
|
+
let matchResult = null;
|
|
1731
|
+
while (Date.now() - startTime < timeout) {
|
|
1732
|
+
try {
|
|
1733
|
+
let scope = null;
|
|
1734
|
+
if (!frameSelectors) {
|
|
1735
|
+
scope = this.page;
|
|
1531
1736
|
}
|
|
1737
|
+
else {
|
|
1738
|
+
scope = await this._findFrameScope(frameSelectors, timeout, state.info);
|
|
1739
|
+
}
|
|
1740
|
+
const snapshot = await scope.locator("body").ariaSnapshot({ timeout });
|
|
1741
|
+
matchResult = snapshotValidation(snapshot, newValue);
|
|
1742
|
+
if (matchResult.errorLine !== -1) {
|
|
1743
|
+
throw new Error("Snapshot validation failed at line " + matchResult.errorLineText);
|
|
1744
|
+
}
|
|
1745
|
+
// highlight and screenshot
|
|
1746
|
+
return state.info;
|
|
1532
1747
|
}
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
throw new Error("element doesn't contain text " + text);
|
|
1748
|
+
catch (e) {
|
|
1749
|
+
// Log error but continue retrying until timeout is reached
|
|
1750
|
+
this.logger.warn("Retrying containsText due to: " + e.message);
|
|
1751
|
+
}
|
|
1752
|
+
await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second before retrying
|
|
1539
1753
|
}
|
|
1540
|
-
|
|
1754
|
+
throw new Error("No snapshot match " + matchResult?.errorLineText);
|
|
1541
1755
|
}
|
|
1542
1756
|
catch (e) {
|
|
1543
1757
|
await _commandError(state, e, this);
|
|
1758
|
+
throw e;
|
|
1544
1759
|
}
|
|
1545
1760
|
finally {
|
|
1546
|
-
_commandFinally(state, this);
|
|
1761
|
+
await _commandFinally(state, this);
|
|
1547
1762
|
}
|
|
1548
1763
|
}
|
|
1549
1764
|
async waitForUserInput(message, world = null) {
|
|
@@ -1581,6 +1796,15 @@ class StableBrowser {
|
|
|
1581
1796
|
// save the data to the file
|
|
1582
1797
|
fs.writeFileSync(dataFile, JSON.stringify(data, null, 2));
|
|
1583
1798
|
}
|
|
1799
|
+
overwriteTestData(testData, world = null) {
|
|
1800
|
+
if (!testData) {
|
|
1801
|
+
return;
|
|
1802
|
+
}
|
|
1803
|
+
// if data file exists, load it
|
|
1804
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1805
|
+
// save the data to the file
|
|
1806
|
+
fs.writeFileSync(dataFile, JSON.stringify(testData, null, 2));
|
|
1807
|
+
}
|
|
1584
1808
|
_getDataFilePath(fileName) {
|
|
1585
1809
|
let dataFile = path.join(this.project_path, "data", fileName);
|
|
1586
1810
|
if (fs.existsSync(dataFile)) {
|
|
@@ -1833,7 +2057,7 @@ class StableBrowser {
|
|
|
1833
2057
|
await _commandError(state, e, this);
|
|
1834
2058
|
}
|
|
1835
2059
|
finally {
|
|
1836
|
-
_commandFinally(state, this);
|
|
2060
|
+
await _commandFinally(state, this);
|
|
1837
2061
|
}
|
|
1838
2062
|
}
|
|
1839
2063
|
async extractAttribute(selectors, attribute, variable, _params = null, options = {}, world = null) {
|
|
@@ -1846,6 +2070,7 @@ class StableBrowser {
|
|
|
1846
2070
|
world,
|
|
1847
2071
|
type: Types.EXTRACT,
|
|
1848
2072
|
text: `Extract attribute from element`,
|
|
2073
|
+
_text: `Extract attribute ${attribute} from ${selectors.element_name}`,
|
|
1849
2074
|
operation: "extractAttribute",
|
|
1850
2075
|
log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1851
2076
|
allowDisabled: true,
|
|
@@ -1863,10 +2088,31 @@ class StableBrowser {
|
|
|
1863
2088
|
case "value":
|
|
1864
2089
|
state.value = await state.element.inputValue();
|
|
1865
2090
|
break;
|
|
2091
|
+
case "text":
|
|
2092
|
+
state.value = await state.element.textContent();
|
|
2093
|
+
break;
|
|
1866
2094
|
default:
|
|
1867
2095
|
state.value = await state.element.getAttribute(attribute);
|
|
1868
2096
|
break;
|
|
1869
2097
|
}
|
|
2098
|
+
if (options !== null) {
|
|
2099
|
+
if (options.regex && options.regex !== "") {
|
|
2100
|
+
// Construct a regex pattern from the provided string
|
|
2101
|
+
const regex = options.regex.slice(1, -1);
|
|
2102
|
+
const regexPattern = new RegExp(regex, "g");
|
|
2103
|
+
const matches = state.value.match(regexPattern);
|
|
2104
|
+
if (matches) {
|
|
2105
|
+
let newValue = "";
|
|
2106
|
+
for (const match of matches) {
|
|
2107
|
+
newValue += match;
|
|
2108
|
+
}
|
|
2109
|
+
state.value = newValue;
|
|
2110
|
+
}
|
|
2111
|
+
}
|
|
2112
|
+
if (options.trimSpaces && options.trimSpaces === true) {
|
|
2113
|
+
state.value = state.value.trim();
|
|
2114
|
+
}
|
|
2115
|
+
}
|
|
1870
2116
|
state.info.value = state.value;
|
|
1871
2117
|
this.setTestData({ [variable]: state.value }, world);
|
|
1872
2118
|
this.logger.info("set test data: " + variable + "=" + state.value);
|
|
@@ -1877,7 +2123,7 @@ class StableBrowser {
|
|
|
1877
2123
|
await _commandError(state, e, this);
|
|
1878
2124
|
}
|
|
1879
2125
|
finally {
|
|
1880
|
-
_commandFinally(state, this);
|
|
2126
|
+
await _commandFinally(state, this);
|
|
1881
2127
|
}
|
|
1882
2128
|
}
|
|
1883
2129
|
async verifyAttribute(selectors, attribute, value, _params = null, options = {}, world = null) {
|
|
@@ -1892,6 +2138,7 @@ class StableBrowser {
|
|
|
1892
2138
|
highlight: true,
|
|
1893
2139
|
screenshot: true,
|
|
1894
2140
|
text: `Verify element attribute`,
|
|
2141
|
+
_text: `Verify attribute ${attribute} from ${selectors.element_name} is ${value}`,
|
|
1895
2142
|
operation: "verifyAttribute",
|
|
1896
2143
|
log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1897
2144
|
allowDisabled: true,
|
|
@@ -1901,12 +2148,15 @@ class StableBrowser {
|
|
|
1901
2148
|
let expectedValue;
|
|
1902
2149
|
try {
|
|
1903
2150
|
await _preCommand(state, this);
|
|
1904
|
-
expectedValue = state.value;
|
|
2151
|
+
expectedValue = await replaceWithLocalTestData(state.value, world);
|
|
1905
2152
|
state.info.expectedValue = expectedValue;
|
|
1906
2153
|
switch (attribute) {
|
|
1907
2154
|
case "innerText":
|
|
1908
2155
|
val = String(await state.element.innerText());
|
|
1909
2156
|
break;
|
|
2157
|
+
case "text":
|
|
2158
|
+
val = String(await state.element.textContent());
|
|
2159
|
+
break;
|
|
1910
2160
|
case "value":
|
|
1911
2161
|
val = String(await state.element.inputValue());
|
|
1912
2162
|
break;
|
|
@@ -1946,7 +2196,7 @@ class StableBrowser {
|
|
|
1946
2196
|
await _commandError(state, e, this);
|
|
1947
2197
|
}
|
|
1948
2198
|
finally {
|
|
1949
|
-
_commandFinally(state, this);
|
|
2199
|
+
await _commandFinally(state, this);
|
|
1950
2200
|
}
|
|
1951
2201
|
}
|
|
1952
2202
|
async extractEmailData(emailAddress, options, world) {
|
|
@@ -2198,6 +2448,69 @@ class StableBrowser {
|
|
|
2198
2448
|
_reportToWorld(world, {
|
|
2199
2449
|
type: Types.VERIFY_PAGE_PATH,
|
|
2200
2450
|
text: "Verify page path",
|
|
2451
|
+
_text: "Verify the page path contains " + pathPart,
|
|
2452
|
+
screenshotId,
|
|
2453
|
+
result: error
|
|
2454
|
+
? {
|
|
2455
|
+
status: "FAILED",
|
|
2456
|
+
startTime,
|
|
2457
|
+
endTime,
|
|
2458
|
+
message: error?.message,
|
|
2459
|
+
}
|
|
2460
|
+
: {
|
|
2461
|
+
status: "PASSED",
|
|
2462
|
+
startTime,
|
|
2463
|
+
endTime,
|
|
2464
|
+
},
|
|
2465
|
+
info: info,
|
|
2466
|
+
});
|
|
2467
|
+
}
|
|
2468
|
+
}
|
|
2469
|
+
async verifyPageTitle(title, options = {}, world = null) {
|
|
2470
|
+
const startTime = Date.now();
|
|
2471
|
+
let error = null;
|
|
2472
|
+
let screenshotId = null;
|
|
2473
|
+
let screenshotPath = null;
|
|
2474
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2475
|
+
const info = {};
|
|
2476
|
+
info.log = "***** verify page title " + title + " *****\n";
|
|
2477
|
+
info.operation = "verifyPageTitle";
|
|
2478
|
+
const newValue = await this._replaceWithLocalData(title, world);
|
|
2479
|
+
if (newValue !== title) {
|
|
2480
|
+
this.logger.info(title + "=" + newValue);
|
|
2481
|
+
title = newValue;
|
|
2482
|
+
}
|
|
2483
|
+
info.title = title;
|
|
2484
|
+
try {
|
|
2485
|
+
for (let i = 0; i < 30; i++) {
|
|
2486
|
+
const foundTitle = await this.page.title();
|
|
2487
|
+
if (!foundTitle.includes(title)) {
|
|
2488
|
+
if (i === 29) {
|
|
2489
|
+
throw new Error(`url ${foundTitle} doesn't contain ${title}`);
|
|
2490
|
+
}
|
|
2491
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2492
|
+
continue;
|
|
2493
|
+
}
|
|
2494
|
+
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2495
|
+
return info;
|
|
2496
|
+
}
|
|
2497
|
+
}
|
|
2498
|
+
catch (e) {
|
|
2499
|
+
//await this.closeUnexpectedPopups();
|
|
2500
|
+
this.logger.error("verify page title failed " + info.log);
|
|
2501
|
+
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2502
|
+
info.screenshotPath = screenshotPath;
|
|
2503
|
+
Object.assign(e, { info: info });
|
|
2504
|
+
error = e;
|
|
2505
|
+
// throw e;
|
|
2506
|
+
await _commandError({ text: "verifyPageTitle", operation: "verifyPageTitle", title, info, throwError: true }, e, this);
|
|
2507
|
+
}
|
|
2508
|
+
finally {
|
|
2509
|
+
const endTime = Date.now();
|
|
2510
|
+
_reportToWorld(world, {
|
|
2511
|
+
type: Types.VERIFY_PAGE_PATH,
|
|
2512
|
+
text: "Verify page title",
|
|
2513
|
+
_text: "Verify the page title contains " + title,
|
|
2201
2514
|
screenshotId,
|
|
2202
2515
|
result: error
|
|
2203
2516
|
? {
|
|
@@ -2215,27 +2528,27 @@ class StableBrowser {
|
|
|
2215
2528
|
});
|
|
2216
2529
|
}
|
|
2217
2530
|
}
|
|
2218
|
-
async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state) {
|
|
2531
|
+
async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state, partial = true, ignoreCase = false) {
|
|
2219
2532
|
const frames = this.page.frames();
|
|
2220
2533
|
let results = [];
|
|
2221
|
-
let ignoreCase = false;
|
|
2534
|
+
// let ignoreCase = false;
|
|
2222
2535
|
for (let i = 0; i < frames.length; i++) {
|
|
2223
2536
|
if (dateAlternatives.date) {
|
|
2224
2537
|
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2225
|
-
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false,
|
|
2538
|
+
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, partial, ignoreCase, {});
|
|
2226
2539
|
result.frame = frames[i];
|
|
2227
2540
|
results.push(result);
|
|
2228
2541
|
}
|
|
2229
2542
|
}
|
|
2230
2543
|
else if (numberAlternatives.number) {
|
|
2231
2544
|
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2232
|
-
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false,
|
|
2545
|
+
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, partial, ignoreCase, {});
|
|
2233
2546
|
result.frame = frames[i];
|
|
2234
2547
|
results.push(result);
|
|
2235
2548
|
}
|
|
2236
2549
|
}
|
|
2237
2550
|
else {
|
|
2238
|
-
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false,
|
|
2551
|
+
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, partial, ignoreCase, {});
|
|
2239
2552
|
result.frame = frames[i];
|
|
2240
2553
|
results.push(result);
|
|
2241
2554
|
}
|
|
@@ -2254,11 +2567,15 @@ class StableBrowser {
|
|
|
2254
2567
|
scroll: false,
|
|
2255
2568
|
highlight: false,
|
|
2256
2569
|
type: Types.VERIFY_PAGE_CONTAINS_TEXT,
|
|
2257
|
-
text: `Verify text exists in page`,
|
|
2570
|
+
text: `Verify the text '${maskValue(text)}' exists in page`,
|
|
2571
|
+
_text: `Verify the text '${text}' exists in page`,
|
|
2258
2572
|
operation: "verifyTextExistInPage",
|
|
2259
2573
|
log: "***** verify text " + text + " exists in page *****\n",
|
|
2260
2574
|
};
|
|
2261
|
-
|
|
2575
|
+
if (testForRegex(text)) {
|
|
2576
|
+
text = text.replace(/\\"/g, '"');
|
|
2577
|
+
}
|
|
2578
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2262
2579
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2263
2580
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2264
2581
|
if (newValue !== text) {
|
|
@@ -2328,7 +2645,7 @@ class StableBrowser {
|
|
|
2328
2645
|
await _commandError(state, e, this);
|
|
2329
2646
|
}
|
|
2330
2647
|
finally {
|
|
2331
|
-
_commandFinally(state, this);
|
|
2648
|
+
await _commandFinally(state, this);
|
|
2332
2649
|
}
|
|
2333
2650
|
}
|
|
2334
2651
|
async waitForTextToDisappear(text, options = {}, world = null) {
|
|
@@ -2341,11 +2658,15 @@ class StableBrowser {
|
|
|
2341
2658
|
scroll: false,
|
|
2342
2659
|
highlight: false,
|
|
2343
2660
|
type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
|
|
2344
|
-
text: `Verify text does not exist in page`,
|
|
2661
|
+
text: `Verify the text '${maskValue(text)}' does not exist in page`,
|
|
2662
|
+
_text: `Verify the text '${text}' does not exist in page`,
|
|
2345
2663
|
operation: "verifyTextNotExistInPage",
|
|
2346
2664
|
log: "***** verify text " + text + " does not exist in page *****\n",
|
|
2347
2665
|
};
|
|
2348
|
-
|
|
2666
|
+
if (testForRegex(text)) {
|
|
2667
|
+
text = text.replace(/\\"/g, '"');
|
|
2668
|
+
}
|
|
2669
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2349
2670
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2350
2671
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2351
2672
|
if (newValue !== text) {
|
|
@@ -2381,7 +2702,7 @@ class StableBrowser {
|
|
|
2381
2702
|
await _commandError(state, e, this);
|
|
2382
2703
|
}
|
|
2383
2704
|
finally {
|
|
2384
|
-
_commandFinally(state, this);
|
|
2705
|
+
await _commandFinally(state, this);
|
|
2385
2706
|
}
|
|
2386
2707
|
}
|
|
2387
2708
|
async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
|
|
@@ -2396,10 +2717,11 @@ class StableBrowser {
|
|
|
2396
2717
|
highlight: false,
|
|
2397
2718
|
type: Types.VERIFY_TEXT_WITH_RELATION,
|
|
2398
2719
|
text: `Verify text with relation to another text`,
|
|
2720
|
+
_text: "Search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found",
|
|
2399
2721
|
operation: "verify_text_with_relation",
|
|
2400
2722
|
log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
|
|
2401
2723
|
};
|
|
2402
|
-
const timeout = this.
|
|
2724
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2403
2725
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2404
2726
|
let newValue = await this._replaceWithLocalData(textAnchor, world);
|
|
2405
2727
|
if (newValue !== textAnchor) {
|
|
@@ -2422,7 +2744,7 @@ class StableBrowser {
|
|
|
2422
2744
|
};
|
|
2423
2745
|
while (true) {
|
|
2424
2746
|
try {
|
|
2425
|
-
resultWithElementsFound = await this.findTextInAllFrames(
|
|
2747
|
+
resultWithElementsFound = await this.findTextInAllFrames(findDateAlternatives(textAnchor), findNumberAlternatives(textAnchor), textAnchor, state, false);
|
|
2426
2748
|
}
|
|
2427
2749
|
catch (error) {
|
|
2428
2750
|
// ignore
|
|
@@ -2450,7 +2772,7 @@ class StableBrowser {
|
|
|
2450
2772
|
const count = await frame.locator(css).count();
|
|
2451
2773
|
for (let j = 0; j < count; j++) {
|
|
2452
2774
|
const continer = await frame.locator(css).nth(j);
|
|
2453
|
-
const result = await this._locateElementByText(continer, textToVerify, "
|
|
2775
|
+
const result = await this._locateElementByText(continer, textToVerify, "*:not(script, style, head)", false, true, true, {});
|
|
2454
2776
|
if (result.elementCount > 0) {
|
|
2455
2777
|
const dataAttribute = "[data-blinq-id-" + result.randomToken + "]";
|
|
2456
2778
|
await this._highlightElements(frame, dataAttribute);
|
|
@@ -2491,8 +2813,32 @@ class StableBrowser {
|
|
|
2491
2813
|
await _commandError(state, e, this);
|
|
2492
2814
|
}
|
|
2493
2815
|
finally {
|
|
2494
|
-
_commandFinally(state, this);
|
|
2816
|
+
await _commandFinally(state, this);
|
|
2817
|
+
}
|
|
2818
|
+
}
|
|
2819
|
+
async findRelatedTextInAllFrames(textAnchor, climb, textToVerify, params = {}, options = {}, world = null) {
|
|
2820
|
+
const frames = this.page.frames();
|
|
2821
|
+
let results = [];
|
|
2822
|
+
let ignoreCase = false;
|
|
2823
|
+
for (let i = 0; i < frames.length; i++) {
|
|
2824
|
+
const result = await this._locateElementByText(frames[i], textAnchor, "*:not(script, style, head)", false, true, ignoreCase, {});
|
|
2825
|
+
result.frame = frames[i];
|
|
2826
|
+
const climbArray = [];
|
|
2827
|
+
for (let i = 0; i < climb; i++) {
|
|
2828
|
+
climbArray.push("..");
|
|
2829
|
+
}
|
|
2830
|
+
let climbXpath = "xpath=" + climbArray.join("/");
|
|
2831
|
+
const newLocator = `[data-blinq-id-${result.randomToken}] ${climb > 0 ? ">> " + climbXpath : ""} >> internal:text=${testForRegex(textToVerify) ? textToVerify : unEscapeString(textToVerify)}`;
|
|
2832
|
+
const count = await frames[i].locator(newLocator).count();
|
|
2833
|
+
if (count > 0) {
|
|
2834
|
+
result.elementCount = count;
|
|
2835
|
+
result.locator = newLocator;
|
|
2836
|
+
results.push(result);
|
|
2837
|
+
}
|
|
2495
2838
|
}
|
|
2839
|
+
// state.info.results = results;
|
|
2840
|
+
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2841
|
+
return resultWithElementsFound;
|
|
2496
2842
|
}
|
|
2497
2843
|
async visualVerification(text, options = {}, world = null) {
|
|
2498
2844
|
const startTime = Date.now();
|
|
@@ -2555,6 +2901,7 @@ class StableBrowser {
|
|
|
2555
2901
|
_reportToWorld(world, {
|
|
2556
2902
|
type: Types.VERIFY_VISUAL,
|
|
2557
2903
|
text: "Visual verification",
|
|
2904
|
+
_text: "Visual verification of " + text,
|
|
2558
2905
|
screenshotId,
|
|
2559
2906
|
result: error
|
|
2560
2907
|
? {
|
|
@@ -2809,7 +3156,13 @@ class StableBrowser {
|
|
|
2809
3156
|
}
|
|
2810
3157
|
}
|
|
2811
3158
|
async _replaceWithLocalData(value, world, _decrypt = true, totpWait = true) {
|
|
2812
|
-
|
|
3159
|
+
try {
|
|
3160
|
+
return await replaceWithLocalTestData(value, world, _decrypt, totpWait, this.context, this);
|
|
3161
|
+
}
|
|
3162
|
+
catch (error) {
|
|
3163
|
+
this.logger.debug(error);
|
|
3164
|
+
throw error;
|
|
3165
|
+
}
|
|
2813
3166
|
}
|
|
2814
3167
|
_getLoadTimeout(options) {
|
|
2815
3168
|
let timeout = 15000;
|
|
@@ -2821,6 +3174,15 @@ class StableBrowser {
|
|
|
2821
3174
|
}
|
|
2822
3175
|
return timeout;
|
|
2823
3176
|
}
|
|
3177
|
+
_getFindElementTimeout(options) {
|
|
3178
|
+
if (options && options.timeout) {
|
|
3179
|
+
return options.timeout;
|
|
3180
|
+
}
|
|
3181
|
+
if (this.configuration.find_element_timeout) {
|
|
3182
|
+
return this.configuration.find_element_timeout;
|
|
3183
|
+
}
|
|
3184
|
+
return 30000;
|
|
3185
|
+
}
|
|
2824
3186
|
async saveStoreState(path = null, world = null) {
|
|
2825
3187
|
const storageState = await this.page.context().storageState();
|
|
2826
3188
|
//const testDataFile = _getDataFile(world, this.context, this);
|
|
@@ -2837,6 +3199,9 @@ class StableBrowser {
|
|
|
2837
3199
|
this.registerEventListeners(this.context);
|
|
2838
3200
|
registerNetworkEvents(this.world, this, this.context, this.page);
|
|
2839
3201
|
registerDownloadEvent(this.page, this.world, this.context);
|
|
3202
|
+
if (this.onRestoreSaveState) {
|
|
3203
|
+
this.onRestoreSaveState(path);
|
|
3204
|
+
}
|
|
2840
3205
|
}
|
|
2841
3206
|
async waitForPageLoad(options = {}, world = null) {
|
|
2842
3207
|
let timeout = this._getLoadTimeout(options);
|
|
@@ -2905,6 +3270,7 @@ class StableBrowser {
|
|
|
2905
3270
|
highlight: false,
|
|
2906
3271
|
type: Types.CLOSE_PAGE,
|
|
2907
3272
|
text: `Close page`,
|
|
3273
|
+
_text: `Close the page`,
|
|
2908
3274
|
operation: "closePage",
|
|
2909
3275
|
log: "***** close page *****\n",
|
|
2910
3276
|
throwError: false,
|
|
@@ -2918,11 +3284,98 @@ class StableBrowser {
|
|
|
2918
3284
|
await _commandError(state, e, this);
|
|
2919
3285
|
}
|
|
2920
3286
|
finally {
|
|
2921
|
-
_commandFinally(state, this);
|
|
3287
|
+
await _commandFinally(state, this);
|
|
3288
|
+
}
|
|
3289
|
+
}
|
|
3290
|
+
async tableCellOperation(headerText, rowText, options, _params, world = null) {
|
|
3291
|
+
let operation = null;
|
|
3292
|
+
if (!options || !options.operation) {
|
|
3293
|
+
throw new Error("operation is not defined");
|
|
3294
|
+
}
|
|
3295
|
+
operation = options.operation;
|
|
3296
|
+
// validate operation is one of the supported operations
|
|
3297
|
+
if (operation != "click" && operation != "hover+click") {
|
|
3298
|
+
throw new Error("operation is not supported");
|
|
3299
|
+
}
|
|
3300
|
+
const state = {
|
|
3301
|
+
options,
|
|
3302
|
+
world,
|
|
3303
|
+
locate: false,
|
|
3304
|
+
scroll: false,
|
|
3305
|
+
highlight: false,
|
|
3306
|
+
type: Types.TABLE_OPERATION,
|
|
3307
|
+
text: `Table operation`,
|
|
3308
|
+
_text: `Table ${operation} operation`,
|
|
3309
|
+
operation: operation,
|
|
3310
|
+
log: "***** Table operation *****\n",
|
|
3311
|
+
};
|
|
3312
|
+
const timeout = this._getFindElementTimeout(options);
|
|
3313
|
+
try {
|
|
3314
|
+
await _preCommand(state, this);
|
|
3315
|
+
const start = Date.now();
|
|
3316
|
+
let cellArea = null;
|
|
3317
|
+
while (true) {
|
|
3318
|
+
try {
|
|
3319
|
+
cellArea = await _findCellArea(headerText, rowText, this, state);
|
|
3320
|
+
if (cellArea) {
|
|
3321
|
+
break;
|
|
3322
|
+
}
|
|
3323
|
+
}
|
|
3324
|
+
catch (e) {
|
|
3325
|
+
// ignore
|
|
3326
|
+
}
|
|
3327
|
+
if (Date.now() - start > timeout) {
|
|
3328
|
+
throw new Error(`Cell not found in table`);
|
|
3329
|
+
}
|
|
3330
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
3331
|
+
}
|
|
3332
|
+
switch (operation) {
|
|
3333
|
+
case "click":
|
|
3334
|
+
if (!options.css) {
|
|
3335
|
+
// will click in the center of the cell
|
|
3336
|
+
let xOffset = 0;
|
|
3337
|
+
let yOffset = 0;
|
|
3338
|
+
if (options.xOffset) {
|
|
3339
|
+
xOffset = options.xOffset;
|
|
3340
|
+
}
|
|
3341
|
+
if (options.yOffset) {
|
|
3342
|
+
yOffset = options.yOffset;
|
|
3343
|
+
}
|
|
3344
|
+
await this.page.mouse.click(cellArea.x + cellArea.width / 2 + xOffset, cellArea.y + cellArea.height / 2 + yOffset);
|
|
3345
|
+
}
|
|
3346
|
+
else {
|
|
3347
|
+
const results = await findElementsInArea(options.css, cellArea, this, options);
|
|
3348
|
+
if (results.length === 0) {
|
|
3349
|
+
throw new Error(`Element not found in cell area`);
|
|
3350
|
+
}
|
|
3351
|
+
state.element = results[0];
|
|
3352
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
3353
|
+
}
|
|
3354
|
+
break;
|
|
3355
|
+
case "hover+click":
|
|
3356
|
+
if (!options.css) {
|
|
3357
|
+
throw new Error("css is not defined");
|
|
3358
|
+
}
|
|
3359
|
+
const results = await findElementsInArea(options.css, cellArea, this, options);
|
|
3360
|
+
if (results.length === 0) {
|
|
3361
|
+
throw new Error(`Element not found in cell area`);
|
|
3362
|
+
}
|
|
3363
|
+
state.element = results[0];
|
|
3364
|
+
await performAction("hover+click", state.element, options, this, state, _params);
|
|
3365
|
+
break;
|
|
3366
|
+
default:
|
|
3367
|
+
throw new Error("operation is not supported");
|
|
3368
|
+
}
|
|
3369
|
+
}
|
|
3370
|
+
catch (e) {
|
|
3371
|
+
await _commandError(state, e, this);
|
|
3372
|
+
}
|
|
3373
|
+
finally {
|
|
3374
|
+
await _commandFinally(state, this);
|
|
2922
3375
|
}
|
|
2923
3376
|
}
|
|
2924
3377
|
saveTestDataAsGlobal(options, world) {
|
|
2925
|
-
const dataFile =
|
|
3378
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
2926
3379
|
process.env.GLOBAL_TEST_DATA_FILE = dataFile;
|
|
2927
3380
|
this.logger.info("Save the scenario test data as global for the following scenarios.");
|
|
2928
3381
|
}
|
|
@@ -2952,6 +3405,7 @@ class StableBrowser {
|
|
|
2952
3405
|
_reportToWorld(world, {
|
|
2953
3406
|
type: Types.SET_VIEWPORT,
|
|
2954
3407
|
text: "set viewport size to " + width + "x" + hight,
|
|
3408
|
+
_text: "Set the viewport size to " + width + "x" + hight,
|
|
2955
3409
|
screenshotId,
|
|
2956
3410
|
result: error
|
|
2957
3411
|
? {
|
|
@@ -3022,7 +3476,39 @@ class StableBrowser {
|
|
|
3022
3476
|
console.log("#-#");
|
|
3023
3477
|
}
|
|
3024
3478
|
}
|
|
3479
|
+
async beforeScenario(world, scenario) {
|
|
3480
|
+
this.beforeScenarioCalled = true;
|
|
3481
|
+
if (scenario && scenario.pickle && scenario.pickle.name) {
|
|
3482
|
+
this.scenarioName = scenario.pickle.name;
|
|
3483
|
+
}
|
|
3484
|
+
if (scenario && scenario.gherkinDocument && scenario.gherkinDocument.feature) {
|
|
3485
|
+
this.featureName = scenario.gherkinDocument.feature.name;
|
|
3486
|
+
}
|
|
3487
|
+
if (this.context) {
|
|
3488
|
+
this.context.examplesRow = extractStepExampleParameters(scenario);
|
|
3489
|
+
}
|
|
3490
|
+
if (this.tags === null && scenario && scenario.pickle && scenario.pickle.tags) {
|
|
3491
|
+
this.tags = scenario.pickle.tags.map((tag) => tag.name);
|
|
3492
|
+
// check if @global_test_data tag is present
|
|
3493
|
+
if (this.tags.includes("@global_test_data")) {
|
|
3494
|
+
this.saveTestDataAsGlobal({}, world);
|
|
3495
|
+
}
|
|
3496
|
+
}
|
|
3497
|
+
// update test data based on feature/scenario
|
|
3498
|
+
let envName = null;
|
|
3499
|
+
if (this.context && this.context.environment) {
|
|
3500
|
+
envName = this.context.environment.name;
|
|
3501
|
+
}
|
|
3502
|
+
if (!process.env.TEMP_RUN) {
|
|
3503
|
+
await getTestData(envName, world, undefined, this.featureName, this.scenarioName);
|
|
3504
|
+
}
|
|
3505
|
+
await loadBrunoParams(this.context, this.context.environment.name);
|
|
3506
|
+
}
|
|
3507
|
+
async afterScenario(world, scenario) { }
|
|
3025
3508
|
async beforeStep(world, step) {
|
|
3509
|
+
if (!this.beforeScenarioCalled) {
|
|
3510
|
+
this.beforeScenario(world, step);
|
|
3511
|
+
}
|
|
3026
3512
|
if (this.stepIndex === undefined) {
|
|
3027
3513
|
this.stepIndex = 0;
|
|
3028
3514
|
}
|
|
@@ -3039,21 +3525,53 @@ class StableBrowser {
|
|
|
3039
3525
|
else {
|
|
3040
3526
|
this.stepName = "step " + this.stepIndex;
|
|
3041
3527
|
}
|
|
3042
|
-
if (this.context) {
|
|
3043
|
-
this.context.examplesRow = extractStepExampleParameters(step);
|
|
3044
|
-
}
|
|
3045
3528
|
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
3046
3529
|
if (this.context.browserObject.context) {
|
|
3047
3530
|
await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
|
|
3048
3531
|
}
|
|
3049
3532
|
}
|
|
3050
|
-
if (this.
|
|
3051
|
-
this.
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3533
|
+
if (this.initSnapshotTaken === false) {
|
|
3534
|
+
this.initSnapshotTaken = true;
|
|
3535
|
+
if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
|
|
3536
|
+
const snapshot = await this.getAriaSnapshot();
|
|
3537
|
+
if (snapshot) {
|
|
3538
|
+
await world.attach(JSON.stringify(snapshot), "application/json+snapshot-before");
|
|
3539
|
+
}
|
|
3540
|
+
}
|
|
3541
|
+
}
|
|
3542
|
+
}
|
|
3543
|
+
async getAriaSnapshot() {
|
|
3544
|
+
try {
|
|
3545
|
+
// find the page url
|
|
3546
|
+
const url = await this.page.url();
|
|
3547
|
+
// extract the path from the url
|
|
3548
|
+
const path = new URL(url).pathname;
|
|
3549
|
+
// get the page title
|
|
3550
|
+
const title = await this.page.title();
|
|
3551
|
+
// go over other frams
|
|
3552
|
+
const frames = this.page.frames();
|
|
3553
|
+
const snapshots = [];
|
|
3554
|
+
const content = [`- path: ${path}`, `- title: ${title}`];
|
|
3555
|
+
const timeout = this.configuration.ariaSnapshotTimeout ? this.configuration.ariaSnapshotTimeout : 3000;
|
|
3556
|
+
for (let i = 0; i < frames.length; i++) {
|
|
3557
|
+
const frame = frames[i];
|
|
3558
|
+
try {
|
|
3559
|
+
// Ensure frame is attached and has body
|
|
3560
|
+
const body = frame.locator("body");
|
|
3561
|
+
await body.waitFor({ timeout }); // wait explicitly
|
|
3562
|
+
const snapshot = await body.ariaSnapshot({ timeout });
|
|
3563
|
+
content.push(`- frame: ${i}`);
|
|
3564
|
+
content.push(snapshot);
|
|
3565
|
+
}
|
|
3566
|
+
catch (innerErr) { }
|
|
3055
3567
|
}
|
|
3568
|
+
return content.join("\n");
|
|
3056
3569
|
}
|
|
3570
|
+
catch (e) {
|
|
3571
|
+
console.log("Error in getAriaSnapshot");
|
|
3572
|
+
//console.debug(e);
|
|
3573
|
+
}
|
|
3574
|
+
return null;
|
|
3057
3575
|
}
|
|
3058
3576
|
async afterStep(world, step) {
|
|
3059
3577
|
this.stepName = null;
|
|
@@ -3062,11 +3580,25 @@ class StableBrowser {
|
|
|
3062
3580
|
await this.context.browserObject.context.tracing.stopChunk({
|
|
3063
3581
|
path: path.join(this.context.browserObject.traceFolder, `trace-${this.stepIndex}.zip`),
|
|
3064
3582
|
});
|
|
3583
|
+
if (world && world.attach) {
|
|
3584
|
+
await world.attach(JSON.stringify({
|
|
3585
|
+
type: "trace",
|
|
3586
|
+
traceFilePath: `trace-${this.stepIndex}.zip`,
|
|
3587
|
+
}), "application/json+trace");
|
|
3588
|
+
}
|
|
3589
|
+
// console.log("trace file created", `trace-${this.stepIndex}.zip`);
|
|
3065
3590
|
}
|
|
3066
3591
|
}
|
|
3067
3592
|
if (this.context) {
|
|
3068
3593
|
this.context.examplesRow = null;
|
|
3069
3594
|
}
|
|
3595
|
+
if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
|
|
3596
|
+
const snapshot = await this.getAriaSnapshot();
|
|
3597
|
+
if (snapshot) {
|
|
3598
|
+
const obj = {};
|
|
3599
|
+
await world.attach(JSON.stringify(snapshot), "application/json+snapshot-after");
|
|
3600
|
+
}
|
|
3601
|
+
}
|
|
3070
3602
|
}
|
|
3071
3603
|
}
|
|
3072
3604
|
function createTimedPromise(promise, label) {
|