automation_model 1.0.401-dev → 1.0.401-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 +292 -147
- 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(this.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,32 +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;
|
|
185
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
|
+
}
|
|
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);
|
|
198
299
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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}`);
|
|
203
324
|
}
|
|
204
|
-
const locator = scope.locator(`${locator.engine}="${selector}"`);
|
|
205
|
-
return locator;
|
|
206
325
|
}
|
|
207
|
-
|
|
326
|
+
if (!locatorReturn) {
|
|
327
|
+
console.error(locator);
|
|
328
|
+
throw new Error("Locator undefined");
|
|
329
|
+
}
|
|
330
|
+
return locatorReturn;
|
|
208
331
|
}
|
|
209
332
|
async _locateElmentByTextClimbCss(scope, text, climb, css, _params) {
|
|
210
333
|
let result = await this._locateElementByText(scope, this._fixUsingParams(text, _params), "*", false, true, _params);
|
|
@@ -415,6 +538,8 @@ class StableBrowser {
|
|
|
415
538
|
if (result.foundElements.length > 0) {
|
|
416
539
|
let dialogCloseLocator = result.foundElements[0].locator;
|
|
417
540
|
await dialogCloseLocator.click();
|
|
541
|
+
// wait for the dialog to close
|
|
542
|
+
await dialogCloseLocator.waitFor({ state: "hidden" });
|
|
418
543
|
return { rerun: true };
|
|
419
544
|
}
|
|
420
545
|
}
|
|
@@ -423,7 +548,7 @@ class StableBrowser {
|
|
|
423
548
|
}
|
|
424
549
|
async _locate(selectors, info, _params, timeout = 30000) {
|
|
425
550
|
for (let i = 0; i < 3; i++) {
|
|
426
|
-
info.log += "attempt " + i + ":
|
|
551
|
+
info.log += "attempt " + i + ": total locators " + selectors.locators.length + "\n";
|
|
427
552
|
for (let j = 0; j < selectors.locators.length; j++) {
|
|
428
553
|
let selector = selectors.locators[j];
|
|
429
554
|
info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
|
|
@@ -443,9 +568,27 @@ class StableBrowser {
|
|
|
443
568
|
//let arrayMode = Array.isArray(selectors);
|
|
444
569
|
let scope = this.page;
|
|
445
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
|
+
};
|
|
446
584
|
info.log += "searching for iframe " + selectors.iframe_src + "/" + selectors.frameLocators + "\n";
|
|
447
585
|
while (true) {
|
|
448
586
|
let frameFound = false;
|
|
587
|
+
if (selectors.nestFrmLoc) {
|
|
588
|
+
scope = findFrame(selectors.nestFrmLoc, scope);
|
|
589
|
+
frameFound = true;
|
|
590
|
+
break;
|
|
591
|
+
}
|
|
449
592
|
if (selectors.frameLocators) {
|
|
450
593
|
for (let i = 0; i < selectors.frameLocators.length; i++) {
|
|
451
594
|
let frameLocator = selectors.frameLocators[i];
|
|
@@ -609,6 +752,9 @@ class StableBrowser {
|
|
|
609
752
|
async click(selectors, _params, options = {}, world = null) {
|
|
610
753
|
this._validateSelectors(selectors);
|
|
611
754
|
const startTime = Date.now();
|
|
755
|
+
if (options && options.context) {
|
|
756
|
+
selectors.locators[0].text = options.context;
|
|
757
|
+
}
|
|
612
758
|
const info = {};
|
|
613
759
|
info.log = "***** click on " + selectors.element_name + " *****\n";
|
|
614
760
|
info.operation = "click";
|
|
@@ -622,14 +768,14 @@ class StableBrowser {
|
|
|
622
768
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
623
769
|
try {
|
|
624
770
|
await this._highlightElements(element);
|
|
625
|
-
await element.click(
|
|
771
|
+
await element.click();
|
|
626
772
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
627
773
|
}
|
|
628
774
|
catch (e) {
|
|
629
775
|
// await this.closeUnexpectedPopups();
|
|
630
776
|
info.log += "click failed, will try again" + "\n";
|
|
631
777
|
element = await this._locate(selectors, info, _params);
|
|
632
|
-
await element.click
|
|
778
|
+
await element.dispatchEvent("click");
|
|
633
779
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
634
780
|
}
|
|
635
781
|
await this.waitForPageLoad();
|
|
@@ -682,7 +828,7 @@ class StableBrowser {
|
|
|
682
828
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
683
829
|
try {
|
|
684
830
|
await this._highlightElements(element);
|
|
685
|
-
await element.setChecked(checked
|
|
831
|
+
await element.setChecked(checked);
|
|
686
832
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
687
833
|
}
|
|
688
834
|
catch (e) {
|
|
@@ -746,7 +892,7 @@ class StableBrowser {
|
|
|
746
892
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
747
893
|
try {
|
|
748
894
|
await this._highlightElements(element);
|
|
749
|
-
await element.hover(
|
|
895
|
+
await element.hover();
|
|
750
896
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
751
897
|
}
|
|
752
898
|
catch (e) {
|
|
@@ -808,7 +954,7 @@ class StableBrowser {
|
|
|
808
954
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
809
955
|
try {
|
|
810
956
|
await this._highlightElements(element);
|
|
811
|
-
await element.selectOption(values
|
|
957
|
+
await element.selectOption(values);
|
|
812
958
|
}
|
|
813
959
|
catch (e) {
|
|
814
960
|
//await this.closeUnexpectedPopups();
|
|
@@ -917,71 +1063,45 @@ class StableBrowser {
|
|
|
917
1063
|
});
|
|
918
1064
|
}
|
|
919
1065
|
}
|
|
920
|
-
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.
|
|
921
1068
|
this._validateSelectors(selectors);
|
|
922
1069
|
const startTime = Date.now();
|
|
923
|
-
let error = null;
|
|
924
|
-
let screenshotId = null;
|
|
925
|
-
let screenshotPath = null;
|
|
926
1070
|
const info = {};
|
|
927
|
-
info.log = "";
|
|
928
|
-
info.operation =
|
|
1071
|
+
info.log = "***** set input value " + selectors.element_name + " *****\n";
|
|
1072
|
+
info.operation = "setInputValue";
|
|
929
1073
|
info.selectors = selectors;
|
|
1074
|
+
value = this._fixUsingParams(value, _params);
|
|
930
1075
|
info.value = value;
|
|
1076
|
+
let error = null;
|
|
1077
|
+
let screenshotId = null;
|
|
1078
|
+
let screenshotPath = null;
|
|
931
1079
|
try {
|
|
932
1080
|
value = await this._replaceWithLocalData(value, this);
|
|
933
1081
|
let element = await this._locate(selectors, info, _params);
|
|
934
|
-
//insert red border around the element
|
|
935
1082
|
await this.scrollIfNeeded(element, info);
|
|
936
1083
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
937
1084
|
await this._highlightElements(element);
|
|
938
1085
|
try {
|
|
939
|
-
await element.
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
value = dayjs(value).format(format);
|
|
943
|
-
await element.fill(value);
|
|
944
|
-
}
|
|
945
|
-
else {
|
|
946
|
-
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
947
|
-
await element.evaluateHandle((el, dateTimeValue) => {
|
|
948
|
-
el.value = ""; // clear input
|
|
949
|
-
el.value = dateTimeValue;
|
|
950
|
-
}, dateTimeValue);
|
|
951
|
-
}
|
|
952
|
-
if (enter) {
|
|
953
|
-
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
954
|
-
await this.page.keyboard.press("Enter");
|
|
955
|
-
await this.waitForPageLoad();
|
|
956
|
-
}
|
|
1086
|
+
await element.evaluateHandle((el, value) => {
|
|
1087
|
+
el.value = value;
|
|
1088
|
+
}, value);
|
|
957
1089
|
}
|
|
958
1090
|
catch (error) {
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
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));
|
|
962
1093
|
info.screenshotPath = screenshotPath;
|
|
963
1094
|
Object.assign(error, { info: info });
|
|
964
|
-
await element.
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
value = dayjs(value).format(format);
|
|
968
|
-
await element.fill(value);
|
|
969
|
-
}
|
|
970
|
-
else {
|
|
971
|
-
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
972
|
-
await element.evaluateHandle((el, dateTimeValue) => {
|
|
973
|
-
el.value = ""; // clear input
|
|
974
|
-
el.value = dateTimeValue;
|
|
975
|
-
}, dateTimeValue);
|
|
976
|
-
}
|
|
977
|
-
if (enter) {
|
|
978
|
-
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
979
|
-
await this.page.keyboard.press("Enter");
|
|
980
|
-
await this.waitForPageLoad();
|
|
981
|
-
}
|
|
1095
|
+
await element.evaluateHandle((el, value) => {
|
|
1096
|
+
el.value = value;
|
|
1097
|
+
});
|
|
982
1098
|
}
|
|
983
1099
|
}
|
|
984
|
-
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 });
|
|
985
1105
|
error = e;
|
|
986
1106
|
throw e;
|
|
987
1107
|
}
|
|
@@ -989,10 +1109,10 @@ class StableBrowser {
|
|
|
989
1109
|
const endTime = Date.now();
|
|
990
1110
|
this._reportToWorld(world, {
|
|
991
1111
|
element_name: selectors.element_name,
|
|
992
|
-
type: Types.
|
|
993
|
-
|
|
1112
|
+
type: Types.SET_INPUT,
|
|
1113
|
+
text: `Set input value`,
|
|
994
1114
|
value: value,
|
|
995
|
-
|
|
1115
|
+
screenshotId,
|
|
996
1116
|
result: error
|
|
997
1117
|
? {
|
|
998
1118
|
status: "FAILED",
|
|
@@ -1009,7 +1129,7 @@ class StableBrowser {
|
|
|
1009
1129
|
});
|
|
1010
1130
|
}
|
|
1011
1131
|
}
|
|
1012
|
-
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) {
|
|
1013
1133
|
this._validateSelectors(selectors);
|
|
1014
1134
|
const startTime = Date.now();
|
|
1015
1135
|
let error = null;
|
|
@@ -1021,6 +1141,7 @@ class StableBrowser {
|
|
|
1021
1141
|
info.selectors = selectors;
|
|
1022
1142
|
info.value = value;
|
|
1023
1143
|
try {
|
|
1144
|
+
value = await this._replaceWithLocalData(value, this);
|
|
1024
1145
|
let element = await this._locate(selectors, info, _params);
|
|
1025
1146
|
//insert red border around the element
|
|
1026
1147
|
await this.scrollIfNeeded(element, info);
|
|
@@ -1029,28 +1150,51 @@ class StableBrowser {
|
|
|
1029
1150
|
try {
|
|
1030
1151
|
await element.click();
|
|
1031
1152
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
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
|
+
}
|
|
1037
1169
|
}
|
|
1038
|
-
catch (
|
|
1170
|
+
catch (err) {
|
|
1039
1171
|
//await this.closeUnexpectedPopups();
|
|
1040
1172
|
this.logger.error("setting date time input failed " + JSON.stringify(info));
|
|
1041
|
-
this.logger.info("Trying again")
|
|
1173
|
+
this.logger.info("Trying again");
|
|
1174
|
+
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1042
1175
|
info.screenshotPath = screenshotPath;
|
|
1043
|
-
Object.assign(
|
|
1176
|
+
Object.assign(err, { info: info });
|
|
1044
1177
|
await element.click();
|
|
1045
1178
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
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
|
+
}
|
|
1051
1195
|
}
|
|
1052
1196
|
}
|
|
1053
|
-
catch (
|
|
1197
|
+
catch (e) {
|
|
1054
1198
|
error = e;
|
|
1055
1199
|
throw e;
|
|
1056
1200
|
}
|
|
@@ -1213,7 +1357,7 @@ class StableBrowser {
|
|
|
1213
1357
|
let element = await this._locate(selectors, info, _params);
|
|
1214
1358
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1215
1359
|
await this._highlightElements(element);
|
|
1216
|
-
await element.fill(value
|
|
1360
|
+
await element.fill(value);
|
|
1217
1361
|
await element.dispatchEvent("change");
|
|
1218
1362
|
if (enter) {
|
|
1219
1363
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
@@ -1432,7 +1576,7 @@ class StableBrowser {
|
|
|
1432
1576
|
return info;
|
|
1433
1577
|
}
|
|
1434
1578
|
catch (e) {
|
|
1435
|
-
|
|
1579
|
+
await this.closeUnexpectedPopups();
|
|
1436
1580
|
this.logger.error("verify element contains text failed " + info.log);
|
|
1437
1581
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1438
1582
|
info.screenshotPath = screenshotPath;
|
|
@@ -1480,6 +1624,29 @@ class StableBrowser {
|
|
|
1480
1624
|
}
|
|
1481
1625
|
return dataFile;
|
|
1482
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
|
+
}
|
|
1483
1650
|
setTestData(testData, world = null) {
|
|
1484
1651
|
if (!testData) {
|
|
1485
1652
|
return;
|
|
@@ -1507,7 +1674,7 @@ class StableBrowser {
|
|
|
1507
1674
|
const data = fs.readFileSync(filePath, "utf8");
|
|
1508
1675
|
const results = [];
|
|
1509
1676
|
return new Promise((resolve, reject) => {
|
|
1510
|
-
const readableStream = new
|
|
1677
|
+
const readableStream = new Readable();
|
|
1511
1678
|
readableStream._read = () => { }; // _read is required but you can noop it
|
|
1512
1679
|
readableStream.push(data);
|
|
1513
1680
|
readableStream.push(null);
|
|
@@ -1687,13 +1854,13 @@ class StableBrowser {
|
|
|
1687
1854
|
])));
|
|
1688
1855
|
const { data } = await client.send("Page.captureScreenshot", {
|
|
1689
1856
|
format: "png",
|
|
1690
|
-
clip: {
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
},
|
|
1857
|
+
// clip: {
|
|
1858
|
+
// x: 0,
|
|
1859
|
+
// y: 0,
|
|
1860
|
+
// width: viewportWidth,
|
|
1861
|
+
// height: viewportHeight,
|
|
1862
|
+
// scale: 1,
|
|
1863
|
+
// },
|
|
1697
1864
|
});
|
|
1698
1865
|
if (!screenshotPath) {
|
|
1699
1866
|
return data;
|
|
@@ -2519,13 +2686,13 @@ class StableBrowser {
|
|
|
2519
2686
|
}
|
|
2520
2687
|
catch (e) {
|
|
2521
2688
|
if (e.label === "networkidle") {
|
|
2522
|
-
console.log("
|
|
2689
|
+
console.log("waited for the network to be idle timeout");
|
|
2523
2690
|
}
|
|
2524
2691
|
else if (e.label === "load") {
|
|
2525
|
-
console.log("
|
|
2692
|
+
console.log("waited for the load timeout");
|
|
2526
2693
|
}
|
|
2527
2694
|
else if (e.label === "domcontentloaded") {
|
|
2528
|
-
console.log("
|
|
2695
|
+
console.log("waited for the domcontent loaded timeout");
|
|
2529
2696
|
}
|
|
2530
2697
|
console.log(".");
|
|
2531
2698
|
}
|
|
@@ -2560,13 +2727,6 @@ class StableBrowser {
|
|
|
2560
2727
|
const info = {};
|
|
2561
2728
|
try {
|
|
2562
2729
|
await this.page.close();
|
|
2563
|
-
if (this.context && this.context.pages && this.context.pages.length > 0) {
|
|
2564
|
-
this.context.pages.pop();
|
|
2565
|
-
this.page = this.context.pages[this.context.pages.length - 1];
|
|
2566
|
-
this.context.page = this.page;
|
|
2567
|
-
let title = await this.page.title();
|
|
2568
|
-
console.log("Switched to page " + title);
|
|
2569
|
-
}
|
|
2570
2730
|
}
|
|
2571
2731
|
catch (e) {
|
|
2572
2732
|
console.log(".");
|
|
@@ -2675,33 +2835,18 @@ class StableBrowser {
|
|
|
2675
2835
|
}
|
|
2676
2836
|
async scrollIfNeeded(element, info) {
|
|
2677
2837
|
try {
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
if (rect &&
|
|
2681
|
-
rect.top >= 0 &&
|
|
2682
|
-
rect.left >= 0 &&
|
|
2683
|
-
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
|
|
2684
|
-
rect.right <= (window.innerWidth || document.documentElement.clientWidth)) {
|
|
2685
|
-
return false;
|
|
2686
|
-
}
|
|
2687
|
-
else {
|
|
2688
|
-
node.scrollIntoView({
|
|
2689
|
-
behavior: "smooth",
|
|
2690
|
-
block: "center",
|
|
2691
|
-
inline: "center",
|
|
2692
|
-
});
|
|
2693
|
-
return true;
|
|
2694
|
-
}
|
|
2838
|
+
await element.scrollIntoViewIfNeeded({
|
|
2839
|
+
timeout: 2000,
|
|
2695
2840
|
});
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
}
|
|
2841
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
2842
|
+
if (info) {
|
|
2843
|
+
info.box = await element.boundingBox({
|
|
2844
|
+
timeout: 1000,
|
|
2845
|
+
});
|
|
2701
2846
|
}
|
|
2702
2847
|
}
|
|
2703
2848
|
catch (e) {
|
|
2704
|
-
console.log("
|
|
2849
|
+
console.log("#-#");
|
|
2705
2850
|
}
|
|
2706
2851
|
}
|
|
2707
2852
|
_reportToWorld(world, properties) {
|