automation_model 1.0.403-dev → 1.0.403-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 +13 -4
- package/lib/stable_browser.js +265 -145
- 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,35 @@ 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.registerConsoleLogListener(page, context, this.webLogFile);
|
|
77
|
-
this.registerRequestListener();
|
|
84
|
+
this.world = world;
|
|
78
85
|
context.pages = [this.page];
|
|
79
86
|
context.pageLoading = { status: false };
|
|
87
|
+
this.registerEventListeners(this.context);
|
|
88
|
+
}
|
|
89
|
+
registerEventListeners(context) {
|
|
90
|
+
this.registerConsoleLogListener(this.page, context);
|
|
91
|
+
this.registerRequestListener(this.page, context, this.webLogFile);
|
|
92
|
+
if (!context.pageLoading) {
|
|
93
|
+
context.pageLoading = { status: false };
|
|
94
|
+
}
|
|
80
95
|
context.playContext.on("page", async function (page) {
|
|
81
96
|
context.pageLoading.status = true;
|
|
82
97
|
this.page = page;
|
|
83
98
|
context.page = page;
|
|
84
99
|
context.pages.push(page);
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
100
|
+
page.on("close", async () => {
|
|
101
|
+
if (this.context && this.context.pages && this.context.pages.length > 1) {
|
|
102
|
+
this.context.pages.pop();
|
|
103
|
+
this.page = this.context.pages[this.context.pages.length - 1];
|
|
104
|
+
this.context.page = this.page;
|
|
105
|
+
try {
|
|
106
|
+
let title = await this.page.title();
|
|
107
|
+
console.log("Switched to page " + title);
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
console.error("Error on page close", error);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
91
113
|
});
|
|
92
114
|
try {
|
|
93
115
|
await this.waitForPageLoad();
|
|
@@ -99,6 +121,36 @@ class StableBrowser {
|
|
|
99
121
|
context.pageLoading.status = false;
|
|
100
122
|
}.bind(this));
|
|
101
123
|
}
|
|
124
|
+
async switchApp(appName) {
|
|
125
|
+
// check if the current app (this.appName) is the same as the new app
|
|
126
|
+
if (this.appName === appName) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
let navigate = false;
|
|
130
|
+
if (!apps[appName]) {
|
|
131
|
+
let newContext = await getContext(null, false, this.logger, appName, false, this);
|
|
132
|
+
navigate = true;
|
|
133
|
+
apps[appName] = {
|
|
134
|
+
context: newContext,
|
|
135
|
+
browser: newContext.browser,
|
|
136
|
+
page: newContext.page,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
const tempContext = {};
|
|
140
|
+
this._copyContext(this, tempContext);
|
|
141
|
+
this._copyContext(apps[appName], this);
|
|
142
|
+
apps[this.appName] = tempContext;
|
|
143
|
+
this.appName = appName;
|
|
144
|
+
if (navigate) {
|
|
145
|
+
await this.goto(this.context.environment.baseUrl);
|
|
146
|
+
await this.waitForPageLoad();
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
_copyContext(from, to) {
|
|
150
|
+
to.browser = from.browser;
|
|
151
|
+
to.page = from.page;
|
|
152
|
+
to.context = from.context;
|
|
153
|
+
}
|
|
102
154
|
getWebLogFile(logFolder) {
|
|
103
155
|
if (!fs.existsSync(logFolder)) {
|
|
104
156
|
fs.mkdirSync(logFolder, { recursive: true });
|
|
@@ -110,37 +162,63 @@ class StableBrowser {
|
|
|
110
162
|
const fileName = nextIndex + ".json";
|
|
111
163
|
return path.join(logFolder, fileName);
|
|
112
164
|
}
|
|
113
|
-
registerConsoleLogListener(page, context
|
|
165
|
+
registerConsoleLogListener(page, context) {
|
|
114
166
|
if (!this.context.webLogger) {
|
|
115
167
|
this.context.webLogger = [];
|
|
116
168
|
}
|
|
117
169
|
page.on("console", async (msg) => {
|
|
118
|
-
|
|
170
|
+
var _a;
|
|
171
|
+
const obj = {
|
|
119
172
|
type: msg.type(),
|
|
120
173
|
text: msg.text(),
|
|
121
174
|
location: msg.location(),
|
|
122
175
|
time: new Date().toISOString(),
|
|
123
|
-
}
|
|
124
|
-
|
|
176
|
+
};
|
|
177
|
+
this.context.webLogger.push(obj);
|
|
178
|
+
(_a = this.world) === null || _a === void 0 ? void 0 : _a.attach(JSON.stringify(obj), { mediaType: "application/json+log" });
|
|
125
179
|
});
|
|
126
180
|
}
|
|
127
|
-
registerRequestListener() {
|
|
128
|
-
this.
|
|
181
|
+
registerRequestListener(page, context, logFile) {
|
|
182
|
+
if (!this.context.networkLogger) {
|
|
183
|
+
this.context.networkLogger = [];
|
|
184
|
+
}
|
|
185
|
+
page.on("request", async (data) => {
|
|
186
|
+
var _a;
|
|
187
|
+
const startTime = new Date().getTime();
|
|
129
188
|
try {
|
|
130
|
-
const pageUrl = new URL(
|
|
189
|
+
const pageUrl = new URL(page.url());
|
|
131
190
|
const requestUrl = new URL(data.url());
|
|
132
191
|
if (pageUrl.hostname === requestUrl.hostname) {
|
|
133
192
|
const method = data.method();
|
|
134
|
-
if (
|
|
193
|
+
if (["POST", "GET", "PUT", "DELETE", "PATCH"].includes(method)) {
|
|
135
194
|
const token = await data.headerValue("Authorization");
|
|
136
195
|
if (token) {
|
|
137
|
-
|
|
196
|
+
context.authtoken = token;
|
|
138
197
|
}
|
|
139
198
|
}
|
|
140
199
|
}
|
|
200
|
+
const response = await data.response();
|
|
201
|
+
const endTime = new Date().getTime();
|
|
202
|
+
const obj = {
|
|
203
|
+
url: data.url(),
|
|
204
|
+
method: data.method(),
|
|
205
|
+
postData: data.postData(),
|
|
206
|
+
error: data.failure() ? data.failure().errorText : null,
|
|
207
|
+
duration: endTime - startTime,
|
|
208
|
+
startTime,
|
|
209
|
+
};
|
|
210
|
+
context.networkLogger.push(obj);
|
|
211
|
+
(_a = this.world) === null || _a === void 0 ? void 0 : _a.attach(JSON.stringify(obj), { mediaType: "application/json+network" });
|
|
141
212
|
}
|
|
142
213
|
catch (error) {
|
|
143
214
|
console.error("Error in request listener", error);
|
|
215
|
+
context.networkLogger.push({
|
|
216
|
+
error: "not able to listen",
|
|
217
|
+
message: error.message,
|
|
218
|
+
stack: error.stack,
|
|
219
|
+
time: new Date().toISOString(),
|
|
220
|
+
});
|
|
221
|
+
// await fs.promises.writeFile(logFile, JSON.stringify(context.networkLogger, null, 2));
|
|
144
222
|
}
|
|
145
223
|
});
|
|
146
224
|
}
|
|
@@ -210,9 +288,7 @@ class StableBrowser {
|
|
|
210
288
|
}
|
|
211
289
|
_getLocator(locator, scope, _params) {
|
|
212
290
|
locator = this._fixLocatorUsingParams(locator, _params);
|
|
213
|
-
|
|
214
|
-
return scope.locator(locator.selector);
|
|
215
|
-
}
|
|
291
|
+
let locatorReturn;
|
|
216
292
|
if (locator.role) {
|
|
217
293
|
if (locator.role[1].nameReg) {
|
|
218
294
|
locator.role[1].name = reg_parser(locator.role[1].nameReg);
|
|
@@ -221,20 +297,42 @@ class StableBrowser {
|
|
|
221
297
|
// if (locator.role[1].name) {
|
|
222
298
|
// locator.role[1].name = this._fixUsingParams(locator.role[1].name, _params);
|
|
223
299
|
// }
|
|
224
|
-
|
|
300
|
+
locatorReturn = scope.getByRole(locator.role[0], locator.role[1]);
|
|
225
301
|
}
|
|
226
302
|
if (locator.css) {
|
|
227
|
-
|
|
303
|
+
locatorReturn = scope.locator(locator.css);
|
|
228
304
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
305
|
+
// handle role/name locators
|
|
306
|
+
// locator.selector will be something like: textbox[name="Username"i]
|
|
307
|
+
if (locator.engine === "internal:role") {
|
|
308
|
+
// extract the role, name and the i/s flags using regex
|
|
309
|
+
const match = locator.selector.match(/(.*)\[(.*)="(.*)"(.*)\]/);
|
|
310
|
+
if (match) {
|
|
311
|
+
const role = match[1];
|
|
312
|
+
const name = match[3];
|
|
313
|
+
const flags = match[4];
|
|
314
|
+
locatorReturn = scope.getByRole(role, { name }, { exact: flags === "i" });
|
|
233
315
|
}
|
|
234
|
-
const locator = scope.locator(`${locator.engine}="${selector}"`);
|
|
235
|
-
return locator;
|
|
236
316
|
}
|
|
237
|
-
|
|
317
|
+
if (locator === null || locator === void 0 ? void 0 : locator.engine) {
|
|
318
|
+
if (locator.engine === "css") {
|
|
319
|
+
locatorReturn = scope.locator(locator.selector);
|
|
320
|
+
}
|
|
321
|
+
else {
|
|
322
|
+
let selector = locator.selector;
|
|
323
|
+
if (locator.engine === "internal:attr") {
|
|
324
|
+
if (!selector.startsWith("[")) {
|
|
325
|
+
selector = `[${selector}]`;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
locatorReturn = scope.locator(`${locator.engine}=${selector}`);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
if (!locatorReturn) {
|
|
332
|
+
console.error(locator);
|
|
333
|
+
throw new Error("Locator undefined");
|
|
334
|
+
}
|
|
335
|
+
return locatorReturn;
|
|
238
336
|
}
|
|
239
337
|
async _locateElmentByTextClimbCss(scope, text, climb, css, _params) {
|
|
240
338
|
let result = await this._locateElementByText(scope, this._fixUsingParams(text, _params), "*", false, true, _params);
|
|
@@ -445,6 +543,8 @@ class StableBrowser {
|
|
|
445
543
|
if (result.foundElements.length > 0) {
|
|
446
544
|
let dialogCloseLocator = result.foundElements[0].locator;
|
|
447
545
|
await dialogCloseLocator.click();
|
|
546
|
+
// wait for the dialog to close
|
|
547
|
+
await dialogCloseLocator.waitFor({ state: "hidden" });
|
|
448
548
|
return { rerun: true };
|
|
449
549
|
}
|
|
450
550
|
}
|
|
@@ -453,7 +553,7 @@ class StableBrowser {
|
|
|
453
553
|
}
|
|
454
554
|
async _locate(selectors, info, _params, timeout = 30000) {
|
|
455
555
|
for (let i = 0; i < 3; i++) {
|
|
456
|
-
info.log += "attempt " + i + ":
|
|
556
|
+
info.log += "attempt " + i + ": total locators " + selectors.locators.length + "\n";
|
|
457
557
|
for (let j = 0; j < selectors.locators.length; j++) {
|
|
458
558
|
let selector = selectors.locators[j];
|
|
459
559
|
info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
|
|
@@ -473,9 +573,27 @@ class StableBrowser {
|
|
|
473
573
|
//let arrayMode = Array.isArray(selectors);
|
|
474
574
|
let scope = this.page;
|
|
475
575
|
if (selectors.iframe_src || selectors.frameLocators) {
|
|
576
|
+
const findFrame = (frame, framescope) => {
|
|
577
|
+
for (let i = 0; i < frame.selectors.length; i++) {
|
|
578
|
+
let frameLocator = frame.selectors[i];
|
|
579
|
+
if (frameLocator.css) {
|
|
580
|
+
framescope = framescope.frameLocator(frameLocator.css);
|
|
581
|
+
break;
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
if (frame.children) {
|
|
585
|
+
return findFrame(frame.children, framescope);
|
|
586
|
+
}
|
|
587
|
+
return framescope;
|
|
588
|
+
};
|
|
476
589
|
info.log += "searching for iframe " + selectors.iframe_src + "/" + selectors.frameLocators + "\n";
|
|
477
590
|
while (true) {
|
|
478
591
|
let frameFound = false;
|
|
592
|
+
if (selectors.nestFrmLoc) {
|
|
593
|
+
scope = findFrame(selectors.nestFrmLoc, scope);
|
|
594
|
+
frameFound = true;
|
|
595
|
+
break;
|
|
596
|
+
}
|
|
479
597
|
if (selectors.frameLocators) {
|
|
480
598
|
for (let i = 0; i < selectors.frameLocators.length; i++) {
|
|
481
599
|
let frameLocator = selectors.frameLocators[i];
|
|
@@ -639,6 +757,9 @@ class StableBrowser {
|
|
|
639
757
|
async click(selectors, _params, options = {}, world = null) {
|
|
640
758
|
this._validateSelectors(selectors);
|
|
641
759
|
const startTime = Date.now();
|
|
760
|
+
if (options && options.context) {
|
|
761
|
+
selectors.locators[0].text = options.context;
|
|
762
|
+
}
|
|
642
763
|
const info = {};
|
|
643
764
|
info.log = "***** click on " + selectors.element_name + " *****\n";
|
|
644
765
|
info.operation = "click";
|
|
@@ -652,14 +773,14 @@ class StableBrowser {
|
|
|
652
773
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
653
774
|
try {
|
|
654
775
|
await this._highlightElements(element);
|
|
655
|
-
await element.click(
|
|
776
|
+
await element.click();
|
|
656
777
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
657
778
|
}
|
|
658
779
|
catch (e) {
|
|
659
780
|
// await this.closeUnexpectedPopups();
|
|
660
781
|
info.log += "click failed, will try again" + "\n";
|
|
661
782
|
element = await this._locate(selectors, info, _params);
|
|
662
|
-
await element.click
|
|
783
|
+
await element.dispatchEvent("click");
|
|
663
784
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
664
785
|
}
|
|
665
786
|
await this.waitForPageLoad();
|
|
@@ -712,7 +833,7 @@ class StableBrowser {
|
|
|
712
833
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
713
834
|
try {
|
|
714
835
|
await this._highlightElements(element);
|
|
715
|
-
await element.setChecked(checked
|
|
836
|
+
await element.setChecked(checked);
|
|
716
837
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
717
838
|
}
|
|
718
839
|
catch (e) {
|
|
@@ -776,7 +897,7 @@ class StableBrowser {
|
|
|
776
897
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
777
898
|
try {
|
|
778
899
|
await this._highlightElements(element);
|
|
779
|
-
await element.hover(
|
|
900
|
+
await element.hover();
|
|
780
901
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
781
902
|
}
|
|
782
903
|
catch (e) {
|
|
@@ -838,7 +959,7 @@ class StableBrowser {
|
|
|
838
959
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
839
960
|
try {
|
|
840
961
|
await this._highlightElements(element);
|
|
841
|
-
await element.selectOption(values
|
|
962
|
+
await element.selectOption(values);
|
|
842
963
|
}
|
|
843
964
|
catch (e) {
|
|
844
965
|
//await this.closeUnexpectedPopups();
|
|
@@ -947,71 +1068,45 @@ class StableBrowser {
|
|
|
947
1068
|
});
|
|
948
1069
|
}
|
|
949
1070
|
}
|
|
950
|
-
async
|
|
1071
|
+
async setInputValue(selectors, value, _params = null, options = {}, world = null) {
|
|
1072
|
+
// set input value for non fillable inputs like date, time, range, color, etc.
|
|
951
1073
|
this._validateSelectors(selectors);
|
|
952
1074
|
const startTime = Date.now();
|
|
953
|
-
let error = null;
|
|
954
|
-
let screenshotId = null;
|
|
955
|
-
let screenshotPath = null;
|
|
956
1075
|
const info = {};
|
|
957
|
-
info.log = "";
|
|
958
|
-
info.operation =
|
|
1076
|
+
info.log = "***** set input value " + selectors.element_name + " *****\n";
|
|
1077
|
+
info.operation = "setInputValue";
|
|
959
1078
|
info.selectors = selectors;
|
|
1079
|
+
value = this._fixUsingParams(value, _params);
|
|
960
1080
|
info.value = value;
|
|
1081
|
+
let error = null;
|
|
1082
|
+
let screenshotId = null;
|
|
1083
|
+
let screenshotPath = null;
|
|
961
1084
|
try {
|
|
962
1085
|
value = await this._replaceWithLocalData(value, this);
|
|
963
1086
|
let element = await this._locate(selectors, info, _params);
|
|
964
|
-
//insert red border around the element
|
|
965
1087
|
await this.scrollIfNeeded(element, info);
|
|
966
1088
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
967
1089
|
await this._highlightElements(element);
|
|
968
1090
|
try {
|
|
969
|
-
await element.
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
value = dayjs(value).format(format);
|
|
973
|
-
await element.fill(value);
|
|
974
|
-
}
|
|
975
|
-
else {
|
|
976
|
-
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
977
|
-
await element.evaluateHandle((el, dateTimeValue) => {
|
|
978
|
-
el.value = ""; // clear input
|
|
979
|
-
el.value = dateTimeValue;
|
|
980
|
-
}, dateTimeValue);
|
|
981
|
-
}
|
|
982
|
-
if (enter) {
|
|
983
|
-
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
984
|
-
await this.page.keyboard.press("Enter");
|
|
985
|
-
await this.waitForPageLoad();
|
|
986
|
-
}
|
|
1091
|
+
await element.evaluateHandle((el, value) => {
|
|
1092
|
+
el.value = value;
|
|
1093
|
+
}, value);
|
|
987
1094
|
}
|
|
988
1095
|
catch (error) {
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
this.logger.info("Trying again")(({ screenshotId, screenshotPath } = await this._screenShot(options, world, info)));
|
|
1096
|
+
this.logger.error("setInputValue failed, will try again");
|
|
1097
|
+
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
992
1098
|
info.screenshotPath = screenshotPath;
|
|
993
1099
|
Object.assign(error, { info: info });
|
|
994
|
-
await element.
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
value = dayjs(value).format(format);
|
|
998
|
-
await element.fill(value);
|
|
999
|
-
}
|
|
1000
|
-
else {
|
|
1001
|
-
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
1002
|
-
await element.evaluateHandle((el, dateTimeValue) => {
|
|
1003
|
-
el.value = ""; // clear input
|
|
1004
|
-
el.value = dateTimeValue;
|
|
1005
|
-
}, dateTimeValue);
|
|
1006
|
-
}
|
|
1007
|
-
if (enter) {
|
|
1008
|
-
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1009
|
-
await this.page.keyboard.press("Enter");
|
|
1010
|
-
await this.waitForPageLoad();
|
|
1011
|
-
}
|
|
1100
|
+
await element.evaluateHandle((el, value) => {
|
|
1101
|
+
el.value = value;
|
|
1102
|
+
});
|
|
1012
1103
|
}
|
|
1013
1104
|
}
|
|
1014
|
-
catch (
|
|
1105
|
+
catch (e) {
|
|
1106
|
+
this.logger.error("setInputValue failed " + info.log);
|
|
1107
|
+
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1108
|
+
info.screenshotPath = screenshotPath;
|
|
1109
|
+
Object.assign(e, { info: info });
|
|
1015
1110
|
error = e;
|
|
1016
1111
|
throw e;
|
|
1017
1112
|
}
|
|
@@ -1019,10 +1114,10 @@ class StableBrowser {
|
|
|
1019
1114
|
const endTime = Date.now();
|
|
1020
1115
|
this._reportToWorld(world, {
|
|
1021
1116
|
element_name: selectors.element_name,
|
|
1022
|
-
type: Types.
|
|
1023
|
-
|
|
1117
|
+
type: Types.SET_INPUT,
|
|
1118
|
+
text: `Set input value`,
|
|
1024
1119
|
value: value,
|
|
1025
|
-
|
|
1120
|
+
screenshotId,
|
|
1026
1121
|
result: error
|
|
1027
1122
|
? {
|
|
1028
1123
|
status: "FAILED",
|
|
@@ -1039,7 +1134,7 @@ class StableBrowser {
|
|
|
1039
1134
|
});
|
|
1040
1135
|
}
|
|
1041
1136
|
}
|
|
1042
|
-
async setDateTime(selectors, value, enter = false, _params = null, options = {}, world = null) {
|
|
1137
|
+
async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
|
|
1043
1138
|
this._validateSelectors(selectors);
|
|
1044
1139
|
const startTime = Date.now();
|
|
1045
1140
|
let error = null;
|
|
@@ -1051,6 +1146,7 @@ class StableBrowser {
|
|
|
1051
1146
|
info.selectors = selectors;
|
|
1052
1147
|
info.value = value;
|
|
1053
1148
|
try {
|
|
1149
|
+
value = await this._replaceWithLocalData(value, this);
|
|
1054
1150
|
let element = await this._locate(selectors, info, _params);
|
|
1055
1151
|
//insert red border around the element
|
|
1056
1152
|
await this.scrollIfNeeded(element, info);
|
|
@@ -1059,28 +1155,51 @@ class StableBrowser {
|
|
|
1059
1155
|
try {
|
|
1060
1156
|
await element.click();
|
|
1061
1157
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1158
|
+
if (format) {
|
|
1159
|
+
value = dayjs(value).format(format);
|
|
1160
|
+
await element.fill(value);
|
|
1161
|
+
}
|
|
1162
|
+
else {
|
|
1163
|
+
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
1164
|
+
await element.evaluateHandle((el, dateTimeValue) => {
|
|
1165
|
+
el.value = ""; // clear input
|
|
1166
|
+
el.value = dateTimeValue;
|
|
1167
|
+
}, dateTimeValue);
|
|
1168
|
+
}
|
|
1169
|
+
if (enter) {
|
|
1170
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1171
|
+
await this.page.keyboard.press("Enter");
|
|
1172
|
+
await this.waitForPageLoad();
|
|
1173
|
+
}
|
|
1067
1174
|
}
|
|
1068
|
-
catch (
|
|
1175
|
+
catch (err) {
|
|
1069
1176
|
//await this.closeUnexpectedPopups();
|
|
1070
1177
|
this.logger.error("setting date time input failed " + JSON.stringify(info));
|
|
1071
|
-
this.logger.info("Trying again")
|
|
1178
|
+
this.logger.info("Trying again");
|
|
1179
|
+
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1072
1180
|
info.screenshotPath = screenshotPath;
|
|
1073
|
-
Object.assign(
|
|
1181
|
+
Object.assign(err, { info: info });
|
|
1074
1182
|
await element.click();
|
|
1075
1183
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1184
|
+
if (format) {
|
|
1185
|
+
value = dayjs(value).format(format);
|
|
1186
|
+
await element.fill(value);
|
|
1187
|
+
}
|
|
1188
|
+
else {
|
|
1189
|
+
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
1190
|
+
await element.evaluateHandle((el, dateTimeValue) => {
|
|
1191
|
+
el.value = ""; // clear input
|
|
1192
|
+
el.value = dateTimeValue;
|
|
1193
|
+
}, dateTimeValue);
|
|
1194
|
+
}
|
|
1195
|
+
if (enter) {
|
|
1196
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1197
|
+
await this.page.keyboard.press("Enter");
|
|
1198
|
+
await this.waitForPageLoad();
|
|
1199
|
+
}
|
|
1081
1200
|
}
|
|
1082
1201
|
}
|
|
1083
|
-
catch (
|
|
1202
|
+
catch (e) {
|
|
1084
1203
|
error = e;
|
|
1085
1204
|
throw e;
|
|
1086
1205
|
}
|
|
@@ -1243,7 +1362,7 @@ class StableBrowser {
|
|
|
1243
1362
|
let element = await this._locate(selectors, info, _params);
|
|
1244
1363
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1245
1364
|
await this._highlightElements(element);
|
|
1246
|
-
await element.fill(value
|
|
1365
|
+
await element.fill(value);
|
|
1247
1366
|
await element.dispatchEvent("change");
|
|
1248
1367
|
if (enter) {
|
|
1249
1368
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
@@ -1462,7 +1581,7 @@ class StableBrowser {
|
|
|
1462
1581
|
return info;
|
|
1463
1582
|
}
|
|
1464
1583
|
catch (e) {
|
|
1465
|
-
|
|
1584
|
+
await this.closeUnexpectedPopups();
|
|
1466
1585
|
this.logger.error("verify element contains text failed " + info.log);
|
|
1467
1586
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1468
1587
|
info.screenshotPath = screenshotPath;
|
|
@@ -1510,6 +1629,29 @@ class StableBrowser {
|
|
|
1510
1629
|
}
|
|
1511
1630
|
return dataFile;
|
|
1512
1631
|
}
|
|
1632
|
+
async waitForUserInput(message, world = null) {
|
|
1633
|
+
if (!message) {
|
|
1634
|
+
message = "# Wait for user input. Press any key to continue";
|
|
1635
|
+
}
|
|
1636
|
+
else {
|
|
1637
|
+
message = "# Wait for user input. " + message;
|
|
1638
|
+
}
|
|
1639
|
+
message += "\n";
|
|
1640
|
+
const value = await new Promise((resolve) => {
|
|
1641
|
+
const rl = readline.createInterface({
|
|
1642
|
+
input: process.stdin,
|
|
1643
|
+
output: process.stdout,
|
|
1644
|
+
});
|
|
1645
|
+
rl.question(message, (answer) => {
|
|
1646
|
+
rl.close();
|
|
1647
|
+
resolve(answer);
|
|
1648
|
+
});
|
|
1649
|
+
});
|
|
1650
|
+
if (value) {
|
|
1651
|
+
this.logger.info(`{{userInput}} was set to: ${value}`);
|
|
1652
|
+
}
|
|
1653
|
+
this.setTestData({ userInput: value }, world);
|
|
1654
|
+
}
|
|
1513
1655
|
setTestData(testData, world = null) {
|
|
1514
1656
|
if (!testData) {
|
|
1515
1657
|
return;
|
|
@@ -1537,7 +1679,7 @@ class StableBrowser {
|
|
|
1537
1679
|
const data = fs.readFileSync(filePath, "utf8");
|
|
1538
1680
|
const results = [];
|
|
1539
1681
|
return new Promise((resolve, reject) => {
|
|
1540
|
-
const readableStream = new
|
|
1682
|
+
const readableStream = new Readable();
|
|
1541
1683
|
readableStream._read = () => { }; // _read is required but you can noop it
|
|
1542
1684
|
readableStream.push(data);
|
|
1543
1685
|
readableStream.push(null);
|
|
@@ -1717,13 +1859,13 @@ class StableBrowser {
|
|
|
1717
1859
|
])));
|
|
1718
1860
|
const { data } = await client.send("Page.captureScreenshot", {
|
|
1719
1861
|
format: "png",
|
|
1720
|
-
clip: {
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
},
|
|
1862
|
+
// clip: {
|
|
1863
|
+
// x: 0,
|
|
1864
|
+
// y: 0,
|
|
1865
|
+
// width: viewportWidth,
|
|
1866
|
+
// height: viewportHeight,
|
|
1867
|
+
// scale: 1,
|
|
1868
|
+
// },
|
|
1727
1869
|
});
|
|
1728
1870
|
if (!screenshotPath) {
|
|
1729
1871
|
return data;
|
|
@@ -2549,13 +2691,13 @@ class StableBrowser {
|
|
|
2549
2691
|
}
|
|
2550
2692
|
catch (e) {
|
|
2551
2693
|
if (e.label === "networkidle") {
|
|
2552
|
-
console.log("
|
|
2694
|
+
console.log("waited for the network to be idle timeout");
|
|
2553
2695
|
}
|
|
2554
2696
|
else if (e.label === "load") {
|
|
2555
|
-
console.log("
|
|
2697
|
+
console.log("waited for the load timeout");
|
|
2556
2698
|
}
|
|
2557
2699
|
else if (e.label === "domcontentloaded") {
|
|
2558
|
-
console.log("
|
|
2700
|
+
console.log("waited for the domcontent loaded timeout");
|
|
2559
2701
|
}
|
|
2560
2702
|
console.log(".");
|
|
2561
2703
|
}
|
|
@@ -2590,13 +2732,6 @@ class StableBrowser {
|
|
|
2590
2732
|
const info = {};
|
|
2591
2733
|
try {
|
|
2592
2734
|
await this.page.close();
|
|
2593
|
-
if (this.context && this.context.pages && this.context.pages.length > 0) {
|
|
2594
|
-
this.context.pages.pop();
|
|
2595
|
-
this.page = this.context.pages[this.context.pages.length - 1];
|
|
2596
|
-
this.context.page = this.page;
|
|
2597
|
-
let title = await this.page.title();
|
|
2598
|
-
console.log("Switched to page " + title);
|
|
2599
|
-
}
|
|
2600
2735
|
}
|
|
2601
2736
|
catch (e) {
|
|
2602
2737
|
console.log(".");
|
|
@@ -2705,33 +2840,18 @@ class StableBrowser {
|
|
|
2705
2840
|
}
|
|
2706
2841
|
async scrollIfNeeded(element, info) {
|
|
2707
2842
|
try {
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
if (rect &&
|
|
2711
|
-
rect.top >= 0 &&
|
|
2712
|
-
rect.left >= 0 &&
|
|
2713
|
-
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
|
|
2714
|
-
rect.right <= (window.innerWidth || document.documentElement.clientWidth)) {
|
|
2715
|
-
return false;
|
|
2716
|
-
}
|
|
2717
|
-
else {
|
|
2718
|
-
node.scrollIntoView({
|
|
2719
|
-
behavior: "smooth",
|
|
2720
|
-
block: "center",
|
|
2721
|
-
inline: "center",
|
|
2722
|
-
});
|
|
2723
|
-
return true;
|
|
2724
|
-
}
|
|
2843
|
+
await element.scrollIntoViewIfNeeded({
|
|
2844
|
+
timeout: 2000,
|
|
2725
2845
|
});
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
}
|
|
2846
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
2847
|
+
if (info) {
|
|
2848
|
+
info.box = await element.boundingBox({
|
|
2849
|
+
timeout: 1000,
|
|
2850
|
+
});
|
|
2731
2851
|
}
|
|
2732
2852
|
}
|
|
2733
2853
|
catch (e) {
|
|
2734
|
-
console.log("
|
|
2854
|
+
console.log("#-#");
|
|
2735
2855
|
}
|
|
2736
2856
|
}
|
|
2737
2857
|
_reportToWorld(world, properties) {
|