automation_model 1.0.421-dev → 1.0.421-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 +22 -10
- package/lib/api.js.map +1 -1
- package/lib/auto_page.js +1 -24
- package/lib/auto_page.js.map +1 -1
- package/lib/axe/axe.mini.js +12 -0
- 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 +213 -0
- package/lib/locate_element.js.map +1 -0
- package/lib/stable_browser.d.ts +13 -3
- package/lib/stable_browser.js +297 -95
- package/lib/stable_browser.js.map +1 -1
- package/lib/test_context.d.ts +2 -0
- package/lib/test_context.js +2 -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 +9 -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";
|
|
@@ -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
|
}
|
|
@@ -262,7 +338,10 @@ class StableBrowser {
|
|
|
262
338
|
return locatorReturn;
|
|
263
339
|
}
|
|
264
340
|
async _locateElmentByTextClimbCss(scope, text, climb, css, _params) {
|
|
265
|
-
|
|
341
|
+
if (css && css.locator) {
|
|
342
|
+
css = css.locator;
|
|
343
|
+
}
|
|
344
|
+
let result = await this._locateElementByText(scope, this._fixUsingParams(text, _params), "*", false, false, _params);
|
|
266
345
|
if (result.elementCount === 0) {
|
|
267
346
|
return;
|
|
268
347
|
}
|
|
@@ -289,6 +368,15 @@ class StableBrowser {
|
|
|
289
368
|
return false;
|
|
290
369
|
}
|
|
291
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;
|
|
292
380
|
function collectAllShadowDomElements(element, result = []) {
|
|
293
381
|
// Check and add the element if it has a shadow root
|
|
294
382
|
if (element.shadowRoot) {
|
|
@@ -307,6 +395,10 @@ class StableBrowser {
|
|
|
307
395
|
if (!tag) {
|
|
308
396
|
tag = "*";
|
|
309
397
|
}
|
|
398
|
+
let regexpSearch = document.getRegex(text);
|
|
399
|
+
if (regexpSearch) {
|
|
400
|
+
regex = true;
|
|
401
|
+
}
|
|
310
402
|
let elements = Array.from(document.querySelectorAll(tag));
|
|
311
403
|
let shadowHosts = [];
|
|
312
404
|
document.collectAllShadowDomElements(document, shadowHosts);
|
|
@@ -322,7 +414,9 @@ class StableBrowser {
|
|
|
322
414
|
let randomToken = null;
|
|
323
415
|
const foundElements = [];
|
|
324
416
|
if (regex) {
|
|
325
|
-
|
|
417
|
+
if (!regexpSearch) {
|
|
418
|
+
regexpSearch = new RegExp(text, "im");
|
|
419
|
+
}
|
|
326
420
|
for (let i = 0; i < elements.length; i++) {
|
|
327
421
|
const element = elements[i];
|
|
328
422
|
if ((element.innerText && regexpSearch.test(element.innerText)) ||
|
|
@@ -336,8 +430,8 @@ class StableBrowser {
|
|
|
336
430
|
for (let i = 0; i < elements.length; i++) {
|
|
337
431
|
const element = elements[i];
|
|
338
432
|
if (partial) {
|
|
339
|
-
if ((element.innerText && element.innerText.trim().includes(text)) ||
|
|
340
|
-
(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()))) {
|
|
341
435
|
foundElements.push(element);
|
|
342
436
|
}
|
|
343
437
|
}
|
|
@@ -382,6 +476,12 @@ class StableBrowser {
|
|
|
382
476
|
}
|
|
383
477
|
async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true) {
|
|
384
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
|
+
}
|
|
385
485
|
//info.log += "searching for locator " + JSON.stringify(locatorSearch) + "\n";
|
|
386
486
|
let locator = null;
|
|
387
487
|
if (locatorSearch.climb && locatorSearch.climb >= 0) {
|
|
@@ -470,6 +570,8 @@ class StableBrowser {
|
|
|
470
570
|
if (result.foundElements.length > 0) {
|
|
471
571
|
let dialogCloseLocator = result.foundElements[0].locator;
|
|
472
572
|
await dialogCloseLocator.click();
|
|
573
|
+
// wait for the dialog to close
|
|
574
|
+
await dialogCloseLocator.waitFor({ state: "hidden" });
|
|
473
575
|
return { rerun: true };
|
|
474
576
|
}
|
|
475
577
|
}
|
|
@@ -478,7 +580,7 @@ class StableBrowser {
|
|
|
478
580
|
}
|
|
479
581
|
async _locate(selectors, info, _params, timeout = 30000) {
|
|
480
582
|
for (let i = 0; i < 3; i++) {
|
|
481
|
-
info.log += "attempt " + i + ":
|
|
583
|
+
info.log += "attempt " + i + ": total locators " + selectors.locators.length + "\n";
|
|
482
584
|
for (let j = 0; j < selectors.locators.length; j++) {
|
|
483
585
|
let selector = selectors.locators[j];
|
|
484
586
|
info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
|
|
@@ -497,10 +599,44 @@ class StableBrowser {
|
|
|
497
599
|
let locatorsCount = 0;
|
|
498
600
|
//let arrayMode = Array.isArray(selectors);
|
|
499
601
|
let scope = this.page;
|
|
602
|
+
// for the simple click usecase
|
|
603
|
+
if (selectors.frame) {
|
|
604
|
+
scope = selectors.frame;
|
|
605
|
+
}
|
|
500
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
|
+
};
|
|
501
632
|
info.log += "searching for iframe " + selectors.iframe_src + "/" + selectors.frameLocators + "\n";
|
|
502
633
|
while (true) {
|
|
503
634
|
let frameFound = false;
|
|
635
|
+
if (selectors.nestFrmLoc) {
|
|
636
|
+
scope = await findFrame(selectors.nestFrmLoc, scope);
|
|
637
|
+
frameFound = true;
|
|
638
|
+
break;
|
|
639
|
+
}
|
|
504
640
|
if (selectors.frameLocators) {
|
|
505
641
|
for (let i = 0; i < selectors.frameLocators.length; i++) {
|
|
506
642
|
let frameLocator = selectors.frameLocators[i];
|
|
@@ -661,6 +797,66 @@ class StableBrowser {
|
|
|
661
797
|
}
|
|
662
798
|
return result;
|
|
663
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
|
+
}
|
|
664
860
|
async click(selectors, _params, options = {}, world = null) {
|
|
665
861
|
this._validateSelectors(selectors);
|
|
666
862
|
const startTime = Date.now();
|
|
@@ -680,14 +876,14 @@ class StableBrowser {
|
|
|
680
876
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
681
877
|
try {
|
|
682
878
|
await this._highlightElements(element);
|
|
683
|
-
await element.click(
|
|
879
|
+
await element.click();
|
|
684
880
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
685
881
|
}
|
|
686
882
|
catch (e) {
|
|
687
883
|
// await this.closeUnexpectedPopups();
|
|
688
884
|
info.log += "click failed, will try again" + "\n";
|
|
689
885
|
element = await this._locate(selectors, info, _params);
|
|
690
|
-
await element.click
|
|
886
|
+
await element.dispatchEvent("click");
|
|
691
887
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
692
888
|
}
|
|
693
889
|
await this.waitForPageLoad();
|
|
@@ -740,7 +936,7 @@ class StableBrowser {
|
|
|
740
936
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
741
937
|
try {
|
|
742
938
|
await this._highlightElements(element);
|
|
743
|
-
await element.setChecked(checked
|
|
939
|
+
await element.setChecked(checked);
|
|
744
940
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
745
941
|
}
|
|
746
942
|
catch (e) {
|
|
@@ -804,7 +1000,7 @@ class StableBrowser {
|
|
|
804
1000
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
805
1001
|
try {
|
|
806
1002
|
await this._highlightElements(element);
|
|
807
|
-
await element.hover(
|
|
1003
|
+
await element.hover();
|
|
808
1004
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
809
1005
|
}
|
|
810
1006
|
catch (e) {
|
|
@@ -866,7 +1062,7 @@ class StableBrowser {
|
|
|
866
1062
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
867
1063
|
try {
|
|
868
1064
|
await this._highlightElements(element);
|
|
869
|
-
await element.selectOption(values
|
|
1065
|
+
await element.selectOption(values);
|
|
870
1066
|
}
|
|
871
1067
|
catch (e) {
|
|
872
1068
|
//await this.closeUnexpectedPopups();
|
|
@@ -1154,7 +1350,6 @@ class StableBrowser {
|
|
|
1154
1350
|
let element = await this._locate(selectors, info, _params);
|
|
1155
1351
|
//insert red border around the element
|
|
1156
1352
|
await this.scrollIfNeeded(element, info);
|
|
1157
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1158
1353
|
await this._highlightElements(element);
|
|
1159
1354
|
if (options === null || options === undefined || !options.press) {
|
|
1160
1355
|
try {
|
|
@@ -1204,6 +1399,7 @@ class StableBrowser {
|
|
|
1204
1399
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1205
1400
|
}
|
|
1206
1401
|
}
|
|
1402
|
+
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1207
1403
|
if (enter === true) {
|
|
1208
1404
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1209
1405
|
await this.page.keyboard.press("Enter");
|
|
@@ -1269,7 +1465,7 @@ class StableBrowser {
|
|
|
1269
1465
|
let element = await this._locate(selectors, info, _params);
|
|
1270
1466
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1271
1467
|
await this._highlightElements(element);
|
|
1272
|
-
await element.fill(value
|
|
1468
|
+
await element.fill(value);
|
|
1273
1469
|
await element.dispatchEvent("change");
|
|
1274
1470
|
if (enter) {
|
|
1275
1471
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
@@ -1488,7 +1684,7 @@ class StableBrowser {
|
|
|
1488
1684
|
return info;
|
|
1489
1685
|
}
|
|
1490
1686
|
catch (e) {
|
|
1491
|
-
|
|
1687
|
+
await this.closeUnexpectedPopups();
|
|
1492
1688
|
this.logger.error("verify element contains text failed " + info.log);
|
|
1493
1689
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1494
1690
|
info.screenshotPath = screenshotPath;
|
|
@@ -1536,6 +1732,29 @@ class StableBrowser {
|
|
|
1536
1732
|
}
|
|
1537
1733
|
return dataFile;
|
|
1538
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
|
+
}
|
|
1539
1758
|
setTestData(testData, world = null) {
|
|
1540
1759
|
if (!testData) {
|
|
1541
1760
|
return;
|
|
@@ -1723,7 +1942,6 @@ class StableBrowser {
|
|
|
1723
1942
|
}
|
|
1724
1943
|
async takeScreenshot(screenshotPath) {
|
|
1725
1944
|
const playContext = this.context.playContext;
|
|
1726
|
-
const client = await playContext.newCDPSession(this.page);
|
|
1727
1945
|
// Using CDP to capture the screenshot
|
|
1728
1946
|
const viewportWidth = Math.max(...(await this.page.evaluate(() => [
|
|
1729
1947
|
document.body.scrollWidth,
|
|
@@ -1733,41 +1951,40 @@ class StableBrowser {
|
|
|
1733
1951
|
document.body.clientWidth,
|
|
1734
1952
|
document.documentElement.clientWidth,
|
|
1735
1953
|
])));
|
|
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
|
-
|
|
1769
|
-
|
|
1770
|
-
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
|
+
}
|
|
1771
1988
|
}
|
|
1772
1989
|
async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
|
|
1773
1990
|
this._validateSelectors(selectors);
|
|
@@ -2127,20 +2344,20 @@ class StableBrowser {
|
|
|
2127
2344
|
for (let i = 0; i < frames.length; i++) {
|
|
2128
2345
|
if (dateAlternatives.date) {
|
|
2129
2346
|
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2130
|
-
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*", true, {});
|
|
2347
|
+
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*", true, true, {});
|
|
2131
2348
|
result.frame = frames[i];
|
|
2132
2349
|
results.push(result);
|
|
2133
2350
|
}
|
|
2134
2351
|
}
|
|
2135
2352
|
else if (numberAlternatives.number) {
|
|
2136
2353
|
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2137
|
-
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*", true, {});
|
|
2354
|
+
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*", true, true, {});
|
|
2138
2355
|
result.frame = frames[i];
|
|
2139
2356
|
results.push(result);
|
|
2140
2357
|
}
|
|
2141
2358
|
}
|
|
2142
2359
|
else {
|
|
2143
|
-
const result = await this._locateElementByText(frames[i], text, "*", true, {});
|
|
2360
|
+
const result = await this._locateElementByText(frames[i], text, "*", true, true, {});
|
|
2144
2361
|
result.frame = frames[i];
|
|
2145
2362
|
results.push(result);
|
|
2146
2363
|
}
|
|
@@ -2575,13 +2792,13 @@ class StableBrowser {
|
|
|
2575
2792
|
}
|
|
2576
2793
|
catch (e) {
|
|
2577
2794
|
if (e.label === "networkidle") {
|
|
2578
|
-
console.log("
|
|
2795
|
+
console.log("waited for the network to be idle timeout");
|
|
2579
2796
|
}
|
|
2580
2797
|
else if (e.label === "load") {
|
|
2581
|
-
console.log("
|
|
2798
|
+
console.log("waited for the load timeout");
|
|
2582
2799
|
}
|
|
2583
2800
|
else if (e.label === "domcontentloaded") {
|
|
2584
|
-
console.log("
|
|
2801
|
+
console.log("waited for the domcontent loaded timeout");
|
|
2585
2802
|
}
|
|
2586
2803
|
console.log(".");
|
|
2587
2804
|
}
|
|
@@ -2724,33 +2941,18 @@ class StableBrowser {
|
|
|
2724
2941
|
}
|
|
2725
2942
|
async scrollIfNeeded(element, info) {
|
|
2726
2943
|
try {
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
if (rect &&
|
|
2730
|
-
rect.top >= 0 &&
|
|
2731
|
-
rect.left >= 0 &&
|
|
2732
|
-
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
|
|
2733
|
-
rect.right <= (window.innerWidth || document.documentElement.clientWidth)) {
|
|
2734
|
-
return false;
|
|
2735
|
-
}
|
|
2736
|
-
else {
|
|
2737
|
-
node.scrollIntoView({
|
|
2738
|
-
behavior: "smooth",
|
|
2739
|
-
block: "center",
|
|
2740
|
-
inline: "center",
|
|
2741
|
-
});
|
|
2742
|
-
return true;
|
|
2743
|
-
}
|
|
2944
|
+
await element.scrollIntoViewIfNeeded({
|
|
2945
|
+
timeout: 2000,
|
|
2744
2946
|
});
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
}
|
|
2947
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
2948
|
+
if (info) {
|
|
2949
|
+
info.box = await element.boundingBox({
|
|
2950
|
+
timeout: 1000,
|
|
2951
|
+
});
|
|
2750
2952
|
}
|
|
2751
2953
|
}
|
|
2752
2954
|
catch (e) {
|
|
2753
|
-
console.log("
|
|
2955
|
+
console.log("#-#");
|
|
2754
2956
|
}
|
|
2755
2957
|
}
|
|
2756
2958
|
_reportToWorld(world, properties) {
|