automation_model 1.0.397-dev → 1.0.397-main
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/locate_element.d.ts +7 -0
- package/lib/locate_element.js +210 -0
- package/lib/locate_element.js.map +1 -0
- package/lib/stable_browser.d.ts +18 -4
- package/lib/stable_browser.js +456 -184
- 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 +12 -7
package/lib/stable_browser.js
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
import { expect } from "@playwright/test";
|
|
3
3
|
import dayjs from "dayjs";
|
|
4
4
|
import fs from "fs";
|
|
5
|
+
import { Jimp } from "jimp";
|
|
5
6
|
import path from "path";
|
|
6
7
|
import reg_parser from "regex-parser";
|
|
7
|
-
import sharp from "sharp";
|
|
8
8
|
import { findDateAlternatives, findNumberAlternatives } from "./analyze_helper.js";
|
|
9
9
|
import { getDateTimeValue } from "./date_time.js";
|
|
10
10
|
import drawRectangle from "./drawRect.js";
|
|
@@ -13,6 +13,10 @@ import { getTableCells, getTableData } from "./table_analyze.js";
|
|
|
13
13
|
import objectPath from "object-path";
|
|
14
14
|
import { decrypt } from "./utils.js";
|
|
15
15
|
import csv from "csv-parser";
|
|
16
|
+
import { Readable } from "node:stream";
|
|
17
|
+
import readline from "readline";
|
|
18
|
+
import { getContext } from "./init_browser.js";
|
|
19
|
+
import { locate_element } from "./locate_element.js";
|
|
16
20
|
const Types = {
|
|
17
21
|
CLICK: "click_element",
|
|
18
22
|
NAVIGATE: "navigate",
|
|
@@ -28,6 +32,7 @@ const Types = {
|
|
|
28
32
|
SELECT: "select_combobox",
|
|
29
33
|
VERIFY_PAGE_PATH: "verify_page_path",
|
|
30
34
|
TYPE_PRESS: "type_press",
|
|
35
|
+
PRESS: "press_key",
|
|
31
36
|
HOVER: "hover_element",
|
|
32
37
|
CHECK: "check_element",
|
|
33
38
|
UNCHECK: "uncheck_element",
|
|
@@ -36,16 +41,22 @@ const Types = {
|
|
|
36
41
|
SET_DATE_TIME: "set_date_time",
|
|
37
42
|
SET_VIEWPORT: "set_viewport",
|
|
38
43
|
VERIFY_VISUAL: "verify_visual",
|
|
44
|
+
LOAD_DATA: "load_data",
|
|
45
|
+
SET_INPUT: "set_input",
|
|
39
46
|
};
|
|
47
|
+
export const apps = {};
|
|
40
48
|
class StableBrowser {
|
|
41
|
-
constructor(browser, page, logger = null, context = null) {
|
|
49
|
+
constructor(browser, page, logger = null, context = null, world = null) {
|
|
42
50
|
this.browser = browser;
|
|
43
51
|
this.page = page;
|
|
44
52
|
this.logger = logger;
|
|
45
53
|
this.context = context;
|
|
54
|
+
this.world = world;
|
|
46
55
|
this.project_path = null;
|
|
47
56
|
this.webLogFile = null;
|
|
57
|
+
this.networkLogger = null;
|
|
48
58
|
this.configuration = null;
|
|
59
|
+
this.appName = "main";
|
|
49
60
|
if (!this.logger) {
|
|
50
61
|
this.logger = console;
|
|
51
62
|
}
|
|
@@ -71,19 +82,36 @@ class StableBrowser {
|
|
|
71
82
|
this.logger.error("unable to read ai_config.json");
|
|
72
83
|
}
|
|
73
84
|
const logFolder = path.join(this.project_path, "logs", "web");
|
|
74
|
-
this.
|
|
75
|
-
this.registerConsoleLogListener(page, context, this.webLogFile);
|
|
76
|
-
this.registerRequestListener();
|
|
85
|
+
this.world = world;
|
|
77
86
|
context.pages = [this.page];
|
|
78
87
|
context.pageLoading = { status: false };
|
|
88
|
+
this.registerEventListeners(this.context);
|
|
89
|
+
}
|
|
90
|
+
registerEventListeners(context) {
|
|
91
|
+
this.registerConsoleLogListener(this.page, context);
|
|
92
|
+
this.registerRequestListener(this.page, context, this.webLogFile);
|
|
93
|
+
if (!context.pageLoading) {
|
|
94
|
+
context.pageLoading = { status: false };
|
|
95
|
+
}
|
|
79
96
|
context.playContext.on("page", async function (page) {
|
|
80
97
|
context.pageLoading.status = true;
|
|
81
98
|
this.page = page;
|
|
82
99
|
context.page = page;
|
|
83
100
|
context.pages.push(page);
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
101
|
+
page.on("close", async () => {
|
|
102
|
+
if (this.context && this.context.pages && this.context.pages.length > 1) {
|
|
103
|
+
this.context.pages.pop();
|
|
104
|
+
this.page = this.context.pages[this.context.pages.length - 1];
|
|
105
|
+
this.context.page = this.page;
|
|
106
|
+
try {
|
|
107
|
+
let title = await this.page.title();
|
|
108
|
+
console.log("Switched to page " + title);
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
console.error("Error on page close", error);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
});
|
|
87
115
|
try {
|
|
88
116
|
await this.waitForPageLoad();
|
|
89
117
|
console.log("Switch page: " + (await page.title()));
|
|
@@ -94,6 +122,36 @@ class StableBrowser {
|
|
|
94
122
|
context.pageLoading.status = false;
|
|
95
123
|
}.bind(this));
|
|
96
124
|
}
|
|
125
|
+
async switchApp(appName) {
|
|
126
|
+
// check if the current app (this.appName) is the same as the new app
|
|
127
|
+
if (this.appName === appName) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
let navigate = false;
|
|
131
|
+
if (!apps[appName]) {
|
|
132
|
+
let newContext = await getContext(null, false, this.logger, appName, false, this);
|
|
133
|
+
navigate = true;
|
|
134
|
+
apps[appName] = {
|
|
135
|
+
context: newContext,
|
|
136
|
+
browser: newContext.browser,
|
|
137
|
+
page: newContext.page,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
const tempContext = {};
|
|
141
|
+
this._copyContext(this, tempContext);
|
|
142
|
+
this._copyContext(apps[appName], this);
|
|
143
|
+
apps[this.appName] = tempContext;
|
|
144
|
+
this.appName = appName;
|
|
145
|
+
if (navigate) {
|
|
146
|
+
await this.goto(this.context.environment.baseUrl);
|
|
147
|
+
await this.waitForPageLoad();
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
_copyContext(from, to) {
|
|
151
|
+
to.browser = from.browser;
|
|
152
|
+
to.page = from.page;
|
|
153
|
+
to.context = from.context;
|
|
154
|
+
}
|
|
97
155
|
getWebLogFile(logFolder) {
|
|
98
156
|
if (!fs.existsSync(logFolder)) {
|
|
99
157
|
fs.mkdirSync(logFolder, { recursive: true });
|
|
@@ -105,37 +163,65 @@ class StableBrowser {
|
|
|
105
163
|
const fileName = nextIndex + ".json";
|
|
106
164
|
return path.join(logFolder, fileName);
|
|
107
165
|
}
|
|
108
|
-
registerConsoleLogListener(page, context
|
|
166
|
+
registerConsoleLogListener(page, context) {
|
|
109
167
|
if (!this.context.webLogger) {
|
|
110
168
|
this.context.webLogger = [];
|
|
111
169
|
}
|
|
112
170
|
page.on("console", async (msg) => {
|
|
113
|
-
|
|
171
|
+
var _a;
|
|
172
|
+
const obj = {
|
|
114
173
|
type: msg.type(),
|
|
115
174
|
text: msg.text(),
|
|
116
175
|
location: msg.location(),
|
|
117
176
|
time: new Date().toISOString(),
|
|
118
|
-
}
|
|
119
|
-
|
|
177
|
+
};
|
|
178
|
+
this.context.webLogger.push(obj);
|
|
179
|
+
if (msg.type() === "error") {
|
|
180
|
+
(_a = this.world) === null || _a === void 0 ? void 0 : _a.attach(JSON.stringify(obj), { mediaType: "application/json+log" });
|
|
181
|
+
}
|
|
120
182
|
});
|
|
121
183
|
}
|
|
122
|
-
registerRequestListener() {
|
|
123
|
-
this.
|
|
184
|
+
registerRequestListener(page, context, logFile) {
|
|
185
|
+
if (!this.context.networkLogger) {
|
|
186
|
+
this.context.networkLogger = [];
|
|
187
|
+
}
|
|
188
|
+
page.on("request", async (data) => {
|
|
189
|
+
var _a;
|
|
190
|
+
const startTime = new Date().getTime();
|
|
124
191
|
try {
|
|
125
|
-
const pageUrl = new URL(
|
|
192
|
+
const pageUrl = new URL(page.url());
|
|
126
193
|
const requestUrl = new URL(data.url());
|
|
127
194
|
if (pageUrl.hostname === requestUrl.hostname) {
|
|
128
195
|
const method = data.method();
|
|
129
|
-
if (
|
|
196
|
+
if (["POST", "GET", "PUT", "DELETE", "PATCH"].includes(method)) {
|
|
130
197
|
const token = await data.headerValue("Authorization");
|
|
131
198
|
if (token) {
|
|
132
|
-
|
|
199
|
+
context.authtoken = token;
|
|
133
200
|
}
|
|
134
201
|
}
|
|
135
202
|
}
|
|
203
|
+
const response = await data.response();
|
|
204
|
+
const endTime = new Date().getTime();
|
|
205
|
+
const obj = {
|
|
206
|
+
url: data.url(),
|
|
207
|
+
method: data.method(),
|
|
208
|
+
postData: data.postData(),
|
|
209
|
+
error: data.failure() ? data.failure().errorText : null,
|
|
210
|
+
duration: endTime - startTime,
|
|
211
|
+
startTime,
|
|
212
|
+
};
|
|
213
|
+
context.networkLogger.push(obj);
|
|
214
|
+
(_a = this.world) === null || _a === void 0 ? void 0 : _a.attach(JSON.stringify(obj), { mediaType: "application/json+network" });
|
|
136
215
|
}
|
|
137
216
|
catch (error) {
|
|
138
217
|
console.error("Error in request listener", error);
|
|
218
|
+
context.networkLogger.push({
|
|
219
|
+
error: "not able to listen",
|
|
220
|
+
message: error.message,
|
|
221
|
+
stack: error.stack,
|
|
222
|
+
time: new Date().toISOString(),
|
|
223
|
+
});
|
|
224
|
+
// await fs.promises.writeFile(logFile, JSON.stringify(context.networkLogger, null, 2));
|
|
139
225
|
}
|
|
140
226
|
});
|
|
141
227
|
}
|
|
@@ -178,26 +264,83 @@ class StableBrowser {
|
|
|
178
264
|
}
|
|
179
265
|
return text;
|
|
180
266
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
267
|
+
_fixLocatorUsingParams(locator, _params) {
|
|
268
|
+
// check if not null
|
|
269
|
+
if (!locator) {
|
|
270
|
+
return locator;
|
|
271
|
+
}
|
|
272
|
+
// clone the locator
|
|
273
|
+
locator = JSON.parse(JSON.stringify(locator));
|
|
274
|
+
this.scanAndManipulate(locator, _params);
|
|
275
|
+
return locator;
|
|
276
|
+
}
|
|
277
|
+
_isObject(value) {
|
|
278
|
+
return value && typeof value === "object" && value.constructor === Object;
|
|
279
|
+
}
|
|
280
|
+
scanAndManipulate(currentObj, _params) {
|
|
281
|
+
for (const key in currentObj) {
|
|
282
|
+
if (typeof currentObj[key] === "string") {
|
|
283
|
+
// Perform string manipulation
|
|
284
|
+
currentObj[key] = this._fixUsingParams(currentObj[key], _params);
|
|
285
|
+
}
|
|
286
|
+
else if (this._isObject(currentObj[key])) {
|
|
287
|
+
// Recursively scan nested objects
|
|
288
|
+
this.scanAndManipulate(currentObj[key], _params);
|
|
289
|
+
}
|
|
184
290
|
}
|
|
291
|
+
}
|
|
292
|
+
_getLocator(locator, scope, _params) {
|
|
293
|
+
locator = this._fixLocatorUsingParams(locator, _params);
|
|
294
|
+
let locatorReturn;
|
|
185
295
|
if (locator.role) {
|
|
186
296
|
if (locator.role[1].nameReg) {
|
|
187
297
|
locator.role[1].name = reg_parser(locator.role[1].nameReg);
|
|
188
298
|
delete locator.role[1].nameReg;
|
|
189
299
|
}
|
|
190
|
-
if (locator.role[1].name) {
|
|
191
|
-
|
|
192
|
-
}
|
|
193
|
-
|
|
300
|
+
// if (locator.role[1].name) {
|
|
301
|
+
// locator.role[1].name = this._fixUsingParams(locator.role[1].name, _params);
|
|
302
|
+
// }
|
|
303
|
+
locatorReturn = scope.getByRole(locator.role[0], locator.role[1]);
|
|
194
304
|
}
|
|
195
305
|
if (locator.css) {
|
|
196
|
-
|
|
306
|
+
locatorReturn = scope.locator(locator.css);
|
|
307
|
+
}
|
|
308
|
+
// handle role/name locators
|
|
309
|
+
// locator.selector will be something like: textbox[name="Username"i]
|
|
310
|
+
if (locator.engine === "internal:role") {
|
|
311
|
+
// extract the role, name and the i/s flags using regex
|
|
312
|
+
const match = locator.selector.match(/(.*)\[(.*)="(.*)"(.*)\]/);
|
|
313
|
+
if (match) {
|
|
314
|
+
const role = match[1];
|
|
315
|
+
const name = match[3];
|
|
316
|
+
const flags = match[4];
|
|
317
|
+
locatorReturn = scope.getByRole(role, { name }, { exact: flags === "i" });
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
if (locator === null || locator === void 0 ? void 0 : locator.engine) {
|
|
321
|
+
if (locator.engine === "css") {
|
|
322
|
+
locatorReturn = scope.locator(locator.selector);
|
|
323
|
+
}
|
|
324
|
+
else {
|
|
325
|
+
let selector = locator.selector;
|
|
326
|
+
if (locator.engine === "internal:attr") {
|
|
327
|
+
if (!selector.startsWith("[")) {
|
|
328
|
+
selector = `[${selector}]`;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
locatorReturn = scope.locator(`${locator.engine}=${selector}`);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
if (!locatorReturn) {
|
|
335
|
+
console.error(locator);
|
|
336
|
+
throw new Error("Locator undefined");
|
|
197
337
|
}
|
|
198
|
-
|
|
338
|
+
return locatorReturn;
|
|
199
339
|
}
|
|
200
340
|
async _locateElmentByTextClimbCss(scope, text, climb, css, _params) {
|
|
341
|
+
if (css && css.locator) {
|
|
342
|
+
css = css.locator;
|
|
343
|
+
}
|
|
201
344
|
let result = await this._locateElementByText(scope, this._fixUsingParams(text, _params), "*", false, true, _params);
|
|
202
345
|
if (result.elementCount === 0) {
|
|
203
346
|
return;
|
|
@@ -225,6 +368,15 @@ class StableBrowser {
|
|
|
225
368
|
return false;
|
|
226
369
|
}
|
|
227
370
|
document.isParent = isParent;
|
|
371
|
+
function getRegex(str) {
|
|
372
|
+
const match = str.match(/^\/(.*?)\/([gimuy]*)$/);
|
|
373
|
+
if (!match) {
|
|
374
|
+
return null;
|
|
375
|
+
}
|
|
376
|
+
let [_, pattern, flags] = match;
|
|
377
|
+
return new RegExp(pattern, flags);
|
|
378
|
+
}
|
|
379
|
+
document.getRegex = getRegex;
|
|
228
380
|
function collectAllShadowDomElements(element, result = []) {
|
|
229
381
|
// Check and add the element if it has a shadow root
|
|
230
382
|
if (element.shadowRoot) {
|
|
@@ -243,6 +395,10 @@ class StableBrowser {
|
|
|
243
395
|
if (!tag) {
|
|
244
396
|
tag = "*";
|
|
245
397
|
}
|
|
398
|
+
let regexpSearch = document.getRegex(text);
|
|
399
|
+
if (regexpSearch) {
|
|
400
|
+
regex = true;
|
|
401
|
+
}
|
|
246
402
|
let elements = Array.from(document.querySelectorAll(tag));
|
|
247
403
|
let shadowHosts = [];
|
|
248
404
|
document.collectAllShadowDomElements(document, shadowHosts);
|
|
@@ -258,7 +414,9 @@ class StableBrowser {
|
|
|
258
414
|
let randomToken = null;
|
|
259
415
|
const foundElements = [];
|
|
260
416
|
if (regex) {
|
|
261
|
-
|
|
417
|
+
if (!regexpSearch) {
|
|
418
|
+
regexpSearch = new RegExp(text, "im");
|
|
419
|
+
}
|
|
262
420
|
for (let i = 0; i < elements.length; i++) {
|
|
263
421
|
const element = elements[i];
|
|
264
422
|
if ((element.innerText && regexpSearch.test(element.innerText)) ||
|
|
@@ -272,8 +430,8 @@ class StableBrowser {
|
|
|
272
430
|
for (let i = 0; i < elements.length; i++) {
|
|
273
431
|
const element = elements[i];
|
|
274
432
|
if (partial) {
|
|
275
|
-
if ((element.innerText && element.innerText.trim().includes(text)) ||
|
|
276
|
-
(element.value && element.value.includes(text))) {
|
|
433
|
+
if ((element.innerText && element.innerText.toLowerCase().trim().includes(text.toLowerCase())) ||
|
|
434
|
+
(element.value && element.value.toLowerCase().includes(text.toLowerCase()))) {
|
|
277
435
|
foundElements.push(element);
|
|
278
436
|
}
|
|
279
437
|
}
|
|
@@ -318,6 +476,12 @@ class StableBrowser {
|
|
|
318
476
|
}
|
|
319
477
|
async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true) {
|
|
320
478
|
let locatorSearch = selectorHierarchy[index];
|
|
479
|
+
try {
|
|
480
|
+
locatorSearch = JSON.parse(this._fixUsingParams(JSON.stringify(locatorSearch), _params));
|
|
481
|
+
}
|
|
482
|
+
catch (e) {
|
|
483
|
+
console.error(e);
|
|
484
|
+
}
|
|
321
485
|
//info.log += "searching for locator " + JSON.stringify(locatorSearch) + "\n";
|
|
322
486
|
let locator = null;
|
|
323
487
|
if (locatorSearch.climb && locatorSearch.climb >= 0) {
|
|
@@ -406,6 +570,8 @@ class StableBrowser {
|
|
|
406
570
|
if (result.foundElements.length > 0) {
|
|
407
571
|
let dialogCloseLocator = result.foundElements[0].locator;
|
|
408
572
|
await dialogCloseLocator.click();
|
|
573
|
+
// wait for the dialog to close
|
|
574
|
+
await dialogCloseLocator.waitFor({ state: "hidden" });
|
|
409
575
|
return { rerun: true };
|
|
410
576
|
}
|
|
411
577
|
}
|
|
@@ -414,7 +580,7 @@ class StableBrowser {
|
|
|
414
580
|
}
|
|
415
581
|
async _locate(selectors, info, _params, timeout = 30000) {
|
|
416
582
|
for (let i = 0; i < 3; i++) {
|
|
417
|
-
info.log += "attempt " + i + ":
|
|
583
|
+
info.log += "attempt " + i + ": total locators " + selectors.locators.length + "\n";
|
|
418
584
|
for (let j = 0; j < selectors.locators.length; j++) {
|
|
419
585
|
let selector = selectors.locators[j];
|
|
420
586
|
info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
|
|
@@ -433,10 +599,44 @@ class StableBrowser {
|
|
|
433
599
|
let locatorsCount = 0;
|
|
434
600
|
//let arrayMode = Array.isArray(selectors);
|
|
435
601
|
let scope = this.page;
|
|
602
|
+
// for the simple click usecase
|
|
603
|
+
if (selectors.frame) {
|
|
604
|
+
scope = selectors.frame;
|
|
605
|
+
}
|
|
436
606
|
if (selectors.iframe_src || selectors.frameLocators) {
|
|
607
|
+
const findFrame = async (frame, framescope) => {
|
|
608
|
+
for (let i = 0; i < frame.selectors.length; i++) {
|
|
609
|
+
let frameLocator = frame.selectors[i];
|
|
610
|
+
if (frameLocator.css) {
|
|
611
|
+
let testframescope = framescope.frameLocator(frameLocator.css);
|
|
612
|
+
if (frameLocator.index) {
|
|
613
|
+
testframescope = framescope.nth(frameLocator.index);
|
|
614
|
+
}
|
|
615
|
+
try {
|
|
616
|
+
await testframescope.owner().evaluateHandle(() => true, null, {
|
|
617
|
+
timeout: 5000,
|
|
618
|
+
});
|
|
619
|
+
framescope = testframescope;
|
|
620
|
+
break;
|
|
621
|
+
}
|
|
622
|
+
catch (error) {
|
|
623
|
+
console.error("frame not found " + frameLocator.css);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
if (frame.children) {
|
|
628
|
+
return await findFrame(frame.children, framescope);
|
|
629
|
+
}
|
|
630
|
+
return framescope;
|
|
631
|
+
};
|
|
437
632
|
info.log += "searching for iframe " + selectors.iframe_src + "/" + selectors.frameLocators + "\n";
|
|
438
633
|
while (true) {
|
|
439
634
|
let frameFound = false;
|
|
635
|
+
if (selectors.nestFrmLoc) {
|
|
636
|
+
scope = await findFrame(selectors.nestFrmLoc, scope);
|
|
637
|
+
frameFound = true;
|
|
638
|
+
break;
|
|
639
|
+
}
|
|
440
640
|
if (selectors.frameLocators) {
|
|
441
641
|
for (let i = 0; i < selectors.frameLocators.length; i++) {
|
|
442
642
|
let frameLocator = selectors.frameLocators[i];
|
|
@@ -597,9 +797,72 @@ class StableBrowser {
|
|
|
597
797
|
}
|
|
598
798
|
return result;
|
|
599
799
|
}
|
|
800
|
+
async simpleClick(elementDescription, _params, options = {}, world = null) {
|
|
801
|
+
const startTime = Date.now();
|
|
802
|
+
let timeout = 30000;
|
|
803
|
+
if (options && options.timeout) {
|
|
804
|
+
timeout = options.timeout;
|
|
805
|
+
}
|
|
806
|
+
while (true) {
|
|
807
|
+
try {
|
|
808
|
+
const result = await locate_element(this.context, elementDescription, "click");
|
|
809
|
+
if ((result === null || result === void 0 ? void 0 : result.elementNumber) >= 0) {
|
|
810
|
+
const selectors = {
|
|
811
|
+
frame: result === null || result === void 0 ? void 0 : result.frame,
|
|
812
|
+
locators: [
|
|
813
|
+
{
|
|
814
|
+
css: result === null || result === void 0 ? void 0 : result.css,
|
|
815
|
+
},
|
|
816
|
+
],
|
|
817
|
+
};
|
|
818
|
+
await this.click(selectors, _params, options, world);
|
|
819
|
+
return;
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
catch (e) {
|
|
823
|
+
if (performance.now() - startTime > timeout) {
|
|
824
|
+
throw e;
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
async simpleClickType(elementDescription, value, _params, options = {}, world = null) {
|
|
831
|
+
const startTime = Date.now();
|
|
832
|
+
let timeout = 30000;
|
|
833
|
+
if (options && options.timeout) {
|
|
834
|
+
timeout = options.timeout;
|
|
835
|
+
}
|
|
836
|
+
while (true) {
|
|
837
|
+
try {
|
|
838
|
+
const result = await locate_element(this.context, elementDescription, "fill", value);
|
|
839
|
+
if ((result === null || result === void 0 ? void 0 : result.elementNumber) >= 0) {
|
|
840
|
+
const selectors = {
|
|
841
|
+
frame: result === null || result === void 0 ? void 0 : result.frame,
|
|
842
|
+
locators: [
|
|
843
|
+
{
|
|
844
|
+
css: result === null || result === void 0 ? void 0 : result.css,
|
|
845
|
+
},
|
|
846
|
+
],
|
|
847
|
+
};
|
|
848
|
+
await this.clickType(selectors, value, false, _params, options, world);
|
|
849
|
+
return;
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
catch (e) {
|
|
853
|
+
if (performance.now() - startTime > timeout) {
|
|
854
|
+
throw e;
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
858
|
+
}
|
|
859
|
+
}
|
|
600
860
|
async click(selectors, _params, options = {}, world = null) {
|
|
601
861
|
this._validateSelectors(selectors);
|
|
602
862
|
const startTime = Date.now();
|
|
863
|
+
if (options && options.context) {
|
|
864
|
+
selectors.locators[0].text = options.context;
|
|
865
|
+
}
|
|
603
866
|
const info = {};
|
|
604
867
|
info.log = "***** click on " + selectors.element_name + " *****\n";
|
|
605
868
|
info.operation = "click";
|
|
@@ -613,14 +876,14 @@ class StableBrowser {
|
|
|
613
876
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
614
877
|
try {
|
|
615
878
|
await this._highlightElements(element);
|
|
616
|
-
await element.click(
|
|
879
|
+
await element.click();
|
|
617
880
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
618
881
|
}
|
|
619
882
|
catch (e) {
|
|
620
883
|
// await this.closeUnexpectedPopups();
|
|
621
884
|
info.log += "click failed, will try again" + "\n";
|
|
622
885
|
element = await this._locate(selectors, info, _params);
|
|
623
|
-
await element.click
|
|
886
|
+
await element.dispatchEvent("click");
|
|
624
887
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
625
888
|
}
|
|
626
889
|
await this.waitForPageLoad();
|
|
@@ -673,7 +936,7 @@ class StableBrowser {
|
|
|
673
936
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
674
937
|
try {
|
|
675
938
|
await this._highlightElements(element);
|
|
676
|
-
await element.setChecked(checked
|
|
939
|
+
await element.setChecked(checked);
|
|
677
940
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
678
941
|
}
|
|
679
942
|
catch (e) {
|
|
@@ -737,7 +1000,7 @@ class StableBrowser {
|
|
|
737
1000
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
738
1001
|
try {
|
|
739
1002
|
await this._highlightElements(element);
|
|
740
|
-
await element.hover(
|
|
1003
|
+
await element.hover();
|
|
741
1004
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
742
1005
|
}
|
|
743
1006
|
catch (e) {
|
|
@@ -799,7 +1062,7 @@ class StableBrowser {
|
|
|
799
1062
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
800
1063
|
try {
|
|
801
1064
|
await this._highlightElements(element);
|
|
802
|
-
await element.selectOption(values
|
|
1065
|
+
await element.selectOption(values);
|
|
803
1066
|
}
|
|
804
1067
|
catch (e) {
|
|
805
1068
|
//await this.closeUnexpectedPopups();
|
|
@@ -908,71 +1171,45 @@ class StableBrowser {
|
|
|
908
1171
|
});
|
|
909
1172
|
}
|
|
910
1173
|
}
|
|
911
|
-
async
|
|
1174
|
+
async setInputValue(selectors, value, _params = null, options = {}, world = null) {
|
|
1175
|
+
// set input value for non fillable inputs like date, time, range, color, etc.
|
|
912
1176
|
this._validateSelectors(selectors);
|
|
913
1177
|
const startTime = Date.now();
|
|
914
|
-
let error = null;
|
|
915
|
-
let screenshotId = null;
|
|
916
|
-
let screenshotPath = null;
|
|
917
1178
|
const info = {};
|
|
918
|
-
info.log = "";
|
|
919
|
-
info.operation =
|
|
1179
|
+
info.log = "***** set input value " + selectors.element_name + " *****\n";
|
|
1180
|
+
info.operation = "setInputValue";
|
|
920
1181
|
info.selectors = selectors;
|
|
1182
|
+
value = this._fixUsingParams(value, _params);
|
|
921
1183
|
info.value = value;
|
|
1184
|
+
let error = null;
|
|
1185
|
+
let screenshotId = null;
|
|
1186
|
+
let screenshotPath = null;
|
|
922
1187
|
try {
|
|
923
1188
|
value = await this._replaceWithLocalData(value, this);
|
|
924
1189
|
let element = await this._locate(selectors, info, _params);
|
|
925
|
-
//insert red border around the element
|
|
926
1190
|
await this.scrollIfNeeded(element, info);
|
|
927
1191
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
928
1192
|
await this._highlightElements(element);
|
|
929
1193
|
try {
|
|
930
|
-
await element.
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
value = dayjs(value).format(format);
|
|
934
|
-
await element.fill(value);
|
|
935
|
-
}
|
|
936
|
-
else {
|
|
937
|
-
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
938
|
-
await element.evaluateHandle((el, dateTimeValue) => {
|
|
939
|
-
el.value = ""; // clear input
|
|
940
|
-
el.value = dateTimeValue;
|
|
941
|
-
}, dateTimeValue);
|
|
942
|
-
}
|
|
943
|
-
if (enter) {
|
|
944
|
-
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
945
|
-
await this.page.keyboard.press("Enter");
|
|
946
|
-
await this.waitForPageLoad();
|
|
947
|
-
}
|
|
1194
|
+
await element.evaluateHandle((el, value) => {
|
|
1195
|
+
el.value = value;
|
|
1196
|
+
}, value);
|
|
948
1197
|
}
|
|
949
1198
|
catch (error) {
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
this.logger.info("Trying again")(({ screenshotId, screenshotPath } = await this._screenShot(options, world, info)));
|
|
1199
|
+
this.logger.error("setInputValue failed, will try again");
|
|
1200
|
+
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
953
1201
|
info.screenshotPath = screenshotPath;
|
|
954
1202
|
Object.assign(error, { info: info });
|
|
955
|
-
await element.
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
value = dayjs(value).format(format);
|
|
959
|
-
await element.fill(value);
|
|
960
|
-
}
|
|
961
|
-
else {
|
|
962
|
-
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
963
|
-
await element.evaluateHandle((el, dateTimeValue) => {
|
|
964
|
-
el.value = ""; // clear input
|
|
965
|
-
el.value = dateTimeValue;
|
|
966
|
-
}, dateTimeValue);
|
|
967
|
-
}
|
|
968
|
-
if (enter) {
|
|
969
|
-
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
970
|
-
await this.page.keyboard.press("Enter");
|
|
971
|
-
await this.waitForPageLoad();
|
|
972
|
-
}
|
|
1203
|
+
await element.evaluateHandle((el, value) => {
|
|
1204
|
+
el.value = value;
|
|
1205
|
+
});
|
|
973
1206
|
}
|
|
974
1207
|
}
|
|
975
|
-
catch (
|
|
1208
|
+
catch (e) {
|
|
1209
|
+
this.logger.error("setInputValue failed " + info.log);
|
|
1210
|
+
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1211
|
+
info.screenshotPath = screenshotPath;
|
|
1212
|
+
Object.assign(e, { info: info });
|
|
976
1213
|
error = e;
|
|
977
1214
|
throw e;
|
|
978
1215
|
}
|
|
@@ -980,10 +1217,10 @@ class StableBrowser {
|
|
|
980
1217
|
const endTime = Date.now();
|
|
981
1218
|
this._reportToWorld(world, {
|
|
982
1219
|
element_name: selectors.element_name,
|
|
983
|
-
type: Types.
|
|
984
|
-
|
|
1220
|
+
type: Types.SET_INPUT,
|
|
1221
|
+
text: `Set input value`,
|
|
985
1222
|
value: value,
|
|
986
|
-
|
|
1223
|
+
screenshotId,
|
|
987
1224
|
result: error
|
|
988
1225
|
? {
|
|
989
1226
|
status: "FAILED",
|
|
@@ -1000,7 +1237,7 @@ class StableBrowser {
|
|
|
1000
1237
|
});
|
|
1001
1238
|
}
|
|
1002
1239
|
}
|
|
1003
|
-
async setDateTime(selectors, value, enter = false, _params = null, options = {}, world = null) {
|
|
1240
|
+
async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
|
|
1004
1241
|
this._validateSelectors(selectors);
|
|
1005
1242
|
const startTime = Date.now();
|
|
1006
1243
|
let error = null;
|
|
@@ -1012,6 +1249,7 @@ class StableBrowser {
|
|
|
1012
1249
|
info.selectors = selectors;
|
|
1013
1250
|
info.value = value;
|
|
1014
1251
|
try {
|
|
1252
|
+
value = await this._replaceWithLocalData(value, this);
|
|
1015
1253
|
let element = await this._locate(selectors, info, _params);
|
|
1016
1254
|
//insert red border around the element
|
|
1017
1255
|
await this.scrollIfNeeded(element, info);
|
|
@@ -1020,28 +1258,51 @@ class StableBrowser {
|
|
|
1020
1258
|
try {
|
|
1021
1259
|
await element.click();
|
|
1022
1260
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1261
|
+
if (format) {
|
|
1262
|
+
value = dayjs(value).format(format);
|
|
1263
|
+
await element.fill(value);
|
|
1264
|
+
}
|
|
1265
|
+
else {
|
|
1266
|
+
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
1267
|
+
await element.evaluateHandle((el, dateTimeValue) => {
|
|
1268
|
+
el.value = ""; // clear input
|
|
1269
|
+
el.value = dateTimeValue;
|
|
1270
|
+
}, dateTimeValue);
|
|
1271
|
+
}
|
|
1272
|
+
if (enter) {
|
|
1273
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1274
|
+
await this.page.keyboard.press("Enter");
|
|
1275
|
+
await this.waitForPageLoad();
|
|
1276
|
+
}
|
|
1028
1277
|
}
|
|
1029
|
-
catch (
|
|
1278
|
+
catch (err) {
|
|
1030
1279
|
//await this.closeUnexpectedPopups();
|
|
1031
1280
|
this.logger.error("setting date time input failed " + JSON.stringify(info));
|
|
1032
|
-
this.logger.info("Trying again")
|
|
1281
|
+
this.logger.info("Trying again");
|
|
1282
|
+
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1033
1283
|
info.screenshotPath = screenshotPath;
|
|
1034
|
-
Object.assign(
|
|
1284
|
+
Object.assign(err, { info: info });
|
|
1035
1285
|
await element.click();
|
|
1036
1286
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1287
|
+
if (format) {
|
|
1288
|
+
value = dayjs(value).format(format);
|
|
1289
|
+
await element.fill(value);
|
|
1290
|
+
}
|
|
1291
|
+
else {
|
|
1292
|
+
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
1293
|
+
await element.evaluateHandle((el, dateTimeValue) => {
|
|
1294
|
+
el.value = ""; // clear input
|
|
1295
|
+
el.value = dateTimeValue;
|
|
1296
|
+
}, dateTimeValue);
|
|
1297
|
+
}
|
|
1298
|
+
if (enter) {
|
|
1299
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1300
|
+
await this.page.keyboard.press("Enter");
|
|
1301
|
+
await this.waitForPageLoad();
|
|
1302
|
+
}
|
|
1042
1303
|
}
|
|
1043
1304
|
}
|
|
1044
|
-
catch (
|
|
1305
|
+
catch (e) {
|
|
1045
1306
|
error = e;
|
|
1046
1307
|
throw e;
|
|
1047
1308
|
}
|
|
@@ -1089,22 +1350,33 @@ class StableBrowser {
|
|
|
1089
1350
|
let element = await this._locate(selectors, info, _params);
|
|
1090
1351
|
//insert red border around the element
|
|
1091
1352
|
await this.scrollIfNeeded(element, info);
|
|
1092
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1093
1353
|
await this._highlightElements(element);
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1354
|
+
if (options === null || options === undefined || !options.press) {
|
|
1355
|
+
try {
|
|
1356
|
+
let currentValue = await element.inputValue();
|
|
1357
|
+
if (currentValue) {
|
|
1358
|
+
await element.fill("");
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
catch (e) {
|
|
1362
|
+
this.logger.info("unable to clear input value");
|
|
1098
1363
|
}
|
|
1099
1364
|
}
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1365
|
+
if (options === null || options === undefined || options.press) {
|
|
1366
|
+
try {
|
|
1367
|
+
await element.click({ timeout: 5000 });
|
|
1368
|
+
}
|
|
1369
|
+
catch (e) {
|
|
1370
|
+
await element.dispatchEvent("click");
|
|
1371
|
+
}
|
|
1105
1372
|
}
|
|
1106
|
-
|
|
1107
|
-
|
|
1373
|
+
else {
|
|
1374
|
+
try {
|
|
1375
|
+
await element.focus();
|
|
1376
|
+
}
|
|
1377
|
+
catch (e) {
|
|
1378
|
+
await element.dispatchEvent("focus");
|
|
1379
|
+
}
|
|
1108
1380
|
}
|
|
1109
1381
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1110
1382
|
const valueSegment = _value.split("&&");
|
|
@@ -1127,6 +1399,7 @@ class StableBrowser {
|
|
|
1127
1399
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1128
1400
|
}
|
|
1129
1401
|
}
|
|
1402
|
+
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1130
1403
|
if (enter === true) {
|
|
1131
1404
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1132
1405
|
await this.page.keyboard.press("Enter");
|
|
@@ -1192,7 +1465,7 @@ class StableBrowser {
|
|
|
1192
1465
|
let element = await this._locate(selectors, info, _params);
|
|
1193
1466
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1194
1467
|
await this._highlightElements(element);
|
|
1195
|
-
await element.fill(value
|
|
1468
|
+
await element.fill(value);
|
|
1196
1469
|
await element.dispatchEvent("change");
|
|
1197
1470
|
if (enter) {
|
|
1198
1471
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
@@ -1411,7 +1684,7 @@ class StableBrowser {
|
|
|
1411
1684
|
return info;
|
|
1412
1685
|
}
|
|
1413
1686
|
catch (e) {
|
|
1414
|
-
|
|
1687
|
+
await this.closeUnexpectedPopups();
|
|
1415
1688
|
this.logger.error("verify element contains text failed " + info.log);
|
|
1416
1689
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1417
1690
|
info.screenshotPath = screenshotPath;
|
|
@@ -1459,6 +1732,29 @@ class StableBrowser {
|
|
|
1459
1732
|
}
|
|
1460
1733
|
return dataFile;
|
|
1461
1734
|
}
|
|
1735
|
+
async waitForUserInput(message, world = null) {
|
|
1736
|
+
if (!message) {
|
|
1737
|
+
message = "# Wait for user input. Press any key to continue";
|
|
1738
|
+
}
|
|
1739
|
+
else {
|
|
1740
|
+
message = "# Wait for user input. " + message;
|
|
1741
|
+
}
|
|
1742
|
+
message += "\n";
|
|
1743
|
+
const value = await new Promise((resolve) => {
|
|
1744
|
+
const rl = readline.createInterface({
|
|
1745
|
+
input: process.stdin,
|
|
1746
|
+
output: process.stdout,
|
|
1747
|
+
});
|
|
1748
|
+
rl.question(message, (answer) => {
|
|
1749
|
+
rl.close();
|
|
1750
|
+
resolve(answer);
|
|
1751
|
+
});
|
|
1752
|
+
});
|
|
1753
|
+
if (value) {
|
|
1754
|
+
this.logger.info(`{{userInput}} was set to: ${value}`);
|
|
1755
|
+
}
|
|
1756
|
+
this.setTestData({ userInput: value }, world);
|
|
1757
|
+
}
|
|
1462
1758
|
setTestData(testData, world = null) {
|
|
1463
1759
|
if (!testData) {
|
|
1464
1760
|
return;
|
|
@@ -1486,7 +1782,7 @@ class StableBrowser {
|
|
|
1486
1782
|
const data = fs.readFileSync(filePath, "utf8");
|
|
1487
1783
|
const results = [];
|
|
1488
1784
|
return new Promise((resolve, reject) => {
|
|
1489
|
-
const readableStream = new
|
|
1785
|
+
const readableStream = new Readable();
|
|
1490
1786
|
readableStream._read = () => { }; // _read is required but you can noop it
|
|
1491
1787
|
readableStream.push(data);
|
|
1492
1788
|
readableStream.push(null);
|
|
@@ -1646,7 +1942,6 @@ class StableBrowser {
|
|
|
1646
1942
|
}
|
|
1647
1943
|
async takeScreenshot(screenshotPath) {
|
|
1648
1944
|
const playContext = this.context.playContext;
|
|
1649
|
-
const client = await playContext.newCDPSession(this.page);
|
|
1650
1945
|
// Using CDP to capture the screenshot
|
|
1651
1946
|
const viewportWidth = Math.max(...(await this.page.evaluate(() => [
|
|
1652
1947
|
document.body.scrollWidth,
|
|
@@ -1656,41 +1951,40 @@ class StableBrowser {
|
|
|
1656
1951
|
document.body.clientWidth,
|
|
1657
1952
|
document.documentElement.clientWidth,
|
|
1658
1953
|
])));
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
}
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
await client.detach();
|
|
1954
|
+
let screenshotBuffer = null;
|
|
1955
|
+
if (this.context.browserName === "chromium") {
|
|
1956
|
+
const client = await playContext.newCDPSession(this.page);
|
|
1957
|
+
const { data } = await client.send("Page.captureScreenshot", {
|
|
1958
|
+
format: "png",
|
|
1959
|
+
// clip: {
|
|
1960
|
+
// x: 0,
|
|
1961
|
+
// y: 0,
|
|
1962
|
+
// width: viewportWidth,
|
|
1963
|
+
// height: viewportHeight,
|
|
1964
|
+
// scale: 1,
|
|
1965
|
+
// },
|
|
1966
|
+
});
|
|
1967
|
+
await client.detach();
|
|
1968
|
+
if (!screenshotPath) {
|
|
1969
|
+
return data;
|
|
1970
|
+
}
|
|
1971
|
+
screenshotBuffer = Buffer.from(data, "base64");
|
|
1972
|
+
}
|
|
1973
|
+
else {
|
|
1974
|
+
screenshotBuffer = await this.page.screenshot();
|
|
1975
|
+
}
|
|
1976
|
+
let image = await Jimp.read(screenshotBuffer);
|
|
1977
|
+
// Get the image dimensions
|
|
1978
|
+
const { width, height } = image.bitmap;
|
|
1979
|
+
const resizeRatio = viewportWidth / width;
|
|
1980
|
+
// Resize the image to fit within the viewport dimensions without enlarging
|
|
1981
|
+
if (width > viewportWidth) {
|
|
1982
|
+
image = image.resize({ w: viewportWidth, h: height * resizeRatio }); // Resize the image while maintaining aspect ratio
|
|
1983
|
+
await image.write(screenshotPath);
|
|
1984
|
+
}
|
|
1985
|
+
else {
|
|
1986
|
+
fs.writeFileSync(screenshotPath, screenshotBuffer);
|
|
1987
|
+
}
|
|
1694
1988
|
}
|
|
1695
1989
|
async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
|
|
1696
1990
|
this._validateSelectors(selectors);
|
|
@@ -2498,13 +2792,13 @@ class StableBrowser {
|
|
|
2498
2792
|
}
|
|
2499
2793
|
catch (e) {
|
|
2500
2794
|
if (e.label === "networkidle") {
|
|
2501
|
-
console.log("
|
|
2795
|
+
console.log("waited for the network to be idle timeout");
|
|
2502
2796
|
}
|
|
2503
2797
|
else if (e.label === "load") {
|
|
2504
|
-
console.log("
|
|
2798
|
+
console.log("waited for the load timeout");
|
|
2505
2799
|
}
|
|
2506
2800
|
else if (e.label === "domcontentloaded") {
|
|
2507
|
-
console.log("
|
|
2801
|
+
console.log("waited for the domcontent loaded timeout");
|
|
2508
2802
|
}
|
|
2509
2803
|
console.log(".");
|
|
2510
2804
|
}
|
|
@@ -2539,13 +2833,6 @@ class StableBrowser {
|
|
|
2539
2833
|
const info = {};
|
|
2540
2834
|
try {
|
|
2541
2835
|
await this.page.close();
|
|
2542
|
-
if (this.context && this.context.pages && this.context.pages.length > 0) {
|
|
2543
|
-
this.context.pages.pop();
|
|
2544
|
-
this.page = this.context.pages[this.context.pages.length - 1];
|
|
2545
|
-
this.context.page = this.page;
|
|
2546
|
-
let title = await this.page.title();
|
|
2547
|
-
console.log("Switched to page " + title);
|
|
2548
|
-
}
|
|
2549
2836
|
}
|
|
2550
2837
|
catch (e) {
|
|
2551
2838
|
console.log(".");
|
|
@@ -2654,33 +2941,18 @@ class StableBrowser {
|
|
|
2654
2941
|
}
|
|
2655
2942
|
async scrollIfNeeded(element, info) {
|
|
2656
2943
|
try {
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
if (rect &&
|
|
2660
|
-
rect.top >= 0 &&
|
|
2661
|
-
rect.left >= 0 &&
|
|
2662
|
-
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
|
|
2663
|
-
rect.right <= (window.innerWidth || document.documentElement.clientWidth)) {
|
|
2664
|
-
return false;
|
|
2665
|
-
}
|
|
2666
|
-
else {
|
|
2667
|
-
node.scrollIntoView({
|
|
2668
|
-
behavior: "smooth",
|
|
2669
|
-
block: "center",
|
|
2670
|
-
inline: "center",
|
|
2671
|
-
});
|
|
2672
|
-
return true;
|
|
2673
|
-
}
|
|
2944
|
+
await element.scrollIntoViewIfNeeded({
|
|
2945
|
+
timeout: 2000,
|
|
2674
2946
|
});
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
}
|
|
2947
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
2948
|
+
if (info) {
|
|
2949
|
+
info.box = await element.boundingBox({
|
|
2950
|
+
timeout: 1000,
|
|
2951
|
+
});
|
|
2680
2952
|
}
|
|
2681
2953
|
}
|
|
2682
2954
|
catch (e) {
|
|
2683
|
-
console.log("
|
|
2955
|
+
console.log("#-#");
|
|
2684
2956
|
}
|
|
2685
2957
|
}
|
|
2686
2958
|
_reportToWorld(world, properties) {
|