automation_model 1.0.411-dev → 1.0.411-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 -5
- package/lib/stable_browser.js +223 -106
- 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
|
|
@@ -242,27 +315,29 @@ class StableBrowser {
|
|
|
242
315
|
}
|
|
243
316
|
}
|
|
244
317
|
if (locator === null || locator === void 0 ? void 0 : locator.engine) {
|
|
245
|
-
if (locator.engine === "
|
|
246
|
-
|
|
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}`);
|
|
247
329
|
}
|
|
248
|
-
locatorReturn = scope.locator(`${locator.engine}=${selector}`);
|
|
249
330
|
}
|
|
250
331
|
if (!locatorReturn) {
|
|
251
332
|
console.error(locator);
|
|
252
333
|
throw new Error("Locator undefined");
|
|
253
334
|
}
|
|
254
|
-
else {
|
|
255
|
-
const count = locatorReturn.count();
|
|
256
|
-
if (count === 0) {
|
|
257
|
-
throw new Error("Elements not found");
|
|
258
|
-
}
|
|
259
|
-
else if (count > 1) {
|
|
260
|
-
throw new Error("Multiple elements found");
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
335
|
return locatorReturn;
|
|
264
336
|
}
|
|
265
337
|
async _locateElmentByTextClimbCss(scope, text, climb, css, _params) {
|
|
338
|
+
if (css && css.locator) {
|
|
339
|
+
css = css.locator;
|
|
340
|
+
}
|
|
266
341
|
let result = await this._locateElementByText(scope, this._fixUsingParams(text, _params), "*", false, true, _params);
|
|
267
342
|
if (result.elementCount === 0) {
|
|
268
343
|
return;
|
|
@@ -290,6 +365,15 @@ class StableBrowser {
|
|
|
290
365
|
return false;
|
|
291
366
|
}
|
|
292
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;
|
|
293
377
|
function collectAllShadowDomElements(element, result = []) {
|
|
294
378
|
// Check and add the element if it has a shadow root
|
|
295
379
|
if (element.shadowRoot) {
|
|
@@ -308,6 +392,10 @@ class StableBrowser {
|
|
|
308
392
|
if (!tag) {
|
|
309
393
|
tag = "*";
|
|
310
394
|
}
|
|
395
|
+
let regexpSearch = document.getRegex(text);
|
|
396
|
+
if (regexpSearch) {
|
|
397
|
+
regex = true;
|
|
398
|
+
}
|
|
311
399
|
let elements = Array.from(document.querySelectorAll(tag));
|
|
312
400
|
let shadowHosts = [];
|
|
313
401
|
document.collectAllShadowDomElements(document, shadowHosts);
|
|
@@ -323,7 +411,9 @@ class StableBrowser {
|
|
|
323
411
|
let randomToken = null;
|
|
324
412
|
const foundElements = [];
|
|
325
413
|
if (regex) {
|
|
326
|
-
|
|
414
|
+
if (!regexpSearch) {
|
|
415
|
+
regexpSearch = new RegExp(text, "im");
|
|
416
|
+
}
|
|
327
417
|
for (let i = 0; i < elements.length; i++) {
|
|
328
418
|
const element = elements[i];
|
|
329
419
|
if ((element.innerText && regexpSearch.test(element.innerText)) ||
|
|
@@ -471,6 +561,8 @@ class StableBrowser {
|
|
|
471
561
|
if (result.foundElements.length > 0) {
|
|
472
562
|
let dialogCloseLocator = result.foundElements[0].locator;
|
|
473
563
|
await dialogCloseLocator.click();
|
|
564
|
+
// wait for the dialog to close
|
|
565
|
+
await dialogCloseLocator.waitFor({ state: "hidden" });
|
|
474
566
|
return { rerun: true };
|
|
475
567
|
}
|
|
476
568
|
}
|
|
@@ -479,7 +571,7 @@ class StableBrowser {
|
|
|
479
571
|
}
|
|
480
572
|
async _locate(selectors, info, _params, timeout = 30000) {
|
|
481
573
|
for (let i = 0; i < 3; i++) {
|
|
482
|
-
info.log += "attempt " + i + ":
|
|
574
|
+
info.log += "attempt " + i + ": total locators " + selectors.locators.length + "\n";
|
|
483
575
|
for (let j = 0; j < selectors.locators.length; j++) {
|
|
484
576
|
let selector = selectors.locators[j];
|
|
485
577
|
info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
|
|
@@ -499,9 +591,30 @@ class StableBrowser {
|
|
|
499
591
|
//let arrayMode = Array.isArray(selectors);
|
|
500
592
|
let scope = this.page;
|
|
501
593
|
if (selectors.iframe_src || selectors.frameLocators) {
|
|
594
|
+
const findFrame = (frame, framescope) => {
|
|
595
|
+
for (let i = 0; i < frame.selectors.length; i++) {
|
|
596
|
+
let frameLocator = frame.selectors[i];
|
|
597
|
+
if (frameLocator.css) {
|
|
598
|
+
framescope = framescope.frameLocator(frameLocator.css);
|
|
599
|
+
if (frameLocator.index) {
|
|
600
|
+
framescope = framescope.nth(frameLocator.index);
|
|
601
|
+
}
|
|
602
|
+
break;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
if (frame.children) {
|
|
606
|
+
return findFrame(frame.children, framescope);
|
|
607
|
+
}
|
|
608
|
+
return framescope;
|
|
609
|
+
};
|
|
502
610
|
info.log += "searching for iframe " + selectors.iframe_src + "/" + selectors.frameLocators + "\n";
|
|
503
611
|
while (true) {
|
|
504
612
|
let frameFound = false;
|
|
613
|
+
if (selectors.nestFrmLoc) {
|
|
614
|
+
scope = findFrame(selectors.nestFrmLoc, scope);
|
|
615
|
+
frameFound = true;
|
|
616
|
+
break;
|
|
617
|
+
}
|
|
505
618
|
if (selectors.frameLocators) {
|
|
506
619
|
for (let i = 0; i < selectors.frameLocators.length; i++) {
|
|
507
620
|
let frameLocator = selectors.frameLocators[i];
|
|
@@ -662,14 +775,12 @@ class StableBrowser {
|
|
|
662
775
|
}
|
|
663
776
|
return result;
|
|
664
777
|
}
|
|
665
|
-
async contextClick(selectors, text, _params, options = {}, world = null) {
|
|
666
|
-
this._validateSelectors(selectors);
|
|
667
|
-
selectors.locators[0].text = text;
|
|
668
|
-
await this.click(selectors, _params, options, world);
|
|
669
|
-
}
|
|
670
778
|
async click(selectors, _params, options = {}, world = null) {
|
|
671
779
|
this._validateSelectors(selectors);
|
|
672
780
|
const startTime = Date.now();
|
|
781
|
+
if (options && options.context) {
|
|
782
|
+
selectors.locators[0].text = options.context;
|
|
783
|
+
}
|
|
673
784
|
const info = {};
|
|
674
785
|
info.log = "***** click on " + selectors.element_name + " *****\n";
|
|
675
786
|
info.operation = "click";
|
|
@@ -683,14 +794,14 @@ class StableBrowser {
|
|
|
683
794
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
684
795
|
try {
|
|
685
796
|
await this._highlightElements(element);
|
|
686
|
-
await element.click(
|
|
797
|
+
await element.click();
|
|
687
798
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
688
799
|
}
|
|
689
800
|
catch (e) {
|
|
690
801
|
// await this.closeUnexpectedPopups();
|
|
691
802
|
info.log += "click failed, will try again" + "\n";
|
|
692
803
|
element = await this._locate(selectors, info, _params);
|
|
693
|
-
await element.click
|
|
804
|
+
await element.dispatchEvent("click");
|
|
694
805
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
695
806
|
}
|
|
696
807
|
await this.waitForPageLoad();
|
|
@@ -743,7 +854,7 @@ class StableBrowser {
|
|
|
743
854
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
744
855
|
try {
|
|
745
856
|
await this._highlightElements(element);
|
|
746
|
-
await element.setChecked(checked
|
|
857
|
+
await element.setChecked(checked);
|
|
747
858
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
748
859
|
}
|
|
749
860
|
catch (e) {
|
|
@@ -807,7 +918,7 @@ class StableBrowser {
|
|
|
807
918
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
808
919
|
try {
|
|
809
920
|
await this._highlightElements(element);
|
|
810
|
-
await element.hover(
|
|
921
|
+
await element.hover();
|
|
811
922
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
812
923
|
}
|
|
813
924
|
catch (e) {
|
|
@@ -869,7 +980,7 @@ class StableBrowser {
|
|
|
869
980
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
870
981
|
try {
|
|
871
982
|
await this._highlightElements(element);
|
|
872
|
-
await element.selectOption(values
|
|
983
|
+
await element.selectOption(values);
|
|
873
984
|
}
|
|
874
985
|
catch (e) {
|
|
875
986
|
//await this.closeUnexpectedPopups();
|
|
@@ -1272,7 +1383,7 @@ class StableBrowser {
|
|
|
1272
1383
|
let element = await this._locate(selectors, info, _params);
|
|
1273
1384
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1274
1385
|
await this._highlightElements(element);
|
|
1275
|
-
await element.fill(value
|
|
1386
|
+
await element.fill(value);
|
|
1276
1387
|
await element.dispatchEvent("change");
|
|
1277
1388
|
if (enter) {
|
|
1278
1389
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
@@ -1491,7 +1602,7 @@ class StableBrowser {
|
|
|
1491
1602
|
return info;
|
|
1492
1603
|
}
|
|
1493
1604
|
catch (e) {
|
|
1494
|
-
|
|
1605
|
+
await this.closeUnexpectedPopups();
|
|
1495
1606
|
this.logger.error("verify element contains text failed " + info.log);
|
|
1496
1607
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1497
1608
|
info.screenshotPath = screenshotPath;
|
|
@@ -1539,6 +1650,29 @@ class StableBrowser {
|
|
|
1539
1650
|
}
|
|
1540
1651
|
return dataFile;
|
|
1541
1652
|
}
|
|
1653
|
+
async waitForUserInput(message, world = null) {
|
|
1654
|
+
if (!message) {
|
|
1655
|
+
message = "# Wait for user input. Press any key to continue";
|
|
1656
|
+
}
|
|
1657
|
+
else {
|
|
1658
|
+
message = "# Wait for user input. " + message;
|
|
1659
|
+
}
|
|
1660
|
+
message += "\n";
|
|
1661
|
+
const value = await new Promise((resolve) => {
|
|
1662
|
+
const rl = readline.createInterface({
|
|
1663
|
+
input: process.stdin,
|
|
1664
|
+
output: process.stdout,
|
|
1665
|
+
});
|
|
1666
|
+
rl.question(message, (answer) => {
|
|
1667
|
+
rl.close();
|
|
1668
|
+
resolve(answer);
|
|
1669
|
+
});
|
|
1670
|
+
});
|
|
1671
|
+
if (value) {
|
|
1672
|
+
this.logger.info(`{{userInput}} was set to: ${value}`);
|
|
1673
|
+
}
|
|
1674
|
+
this.setTestData({ userInput: value }, world);
|
|
1675
|
+
}
|
|
1542
1676
|
setTestData(testData, world = null) {
|
|
1543
1677
|
if (!testData) {
|
|
1544
1678
|
return;
|
|
@@ -1726,7 +1860,6 @@ class StableBrowser {
|
|
|
1726
1860
|
}
|
|
1727
1861
|
async takeScreenshot(screenshotPath) {
|
|
1728
1862
|
const playContext = this.context.playContext;
|
|
1729
|
-
const client = await playContext.newCDPSession(this.page);
|
|
1730
1863
|
// Using CDP to capture the screenshot
|
|
1731
1864
|
const viewportWidth = Math.max(...(await this.page.evaluate(() => [
|
|
1732
1865
|
document.body.scrollWidth,
|
|
@@ -1736,41 +1869,40 @@ class StableBrowser {
|
|
|
1736
1869
|
document.body.clientWidth,
|
|
1737
1870
|
document.documentElement.clientWidth,
|
|
1738
1871
|
])));
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
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
|
-
await client.detach();
|
|
1872
|
+
let screenshotBuffer = null;
|
|
1873
|
+
if (this.context.browserName === "chromium") {
|
|
1874
|
+
const client = await playContext.newCDPSession(this.page);
|
|
1875
|
+
const { data } = await client.send("Page.captureScreenshot", {
|
|
1876
|
+
format: "png",
|
|
1877
|
+
// clip: {
|
|
1878
|
+
// x: 0,
|
|
1879
|
+
// y: 0,
|
|
1880
|
+
// width: viewportWidth,
|
|
1881
|
+
// height: viewportHeight,
|
|
1882
|
+
// scale: 1,
|
|
1883
|
+
// },
|
|
1884
|
+
});
|
|
1885
|
+
await client.detach();
|
|
1886
|
+
if (!screenshotPath) {
|
|
1887
|
+
return data;
|
|
1888
|
+
}
|
|
1889
|
+
screenshotBuffer = Buffer.from(data, "base64");
|
|
1890
|
+
}
|
|
1891
|
+
else {
|
|
1892
|
+
screenshotBuffer = await this.page.screenshot();
|
|
1893
|
+
}
|
|
1894
|
+
let image = await Jimp.read(screenshotBuffer);
|
|
1895
|
+
// Get the image dimensions
|
|
1896
|
+
const { width, height } = image.bitmap;
|
|
1897
|
+
const resizeRatio = viewportWidth / width;
|
|
1898
|
+
// Resize the image to fit within the viewport dimensions without enlarging
|
|
1899
|
+
if (width > viewportWidth) {
|
|
1900
|
+
image = image.resize({ w: viewportWidth, h: height * resizeRatio }); // Resize the image while maintaining aspect ratio
|
|
1901
|
+
await image.write(screenshotPath);
|
|
1902
|
+
}
|
|
1903
|
+
else {
|
|
1904
|
+
fs.writeFileSync(screenshotPath, screenshotBuffer);
|
|
1905
|
+
}
|
|
1774
1906
|
}
|
|
1775
1907
|
async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
|
|
1776
1908
|
this._validateSelectors(selectors);
|
|
@@ -2578,13 +2710,13 @@ class StableBrowser {
|
|
|
2578
2710
|
}
|
|
2579
2711
|
catch (e) {
|
|
2580
2712
|
if (e.label === "networkidle") {
|
|
2581
|
-
console.log("
|
|
2713
|
+
console.log("waited for the network to be idle timeout");
|
|
2582
2714
|
}
|
|
2583
2715
|
else if (e.label === "load") {
|
|
2584
|
-
console.log("
|
|
2716
|
+
console.log("waited for the load timeout");
|
|
2585
2717
|
}
|
|
2586
2718
|
else if (e.label === "domcontentloaded") {
|
|
2587
|
-
console.log("
|
|
2719
|
+
console.log("waited for the domcontent loaded timeout");
|
|
2588
2720
|
}
|
|
2589
2721
|
console.log(".");
|
|
2590
2722
|
}
|
|
@@ -2727,33 +2859,18 @@ class StableBrowser {
|
|
|
2727
2859
|
}
|
|
2728
2860
|
async scrollIfNeeded(element, info) {
|
|
2729
2861
|
try {
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
if (rect &&
|
|
2733
|
-
rect.top >= 0 &&
|
|
2734
|
-
rect.left >= 0 &&
|
|
2735
|
-
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
|
|
2736
|
-
rect.right <= (window.innerWidth || document.documentElement.clientWidth)) {
|
|
2737
|
-
return false;
|
|
2738
|
-
}
|
|
2739
|
-
else {
|
|
2740
|
-
node.scrollIntoView({
|
|
2741
|
-
behavior: "smooth",
|
|
2742
|
-
block: "center",
|
|
2743
|
-
inline: "center",
|
|
2744
|
-
});
|
|
2745
|
-
return true;
|
|
2746
|
-
}
|
|
2862
|
+
await element.scrollIntoViewIfNeeded({
|
|
2863
|
+
timeout: 2000,
|
|
2747
2864
|
});
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
}
|
|
2865
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
2866
|
+
if (info) {
|
|
2867
|
+
info.box = await element.boundingBox({
|
|
2868
|
+
timeout: 1000,
|
|
2869
|
+
});
|
|
2753
2870
|
}
|
|
2754
2871
|
}
|
|
2755
2872
|
catch (e) {
|
|
2756
|
-
console.log("
|
|
2873
|
+
console.log("#-#");
|
|
2757
2874
|
}
|
|
2758
2875
|
}
|
|
2759
2876
|
_reportToWorld(world, properties) {
|