automation_model 1.0.402-dev → 1.0.402-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 -150
- 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,22 +81,33 @@ 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(this.context);
|
|
86
|
+
}
|
|
87
|
+
registerEventListeners(context) {
|
|
88
|
+
this.registerConsoleLogListener(this.page, context);
|
|
89
|
+
this.registerRequestListener(this.page, context, this.webLogFile);
|
|
90
|
+
if (!context.pageLoading) {
|
|
91
|
+
context.pageLoading = { status: false };
|
|
92
|
+
}
|
|
80
93
|
context.playContext.on("page", async function (page) {
|
|
81
94
|
context.pageLoading.status = true;
|
|
82
95
|
this.page = page;
|
|
83
96
|
context.page = page;
|
|
84
97
|
context.pages.push(page);
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
98
|
+
page.on("close", async () => {
|
|
99
|
+
if (this.context && this.context.pages && this.context.pages.length > 1) {
|
|
100
|
+
this.context.pages.pop();
|
|
101
|
+
this.page = this.context.pages[this.context.pages.length - 1];
|
|
102
|
+
this.context.page = this.page;
|
|
103
|
+
try {
|
|
104
|
+
let title = await this.page.title();
|
|
105
|
+
console.log("Switched to page " + title);
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
console.error("Error on page close", error);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
91
111
|
});
|
|
92
112
|
try {
|
|
93
113
|
await this.waitForPageLoad();
|
|
@@ -99,6 +119,36 @@ class StableBrowser {
|
|
|
99
119
|
context.pageLoading.status = false;
|
|
100
120
|
}.bind(this));
|
|
101
121
|
}
|
|
122
|
+
async switchApp(appName) {
|
|
123
|
+
// check if the current app (this.appName) is the same as the new app
|
|
124
|
+
if (this.appName === appName) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
let navigate = false;
|
|
128
|
+
if (!apps[appName]) {
|
|
129
|
+
let newContext = await getContext(null, false, this.logger, appName, false, this);
|
|
130
|
+
navigate = true;
|
|
131
|
+
apps[appName] = {
|
|
132
|
+
context: newContext,
|
|
133
|
+
browser: newContext.browser,
|
|
134
|
+
page: newContext.page,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
const tempContext = {};
|
|
138
|
+
this._copyContext(this, tempContext);
|
|
139
|
+
this._copyContext(apps[appName], this);
|
|
140
|
+
apps[this.appName] = tempContext;
|
|
141
|
+
this.appName = appName;
|
|
142
|
+
if (navigate) {
|
|
143
|
+
await this.goto(this.context.environment.baseUrl);
|
|
144
|
+
await this.waitForPageLoad();
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
_copyContext(from, to) {
|
|
148
|
+
to.browser = from.browser;
|
|
149
|
+
to.page = from.page;
|
|
150
|
+
to.context = from.context;
|
|
151
|
+
}
|
|
102
152
|
getWebLogFile(logFolder) {
|
|
103
153
|
if (!fs.existsSync(logFolder)) {
|
|
104
154
|
fs.mkdirSync(logFolder, { recursive: true });
|
|
@@ -110,37 +160,63 @@ class StableBrowser {
|
|
|
110
160
|
const fileName = nextIndex + ".json";
|
|
111
161
|
return path.join(logFolder, fileName);
|
|
112
162
|
}
|
|
113
|
-
registerConsoleLogListener(page, context
|
|
163
|
+
registerConsoleLogListener(page, context) {
|
|
114
164
|
if (!this.context.webLogger) {
|
|
115
165
|
this.context.webLogger = [];
|
|
116
166
|
}
|
|
117
167
|
page.on("console", async (msg) => {
|
|
118
|
-
|
|
168
|
+
var _a;
|
|
169
|
+
const obj = {
|
|
119
170
|
type: msg.type(),
|
|
120
171
|
text: msg.text(),
|
|
121
172
|
location: msg.location(),
|
|
122
173
|
time: new Date().toISOString(),
|
|
123
|
-
}
|
|
124
|
-
|
|
174
|
+
};
|
|
175
|
+
this.context.webLogger.push(obj);
|
|
176
|
+
(_a = this.world) === null || _a === void 0 ? void 0 : _a.attach(JSON.stringify(obj), { mediaType: "application/json+log" });
|
|
125
177
|
});
|
|
126
178
|
}
|
|
127
|
-
registerRequestListener() {
|
|
128
|
-
this.
|
|
179
|
+
registerRequestListener(page, context, logFile) {
|
|
180
|
+
if (!this.context.networkLogger) {
|
|
181
|
+
this.context.networkLogger = [];
|
|
182
|
+
}
|
|
183
|
+
page.on("request", async (data) => {
|
|
184
|
+
var _a;
|
|
185
|
+
const startTime = new Date().getTime();
|
|
129
186
|
try {
|
|
130
|
-
const pageUrl = new URL(
|
|
187
|
+
const pageUrl = new URL(page.url());
|
|
131
188
|
const requestUrl = new URL(data.url());
|
|
132
189
|
if (pageUrl.hostname === requestUrl.hostname) {
|
|
133
190
|
const method = data.method();
|
|
134
|
-
if (
|
|
191
|
+
if (["POST", "GET", "PUT", "DELETE", "PATCH"].includes(method)) {
|
|
135
192
|
const token = await data.headerValue("Authorization");
|
|
136
193
|
if (token) {
|
|
137
|
-
|
|
194
|
+
context.authtoken = token;
|
|
138
195
|
}
|
|
139
196
|
}
|
|
140
197
|
}
|
|
198
|
+
const response = await data.response();
|
|
199
|
+
const endTime = new Date().getTime();
|
|
200
|
+
const obj = {
|
|
201
|
+
url: data.url(),
|
|
202
|
+
method: data.method(),
|
|
203
|
+
postData: data.postData(),
|
|
204
|
+
error: data.failure() ? data.failure().errorText : null,
|
|
205
|
+
duration: endTime - startTime,
|
|
206
|
+
startTime,
|
|
207
|
+
};
|
|
208
|
+
context.networkLogger.push(obj);
|
|
209
|
+
(_a = this.world) === null || _a === void 0 ? void 0 : _a.attach(JSON.stringify(obj), { mediaType: "application/json+network" });
|
|
141
210
|
}
|
|
142
211
|
catch (error) {
|
|
143
212
|
console.error("Error in request listener", error);
|
|
213
|
+
context.networkLogger.push({
|
|
214
|
+
error: "not able to listen",
|
|
215
|
+
message: error.message,
|
|
216
|
+
stack: error.stack,
|
|
217
|
+
time: new Date().toISOString(),
|
|
218
|
+
});
|
|
219
|
+
// await fs.promises.writeFile(logFile, JSON.stringify(context.networkLogger, null, 2));
|
|
144
220
|
}
|
|
145
221
|
});
|
|
146
222
|
}
|
|
@@ -183,32 +259,78 @@ class StableBrowser {
|
|
|
183
259
|
}
|
|
184
260
|
return text;
|
|
185
261
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
262
|
+
_fixLocatorUsingParams(locator, _params) {
|
|
263
|
+
// check if not null
|
|
264
|
+
if (!locator) {
|
|
265
|
+
return locator;
|
|
189
266
|
}
|
|
267
|
+
// clone the locator
|
|
268
|
+
locator = JSON.parse(JSON.stringify(locator));
|
|
269
|
+
this.scanAndManipulate(locator, _params);
|
|
270
|
+
return locator;
|
|
271
|
+
}
|
|
272
|
+
_isObject(value) {
|
|
273
|
+
return value && typeof value === "object" && value.constructor === Object;
|
|
274
|
+
}
|
|
275
|
+
scanAndManipulate(currentObj, _params) {
|
|
276
|
+
for (const key in currentObj) {
|
|
277
|
+
if (typeof currentObj[key] === "string") {
|
|
278
|
+
// Perform string manipulation
|
|
279
|
+
currentObj[key] = this._fixUsingParams(currentObj[key], _params);
|
|
280
|
+
}
|
|
281
|
+
else if (this._isObject(currentObj[key])) {
|
|
282
|
+
// Recursively scan nested objects
|
|
283
|
+
this.scanAndManipulate(currentObj[key], _params);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
_getLocator(locator, scope, _params) {
|
|
288
|
+
locator = this._fixLocatorUsingParams(locator, _params);
|
|
289
|
+
let locatorReturn;
|
|
190
290
|
if (locator.role) {
|
|
191
291
|
if (locator.role[1].nameReg) {
|
|
192
292
|
locator.role[1].name = reg_parser(locator.role[1].nameReg);
|
|
193
293
|
delete locator.role[1].nameReg;
|
|
194
294
|
}
|
|
195
|
-
if (locator.role[1].name) {
|
|
196
|
-
|
|
197
|
-
}
|
|
198
|
-
|
|
295
|
+
// if (locator.role[1].name) {
|
|
296
|
+
// locator.role[1].name = this._fixUsingParams(locator.role[1].name, _params);
|
|
297
|
+
// }
|
|
298
|
+
locatorReturn = scope.getByRole(locator.role[0], locator.role[1]);
|
|
199
299
|
}
|
|
200
300
|
if (locator.css) {
|
|
201
|
-
|
|
301
|
+
locatorReturn = scope.locator(locator.css);
|
|
202
302
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
303
|
+
// handle role/name locators
|
|
304
|
+
// locator.selector will be something like: textbox[name="Username"i]
|
|
305
|
+
if (locator.engine === "internal:role") {
|
|
306
|
+
// extract the role, name and the i/s flags using regex
|
|
307
|
+
const match = locator.selector.match(/(.*)\[(.*)="(.*)"(.*)\]/);
|
|
308
|
+
if (match) {
|
|
309
|
+
const role = match[1];
|
|
310
|
+
const name = match[3];
|
|
311
|
+
const flags = match[4];
|
|
312
|
+
locatorReturn = scope.getByRole(role, { name }, { exact: flags === "i" });
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
if (locator === null || locator === void 0 ? void 0 : locator.engine) {
|
|
316
|
+
if (locator.engine === "css") {
|
|
317
|
+
locatorReturn = scope.locator(locator.selector);
|
|
318
|
+
}
|
|
319
|
+
else {
|
|
320
|
+
let selector = locator.selector;
|
|
321
|
+
if (locator.engine === "internal:attr") {
|
|
322
|
+
if (!selector.startsWith("[")) {
|
|
323
|
+
selector = `[${selector}]`;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
locatorReturn = scope.locator(`${locator.engine}=${selector}`);
|
|
207
327
|
}
|
|
208
|
-
const locator = scope.locator(`${locator.engine}="${selector}"`);
|
|
209
|
-
return locator;
|
|
210
328
|
}
|
|
211
|
-
|
|
329
|
+
if (!locatorReturn) {
|
|
330
|
+
console.error(locator);
|
|
331
|
+
throw new Error("Locator undefined");
|
|
332
|
+
}
|
|
333
|
+
return locatorReturn;
|
|
212
334
|
}
|
|
213
335
|
async _locateElmentByTextClimbCss(scope, text, climb, css, _params) {
|
|
214
336
|
let result = await this._locateElementByText(scope, this._fixUsingParams(text, _params), "*", false, true, _params);
|
|
@@ -419,6 +541,8 @@ class StableBrowser {
|
|
|
419
541
|
if (result.foundElements.length > 0) {
|
|
420
542
|
let dialogCloseLocator = result.foundElements[0].locator;
|
|
421
543
|
await dialogCloseLocator.click();
|
|
544
|
+
// wait for the dialog to close
|
|
545
|
+
await dialogCloseLocator.waitFor({ state: "hidden" });
|
|
422
546
|
return { rerun: true };
|
|
423
547
|
}
|
|
424
548
|
}
|
|
@@ -427,7 +551,7 @@ class StableBrowser {
|
|
|
427
551
|
}
|
|
428
552
|
async _locate(selectors, info, _params, timeout = 30000) {
|
|
429
553
|
for (let i = 0; i < 3; i++) {
|
|
430
|
-
info.log += "attempt " + i + ":
|
|
554
|
+
info.log += "attempt " + i + ": total locators " + selectors.locators.length + "\n";
|
|
431
555
|
for (let j = 0; j < selectors.locators.length; j++) {
|
|
432
556
|
let selector = selectors.locators[j];
|
|
433
557
|
info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
|
|
@@ -447,9 +571,27 @@ class StableBrowser {
|
|
|
447
571
|
//let arrayMode = Array.isArray(selectors);
|
|
448
572
|
let scope = this.page;
|
|
449
573
|
if (selectors.iframe_src || selectors.frameLocators) {
|
|
574
|
+
const findFrame = (frame, framescope) => {
|
|
575
|
+
for (let i = 0; i < frame.selectors.length; i++) {
|
|
576
|
+
let frameLocator = frame.selectors[i];
|
|
577
|
+
if (frameLocator.css) {
|
|
578
|
+
framescope = framescope.frameLocator(frameLocator.css);
|
|
579
|
+
break;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
if (frame.children) {
|
|
583
|
+
return findFrame(frame.children, framescope);
|
|
584
|
+
}
|
|
585
|
+
return framescope;
|
|
586
|
+
};
|
|
450
587
|
info.log += "searching for iframe " + selectors.iframe_src + "/" + selectors.frameLocators + "\n";
|
|
451
588
|
while (true) {
|
|
452
589
|
let frameFound = false;
|
|
590
|
+
if (selectors.nestFrmLoc) {
|
|
591
|
+
scope = findFrame(selectors.nestFrmLoc, scope);
|
|
592
|
+
frameFound = true;
|
|
593
|
+
break;
|
|
594
|
+
}
|
|
453
595
|
if (selectors.frameLocators) {
|
|
454
596
|
for (let i = 0; i < selectors.frameLocators.length; i++) {
|
|
455
597
|
let frameLocator = selectors.frameLocators[i];
|
|
@@ -613,6 +755,9 @@ class StableBrowser {
|
|
|
613
755
|
async click(selectors, _params, options = {}, world = null) {
|
|
614
756
|
this._validateSelectors(selectors);
|
|
615
757
|
const startTime = Date.now();
|
|
758
|
+
if (options && options.context) {
|
|
759
|
+
selectors.locators[0].text = options.context;
|
|
760
|
+
}
|
|
616
761
|
const info = {};
|
|
617
762
|
info.log = "***** click on " + selectors.element_name + " *****\n";
|
|
618
763
|
info.operation = "click";
|
|
@@ -626,14 +771,14 @@ class StableBrowser {
|
|
|
626
771
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
627
772
|
try {
|
|
628
773
|
await this._highlightElements(element);
|
|
629
|
-
await element.click(
|
|
774
|
+
await element.click();
|
|
630
775
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
631
776
|
}
|
|
632
777
|
catch (e) {
|
|
633
778
|
// await this.closeUnexpectedPopups();
|
|
634
779
|
info.log += "click failed, will try again" + "\n";
|
|
635
780
|
element = await this._locate(selectors, info, _params);
|
|
636
|
-
await element.click
|
|
781
|
+
await element.dispatchEvent("click");
|
|
637
782
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
638
783
|
}
|
|
639
784
|
await this.waitForPageLoad();
|
|
@@ -686,7 +831,7 @@ class StableBrowser {
|
|
|
686
831
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
687
832
|
try {
|
|
688
833
|
await this._highlightElements(element);
|
|
689
|
-
await element.setChecked(checked
|
|
834
|
+
await element.setChecked(checked);
|
|
690
835
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
691
836
|
}
|
|
692
837
|
catch (e) {
|
|
@@ -750,7 +895,7 @@ class StableBrowser {
|
|
|
750
895
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
751
896
|
try {
|
|
752
897
|
await this._highlightElements(element);
|
|
753
|
-
await element.hover(
|
|
898
|
+
await element.hover();
|
|
754
899
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
755
900
|
}
|
|
756
901
|
catch (e) {
|
|
@@ -812,7 +957,7 @@ class StableBrowser {
|
|
|
812
957
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
813
958
|
try {
|
|
814
959
|
await this._highlightElements(element);
|
|
815
|
-
await element.selectOption(values
|
|
960
|
+
await element.selectOption(values);
|
|
816
961
|
}
|
|
817
962
|
catch (e) {
|
|
818
963
|
//await this.closeUnexpectedPopups();
|
|
@@ -921,71 +1066,45 @@ class StableBrowser {
|
|
|
921
1066
|
});
|
|
922
1067
|
}
|
|
923
1068
|
}
|
|
924
|
-
async
|
|
1069
|
+
async setInputValue(selectors, value, _params = null, options = {}, world = null) {
|
|
1070
|
+
// set input value for non fillable inputs like date, time, range, color, etc.
|
|
925
1071
|
this._validateSelectors(selectors);
|
|
926
1072
|
const startTime = Date.now();
|
|
927
|
-
let error = null;
|
|
928
|
-
let screenshotId = null;
|
|
929
|
-
let screenshotPath = null;
|
|
930
1073
|
const info = {};
|
|
931
|
-
info.log = "";
|
|
932
|
-
info.operation =
|
|
1074
|
+
info.log = "***** set input value " + selectors.element_name + " *****\n";
|
|
1075
|
+
info.operation = "setInputValue";
|
|
933
1076
|
info.selectors = selectors;
|
|
1077
|
+
value = this._fixUsingParams(value, _params);
|
|
934
1078
|
info.value = value;
|
|
1079
|
+
let error = null;
|
|
1080
|
+
let screenshotId = null;
|
|
1081
|
+
let screenshotPath = null;
|
|
935
1082
|
try {
|
|
936
1083
|
value = await this._replaceWithLocalData(value, this);
|
|
937
1084
|
let element = await this._locate(selectors, info, _params);
|
|
938
|
-
//insert red border around the element
|
|
939
1085
|
await this.scrollIfNeeded(element, info);
|
|
940
1086
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
941
1087
|
await this._highlightElements(element);
|
|
942
1088
|
try {
|
|
943
|
-
await element.
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
value = dayjs(value).format(format);
|
|
947
|
-
await element.fill(value);
|
|
948
|
-
}
|
|
949
|
-
else {
|
|
950
|
-
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
951
|
-
await element.evaluateHandle((el, dateTimeValue) => {
|
|
952
|
-
el.value = ""; // clear input
|
|
953
|
-
el.value = dateTimeValue;
|
|
954
|
-
}, dateTimeValue);
|
|
955
|
-
}
|
|
956
|
-
if (enter) {
|
|
957
|
-
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
958
|
-
await this.page.keyboard.press("Enter");
|
|
959
|
-
await this.waitForPageLoad();
|
|
960
|
-
}
|
|
1089
|
+
await element.evaluateHandle((el, value) => {
|
|
1090
|
+
el.value = value;
|
|
1091
|
+
}, value);
|
|
961
1092
|
}
|
|
962
1093
|
catch (error) {
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
this.logger.info("Trying again")(({ screenshotId, screenshotPath } = await this._screenShot(options, world, info)));
|
|
1094
|
+
this.logger.error("setInputValue failed, will try again");
|
|
1095
|
+
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
966
1096
|
info.screenshotPath = screenshotPath;
|
|
967
1097
|
Object.assign(error, { info: info });
|
|
968
|
-
await element.
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
value = dayjs(value).format(format);
|
|
972
|
-
await element.fill(value);
|
|
973
|
-
}
|
|
974
|
-
else {
|
|
975
|
-
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
976
|
-
await element.evaluateHandle((el, dateTimeValue) => {
|
|
977
|
-
el.value = ""; // clear input
|
|
978
|
-
el.value = dateTimeValue;
|
|
979
|
-
}, dateTimeValue);
|
|
980
|
-
}
|
|
981
|
-
if (enter) {
|
|
982
|
-
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
983
|
-
await this.page.keyboard.press("Enter");
|
|
984
|
-
await this.waitForPageLoad();
|
|
985
|
-
}
|
|
1098
|
+
await element.evaluateHandle((el, value) => {
|
|
1099
|
+
el.value = value;
|
|
1100
|
+
});
|
|
986
1101
|
}
|
|
987
1102
|
}
|
|
988
|
-
catch (
|
|
1103
|
+
catch (e) {
|
|
1104
|
+
this.logger.error("setInputValue failed " + info.log);
|
|
1105
|
+
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1106
|
+
info.screenshotPath = screenshotPath;
|
|
1107
|
+
Object.assign(e, { info: info });
|
|
989
1108
|
error = e;
|
|
990
1109
|
throw e;
|
|
991
1110
|
}
|
|
@@ -993,10 +1112,10 @@ class StableBrowser {
|
|
|
993
1112
|
const endTime = Date.now();
|
|
994
1113
|
this._reportToWorld(world, {
|
|
995
1114
|
element_name: selectors.element_name,
|
|
996
|
-
type: Types.
|
|
997
|
-
|
|
1115
|
+
type: Types.SET_INPUT,
|
|
1116
|
+
text: `Set input value`,
|
|
998
1117
|
value: value,
|
|
999
|
-
|
|
1118
|
+
screenshotId,
|
|
1000
1119
|
result: error
|
|
1001
1120
|
? {
|
|
1002
1121
|
status: "FAILED",
|
|
@@ -1013,7 +1132,7 @@ class StableBrowser {
|
|
|
1013
1132
|
});
|
|
1014
1133
|
}
|
|
1015
1134
|
}
|
|
1016
|
-
async setDateTime(selectors, value, enter = false, _params = null, options = {}, world = null) {
|
|
1135
|
+
async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
|
|
1017
1136
|
this._validateSelectors(selectors);
|
|
1018
1137
|
const startTime = Date.now();
|
|
1019
1138
|
let error = null;
|
|
@@ -1025,6 +1144,7 @@ class StableBrowser {
|
|
|
1025
1144
|
info.selectors = selectors;
|
|
1026
1145
|
info.value = value;
|
|
1027
1146
|
try {
|
|
1147
|
+
value = await this._replaceWithLocalData(value, this);
|
|
1028
1148
|
let element = await this._locate(selectors, info, _params);
|
|
1029
1149
|
//insert red border around the element
|
|
1030
1150
|
await this.scrollIfNeeded(element, info);
|
|
@@ -1033,28 +1153,51 @@ class StableBrowser {
|
|
|
1033
1153
|
try {
|
|
1034
1154
|
await element.click();
|
|
1035
1155
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1156
|
+
if (format) {
|
|
1157
|
+
value = dayjs(value).format(format);
|
|
1158
|
+
await element.fill(value);
|
|
1159
|
+
}
|
|
1160
|
+
else {
|
|
1161
|
+
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
1162
|
+
await element.evaluateHandle((el, dateTimeValue) => {
|
|
1163
|
+
el.value = ""; // clear input
|
|
1164
|
+
el.value = dateTimeValue;
|
|
1165
|
+
}, dateTimeValue);
|
|
1166
|
+
}
|
|
1167
|
+
if (enter) {
|
|
1168
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1169
|
+
await this.page.keyboard.press("Enter");
|
|
1170
|
+
await this.waitForPageLoad();
|
|
1171
|
+
}
|
|
1041
1172
|
}
|
|
1042
|
-
catch (
|
|
1173
|
+
catch (err) {
|
|
1043
1174
|
//await this.closeUnexpectedPopups();
|
|
1044
1175
|
this.logger.error("setting date time input failed " + JSON.stringify(info));
|
|
1045
|
-
this.logger.info("Trying again")
|
|
1176
|
+
this.logger.info("Trying again");
|
|
1177
|
+
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1046
1178
|
info.screenshotPath = screenshotPath;
|
|
1047
|
-
Object.assign(
|
|
1179
|
+
Object.assign(err, { info: info });
|
|
1048
1180
|
await element.click();
|
|
1049
1181
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1182
|
+
if (format) {
|
|
1183
|
+
value = dayjs(value).format(format);
|
|
1184
|
+
await element.fill(value);
|
|
1185
|
+
}
|
|
1186
|
+
else {
|
|
1187
|
+
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
1188
|
+
await element.evaluateHandle((el, dateTimeValue) => {
|
|
1189
|
+
el.value = ""; // clear input
|
|
1190
|
+
el.value = dateTimeValue;
|
|
1191
|
+
}, dateTimeValue);
|
|
1192
|
+
}
|
|
1193
|
+
if (enter) {
|
|
1194
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1195
|
+
await this.page.keyboard.press("Enter");
|
|
1196
|
+
await this.waitForPageLoad();
|
|
1197
|
+
}
|
|
1055
1198
|
}
|
|
1056
1199
|
}
|
|
1057
|
-
catch (
|
|
1200
|
+
catch (e) {
|
|
1058
1201
|
error = e;
|
|
1059
1202
|
throw e;
|
|
1060
1203
|
}
|
|
@@ -1217,7 +1360,7 @@ class StableBrowser {
|
|
|
1217
1360
|
let element = await this._locate(selectors, info, _params);
|
|
1218
1361
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1219
1362
|
await this._highlightElements(element);
|
|
1220
|
-
await element.fill(value
|
|
1363
|
+
await element.fill(value);
|
|
1221
1364
|
await element.dispatchEvent("change");
|
|
1222
1365
|
if (enter) {
|
|
1223
1366
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
@@ -1436,7 +1579,7 @@ class StableBrowser {
|
|
|
1436
1579
|
return info;
|
|
1437
1580
|
}
|
|
1438
1581
|
catch (e) {
|
|
1439
|
-
|
|
1582
|
+
await this.closeUnexpectedPopups();
|
|
1440
1583
|
this.logger.error("verify element contains text failed " + info.log);
|
|
1441
1584
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1442
1585
|
info.screenshotPath = screenshotPath;
|
|
@@ -1484,6 +1627,29 @@ class StableBrowser {
|
|
|
1484
1627
|
}
|
|
1485
1628
|
return dataFile;
|
|
1486
1629
|
}
|
|
1630
|
+
async waitForUserInput(message, world = null) {
|
|
1631
|
+
if (!message) {
|
|
1632
|
+
message = "# Wait for user input. Press any key to continue";
|
|
1633
|
+
}
|
|
1634
|
+
else {
|
|
1635
|
+
message = "# Wait for user input. " + message;
|
|
1636
|
+
}
|
|
1637
|
+
message += "\n";
|
|
1638
|
+
const value = await new Promise((resolve) => {
|
|
1639
|
+
const rl = readline.createInterface({
|
|
1640
|
+
input: process.stdin,
|
|
1641
|
+
output: process.stdout,
|
|
1642
|
+
});
|
|
1643
|
+
rl.question(message, (answer) => {
|
|
1644
|
+
rl.close();
|
|
1645
|
+
resolve(answer);
|
|
1646
|
+
});
|
|
1647
|
+
});
|
|
1648
|
+
if (value) {
|
|
1649
|
+
this.logger.info(`{{userInput}} was set to: ${value}`);
|
|
1650
|
+
}
|
|
1651
|
+
this.setTestData({ userInput: value }, world);
|
|
1652
|
+
}
|
|
1487
1653
|
setTestData(testData, world = null) {
|
|
1488
1654
|
if (!testData) {
|
|
1489
1655
|
return;
|
|
@@ -1511,7 +1677,7 @@ class StableBrowser {
|
|
|
1511
1677
|
const data = fs.readFileSync(filePath, "utf8");
|
|
1512
1678
|
const results = [];
|
|
1513
1679
|
return new Promise((resolve, reject) => {
|
|
1514
|
-
const readableStream = new
|
|
1680
|
+
const readableStream = new Readable();
|
|
1515
1681
|
readableStream._read = () => { }; // _read is required but you can noop it
|
|
1516
1682
|
readableStream.push(data);
|
|
1517
1683
|
readableStream.push(null);
|
|
@@ -1691,13 +1857,13 @@ class StableBrowser {
|
|
|
1691
1857
|
])));
|
|
1692
1858
|
const { data } = await client.send("Page.captureScreenshot", {
|
|
1693
1859
|
format: "png",
|
|
1694
|
-
clip: {
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
},
|
|
1860
|
+
// clip: {
|
|
1861
|
+
// x: 0,
|
|
1862
|
+
// y: 0,
|
|
1863
|
+
// width: viewportWidth,
|
|
1864
|
+
// height: viewportHeight,
|
|
1865
|
+
// scale: 1,
|
|
1866
|
+
// },
|
|
1701
1867
|
});
|
|
1702
1868
|
if (!screenshotPath) {
|
|
1703
1869
|
return data;
|
|
@@ -2523,13 +2689,13 @@ class StableBrowser {
|
|
|
2523
2689
|
}
|
|
2524
2690
|
catch (e) {
|
|
2525
2691
|
if (e.label === "networkidle") {
|
|
2526
|
-
console.log("
|
|
2692
|
+
console.log("waited for the network to be idle timeout");
|
|
2527
2693
|
}
|
|
2528
2694
|
else if (e.label === "load") {
|
|
2529
|
-
console.log("
|
|
2695
|
+
console.log("waited for the load timeout");
|
|
2530
2696
|
}
|
|
2531
2697
|
else if (e.label === "domcontentloaded") {
|
|
2532
|
-
console.log("
|
|
2698
|
+
console.log("waited for the domcontent loaded timeout");
|
|
2533
2699
|
}
|
|
2534
2700
|
console.log(".");
|
|
2535
2701
|
}
|
|
@@ -2564,13 +2730,6 @@ class StableBrowser {
|
|
|
2564
2730
|
const info = {};
|
|
2565
2731
|
try {
|
|
2566
2732
|
await this.page.close();
|
|
2567
|
-
if (this.context && this.context.pages && this.context.pages.length > 0) {
|
|
2568
|
-
this.context.pages.pop();
|
|
2569
|
-
this.page = this.context.pages[this.context.pages.length - 1];
|
|
2570
|
-
this.context.page = this.page;
|
|
2571
|
-
let title = await this.page.title();
|
|
2572
|
-
console.log("Switched to page " + title);
|
|
2573
|
-
}
|
|
2574
2733
|
}
|
|
2575
2734
|
catch (e) {
|
|
2576
2735
|
console.log(".");
|
|
@@ -2679,33 +2838,18 @@ class StableBrowser {
|
|
|
2679
2838
|
}
|
|
2680
2839
|
async scrollIfNeeded(element, info) {
|
|
2681
2840
|
try {
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
if (rect &&
|
|
2685
|
-
rect.top >= 0 &&
|
|
2686
|
-
rect.left >= 0 &&
|
|
2687
|
-
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
|
|
2688
|
-
rect.right <= (window.innerWidth || document.documentElement.clientWidth)) {
|
|
2689
|
-
return false;
|
|
2690
|
-
}
|
|
2691
|
-
else {
|
|
2692
|
-
node.scrollIntoView({
|
|
2693
|
-
behavior: "smooth",
|
|
2694
|
-
block: "center",
|
|
2695
|
-
inline: "center",
|
|
2696
|
-
});
|
|
2697
|
-
return true;
|
|
2698
|
-
}
|
|
2841
|
+
await element.scrollIntoViewIfNeeded({
|
|
2842
|
+
timeout: 2000,
|
|
2699
2843
|
});
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
}
|
|
2844
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
2845
|
+
if (info) {
|
|
2846
|
+
info.box = await element.boundingBox({
|
|
2847
|
+
timeout: 1000,
|
|
2848
|
+
});
|
|
2705
2849
|
}
|
|
2706
2850
|
}
|
|
2707
2851
|
catch (e) {
|
|
2708
|
-
console.log("
|
|
2852
|
+
console.log("#-#");
|
|
2709
2853
|
}
|
|
2710
2854
|
}
|
|
2711
2855
|
_reportToWorld(world, properties) {
|