automation_model 1.0.602-dev → 1.0.602-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 +106 -43
- 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/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 +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 +19 -1
- package/lib/stable_browser.js +644 -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 +214 -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,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,44 +1604,52 @@ 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
1655
|
async waitForUserInput(message, world = null) {
|
|
@@ -1574,6 +1687,15 @@ class StableBrowser {
|
|
|
1574
1687
|
// save the data to the file
|
|
1575
1688
|
fs.writeFileSync(dataFile, JSON.stringify(data, null, 2));
|
|
1576
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
|
+
}
|
|
1577
1699
|
_getDataFilePath(fileName) {
|
|
1578
1700
|
let dataFile = path.join(this.project_path, "data", fileName);
|
|
1579
1701
|
if (fs.existsSync(dataFile)) {
|
|
@@ -1803,6 +1925,7 @@ class StableBrowser {
|
|
|
1803
1925
|
else {
|
|
1804
1926
|
fs.writeFileSync(screenshotPath, screenshotBuffer);
|
|
1805
1927
|
}
|
|
1928
|
+
return screenshotBuffer;
|
|
1806
1929
|
}
|
|
1807
1930
|
async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
|
|
1808
1931
|
const state = {
|
|
@@ -1825,7 +1948,7 @@ class StableBrowser {
|
|
|
1825
1948
|
await _commandError(state, e, this);
|
|
1826
1949
|
}
|
|
1827
1950
|
finally {
|
|
1828
|
-
_commandFinally(state, this);
|
|
1951
|
+
await _commandFinally(state, this);
|
|
1829
1952
|
}
|
|
1830
1953
|
}
|
|
1831
1954
|
async extractAttribute(selectors, attribute, variable, _params = null, options = {}, world = null) {
|
|
@@ -1838,6 +1961,7 @@ class StableBrowser {
|
|
|
1838
1961
|
world,
|
|
1839
1962
|
type: Types.EXTRACT,
|
|
1840
1963
|
text: `Extract attribute from element`,
|
|
1964
|
+
_text: `Extract attribute ${attribute} from ${selectors.element_name}`,
|
|
1841
1965
|
operation: "extractAttribute",
|
|
1842
1966
|
log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1843
1967
|
allowDisabled: true,
|
|
@@ -1855,6 +1979,9 @@ class StableBrowser {
|
|
|
1855
1979
|
case "value":
|
|
1856
1980
|
state.value = await state.element.inputValue();
|
|
1857
1981
|
break;
|
|
1982
|
+
case "text":
|
|
1983
|
+
state.value = await state.element.textContent();
|
|
1984
|
+
break;
|
|
1858
1985
|
default:
|
|
1859
1986
|
state.value = await state.element.getAttribute(attribute);
|
|
1860
1987
|
break;
|
|
@@ -1869,7 +1996,7 @@ class StableBrowser {
|
|
|
1869
1996
|
await _commandError(state, e, this);
|
|
1870
1997
|
}
|
|
1871
1998
|
finally {
|
|
1872
|
-
_commandFinally(state, this);
|
|
1999
|
+
await _commandFinally(state, this);
|
|
1873
2000
|
}
|
|
1874
2001
|
}
|
|
1875
2002
|
async verifyAttribute(selectors, attribute, value, _params = null, options = {}, world = null) {
|
|
@@ -1884,18 +2011,25 @@ class StableBrowser {
|
|
|
1884
2011
|
highlight: true,
|
|
1885
2012
|
screenshot: true,
|
|
1886
2013
|
text: `Verify element attribute`,
|
|
2014
|
+
_text: `Verify attribute ${attribute} from ${selectors.element_name} is ${value}`,
|
|
1887
2015
|
operation: "verifyAttribute",
|
|
1888
2016
|
log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1889
2017
|
allowDisabled: true,
|
|
1890
2018
|
};
|
|
1891
2019
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1892
2020
|
let val;
|
|
2021
|
+
let expectedValue;
|
|
1893
2022
|
try {
|
|
1894
2023
|
await _preCommand(state, this);
|
|
2024
|
+
expectedValue = await replaceWithLocalTestData(state.value, world);
|
|
2025
|
+
state.info.expectedValue = expectedValue;
|
|
1895
2026
|
switch (attribute) {
|
|
1896
2027
|
case "innerText":
|
|
1897
2028
|
val = String(await state.element.innerText());
|
|
1898
2029
|
break;
|
|
2030
|
+
case "text":
|
|
2031
|
+
val = String(await state.element.textContent());
|
|
2032
|
+
break;
|
|
1899
2033
|
case "value":
|
|
1900
2034
|
val = String(await state.element.inputValue());
|
|
1901
2035
|
break;
|
|
@@ -1913,26 +2047,29 @@ class StableBrowser {
|
|
|
1913
2047
|
val = String(await state.element.getAttribute(attribute));
|
|
1914
2048
|
break;
|
|
1915
2049
|
}
|
|
2050
|
+
state.info.value = val;
|
|
1916
2051
|
let regex;
|
|
1917
|
-
if (
|
|
1918
|
-
const patternBody =
|
|
2052
|
+
if (expectedValue.startsWith("/") && expectedValue.endsWith("/")) {
|
|
2053
|
+
const patternBody = expectedValue.slice(1, -1);
|
|
1919
2054
|
regex = new RegExp(patternBody, "g");
|
|
1920
2055
|
}
|
|
1921
2056
|
else {
|
|
1922
|
-
const escapedPattern =
|
|
2057
|
+
const escapedPattern = expectedValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1923
2058
|
regex = new RegExp(escapedPattern, "g");
|
|
1924
2059
|
}
|
|
1925
2060
|
if (!val.match(regex)) {
|
|
1926
|
-
|
|
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);
|
|
1927
2065
|
}
|
|
1928
|
-
state.info.expectedValue = val;
|
|
1929
2066
|
return state.info;
|
|
1930
2067
|
}
|
|
1931
2068
|
catch (e) {
|
|
1932
2069
|
await _commandError(state, e, this);
|
|
1933
2070
|
}
|
|
1934
2071
|
finally {
|
|
1935
|
-
_commandFinally(state, this);
|
|
2072
|
+
await _commandFinally(state, this);
|
|
1936
2073
|
}
|
|
1937
2074
|
}
|
|
1938
2075
|
async extractEmailData(emailAddress, options, world) {
|
|
@@ -2184,6 +2321,7 @@ class StableBrowser {
|
|
|
2184
2321
|
_reportToWorld(world, {
|
|
2185
2322
|
type: Types.VERIFY_PAGE_PATH,
|
|
2186
2323
|
text: "Verify page path",
|
|
2324
|
+
_text: "Verify the page path contains " + pathPart,
|
|
2187
2325
|
screenshotId,
|
|
2188
2326
|
result: error
|
|
2189
2327
|
? {
|
|
@@ -2201,27 +2339,89 @@ class StableBrowser {
|
|
|
2201
2339
|
});
|
|
2202
2340
|
}
|
|
2203
2341
|
}
|
|
2204
|
-
async
|
|
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,
|
|
2387
|
+
screenshotId,
|
|
2388
|
+
result: error
|
|
2389
|
+
? {
|
|
2390
|
+
status: "FAILED",
|
|
2391
|
+
startTime,
|
|
2392
|
+
endTime,
|
|
2393
|
+
message: error?.message,
|
|
2394
|
+
}
|
|
2395
|
+
: {
|
|
2396
|
+
status: "PASSED",
|
|
2397
|
+
startTime,
|
|
2398
|
+
endTime,
|
|
2399
|
+
},
|
|
2400
|
+
info: info,
|
|
2401
|
+
});
|
|
2402
|
+
}
|
|
2403
|
+
}
|
|
2404
|
+
async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state, partial = true, ignoreCase = false) {
|
|
2205
2405
|
const frames = this.page.frames();
|
|
2206
2406
|
let results = [];
|
|
2207
|
-
let ignoreCase = false;
|
|
2407
|
+
// let ignoreCase = false;
|
|
2208
2408
|
for (let i = 0; i < frames.length; i++) {
|
|
2209
2409
|
if (dateAlternatives.date) {
|
|
2210
2410
|
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,
|
|
2411
|
+
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, partial, ignoreCase, {});
|
|
2212
2412
|
result.frame = frames[i];
|
|
2213
2413
|
results.push(result);
|
|
2214
2414
|
}
|
|
2215
2415
|
}
|
|
2216
2416
|
else if (numberAlternatives.number) {
|
|
2217
2417
|
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,
|
|
2418
|
+
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, partial, ignoreCase, {});
|
|
2219
2419
|
result.frame = frames[i];
|
|
2220
2420
|
results.push(result);
|
|
2221
2421
|
}
|
|
2222
2422
|
}
|
|
2223
2423
|
else {
|
|
2224
|
-
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, {});
|
|
2225
2425
|
result.frame = frames[i];
|
|
2226
2426
|
results.push(result);
|
|
2227
2427
|
}
|
|
@@ -2240,11 +2440,15 @@ class StableBrowser {
|
|
|
2240
2440
|
scroll: false,
|
|
2241
2441
|
highlight: false,
|
|
2242
2442
|
type: Types.VERIFY_PAGE_CONTAINS_TEXT,
|
|
2243
|
-
text: `Verify text exists in page`,
|
|
2443
|
+
text: `Verify the text '${text}' exists in page`,
|
|
2444
|
+
_text: `Verify the text '${text}' exists in page`,
|
|
2244
2445
|
operation: "verifyTextExistInPage",
|
|
2245
2446
|
log: "***** verify text " + text + " exists in page *****\n",
|
|
2246
2447
|
};
|
|
2247
|
-
|
|
2448
|
+
if (testForRegex(text)) {
|
|
2449
|
+
text = text.replace(/\\"/g, '"');
|
|
2450
|
+
}
|
|
2451
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2248
2452
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2249
2453
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2250
2454
|
if (newValue !== text) {
|
|
@@ -2257,7 +2461,15 @@ class StableBrowser {
|
|
|
2257
2461
|
await _preCommand(state, this);
|
|
2258
2462
|
state.info.text = text;
|
|
2259
2463
|
while (true) {
|
|
2260
|
-
|
|
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
|
+
}
|
|
2261
2473
|
if (resultWithElementsFound.length === 0) {
|
|
2262
2474
|
if (Date.now() - state.startTime > timeout) {
|
|
2263
2475
|
throw new Error(`Text ${text} not found in page`);
|
|
@@ -2265,35 +2477,40 @@ class StableBrowser {
|
|
|
2265
2477
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2266
2478
|
continue;
|
|
2267
2479
|
}
|
|
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
|
-
|
|
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
|
+
}
|
|
2293
2507
|
}
|
|
2508
|
+
await _screenshot(state, this);
|
|
2509
|
+
return state.info;
|
|
2510
|
+
}
|
|
2511
|
+
catch (error) {
|
|
2512
|
+
console.error(error);
|
|
2294
2513
|
}
|
|
2295
|
-
await _screenshot(state, this);
|
|
2296
|
-
return state.info;
|
|
2297
2514
|
}
|
|
2298
2515
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2299
2516
|
}
|
|
@@ -2301,7 +2518,7 @@ class StableBrowser {
|
|
|
2301
2518
|
await _commandError(state, e, this);
|
|
2302
2519
|
}
|
|
2303
2520
|
finally {
|
|
2304
|
-
_commandFinally(state, this);
|
|
2521
|
+
await _commandFinally(state, this);
|
|
2305
2522
|
}
|
|
2306
2523
|
}
|
|
2307
2524
|
async waitForTextToDisappear(text, options = {}, world = null) {
|
|
@@ -2314,11 +2531,15 @@ class StableBrowser {
|
|
|
2314
2531
|
scroll: false,
|
|
2315
2532
|
highlight: false,
|
|
2316
2533
|
type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
|
|
2317
|
-
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`,
|
|
2318
2536
|
operation: "verifyTextNotExistInPage",
|
|
2319
2537
|
log: "***** verify text " + text + " does not exist in page *****\n",
|
|
2320
2538
|
};
|
|
2321
|
-
|
|
2539
|
+
if (testForRegex(text)) {
|
|
2540
|
+
text = text.replace(/\\"/g, '"');
|
|
2541
|
+
}
|
|
2542
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2322
2543
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2323
2544
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2324
2545
|
if (newValue !== text) {
|
|
@@ -2330,8 +2551,16 @@ class StableBrowser {
|
|
|
2330
2551
|
try {
|
|
2331
2552
|
await _preCommand(state, this);
|
|
2332
2553
|
state.info.text = text;
|
|
2554
|
+
let resultWithElementsFound = {
|
|
2555
|
+
length: null, // initial cannot be 0
|
|
2556
|
+
};
|
|
2333
2557
|
while (true) {
|
|
2334
|
-
|
|
2558
|
+
try {
|
|
2559
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2560
|
+
}
|
|
2561
|
+
catch (error) {
|
|
2562
|
+
// ignore
|
|
2563
|
+
}
|
|
2335
2564
|
if (resultWithElementsFound.length === 0) {
|
|
2336
2565
|
await _screenshot(state, this);
|
|
2337
2566
|
return state.info;
|
|
@@ -2346,7 +2575,7 @@ class StableBrowser {
|
|
|
2346
2575
|
await _commandError(state, e, this);
|
|
2347
2576
|
}
|
|
2348
2577
|
finally {
|
|
2349
|
-
_commandFinally(state, this);
|
|
2578
|
+
await _commandFinally(state, this);
|
|
2350
2579
|
}
|
|
2351
2580
|
}
|
|
2352
2581
|
async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
|
|
@@ -2361,10 +2590,11 @@ class StableBrowser {
|
|
|
2361
2590
|
highlight: false,
|
|
2362
2591
|
type: Types.VERIFY_TEXT_WITH_RELATION,
|
|
2363
2592
|
text: `Verify text with relation to another text`,
|
|
2593
|
+
_text: "Search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found",
|
|
2364
2594
|
operation: "verify_text_with_relation",
|
|
2365
2595
|
log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
|
|
2366
2596
|
};
|
|
2367
|
-
const timeout = this.
|
|
2597
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2368
2598
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2369
2599
|
let newValue = await this._replaceWithLocalData(textAnchor, world);
|
|
2370
2600
|
if (newValue !== textAnchor) {
|
|
@@ -2382,8 +2612,16 @@ class StableBrowser {
|
|
|
2382
2612
|
try {
|
|
2383
2613
|
await _preCommand(state, this);
|
|
2384
2614
|
state.info.text = textToVerify;
|
|
2615
|
+
let resultWithElementsFound = {
|
|
2616
|
+
length: 0,
|
|
2617
|
+
};
|
|
2385
2618
|
while (true) {
|
|
2386
|
-
|
|
2619
|
+
try {
|
|
2620
|
+
resultWithElementsFound = await this.findTextInAllFrames(findDateAlternatives(textAnchor), findNumberAlternatives(textAnchor), textAnchor, state, false);
|
|
2621
|
+
}
|
|
2622
|
+
catch (error) {
|
|
2623
|
+
// ignore
|
|
2624
|
+
}
|
|
2387
2625
|
if (resultWithElementsFound.length === 0) {
|
|
2388
2626
|
if (Date.now() - state.startTime > timeout) {
|
|
2389
2627
|
throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
|
|
@@ -2391,51 +2629,56 @@ class StableBrowser {
|
|
|
2391
2629
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2392
2630
|
continue;
|
|
2393
2631
|
}
|
|
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
|
-
|
|
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;
|
|
2433
2675
|
}
|
|
2434
|
-
await _screenshot(state, this);
|
|
2435
|
-
return state.info;
|
|
2436
2676
|
}
|
|
2437
2677
|
}
|
|
2438
2678
|
}
|
|
2679
|
+
catch (error) {
|
|
2680
|
+
console.error(error);
|
|
2681
|
+
}
|
|
2439
2682
|
}
|
|
2440
2683
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2441
2684
|
}
|
|
@@ -2443,9 +2686,33 @@ class StableBrowser {
|
|
|
2443
2686
|
await _commandError(state, e, this);
|
|
2444
2687
|
}
|
|
2445
2688
|
finally {
|
|
2446
|
-
_commandFinally(state, this);
|
|
2689
|
+
await _commandFinally(state, this);
|
|
2447
2690
|
}
|
|
2448
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
|
+
}
|
|
2711
|
+
}
|
|
2712
|
+
// state.info.results = results;
|
|
2713
|
+
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2714
|
+
return resultWithElementsFound;
|
|
2715
|
+
}
|
|
2449
2716
|
async visualVerification(text, options = {}, world = null) {
|
|
2450
2717
|
const startTime = Date.now();
|
|
2451
2718
|
let error = null;
|
|
@@ -2464,10 +2731,13 @@ class StableBrowser {
|
|
|
2464
2731
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2465
2732
|
info.screenshotPath = screenshotPath;
|
|
2466
2733
|
const screenshot = await this.takeScreenshot();
|
|
2467
|
-
|
|
2468
|
-
method: "
|
|
2734
|
+
let request = {
|
|
2735
|
+
method: "post",
|
|
2736
|
+
maxBodyLength: Infinity,
|
|
2469
2737
|
url: `${serviceUrl}/api/runs/screenshots/validate-screenshot`,
|
|
2470
2738
|
headers: {
|
|
2739
|
+
"x-bvt-project-id": path.basename(this.project_path),
|
|
2740
|
+
"x-source": "aaa",
|
|
2471
2741
|
"Content-Type": "application/json",
|
|
2472
2742
|
Authorization: `Bearer ${process.env.TOKEN}`,
|
|
2473
2743
|
},
|
|
@@ -2476,7 +2746,7 @@ class StableBrowser {
|
|
|
2476
2746
|
screenshot: screenshot,
|
|
2477
2747
|
}),
|
|
2478
2748
|
};
|
|
2479
|
-
|
|
2749
|
+
const result = await axios.request(request);
|
|
2480
2750
|
if (result.data.status !== true) {
|
|
2481
2751
|
throw new Error("Visual validation failed");
|
|
2482
2752
|
}
|
|
@@ -2504,6 +2774,7 @@ class StableBrowser {
|
|
|
2504
2774
|
_reportToWorld(world, {
|
|
2505
2775
|
type: Types.VERIFY_VISUAL,
|
|
2506
2776
|
text: "Visual verification",
|
|
2777
|
+
_text: "Visual verification of " + text,
|
|
2507
2778
|
screenshotId,
|
|
2508
2779
|
result: error
|
|
2509
2780
|
? {
|
|
@@ -2770,6 +3041,15 @@ class StableBrowser {
|
|
|
2770
3041
|
}
|
|
2771
3042
|
return timeout;
|
|
2772
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
|
+
}
|
|
2773
3053
|
async saveStoreState(path = null, world = null) {
|
|
2774
3054
|
const storageState = await this.page.context().storageState();
|
|
2775
3055
|
//const testDataFile = _getDataFile(world, this.context, this);
|
|
@@ -2781,6 +3061,12 @@ class StableBrowser {
|
|
|
2781
3061
|
await this.setTestData({ storageState: storageState }, world);
|
|
2782
3062
|
}
|
|
2783
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
|
+
}
|
|
2784
3070
|
async waitForPageLoad(options = {}, world = null) {
|
|
2785
3071
|
let timeout = this._getLoadTimeout(options);
|
|
2786
3072
|
const promiseArray = [];
|
|
@@ -2848,6 +3134,7 @@ class StableBrowser {
|
|
|
2848
3134
|
highlight: false,
|
|
2849
3135
|
type: Types.CLOSE_PAGE,
|
|
2850
3136
|
text: `Close page`,
|
|
3137
|
+
_text: `Close the page`,
|
|
2851
3138
|
operation: "closePage",
|
|
2852
3139
|
log: "***** close page *****\n",
|
|
2853
3140
|
throwError: false,
|
|
@@ -2861,11 +3148,98 @@ class StableBrowser {
|
|
|
2861
3148
|
await _commandError(state, e, this);
|
|
2862
3149
|
}
|
|
2863
3150
|
finally {
|
|
2864
|
-
_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);
|
|
2865
3239
|
}
|
|
2866
3240
|
}
|
|
2867
3241
|
saveTestDataAsGlobal(options, world) {
|
|
2868
|
-
const dataFile =
|
|
3242
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
2869
3243
|
process.env.GLOBAL_TEST_DATA_FILE = dataFile;
|
|
2870
3244
|
this.logger.info("Save the scenario test data as global for the following scenarios.");
|
|
2871
3245
|
}
|
|
@@ -2895,6 +3269,7 @@ class StableBrowser {
|
|
|
2895
3269
|
_reportToWorld(world, {
|
|
2896
3270
|
type: Types.SET_VIEWPORT,
|
|
2897
3271
|
text: "set viewport size to " + width + "x" + hight,
|
|
3272
|
+
_text: "Set the viewport size to " + width + "x" + hight,
|
|
2898
3273
|
screenshotId,
|
|
2899
3274
|
result: error
|
|
2900
3275
|
? {
|
|
@@ -2965,7 +3340,39 @@ class StableBrowser {
|
|
|
2965
3340
|
console.log("#-#");
|
|
2966
3341
|
}
|
|
2967
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) { }
|
|
2968
3372
|
async beforeStep(world, step) {
|
|
3373
|
+
if (!this.beforeScenarioCalled) {
|
|
3374
|
+
this.beforeScenario(world, step);
|
|
3375
|
+
}
|
|
2969
3376
|
if (this.stepIndex === undefined) {
|
|
2970
3377
|
this.stepIndex = 0;
|
|
2971
3378
|
}
|
|
@@ -2982,22 +3389,48 @@ class StableBrowser {
|
|
|
2982
3389
|
else {
|
|
2983
3390
|
this.stepName = "step " + this.stepIndex;
|
|
2984
3391
|
}
|
|
2985
|
-
if (this.context) {
|
|
2986
|
-
this.context.examplesRow = extractStepExampleParameters(step);
|
|
2987
|
-
}
|
|
2988
3392
|
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
2989
3393
|
if (this.context.browserObject.context) {
|
|
2990
3394
|
await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
|
|
2991
3395
|
}
|
|
2992
3396
|
}
|
|
2993
|
-
if (this.
|
|
2994
|
-
this.
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
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
|
+
}
|
|
2998
3404
|
}
|
|
2999
3405
|
}
|
|
3000
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.log("Error in getAriaSnapshot");
|
|
3430
|
+
console.debug(e);
|
|
3431
|
+
}
|
|
3432
|
+
return null;
|
|
3433
|
+
}
|
|
3001
3434
|
async afterStep(world, step) {
|
|
3002
3435
|
this.stepName = null;
|
|
3003
3436
|
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
@@ -3005,11 +3438,25 @@ class StableBrowser {
|
|
|
3005
3438
|
await this.context.browserObject.context.tracing.stopChunk({
|
|
3006
3439
|
path: path.join(this.context.browserObject.traceFolder, `trace-${this.stepIndex}.zip`),
|
|
3007
3440
|
});
|
|
3441
|
+
if (world && world.attach) {
|
|
3442
|
+
await world.attach(JSON.stringify({
|
|
3443
|
+
type: "trace",
|
|
3444
|
+
traceFilePath: `trace-${this.stepIndex}.zip`,
|
|
3445
|
+
}), "application/json+trace");
|
|
3446
|
+
}
|
|
3447
|
+
// console.log("trace file created", `trace-${this.stepIndex}.zip`);
|
|
3008
3448
|
}
|
|
3009
3449
|
}
|
|
3010
3450
|
if (this.context) {
|
|
3011
3451
|
this.context.examplesRow = null;
|
|
3012
3452
|
}
|
|
3453
|
+
if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
|
|
3454
|
+
const snapshot = await this.getAriaSnapshot();
|
|
3455
|
+
if (snapshot) {
|
|
3456
|
+
const obj = {};
|
|
3457
|
+
await world.attach(JSON.stringify(snapshot), "application/json+snapshot-after");
|
|
3458
|
+
}
|
|
3459
|
+
}
|
|
3013
3460
|
}
|
|
3014
3461
|
}
|
|
3015
3462
|
function createTimedPromise(promise, label) {
|