automation_model 1.0.610-dev → 1.0.610-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 +76 -11
- package/lib/auto_page.js.map +1 -1
- package/lib/browser_manager.js +53 -16
- 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/error-messages.js +6 -0
- package/lib/error-messages.js.map +1 -1
- package/lib/file_checker.d.ts +1 -0
- package/lib/file_checker.js +61 -0
- package/lib/file_checker.js.map +1 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.js +2 -0
- package/lib/index.js.map +1 -1
- package/lib/init_browser.d.ts +3 -2
- package/lib/init_browser.js +48 -11
- 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 +696 -198
- 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 +3 -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,18 +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
|
-
import { getContext } from "./init_browser.js";
|
|
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";
|
|
24
|
+
import axios from "axios";
|
|
25
|
+
import { _findCellArea, findElementsInArea } from "./table_helper.js";
|
|
26
|
+
import { loadBrunoParams } from "./bruno.js";
|
|
23
27
|
export const Types = {
|
|
24
28
|
CLICK: "click_element",
|
|
29
|
+
WAIT_ELEMENT: "wait_element",
|
|
25
30
|
NAVIGATE: "navigate",
|
|
26
31
|
FILL: "fill_element",
|
|
27
32
|
EXECUTE: "execute_page_method",
|
|
@@ -43,6 +48,7 @@ export const Types = {
|
|
|
43
48
|
UNCHECK: "uncheck_element",
|
|
44
49
|
EXTRACT: "extract_attribute",
|
|
45
50
|
CLOSE_PAGE: "close_page",
|
|
51
|
+
TABLE_OPERATION: "table_operation",
|
|
46
52
|
SET_DATE_TIME: "set_date_time",
|
|
47
53
|
SET_VIEWPORT: "set_viewport",
|
|
48
54
|
VERIFY_VISUAL: "verify_visual",
|
|
@@ -51,6 +57,9 @@ export const Types = {
|
|
|
51
57
|
WAIT_FOR_TEXT_TO_DISAPPEAR: "wait_for_text_to_disappear",
|
|
52
58
|
VERIFY_ATTRIBUTE: "verify_element_attribute",
|
|
53
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",
|
|
54
63
|
};
|
|
55
64
|
export const apps = {};
|
|
56
65
|
const formatElementName = (elementName) => {
|
|
@@ -69,6 +78,7 @@ class StableBrowser {
|
|
|
69
78
|
appName = "main";
|
|
70
79
|
tags = null;
|
|
71
80
|
isRecording = false;
|
|
81
|
+
initSnapshotTaken = false;
|
|
72
82
|
constructor(browser, page, logger = null, context = null, world = null) {
|
|
73
83
|
this.browser = browser;
|
|
74
84
|
this.page = page;
|
|
@@ -175,6 +185,30 @@ class StableBrowser {
|
|
|
175
185
|
await this.waitForPageLoad();
|
|
176
186
|
}
|
|
177
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
|
+
}
|
|
178
212
|
registerConsoleLogListener(page, context) {
|
|
179
213
|
if (!this.context.webLogger) {
|
|
180
214
|
this.context.webLogger = [];
|
|
@@ -270,7 +304,7 @@ class StableBrowser {
|
|
|
270
304
|
_commandError(state, error, this);
|
|
271
305
|
}
|
|
272
306
|
finally {
|
|
273
|
-
_commandFinally(state, this);
|
|
307
|
+
await _commandFinally(state, this);
|
|
274
308
|
}
|
|
275
309
|
}
|
|
276
310
|
async _getLocator(locator, scope, _params) {
|
|
@@ -351,7 +385,7 @@ class StableBrowser {
|
|
|
351
385
|
return resultCss;
|
|
352
386
|
}
|
|
353
387
|
async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, ignoreCase = true, _params) {
|
|
354
|
-
const query = _convertToRegexQuery(text1, regex1, !partial1, ignoreCase)
|
|
388
|
+
const query = `${_convertToRegexQuery(text1, regex1, !partial1, ignoreCase)}`;
|
|
355
389
|
const locator = scope.locator(query);
|
|
356
390
|
const count = await locator.count();
|
|
357
391
|
if (!tag1) {
|
|
@@ -371,6 +405,12 @@ class StableBrowser {
|
|
|
371
405
|
if (!el.setAttribute) {
|
|
372
406
|
el = el.parentElement;
|
|
373
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
|
+
// }
|
|
374
414
|
el.setAttribute("data-blinq-id-" + randomToken, "");
|
|
375
415
|
return true;
|
|
376
416
|
}, [tag1, randomToken]))) {
|
|
@@ -557,9 +597,24 @@ class StableBrowser {
|
|
|
557
597
|
element.evaluate((el, randomToken) => {
|
|
558
598
|
el.setAttribute("data-blinq-id-" + randomToken, "");
|
|
559
599
|
}, randomToken);
|
|
600
|
+
// if (element._frame) {
|
|
601
|
+
// return element;
|
|
602
|
+
// }
|
|
560
603
|
const scope = element._frame ?? element.page();
|
|
561
|
-
|
|
562
|
-
|
|
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);
|
|
563
618
|
}
|
|
564
619
|
}
|
|
565
620
|
throw new Error("unable to locate element " + JSON.stringify(selectors));
|
|
@@ -711,14 +766,9 @@ class StableBrowser {
|
|
|
711
766
|
// info.log += "scanning locators in priority 2" + "\n";
|
|
712
767
|
result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
713
768
|
}
|
|
714
|
-
if (result.foundElements.length === 0 && onlyPriority3) {
|
|
769
|
+
if (result.foundElements.length === 0 && (onlyPriority3 || !highPriorityOnly)) {
|
|
715
770
|
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
716
771
|
}
|
|
717
|
-
else {
|
|
718
|
-
if (result.foundElements.length === 0 && !highPriorityOnly) {
|
|
719
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
720
|
-
}
|
|
721
|
-
}
|
|
722
772
|
let foundElements = result.foundElements;
|
|
723
773
|
if (foundElements.length === 1 && foundElements[0].unique) {
|
|
724
774
|
info.box = foundElements[0].box;
|
|
@@ -773,6 +823,11 @@ class StableBrowser {
|
|
|
773
823
|
visibleOnly = false;
|
|
774
824
|
}
|
|
775
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
|
+
}
|
|
776
831
|
}
|
|
777
832
|
this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
|
|
778
833
|
// if (info.locatorLog) {
|
|
@@ -819,9 +874,40 @@ class StableBrowser {
|
|
|
819
874
|
result.locatorIndex = i;
|
|
820
875
|
}
|
|
821
876
|
if (foundLocators.length > 1) {
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
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
|
+
}
|
|
825
911
|
}
|
|
826
912
|
}
|
|
827
913
|
}
|
|
@@ -869,7 +955,7 @@ class StableBrowser {
|
|
|
869
955
|
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
870
956
|
}
|
|
871
957
|
finally {
|
|
872
|
-
_commandFinally(state, this);
|
|
958
|
+
await _commandFinally(state, this);
|
|
873
959
|
}
|
|
874
960
|
}
|
|
875
961
|
}
|
|
@@ -918,7 +1004,7 @@ class StableBrowser {
|
|
|
918
1004
|
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
919
1005
|
}
|
|
920
1006
|
finally {
|
|
921
|
-
_commandFinally(state, this);
|
|
1007
|
+
await _commandFinally(state, this);
|
|
922
1008
|
}
|
|
923
1009
|
}
|
|
924
1010
|
}
|
|
@@ -932,25 +1018,14 @@ class StableBrowser {
|
|
|
932
1018
|
options,
|
|
933
1019
|
world,
|
|
934
1020
|
text: "Click element",
|
|
1021
|
+
_text: "Click on " + selectors.element_name,
|
|
935
1022
|
type: Types.CLICK,
|
|
936
1023
|
operation: "click",
|
|
937
1024
|
log: "***** click on " + selectors.element_name + " *****\n",
|
|
938
1025
|
};
|
|
939
1026
|
try {
|
|
940
1027
|
await _preCommand(state, this);
|
|
941
|
-
|
|
942
|
-
// state.selectors.locators[0].text = state.options.context;
|
|
943
|
-
// }
|
|
944
|
-
try {
|
|
945
|
-
await state.element.click();
|
|
946
|
-
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
947
|
-
}
|
|
948
|
-
catch (e) {
|
|
949
|
-
// await this.closeUnexpectedPopups();
|
|
950
|
-
state.element = await this._locate(selectors, state.info, _params);
|
|
951
|
-
await state.element.dispatchEvent("click");
|
|
952
|
-
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
953
|
-
}
|
|
1028
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
954
1029
|
await this.waitForPageLoad();
|
|
955
1030
|
return state.info;
|
|
956
1031
|
}
|
|
@@ -958,9 +1033,41 @@ class StableBrowser {
|
|
|
958
1033
|
await _commandError(state, e, this);
|
|
959
1034
|
}
|
|
960
1035
|
finally {
|
|
961
|
-
_commandFinally(state, this);
|
|
1036
|
+
await _commandFinally(state, this);
|
|
962
1037
|
}
|
|
963
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
|
+
}
|
|
964
1071
|
async setCheck(selectors, checked = true, _params, options = {}, world = null) {
|
|
965
1072
|
const state = {
|
|
966
1073
|
selectors,
|
|
@@ -969,6 +1076,7 @@ class StableBrowser {
|
|
|
969
1076
|
world,
|
|
970
1077
|
type: checked ? Types.CHECK : Types.UNCHECK,
|
|
971
1078
|
text: checked ? `Check element` : `Uncheck element`,
|
|
1079
|
+
_text: checked ? `Check ${selectors.element_name}` : `Uncheck ${selectors.element_name}`,
|
|
972
1080
|
operation: "setCheck",
|
|
973
1081
|
log: "***** check " + selectors.element_name + " *****\n",
|
|
974
1082
|
};
|
|
@@ -980,7 +1088,7 @@ class StableBrowser {
|
|
|
980
1088
|
try {
|
|
981
1089
|
// if (world && world.screenshot && !world.screenshotPath) {
|
|
982
1090
|
// console.log(`Highlighting while running from recorder`);
|
|
983
|
-
await this._highlightElements(element);
|
|
1091
|
+
await this._highlightElements(state.element);
|
|
984
1092
|
await state.element.setChecked(checked);
|
|
985
1093
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
986
1094
|
// await this._unHighlightElements(element);
|
|
@@ -1007,7 +1115,7 @@ class StableBrowser {
|
|
|
1007
1115
|
await _commandError(state, e, this);
|
|
1008
1116
|
}
|
|
1009
1117
|
finally {
|
|
1010
|
-
_commandFinally(state, this);
|
|
1118
|
+
await _commandFinally(state, this);
|
|
1011
1119
|
}
|
|
1012
1120
|
}
|
|
1013
1121
|
async hover(selectors, _params, options = {}, world = null) {
|
|
@@ -1018,24 +1126,13 @@ class StableBrowser {
|
|
|
1018
1126
|
world,
|
|
1019
1127
|
type: Types.HOVER,
|
|
1020
1128
|
text: `Hover element`,
|
|
1129
|
+
_text: `Hover on ${selectors.element_name}`,
|
|
1021
1130
|
operation: "hover",
|
|
1022
1131
|
log: "***** hover " + selectors.element_name + " *****\n",
|
|
1023
1132
|
};
|
|
1024
1133
|
try {
|
|
1025
1134
|
await _preCommand(state, this);
|
|
1026
|
-
|
|
1027
|
-
await state.element.hover();
|
|
1028
|
-
// await _screenshot(state, this);
|
|
1029
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1030
|
-
}
|
|
1031
|
-
catch (e) {
|
|
1032
|
-
//await this.closeUnexpectedPopups();
|
|
1033
|
-
state.info.log += "hover failed, will try again" + "\n";
|
|
1034
|
-
state.element = await this._locate(selectors, state.info, _params);
|
|
1035
|
-
await state.element.hover({ timeout: 10000 });
|
|
1036
|
-
// await _screenshot(state, this);
|
|
1037
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1038
|
-
}
|
|
1135
|
+
await performAction("hover", state.element, options, this, state, _params);
|
|
1039
1136
|
await _screenshot(state, this);
|
|
1040
1137
|
await this.waitForPageLoad();
|
|
1041
1138
|
return state.info;
|
|
@@ -1044,7 +1141,7 @@ class StableBrowser {
|
|
|
1044
1141
|
await _commandError(state, e, this);
|
|
1045
1142
|
}
|
|
1046
1143
|
finally {
|
|
1047
|
-
_commandFinally(state, this);
|
|
1144
|
+
await _commandFinally(state, this);
|
|
1048
1145
|
}
|
|
1049
1146
|
}
|
|
1050
1147
|
async selectOption(selectors, values, _params = null, options = {}, world = null) {
|
|
@@ -1059,6 +1156,7 @@ class StableBrowser {
|
|
|
1059
1156
|
value: values.toString(),
|
|
1060
1157
|
type: Types.SELECT,
|
|
1061
1158
|
text: `Select option: ${values}`,
|
|
1159
|
+
_text: `Select option: ${values} on ${selectors.element_name}`,
|
|
1062
1160
|
operation: "selectOption",
|
|
1063
1161
|
log: "***** select option " + selectors.element_name + " *****\n",
|
|
1064
1162
|
};
|
|
@@ -1079,7 +1177,7 @@ class StableBrowser {
|
|
|
1079
1177
|
await _commandError(state, e, this);
|
|
1080
1178
|
}
|
|
1081
1179
|
finally {
|
|
1082
|
-
_commandFinally(state, this);
|
|
1180
|
+
await _commandFinally(state, this);
|
|
1083
1181
|
}
|
|
1084
1182
|
}
|
|
1085
1183
|
async type(_value, _params = null, options = {}, world = null) {
|
|
@@ -1093,6 +1191,7 @@ class StableBrowser {
|
|
|
1093
1191
|
highlight: false,
|
|
1094
1192
|
type: Types.TYPE_PRESS,
|
|
1095
1193
|
text: `Type value: ${_value}`,
|
|
1194
|
+
_text: `Type value: ${_value}`,
|
|
1096
1195
|
operation: "type",
|
|
1097
1196
|
log: "",
|
|
1098
1197
|
};
|
|
@@ -1124,7 +1223,7 @@ class StableBrowser {
|
|
|
1124
1223
|
await _commandError(state, e, this);
|
|
1125
1224
|
}
|
|
1126
1225
|
finally {
|
|
1127
|
-
_commandFinally(state, this);
|
|
1226
|
+
await _commandFinally(state, this);
|
|
1128
1227
|
}
|
|
1129
1228
|
}
|
|
1130
1229
|
async setInputValue(selectors, value, _params = null, options = {}, world = null) {
|
|
@@ -1160,7 +1259,7 @@ class StableBrowser {
|
|
|
1160
1259
|
await _commandError(state, e, this);
|
|
1161
1260
|
}
|
|
1162
1261
|
finally {
|
|
1163
|
-
_commandFinally(state, this);
|
|
1262
|
+
await _commandFinally(state, this);
|
|
1164
1263
|
}
|
|
1165
1264
|
}
|
|
1166
1265
|
async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
|
|
@@ -1172,6 +1271,7 @@ class StableBrowser {
|
|
|
1172
1271
|
world,
|
|
1173
1272
|
type: Types.SET_DATE_TIME,
|
|
1174
1273
|
text: `Set date time value: ${value}`,
|
|
1274
|
+
_text: `Set date time value: ${value} on ${selectors.element_name}`,
|
|
1175
1275
|
operation: "setDateTime",
|
|
1176
1276
|
log: "***** set date time value " + selectors.element_name + " *****\n",
|
|
1177
1277
|
throwError: false,
|
|
@@ -1179,7 +1279,7 @@ class StableBrowser {
|
|
|
1179
1279
|
try {
|
|
1180
1280
|
await _preCommand(state, this);
|
|
1181
1281
|
try {
|
|
1182
|
-
await state.element
|
|
1282
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
1183
1283
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1184
1284
|
if (format) {
|
|
1185
1285
|
state.value = dayjs(state.value).format(format);
|
|
@@ -1228,7 +1328,7 @@ class StableBrowser {
|
|
|
1228
1328
|
await _commandError(state, e, this);
|
|
1229
1329
|
}
|
|
1230
1330
|
finally {
|
|
1231
|
-
_commandFinally(state, this);
|
|
1331
|
+
await _commandFinally(state, this);
|
|
1232
1332
|
}
|
|
1233
1333
|
}
|
|
1234
1334
|
async clickType(selectors, _value, enter = false, _params = null, options = {}, world = null) {
|
|
@@ -1243,9 +1343,13 @@ class StableBrowser {
|
|
|
1243
1343
|
world,
|
|
1244
1344
|
type: Types.FILL,
|
|
1245
1345
|
text: `Click type input with value: ${_value}`,
|
|
1346
|
+
_text: "Fill " + selectors.element_name + " with value " + maskValue(_value),
|
|
1246
1347
|
operation: "clickType",
|
|
1247
1348
|
log: "***** clickType on " + selectors.element_name + " with value " + maskValue(_value) + "*****\n",
|
|
1248
1349
|
};
|
|
1350
|
+
if (!options) {
|
|
1351
|
+
options = {};
|
|
1352
|
+
}
|
|
1249
1353
|
if (newValue !== _value) {
|
|
1250
1354
|
//this.logger.info(_value + "=" + newValue);
|
|
1251
1355
|
_value = newValue;
|
|
@@ -1253,7 +1357,7 @@ class StableBrowser {
|
|
|
1253
1357
|
try {
|
|
1254
1358
|
await _preCommand(state, this);
|
|
1255
1359
|
state.info.value = _value;
|
|
1256
|
-
if (
|
|
1360
|
+
if (!options.press) {
|
|
1257
1361
|
try {
|
|
1258
1362
|
let currentValue = await state.element.inputValue();
|
|
1259
1363
|
if (currentValue) {
|
|
@@ -1264,13 +1368,9 @@ class StableBrowser {
|
|
|
1264
1368
|
this.logger.info("unable to clear input value");
|
|
1265
1369
|
}
|
|
1266
1370
|
}
|
|
1267
|
-
if (options
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
}
|
|
1271
|
-
catch (e) {
|
|
1272
|
-
await state.element.dispatchEvent("click");
|
|
1273
|
-
}
|
|
1371
|
+
if (options.press) {
|
|
1372
|
+
options.timeout = 5000;
|
|
1373
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
1274
1374
|
}
|
|
1275
1375
|
else {
|
|
1276
1376
|
try {
|
|
@@ -1328,7 +1428,7 @@ class StableBrowser {
|
|
|
1328
1428
|
await _commandError(state, e, this);
|
|
1329
1429
|
}
|
|
1330
1430
|
finally {
|
|
1331
|
-
_commandFinally(state, this);
|
|
1431
|
+
await _commandFinally(state, this);
|
|
1332
1432
|
}
|
|
1333
1433
|
}
|
|
1334
1434
|
async fill(selectors, value, enter = false, _params = null, options = {}, world = null) {
|
|
@@ -1358,13 +1458,49 @@ class StableBrowser {
|
|
|
1358
1458
|
await _commandError(state, e, this);
|
|
1359
1459
|
}
|
|
1360
1460
|
finally {
|
|
1361
|
-
_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);
|
|
1362
1497
|
}
|
|
1363
1498
|
}
|
|
1364
1499
|
async getText(selectors, _params = null, options = {}, info = {}, world = null) {
|
|
1365
1500
|
return await this._getText(selectors, 0, _params, options, info, world);
|
|
1366
1501
|
}
|
|
1367
1502
|
async _getText(selectors, climb, _params = null, options = {}, info = {}, world = null) {
|
|
1503
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1368
1504
|
_validateSelectors(selectors);
|
|
1369
1505
|
let screenshotId = null;
|
|
1370
1506
|
let screenshotPath = null;
|
|
@@ -1374,7 +1510,7 @@ class StableBrowser {
|
|
|
1374
1510
|
}
|
|
1375
1511
|
info.operation = "getText";
|
|
1376
1512
|
info.selectors = selectors;
|
|
1377
|
-
let element = await this._locate(selectors, info, _params);
|
|
1513
|
+
let element = await this._locate(selectors, info, _params, timeout);
|
|
1378
1514
|
if (climb > 0) {
|
|
1379
1515
|
const climbArray = [];
|
|
1380
1516
|
for (let i = 0; i < climb; i++) {
|
|
@@ -1441,6 +1577,7 @@ class StableBrowser {
|
|
|
1441
1577
|
highlight: false,
|
|
1442
1578
|
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1443
1579
|
text: `Verify element contains pattern: ${pattern}`,
|
|
1580
|
+
_text: "Verify element " + selectors.element_name + " contains pattern " + pattern,
|
|
1444
1581
|
operation: "containsPattern",
|
|
1445
1582
|
log: "***** verify element " + selectors.element_name + " contains pattern " + pattern + " *****\n",
|
|
1446
1583
|
};
|
|
@@ -1472,10 +1609,12 @@ class StableBrowser {
|
|
|
1472
1609
|
await _commandError(state, e, this);
|
|
1473
1610
|
}
|
|
1474
1611
|
finally {
|
|
1475
|
-
_commandFinally(state, this);
|
|
1612
|
+
await _commandFinally(state, this);
|
|
1476
1613
|
}
|
|
1477
1614
|
}
|
|
1478
1615
|
async containsText(selectors, text, climb, _params = null, options = {}, world = null) {
|
|
1616
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1617
|
+
const startTime = Date.now();
|
|
1479
1618
|
const state = {
|
|
1480
1619
|
selectors,
|
|
1481
1620
|
_params,
|
|
@@ -1502,44 +1641,52 @@ class StableBrowser {
|
|
|
1502
1641
|
}
|
|
1503
1642
|
let foundObj = null;
|
|
1504
1643
|
try {
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
const dateAlternatives = findDateAlternatives(text);
|
|
1512
|
-
const numberAlternatives = findNumberAlternatives(text);
|
|
1513
|
-
if (dateAlternatives.date) {
|
|
1514
|
-
for (let i = 0; i < dateAlternatives.dates.length; i++) {
|
|
1515
|
-
if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
|
|
1516
|
-
foundObj?.value?.includes(dateAlternatives.dates[i])) {
|
|
1517
|
-
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);
|
|
1518
1650
|
}
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
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)) {
|
|
1526
1671
|
return state.info;
|
|
1527
1672
|
}
|
|
1528
1673
|
}
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
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
|
|
1535
1679
|
}
|
|
1536
|
-
|
|
1680
|
+
state.info.foundText = foundObj?.text;
|
|
1681
|
+
state.info.value = foundObj?.value;
|
|
1682
|
+
throw new Error("element doesn't contain text " + text);
|
|
1537
1683
|
}
|
|
1538
1684
|
catch (e) {
|
|
1539
1685
|
await _commandError(state, e, this);
|
|
1686
|
+
throw e;
|
|
1540
1687
|
}
|
|
1541
1688
|
finally {
|
|
1542
|
-
_commandFinally(state, this);
|
|
1689
|
+
await _commandFinally(state, this);
|
|
1543
1690
|
}
|
|
1544
1691
|
}
|
|
1545
1692
|
async waitForUserInput(message, world = null) {
|
|
@@ -1577,6 +1724,15 @@ class StableBrowser {
|
|
|
1577
1724
|
// save the data to the file
|
|
1578
1725
|
fs.writeFileSync(dataFile, JSON.stringify(data, null, 2));
|
|
1579
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
|
+
}
|
|
1580
1736
|
_getDataFilePath(fileName) {
|
|
1581
1737
|
let dataFile = path.join(this.project_path, "data", fileName);
|
|
1582
1738
|
if (fs.existsSync(dataFile)) {
|
|
@@ -1806,6 +1962,7 @@ class StableBrowser {
|
|
|
1806
1962
|
else {
|
|
1807
1963
|
fs.writeFileSync(screenshotPath, screenshotBuffer);
|
|
1808
1964
|
}
|
|
1965
|
+
return screenshotBuffer;
|
|
1809
1966
|
}
|
|
1810
1967
|
async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
|
|
1811
1968
|
const state = {
|
|
@@ -1828,7 +1985,7 @@ class StableBrowser {
|
|
|
1828
1985
|
await _commandError(state, e, this);
|
|
1829
1986
|
}
|
|
1830
1987
|
finally {
|
|
1831
|
-
_commandFinally(state, this);
|
|
1988
|
+
await _commandFinally(state, this);
|
|
1832
1989
|
}
|
|
1833
1990
|
}
|
|
1834
1991
|
async extractAttribute(selectors, attribute, variable, _params = null, options = {}, world = null) {
|
|
@@ -1841,6 +1998,7 @@ class StableBrowser {
|
|
|
1841
1998
|
world,
|
|
1842
1999
|
type: Types.EXTRACT,
|
|
1843
2000
|
text: `Extract attribute from element`,
|
|
2001
|
+
_text: `Extract attribute ${attribute} from ${selectors.element_name}`,
|
|
1844
2002
|
operation: "extractAttribute",
|
|
1845
2003
|
log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1846
2004
|
allowDisabled: true,
|
|
@@ -1858,10 +2016,31 @@ class StableBrowser {
|
|
|
1858
2016
|
case "value":
|
|
1859
2017
|
state.value = await state.element.inputValue();
|
|
1860
2018
|
break;
|
|
2019
|
+
case "text":
|
|
2020
|
+
state.value = await state.element.textContent();
|
|
2021
|
+
break;
|
|
1861
2022
|
default:
|
|
1862
2023
|
state.value = await state.element.getAttribute(attribute);
|
|
1863
2024
|
break;
|
|
1864
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
|
+
}
|
|
1865
2044
|
state.info.value = state.value;
|
|
1866
2045
|
this.setTestData({ [variable]: state.value }, world);
|
|
1867
2046
|
this.logger.info("set test data: " + variable + "=" + state.value);
|
|
@@ -1872,7 +2051,7 @@ class StableBrowser {
|
|
|
1872
2051
|
await _commandError(state, e, this);
|
|
1873
2052
|
}
|
|
1874
2053
|
finally {
|
|
1875
|
-
_commandFinally(state, this);
|
|
2054
|
+
await _commandFinally(state, this);
|
|
1876
2055
|
}
|
|
1877
2056
|
}
|
|
1878
2057
|
async verifyAttribute(selectors, attribute, value, _params = null, options = {}, world = null) {
|
|
@@ -1887,18 +2066,25 @@ class StableBrowser {
|
|
|
1887
2066
|
highlight: true,
|
|
1888
2067
|
screenshot: true,
|
|
1889
2068
|
text: `Verify element attribute`,
|
|
2069
|
+
_text: `Verify attribute ${attribute} from ${selectors.element_name} is ${value}`,
|
|
1890
2070
|
operation: "verifyAttribute",
|
|
1891
2071
|
log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1892
2072
|
allowDisabled: true,
|
|
1893
2073
|
};
|
|
1894
2074
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1895
2075
|
let val;
|
|
2076
|
+
let expectedValue;
|
|
1896
2077
|
try {
|
|
1897
2078
|
await _preCommand(state, this);
|
|
2079
|
+
expectedValue = await replaceWithLocalTestData(state.value, world);
|
|
2080
|
+
state.info.expectedValue = expectedValue;
|
|
1898
2081
|
switch (attribute) {
|
|
1899
2082
|
case "innerText":
|
|
1900
2083
|
val = String(await state.element.innerText());
|
|
1901
2084
|
break;
|
|
2085
|
+
case "text":
|
|
2086
|
+
val = String(await state.element.textContent());
|
|
2087
|
+
break;
|
|
1902
2088
|
case "value":
|
|
1903
2089
|
val = String(await state.element.inputValue());
|
|
1904
2090
|
break;
|
|
@@ -1916,27 +2102,29 @@ class StableBrowser {
|
|
|
1916
2102
|
val = String(await state.element.getAttribute(attribute));
|
|
1917
2103
|
break;
|
|
1918
2104
|
}
|
|
2105
|
+
state.info.value = val;
|
|
1919
2106
|
let regex;
|
|
1920
|
-
if (
|
|
1921
|
-
const patternBody =
|
|
2107
|
+
if (expectedValue.startsWith("/") && expectedValue.endsWith("/")) {
|
|
2108
|
+
const patternBody = expectedValue.slice(1, -1);
|
|
1922
2109
|
regex = new RegExp(patternBody, "g");
|
|
1923
2110
|
}
|
|
1924
2111
|
else {
|
|
1925
|
-
const escapedPattern =
|
|
2112
|
+
const escapedPattern = expectedValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1926
2113
|
regex = new RegExp(escapedPattern, "g");
|
|
1927
2114
|
}
|
|
1928
2115
|
if (!val.match(regex)) {
|
|
1929
|
-
|
|
2116
|
+
let errorMessage = `The ${attribute} attribute has a value of "${val}", but the expected value is "${expectedValue}"`;
|
|
2117
|
+
state.info.failCause.assertionFailed = true;
|
|
2118
|
+
state.info.failCause.lastError = errorMessage;
|
|
2119
|
+
throw new Error(errorMessage);
|
|
1930
2120
|
}
|
|
1931
|
-
state.info.expectedValue = value;
|
|
1932
|
-
state.info.value = val;
|
|
1933
2121
|
return state.info;
|
|
1934
2122
|
}
|
|
1935
2123
|
catch (e) {
|
|
1936
2124
|
await _commandError(state, e, this);
|
|
1937
2125
|
}
|
|
1938
2126
|
finally {
|
|
1939
|
-
_commandFinally(state, this);
|
|
2127
|
+
await _commandFinally(state, this);
|
|
1940
2128
|
}
|
|
1941
2129
|
}
|
|
1942
2130
|
async extractEmailData(emailAddress, options, world) {
|
|
@@ -2188,6 +2376,7 @@ class StableBrowser {
|
|
|
2188
2376
|
_reportToWorld(world, {
|
|
2189
2377
|
type: Types.VERIFY_PAGE_PATH,
|
|
2190
2378
|
text: "Verify page path",
|
|
2379
|
+
_text: "Verify the page path contains " + pathPart,
|
|
2191
2380
|
screenshotId,
|
|
2192
2381
|
result: error
|
|
2193
2382
|
? {
|
|
@@ -2205,27 +2394,89 @@ class StableBrowser {
|
|
|
2205
2394
|
});
|
|
2206
2395
|
}
|
|
2207
2396
|
}
|
|
2208
|
-
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) {
|
|
2209
2460
|
const frames = this.page.frames();
|
|
2210
2461
|
let results = [];
|
|
2211
|
-
let ignoreCase = false;
|
|
2462
|
+
// let ignoreCase = false;
|
|
2212
2463
|
for (let i = 0; i < frames.length; i++) {
|
|
2213
2464
|
if (dateAlternatives.date) {
|
|
2214
2465
|
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2215
|
-
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, {});
|
|
2216
2467
|
result.frame = frames[i];
|
|
2217
2468
|
results.push(result);
|
|
2218
2469
|
}
|
|
2219
2470
|
}
|
|
2220
2471
|
else if (numberAlternatives.number) {
|
|
2221
2472
|
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2222
|
-
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, {});
|
|
2223
2474
|
result.frame = frames[i];
|
|
2224
2475
|
results.push(result);
|
|
2225
2476
|
}
|
|
2226
2477
|
}
|
|
2227
2478
|
else {
|
|
2228
|
-
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, {});
|
|
2229
2480
|
result.frame = frames[i];
|
|
2230
2481
|
results.push(result);
|
|
2231
2482
|
}
|
|
@@ -2244,11 +2495,15 @@ class StableBrowser {
|
|
|
2244
2495
|
scroll: false,
|
|
2245
2496
|
highlight: false,
|
|
2246
2497
|
type: Types.VERIFY_PAGE_CONTAINS_TEXT,
|
|
2247
|
-
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`,
|
|
2248
2500
|
operation: "verifyTextExistInPage",
|
|
2249
2501
|
log: "***** verify text " + text + " exists in page *****\n",
|
|
2250
2502
|
};
|
|
2251
|
-
|
|
2503
|
+
if (testForRegex(text)) {
|
|
2504
|
+
text = text.replace(/\\"/g, '"');
|
|
2505
|
+
}
|
|
2506
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2252
2507
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2253
2508
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2254
2509
|
if (newValue !== text) {
|
|
@@ -2261,7 +2516,15 @@ class StableBrowser {
|
|
|
2261
2516
|
await _preCommand(state, this);
|
|
2262
2517
|
state.info.text = text;
|
|
2263
2518
|
while (true) {
|
|
2264
|
-
|
|
2519
|
+
let resultWithElementsFound = {
|
|
2520
|
+
length: 0,
|
|
2521
|
+
};
|
|
2522
|
+
try {
|
|
2523
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2524
|
+
}
|
|
2525
|
+
catch (error) {
|
|
2526
|
+
// ignore
|
|
2527
|
+
}
|
|
2265
2528
|
if (resultWithElementsFound.length === 0) {
|
|
2266
2529
|
if (Date.now() - state.startTime > timeout) {
|
|
2267
2530
|
throw new Error(`Text ${text} not found in page`);
|
|
@@ -2269,35 +2532,40 @@ class StableBrowser {
|
|
|
2269
2532
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2270
2533
|
continue;
|
|
2271
2534
|
}
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2535
|
+
try {
|
|
2536
|
+
if (resultWithElementsFound[0].randomToken) {
|
|
2537
|
+
const frame = resultWithElementsFound[0].frame;
|
|
2538
|
+
const dataAttribute = `[data-blinq-id-${resultWithElementsFound[0].randomToken}]`;
|
|
2539
|
+
await this._highlightElements(frame, dataAttribute);
|
|
2540
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
2541
|
+
// console.log(`Highlighting for verify text is found while running from recorder`);
|
|
2542
|
+
// this._highlightElements(frame, dataAttribute).then(async () => {
|
|
2543
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2544
|
+
// this._unhighlightElements(frame, dataAttribute)
|
|
2545
|
+
// .then(async () => {
|
|
2546
|
+
// console.log(`Unhighlighted frame dataAttribute successfully`);
|
|
2547
|
+
// })
|
|
2548
|
+
// .catch(
|
|
2549
|
+
// (e) => {}
|
|
2550
|
+
// console.error(e)
|
|
2551
|
+
// );
|
|
2552
|
+
// });
|
|
2553
|
+
// }
|
|
2554
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2555
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2556
|
+
// await this._unhighlightElements(frame, dataAttribute);
|
|
2557
|
+
if (element) {
|
|
2558
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2559
|
+
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2560
|
+
// await _screenshot(state, this, element);
|
|
2561
|
+
}
|
|
2297
2562
|
}
|
|
2563
|
+
await _screenshot(state, this);
|
|
2564
|
+
return state.info;
|
|
2565
|
+
}
|
|
2566
|
+
catch (error) {
|
|
2567
|
+
console.error(error);
|
|
2298
2568
|
}
|
|
2299
|
-
await _screenshot(state, this);
|
|
2300
|
-
return state.info;
|
|
2301
2569
|
}
|
|
2302
2570
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2303
2571
|
}
|
|
@@ -2305,7 +2573,7 @@ class StableBrowser {
|
|
|
2305
2573
|
await _commandError(state, e, this);
|
|
2306
2574
|
}
|
|
2307
2575
|
finally {
|
|
2308
|
-
_commandFinally(state, this);
|
|
2576
|
+
await _commandFinally(state, this);
|
|
2309
2577
|
}
|
|
2310
2578
|
}
|
|
2311
2579
|
async waitForTextToDisappear(text, options = {}, world = null) {
|
|
@@ -2318,11 +2586,15 @@ class StableBrowser {
|
|
|
2318
2586
|
scroll: false,
|
|
2319
2587
|
highlight: false,
|
|
2320
2588
|
type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
|
|
2321
|
-
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`,
|
|
2322
2591
|
operation: "verifyTextNotExistInPage",
|
|
2323
2592
|
log: "***** verify text " + text + " does not exist in page *****\n",
|
|
2324
2593
|
};
|
|
2325
|
-
|
|
2594
|
+
if (testForRegex(text)) {
|
|
2595
|
+
text = text.replace(/\\"/g, '"');
|
|
2596
|
+
}
|
|
2597
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2326
2598
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2327
2599
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2328
2600
|
if (newValue !== text) {
|
|
@@ -2334,8 +2606,16 @@ class StableBrowser {
|
|
|
2334
2606
|
try {
|
|
2335
2607
|
await _preCommand(state, this);
|
|
2336
2608
|
state.info.text = text;
|
|
2609
|
+
let resultWithElementsFound = {
|
|
2610
|
+
length: null, // initial cannot be 0
|
|
2611
|
+
};
|
|
2337
2612
|
while (true) {
|
|
2338
|
-
|
|
2613
|
+
try {
|
|
2614
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2615
|
+
}
|
|
2616
|
+
catch (error) {
|
|
2617
|
+
// ignore
|
|
2618
|
+
}
|
|
2339
2619
|
if (resultWithElementsFound.length === 0) {
|
|
2340
2620
|
await _screenshot(state, this);
|
|
2341
2621
|
return state.info;
|
|
@@ -2350,7 +2630,7 @@ class StableBrowser {
|
|
|
2350
2630
|
await _commandError(state, e, this);
|
|
2351
2631
|
}
|
|
2352
2632
|
finally {
|
|
2353
|
-
_commandFinally(state, this);
|
|
2633
|
+
await _commandFinally(state, this);
|
|
2354
2634
|
}
|
|
2355
2635
|
}
|
|
2356
2636
|
async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
|
|
@@ -2365,10 +2645,11 @@ class StableBrowser {
|
|
|
2365
2645
|
highlight: false,
|
|
2366
2646
|
type: Types.VERIFY_TEXT_WITH_RELATION,
|
|
2367
2647
|
text: `Verify text with relation to another text`,
|
|
2648
|
+
_text: "Search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found",
|
|
2368
2649
|
operation: "verify_text_with_relation",
|
|
2369
2650
|
log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
|
|
2370
2651
|
};
|
|
2371
|
-
const timeout = this.
|
|
2652
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2372
2653
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2373
2654
|
let newValue = await this._replaceWithLocalData(textAnchor, world);
|
|
2374
2655
|
if (newValue !== textAnchor) {
|
|
@@ -2386,8 +2667,16 @@ class StableBrowser {
|
|
|
2386
2667
|
try {
|
|
2387
2668
|
await _preCommand(state, this);
|
|
2388
2669
|
state.info.text = textToVerify;
|
|
2670
|
+
let resultWithElementsFound = {
|
|
2671
|
+
length: 0,
|
|
2672
|
+
};
|
|
2389
2673
|
while (true) {
|
|
2390
|
-
|
|
2674
|
+
try {
|
|
2675
|
+
resultWithElementsFound = await this.findTextInAllFrames(findDateAlternatives(textAnchor), findNumberAlternatives(textAnchor), textAnchor, state, false);
|
|
2676
|
+
}
|
|
2677
|
+
catch (error) {
|
|
2678
|
+
// ignore
|
|
2679
|
+
}
|
|
2391
2680
|
if (resultWithElementsFound.length === 0) {
|
|
2392
2681
|
if (Date.now() - state.startTime > timeout) {
|
|
2393
2682
|
throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
|
|
@@ -2395,51 +2684,56 @@ class StableBrowser {
|
|
|
2395
2684
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2396
2685
|
continue;
|
|
2397
2686
|
}
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2687
|
+
try {
|
|
2688
|
+
for (let i = 0; i < resultWithElementsFound.length; i++) {
|
|
2689
|
+
foundAncore = true;
|
|
2690
|
+
const result = resultWithElementsFound[i];
|
|
2691
|
+
const token = result.randomToken;
|
|
2692
|
+
const frame = result.frame;
|
|
2693
|
+
let css = `[data-blinq-id-${token}]`;
|
|
2694
|
+
const climbArray1 = [];
|
|
2695
|
+
for (let i = 0; i < climb; i++) {
|
|
2696
|
+
climbArray1.push("..");
|
|
2697
|
+
}
|
|
2698
|
+
let climbXpath = "xpath=" + climbArray1.join("/");
|
|
2699
|
+
css = css + " >> " + climbXpath;
|
|
2700
|
+
const count = await frame.locator(css).count();
|
|
2701
|
+
for (let j = 0; j < count; j++) {
|
|
2702
|
+
const continer = await frame.locator(css).nth(j);
|
|
2703
|
+
const result = await this._locateElementByText(continer, textToVerify, "*:not(script, style, head)", false, true, true, {});
|
|
2704
|
+
if (result.elementCount > 0) {
|
|
2705
|
+
const dataAttribute = "[data-blinq-id-" + result.randomToken + "]";
|
|
2706
|
+
await this._highlightElements(frame, dataAttribute);
|
|
2707
|
+
//const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
|
|
2708
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
2709
|
+
// console.log(`Highlighting for vtrt while running from recorder`);
|
|
2710
|
+
// this._highlightElements(frame, dataAttribute)
|
|
2711
|
+
// .then(async () => {
|
|
2712
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2713
|
+
// this._unhighlightElements(frame, dataAttribute).then(
|
|
2714
|
+
// () => {}
|
|
2715
|
+
// console.log(`Unhighlighting vrtr in recorder is successful`)
|
|
2716
|
+
// );
|
|
2717
|
+
// })
|
|
2718
|
+
// .catch(e);
|
|
2719
|
+
// }
|
|
2720
|
+
//await this._highlightElements(frame, cssAnchor);
|
|
2721
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2722
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2723
|
+
// await this._unhighlightElements(frame, dataAttribute);
|
|
2724
|
+
if (element) {
|
|
2725
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2726
|
+
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2727
|
+
}
|
|
2728
|
+
await _screenshot(state, this);
|
|
2729
|
+
return state.info;
|
|
2437
2730
|
}
|
|
2438
|
-
await _screenshot(state, this);
|
|
2439
|
-
return state.info;
|
|
2440
2731
|
}
|
|
2441
2732
|
}
|
|
2442
2733
|
}
|
|
2734
|
+
catch (error) {
|
|
2735
|
+
console.error(error);
|
|
2736
|
+
}
|
|
2443
2737
|
}
|
|
2444
2738
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2445
2739
|
}
|
|
@@ -2447,8 +2741,32 @@ class StableBrowser {
|
|
|
2447
2741
|
await _commandError(state, e, this);
|
|
2448
2742
|
}
|
|
2449
2743
|
finally {
|
|
2450
|
-
_commandFinally(state, this);
|
|
2744
|
+
await _commandFinally(state, this);
|
|
2745
|
+
}
|
|
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
|
+
}
|
|
2451
2766
|
}
|
|
2767
|
+
// state.info.results = results;
|
|
2768
|
+
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2769
|
+
return resultWithElementsFound;
|
|
2452
2770
|
}
|
|
2453
2771
|
async visualVerification(text, options = {}, world = null) {
|
|
2454
2772
|
const startTime = Date.now();
|
|
@@ -2468,10 +2786,13 @@ class StableBrowser {
|
|
|
2468
2786
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2469
2787
|
info.screenshotPath = screenshotPath;
|
|
2470
2788
|
const screenshot = await this.takeScreenshot();
|
|
2471
|
-
|
|
2472
|
-
method: "
|
|
2789
|
+
let request = {
|
|
2790
|
+
method: "post",
|
|
2791
|
+
maxBodyLength: Infinity,
|
|
2473
2792
|
url: `${serviceUrl}/api/runs/screenshots/validate-screenshot`,
|
|
2474
2793
|
headers: {
|
|
2794
|
+
"x-bvt-project-id": path.basename(this.project_path),
|
|
2795
|
+
"x-source": "aaa",
|
|
2475
2796
|
"Content-Type": "application/json",
|
|
2476
2797
|
Authorization: `Bearer ${process.env.TOKEN}`,
|
|
2477
2798
|
},
|
|
@@ -2480,7 +2801,7 @@ class StableBrowser {
|
|
|
2480
2801
|
screenshot: screenshot,
|
|
2481
2802
|
}),
|
|
2482
2803
|
};
|
|
2483
|
-
|
|
2804
|
+
const result = await axios.request(request);
|
|
2484
2805
|
if (result.data.status !== true) {
|
|
2485
2806
|
throw new Error("Visual validation failed");
|
|
2486
2807
|
}
|
|
@@ -2508,6 +2829,7 @@ class StableBrowser {
|
|
|
2508
2829
|
_reportToWorld(world, {
|
|
2509
2830
|
type: Types.VERIFY_VISUAL,
|
|
2510
2831
|
text: "Visual verification",
|
|
2832
|
+
_text: "Visual verification of " + text,
|
|
2511
2833
|
screenshotId,
|
|
2512
2834
|
result: error
|
|
2513
2835
|
? {
|
|
@@ -2774,6 +3096,15 @@ class StableBrowser {
|
|
|
2774
3096
|
}
|
|
2775
3097
|
return timeout;
|
|
2776
3098
|
}
|
|
3099
|
+
_getFindElementTimeout(options) {
|
|
3100
|
+
if (options && options.timeout) {
|
|
3101
|
+
return options.timeout;
|
|
3102
|
+
}
|
|
3103
|
+
if (this.configuration.find_element_timeout) {
|
|
3104
|
+
return this.configuration.find_element_timeout;
|
|
3105
|
+
}
|
|
3106
|
+
return 30000;
|
|
3107
|
+
}
|
|
2777
3108
|
async saveStoreState(path = null, world = null) {
|
|
2778
3109
|
const storageState = await this.page.context().storageState();
|
|
2779
3110
|
//const testDataFile = _getDataFile(world, this.context, this);
|
|
@@ -2785,6 +3116,12 @@ class StableBrowser {
|
|
|
2785
3116
|
await this.setTestData({ storageState: storageState }, world);
|
|
2786
3117
|
}
|
|
2787
3118
|
}
|
|
3119
|
+
async restoreSaveState(path = null, world = null) {
|
|
3120
|
+
await refreshBrowser(this, path, world);
|
|
3121
|
+
this.registerEventListeners(this.context);
|
|
3122
|
+
registerNetworkEvents(this.world, this, this.context, this.page);
|
|
3123
|
+
registerDownloadEvent(this.page, this.world, this.context);
|
|
3124
|
+
}
|
|
2788
3125
|
async waitForPageLoad(options = {}, world = null) {
|
|
2789
3126
|
let timeout = this._getLoadTimeout(options);
|
|
2790
3127
|
const promiseArray = [];
|
|
@@ -2852,6 +3189,7 @@ class StableBrowser {
|
|
|
2852
3189
|
highlight: false,
|
|
2853
3190
|
type: Types.CLOSE_PAGE,
|
|
2854
3191
|
text: `Close page`,
|
|
3192
|
+
_text: `Close the page`,
|
|
2855
3193
|
operation: "closePage",
|
|
2856
3194
|
log: "***** close page *****\n",
|
|
2857
3195
|
throwError: false,
|
|
@@ -2865,11 +3203,98 @@ class StableBrowser {
|
|
|
2865
3203
|
await _commandError(state, e, this);
|
|
2866
3204
|
}
|
|
2867
3205
|
finally {
|
|
2868
|
-
_commandFinally(state, this);
|
|
3206
|
+
await _commandFinally(state, this);
|
|
3207
|
+
}
|
|
3208
|
+
}
|
|
3209
|
+
async tableCellOperation(headerText, rowText, options, _params, world = null) {
|
|
3210
|
+
let operation = null;
|
|
3211
|
+
if (!options || !options.operation) {
|
|
3212
|
+
throw new Error("operation is not defined");
|
|
3213
|
+
}
|
|
3214
|
+
operation = options.operation;
|
|
3215
|
+
// validate operation is one of the supported operations
|
|
3216
|
+
if (operation != "click" && operation != "hover+click") {
|
|
3217
|
+
throw new Error("operation is not supported");
|
|
3218
|
+
}
|
|
3219
|
+
const state = {
|
|
3220
|
+
options,
|
|
3221
|
+
world,
|
|
3222
|
+
locate: false,
|
|
3223
|
+
scroll: false,
|
|
3224
|
+
highlight: false,
|
|
3225
|
+
type: Types.TABLE_OPERATION,
|
|
3226
|
+
text: `Table operation`,
|
|
3227
|
+
_text: `Table ${operation} operation`,
|
|
3228
|
+
operation: operation,
|
|
3229
|
+
log: "***** Table operation *****\n",
|
|
3230
|
+
};
|
|
3231
|
+
const timeout = this._getFindElementTimeout(options);
|
|
3232
|
+
try {
|
|
3233
|
+
await _preCommand(state, this);
|
|
3234
|
+
const start = Date.now();
|
|
3235
|
+
let cellArea = null;
|
|
3236
|
+
while (true) {
|
|
3237
|
+
try {
|
|
3238
|
+
cellArea = await _findCellArea(headerText, rowText, this, state);
|
|
3239
|
+
if (cellArea) {
|
|
3240
|
+
break;
|
|
3241
|
+
}
|
|
3242
|
+
}
|
|
3243
|
+
catch (e) {
|
|
3244
|
+
// ignore
|
|
3245
|
+
}
|
|
3246
|
+
if (Date.now() - start > timeout) {
|
|
3247
|
+
throw new Error(`Cell not found in table`);
|
|
3248
|
+
}
|
|
3249
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
3250
|
+
}
|
|
3251
|
+
switch (operation) {
|
|
3252
|
+
case "click":
|
|
3253
|
+
if (!options.css) {
|
|
3254
|
+
// will click in the center of the cell
|
|
3255
|
+
let xOffset = 0;
|
|
3256
|
+
let yOffset = 0;
|
|
3257
|
+
if (options.xOffset) {
|
|
3258
|
+
xOffset = options.xOffset;
|
|
3259
|
+
}
|
|
3260
|
+
if (options.yOffset) {
|
|
3261
|
+
yOffset = options.yOffset;
|
|
3262
|
+
}
|
|
3263
|
+
await this.page.mouse.click(cellArea.x + cellArea.width / 2 + xOffset, cellArea.y + cellArea.height / 2 + yOffset);
|
|
3264
|
+
}
|
|
3265
|
+
else {
|
|
3266
|
+
const results = await findElementsInArea(options.css, cellArea, this, options);
|
|
3267
|
+
if (results.length === 0) {
|
|
3268
|
+
throw new Error(`Element not found in cell area`);
|
|
3269
|
+
}
|
|
3270
|
+
state.element = results[0];
|
|
3271
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
3272
|
+
}
|
|
3273
|
+
break;
|
|
3274
|
+
case "hover+click":
|
|
3275
|
+
if (!options.css) {
|
|
3276
|
+
throw new Error("css is not defined");
|
|
3277
|
+
}
|
|
3278
|
+
const results = await findElementsInArea(options.css, cellArea, this, options);
|
|
3279
|
+
if (results.length === 0) {
|
|
3280
|
+
throw new Error(`Element not found in cell area`);
|
|
3281
|
+
}
|
|
3282
|
+
state.element = results[0];
|
|
3283
|
+
await performAction("hover+click", state.element, options, this, state, _params);
|
|
3284
|
+
break;
|
|
3285
|
+
default:
|
|
3286
|
+
throw new Error("operation is not supported");
|
|
3287
|
+
}
|
|
3288
|
+
}
|
|
3289
|
+
catch (e) {
|
|
3290
|
+
await _commandError(state, e, this);
|
|
3291
|
+
}
|
|
3292
|
+
finally {
|
|
3293
|
+
await _commandFinally(state, this);
|
|
2869
3294
|
}
|
|
2870
3295
|
}
|
|
2871
3296
|
saveTestDataAsGlobal(options, world) {
|
|
2872
|
-
const dataFile =
|
|
3297
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
2873
3298
|
process.env.GLOBAL_TEST_DATA_FILE = dataFile;
|
|
2874
3299
|
this.logger.info("Save the scenario test data as global for the following scenarios.");
|
|
2875
3300
|
}
|
|
@@ -2899,6 +3324,7 @@ class StableBrowser {
|
|
|
2899
3324
|
_reportToWorld(world, {
|
|
2900
3325
|
type: Types.SET_VIEWPORT,
|
|
2901
3326
|
text: "set viewport size to " + width + "x" + hight,
|
|
3327
|
+
_text: "Set the viewport size to " + width + "x" + hight,
|
|
2902
3328
|
screenshotId,
|
|
2903
3329
|
result: error
|
|
2904
3330
|
? {
|
|
@@ -2969,7 +3395,39 @@ class StableBrowser {
|
|
|
2969
3395
|
console.log("#-#");
|
|
2970
3396
|
}
|
|
2971
3397
|
}
|
|
3398
|
+
async beforeScenario(world, scenario) {
|
|
3399
|
+
this.beforeScenarioCalled = true;
|
|
3400
|
+
if (scenario && scenario.pickle && scenario.pickle.name) {
|
|
3401
|
+
this.scenarioName = scenario.pickle.name;
|
|
3402
|
+
}
|
|
3403
|
+
if (scenario && scenario.gherkinDocument && scenario.gherkinDocument.feature) {
|
|
3404
|
+
this.featureName = scenario.gherkinDocument.feature.name;
|
|
3405
|
+
}
|
|
3406
|
+
if (this.context) {
|
|
3407
|
+
this.context.examplesRow = extractStepExampleParameters(scenario);
|
|
3408
|
+
}
|
|
3409
|
+
if (this.tags === null && scenario && scenario.pickle && scenario.pickle.tags) {
|
|
3410
|
+
this.tags = scenario.pickle.tags.map((tag) => tag.name);
|
|
3411
|
+
// check if @global_test_data tag is present
|
|
3412
|
+
if (this.tags.includes("@global_test_data")) {
|
|
3413
|
+
this.saveTestDataAsGlobal({}, world);
|
|
3414
|
+
}
|
|
3415
|
+
}
|
|
3416
|
+
// update test data based on feature/scenario
|
|
3417
|
+
let envName = null;
|
|
3418
|
+
if (this.context && this.context.environment) {
|
|
3419
|
+
envName = this.context.environment.name;
|
|
3420
|
+
}
|
|
3421
|
+
if (!process.env.TEMP_RUN) {
|
|
3422
|
+
await getTestData(envName, world, undefined, this.featureName, this.scenarioName);
|
|
3423
|
+
}
|
|
3424
|
+
await loadBrunoParams(this.context, this.context.environment.name);
|
|
3425
|
+
}
|
|
3426
|
+
async afterScenario(world, scenario) { }
|
|
2972
3427
|
async beforeStep(world, step) {
|
|
3428
|
+
if (!this.beforeScenarioCalled) {
|
|
3429
|
+
this.beforeScenario(world, step);
|
|
3430
|
+
}
|
|
2973
3431
|
if (this.stepIndex === undefined) {
|
|
2974
3432
|
this.stepIndex = 0;
|
|
2975
3433
|
}
|
|
@@ -2986,22 +3444,48 @@ class StableBrowser {
|
|
|
2986
3444
|
else {
|
|
2987
3445
|
this.stepName = "step " + this.stepIndex;
|
|
2988
3446
|
}
|
|
2989
|
-
if (this.context) {
|
|
2990
|
-
this.context.examplesRow = extractStepExampleParameters(step);
|
|
2991
|
-
}
|
|
2992
3447
|
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
2993
3448
|
if (this.context.browserObject.context) {
|
|
2994
3449
|
await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
|
|
2995
3450
|
}
|
|
2996
3451
|
}
|
|
2997
|
-
if (this.
|
|
2998
|
-
this.
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3452
|
+
if (this.initSnapshotTaken === false) {
|
|
3453
|
+
this.initSnapshotTaken = true;
|
|
3454
|
+
if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
|
|
3455
|
+
const snapshot = await this.getAriaSnapshot();
|
|
3456
|
+
if (snapshot) {
|
|
3457
|
+
await world.attach(JSON.stringify(snapshot), "application/json+snapshot-before");
|
|
3458
|
+
}
|
|
3002
3459
|
}
|
|
3003
3460
|
}
|
|
3004
3461
|
}
|
|
3462
|
+
async getAriaSnapshot() {
|
|
3463
|
+
try {
|
|
3464
|
+
// find the page url
|
|
3465
|
+
const url = await this.page.url();
|
|
3466
|
+
// extract the path from the url
|
|
3467
|
+
const path = new URL(url).pathname;
|
|
3468
|
+
// get the page title
|
|
3469
|
+
const title = await this.page.title();
|
|
3470
|
+
// go over other frams
|
|
3471
|
+
const frames = this.page.frames();
|
|
3472
|
+
const snapshots = [];
|
|
3473
|
+
const content = [`- path: ${path}`, `- title: ${title}`];
|
|
3474
|
+
const timeout = this.configuration.ariaSnapshotTimeout ? this.configuration.ariaSnapshotTimeout : 3000;
|
|
3475
|
+
for (let i = 0; i < frames.length; i++) {
|
|
3476
|
+
content.push(`- frame: ${i}`);
|
|
3477
|
+
const frame = frames[i];
|
|
3478
|
+
const snapshot = await frame.locator("body").ariaSnapshot({ timeout });
|
|
3479
|
+
content.push(snapshot);
|
|
3480
|
+
}
|
|
3481
|
+
return content.join("\n");
|
|
3482
|
+
}
|
|
3483
|
+
catch (e) {
|
|
3484
|
+
console.log("Error in getAriaSnapshot");
|
|
3485
|
+
console.debug(e);
|
|
3486
|
+
}
|
|
3487
|
+
return null;
|
|
3488
|
+
}
|
|
3005
3489
|
async afterStep(world, step) {
|
|
3006
3490
|
this.stepName = null;
|
|
3007
3491
|
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
@@ -3009,11 +3493,25 @@ class StableBrowser {
|
|
|
3009
3493
|
await this.context.browserObject.context.tracing.stopChunk({
|
|
3010
3494
|
path: path.join(this.context.browserObject.traceFolder, `trace-${this.stepIndex}.zip`),
|
|
3011
3495
|
});
|
|
3496
|
+
if (world && world.attach) {
|
|
3497
|
+
await world.attach(JSON.stringify({
|
|
3498
|
+
type: "trace",
|
|
3499
|
+
traceFilePath: `trace-${this.stepIndex}.zip`,
|
|
3500
|
+
}), "application/json+trace");
|
|
3501
|
+
}
|
|
3502
|
+
// console.log("trace file created", `trace-${this.stepIndex}.zip`);
|
|
3012
3503
|
}
|
|
3013
3504
|
}
|
|
3014
3505
|
if (this.context) {
|
|
3015
3506
|
this.context.examplesRow = null;
|
|
3016
3507
|
}
|
|
3508
|
+
if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
|
|
3509
|
+
const snapshot = await this.getAriaSnapshot();
|
|
3510
|
+
if (snapshot) {
|
|
3511
|
+
const obj = {};
|
|
3512
|
+
await world.attach(JSON.stringify(snapshot), "application/json+snapshot-after");
|
|
3513
|
+
}
|
|
3514
|
+
}
|
|
3017
3515
|
}
|
|
3018
3516
|
}
|
|
3019
3517
|
function createTimedPromise(promise, label) {
|