automation_model 1.0.400-dev → 1.0.400-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 +29 -2
- 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 +16 -4
- package/lib/stable_browser.js +294 -141
- 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,16 +40,22 @@ 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
|
-
constructor(browser, page, logger = null, context = null) {
|
|
48
|
+
constructor(browser, page, logger = null, context = null, world = null) {
|
|
43
49
|
this.browser = browser;
|
|
44
50
|
this.page = page;
|
|
45
51
|
this.logger = logger;
|
|
46
52
|
this.context = context;
|
|
53
|
+
this.world = world;
|
|
47
54
|
this.project_path = null;
|
|
48
55
|
this.webLogFile = null;
|
|
56
|
+
this.networkLogger = null;
|
|
49
57
|
this.configuration = null;
|
|
58
|
+
this.appName = "main";
|
|
50
59
|
if (!this.logger) {
|
|
51
60
|
this.logger = console;
|
|
52
61
|
}
|
|
@@ -72,19 +81,31 @@ class StableBrowser {
|
|
|
72
81
|
this.logger.error("unable to read ai_config.json");
|
|
73
82
|
}
|
|
74
83
|
const logFolder = path.join(this.project_path, "logs", "web");
|
|
75
|
-
this.
|
|
76
|
-
this.
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
84
|
+
this.world = world;
|
|
85
|
+
this.registerEventListeners(context);
|
|
86
|
+
}
|
|
87
|
+
registerEventListeners(context) {
|
|
88
|
+
this.registerConsoleLogListener(this.page, context);
|
|
89
|
+
this.registerRequestListener(this.page, context, this.webLogFile);
|
|
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 });
|
|
@@ -106,37 +157,63 @@ class StableBrowser {
|
|
|
106
157
|
const fileName = nextIndex + ".json";
|
|
107
158
|
return path.join(logFolder, fileName);
|
|
108
159
|
}
|
|
109
|
-
registerConsoleLogListener(page, context
|
|
160
|
+
registerConsoleLogListener(page, context) {
|
|
110
161
|
if (!this.context.webLogger) {
|
|
111
162
|
this.context.webLogger = [];
|
|
112
163
|
}
|
|
113
164
|
page.on("console", async (msg) => {
|
|
114
|
-
|
|
165
|
+
var _a;
|
|
166
|
+
const obj = {
|
|
115
167
|
type: msg.type(),
|
|
116
168
|
text: msg.text(),
|
|
117
169
|
location: msg.location(),
|
|
118
170
|
time: new Date().toISOString(),
|
|
119
|
-
}
|
|
120
|
-
|
|
171
|
+
};
|
|
172
|
+
this.context.webLogger.push(obj);
|
|
173
|
+
(_a = this.world) === null || _a === void 0 ? void 0 : _a.attach(JSON.stringify(obj), { mediaType: "application/json+log" });
|
|
121
174
|
});
|
|
122
175
|
}
|
|
123
|
-
registerRequestListener() {
|
|
124
|
-
this.
|
|
176
|
+
registerRequestListener(page, context, logFile) {
|
|
177
|
+
if (!this.context.networkLogger) {
|
|
178
|
+
this.context.networkLogger = [];
|
|
179
|
+
}
|
|
180
|
+
page.on("request", async (data) => {
|
|
181
|
+
var _a;
|
|
182
|
+
const startTime = new Date().getTime();
|
|
125
183
|
try {
|
|
126
|
-
const pageUrl = new URL(
|
|
184
|
+
const pageUrl = new URL(page.url());
|
|
127
185
|
const requestUrl = new URL(data.url());
|
|
128
186
|
if (pageUrl.hostname === requestUrl.hostname) {
|
|
129
187
|
const method = data.method();
|
|
130
|
-
if (
|
|
188
|
+
if (["POST", "GET", "PUT", "DELETE", "PATCH"].includes(method)) {
|
|
131
189
|
const token = await data.headerValue("Authorization");
|
|
132
190
|
if (token) {
|
|
133
|
-
|
|
191
|
+
context.authtoken = token;
|
|
134
192
|
}
|
|
135
193
|
}
|
|
136
194
|
}
|
|
195
|
+
const response = await data.response();
|
|
196
|
+
const endTime = new Date().getTime();
|
|
197
|
+
const obj = {
|
|
198
|
+
url: data.url(),
|
|
199
|
+
method: data.method(),
|
|
200
|
+
postData: data.postData(),
|
|
201
|
+
error: data.failure() ? data.failure().errorText : null,
|
|
202
|
+
duration: endTime - startTime,
|
|
203
|
+
startTime,
|
|
204
|
+
};
|
|
205
|
+
context.networkLogger.push(obj);
|
|
206
|
+
(_a = this.world) === null || _a === void 0 ? void 0 : _a.attach(JSON.stringify(obj), { mediaType: "application/json+network" });
|
|
137
207
|
}
|
|
138
208
|
catch (error) {
|
|
139
209
|
console.error("Error in request listener", error);
|
|
210
|
+
context.networkLogger.push({
|
|
211
|
+
error: "not able to listen",
|
|
212
|
+
message: error.message,
|
|
213
|
+
stack: error.stack,
|
|
214
|
+
time: new Date().toISOString(),
|
|
215
|
+
});
|
|
216
|
+
// await fs.promises.writeFile(logFile, JSON.stringify(context.networkLogger, null, 2));
|
|
140
217
|
}
|
|
141
218
|
});
|
|
142
219
|
}
|
|
@@ -179,24 +256,78 @@ class StableBrowser {
|
|
|
179
256
|
}
|
|
180
257
|
return text;
|
|
181
258
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
259
|
+
_fixLocatorUsingParams(locator, _params) {
|
|
260
|
+
// check if not null
|
|
261
|
+
if (!locator) {
|
|
262
|
+
return locator;
|
|
263
|
+
}
|
|
264
|
+
// clone the locator
|
|
265
|
+
locator = JSON.parse(JSON.stringify(locator));
|
|
266
|
+
this.scanAndManipulate(locator, _params);
|
|
267
|
+
return locator;
|
|
268
|
+
}
|
|
269
|
+
_isObject(value) {
|
|
270
|
+
return value && typeof value === "object" && value.constructor === Object;
|
|
271
|
+
}
|
|
272
|
+
scanAndManipulate(currentObj, _params) {
|
|
273
|
+
for (const key in currentObj) {
|
|
274
|
+
if (typeof currentObj[key] === "string") {
|
|
275
|
+
// Perform string manipulation
|
|
276
|
+
currentObj[key] = this._fixUsingParams(currentObj[key], _params);
|
|
277
|
+
}
|
|
278
|
+
else if (this._isObject(currentObj[key])) {
|
|
279
|
+
// Recursively scan nested objects
|
|
280
|
+
this.scanAndManipulate(currentObj[key], _params);
|
|
281
|
+
}
|
|
185
282
|
}
|
|
283
|
+
}
|
|
284
|
+
_getLocator(locator, scope, _params) {
|
|
285
|
+
locator = this._fixLocatorUsingParams(locator, _params);
|
|
286
|
+
let locatorReturn;
|
|
186
287
|
if (locator.role) {
|
|
187
288
|
if (locator.role[1].nameReg) {
|
|
188
289
|
locator.role[1].name = reg_parser(locator.role[1].nameReg);
|
|
189
290
|
delete locator.role[1].nameReg;
|
|
190
291
|
}
|
|
191
|
-
if (locator.role[1].name) {
|
|
192
|
-
|
|
193
|
-
}
|
|
194
|
-
|
|
292
|
+
// if (locator.role[1].name) {
|
|
293
|
+
// locator.role[1].name = this._fixUsingParams(locator.role[1].name, _params);
|
|
294
|
+
// }
|
|
295
|
+
locatorReturn = scope.getByRole(locator.role[0], locator.role[1]);
|
|
195
296
|
}
|
|
196
297
|
if (locator.css) {
|
|
197
|
-
|
|
298
|
+
locatorReturn = scope.locator(locator.css);
|
|
299
|
+
}
|
|
300
|
+
// handle role/name locators
|
|
301
|
+
// locator.selector will be something like: textbox[name="Username"i]
|
|
302
|
+
if (locator.engine === "internal:role") {
|
|
303
|
+
// extract the role, name and the i/s flags using regex
|
|
304
|
+
const match = locator.selector.match(/(.*)\[(.*)="(.*)"(.*)\]/);
|
|
305
|
+
if (match) {
|
|
306
|
+
const role = match[1];
|
|
307
|
+
const name = match[3];
|
|
308
|
+
const flags = match[4];
|
|
309
|
+
locatorReturn = scope.getByRole(role, { name }, { exact: flags === "i" });
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
if (locator === null || locator === void 0 ? void 0 : locator.engine) {
|
|
313
|
+
if (locator.engine === "css") {
|
|
314
|
+
locatorReturn = scope.locator(locator.selector);
|
|
315
|
+
}
|
|
316
|
+
else {
|
|
317
|
+
let selector = locator.selector;
|
|
318
|
+
if (locator.engine === "internal:attr") {
|
|
319
|
+
if (!selector.startsWith("[")) {
|
|
320
|
+
selector = `[${selector}]`;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
locatorReturn = scope.locator(`${locator.engine}=${selector}`);
|
|
324
|
+
}
|
|
198
325
|
}
|
|
199
|
-
|
|
326
|
+
if (!locatorReturn) {
|
|
327
|
+
console.error(locator);
|
|
328
|
+
throw new Error("Locator undefined");
|
|
329
|
+
}
|
|
330
|
+
return locatorReturn;
|
|
200
331
|
}
|
|
201
332
|
async _locateElmentByTextClimbCss(scope, text, climb, css, _params) {
|
|
202
333
|
let result = await this._locateElementByText(scope, this._fixUsingParams(text, _params), "*", false, true, _params);
|
|
@@ -407,6 +538,8 @@ class StableBrowser {
|
|
|
407
538
|
if (result.foundElements.length > 0) {
|
|
408
539
|
let dialogCloseLocator = result.foundElements[0].locator;
|
|
409
540
|
await dialogCloseLocator.click();
|
|
541
|
+
// wait for the dialog to close
|
|
542
|
+
await dialogCloseLocator.waitFor({ state: "hidden" });
|
|
410
543
|
return { rerun: true };
|
|
411
544
|
}
|
|
412
545
|
}
|
|
@@ -415,7 +548,7 @@ class StableBrowser {
|
|
|
415
548
|
}
|
|
416
549
|
async _locate(selectors, info, _params, timeout = 30000) {
|
|
417
550
|
for (let i = 0; i < 3; i++) {
|
|
418
|
-
info.log += "attempt " + i + ":
|
|
551
|
+
info.log += "attempt " + i + ": total locators " + selectors.locators.length + "\n";
|
|
419
552
|
for (let j = 0; j < selectors.locators.length; j++) {
|
|
420
553
|
let selector = selectors.locators[j];
|
|
421
554
|
info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
|
|
@@ -435,9 +568,27 @@ class StableBrowser {
|
|
|
435
568
|
//let arrayMode = Array.isArray(selectors);
|
|
436
569
|
let scope = this.page;
|
|
437
570
|
if (selectors.iframe_src || selectors.frameLocators) {
|
|
571
|
+
const findFrame = (frame, framescope) => {
|
|
572
|
+
for (let i = 0; i < frame.selectors.length; i++) {
|
|
573
|
+
let frameLocator = frame.selectors[i];
|
|
574
|
+
if (frameLocator.css) {
|
|
575
|
+
framescope = framescope.frameLocator(frameLocator.css);
|
|
576
|
+
break;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
if (frame.children) {
|
|
580
|
+
return findFrame(frame.children, framescope);
|
|
581
|
+
}
|
|
582
|
+
return framescope;
|
|
583
|
+
};
|
|
438
584
|
info.log += "searching for iframe " + selectors.iframe_src + "/" + selectors.frameLocators + "\n";
|
|
439
585
|
while (true) {
|
|
440
586
|
let frameFound = false;
|
|
587
|
+
if (selectors.nestFrmLoc) {
|
|
588
|
+
scope = findFrame(selectors.nestFrmLoc, scope);
|
|
589
|
+
frameFound = true;
|
|
590
|
+
break;
|
|
591
|
+
}
|
|
441
592
|
if (selectors.frameLocators) {
|
|
442
593
|
for (let i = 0; i < selectors.frameLocators.length; i++) {
|
|
443
594
|
let frameLocator = selectors.frameLocators[i];
|
|
@@ -601,6 +752,9 @@ class StableBrowser {
|
|
|
601
752
|
async click(selectors, _params, options = {}, world = null) {
|
|
602
753
|
this._validateSelectors(selectors);
|
|
603
754
|
const startTime = Date.now();
|
|
755
|
+
if (options && options.context) {
|
|
756
|
+
selectors.locators[0].text = options.context;
|
|
757
|
+
}
|
|
604
758
|
const info = {};
|
|
605
759
|
info.log = "***** click on " + selectors.element_name + " *****\n";
|
|
606
760
|
info.operation = "click";
|
|
@@ -614,14 +768,14 @@ class StableBrowser {
|
|
|
614
768
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
615
769
|
try {
|
|
616
770
|
await this._highlightElements(element);
|
|
617
|
-
await element.click(
|
|
771
|
+
await element.click();
|
|
618
772
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
619
773
|
}
|
|
620
774
|
catch (e) {
|
|
621
775
|
// await this.closeUnexpectedPopups();
|
|
622
776
|
info.log += "click failed, will try again" + "\n";
|
|
623
777
|
element = await this._locate(selectors, info, _params);
|
|
624
|
-
await element.click
|
|
778
|
+
await element.dispatchEvent("click");
|
|
625
779
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
626
780
|
}
|
|
627
781
|
await this.waitForPageLoad();
|
|
@@ -674,7 +828,7 @@ class StableBrowser {
|
|
|
674
828
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
675
829
|
try {
|
|
676
830
|
await this._highlightElements(element);
|
|
677
|
-
await element.setChecked(checked
|
|
831
|
+
await element.setChecked(checked);
|
|
678
832
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
679
833
|
}
|
|
680
834
|
catch (e) {
|
|
@@ -738,7 +892,7 @@ class StableBrowser {
|
|
|
738
892
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
739
893
|
try {
|
|
740
894
|
await this._highlightElements(element);
|
|
741
|
-
await element.hover(
|
|
895
|
+
await element.hover();
|
|
742
896
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
743
897
|
}
|
|
744
898
|
catch (e) {
|
|
@@ -800,7 +954,7 @@ class StableBrowser {
|
|
|
800
954
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
801
955
|
try {
|
|
802
956
|
await this._highlightElements(element);
|
|
803
|
-
await element.selectOption(values
|
|
957
|
+
await element.selectOption(values);
|
|
804
958
|
}
|
|
805
959
|
catch (e) {
|
|
806
960
|
//await this.closeUnexpectedPopups();
|
|
@@ -909,71 +1063,45 @@ class StableBrowser {
|
|
|
909
1063
|
});
|
|
910
1064
|
}
|
|
911
1065
|
}
|
|
912
|
-
async
|
|
1066
|
+
async setInputValue(selectors, value, _params = null, options = {}, world = null) {
|
|
1067
|
+
// set input value for non fillable inputs like date, time, range, color, etc.
|
|
913
1068
|
this._validateSelectors(selectors);
|
|
914
1069
|
const startTime = Date.now();
|
|
915
|
-
let error = null;
|
|
916
|
-
let screenshotId = null;
|
|
917
|
-
let screenshotPath = null;
|
|
918
1070
|
const info = {};
|
|
919
|
-
info.log = "";
|
|
920
|
-
info.operation =
|
|
1071
|
+
info.log = "***** set input value " + selectors.element_name + " *****\n";
|
|
1072
|
+
info.operation = "setInputValue";
|
|
921
1073
|
info.selectors = selectors;
|
|
1074
|
+
value = this._fixUsingParams(value, _params);
|
|
922
1075
|
info.value = value;
|
|
1076
|
+
let error = null;
|
|
1077
|
+
let screenshotId = null;
|
|
1078
|
+
let screenshotPath = null;
|
|
923
1079
|
try {
|
|
924
1080
|
value = await this._replaceWithLocalData(value, this);
|
|
925
1081
|
let element = await this._locate(selectors, info, _params);
|
|
926
|
-
//insert red border around the element
|
|
927
1082
|
await this.scrollIfNeeded(element, info);
|
|
928
1083
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
929
1084
|
await this._highlightElements(element);
|
|
930
1085
|
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
|
-
}
|
|
1086
|
+
await element.evaluateHandle((el, value) => {
|
|
1087
|
+
el.value = value;
|
|
1088
|
+
}, value);
|
|
949
1089
|
}
|
|
950
1090
|
catch (error) {
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
this.logger.info("Trying again")(({ screenshotId, screenshotPath } = await this._screenShot(options, world, info)));
|
|
1091
|
+
this.logger.error("setInputValue failed, will try again");
|
|
1092
|
+
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
954
1093
|
info.screenshotPath = screenshotPath;
|
|
955
1094
|
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
|
-
}
|
|
1095
|
+
await element.evaluateHandle((el, value) => {
|
|
1096
|
+
el.value = value;
|
|
1097
|
+
});
|
|
974
1098
|
}
|
|
975
1099
|
}
|
|
976
|
-
catch (
|
|
1100
|
+
catch (e) {
|
|
1101
|
+
this.logger.error("setInputValue failed " + info.log);
|
|
1102
|
+
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1103
|
+
info.screenshotPath = screenshotPath;
|
|
1104
|
+
Object.assign(e, { info: info });
|
|
977
1105
|
error = e;
|
|
978
1106
|
throw e;
|
|
979
1107
|
}
|
|
@@ -981,10 +1109,10 @@ class StableBrowser {
|
|
|
981
1109
|
const endTime = Date.now();
|
|
982
1110
|
this._reportToWorld(world, {
|
|
983
1111
|
element_name: selectors.element_name,
|
|
984
|
-
type: Types.
|
|
985
|
-
|
|
1112
|
+
type: Types.SET_INPUT,
|
|
1113
|
+
text: `Set input value`,
|
|
986
1114
|
value: value,
|
|
987
|
-
|
|
1115
|
+
screenshotId,
|
|
988
1116
|
result: error
|
|
989
1117
|
? {
|
|
990
1118
|
status: "FAILED",
|
|
@@ -1001,7 +1129,7 @@ class StableBrowser {
|
|
|
1001
1129
|
});
|
|
1002
1130
|
}
|
|
1003
1131
|
}
|
|
1004
|
-
async setDateTime(selectors, value, enter = false, _params = null, options = {}, world = null) {
|
|
1132
|
+
async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
|
|
1005
1133
|
this._validateSelectors(selectors);
|
|
1006
1134
|
const startTime = Date.now();
|
|
1007
1135
|
let error = null;
|
|
@@ -1013,6 +1141,7 @@ class StableBrowser {
|
|
|
1013
1141
|
info.selectors = selectors;
|
|
1014
1142
|
info.value = value;
|
|
1015
1143
|
try {
|
|
1144
|
+
value = await this._replaceWithLocalData(value, this);
|
|
1016
1145
|
let element = await this._locate(selectors, info, _params);
|
|
1017
1146
|
//insert red border around the element
|
|
1018
1147
|
await this.scrollIfNeeded(element, info);
|
|
@@ -1021,28 +1150,51 @@ class StableBrowser {
|
|
|
1021
1150
|
try {
|
|
1022
1151
|
await element.click();
|
|
1023
1152
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
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
|
+
}
|
|
1029
1169
|
}
|
|
1030
|
-
catch (
|
|
1170
|
+
catch (err) {
|
|
1031
1171
|
//await this.closeUnexpectedPopups();
|
|
1032
1172
|
this.logger.error("setting date time input failed " + JSON.stringify(info));
|
|
1033
|
-
this.logger.info("Trying again")
|
|
1173
|
+
this.logger.info("Trying again");
|
|
1174
|
+
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1034
1175
|
info.screenshotPath = screenshotPath;
|
|
1035
|
-
Object.assign(
|
|
1176
|
+
Object.assign(err, { info: info });
|
|
1036
1177
|
await element.click();
|
|
1037
1178
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1179
|
+
if (format) {
|
|
1180
|
+
value = dayjs(value).format(format);
|
|
1181
|
+
await element.fill(value);
|
|
1182
|
+
}
|
|
1183
|
+
else {
|
|
1184
|
+
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
1185
|
+
await element.evaluateHandle((el, dateTimeValue) => {
|
|
1186
|
+
el.value = ""; // clear input
|
|
1187
|
+
el.value = dateTimeValue;
|
|
1188
|
+
}, dateTimeValue);
|
|
1189
|
+
}
|
|
1190
|
+
if (enter) {
|
|
1191
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1192
|
+
await this.page.keyboard.press("Enter");
|
|
1193
|
+
await this.waitForPageLoad();
|
|
1194
|
+
}
|
|
1043
1195
|
}
|
|
1044
1196
|
}
|
|
1045
|
-
catch (
|
|
1197
|
+
catch (e) {
|
|
1046
1198
|
error = e;
|
|
1047
1199
|
throw e;
|
|
1048
1200
|
}
|
|
@@ -1205,7 +1357,7 @@ class StableBrowser {
|
|
|
1205
1357
|
let element = await this._locate(selectors, info, _params);
|
|
1206
1358
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1207
1359
|
await this._highlightElements(element);
|
|
1208
|
-
await element.fill(value
|
|
1360
|
+
await element.fill(value);
|
|
1209
1361
|
await element.dispatchEvent("change");
|
|
1210
1362
|
if (enter) {
|
|
1211
1363
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
@@ -1424,7 +1576,7 @@ class StableBrowser {
|
|
|
1424
1576
|
return info;
|
|
1425
1577
|
}
|
|
1426
1578
|
catch (e) {
|
|
1427
|
-
|
|
1579
|
+
await this.closeUnexpectedPopups();
|
|
1428
1580
|
this.logger.error("verify element contains text failed " + info.log);
|
|
1429
1581
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1430
1582
|
info.screenshotPath = screenshotPath;
|
|
@@ -1472,6 +1624,29 @@ class StableBrowser {
|
|
|
1472
1624
|
}
|
|
1473
1625
|
return dataFile;
|
|
1474
1626
|
}
|
|
1627
|
+
async waitForUserInput(message, world = null) {
|
|
1628
|
+
if (!message) {
|
|
1629
|
+
message = "# Wait for user input. Press any key to continue";
|
|
1630
|
+
}
|
|
1631
|
+
else {
|
|
1632
|
+
message = "# Wait for user input. " + message;
|
|
1633
|
+
}
|
|
1634
|
+
message += "\n";
|
|
1635
|
+
const value = await new Promise((resolve) => {
|
|
1636
|
+
const rl = readline.createInterface({
|
|
1637
|
+
input: process.stdin,
|
|
1638
|
+
output: process.stdout,
|
|
1639
|
+
});
|
|
1640
|
+
rl.question(message, (answer) => {
|
|
1641
|
+
rl.close();
|
|
1642
|
+
resolve(answer);
|
|
1643
|
+
});
|
|
1644
|
+
});
|
|
1645
|
+
if (value) {
|
|
1646
|
+
this.logger.info(`{{userInput}} was set to: ${value}`);
|
|
1647
|
+
}
|
|
1648
|
+
this.setTestData({ userInput: value }, world);
|
|
1649
|
+
}
|
|
1475
1650
|
setTestData(testData, world = null) {
|
|
1476
1651
|
if (!testData) {
|
|
1477
1652
|
return;
|
|
@@ -1499,7 +1674,7 @@ class StableBrowser {
|
|
|
1499
1674
|
const data = fs.readFileSync(filePath, "utf8");
|
|
1500
1675
|
const results = [];
|
|
1501
1676
|
return new Promise((resolve, reject) => {
|
|
1502
|
-
const readableStream = new
|
|
1677
|
+
const readableStream = new Readable();
|
|
1503
1678
|
readableStream._read = () => { }; // _read is required but you can noop it
|
|
1504
1679
|
readableStream.push(data);
|
|
1505
1680
|
readableStream.push(null);
|
|
@@ -1679,13 +1854,13 @@ class StableBrowser {
|
|
|
1679
1854
|
])));
|
|
1680
1855
|
const { data } = await client.send("Page.captureScreenshot", {
|
|
1681
1856
|
format: "png",
|
|
1682
|
-
clip: {
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
},
|
|
1857
|
+
// clip: {
|
|
1858
|
+
// x: 0,
|
|
1859
|
+
// y: 0,
|
|
1860
|
+
// width: viewportWidth,
|
|
1861
|
+
// height: viewportHeight,
|
|
1862
|
+
// scale: 1,
|
|
1863
|
+
// },
|
|
1689
1864
|
});
|
|
1690
1865
|
if (!screenshotPath) {
|
|
1691
1866
|
return data;
|
|
@@ -2511,13 +2686,13 @@ class StableBrowser {
|
|
|
2511
2686
|
}
|
|
2512
2687
|
catch (e) {
|
|
2513
2688
|
if (e.label === "networkidle") {
|
|
2514
|
-
console.log("
|
|
2689
|
+
console.log("waited for the network to be idle timeout");
|
|
2515
2690
|
}
|
|
2516
2691
|
else if (e.label === "load") {
|
|
2517
|
-
console.log("
|
|
2692
|
+
console.log("waited for the load timeout");
|
|
2518
2693
|
}
|
|
2519
2694
|
else if (e.label === "domcontentloaded") {
|
|
2520
|
-
console.log("
|
|
2695
|
+
console.log("waited for the domcontent loaded timeout");
|
|
2521
2696
|
}
|
|
2522
2697
|
console.log(".");
|
|
2523
2698
|
}
|
|
@@ -2552,13 +2727,6 @@ class StableBrowser {
|
|
|
2552
2727
|
const info = {};
|
|
2553
2728
|
try {
|
|
2554
2729
|
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
2730
|
}
|
|
2563
2731
|
catch (e) {
|
|
2564
2732
|
console.log(".");
|
|
@@ -2667,33 +2835,18 @@ class StableBrowser {
|
|
|
2667
2835
|
}
|
|
2668
2836
|
async scrollIfNeeded(element, info) {
|
|
2669
2837
|
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
|
-
}
|
|
2838
|
+
await element.scrollIntoViewIfNeeded({
|
|
2839
|
+
timeout: 2000,
|
|
2687
2840
|
});
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
}
|
|
2841
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
2842
|
+
if (info) {
|
|
2843
|
+
info.box = await element.boundingBox({
|
|
2844
|
+
timeout: 1000,
|
|
2845
|
+
});
|
|
2693
2846
|
}
|
|
2694
2847
|
}
|
|
2695
2848
|
catch (e) {
|
|
2696
|
-
console.log("
|
|
2849
|
+
console.log("#-#");
|
|
2697
2850
|
}
|
|
2698
2851
|
}
|
|
2699
2852
|
_reportToWorld(world, properties) {
|