automation_model 1.0.399-dev → 1.0.399-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 +21 -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 +253 -126
- 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",
|
|
@@ -37,7 +40,10 @@ const Types = {
|
|
|
37
40
|
SET_DATE_TIME: "set_date_time",
|
|
38
41
|
SET_VIEWPORT: "set_viewport",
|
|
39
42
|
VERIFY_VISUAL: "verify_visual",
|
|
43
|
+
LOAD_DATA: "load_data",
|
|
44
|
+
SET_INPUT: "set_input",
|
|
40
45
|
};
|
|
46
|
+
export const apps = {};
|
|
41
47
|
class StableBrowser {
|
|
42
48
|
constructor(browser, page, logger = null, context = null) {
|
|
43
49
|
this.browser = browser;
|
|
@@ -47,6 +53,7 @@ class StableBrowser {
|
|
|
47
53
|
this.project_path = null;
|
|
48
54
|
this.webLogFile = null;
|
|
49
55
|
this.configuration = null;
|
|
56
|
+
this.appName = "main";
|
|
50
57
|
if (!this.logger) {
|
|
51
58
|
this.logger = console;
|
|
52
59
|
}
|
|
@@ -77,14 +84,28 @@ class StableBrowser {
|
|
|
77
84
|
this.registerRequestListener();
|
|
78
85
|
context.pages = [this.page];
|
|
79
86
|
context.pageLoading = { status: false };
|
|
87
|
+
this.registerPageEventListeners(context);
|
|
88
|
+
}
|
|
89
|
+
registerPageEventListeners(context) {
|
|
80
90
|
context.playContext.on("page", async function (page) {
|
|
81
91
|
context.pageLoading.status = true;
|
|
82
92
|
this.page = page;
|
|
83
93
|
context.page = page;
|
|
84
94
|
context.pages.push(page);
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
+
});
|
|
88
109
|
try {
|
|
89
110
|
await this.waitForPageLoad();
|
|
90
111
|
console.log("Switch page: " + (await page.title()));
|
|
@@ -95,6 +116,36 @@ class StableBrowser {
|
|
|
95
116
|
context.pageLoading.status = false;
|
|
96
117
|
}.bind(this));
|
|
97
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
|
+
}
|
|
98
149
|
getWebLogFile(logFolder) {
|
|
99
150
|
if (!fs.existsSync(logFolder)) {
|
|
100
151
|
fs.mkdirSync(logFolder, { recursive: true });
|
|
@@ -179,24 +230,78 @@ class StableBrowser {
|
|
|
179
230
|
}
|
|
180
231
|
return text;
|
|
181
232
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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
|
+
}
|
|
185
256
|
}
|
|
257
|
+
}
|
|
258
|
+
_getLocator(locator, scope, _params) {
|
|
259
|
+
locator = this._fixLocatorUsingParams(locator, _params);
|
|
260
|
+
let locatorReturn;
|
|
186
261
|
if (locator.role) {
|
|
187
262
|
if (locator.role[1].nameReg) {
|
|
188
263
|
locator.role[1].name = reg_parser(locator.role[1].nameReg);
|
|
189
264
|
delete locator.role[1].nameReg;
|
|
190
265
|
}
|
|
191
|
-
if (locator.role[1].name) {
|
|
192
|
-
|
|
193
|
-
}
|
|
194
|
-
|
|
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]);
|
|
195
270
|
}
|
|
196
271
|
if (locator.css) {
|
|
197
|
-
|
|
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
|
+
}
|
|
285
|
+
}
|
|
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");
|
|
198
303
|
}
|
|
199
|
-
|
|
304
|
+
return locatorReturn;
|
|
200
305
|
}
|
|
201
306
|
async _locateElmentByTextClimbCss(scope, text, climb, css, _params) {
|
|
202
307
|
let result = await this._locateElementByText(scope, this._fixUsingParams(text, _params), "*", false, true, _params);
|
|
@@ -407,6 +512,8 @@ class StableBrowser {
|
|
|
407
512
|
if (result.foundElements.length > 0) {
|
|
408
513
|
let dialogCloseLocator = result.foundElements[0].locator;
|
|
409
514
|
await dialogCloseLocator.click();
|
|
515
|
+
// wait for the dialog to close
|
|
516
|
+
await dialogCloseLocator.waitFor({ state: "hidden" });
|
|
410
517
|
return { rerun: true };
|
|
411
518
|
}
|
|
412
519
|
}
|
|
@@ -415,7 +522,7 @@ class StableBrowser {
|
|
|
415
522
|
}
|
|
416
523
|
async _locate(selectors, info, _params, timeout = 30000) {
|
|
417
524
|
for (let i = 0; i < 3; i++) {
|
|
418
|
-
info.log += "attempt " + i + ":
|
|
525
|
+
info.log += "attempt " + i + ": total locators " + selectors.locators.length + "\n";
|
|
419
526
|
for (let j = 0; j < selectors.locators.length; j++) {
|
|
420
527
|
let selector = selectors.locators[j];
|
|
421
528
|
info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
|
|
@@ -435,9 +542,27 @@ class StableBrowser {
|
|
|
435
542
|
//let arrayMode = Array.isArray(selectors);
|
|
436
543
|
let scope = this.page;
|
|
437
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
|
+
};
|
|
438
558
|
info.log += "searching for iframe " + selectors.iframe_src + "/" + selectors.frameLocators + "\n";
|
|
439
559
|
while (true) {
|
|
440
560
|
let frameFound = false;
|
|
561
|
+
if (selectors.nestFrmLoc) {
|
|
562
|
+
scope = findFrame(selectors.nestFrmLoc, scope);
|
|
563
|
+
frameFound = true;
|
|
564
|
+
break;
|
|
565
|
+
}
|
|
441
566
|
if (selectors.frameLocators) {
|
|
442
567
|
for (let i = 0; i < selectors.frameLocators.length; i++) {
|
|
443
568
|
let frameLocator = selectors.frameLocators[i];
|
|
@@ -601,6 +726,9 @@ class StableBrowser {
|
|
|
601
726
|
async click(selectors, _params, options = {}, world = null) {
|
|
602
727
|
this._validateSelectors(selectors);
|
|
603
728
|
const startTime = Date.now();
|
|
729
|
+
if (options && options.context) {
|
|
730
|
+
selectors.locators[0].text = options.context;
|
|
731
|
+
}
|
|
604
732
|
const info = {};
|
|
605
733
|
info.log = "***** click on " + selectors.element_name + " *****\n";
|
|
606
734
|
info.operation = "click";
|
|
@@ -614,14 +742,14 @@ class StableBrowser {
|
|
|
614
742
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
615
743
|
try {
|
|
616
744
|
await this._highlightElements(element);
|
|
617
|
-
await element.click(
|
|
745
|
+
await element.click();
|
|
618
746
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
619
747
|
}
|
|
620
748
|
catch (e) {
|
|
621
749
|
// await this.closeUnexpectedPopups();
|
|
622
750
|
info.log += "click failed, will try again" + "\n";
|
|
623
751
|
element = await this._locate(selectors, info, _params);
|
|
624
|
-
await element.click
|
|
752
|
+
await element.dispatchEvent("click");
|
|
625
753
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
626
754
|
}
|
|
627
755
|
await this.waitForPageLoad();
|
|
@@ -674,7 +802,7 @@ class StableBrowser {
|
|
|
674
802
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
675
803
|
try {
|
|
676
804
|
await this._highlightElements(element);
|
|
677
|
-
await element.setChecked(checked
|
|
805
|
+
await element.setChecked(checked);
|
|
678
806
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
679
807
|
}
|
|
680
808
|
catch (e) {
|
|
@@ -738,7 +866,7 @@ class StableBrowser {
|
|
|
738
866
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
739
867
|
try {
|
|
740
868
|
await this._highlightElements(element);
|
|
741
|
-
await element.hover(
|
|
869
|
+
await element.hover();
|
|
742
870
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
743
871
|
}
|
|
744
872
|
catch (e) {
|
|
@@ -800,7 +928,7 @@ class StableBrowser {
|
|
|
800
928
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
801
929
|
try {
|
|
802
930
|
await this._highlightElements(element);
|
|
803
|
-
await element.selectOption(values
|
|
931
|
+
await element.selectOption(values);
|
|
804
932
|
}
|
|
805
933
|
catch (e) {
|
|
806
934
|
//await this.closeUnexpectedPopups();
|
|
@@ -909,71 +1037,45 @@ class StableBrowser {
|
|
|
909
1037
|
});
|
|
910
1038
|
}
|
|
911
1039
|
}
|
|
912
|
-
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.
|
|
913
1042
|
this._validateSelectors(selectors);
|
|
914
1043
|
const startTime = Date.now();
|
|
915
|
-
let error = null;
|
|
916
|
-
let screenshotId = null;
|
|
917
|
-
let screenshotPath = null;
|
|
918
1044
|
const info = {};
|
|
919
|
-
info.log = "";
|
|
920
|
-
info.operation =
|
|
1045
|
+
info.log = "***** set input value " + selectors.element_name + " *****\n";
|
|
1046
|
+
info.operation = "setInputValue";
|
|
921
1047
|
info.selectors = selectors;
|
|
1048
|
+
value = this._fixUsingParams(value, _params);
|
|
922
1049
|
info.value = value;
|
|
1050
|
+
let error = null;
|
|
1051
|
+
let screenshotId = null;
|
|
1052
|
+
let screenshotPath = null;
|
|
923
1053
|
try {
|
|
924
1054
|
value = await this._replaceWithLocalData(value, this);
|
|
925
1055
|
let element = await this._locate(selectors, info, _params);
|
|
926
|
-
//insert red border around the element
|
|
927
1056
|
await this.scrollIfNeeded(element, info);
|
|
928
1057
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
929
1058
|
await this._highlightElements(element);
|
|
930
1059
|
try {
|
|
931
|
-
await element.
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
value = dayjs(value).format(format);
|
|
935
|
-
await element.fill(value);
|
|
936
|
-
}
|
|
937
|
-
else {
|
|
938
|
-
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
939
|
-
await element.evaluateHandle((el, dateTimeValue) => {
|
|
940
|
-
el.value = ""; // clear input
|
|
941
|
-
el.value = dateTimeValue;
|
|
942
|
-
}, dateTimeValue);
|
|
943
|
-
}
|
|
944
|
-
if (enter) {
|
|
945
|
-
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
946
|
-
await this.page.keyboard.press("Enter");
|
|
947
|
-
await this.waitForPageLoad();
|
|
948
|
-
}
|
|
1060
|
+
await element.evaluateHandle((el, value) => {
|
|
1061
|
+
el.value = value;
|
|
1062
|
+
}, value);
|
|
949
1063
|
}
|
|
950
1064
|
catch (error) {
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
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));
|
|
954
1067
|
info.screenshotPath = screenshotPath;
|
|
955
1068
|
Object.assign(error, { info: info });
|
|
956
|
-
await element.
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
value = dayjs(value).format(format);
|
|
960
|
-
await element.fill(value);
|
|
961
|
-
}
|
|
962
|
-
else {
|
|
963
|
-
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
964
|
-
await element.evaluateHandle((el, dateTimeValue) => {
|
|
965
|
-
el.value = ""; // clear input
|
|
966
|
-
el.value = dateTimeValue;
|
|
967
|
-
}, dateTimeValue);
|
|
968
|
-
}
|
|
969
|
-
if (enter) {
|
|
970
|
-
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
971
|
-
await this.page.keyboard.press("Enter");
|
|
972
|
-
await this.waitForPageLoad();
|
|
973
|
-
}
|
|
1069
|
+
await element.evaluateHandle((el, value) => {
|
|
1070
|
+
el.value = value;
|
|
1071
|
+
});
|
|
974
1072
|
}
|
|
975
1073
|
}
|
|
976
|
-
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 });
|
|
977
1079
|
error = e;
|
|
978
1080
|
throw e;
|
|
979
1081
|
}
|
|
@@ -981,10 +1083,10 @@ class StableBrowser {
|
|
|
981
1083
|
const endTime = Date.now();
|
|
982
1084
|
this._reportToWorld(world, {
|
|
983
1085
|
element_name: selectors.element_name,
|
|
984
|
-
type: Types.
|
|
985
|
-
|
|
1086
|
+
type: Types.SET_INPUT,
|
|
1087
|
+
text: `Set input value`,
|
|
986
1088
|
value: value,
|
|
987
|
-
|
|
1089
|
+
screenshotId,
|
|
988
1090
|
result: error
|
|
989
1091
|
? {
|
|
990
1092
|
status: "FAILED",
|
|
@@ -1001,7 +1103,7 @@ class StableBrowser {
|
|
|
1001
1103
|
});
|
|
1002
1104
|
}
|
|
1003
1105
|
}
|
|
1004
|
-
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) {
|
|
1005
1107
|
this._validateSelectors(selectors);
|
|
1006
1108
|
const startTime = Date.now();
|
|
1007
1109
|
let error = null;
|
|
@@ -1013,6 +1115,7 @@ class StableBrowser {
|
|
|
1013
1115
|
info.selectors = selectors;
|
|
1014
1116
|
info.value = value;
|
|
1015
1117
|
try {
|
|
1118
|
+
value = await this._replaceWithLocalData(value, this);
|
|
1016
1119
|
let element = await this._locate(selectors, info, _params);
|
|
1017
1120
|
//insert red border around the element
|
|
1018
1121
|
await this.scrollIfNeeded(element, info);
|
|
@@ -1021,28 +1124,51 @@ class StableBrowser {
|
|
|
1021
1124
|
try {
|
|
1022
1125
|
await element.click();
|
|
1023
1126
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
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
|
+
}
|
|
1029
1143
|
}
|
|
1030
|
-
catch (
|
|
1144
|
+
catch (err) {
|
|
1031
1145
|
//await this.closeUnexpectedPopups();
|
|
1032
1146
|
this.logger.error("setting date time input failed " + JSON.stringify(info));
|
|
1033
|
-
this.logger.info("Trying again")
|
|
1147
|
+
this.logger.info("Trying again");
|
|
1148
|
+
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1034
1149
|
info.screenshotPath = screenshotPath;
|
|
1035
|
-
Object.assign(
|
|
1150
|
+
Object.assign(err, { info: info });
|
|
1036
1151
|
await element.click();
|
|
1037
1152
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
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
|
+
}
|
|
1043
1169
|
}
|
|
1044
1170
|
}
|
|
1045
|
-
catch (
|
|
1171
|
+
catch (e) {
|
|
1046
1172
|
error = e;
|
|
1047
1173
|
throw e;
|
|
1048
1174
|
}
|
|
@@ -1205,7 +1331,7 @@ class StableBrowser {
|
|
|
1205
1331
|
let element = await this._locate(selectors, info, _params);
|
|
1206
1332
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1207
1333
|
await this._highlightElements(element);
|
|
1208
|
-
await element.fill(value
|
|
1334
|
+
await element.fill(value);
|
|
1209
1335
|
await element.dispatchEvent("change");
|
|
1210
1336
|
if (enter) {
|
|
1211
1337
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
@@ -1424,7 +1550,7 @@ class StableBrowser {
|
|
|
1424
1550
|
return info;
|
|
1425
1551
|
}
|
|
1426
1552
|
catch (e) {
|
|
1427
|
-
|
|
1553
|
+
await this.closeUnexpectedPopups();
|
|
1428
1554
|
this.logger.error("verify element contains text failed " + info.log);
|
|
1429
1555
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1430
1556
|
info.screenshotPath = screenshotPath;
|
|
@@ -1472,6 +1598,29 @@ class StableBrowser {
|
|
|
1472
1598
|
}
|
|
1473
1599
|
return dataFile;
|
|
1474
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
|
+
}
|
|
1475
1624
|
setTestData(testData, world = null) {
|
|
1476
1625
|
if (!testData) {
|
|
1477
1626
|
return;
|
|
@@ -1499,7 +1648,7 @@ class StableBrowser {
|
|
|
1499
1648
|
const data = fs.readFileSync(filePath, "utf8");
|
|
1500
1649
|
const results = [];
|
|
1501
1650
|
return new Promise((resolve, reject) => {
|
|
1502
|
-
const readableStream = new
|
|
1651
|
+
const readableStream = new Readable();
|
|
1503
1652
|
readableStream._read = () => { }; // _read is required but you can noop it
|
|
1504
1653
|
readableStream.push(data);
|
|
1505
1654
|
readableStream.push(null);
|
|
@@ -1679,13 +1828,13 @@ class StableBrowser {
|
|
|
1679
1828
|
])));
|
|
1680
1829
|
const { data } = await client.send("Page.captureScreenshot", {
|
|
1681
1830
|
format: "png",
|
|
1682
|
-
clip: {
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
},
|
|
1831
|
+
// clip: {
|
|
1832
|
+
// x: 0,
|
|
1833
|
+
// y: 0,
|
|
1834
|
+
// width: viewportWidth,
|
|
1835
|
+
// height: viewportHeight,
|
|
1836
|
+
// scale: 1,
|
|
1837
|
+
// },
|
|
1689
1838
|
});
|
|
1690
1839
|
if (!screenshotPath) {
|
|
1691
1840
|
return data;
|
|
@@ -2511,13 +2660,13 @@ class StableBrowser {
|
|
|
2511
2660
|
}
|
|
2512
2661
|
catch (e) {
|
|
2513
2662
|
if (e.label === "networkidle") {
|
|
2514
|
-
console.log("
|
|
2663
|
+
console.log("waited for the network to be idle timeout");
|
|
2515
2664
|
}
|
|
2516
2665
|
else if (e.label === "load") {
|
|
2517
|
-
console.log("
|
|
2666
|
+
console.log("waited for the load timeout");
|
|
2518
2667
|
}
|
|
2519
2668
|
else if (e.label === "domcontentloaded") {
|
|
2520
|
-
console.log("
|
|
2669
|
+
console.log("waited for the domcontent loaded timeout");
|
|
2521
2670
|
}
|
|
2522
2671
|
console.log(".");
|
|
2523
2672
|
}
|
|
@@ -2552,13 +2701,6 @@ class StableBrowser {
|
|
|
2552
2701
|
const info = {};
|
|
2553
2702
|
try {
|
|
2554
2703
|
await this.page.close();
|
|
2555
|
-
if (this.context && this.context.pages && this.context.pages.length > 0) {
|
|
2556
|
-
this.context.pages.pop();
|
|
2557
|
-
this.page = this.context.pages[this.context.pages.length - 1];
|
|
2558
|
-
this.context.page = this.page;
|
|
2559
|
-
let title = await this.page.title();
|
|
2560
|
-
console.log("Switched to page " + title);
|
|
2561
|
-
}
|
|
2562
2704
|
}
|
|
2563
2705
|
catch (e) {
|
|
2564
2706
|
console.log(".");
|
|
@@ -2667,33 +2809,18 @@ class StableBrowser {
|
|
|
2667
2809
|
}
|
|
2668
2810
|
async scrollIfNeeded(element, info) {
|
|
2669
2811
|
try {
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
if (rect &&
|
|
2673
|
-
rect.top >= 0 &&
|
|
2674
|
-
rect.left >= 0 &&
|
|
2675
|
-
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
|
|
2676
|
-
rect.right <= (window.innerWidth || document.documentElement.clientWidth)) {
|
|
2677
|
-
return false;
|
|
2678
|
-
}
|
|
2679
|
-
else {
|
|
2680
|
-
node.scrollIntoView({
|
|
2681
|
-
behavior: "smooth",
|
|
2682
|
-
block: "center",
|
|
2683
|
-
inline: "center",
|
|
2684
|
-
});
|
|
2685
|
-
return true;
|
|
2686
|
-
}
|
|
2812
|
+
await element.scrollIntoViewIfNeeded({
|
|
2813
|
+
timeout: 2000,
|
|
2687
2814
|
});
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
}
|
|
2815
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
2816
|
+
if (info) {
|
|
2817
|
+
info.box = await element.boundingBox({
|
|
2818
|
+
timeout: 1000,
|
|
2819
|
+
});
|
|
2693
2820
|
}
|
|
2694
2821
|
}
|
|
2695
2822
|
catch (e) {
|
|
2696
|
-
console.log("
|
|
2823
|
+
console.log("#-#");
|
|
2697
2824
|
}
|
|
2698
2825
|
}
|
|
2699
2826
|
_reportToWorld(world, properties) {
|