automation_model 1.0.414-dev → 1.0.414-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 +38 -3
- package/lib/init_browser.js.map +1 -1
- package/lib/stable_browser.d.ts +12 -4
- package/lib/stable_browser.js +234 -112
- package/lib/stable_browser.js.map +1 -1
- package/lib/test_context.d.ts +3 -0
- package/lib/test_context.js +3 -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",
|
|
@@ -41,15 +43,19 @@ const Types = {
|
|
|
41
43
|
LOAD_DATA: "load_data",
|
|
42
44
|
SET_INPUT: "set_input",
|
|
43
45
|
};
|
|
46
|
+
export const apps = {};
|
|
44
47
|
class StableBrowser {
|
|
45
|
-
constructor(browser, page, logger = null, context = null) {
|
|
48
|
+
constructor(browser, page, logger = null, context = null, world = null) {
|
|
46
49
|
this.browser = browser;
|
|
47
50
|
this.page = page;
|
|
48
51
|
this.logger = logger;
|
|
49
52
|
this.context = context;
|
|
53
|
+
this.world = world;
|
|
50
54
|
this.project_path = null;
|
|
51
55
|
this.webLogFile = null;
|
|
56
|
+
this.networkLogger = null;
|
|
52
57
|
this.configuration = null;
|
|
58
|
+
this.appName = "main";
|
|
53
59
|
if (!this.logger) {
|
|
54
60
|
this.logger = console;
|
|
55
61
|
}
|
|
@@ -75,23 +81,34 @@ class StableBrowser {
|
|
|
75
81
|
this.logger.error("unable to read ai_config.json");
|
|
76
82
|
}
|
|
77
83
|
const logFolder = path.join(this.project_path, "logs", "web");
|
|
78
|
-
this.
|
|
79
|
-
this.registerConsoleLogListener(page, context, this.webLogFile);
|
|
80
|
-
this.registerRequestListener();
|
|
84
|
+
this.world = world;
|
|
81
85
|
context.pages = [this.page];
|
|
82
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
|
+
}
|
|
83
95
|
context.playContext.on("page", async function (page) {
|
|
84
96
|
context.pageLoading.status = true;
|
|
85
97
|
this.page = page;
|
|
86
98
|
context.page = page;
|
|
87
99
|
context.pages.push(page);
|
|
88
100
|
page.on("close", async () => {
|
|
89
|
-
if (this.context && this.context.pages && this.context.pages.length >
|
|
101
|
+
if (this.context && this.context.pages && this.context.pages.length > 1) {
|
|
90
102
|
this.context.pages.pop();
|
|
91
103
|
this.page = this.context.pages[this.context.pages.length - 1];
|
|
92
104
|
this.context.page = this.page;
|
|
93
|
-
|
|
94
|
-
|
|
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
|
+
}
|
|
95
112
|
}
|
|
96
113
|
});
|
|
97
114
|
try {
|
|
@@ -104,6 +121,36 @@ class StableBrowser {
|
|
|
104
121
|
context.pageLoading.status = false;
|
|
105
122
|
}.bind(this));
|
|
106
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
|
+
}
|
|
107
154
|
getWebLogFile(logFolder) {
|
|
108
155
|
if (!fs.existsSync(logFolder)) {
|
|
109
156
|
fs.mkdirSync(logFolder, { recursive: true });
|
|
@@ -115,37 +162,63 @@ class StableBrowser {
|
|
|
115
162
|
const fileName = nextIndex + ".json";
|
|
116
163
|
return path.join(logFolder, fileName);
|
|
117
164
|
}
|
|
118
|
-
registerConsoleLogListener(page, context
|
|
165
|
+
registerConsoleLogListener(page, context) {
|
|
119
166
|
if (!this.context.webLogger) {
|
|
120
167
|
this.context.webLogger = [];
|
|
121
168
|
}
|
|
122
169
|
page.on("console", async (msg) => {
|
|
123
|
-
|
|
170
|
+
var _a;
|
|
171
|
+
const obj = {
|
|
124
172
|
type: msg.type(),
|
|
125
173
|
text: msg.text(),
|
|
126
174
|
location: msg.location(),
|
|
127
175
|
time: new Date().toISOString(),
|
|
128
|
-
}
|
|
129
|
-
|
|
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" });
|
|
130
179
|
});
|
|
131
180
|
}
|
|
132
|
-
registerRequestListener() {
|
|
133
|
-
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();
|
|
134
188
|
try {
|
|
135
|
-
const pageUrl = new URL(
|
|
189
|
+
const pageUrl = new URL(page.url());
|
|
136
190
|
const requestUrl = new URL(data.url());
|
|
137
191
|
if (pageUrl.hostname === requestUrl.hostname) {
|
|
138
192
|
const method = data.method();
|
|
139
|
-
if (
|
|
193
|
+
if (["POST", "GET", "PUT", "DELETE", "PATCH"].includes(method)) {
|
|
140
194
|
const token = await data.headerValue("Authorization");
|
|
141
195
|
if (token) {
|
|
142
|
-
|
|
196
|
+
context.authtoken = token;
|
|
143
197
|
}
|
|
144
198
|
}
|
|
145
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" });
|
|
146
212
|
}
|
|
147
213
|
catch (error) {
|
|
148
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));
|
|
149
222
|
}
|
|
150
223
|
});
|
|
151
224
|
}
|
|
@@ -226,7 +299,7 @@ class StableBrowser {
|
|
|
226
299
|
// }
|
|
227
300
|
locatorReturn = scope.getByRole(locator.role[0], locator.role[1]);
|
|
228
301
|
}
|
|
229
|
-
if (locator.css
|
|
302
|
+
if (locator.css) {
|
|
230
303
|
locatorReturn = scope.locator(locator.css);
|
|
231
304
|
}
|
|
232
305
|
// handle role/name locators
|
|
@@ -241,34 +314,30 @@ class StableBrowser {
|
|
|
241
314
|
locatorReturn = scope.getByRole(role, { name }, { exact: flags === "i" });
|
|
242
315
|
}
|
|
243
316
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
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}`);
|
|
252
329
|
}
|
|
253
|
-
}
|
|
254
|
-
if (locator.engine === "internal:attr") {
|
|
255
|
-
selector = `[${selector}]`;
|
|
256
|
-
locatorReturn = scope.locator(`${locator.engine}=${selector}`);
|
|
257
330
|
}
|
|
258
331
|
if (!locatorReturn) {
|
|
259
332
|
console.error(locator);
|
|
260
|
-
throw new Error("Locator "
|
|
261
|
-
// } else {
|
|
262
|
-
// const count = locatorReturn.count();
|
|
263
|
-
// if (count === 0) {
|
|
264
|
-
// throw new Error("Elements not found");
|
|
265
|
-
// } else if (count > 1) {
|
|
266
|
-
// throw new Error("Multiple elements found");
|
|
267
|
-
// }
|
|
333
|
+
throw new Error("Locator undefined");
|
|
268
334
|
}
|
|
269
335
|
return locatorReturn;
|
|
270
336
|
}
|
|
271
337
|
async _locateElmentByTextClimbCss(scope, text, climb, css, _params) {
|
|
338
|
+
if (css && css.locator) {
|
|
339
|
+
css = css.locator;
|
|
340
|
+
}
|
|
272
341
|
let result = await this._locateElementByText(scope, this._fixUsingParams(text, _params), "*", false, true, _params);
|
|
273
342
|
if (result.elementCount === 0) {
|
|
274
343
|
return;
|
|
@@ -296,6 +365,15 @@ class StableBrowser {
|
|
|
296
365
|
return false;
|
|
297
366
|
}
|
|
298
367
|
document.isParent = isParent;
|
|
368
|
+
function getRegex(str) {
|
|
369
|
+
const match = str.match(/^\/(.*?)\/([gimuy]*)$/);
|
|
370
|
+
if (!match) {
|
|
371
|
+
return null;
|
|
372
|
+
}
|
|
373
|
+
let [_, pattern, flags] = match;
|
|
374
|
+
return new RegExp(pattern, flags);
|
|
375
|
+
}
|
|
376
|
+
document.getRegex = getRegex;
|
|
299
377
|
function collectAllShadowDomElements(element, result = []) {
|
|
300
378
|
// Check and add the element if it has a shadow root
|
|
301
379
|
if (element.shadowRoot) {
|
|
@@ -314,6 +392,10 @@ class StableBrowser {
|
|
|
314
392
|
if (!tag) {
|
|
315
393
|
tag = "*";
|
|
316
394
|
}
|
|
395
|
+
let regexpSearch = document.getRegex(text);
|
|
396
|
+
if (regexpSearch) {
|
|
397
|
+
regex = true;
|
|
398
|
+
}
|
|
317
399
|
let elements = Array.from(document.querySelectorAll(tag));
|
|
318
400
|
let shadowHosts = [];
|
|
319
401
|
document.collectAllShadowDomElements(document, shadowHosts);
|
|
@@ -329,7 +411,9 @@ class StableBrowser {
|
|
|
329
411
|
let randomToken = null;
|
|
330
412
|
const foundElements = [];
|
|
331
413
|
if (regex) {
|
|
332
|
-
|
|
414
|
+
if (!regexpSearch) {
|
|
415
|
+
regexpSearch = new RegExp(text, "im");
|
|
416
|
+
}
|
|
333
417
|
for (let i = 0; i < elements.length; i++) {
|
|
334
418
|
const element = elements[i];
|
|
335
419
|
if ((element.innerText && regexpSearch.test(element.innerText)) ||
|
|
@@ -343,8 +427,8 @@ class StableBrowser {
|
|
|
343
427
|
for (let i = 0; i < elements.length; i++) {
|
|
344
428
|
const element = elements[i];
|
|
345
429
|
if (partial) {
|
|
346
|
-
if ((element.innerText && element.innerText.trim().includes(text)) ||
|
|
347
|
-
(element.value && element.value.includes(text))) {
|
|
430
|
+
if ((element.innerText && element.innerText.toLowerCase().trim().includes(text.toLowerCase())) ||
|
|
431
|
+
(element.value && element.value.toLowerCase().includes(text.toLowerCase()))) {
|
|
348
432
|
foundElements.push(element);
|
|
349
433
|
}
|
|
350
434
|
}
|
|
@@ -477,6 +561,8 @@ class StableBrowser {
|
|
|
477
561
|
if (result.foundElements.length > 0) {
|
|
478
562
|
let dialogCloseLocator = result.foundElements[0].locator;
|
|
479
563
|
await dialogCloseLocator.click();
|
|
564
|
+
// wait for the dialog to close
|
|
565
|
+
await dialogCloseLocator.waitFor({ state: "hidden" });
|
|
480
566
|
return { rerun: true };
|
|
481
567
|
}
|
|
482
568
|
}
|
|
@@ -485,7 +571,7 @@ class StableBrowser {
|
|
|
485
571
|
}
|
|
486
572
|
async _locate(selectors, info, _params, timeout = 30000) {
|
|
487
573
|
for (let i = 0; i < 3; i++) {
|
|
488
|
-
info.log += "attempt " + i + ":
|
|
574
|
+
info.log += "attempt " + i + ": total locators " + selectors.locators.length + "\n";
|
|
489
575
|
for (let j = 0; j < selectors.locators.length; j++) {
|
|
490
576
|
let selector = selectors.locators[j];
|
|
491
577
|
info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
|
|
@@ -505,9 +591,39 @@ class StableBrowser {
|
|
|
505
591
|
//let arrayMode = Array.isArray(selectors);
|
|
506
592
|
let scope = this.page;
|
|
507
593
|
if (selectors.iframe_src || selectors.frameLocators) {
|
|
594
|
+
const findFrame = async (frame, framescope) => {
|
|
595
|
+
for (let i = 0; i < frame.selectors.length; i++) {
|
|
596
|
+
let frameLocator = frame.selectors[i];
|
|
597
|
+
if (frameLocator.css) {
|
|
598
|
+
let testframescope = framescope.frameLocator(frameLocator.css);
|
|
599
|
+
if (frameLocator.index) {
|
|
600
|
+
testframescope = framescope.nth(frameLocator.index);
|
|
601
|
+
}
|
|
602
|
+
try {
|
|
603
|
+
await testframescope.owner().evaluateHandle(() => true, null, {
|
|
604
|
+
timeout: 5000,
|
|
605
|
+
});
|
|
606
|
+
framescope = testframescope;
|
|
607
|
+
break;
|
|
608
|
+
}
|
|
609
|
+
catch (error) {
|
|
610
|
+
console.error("frame not found " + frameLocator.css);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
if (frame.children) {
|
|
615
|
+
return await findFrame(frame.children, framescope);
|
|
616
|
+
}
|
|
617
|
+
return framescope;
|
|
618
|
+
};
|
|
508
619
|
info.log += "searching for iframe " + selectors.iframe_src + "/" + selectors.frameLocators + "\n";
|
|
509
620
|
while (true) {
|
|
510
621
|
let frameFound = false;
|
|
622
|
+
if (selectors.nestFrmLoc) {
|
|
623
|
+
scope = await findFrame(selectors.nestFrmLoc, scope);
|
|
624
|
+
frameFound = true;
|
|
625
|
+
break;
|
|
626
|
+
}
|
|
511
627
|
if (selectors.frameLocators) {
|
|
512
628
|
for (let i = 0; i < selectors.frameLocators.length; i++) {
|
|
513
629
|
let frameLocator = selectors.frameLocators[i];
|
|
@@ -687,14 +803,14 @@ class StableBrowser {
|
|
|
687
803
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
688
804
|
try {
|
|
689
805
|
await this._highlightElements(element);
|
|
690
|
-
await element.click(
|
|
806
|
+
await element.click();
|
|
691
807
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
692
808
|
}
|
|
693
809
|
catch (e) {
|
|
694
810
|
// await this.closeUnexpectedPopups();
|
|
695
811
|
info.log += "click failed, will try again" + "\n";
|
|
696
812
|
element = await this._locate(selectors, info, _params);
|
|
697
|
-
await element.click
|
|
813
|
+
await element.dispatchEvent("click");
|
|
698
814
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
699
815
|
}
|
|
700
816
|
await this.waitForPageLoad();
|
|
@@ -747,7 +863,7 @@ class StableBrowser {
|
|
|
747
863
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
748
864
|
try {
|
|
749
865
|
await this._highlightElements(element);
|
|
750
|
-
await element.setChecked(checked
|
|
866
|
+
await element.setChecked(checked);
|
|
751
867
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
752
868
|
}
|
|
753
869
|
catch (e) {
|
|
@@ -811,7 +927,7 @@ class StableBrowser {
|
|
|
811
927
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
812
928
|
try {
|
|
813
929
|
await this._highlightElements(element);
|
|
814
|
-
await element.hover(
|
|
930
|
+
await element.hover();
|
|
815
931
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
816
932
|
}
|
|
817
933
|
catch (e) {
|
|
@@ -873,7 +989,7 @@ class StableBrowser {
|
|
|
873
989
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
874
990
|
try {
|
|
875
991
|
await this._highlightElements(element);
|
|
876
|
-
await element.selectOption(values
|
|
992
|
+
await element.selectOption(values);
|
|
877
993
|
}
|
|
878
994
|
catch (e) {
|
|
879
995
|
//await this.closeUnexpectedPopups();
|
|
@@ -1161,7 +1277,6 @@ class StableBrowser {
|
|
|
1161
1277
|
let element = await this._locate(selectors, info, _params);
|
|
1162
1278
|
//insert red border around the element
|
|
1163
1279
|
await this.scrollIfNeeded(element, info);
|
|
1164
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1165
1280
|
await this._highlightElements(element);
|
|
1166
1281
|
if (options === null || options === undefined || !options.press) {
|
|
1167
1282
|
try {
|
|
@@ -1211,6 +1326,7 @@ class StableBrowser {
|
|
|
1211
1326
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1212
1327
|
}
|
|
1213
1328
|
}
|
|
1329
|
+
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1214
1330
|
if (enter === true) {
|
|
1215
1331
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1216
1332
|
await this.page.keyboard.press("Enter");
|
|
@@ -1276,7 +1392,7 @@ class StableBrowser {
|
|
|
1276
1392
|
let element = await this._locate(selectors, info, _params);
|
|
1277
1393
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1278
1394
|
await this._highlightElements(element);
|
|
1279
|
-
await element.fill(value
|
|
1395
|
+
await element.fill(value);
|
|
1280
1396
|
await element.dispatchEvent("change");
|
|
1281
1397
|
if (enter) {
|
|
1282
1398
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
@@ -1495,7 +1611,7 @@ class StableBrowser {
|
|
|
1495
1611
|
return info;
|
|
1496
1612
|
}
|
|
1497
1613
|
catch (e) {
|
|
1498
|
-
|
|
1614
|
+
await this.closeUnexpectedPopups();
|
|
1499
1615
|
this.logger.error("verify element contains text failed " + info.log);
|
|
1500
1616
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1501
1617
|
info.screenshotPath = screenshotPath;
|
|
@@ -1543,6 +1659,29 @@ class StableBrowser {
|
|
|
1543
1659
|
}
|
|
1544
1660
|
return dataFile;
|
|
1545
1661
|
}
|
|
1662
|
+
async waitForUserInput(message, world = null) {
|
|
1663
|
+
if (!message) {
|
|
1664
|
+
message = "# Wait for user input. Press any key to continue";
|
|
1665
|
+
}
|
|
1666
|
+
else {
|
|
1667
|
+
message = "# Wait for user input. " + message;
|
|
1668
|
+
}
|
|
1669
|
+
message += "\n";
|
|
1670
|
+
const value = await new Promise((resolve) => {
|
|
1671
|
+
const rl = readline.createInterface({
|
|
1672
|
+
input: process.stdin,
|
|
1673
|
+
output: process.stdout,
|
|
1674
|
+
});
|
|
1675
|
+
rl.question(message, (answer) => {
|
|
1676
|
+
rl.close();
|
|
1677
|
+
resolve(answer);
|
|
1678
|
+
});
|
|
1679
|
+
});
|
|
1680
|
+
if (value) {
|
|
1681
|
+
this.logger.info(`{{userInput}} was set to: ${value}`);
|
|
1682
|
+
}
|
|
1683
|
+
this.setTestData({ userInput: value }, world);
|
|
1684
|
+
}
|
|
1546
1685
|
setTestData(testData, world = null) {
|
|
1547
1686
|
if (!testData) {
|
|
1548
1687
|
return;
|
|
@@ -1730,7 +1869,6 @@ class StableBrowser {
|
|
|
1730
1869
|
}
|
|
1731
1870
|
async takeScreenshot(screenshotPath) {
|
|
1732
1871
|
const playContext = this.context.playContext;
|
|
1733
|
-
const client = await playContext.newCDPSession(this.page);
|
|
1734
1872
|
// Using CDP to capture the screenshot
|
|
1735
1873
|
const viewportWidth = Math.max(...(await this.page.evaluate(() => [
|
|
1736
1874
|
document.body.scrollWidth,
|
|
@@ -1740,41 +1878,40 @@ class StableBrowser {
|
|
|
1740
1878
|
document.body.clientWidth,
|
|
1741
1879
|
document.documentElement.clientWidth,
|
|
1742
1880
|
])));
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
}
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
await client.detach();
|
|
1881
|
+
let screenshotBuffer = null;
|
|
1882
|
+
if (this.context.browserName === "chromium") {
|
|
1883
|
+
const client = await playContext.newCDPSession(this.page);
|
|
1884
|
+
const { data } = await client.send("Page.captureScreenshot", {
|
|
1885
|
+
format: "png",
|
|
1886
|
+
// clip: {
|
|
1887
|
+
// x: 0,
|
|
1888
|
+
// y: 0,
|
|
1889
|
+
// width: viewportWidth,
|
|
1890
|
+
// height: viewportHeight,
|
|
1891
|
+
// scale: 1,
|
|
1892
|
+
// },
|
|
1893
|
+
});
|
|
1894
|
+
await client.detach();
|
|
1895
|
+
if (!screenshotPath) {
|
|
1896
|
+
return data;
|
|
1897
|
+
}
|
|
1898
|
+
screenshotBuffer = Buffer.from(data, "base64");
|
|
1899
|
+
}
|
|
1900
|
+
else {
|
|
1901
|
+
screenshotBuffer = await this.page.screenshot();
|
|
1902
|
+
}
|
|
1903
|
+
let image = await Jimp.read(screenshotBuffer);
|
|
1904
|
+
// Get the image dimensions
|
|
1905
|
+
const { width, height } = image.bitmap;
|
|
1906
|
+
const resizeRatio = viewportWidth / width;
|
|
1907
|
+
// Resize the image to fit within the viewport dimensions without enlarging
|
|
1908
|
+
if (width > viewportWidth) {
|
|
1909
|
+
image = image.resize({ w: viewportWidth, h: height * resizeRatio }); // Resize the image while maintaining aspect ratio
|
|
1910
|
+
await image.write(screenshotPath);
|
|
1911
|
+
}
|
|
1912
|
+
else {
|
|
1913
|
+
fs.writeFileSync(screenshotPath, screenshotBuffer);
|
|
1914
|
+
}
|
|
1778
1915
|
}
|
|
1779
1916
|
async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
|
|
1780
1917
|
this._validateSelectors(selectors);
|
|
@@ -2582,13 +2719,13 @@ class StableBrowser {
|
|
|
2582
2719
|
}
|
|
2583
2720
|
catch (e) {
|
|
2584
2721
|
if (e.label === "networkidle") {
|
|
2585
|
-
console.log("
|
|
2722
|
+
console.log("waited for the network to be idle timeout");
|
|
2586
2723
|
}
|
|
2587
2724
|
else if (e.label === "load") {
|
|
2588
|
-
console.log("
|
|
2725
|
+
console.log("waited for the load timeout");
|
|
2589
2726
|
}
|
|
2590
2727
|
else if (e.label === "domcontentloaded") {
|
|
2591
|
-
console.log("
|
|
2728
|
+
console.log("waited for the domcontent loaded timeout");
|
|
2592
2729
|
}
|
|
2593
2730
|
console.log(".");
|
|
2594
2731
|
}
|
|
@@ -2731,33 +2868,18 @@ class StableBrowser {
|
|
|
2731
2868
|
}
|
|
2732
2869
|
async scrollIfNeeded(element, info) {
|
|
2733
2870
|
try {
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
if (rect &&
|
|
2737
|
-
rect.top >= 0 &&
|
|
2738
|
-
rect.left >= 0 &&
|
|
2739
|
-
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
|
|
2740
|
-
rect.right <= (window.innerWidth || document.documentElement.clientWidth)) {
|
|
2741
|
-
return false;
|
|
2742
|
-
}
|
|
2743
|
-
else {
|
|
2744
|
-
node.scrollIntoView({
|
|
2745
|
-
behavior: "smooth",
|
|
2746
|
-
block: "center",
|
|
2747
|
-
inline: "center",
|
|
2748
|
-
});
|
|
2749
|
-
return true;
|
|
2750
|
-
}
|
|
2871
|
+
await element.scrollIntoViewIfNeeded({
|
|
2872
|
+
timeout: 2000,
|
|
2751
2873
|
});
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
}
|
|
2874
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
2875
|
+
if (info) {
|
|
2876
|
+
info.box = await element.boundingBox({
|
|
2877
|
+
timeout: 1000,
|
|
2878
|
+
});
|
|
2757
2879
|
}
|
|
2758
2880
|
}
|
|
2759
2881
|
catch (e) {
|
|
2760
|
-
console.log("
|
|
2882
|
+
console.log("#-#");
|
|
2761
2883
|
}
|
|
2762
2884
|
}
|
|
2763
2885
|
_reportToWorld(world, properties) {
|