automation_model 1.0.599-dev → 1.0.599-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 +4 -2
- package/lib/auto_page.js +140 -8
- 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 +338 -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/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/lib/init_browser.d.ts +3 -2
- package/lib/init_browser.js +64 -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 +20 -2
- package/lib/stable_browser.js +656 -215
- 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 -3
- package/lib/utils.js +209 -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, } 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,7 @@ 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",
|
|
54
61
|
};
|
|
55
62
|
export const apps = {};
|
|
56
63
|
const formatElementName = (elementName) => {
|
|
@@ -69,6 +76,7 @@ class StableBrowser {
|
|
|
69
76
|
appName = "main";
|
|
70
77
|
tags = null;
|
|
71
78
|
isRecording = false;
|
|
79
|
+
initSnapshotTaken = false;
|
|
72
80
|
constructor(browser, page, logger = null, context = null, world = null) {
|
|
73
81
|
this.browser = browser;
|
|
74
82
|
this.page = page;
|
|
@@ -175,6 +183,30 @@ class StableBrowser {
|
|
|
175
183
|
await this.waitForPageLoad();
|
|
176
184
|
}
|
|
177
185
|
}
|
|
186
|
+
async switchTab(tabTitleOrIndex) {
|
|
187
|
+
// first check if the tabNameOrIndex is a number
|
|
188
|
+
let index = parseInt(tabTitleOrIndex);
|
|
189
|
+
if (!isNaN(index)) {
|
|
190
|
+
if (index >= 0 && index < this.context.pages.length) {
|
|
191
|
+
this.page = this.context.pages[index];
|
|
192
|
+
this.context.page = this.page;
|
|
193
|
+
await this.page.bringToFront();
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
// if the tabNameOrIndex is a string, find the tab by name
|
|
198
|
+
for (let i = 0; i < this.context.pages.length; i++) {
|
|
199
|
+
let page = this.context.pages[i];
|
|
200
|
+
let title = await page.title();
|
|
201
|
+
if (title.includes(tabTitleOrIndex)) {
|
|
202
|
+
this.page = page;
|
|
203
|
+
this.context.page = this.page;
|
|
204
|
+
await this.page.bringToFront();
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
throw new Error("Tab not found: " + tabTitleOrIndex);
|
|
209
|
+
}
|
|
178
210
|
registerConsoleLogListener(page, context) {
|
|
179
211
|
if (!this.context.webLogger) {
|
|
180
212
|
this.context.webLogger = [];
|
|
@@ -239,6 +271,9 @@ class StableBrowser {
|
|
|
239
271
|
// await closeUnexpectedPopups(this.page);
|
|
240
272
|
// }
|
|
241
273
|
async goto(url, world = null) {
|
|
274
|
+
if (!url) {
|
|
275
|
+
throw new Error("url is null, verify that the environment file is correct");
|
|
276
|
+
}
|
|
242
277
|
if (!url.startsWith("http")) {
|
|
243
278
|
url = "https://" + url;
|
|
244
279
|
}
|
|
@@ -267,7 +302,7 @@ class StableBrowser {
|
|
|
267
302
|
_commandError(state, error, this);
|
|
268
303
|
}
|
|
269
304
|
finally {
|
|
270
|
-
_commandFinally(state, this);
|
|
305
|
+
await _commandFinally(state, this);
|
|
271
306
|
}
|
|
272
307
|
}
|
|
273
308
|
async _getLocator(locator, scope, _params) {
|
|
@@ -348,7 +383,7 @@ class StableBrowser {
|
|
|
348
383
|
return resultCss;
|
|
349
384
|
}
|
|
350
385
|
async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, ignoreCase = true, _params) {
|
|
351
|
-
const query = _convertToRegexQuery(text1, regex1, !partial1, ignoreCase)
|
|
386
|
+
const query = `${_convertToRegexQuery(text1, regex1, !partial1, ignoreCase)}`;
|
|
352
387
|
const locator = scope.locator(query);
|
|
353
388
|
const count = await locator.count();
|
|
354
389
|
if (!tag1) {
|
|
@@ -368,6 +403,12 @@ class StableBrowser {
|
|
|
368
403
|
if (!el.setAttribute) {
|
|
369
404
|
el = el.parentElement;
|
|
370
405
|
}
|
|
406
|
+
// remove any attributes start with data-blinq-id
|
|
407
|
+
// for (let i = 0; i < el.attributes.length; i++) {
|
|
408
|
+
// if (el.attributes[i].name.startsWith("data-blinq-id")) {
|
|
409
|
+
// el.removeAttribute(el.attributes[i].name);
|
|
410
|
+
// }
|
|
411
|
+
// }
|
|
371
412
|
el.setAttribute("data-blinq-id-" + randomToken, "");
|
|
372
413
|
return true;
|
|
373
414
|
}, [tag1, randomToken]))) {
|
|
@@ -554,9 +595,24 @@ class StableBrowser {
|
|
|
554
595
|
element.evaluate((el, randomToken) => {
|
|
555
596
|
el.setAttribute("data-blinq-id-" + randomToken, "");
|
|
556
597
|
}, randomToken);
|
|
598
|
+
// if (element._frame) {
|
|
599
|
+
// return element;
|
|
600
|
+
// }
|
|
557
601
|
const scope = element._frame ?? element.page();
|
|
558
|
-
|
|
559
|
-
|
|
602
|
+
let newElementSelector = "[data-blinq-id-" + randomToken + "]";
|
|
603
|
+
let prefixSelector = "";
|
|
604
|
+
const frameControlSelector = " >> internal:control=enter-frame";
|
|
605
|
+
const frameSelectorIndex = element._selector.lastIndexOf(frameControlSelector);
|
|
606
|
+
if (frameSelectorIndex !== -1) {
|
|
607
|
+
// remove everything after the >> internal:control=enter-frame
|
|
608
|
+
const frameSelector = element._selector.substring(0, frameSelectorIndex);
|
|
609
|
+
prefixSelector = frameSelector + " >> internal:control=enter-frame >>";
|
|
610
|
+
}
|
|
611
|
+
// if (element?._frame?._selector) {
|
|
612
|
+
// prefixSelector = element._frame._selector + " >> " + prefixSelector;
|
|
613
|
+
// }
|
|
614
|
+
const newSelector = prefixSelector + newElementSelector;
|
|
615
|
+
return scope.locator(newSelector);
|
|
560
616
|
}
|
|
561
617
|
}
|
|
562
618
|
throw new Error("unable to locate element " + JSON.stringify(selectors));
|
|
@@ -708,14 +764,9 @@ class StableBrowser {
|
|
|
708
764
|
// info.log += "scanning locators in priority 2" + "\n";
|
|
709
765
|
result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
710
766
|
}
|
|
711
|
-
if (result.foundElements.length === 0 && onlyPriority3) {
|
|
767
|
+
if (result.foundElements.length === 0 && (onlyPriority3 || !highPriorityOnly)) {
|
|
712
768
|
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
713
769
|
}
|
|
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
770
|
let foundElements = result.foundElements;
|
|
720
771
|
if (foundElements.length === 1 && foundElements[0].unique) {
|
|
721
772
|
info.box = foundElements[0].box;
|
|
@@ -770,6 +821,11 @@ class StableBrowser {
|
|
|
770
821
|
visibleOnly = false;
|
|
771
822
|
}
|
|
772
823
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
824
|
+
// sheck of more of half of the timeout has passed
|
|
825
|
+
if (Date.now() - startTime > timeout / 2) {
|
|
826
|
+
highPriorityOnly = false;
|
|
827
|
+
visibleOnly = false;
|
|
828
|
+
}
|
|
773
829
|
}
|
|
774
830
|
this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
|
|
775
831
|
// if (info.locatorLog) {
|
|
@@ -816,9 +872,40 @@ class StableBrowser {
|
|
|
816
872
|
result.locatorIndex = i;
|
|
817
873
|
}
|
|
818
874
|
if (foundLocators.length > 1) {
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
875
|
+
// remove elements that consume the same space with 10 pixels tolerance
|
|
876
|
+
const boxes = [];
|
|
877
|
+
for (let j = 0; j < foundLocators.length; j++) {
|
|
878
|
+
boxes.push({ box: await foundLocators[j].boundingBox(), locator: foundLocators[j] });
|
|
879
|
+
}
|
|
880
|
+
for (let j = 0; j < boxes.length; j++) {
|
|
881
|
+
for (let k = 0; k < boxes.length; k++) {
|
|
882
|
+
if (j === k) {
|
|
883
|
+
continue;
|
|
884
|
+
}
|
|
885
|
+
// check if x, y, width, height are the same with 10 pixels tolerance
|
|
886
|
+
if (Math.abs(boxes[j].box.x - boxes[k].box.x) < 10 &&
|
|
887
|
+
Math.abs(boxes[j].box.y - boxes[k].box.y) < 10 &&
|
|
888
|
+
Math.abs(boxes[j].box.width - boxes[k].box.width) < 10 &&
|
|
889
|
+
Math.abs(boxes[j].box.height - boxes[k].box.height) < 10) {
|
|
890
|
+
// as the element is not unique, will remove it
|
|
891
|
+
boxes.splice(k, 1);
|
|
892
|
+
k--;
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
if (boxes.length === 1) {
|
|
897
|
+
result.foundElements.push({
|
|
898
|
+
locator: boxes[0].locator.first(),
|
|
899
|
+
box: boxes[0].box,
|
|
900
|
+
unique: true,
|
|
901
|
+
});
|
|
902
|
+
result.locatorIndex = i;
|
|
903
|
+
}
|
|
904
|
+
else {
|
|
905
|
+
info.failCause.foundMultiple = true;
|
|
906
|
+
if (info.locatorLog) {
|
|
907
|
+
info.locatorLog.setLocatorSearchStatus(JSON.stringify(locatorsGroup[i]), "FOUND_NOT_UNIQUE");
|
|
908
|
+
}
|
|
822
909
|
}
|
|
823
910
|
}
|
|
824
911
|
}
|
|
@@ -866,7 +953,7 @@ class StableBrowser {
|
|
|
866
953
|
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
867
954
|
}
|
|
868
955
|
finally {
|
|
869
|
-
_commandFinally(state, this);
|
|
956
|
+
await _commandFinally(state, this);
|
|
870
957
|
}
|
|
871
958
|
}
|
|
872
959
|
}
|
|
@@ -915,7 +1002,7 @@ class StableBrowser {
|
|
|
915
1002
|
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
916
1003
|
}
|
|
917
1004
|
finally {
|
|
918
|
-
_commandFinally(state, this);
|
|
1005
|
+
await _commandFinally(state, this);
|
|
919
1006
|
}
|
|
920
1007
|
}
|
|
921
1008
|
}
|
|
@@ -929,25 +1016,14 @@ class StableBrowser {
|
|
|
929
1016
|
options,
|
|
930
1017
|
world,
|
|
931
1018
|
text: "Click element",
|
|
1019
|
+
_text: "Click on " + selectors.element_name,
|
|
932
1020
|
type: Types.CLICK,
|
|
933
1021
|
operation: "click",
|
|
934
1022
|
log: "***** click on " + selectors.element_name + " *****\n",
|
|
935
1023
|
};
|
|
936
1024
|
try {
|
|
937
1025
|
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
|
-
}
|
|
1026
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
951
1027
|
await this.waitForPageLoad();
|
|
952
1028
|
return state.info;
|
|
953
1029
|
}
|
|
@@ -955,9 +1031,41 @@ class StableBrowser {
|
|
|
955
1031
|
await _commandError(state, e, this);
|
|
956
1032
|
}
|
|
957
1033
|
finally {
|
|
958
|
-
_commandFinally(state, this);
|
|
1034
|
+
await _commandFinally(state, this);
|
|
959
1035
|
}
|
|
960
1036
|
}
|
|
1037
|
+
async waitForElement(selectors, _params, options = {}, world = null) {
|
|
1038
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1039
|
+
const state = {
|
|
1040
|
+
selectors,
|
|
1041
|
+
_params,
|
|
1042
|
+
options,
|
|
1043
|
+
world,
|
|
1044
|
+
text: "Wait for element",
|
|
1045
|
+
_text: "Wait for " + selectors.element_name,
|
|
1046
|
+
type: Types.WAIT_ELEMENT,
|
|
1047
|
+
operation: "waitForElement",
|
|
1048
|
+
log: "***** wait for " + selectors.element_name + " *****\n",
|
|
1049
|
+
};
|
|
1050
|
+
let found = false;
|
|
1051
|
+
try {
|
|
1052
|
+
await _preCommand(state, this);
|
|
1053
|
+
// if (state.options && state.options.context) {
|
|
1054
|
+
// state.selectors.locators[0].text = state.options.context;
|
|
1055
|
+
// }
|
|
1056
|
+
await state.element.waitFor({ timeout: timeout });
|
|
1057
|
+
found = true;
|
|
1058
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1059
|
+
}
|
|
1060
|
+
catch (e) {
|
|
1061
|
+
console.error("Error on waitForElement", e);
|
|
1062
|
+
// await _commandError(state, e, this);
|
|
1063
|
+
}
|
|
1064
|
+
finally {
|
|
1065
|
+
await _commandFinally(state, this);
|
|
1066
|
+
}
|
|
1067
|
+
return found;
|
|
1068
|
+
}
|
|
961
1069
|
async setCheck(selectors, checked = true, _params, options = {}, world = null) {
|
|
962
1070
|
const state = {
|
|
963
1071
|
selectors,
|
|
@@ -966,6 +1074,7 @@ class StableBrowser {
|
|
|
966
1074
|
world,
|
|
967
1075
|
type: checked ? Types.CHECK : Types.UNCHECK,
|
|
968
1076
|
text: checked ? `Check element` : `Uncheck element`,
|
|
1077
|
+
_text: checked ? `Check ${selectors.element_name}` : `Uncheck ${selectors.element_name}`,
|
|
969
1078
|
operation: "setCheck",
|
|
970
1079
|
log: "***** check " + selectors.element_name + " *****\n",
|
|
971
1080
|
};
|
|
@@ -977,7 +1086,7 @@ class StableBrowser {
|
|
|
977
1086
|
try {
|
|
978
1087
|
// if (world && world.screenshot && !world.screenshotPath) {
|
|
979
1088
|
// console.log(`Highlighting while running from recorder`);
|
|
980
|
-
await this._highlightElements(element);
|
|
1089
|
+
await this._highlightElements(state.element);
|
|
981
1090
|
await state.element.setChecked(checked);
|
|
982
1091
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
983
1092
|
// await this._unHighlightElements(element);
|
|
@@ -1004,7 +1113,7 @@ class StableBrowser {
|
|
|
1004
1113
|
await _commandError(state, e, this);
|
|
1005
1114
|
}
|
|
1006
1115
|
finally {
|
|
1007
|
-
_commandFinally(state, this);
|
|
1116
|
+
await _commandFinally(state, this);
|
|
1008
1117
|
}
|
|
1009
1118
|
}
|
|
1010
1119
|
async hover(selectors, _params, options = {}, world = null) {
|
|
@@ -1015,24 +1124,13 @@ class StableBrowser {
|
|
|
1015
1124
|
world,
|
|
1016
1125
|
type: Types.HOVER,
|
|
1017
1126
|
text: `Hover element`,
|
|
1127
|
+
_text: `Hover on ${selectors.element_name}`,
|
|
1018
1128
|
operation: "hover",
|
|
1019
1129
|
log: "***** hover " + selectors.element_name + " *****\n",
|
|
1020
1130
|
};
|
|
1021
1131
|
try {
|
|
1022
1132
|
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
|
-
}
|
|
1133
|
+
await performAction("hover", state.element, options, this, state, _params);
|
|
1036
1134
|
await _screenshot(state, this);
|
|
1037
1135
|
await this.waitForPageLoad();
|
|
1038
1136
|
return state.info;
|
|
@@ -1041,7 +1139,7 @@ class StableBrowser {
|
|
|
1041
1139
|
await _commandError(state, e, this);
|
|
1042
1140
|
}
|
|
1043
1141
|
finally {
|
|
1044
|
-
_commandFinally(state, this);
|
|
1142
|
+
await _commandFinally(state, this);
|
|
1045
1143
|
}
|
|
1046
1144
|
}
|
|
1047
1145
|
async selectOption(selectors, values, _params = null, options = {}, world = null) {
|
|
@@ -1056,6 +1154,7 @@ class StableBrowser {
|
|
|
1056
1154
|
value: values.toString(),
|
|
1057
1155
|
type: Types.SELECT,
|
|
1058
1156
|
text: `Select option: ${values}`,
|
|
1157
|
+
_text: `Select option: ${values} on ${selectors.element_name}`,
|
|
1059
1158
|
operation: "selectOption",
|
|
1060
1159
|
log: "***** select option " + selectors.element_name + " *****\n",
|
|
1061
1160
|
};
|
|
@@ -1076,7 +1175,7 @@ class StableBrowser {
|
|
|
1076
1175
|
await _commandError(state, e, this);
|
|
1077
1176
|
}
|
|
1078
1177
|
finally {
|
|
1079
|
-
_commandFinally(state, this);
|
|
1178
|
+
await _commandFinally(state, this);
|
|
1080
1179
|
}
|
|
1081
1180
|
}
|
|
1082
1181
|
async type(_value, _params = null, options = {}, world = null) {
|
|
@@ -1090,6 +1189,7 @@ class StableBrowser {
|
|
|
1090
1189
|
highlight: false,
|
|
1091
1190
|
type: Types.TYPE_PRESS,
|
|
1092
1191
|
text: `Type value: ${_value}`,
|
|
1192
|
+
_text: `Type value: ${_value}`,
|
|
1093
1193
|
operation: "type",
|
|
1094
1194
|
log: "",
|
|
1095
1195
|
};
|
|
@@ -1121,7 +1221,7 @@ class StableBrowser {
|
|
|
1121
1221
|
await _commandError(state, e, this);
|
|
1122
1222
|
}
|
|
1123
1223
|
finally {
|
|
1124
|
-
_commandFinally(state, this);
|
|
1224
|
+
await _commandFinally(state, this);
|
|
1125
1225
|
}
|
|
1126
1226
|
}
|
|
1127
1227
|
async setInputValue(selectors, value, _params = null, options = {}, world = null) {
|
|
@@ -1157,7 +1257,7 @@ class StableBrowser {
|
|
|
1157
1257
|
await _commandError(state, e, this);
|
|
1158
1258
|
}
|
|
1159
1259
|
finally {
|
|
1160
|
-
_commandFinally(state, this);
|
|
1260
|
+
await _commandFinally(state, this);
|
|
1161
1261
|
}
|
|
1162
1262
|
}
|
|
1163
1263
|
async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
|
|
@@ -1169,6 +1269,7 @@ class StableBrowser {
|
|
|
1169
1269
|
world,
|
|
1170
1270
|
type: Types.SET_DATE_TIME,
|
|
1171
1271
|
text: `Set date time value: ${value}`,
|
|
1272
|
+
_text: `Set date time value: ${value} on ${selectors.element_name}`,
|
|
1172
1273
|
operation: "setDateTime",
|
|
1173
1274
|
log: "***** set date time value " + selectors.element_name + " *****\n",
|
|
1174
1275
|
throwError: false,
|
|
@@ -1176,7 +1277,7 @@ class StableBrowser {
|
|
|
1176
1277
|
try {
|
|
1177
1278
|
await _preCommand(state, this);
|
|
1178
1279
|
try {
|
|
1179
|
-
await state.element
|
|
1280
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
1180
1281
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1181
1282
|
if (format) {
|
|
1182
1283
|
state.value = dayjs(state.value).format(format);
|
|
@@ -1225,7 +1326,7 @@ class StableBrowser {
|
|
|
1225
1326
|
await _commandError(state, e, this);
|
|
1226
1327
|
}
|
|
1227
1328
|
finally {
|
|
1228
|
-
_commandFinally(state, this);
|
|
1329
|
+
await _commandFinally(state, this);
|
|
1229
1330
|
}
|
|
1230
1331
|
}
|
|
1231
1332
|
async clickType(selectors, _value, enter = false, _params = null, options = {}, world = null) {
|
|
@@ -1240,9 +1341,13 @@ class StableBrowser {
|
|
|
1240
1341
|
world,
|
|
1241
1342
|
type: Types.FILL,
|
|
1242
1343
|
text: `Click type input with value: ${_value}`,
|
|
1344
|
+
_text: "Fill " + selectors.element_name + " with value " + maskValue(_value),
|
|
1243
1345
|
operation: "clickType",
|
|
1244
1346
|
log: "***** clickType on " + selectors.element_name + " with value " + maskValue(_value) + "*****\n",
|
|
1245
1347
|
};
|
|
1348
|
+
if (!options) {
|
|
1349
|
+
options = {};
|
|
1350
|
+
}
|
|
1246
1351
|
if (newValue !== _value) {
|
|
1247
1352
|
//this.logger.info(_value + "=" + newValue);
|
|
1248
1353
|
_value = newValue;
|
|
@@ -1250,7 +1355,7 @@ class StableBrowser {
|
|
|
1250
1355
|
try {
|
|
1251
1356
|
await _preCommand(state, this);
|
|
1252
1357
|
state.info.value = _value;
|
|
1253
|
-
if (
|
|
1358
|
+
if (!options.press) {
|
|
1254
1359
|
try {
|
|
1255
1360
|
let currentValue = await state.element.inputValue();
|
|
1256
1361
|
if (currentValue) {
|
|
@@ -1261,13 +1366,9 @@ class StableBrowser {
|
|
|
1261
1366
|
this.logger.info("unable to clear input value");
|
|
1262
1367
|
}
|
|
1263
1368
|
}
|
|
1264
|
-
if (options
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
}
|
|
1268
|
-
catch (e) {
|
|
1269
|
-
await state.element.dispatchEvent("click");
|
|
1270
|
-
}
|
|
1369
|
+
if (options.press) {
|
|
1370
|
+
options.timeout = 5000;
|
|
1371
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
1271
1372
|
}
|
|
1272
1373
|
else {
|
|
1273
1374
|
try {
|
|
@@ -1325,7 +1426,7 @@ class StableBrowser {
|
|
|
1325
1426
|
await _commandError(state, e, this);
|
|
1326
1427
|
}
|
|
1327
1428
|
finally {
|
|
1328
|
-
_commandFinally(state, this);
|
|
1429
|
+
await _commandFinally(state, this);
|
|
1329
1430
|
}
|
|
1330
1431
|
}
|
|
1331
1432
|
async fill(selectors, value, enter = false, _params = null, options = {}, world = null) {
|
|
@@ -1355,13 +1456,14 @@ class StableBrowser {
|
|
|
1355
1456
|
await _commandError(state, e, this);
|
|
1356
1457
|
}
|
|
1357
1458
|
finally {
|
|
1358
|
-
_commandFinally(state, this);
|
|
1459
|
+
await _commandFinally(state, this);
|
|
1359
1460
|
}
|
|
1360
1461
|
}
|
|
1361
1462
|
async getText(selectors, _params = null, options = {}, info = {}, world = null) {
|
|
1362
1463
|
return await this._getText(selectors, 0, _params, options, info, world);
|
|
1363
1464
|
}
|
|
1364
1465
|
async _getText(selectors, climb, _params = null, options = {}, info = {}, world = null) {
|
|
1466
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1365
1467
|
_validateSelectors(selectors);
|
|
1366
1468
|
let screenshotId = null;
|
|
1367
1469
|
let screenshotPath = null;
|
|
@@ -1371,7 +1473,7 @@ class StableBrowser {
|
|
|
1371
1473
|
}
|
|
1372
1474
|
info.operation = "getText";
|
|
1373
1475
|
info.selectors = selectors;
|
|
1374
|
-
let element = await this._locate(selectors, info, _params);
|
|
1476
|
+
let element = await this._locate(selectors, info, _params, timeout);
|
|
1375
1477
|
if (climb > 0) {
|
|
1376
1478
|
const climbArray = [];
|
|
1377
1479
|
for (let i = 0; i < climb; i++) {
|
|
@@ -1438,6 +1540,7 @@ class StableBrowser {
|
|
|
1438
1540
|
highlight: false,
|
|
1439
1541
|
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1440
1542
|
text: `Verify element contains pattern: ${pattern}`,
|
|
1543
|
+
_text: "Verify element " + selectors.element_name + " contains pattern " + pattern,
|
|
1441
1544
|
operation: "containsPattern",
|
|
1442
1545
|
log: "***** verify element " + selectors.element_name + " contains pattern " + pattern + " *****\n",
|
|
1443
1546
|
};
|
|
@@ -1469,10 +1572,12 @@ class StableBrowser {
|
|
|
1469
1572
|
await _commandError(state, e, this);
|
|
1470
1573
|
}
|
|
1471
1574
|
finally {
|
|
1472
|
-
_commandFinally(state, this);
|
|
1575
|
+
await _commandFinally(state, this);
|
|
1473
1576
|
}
|
|
1474
1577
|
}
|
|
1475
1578
|
async containsText(selectors, text, climb, _params = null, options = {}, world = null) {
|
|
1579
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1580
|
+
const startTime = Date.now();
|
|
1476
1581
|
const state = {
|
|
1477
1582
|
selectors,
|
|
1478
1583
|
_params,
|
|
@@ -1499,62 +1604,54 @@ class StableBrowser {
|
|
|
1499
1604
|
}
|
|
1500
1605
|
let foundObj = null;
|
|
1501
1606
|
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;
|
|
1607
|
+
while (Date.now() - startTime < timeout) {
|
|
1608
|
+
try {
|
|
1609
|
+
await _preCommand(state, this);
|
|
1610
|
+
foundObj = await this._getText(selectors, climb, _params, { timeout: 3000 }, state.info, world);
|
|
1611
|
+
if (foundObj && foundObj.element) {
|
|
1612
|
+
await this.scrollIfNeeded(foundObj.element, state.info);
|
|
1515
1613
|
}
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1614
|
+
await _screenshot(state, this);
|
|
1615
|
+
const dateAlternatives = findDateAlternatives(text);
|
|
1616
|
+
const numberAlternatives = findNumberAlternatives(text);
|
|
1617
|
+
if (dateAlternatives.date) {
|
|
1618
|
+
for (let i = 0; i < dateAlternatives.dates.length; i++) {
|
|
1619
|
+
if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
|
|
1620
|
+
foundObj?.value?.includes(dateAlternatives.dates[i])) {
|
|
1621
|
+
return state.info;
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
else if (numberAlternatives.number) {
|
|
1626
|
+
for (let i = 0; i < numberAlternatives.numbers.length; i++) {
|
|
1627
|
+
if (foundObj?.text.includes(numberAlternatives.numbers[i]) ||
|
|
1628
|
+
foundObj?.value?.includes(numberAlternatives.numbers[i])) {
|
|
1629
|
+
return state.info;
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
else if (foundObj?.text.includes(text) || foundObj?.value?.includes(text)) {
|
|
1523
1634
|
return state.info;
|
|
1524
1635
|
}
|
|
1525
1636
|
}
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
throw new Error("element doesn't contain text " + text);
|
|
1637
|
+
catch (e) {
|
|
1638
|
+
// Log error but continue retrying until timeout is reached
|
|
1639
|
+
this.logger.warn("Retrying containsText due to: " + e.message);
|
|
1640
|
+
}
|
|
1641
|
+
await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second before retrying
|
|
1532
1642
|
}
|
|
1533
|
-
|
|
1643
|
+
state.info.foundText = foundObj?.text;
|
|
1644
|
+
state.info.value = foundObj?.value;
|
|
1645
|
+
throw new Error("element doesn't contain text " + text);
|
|
1534
1646
|
}
|
|
1535
1647
|
catch (e) {
|
|
1536
1648
|
await _commandError(state, e, this);
|
|
1649
|
+
throw e;
|
|
1537
1650
|
}
|
|
1538
1651
|
finally {
|
|
1539
|
-
_commandFinally(state, this);
|
|
1652
|
+
await _commandFinally(state, this);
|
|
1540
1653
|
}
|
|
1541
1654
|
}
|
|
1542
|
-
_getDataFile(world = null) {
|
|
1543
|
-
let dataFile = null;
|
|
1544
|
-
if (world && world.reportFolder) {
|
|
1545
|
-
dataFile = path.join(world.reportFolder, "data.json");
|
|
1546
|
-
}
|
|
1547
|
-
else if (this.reportFolder) {
|
|
1548
|
-
dataFile = path.join(this.reportFolder, "data.json");
|
|
1549
|
-
}
|
|
1550
|
-
else if (this.context && this.context.reportFolder) {
|
|
1551
|
-
dataFile = path.join(this.context.reportFolder, "data.json");
|
|
1552
|
-
}
|
|
1553
|
-
else {
|
|
1554
|
-
dataFile = "data.json";
|
|
1555
|
-
}
|
|
1556
|
-
return dataFile;
|
|
1557
|
-
}
|
|
1558
1655
|
async waitForUserInput(message, world = null) {
|
|
1559
1656
|
if (!message) {
|
|
1560
1657
|
message = "# Wait for user input. Press any key to continue";
|
|
@@ -1583,13 +1680,22 @@ class StableBrowser {
|
|
|
1583
1680
|
return;
|
|
1584
1681
|
}
|
|
1585
1682
|
// if data file exists, load it
|
|
1586
|
-
const dataFile =
|
|
1683
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1587
1684
|
let data = this.getTestData(world);
|
|
1588
1685
|
// merge the testData with the existing data
|
|
1589
1686
|
Object.assign(data, testData);
|
|
1590
1687
|
// save the data to the file
|
|
1591
1688
|
fs.writeFileSync(dataFile, JSON.stringify(data, null, 2));
|
|
1592
1689
|
}
|
|
1690
|
+
overwriteTestData(testData, world = null) {
|
|
1691
|
+
if (!testData) {
|
|
1692
|
+
return;
|
|
1693
|
+
}
|
|
1694
|
+
// if data file exists, load it
|
|
1695
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1696
|
+
// save the data to the file
|
|
1697
|
+
fs.writeFileSync(dataFile, JSON.stringify(testData, null, 2));
|
|
1698
|
+
}
|
|
1593
1699
|
_getDataFilePath(fileName) {
|
|
1594
1700
|
let dataFile = path.join(this.project_path, "data", fileName);
|
|
1595
1701
|
if (fs.existsSync(dataFile)) {
|
|
@@ -1686,7 +1792,7 @@ class StableBrowser {
|
|
|
1686
1792
|
}
|
|
1687
1793
|
}
|
|
1688
1794
|
getTestData(world = null) {
|
|
1689
|
-
const dataFile =
|
|
1795
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1690
1796
|
let data = {};
|
|
1691
1797
|
if (fs.existsSync(dataFile)) {
|
|
1692
1798
|
data = JSON.parse(fs.readFileSync(dataFile, "utf8"));
|
|
@@ -1819,6 +1925,7 @@ class StableBrowser {
|
|
|
1819
1925
|
else {
|
|
1820
1926
|
fs.writeFileSync(screenshotPath, screenshotBuffer);
|
|
1821
1927
|
}
|
|
1928
|
+
return screenshotBuffer;
|
|
1822
1929
|
}
|
|
1823
1930
|
async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
|
|
1824
1931
|
const state = {
|
|
@@ -1841,7 +1948,7 @@ class StableBrowser {
|
|
|
1841
1948
|
await _commandError(state, e, this);
|
|
1842
1949
|
}
|
|
1843
1950
|
finally {
|
|
1844
|
-
_commandFinally(state, this);
|
|
1951
|
+
await _commandFinally(state, this);
|
|
1845
1952
|
}
|
|
1846
1953
|
}
|
|
1847
1954
|
async extractAttribute(selectors, attribute, variable, _params = null, options = {}, world = null) {
|
|
@@ -1854,6 +1961,7 @@ class StableBrowser {
|
|
|
1854
1961
|
world,
|
|
1855
1962
|
type: Types.EXTRACT,
|
|
1856
1963
|
text: `Extract attribute from element`,
|
|
1964
|
+
_text: `Extract attribute ${attribute} from ${selectors.element_name}`,
|
|
1857
1965
|
operation: "extractAttribute",
|
|
1858
1966
|
log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1859
1967
|
allowDisabled: true,
|
|
@@ -1871,6 +1979,9 @@ class StableBrowser {
|
|
|
1871
1979
|
case "value":
|
|
1872
1980
|
state.value = await state.element.inputValue();
|
|
1873
1981
|
break;
|
|
1982
|
+
case "text":
|
|
1983
|
+
state.value = await state.element.textContent();
|
|
1984
|
+
break;
|
|
1874
1985
|
default:
|
|
1875
1986
|
state.value = await state.element.getAttribute(attribute);
|
|
1876
1987
|
break;
|
|
@@ -1885,7 +1996,7 @@ class StableBrowser {
|
|
|
1885
1996
|
await _commandError(state, e, this);
|
|
1886
1997
|
}
|
|
1887
1998
|
finally {
|
|
1888
|
-
_commandFinally(state, this);
|
|
1999
|
+
await _commandFinally(state, this);
|
|
1889
2000
|
}
|
|
1890
2001
|
}
|
|
1891
2002
|
async verifyAttribute(selectors, attribute, value, _params = null, options = {}, world = null) {
|
|
@@ -1900,18 +2011,25 @@ class StableBrowser {
|
|
|
1900
2011
|
highlight: true,
|
|
1901
2012
|
screenshot: true,
|
|
1902
2013
|
text: `Verify element attribute`,
|
|
2014
|
+
_text: `Verify attribute ${attribute} from ${selectors.element_name} is ${value}`,
|
|
1903
2015
|
operation: "verifyAttribute",
|
|
1904
2016
|
log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1905
2017
|
allowDisabled: true,
|
|
1906
2018
|
};
|
|
1907
2019
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1908
2020
|
let val;
|
|
2021
|
+
let expectedValue;
|
|
1909
2022
|
try {
|
|
1910
2023
|
await _preCommand(state, this);
|
|
2024
|
+
expectedValue = await replaceWithLocalTestData(state.value, world);
|
|
2025
|
+
state.info.expectedValue = expectedValue;
|
|
1911
2026
|
switch (attribute) {
|
|
1912
2027
|
case "innerText":
|
|
1913
2028
|
val = String(await state.element.innerText());
|
|
1914
2029
|
break;
|
|
2030
|
+
case "text":
|
|
2031
|
+
val = String(await state.element.textContent());
|
|
2032
|
+
break;
|
|
1915
2033
|
case "value":
|
|
1916
2034
|
val = String(await state.element.inputValue());
|
|
1917
2035
|
break;
|
|
@@ -1929,26 +2047,29 @@ class StableBrowser {
|
|
|
1929
2047
|
val = String(await state.element.getAttribute(attribute));
|
|
1930
2048
|
break;
|
|
1931
2049
|
}
|
|
2050
|
+
state.info.value = val;
|
|
1932
2051
|
let regex;
|
|
1933
|
-
if (
|
|
1934
|
-
const patternBody =
|
|
2052
|
+
if (expectedValue.startsWith("/") && expectedValue.endsWith("/")) {
|
|
2053
|
+
const patternBody = expectedValue.slice(1, -1);
|
|
1935
2054
|
regex = new RegExp(patternBody, "g");
|
|
1936
2055
|
}
|
|
1937
2056
|
else {
|
|
1938
|
-
const escapedPattern =
|
|
2057
|
+
const escapedPattern = expectedValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1939
2058
|
regex = new RegExp(escapedPattern, "g");
|
|
1940
2059
|
}
|
|
1941
2060
|
if (!val.match(regex)) {
|
|
1942
|
-
|
|
2061
|
+
let errorMessage = `The ${attribute} attribute has a value of "${val}", but the expected value is "${expectedValue}"`;
|
|
2062
|
+
state.info.failCause.assertionFailed = true;
|
|
2063
|
+
state.info.failCause.lastError = errorMessage;
|
|
2064
|
+
throw new Error(errorMessage);
|
|
1943
2065
|
}
|
|
1944
|
-
state.info.expectedValue = val;
|
|
1945
2066
|
return state.info;
|
|
1946
2067
|
}
|
|
1947
2068
|
catch (e) {
|
|
1948
2069
|
await _commandError(state, e, this);
|
|
1949
2070
|
}
|
|
1950
2071
|
finally {
|
|
1951
|
-
_commandFinally(state, this);
|
|
2072
|
+
await _commandFinally(state, this);
|
|
1952
2073
|
}
|
|
1953
2074
|
}
|
|
1954
2075
|
async extractEmailData(emailAddress, options, world) {
|
|
@@ -2200,6 +2321,69 @@ class StableBrowser {
|
|
|
2200
2321
|
_reportToWorld(world, {
|
|
2201
2322
|
type: Types.VERIFY_PAGE_PATH,
|
|
2202
2323
|
text: "Verify page path",
|
|
2324
|
+
_text: "Verify the page path contains " + pathPart,
|
|
2325
|
+
screenshotId,
|
|
2326
|
+
result: error
|
|
2327
|
+
? {
|
|
2328
|
+
status: "FAILED",
|
|
2329
|
+
startTime,
|
|
2330
|
+
endTime,
|
|
2331
|
+
message: error?.message,
|
|
2332
|
+
}
|
|
2333
|
+
: {
|
|
2334
|
+
status: "PASSED",
|
|
2335
|
+
startTime,
|
|
2336
|
+
endTime,
|
|
2337
|
+
},
|
|
2338
|
+
info: info,
|
|
2339
|
+
});
|
|
2340
|
+
}
|
|
2341
|
+
}
|
|
2342
|
+
async verifyPageTitle(title, options = {}, world = null) {
|
|
2343
|
+
const startTime = Date.now();
|
|
2344
|
+
let error = null;
|
|
2345
|
+
let screenshotId = null;
|
|
2346
|
+
let screenshotPath = null;
|
|
2347
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2348
|
+
const info = {};
|
|
2349
|
+
info.log = "***** verify page title " + title + " *****\n";
|
|
2350
|
+
info.operation = "verifyPageTitle";
|
|
2351
|
+
const newValue = await this._replaceWithLocalData(title, world);
|
|
2352
|
+
if (newValue !== title) {
|
|
2353
|
+
this.logger.info(title + "=" + newValue);
|
|
2354
|
+
title = newValue;
|
|
2355
|
+
}
|
|
2356
|
+
info.title = title;
|
|
2357
|
+
try {
|
|
2358
|
+
for (let i = 0; i < 30; i++) {
|
|
2359
|
+
const foundTitle = await this.page.title();
|
|
2360
|
+
if (!foundTitle.includes(title)) {
|
|
2361
|
+
if (i === 29) {
|
|
2362
|
+
throw new Error(`url ${foundTitle} doesn't contain ${title}`);
|
|
2363
|
+
}
|
|
2364
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2365
|
+
continue;
|
|
2366
|
+
}
|
|
2367
|
+
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2368
|
+
return info;
|
|
2369
|
+
}
|
|
2370
|
+
}
|
|
2371
|
+
catch (e) {
|
|
2372
|
+
//await this.closeUnexpectedPopups();
|
|
2373
|
+
this.logger.error("verify page title failed " + info.log);
|
|
2374
|
+
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2375
|
+
info.screenshotPath = screenshotPath;
|
|
2376
|
+
Object.assign(e, { info: info });
|
|
2377
|
+
error = e;
|
|
2378
|
+
// throw e;
|
|
2379
|
+
await _commandError({ text: "verifyPageTitle", operation: "verifyPageTitle", title, info, throwError: true }, e, this);
|
|
2380
|
+
}
|
|
2381
|
+
finally {
|
|
2382
|
+
const endTime = Date.now();
|
|
2383
|
+
_reportToWorld(world, {
|
|
2384
|
+
type: Types.VERIFY_PAGE_PATH,
|
|
2385
|
+
text: "Verify page title",
|
|
2386
|
+
_text: "Verify the page title contains " + title,
|
|
2203
2387
|
screenshotId,
|
|
2204
2388
|
result: error
|
|
2205
2389
|
? {
|
|
@@ -2217,27 +2401,27 @@ class StableBrowser {
|
|
|
2217
2401
|
});
|
|
2218
2402
|
}
|
|
2219
2403
|
}
|
|
2220
|
-
async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state) {
|
|
2404
|
+
async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state, partial = true, ignoreCase = false) {
|
|
2221
2405
|
const frames = this.page.frames();
|
|
2222
2406
|
let results = [];
|
|
2223
|
-
let ignoreCase = false;
|
|
2407
|
+
// let ignoreCase = false;
|
|
2224
2408
|
for (let i = 0; i < frames.length; i++) {
|
|
2225
2409
|
if (dateAlternatives.date) {
|
|
2226
2410
|
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2227
|
-
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false,
|
|
2411
|
+
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, partial, ignoreCase, {});
|
|
2228
2412
|
result.frame = frames[i];
|
|
2229
2413
|
results.push(result);
|
|
2230
2414
|
}
|
|
2231
2415
|
}
|
|
2232
2416
|
else if (numberAlternatives.number) {
|
|
2233
2417
|
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2234
|
-
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false,
|
|
2418
|
+
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, partial, ignoreCase, {});
|
|
2235
2419
|
result.frame = frames[i];
|
|
2236
2420
|
results.push(result);
|
|
2237
2421
|
}
|
|
2238
2422
|
}
|
|
2239
2423
|
else {
|
|
2240
|
-
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false,
|
|
2424
|
+
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, partial, ignoreCase, {});
|
|
2241
2425
|
result.frame = frames[i];
|
|
2242
2426
|
results.push(result);
|
|
2243
2427
|
}
|
|
@@ -2256,11 +2440,15 @@ class StableBrowser {
|
|
|
2256
2440
|
scroll: false,
|
|
2257
2441
|
highlight: false,
|
|
2258
2442
|
type: Types.VERIFY_PAGE_CONTAINS_TEXT,
|
|
2259
|
-
text: `Verify text exists in page`,
|
|
2443
|
+
text: `Verify the text '${text}' exists in page`,
|
|
2444
|
+
_text: `Verify the text '${text}' exists in page`,
|
|
2260
2445
|
operation: "verifyTextExistInPage",
|
|
2261
2446
|
log: "***** verify text " + text + " exists in page *****\n",
|
|
2262
2447
|
};
|
|
2263
|
-
|
|
2448
|
+
if (testForRegex(text)) {
|
|
2449
|
+
text = text.replace(/\\"/g, '"');
|
|
2450
|
+
}
|
|
2451
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2264
2452
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2265
2453
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2266
2454
|
if (newValue !== text) {
|
|
@@ -2273,7 +2461,15 @@ class StableBrowser {
|
|
|
2273
2461
|
await _preCommand(state, this);
|
|
2274
2462
|
state.info.text = text;
|
|
2275
2463
|
while (true) {
|
|
2276
|
-
|
|
2464
|
+
let resultWithElementsFound = {
|
|
2465
|
+
length: 0,
|
|
2466
|
+
};
|
|
2467
|
+
try {
|
|
2468
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2469
|
+
}
|
|
2470
|
+
catch (error) {
|
|
2471
|
+
// ignore
|
|
2472
|
+
}
|
|
2277
2473
|
if (resultWithElementsFound.length === 0) {
|
|
2278
2474
|
if (Date.now() - state.startTime > timeout) {
|
|
2279
2475
|
throw new Error(`Text ${text} not found in page`);
|
|
@@ -2281,35 +2477,40 @@ class StableBrowser {
|
|
|
2281
2477
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2282
2478
|
continue;
|
|
2283
2479
|
}
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2480
|
+
try {
|
|
2481
|
+
if (resultWithElementsFound[0].randomToken) {
|
|
2482
|
+
const frame = resultWithElementsFound[0].frame;
|
|
2483
|
+
const dataAttribute = `[data-blinq-id-${resultWithElementsFound[0].randomToken}]`;
|
|
2484
|
+
await this._highlightElements(frame, dataAttribute);
|
|
2485
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
2486
|
+
// console.log(`Highlighting for verify text is found while running from recorder`);
|
|
2487
|
+
// this._highlightElements(frame, dataAttribute).then(async () => {
|
|
2488
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2489
|
+
// this._unhighlightElements(frame, dataAttribute)
|
|
2490
|
+
// .then(async () => {
|
|
2491
|
+
// console.log(`Unhighlighted frame dataAttribute successfully`);
|
|
2492
|
+
// })
|
|
2493
|
+
// .catch(
|
|
2494
|
+
// (e) => {}
|
|
2495
|
+
// console.error(e)
|
|
2496
|
+
// );
|
|
2497
|
+
// });
|
|
2498
|
+
// }
|
|
2499
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2500
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2501
|
+
// await this._unhighlightElements(frame, dataAttribute);
|
|
2502
|
+
if (element) {
|
|
2503
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2504
|
+
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2505
|
+
// await _screenshot(state, this, element);
|
|
2506
|
+
}
|
|
2309
2507
|
}
|
|
2508
|
+
await _screenshot(state, this);
|
|
2509
|
+
return state.info;
|
|
2510
|
+
}
|
|
2511
|
+
catch (error) {
|
|
2512
|
+
console.error(error);
|
|
2310
2513
|
}
|
|
2311
|
-
await _screenshot(state, this);
|
|
2312
|
-
return state.info;
|
|
2313
2514
|
}
|
|
2314
2515
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2315
2516
|
}
|
|
@@ -2317,7 +2518,7 @@ class StableBrowser {
|
|
|
2317
2518
|
await _commandError(state, e, this);
|
|
2318
2519
|
}
|
|
2319
2520
|
finally {
|
|
2320
|
-
_commandFinally(state, this);
|
|
2521
|
+
await _commandFinally(state, this);
|
|
2321
2522
|
}
|
|
2322
2523
|
}
|
|
2323
2524
|
async waitForTextToDisappear(text, options = {}, world = null) {
|
|
@@ -2330,11 +2531,15 @@ class StableBrowser {
|
|
|
2330
2531
|
scroll: false,
|
|
2331
2532
|
highlight: false,
|
|
2332
2533
|
type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
|
|
2333
|
-
text: `Verify text does not exist in page`,
|
|
2534
|
+
text: `Verify the text '${text}' does not exist in page`,
|
|
2535
|
+
_text: `Verify the text '${text}' does not exist in page`,
|
|
2334
2536
|
operation: "verifyTextNotExistInPage",
|
|
2335
2537
|
log: "***** verify text " + text + " does not exist in page *****\n",
|
|
2336
2538
|
};
|
|
2337
|
-
|
|
2539
|
+
if (testForRegex(text)) {
|
|
2540
|
+
text = text.replace(/\\"/g, '"');
|
|
2541
|
+
}
|
|
2542
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2338
2543
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2339
2544
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2340
2545
|
if (newValue !== text) {
|
|
@@ -2346,8 +2551,16 @@ class StableBrowser {
|
|
|
2346
2551
|
try {
|
|
2347
2552
|
await _preCommand(state, this);
|
|
2348
2553
|
state.info.text = text;
|
|
2554
|
+
let resultWithElementsFound = {
|
|
2555
|
+
length: null, // initial cannot be 0
|
|
2556
|
+
};
|
|
2349
2557
|
while (true) {
|
|
2350
|
-
|
|
2558
|
+
try {
|
|
2559
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2560
|
+
}
|
|
2561
|
+
catch (error) {
|
|
2562
|
+
// ignore
|
|
2563
|
+
}
|
|
2351
2564
|
if (resultWithElementsFound.length === 0) {
|
|
2352
2565
|
await _screenshot(state, this);
|
|
2353
2566
|
return state.info;
|
|
@@ -2362,7 +2575,7 @@ class StableBrowser {
|
|
|
2362
2575
|
await _commandError(state, e, this);
|
|
2363
2576
|
}
|
|
2364
2577
|
finally {
|
|
2365
|
-
_commandFinally(state, this);
|
|
2578
|
+
await _commandFinally(state, this);
|
|
2366
2579
|
}
|
|
2367
2580
|
}
|
|
2368
2581
|
async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
|
|
@@ -2377,10 +2590,11 @@ class StableBrowser {
|
|
|
2377
2590
|
highlight: false,
|
|
2378
2591
|
type: Types.VERIFY_TEXT_WITH_RELATION,
|
|
2379
2592
|
text: `Verify text with relation to another text`,
|
|
2593
|
+
_text: "Search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found",
|
|
2380
2594
|
operation: "verify_text_with_relation",
|
|
2381
2595
|
log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
|
|
2382
2596
|
};
|
|
2383
|
-
const timeout = this.
|
|
2597
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2384
2598
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2385
2599
|
let newValue = await this._replaceWithLocalData(textAnchor, world);
|
|
2386
2600
|
if (newValue !== textAnchor) {
|
|
@@ -2398,8 +2612,16 @@ class StableBrowser {
|
|
|
2398
2612
|
try {
|
|
2399
2613
|
await _preCommand(state, this);
|
|
2400
2614
|
state.info.text = textToVerify;
|
|
2615
|
+
let resultWithElementsFound = {
|
|
2616
|
+
length: 0,
|
|
2617
|
+
};
|
|
2401
2618
|
while (true) {
|
|
2402
|
-
|
|
2619
|
+
try {
|
|
2620
|
+
resultWithElementsFound = await this.findTextInAllFrames(findDateAlternatives(textAnchor), findNumberAlternatives(textAnchor), textAnchor, state, false);
|
|
2621
|
+
}
|
|
2622
|
+
catch (error) {
|
|
2623
|
+
// ignore
|
|
2624
|
+
}
|
|
2403
2625
|
if (resultWithElementsFound.length === 0) {
|
|
2404
2626
|
if (Date.now() - state.startTime > timeout) {
|
|
2405
2627
|
throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
|
|
@@ -2407,51 +2629,56 @@ class StableBrowser {
|
|
|
2407
2629
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2408
2630
|
continue;
|
|
2409
2631
|
}
|
|
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
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2632
|
+
try {
|
|
2633
|
+
for (let i = 0; i < resultWithElementsFound.length; i++) {
|
|
2634
|
+
foundAncore = true;
|
|
2635
|
+
const result = resultWithElementsFound[i];
|
|
2636
|
+
const token = result.randomToken;
|
|
2637
|
+
const frame = result.frame;
|
|
2638
|
+
let css = `[data-blinq-id-${token}]`;
|
|
2639
|
+
const climbArray1 = [];
|
|
2640
|
+
for (let i = 0; i < climb; i++) {
|
|
2641
|
+
climbArray1.push("..");
|
|
2642
|
+
}
|
|
2643
|
+
let climbXpath = "xpath=" + climbArray1.join("/");
|
|
2644
|
+
css = css + " >> " + climbXpath;
|
|
2645
|
+
const count = await frame.locator(css).count();
|
|
2646
|
+
for (let j = 0; j < count; j++) {
|
|
2647
|
+
const continer = await frame.locator(css).nth(j);
|
|
2648
|
+
const result = await this._locateElementByText(continer, textToVerify, "*:not(script, style, head)", false, true, true, {});
|
|
2649
|
+
if (result.elementCount > 0) {
|
|
2650
|
+
const dataAttribute = "[data-blinq-id-" + result.randomToken + "]";
|
|
2651
|
+
await this._highlightElements(frame, dataAttribute);
|
|
2652
|
+
//const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
|
|
2653
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
2654
|
+
// console.log(`Highlighting for vtrt while running from recorder`);
|
|
2655
|
+
// this._highlightElements(frame, dataAttribute)
|
|
2656
|
+
// .then(async () => {
|
|
2657
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2658
|
+
// this._unhighlightElements(frame, dataAttribute).then(
|
|
2659
|
+
// () => {}
|
|
2660
|
+
// console.log(`Unhighlighting vrtr in recorder is successful`)
|
|
2661
|
+
// );
|
|
2662
|
+
// })
|
|
2663
|
+
// .catch(e);
|
|
2664
|
+
// }
|
|
2665
|
+
//await this._highlightElements(frame, cssAnchor);
|
|
2666
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2667
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2668
|
+
// await this._unhighlightElements(frame, dataAttribute);
|
|
2669
|
+
if (element) {
|
|
2670
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2671
|
+
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2672
|
+
}
|
|
2673
|
+
await _screenshot(state, this);
|
|
2674
|
+
return state.info;
|
|
2449
2675
|
}
|
|
2450
|
-
await _screenshot(state, this);
|
|
2451
|
-
return state.info;
|
|
2452
2676
|
}
|
|
2453
2677
|
}
|
|
2454
2678
|
}
|
|
2679
|
+
catch (error) {
|
|
2680
|
+
console.error(error);
|
|
2681
|
+
}
|
|
2455
2682
|
}
|
|
2456
2683
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2457
2684
|
}
|
|
@@ -2459,8 +2686,32 @@ class StableBrowser {
|
|
|
2459
2686
|
await _commandError(state, e, this);
|
|
2460
2687
|
}
|
|
2461
2688
|
finally {
|
|
2462
|
-
_commandFinally(state, this);
|
|
2689
|
+
await _commandFinally(state, this);
|
|
2690
|
+
}
|
|
2691
|
+
}
|
|
2692
|
+
async findRelatedTextInAllFrames(textAnchor, climb, textToVerify, params = {}, options = {}, world = null) {
|
|
2693
|
+
const frames = this.page.frames();
|
|
2694
|
+
let results = [];
|
|
2695
|
+
let ignoreCase = false;
|
|
2696
|
+
for (let i = 0; i < frames.length; i++) {
|
|
2697
|
+
const result = await this._locateElementByText(frames[i], textAnchor, "*:not(script, style, head)", false, true, ignoreCase, {});
|
|
2698
|
+
result.frame = frames[i];
|
|
2699
|
+
const climbArray = [];
|
|
2700
|
+
for (let i = 0; i < climb; i++) {
|
|
2701
|
+
climbArray.push("..");
|
|
2702
|
+
}
|
|
2703
|
+
let climbXpath = "xpath=" + climbArray.join("/");
|
|
2704
|
+
const newLocator = `[data-blinq-id-${result.randomToken}] ${climb > 0 ? ">> " + climbXpath : ""} >> internal:text=${testForRegex(textToVerify) ? textToVerify : unEscapeString(textToVerify)}`;
|
|
2705
|
+
const count = await frames[i].locator(newLocator).count();
|
|
2706
|
+
if (count > 0) {
|
|
2707
|
+
result.elementCount = count;
|
|
2708
|
+
result.locator = newLocator;
|
|
2709
|
+
results.push(result);
|
|
2710
|
+
}
|
|
2463
2711
|
}
|
|
2712
|
+
// state.info.results = results;
|
|
2713
|
+
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2714
|
+
return resultWithElementsFound;
|
|
2464
2715
|
}
|
|
2465
2716
|
async visualVerification(text, options = {}, world = null) {
|
|
2466
2717
|
const startTime = Date.now();
|
|
@@ -2480,10 +2731,13 @@ class StableBrowser {
|
|
|
2480
2731
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2481
2732
|
info.screenshotPath = screenshotPath;
|
|
2482
2733
|
const screenshot = await this.takeScreenshot();
|
|
2483
|
-
|
|
2484
|
-
method: "
|
|
2734
|
+
let request = {
|
|
2735
|
+
method: "post",
|
|
2736
|
+
maxBodyLength: Infinity,
|
|
2485
2737
|
url: `${serviceUrl}/api/runs/screenshots/validate-screenshot`,
|
|
2486
2738
|
headers: {
|
|
2739
|
+
"x-bvt-project-id": path.basename(this.project_path),
|
|
2740
|
+
"x-source": "aaa",
|
|
2487
2741
|
"Content-Type": "application/json",
|
|
2488
2742
|
Authorization: `Bearer ${process.env.TOKEN}`,
|
|
2489
2743
|
},
|
|
@@ -2492,7 +2746,7 @@ class StableBrowser {
|
|
|
2492
2746
|
screenshot: screenshot,
|
|
2493
2747
|
}),
|
|
2494
2748
|
};
|
|
2495
|
-
|
|
2749
|
+
const result = await axios.request(request);
|
|
2496
2750
|
if (result.data.status !== true) {
|
|
2497
2751
|
throw new Error("Visual validation failed");
|
|
2498
2752
|
}
|
|
@@ -2520,6 +2774,7 @@ class StableBrowser {
|
|
|
2520
2774
|
_reportToWorld(world, {
|
|
2521
2775
|
type: Types.VERIFY_VISUAL,
|
|
2522
2776
|
text: "Visual verification",
|
|
2777
|
+
_text: "Visual verification of " + text,
|
|
2523
2778
|
screenshotId,
|
|
2524
2779
|
result: error
|
|
2525
2780
|
? {
|
|
@@ -2786,6 +3041,32 @@ class StableBrowser {
|
|
|
2786
3041
|
}
|
|
2787
3042
|
return timeout;
|
|
2788
3043
|
}
|
|
3044
|
+
_getFindElementTimeout(options) {
|
|
3045
|
+
if (options && options.timeout) {
|
|
3046
|
+
return options.timeout;
|
|
3047
|
+
}
|
|
3048
|
+
if (this.configuration.find_element_timeout) {
|
|
3049
|
+
return this.configuration.find_element_timeout;
|
|
3050
|
+
}
|
|
3051
|
+
return 30000;
|
|
3052
|
+
}
|
|
3053
|
+
async saveStoreState(path = null, world = null) {
|
|
3054
|
+
const storageState = await this.page.context().storageState();
|
|
3055
|
+
//const testDataFile = _getDataFile(world, this.context, this);
|
|
3056
|
+
if (path) {
|
|
3057
|
+
// save { storageState: storageState } into the path
|
|
3058
|
+
fs.writeFileSync(path, JSON.stringify({ storageState: storageState }, null, 2));
|
|
3059
|
+
}
|
|
3060
|
+
else {
|
|
3061
|
+
await this.setTestData({ storageState: storageState }, world);
|
|
3062
|
+
}
|
|
3063
|
+
}
|
|
3064
|
+
async restoreSaveState(path = null, world = null) {
|
|
3065
|
+
await refreshBrowser(this, path, world);
|
|
3066
|
+
this.registerEventListeners(this.context);
|
|
3067
|
+
registerNetworkEvents(this.world, this, this.context, this.page);
|
|
3068
|
+
registerDownloadEvent(this.page, this.world, this.context);
|
|
3069
|
+
}
|
|
2789
3070
|
async waitForPageLoad(options = {}, world = null) {
|
|
2790
3071
|
let timeout = this._getLoadTimeout(options);
|
|
2791
3072
|
const promiseArray = [];
|
|
@@ -2853,6 +3134,7 @@ class StableBrowser {
|
|
|
2853
3134
|
highlight: false,
|
|
2854
3135
|
type: Types.CLOSE_PAGE,
|
|
2855
3136
|
text: `Close page`,
|
|
3137
|
+
_text: `Close the page`,
|
|
2856
3138
|
operation: "closePage",
|
|
2857
3139
|
log: "***** close page *****\n",
|
|
2858
3140
|
throwError: false,
|
|
@@ -2866,11 +3148,98 @@ class StableBrowser {
|
|
|
2866
3148
|
await _commandError(state, e, this);
|
|
2867
3149
|
}
|
|
2868
3150
|
finally {
|
|
2869
|
-
_commandFinally(state, this);
|
|
3151
|
+
await _commandFinally(state, this);
|
|
3152
|
+
}
|
|
3153
|
+
}
|
|
3154
|
+
async tableCellOperation(headerText, rowText, options, _params, world = null) {
|
|
3155
|
+
let operation = null;
|
|
3156
|
+
if (!options || !options.operation) {
|
|
3157
|
+
throw new Error("operation is not defined");
|
|
3158
|
+
}
|
|
3159
|
+
operation = options.operation;
|
|
3160
|
+
// validate operation is one of the supported operations
|
|
3161
|
+
if (operation != "click" && operation != "hover+click") {
|
|
3162
|
+
throw new Error("operation is not supported");
|
|
3163
|
+
}
|
|
3164
|
+
const state = {
|
|
3165
|
+
options,
|
|
3166
|
+
world,
|
|
3167
|
+
locate: false,
|
|
3168
|
+
scroll: false,
|
|
3169
|
+
highlight: false,
|
|
3170
|
+
type: Types.TABLE_OPERATION,
|
|
3171
|
+
text: `Table operation`,
|
|
3172
|
+
_text: `Table ${operation} operation`,
|
|
3173
|
+
operation: operation,
|
|
3174
|
+
log: "***** Table operation *****\n",
|
|
3175
|
+
};
|
|
3176
|
+
const timeout = this._getFindElementTimeout(options);
|
|
3177
|
+
try {
|
|
3178
|
+
await _preCommand(state, this);
|
|
3179
|
+
const start = Date.now();
|
|
3180
|
+
let cellArea = null;
|
|
3181
|
+
while (true) {
|
|
3182
|
+
try {
|
|
3183
|
+
cellArea = await _findCellArea(headerText, rowText, this, state);
|
|
3184
|
+
if (cellArea) {
|
|
3185
|
+
break;
|
|
3186
|
+
}
|
|
3187
|
+
}
|
|
3188
|
+
catch (e) {
|
|
3189
|
+
// ignore
|
|
3190
|
+
}
|
|
3191
|
+
if (Date.now() - start > timeout) {
|
|
3192
|
+
throw new Error(`Cell not found in table`);
|
|
3193
|
+
}
|
|
3194
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
3195
|
+
}
|
|
3196
|
+
switch (operation) {
|
|
3197
|
+
case "click":
|
|
3198
|
+
if (!options.css) {
|
|
3199
|
+
// will click in the center of the cell
|
|
3200
|
+
let xOffset = 0;
|
|
3201
|
+
let yOffset = 0;
|
|
3202
|
+
if (options.xOffset) {
|
|
3203
|
+
xOffset = options.xOffset;
|
|
3204
|
+
}
|
|
3205
|
+
if (options.yOffset) {
|
|
3206
|
+
yOffset = options.yOffset;
|
|
3207
|
+
}
|
|
3208
|
+
await this.page.mouse.click(cellArea.x + cellArea.width / 2 + xOffset, cellArea.y + cellArea.height / 2 + yOffset);
|
|
3209
|
+
}
|
|
3210
|
+
else {
|
|
3211
|
+
const results = await findElementsInArea(options.css, cellArea, this, options);
|
|
3212
|
+
if (results.length === 0) {
|
|
3213
|
+
throw new Error(`Element not found in cell area`);
|
|
3214
|
+
}
|
|
3215
|
+
state.element = results[0];
|
|
3216
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
3217
|
+
}
|
|
3218
|
+
break;
|
|
3219
|
+
case "hover+click":
|
|
3220
|
+
if (!options.css) {
|
|
3221
|
+
throw new Error("css is not defined");
|
|
3222
|
+
}
|
|
3223
|
+
const results = await findElementsInArea(options.css, cellArea, this, options);
|
|
3224
|
+
if (results.length === 0) {
|
|
3225
|
+
throw new Error(`Element not found in cell area`);
|
|
3226
|
+
}
|
|
3227
|
+
state.element = results[0];
|
|
3228
|
+
await performAction("hover+click", state.element, options, this, state, _params);
|
|
3229
|
+
break;
|
|
3230
|
+
default:
|
|
3231
|
+
throw new Error("operation is not supported");
|
|
3232
|
+
}
|
|
3233
|
+
}
|
|
3234
|
+
catch (e) {
|
|
3235
|
+
await _commandError(state, e, this);
|
|
3236
|
+
}
|
|
3237
|
+
finally {
|
|
3238
|
+
await _commandFinally(state, this);
|
|
2870
3239
|
}
|
|
2871
3240
|
}
|
|
2872
3241
|
saveTestDataAsGlobal(options, world) {
|
|
2873
|
-
const dataFile =
|
|
3242
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
2874
3243
|
process.env.GLOBAL_TEST_DATA_FILE = dataFile;
|
|
2875
3244
|
this.logger.info("Save the scenario test data as global for the following scenarios.");
|
|
2876
3245
|
}
|
|
@@ -2900,6 +3269,7 @@ class StableBrowser {
|
|
|
2900
3269
|
_reportToWorld(world, {
|
|
2901
3270
|
type: Types.SET_VIEWPORT,
|
|
2902
3271
|
text: "set viewport size to " + width + "x" + hight,
|
|
3272
|
+
_text: "Set the viewport size to " + width + "x" + hight,
|
|
2903
3273
|
screenshotId,
|
|
2904
3274
|
result: error
|
|
2905
3275
|
? {
|
|
@@ -2970,7 +3340,39 @@ class StableBrowser {
|
|
|
2970
3340
|
console.log("#-#");
|
|
2971
3341
|
}
|
|
2972
3342
|
}
|
|
3343
|
+
async beforeScenario(world, scenario) {
|
|
3344
|
+
this.beforeScenarioCalled = true;
|
|
3345
|
+
if (scenario && scenario.pickle && scenario.pickle.name) {
|
|
3346
|
+
this.scenarioName = scenario.pickle.name;
|
|
3347
|
+
}
|
|
3348
|
+
if (scenario && scenario.gherkinDocument && scenario.gherkinDocument.feature) {
|
|
3349
|
+
this.featureName = scenario.gherkinDocument.feature.name;
|
|
3350
|
+
}
|
|
3351
|
+
if (this.context) {
|
|
3352
|
+
this.context.examplesRow = extractStepExampleParameters(scenario);
|
|
3353
|
+
}
|
|
3354
|
+
if (this.tags === null && scenario && scenario.pickle && scenario.pickle.tags) {
|
|
3355
|
+
this.tags = scenario.pickle.tags.map((tag) => tag.name);
|
|
3356
|
+
// check if @global_test_data tag is present
|
|
3357
|
+
if (this.tags.includes("@global_test_data")) {
|
|
3358
|
+
this.saveTestDataAsGlobal({}, world);
|
|
3359
|
+
}
|
|
3360
|
+
}
|
|
3361
|
+
// update test data based on feature/scenario
|
|
3362
|
+
let envName = null;
|
|
3363
|
+
if (this.context && this.context.environment) {
|
|
3364
|
+
envName = this.context.environment.name;
|
|
3365
|
+
}
|
|
3366
|
+
if (!process.env.TEMP_RUN) {
|
|
3367
|
+
await getTestData(envName, world, undefined, this.featureName, this.scenarioName);
|
|
3368
|
+
}
|
|
3369
|
+
await loadBrunoParams(this.context, this.context.environment.name);
|
|
3370
|
+
}
|
|
3371
|
+
async afterScenario(world, scenario) { }
|
|
2973
3372
|
async beforeStep(world, step) {
|
|
3373
|
+
if (!this.beforeScenarioCalled) {
|
|
3374
|
+
this.beforeScenario(world, step);
|
|
3375
|
+
}
|
|
2974
3376
|
if (this.stepIndex === undefined) {
|
|
2975
3377
|
this.stepIndex = 0;
|
|
2976
3378
|
}
|
|
@@ -2987,22 +3389,47 @@ class StableBrowser {
|
|
|
2987
3389
|
else {
|
|
2988
3390
|
this.stepName = "step " + this.stepIndex;
|
|
2989
3391
|
}
|
|
2990
|
-
if (this.context) {
|
|
2991
|
-
this.context.examplesRow = extractStepExampleParameters(step);
|
|
2992
|
-
}
|
|
2993
3392
|
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
2994
3393
|
if (this.context.browserObject.context) {
|
|
2995
3394
|
await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
|
|
2996
3395
|
}
|
|
2997
3396
|
}
|
|
2998
|
-
if (this.
|
|
2999
|
-
this.
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3397
|
+
if (this.initSnapshotTaken === false) {
|
|
3398
|
+
this.initSnapshotTaken = true;
|
|
3399
|
+
if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
|
|
3400
|
+
const snapshot = await this.getAriaSnapshot();
|
|
3401
|
+
if (snapshot) {
|
|
3402
|
+
await world.attach(JSON.stringify(snapshot), "application/json+snapshot-before");
|
|
3403
|
+
}
|
|
3003
3404
|
}
|
|
3004
3405
|
}
|
|
3005
3406
|
}
|
|
3407
|
+
async getAriaSnapshot() {
|
|
3408
|
+
try {
|
|
3409
|
+
// find the page url
|
|
3410
|
+
const url = await this.page.url();
|
|
3411
|
+
// extract the path from the url
|
|
3412
|
+
const path = new URL(url).pathname;
|
|
3413
|
+
// get the page title
|
|
3414
|
+
const title = await this.page.title();
|
|
3415
|
+
// go over other frams
|
|
3416
|
+
const frames = this.page.frames();
|
|
3417
|
+
const snapshots = [];
|
|
3418
|
+
const content = [`- path: ${path}`, `- title: ${title}`];
|
|
3419
|
+
const timeout = this.configuration.ariaSnapshotTimeout ? this.configuration.ariaSnapshotTimeout : 3000;
|
|
3420
|
+
for (let i = 0; i < frames.length; i++) {
|
|
3421
|
+
content.push(`- frame: ${i}`);
|
|
3422
|
+
const frame = frames[i];
|
|
3423
|
+
const snapshot = await frame.locator("body").ariaSnapshot({ timeout });
|
|
3424
|
+
content.push(snapshot);
|
|
3425
|
+
}
|
|
3426
|
+
return content.join("\n");
|
|
3427
|
+
}
|
|
3428
|
+
catch (e) {
|
|
3429
|
+
console.error(e);
|
|
3430
|
+
}
|
|
3431
|
+
return null;
|
|
3432
|
+
}
|
|
3006
3433
|
async afterStep(world, step) {
|
|
3007
3434
|
this.stepName = null;
|
|
3008
3435
|
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
@@ -3010,11 +3437,25 @@ class StableBrowser {
|
|
|
3010
3437
|
await this.context.browserObject.context.tracing.stopChunk({
|
|
3011
3438
|
path: path.join(this.context.browserObject.traceFolder, `trace-${this.stepIndex}.zip`),
|
|
3012
3439
|
});
|
|
3440
|
+
if (world && world.attach) {
|
|
3441
|
+
await world.attach(JSON.stringify({
|
|
3442
|
+
type: "trace",
|
|
3443
|
+
traceFilePath: `trace-${this.stepIndex}.zip`,
|
|
3444
|
+
}), "application/json+trace");
|
|
3445
|
+
}
|
|
3446
|
+
// console.log("trace file created", `trace-${this.stepIndex}.zip`);
|
|
3013
3447
|
}
|
|
3014
3448
|
}
|
|
3015
3449
|
if (this.context) {
|
|
3016
3450
|
this.context.examplesRow = null;
|
|
3017
3451
|
}
|
|
3452
|
+
if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
|
|
3453
|
+
const snapshot = await this.getAriaSnapshot();
|
|
3454
|
+
if (snapshot) {
|
|
3455
|
+
const obj = {};
|
|
3456
|
+
await world.attach(JSON.stringify(snapshot), "application/json+snapshot-after");
|
|
3457
|
+
}
|
|
3458
|
+
}
|
|
3018
3459
|
}
|
|
3019
3460
|
}
|
|
3020
3461
|
function createTimedPromise(promise, label) {
|