automation_model 1.0.416-dev → 1.0.416-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/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 +14 -4
- package/lib/stable_browser.js +305 -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,9 @@ 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";
|
|
19
|
+
import { locate_element } from "./locate_element.js";
|
|
17
20
|
const Types = {
|
|
18
21
|
CLICK: "click_element",
|
|
19
22
|
NAVIGATE: "navigate",
|
|
@@ -41,15 +44,19 @@ const Types = {
|
|
|
41
44
|
LOAD_DATA: "load_data",
|
|
42
45
|
SET_INPUT: "set_input",
|
|
43
46
|
};
|
|
47
|
+
export const apps = {};
|
|
44
48
|
class StableBrowser {
|
|
45
|
-
constructor(browser, page, logger = null, context = null) {
|
|
49
|
+
constructor(browser, page, logger = null, context = null, world = null) {
|
|
46
50
|
this.browser = browser;
|
|
47
51
|
this.page = page;
|
|
48
52
|
this.logger = logger;
|
|
49
53
|
this.context = context;
|
|
54
|
+
this.world = world;
|
|
50
55
|
this.project_path = null;
|
|
51
56
|
this.webLogFile = null;
|
|
57
|
+
this.networkLogger = null;
|
|
52
58
|
this.configuration = null;
|
|
59
|
+
this.appName = "main";
|
|
53
60
|
if (!this.logger) {
|
|
54
61
|
this.logger = console;
|
|
55
62
|
}
|
|
@@ -75,23 +82,34 @@ class StableBrowser {
|
|
|
75
82
|
this.logger.error("unable to read ai_config.json");
|
|
76
83
|
}
|
|
77
84
|
const logFolder = path.join(this.project_path, "logs", "web");
|
|
78
|
-
this.
|
|
79
|
-
this.registerConsoleLogListener(page, context, this.webLogFile);
|
|
80
|
-
this.registerRequestListener();
|
|
85
|
+
this.world = world;
|
|
81
86
|
context.pages = [this.page];
|
|
82
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
|
+
}
|
|
83
96
|
context.playContext.on("page", async function (page) {
|
|
84
97
|
context.pageLoading.status = true;
|
|
85
98
|
this.page = page;
|
|
86
99
|
context.page = page;
|
|
87
100
|
context.pages.push(page);
|
|
88
101
|
page.on("close", async () => {
|
|
89
|
-
if (this.context && this.context.pages && this.context.pages.length >
|
|
102
|
+
if (this.context && this.context.pages && this.context.pages.length > 1) {
|
|
90
103
|
this.context.pages.pop();
|
|
91
104
|
this.page = this.context.pages[this.context.pages.length - 1];
|
|
92
105
|
this.context.page = this.page;
|
|
93
|
-
|
|
94
|
-
|
|
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
|
+
}
|
|
95
113
|
}
|
|
96
114
|
});
|
|
97
115
|
try {
|
|
@@ -104,6 +122,36 @@ class StableBrowser {
|
|
|
104
122
|
context.pageLoading.status = false;
|
|
105
123
|
}.bind(this));
|
|
106
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
|
+
}
|
|
107
155
|
getWebLogFile(logFolder) {
|
|
108
156
|
if (!fs.existsSync(logFolder)) {
|
|
109
157
|
fs.mkdirSync(logFolder, { recursive: true });
|
|
@@ -115,37 +163,63 @@ class StableBrowser {
|
|
|
115
163
|
const fileName = nextIndex + ".json";
|
|
116
164
|
return path.join(logFolder, fileName);
|
|
117
165
|
}
|
|
118
|
-
registerConsoleLogListener(page, context
|
|
166
|
+
registerConsoleLogListener(page, context) {
|
|
119
167
|
if (!this.context.webLogger) {
|
|
120
168
|
this.context.webLogger = [];
|
|
121
169
|
}
|
|
122
170
|
page.on("console", async (msg) => {
|
|
123
|
-
|
|
171
|
+
var _a;
|
|
172
|
+
const obj = {
|
|
124
173
|
type: msg.type(),
|
|
125
174
|
text: msg.text(),
|
|
126
175
|
location: msg.location(),
|
|
127
176
|
time: new Date().toISOString(),
|
|
128
|
-
}
|
|
129
|
-
|
|
177
|
+
};
|
|
178
|
+
this.context.webLogger.push(obj);
|
|
179
|
+
(_a = this.world) === null || _a === void 0 ? void 0 : _a.attach(JSON.stringify(obj), { mediaType: "application/json+log" });
|
|
130
180
|
});
|
|
131
181
|
}
|
|
132
|
-
registerRequestListener() {
|
|
133
|
-
this.
|
|
182
|
+
registerRequestListener(page, context, logFile) {
|
|
183
|
+
if (!this.context.networkLogger) {
|
|
184
|
+
this.context.networkLogger = [];
|
|
185
|
+
}
|
|
186
|
+
page.on("request", async (data) => {
|
|
187
|
+
var _a;
|
|
188
|
+
const startTime = new Date().getTime();
|
|
134
189
|
try {
|
|
135
|
-
const pageUrl = new URL(
|
|
190
|
+
const pageUrl = new URL(page.url());
|
|
136
191
|
const requestUrl = new URL(data.url());
|
|
137
192
|
if (pageUrl.hostname === requestUrl.hostname) {
|
|
138
193
|
const method = data.method();
|
|
139
|
-
if (
|
|
194
|
+
if (["POST", "GET", "PUT", "DELETE", "PATCH"].includes(method)) {
|
|
140
195
|
const token = await data.headerValue("Authorization");
|
|
141
196
|
if (token) {
|
|
142
|
-
|
|
197
|
+
context.authtoken = token;
|
|
143
198
|
}
|
|
144
199
|
}
|
|
145
200
|
}
|
|
201
|
+
const response = await data.response();
|
|
202
|
+
const endTime = new Date().getTime();
|
|
203
|
+
const obj = {
|
|
204
|
+
url: data.url(),
|
|
205
|
+
method: data.method(),
|
|
206
|
+
postData: data.postData(),
|
|
207
|
+
error: data.failure() ? data.failure().errorText : null,
|
|
208
|
+
duration: endTime - startTime,
|
|
209
|
+
startTime,
|
|
210
|
+
};
|
|
211
|
+
context.networkLogger.push(obj);
|
|
212
|
+
(_a = this.world) === null || _a === void 0 ? void 0 : _a.attach(JSON.stringify(obj), { mediaType: "application/json+network" });
|
|
146
213
|
}
|
|
147
214
|
catch (error) {
|
|
148
215
|
console.error("Error in request listener", error);
|
|
216
|
+
context.networkLogger.push({
|
|
217
|
+
error: "not able to listen",
|
|
218
|
+
message: error.message,
|
|
219
|
+
stack: error.stack,
|
|
220
|
+
time: new Date().toISOString(),
|
|
221
|
+
});
|
|
222
|
+
// await fs.promises.writeFile(logFile, JSON.stringify(context.networkLogger, null, 2));
|
|
149
223
|
}
|
|
150
224
|
});
|
|
151
225
|
}
|
|
@@ -226,7 +300,7 @@ class StableBrowser {
|
|
|
226
300
|
// }
|
|
227
301
|
locatorReturn = scope.getByRole(locator.role[0], locator.role[1]);
|
|
228
302
|
}
|
|
229
|
-
if (locator.css
|
|
303
|
+
if (locator.css) {
|
|
230
304
|
locatorReturn = scope.locator(locator.css);
|
|
231
305
|
}
|
|
232
306
|
// handle role/name locators
|
|
@@ -241,34 +315,30 @@ class StableBrowser {
|
|
|
241
315
|
locatorReturn = scope.getByRole(role, { name }, { exact: flags === "i" });
|
|
242
316
|
}
|
|
243
317
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
318
|
+
if (locator === null || locator === void 0 ? void 0 : locator.engine) {
|
|
319
|
+
if (locator.engine === "css") {
|
|
320
|
+
locatorReturn = scope.locator(locator.selector);
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
let selector = locator.selector;
|
|
324
|
+
if (locator.engine === "internal:attr") {
|
|
325
|
+
if (!selector.startsWith("[")) {
|
|
326
|
+
selector = `[${selector}]`;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
locatorReturn = scope.locator(`${locator.engine}=${selector}`);
|
|
252
330
|
}
|
|
253
|
-
}
|
|
254
|
-
if (locator.engine === "internal:attr") {
|
|
255
|
-
selector = `[${selector}]`;
|
|
256
|
-
locatorReturn = scope.locator(`${locator.engine}=${selector}`);
|
|
257
331
|
}
|
|
258
332
|
if (!locatorReturn) {
|
|
259
333
|
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
|
-
// }
|
|
334
|
+
throw new Error("Locator undefined");
|
|
268
335
|
}
|
|
269
336
|
return locatorReturn;
|
|
270
337
|
}
|
|
271
338
|
async _locateElmentByTextClimbCss(scope, text, climb, css, _params) {
|
|
339
|
+
if (css && css.locator) {
|
|
340
|
+
css = css.locator;
|
|
341
|
+
}
|
|
272
342
|
let result = await this._locateElementByText(scope, this._fixUsingParams(text, _params), "*", false, true, _params);
|
|
273
343
|
if (result.elementCount === 0) {
|
|
274
344
|
return;
|
|
@@ -296,6 +366,15 @@ class StableBrowser {
|
|
|
296
366
|
return false;
|
|
297
367
|
}
|
|
298
368
|
document.isParent = isParent;
|
|
369
|
+
function getRegex(str) {
|
|
370
|
+
const match = str.match(/^\/(.*?)\/([gimuy]*)$/);
|
|
371
|
+
if (!match) {
|
|
372
|
+
return null;
|
|
373
|
+
}
|
|
374
|
+
let [_, pattern, flags] = match;
|
|
375
|
+
return new RegExp(pattern, flags);
|
|
376
|
+
}
|
|
377
|
+
document.getRegex = getRegex;
|
|
299
378
|
function collectAllShadowDomElements(element, result = []) {
|
|
300
379
|
// Check and add the element if it has a shadow root
|
|
301
380
|
if (element.shadowRoot) {
|
|
@@ -314,6 +393,10 @@ class StableBrowser {
|
|
|
314
393
|
if (!tag) {
|
|
315
394
|
tag = "*";
|
|
316
395
|
}
|
|
396
|
+
let regexpSearch = document.getRegex(text);
|
|
397
|
+
if (regexpSearch) {
|
|
398
|
+
regex = true;
|
|
399
|
+
}
|
|
317
400
|
let elements = Array.from(document.querySelectorAll(tag));
|
|
318
401
|
let shadowHosts = [];
|
|
319
402
|
document.collectAllShadowDomElements(document, shadowHosts);
|
|
@@ -329,7 +412,9 @@ class StableBrowser {
|
|
|
329
412
|
let randomToken = null;
|
|
330
413
|
const foundElements = [];
|
|
331
414
|
if (regex) {
|
|
332
|
-
|
|
415
|
+
if (!regexpSearch) {
|
|
416
|
+
regexpSearch = new RegExp(text, "im");
|
|
417
|
+
}
|
|
333
418
|
for (let i = 0; i < elements.length; i++) {
|
|
334
419
|
const element = elements[i];
|
|
335
420
|
if ((element.innerText && regexpSearch.test(element.innerText)) ||
|
|
@@ -343,8 +428,8 @@ class StableBrowser {
|
|
|
343
428
|
for (let i = 0; i < elements.length; i++) {
|
|
344
429
|
const element = elements[i];
|
|
345
430
|
if (partial) {
|
|
346
|
-
if ((element.innerText && element.innerText.trim().includes(text)) ||
|
|
347
|
-
(element.value && element.value.includes(text))) {
|
|
431
|
+
if ((element.innerText && element.innerText.toLowerCase().trim().includes(text.toLowerCase())) ||
|
|
432
|
+
(element.value && element.value.toLowerCase().includes(text.toLowerCase()))) {
|
|
348
433
|
foundElements.push(element);
|
|
349
434
|
}
|
|
350
435
|
}
|
|
@@ -389,6 +474,12 @@ class StableBrowser {
|
|
|
389
474
|
}
|
|
390
475
|
async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true) {
|
|
391
476
|
let locatorSearch = selectorHierarchy[index];
|
|
477
|
+
try {
|
|
478
|
+
locatorSearch = JSON.parse(this._fixUsingParams(JSON.stringify(locatorSearch), _params));
|
|
479
|
+
}
|
|
480
|
+
catch (e) {
|
|
481
|
+
console.error(e);
|
|
482
|
+
}
|
|
392
483
|
//info.log += "searching for locator " + JSON.stringify(locatorSearch) + "\n";
|
|
393
484
|
let locator = null;
|
|
394
485
|
if (locatorSearch.climb && locatorSearch.climb >= 0) {
|
|
@@ -477,6 +568,8 @@ class StableBrowser {
|
|
|
477
568
|
if (result.foundElements.length > 0) {
|
|
478
569
|
let dialogCloseLocator = result.foundElements[0].locator;
|
|
479
570
|
await dialogCloseLocator.click();
|
|
571
|
+
// wait for the dialog to close
|
|
572
|
+
await dialogCloseLocator.waitFor({ state: "hidden" });
|
|
480
573
|
return { rerun: true };
|
|
481
574
|
}
|
|
482
575
|
}
|
|
@@ -485,7 +578,7 @@ class StableBrowser {
|
|
|
485
578
|
}
|
|
486
579
|
async _locate(selectors, info, _params, timeout = 30000) {
|
|
487
580
|
for (let i = 0; i < 3; i++) {
|
|
488
|
-
info.log += "attempt " + i + ":
|
|
581
|
+
info.log += "attempt " + i + ": total locators " + selectors.locators.length + "\n";
|
|
489
582
|
for (let j = 0; j < selectors.locators.length; j++) {
|
|
490
583
|
let selector = selectors.locators[j];
|
|
491
584
|
info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
|
|
@@ -504,10 +597,44 @@ class StableBrowser {
|
|
|
504
597
|
let locatorsCount = 0;
|
|
505
598
|
//let arrayMode = Array.isArray(selectors);
|
|
506
599
|
let scope = this.page;
|
|
600
|
+
// for the simple click usecase
|
|
601
|
+
if (selectors.frame) {
|
|
602
|
+
scope = selectors.frame;
|
|
603
|
+
}
|
|
507
604
|
if (selectors.iframe_src || selectors.frameLocators) {
|
|
605
|
+
const findFrame = async (frame, framescope) => {
|
|
606
|
+
for (let i = 0; i < frame.selectors.length; i++) {
|
|
607
|
+
let frameLocator = frame.selectors[i];
|
|
608
|
+
if (frameLocator.css) {
|
|
609
|
+
let testframescope = framescope.frameLocator(frameLocator.css);
|
|
610
|
+
if (frameLocator.index) {
|
|
611
|
+
testframescope = framescope.nth(frameLocator.index);
|
|
612
|
+
}
|
|
613
|
+
try {
|
|
614
|
+
await testframescope.owner().evaluateHandle(() => true, null, {
|
|
615
|
+
timeout: 5000,
|
|
616
|
+
});
|
|
617
|
+
framescope = testframescope;
|
|
618
|
+
break;
|
|
619
|
+
}
|
|
620
|
+
catch (error) {
|
|
621
|
+
console.error("frame not found " + frameLocator.css);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
if (frame.children) {
|
|
626
|
+
return await findFrame(frame.children, framescope);
|
|
627
|
+
}
|
|
628
|
+
return framescope;
|
|
629
|
+
};
|
|
508
630
|
info.log += "searching for iframe " + selectors.iframe_src + "/" + selectors.frameLocators + "\n";
|
|
509
631
|
while (true) {
|
|
510
632
|
let frameFound = false;
|
|
633
|
+
if (selectors.nestFrmLoc) {
|
|
634
|
+
scope = await findFrame(selectors.nestFrmLoc, scope);
|
|
635
|
+
frameFound = true;
|
|
636
|
+
break;
|
|
637
|
+
}
|
|
511
638
|
if (selectors.frameLocators) {
|
|
512
639
|
for (let i = 0; i < selectors.frameLocators.length; i++) {
|
|
513
640
|
let frameLocator = selectors.frameLocators[i];
|
|
@@ -668,6 +795,66 @@ class StableBrowser {
|
|
|
668
795
|
}
|
|
669
796
|
return result;
|
|
670
797
|
}
|
|
798
|
+
async simpleClick(elementDescription, _params, options = {}, world = null) {
|
|
799
|
+
const startTime = Date.now();
|
|
800
|
+
let timeout = 30000;
|
|
801
|
+
if (options && options.timeout) {
|
|
802
|
+
timeout = options.timeout;
|
|
803
|
+
}
|
|
804
|
+
while (true) {
|
|
805
|
+
try {
|
|
806
|
+
const result = await locate_element(this.context, elementDescription, "click");
|
|
807
|
+
if ((result === null || result === void 0 ? void 0 : result.elementNumber) >= 0) {
|
|
808
|
+
const selectors = {
|
|
809
|
+
frame: result === null || result === void 0 ? void 0 : result.frame,
|
|
810
|
+
locators: [
|
|
811
|
+
{
|
|
812
|
+
css: result === null || result === void 0 ? void 0 : result.css,
|
|
813
|
+
},
|
|
814
|
+
],
|
|
815
|
+
};
|
|
816
|
+
await this.click(selectors, _params, options, world);
|
|
817
|
+
return;
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
catch (e) {
|
|
821
|
+
if (performance.now() - startTime > timeout) {
|
|
822
|
+
throw e;
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
async simpleClickType(elementDescription, value, _params, options = {}, world = null) {
|
|
829
|
+
const startTime = Date.now();
|
|
830
|
+
let timeout = 30000;
|
|
831
|
+
if (options && options.timeout) {
|
|
832
|
+
timeout = options.timeout;
|
|
833
|
+
}
|
|
834
|
+
while (true) {
|
|
835
|
+
try {
|
|
836
|
+
const result = await locate_element(this.context, elementDescription, "fill", value);
|
|
837
|
+
if ((result === null || result === void 0 ? void 0 : result.elementNumber) >= 0) {
|
|
838
|
+
const selectors = {
|
|
839
|
+
frame: result === null || result === void 0 ? void 0 : result.frame,
|
|
840
|
+
locators: [
|
|
841
|
+
{
|
|
842
|
+
css: result === null || result === void 0 ? void 0 : result.css,
|
|
843
|
+
},
|
|
844
|
+
],
|
|
845
|
+
};
|
|
846
|
+
await this.clickType(selectors, value, false, _params, options, world);
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
catch (e) {
|
|
851
|
+
if (performance.now() - startTime > timeout) {
|
|
852
|
+
throw e;
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
856
|
+
}
|
|
857
|
+
}
|
|
671
858
|
async click(selectors, _params, options = {}, world = null) {
|
|
672
859
|
this._validateSelectors(selectors);
|
|
673
860
|
const startTime = Date.now();
|
|
@@ -687,14 +874,14 @@ class StableBrowser {
|
|
|
687
874
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
688
875
|
try {
|
|
689
876
|
await this._highlightElements(element);
|
|
690
|
-
await element.click(
|
|
877
|
+
await element.click();
|
|
691
878
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
692
879
|
}
|
|
693
880
|
catch (e) {
|
|
694
881
|
// await this.closeUnexpectedPopups();
|
|
695
882
|
info.log += "click failed, will try again" + "\n";
|
|
696
883
|
element = await this._locate(selectors, info, _params);
|
|
697
|
-
await element.click
|
|
884
|
+
await element.dispatchEvent("click");
|
|
698
885
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
699
886
|
}
|
|
700
887
|
await this.waitForPageLoad();
|
|
@@ -747,7 +934,7 @@ class StableBrowser {
|
|
|
747
934
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
748
935
|
try {
|
|
749
936
|
await this._highlightElements(element);
|
|
750
|
-
await element.setChecked(checked
|
|
937
|
+
await element.setChecked(checked);
|
|
751
938
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
752
939
|
}
|
|
753
940
|
catch (e) {
|
|
@@ -811,7 +998,7 @@ class StableBrowser {
|
|
|
811
998
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
812
999
|
try {
|
|
813
1000
|
await this._highlightElements(element);
|
|
814
|
-
await element.hover(
|
|
1001
|
+
await element.hover();
|
|
815
1002
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
816
1003
|
}
|
|
817
1004
|
catch (e) {
|
|
@@ -873,7 +1060,7 @@ class StableBrowser {
|
|
|
873
1060
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
874
1061
|
try {
|
|
875
1062
|
await this._highlightElements(element);
|
|
876
|
-
await element.selectOption(values
|
|
1063
|
+
await element.selectOption(values);
|
|
877
1064
|
}
|
|
878
1065
|
catch (e) {
|
|
879
1066
|
//await this.closeUnexpectedPopups();
|
|
@@ -1161,7 +1348,6 @@ class StableBrowser {
|
|
|
1161
1348
|
let element = await this._locate(selectors, info, _params);
|
|
1162
1349
|
//insert red border around the element
|
|
1163
1350
|
await this.scrollIfNeeded(element, info);
|
|
1164
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1165
1351
|
await this._highlightElements(element);
|
|
1166
1352
|
if (options === null || options === undefined || !options.press) {
|
|
1167
1353
|
try {
|
|
@@ -1211,6 +1397,7 @@ class StableBrowser {
|
|
|
1211
1397
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1212
1398
|
}
|
|
1213
1399
|
}
|
|
1400
|
+
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1214
1401
|
if (enter === true) {
|
|
1215
1402
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1216
1403
|
await this.page.keyboard.press("Enter");
|
|
@@ -1276,7 +1463,7 @@ class StableBrowser {
|
|
|
1276
1463
|
let element = await this._locate(selectors, info, _params);
|
|
1277
1464
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1278
1465
|
await this._highlightElements(element);
|
|
1279
|
-
await element.fill(value
|
|
1466
|
+
await element.fill(value);
|
|
1280
1467
|
await element.dispatchEvent("change");
|
|
1281
1468
|
if (enter) {
|
|
1282
1469
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
@@ -1495,7 +1682,7 @@ class StableBrowser {
|
|
|
1495
1682
|
return info;
|
|
1496
1683
|
}
|
|
1497
1684
|
catch (e) {
|
|
1498
|
-
|
|
1685
|
+
await this.closeUnexpectedPopups();
|
|
1499
1686
|
this.logger.error("verify element contains text failed " + info.log);
|
|
1500
1687
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1501
1688
|
info.screenshotPath = screenshotPath;
|
|
@@ -1543,6 +1730,29 @@ class StableBrowser {
|
|
|
1543
1730
|
}
|
|
1544
1731
|
return dataFile;
|
|
1545
1732
|
}
|
|
1733
|
+
async waitForUserInput(message, world = null) {
|
|
1734
|
+
if (!message) {
|
|
1735
|
+
message = "# Wait for user input. Press any key to continue";
|
|
1736
|
+
}
|
|
1737
|
+
else {
|
|
1738
|
+
message = "# Wait for user input. " + message;
|
|
1739
|
+
}
|
|
1740
|
+
message += "\n";
|
|
1741
|
+
const value = await new Promise((resolve) => {
|
|
1742
|
+
const rl = readline.createInterface({
|
|
1743
|
+
input: process.stdin,
|
|
1744
|
+
output: process.stdout,
|
|
1745
|
+
});
|
|
1746
|
+
rl.question(message, (answer) => {
|
|
1747
|
+
rl.close();
|
|
1748
|
+
resolve(answer);
|
|
1749
|
+
});
|
|
1750
|
+
});
|
|
1751
|
+
if (value) {
|
|
1752
|
+
this.logger.info(`{{userInput}} was set to: ${value}`);
|
|
1753
|
+
}
|
|
1754
|
+
this.setTestData({ userInput: value }, world);
|
|
1755
|
+
}
|
|
1546
1756
|
setTestData(testData, world = null) {
|
|
1547
1757
|
if (!testData) {
|
|
1548
1758
|
return;
|
|
@@ -1730,7 +1940,6 @@ class StableBrowser {
|
|
|
1730
1940
|
}
|
|
1731
1941
|
async takeScreenshot(screenshotPath) {
|
|
1732
1942
|
const playContext = this.context.playContext;
|
|
1733
|
-
const client = await playContext.newCDPSession(this.page);
|
|
1734
1943
|
// Using CDP to capture the screenshot
|
|
1735
1944
|
const viewportWidth = Math.max(...(await this.page.evaluate(() => [
|
|
1736
1945
|
document.body.scrollWidth,
|
|
@@ -1740,41 +1949,40 @@ class StableBrowser {
|
|
|
1740
1949
|
document.body.clientWidth,
|
|
1741
1950
|
document.documentElement.clientWidth,
|
|
1742
1951
|
])));
|
|
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();
|
|
1952
|
+
let screenshotBuffer = null;
|
|
1953
|
+
if (this.context.browserName === "chromium") {
|
|
1954
|
+
const client = await playContext.newCDPSession(this.page);
|
|
1955
|
+
const { data } = await client.send("Page.captureScreenshot", {
|
|
1956
|
+
format: "png",
|
|
1957
|
+
// clip: {
|
|
1958
|
+
// x: 0,
|
|
1959
|
+
// y: 0,
|
|
1960
|
+
// width: viewportWidth,
|
|
1961
|
+
// height: viewportHeight,
|
|
1962
|
+
// scale: 1,
|
|
1963
|
+
// },
|
|
1964
|
+
});
|
|
1965
|
+
await client.detach();
|
|
1966
|
+
if (!screenshotPath) {
|
|
1967
|
+
return data;
|
|
1968
|
+
}
|
|
1969
|
+
screenshotBuffer = Buffer.from(data, "base64");
|
|
1970
|
+
}
|
|
1971
|
+
else {
|
|
1972
|
+
screenshotBuffer = await this.page.screenshot();
|
|
1973
|
+
}
|
|
1974
|
+
let image = await Jimp.read(screenshotBuffer);
|
|
1975
|
+
// Get the image dimensions
|
|
1976
|
+
const { width, height } = image.bitmap;
|
|
1977
|
+
const resizeRatio = viewportWidth / width;
|
|
1978
|
+
// Resize the image to fit within the viewport dimensions without enlarging
|
|
1979
|
+
if (width > viewportWidth) {
|
|
1980
|
+
image = image.resize({ w: viewportWidth, h: height * resizeRatio }); // Resize the image while maintaining aspect ratio
|
|
1981
|
+
await image.write(screenshotPath);
|
|
1982
|
+
}
|
|
1983
|
+
else {
|
|
1984
|
+
fs.writeFileSync(screenshotPath, screenshotBuffer);
|
|
1985
|
+
}
|
|
1778
1986
|
}
|
|
1779
1987
|
async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
|
|
1780
1988
|
this._validateSelectors(selectors);
|
|
@@ -2582,13 +2790,13 @@ class StableBrowser {
|
|
|
2582
2790
|
}
|
|
2583
2791
|
catch (e) {
|
|
2584
2792
|
if (e.label === "networkidle") {
|
|
2585
|
-
console.log("
|
|
2793
|
+
console.log("waited for the network to be idle timeout");
|
|
2586
2794
|
}
|
|
2587
2795
|
else if (e.label === "load") {
|
|
2588
|
-
console.log("
|
|
2796
|
+
console.log("waited for the load timeout");
|
|
2589
2797
|
}
|
|
2590
2798
|
else if (e.label === "domcontentloaded") {
|
|
2591
|
-
console.log("
|
|
2799
|
+
console.log("waited for the domcontent loaded timeout");
|
|
2592
2800
|
}
|
|
2593
2801
|
console.log(".");
|
|
2594
2802
|
}
|
|
@@ -2731,33 +2939,18 @@ class StableBrowser {
|
|
|
2731
2939
|
}
|
|
2732
2940
|
async scrollIfNeeded(element, info) {
|
|
2733
2941
|
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
|
-
}
|
|
2942
|
+
await element.scrollIntoViewIfNeeded({
|
|
2943
|
+
timeout: 2000,
|
|
2751
2944
|
});
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
}
|
|
2945
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
2946
|
+
if (info) {
|
|
2947
|
+
info.box = await element.boundingBox({
|
|
2948
|
+
timeout: 1000,
|
|
2949
|
+
});
|
|
2757
2950
|
}
|
|
2758
2951
|
}
|
|
2759
2952
|
catch (e) {
|
|
2760
|
-
console.log("
|
|
2953
|
+
console.log("#-#");
|
|
2761
2954
|
}
|
|
2762
2955
|
}
|
|
2763
2956
|
_reportToWorld(world, properties) {
|