automation_model 1.0.398-dev → 1.0.398-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/lib/api.js +20 -10
- package/lib/api.js.map +1 -1
- package/lib/auto_page.d.ts +1 -1
- package/lib/auto_page.js +28 -1
- package/lib/auto_page.js.map +1 -1
- package/lib/browser_manager.js +3 -2
- package/lib/browser_manager.js.map +1 -1
- package/lib/environment.d.ts +3 -0
- package/lib/environment.js +1 -0
- package/lib/environment.js.map +1 -1
- package/lib/init_browser.d.ts +2 -1
- package/lib/init_browser.js +14 -3
- package/lib/init_browser.js.map +1 -1
- package/lib/stable_browser.d.ts +11 -1
- package/lib/stable_browser.js +277 -137
- package/lib/stable_browser.js.map +1 -1
- package/lib/test_context.d.ts +1 -0
- package/lib/test_context.js +1 -0
- package/lib/test_context.js.map +1 -1
- package/lib/utils.js +2 -2
- package/lib/utils.js.map +1 -1
- package/package.json +6 -5
package/lib/stable_browser.js
CHANGED
|
@@ -13,6 +13,9 @@ import { getTableCells, getTableData } from "./table_analyze.js";
|
|
|
13
13
|
import objectPath from "object-path";
|
|
14
14
|
import { decrypt } from "./utils.js";
|
|
15
15
|
import csv from "csv-parser";
|
|
16
|
+
import { Readable } from "node:stream";
|
|
17
|
+
import readline from "readline";
|
|
18
|
+
import { getContext } from "./init_browser.js";
|
|
16
19
|
const Types = {
|
|
17
20
|
CLICK: "click_element",
|
|
18
21
|
NAVIGATE: "navigate",
|
|
@@ -28,6 +31,7 @@ const Types = {
|
|
|
28
31
|
SELECT: "select_combobox",
|
|
29
32
|
VERIFY_PAGE_PATH: "verify_page_path",
|
|
30
33
|
TYPE_PRESS: "type_press",
|
|
34
|
+
PRESS: "press_key",
|
|
31
35
|
HOVER: "hover_element",
|
|
32
36
|
CHECK: "check_element",
|
|
33
37
|
UNCHECK: "uncheck_element",
|
|
@@ -36,7 +40,10 @@ const Types = {
|
|
|
36
40
|
SET_DATE_TIME: "set_date_time",
|
|
37
41
|
SET_VIEWPORT: "set_viewport",
|
|
38
42
|
VERIFY_VISUAL: "verify_visual",
|
|
43
|
+
LOAD_DATA: "load_data",
|
|
44
|
+
SET_INPUT: "set_input",
|
|
39
45
|
};
|
|
46
|
+
export const apps = {};
|
|
40
47
|
class StableBrowser {
|
|
41
48
|
constructor(browser, page, logger = null, context = null) {
|
|
42
49
|
this.browser = browser;
|
|
@@ -46,6 +53,7 @@ class StableBrowser {
|
|
|
46
53
|
this.project_path = null;
|
|
47
54
|
this.webLogFile = null;
|
|
48
55
|
this.configuration = null;
|
|
56
|
+
this.appName = "main";
|
|
49
57
|
if (!this.logger) {
|
|
50
58
|
this.logger = console;
|
|
51
59
|
}
|
|
@@ -76,14 +84,28 @@ class StableBrowser {
|
|
|
76
84
|
this.registerRequestListener();
|
|
77
85
|
context.pages = [this.page];
|
|
78
86
|
context.pageLoading = { status: false };
|
|
87
|
+
this.registerPageEventListeners(context);
|
|
88
|
+
}
|
|
89
|
+
registerPageEventListeners(context) {
|
|
79
90
|
context.playContext.on("page", async function (page) {
|
|
80
91
|
context.pageLoading.status = true;
|
|
81
92
|
this.page = page;
|
|
82
93
|
context.page = page;
|
|
83
94
|
context.pages.push(page);
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
95
|
+
page.on("close", async () => {
|
|
96
|
+
if (this.context && this.context.pages && this.context.pages.length > 1) {
|
|
97
|
+
this.context.pages.pop();
|
|
98
|
+
this.page = this.context.pages[this.context.pages.length - 1];
|
|
99
|
+
this.context.page = this.page;
|
|
100
|
+
try {
|
|
101
|
+
let title = await this.page.title();
|
|
102
|
+
console.log("Switched to page " + title);
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
console.error("Error on page close", error);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
});
|
|
87
109
|
try {
|
|
88
110
|
await this.waitForPageLoad();
|
|
89
111
|
console.log("Switch page: " + (await page.title()));
|
|
@@ -94,6 +116,36 @@ class StableBrowser {
|
|
|
94
116
|
context.pageLoading.status = false;
|
|
95
117
|
}.bind(this));
|
|
96
118
|
}
|
|
119
|
+
async switchApp(appName) {
|
|
120
|
+
// check if the current app (this.appName) is the same as the new app
|
|
121
|
+
if (this.appName === appName) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
let navigate = false;
|
|
125
|
+
if (!apps[appName]) {
|
|
126
|
+
let newContext = await getContext(null, false, this.logger, appName, false, this);
|
|
127
|
+
navigate = true;
|
|
128
|
+
apps[appName] = {
|
|
129
|
+
context: newContext,
|
|
130
|
+
browser: newContext.browser,
|
|
131
|
+
page: newContext.page,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
const tempContext = {};
|
|
135
|
+
this._copyContext(this, tempContext);
|
|
136
|
+
this._copyContext(apps[appName], this);
|
|
137
|
+
apps[this.appName] = tempContext;
|
|
138
|
+
this.appName = appName;
|
|
139
|
+
if (navigate) {
|
|
140
|
+
await this.goto(this.context.environment.baseUrl);
|
|
141
|
+
await this.waitForPageLoad();
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
_copyContext(from, to) {
|
|
145
|
+
to.browser = from.browser;
|
|
146
|
+
to.page = from.page;
|
|
147
|
+
to.context = from.context;
|
|
148
|
+
}
|
|
97
149
|
getWebLogFile(logFolder) {
|
|
98
150
|
if (!fs.existsSync(logFolder)) {
|
|
99
151
|
fs.mkdirSync(logFolder, { recursive: true });
|
|
@@ -178,24 +230,78 @@ class StableBrowser {
|
|
|
178
230
|
}
|
|
179
231
|
return text;
|
|
180
232
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
233
|
+
_fixLocatorUsingParams(locator, _params) {
|
|
234
|
+
// check if not null
|
|
235
|
+
if (!locator) {
|
|
236
|
+
return locator;
|
|
237
|
+
}
|
|
238
|
+
// clone the locator
|
|
239
|
+
locator = JSON.parse(JSON.stringify(locator));
|
|
240
|
+
this.scanAndManipulate(locator, _params);
|
|
241
|
+
return locator;
|
|
242
|
+
}
|
|
243
|
+
_isObject(value) {
|
|
244
|
+
return value && typeof value === "object" && value.constructor === Object;
|
|
245
|
+
}
|
|
246
|
+
scanAndManipulate(currentObj, _params) {
|
|
247
|
+
for (const key in currentObj) {
|
|
248
|
+
if (typeof currentObj[key] === "string") {
|
|
249
|
+
// Perform string manipulation
|
|
250
|
+
currentObj[key] = this._fixUsingParams(currentObj[key], _params);
|
|
251
|
+
}
|
|
252
|
+
else if (this._isObject(currentObj[key])) {
|
|
253
|
+
// Recursively scan nested objects
|
|
254
|
+
this.scanAndManipulate(currentObj[key], _params);
|
|
255
|
+
}
|
|
184
256
|
}
|
|
257
|
+
}
|
|
258
|
+
_getLocator(locator, scope, _params) {
|
|
259
|
+
locator = this._fixLocatorUsingParams(locator, _params);
|
|
260
|
+
let locatorReturn;
|
|
185
261
|
if (locator.role) {
|
|
186
262
|
if (locator.role[1].nameReg) {
|
|
187
263
|
locator.role[1].name = reg_parser(locator.role[1].nameReg);
|
|
188
264
|
delete locator.role[1].nameReg;
|
|
189
265
|
}
|
|
190
|
-
if (locator.role[1].name) {
|
|
191
|
-
|
|
192
|
-
}
|
|
193
|
-
|
|
266
|
+
// if (locator.role[1].name) {
|
|
267
|
+
// locator.role[1].name = this._fixUsingParams(locator.role[1].name, _params);
|
|
268
|
+
// }
|
|
269
|
+
locatorReturn = scope.getByRole(locator.role[0], locator.role[1]);
|
|
194
270
|
}
|
|
195
271
|
if (locator.css) {
|
|
196
|
-
|
|
272
|
+
locatorReturn = scope.locator(locator.css);
|
|
273
|
+
}
|
|
274
|
+
// handle role/name locators
|
|
275
|
+
// locator.selector will be something like: textbox[name="Username"i]
|
|
276
|
+
if (locator.engine === "internal:role") {
|
|
277
|
+
// extract the role, name and the i/s flags using regex
|
|
278
|
+
const match = locator.selector.match(/(.*)\[(.*)="(.*)"(.*)\]/);
|
|
279
|
+
if (match) {
|
|
280
|
+
const role = match[1];
|
|
281
|
+
const name = match[3];
|
|
282
|
+
const flags = match[4];
|
|
283
|
+
locatorReturn = scope.getByRole(role, { name }, { exact: flags === "i" });
|
|
284
|
+
}
|
|
197
285
|
}
|
|
198
|
-
|
|
286
|
+
if (locator === null || locator === void 0 ? void 0 : locator.engine) {
|
|
287
|
+
if (locator.engine === "css") {
|
|
288
|
+
locatorReturn = scope.locator(locator.selector);
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
let selector = locator.selector;
|
|
292
|
+
if (locator.engine === "internal:attr") {
|
|
293
|
+
if (!selector.startsWith("[")) {
|
|
294
|
+
selector = `[${selector}]`;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
locatorReturn = scope.locator(`${locator.engine}=${selector}`);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
if (!locatorReturn) {
|
|
301
|
+
console.error(locator);
|
|
302
|
+
throw new Error("Locator undefined");
|
|
303
|
+
}
|
|
304
|
+
return locatorReturn;
|
|
199
305
|
}
|
|
200
306
|
async _locateElmentByTextClimbCss(scope, text, climb, css, _params) {
|
|
201
307
|
let result = await this._locateElementByText(scope, this._fixUsingParams(text, _params), "*", false, true, _params);
|
|
@@ -406,6 +512,8 @@ class StableBrowser {
|
|
|
406
512
|
if (result.foundElements.length > 0) {
|
|
407
513
|
let dialogCloseLocator = result.foundElements[0].locator;
|
|
408
514
|
await dialogCloseLocator.click();
|
|
515
|
+
// wait for the dialog to close
|
|
516
|
+
await dialogCloseLocator.waitFor({ state: "hidden" });
|
|
409
517
|
return { rerun: true };
|
|
410
518
|
}
|
|
411
519
|
}
|
|
@@ -414,7 +522,7 @@ class StableBrowser {
|
|
|
414
522
|
}
|
|
415
523
|
async _locate(selectors, info, _params, timeout = 30000) {
|
|
416
524
|
for (let i = 0; i < 3; i++) {
|
|
417
|
-
info.log += "attempt " + i + ":
|
|
525
|
+
info.log += "attempt " + i + ": total locators " + selectors.locators.length + "\n";
|
|
418
526
|
for (let j = 0; j < selectors.locators.length; j++) {
|
|
419
527
|
let selector = selectors.locators[j];
|
|
420
528
|
info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
|
|
@@ -434,9 +542,27 @@ class StableBrowser {
|
|
|
434
542
|
//let arrayMode = Array.isArray(selectors);
|
|
435
543
|
let scope = this.page;
|
|
436
544
|
if (selectors.iframe_src || selectors.frameLocators) {
|
|
545
|
+
const findFrame = (frame, framescope) => {
|
|
546
|
+
for (let i = 0; i < frame.selectors.length; i++) {
|
|
547
|
+
let frameLocator = frame.selectors[i];
|
|
548
|
+
if (frameLocator.css) {
|
|
549
|
+
framescope = framescope.frameLocator(frameLocator.css);
|
|
550
|
+
break;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
if (frame.children) {
|
|
554
|
+
return findFrame(frame.children, framescope);
|
|
555
|
+
}
|
|
556
|
+
return framescope;
|
|
557
|
+
};
|
|
437
558
|
info.log += "searching for iframe " + selectors.iframe_src + "/" + selectors.frameLocators + "\n";
|
|
438
559
|
while (true) {
|
|
439
560
|
let frameFound = false;
|
|
561
|
+
if (selectors.nestFrmLoc) {
|
|
562
|
+
scope = findFrame(selectors.nestFrmLoc, scope);
|
|
563
|
+
frameFound = true;
|
|
564
|
+
break;
|
|
565
|
+
}
|
|
440
566
|
if (selectors.frameLocators) {
|
|
441
567
|
for (let i = 0; i < selectors.frameLocators.length; i++) {
|
|
442
568
|
let frameLocator = selectors.frameLocators[i];
|
|
@@ -600,6 +726,9 @@ class StableBrowser {
|
|
|
600
726
|
async click(selectors, _params, options = {}, world = null) {
|
|
601
727
|
this._validateSelectors(selectors);
|
|
602
728
|
const startTime = Date.now();
|
|
729
|
+
if (options && options.context) {
|
|
730
|
+
selectors.locators[0].text = options.context;
|
|
731
|
+
}
|
|
603
732
|
const info = {};
|
|
604
733
|
info.log = "***** click on " + selectors.element_name + " *****\n";
|
|
605
734
|
info.operation = "click";
|
|
@@ -613,14 +742,14 @@ class StableBrowser {
|
|
|
613
742
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
614
743
|
try {
|
|
615
744
|
await this._highlightElements(element);
|
|
616
|
-
await element.click(
|
|
745
|
+
await element.click();
|
|
617
746
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
618
747
|
}
|
|
619
748
|
catch (e) {
|
|
620
749
|
// await this.closeUnexpectedPopups();
|
|
621
750
|
info.log += "click failed, will try again" + "\n";
|
|
622
751
|
element = await this._locate(selectors, info, _params);
|
|
623
|
-
await element.click
|
|
752
|
+
await element.dispatchEvent("click");
|
|
624
753
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
625
754
|
}
|
|
626
755
|
await this.waitForPageLoad();
|
|
@@ -673,7 +802,7 @@ class StableBrowser {
|
|
|
673
802
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
674
803
|
try {
|
|
675
804
|
await this._highlightElements(element);
|
|
676
|
-
await element.setChecked(checked
|
|
805
|
+
await element.setChecked(checked);
|
|
677
806
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
678
807
|
}
|
|
679
808
|
catch (e) {
|
|
@@ -737,7 +866,7 @@ class StableBrowser {
|
|
|
737
866
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
738
867
|
try {
|
|
739
868
|
await this._highlightElements(element);
|
|
740
|
-
await element.hover(
|
|
869
|
+
await element.hover();
|
|
741
870
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
742
871
|
}
|
|
743
872
|
catch (e) {
|
|
@@ -799,7 +928,7 @@ class StableBrowser {
|
|
|
799
928
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
800
929
|
try {
|
|
801
930
|
await this._highlightElements(element);
|
|
802
|
-
await element.selectOption(values
|
|
931
|
+
await element.selectOption(values);
|
|
803
932
|
}
|
|
804
933
|
catch (e) {
|
|
805
934
|
//await this.closeUnexpectedPopups();
|
|
@@ -908,71 +1037,45 @@ class StableBrowser {
|
|
|
908
1037
|
});
|
|
909
1038
|
}
|
|
910
1039
|
}
|
|
911
|
-
async
|
|
1040
|
+
async setInputValue(selectors, value, _params = null, options = {}, world = null) {
|
|
1041
|
+
// set input value for non fillable inputs like date, time, range, color, etc.
|
|
912
1042
|
this._validateSelectors(selectors);
|
|
913
1043
|
const startTime = Date.now();
|
|
914
|
-
let error = null;
|
|
915
|
-
let screenshotId = null;
|
|
916
|
-
let screenshotPath = null;
|
|
917
1044
|
const info = {};
|
|
918
|
-
info.log = "";
|
|
919
|
-
info.operation =
|
|
1045
|
+
info.log = "***** set input value " + selectors.element_name + " *****\n";
|
|
1046
|
+
info.operation = "setInputValue";
|
|
920
1047
|
info.selectors = selectors;
|
|
1048
|
+
value = this._fixUsingParams(value, _params);
|
|
921
1049
|
info.value = value;
|
|
1050
|
+
let error = null;
|
|
1051
|
+
let screenshotId = null;
|
|
1052
|
+
let screenshotPath = null;
|
|
922
1053
|
try {
|
|
923
1054
|
value = await this._replaceWithLocalData(value, this);
|
|
924
1055
|
let element = await this._locate(selectors, info, _params);
|
|
925
|
-
//insert red border around the element
|
|
926
1056
|
await this.scrollIfNeeded(element, info);
|
|
927
1057
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
928
1058
|
await this._highlightElements(element);
|
|
929
1059
|
try {
|
|
930
|
-
await element.
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
value = dayjs(value).format(format);
|
|
934
|
-
await element.fill(value);
|
|
935
|
-
}
|
|
936
|
-
else {
|
|
937
|
-
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
938
|
-
await element.evaluateHandle((el, dateTimeValue) => {
|
|
939
|
-
el.value = ""; // clear input
|
|
940
|
-
el.value = dateTimeValue;
|
|
941
|
-
}, dateTimeValue);
|
|
942
|
-
}
|
|
943
|
-
if (enter) {
|
|
944
|
-
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
945
|
-
await this.page.keyboard.press("Enter");
|
|
946
|
-
await this.waitForPageLoad();
|
|
947
|
-
}
|
|
1060
|
+
await element.evaluateHandle((el, value) => {
|
|
1061
|
+
el.value = value;
|
|
1062
|
+
}, value);
|
|
948
1063
|
}
|
|
949
1064
|
catch (error) {
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
this.logger.info("Trying again")(({ screenshotId, screenshotPath } = await this._screenShot(options, world, info)));
|
|
1065
|
+
this.logger.error("setInputValue failed, will try again");
|
|
1066
|
+
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
953
1067
|
info.screenshotPath = screenshotPath;
|
|
954
1068
|
Object.assign(error, { info: info });
|
|
955
|
-
await element.
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
value = dayjs(value).format(format);
|
|
959
|
-
await element.fill(value);
|
|
960
|
-
}
|
|
961
|
-
else {
|
|
962
|
-
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
963
|
-
await element.evaluateHandle((el, dateTimeValue) => {
|
|
964
|
-
el.value = ""; // clear input
|
|
965
|
-
el.value = dateTimeValue;
|
|
966
|
-
}, dateTimeValue);
|
|
967
|
-
}
|
|
968
|
-
if (enter) {
|
|
969
|
-
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
970
|
-
await this.page.keyboard.press("Enter");
|
|
971
|
-
await this.waitForPageLoad();
|
|
972
|
-
}
|
|
1069
|
+
await element.evaluateHandle((el, value) => {
|
|
1070
|
+
el.value = value;
|
|
1071
|
+
});
|
|
973
1072
|
}
|
|
974
1073
|
}
|
|
975
|
-
catch (
|
|
1074
|
+
catch (e) {
|
|
1075
|
+
this.logger.error("setInputValue failed " + info.log);
|
|
1076
|
+
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1077
|
+
info.screenshotPath = screenshotPath;
|
|
1078
|
+
Object.assign(e, { info: info });
|
|
976
1079
|
error = e;
|
|
977
1080
|
throw e;
|
|
978
1081
|
}
|
|
@@ -980,10 +1083,10 @@ class StableBrowser {
|
|
|
980
1083
|
const endTime = Date.now();
|
|
981
1084
|
this._reportToWorld(world, {
|
|
982
1085
|
element_name: selectors.element_name,
|
|
983
|
-
type: Types.
|
|
984
|
-
|
|
1086
|
+
type: Types.SET_INPUT,
|
|
1087
|
+
text: `Set input value`,
|
|
985
1088
|
value: value,
|
|
986
|
-
|
|
1089
|
+
screenshotId,
|
|
987
1090
|
result: error
|
|
988
1091
|
? {
|
|
989
1092
|
status: "FAILED",
|
|
@@ -1000,7 +1103,7 @@ class StableBrowser {
|
|
|
1000
1103
|
});
|
|
1001
1104
|
}
|
|
1002
1105
|
}
|
|
1003
|
-
async setDateTime(selectors, value, enter = false, _params = null, options = {}, world = null) {
|
|
1106
|
+
async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
|
|
1004
1107
|
this._validateSelectors(selectors);
|
|
1005
1108
|
const startTime = Date.now();
|
|
1006
1109
|
let error = null;
|
|
@@ -1012,6 +1115,7 @@ class StableBrowser {
|
|
|
1012
1115
|
info.selectors = selectors;
|
|
1013
1116
|
info.value = value;
|
|
1014
1117
|
try {
|
|
1118
|
+
value = await this._replaceWithLocalData(value, this);
|
|
1015
1119
|
let element = await this._locate(selectors, info, _params);
|
|
1016
1120
|
//insert red border around the element
|
|
1017
1121
|
await this.scrollIfNeeded(element, info);
|
|
@@ -1020,28 +1124,51 @@ class StableBrowser {
|
|
|
1020
1124
|
try {
|
|
1021
1125
|
await element.click();
|
|
1022
1126
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1127
|
+
if (format) {
|
|
1128
|
+
value = dayjs(value).format(format);
|
|
1129
|
+
await element.fill(value);
|
|
1130
|
+
}
|
|
1131
|
+
else {
|
|
1132
|
+
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
1133
|
+
await element.evaluateHandle((el, dateTimeValue) => {
|
|
1134
|
+
el.value = ""; // clear input
|
|
1135
|
+
el.value = dateTimeValue;
|
|
1136
|
+
}, dateTimeValue);
|
|
1137
|
+
}
|
|
1138
|
+
if (enter) {
|
|
1139
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1140
|
+
await this.page.keyboard.press("Enter");
|
|
1141
|
+
await this.waitForPageLoad();
|
|
1142
|
+
}
|
|
1028
1143
|
}
|
|
1029
|
-
catch (
|
|
1144
|
+
catch (err) {
|
|
1030
1145
|
//await this.closeUnexpectedPopups();
|
|
1031
1146
|
this.logger.error("setting date time input failed " + JSON.stringify(info));
|
|
1032
|
-
this.logger.info("Trying again")
|
|
1147
|
+
this.logger.info("Trying again");
|
|
1148
|
+
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1033
1149
|
info.screenshotPath = screenshotPath;
|
|
1034
|
-
Object.assign(
|
|
1150
|
+
Object.assign(err, { info: info });
|
|
1035
1151
|
await element.click();
|
|
1036
1152
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1153
|
+
if (format) {
|
|
1154
|
+
value = dayjs(value).format(format);
|
|
1155
|
+
await element.fill(value);
|
|
1156
|
+
}
|
|
1157
|
+
else {
|
|
1158
|
+
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
1159
|
+
await element.evaluateHandle((el, dateTimeValue) => {
|
|
1160
|
+
el.value = ""; // clear input
|
|
1161
|
+
el.value = dateTimeValue;
|
|
1162
|
+
}, dateTimeValue);
|
|
1163
|
+
}
|
|
1164
|
+
if (enter) {
|
|
1165
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1166
|
+
await this.page.keyboard.press("Enter");
|
|
1167
|
+
await this.waitForPageLoad();
|
|
1168
|
+
}
|
|
1042
1169
|
}
|
|
1043
1170
|
}
|
|
1044
|
-
catch (
|
|
1171
|
+
catch (e) {
|
|
1045
1172
|
error = e;
|
|
1046
1173
|
throw e;
|
|
1047
1174
|
}
|
|
@@ -1091,20 +1218,32 @@ class StableBrowser {
|
|
|
1091
1218
|
await this.scrollIfNeeded(element, info);
|
|
1092
1219
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1093
1220
|
await this._highlightElements(element);
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1221
|
+
if (options === null || options === undefined || !options.press) {
|
|
1222
|
+
try {
|
|
1223
|
+
let currentValue = await element.inputValue();
|
|
1224
|
+
if (currentValue) {
|
|
1225
|
+
await element.fill("");
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
catch (e) {
|
|
1229
|
+
this.logger.info("unable to clear input value");
|
|
1098
1230
|
}
|
|
1099
1231
|
}
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1232
|
+
if (options === null || options === undefined || options.press) {
|
|
1233
|
+
try {
|
|
1234
|
+
await element.click({ timeout: 5000 });
|
|
1235
|
+
}
|
|
1236
|
+
catch (e) {
|
|
1237
|
+
await element.dispatchEvent("click");
|
|
1238
|
+
}
|
|
1105
1239
|
}
|
|
1106
|
-
|
|
1107
|
-
|
|
1240
|
+
else {
|
|
1241
|
+
try {
|
|
1242
|
+
await element.focus();
|
|
1243
|
+
}
|
|
1244
|
+
catch (e) {
|
|
1245
|
+
await element.dispatchEvent("focus");
|
|
1246
|
+
}
|
|
1108
1247
|
}
|
|
1109
1248
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1110
1249
|
const valueSegment = _value.split("&&");
|
|
@@ -1192,7 +1331,7 @@ class StableBrowser {
|
|
|
1192
1331
|
let element = await this._locate(selectors, info, _params);
|
|
1193
1332
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1194
1333
|
await this._highlightElements(element);
|
|
1195
|
-
await element.fill(value
|
|
1334
|
+
await element.fill(value);
|
|
1196
1335
|
await element.dispatchEvent("change");
|
|
1197
1336
|
if (enter) {
|
|
1198
1337
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
@@ -1411,7 +1550,7 @@ class StableBrowser {
|
|
|
1411
1550
|
return info;
|
|
1412
1551
|
}
|
|
1413
1552
|
catch (e) {
|
|
1414
|
-
|
|
1553
|
+
await this.closeUnexpectedPopups();
|
|
1415
1554
|
this.logger.error("verify element contains text failed " + info.log);
|
|
1416
1555
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1417
1556
|
info.screenshotPath = screenshotPath;
|
|
@@ -1459,6 +1598,29 @@ class StableBrowser {
|
|
|
1459
1598
|
}
|
|
1460
1599
|
return dataFile;
|
|
1461
1600
|
}
|
|
1601
|
+
async waitForUserInput(message, world = null) {
|
|
1602
|
+
if (!message) {
|
|
1603
|
+
message = "# Wait for user input. Press any key to continue";
|
|
1604
|
+
}
|
|
1605
|
+
else {
|
|
1606
|
+
message = "# Wait for user input. " + message;
|
|
1607
|
+
}
|
|
1608
|
+
message += "\n";
|
|
1609
|
+
const value = await new Promise((resolve) => {
|
|
1610
|
+
const rl = readline.createInterface({
|
|
1611
|
+
input: process.stdin,
|
|
1612
|
+
output: process.stdout,
|
|
1613
|
+
});
|
|
1614
|
+
rl.question(message, (answer) => {
|
|
1615
|
+
rl.close();
|
|
1616
|
+
resolve(answer);
|
|
1617
|
+
});
|
|
1618
|
+
});
|
|
1619
|
+
if (value) {
|
|
1620
|
+
this.logger.info(`{{userInput}} was set to: ${value}`);
|
|
1621
|
+
}
|
|
1622
|
+
this.setTestData({ userInput: value }, world);
|
|
1623
|
+
}
|
|
1462
1624
|
setTestData(testData, world = null) {
|
|
1463
1625
|
if (!testData) {
|
|
1464
1626
|
return;
|
|
@@ -1486,7 +1648,7 @@ class StableBrowser {
|
|
|
1486
1648
|
const data = fs.readFileSync(filePath, "utf8");
|
|
1487
1649
|
const results = [];
|
|
1488
1650
|
return new Promise((resolve, reject) => {
|
|
1489
|
-
const readableStream = new
|
|
1651
|
+
const readableStream = new Readable();
|
|
1490
1652
|
readableStream._read = () => { }; // _read is required but you can noop it
|
|
1491
1653
|
readableStream.push(data);
|
|
1492
1654
|
readableStream.push(null);
|
|
@@ -1666,13 +1828,13 @@ class StableBrowser {
|
|
|
1666
1828
|
])));
|
|
1667
1829
|
const { data } = await client.send("Page.captureScreenshot", {
|
|
1668
1830
|
format: "png",
|
|
1669
|
-
clip: {
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
},
|
|
1831
|
+
// clip: {
|
|
1832
|
+
// x: 0,
|
|
1833
|
+
// y: 0,
|
|
1834
|
+
// width: viewportWidth,
|
|
1835
|
+
// height: viewportHeight,
|
|
1836
|
+
// scale: 1,
|
|
1837
|
+
// },
|
|
1676
1838
|
});
|
|
1677
1839
|
if (!screenshotPath) {
|
|
1678
1840
|
return data;
|
|
@@ -2498,13 +2660,13 @@ class StableBrowser {
|
|
|
2498
2660
|
}
|
|
2499
2661
|
catch (e) {
|
|
2500
2662
|
if (e.label === "networkidle") {
|
|
2501
|
-
console.log("
|
|
2663
|
+
console.log("waited for the network to be idle timeout");
|
|
2502
2664
|
}
|
|
2503
2665
|
else if (e.label === "load") {
|
|
2504
|
-
console.log("
|
|
2666
|
+
console.log("waited for the load timeout");
|
|
2505
2667
|
}
|
|
2506
2668
|
else if (e.label === "domcontentloaded") {
|
|
2507
|
-
console.log("
|
|
2669
|
+
console.log("waited for the domcontent loaded timeout");
|
|
2508
2670
|
}
|
|
2509
2671
|
console.log(".");
|
|
2510
2672
|
}
|
|
@@ -2539,13 +2701,6 @@ class StableBrowser {
|
|
|
2539
2701
|
const info = {};
|
|
2540
2702
|
try {
|
|
2541
2703
|
await this.page.close();
|
|
2542
|
-
if (this.context && this.context.pages && this.context.pages.length > 0) {
|
|
2543
|
-
this.context.pages.pop();
|
|
2544
|
-
this.page = this.context.pages[this.context.pages.length - 1];
|
|
2545
|
-
this.context.page = this.page;
|
|
2546
|
-
let title = await this.page.title();
|
|
2547
|
-
console.log("Switched to page " + title);
|
|
2548
|
-
}
|
|
2549
2704
|
}
|
|
2550
2705
|
catch (e) {
|
|
2551
2706
|
console.log(".");
|
|
@@ -2654,33 +2809,18 @@ class StableBrowser {
|
|
|
2654
2809
|
}
|
|
2655
2810
|
async scrollIfNeeded(element, info) {
|
|
2656
2811
|
try {
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
if (rect &&
|
|
2660
|
-
rect.top >= 0 &&
|
|
2661
|
-
rect.left >= 0 &&
|
|
2662
|
-
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
|
|
2663
|
-
rect.right <= (window.innerWidth || document.documentElement.clientWidth)) {
|
|
2664
|
-
return false;
|
|
2665
|
-
}
|
|
2666
|
-
else {
|
|
2667
|
-
node.scrollIntoView({
|
|
2668
|
-
behavior: "smooth",
|
|
2669
|
-
block: "center",
|
|
2670
|
-
inline: "center",
|
|
2671
|
-
});
|
|
2672
|
-
return true;
|
|
2673
|
-
}
|
|
2812
|
+
await element.scrollIntoViewIfNeeded({
|
|
2813
|
+
timeout: 2000,
|
|
2674
2814
|
});
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
}
|
|
2815
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
2816
|
+
if (info) {
|
|
2817
|
+
info.box = await element.boundingBox({
|
|
2818
|
+
timeout: 1000,
|
|
2819
|
+
});
|
|
2680
2820
|
}
|
|
2681
2821
|
}
|
|
2682
2822
|
catch (e) {
|
|
2683
|
-
console.log("
|
|
2823
|
+
console.log("#-#");
|
|
2684
2824
|
}
|
|
2685
2825
|
}
|
|
2686
2826
|
_reportToWorld(world, properties) {
|