automation_model 1.0.422-dev → 1.0.422-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 +289 -92
- 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,11 +82,17 @@ 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;
|
|
@@ -109,6 +122,36 @@ class StableBrowser {
|
|
|
109
122
|
context.pageLoading.status = false;
|
|
110
123
|
}.bind(this));
|
|
111
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
|
+
}
|
|
112
155
|
getWebLogFile(logFolder) {
|
|
113
156
|
if (!fs.existsSync(logFolder)) {
|
|
114
157
|
fs.mkdirSync(logFolder, { recursive: true });
|
|
@@ -120,37 +163,65 @@ class StableBrowser {
|
|
|
120
163
|
const fileName = nextIndex + ".json";
|
|
121
164
|
return path.join(logFolder, fileName);
|
|
122
165
|
}
|
|
123
|
-
registerConsoleLogListener(page, context
|
|
166
|
+
registerConsoleLogListener(page, context) {
|
|
124
167
|
if (!this.context.webLogger) {
|
|
125
168
|
this.context.webLogger = [];
|
|
126
169
|
}
|
|
127
170
|
page.on("console", async (msg) => {
|
|
128
|
-
|
|
171
|
+
var _a;
|
|
172
|
+
const obj = {
|
|
129
173
|
type: msg.type(),
|
|
130
174
|
text: msg.text(),
|
|
131
175
|
location: msg.location(),
|
|
132
176
|
time: new Date().toISOString(),
|
|
133
|
-
}
|
|
134
|
-
|
|
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
|
+
}
|
|
135
182
|
});
|
|
136
183
|
}
|
|
137
|
-
registerRequestListener() {
|
|
138
|
-
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();
|
|
139
191
|
try {
|
|
140
|
-
const pageUrl = new URL(
|
|
192
|
+
const pageUrl = new URL(page.url());
|
|
141
193
|
const requestUrl = new URL(data.url());
|
|
142
194
|
if (pageUrl.hostname === requestUrl.hostname) {
|
|
143
195
|
const method = data.method();
|
|
144
|
-
if (
|
|
196
|
+
if (["POST", "GET", "PUT", "DELETE", "PATCH"].includes(method)) {
|
|
145
197
|
const token = await data.headerValue("Authorization");
|
|
146
198
|
if (token) {
|
|
147
|
-
|
|
199
|
+
context.authtoken = token;
|
|
148
200
|
}
|
|
149
201
|
}
|
|
150
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" });
|
|
151
215
|
}
|
|
152
216
|
catch (error) {
|
|
153
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));
|
|
154
225
|
}
|
|
155
226
|
});
|
|
156
227
|
}
|
|
@@ -267,7 +338,10 @@ class StableBrowser {
|
|
|
267
338
|
return locatorReturn;
|
|
268
339
|
}
|
|
269
340
|
async _locateElmentByTextClimbCss(scope, text, climb, css, _params) {
|
|
270
|
-
|
|
341
|
+
if (css && css.locator) {
|
|
342
|
+
css = css.locator;
|
|
343
|
+
}
|
|
344
|
+
let result = await this._locateElementByText(scope, this._fixUsingParams(text, _params), "*", false, false, _params);
|
|
271
345
|
if (result.elementCount === 0) {
|
|
272
346
|
return;
|
|
273
347
|
}
|
|
@@ -294,6 +368,15 @@ class StableBrowser {
|
|
|
294
368
|
return false;
|
|
295
369
|
}
|
|
296
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;
|
|
297
380
|
function collectAllShadowDomElements(element, result = []) {
|
|
298
381
|
// Check and add the element if it has a shadow root
|
|
299
382
|
if (element.shadowRoot) {
|
|
@@ -312,6 +395,10 @@ class StableBrowser {
|
|
|
312
395
|
if (!tag) {
|
|
313
396
|
tag = "*";
|
|
314
397
|
}
|
|
398
|
+
let regexpSearch = document.getRegex(text);
|
|
399
|
+
if (regexpSearch) {
|
|
400
|
+
regex = true;
|
|
401
|
+
}
|
|
315
402
|
let elements = Array.from(document.querySelectorAll(tag));
|
|
316
403
|
let shadowHosts = [];
|
|
317
404
|
document.collectAllShadowDomElements(document, shadowHosts);
|
|
@@ -327,7 +414,9 @@ class StableBrowser {
|
|
|
327
414
|
let randomToken = null;
|
|
328
415
|
const foundElements = [];
|
|
329
416
|
if (regex) {
|
|
330
|
-
|
|
417
|
+
if (!regexpSearch) {
|
|
418
|
+
regexpSearch = new RegExp(text, "im");
|
|
419
|
+
}
|
|
331
420
|
for (let i = 0; i < elements.length; i++) {
|
|
332
421
|
const element = elements[i];
|
|
333
422
|
if ((element.innerText && regexpSearch.test(element.innerText)) ||
|
|
@@ -341,8 +430,8 @@ class StableBrowser {
|
|
|
341
430
|
for (let i = 0; i < elements.length; i++) {
|
|
342
431
|
const element = elements[i];
|
|
343
432
|
if (partial) {
|
|
344
|
-
if ((element.innerText && element.innerText.trim().includes(text)) ||
|
|
345
|
-
(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()))) {
|
|
346
435
|
foundElements.push(element);
|
|
347
436
|
}
|
|
348
437
|
}
|
|
@@ -387,6 +476,12 @@ class StableBrowser {
|
|
|
387
476
|
}
|
|
388
477
|
async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true) {
|
|
389
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
|
+
}
|
|
390
485
|
//info.log += "searching for locator " + JSON.stringify(locatorSearch) + "\n";
|
|
391
486
|
let locator = null;
|
|
392
487
|
if (locatorSearch.climb && locatorSearch.climb >= 0) {
|
|
@@ -475,6 +570,8 @@ class StableBrowser {
|
|
|
475
570
|
if (result.foundElements.length > 0) {
|
|
476
571
|
let dialogCloseLocator = result.foundElements[0].locator;
|
|
477
572
|
await dialogCloseLocator.click();
|
|
573
|
+
// wait for the dialog to close
|
|
574
|
+
await dialogCloseLocator.waitFor({ state: "hidden" });
|
|
478
575
|
return { rerun: true };
|
|
479
576
|
}
|
|
480
577
|
}
|
|
@@ -483,7 +580,7 @@ class StableBrowser {
|
|
|
483
580
|
}
|
|
484
581
|
async _locate(selectors, info, _params, timeout = 30000) {
|
|
485
582
|
for (let i = 0; i < 3; i++) {
|
|
486
|
-
info.log += "attempt " + i + ":
|
|
583
|
+
info.log += "attempt " + i + ": total locators " + selectors.locators.length + "\n";
|
|
487
584
|
for (let j = 0; j < selectors.locators.length; j++) {
|
|
488
585
|
let selector = selectors.locators[j];
|
|
489
586
|
info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
|
|
@@ -502,10 +599,44 @@ class StableBrowser {
|
|
|
502
599
|
let locatorsCount = 0;
|
|
503
600
|
//let arrayMode = Array.isArray(selectors);
|
|
504
601
|
let scope = this.page;
|
|
602
|
+
// for the simple click usecase
|
|
603
|
+
if (selectors.frame) {
|
|
604
|
+
scope = selectors.frame;
|
|
605
|
+
}
|
|
505
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
|
+
};
|
|
506
632
|
info.log += "searching for iframe " + selectors.iframe_src + "/" + selectors.frameLocators + "\n";
|
|
507
633
|
while (true) {
|
|
508
634
|
let frameFound = false;
|
|
635
|
+
if (selectors.nestFrmLoc) {
|
|
636
|
+
scope = await findFrame(selectors.nestFrmLoc, scope);
|
|
637
|
+
frameFound = true;
|
|
638
|
+
break;
|
|
639
|
+
}
|
|
509
640
|
if (selectors.frameLocators) {
|
|
510
641
|
for (let i = 0; i < selectors.frameLocators.length; i++) {
|
|
511
642
|
let frameLocator = selectors.frameLocators[i];
|
|
@@ -666,6 +797,66 @@ class StableBrowser {
|
|
|
666
797
|
}
|
|
667
798
|
return result;
|
|
668
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
|
+
}
|
|
669
860
|
async click(selectors, _params, options = {}, world = null) {
|
|
670
861
|
this._validateSelectors(selectors);
|
|
671
862
|
const startTime = Date.now();
|
|
@@ -685,14 +876,14 @@ class StableBrowser {
|
|
|
685
876
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
686
877
|
try {
|
|
687
878
|
await this._highlightElements(element);
|
|
688
|
-
await element.click(
|
|
879
|
+
await element.click();
|
|
689
880
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
690
881
|
}
|
|
691
882
|
catch (e) {
|
|
692
883
|
// await this.closeUnexpectedPopups();
|
|
693
884
|
info.log += "click failed, will try again" + "\n";
|
|
694
885
|
element = await this._locate(selectors, info, _params);
|
|
695
|
-
await element.click
|
|
886
|
+
await element.dispatchEvent("click");
|
|
696
887
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
697
888
|
}
|
|
698
889
|
await this.waitForPageLoad();
|
|
@@ -745,7 +936,7 @@ class StableBrowser {
|
|
|
745
936
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
746
937
|
try {
|
|
747
938
|
await this._highlightElements(element);
|
|
748
|
-
await element.setChecked(checked
|
|
939
|
+
await element.setChecked(checked);
|
|
749
940
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
750
941
|
}
|
|
751
942
|
catch (e) {
|
|
@@ -809,7 +1000,7 @@ class StableBrowser {
|
|
|
809
1000
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
810
1001
|
try {
|
|
811
1002
|
await this._highlightElements(element);
|
|
812
|
-
await element.hover(
|
|
1003
|
+
await element.hover();
|
|
813
1004
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
814
1005
|
}
|
|
815
1006
|
catch (e) {
|
|
@@ -871,7 +1062,7 @@ class StableBrowser {
|
|
|
871
1062
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
872
1063
|
try {
|
|
873
1064
|
await this._highlightElements(element);
|
|
874
|
-
await element.selectOption(values
|
|
1065
|
+
await element.selectOption(values);
|
|
875
1066
|
}
|
|
876
1067
|
catch (e) {
|
|
877
1068
|
//await this.closeUnexpectedPopups();
|
|
@@ -1159,7 +1350,6 @@ class StableBrowser {
|
|
|
1159
1350
|
let element = await this._locate(selectors, info, _params);
|
|
1160
1351
|
//insert red border around the element
|
|
1161
1352
|
await this.scrollIfNeeded(element, info);
|
|
1162
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1163
1353
|
await this._highlightElements(element);
|
|
1164
1354
|
if (options === null || options === undefined || !options.press) {
|
|
1165
1355
|
try {
|
|
@@ -1209,6 +1399,7 @@ class StableBrowser {
|
|
|
1209
1399
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1210
1400
|
}
|
|
1211
1401
|
}
|
|
1402
|
+
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1212
1403
|
if (enter === true) {
|
|
1213
1404
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1214
1405
|
await this.page.keyboard.press("Enter");
|
|
@@ -1274,7 +1465,7 @@ class StableBrowser {
|
|
|
1274
1465
|
let element = await this._locate(selectors, info, _params);
|
|
1275
1466
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1276
1467
|
await this._highlightElements(element);
|
|
1277
|
-
await element.fill(value
|
|
1468
|
+
await element.fill(value);
|
|
1278
1469
|
await element.dispatchEvent("change");
|
|
1279
1470
|
if (enter) {
|
|
1280
1471
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
@@ -1493,7 +1684,7 @@ class StableBrowser {
|
|
|
1493
1684
|
return info;
|
|
1494
1685
|
}
|
|
1495
1686
|
catch (e) {
|
|
1496
|
-
|
|
1687
|
+
await this.closeUnexpectedPopups();
|
|
1497
1688
|
this.logger.error("verify element contains text failed " + info.log);
|
|
1498
1689
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1499
1690
|
info.screenshotPath = screenshotPath;
|
|
@@ -1541,6 +1732,29 @@ class StableBrowser {
|
|
|
1541
1732
|
}
|
|
1542
1733
|
return dataFile;
|
|
1543
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
|
+
}
|
|
1544
1758
|
setTestData(testData, world = null) {
|
|
1545
1759
|
if (!testData) {
|
|
1546
1760
|
return;
|
|
@@ -1728,7 +1942,6 @@ class StableBrowser {
|
|
|
1728
1942
|
}
|
|
1729
1943
|
async takeScreenshot(screenshotPath) {
|
|
1730
1944
|
const playContext = this.context.playContext;
|
|
1731
|
-
const client = await playContext.newCDPSession(this.page);
|
|
1732
1945
|
// Using CDP to capture the screenshot
|
|
1733
1946
|
const viewportWidth = Math.max(...(await this.page.evaluate(() => [
|
|
1734
1947
|
document.body.scrollWidth,
|
|
@@ -1738,41 +1951,40 @@ class StableBrowser {
|
|
|
1738
1951
|
document.body.clientWidth,
|
|
1739
1952
|
document.documentElement.clientWidth,
|
|
1740
1953
|
])));
|
|
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
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
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
|
+
}
|
|
1776
1988
|
}
|
|
1777
1989
|
async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
|
|
1778
1990
|
this._validateSelectors(selectors);
|
|
@@ -2132,20 +2344,20 @@ class StableBrowser {
|
|
|
2132
2344
|
for (let i = 0; i < frames.length; i++) {
|
|
2133
2345
|
if (dateAlternatives.date) {
|
|
2134
2346
|
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2135
|
-
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*", true, {});
|
|
2347
|
+
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*", true, true, {});
|
|
2136
2348
|
result.frame = frames[i];
|
|
2137
2349
|
results.push(result);
|
|
2138
2350
|
}
|
|
2139
2351
|
}
|
|
2140
2352
|
else if (numberAlternatives.number) {
|
|
2141
2353
|
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2142
|
-
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*", true, {});
|
|
2354
|
+
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*", true, true, {});
|
|
2143
2355
|
result.frame = frames[i];
|
|
2144
2356
|
results.push(result);
|
|
2145
2357
|
}
|
|
2146
2358
|
}
|
|
2147
2359
|
else {
|
|
2148
|
-
const result = await this._locateElementByText(frames[i], text, "*", true, {});
|
|
2360
|
+
const result = await this._locateElementByText(frames[i], text, "*", true, true, {});
|
|
2149
2361
|
result.frame = frames[i];
|
|
2150
2362
|
results.push(result);
|
|
2151
2363
|
}
|
|
@@ -2580,13 +2792,13 @@ class StableBrowser {
|
|
|
2580
2792
|
}
|
|
2581
2793
|
catch (e) {
|
|
2582
2794
|
if (e.label === "networkidle") {
|
|
2583
|
-
console.log("
|
|
2795
|
+
console.log("waited for the network to be idle timeout");
|
|
2584
2796
|
}
|
|
2585
2797
|
else if (e.label === "load") {
|
|
2586
|
-
console.log("
|
|
2798
|
+
console.log("waited for the load timeout");
|
|
2587
2799
|
}
|
|
2588
2800
|
else if (e.label === "domcontentloaded") {
|
|
2589
|
-
console.log("
|
|
2801
|
+
console.log("waited for the domcontent loaded timeout");
|
|
2590
2802
|
}
|
|
2591
2803
|
console.log(".");
|
|
2592
2804
|
}
|
|
@@ -2729,33 +2941,18 @@ class StableBrowser {
|
|
|
2729
2941
|
}
|
|
2730
2942
|
async scrollIfNeeded(element, info) {
|
|
2731
2943
|
try {
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
if (rect &&
|
|
2735
|
-
rect.top >= 0 &&
|
|
2736
|
-
rect.left >= 0 &&
|
|
2737
|
-
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
|
|
2738
|
-
rect.right <= (window.innerWidth || document.documentElement.clientWidth)) {
|
|
2739
|
-
return false;
|
|
2740
|
-
}
|
|
2741
|
-
else {
|
|
2742
|
-
node.scrollIntoView({
|
|
2743
|
-
behavior: "smooth",
|
|
2744
|
-
block: "center",
|
|
2745
|
-
inline: "center",
|
|
2746
|
-
});
|
|
2747
|
-
return true;
|
|
2748
|
-
}
|
|
2944
|
+
await element.scrollIntoViewIfNeeded({
|
|
2945
|
+
timeout: 2000,
|
|
2749
2946
|
});
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
}
|
|
2947
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
2948
|
+
if (info) {
|
|
2949
|
+
info.box = await element.boundingBox({
|
|
2950
|
+
timeout: 1000,
|
|
2951
|
+
});
|
|
2755
2952
|
}
|
|
2756
2953
|
}
|
|
2757
2954
|
catch (e) {
|
|
2758
|
-
console.log("
|
|
2955
|
+
console.log("#-#");
|
|
2759
2956
|
}
|
|
2760
2957
|
}
|
|
2761
2958
|
_reportToWorld(world, properties) {
|