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