automation_model 1.0.606-dev → 1.0.606-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 +3 -1
- package/lib/auto_page.js +78 -18
- 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 +52 -12
- 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 +680 -197
- 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 +217 -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 = [];
|
|
@@ -239,6 +273,9 @@ class StableBrowser {
|
|
|
239
273
|
// await closeUnexpectedPopups(this.page);
|
|
240
274
|
// }
|
|
241
275
|
async goto(url, world = null) {
|
|
276
|
+
if (!url) {
|
|
277
|
+
throw new Error("url is null, verify that the environment file is correct");
|
|
278
|
+
}
|
|
242
279
|
if (!url.startsWith("http")) {
|
|
243
280
|
url = "https://" + url;
|
|
244
281
|
}
|
|
@@ -267,7 +304,7 @@ class StableBrowser {
|
|
|
267
304
|
_commandError(state, error, this);
|
|
268
305
|
}
|
|
269
306
|
finally {
|
|
270
|
-
_commandFinally(state, this);
|
|
307
|
+
await _commandFinally(state, this);
|
|
271
308
|
}
|
|
272
309
|
}
|
|
273
310
|
async _getLocator(locator, scope, _params) {
|
|
@@ -348,7 +385,7 @@ class StableBrowser {
|
|
|
348
385
|
return resultCss;
|
|
349
386
|
}
|
|
350
387
|
async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, ignoreCase = true, _params) {
|
|
351
|
-
const query = _convertToRegexQuery(text1, regex1, !partial1, ignoreCase)
|
|
388
|
+
const query = `${_convertToRegexQuery(text1, regex1, !partial1, ignoreCase)}`;
|
|
352
389
|
const locator = scope.locator(query);
|
|
353
390
|
const count = await locator.count();
|
|
354
391
|
if (!tag1) {
|
|
@@ -368,6 +405,12 @@ class StableBrowser {
|
|
|
368
405
|
if (!el.setAttribute) {
|
|
369
406
|
el = el.parentElement;
|
|
370
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
|
+
// }
|
|
371
414
|
el.setAttribute("data-blinq-id-" + randomToken, "");
|
|
372
415
|
return true;
|
|
373
416
|
}, [tag1, randomToken]))) {
|
|
@@ -554,9 +597,24 @@ class StableBrowser {
|
|
|
554
597
|
element.evaluate((el, randomToken) => {
|
|
555
598
|
el.setAttribute("data-blinq-id-" + randomToken, "");
|
|
556
599
|
}, randomToken);
|
|
600
|
+
// if (element._frame) {
|
|
601
|
+
// return element;
|
|
602
|
+
// }
|
|
557
603
|
const scope = element._frame ?? element.page();
|
|
558
|
-
|
|
559
|
-
|
|
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);
|
|
560
618
|
}
|
|
561
619
|
}
|
|
562
620
|
throw new Error("unable to locate element " + JSON.stringify(selectors));
|
|
@@ -708,14 +766,9 @@ class StableBrowser {
|
|
|
708
766
|
// info.log += "scanning locators in priority 2" + "\n";
|
|
709
767
|
result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
710
768
|
}
|
|
711
|
-
if (result.foundElements.length === 0 && onlyPriority3) {
|
|
769
|
+
if (result.foundElements.length === 0 && (onlyPriority3 || !highPriorityOnly)) {
|
|
712
770
|
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
713
771
|
}
|
|
714
|
-
else {
|
|
715
|
-
if (result.foundElements.length === 0 && !highPriorityOnly) {
|
|
716
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
717
|
-
}
|
|
718
|
-
}
|
|
719
772
|
let foundElements = result.foundElements;
|
|
720
773
|
if (foundElements.length === 1 && foundElements[0].unique) {
|
|
721
774
|
info.box = foundElements[0].box;
|
|
@@ -770,6 +823,11 @@ class StableBrowser {
|
|
|
770
823
|
visibleOnly = false;
|
|
771
824
|
}
|
|
772
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
|
+
}
|
|
773
831
|
}
|
|
774
832
|
this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
|
|
775
833
|
// if (info.locatorLog) {
|
|
@@ -816,9 +874,40 @@ class StableBrowser {
|
|
|
816
874
|
result.locatorIndex = i;
|
|
817
875
|
}
|
|
818
876
|
if (foundLocators.length > 1) {
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
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
|
+
}
|
|
822
911
|
}
|
|
823
912
|
}
|
|
824
913
|
}
|
|
@@ -866,7 +955,7 @@ class StableBrowser {
|
|
|
866
955
|
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
867
956
|
}
|
|
868
957
|
finally {
|
|
869
|
-
_commandFinally(state, this);
|
|
958
|
+
await _commandFinally(state, this);
|
|
870
959
|
}
|
|
871
960
|
}
|
|
872
961
|
}
|
|
@@ -915,7 +1004,7 @@ class StableBrowser {
|
|
|
915
1004
|
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
916
1005
|
}
|
|
917
1006
|
finally {
|
|
918
|
-
_commandFinally(state, this);
|
|
1007
|
+
await _commandFinally(state, this);
|
|
919
1008
|
}
|
|
920
1009
|
}
|
|
921
1010
|
}
|
|
@@ -929,25 +1018,14 @@ class StableBrowser {
|
|
|
929
1018
|
options,
|
|
930
1019
|
world,
|
|
931
1020
|
text: "Click element",
|
|
1021
|
+
_text: "Click on " + selectors.element_name,
|
|
932
1022
|
type: Types.CLICK,
|
|
933
1023
|
operation: "click",
|
|
934
1024
|
log: "***** click on " + selectors.element_name + " *****\n",
|
|
935
1025
|
};
|
|
936
1026
|
try {
|
|
937
1027
|
await _preCommand(state, this);
|
|
938
|
-
|
|
939
|
-
// state.selectors.locators[0].text = state.options.context;
|
|
940
|
-
// }
|
|
941
|
-
try {
|
|
942
|
-
await state.element.click();
|
|
943
|
-
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
944
|
-
}
|
|
945
|
-
catch (e) {
|
|
946
|
-
// await this.closeUnexpectedPopups();
|
|
947
|
-
state.element = await this._locate(selectors, state.info, _params);
|
|
948
|
-
await state.element.dispatchEvent("click");
|
|
949
|
-
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
950
|
-
}
|
|
1028
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
951
1029
|
await this.waitForPageLoad();
|
|
952
1030
|
return state.info;
|
|
953
1031
|
}
|
|
@@ -955,9 +1033,41 @@ class StableBrowser {
|
|
|
955
1033
|
await _commandError(state, e, this);
|
|
956
1034
|
}
|
|
957
1035
|
finally {
|
|
958
|
-
_commandFinally(state, this);
|
|
1036
|
+
await _commandFinally(state, this);
|
|
959
1037
|
}
|
|
960
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
|
+
}
|
|
961
1071
|
async setCheck(selectors, checked = true, _params, options = {}, world = null) {
|
|
962
1072
|
const state = {
|
|
963
1073
|
selectors,
|
|
@@ -966,6 +1076,7 @@ class StableBrowser {
|
|
|
966
1076
|
world,
|
|
967
1077
|
type: checked ? Types.CHECK : Types.UNCHECK,
|
|
968
1078
|
text: checked ? `Check element` : `Uncheck element`,
|
|
1079
|
+
_text: checked ? `Check ${selectors.element_name}` : `Uncheck ${selectors.element_name}`,
|
|
969
1080
|
operation: "setCheck",
|
|
970
1081
|
log: "***** check " + selectors.element_name + " *****\n",
|
|
971
1082
|
};
|
|
@@ -977,7 +1088,7 @@ class StableBrowser {
|
|
|
977
1088
|
try {
|
|
978
1089
|
// if (world && world.screenshot && !world.screenshotPath) {
|
|
979
1090
|
// console.log(`Highlighting while running from recorder`);
|
|
980
|
-
await this._highlightElements(element);
|
|
1091
|
+
await this._highlightElements(state.element);
|
|
981
1092
|
await state.element.setChecked(checked);
|
|
982
1093
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
983
1094
|
// await this._unHighlightElements(element);
|
|
@@ -1004,7 +1115,7 @@ class StableBrowser {
|
|
|
1004
1115
|
await _commandError(state, e, this);
|
|
1005
1116
|
}
|
|
1006
1117
|
finally {
|
|
1007
|
-
_commandFinally(state, this);
|
|
1118
|
+
await _commandFinally(state, this);
|
|
1008
1119
|
}
|
|
1009
1120
|
}
|
|
1010
1121
|
async hover(selectors, _params, options = {}, world = null) {
|
|
@@ -1015,24 +1126,13 @@ class StableBrowser {
|
|
|
1015
1126
|
world,
|
|
1016
1127
|
type: Types.HOVER,
|
|
1017
1128
|
text: `Hover element`,
|
|
1129
|
+
_text: `Hover on ${selectors.element_name}`,
|
|
1018
1130
|
operation: "hover",
|
|
1019
1131
|
log: "***** hover " + selectors.element_name + " *****\n",
|
|
1020
1132
|
};
|
|
1021
1133
|
try {
|
|
1022
1134
|
await _preCommand(state, this);
|
|
1023
|
-
|
|
1024
|
-
await state.element.hover();
|
|
1025
|
-
// await _screenshot(state, this);
|
|
1026
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1027
|
-
}
|
|
1028
|
-
catch (e) {
|
|
1029
|
-
//await this.closeUnexpectedPopups();
|
|
1030
|
-
state.info.log += "hover failed, will try again" + "\n";
|
|
1031
|
-
state.element = await this._locate(selectors, state.info, _params);
|
|
1032
|
-
await state.element.hover({ timeout: 10000 });
|
|
1033
|
-
// await _screenshot(state, this);
|
|
1034
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1035
|
-
}
|
|
1135
|
+
await performAction("hover", state.element, options, this, state, _params);
|
|
1036
1136
|
await _screenshot(state, this);
|
|
1037
1137
|
await this.waitForPageLoad();
|
|
1038
1138
|
return state.info;
|
|
@@ -1041,7 +1141,7 @@ class StableBrowser {
|
|
|
1041
1141
|
await _commandError(state, e, this);
|
|
1042
1142
|
}
|
|
1043
1143
|
finally {
|
|
1044
|
-
_commandFinally(state, this);
|
|
1144
|
+
await _commandFinally(state, this);
|
|
1045
1145
|
}
|
|
1046
1146
|
}
|
|
1047
1147
|
async selectOption(selectors, values, _params = null, options = {}, world = null) {
|
|
@@ -1056,6 +1156,7 @@ class StableBrowser {
|
|
|
1056
1156
|
value: values.toString(),
|
|
1057
1157
|
type: Types.SELECT,
|
|
1058
1158
|
text: `Select option: ${values}`,
|
|
1159
|
+
_text: `Select option: ${values} on ${selectors.element_name}`,
|
|
1059
1160
|
operation: "selectOption",
|
|
1060
1161
|
log: "***** select option " + selectors.element_name + " *****\n",
|
|
1061
1162
|
};
|
|
@@ -1076,7 +1177,7 @@ class StableBrowser {
|
|
|
1076
1177
|
await _commandError(state, e, this);
|
|
1077
1178
|
}
|
|
1078
1179
|
finally {
|
|
1079
|
-
_commandFinally(state, this);
|
|
1180
|
+
await _commandFinally(state, this);
|
|
1080
1181
|
}
|
|
1081
1182
|
}
|
|
1082
1183
|
async type(_value, _params = null, options = {}, world = null) {
|
|
@@ -1090,6 +1191,7 @@ class StableBrowser {
|
|
|
1090
1191
|
highlight: false,
|
|
1091
1192
|
type: Types.TYPE_PRESS,
|
|
1092
1193
|
text: `Type value: ${_value}`,
|
|
1194
|
+
_text: `Type value: ${_value}`,
|
|
1093
1195
|
operation: "type",
|
|
1094
1196
|
log: "",
|
|
1095
1197
|
};
|
|
@@ -1121,7 +1223,7 @@ class StableBrowser {
|
|
|
1121
1223
|
await _commandError(state, e, this);
|
|
1122
1224
|
}
|
|
1123
1225
|
finally {
|
|
1124
|
-
_commandFinally(state, this);
|
|
1226
|
+
await _commandFinally(state, this);
|
|
1125
1227
|
}
|
|
1126
1228
|
}
|
|
1127
1229
|
async setInputValue(selectors, value, _params = null, options = {}, world = null) {
|
|
@@ -1157,7 +1259,7 @@ class StableBrowser {
|
|
|
1157
1259
|
await _commandError(state, e, this);
|
|
1158
1260
|
}
|
|
1159
1261
|
finally {
|
|
1160
|
-
_commandFinally(state, this);
|
|
1262
|
+
await _commandFinally(state, this);
|
|
1161
1263
|
}
|
|
1162
1264
|
}
|
|
1163
1265
|
async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
|
|
@@ -1169,6 +1271,7 @@ class StableBrowser {
|
|
|
1169
1271
|
world,
|
|
1170
1272
|
type: Types.SET_DATE_TIME,
|
|
1171
1273
|
text: `Set date time value: ${value}`,
|
|
1274
|
+
_text: `Set date time value: ${value} on ${selectors.element_name}`,
|
|
1172
1275
|
operation: "setDateTime",
|
|
1173
1276
|
log: "***** set date time value " + selectors.element_name + " *****\n",
|
|
1174
1277
|
throwError: false,
|
|
@@ -1176,7 +1279,7 @@ class StableBrowser {
|
|
|
1176
1279
|
try {
|
|
1177
1280
|
await _preCommand(state, this);
|
|
1178
1281
|
try {
|
|
1179
|
-
await state.element
|
|
1282
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
1180
1283
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1181
1284
|
if (format) {
|
|
1182
1285
|
state.value = dayjs(state.value).format(format);
|
|
@@ -1225,7 +1328,7 @@ class StableBrowser {
|
|
|
1225
1328
|
await _commandError(state, e, this);
|
|
1226
1329
|
}
|
|
1227
1330
|
finally {
|
|
1228
|
-
_commandFinally(state, this);
|
|
1331
|
+
await _commandFinally(state, this);
|
|
1229
1332
|
}
|
|
1230
1333
|
}
|
|
1231
1334
|
async clickType(selectors, _value, enter = false, _params = null, options = {}, world = null) {
|
|
@@ -1240,9 +1343,13 @@ class StableBrowser {
|
|
|
1240
1343
|
world,
|
|
1241
1344
|
type: Types.FILL,
|
|
1242
1345
|
text: `Click type input with value: ${_value}`,
|
|
1346
|
+
_text: "Fill " + selectors.element_name + " with value " + maskValue(_value),
|
|
1243
1347
|
operation: "clickType",
|
|
1244
1348
|
log: "***** clickType on " + selectors.element_name + " with value " + maskValue(_value) + "*****\n",
|
|
1245
1349
|
};
|
|
1350
|
+
if (!options) {
|
|
1351
|
+
options = {};
|
|
1352
|
+
}
|
|
1246
1353
|
if (newValue !== _value) {
|
|
1247
1354
|
//this.logger.info(_value + "=" + newValue);
|
|
1248
1355
|
_value = newValue;
|
|
@@ -1250,7 +1357,7 @@ class StableBrowser {
|
|
|
1250
1357
|
try {
|
|
1251
1358
|
await _preCommand(state, this);
|
|
1252
1359
|
state.info.value = _value;
|
|
1253
|
-
if (
|
|
1360
|
+
if (!options.press) {
|
|
1254
1361
|
try {
|
|
1255
1362
|
let currentValue = await state.element.inputValue();
|
|
1256
1363
|
if (currentValue) {
|
|
@@ -1261,13 +1368,9 @@ class StableBrowser {
|
|
|
1261
1368
|
this.logger.info("unable to clear input value");
|
|
1262
1369
|
}
|
|
1263
1370
|
}
|
|
1264
|
-
if (options
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
}
|
|
1268
|
-
catch (e) {
|
|
1269
|
-
await state.element.dispatchEvent("click");
|
|
1270
|
-
}
|
|
1371
|
+
if (options.press) {
|
|
1372
|
+
options.timeout = 5000;
|
|
1373
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
1271
1374
|
}
|
|
1272
1375
|
else {
|
|
1273
1376
|
try {
|
|
@@ -1325,7 +1428,7 @@ class StableBrowser {
|
|
|
1325
1428
|
await _commandError(state, e, this);
|
|
1326
1429
|
}
|
|
1327
1430
|
finally {
|
|
1328
|
-
_commandFinally(state, this);
|
|
1431
|
+
await _commandFinally(state, this);
|
|
1329
1432
|
}
|
|
1330
1433
|
}
|
|
1331
1434
|
async fill(selectors, value, enter = false, _params = null, options = {}, world = null) {
|
|
@@ -1355,13 +1458,48 @@ class StableBrowser {
|
|
|
1355
1458
|
await _commandError(state, e, this);
|
|
1356
1459
|
}
|
|
1357
1460
|
finally {
|
|
1358
|
-
_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
|
+
options,
|
|
1470
|
+
world,
|
|
1471
|
+
type: Types.SET_INPUT_FILES,
|
|
1472
|
+
text: `Set input files`,
|
|
1473
|
+
_text: `Set input files on ${selectors.element_name}`,
|
|
1474
|
+
operation: "setInputFiles",
|
|
1475
|
+
log: "***** set input files " + selectors.element_name + " *****\n",
|
|
1476
|
+
};
|
|
1477
|
+
const uploadsFolder = this.configuration.uploadsFolder ?? "data/uploads";
|
|
1478
|
+
try {
|
|
1479
|
+
await _preCommand(state, this);
|
|
1480
|
+
for (let i = 0; i < files.length; i++) {
|
|
1481
|
+
const file = files[i];
|
|
1482
|
+
const filePath = path.join(uploadsFolder, file);
|
|
1483
|
+
if (!fs.existsSync(filePath)) {
|
|
1484
|
+
throw new Error(`File not found: ${filePath}`);
|
|
1485
|
+
}
|
|
1486
|
+
state.files[i] = filePath;
|
|
1487
|
+
}
|
|
1488
|
+
await state.element.setInputFiles(files);
|
|
1489
|
+
return state.info;
|
|
1490
|
+
}
|
|
1491
|
+
catch (e) {
|
|
1492
|
+
await _commandError(state, e, this);
|
|
1493
|
+
}
|
|
1494
|
+
finally {
|
|
1495
|
+
await _commandFinally(state, this);
|
|
1359
1496
|
}
|
|
1360
1497
|
}
|
|
1361
1498
|
async getText(selectors, _params = null, options = {}, info = {}, world = null) {
|
|
1362
1499
|
return await this._getText(selectors, 0, _params, options, info, world);
|
|
1363
1500
|
}
|
|
1364
1501
|
async _getText(selectors, climb, _params = null, options = {}, info = {}, world = null) {
|
|
1502
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1365
1503
|
_validateSelectors(selectors);
|
|
1366
1504
|
let screenshotId = null;
|
|
1367
1505
|
let screenshotPath = null;
|
|
@@ -1371,7 +1509,7 @@ class StableBrowser {
|
|
|
1371
1509
|
}
|
|
1372
1510
|
info.operation = "getText";
|
|
1373
1511
|
info.selectors = selectors;
|
|
1374
|
-
let element = await this._locate(selectors, info, _params);
|
|
1512
|
+
let element = await this._locate(selectors, info, _params, timeout);
|
|
1375
1513
|
if (climb > 0) {
|
|
1376
1514
|
const climbArray = [];
|
|
1377
1515
|
for (let i = 0; i < climb; i++) {
|
|
@@ -1438,6 +1576,7 @@ class StableBrowser {
|
|
|
1438
1576
|
highlight: false,
|
|
1439
1577
|
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1440
1578
|
text: `Verify element contains pattern: ${pattern}`,
|
|
1579
|
+
_text: "Verify element " + selectors.element_name + " contains pattern " + pattern,
|
|
1441
1580
|
operation: "containsPattern",
|
|
1442
1581
|
log: "***** verify element " + selectors.element_name + " contains pattern " + pattern + " *****\n",
|
|
1443
1582
|
};
|
|
@@ -1469,10 +1608,12 @@ class StableBrowser {
|
|
|
1469
1608
|
await _commandError(state, e, this);
|
|
1470
1609
|
}
|
|
1471
1610
|
finally {
|
|
1472
|
-
_commandFinally(state, this);
|
|
1611
|
+
await _commandFinally(state, this);
|
|
1473
1612
|
}
|
|
1474
1613
|
}
|
|
1475
1614
|
async containsText(selectors, text, climb, _params = null, options = {}, world = null) {
|
|
1615
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1616
|
+
const startTime = Date.now();
|
|
1476
1617
|
const state = {
|
|
1477
1618
|
selectors,
|
|
1478
1619
|
_params,
|
|
@@ -1499,44 +1640,52 @@ class StableBrowser {
|
|
|
1499
1640
|
}
|
|
1500
1641
|
let foundObj = null;
|
|
1501
1642
|
try {
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
const dateAlternatives = findDateAlternatives(text);
|
|
1509
|
-
const numberAlternatives = findNumberAlternatives(text);
|
|
1510
|
-
if (dateAlternatives.date) {
|
|
1511
|
-
for (let i = 0; i < dateAlternatives.dates.length; i++) {
|
|
1512
|
-
if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
|
|
1513
|
-
foundObj?.value?.includes(dateAlternatives.dates[i])) {
|
|
1514
|
-
return state.info;
|
|
1643
|
+
while (Date.now() - startTime < timeout) {
|
|
1644
|
+
try {
|
|
1645
|
+
await _preCommand(state, this);
|
|
1646
|
+
foundObj = await this._getText(selectors, climb, _params, { timeout: 3000 }, state.info, world);
|
|
1647
|
+
if (foundObj && foundObj.element) {
|
|
1648
|
+
await this.scrollIfNeeded(foundObj.element, state.info);
|
|
1515
1649
|
}
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1650
|
+
await _screenshot(state, this);
|
|
1651
|
+
const dateAlternatives = findDateAlternatives(text);
|
|
1652
|
+
const numberAlternatives = findNumberAlternatives(text);
|
|
1653
|
+
if (dateAlternatives.date) {
|
|
1654
|
+
for (let i = 0; i < dateAlternatives.dates.length; i++) {
|
|
1655
|
+
if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
|
|
1656
|
+
foundObj?.value?.includes(dateAlternatives.dates[i])) {
|
|
1657
|
+
return state.info;
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
else if (numberAlternatives.number) {
|
|
1662
|
+
for (let i = 0; i < numberAlternatives.numbers.length; i++) {
|
|
1663
|
+
if (foundObj?.text.includes(numberAlternatives.numbers[i]) ||
|
|
1664
|
+
foundObj?.value?.includes(numberAlternatives.numbers[i])) {
|
|
1665
|
+
return state.info;
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
else if (foundObj?.text.includes(text) || foundObj?.value?.includes(text)) {
|
|
1523
1670
|
return state.info;
|
|
1524
1671
|
}
|
|
1525
1672
|
}
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
throw new Error("element doesn't contain text " + text);
|
|
1673
|
+
catch (e) {
|
|
1674
|
+
// Log error but continue retrying until timeout is reached
|
|
1675
|
+
this.logger.warn("Retrying containsText due to: " + e.message);
|
|
1676
|
+
}
|
|
1677
|
+
await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second before retrying
|
|
1532
1678
|
}
|
|
1533
|
-
|
|
1679
|
+
state.info.foundText = foundObj?.text;
|
|
1680
|
+
state.info.value = foundObj?.value;
|
|
1681
|
+
throw new Error("element doesn't contain text " + text);
|
|
1534
1682
|
}
|
|
1535
1683
|
catch (e) {
|
|
1536
1684
|
await _commandError(state, e, this);
|
|
1685
|
+
throw e;
|
|
1537
1686
|
}
|
|
1538
1687
|
finally {
|
|
1539
|
-
_commandFinally(state, this);
|
|
1688
|
+
await _commandFinally(state, this);
|
|
1540
1689
|
}
|
|
1541
1690
|
}
|
|
1542
1691
|
async waitForUserInput(message, world = null) {
|
|
@@ -1574,6 +1723,15 @@ class StableBrowser {
|
|
|
1574
1723
|
// save the data to the file
|
|
1575
1724
|
fs.writeFileSync(dataFile, JSON.stringify(data, null, 2));
|
|
1576
1725
|
}
|
|
1726
|
+
overwriteTestData(testData, world = null) {
|
|
1727
|
+
if (!testData) {
|
|
1728
|
+
return;
|
|
1729
|
+
}
|
|
1730
|
+
// if data file exists, load it
|
|
1731
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1732
|
+
// save the data to the file
|
|
1733
|
+
fs.writeFileSync(dataFile, JSON.stringify(testData, null, 2));
|
|
1734
|
+
}
|
|
1577
1735
|
_getDataFilePath(fileName) {
|
|
1578
1736
|
let dataFile = path.join(this.project_path, "data", fileName);
|
|
1579
1737
|
if (fs.existsSync(dataFile)) {
|
|
@@ -1803,6 +1961,7 @@ class StableBrowser {
|
|
|
1803
1961
|
else {
|
|
1804
1962
|
fs.writeFileSync(screenshotPath, screenshotBuffer);
|
|
1805
1963
|
}
|
|
1964
|
+
return screenshotBuffer;
|
|
1806
1965
|
}
|
|
1807
1966
|
async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
|
|
1808
1967
|
const state = {
|
|
@@ -1825,7 +1984,7 @@ class StableBrowser {
|
|
|
1825
1984
|
await _commandError(state, e, this);
|
|
1826
1985
|
}
|
|
1827
1986
|
finally {
|
|
1828
|
-
_commandFinally(state, this);
|
|
1987
|
+
await _commandFinally(state, this);
|
|
1829
1988
|
}
|
|
1830
1989
|
}
|
|
1831
1990
|
async extractAttribute(selectors, attribute, variable, _params = null, options = {}, world = null) {
|
|
@@ -1838,6 +1997,7 @@ class StableBrowser {
|
|
|
1838
1997
|
world,
|
|
1839
1998
|
type: Types.EXTRACT,
|
|
1840
1999
|
text: `Extract attribute from element`,
|
|
2000
|
+
_text: `Extract attribute ${attribute} from ${selectors.element_name}`,
|
|
1841
2001
|
operation: "extractAttribute",
|
|
1842
2002
|
log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1843
2003
|
allowDisabled: true,
|
|
@@ -1855,6 +2015,9 @@ class StableBrowser {
|
|
|
1855
2015
|
case "value":
|
|
1856
2016
|
state.value = await state.element.inputValue();
|
|
1857
2017
|
break;
|
|
2018
|
+
case "text":
|
|
2019
|
+
state.value = await state.element.textContent();
|
|
2020
|
+
break;
|
|
1858
2021
|
default:
|
|
1859
2022
|
state.value = await state.element.getAttribute(attribute);
|
|
1860
2023
|
break;
|
|
@@ -1869,7 +2032,7 @@ class StableBrowser {
|
|
|
1869
2032
|
await _commandError(state, e, this);
|
|
1870
2033
|
}
|
|
1871
2034
|
finally {
|
|
1872
|
-
_commandFinally(state, this);
|
|
2035
|
+
await _commandFinally(state, this);
|
|
1873
2036
|
}
|
|
1874
2037
|
}
|
|
1875
2038
|
async verifyAttribute(selectors, attribute, value, _params = null, options = {}, world = null) {
|
|
@@ -1884,18 +2047,25 @@ class StableBrowser {
|
|
|
1884
2047
|
highlight: true,
|
|
1885
2048
|
screenshot: true,
|
|
1886
2049
|
text: `Verify element attribute`,
|
|
2050
|
+
_text: `Verify attribute ${attribute} from ${selectors.element_name} is ${value}`,
|
|
1887
2051
|
operation: "verifyAttribute",
|
|
1888
2052
|
log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1889
2053
|
allowDisabled: true,
|
|
1890
2054
|
};
|
|
1891
2055
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1892
2056
|
let val;
|
|
2057
|
+
let expectedValue;
|
|
1893
2058
|
try {
|
|
1894
2059
|
await _preCommand(state, this);
|
|
2060
|
+
expectedValue = await replaceWithLocalTestData(state.value, world);
|
|
2061
|
+
state.info.expectedValue = expectedValue;
|
|
1895
2062
|
switch (attribute) {
|
|
1896
2063
|
case "innerText":
|
|
1897
2064
|
val = String(await state.element.innerText());
|
|
1898
2065
|
break;
|
|
2066
|
+
case "text":
|
|
2067
|
+
val = String(await state.element.textContent());
|
|
2068
|
+
break;
|
|
1899
2069
|
case "value":
|
|
1900
2070
|
val = String(await state.element.inputValue());
|
|
1901
2071
|
break;
|
|
@@ -1913,26 +2083,29 @@ class StableBrowser {
|
|
|
1913
2083
|
val = String(await state.element.getAttribute(attribute));
|
|
1914
2084
|
break;
|
|
1915
2085
|
}
|
|
2086
|
+
state.info.value = val;
|
|
1916
2087
|
let regex;
|
|
1917
|
-
if (
|
|
1918
|
-
const patternBody =
|
|
2088
|
+
if (expectedValue.startsWith("/") && expectedValue.endsWith("/")) {
|
|
2089
|
+
const patternBody = expectedValue.slice(1, -1);
|
|
1919
2090
|
regex = new RegExp(patternBody, "g");
|
|
1920
2091
|
}
|
|
1921
2092
|
else {
|
|
1922
|
-
const escapedPattern =
|
|
2093
|
+
const escapedPattern = expectedValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1923
2094
|
regex = new RegExp(escapedPattern, "g");
|
|
1924
2095
|
}
|
|
1925
2096
|
if (!val.match(regex)) {
|
|
1926
|
-
|
|
2097
|
+
let errorMessage = `The ${attribute} attribute has a value of "${val}", but the expected value is "${expectedValue}"`;
|
|
2098
|
+
state.info.failCause.assertionFailed = true;
|
|
2099
|
+
state.info.failCause.lastError = errorMessage;
|
|
2100
|
+
throw new Error(errorMessage);
|
|
1927
2101
|
}
|
|
1928
|
-
state.info.expectedValue = val;
|
|
1929
2102
|
return state.info;
|
|
1930
2103
|
}
|
|
1931
2104
|
catch (e) {
|
|
1932
2105
|
await _commandError(state, e, this);
|
|
1933
2106
|
}
|
|
1934
2107
|
finally {
|
|
1935
|
-
_commandFinally(state, this);
|
|
2108
|
+
await _commandFinally(state, this);
|
|
1936
2109
|
}
|
|
1937
2110
|
}
|
|
1938
2111
|
async extractEmailData(emailAddress, options, world) {
|
|
@@ -2184,6 +2357,7 @@ class StableBrowser {
|
|
|
2184
2357
|
_reportToWorld(world, {
|
|
2185
2358
|
type: Types.VERIFY_PAGE_PATH,
|
|
2186
2359
|
text: "Verify page path",
|
|
2360
|
+
_text: "Verify the page path contains " + pathPart,
|
|
2187
2361
|
screenshotId,
|
|
2188
2362
|
result: error
|
|
2189
2363
|
? {
|
|
@@ -2201,27 +2375,89 @@ class StableBrowser {
|
|
|
2201
2375
|
});
|
|
2202
2376
|
}
|
|
2203
2377
|
}
|
|
2204
|
-
async
|
|
2378
|
+
async verifyPageTitle(title, options = {}, world = null) {
|
|
2379
|
+
const startTime = Date.now();
|
|
2380
|
+
let error = null;
|
|
2381
|
+
let screenshotId = null;
|
|
2382
|
+
let screenshotPath = null;
|
|
2383
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2384
|
+
const info = {};
|
|
2385
|
+
info.log = "***** verify page title " + title + " *****\n";
|
|
2386
|
+
info.operation = "verifyPageTitle";
|
|
2387
|
+
const newValue = await this._replaceWithLocalData(title, world);
|
|
2388
|
+
if (newValue !== title) {
|
|
2389
|
+
this.logger.info(title + "=" + newValue);
|
|
2390
|
+
title = newValue;
|
|
2391
|
+
}
|
|
2392
|
+
info.title = title;
|
|
2393
|
+
try {
|
|
2394
|
+
for (let i = 0; i < 30; i++) {
|
|
2395
|
+
const foundTitle = await this.page.title();
|
|
2396
|
+
if (!foundTitle.includes(title)) {
|
|
2397
|
+
if (i === 29) {
|
|
2398
|
+
throw new Error(`url ${foundTitle} doesn't contain ${title}`);
|
|
2399
|
+
}
|
|
2400
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2401
|
+
continue;
|
|
2402
|
+
}
|
|
2403
|
+
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2404
|
+
return info;
|
|
2405
|
+
}
|
|
2406
|
+
}
|
|
2407
|
+
catch (e) {
|
|
2408
|
+
//await this.closeUnexpectedPopups();
|
|
2409
|
+
this.logger.error("verify page title failed " + info.log);
|
|
2410
|
+
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2411
|
+
info.screenshotPath = screenshotPath;
|
|
2412
|
+
Object.assign(e, { info: info });
|
|
2413
|
+
error = e;
|
|
2414
|
+
// throw e;
|
|
2415
|
+
await _commandError({ text: "verifyPageTitle", operation: "verifyPageTitle", title, info, throwError: true }, e, this);
|
|
2416
|
+
}
|
|
2417
|
+
finally {
|
|
2418
|
+
const endTime = Date.now();
|
|
2419
|
+
_reportToWorld(world, {
|
|
2420
|
+
type: Types.VERIFY_PAGE_PATH,
|
|
2421
|
+
text: "Verify page title",
|
|
2422
|
+
_text: "Verify the page title contains " + title,
|
|
2423
|
+
screenshotId,
|
|
2424
|
+
result: error
|
|
2425
|
+
? {
|
|
2426
|
+
status: "FAILED",
|
|
2427
|
+
startTime,
|
|
2428
|
+
endTime,
|
|
2429
|
+
message: error?.message,
|
|
2430
|
+
}
|
|
2431
|
+
: {
|
|
2432
|
+
status: "PASSED",
|
|
2433
|
+
startTime,
|
|
2434
|
+
endTime,
|
|
2435
|
+
},
|
|
2436
|
+
info: info,
|
|
2437
|
+
});
|
|
2438
|
+
}
|
|
2439
|
+
}
|
|
2440
|
+
async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state, partial = true, ignoreCase = false) {
|
|
2205
2441
|
const frames = this.page.frames();
|
|
2206
2442
|
let results = [];
|
|
2207
|
-
let ignoreCase = false;
|
|
2443
|
+
// let ignoreCase = false;
|
|
2208
2444
|
for (let i = 0; i < frames.length; i++) {
|
|
2209
2445
|
if (dateAlternatives.date) {
|
|
2210
2446
|
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2211
|
-
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false,
|
|
2447
|
+
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, partial, ignoreCase, {});
|
|
2212
2448
|
result.frame = frames[i];
|
|
2213
2449
|
results.push(result);
|
|
2214
2450
|
}
|
|
2215
2451
|
}
|
|
2216
2452
|
else if (numberAlternatives.number) {
|
|
2217
2453
|
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2218
|
-
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false,
|
|
2454
|
+
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, partial, ignoreCase, {});
|
|
2219
2455
|
result.frame = frames[i];
|
|
2220
2456
|
results.push(result);
|
|
2221
2457
|
}
|
|
2222
2458
|
}
|
|
2223
2459
|
else {
|
|
2224
|
-
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false,
|
|
2460
|
+
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, partial, ignoreCase, {});
|
|
2225
2461
|
result.frame = frames[i];
|
|
2226
2462
|
results.push(result);
|
|
2227
2463
|
}
|
|
@@ -2240,11 +2476,15 @@ class StableBrowser {
|
|
|
2240
2476
|
scroll: false,
|
|
2241
2477
|
highlight: false,
|
|
2242
2478
|
type: Types.VERIFY_PAGE_CONTAINS_TEXT,
|
|
2243
|
-
text: `Verify text exists in page`,
|
|
2479
|
+
text: `Verify the text '${maskValue(text)}' exists in page`,
|
|
2480
|
+
_text: `Verify the text '${text}' exists in page`,
|
|
2244
2481
|
operation: "verifyTextExistInPage",
|
|
2245
2482
|
log: "***** verify text " + text + " exists in page *****\n",
|
|
2246
2483
|
};
|
|
2247
|
-
|
|
2484
|
+
if (testForRegex(text)) {
|
|
2485
|
+
text = text.replace(/\\"/g, '"');
|
|
2486
|
+
}
|
|
2487
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2248
2488
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2249
2489
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2250
2490
|
if (newValue !== text) {
|
|
@@ -2257,7 +2497,15 @@ class StableBrowser {
|
|
|
2257
2497
|
await _preCommand(state, this);
|
|
2258
2498
|
state.info.text = text;
|
|
2259
2499
|
while (true) {
|
|
2260
|
-
|
|
2500
|
+
let resultWithElementsFound = {
|
|
2501
|
+
length: 0,
|
|
2502
|
+
};
|
|
2503
|
+
try {
|
|
2504
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2505
|
+
}
|
|
2506
|
+
catch (error) {
|
|
2507
|
+
// ignore
|
|
2508
|
+
}
|
|
2261
2509
|
if (resultWithElementsFound.length === 0) {
|
|
2262
2510
|
if (Date.now() - state.startTime > timeout) {
|
|
2263
2511
|
throw new Error(`Text ${text} not found in page`);
|
|
@@ -2265,35 +2513,40 @@ class StableBrowser {
|
|
|
2265
2513
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2266
2514
|
continue;
|
|
2267
2515
|
}
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2516
|
+
try {
|
|
2517
|
+
if (resultWithElementsFound[0].randomToken) {
|
|
2518
|
+
const frame = resultWithElementsFound[0].frame;
|
|
2519
|
+
const dataAttribute = `[data-blinq-id-${resultWithElementsFound[0].randomToken}]`;
|
|
2520
|
+
await this._highlightElements(frame, dataAttribute);
|
|
2521
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
2522
|
+
// console.log(`Highlighting for verify text is found while running from recorder`);
|
|
2523
|
+
// this._highlightElements(frame, dataAttribute).then(async () => {
|
|
2524
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2525
|
+
// this._unhighlightElements(frame, dataAttribute)
|
|
2526
|
+
// .then(async () => {
|
|
2527
|
+
// console.log(`Unhighlighted frame dataAttribute successfully`);
|
|
2528
|
+
// })
|
|
2529
|
+
// .catch(
|
|
2530
|
+
// (e) => {}
|
|
2531
|
+
// console.error(e)
|
|
2532
|
+
// );
|
|
2533
|
+
// });
|
|
2534
|
+
// }
|
|
2535
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2536
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2537
|
+
// await this._unhighlightElements(frame, dataAttribute);
|
|
2538
|
+
if (element) {
|
|
2539
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2540
|
+
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2541
|
+
// await _screenshot(state, this, element);
|
|
2542
|
+
}
|
|
2293
2543
|
}
|
|
2544
|
+
await _screenshot(state, this);
|
|
2545
|
+
return state.info;
|
|
2546
|
+
}
|
|
2547
|
+
catch (error) {
|
|
2548
|
+
console.error(error);
|
|
2294
2549
|
}
|
|
2295
|
-
await _screenshot(state, this);
|
|
2296
|
-
return state.info;
|
|
2297
2550
|
}
|
|
2298
2551
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2299
2552
|
}
|
|
@@ -2301,7 +2554,7 @@ class StableBrowser {
|
|
|
2301
2554
|
await _commandError(state, e, this);
|
|
2302
2555
|
}
|
|
2303
2556
|
finally {
|
|
2304
|
-
_commandFinally(state, this);
|
|
2557
|
+
await _commandFinally(state, this);
|
|
2305
2558
|
}
|
|
2306
2559
|
}
|
|
2307
2560
|
async waitForTextToDisappear(text, options = {}, world = null) {
|
|
@@ -2314,11 +2567,15 @@ class StableBrowser {
|
|
|
2314
2567
|
scroll: false,
|
|
2315
2568
|
highlight: false,
|
|
2316
2569
|
type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
|
|
2317
|
-
text: `Verify text does not exist in page`,
|
|
2570
|
+
text: `Verify the text '${maskValue(text)}' does not exist in page`,
|
|
2571
|
+
_text: `Verify the text '${text}' does not exist in page`,
|
|
2318
2572
|
operation: "verifyTextNotExistInPage",
|
|
2319
2573
|
log: "***** verify text " + text + " does not exist in page *****\n",
|
|
2320
2574
|
};
|
|
2321
|
-
|
|
2575
|
+
if (testForRegex(text)) {
|
|
2576
|
+
text = text.replace(/\\"/g, '"');
|
|
2577
|
+
}
|
|
2578
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2322
2579
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2323
2580
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2324
2581
|
if (newValue !== text) {
|
|
@@ -2330,8 +2587,16 @@ class StableBrowser {
|
|
|
2330
2587
|
try {
|
|
2331
2588
|
await _preCommand(state, this);
|
|
2332
2589
|
state.info.text = text;
|
|
2590
|
+
let resultWithElementsFound = {
|
|
2591
|
+
length: null, // initial cannot be 0
|
|
2592
|
+
};
|
|
2333
2593
|
while (true) {
|
|
2334
|
-
|
|
2594
|
+
try {
|
|
2595
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2596
|
+
}
|
|
2597
|
+
catch (error) {
|
|
2598
|
+
// ignore
|
|
2599
|
+
}
|
|
2335
2600
|
if (resultWithElementsFound.length === 0) {
|
|
2336
2601
|
await _screenshot(state, this);
|
|
2337
2602
|
return state.info;
|
|
@@ -2346,7 +2611,7 @@ class StableBrowser {
|
|
|
2346
2611
|
await _commandError(state, e, this);
|
|
2347
2612
|
}
|
|
2348
2613
|
finally {
|
|
2349
|
-
_commandFinally(state, this);
|
|
2614
|
+
await _commandFinally(state, this);
|
|
2350
2615
|
}
|
|
2351
2616
|
}
|
|
2352
2617
|
async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
|
|
@@ -2361,10 +2626,11 @@ class StableBrowser {
|
|
|
2361
2626
|
highlight: false,
|
|
2362
2627
|
type: Types.VERIFY_TEXT_WITH_RELATION,
|
|
2363
2628
|
text: `Verify text with relation to another text`,
|
|
2629
|
+
_text: "Search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found",
|
|
2364
2630
|
operation: "verify_text_with_relation",
|
|
2365
2631
|
log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
|
|
2366
2632
|
};
|
|
2367
|
-
const timeout = this.
|
|
2633
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2368
2634
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2369
2635
|
let newValue = await this._replaceWithLocalData(textAnchor, world);
|
|
2370
2636
|
if (newValue !== textAnchor) {
|
|
@@ -2382,8 +2648,16 @@ class StableBrowser {
|
|
|
2382
2648
|
try {
|
|
2383
2649
|
await _preCommand(state, this);
|
|
2384
2650
|
state.info.text = textToVerify;
|
|
2651
|
+
let resultWithElementsFound = {
|
|
2652
|
+
length: 0,
|
|
2653
|
+
};
|
|
2385
2654
|
while (true) {
|
|
2386
|
-
|
|
2655
|
+
try {
|
|
2656
|
+
resultWithElementsFound = await this.findTextInAllFrames(findDateAlternatives(textAnchor), findNumberAlternatives(textAnchor), textAnchor, state, false);
|
|
2657
|
+
}
|
|
2658
|
+
catch (error) {
|
|
2659
|
+
// ignore
|
|
2660
|
+
}
|
|
2387
2661
|
if (resultWithElementsFound.length === 0) {
|
|
2388
2662
|
if (Date.now() - state.startTime > timeout) {
|
|
2389
2663
|
throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
|
|
@@ -2391,51 +2665,56 @@ class StableBrowser {
|
|
|
2391
2665
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2392
2666
|
continue;
|
|
2393
2667
|
}
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
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
|
-
|
|
2668
|
+
try {
|
|
2669
|
+
for (let i = 0; i < resultWithElementsFound.length; i++) {
|
|
2670
|
+
foundAncore = true;
|
|
2671
|
+
const result = resultWithElementsFound[i];
|
|
2672
|
+
const token = result.randomToken;
|
|
2673
|
+
const frame = result.frame;
|
|
2674
|
+
let css = `[data-blinq-id-${token}]`;
|
|
2675
|
+
const climbArray1 = [];
|
|
2676
|
+
for (let i = 0; i < climb; i++) {
|
|
2677
|
+
climbArray1.push("..");
|
|
2678
|
+
}
|
|
2679
|
+
let climbXpath = "xpath=" + climbArray1.join("/");
|
|
2680
|
+
css = css + " >> " + climbXpath;
|
|
2681
|
+
const count = await frame.locator(css).count();
|
|
2682
|
+
for (let j = 0; j < count; j++) {
|
|
2683
|
+
const continer = await frame.locator(css).nth(j);
|
|
2684
|
+
const result = await this._locateElementByText(continer, textToVerify, "*:not(script, style, head)", false, true, true, {});
|
|
2685
|
+
if (result.elementCount > 0) {
|
|
2686
|
+
const dataAttribute = "[data-blinq-id-" + result.randomToken + "]";
|
|
2687
|
+
await this._highlightElements(frame, dataAttribute);
|
|
2688
|
+
//const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
|
|
2689
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
2690
|
+
// console.log(`Highlighting for vtrt while running from recorder`);
|
|
2691
|
+
// this._highlightElements(frame, dataAttribute)
|
|
2692
|
+
// .then(async () => {
|
|
2693
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2694
|
+
// this._unhighlightElements(frame, dataAttribute).then(
|
|
2695
|
+
// () => {}
|
|
2696
|
+
// console.log(`Unhighlighting vrtr in recorder is successful`)
|
|
2697
|
+
// );
|
|
2698
|
+
// })
|
|
2699
|
+
// .catch(e);
|
|
2700
|
+
// }
|
|
2701
|
+
//await this._highlightElements(frame, cssAnchor);
|
|
2702
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2703
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2704
|
+
// await this._unhighlightElements(frame, dataAttribute);
|
|
2705
|
+
if (element) {
|
|
2706
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2707
|
+
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2708
|
+
}
|
|
2709
|
+
await _screenshot(state, this);
|
|
2710
|
+
return state.info;
|
|
2433
2711
|
}
|
|
2434
|
-
await _screenshot(state, this);
|
|
2435
|
-
return state.info;
|
|
2436
2712
|
}
|
|
2437
2713
|
}
|
|
2438
2714
|
}
|
|
2715
|
+
catch (error) {
|
|
2716
|
+
console.error(error);
|
|
2717
|
+
}
|
|
2439
2718
|
}
|
|
2440
2719
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2441
2720
|
}
|
|
@@ -2443,9 +2722,33 @@ class StableBrowser {
|
|
|
2443
2722
|
await _commandError(state, e, this);
|
|
2444
2723
|
}
|
|
2445
2724
|
finally {
|
|
2446
|
-
_commandFinally(state, this);
|
|
2725
|
+
await _commandFinally(state, this);
|
|
2447
2726
|
}
|
|
2448
2727
|
}
|
|
2728
|
+
async findRelatedTextInAllFrames(textAnchor, climb, textToVerify, params = {}, options = {}, world = null) {
|
|
2729
|
+
const frames = this.page.frames();
|
|
2730
|
+
let results = [];
|
|
2731
|
+
let ignoreCase = false;
|
|
2732
|
+
for (let i = 0; i < frames.length; i++) {
|
|
2733
|
+
const result = await this._locateElementByText(frames[i], textAnchor, "*:not(script, style, head)", false, true, ignoreCase, {});
|
|
2734
|
+
result.frame = frames[i];
|
|
2735
|
+
const climbArray = [];
|
|
2736
|
+
for (let i = 0; i < climb; i++) {
|
|
2737
|
+
climbArray.push("..");
|
|
2738
|
+
}
|
|
2739
|
+
let climbXpath = "xpath=" + climbArray.join("/");
|
|
2740
|
+
const newLocator = `[data-blinq-id-${result.randomToken}] ${climb > 0 ? ">> " + climbXpath : ""} >> internal:text=${testForRegex(textToVerify) ? textToVerify : unEscapeString(textToVerify)}`;
|
|
2741
|
+
const count = await frames[i].locator(newLocator).count();
|
|
2742
|
+
if (count > 0) {
|
|
2743
|
+
result.elementCount = count;
|
|
2744
|
+
result.locator = newLocator;
|
|
2745
|
+
results.push(result);
|
|
2746
|
+
}
|
|
2747
|
+
}
|
|
2748
|
+
// state.info.results = results;
|
|
2749
|
+
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2750
|
+
return resultWithElementsFound;
|
|
2751
|
+
}
|
|
2449
2752
|
async visualVerification(text, options = {}, world = null) {
|
|
2450
2753
|
const startTime = Date.now();
|
|
2451
2754
|
let error = null;
|
|
@@ -2464,10 +2767,13 @@ class StableBrowser {
|
|
|
2464
2767
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2465
2768
|
info.screenshotPath = screenshotPath;
|
|
2466
2769
|
const screenshot = await this.takeScreenshot();
|
|
2467
|
-
|
|
2468
|
-
method: "
|
|
2770
|
+
let request = {
|
|
2771
|
+
method: "post",
|
|
2772
|
+
maxBodyLength: Infinity,
|
|
2469
2773
|
url: `${serviceUrl}/api/runs/screenshots/validate-screenshot`,
|
|
2470
2774
|
headers: {
|
|
2775
|
+
"x-bvt-project-id": path.basename(this.project_path),
|
|
2776
|
+
"x-source": "aaa",
|
|
2471
2777
|
"Content-Type": "application/json",
|
|
2472
2778
|
Authorization: `Bearer ${process.env.TOKEN}`,
|
|
2473
2779
|
},
|
|
@@ -2476,7 +2782,7 @@ class StableBrowser {
|
|
|
2476
2782
|
screenshot: screenshot,
|
|
2477
2783
|
}),
|
|
2478
2784
|
};
|
|
2479
|
-
|
|
2785
|
+
const result = await axios.request(request);
|
|
2480
2786
|
if (result.data.status !== true) {
|
|
2481
2787
|
throw new Error("Visual validation failed");
|
|
2482
2788
|
}
|
|
@@ -2504,6 +2810,7 @@ class StableBrowser {
|
|
|
2504
2810
|
_reportToWorld(world, {
|
|
2505
2811
|
type: Types.VERIFY_VISUAL,
|
|
2506
2812
|
text: "Visual verification",
|
|
2813
|
+
_text: "Visual verification of " + text,
|
|
2507
2814
|
screenshotId,
|
|
2508
2815
|
result: error
|
|
2509
2816
|
? {
|
|
@@ -2770,6 +3077,15 @@ class StableBrowser {
|
|
|
2770
3077
|
}
|
|
2771
3078
|
return timeout;
|
|
2772
3079
|
}
|
|
3080
|
+
_getFindElementTimeout(options) {
|
|
3081
|
+
if (options && options.timeout) {
|
|
3082
|
+
return options.timeout;
|
|
3083
|
+
}
|
|
3084
|
+
if (this.configuration.find_element_timeout) {
|
|
3085
|
+
return this.configuration.find_element_timeout;
|
|
3086
|
+
}
|
|
3087
|
+
return 30000;
|
|
3088
|
+
}
|
|
2773
3089
|
async saveStoreState(path = null, world = null) {
|
|
2774
3090
|
const storageState = await this.page.context().storageState();
|
|
2775
3091
|
//const testDataFile = _getDataFile(world, this.context, this);
|
|
@@ -2781,6 +3097,12 @@ class StableBrowser {
|
|
|
2781
3097
|
await this.setTestData({ storageState: storageState }, world);
|
|
2782
3098
|
}
|
|
2783
3099
|
}
|
|
3100
|
+
async restoreSaveState(path = null, world = null) {
|
|
3101
|
+
await refreshBrowser(this, path, world);
|
|
3102
|
+
this.registerEventListeners(this.context);
|
|
3103
|
+
registerNetworkEvents(this.world, this, this.context, this.page);
|
|
3104
|
+
registerDownloadEvent(this.page, this.world, this.context);
|
|
3105
|
+
}
|
|
2784
3106
|
async waitForPageLoad(options = {}, world = null) {
|
|
2785
3107
|
let timeout = this._getLoadTimeout(options);
|
|
2786
3108
|
const promiseArray = [];
|
|
@@ -2848,6 +3170,7 @@ class StableBrowser {
|
|
|
2848
3170
|
highlight: false,
|
|
2849
3171
|
type: Types.CLOSE_PAGE,
|
|
2850
3172
|
text: `Close page`,
|
|
3173
|
+
_text: `Close the page`,
|
|
2851
3174
|
operation: "closePage",
|
|
2852
3175
|
log: "***** close page *****\n",
|
|
2853
3176
|
throwError: false,
|
|
@@ -2861,11 +3184,98 @@ class StableBrowser {
|
|
|
2861
3184
|
await _commandError(state, e, this);
|
|
2862
3185
|
}
|
|
2863
3186
|
finally {
|
|
2864
|
-
_commandFinally(state, this);
|
|
3187
|
+
await _commandFinally(state, this);
|
|
3188
|
+
}
|
|
3189
|
+
}
|
|
3190
|
+
async tableCellOperation(headerText, rowText, options, _params, world = null) {
|
|
3191
|
+
let operation = null;
|
|
3192
|
+
if (!options || !options.operation) {
|
|
3193
|
+
throw new Error("operation is not defined");
|
|
3194
|
+
}
|
|
3195
|
+
operation = options.operation;
|
|
3196
|
+
// validate operation is one of the supported operations
|
|
3197
|
+
if (operation != "click" && operation != "hover+click") {
|
|
3198
|
+
throw new Error("operation is not supported");
|
|
3199
|
+
}
|
|
3200
|
+
const state = {
|
|
3201
|
+
options,
|
|
3202
|
+
world,
|
|
3203
|
+
locate: false,
|
|
3204
|
+
scroll: false,
|
|
3205
|
+
highlight: false,
|
|
3206
|
+
type: Types.TABLE_OPERATION,
|
|
3207
|
+
text: `Table operation`,
|
|
3208
|
+
_text: `Table ${operation} operation`,
|
|
3209
|
+
operation: operation,
|
|
3210
|
+
log: "***** Table operation *****\n",
|
|
3211
|
+
};
|
|
3212
|
+
const timeout = this._getFindElementTimeout(options);
|
|
3213
|
+
try {
|
|
3214
|
+
await _preCommand(state, this);
|
|
3215
|
+
const start = Date.now();
|
|
3216
|
+
let cellArea = null;
|
|
3217
|
+
while (true) {
|
|
3218
|
+
try {
|
|
3219
|
+
cellArea = await _findCellArea(headerText, rowText, this, state);
|
|
3220
|
+
if (cellArea) {
|
|
3221
|
+
break;
|
|
3222
|
+
}
|
|
3223
|
+
}
|
|
3224
|
+
catch (e) {
|
|
3225
|
+
// ignore
|
|
3226
|
+
}
|
|
3227
|
+
if (Date.now() - start > timeout) {
|
|
3228
|
+
throw new Error(`Cell not found in table`);
|
|
3229
|
+
}
|
|
3230
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
3231
|
+
}
|
|
3232
|
+
switch (operation) {
|
|
3233
|
+
case "click":
|
|
3234
|
+
if (!options.css) {
|
|
3235
|
+
// will click in the center of the cell
|
|
3236
|
+
let xOffset = 0;
|
|
3237
|
+
let yOffset = 0;
|
|
3238
|
+
if (options.xOffset) {
|
|
3239
|
+
xOffset = options.xOffset;
|
|
3240
|
+
}
|
|
3241
|
+
if (options.yOffset) {
|
|
3242
|
+
yOffset = options.yOffset;
|
|
3243
|
+
}
|
|
3244
|
+
await this.page.mouse.click(cellArea.x + cellArea.width / 2 + xOffset, cellArea.y + cellArea.height / 2 + yOffset);
|
|
3245
|
+
}
|
|
3246
|
+
else {
|
|
3247
|
+
const results = await findElementsInArea(options.css, cellArea, this, options);
|
|
3248
|
+
if (results.length === 0) {
|
|
3249
|
+
throw new Error(`Element not found in cell area`);
|
|
3250
|
+
}
|
|
3251
|
+
state.element = results[0];
|
|
3252
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
3253
|
+
}
|
|
3254
|
+
break;
|
|
3255
|
+
case "hover+click":
|
|
3256
|
+
if (!options.css) {
|
|
3257
|
+
throw new Error("css is not defined");
|
|
3258
|
+
}
|
|
3259
|
+
const results = await findElementsInArea(options.css, cellArea, this, options);
|
|
3260
|
+
if (results.length === 0) {
|
|
3261
|
+
throw new Error(`Element not found in cell area`);
|
|
3262
|
+
}
|
|
3263
|
+
state.element = results[0];
|
|
3264
|
+
await performAction("hover+click", state.element, options, this, state, _params);
|
|
3265
|
+
break;
|
|
3266
|
+
default:
|
|
3267
|
+
throw new Error("operation is not supported");
|
|
3268
|
+
}
|
|
3269
|
+
}
|
|
3270
|
+
catch (e) {
|
|
3271
|
+
await _commandError(state, e, this);
|
|
3272
|
+
}
|
|
3273
|
+
finally {
|
|
3274
|
+
await _commandFinally(state, this);
|
|
2865
3275
|
}
|
|
2866
3276
|
}
|
|
2867
3277
|
saveTestDataAsGlobal(options, world) {
|
|
2868
|
-
const dataFile =
|
|
3278
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
2869
3279
|
process.env.GLOBAL_TEST_DATA_FILE = dataFile;
|
|
2870
3280
|
this.logger.info("Save the scenario test data as global for the following scenarios.");
|
|
2871
3281
|
}
|
|
@@ -2895,6 +3305,7 @@ class StableBrowser {
|
|
|
2895
3305
|
_reportToWorld(world, {
|
|
2896
3306
|
type: Types.SET_VIEWPORT,
|
|
2897
3307
|
text: "set viewport size to " + width + "x" + hight,
|
|
3308
|
+
_text: "Set the viewport size to " + width + "x" + hight,
|
|
2898
3309
|
screenshotId,
|
|
2899
3310
|
result: error
|
|
2900
3311
|
? {
|
|
@@ -2965,7 +3376,39 @@ class StableBrowser {
|
|
|
2965
3376
|
console.log("#-#");
|
|
2966
3377
|
}
|
|
2967
3378
|
}
|
|
3379
|
+
async beforeScenario(world, scenario) {
|
|
3380
|
+
this.beforeScenarioCalled = true;
|
|
3381
|
+
if (scenario && scenario.pickle && scenario.pickle.name) {
|
|
3382
|
+
this.scenarioName = scenario.pickle.name;
|
|
3383
|
+
}
|
|
3384
|
+
if (scenario && scenario.gherkinDocument && scenario.gherkinDocument.feature) {
|
|
3385
|
+
this.featureName = scenario.gherkinDocument.feature.name;
|
|
3386
|
+
}
|
|
3387
|
+
if (this.context) {
|
|
3388
|
+
this.context.examplesRow = extractStepExampleParameters(scenario);
|
|
3389
|
+
}
|
|
3390
|
+
if (this.tags === null && scenario && scenario.pickle && scenario.pickle.tags) {
|
|
3391
|
+
this.tags = scenario.pickle.tags.map((tag) => tag.name);
|
|
3392
|
+
// check if @global_test_data tag is present
|
|
3393
|
+
if (this.tags.includes("@global_test_data")) {
|
|
3394
|
+
this.saveTestDataAsGlobal({}, world);
|
|
3395
|
+
}
|
|
3396
|
+
}
|
|
3397
|
+
// update test data based on feature/scenario
|
|
3398
|
+
let envName = null;
|
|
3399
|
+
if (this.context && this.context.environment) {
|
|
3400
|
+
envName = this.context.environment.name;
|
|
3401
|
+
}
|
|
3402
|
+
if (!process.env.TEMP_RUN) {
|
|
3403
|
+
await getTestData(envName, world, undefined, this.featureName, this.scenarioName);
|
|
3404
|
+
}
|
|
3405
|
+
await loadBrunoParams(this.context, this.context.environment.name);
|
|
3406
|
+
}
|
|
3407
|
+
async afterScenario(world, scenario) { }
|
|
2968
3408
|
async beforeStep(world, step) {
|
|
3409
|
+
if (!this.beforeScenarioCalled) {
|
|
3410
|
+
this.beforeScenario(world, step);
|
|
3411
|
+
}
|
|
2969
3412
|
if (this.stepIndex === undefined) {
|
|
2970
3413
|
this.stepIndex = 0;
|
|
2971
3414
|
}
|
|
@@ -2982,22 +3425,48 @@ class StableBrowser {
|
|
|
2982
3425
|
else {
|
|
2983
3426
|
this.stepName = "step " + this.stepIndex;
|
|
2984
3427
|
}
|
|
2985
|
-
if (this.context) {
|
|
2986
|
-
this.context.examplesRow = extractStepExampleParameters(step);
|
|
2987
|
-
}
|
|
2988
3428
|
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
2989
3429
|
if (this.context.browserObject.context) {
|
|
2990
3430
|
await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
|
|
2991
3431
|
}
|
|
2992
3432
|
}
|
|
2993
|
-
if (this.
|
|
2994
|
-
this.
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
3433
|
+
if (this.initSnapshotTaken === false) {
|
|
3434
|
+
this.initSnapshotTaken = true;
|
|
3435
|
+
if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
|
|
3436
|
+
const snapshot = await this.getAriaSnapshot();
|
|
3437
|
+
if (snapshot) {
|
|
3438
|
+
await world.attach(JSON.stringify(snapshot), "application/json+snapshot-before");
|
|
3439
|
+
}
|
|
2998
3440
|
}
|
|
2999
3441
|
}
|
|
3000
3442
|
}
|
|
3443
|
+
async getAriaSnapshot() {
|
|
3444
|
+
try {
|
|
3445
|
+
// find the page url
|
|
3446
|
+
const url = await this.page.url();
|
|
3447
|
+
// extract the path from the url
|
|
3448
|
+
const path = new URL(url).pathname;
|
|
3449
|
+
// get the page title
|
|
3450
|
+
const title = await this.page.title();
|
|
3451
|
+
// go over other frams
|
|
3452
|
+
const frames = this.page.frames();
|
|
3453
|
+
const snapshots = [];
|
|
3454
|
+
const content = [`- path: ${path}`, `- title: ${title}`];
|
|
3455
|
+
const timeout = this.configuration.ariaSnapshotTimeout ? this.configuration.ariaSnapshotTimeout : 3000;
|
|
3456
|
+
for (let i = 0; i < frames.length; i++) {
|
|
3457
|
+
content.push(`- frame: ${i}`);
|
|
3458
|
+
const frame = frames[i];
|
|
3459
|
+
const snapshot = await frame.locator("body").ariaSnapshot({ timeout });
|
|
3460
|
+
content.push(snapshot);
|
|
3461
|
+
}
|
|
3462
|
+
return content.join("\n");
|
|
3463
|
+
}
|
|
3464
|
+
catch (e) {
|
|
3465
|
+
console.log("Error in getAriaSnapshot");
|
|
3466
|
+
console.debug(e);
|
|
3467
|
+
}
|
|
3468
|
+
return null;
|
|
3469
|
+
}
|
|
3001
3470
|
async afterStep(world, step) {
|
|
3002
3471
|
this.stepName = null;
|
|
3003
3472
|
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
@@ -3005,11 +3474,25 @@ class StableBrowser {
|
|
|
3005
3474
|
await this.context.browserObject.context.tracing.stopChunk({
|
|
3006
3475
|
path: path.join(this.context.browserObject.traceFolder, `trace-${this.stepIndex}.zip`),
|
|
3007
3476
|
});
|
|
3477
|
+
if (world && world.attach) {
|
|
3478
|
+
await world.attach(JSON.stringify({
|
|
3479
|
+
type: "trace",
|
|
3480
|
+
traceFilePath: `trace-${this.stepIndex}.zip`,
|
|
3481
|
+
}), "application/json+trace");
|
|
3482
|
+
}
|
|
3483
|
+
// console.log("trace file created", `trace-${this.stepIndex}.zip`);
|
|
3008
3484
|
}
|
|
3009
3485
|
}
|
|
3010
3486
|
if (this.context) {
|
|
3011
3487
|
this.context.examplesRow = null;
|
|
3012
3488
|
}
|
|
3489
|
+
if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
|
|
3490
|
+
const snapshot = await this.getAriaSnapshot();
|
|
3491
|
+
if (snapshot) {
|
|
3492
|
+
const obj = {};
|
|
3493
|
+
await world.attach(JSON.stringify(snapshot), "application/json+snapshot-after");
|
|
3494
|
+
}
|
|
3495
|
+
}
|
|
3013
3496
|
}
|
|
3014
3497
|
}
|
|
3015
3498
|
function createTimedPromise(promise, label) {
|