automation_model 1.0.417-dev → 1.0.417-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 +13 -3
- package/lib/stable_browser.js +296 -92
- 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,65 @@ 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
|
+
if (msg.type() === "error") {
|
|
180
|
+
(_a = this.world) === null || _a === void 0 ? void 0 : _a.attach(JSON.stringify(obj), { mediaType: "application/json+log" });
|
|
181
|
+
}
|
|
130
182
|
});
|
|
131
183
|
}
|
|
132
|
-
registerRequestListener() {
|
|
133
|
-
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();
|
|
134
191
|
try {
|
|
135
|
-
const pageUrl = new URL(
|
|
192
|
+
const pageUrl = new URL(page.url());
|
|
136
193
|
const requestUrl = new URL(data.url());
|
|
137
194
|
if (pageUrl.hostname === requestUrl.hostname) {
|
|
138
195
|
const method = data.method();
|
|
139
|
-
if (
|
|
196
|
+
if (["POST", "GET", "PUT", "DELETE", "PATCH"].includes(method)) {
|
|
140
197
|
const token = await data.headerValue("Authorization");
|
|
141
198
|
if (token) {
|
|
142
|
-
|
|
199
|
+
context.authtoken = token;
|
|
143
200
|
}
|
|
144
201
|
}
|
|
145
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" });
|
|
146
215
|
}
|
|
147
216
|
catch (error) {
|
|
148
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));
|
|
149
225
|
}
|
|
150
226
|
});
|
|
151
227
|
}
|
|
@@ -248,7 +324,9 @@ class StableBrowser {
|
|
|
248
324
|
else {
|
|
249
325
|
let selector = locator.selector;
|
|
250
326
|
if (locator.engine === "internal:attr") {
|
|
251
|
-
selector
|
|
327
|
+
if (!selector.startsWith("[")) {
|
|
328
|
+
selector = `[${selector}]`;
|
|
329
|
+
}
|
|
252
330
|
}
|
|
253
331
|
locatorReturn = scope.locator(`${locator.engine}=${selector}`);
|
|
254
332
|
}
|
|
@@ -260,6 +338,9 @@ class StableBrowser {
|
|
|
260
338
|
return locatorReturn;
|
|
261
339
|
}
|
|
262
340
|
async _locateElmentByTextClimbCss(scope, text, climb, css, _params) {
|
|
341
|
+
if (css && css.locator) {
|
|
342
|
+
css = css.locator;
|
|
343
|
+
}
|
|
263
344
|
let result = await this._locateElementByText(scope, this._fixUsingParams(text, _params), "*", false, true, _params);
|
|
264
345
|
if (result.elementCount === 0) {
|
|
265
346
|
return;
|
|
@@ -287,6 +368,15 @@ class StableBrowser {
|
|
|
287
368
|
return false;
|
|
288
369
|
}
|
|
289
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;
|
|
290
380
|
function collectAllShadowDomElements(element, result = []) {
|
|
291
381
|
// Check and add the element if it has a shadow root
|
|
292
382
|
if (element.shadowRoot) {
|
|
@@ -305,6 +395,10 @@ class StableBrowser {
|
|
|
305
395
|
if (!tag) {
|
|
306
396
|
tag = "*";
|
|
307
397
|
}
|
|
398
|
+
let regexpSearch = document.getRegex(text);
|
|
399
|
+
if (regexpSearch) {
|
|
400
|
+
regex = true;
|
|
401
|
+
}
|
|
308
402
|
let elements = Array.from(document.querySelectorAll(tag));
|
|
309
403
|
let shadowHosts = [];
|
|
310
404
|
document.collectAllShadowDomElements(document, shadowHosts);
|
|
@@ -320,7 +414,9 @@ class StableBrowser {
|
|
|
320
414
|
let randomToken = null;
|
|
321
415
|
const foundElements = [];
|
|
322
416
|
if (regex) {
|
|
323
|
-
|
|
417
|
+
if (!regexpSearch) {
|
|
418
|
+
regexpSearch = new RegExp(text, "im");
|
|
419
|
+
}
|
|
324
420
|
for (let i = 0; i < elements.length; i++) {
|
|
325
421
|
const element = elements[i];
|
|
326
422
|
if ((element.innerText && regexpSearch.test(element.innerText)) ||
|
|
@@ -334,8 +430,8 @@ class StableBrowser {
|
|
|
334
430
|
for (let i = 0; i < elements.length; i++) {
|
|
335
431
|
const element = elements[i];
|
|
336
432
|
if (partial) {
|
|
337
|
-
if ((element.innerText && element.innerText.trim().includes(text)) ||
|
|
338
|
-
(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()))) {
|
|
339
435
|
foundElements.push(element);
|
|
340
436
|
}
|
|
341
437
|
}
|
|
@@ -380,6 +476,12 @@ class StableBrowser {
|
|
|
380
476
|
}
|
|
381
477
|
async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true) {
|
|
382
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
|
+
}
|
|
383
485
|
//info.log += "searching for locator " + JSON.stringify(locatorSearch) + "\n";
|
|
384
486
|
let locator = null;
|
|
385
487
|
if (locatorSearch.climb && locatorSearch.climb >= 0) {
|
|
@@ -468,6 +570,8 @@ class StableBrowser {
|
|
|
468
570
|
if (result.foundElements.length > 0) {
|
|
469
571
|
let dialogCloseLocator = result.foundElements[0].locator;
|
|
470
572
|
await dialogCloseLocator.click();
|
|
573
|
+
// wait for the dialog to close
|
|
574
|
+
await dialogCloseLocator.waitFor({ state: "hidden" });
|
|
471
575
|
return { rerun: true };
|
|
472
576
|
}
|
|
473
577
|
}
|
|
@@ -476,7 +580,7 @@ class StableBrowser {
|
|
|
476
580
|
}
|
|
477
581
|
async _locate(selectors, info, _params, timeout = 30000) {
|
|
478
582
|
for (let i = 0; i < 3; i++) {
|
|
479
|
-
info.log += "attempt " + i + ":
|
|
583
|
+
info.log += "attempt " + i + ": total locators " + selectors.locators.length + "\n";
|
|
480
584
|
for (let j = 0; j < selectors.locators.length; j++) {
|
|
481
585
|
let selector = selectors.locators[j];
|
|
482
586
|
info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
|
|
@@ -495,10 +599,44 @@ class StableBrowser {
|
|
|
495
599
|
let locatorsCount = 0;
|
|
496
600
|
//let arrayMode = Array.isArray(selectors);
|
|
497
601
|
let scope = this.page;
|
|
602
|
+
// for the simple click usecase
|
|
603
|
+
if (selectors.frame) {
|
|
604
|
+
scope = selectors.frame;
|
|
605
|
+
}
|
|
498
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
|
+
};
|
|
499
632
|
info.log += "searching for iframe " + selectors.iframe_src + "/" + selectors.frameLocators + "\n";
|
|
500
633
|
while (true) {
|
|
501
634
|
let frameFound = false;
|
|
635
|
+
if (selectors.nestFrmLoc) {
|
|
636
|
+
scope = await findFrame(selectors.nestFrmLoc, scope);
|
|
637
|
+
frameFound = true;
|
|
638
|
+
break;
|
|
639
|
+
}
|
|
502
640
|
if (selectors.frameLocators) {
|
|
503
641
|
for (let i = 0; i < selectors.frameLocators.length; i++) {
|
|
504
642
|
let frameLocator = selectors.frameLocators[i];
|
|
@@ -659,6 +797,66 @@ class StableBrowser {
|
|
|
659
797
|
}
|
|
660
798
|
return result;
|
|
661
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
|
+
}
|
|
662
860
|
async click(selectors, _params, options = {}, world = null) {
|
|
663
861
|
this._validateSelectors(selectors);
|
|
664
862
|
const startTime = Date.now();
|
|
@@ -678,14 +876,14 @@ class StableBrowser {
|
|
|
678
876
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
679
877
|
try {
|
|
680
878
|
await this._highlightElements(element);
|
|
681
|
-
await element.click(
|
|
879
|
+
await element.click();
|
|
682
880
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
683
881
|
}
|
|
684
882
|
catch (e) {
|
|
685
883
|
// await this.closeUnexpectedPopups();
|
|
686
884
|
info.log += "click failed, will try again" + "\n";
|
|
687
885
|
element = await this._locate(selectors, info, _params);
|
|
688
|
-
await element.click
|
|
886
|
+
await element.dispatchEvent("click");
|
|
689
887
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
690
888
|
}
|
|
691
889
|
await this.waitForPageLoad();
|
|
@@ -738,7 +936,7 @@ class StableBrowser {
|
|
|
738
936
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
739
937
|
try {
|
|
740
938
|
await this._highlightElements(element);
|
|
741
|
-
await element.setChecked(checked
|
|
939
|
+
await element.setChecked(checked);
|
|
742
940
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
743
941
|
}
|
|
744
942
|
catch (e) {
|
|
@@ -802,7 +1000,7 @@ class StableBrowser {
|
|
|
802
1000
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
803
1001
|
try {
|
|
804
1002
|
await this._highlightElements(element);
|
|
805
|
-
await element.hover(
|
|
1003
|
+
await element.hover();
|
|
806
1004
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
807
1005
|
}
|
|
808
1006
|
catch (e) {
|
|
@@ -864,7 +1062,7 @@ class StableBrowser {
|
|
|
864
1062
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
865
1063
|
try {
|
|
866
1064
|
await this._highlightElements(element);
|
|
867
|
-
await element.selectOption(values
|
|
1065
|
+
await element.selectOption(values);
|
|
868
1066
|
}
|
|
869
1067
|
catch (e) {
|
|
870
1068
|
//await this.closeUnexpectedPopups();
|
|
@@ -1152,7 +1350,6 @@ class StableBrowser {
|
|
|
1152
1350
|
let element = await this._locate(selectors, info, _params);
|
|
1153
1351
|
//insert red border around the element
|
|
1154
1352
|
await this.scrollIfNeeded(element, info);
|
|
1155
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1156
1353
|
await this._highlightElements(element);
|
|
1157
1354
|
if (options === null || options === undefined || !options.press) {
|
|
1158
1355
|
try {
|
|
@@ -1202,6 +1399,7 @@ class StableBrowser {
|
|
|
1202
1399
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1203
1400
|
}
|
|
1204
1401
|
}
|
|
1402
|
+
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1205
1403
|
if (enter === true) {
|
|
1206
1404
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1207
1405
|
await this.page.keyboard.press("Enter");
|
|
@@ -1267,7 +1465,7 @@ class StableBrowser {
|
|
|
1267
1465
|
let element = await this._locate(selectors, info, _params);
|
|
1268
1466
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1269
1467
|
await this._highlightElements(element);
|
|
1270
|
-
await element.fill(value
|
|
1468
|
+
await element.fill(value);
|
|
1271
1469
|
await element.dispatchEvent("change");
|
|
1272
1470
|
if (enter) {
|
|
1273
1471
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
@@ -1486,7 +1684,7 @@ class StableBrowser {
|
|
|
1486
1684
|
return info;
|
|
1487
1685
|
}
|
|
1488
1686
|
catch (e) {
|
|
1489
|
-
|
|
1687
|
+
await this.closeUnexpectedPopups();
|
|
1490
1688
|
this.logger.error("verify element contains text failed " + info.log);
|
|
1491
1689
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1492
1690
|
info.screenshotPath = screenshotPath;
|
|
@@ -1534,6 +1732,29 @@ class StableBrowser {
|
|
|
1534
1732
|
}
|
|
1535
1733
|
return dataFile;
|
|
1536
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
|
+
}
|
|
1537
1758
|
setTestData(testData, world = null) {
|
|
1538
1759
|
if (!testData) {
|
|
1539
1760
|
return;
|
|
@@ -1721,7 +1942,6 @@ class StableBrowser {
|
|
|
1721
1942
|
}
|
|
1722
1943
|
async takeScreenshot(screenshotPath) {
|
|
1723
1944
|
const playContext = this.context.playContext;
|
|
1724
|
-
const client = await playContext.newCDPSession(this.page);
|
|
1725
1945
|
// Using CDP to capture the screenshot
|
|
1726
1946
|
const viewportWidth = Math.max(...(await this.page.evaluate(() => [
|
|
1727
1947
|
document.body.scrollWidth,
|
|
@@ -1731,41 +1951,40 @@ class StableBrowser {
|
|
|
1731
1951
|
document.body.clientWidth,
|
|
1732
1952
|
document.documentElement.clientWidth,
|
|
1733
1953
|
])));
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
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
|
+
}
|
|
1769
1988
|
}
|
|
1770
1989
|
async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
|
|
1771
1990
|
this._validateSelectors(selectors);
|
|
@@ -2573,13 +2792,13 @@ class StableBrowser {
|
|
|
2573
2792
|
}
|
|
2574
2793
|
catch (e) {
|
|
2575
2794
|
if (e.label === "networkidle") {
|
|
2576
|
-
console.log("
|
|
2795
|
+
console.log("waited for the network to be idle timeout");
|
|
2577
2796
|
}
|
|
2578
2797
|
else if (e.label === "load") {
|
|
2579
|
-
console.log("
|
|
2798
|
+
console.log("waited for the load timeout");
|
|
2580
2799
|
}
|
|
2581
2800
|
else if (e.label === "domcontentloaded") {
|
|
2582
|
-
console.log("
|
|
2801
|
+
console.log("waited for the domcontent loaded timeout");
|
|
2583
2802
|
}
|
|
2584
2803
|
console.log(".");
|
|
2585
2804
|
}
|
|
@@ -2722,33 +2941,18 @@ class StableBrowser {
|
|
|
2722
2941
|
}
|
|
2723
2942
|
async scrollIfNeeded(element, info) {
|
|
2724
2943
|
try {
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
if (rect &&
|
|
2728
|
-
rect.top >= 0 &&
|
|
2729
|
-
rect.left >= 0 &&
|
|
2730
|
-
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
|
|
2731
|
-
rect.right <= (window.innerWidth || document.documentElement.clientWidth)) {
|
|
2732
|
-
return false;
|
|
2733
|
-
}
|
|
2734
|
-
else {
|
|
2735
|
-
node.scrollIntoView({
|
|
2736
|
-
behavior: "smooth",
|
|
2737
|
-
block: "center",
|
|
2738
|
-
inline: "center",
|
|
2739
|
-
});
|
|
2740
|
-
return true;
|
|
2741
|
-
}
|
|
2944
|
+
await element.scrollIntoViewIfNeeded({
|
|
2945
|
+
timeout: 2000,
|
|
2742
2946
|
});
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
}
|
|
2947
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
2948
|
+
if (info) {
|
|
2949
|
+
info.box = await element.boundingBox({
|
|
2950
|
+
timeout: 1000,
|
|
2951
|
+
});
|
|
2748
2952
|
}
|
|
2749
2953
|
}
|
|
2750
2954
|
catch (e) {
|
|
2751
|
-
console.log("
|
|
2955
|
+
console.log("#-#");
|
|
2752
2956
|
}
|
|
2753
2957
|
}
|
|
2754
2958
|
_reportToWorld(world, properties) {
|