automation_model 1.0.405-dev → 1.0.405-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 +280 -84
- 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 +8 -6
package/lib/stable_browser.js
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
import { expect } from "@playwright/test";
|
|
3
3
|
import dayjs from "dayjs";
|
|
4
4
|
import fs from "fs";
|
|
5
|
+
import { Jimp } from "jimp";
|
|
5
6
|
import path from "path";
|
|
6
7
|
import reg_parser from "regex-parser";
|
|
7
|
-
import sharp from "sharp";
|
|
8
8
|
import { findDateAlternatives, findNumberAlternatives } from "./analyze_helper.js";
|
|
9
9
|
import { getDateTimeValue } from "./date_time.js";
|
|
10
10
|
import drawRectangle from "./drawRect.js";
|
|
@@ -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,23 +81,34 @@ 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
100
|
page.on("close", async () => {
|
|
86
|
-
if (this.context && this.context.pages && this.context.pages.length >
|
|
101
|
+
if (this.context && this.context.pages && this.context.pages.length > 1) {
|
|
87
102
|
this.context.pages.pop();
|
|
88
103
|
this.page = this.context.pages[this.context.pages.length - 1];
|
|
89
104
|
this.context.page = this.page;
|
|
90
|
-
|
|
91
|
-
|
|
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
|
+
}
|
|
92
112
|
}
|
|
93
113
|
});
|
|
94
114
|
try {
|
|
@@ -101,6 +121,36 @@ class StableBrowser {
|
|
|
101
121
|
context.pageLoading.status = false;
|
|
102
122
|
}.bind(this));
|
|
103
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
|
+
}
|
|
104
154
|
getWebLogFile(logFolder) {
|
|
105
155
|
if (!fs.existsSync(logFolder)) {
|
|
106
156
|
fs.mkdirSync(logFolder, { recursive: true });
|
|
@@ -112,37 +162,63 @@ class StableBrowser {
|
|
|
112
162
|
const fileName = nextIndex + ".json";
|
|
113
163
|
return path.join(logFolder, fileName);
|
|
114
164
|
}
|
|
115
|
-
registerConsoleLogListener(page, context
|
|
165
|
+
registerConsoleLogListener(page, context) {
|
|
116
166
|
if (!this.context.webLogger) {
|
|
117
167
|
this.context.webLogger = [];
|
|
118
168
|
}
|
|
119
169
|
page.on("console", async (msg) => {
|
|
120
|
-
|
|
170
|
+
var _a;
|
|
171
|
+
const obj = {
|
|
121
172
|
type: msg.type(),
|
|
122
173
|
text: msg.text(),
|
|
123
174
|
location: msg.location(),
|
|
124
175
|
time: new Date().toISOString(),
|
|
125
|
-
}
|
|
126
|
-
|
|
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" });
|
|
127
179
|
});
|
|
128
180
|
}
|
|
129
|
-
registerRequestListener() {
|
|
130
|
-
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();
|
|
131
188
|
try {
|
|
132
|
-
const pageUrl = new URL(
|
|
189
|
+
const pageUrl = new URL(page.url());
|
|
133
190
|
const requestUrl = new URL(data.url());
|
|
134
191
|
if (pageUrl.hostname === requestUrl.hostname) {
|
|
135
192
|
const method = data.method();
|
|
136
|
-
if (
|
|
193
|
+
if (["POST", "GET", "PUT", "DELETE", "PATCH"].includes(method)) {
|
|
137
194
|
const token = await data.headerValue("Authorization");
|
|
138
195
|
if (token) {
|
|
139
|
-
|
|
196
|
+
context.authtoken = token;
|
|
140
197
|
}
|
|
141
198
|
}
|
|
142
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" });
|
|
143
212
|
}
|
|
144
213
|
catch (error) {
|
|
145
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));
|
|
146
222
|
}
|
|
147
223
|
});
|
|
148
224
|
}
|
|
@@ -212,9 +288,7 @@ class StableBrowser {
|
|
|
212
288
|
}
|
|
213
289
|
_getLocator(locator, scope, _params) {
|
|
214
290
|
locator = this._fixLocatorUsingParams(locator, _params);
|
|
215
|
-
|
|
216
|
-
return scope.locator(locator.selector);
|
|
217
|
-
}
|
|
291
|
+
let locatorReturn;
|
|
218
292
|
if (locator.role) {
|
|
219
293
|
if (locator.role[1].nameReg) {
|
|
220
294
|
locator.role[1].name = reg_parser(locator.role[1].nameReg);
|
|
@@ -223,20 +297,42 @@ class StableBrowser {
|
|
|
223
297
|
// if (locator.role[1].name) {
|
|
224
298
|
// locator.role[1].name = this._fixUsingParams(locator.role[1].name, _params);
|
|
225
299
|
// }
|
|
226
|
-
|
|
300
|
+
locatorReturn = scope.getByRole(locator.role[0], locator.role[1]);
|
|
227
301
|
}
|
|
228
302
|
if (locator.css) {
|
|
229
|
-
|
|
303
|
+
locatorReturn = scope.locator(locator.css);
|
|
230
304
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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" });
|
|
235
315
|
}
|
|
236
|
-
const locator = scope.locator(`${locator.engine}="${selector}"`);
|
|
237
|
-
return locator;
|
|
238
316
|
}
|
|
239
|
-
|
|
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;
|
|
240
336
|
}
|
|
241
337
|
async _locateElmentByTextClimbCss(scope, text, climb, css, _params) {
|
|
242
338
|
let result = await this._locateElementByText(scope, this._fixUsingParams(text, _params), "*", false, true, _params);
|
|
@@ -447,6 +543,8 @@ class StableBrowser {
|
|
|
447
543
|
if (result.foundElements.length > 0) {
|
|
448
544
|
let dialogCloseLocator = result.foundElements[0].locator;
|
|
449
545
|
await dialogCloseLocator.click();
|
|
546
|
+
// wait for the dialog to close
|
|
547
|
+
await dialogCloseLocator.waitFor({ state: "hidden" });
|
|
450
548
|
return { rerun: true };
|
|
451
549
|
}
|
|
452
550
|
}
|
|
@@ -455,7 +553,7 @@ class StableBrowser {
|
|
|
455
553
|
}
|
|
456
554
|
async _locate(selectors, info, _params, timeout = 30000) {
|
|
457
555
|
for (let i = 0; i < 3; i++) {
|
|
458
|
-
info.log += "attempt " + i + ":
|
|
556
|
+
info.log += "attempt " + i + ": total locators " + selectors.locators.length + "\n";
|
|
459
557
|
for (let j = 0; j < selectors.locators.length; j++) {
|
|
460
558
|
let selector = selectors.locators[j];
|
|
461
559
|
info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
|
|
@@ -475,9 +573,30 @@ class StableBrowser {
|
|
|
475
573
|
//let arrayMode = Array.isArray(selectors);
|
|
476
574
|
let scope = this.page;
|
|
477
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
|
+
if (frameLocator.index) {
|
|
582
|
+
framescope = framescope.nth(frameLocator.index);
|
|
583
|
+
}
|
|
584
|
+
break;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
if (frame.children) {
|
|
588
|
+
return findFrame(frame.children, framescope);
|
|
589
|
+
}
|
|
590
|
+
return framescope;
|
|
591
|
+
};
|
|
478
592
|
info.log += "searching for iframe " + selectors.iframe_src + "/" + selectors.frameLocators + "\n";
|
|
479
593
|
while (true) {
|
|
480
594
|
let frameFound = false;
|
|
595
|
+
if (selectors.nestFrmLoc) {
|
|
596
|
+
scope = findFrame(selectors.nestFrmLoc, scope);
|
|
597
|
+
frameFound = true;
|
|
598
|
+
break;
|
|
599
|
+
}
|
|
481
600
|
if (selectors.frameLocators) {
|
|
482
601
|
for (let i = 0; i < selectors.frameLocators.length; i++) {
|
|
483
602
|
let frameLocator = selectors.frameLocators[i];
|
|
@@ -641,6 +760,9 @@ class StableBrowser {
|
|
|
641
760
|
async click(selectors, _params, options = {}, world = null) {
|
|
642
761
|
this._validateSelectors(selectors);
|
|
643
762
|
const startTime = Date.now();
|
|
763
|
+
if (options && options.context) {
|
|
764
|
+
selectors.locators[0].text = options.context;
|
|
765
|
+
}
|
|
644
766
|
const info = {};
|
|
645
767
|
info.log = "***** click on " + selectors.element_name + " *****\n";
|
|
646
768
|
info.operation = "click";
|
|
@@ -654,14 +776,14 @@ class StableBrowser {
|
|
|
654
776
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
655
777
|
try {
|
|
656
778
|
await this._highlightElements(element);
|
|
657
|
-
await element.click(
|
|
779
|
+
await element.click();
|
|
658
780
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
659
781
|
}
|
|
660
782
|
catch (e) {
|
|
661
783
|
// await this.closeUnexpectedPopups();
|
|
662
784
|
info.log += "click failed, will try again" + "\n";
|
|
663
785
|
element = await this._locate(selectors, info, _params);
|
|
664
|
-
await element.click
|
|
786
|
+
await element.dispatchEvent("click");
|
|
665
787
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
666
788
|
}
|
|
667
789
|
await this.waitForPageLoad();
|
|
@@ -714,7 +836,7 @@ class StableBrowser {
|
|
|
714
836
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
715
837
|
try {
|
|
716
838
|
await this._highlightElements(element);
|
|
717
|
-
await element.setChecked(checked
|
|
839
|
+
await element.setChecked(checked);
|
|
718
840
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
719
841
|
}
|
|
720
842
|
catch (e) {
|
|
@@ -778,7 +900,7 @@ class StableBrowser {
|
|
|
778
900
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
779
901
|
try {
|
|
780
902
|
await this._highlightElements(element);
|
|
781
|
-
await element.hover(
|
|
903
|
+
await element.hover();
|
|
782
904
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
783
905
|
}
|
|
784
906
|
catch (e) {
|
|
@@ -840,7 +962,7 @@ class StableBrowser {
|
|
|
840
962
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
841
963
|
try {
|
|
842
964
|
await this._highlightElements(element);
|
|
843
|
-
await element.selectOption(values
|
|
965
|
+
await element.selectOption(values);
|
|
844
966
|
}
|
|
845
967
|
catch (e) {
|
|
846
968
|
//await this.closeUnexpectedPopups();
|
|
@@ -949,6 +1071,72 @@ class StableBrowser {
|
|
|
949
1071
|
});
|
|
950
1072
|
}
|
|
951
1073
|
}
|
|
1074
|
+
async setInputValue(selectors, value, _params = null, options = {}, world = null) {
|
|
1075
|
+
// set input value for non fillable inputs like date, time, range, color, etc.
|
|
1076
|
+
this._validateSelectors(selectors);
|
|
1077
|
+
const startTime = Date.now();
|
|
1078
|
+
const info = {};
|
|
1079
|
+
info.log = "***** set input value " + selectors.element_name + " *****\n";
|
|
1080
|
+
info.operation = "setInputValue";
|
|
1081
|
+
info.selectors = selectors;
|
|
1082
|
+
value = this._fixUsingParams(value, _params);
|
|
1083
|
+
info.value = value;
|
|
1084
|
+
let error = null;
|
|
1085
|
+
let screenshotId = null;
|
|
1086
|
+
let screenshotPath = null;
|
|
1087
|
+
try {
|
|
1088
|
+
value = await this._replaceWithLocalData(value, this);
|
|
1089
|
+
let element = await this._locate(selectors, info, _params);
|
|
1090
|
+
await this.scrollIfNeeded(element, info);
|
|
1091
|
+
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1092
|
+
await this._highlightElements(element);
|
|
1093
|
+
try {
|
|
1094
|
+
await element.evaluateHandle((el, value) => {
|
|
1095
|
+
el.value = value;
|
|
1096
|
+
}, value);
|
|
1097
|
+
}
|
|
1098
|
+
catch (error) {
|
|
1099
|
+
this.logger.error("setInputValue failed, will try again");
|
|
1100
|
+
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1101
|
+
info.screenshotPath = screenshotPath;
|
|
1102
|
+
Object.assign(error, { info: info });
|
|
1103
|
+
await element.evaluateHandle((el, value) => {
|
|
1104
|
+
el.value = value;
|
|
1105
|
+
});
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
catch (e) {
|
|
1109
|
+
this.logger.error("setInputValue failed " + info.log);
|
|
1110
|
+
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1111
|
+
info.screenshotPath = screenshotPath;
|
|
1112
|
+
Object.assign(e, { info: info });
|
|
1113
|
+
error = e;
|
|
1114
|
+
throw e;
|
|
1115
|
+
}
|
|
1116
|
+
finally {
|
|
1117
|
+
const endTime = Date.now();
|
|
1118
|
+
this._reportToWorld(world, {
|
|
1119
|
+
element_name: selectors.element_name,
|
|
1120
|
+
type: Types.SET_INPUT,
|
|
1121
|
+
text: `Set input value`,
|
|
1122
|
+
value: value,
|
|
1123
|
+
screenshotId,
|
|
1124
|
+
result: error
|
|
1125
|
+
? {
|
|
1126
|
+
status: "FAILED",
|
|
1127
|
+
startTime,
|
|
1128
|
+
endTime,
|
|
1129
|
+
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1130
|
+
}
|
|
1131
|
+
: {
|
|
1132
|
+
status: "PASSED",
|
|
1133
|
+
startTime,
|
|
1134
|
+
endTime,
|
|
1135
|
+
},
|
|
1136
|
+
info: info,
|
|
1137
|
+
});
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
952
1140
|
async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
|
|
953
1141
|
this._validateSelectors(selectors);
|
|
954
1142
|
const startTime = Date.now();
|
|
@@ -990,7 +1178,8 @@ class StableBrowser {
|
|
|
990
1178
|
catch (err) {
|
|
991
1179
|
//await this.closeUnexpectedPopups();
|
|
992
1180
|
this.logger.error("setting date time input failed " + JSON.stringify(info));
|
|
993
|
-
this.logger.info("Trying again")
|
|
1181
|
+
this.logger.info("Trying again");
|
|
1182
|
+
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
994
1183
|
info.screenshotPath = screenshotPath;
|
|
995
1184
|
Object.assign(err, { info: info });
|
|
996
1185
|
await element.click();
|
|
@@ -1176,7 +1365,7 @@ class StableBrowser {
|
|
|
1176
1365
|
let element = await this._locate(selectors, info, _params);
|
|
1177
1366
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1178
1367
|
await this._highlightElements(element);
|
|
1179
|
-
await element.fill(value
|
|
1368
|
+
await element.fill(value);
|
|
1180
1369
|
await element.dispatchEvent("change");
|
|
1181
1370
|
if (enter) {
|
|
1182
1371
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
@@ -1395,7 +1584,7 @@ class StableBrowser {
|
|
|
1395
1584
|
return info;
|
|
1396
1585
|
}
|
|
1397
1586
|
catch (e) {
|
|
1398
|
-
|
|
1587
|
+
await this.closeUnexpectedPopups();
|
|
1399
1588
|
this.logger.error("verify element contains text failed " + info.log);
|
|
1400
1589
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1401
1590
|
info.screenshotPath = screenshotPath;
|
|
@@ -1443,6 +1632,29 @@ class StableBrowser {
|
|
|
1443
1632
|
}
|
|
1444
1633
|
return dataFile;
|
|
1445
1634
|
}
|
|
1635
|
+
async waitForUserInput(message, world = null) {
|
|
1636
|
+
if (!message) {
|
|
1637
|
+
message = "# Wait for user input. Press any key to continue";
|
|
1638
|
+
}
|
|
1639
|
+
else {
|
|
1640
|
+
message = "# Wait for user input. " + message;
|
|
1641
|
+
}
|
|
1642
|
+
message += "\n";
|
|
1643
|
+
const value = await new Promise((resolve) => {
|
|
1644
|
+
const rl = readline.createInterface({
|
|
1645
|
+
input: process.stdin,
|
|
1646
|
+
output: process.stdout,
|
|
1647
|
+
});
|
|
1648
|
+
rl.question(message, (answer) => {
|
|
1649
|
+
rl.close();
|
|
1650
|
+
resolve(answer);
|
|
1651
|
+
});
|
|
1652
|
+
});
|
|
1653
|
+
if (value) {
|
|
1654
|
+
this.logger.info(`{{userInput}} was set to: ${value}`);
|
|
1655
|
+
}
|
|
1656
|
+
this.setTestData({ userInput: value }, world);
|
|
1657
|
+
}
|
|
1446
1658
|
setTestData(testData, world = null) {
|
|
1447
1659
|
if (!testData) {
|
|
1448
1660
|
return;
|
|
@@ -1470,7 +1682,7 @@ class StableBrowser {
|
|
|
1470
1682
|
const data = fs.readFileSync(filePath, "utf8");
|
|
1471
1683
|
const results = [];
|
|
1472
1684
|
return new Promise((resolve, reject) => {
|
|
1473
|
-
const readableStream = new
|
|
1685
|
+
const readableStream = new Readable();
|
|
1474
1686
|
readableStream._read = () => { }; // _read is required but you can noop it
|
|
1475
1687
|
readableStream.push(data);
|
|
1476
1688
|
readableStream.push(null);
|
|
@@ -1650,30 +1862,29 @@ class StableBrowser {
|
|
|
1650
1862
|
])));
|
|
1651
1863
|
const { data } = await client.send("Page.captureScreenshot", {
|
|
1652
1864
|
format: "png",
|
|
1653
|
-
clip: {
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
},
|
|
1865
|
+
// clip: {
|
|
1866
|
+
// x: 0,
|
|
1867
|
+
// y: 0,
|
|
1868
|
+
// width: viewportWidth,
|
|
1869
|
+
// height: viewportHeight,
|
|
1870
|
+
// scale: 1,
|
|
1871
|
+
// },
|
|
1660
1872
|
});
|
|
1661
1873
|
if (!screenshotPath) {
|
|
1662
1874
|
return data;
|
|
1663
1875
|
}
|
|
1664
1876
|
let screenshotBuffer = Buffer.from(data, "base64");
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
}
|
|
1676
|
-
fs.writeFileSync(screenshotPath, screenshotBuffer);
|
|
1877
|
+
let image = await Jimp.read(screenshotBuffer);
|
|
1878
|
+
// Get the image dimensions
|
|
1879
|
+
const { width, height } = image.bitmap;
|
|
1880
|
+
// Resize the image to fit within the viewport dimensions without enlarging
|
|
1881
|
+
if (width > viewportWidth || height > viewportHeight) {
|
|
1882
|
+
image = image.resize({ w: viewportWidth, h: viewportHeight }); // Resize the image while maintaining aspect ratio
|
|
1883
|
+
await image.write(screenshotPath);
|
|
1884
|
+
}
|
|
1885
|
+
else {
|
|
1886
|
+
fs.writeFileSync(screenshotPath, screenshotBuffer);
|
|
1887
|
+
}
|
|
1677
1888
|
await client.detach();
|
|
1678
1889
|
}
|
|
1679
1890
|
async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
|
|
@@ -2482,13 +2693,13 @@ class StableBrowser {
|
|
|
2482
2693
|
}
|
|
2483
2694
|
catch (e) {
|
|
2484
2695
|
if (e.label === "networkidle") {
|
|
2485
|
-
console.log("
|
|
2696
|
+
console.log("waited for the network to be idle timeout");
|
|
2486
2697
|
}
|
|
2487
2698
|
else if (e.label === "load") {
|
|
2488
|
-
console.log("
|
|
2699
|
+
console.log("waited for the load timeout");
|
|
2489
2700
|
}
|
|
2490
2701
|
else if (e.label === "domcontentloaded") {
|
|
2491
|
-
console.log("
|
|
2702
|
+
console.log("waited for the domcontent loaded timeout");
|
|
2492
2703
|
}
|
|
2493
2704
|
console.log(".");
|
|
2494
2705
|
}
|
|
@@ -2631,33 +2842,18 @@ class StableBrowser {
|
|
|
2631
2842
|
}
|
|
2632
2843
|
async scrollIfNeeded(element, info) {
|
|
2633
2844
|
try {
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
if (rect &&
|
|
2637
|
-
rect.top >= 0 &&
|
|
2638
|
-
rect.left >= 0 &&
|
|
2639
|
-
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
|
|
2640
|
-
rect.right <= (window.innerWidth || document.documentElement.clientWidth)) {
|
|
2641
|
-
return false;
|
|
2642
|
-
}
|
|
2643
|
-
else {
|
|
2644
|
-
node.scrollIntoView({
|
|
2645
|
-
behavior: "smooth",
|
|
2646
|
-
block: "center",
|
|
2647
|
-
inline: "center",
|
|
2648
|
-
});
|
|
2649
|
-
return true;
|
|
2650
|
-
}
|
|
2845
|
+
await element.scrollIntoViewIfNeeded({
|
|
2846
|
+
timeout: 2000,
|
|
2651
2847
|
});
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
}
|
|
2848
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
2849
|
+
if (info) {
|
|
2850
|
+
info.box = await element.boundingBox({
|
|
2851
|
+
timeout: 1000,
|
|
2852
|
+
});
|
|
2657
2853
|
}
|
|
2658
2854
|
}
|
|
2659
2855
|
catch (e) {
|
|
2660
|
-
console.log("
|
|
2856
|
+
console.log("#-#");
|
|
2661
2857
|
}
|
|
2662
2858
|
}
|
|
2663
2859
|
_reportToWorld(world, properties) {
|