automation_model 1.0.406-dev → 1.0.406-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 +6 -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 +37 -3
- package/lib/init_browser.js.map +1 -1
- package/lib/stable_browser.d.ts +13 -4
- package/lib/stable_browser.js +277 -83
- 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";
|
|
@@ -14,6 +14,8 @@ import objectPath from "object-path";
|
|
|
14
14
|
import { decrypt } from "./utils.js";
|
|
15
15
|
import csv from "csv-parser";
|
|
16
16
|
import { Readable } from "node:stream";
|
|
17
|
+
import readline from "readline";
|
|
18
|
+
import { getContext } from "./init_browser.js";
|
|
17
19
|
const Types = {
|
|
18
20
|
CLICK: "click_element",
|
|
19
21
|
NAVIGATE: "navigate",
|
|
@@ -39,16 +41,21 @@ const Types = {
|
|
|
39
41
|
SET_VIEWPORT: "set_viewport",
|
|
40
42
|
VERIFY_VISUAL: "verify_visual",
|
|
41
43
|
LOAD_DATA: "load_data",
|
|
44
|
+
SET_INPUT: "set_input",
|
|
42
45
|
};
|
|
46
|
+
export const apps = {};
|
|
43
47
|
class StableBrowser {
|
|
44
|
-
constructor(browser, page, logger = null, context = null) {
|
|
48
|
+
constructor(browser, page, logger = null, context = null, world = null) {
|
|
45
49
|
this.browser = browser;
|
|
46
50
|
this.page = page;
|
|
47
51
|
this.logger = logger;
|
|
48
52
|
this.context = context;
|
|
53
|
+
this.world = world;
|
|
49
54
|
this.project_path = null;
|
|
50
55
|
this.webLogFile = null;
|
|
56
|
+
this.networkLogger = null;
|
|
51
57
|
this.configuration = null;
|
|
58
|
+
this.appName = "main";
|
|
52
59
|
if (!this.logger) {
|
|
53
60
|
this.logger = console;
|
|
54
61
|
}
|
|
@@ -74,23 +81,34 @@ class StableBrowser {
|
|
|
74
81
|
this.logger.error("unable to read ai_config.json");
|
|
75
82
|
}
|
|
76
83
|
const logFolder = path.join(this.project_path, "logs", "web");
|
|
77
|
-
this.
|
|
78
|
-
this.registerConsoleLogListener(page, context, this.webLogFile);
|
|
79
|
-
this.registerRequestListener();
|
|
84
|
+
this.world = world;
|
|
80
85
|
context.pages = [this.page];
|
|
81
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
|
+
}
|
|
82
95
|
context.playContext.on("page", async function (page) {
|
|
83
96
|
context.pageLoading.status = true;
|
|
84
97
|
this.page = page;
|
|
85
98
|
context.page = page;
|
|
86
99
|
context.pages.push(page);
|
|
87
100
|
page.on("close", async () => {
|
|
88
|
-
if (this.context && this.context.pages && this.context.pages.length >
|
|
101
|
+
if (this.context && this.context.pages && this.context.pages.length > 1) {
|
|
89
102
|
this.context.pages.pop();
|
|
90
103
|
this.page = this.context.pages[this.context.pages.length - 1];
|
|
91
104
|
this.context.page = this.page;
|
|
92
|
-
|
|
93
|
-
|
|
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
|
+
}
|
|
94
112
|
}
|
|
95
113
|
});
|
|
96
114
|
try {
|
|
@@ -103,6 +121,36 @@ class StableBrowser {
|
|
|
103
121
|
context.pageLoading.status = false;
|
|
104
122
|
}.bind(this));
|
|
105
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
|
+
}
|
|
106
154
|
getWebLogFile(logFolder) {
|
|
107
155
|
if (!fs.existsSync(logFolder)) {
|
|
108
156
|
fs.mkdirSync(logFolder, { recursive: true });
|
|
@@ -114,37 +162,63 @@ class StableBrowser {
|
|
|
114
162
|
const fileName = nextIndex + ".json";
|
|
115
163
|
return path.join(logFolder, fileName);
|
|
116
164
|
}
|
|
117
|
-
registerConsoleLogListener(page, context
|
|
165
|
+
registerConsoleLogListener(page, context) {
|
|
118
166
|
if (!this.context.webLogger) {
|
|
119
167
|
this.context.webLogger = [];
|
|
120
168
|
}
|
|
121
169
|
page.on("console", async (msg) => {
|
|
122
|
-
|
|
170
|
+
var _a;
|
|
171
|
+
const obj = {
|
|
123
172
|
type: msg.type(),
|
|
124
173
|
text: msg.text(),
|
|
125
174
|
location: msg.location(),
|
|
126
175
|
time: new Date().toISOString(),
|
|
127
|
-
}
|
|
128
|
-
|
|
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" });
|
|
129
179
|
});
|
|
130
180
|
}
|
|
131
|
-
registerRequestListener() {
|
|
132
|
-
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();
|
|
133
188
|
try {
|
|
134
|
-
const pageUrl = new URL(
|
|
189
|
+
const pageUrl = new URL(page.url());
|
|
135
190
|
const requestUrl = new URL(data.url());
|
|
136
191
|
if (pageUrl.hostname === requestUrl.hostname) {
|
|
137
192
|
const method = data.method();
|
|
138
|
-
if (
|
|
193
|
+
if (["POST", "GET", "PUT", "DELETE", "PATCH"].includes(method)) {
|
|
139
194
|
const token = await data.headerValue("Authorization");
|
|
140
195
|
if (token) {
|
|
141
|
-
|
|
196
|
+
context.authtoken = token;
|
|
142
197
|
}
|
|
143
198
|
}
|
|
144
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" });
|
|
145
212
|
}
|
|
146
213
|
catch (error) {
|
|
147
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));
|
|
148
222
|
}
|
|
149
223
|
});
|
|
150
224
|
}
|
|
@@ -214,9 +288,7 @@ class StableBrowser {
|
|
|
214
288
|
}
|
|
215
289
|
_getLocator(locator, scope, _params) {
|
|
216
290
|
locator = this._fixLocatorUsingParams(locator, _params);
|
|
217
|
-
|
|
218
|
-
return scope.locator(locator.selector);
|
|
219
|
-
}
|
|
291
|
+
let locatorReturn;
|
|
220
292
|
if (locator.role) {
|
|
221
293
|
if (locator.role[1].nameReg) {
|
|
222
294
|
locator.role[1].name = reg_parser(locator.role[1].nameReg);
|
|
@@ -225,20 +297,42 @@ class StableBrowser {
|
|
|
225
297
|
// if (locator.role[1].name) {
|
|
226
298
|
// locator.role[1].name = this._fixUsingParams(locator.role[1].name, _params);
|
|
227
299
|
// }
|
|
228
|
-
|
|
300
|
+
locatorReturn = scope.getByRole(locator.role[0], locator.role[1]);
|
|
229
301
|
}
|
|
230
302
|
if (locator.css) {
|
|
231
|
-
|
|
303
|
+
locatorReturn = scope.locator(locator.css);
|
|
232
304
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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" });
|
|
237
315
|
}
|
|
238
|
-
const locator = scope.locator(`${locator.engine}="${selector}"`);
|
|
239
|
-
return locator;
|
|
240
316
|
}
|
|
241
|
-
|
|
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;
|
|
242
336
|
}
|
|
243
337
|
async _locateElmentByTextClimbCss(scope, text, climb, css, _params) {
|
|
244
338
|
let result = await this._locateElementByText(scope, this._fixUsingParams(text, _params), "*", false, true, _params);
|
|
@@ -449,6 +543,8 @@ class StableBrowser {
|
|
|
449
543
|
if (result.foundElements.length > 0) {
|
|
450
544
|
let dialogCloseLocator = result.foundElements[0].locator;
|
|
451
545
|
await dialogCloseLocator.click();
|
|
546
|
+
// wait for the dialog to close
|
|
547
|
+
await dialogCloseLocator.waitFor({ state: "hidden" });
|
|
452
548
|
return { rerun: true };
|
|
453
549
|
}
|
|
454
550
|
}
|
|
@@ -457,7 +553,7 @@ class StableBrowser {
|
|
|
457
553
|
}
|
|
458
554
|
async _locate(selectors, info, _params, timeout = 30000) {
|
|
459
555
|
for (let i = 0; i < 3; i++) {
|
|
460
|
-
info.log += "attempt " + i + ":
|
|
556
|
+
info.log += "attempt " + i + ": total locators " + selectors.locators.length + "\n";
|
|
461
557
|
for (let j = 0; j < selectors.locators.length; j++) {
|
|
462
558
|
let selector = selectors.locators[j];
|
|
463
559
|
info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
|
|
@@ -477,9 +573,30 @@ class StableBrowser {
|
|
|
477
573
|
//let arrayMode = Array.isArray(selectors);
|
|
478
574
|
let scope = this.page;
|
|
479
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
|
+
};
|
|
480
592
|
info.log += "searching for iframe " + selectors.iframe_src + "/" + selectors.frameLocators + "\n";
|
|
481
593
|
while (true) {
|
|
482
594
|
let frameFound = false;
|
|
595
|
+
if (selectors.nestFrmLoc) {
|
|
596
|
+
scope = findFrame(selectors.nestFrmLoc, scope);
|
|
597
|
+
frameFound = true;
|
|
598
|
+
break;
|
|
599
|
+
}
|
|
483
600
|
if (selectors.frameLocators) {
|
|
484
601
|
for (let i = 0; i < selectors.frameLocators.length; i++) {
|
|
485
602
|
let frameLocator = selectors.frameLocators[i];
|
|
@@ -643,6 +760,9 @@ class StableBrowser {
|
|
|
643
760
|
async click(selectors, _params, options = {}, world = null) {
|
|
644
761
|
this._validateSelectors(selectors);
|
|
645
762
|
const startTime = Date.now();
|
|
763
|
+
if (options && options.context) {
|
|
764
|
+
selectors.locators[0].text = options.context;
|
|
765
|
+
}
|
|
646
766
|
const info = {};
|
|
647
767
|
info.log = "***** click on " + selectors.element_name + " *****\n";
|
|
648
768
|
info.operation = "click";
|
|
@@ -656,14 +776,14 @@ class StableBrowser {
|
|
|
656
776
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
657
777
|
try {
|
|
658
778
|
await this._highlightElements(element);
|
|
659
|
-
await element.click(
|
|
779
|
+
await element.click();
|
|
660
780
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
661
781
|
}
|
|
662
782
|
catch (e) {
|
|
663
783
|
// await this.closeUnexpectedPopups();
|
|
664
784
|
info.log += "click failed, will try again" + "\n";
|
|
665
785
|
element = await this._locate(selectors, info, _params);
|
|
666
|
-
await element.click
|
|
786
|
+
await element.dispatchEvent("click");
|
|
667
787
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
668
788
|
}
|
|
669
789
|
await this.waitForPageLoad();
|
|
@@ -716,7 +836,7 @@ class StableBrowser {
|
|
|
716
836
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
717
837
|
try {
|
|
718
838
|
await this._highlightElements(element);
|
|
719
|
-
await element.setChecked(checked
|
|
839
|
+
await element.setChecked(checked);
|
|
720
840
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
721
841
|
}
|
|
722
842
|
catch (e) {
|
|
@@ -780,7 +900,7 @@ class StableBrowser {
|
|
|
780
900
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
781
901
|
try {
|
|
782
902
|
await this._highlightElements(element);
|
|
783
|
-
await element.hover(
|
|
903
|
+
await element.hover();
|
|
784
904
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
785
905
|
}
|
|
786
906
|
catch (e) {
|
|
@@ -842,7 +962,7 @@ class StableBrowser {
|
|
|
842
962
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
843
963
|
try {
|
|
844
964
|
await this._highlightElements(element);
|
|
845
|
-
await element.selectOption(values
|
|
965
|
+
await element.selectOption(values);
|
|
846
966
|
}
|
|
847
967
|
catch (e) {
|
|
848
968
|
//await this.closeUnexpectedPopups();
|
|
@@ -951,6 +1071,72 @@ class StableBrowser {
|
|
|
951
1071
|
});
|
|
952
1072
|
}
|
|
953
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
|
+
}
|
|
954
1140
|
async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
|
|
955
1141
|
this._validateSelectors(selectors);
|
|
956
1142
|
const startTime = Date.now();
|
|
@@ -992,7 +1178,8 @@ class StableBrowser {
|
|
|
992
1178
|
catch (err) {
|
|
993
1179
|
//await this.closeUnexpectedPopups();
|
|
994
1180
|
this.logger.error("setting date time input failed " + JSON.stringify(info));
|
|
995
|
-
this.logger.info("Trying again")
|
|
1181
|
+
this.logger.info("Trying again");
|
|
1182
|
+
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
996
1183
|
info.screenshotPath = screenshotPath;
|
|
997
1184
|
Object.assign(err, { info: info });
|
|
998
1185
|
await element.click();
|
|
@@ -1178,7 +1365,7 @@ class StableBrowser {
|
|
|
1178
1365
|
let element = await this._locate(selectors, info, _params);
|
|
1179
1366
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1180
1367
|
await this._highlightElements(element);
|
|
1181
|
-
await element.fill(value
|
|
1368
|
+
await element.fill(value);
|
|
1182
1369
|
await element.dispatchEvent("change");
|
|
1183
1370
|
if (enter) {
|
|
1184
1371
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
@@ -1397,7 +1584,7 @@ class StableBrowser {
|
|
|
1397
1584
|
return info;
|
|
1398
1585
|
}
|
|
1399
1586
|
catch (e) {
|
|
1400
|
-
|
|
1587
|
+
await this.closeUnexpectedPopups();
|
|
1401
1588
|
this.logger.error("verify element contains text failed " + info.log);
|
|
1402
1589
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1403
1590
|
info.screenshotPath = screenshotPath;
|
|
@@ -1445,6 +1632,29 @@ class StableBrowser {
|
|
|
1445
1632
|
}
|
|
1446
1633
|
return dataFile;
|
|
1447
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
|
+
}
|
|
1448
1658
|
setTestData(testData, world = null) {
|
|
1449
1659
|
if (!testData) {
|
|
1450
1660
|
return;
|
|
@@ -1652,30 +1862,29 @@ class StableBrowser {
|
|
|
1652
1862
|
])));
|
|
1653
1863
|
const { data } = await client.send("Page.captureScreenshot", {
|
|
1654
1864
|
format: "png",
|
|
1655
|
-
clip: {
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
},
|
|
1865
|
+
// clip: {
|
|
1866
|
+
// x: 0,
|
|
1867
|
+
// y: 0,
|
|
1868
|
+
// width: viewportWidth,
|
|
1869
|
+
// height: viewportHeight,
|
|
1870
|
+
// scale: 1,
|
|
1871
|
+
// },
|
|
1662
1872
|
});
|
|
1663
1873
|
if (!screenshotPath) {
|
|
1664
1874
|
return data;
|
|
1665
1875
|
}
|
|
1666
1876
|
let screenshotBuffer = Buffer.from(data, "base64");
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
}
|
|
1678
|
-
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
|
+
}
|
|
1679
1888
|
await client.detach();
|
|
1680
1889
|
}
|
|
1681
1890
|
async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
|
|
@@ -2484,13 +2693,13 @@ class StableBrowser {
|
|
|
2484
2693
|
}
|
|
2485
2694
|
catch (e) {
|
|
2486
2695
|
if (e.label === "networkidle") {
|
|
2487
|
-
console.log("
|
|
2696
|
+
console.log("waited for the network to be idle timeout");
|
|
2488
2697
|
}
|
|
2489
2698
|
else if (e.label === "load") {
|
|
2490
|
-
console.log("
|
|
2699
|
+
console.log("waited for the load timeout");
|
|
2491
2700
|
}
|
|
2492
2701
|
else if (e.label === "domcontentloaded") {
|
|
2493
|
-
console.log("
|
|
2702
|
+
console.log("waited for the domcontent loaded timeout");
|
|
2494
2703
|
}
|
|
2495
2704
|
console.log(".");
|
|
2496
2705
|
}
|
|
@@ -2633,33 +2842,18 @@ class StableBrowser {
|
|
|
2633
2842
|
}
|
|
2634
2843
|
async scrollIfNeeded(element, info) {
|
|
2635
2844
|
try {
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
if (rect &&
|
|
2639
|
-
rect.top >= 0 &&
|
|
2640
|
-
rect.left >= 0 &&
|
|
2641
|
-
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
|
|
2642
|
-
rect.right <= (window.innerWidth || document.documentElement.clientWidth)) {
|
|
2643
|
-
return false;
|
|
2644
|
-
}
|
|
2645
|
-
else {
|
|
2646
|
-
node.scrollIntoView({
|
|
2647
|
-
behavior: "smooth",
|
|
2648
|
-
block: "center",
|
|
2649
|
-
inline: "center",
|
|
2650
|
-
});
|
|
2651
|
-
return true;
|
|
2652
|
-
}
|
|
2845
|
+
await element.scrollIntoViewIfNeeded({
|
|
2846
|
+
timeout: 2000,
|
|
2653
2847
|
});
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
}
|
|
2848
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
2849
|
+
if (info) {
|
|
2850
|
+
info.box = await element.boundingBox({
|
|
2851
|
+
timeout: 1000,
|
|
2852
|
+
});
|
|
2659
2853
|
}
|
|
2660
2854
|
}
|
|
2661
2855
|
catch (e) {
|
|
2662
|
-
console.log("
|
|
2856
|
+
console.log("#-#");
|
|
2663
2857
|
}
|
|
2664
2858
|
}
|
|
2665
2859
|
_reportToWorld(world, properties) {
|