automation_model 1.0.415-dev → 1.0.415-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 +240 -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
|
}
|
|
@@ -389,6 +473,12 @@ class StableBrowser {
|
|
|
389
473
|
}
|
|
390
474
|
async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true) {
|
|
391
475
|
let locatorSearch = selectorHierarchy[index];
|
|
476
|
+
try {
|
|
477
|
+
locatorSearch = JSON.parse(this._fixUsingParams(JSON.stringify(locatorSearch), _params));
|
|
478
|
+
}
|
|
479
|
+
catch (e) {
|
|
480
|
+
console.error(e);
|
|
481
|
+
}
|
|
392
482
|
//info.log += "searching for locator " + JSON.stringify(locatorSearch) + "\n";
|
|
393
483
|
let locator = null;
|
|
394
484
|
if (locatorSearch.climb && locatorSearch.climb >= 0) {
|
|
@@ -477,6 +567,8 @@ class StableBrowser {
|
|
|
477
567
|
if (result.foundElements.length > 0) {
|
|
478
568
|
let dialogCloseLocator = result.foundElements[0].locator;
|
|
479
569
|
await dialogCloseLocator.click();
|
|
570
|
+
// wait for the dialog to close
|
|
571
|
+
await dialogCloseLocator.waitFor({ state: "hidden" });
|
|
480
572
|
return { rerun: true };
|
|
481
573
|
}
|
|
482
574
|
}
|
|
@@ -485,7 +577,7 @@ class StableBrowser {
|
|
|
485
577
|
}
|
|
486
578
|
async _locate(selectors, info, _params, timeout = 30000) {
|
|
487
579
|
for (let i = 0; i < 3; i++) {
|
|
488
|
-
info.log += "attempt " + i + ":
|
|
580
|
+
info.log += "attempt " + i + ": total locators " + selectors.locators.length + "\n";
|
|
489
581
|
for (let j = 0; j < selectors.locators.length; j++) {
|
|
490
582
|
let selector = selectors.locators[j];
|
|
491
583
|
info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
|
|
@@ -505,9 +597,39 @@ class StableBrowser {
|
|
|
505
597
|
//let arrayMode = Array.isArray(selectors);
|
|
506
598
|
let scope = this.page;
|
|
507
599
|
if (selectors.iframe_src || selectors.frameLocators) {
|
|
600
|
+
const findFrame = async (frame, framescope) => {
|
|
601
|
+
for (let i = 0; i < frame.selectors.length; i++) {
|
|
602
|
+
let frameLocator = frame.selectors[i];
|
|
603
|
+
if (frameLocator.css) {
|
|
604
|
+
let testframescope = framescope.frameLocator(frameLocator.css);
|
|
605
|
+
if (frameLocator.index) {
|
|
606
|
+
testframescope = framescope.nth(frameLocator.index);
|
|
607
|
+
}
|
|
608
|
+
try {
|
|
609
|
+
await testframescope.owner().evaluateHandle(() => true, null, {
|
|
610
|
+
timeout: 5000,
|
|
611
|
+
});
|
|
612
|
+
framescope = testframescope;
|
|
613
|
+
break;
|
|
614
|
+
}
|
|
615
|
+
catch (error) {
|
|
616
|
+
console.error("frame not found " + frameLocator.css);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
if (frame.children) {
|
|
621
|
+
return await findFrame(frame.children, framescope);
|
|
622
|
+
}
|
|
623
|
+
return framescope;
|
|
624
|
+
};
|
|
508
625
|
info.log += "searching for iframe " + selectors.iframe_src + "/" + selectors.frameLocators + "\n";
|
|
509
626
|
while (true) {
|
|
510
627
|
let frameFound = false;
|
|
628
|
+
if (selectors.nestFrmLoc) {
|
|
629
|
+
scope = await findFrame(selectors.nestFrmLoc, scope);
|
|
630
|
+
frameFound = true;
|
|
631
|
+
break;
|
|
632
|
+
}
|
|
511
633
|
if (selectors.frameLocators) {
|
|
512
634
|
for (let i = 0; i < selectors.frameLocators.length; i++) {
|
|
513
635
|
let frameLocator = selectors.frameLocators[i];
|
|
@@ -687,14 +809,14 @@ class StableBrowser {
|
|
|
687
809
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
688
810
|
try {
|
|
689
811
|
await this._highlightElements(element);
|
|
690
|
-
await element.click(
|
|
812
|
+
await element.click();
|
|
691
813
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
692
814
|
}
|
|
693
815
|
catch (e) {
|
|
694
816
|
// await this.closeUnexpectedPopups();
|
|
695
817
|
info.log += "click failed, will try again" + "\n";
|
|
696
818
|
element = await this._locate(selectors, info, _params);
|
|
697
|
-
await element.click
|
|
819
|
+
await element.dispatchEvent("click");
|
|
698
820
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
699
821
|
}
|
|
700
822
|
await this.waitForPageLoad();
|
|
@@ -747,7 +869,7 @@ class StableBrowser {
|
|
|
747
869
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
748
870
|
try {
|
|
749
871
|
await this._highlightElements(element);
|
|
750
|
-
await element.setChecked(checked
|
|
872
|
+
await element.setChecked(checked);
|
|
751
873
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
752
874
|
}
|
|
753
875
|
catch (e) {
|
|
@@ -811,7 +933,7 @@ class StableBrowser {
|
|
|
811
933
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
812
934
|
try {
|
|
813
935
|
await this._highlightElements(element);
|
|
814
|
-
await element.hover(
|
|
936
|
+
await element.hover();
|
|
815
937
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
816
938
|
}
|
|
817
939
|
catch (e) {
|
|
@@ -873,7 +995,7 @@ class StableBrowser {
|
|
|
873
995
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
874
996
|
try {
|
|
875
997
|
await this._highlightElements(element);
|
|
876
|
-
await element.selectOption(values
|
|
998
|
+
await element.selectOption(values);
|
|
877
999
|
}
|
|
878
1000
|
catch (e) {
|
|
879
1001
|
//await this.closeUnexpectedPopups();
|
|
@@ -1161,7 +1283,6 @@ class StableBrowser {
|
|
|
1161
1283
|
let element = await this._locate(selectors, info, _params);
|
|
1162
1284
|
//insert red border around the element
|
|
1163
1285
|
await this.scrollIfNeeded(element, info);
|
|
1164
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1165
1286
|
await this._highlightElements(element);
|
|
1166
1287
|
if (options === null || options === undefined || !options.press) {
|
|
1167
1288
|
try {
|
|
@@ -1211,6 +1332,7 @@ class StableBrowser {
|
|
|
1211
1332
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1212
1333
|
}
|
|
1213
1334
|
}
|
|
1335
|
+
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1214
1336
|
if (enter === true) {
|
|
1215
1337
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1216
1338
|
await this.page.keyboard.press("Enter");
|
|
@@ -1276,7 +1398,7 @@ class StableBrowser {
|
|
|
1276
1398
|
let element = await this._locate(selectors, info, _params);
|
|
1277
1399
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1278
1400
|
await this._highlightElements(element);
|
|
1279
|
-
await element.fill(value
|
|
1401
|
+
await element.fill(value);
|
|
1280
1402
|
await element.dispatchEvent("change");
|
|
1281
1403
|
if (enter) {
|
|
1282
1404
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
@@ -1495,7 +1617,7 @@ class StableBrowser {
|
|
|
1495
1617
|
return info;
|
|
1496
1618
|
}
|
|
1497
1619
|
catch (e) {
|
|
1498
|
-
|
|
1620
|
+
await this.closeUnexpectedPopups();
|
|
1499
1621
|
this.logger.error("verify element contains text failed " + info.log);
|
|
1500
1622
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1501
1623
|
info.screenshotPath = screenshotPath;
|
|
@@ -1543,6 +1665,29 @@ class StableBrowser {
|
|
|
1543
1665
|
}
|
|
1544
1666
|
return dataFile;
|
|
1545
1667
|
}
|
|
1668
|
+
async waitForUserInput(message, world = null) {
|
|
1669
|
+
if (!message) {
|
|
1670
|
+
message = "# Wait for user input. Press any key to continue";
|
|
1671
|
+
}
|
|
1672
|
+
else {
|
|
1673
|
+
message = "# Wait for user input. " + message;
|
|
1674
|
+
}
|
|
1675
|
+
message += "\n";
|
|
1676
|
+
const value = await new Promise((resolve) => {
|
|
1677
|
+
const rl = readline.createInterface({
|
|
1678
|
+
input: process.stdin,
|
|
1679
|
+
output: process.stdout,
|
|
1680
|
+
});
|
|
1681
|
+
rl.question(message, (answer) => {
|
|
1682
|
+
rl.close();
|
|
1683
|
+
resolve(answer);
|
|
1684
|
+
});
|
|
1685
|
+
});
|
|
1686
|
+
if (value) {
|
|
1687
|
+
this.logger.info(`{{userInput}} was set to: ${value}`);
|
|
1688
|
+
}
|
|
1689
|
+
this.setTestData({ userInput: value }, world);
|
|
1690
|
+
}
|
|
1546
1691
|
setTestData(testData, world = null) {
|
|
1547
1692
|
if (!testData) {
|
|
1548
1693
|
return;
|
|
@@ -1730,7 +1875,6 @@ class StableBrowser {
|
|
|
1730
1875
|
}
|
|
1731
1876
|
async takeScreenshot(screenshotPath) {
|
|
1732
1877
|
const playContext = this.context.playContext;
|
|
1733
|
-
const client = await playContext.newCDPSession(this.page);
|
|
1734
1878
|
// Using CDP to capture the screenshot
|
|
1735
1879
|
const viewportWidth = Math.max(...(await this.page.evaluate(() => [
|
|
1736
1880
|
document.body.scrollWidth,
|
|
@@ -1740,41 +1884,40 @@ class StableBrowser {
|
|
|
1740
1884
|
document.body.clientWidth,
|
|
1741
1885
|
document.documentElement.clientWidth,
|
|
1742
1886
|
])));
|
|
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();
|
|
1887
|
+
let screenshotBuffer = null;
|
|
1888
|
+
if (this.context.browserName === "chromium") {
|
|
1889
|
+
const client = await playContext.newCDPSession(this.page);
|
|
1890
|
+
const { data } = await client.send("Page.captureScreenshot", {
|
|
1891
|
+
format: "png",
|
|
1892
|
+
// clip: {
|
|
1893
|
+
// x: 0,
|
|
1894
|
+
// y: 0,
|
|
1895
|
+
// width: viewportWidth,
|
|
1896
|
+
// height: viewportHeight,
|
|
1897
|
+
// scale: 1,
|
|
1898
|
+
// },
|
|
1899
|
+
});
|
|
1900
|
+
await client.detach();
|
|
1901
|
+
if (!screenshotPath) {
|
|
1902
|
+
return data;
|
|
1903
|
+
}
|
|
1904
|
+
screenshotBuffer = Buffer.from(data, "base64");
|
|
1905
|
+
}
|
|
1906
|
+
else {
|
|
1907
|
+
screenshotBuffer = await this.page.screenshot();
|
|
1908
|
+
}
|
|
1909
|
+
let image = await Jimp.read(screenshotBuffer);
|
|
1910
|
+
// Get the image dimensions
|
|
1911
|
+
const { width, height } = image.bitmap;
|
|
1912
|
+
const resizeRatio = viewportWidth / width;
|
|
1913
|
+
// Resize the image to fit within the viewport dimensions without enlarging
|
|
1914
|
+
if (width > viewportWidth) {
|
|
1915
|
+
image = image.resize({ w: viewportWidth, h: height * resizeRatio }); // Resize the image while maintaining aspect ratio
|
|
1916
|
+
await image.write(screenshotPath);
|
|
1917
|
+
}
|
|
1918
|
+
else {
|
|
1919
|
+
fs.writeFileSync(screenshotPath, screenshotBuffer);
|
|
1920
|
+
}
|
|
1778
1921
|
}
|
|
1779
1922
|
async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
|
|
1780
1923
|
this._validateSelectors(selectors);
|
|
@@ -2582,13 +2725,13 @@ class StableBrowser {
|
|
|
2582
2725
|
}
|
|
2583
2726
|
catch (e) {
|
|
2584
2727
|
if (e.label === "networkidle") {
|
|
2585
|
-
console.log("
|
|
2728
|
+
console.log("waited for the network to be idle timeout");
|
|
2586
2729
|
}
|
|
2587
2730
|
else if (e.label === "load") {
|
|
2588
|
-
console.log("
|
|
2731
|
+
console.log("waited for the load timeout");
|
|
2589
2732
|
}
|
|
2590
2733
|
else if (e.label === "domcontentloaded") {
|
|
2591
|
-
console.log("
|
|
2734
|
+
console.log("waited for the domcontent loaded timeout");
|
|
2592
2735
|
}
|
|
2593
2736
|
console.log(".");
|
|
2594
2737
|
}
|
|
@@ -2731,33 +2874,18 @@ class StableBrowser {
|
|
|
2731
2874
|
}
|
|
2732
2875
|
async scrollIfNeeded(element, info) {
|
|
2733
2876
|
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
|
-
}
|
|
2877
|
+
await element.scrollIntoViewIfNeeded({
|
|
2878
|
+
timeout: 2000,
|
|
2751
2879
|
});
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
}
|
|
2880
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
2881
|
+
if (info) {
|
|
2882
|
+
info.box = await element.boundingBox({
|
|
2883
|
+
timeout: 1000,
|
|
2884
|
+
});
|
|
2757
2885
|
}
|
|
2758
2886
|
}
|
|
2759
2887
|
catch (e) {
|
|
2760
|
-
console.log("
|
|
2888
|
+
console.log("#-#");
|
|
2761
2889
|
}
|
|
2762
2890
|
}
|
|
2763
2891
|
_reportToWorld(world, properties) {
|