automation_model 1.0.598-dev → 1.0.598-stage
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +130 -0
- package/lib/api.js +35 -21
- package/lib/api.js.map +1 -1
- package/lib/auto_page.d.ts +4 -2
- package/lib/auto_page.js +140 -8
- package/lib/auto_page.js.map +1 -1
- package/lib/browser_manager.js +53 -16
- package/lib/browser_manager.js.map +1 -1
- package/lib/bruno.d.ts +2 -0
- package/lib/bruno.js +371 -0
- package/lib/bruno.js.map +1 -0
- package/lib/command_common.d.ts +4 -4
- package/lib/command_common.js +35 -23
- package/lib/command_common.js.map +1 -1
- package/lib/error-messages.js +6 -0
- package/lib/error-messages.js.map +1 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/lib/init_browser.d.ts +3 -2
- package/lib/init_browser.js +64 -11
- package/lib/init_browser.js.map +1 -1
- package/lib/locate_element.js +2 -2
- package/lib/locate_element.js.map +1 -1
- package/lib/network.d.ts +1 -1
- package/lib/network.js +5 -5
- package/lib/network.js.map +1 -1
- package/lib/stable_browser.d.ts +20 -2
- package/lib/stable_browser.js +664 -217
- package/lib/stable_browser.js.map +1 -1
- package/lib/table_helper.d.ts +19 -0
- package/lib/table_helper.js +116 -0
- package/lib/table_helper.js.map +1 -0
- package/lib/test_context.d.ts +3 -0
- package/lib/test_context.js +2 -0
- package/lib/test_context.js.map +1 -1
- package/lib/utils.d.ts +8 -3
- package/lib/utils.js +209 -19
- package/lib/utils.js.map +1 -1
- package/package.json +9 -8
package/lib/stable_browser.js
CHANGED
|
@@ -10,18 +10,23 @@ import { getDateTimeValue } from "./date_time.js";
|
|
|
10
10
|
import drawRectangle from "./drawRect.js";
|
|
11
11
|
//import { closeUnexpectedPopups } from "./popups.js";
|
|
12
12
|
import { getTableCells, getTableData } from "./table_analyze.js";
|
|
13
|
-
import { _convertToRegexQuery, _copyContext, _fixLocatorUsingParams, _fixUsingParams, _getServerUrl, extractStepExampleParameters, KEYBOARD_EVENTS, maskValue, replaceWithLocalTestData, scrollPageToLoadLazyElements, unEscapeString, } from "./utils.js";
|
|
13
|
+
import { _convertToRegexQuery, _copyContext, _fixLocatorUsingParams, _fixUsingParams, _getServerUrl, extractStepExampleParameters, KEYBOARD_EVENTS, maskValue, replaceWithLocalTestData, scrollPageToLoadLazyElements, unEscapeString, _getDataFile, testForRegex, performAction, } from "./utils.js";
|
|
14
14
|
import csv from "csv-parser";
|
|
15
15
|
import { Readable } from "node:stream";
|
|
16
16
|
import readline from "readline";
|
|
17
|
-
import { getContext } from "./init_browser.js";
|
|
17
|
+
import { getContext, refreshBrowser } from "./init_browser.js";
|
|
18
|
+
import { getTestData } from "./auto_page.js";
|
|
18
19
|
import { locate_element } from "./locate_element.js";
|
|
19
20
|
import { randomUUID } from "crypto";
|
|
20
21
|
import { _commandError, _commandFinally, _preCommand, _validateSelectors, _screenshot, _reportToWorld, } from "./command_common.js";
|
|
21
22
|
import { registerDownloadEvent, registerNetworkEvents } from "./network.js";
|
|
22
23
|
import { LocatorLog } from "./locator_log.js";
|
|
24
|
+
import axios from "axios";
|
|
25
|
+
import { _findCellArea, findElementsInArea } from "./table_helper.js";
|
|
26
|
+
import { loadBrunoParams } from "./bruno.js";
|
|
23
27
|
export const Types = {
|
|
24
28
|
CLICK: "click_element",
|
|
29
|
+
WAIT_ELEMENT: "wait_element",
|
|
25
30
|
NAVIGATE: "navigate",
|
|
26
31
|
FILL: "fill_element",
|
|
27
32
|
EXECUTE: "execute_page_method",
|
|
@@ -43,6 +48,7 @@ export const Types = {
|
|
|
43
48
|
UNCHECK: "uncheck_element",
|
|
44
49
|
EXTRACT: "extract_attribute",
|
|
45
50
|
CLOSE_PAGE: "close_page",
|
|
51
|
+
TABLE_OPERATION: "table_operation",
|
|
46
52
|
SET_DATE_TIME: "set_date_time",
|
|
47
53
|
SET_VIEWPORT: "set_viewport",
|
|
48
54
|
VERIFY_VISUAL: "verify_visual",
|
|
@@ -51,6 +57,7 @@ export const Types = {
|
|
|
51
57
|
WAIT_FOR_TEXT_TO_DISAPPEAR: "wait_for_text_to_disappear",
|
|
52
58
|
VERIFY_ATTRIBUTE: "verify_element_attribute",
|
|
53
59
|
VERIFY_TEXT_WITH_RELATION: "verify_text_with_relation",
|
|
60
|
+
BRUNO: "bruno",
|
|
54
61
|
};
|
|
55
62
|
export const apps = {};
|
|
56
63
|
const formatElementName = (elementName) => {
|
|
@@ -69,6 +76,7 @@ class StableBrowser {
|
|
|
69
76
|
appName = "main";
|
|
70
77
|
tags = null;
|
|
71
78
|
isRecording = false;
|
|
79
|
+
initSnapshotTaken = false;
|
|
72
80
|
constructor(browser, page, logger = null, context = null, world = null) {
|
|
73
81
|
this.browser = browser;
|
|
74
82
|
this.page = page;
|
|
@@ -175,6 +183,30 @@ class StableBrowser {
|
|
|
175
183
|
await this.waitForPageLoad();
|
|
176
184
|
}
|
|
177
185
|
}
|
|
186
|
+
async switchTab(tabTitleOrIndex) {
|
|
187
|
+
// first check if the tabNameOrIndex is a number
|
|
188
|
+
let index = parseInt(tabTitleOrIndex);
|
|
189
|
+
if (!isNaN(index)) {
|
|
190
|
+
if (index >= 0 && index < this.context.pages.length) {
|
|
191
|
+
this.page = this.context.pages[index];
|
|
192
|
+
this.context.page = this.page;
|
|
193
|
+
await this.page.bringToFront();
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
// if the tabNameOrIndex is a string, find the tab by name
|
|
198
|
+
for (let i = 0; i < this.context.pages.length; i++) {
|
|
199
|
+
let page = this.context.pages[i];
|
|
200
|
+
let title = await page.title();
|
|
201
|
+
if (title.includes(tabTitleOrIndex)) {
|
|
202
|
+
this.page = page;
|
|
203
|
+
this.context.page = this.page;
|
|
204
|
+
await this.page.bringToFront();
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
throw new Error("Tab not found: " + tabTitleOrIndex);
|
|
209
|
+
}
|
|
178
210
|
registerConsoleLogListener(page, context) {
|
|
179
211
|
if (!this.context.webLogger) {
|
|
180
212
|
this.context.webLogger = [];
|
|
@@ -239,6 +271,9 @@ class StableBrowser {
|
|
|
239
271
|
// await closeUnexpectedPopups(this.page);
|
|
240
272
|
// }
|
|
241
273
|
async goto(url, world = null) {
|
|
274
|
+
if (!url) {
|
|
275
|
+
throw new Error("url is null, verify that the environment file is correct");
|
|
276
|
+
}
|
|
242
277
|
if (!url.startsWith("http")) {
|
|
243
278
|
url = "https://" + url;
|
|
244
279
|
}
|
|
@@ -267,7 +302,7 @@ class StableBrowser {
|
|
|
267
302
|
_commandError(state, error, this);
|
|
268
303
|
}
|
|
269
304
|
finally {
|
|
270
|
-
_commandFinally(state, this);
|
|
305
|
+
await _commandFinally(state, this);
|
|
271
306
|
}
|
|
272
307
|
}
|
|
273
308
|
async _getLocator(locator, scope, _params) {
|
|
@@ -348,7 +383,7 @@ class StableBrowser {
|
|
|
348
383
|
return resultCss;
|
|
349
384
|
}
|
|
350
385
|
async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, ignoreCase = true, _params) {
|
|
351
|
-
const query = _convertToRegexQuery(text1, regex1, !partial1, ignoreCase)
|
|
386
|
+
const query = `${_convertToRegexQuery(text1, regex1, !partial1, ignoreCase)}`;
|
|
352
387
|
const locator = scope.locator(query);
|
|
353
388
|
const count = await locator.count();
|
|
354
389
|
if (!tag1) {
|
|
@@ -368,6 +403,12 @@ class StableBrowser {
|
|
|
368
403
|
if (!el.setAttribute) {
|
|
369
404
|
el = el.parentElement;
|
|
370
405
|
}
|
|
406
|
+
// remove any attributes start with data-blinq-id
|
|
407
|
+
// for (let i = 0; i < el.attributes.length; i++) {
|
|
408
|
+
// if (el.attributes[i].name.startsWith("data-blinq-id")) {
|
|
409
|
+
// el.removeAttribute(el.attributes[i].name);
|
|
410
|
+
// }
|
|
411
|
+
// }
|
|
371
412
|
el.setAttribute("data-blinq-id-" + randomToken, "");
|
|
372
413
|
return true;
|
|
373
414
|
}, [tag1, randomToken]))) {
|
|
@@ -550,7 +591,28 @@ class StableBrowser {
|
|
|
550
591
|
}
|
|
551
592
|
let element = await this._locate_internal(selectors, info, _params, timeout, allowDisabled);
|
|
552
593
|
if (!element.rerun) {
|
|
553
|
-
|
|
594
|
+
const randomToken = Math.random().toString(36).substring(7);
|
|
595
|
+
element.evaluate((el, randomToken) => {
|
|
596
|
+
el.setAttribute("data-blinq-id-" + randomToken, "");
|
|
597
|
+
}, randomToken);
|
|
598
|
+
// if (element._frame) {
|
|
599
|
+
// return element;
|
|
600
|
+
// }
|
|
601
|
+
const scope = element._frame ?? element.page();
|
|
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);
|
|
554
616
|
}
|
|
555
617
|
}
|
|
556
618
|
throw new Error("unable to locate element " + JSON.stringify(selectors));
|
|
@@ -702,14 +764,9 @@ class StableBrowser {
|
|
|
702
764
|
// info.log += "scanning locators in priority 2" + "\n";
|
|
703
765
|
result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
704
766
|
}
|
|
705
|
-
if (result.foundElements.length === 0 && onlyPriority3) {
|
|
767
|
+
if (result.foundElements.length === 0 && (onlyPriority3 || !highPriorityOnly)) {
|
|
706
768
|
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
707
769
|
}
|
|
708
|
-
else {
|
|
709
|
-
if (result.foundElements.length === 0 && !highPriorityOnly) {
|
|
710
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
711
|
-
}
|
|
712
|
-
}
|
|
713
770
|
let foundElements = result.foundElements;
|
|
714
771
|
if (foundElements.length === 1 && foundElements[0].unique) {
|
|
715
772
|
info.box = foundElements[0].box;
|
|
@@ -764,6 +821,11 @@ class StableBrowser {
|
|
|
764
821
|
visibleOnly = false;
|
|
765
822
|
}
|
|
766
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
|
+
}
|
|
767
829
|
}
|
|
768
830
|
this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
|
|
769
831
|
// if (info.locatorLog) {
|
|
@@ -810,9 +872,40 @@ class StableBrowser {
|
|
|
810
872
|
result.locatorIndex = i;
|
|
811
873
|
}
|
|
812
874
|
if (foundLocators.length > 1) {
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
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
|
+
}
|
|
816
909
|
}
|
|
817
910
|
}
|
|
818
911
|
}
|
|
@@ -860,7 +953,7 @@ class StableBrowser {
|
|
|
860
953
|
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
861
954
|
}
|
|
862
955
|
finally {
|
|
863
|
-
_commandFinally(state, this);
|
|
956
|
+
await _commandFinally(state, this);
|
|
864
957
|
}
|
|
865
958
|
}
|
|
866
959
|
}
|
|
@@ -909,7 +1002,7 @@ class StableBrowser {
|
|
|
909
1002
|
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
910
1003
|
}
|
|
911
1004
|
finally {
|
|
912
|
-
_commandFinally(state, this);
|
|
1005
|
+
await _commandFinally(state, this);
|
|
913
1006
|
}
|
|
914
1007
|
}
|
|
915
1008
|
}
|
|
@@ -923,25 +1016,14 @@ class StableBrowser {
|
|
|
923
1016
|
options,
|
|
924
1017
|
world,
|
|
925
1018
|
text: "Click element",
|
|
1019
|
+
_text: "Click on " + selectors.element_name,
|
|
926
1020
|
type: Types.CLICK,
|
|
927
1021
|
operation: "click",
|
|
928
1022
|
log: "***** click on " + selectors.element_name + " *****\n",
|
|
929
1023
|
};
|
|
930
1024
|
try {
|
|
931
1025
|
await _preCommand(state, this);
|
|
932
|
-
|
|
933
|
-
// state.selectors.locators[0].text = state.options.context;
|
|
934
|
-
// }
|
|
935
|
-
try {
|
|
936
|
-
await state.element.click();
|
|
937
|
-
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
938
|
-
}
|
|
939
|
-
catch (e) {
|
|
940
|
-
// await this.closeUnexpectedPopups();
|
|
941
|
-
state.element = await this._locate(selectors, state.info, _params);
|
|
942
|
-
await state.element.dispatchEvent("click");
|
|
943
|
-
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
944
|
-
}
|
|
1026
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
945
1027
|
await this.waitForPageLoad();
|
|
946
1028
|
return state.info;
|
|
947
1029
|
}
|
|
@@ -949,9 +1031,41 @@ class StableBrowser {
|
|
|
949
1031
|
await _commandError(state, e, this);
|
|
950
1032
|
}
|
|
951
1033
|
finally {
|
|
952
|
-
_commandFinally(state, this);
|
|
1034
|
+
await _commandFinally(state, this);
|
|
953
1035
|
}
|
|
954
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
|
+
}
|
|
955
1069
|
async setCheck(selectors, checked = true, _params, options = {}, world = null) {
|
|
956
1070
|
const state = {
|
|
957
1071
|
selectors,
|
|
@@ -960,6 +1074,7 @@ class StableBrowser {
|
|
|
960
1074
|
world,
|
|
961
1075
|
type: checked ? Types.CHECK : Types.UNCHECK,
|
|
962
1076
|
text: checked ? `Check element` : `Uncheck element`,
|
|
1077
|
+
_text: checked ? `Check ${selectors.element_name}` : `Uncheck ${selectors.element_name}`,
|
|
963
1078
|
operation: "setCheck",
|
|
964
1079
|
log: "***** check " + selectors.element_name + " *****\n",
|
|
965
1080
|
};
|
|
@@ -971,7 +1086,7 @@ class StableBrowser {
|
|
|
971
1086
|
try {
|
|
972
1087
|
// if (world && world.screenshot && !world.screenshotPath) {
|
|
973
1088
|
// console.log(`Highlighting while running from recorder`);
|
|
974
|
-
await this._highlightElements(element);
|
|
1089
|
+
await this._highlightElements(state.element);
|
|
975
1090
|
await state.element.setChecked(checked);
|
|
976
1091
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
977
1092
|
// await this._unHighlightElements(element);
|
|
@@ -998,7 +1113,7 @@ class StableBrowser {
|
|
|
998
1113
|
await _commandError(state, e, this);
|
|
999
1114
|
}
|
|
1000
1115
|
finally {
|
|
1001
|
-
_commandFinally(state, this);
|
|
1116
|
+
await _commandFinally(state, this);
|
|
1002
1117
|
}
|
|
1003
1118
|
}
|
|
1004
1119
|
async hover(selectors, _params, options = {}, world = null) {
|
|
@@ -1009,24 +1124,13 @@ class StableBrowser {
|
|
|
1009
1124
|
world,
|
|
1010
1125
|
type: Types.HOVER,
|
|
1011
1126
|
text: `Hover element`,
|
|
1127
|
+
_text: `Hover on ${selectors.element_name}`,
|
|
1012
1128
|
operation: "hover",
|
|
1013
1129
|
log: "***** hover " + selectors.element_name + " *****\n",
|
|
1014
1130
|
};
|
|
1015
1131
|
try {
|
|
1016
1132
|
await _preCommand(state, this);
|
|
1017
|
-
|
|
1018
|
-
await state.element.hover();
|
|
1019
|
-
// await _screenshot(state, this);
|
|
1020
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1021
|
-
}
|
|
1022
|
-
catch (e) {
|
|
1023
|
-
//await this.closeUnexpectedPopups();
|
|
1024
|
-
state.info.log += "hover failed, will try again" + "\n";
|
|
1025
|
-
state.element = await this._locate(selectors, state.info, _params);
|
|
1026
|
-
await state.element.hover({ timeout: 10000 });
|
|
1027
|
-
// await _screenshot(state, this);
|
|
1028
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1029
|
-
}
|
|
1133
|
+
await performAction("hover", state.element, options, this, state, _params);
|
|
1030
1134
|
await _screenshot(state, this);
|
|
1031
1135
|
await this.waitForPageLoad();
|
|
1032
1136
|
return state.info;
|
|
@@ -1035,7 +1139,7 @@ class StableBrowser {
|
|
|
1035
1139
|
await _commandError(state, e, this);
|
|
1036
1140
|
}
|
|
1037
1141
|
finally {
|
|
1038
|
-
_commandFinally(state, this);
|
|
1142
|
+
await _commandFinally(state, this);
|
|
1039
1143
|
}
|
|
1040
1144
|
}
|
|
1041
1145
|
async selectOption(selectors, values, _params = null, options = {}, world = null) {
|
|
@@ -1050,6 +1154,7 @@ class StableBrowser {
|
|
|
1050
1154
|
value: values.toString(),
|
|
1051
1155
|
type: Types.SELECT,
|
|
1052
1156
|
text: `Select option: ${values}`,
|
|
1157
|
+
_text: `Select option: ${values} on ${selectors.element_name}`,
|
|
1053
1158
|
operation: "selectOption",
|
|
1054
1159
|
log: "***** select option " + selectors.element_name + " *****\n",
|
|
1055
1160
|
};
|
|
@@ -1070,7 +1175,7 @@ class StableBrowser {
|
|
|
1070
1175
|
await _commandError(state, e, this);
|
|
1071
1176
|
}
|
|
1072
1177
|
finally {
|
|
1073
|
-
_commandFinally(state, this);
|
|
1178
|
+
await _commandFinally(state, this);
|
|
1074
1179
|
}
|
|
1075
1180
|
}
|
|
1076
1181
|
async type(_value, _params = null, options = {}, world = null) {
|
|
@@ -1084,6 +1189,7 @@ class StableBrowser {
|
|
|
1084
1189
|
highlight: false,
|
|
1085
1190
|
type: Types.TYPE_PRESS,
|
|
1086
1191
|
text: `Type value: ${_value}`,
|
|
1192
|
+
_text: `Type value: ${_value}`,
|
|
1087
1193
|
operation: "type",
|
|
1088
1194
|
log: "",
|
|
1089
1195
|
};
|
|
@@ -1115,7 +1221,7 @@ class StableBrowser {
|
|
|
1115
1221
|
await _commandError(state, e, this);
|
|
1116
1222
|
}
|
|
1117
1223
|
finally {
|
|
1118
|
-
_commandFinally(state, this);
|
|
1224
|
+
await _commandFinally(state, this);
|
|
1119
1225
|
}
|
|
1120
1226
|
}
|
|
1121
1227
|
async setInputValue(selectors, value, _params = null, options = {}, world = null) {
|
|
@@ -1151,7 +1257,7 @@ class StableBrowser {
|
|
|
1151
1257
|
await _commandError(state, e, this);
|
|
1152
1258
|
}
|
|
1153
1259
|
finally {
|
|
1154
|
-
_commandFinally(state, this);
|
|
1260
|
+
await _commandFinally(state, this);
|
|
1155
1261
|
}
|
|
1156
1262
|
}
|
|
1157
1263
|
async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
|
|
@@ -1163,6 +1269,7 @@ class StableBrowser {
|
|
|
1163
1269
|
world,
|
|
1164
1270
|
type: Types.SET_DATE_TIME,
|
|
1165
1271
|
text: `Set date time value: ${value}`,
|
|
1272
|
+
_text: `Set date time value: ${value} on ${selectors.element_name}`,
|
|
1166
1273
|
operation: "setDateTime",
|
|
1167
1274
|
log: "***** set date time value " + selectors.element_name + " *****\n",
|
|
1168
1275
|
throwError: false,
|
|
@@ -1170,7 +1277,7 @@ class StableBrowser {
|
|
|
1170
1277
|
try {
|
|
1171
1278
|
await _preCommand(state, this);
|
|
1172
1279
|
try {
|
|
1173
|
-
await state.element
|
|
1280
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
1174
1281
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1175
1282
|
if (format) {
|
|
1176
1283
|
state.value = dayjs(state.value).format(format);
|
|
@@ -1219,7 +1326,7 @@ class StableBrowser {
|
|
|
1219
1326
|
await _commandError(state, e, this);
|
|
1220
1327
|
}
|
|
1221
1328
|
finally {
|
|
1222
|
-
_commandFinally(state, this);
|
|
1329
|
+
await _commandFinally(state, this);
|
|
1223
1330
|
}
|
|
1224
1331
|
}
|
|
1225
1332
|
async clickType(selectors, _value, enter = false, _params = null, options = {}, world = null) {
|
|
@@ -1234,9 +1341,13 @@ class StableBrowser {
|
|
|
1234
1341
|
world,
|
|
1235
1342
|
type: Types.FILL,
|
|
1236
1343
|
text: `Click type input with value: ${_value}`,
|
|
1344
|
+
_text: "Fill " + selectors.element_name + " with value " + maskValue(_value),
|
|
1237
1345
|
operation: "clickType",
|
|
1238
1346
|
log: "***** clickType on " + selectors.element_name + " with value " + maskValue(_value) + "*****\n",
|
|
1239
1347
|
};
|
|
1348
|
+
if (!options) {
|
|
1349
|
+
options = {};
|
|
1350
|
+
}
|
|
1240
1351
|
if (newValue !== _value) {
|
|
1241
1352
|
//this.logger.info(_value + "=" + newValue);
|
|
1242
1353
|
_value = newValue;
|
|
@@ -1244,7 +1355,7 @@ class StableBrowser {
|
|
|
1244
1355
|
try {
|
|
1245
1356
|
await _preCommand(state, this);
|
|
1246
1357
|
state.info.value = _value;
|
|
1247
|
-
if (
|
|
1358
|
+
if (!options.press) {
|
|
1248
1359
|
try {
|
|
1249
1360
|
let currentValue = await state.element.inputValue();
|
|
1250
1361
|
if (currentValue) {
|
|
@@ -1255,13 +1366,9 @@ class StableBrowser {
|
|
|
1255
1366
|
this.logger.info("unable to clear input value");
|
|
1256
1367
|
}
|
|
1257
1368
|
}
|
|
1258
|
-
if (options
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
}
|
|
1262
|
-
catch (e) {
|
|
1263
|
-
await state.element.dispatchEvent("click");
|
|
1264
|
-
}
|
|
1369
|
+
if (options.press) {
|
|
1370
|
+
options.timeout = 5000;
|
|
1371
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
1265
1372
|
}
|
|
1266
1373
|
else {
|
|
1267
1374
|
try {
|
|
@@ -1319,7 +1426,7 @@ class StableBrowser {
|
|
|
1319
1426
|
await _commandError(state, e, this);
|
|
1320
1427
|
}
|
|
1321
1428
|
finally {
|
|
1322
|
-
_commandFinally(state, this);
|
|
1429
|
+
await _commandFinally(state, this);
|
|
1323
1430
|
}
|
|
1324
1431
|
}
|
|
1325
1432
|
async fill(selectors, value, enter = false, _params = null, options = {}, world = null) {
|
|
@@ -1349,13 +1456,14 @@ class StableBrowser {
|
|
|
1349
1456
|
await _commandError(state, e, this);
|
|
1350
1457
|
}
|
|
1351
1458
|
finally {
|
|
1352
|
-
_commandFinally(state, this);
|
|
1459
|
+
await _commandFinally(state, this);
|
|
1353
1460
|
}
|
|
1354
1461
|
}
|
|
1355
1462
|
async getText(selectors, _params = null, options = {}, info = {}, world = null) {
|
|
1356
1463
|
return await this._getText(selectors, 0, _params, options, info, world);
|
|
1357
1464
|
}
|
|
1358
1465
|
async _getText(selectors, climb, _params = null, options = {}, info = {}, world = null) {
|
|
1466
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1359
1467
|
_validateSelectors(selectors);
|
|
1360
1468
|
let screenshotId = null;
|
|
1361
1469
|
let screenshotPath = null;
|
|
@@ -1365,7 +1473,7 @@ class StableBrowser {
|
|
|
1365
1473
|
}
|
|
1366
1474
|
info.operation = "getText";
|
|
1367
1475
|
info.selectors = selectors;
|
|
1368
|
-
let element = await this._locate(selectors, info, _params);
|
|
1476
|
+
let element = await this._locate(selectors, info, _params, timeout);
|
|
1369
1477
|
if (climb > 0) {
|
|
1370
1478
|
const climbArray = [];
|
|
1371
1479
|
for (let i = 0; i < climb; i++) {
|
|
@@ -1432,6 +1540,7 @@ class StableBrowser {
|
|
|
1432
1540
|
highlight: false,
|
|
1433
1541
|
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1434
1542
|
text: `Verify element contains pattern: ${pattern}`,
|
|
1543
|
+
_text: "Verify element " + selectors.element_name + " contains pattern " + pattern,
|
|
1435
1544
|
operation: "containsPattern",
|
|
1436
1545
|
log: "***** verify element " + selectors.element_name + " contains pattern " + pattern + " *****\n",
|
|
1437
1546
|
};
|
|
@@ -1463,10 +1572,12 @@ class StableBrowser {
|
|
|
1463
1572
|
await _commandError(state, e, this);
|
|
1464
1573
|
}
|
|
1465
1574
|
finally {
|
|
1466
|
-
_commandFinally(state, this);
|
|
1575
|
+
await _commandFinally(state, this);
|
|
1467
1576
|
}
|
|
1468
1577
|
}
|
|
1469
1578
|
async containsText(selectors, text, climb, _params = null, options = {}, world = null) {
|
|
1579
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1580
|
+
const startTime = Date.now();
|
|
1470
1581
|
const state = {
|
|
1471
1582
|
selectors,
|
|
1472
1583
|
_params,
|
|
@@ -1493,62 +1604,54 @@ class StableBrowser {
|
|
|
1493
1604
|
}
|
|
1494
1605
|
let foundObj = null;
|
|
1495
1606
|
try {
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
const dateAlternatives = findDateAlternatives(text);
|
|
1503
|
-
const numberAlternatives = findNumberAlternatives(text);
|
|
1504
|
-
if (dateAlternatives.date) {
|
|
1505
|
-
for (let i = 0; i < dateAlternatives.dates.length; i++) {
|
|
1506
|
-
if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
|
|
1507
|
-
foundObj?.value?.includes(dateAlternatives.dates[i])) {
|
|
1508
|
-
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);
|
|
1509
1613
|
}
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
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)) {
|
|
1517
1634
|
return state.info;
|
|
1518
1635
|
}
|
|
1519
1636
|
}
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
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
|
|
1526
1642
|
}
|
|
1527
|
-
|
|
1643
|
+
state.info.foundText = foundObj?.text;
|
|
1644
|
+
state.info.value = foundObj?.value;
|
|
1645
|
+
throw new Error("element doesn't contain text " + text);
|
|
1528
1646
|
}
|
|
1529
1647
|
catch (e) {
|
|
1530
1648
|
await _commandError(state, e, this);
|
|
1649
|
+
throw e;
|
|
1531
1650
|
}
|
|
1532
1651
|
finally {
|
|
1533
|
-
_commandFinally(state, this);
|
|
1652
|
+
await _commandFinally(state, this);
|
|
1534
1653
|
}
|
|
1535
1654
|
}
|
|
1536
|
-
_getDataFile(world = null) {
|
|
1537
|
-
let dataFile = null;
|
|
1538
|
-
if (world && world.reportFolder) {
|
|
1539
|
-
dataFile = path.join(world.reportFolder, "data.json");
|
|
1540
|
-
}
|
|
1541
|
-
else if (this.reportFolder) {
|
|
1542
|
-
dataFile = path.join(this.reportFolder, "data.json");
|
|
1543
|
-
}
|
|
1544
|
-
else if (this.context && this.context.reportFolder) {
|
|
1545
|
-
dataFile = path.join(this.context.reportFolder, "data.json");
|
|
1546
|
-
}
|
|
1547
|
-
else {
|
|
1548
|
-
dataFile = "data.json";
|
|
1549
|
-
}
|
|
1550
|
-
return dataFile;
|
|
1551
|
-
}
|
|
1552
1655
|
async waitForUserInput(message, world = null) {
|
|
1553
1656
|
if (!message) {
|
|
1554
1657
|
message = "# Wait for user input. Press any key to continue";
|
|
@@ -1577,13 +1680,22 @@ class StableBrowser {
|
|
|
1577
1680
|
return;
|
|
1578
1681
|
}
|
|
1579
1682
|
// if data file exists, load it
|
|
1580
|
-
const dataFile =
|
|
1683
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1581
1684
|
let data = this.getTestData(world);
|
|
1582
1685
|
// merge the testData with the existing data
|
|
1583
1686
|
Object.assign(data, testData);
|
|
1584
1687
|
// save the data to the file
|
|
1585
1688
|
fs.writeFileSync(dataFile, JSON.stringify(data, null, 2));
|
|
1586
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
|
+
}
|
|
1587
1699
|
_getDataFilePath(fileName) {
|
|
1588
1700
|
let dataFile = path.join(this.project_path, "data", fileName);
|
|
1589
1701
|
if (fs.existsSync(dataFile)) {
|
|
@@ -1680,7 +1792,7 @@ class StableBrowser {
|
|
|
1680
1792
|
}
|
|
1681
1793
|
}
|
|
1682
1794
|
getTestData(world = null) {
|
|
1683
|
-
const dataFile =
|
|
1795
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1684
1796
|
let data = {};
|
|
1685
1797
|
if (fs.existsSync(dataFile)) {
|
|
1686
1798
|
data = JSON.parse(fs.readFileSync(dataFile, "utf8"));
|
|
@@ -1813,6 +1925,7 @@ class StableBrowser {
|
|
|
1813
1925
|
else {
|
|
1814
1926
|
fs.writeFileSync(screenshotPath, screenshotBuffer);
|
|
1815
1927
|
}
|
|
1928
|
+
return screenshotBuffer;
|
|
1816
1929
|
}
|
|
1817
1930
|
async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
|
|
1818
1931
|
const state = {
|
|
@@ -1835,7 +1948,7 @@ class StableBrowser {
|
|
|
1835
1948
|
await _commandError(state, e, this);
|
|
1836
1949
|
}
|
|
1837
1950
|
finally {
|
|
1838
|
-
_commandFinally(state, this);
|
|
1951
|
+
await _commandFinally(state, this);
|
|
1839
1952
|
}
|
|
1840
1953
|
}
|
|
1841
1954
|
async extractAttribute(selectors, attribute, variable, _params = null, options = {}, world = null) {
|
|
@@ -1848,6 +1961,7 @@ class StableBrowser {
|
|
|
1848
1961
|
world,
|
|
1849
1962
|
type: Types.EXTRACT,
|
|
1850
1963
|
text: `Extract attribute from element`,
|
|
1964
|
+
_text: `Extract attribute ${attribute} from ${selectors.element_name}`,
|
|
1851
1965
|
operation: "extractAttribute",
|
|
1852
1966
|
log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1853
1967
|
allowDisabled: true,
|
|
@@ -1865,6 +1979,9 @@ class StableBrowser {
|
|
|
1865
1979
|
case "value":
|
|
1866
1980
|
state.value = await state.element.inputValue();
|
|
1867
1981
|
break;
|
|
1982
|
+
case "text":
|
|
1983
|
+
state.value = await state.element.textContent();
|
|
1984
|
+
break;
|
|
1868
1985
|
default:
|
|
1869
1986
|
state.value = await state.element.getAttribute(attribute);
|
|
1870
1987
|
break;
|
|
@@ -1879,7 +1996,7 @@ class StableBrowser {
|
|
|
1879
1996
|
await _commandError(state, e, this);
|
|
1880
1997
|
}
|
|
1881
1998
|
finally {
|
|
1882
|
-
_commandFinally(state, this);
|
|
1999
|
+
await _commandFinally(state, this);
|
|
1883
2000
|
}
|
|
1884
2001
|
}
|
|
1885
2002
|
async verifyAttribute(selectors, attribute, value, _params = null, options = {}, world = null) {
|
|
@@ -1894,18 +2011,25 @@ class StableBrowser {
|
|
|
1894
2011
|
highlight: true,
|
|
1895
2012
|
screenshot: true,
|
|
1896
2013
|
text: `Verify element attribute`,
|
|
2014
|
+
_text: `Verify attribute ${attribute} from ${selectors.element_name} is ${value}`,
|
|
1897
2015
|
operation: "verifyAttribute",
|
|
1898
2016
|
log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1899
2017
|
allowDisabled: true,
|
|
1900
2018
|
};
|
|
1901
2019
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1902
2020
|
let val;
|
|
2021
|
+
let expectedValue;
|
|
1903
2022
|
try {
|
|
1904
2023
|
await _preCommand(state, this);
|
|
2024
|
+
expectedValue = await replaceWithLocalTestData(state.value, world);
|
|
2025
|
+
state.info.expectedValue = expectedValue;
|
|
1905
2026
|
switch (attribute) {
|
|
1906
2027
|
case "innerText":
|
|
1907
2028
|
val = String(await state.element.innerText());
|
|
1908
2029
|
break;
|
|
2030
|
+
case "text":
|
|
2031
|
+
val = String(await state.element.textContent());
|
|
2032
|
+
break;
|
|
1909
2033
|
case "value":
|
|
1910
2034
|
val = String(await state.element.inputValue());
|
|
1911
2035
|
break;
|
|
@@ -1923,26 +2047,29 @@ class StableBrowser {
|
|
|
1923
2047
|
val = String(await state.element.getAttribute(attribute));
|
|
1924
2048
|
break;
|
|
1925
2049
|
}
|
|
2050
|
+
state.info.value = val;
|
|
1926
2051
|
let regex;
|
|
1927
|
-
if (
|
|
1928
|
-
const patternBody =
|
|
2052
|
+
if (expectedValue.startsWith("/") && expectedValue.endsWith("/")) {
|
|
2053
|
+
const patternBody = expectedValue.slice(1, -1);
|
|
1929
2054
|
regex = new RegExp(patternBody, "g");
|
|
1930
2055
|
}
|
|
1931
2056
|
else {
|
|
1932
|
-
const escapedPattern =
|
|
2057
|
+
const escapedPattern = expectedValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1933
2058
|
regex = new RegExp(escapedPattern, "g");
|
|
1934
2059
|
}
|
|
1935
2060
|
if (!val.match(regex)) {
|
|
1936
|
-
|
|
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);
|
|
1937
2065
|
}
|
|
1938
|
-
state.info.expectedValue = val;
|
|
1939
2066
|
return state.info;
|
|
1940
2067
|
}
|
|
1941
2068
|
catch (e) {
|
|
1942
2069
|
await _commandError(state, e, this);
|
|
1943
2070
|
}
|
|
1944
2071
|
finally {
|
|
1945
|
-
_commandFinally(state, this);
|
|
2072
|
+
await _commandFinally(state, this);
|
|
1946
2073
|
}
|
|
1947
2074
|
}
|
|
1948
2075
|
async extractEmailData(emailAddress, options, world) {
|
|
@@ -2048,7 +2175,7 @@ class StableBrowser {
|
|
|
2048
2175
|
// console.log(`New outline is: ${node.style.outline}`);
|
|
2049
2176
|
if (window) {
|
|
2050
2177
|
window.addEventListener("beforeunload", function (e) {
|
|
2051
|
-
node.style.outline =
|
|
2178
|
+
node.style.outline = originalOutline;
|
|
2052
2179
|
});
|
|
2053
2180
|
}
|
|
2054
2181
|
setTimeout(function () {
|
|
@@ -2081,12 +2208,12 @@ class StableBrowser {
|
|
|
2081
2208
|
element.style.outline = "2px solid red";
|
|
2082
2209
|
if (window) {
|
|
2083
2210
|
window.addEventListener("beforeunload", function (e) {
|
|
2084
|
-
element.style.outline =
|
|
2211
|
+
element.style.outline = originalOutline;
|
|
2085
2212
|
});
|
|
2086
2213
|
}
|
|
2087
2214
|
// Set a timeout to revert to the original border after 2 seconds
|
|
2088
2215
|
setTimeout(function () {
|
|
2089
|
-
element.style.outline =
|
|
2216
|
+
element.style.outline = originalOutline;
|
|
2090
2217
|
}, 2000);
|
|
2091
2218
|
}
|
|
2092
2219
|
return;
|
|
@@ -2194,6 +2321,69 @@ class StableBrowser {
|
|
|
2194
2321
|
_reportToWorld(world, {
|
|
2195
2322
|
type: Types.VERIFY_PAGE_PATH,
|
|
2196
2323
|
text: "Verify page path",
|
|
2324
|
+
_text: "Verify the page path contains " + pathPart,
|
|
2325
|
+
screenshotId,
|
|
2326
|
+
result: error
|
|
2327
|
+
? {
|
|
2328
|
+
status: "FAILED",
|
|
2329
|
+
startTime,
|
|
2330
|
+
endTime,
|
|
2331
|
+
message: error?.message,
|
|
2332
|
+
}
|
|
2333
|
+
: {
|
|
2334
|
+
status: "PASSED",
|
|
2335
|
+
startTime,
|
|
2336
|
+
endTime,
|
|
2337
|
+
},
|
|
2338
|
+
info: info,
|
|
2339
|
+
});
|
|
2340
|
+
}
|
|
2341
|
+
}
|
|
2342
|
+
async verifyPageTitle(title, options = {}, world = null) {
|
|
2343
|
+
const startTime = Date.now();
|
|
2344
|
+
let error = null;
|
|
2345
|
+
let screenshotId = null;
|
|
2346
|
+
let screenshotPath = null;
|
|
2347
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2348
|
+
const info = {};
|
|
2349
|
+
info.log = "***** verify page title " + title + " *****\n";
|
|
2350
|
+
info.operation = "verifyPageTitle";
|
|
2351
|
+
const newValue = await this._replaceWithLocalData(title, world);
|
|
2352
|
+
if (newValue !== title) {
|
|
2353
|
+
this.logger.info(title + "=" + newValue);
|
|
2354
|
+
title = newValue;
|
|
2355
|
+
}
|
|
2356
|
+
info.title = title;
|
|
2357
|
+
try {
|
|
2358
|
+
for (let i = 0; i < 30; i++) {
|
|
2359
|
+
const foundTitle = await this.page.title();
|
|
2360
|
+
if (!foundTitle.includes(title)) {
|
|
2361
|
+
if (i === 29) {
|
|
2362
|
+
throw new Error(`url ${foundTitle} doesn't contain ${title}`);
|
|
2363
|
+
}
|
|
2364
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2365
|
+
continue;
|
|
2366
|
+
}
|
|
2367
|
+
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2368
|
+
return info;
|
|
2369
|
+
}
|
|
2370
|
+
}
|
|
2371
|
+
catch (e) {
|
|
2372
|
+
//await this.closeUnexpectedPopups();
|
|
2373
|
+
this.logger.error("verify page title failed " + info.log);
|
|
2374
|
+
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2375
|
+
info.screenshotPath = screenshotPath;
|
|
2376
|
+
Object.assign(e, { info: info });
|
|
2377
|
+
error = e;
|
|
2378
|
+
// throw e;
|
|
2379
|
+
await _commandError({ text: "verifyPageTitle", operation: "verifyPageTitle", title, info, throwError: true }, e, this);
|
|
2380
|
+
}
|
|
2381
|
+
finally {
|
|
2382
|
+
const endTime = Date.now();
|
|
2383
|
+
_reportToWorld(world, {
|
|
2384
|
+
type: Types.VERIFY_PAGE_PATH,
|
|
2385
|
+
text: "Verify page title",
|
|
2386
|
+
_text: "Verify the page title contains " + title,
|
|
2197
2387
|
screenshotId,
|
|
2198
2388
|
result: error
|
|
2199
2389
|
? {
|
|
@@ -2211,27 +2401,27 @@ class StableBrowser {
|
|
|
2211
2401
|
});
|
|
2212
2402
|
}
|
|
2213
2403
|
}
|
|
2214
|
-
async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state) {
|
|
2404
|
+
async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state, partial = true, ignoreCase = false) {
|
|
2215
2405
|
const frames = this.page.frames();
|
|
2216
2406
|
let results = [];
|
|
2217
|
-
let ignoreCase = false;
|
|
2407
|
+
// let ignoreCase = false;
|
|
2218
2408
|
for (let i = 0; i < frames.length; i++) {
|
|
2219
2409
|
if (dateAlternatives.date) {
|
|
2220
2410
|
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2221
|
-
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, {});
|
|
2222
2412
|
result.frame = frames[i];
|
|
2223
2413
|
results.push(result);
|
|
2224
2414
|
}
|
|
2225
2415
|
}
|
|
2226
2416
|
else if (numberAlternatives.number) {
|
|
2227
2417
|
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2228
|
-
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, {});
|
|
2229
2419
|
result.frame = frames[i];
|
|
2230
2420
|
results.push(result);
|
|
2231
2421
|
}
|
|
2232
2422
|
}
|
|
2233
2423
|
else {
|
|
2234
|
-
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, {});
|
|
2235
2425
|
result.frame = frames[i];
|
|
2236
2426
|
results.push(result);
|
|
2237
2427
|
}
|
|
@@ -2250,11 +2440,15 @@ class StableBrowser {
|
|
|
2250
2440
|
scroll: false,
|
|
2251
2441
|
highlight: false,
|
|
2252
2442
|
type: Types.VERIFY_PAGE_CONTAINS_TEXT,
|
|
2253
|
-
text: `Verify text exists in page`,
|
|
2443
|
+
text: `Verify the text '${text}' exists in page`,
|
|
2444
|
+
_text: `Verify the text '${text}' exists in page`,
|
|
2254
2445
|
operation: "verifyTextExistInPage",
|
|
2255
2446
|
log: "***** verify text " + text + " exists in page *****\n",
|
|
2256
2447
|
};
|
|
2257
|
-
|
|
2448
|
+
if (testForRegex(text)) {
|
|
2449
|
+
text = text.replace(/\\"/g, '"');
|
|
2450
|
+
}
|
|
2451
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2258
2452
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2259
2453
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2260
2454
|
if (newValue !== text) {
|
|
@@ -2267,7 +2461,15 @@ class StableBrowser {
|
|
|
2267
2461
|
await _preCommand(state, this);
|
|
2268
2462
|
state.info.text = text;
|
|
2269
2463
|
while (true) {
|
|
2270
|
-
|
|
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
|
+
}
|
|
2271
2473
|
if (resultWithElementsFound.length === 0) {
|
|
2272
2474
|
if (Date.now() - state.startTime > timeout) {
|
|
2273
2475
|
throw new Error(`Text ${text} not found in page`);
|
|
@@ -2275,35 +2477,40 @@ class StableBrowser {
|
|
|
2275
2477
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2276
2478
|
continue;
|
|
2277
2479
|
}
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
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
|
+
}
|
|
2303
2507
|
}
|
|
2508
|
+
await _screenshot(state, this);
|
|
2509
|
+
return state.info;
|
|
2510
|
+
}
|
|
2511
|
+
catch (error) {
|
|
2512
|
+
console.error(error);
|
|
2304
2513
|
}
|
|
2305
|
-
await _screenshot(state, this);
|
|
2306
|
-
return state.info;
|
|
2307
2514
|
}
|
|
2308
2515
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2309
2516
|
}
|
|
@@ -2311,7 +2518,7 @@ class StableBrowser {
|
|
|
2311
2518
|
await _commandError(state, e, this);
|
|
2312
2519
|
}
|
|
2313
2520
|
finally {
|
|
2314
|
-
_commandFinally(state, this);
|
|
2521
|
+
await _commandFinally(state, this);
|
|
2315
2522
|
}
|
|
2316
2523
|
}
|
|
2317
2524
|
async waitForTextToDisappear(text, options = {}, world = null) {
|
|
@@ -2324,11 +2531,15 @@ class StableBrowser {
|
|
|
2324
2531
|
scroll: false,
|
|
2325
2532
|
highlight: false,
|
|
2326
2533
|
type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
|
|
2327
|
-
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`,
|
|
2328
2536
|
operation: "verifyTextNotExistInPage",
|
|
2329
2537
|
log: "***** verify text " + text + " does not exist in page *****\n",
|
|
2330
2538
|
};
|
|
2331
|
-
|
|
2539
|
+
if (testForRegex(text)) {
|
|
2540
|
+
text = text.replace(/\\"/g, '"');
|
|
2541
|
+
}
|
|
2542
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2332
2543
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2333
2544
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2334
2545
|
if (newValue !== text) {
|
|
@@ -2340,8 +2551,16 @@ class StableBrowser {
|
|
|
2340
2551
|
try {
|
|
2341
2552
|
await _preCommand(state, this);
|
|
2342
2553
|
state.info.text = text;
|
|
2554
|
+
let resultWithElementsFound = {
|
|
2555
|
+
length: null, // initial cannot be 0
|
|
2556
|
+
};
|
|
2343
2557
|
while (true) {
|
|
2344
|
-
|
|
2558
|
+
try {
|
|
2559
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2560
|
+
}
|
|
2561
|
+
catch (error) {
|
|
2562
|
+
// ignore
|
|
2563
|
+
}
|
|
2345
2564
|
if (resultWithElementsFound.length === 0) {
|
|
2346
2565
|
await _screenshot(state, this);
|
|
2347
2566
|
return state.info;
|
|
@@ -2356,7 +2575,7 @@ class StableBrowser {
|
|
|
2356
2575
|
await _commandError(state, e, this);
|
|
2357
2576
|
}
|
|
2358
2577
|
finally {
|
|
2359
|
-
_commandFinally(state, this);
|
|
2578
|
+
await _commandFinally(state, this);
|
|
2360
2579
|
}
|
|
2361
2580
|
}
|
|
2362
2581
|
async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
|
|
@@ -2371,10 +2590,11 @@ class StableBrowser {
|
|
|
2371
2590
|
highlight: false,
|
|
2372
2591
|
type: Types.VERIFY_TEXT_WITH_RELATION,
|
|
2373
2592
|
text: `Verify text with relation to another text`,
|
|
2593
|
+
_text: "Search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found",
|
|
2374
2594
|
operation: "verify_text_with_relation",
|
|
2375
2595
|
log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
|
|
2376
2596
|
};
|
|
2377
|
-
const timeout = this.
|
|
2597
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2378
2598
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2379
2599
|
let newValue = await this._replaceWithLocalData(textAnchor, world);
|
|
2380
2600
|
if (newValue !== textAnchor) {
|
|
@@ -2392,8 +2612,16 @@ class StableBrowser {
|
|
|
2392
2612
|
try {
|
|
2393
2613
|
await _preCommand(state, this);
|
|
2394
2614
|
state.info.text = textToVerify;
|
|
2615
|
+
let resultWithElementsFound = {
|
|
2616
|
+
length: 0,
|
|
2617
|
+
};
|
|
2395
2618
|
while (true) {
|
|
2396
|
-
|
|
2619
|
+
try {
|
|
2620
|
+
resultWithElementsFound = await this.findTextInAllFrames(findDateAlternatives(textAnchor), findNumberAlternatives(textAnchor), textAnchor, state, false);
|
|
2621
|
+
}
|
|
2622
|
+
catch (error) {
|
|
2623
|
+
// ignore
|
|
2624
|
+
}
|
|
2397
2625
|
if (resultWithElementsFound.length === 0) {
|
|
2398
2626
|
if (Date.now() - state.startTime > timeout) {
|
|
2399
2627
|
throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
|
|
@@ -2401,51 +2629,56 @@ class StableBrowser {
|
|
|
2401
2629
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2402
2630
|
continue;
|
|
2403
2631
|
}
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
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;
|
|
2443
2675
|
}
|
|
2444
|
-
await _screenshot(state, this);
|
|
2445
|
-
return state.info;
|
|
2446
2676
|
}
|
|
2447
2677
|
}
|
|
2448
2678
|
}
|
|
2679
|
+
catch (error) {
|
|
2680
|
+
console.error(error);
|
|
2681
|
+
}
|
|
2449
2682
|
}
|
|
2450
2683
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2451
2684
|
}
|
|
@@ -2453,8 +2686,32 @@ class StableBrowser {
|
|
|
2453
2686
|
await _commandError(state, e, this);
|
|
2454
2687
|
}
|
|
2455
2688
|
finally {
|
|
2456
|
-
_commandFinally(state, this);
|
|
2689
|
+
await _commandFinally(state, this);
|
|
2690
|
+
}
|
|
2691
|
+
}
|
|
2692
|
+
async findRelatedTextInAllFrames(textAnchor, climb, textToVerify, params = {}, options = {}, world = null) {
|
|
2693
|
+
const frames = this.page.frames();
|
|
2694
|
+
let results = [];
|
|
2695
|
+
let ignoreCase = false;
|
|
2696
|
+
for (let i = 0; i < frames.length; i++) {
|
|
2697
|
+
const result = await this._locateElementByText(frames[i], textAnchor, "*:not(script, style, head)", false, true, ignoreCase, {});
|
|
2698
|
+
result.frame = frames[i];
|
|
2699
|
+
const climbArray = [];
|
|
2700
|
+
for (let i = 0; i < climb; i++) {
|
|
2701
|
+
climbArray.push("..");
|
|
2702
|
+
}
|
|
2703
|
+
let climbXpath = "xpath=" + climbArray.join("/");
|
|
2704
|
+
const newLocator = `[data-blinq-id-${result.randomToken}] ${climb > 0 ? ">> " + climbXpath : ""} >> internal:text=${testForRegex(textToVerify) ? textToVerify : unEscapeString(textToVerify)}`;
|
|
2705
|
+
const count = await frames[i].locator(newLocator).count();
|
|
2706
|
+
if (count > 0) {
|
|
2707
|
+
result.elementCount = count;
|
|
2708
|
+
result.locator = newLocator;
|
|
2709
|
+
results.push(result);
|
|
2710
|
+
}
|
|
2457
2711
|
}
|
|
2712
|
+
// state.info.results = results;
|
|
2713
|
+
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2714
|
+
return resultWithElementsFound;
|
|
2458
2715
|
}
|
|
2459
2716
|
async visualVerification(text, options = {}, world = null) {
|
|
2460
2717
|
const startTime = Date.now();
|
|
@@ -2474,10 +2731,13 @@ class StableBrowser {
|
|
|
2474
2731
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2475
2732
|
info.screenshotPath = screenshotPath;
|
|
2476
2733
|
const screenshot = await this.takeScreenshot();
|
|
2477
|
-
|
|
2478
|
-
method: "
|
|
2734
|
+
let request = {
|
|
2735
|
+
method: "post",
|
|
2736
|
+
maxBodyLength: Infinity,
|
|
2479
2737
|
url: `${serviceUrl}/api/runs/screenshots/validate-screenshot`,
|
|
2480
2738
|
headers: {
|
|
2739
|
+
"x-bvt-project-id": path.basename(this.project_path),
|
|
2740
|
+
"x-source": "aaa",
|
|
2481
2741
|
"Content-Type": "application/json",
|
|
2482
2742
|
Authorization: `Bearer ${process.env.TOKEN}`,
|
|
2483
2743
|
},
|
|
@@ -2486,7 +2746,7 @@ class StableBrowser {
|
|
|
2486
2746
|
screenshot: screenshot,
|
|
2487
2747
|
}),
|
|
2488
2748
|
};
|
|
2489
|
-
|
|
2749
|
+
const result = await axios.request(request);
|
|
2490
2750
|
if (result.data.status !== true) {
|
|
2491
2751
|
throw new Error("Visual validation failed");
|
|
2492
2752
|
}
|
|
@@ -2514,6 +2774,7 @@ class StableBrowser {
|
|
|
2514
2774
|
_reportToWorld(world, {
|
|
2515
2775
|
type: Types.VERIFY_VISUAL,
|
|
2516
2776
|
text: "Visual verification",
|
|
2777
|
+
_text: "Visual verification of " + text,
|
|
2517
2778
|
screenshotId,
|
|
2518
2779
|
result: error
|
|
2519
2780
|
? {
|
|
@@ -2780,6 +3041,32 @@ class StableBrowser {
|
|
|
2780
3041
|
}
|
|
2781
3042
|
return timeout;
|
|
2782
3043
|
}
|
|
3044
|
+
_getFindElementTimeout(options) {
|
|
3045
|
+
if (options && options.timeout) {
|
|
3046
|
+
return options.timeout;
|
|
3047
|
+
}
|
|
3048
|
+
if (this.configuration.find_element_timeout) {
|
|
3049
|
+
return this.configuration.find_element_timeout;
|
|
3050
|
+
}
|
|
3051
|
+
return 30000;
|
|
3052
|
+
}
|
|
3053
|
+
async saveStoreState(path = null, world = null) {
|
|
3054
|
+
const storageState = await this.page.context().storageState();
|
|
3055
|
+
//const testDataFile = _getDataFile(world, this.context, this);
|
|
3056
|
+
if (path) {
|
|
3057
|
+
// save { storageState: storageState } into the path
|
|
3058
|
+
fs.writeFileSync(path, JSON.stringify({ storageState: storageState }, null, 2));
|
|
3059
|
+
}
|
|
3060
|
+
else {
|
|
3061
|
+
await this.setTestData({ storageState: storageState }, world);
|
|
3062
|
+
}
|
|
3063
|
+
}
|
|
3064
|
+
async restoreSaveState(path = null, world = null) {
|
|
3065
|
+
await refreshBrowser(this, path, world);
|
|
3066
|
+
this.registerEventListeners(this.context);
|
|
3067
|
+
registerNetworkEvents(this.world, this, this.context, this.page);
|
|
3068
|
+
registerDownloadEvent(this.page, this.world, this.context);
|
|
3069
|
+
}
|
|
2783
3070
|
async waitForPageLoad(options = {}, world = null) {
|
|
2784
3071
|
let timeout = this._getLoadTimeout(options);
|
|
2785
3072
|
const promiseArray = [];
|
|
@@ -2847,6 +3134,7 @@ class StableBrowser {
|
|
|
2847
3134
|
highlight: false,
|
|
2848
3135
|
type: Types.CLOSE_PAGE,
|
|
2849
3136
|
text: `Close page`,
|
|
3137
|
+
_text: `Close the page`,
|
|
2850
3138
|
operation: "closePage",
|
|
2851
3139
|
log: "***** close page *****\n",
|
|
2852
3140
|
throwError: false,
|
|
@@ -2860,11 +3148,98 @@ class StableBrowser {
|
|
|
2860
3148
|
await _commandError(state, e, this);
|
|
2861
3149
|
}
|
|
2862
3150
|
finally {
|
|
2863
|
-
_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);
|
|
2864
3239
|
}
|
|
2865
3240
|
}
|
|
2866
3241
|
saveTestDataAsGlobal(options, world) {
|
|
2867
|
-
const dataFile =
|
|
3242
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
2868
3243
|
process.env.GLOBAL_TEST_DATA_FILE = dataFile;
|
|
2869
3244
|
this.logger.info("Save the scenario test data as global for the following scenarios.");
|
|
2870
3245
|
}
|
|
@@ -2894,6 +3269,7 @@ class StableBrowser {
|
|
|
2894
3269
|
_reportToWorld(world, {
|
|
2895
3270
|
type: Types.SET_VIEWPORT,
|
|
2896
3271
|
text: "set viewport size to " + width + "x" + hight,
|
|
3272
|
+
_text: "Set the viewport size to " + width + "x" + hight,
|
|
2897
3273
|
screenshotId,
|
|
2898
3274
|
result: error
|
|
2899
3275
|
? {
|
|
@@ -2964,7 +3340,39 @@ class StableBrowser {
|
|
|
2964
3340
|
console.log("#-#");
|
|
2965
3341
|
}
|
|
2966
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) { }
|
|
2967
3372
|
async beforeStep(world, step) {
|
|
3373
|
+
if (!this.beforeScenarioCalled) {
|
|
3374
|
+
this.beforeScenario(world, step);
|
|
3375
|
+
}
|
|
2968
3376
|
if (this.stepIndex === undefined) {
|
|
2969
3377
|
this.stepIndex = 0;
|
|
2970
3378
|
}
|
|
@@ -2981,22 +3389,47 @@ class StableBrowser {
|
|
|
2981
3389
|
else {
|
|
2982
3390
|
this.stepName = "step " + this.stepIndex;
|
|
2983
3391
|
}
|
|
2984
|
-
if (this.context) {
|
|
2985
|
-
this.context.examplesRow = extractStepExampleParameters(step);
|
|
2986
|
-
}
|
|
2987
3392
|
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
2988
3393
|
if (this.context.browserObject.context) {
|
|
2989
3394
|
await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
|
|
2990
3395
|
}
|
|
2991
3396
|
}
|
|
2992
|
-
if (this.
|
|
2993
|
-
this.
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
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
|
+
}
|
|
2997
3404
|
}
|
|
2998
3405
|
}
|
|
2999
3406
|
}
|
|
3407
|
+
async getAriaSnapshot() {
|
|
3408
|
+
try {
|
|
3409
|
+
// find the page url
|
|
3410
|
+
const url = await this.page.url();
|
|
3411
|
+
// extract the path from the url
|
|
3412
|
+
const path = new URL(url).pathname;
|
|
3413
|
+
// get the page title
|
|
3414
|
+
const title = await this.page.title();
|
|
3415
|
+
// go over other frams
|
|
3416
|
+
const frames = this.page.frames();
|
|
3417
|
+
const snapshots = [];
|
|
3418
|
+
const content = [`- path: ${path}`, `- title: ${title}`];
|
|
3419
|
+
const timeout = this.configuration.ariaSnapshotTimeout ? this.configuration.ariaSnapshotTimeout : 3000;
|
|
3420
|
+
for (let i = 0; i < frames.length; i++) {
|
|
3421
|
+
content.push(`- frame: ${i}`);
|
|
3422
|
+
const frame = frames[i];
|
|
3423
|
+
const snapshot = await frame.locator("body").ariaSnapshot({ timeout });
|
|
3424
|
+
content.push(snapshot);
|
|
3425
|
+
}
|
|
3426
|
+
return content.join("\n");
|
|
3427
|
+
}
|
|
3428
|
+
catch (e) {
|
|
3429
|
+
console.error(e);
|
|
3430
|
+
}
|
|
3431
|
+
return null;
|
|
3432
|
+
}
|
|
3000
3433
|
async afterStep(world, step) {
|
|
3001
3434
|
this.stepName = null;
|
|
3002
3435
|
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
@@ -3004,11 +3437,25 @@ class StableBrowser {
|
|
|
3004
3437
|
await this.context.browserObject.context.tracing.stopChunk({
|
|
3005
3438
|
path: path.join(this.context.browserObject.traceFolder, `trace-${this.stepIndex}.zip`),
|
|
3006
3439
|
});
|
|
3440
|
+
if (world && world.attach) {
|
|
3441
|
+
await world.attach(JSON.stringify({
|
|
3442
|
+
type: "trace",
|
|
3443
|
+
traceFilePath: `trace-${this.stepIndex}.zip`,
|
|
3444
|
+
}), "application/json+trace");
|
|
3445
|
+
}
|
|
3446
|
+
// console.log("trace file created", `trace-${this.stepIndex}.zip`);
|
|
3007
3447
|
}
|
|
3008
3448
|
}
|
|
3009
3449
|
if (this.context) {
|
|
3010
3450
|
this.context.examplesRow = null;
|
|
3011
3451
|
}
|
|
3452
|
+
if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
|
|
3453
|
+
const snapshot = await this.getAriaSnapshot();
|
|
3454
|
+
if (snapshot) {
|
|
3455
|
+
const obj = {};
|
|
3456
|
+
await world.attach(JSON.stringify(snapshot), "application/json+snapshot-after");
|
|
3457
|
+
}
|
|
3458
|
+
}
|
|
3012
3459
|
}
|
|
3013
3460
|
}
|
|
3014
3461
|
function createTimedPromise(promise, label) {
|