automation_model 1.0.441-dev → 1.0.441-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.d.ts +42 -1
- package/lib/api.js +193 -9
- package/lib/api.js.map +1 -1
- package/lib/auto_page.js +21 -39
- package/lib/auto_page.js.map +1 -1
- package/lib/axe/axe.mini.js +12 -0
- package/lib/browser_manager.js +19 -9
- package/lib/browser_manager.js.map +1 -1
- package/lib/command_common.d.ts +5 -0
- package/lib/command_common.js +119 -0
- package/lib/command_common.js.map +1 -0
- package/lib/environment.d.ts +3 -0
- package/lib/environment.js +5 -2
- package/lib/environment.js.map +1 -1
- package/lib/init_browser.d.ts +2 -1
- package/lib/init_browser.js +51 -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/network.d.ts +3 -0
- package/lib/network.js +144 -0
- package/lib/network.js.map +1 -0
- package/lib/stable_browser.d.ts +26 -16
- package/lib/stable_browser.js +562 -634
- package/lib/stable_browser.js.map +1 -1
- package/lib/test_context.d.ts +2 -0
- package/lib/test_context.js +11 -10
- package/lib/test_context.js.map +1 -1
- package/lib/utils.d.ts +3 -1
- package/lib/utils.js +68 -1
- package/lib/utils.js.map +1 -1
- package/package.json +7 -9
package/lib/stable_browser.js
CHANGED
|
@@ -2,19 +2,22 @@
|
|
|
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";
|
|
11
11
|
//import { closeUnexpectedPopups } from "./popups.js";
|
|
12
12
|
import { getTableCells, getTableData } from "./table_analyze.js";
|
|
13
|
-
import
|
|
14
|
-
import { decrypt } from "./utils.js";
|
|
13
|
+
import { maskValue, replaceWithLocalTestData } from "./utils.js";
|
|
15
14
|
import csv from "csv-parser";
|
|
16
15
|
import { Readable } from "node:stream";
|
|
17
16
|
import readline from "readline";
|
|
17
|
+
import { getContext } from "./init_browser.js";
|
|
18
|
+
import { locate_element } from "./locate_element.js";
|
|
19
|
+
import { _commandError, _commandFinally, _preCommand, _validateSelectors, _screenshot } from "./command_common.js";
|
|
20
|
+
import { registerDownloadEvent, registerNetworkEvents } from "./network.js";
|
|
18
21
|
const Types = {
|
|
19
22
|
CLICK: "click_element",
|
|
20
23
|
NAVIGATE: "navigate",
|
|
@@ -42,15 +45,24 @@ const Types = {
|
|
|
42
45
|
LOAD_DATA: "load_data",
|
|
43
46
|
SET_INPUT: "set_input",
|
|
44
47
|
};
|
|
48
|
+
export const apps = {};
|
|
45
49
|
class StableBrowser {
|
|
46
|
-
|
|
50
|
+
browser;
|
|
51
|
+
page;
|
|
52
|
+
logger;
|
|
53
|
+
context;
|
|
54
|
+
world;
|
|
55
|
+
project_path = null;
|
|
56
|
+
webLogFile = null;
|
|
57
|
+
networkLogger = null;
|
|
58
|
+
configuration = null;
|
|
59
|
+
appName = "main";
|
|
60
|
+
constructor(browser, page, logger = null, context = null, world = null) {
|
|
47
61
|
this.browser = browser;
|
|
48
62
|
this.page = page;
|
|
49
63
|
this.logger = logger;
|
|
50
64
|
this.context = context;
|
|
51
|
-
this.
|
|
52
|
-
this.webLogFile = null;
|
|
53
|
-
this.configuration = null;
|
|
65
|
+
this.world = world;
|
|
54
66
|
if (!this.logger) {
|
|
55
67
|
this.logger = console;
|
|
56
68
|
}
|
|
@@ -76,16 +88,31 @@ class StableBrowser {
|
|
|
76
88
|
this.logger.error("unable to read ai_config.json");
|
|
77
89
|
}
|
|
78
90
|
const logFolder = path.join(this.project_path, "logs", "web");
|
|
79
|
-
this.
|
|
80
|
-
this.registerConsoleLogListener(page, context, this.webLogFile);
|
|
81
|
-
this.registerRequestListener();
|
|
91
|
+
this.world = world;
|
|
82
92
|
context.pages = [this.page];
|
|
83
93
|
context.pageLoading = { status: false };
|
|
94
|
+
this.registerEventListeners(this.context);
|
|
95
|
+
registerNetworkEvents(this.world, this, this.context, this.page);
|
|
96
|
+
registerDownloadEvent(this.page, this.world, this.context);
|
|
97
|
+
}
|
|
98
|
+
registerEventListeners(context) {
|
|
99
|
+
this.registerConsoleLogListener(this.page, context);
|
|
100
|
+
this.registerRequestListener(this.page, context, this.webLogFile);
|
|
101
|
+
if (!context.pageLoading) {
|
|
102
|
+
context.pageLoading = { status: false };
|
|
103
|
+
}
|
|
84
104
|
context.playContext.on("page", async function (page) {
|
|
105
|
+
if (this.configuration && this.configuration.closePopups === true) {
|
|
106
|
+
console.log("close unexpected popups");
|
|
107
|
+
await page.close();
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
85
110
|
context.pageLoading.status = true;
|
|
86
111
|
this.page = page;
|
|
87
112
|
context.page = page;
|
|
88
113
|
context.pages.push(page);
|
|
114
|
+
registerNetworkEvents(this.world, this, context, this.page);
|
|
115
|
+
registerDownloadEvent(this.page, this.world, context);
|
|
89
116
|
page.on("close", async () => {
|
|
90
117
|
if (this.context && this.context.pages && this.context.pages.length > 1) {
|
|
91
118
|
this.context.pages.pop();
|
|
@@ -110,6 +137,36 @@ class StableBrowser {
|
|
|
110
137
|
context.pageLoading.status = false;
|
|
111
138
|
}.bind(this));
|
|
112
139
|
}
|
|
140
|
+
async switchApp(appName) {
|
|
141
|
+
// check if the current app (this.appName) is the same as the new app
|
|
142
|
+
if (this.appName === appName) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
let navigate = false;
|
|
146
|
+
if (!apps[appName]) {
|
|
147
|
+
let newContext = await getContext(null, false, this.logger, appName, false, this);
|
|
148
|
+
navigate = true;
|
|
149
|
+
apps[appName] = {
|
|
150
|
+
context: newContext,
|
|
151
|
+
browser: newContext.browser,
|
|
152
|
+
page: newContext.page,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
const tempContext = {};
|
|
156
|
+
this._copyContext(this, tempContext);
|
|
157
|
+
this._copyContext(apps[appName], this);
|
|
158
|
+
apps[this.appName] = tempContext;
|
|
159
|
+
this.appName = appName;
|
|
160
|
+
if (navigate) {
|
|
161
|
+
await this.goto(this.context.environment.baseUrl);
|
|
162
|
+
await this.waitForPageLoad();
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
_copyContext(from, to) {
|
|
166
|
+
to.browser = from.browser;
|
|
167
|
+
to.page = from.page;
|
|
168
|
+
to.context = from.context;
|
|
169
|
+
}
|
|
113
170
|
getWebLogFile(logFolder) {
|
|
114
171
|
if (!fs.existsSync(logFolder)) {
|
|
115
172
|
fs.mkdirSync(logFolder, { recursive: true });
|
|
@@ -121,37 +178,63 @@ class StableBrowser {
|
|
|
121
178
|
const fileName = nextIndex + ".json";
|
|
122
179
|
return path.join(logFolder, fileName);
|
|
123
180
|
}
|
|
124
|
-
registerConsoleLogListener(page, context
|
|
181
|
+
registerConsoleLogListener(page, context) {
|
|
125
182
|
if (!this.context.webLogger) {
|
|
126
183
|
this.context.webLogger = [];
|
|
127
184
|
}
|
|
128
185
|
page.on("console", async (msg) => {
|
|
129
|
-
|
|
186
|
+
const obj = {
|
|
130
187
|
type: msg.type(),
|
|
131
188
|
text: msg.text(),
|
|
132
189
|
location: msg.location(),
|
|
133
190
|
time: new Date().toISOString(),
|
|
134
|
-
}
|
|
135
|
-
|
|
191
|
+
};
|
|
192
|
+
this.context.webLogger.push(obj);
|
|
193
|
+
if (msg.type() === "error") {
|
|
194
|
+
this.world?.attach(JSON.stringify(obj), { mediaType: "application/json+log" });
|
|
195
|
+
}
|
|
136
196
|
});
|
|
137
197
|
}
|
|
138
|
-
registerRequestListener() {
|
|
139
|
-
this.
|
|
198
|
+
registerRequestListener(page, context, logFile) {
|
|
199
|
+
if (!this.context.networkLogger) {
|
|
200
|
+
this.context.networkLogger = [];
|
|
201
|
+
}
|
|
202
|
+
page.on("request", async (data) => {
|
|
203
|
+
const startTime = new Date().getTime();
|
|
140
204
|
try {
|
|
141
|
-
const pageUrl = new URL(
|
|
205
|
+
const pageUrl = new URL(page.url());
|
|
142
206
|
const requestUrl = new URL(data.url());
|
|
143
207
|
if (pageUrl.hostname === requestUrl.hostname) {
|
|
144
208
|
const method = data.method();
|
|
145
|
-
if (
|
|
209
|
+
if (["POST", "GET", "PUT", "DELETE", "PATCH"].includes(method)) {
|
|
146
210
|
const token = await data.headerValue("Authorization");
|
|
147
211
|
if (token) {
|
|
148
|
-
|
|
212
|
+
context.authtoken = token;
|
|
149
213
|
}
|
|
150
214
|
}
|
|
151
215
|
}
|
|
216
|
+
const response = await data.response();
|
|
217
|
+
const endTime = new Date().getTime();
|
|
218
|
+
const obj = {
|
|
219
|
+
url: data.url(),
|
|
220
|
+
method: data.method(),
|
|
221
|
+
postData: data.postData(),
|
|
222
|
+
error: data.failure() ? data.failure().errorText : null,
|
|
223
|
+
duration: endTime - startTime,
|
|
224
|
+
startTime,
|
|
225
|
+
};
|
|
226
|
+
context.networkLogger.push(obj);
|
|
227
|
+
this.world?.attach(JSON.stringify(obj), { mediaType: "application/json+network" });
|
|
152
228
|
}
|
|
153
229
|
catch (error) {
|
|
154
230
|
console.error("Error in request listener", error);
|
|
231
|
+
context.networkLogger.push({
|
|
232
|
+
error: "not able to listen",
|
|
233
|
+
message: error.message,
|
|
234
|
+
stack: error.stack,
|
|
235
|
+
time: new Date().toISOString(),
|
|
236
|
+
});
|
|
237
|
+
// await fs.promises.writeFile(logFile, JSON.stringify(context.networkLogger, null, 2));
|
|
155
238
|
}
|
|
156
239
|
});
|
|
157
240
|
}
|
|
@@ -166,20 +249,6 @@ class StableBrowser {
|
|
|
166
249
|
timeout: 60000,
|
|
167
250
|
});
|
|
168
251
|
}
|
|
169
|
-
_validateSelectors(selectors) {
|
|
170
|
-
if (!selectors) {
|
|
171
|
-
throw new Error("selectors is null");
|
|
172
|
-
}
|
|
173
|
-
if (!selectors.locators) {
|
|
174
|
-
throw new Error("selectors.locators is null");
|
|
175
|
-
}
|
|
176
|
-
if (!Array.isArray(selectors.locators)) {
|
|
177
|
-
throw new Error("selectors.locators expected to be array");
|
|
178
|
-
}
|
|
179
|
-
if (selectors.locators.length === 0) {
|
|
180
|
-
throw new Error("selectors.locators expected to be non empty array");
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
252
|
_fixUsingParams(text, _params) {
|
|
184
253
|
if (!_params || typeof text !== "string") {
|
|
185
254
|
return text;
|
|
@@ -247,7 +316,7 @@ class StableBrowser {
|
|
|
247
316
|
locatorReturn = scope.getByRole(role, { name }, { exact: flags === "i" });
|
|
248
317
|
}
|
|
249
318
|
}
|
|
250
|
-
if (locator
|
|
319
|
+
if (locator?.engine) {
|
|
251
320
|
if (locator.engine === "css") {
|
|
252
321
|
locatorReturn = scope.locator(locator.selector);
|
|
253
322
|
}
|
|
@@ -268,7 +337,10 @@ class StableBrowser {
|
|
|
268
337
|
return locatorReturn;
|
|
269
338
|
}
|
|
270
339
|
async _locateElmentByTextClimbCss(scope, text, climb, css, _params) {
|
|
271
|
-
|
|
340
|
+
if (css && css.locator) {
|
|
341
|
+
css = css.locator;
|
|
342
|
+
}
|
|
343
|
+
let result = await this._locateElementByText(scope, this._fixUsingParams(text, _params), "*:not(script, style, head)", false, false, _params);
|
|
272
344
|
if (result.elementCount === 0) {
|
|
273
345
|
return;
|
|
274
346
|
}
|
|
@@ -283,7 +355,7 @@ class StableBrowser {
|
|
|
283
355
|
}
|
|
284
356
|
async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, _params) {
|
|
285
357
|
//const stringifyText = JSON.stringify(text);
|
|
286
|
-
return await scope.evaluate(([text, tag, regex, partial]) => {
|
|
358
|
+
return await scope.locator(":root").evaluate((_node, [text, tag, regex, partial]) => {
|
|
287
359
|
function isParent(parent, child) {
|
|
288
360
|
let currentNode = child.parentNode;
|
|
289
361
|
while (currentNode !== null) {
|
|
@@ -295,6 +367,15 @@ class StableBrowser {
|
|
|
295
367
|
return false;
|
|
296
368
|
}
|
|
297
369
|
document.isParent = isParent;
|
|
370
|
+
function getRegex(str) {
|
|
371
|
+
const match = str.match(/^\/(.*?)\/([gimuy]*)$/);
|
|
372
|
+
if (!match) {
|
|
373
|
+
return null;
|
|
374
|
+
}
|
|
375
|
+
let [_, pattern, flags] = match;
|
|
376
|
+
return new RegExp(pattern, flags);
|
|
377
|
+
}
|
|
378
|
+
document.getRegex = getRegex;
|
|
298
379
|
function collectAllShadowDomElements(element, result = []) {
|
|
299
380
|
// Check and add the element if it has a shadow root
|
|
300
381
|
if (element.shadowRoot) {
|
|
@@ -311,7 +392,11 @@ class StableBrowser {
|
|
|
311
392
|
}
|
|
312
393
|
document.collectAllShadowDomElements = collectAllShadowDomElements;
|
|
313
394
|
if (!tag) {
|
|
314
|
-
tag = "
|
|
395
|
+
tag = "*:not(script, style, head)";
|
|
396
|
+
}
|
|
397
|
+
let regexpSearch = document.getRegex(text);
|
|
398
|
+
if (regexpSearch) {
|
|
399
|
+
regex = true;
|
|
315
400
|
}
|
|
316
401
|
let elements = Array.from(document.querySelectorAll(tag));
|
|
317
402
|
let shadowHosts = [];
|
|
@@ -328,7 +413,9 @@ class StableBrowser {
|
|
|
328
413
|
let randomToken = null;
|
|
329
414
|
const foundElements = [];
|
|
330
415
|
if (regex) {
|
|
331
|
-
|
|
416
|
+
if (!regexpSearch) {
|
|
417
|
+
regexpSearch = new RegExp(text, "im");
|
|
418
|
+
}
|
|
332
419
|
for (let i = 0; i < elements.length; i++) {
|
|
333
420
|
const element = elements[i];
|
|
334
421
|
if ((element.innerText && regexpSearch.test(element.innerText)) ||
|
|
@@ -342,8 +429,8 @@ class StableBrowser {
|
|
|
342
429
|
for (let i = 0; i < elements.length; i++) {
|
|
343
430
|
const element = elements[i];
|
|
344
431
|
if (partial) {
|
|
345
|
-
if ((element.innerText && element.innerText.trim().includes(text)) ||
|
|
346
|
-
(element.value && element.value.includes(text))) {
|
|
432
|
+
if ((element.innerText && element.innerText.toLowerCase().trim().includes(text.toLowerCase())) ||
|
|
433
|
+
(element.value && element.value.toLowerCase().includes(text.toLowerCase()))) {
|
|
347
434
|
foundElements.push(element);
|
|
348
435
|
}
|
|
349
436
|
}
|
|
@@ -388,18 +475,29 @@ class StableBrowser {
|
|
|
388
475
|
}
|
|
389
476
|
async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true) {
|
|
390
477
|
let locatorSearch = selectorHierarchy[index];
|
|
478
|
+
try {
|
|
479
|
+
locatorSearch = JSON.parse(this._fixUsingParams(JSON.stringify(locatorSearch), _params));
|
|
480
|
+
}
|
|
481
|
+
catch (e) {
|
|
482
|
+
console.error(e);
|
|
483
|
+
}
|
|
391
484
|
//info.log += "searching for locator " + JSON.stringify(locatorSearch) + "\n";
|
|
392
485
|
let locator = null;
|
|
393
486
|
if (locatorSearch.climb && locatorSearch.climb >= 0) {
|
|
394
487
|
let locatorString = await this._locateElmentByTextClimbCss(scope, locatorSearch.text, locatorSearch.climb, locatorSearch.css, _params);
|
|
395
488
|
if (!locatorString) {
|
|
489
|
+
info.failCause.textNotFound = true;
|
|
490
|
+
info.failCause.lastError = "failed to locate element by text: " + locatorSearch.text;
|
|
396
491
|
return;
|
|
397
492
|
}
|
|
398
493
|
locator = this._getLocator({ css: locatorString }, scope, _params);
|
|
399
494
|
}
|
|
400
495
|
else if (locatorSearch.text) {
|
|
401
|
-
let
|
|
496
|
+
let text = this._fixUsingParams(locatorSearch.text, _params);
|
|
497
|
+
let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, _params);
|
|
402
498
|
if (result.elementCount === 0) {
|
|
499
|
+
info.failCause.textNotFound = true;
|
|
500
|
+
info.failCause.lastError = "failed to locate element by text: " + text;
|
|
403
501
|
return;
|
|
404
502
|
}
|
|
405
503
|
locatorSearch.css = "[data-blinq-id='blinq-id-" + result.randomToken + "']";
|
|
@@ -416,6 +514,9 @@ class StableBrowser {
|
|
|
416
514
|
// cssHref = true;
|
|
417
515
|
// }
|
|
418
516
|
let count = await locator.count();
|
|
517
|
+
if (count > 0 && !info.failCause.count) {
|
|
518
|
+
info.failCause.count = count;
|
|
519
|
+
}
|
|
419
520
|
//info.log += "total elements found " + count + "\n";
|
|
420
521
|
//let visibleCount = 0;
|
|
421
522
|
let visibleLocator = null;
|
|
@@ -433,6 +534,8 @@ class StableBrowser {
|
|
|
433
534
|
foundLocators.push(locator.nth(j));
|
|
434
535
|
}
|
|
435
536
|
else {
|
|
537
|
+
info.failCause.visible = visible;
|
|
538
|
+
info.failCause.enabled = enabled;
|
|
436
539
|
if (!info.printMessages) {
|
|
437
540
|
info.printMessages = {};
|
|
438
541
|
}
|
|
@@ -444,6 +547,11 @@ class StableBrowser {
|
|
|
444
547
|
}
|
|
445
548
|
}
|
|
446
549
|
async closeUnexpectedPopups(info, _params) {
|
|
550
|
+
if (!info) {
|
|
551
|
+
info = {};
|
|
552
|
+
info.failCause = {};
|
|
553
|
+
info.log = "";
|
|
554
|
+
}
|
|
447
555
|
if (this.configuration.popupHandlers && this.configuration.popupHandlers.length > 0) {
|
|
448
556
|
if (!info) {
|
|
449
557
|
info = {};
|
|
@@ -476,13 +584,18 @@ class StableBrowser {
|
|
|
476
584
|
if (result.foundElements.length > 0) {
|
|
477
585
|
let dialogCloseLocator = result.foundElements[0].locator;
|
|
478
586
|
await dialogCloseLocator.click();
|
|
587
|
+
// wait for the dialog to close
|
|
588
|
+
await dialogCloseLocator.waitFor({ state: "hidden" });
|
|
479
589
|
return { rerun: true };
|
|
480
590
|
}
|
|
481
591
|
}
|
|
482
592
|
}
|
|
483
593
|
return { rerun: false };
|
|
484
594
|
}
|
|
485
|
-
async _locate(selectors, info, _params, timeout
|
|
595
|
+
async _locate(selectors, info, _params, timeout) {
|
|
596
|
+
if (!timeout) {
|
|
597
|
+
timeout = 30000;
|
|
598
|
+
}
|
|
486
599
|
for (let i = 0; i < 3; i++) {
|
|
487
600
|
info.log += "attempt " + i + ": total locators " + selectors.locators.length + "\n";
|
|
488
601
|
for (let j = 0; j < selectors.locators.length; j++) {
|
|
@@ -496,32 +609,46 @@ class StableBrowser {
|
|
|
496
609
|
}
|
|
497
610
|
throw new Error("unable to locate element " + JSON.stringify(selectors));
|
|
498
611
|
}
|
|
499
|
-
async
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
612
|
+
async _findFrameScope(selectors, timeout = 30000, info) {
|
|
613
|
+
if (!info) {
|
|
614
|
+
info = {};
|
|
615
|
+
info.failCause = {};
|
|
616
|
+
info.log = "";
|
|
617
|
+
}
|
|
505
618
|
let scope = this.page;
|
|
619
|
+
if (selectors.frame) {
|
|
620
|
+
return selectors.frame;
|
|
621
|
+
}
|
|
506
622
|
if (selectors.iframe_src || selectors.frameLocators) {
|
|
507
|
-
const findFrame = (frame, framescope) => {
|
|
623
|
+
const findFrame = async (frame, framescope) => {
|
|
508
624
|
for (let i = 0; i < frame.selectors.length; i++) {
|
|
509
625
|
let frameLocator = frame.selectors[i];
|
|
510
626
|
if (frameLocator.css) {
|
|
511
|
-
|
|
512
|
-
|
|
627
|
+
let testframescope = framescope.frameLocator(frameLocator.css);
|
|
628
|
+
if (frameLocator.index) {
|
|
629
|
+
testframescope = framescope.nth(frameLocator.index);
|
|
630
|
+
}
|
|
631
|
+
try {
|
|
632
|
+
await testframescope.owner().evaluateHandle(() => true, null, {
|
|
633
|
+
timeout: 5000,
|
|
634
|
+
});
|
|
635
|
+
framescope = testframescope;
|
|
636
|
+
break;
|
|
637
|
+
}
|
|
638
|
+
catch (error) {
|
|
639
|
+
console.error("frame not found " + frameLocator.css);
|
|
640
|
+
}
|
|
513
641
|
}
|
|
514
642
|
}
|
|
515
643
|
if (frame.children) {
|
|
516
|
-
return findFrame(frame.children, framescope);
|
|
644
|
+
return await findFrame(frame.children, framescope);
|
|
517
645
|
}
|
|
518
646
|
return framescope;
|
|
519
647
|
};
|
|
520
|
-
info.log += "searching for iframe " + selectors.iframe_src + "/" + selectors.frameLocators + "\n";
|
|
521
648
|
while (true) {
|
|
522
649
|
let frameFound = false;
|
|
523
650
|
if (selectors.nestFrmLoc) {
|
|
524
|
-
scope = findFrame(selectors.nestFrmLoc, scope);
|
|
651
|
+
scope = await findFrame(selectors.nestFrmLoc, scope);
|
|
525
652
|
frameFound = true;
|
|
526
653
|
break;
|
|
527
654
|
}
|
|
@@ -541,6 +668,8 @@ class StableBrowser {
|
|
|
541
668
|
if (!scope) {
|
|
542
669
|
info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
|
|
543
670
|
if (performance.now() - startTime > timeout) {
|
|
671
|
+
info.failCause.iframeNotFound = true;
|
|
672
|
+
info.failCause.lastError = "unable to locate iframe " + selectors.iframe_src;
|
|
544
673
|
throw new Error("unable to locate iframe " + selectors.iframe_src);
|
|
545
674
|
}
|
|
546
675
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -550,6 +679,30 @@ class StableBrowser {
|
|
|
550
679
|
}
|
|
551
680
|
}
|
|
552
681
|
}
|
|
682
|
+
if (!scope) {
|
|
683
|
+
scope = this.page;
|
|
684
|
+
}
|
|
685
|
+
return scope;
|
|
686
|
+
}
|
|
687
|
+
async _getDocumentBody(selectors, timeout = 30000, info) {
|
|
688
|
+
let scope = await this._findFrameScope(selectors, timeout, info);
|
|
689
|
+
return scope.evaluate(() => {
|
|
690
|
+
var bodyContent = document.body.innerHTML;
|
|
691
|
+
return bodyContent;
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
async _locate_internal(selectors, info, _params, timeout = 30000) {
|
|
695
|
+
if (!info) {
|
|
696
|
+
info = {};
|
|
697
|
+
info.failCause = {};
|
|
698
|
+
info.log = "";
|
|
699
|
+
}
|
|
700
|
+
let highPriorityTimeout = 5000;
|
|
701
|
+
let visibleOnlyTimeout = 6000;
|
|
702
|
+
let startTime = performance.now();
|
|
703
|
+
let locatorsCount = 0;
|
|
704
|
+
//let arrayMode = Array.isArray(selectors);
|
|
705
|
+
let scope = await this._findFrameScope(selectors, timeout, info);
|
|
553
706
|
let selectorsLocators = null;
|
|
554
707
|
selectorsLocators = selectors.locators;
|
|
555
708
|
// group selectors by priority
|
|
@@ -651,6 +804,8 @@ class StableBrowser {
|
|
|
651
804
|
}
|
|
652
805
|
this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
|
|
653
806
|
info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
|
|
807
|
+
info.failCause.locatorNotFound = true;
|
|
808
|
+
info.failCause.lastError = "failed to locate unique element";
|
|
654
809
|
throw new Error("failed to locate first element no elements found, " + info.log);
|
|
655
810
|
}
|
|
656
811
|
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
|
|
@@ -682,89 +837,127 @@ class StableBrowser {
|
|
|
682
837
|
});
|
|
683
838
|
result.locatorIndex = i;
|
|
684
839
|
}
|
|
840
|
+
if (foundLocators.length > 1) {
|
|
841
|
+
info.failCause.foundMultiple = true;
|
|
842
|
+
}
|
|
685
843
|
}
|
|
686
844
|
return result;
|
|
687
845
|
}
|
|
688
|
-
async
|
|
689
|
-
this._validateSelectors(selectors);
|
|
846
|
+
async simpleClick(elementDescription, _params, options = {}, world = null) {
|
|
690
847
|
const startTime = Date.now();
|
|
691
|
-
|
|
692
|
-
|
|
848
|
+
let timeout = 30000;
|
|
849
|
+
if (options && options.timeout) {
|
|
850
|
+
timeout = options.timeout;
|
|
693
851
|
}
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
852
|
+
while (true) {
|
|
853
|
+
try {
|
|
854
|
+
const result = await locate_element(this.context, elementDescription, "click");
|
|
855
|
+
if (result?.elementNumber >= 0) {
|
|
856
|
+
const selectors = {
|
|
857
|
+
frame: result?.frame,
|
|
858
|
+
locators: [
|
|
859
|
+
{
|
|
860
|
+
css: result?.css,
|
|
861
|
+
},
|
|
862
|
+
],
|
|
863
|
+
};
|
|
864
|
+
await this.click(selectors, _params, options, world);
|
|
865
|
+
return;
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
catch (e) {
|
|
869
|
+
if (performance.now() - startTime > timeout) {
|
|
870
|
+
throw e;
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
async simpleClickType(elementDescription, value, _params, options = {}, world = null) {
|
|
877
|
+
const startTime = Date.now();
|
|
878
|
+
let timeout = 30000;
|
|
879
|
+
if (options && options.timeout) {
|
|
880
|
+
timeout = options.timeout;
|
|
881
|
+
}
|
|
882
|
+
while (true) {
|
|
883
|
+
try {
|
|
884
|
+
const result = await locate_element(this.context, elementDescription, "fill", value);
|
|
885
|
+
if (result?.elementNumber >= 0) {
|
|
886
|
+
const selectors = {
|
|
887
|
+
frame: result?.frame,
|
|
888
|
+
locators: [
|
|
889
|
+
{
|
|
890
|
+
css: result?.css,
|
|
891
|
+
},
|
|
892
|
+
],
|
|
893
|
+
};
|
|
894
|
+
await this.clickType(selectors, value, false, _params, options, world);
|
|
895
|
+
return;
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
catch (e) {
|
|
899
|
+
if (performance.now() - startTime > timeout) {
|
|
900
|
+
throw e;
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
async click(selectors, _params, options = {}, world = null) {
|
|
907
|
+
const state = {
|
|
908
|
+
selectors,
|
|
909
|
+
_params,
|
|
910
|
+
options,
|
|
911
|
+
world,
|
|
912
|
+
text: "Click element",
|
|
913
|
+
type: Types.CLICK,
|
|
914
|
+
operation: "click",
|
|
915
|
+
log: "***** click on " + selectors.element_name + " *****\n",
|
|
916
|
+
};
|
|
701
917
|
try {
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
918
|
+
await _preCommand(state, this);
|
|
919
|
+
if (state.options && state.options.context) {
|
|
920
|
+
state.selectors.locators[0].text = state.options.context;
|
|
921
|
+
}
|
|
705
922
|
try {
|
|
706
|
-
await
|
|
707
|
-
await element.click();
|
|
923
|
+
await state.element.click();
|
|
708
924
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
709
925
|
}
|
|
710
926
|
catch (e) {
|
|
711
927
|
// await this.closeUnexpectedPopups();
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
await element.dispatchEvent("click");
|
|
928
|
+
state.element = await this._locate(selectors, state.info, _params);
|
|
929
|
+
await state.element.dispatchEvent("click");
|
|
715
930
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
716
931
|
}
|
|
717
932
|
await this.waitForPageLoad();
|
|
718
|
-
return info;
|
|
933
|
+
return state.info;
|
|
719
934
|
}
|
|
720
935
|
catch (e) {
|
|
721
|
-
|
|
722
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
723
|
-
info.screenshotPath = screenshotPath;
|
|
724
|
-
Object.assign(e, { info: info });
|
|
725
|
-
error = e;
|
|
726
|
-
throw e;
|
|
936
|
+
await _commandError(state, e, this);
|
|
727
937
|
}
|
|
728
938
|
finally {
|
|
729
|
-
|
|
730
|
-
this._reportToWorld(world, {
|
|
731
|
-
element_name: selectors.element_name,
|
|
732
|
-
type: Types.CLICK,
|
|
733
|
-
text: `Click element`,
|
|
734
|
-
screenshotId,
|
|
735
|
-
result: error
|
|
736
|
-
? {
|
|
737
|
-
status: "FAILED",
|
|
738
|
-
startTime,
|
|
739
|
-
endTime,
|
|
740
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
741
|
-
}
|
|
742
|
-
: {
|
|
743
|
-
status: "PASSED",
|
|
744
|
-
startTime,
|
|
745
|
-
endTime,
|
|
746
|
-
},
|
|
747
|
-
info: info,
|
|
748
|
-
});
|
|
939
|
+
_commandFinally(state, this);
|
|
749
940
|
}
|
|
750
941
|
}
|
|
751
942
|
async setCheck(selectors, checked = true, _params, options = {}, world = null) {
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
943
|
+
const state = {
|
|
944
|
+
selectors,
|
|
945
|
+
_params,
|
|
946
|
+
options,
|
|
947
|
+
world,
|
|
948
|
+
type: checked ? Types.CHECK : Types.UNCHECK,
|
|
949
|
+
text: checked ? `Check element` : `Uncheck element`,
|
|
950
|
+
operation: "setCheck",
|
|
951
|
+
log: "***** check " + selectors.element_name + " *****\n",
|
|
952
|
+
};
|
|
762
953
|
try {
|
|
763
|
-
|
|
764
|
-
|
|
954
|
+
await _preCommand(state, this);
|
|
955
|
+
state.info.checked = checked;
|
|
956
|
+
// let element = await this._locate(selectors, info, _params);
|
|
957
|
+
// ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
765
958
|
try {
|
|
766
|
-
await this._highlightElements(element);
|
|
767
|
-
await element.setChecked(checked);
|
|
959
|
+
// await this._highlightElements(element);
|
|
960
|
+
await state.element.setChecked(checked);
|
|
768
961
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
769
962
|
}
|
|
770
963
|
catch (e) {
|
|
@@ -773,179 +966,108 @@ class StableBrowser {
|
|
|
773
966
|
}
|
|
774
967
|
else {
|
|
775
968
|
//await this.closeUnexpectedPopups();
|
|
776
|
-
info.log += "setCheck failed, will try again" + "\n";
|
|
777
|
-
element = await this._locate(selectors, info, _params);
|
|
778
|
-
await element.setChecked(checked, { timeout: 5000, force: true });
|
|
969
|
+
state.info.log += "setCheck failed, will try again" + "\n";
|
|
970
|
+
state.element = await this._locate(selectors, state.info, _params);
|
|
971
|
+
await state.element.setChecked(checked, { timeout: 5000, force: true });
|
|
779
972
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
780
973
|
}
|
|
781
974
|
}
|
|
782
975
|
await this.waitForPageLoad();
|
|
783
|
-
return info;
|
|
976
|
+
return state.info;
|
|
784
977
|
}
|
|
785
978
|
catch (e) {
|
|
786
|
-
|
|
787
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
788
|
-
info.screenshotPath = screenshotPath;
|
|
789
|
-
Object.assign(e, { info: info });
|
|
790
|
-
error = e;
|
|
791
|
-
throw e;
|
|
979
|
+
await _commandError(state, e, this);
|
|
792
980
|
}
|
|
793
981
|
finally {
|
|
794
|
-
|
|
795
|
-
this._reportToWorld(world, {
|
|
796
|
-
element_name: selectors.element_name,
|
|
797
|
-
type: checked ? Types.CHECK : Types.UNCHECK,
|
|
798
|
-
text: checked ? `Check element` : `Uncheck element`,
|
|
799
|
-
screenshotId,
|
|
800
|
-
result: error
|
|
801
|
-
? {
|
|
802
|
-
status: "FAILED",
|
|
803
|
-
startTime,
|
|
804
|
-
endTime,
|
|
805
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
806
|
-
}
|
|
807
|
-
: {
|
|
808
|
-
status: "PASSED",
|
|
809
|
-
startTime,
|
|
810
|
-
endTime,
|
|
811
|
-
},
|
|
812
|
-
info: info,
|
|
813
|
-
});
|
|
982
|
+
_commandFinally(state, this);
|
|
814
983
|
}
|
|
815
984
|
}
|
|
816
985
|
async hover(selectors, _params, options = {}, world = null) {
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
986
|
+
const state = {
|
|
987
|
+
selectors,
|
|
988
|
+
_params,
|
|
989
|
+
options,
|
|
990
|
+
world,
|
|
991
|
+
type: Types.HOVER,
|
|
992
|
+
text: `Hover element`,
|
|
993
|
+
operation: "hover",
|
|
994
|
+
log: "***** hover " + selectors.element_name + " *****\n",
|
|
995
|
+
};
|
|
826
996
|
try {
|
|
827
|
-
|
|
828
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
997
|
+
await _preCommand(state, this);
|
|
829
998
|
try {
|
|
830
|
-
await
|
|
831
|
-
await element.hover();
|
|
999
|
+
await state.element.hover();
|
|
832
1000
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
833
1001
|
}
|
|
834
1002
|
catch (e) {
|
|
835
1003
|
//await this.closeUnexpectedPopups();
|
|
836
|
-
info.log += "hover failed, will try again" + "\n";
|
|
837
|
-
element = await this._locate(selectors, info, _params);
|
|
838
|
-
await element.hover({ timeout: 10000 });
|
|
1004
|
+
state.info.log += "hover failed, will try again" + "\n";
|
|
1005
|
+
state.element = await this._locate(selectors, state.info, _params);
|
|
1006
|
+
await state.element.hover({ timeout: 10000 });
|
|
839
1007
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
840
1008
|
}
|
|
841
1009
|
await this.waitForPageLoad();
|
|
842
|
-
return info;
|
|
1010
|
+
return state.info;
|
|
843
1011
|
}
|
|
844
1012
|
catch (e) {
|
|
845
|
-
|
|
846
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
847
|
-
info.screenshotPath = screenshotPath;
|
|
848
|
-
Object.assign(e, { info: info });
|
|
849
|
-
error = e;
|
|
850
|
-
throw e;
|
|
1013
|
+
await _commandError(state, e, this);
|
|
851
1014
|
}
|
|
852
1015
|
finally {
|
|
853
|
-
|
|
854
|
-
this._reportToWorld(world, {
|
|
855
|
-
element_name: selectors.element_name,
|
|
856
|
-
type: Types.HOVER,
|
|
857
|
-
text: `Hover element`,
|
|
858
|
-
screenshotId,
|
|
859
|
-
result: error
|
|
860
|
-
? {
|
|
861
|
-
status: "FAILED",
|
|
862
|
-
startTime,
|
|
863
|
-
endTime,
|
|
864
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
865
|
-
}
|
|
866
|
-
: {
|
|
867
|
-
status: "PASSED",
|
|
868
|
-
startTime,
|
|
869
|
-
endTime,
|
|
870
|
-
},
|
|
871
|
-
info: info,
|
|
872
|
-
});
|
|
1016
|
+
_commandFinally(state, this);
|
|
873
1017
|
}
|
|
874
1018
|
}
|
|
875
1019
|
async selectOption(selectors, values, _params = null, options = {}, world = null) {
|
|
876
|
-
this._validateSelectors(selectors);
|
|
877
1020
|
if (!values) {
|
|
878
1021
|
throw new Error("values is null");
|
|
879
1022
|
}
|
|
880
|
-
const
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
1023
|
+
const state = {
|
|
1024
|
+
selectors,
|
|
1025
|
+
_params,
|
|
1026
|
+
options,
|
|
1027
|
+
world,
|
|
1028
|
+
value: values.toString(),
|
|
1029
|
+
type: Types.SELECT,
|
|
1030
|
+
text: `Select option: ${values}`,
|
|
1031
|
+
operation: "selectOption",
|
|
1032
|
+
log: "***** select option " + selectors.element_name + " *****\n",
|
|
1033
|
+
};
|
|
888
1034
|
try {
|
|
889
|
-
|
|
890
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1035
|
+
await _preCommand(state, this);
|
|
891
1036
|
try {
|
|
892
|
-
await
|
|
893
|
-
await element.selectOption(values);
|
|
1037
|
+
await state.element.selectOption(values);
|
|
894
1038
|
}
|
|
895
1039
|
catch (e) {
|
|
896
1040
|
//await this.closeUnexpectedPopups();
|
|
897
|
-
info.log += "selectOption failed, will try force" + "\n";
|
|
898
|
-
await element.selectOption(values, { timeout: 10000, force: true });
|
|
1041
|
+
state.info.log += "selectOption failed, will try force" + "\n";
|
|
1042
|
+
await state.element.selectOption(values, { timeout: 10000, force: true });
|
|
899
1043
|
}
|
|
900
1044
|
await this.waitForPageLoad();
|
|
901
|
-
return info;
|
|
1045
|
+
return state.info;
|
|
902
1046
|
}
|
|
903
1047
|
catch (e) {
|
|
904
|
-
|
|
905
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
906
|
-
info.screenshotPath = screenshotPath;
|
|
907
|
-
Object.assign(e, { info: info });
|
|
908
|
-
this.logger.info("click failed, will try next selector");
|
|
909
|
-
error = e;
|
|
910
|
-
throw e;
|
|
1048
|
+
await _commandError(state, e, this);
|
|
911
1049
|
}
|
|
912
1050
|
finally {
|
|
913
|
-
|
|
914
|
-
this._reportToWorld(world, {
|
|
915
|
-
element_name: selectors.element_name,
|
|
916
|
-
type: Types.SELECT,
|
|
917
|
-
text: `Select option: ${values}`,
|
|
918
|
-
value: values.toString(),
|
|
919
|
-
screenshotId,
|
|
920
|
-
result: error
|
|
921
|
-
? {
|
|
922
|
-
status: "FAILED",
|
|
923
|
-
startTime,
|
|
924
|
-
endTime,
|
|
925
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
926
|
-
}
|
|
927
|
-
: {
|
|
928
|
-
status: "PASSED",
|
|
929
|
-
startTime,
|
|
930
|
-
endTime,
|
|
931
|
-
},
|
|
932
|
-
info: info,
|
|
933
|
-
});
|
|
1051
|
+
_commandFinally(state, this);
|
|
934
1052
|
}
|
|
935
1053
|
}
|
|
936
1054
|
async type(_value, _params = null, options = {}, world = null) {
|
|
937
|
-
const
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
1055
|
+
const state = {
|
|
1056
|
+
value: _value,
|
|
1057
|
+
_params,
|
|
1058
|
+
options,
|
|
1059
|
+
world,
|
|
1060
|
+
locate: false,
|
|
1061
|
+
scroll: false,
|
|
1062
|
+
highlight: false,
|
|
1063
|
+
type: Types.TYPE_PRESS,
|
|
1064
|
+
text: `Type value: ${_value}`,
|
|
1065
|
+
operation: "type",
|
|
1066
|
+
log: "",
|
|
1067
|
+
};
|
|
946
1068
|
try {
|
|
947
|
-
|
|
948
|
-
const valueSegment =
|
|
1069
|
+
await _preCommand(state, this);
|
|
1070
|
+
const valueSegment = state.value.split("&&");
|
|
949
1071
|
for (let i = 0; i < valueSegment.length; i++) {
|
|
950
1072
|
if (i > 0) {
|
|
951
1073
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -965,108 +1087,53 @@ class StableBrowser {
|
|
|
965
1087
|
await this.page.keyboard.type(value);
|
|
966
1088
|
}
|
|
967
1089
|
}
|
|
968
|
-
return info;
|
|
1090
|
+
return state.info;
|
|
969
1091
|
}
|
|
970
1092
|
catch (e) {
|
|
971
|
-
|
|
972
|
-
this.logger.error("type failed " + info.log);
|
|
973
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
974
|
-
info.screenshotPath = screenshotPath;
|
|
975
|
-
Object.assign(e, { info: info });
|
|
976
|
-
error = e;
|
|
977
|
-
throw e;
|
|
1093
|
+
await _commandError(state, e, this);
|
|
978
1094
|
}
|
|
979
1095
|
finally {
|
|
980
|
-
|
|
981
|
-
this._reportToWorld(world, {
|
|
982
|
-
type: Types.TYPE_PRESS,
|
|
983
|
-
screenshotId,
|
|
984
|
-
value: _value,
|
|
985
|
-
text: `type value: ${_value}`,
|
|
986
|
-
result: error
|
|
987
|
-
? {
|
|
988
|
-
status: "FAILED",
|
|
989
|
-
startTime,
|
|
990
|
-
endTime,
|
|
991
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
992
|
-
}
|
|
993
|
-
: {
|
|
994
|
-
status: "PASSED",
|
|
995
|
-
startTime,
|
|
996
|
-
endTime,
|
|
997
|
-
},
|
|
998
|
-
info: info,
|
|
999
|
-
});
|
|
1096
|
+
_commandFinally(state, this);
|
|
1000
1097
|
}
|
|
1001
1098
|
}
|
|
1002
1099
|
async setInputValue(selectors, value, _params = null, options = {}, world = null) {
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
let screenshotPath = null;
|
|
1100
|
+
const state = {
|
|
1101
|
+
selectors,
|
|
1102
|
+
_params,
|
|
1103
|
+
value,
|
|
1104
|
+
options,
|
|
1105
|
+
world,
|
|
1106
|
+
type: Types.SET_INPUT,
|
|
1107
|
+
text: `Set input value`,
|
|
1108
|
+
operation: "setInputValue",
|
|
1109
|
+
log: "***** set input value " + selectors.element_name + " *****\n",
|
|
1110
|
+
};
|
|
1015
1111
|
try {
|
|
1016
|
-
|
|
1017
|
-
let
|
|
1018
|
-
await this.scrollIfNeeded(element, info);
|
|
1019
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1020
|
-
await this._highlightElements(element);
|
|
1112
|
+
await _preCommand(state, this);
|
|
1113
|
+
let value = await this._replaceWithLocalData(state.value, this);
|
|
1021
1114
|
try {
|
|
1022
|
-
await element.evaluateHandle((el, value) => {
|
|
1115
|
+
await state.element.evaluateHandle((el, value) => {
|
|
1023
1116
|
el.value = value;
|
|
1024
1117
|
}, value);
|
|
1025
1118
|
}
|
|
1026
1119
|
catch (error) {
|
|
1027
1120
|
this.logger.error("setInputValue failed, will try again");
|
|
1028
|
-
|
|
1029
|
-
info.
|
|
1030
|
-
|
|
1031
|
-
await element.evaluateHandle((el, value) => {
|
|
1121
|
+
await _screenshot(state, this);
|
|
1122
|
+
Object.assign(error, { info: state.info });
|
|
1123
|
+
await state.element.evaluateHandle((el, value) => {
|
|
1032
1124
|
el.value = value;
|
|
1033
1125
|
});
|
|
1034
1126
|
}
|
|
1035
1127
|
}
|
|
1036
1128
|
catch (e) {
|
|
1037
|
-
|
|
1038
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1039
|
-
info.screenshotPath = screenshotPath;
|
|
1040
|
-
Object.assign(e, { info: info });
|
|
1041
|
-
error = e;
|
|
1042
|
-
throw e;
|
|
1129
|
+
await _commandError(state, e, this);
|
|
1043
1130
|
}
|
|
1044
1131
|
finally {
|
|
1045
|
-
|
|
1046
|
-
this._reportToWorld(world, {
|
|
1047
|
-
element_name: selectors.element_name,
|
|
1048
|
-
type: Types.SET_INPUT,
|
|
1049
|
-
text: `Set input value`,
|
|
1050
|
-
value: value,
|
|
1051
|
-
screenshotId,
|
|
1052
|
-
result: error
|
|
1053
|
-
? {
|
|
1054
|
-
status: "FAILED",
|
|
1055
|
-
startTime,
|
|
1056
|
-
endTime,
|
|
1057
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1058
|
-
}
|
|
1059
|
-
: {
|
|
1060
|
-
status: "PASSED",
|
|
1061
|
-
startTime,
|
|
1062
|
-
endTime,
|
|
1063
|
-
},
|
|
1064
|
-
info: info,
|
|
1065
|
-
});
|
|
1132
|
+
_commandFinally(state, this);
|
|
1066
1133
|
}
|
|
1067
1134
|
}
|
|
1068
1135
|
async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
|
|
1069
|
-
|
|
1136
|
+
_validateSelectors(selectors);
|
|
1070
1137
|
const startTime = Date.now();
|
|
1071
1138
|
let error = null;
|
|
1072
1139
|
let screenshotId = null;
|
|
@@ -1159,32 +1226,32 @@ class StableBrowser {
|
|
|
1159
1226
|
}
|
|
1160
1227
|
}
|
|
1161
1228
|
async clickType(selectors, _value, enter = false, _params = null, options = {}, world = null) {
|
|
1162
|
-
|
|
1163
|
-
const startTime = Date.now();
|
|
1164
|
-
let error = null;
|
|
1165
|
-
let screenshotId = null;
|
|
1166
|
-
let screenshotPath = null;
|
|
1167
|
-
const info = {};
|
|
1168
|
-
info.log = "***** clickType on " + selectors.element_name + " with value " + _value + "*****\n";
|
|
1169
|
-
info.operation = "clickType";
|
|
1170
|
-
info.selectors = selectors;
|
|
1229
|
+
_value = unEscapeString(_value);
|
|
1171
1230
|
const newValue = await this._replaceWithLocalData(_value, world);
|
|
1231
|
+
const state = {
|
|
1232
|
+
selectors,
|
|
1233
|
+
_params,
|
|
1234
|
+
value: newValue,
|
|
1235
|
+
originalValue: _value,
|
|
1236
|
+
options,
|
|
1237
|
+
world,
|
|
1238
|
+
type: Types.FILL,
|
|
1239
|
+
text: `Click type input with value: ${_value}`,
|
|
1240
|
+
operation: "clickType",
|
|
1241
|
+
log: "***** clickType on " + selectors.element_name + " with value " + maskValue(_value) + "*****\n",
|
|
1242
|
+
};
|
|
1172
1243
|
if (newValue !== _value) {
|
|
1173
1244
|
//this.logger.info(_value + "=" + newValue);
|
|
1174
1245
|
_value = newValue;
|
|
1175
1246
|
}
|
|
1176
|
-
info.value = _value;
|
|
1177
1247
|
try {
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
await this.scrollIfNeeded(element, info);
|
|
1181
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1182
|
-
await this._highlightElements(element);
|
|
1248
|
+
await _preCommand(state, this);
|
|
1249
|
+
state.info.value = _value;
|
|
1183
1250
|
if (options === null || options === undefined || !options.press) {
|
|
1184
1251
|
try {
|
|
1185
|
-
let currentValue = await element.inputValue();
|
|
1252
|
+
let currentValue = await state.element.inputValue();
|
|
1186
1253
|
if (currentValue) {
|
|
1187
|
-
await element.fill("");
|
|
1254
|
+
await state.element.fill("");
|
|
1188
1255
|
}
|
|
1189
1256
|
}
|
|
1190
1257
|
catch (e) {
|
|
@@ -1193,22 +1260,22 @@ class StableBrowser {
|
|
|
1193
1260
|
}
|
|
1194
1261
|
if (options === null || options === undefined || options.press) {
|
|
1195
1262
|
try {
|
|
1196
|
-
await element.click({ timeout: 5000 });
|
|
1263
|
+
await state.element.click({ timeout: 5000 });
|
|
1197
1264
|
}
|
|
1198
1265
|
catch (e) {
|
|
1199
|
-
await element.dispatchEvent("click");
|
|
1266
|
+
await state.element.dispatchEvent("click");
|
|
1200
1267
|
}
|
|
1201
1268
|
}
|
|
1202
1269
|
else {
|
|
1203
1270
|
try {
|
|
1204
|
-
await element.focus();
|
|
1271
|
+
await state.element.focus();
|
|
1205
1272
|
}
|
|
1206
1273
|
catch (e) {
|
|
1207
|
-
await element.dispatchEvent("focus");
|
|
1274
|
+
await state.element.dispatchEvent("focus");
|
|
1208
1275
|
}
|
|
1209
1276
|
}
|
|
1210
1277
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1211
|
-
const valueSegment =
|
|
1278
|
+
const valueSegment = state.value.split("&&");
|
|
1212
1279
|
for (let i = 0; i < valueSegment.length; i++) {
|
|
1213
1280
|
if (i > 0) {
|
|
1214
1281
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -1228,13 +1295,14 @@ class StableBrowser {
|
|
|
1228
1295
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1229
1296
|
}
|
|
1230
1297
|
}
|
|
1298
|
+
await _screenshot(state, this);
|
|
1231
1299
|
if (enter === true) {
|
|
1232
1300
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1233
1301
|
await this.page.keyboard.press("Enter");
|
|
1234
1302
|
await this.waitForPageLoad();
|
|
1235
1303
|
}
|
|
1236
1304
|
else if (enter === false) {
|
|
1237
|
-
await element.dispatchEvent("change");
|
|
1305
|
+
await state.element.dispatchEvent("change");
|
|
1238
1306
|
//await this.page.keyboard.press("Tab");
|
|
1239
1307
|
}
|
|
1240
1308
|
else {
|
|
@@ -1243,103 +1311,50 @@ class StableBrowser {
|
|
|
1243
1311
|
await this.waitForPageLoad();
|
|
1244
1312
|
}
|
|
1245
1313
|
}
|
|
1246
|
-
return info;
|
|
1314
|
+
return state.info;
|
|
1247
1315
|
}
|
|
1248
1316
|
catch (e) {
|
|
1249
|
-
|
|
1250
|
-
this.logger.error("fill failed " + JSON.stringify(info));
|
|
1251
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1252
|
-
info.screenshotPath = screenshotPath;
|
|
1253
|
-
Object.assign(e, { info: info });
|
|
1254
|
-
error = e;
|
|
1255
|
-
throw e;
|
|
1317
|
+
await _commandError(state, e, this);
|
|
1256
1318
|
}
|
|
1257
1319
|
finally {
|
|
1258
|
-
|
|
1259
|
-
this._reportToWorld(world, {
|
|
1260
|
-
element_name: selectors.element_name,
|
|
1261
|
-
type: Types.FILL,
|
|
1262
|
-
screenshotId,
|
|
1263
|
-
value: _value,
|
|
1264
|
-
text: `clickType input with value: ${_value}`,
|
|
1265
|
-
result: error
|
|
1266
|
-
? {
|
|
1267
|
-
status: "FAILED",
|
|
1268
|
-
startTime,
|
|
1269
|
-
endTime,
|
|
1270
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1271
|
-
}
|
|
1272
|
-
: {
|
|
1273
|
-
status: "PASSED",
|
|
1274
|
-
startTime,
|
|
1275
|
-
endTime,
|
|
1276
|
-
},
|
|
1277
|
-
info: info,
|
|
1278
|
-
});
|
|
1320
|
+
_commandFinally(state, this);
|
|
1279
1321
|
}
|
|
1280
1322
|
}
|
|
1281
1323
|
async fill(selectors, value, enter = false, _params = null, options = {}, world = null) {
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1324
|
+
const state = {
|
|
1325
|
+
selectors,
|
|
1326
|
+
_params,
|
|
1327
|
+
value: unEscapeString(value),
|
|
1328
|
+
options,
|
|
1329
|
+
world,
|
|
1330
|
+
type: Types.FILL,
|
|
1331
|
+
text: `Fill input with value: ${value}`,
|
|
1332
|
+
operation: "fill",
|
|
1333
|
+
log: "***** fill on " + selectors.element_name + " with value " + value + "*****\n",
|
|
1334
|
+
};
|
|
1292
1335
|
try {
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
await
|
|
1296
|
-
await element.fill(value);
|
|
1297
|
-
await element.dispatchEvent("change");
|
|
1336
|
+
await _preCommand(state, this);
|
|
1337
|
+
await state.element.fill(value);
|
|
1338
|
+
await state.element.dispatchEvent("change");
|
|
1298
1339
|
if (enter) {
|
|
1299
1340
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1300
1341
|
await this.page.keyboard.press("Enter");
|
|
1301
1342
|
}
|
|
1302
1343
|
await this.waitForPageLoad();
|
|
1303
|
-
return info;
|
|
1344
|
+
return state.info;
|
|
1304
1345
|
}
|
|
1305
1346
|
catch (e) {
|
|
1306
|
-
|
|
1307
|
-
this.logger.error("fill failed " + info.log);
|
|
1308
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1309
|
-
info.screenshotPath = screenshotPath;
|
|
1310
|
-
Object.assign(e, { info: info });
|
|
1311
|
-
error = e;
|
|
1312
|
-
throw e;
|
|
1347
|
+
await _commandError(state, e, this);
|
|
1313
1348
|
}
|
|
1314
1349
|
finally {
|
|
1315
|
-
|
|
1316
|
-
this._reportToWorld(world, {
|
|
1317
|
-
element_name: selectors.element_name,
|
|
1318
|
-
type: Types.FILL,
|
|
1319
|
-
screenshotId,
|
|
1320
|
-
value,
|
|
1321
|
-
text: `Fill input with value: ${value}`,
|
|
1322
|
-
result: error
|
|
1323
|
-
? {
|
|
1324
|
-
status: "FAILED",
|
|
1325
|
-
startTime,
|
|
1326
|
-
endTime,
|
|
1327
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1328
|
-
}
|
|
1329
|
-
: {
|
|
1330
|
-
status: "PASSED",
|
|
1331
|
-
startTime,
|
|
1332
|
-
endTime,
|
|
1333
|
-
},
|
|
1334
|
-
info: info,
|
|
1335
|
-
});
|
|
1350
|
+
_commandFinally(state, this);
|
|
1336
1351
|
}
|
|
1337
1352
|
}
|
|
1338
1353
|
async getText(selectors, _params = null, options = {}, info = {}, world = null) {
|
|
1339
1354
|
return await this._getText(selectors, 0, _params, options, info, world);
|
|
1340
1355
|
}
|
|
1341
1356
|
async _getText(selectors, climb, _params = null, options = {}, info = {}, world = null) {
|
|
1342
|
-
|
|
1357
|
+
_validateSelectors(selectors);
|
|
1343
1358
|
let screenshotId = null;
|
|
1344
1359
|
let screenshotPath = null;
|
|
1345
1360
|
if (!info.log) {
|
|
@@ -1383,165 +1398,124 @@ class StableBrowser {
|
|
|
1383
1398
|
}
|
|
1384
1399
|
}
|
|
1385
1400
|
async containsPattern(selectors, pattern, text, _params = null, options = {}, world = null) {
|
|
1386
|
-
var _a;
|
|
1387
|
-
this._validateSelectors(selectors);
|
|
1388
1401
|
if (!pattern) {
|
|
1389
1402
|
throw new Error("pattern is null");
|
|
1390
1403
|
}
|
|
1391
1404
|
if (!text) {
|
|
1392
1405
|
throw new Error("text is null");
|
|
1393
1406
|
}
|
|
1407
|
+
const state = {
|
|
1408
|
+
selectors,
|
|
1409
|
+
_params,
|
|
1410
|
+
pattern,
|
|
1411
|
+
value: pattern,
|
|
1412
|
+
options,
|
|
1413
|
+
world,
|
|
1414
|
+
locate: false,
|
|
1415
|
+
scroll: false,
|
|
1416
|
+
screenshot: false,
|
|
1417
|
+
highlight: false,
|
|
1418
|
+
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1419
|
+
text: `Verify element contains pattern: ${pattern}`,
|
|
1420
|
+
operation: "containsPattern",
|
|
1421
|
+
log: "***** verify element " + selectors.element_name + " contains pattern " + pattern + " *****\n",
|
|
1422
|
+
};
|
|
1394
1423
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
1395
1424
|
if (newValue !== text) {
|
|
1396
1425
|
this.logger.info(text + "=" + newValue);
|
|
1397
1426
|
text = newValue;
|
|
1398
1427
|
}
|
|
1399
|
-
const startTime = Date.now();
|
|
1400
|
-
let error = null;
|
|
1401
|
-
let screenshotId = null;
|
|
1402
|
-
let screenshotPath = null;
|
|
1403
|
-
const info = {};
|
|
1404
|
-
info.log =
|
|
1405
|
-
"***** verify element " + selectors.element_name + " contains pattern " + pattern + "/" + text + " *****\n";
|
|
1406
|
-
info.operation = "containsPattern";
|
|
1407
|
-
info.selectors = selectors;
|
|
1408
|
-
info.value = text;
|
|
1409
|
-
info.pattern = pattern;
|
|
1410
1428
|
let foundObj = null;
|
|
1411
1429
|
try {
|
|
1412
|
-
|
|
1430
|
+
await _preCommand(state, this);
|
|
1431
|
+
state.info.pattern = pattern;
|
|
1432
|
+
foundObj = await this._getText(selectors, 0, _params, options, state.info, world);
|
|
1413
1433
|
if (foundObj && foundObj.element) {
|
|
1414
|
-
await this.scrollIfNeeded(foundObj.element, info);
|
|
1434
|
+
await this.scrollIfNeeded(foundObj.element, state.info);
|
|
1415
1435
|
}
|
|
1416
|
-
|
|
1436
|
+
await _screenshot(state, this);
|
|
1417
1437
|
let escapedText = text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
|
|
1418
1438
|
pattern = pattern.replace("{text}", escapedText);
|
|
1419
1439
|
let regex = new RegExp(pattern, "im");
|
|
1420
|
-
if (!regex.test(foundObj
|
|
1421
|
-
info.foundText = foundObj
|
|
1440
|
+
if (!regex.test(foundObj?.text) && !foundObj?.value?.includes(text)) {
|
|
1441
|
+
state.info.foundText = foundObj?.text;
|
|
1422
1442
|
throw new Error("element doesn't contain text " + text);
|
|
1423
1443
|
}
|
|
1424
|
-
return info;
|
|
1444
|
+
return state.info;
|
|
1425
1445
|
}
|
|
1426
1446
|
catch (e) {
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
this.logger.error("found text " + (foundObj === null || foundObj === void 0 ? void 0 : foundObj.text) + " pattern " + pattern);
|
|
1430
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1431
|
-
info.screenshotPath = screenshotPath;
|
|
1432
|
-
Object.assign(e, { info: info });
|
|
1433
|
-
error = e;
|
|
1434
|
-
throw e;
|
|
1447
|
+
this.logger.error("found text " + foundObj?.text + " pattern " + pattern);
|
|
1448
|
+
await _commandError(state, e, this);
|
|
1435
1449
|
}
|
|
1436
1450
|
finally {
|
|
1437
|
-
|
|
1438
|
-
this._reportToWorld(world, {
|
|
1439
|
-
element_name: selectors.element_name,
|
|
1440
|
-
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1441
|
-
value: pattern,
|
|
1442
|
-
text: `Verify element contains pattern: ${pattern}`,
|
|
1443
|
-
screenshotId: foundObj === null || foundObj === void 0 ? void 0 : foundObj.screenshotId,
|
|
1444
|
-
result: error
|
|
1445
|
-
? {
|
|
1446
|
-
status: "FAILED",
|
|
1447
|
-
startTime,
|
|
1448
|
-
endTime,
|
|
1449
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1450
|
-
}
|
|
1451
|
-
: {
|
|
1452
|
-
status: "PASSED",
|
|
1453
|
-
startTime,
|
|
1454
|
-
endTime,
|
|
1455
|
-
},
|
|
1456
|
-
info: info,
|
|
1457
|
-
});
|
|
1451
|
+
_commandFinally(state, this);
|
|
1458
1452
|
}
|
|
1459
1453
|
}
|
|
1460
1454
|
async containsText(selectors, text, climb, _params = null, options = {}, world = null) {
|
|
1461
|
-
|
|
1462
|
-
|
|
1455
|
+
const state = {
|
|
1456
|
+
selectors,
|
|
1457
|
+
_params,
|
|
1458
|
+
value: text,
|
|
1459
|
+
options,
|
|
1460
|
+
world,
|
|
1461
|
+
locate: false,
|
|
1462
|
+
scroll: false,
|
|
1463
|
+
screenshot: false,
|
|
1464
|
+
highlight: false,
|
|
1465
|
+
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1466
|
+
text: `Verify element contains text: ${text}`,
|
|
1467
|
+
operation: "containsText",
|
|
1468
|
+
log: "***** verify element " + selectors.element_name + " contains text " + text + " *****\n",
|
|
1469
|
+
};
|
|
1463
1470
|
if (!text) {
|
|
1464
1471
|
throw new Error("text is null");
|
|
1465
1472
|
}
|
|
1466
|
-
|
|
1467
|
-
let error = null;
|
|
1468
|
-
let screenshotId = null;
|
|
1469
|
-
let screenshotPath = null;
|
|
1470
|
-
const info = {};
|
|
1471
|
-
info.log = "***** verify element " + selectors.element_name + " contains text " + text + " *****\n";
|
|
1472
|
-
info.operation = "containsText";
|
|
1473
|
-
info.selectors = selectors;
|
|
1473
|
+
text = unEscapeString(text);
|
|
1474
1474
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
1475
1475
|
if (newValue !== text) {
|
|
1476
1476
|
this.logger.info(text + "=" + newValue);
|
|
1477
1477
|
text = newValue;
|
|
1478
1478
|
}
|
|
1479
|
-
info.value = text;
|
|
1480
1479
|
let foundObj = null;
|
|
1481
1480
|
try {
|
|
1482
|
-
|
|
1481
|
+
await _preCommand(state, this);
|
|
1482
|
+
foundObj = await this._getText(selectors, climb, _params, options, state.info, world);
|
|
1483
1483
|
if (foundObj && foundObj.element) {
|
|
1484
|
-
await this.scrollIfNeeded(foundObj.element, info);
|
|
1484
|
+
await this.scrollIfNeeded(foundObj.element, state.info);
|
|
1485
1485
|
}
|
|
1486
|
-
|
|
1486
|
+
await _screenshot(state, this);
|
|
1487
1487
|
const dateAlternatives = findDateAlternatives(text);
|
|
1488
1488
|
const numberAlternatives = findNumberAlternatives(text);
|
|
1489
1489
|
if (dateAlternatives.date) {
|
|
1490
1490
|
for (let i = 0; i < dateAlternatives.dates.length; i++) {
|
|
1491
|
-
if (
|
|
1492
|
-
|
|
1493
|
-
return info;
|
|
1491
|
+
if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
|
|
1492
|
+
foundObj?.value?.includes(dateAlternatives.dates[i])) {
|
|
1493
|
+
return state.info;
|
|
1494
1494
|
}
|
|
1495
1495
|
}
|
|
1496
1496
|
throw new Error("element doesn't contain text " + text);
|
|
1497
1497
|
}
|
|
1498
1498
|
else if (numberAlternatives.number) {
|
|
1499
1499
|
for (let i = 0; i < numberAlternatives.numbers.length; i++) {
|
|
1500
|
-
if (
|
|
1501
|
-
|
|
1502
|
-
return info;
|
|
1500
|
+
if (foundObj?.text.includes(numberAlternatives.numbers[i]) ||
|
|
1501
|
+
foundObj?.value?.includes(numberAlternatives.numbers[i])) {
|
|
1502
|
+
return state.info;
|
|
1503
1503
|
}
|
|
1504
1504
|
}
|
|
1505
1505
|
throw new Error("element doesn't contain text " + text);
|
|
1506
1506
|
}
|
|
1507
|
-
else if (!
|
|
1508
|
-
info.foundText = foundObj
|
|
1509
|
-
info.value = foundObj
|
|
1507
|
+
else if (!foundObj?.text.includes(text) && !foundObj?.value?.includes(text)) {
|
|
1508
|
+
state.info.foundText = foundObj?.text;
|
|
1509
|
+
state.info.value = foundObj?.value;
|
|
1510
1510
|
throw new Error("element doesn't contain text " + text);
|
|
1511
1511
|
}
|
|
1512
|
-
return info;
|
|
1512
|
+
return state.info;
|
|
1513
1513
|
}
|
|
1514
1514
|
catch (e) {
|
|
1515
|
-
|
|
1516
|
-
this.logger.error("verify element contains text failed " + info.log);
|
|
1517
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1518
|
-
info.screenshotPath = screenshotPath;
|
|
1519
|
-
Object.assign(e, { info: info });
|
|
1520
|
-
error = e;
|
|
1521
|
-
throw e;
|
|
1515
|
+
await _commandError(state, e, this);
|
|
1522
1516
|
}
|
|
1523
1517
|
finally {
|
|
1524
|
-
|
|
1525
|
-
this._reportToWorld(world, {
|
|
1526
|
-
element_name: selectors.element_name,
|
|
1527
|
-
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1528
|
-
text: `Verify element contains text: ${text}`,
|
|
1529
|
-
value: text,
|
|
1530
|
-
screenshotId: foundObj === null || foundObj === void 0 ? void 0 : foundObj.screenshotId,
|
|
1531
|
-
result: error
|
|
1532
|
-
? {
|
|
1533
|
-
status: "FAILED",
|
|
1534
|
-
startTime,
|
|
1535
|
-
endTime,
|
|
1536
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1537
|
-
}
|
|
1538
|
-
: {
|
|
1539
|
-
status: "PASSED",
|
|
1540
|
-
startTime,
|
|
1541
|
-
endTime,
|
|
1542
|
-
},
|
|
1543
|
-
info: info,
|
|
1544
|
-
});
|
|
1518
|
+
_commandFinally(state, this);
|
|
1545
1519
|
}
|
|
1546
1520
|
}
|
|
1547
1521
|
_getDataFile(world = null) {
|
|
@@ -1770,7 +1744,6 @@ class StableBrowser {
|
|
|
1770
1744
|
}
|
|
1771
1745
|
async takeScreenshot(screenshotPath) {
|
|
1772
1746
|
const playContext = this.context.playContext;
|
|
1773
|
-
const client = await playContext.newCDPSession(this.page);
|
|
1774
1747
|
// Using CDP to capture the screenshot
|
|
1775
1748
|
const viewportWidth = Math.max(...(await this.page.evaluate(() => [
|
|
1776
1749
|
document.body.scrollWidth,
|
|
@@ -1780,97 +1753,67 @@ class StableBrowser {
|
|
|
1780
1753
|
document.body.clientWidth,
|
|
1781
1754
|
document.documentElement.clientWidth,
|
|
1782
1755
|
])));
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
await client.detach();
|
|
1756
|
+
let screenshotBuffer = null;
|
|
1757
|
+
if (this.context.browserName === "chromium") {
|
|
1758
|
+
const client = await playContext.newCDPSession(this.page);
|
|
1759
|
+
const { data } = await client.send("Page.captureScreenshot", {
|
|
1760
|
+
format: "png",
|
|
1761
|
+
// clip: {
|
|
1762
|
+
// x: 0,
|
|
1763
|
+
// y: 0,
|
|
1764
|
+
// width: viewportWidth,
|
|
1765
|
+
// height: viewportHeight,
|
|
1766
|
+
// scale: 1,
|
|
1767
|
+
// },
|
|
1768
|
+
});
|
|
1769
|
+
await client.detach();
|
|
1770
|
+
if (!screenshotPath) {
|
|
1771
|
+
return data;
|
|
1772
|
+
}
|
|
1773
|
+
screenshotBuffer = Buffer.from(data, "base64");
|
|
1774
|
+
}
|
|
1775
|
+
else {
|
|
1776
|
+
screenshotBuffer = await this.page.screenshot();
|
|
1777
|
+
}
|
|
1778
|
+
let image = await Jimp.read(screenshotBuffer);
|
|
1779
|
+
// Get the image dimensions
|
|
1780
|
+
const { width, height } = image.bitmap;
|
|
1781
|
+
const resizeRatio = viewportWidth / width;
|
|
1782
|
+
// Resize the image to fit within the viewport dimensions without enlarging
|
|
1783
|
+
if (width > viewportWidth) {
|
|
1784
|
+
image = image.resize({ w: viewportWidth, h: height * resizeRatio }); // Resize the image while maintaining aspect ratio
|
|
1785
|
+
await image.write(screenshotPath);
|
|
1786
|
+
}
|
|
1787
|
+
else {
|
|
1788
|
+
fs.writeFileSync(screenshotPath, screenshotBuffer);
|
|
1789
|
+
}
|
|
1818
1790
|
}
|
|
1819
1791
|
async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1792
|
+
const state = {
|
|
1793
|
+
selectors,
|
|
1794
|
+
_params,
|
|
1795
|
+
options,
|
|
1796
|
+
world,
|
|
1797
|
+
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1798
|
+
text: `Verify element exists in page`,
|
|
1799
|
+
operation: "verifyElementExistInPage",
|
|
1800
|
+
log: "***** verify element " + selectors.element_name + " exists in page *****\n",
|
|
1801
|
+
};
|
|
1825
1802
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1826
|
-
const info = {};
|
|
1827
|
-
info.log = "***** verify element " + selectors.element_name + " exists in page *****\n";
|
|
1828
|
-
info.operation = "verify";
|
|
1829
|
-
info.selectors = selectors;
|
|
1830
1803
|
try {
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
}
|
|
1835
|
-
await this._highlightElements(element);
|
|
1836
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1837
|
-
await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
1838
|
-
return info;
|
|
1804
|
+
await _preCommand(state, this);
|
|
1805
|
+
await expect(state.element).toHaveCount(1, { timeout: 10000 });
|
|
1806
|
+
return state.info;
|
|
1839
1807
|
}
|
|
1840
1808
|
catch (e) {
|
|
1841
|
-
|
|
1842
|
-
this.logger.error("verify failed " + info.log);
|
|
1843
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1844
|
-
info.screenshotPath = screenshotPath;
|
|
1845
|
-
Object.assign(e, { info: info });
|
|
1846
|
-
error = e;
|
|
1847
|
-
throw e;
|
|
1809
|
+
await _commandError(state, e, this);
|
|
1848
1810
|
}
|
|
1849
1811
|
finally {
|
|
1850
|
-
|
|
1851
|
-
this._reportToWorld(world, {
|
|
1852
|
-
element_name: selectors.element_name,
|
|
1853
|
-
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1854
|
-
text: "Verify element exists in page",
|
|
1855
|
-
screenshotId,
|
|
1856
|
-
result: error
|
|
1857
|
-
? {
|
|
1858
|
-
status: "FAILED",
|
|
1859
|
-
startTime,
|
|
1860
|
-
endTime,
|
|
1861
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1862
|
-
}
|
|
1863
|
-
: {
|
|
1864
|
-
status: "PASSED",
|
|
1865
|
-
startTime,
|
|
1866
|
-
endTime,
|
|
1867
|
-
},
|
|
1868
|
-
info: info,
|
|
1869
|
-
});
|
|
1812
|
+
_commandFinally(state, this);
|
|
1870
1813
|
}
|
|
1871
1814
|
}
|
|
1872
1815
|
async extractAttribute(selectors, attribute, variable, _params = null, options = {}, world = null) {
|
|
1873
|
-
|
|
1816
|
+
_validateSelectors(selectors);
|
|
1874
1817
|
const startTime = Date.now();
|
|
1875
1818
|
let error = null;
|
|
1876
1819
|
let screenshotId = null;
|
|
@@ -1929,7 +1872,7 @@ class StableBrowser {
|
|
|
1929
1872
|
status: "FAILED",
|
|
1930
1873
|
startTime,
|
|
1931
1874
|
endTime,
|
|
1932
|
-
message: error
|
|
1875
|
+
message: error?.message,
|
|
1933
1876
|
}
|
|
1934
1877
|
: {
|
|
1935
1878
|
status: "PASSED",
|
|
@@ -2138,7 +2081,7 @@ class StableBrowser {
|
|
|
2138
2081
|
status: "FAILED",
|
|
2139
2082
|
startTime,
|
|
2140
2083
|
endTime,
|
|
2141
|
-
message: error
|
|
2084
|
+
message: error?.message,
|
|
2142
2085
|
}
|
|
2143
2086
|
: {
|
|
2144
2087
|
status: "PASSED",
|
|
@@ -2174,20 +2117,20 @@ class StableBrowser {
|
|
|
2174
2117
|
for (let i = 0; i < frames.length; i++) {
|
|
2175
2118
|
if (dateAlternatives.date) {
|
|
2176
2119
|
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2177
|
-
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "
|
|
2120
|
+
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", true, true, {});
|
|
2178
2121
|
result.frame = frames[i];
|
|
2179
2122
|
results.push(result);
|
|
2180
2123
|
}
|
|
2181
2124
|
}
|
|
2182
2125
|
else if (numberAlternatives.number) {
|
|
2183
2126
|
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2184
|
-
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "
|
|
2127
|
+
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
|
|
2185
2128
|
result.frame = frames[i];
|
|
2186
2129
|
results.push(result);
|
|
2187
2130
|
}
|
|
2188
2131
|
}
|
|
2189
2132
|
else {
|
|
2190
|
-
const result = await this._locateElementByText(frames[i], text, "
|
|
2133
|
+
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
|
|
2191
2134
|
result.frame = frames[i];
|
|
2192
2135
|
results.push(result);
|
|
2193
2136
|
}
|
|
@@ -2236,7 +2179,7 @@ class StableBrowser {
|
|
|
2236
2179
|
status: "FAILED",
|
|
2237
2180
|
startTime,
|
|
2238
2181
|
endTime,
|
|
2239
|
-
message: error
|
|
2182
|
+
message: error?.message,
|
|
2240
2183
|
}
|
|
2241
2184
|
: {
|
|
2242
2185
|
status: "PASSED",
|
|
@@ -2320,7 +2263,7 @@ class StableBrowser {
|
|
|
2320
2263
|
status: "FAILED",
|
|
2321
2264
|
startTime,
|
|
2322
2265
|
endTime,
|
|
2323
|
-
message: error
|
|
2266
|
+
message: error?.message,
|
|
2324
2267
|
}
|
|
2325
2268
|
: {
|
|
2326
2269
|
status: "PASSED",
|
|
@@ -2352,7 +2295,7 @@ class StableBrowser {
|
|
|
2352
2295
|
this.logger.info("Table data verified");
|
|
2353
2296
|
}
|
|
2354
2297
|
async getTableData(selectors, _params = null, options = {}, world = null) {
|
|
2355
|
-
|
|
2298
|
+
_validateSelectors(selectors);
|
|
2356
2299
|
const startTime = Date.now();
|
|
2357
2300
|
let error = null;
|
|
2358
2301
|
let screenshotId = null;
|
|
@@ -2388,7 +2331,7 @@ class StableBrowser {
|
|
|
2388
2331
|
status: "FAILED",
|
|
2389
2332
|
startTime,
|
|
2390
2333
|
endTime,
|
|
2391
|
-
message: error
|
|
2334
|
+
message: error?.message,
|
|
2392
2335
|
}
|
|
2393
2336
|
: {
|
|
2394
2337
|
status: "PASSED",
|
|
@@ -2400,7 +2343,7 @@ class StableBrowser {
|
|
|
2400
2343
|
}
|
|
2401
2344
|
}
|
|
2402
2345
|
async analyzeTable(selectors, query, operator, value, _params = null, options = {}, world = null) {
|
|
2403
|
-
|
|
2346
|
+
_validateSelectors(selectors);
|
|
2404
2347
|
if (!query) {
|
|
2405
2348
|
throw new Error("query is null");
|
|
2406
2349
|
}
|
|
@@ -2553,7 +2496,7 @@ class StableBrowser {
|
|
|
2553
2496
|
status: "FAILED",
|
|
2554
2497
|
startTime,
|
|
2555
2498
|
endTime,
|
|
2556
|
-
message: error
|
|
2499
|
+
message: error?.message,
|
|
2557
2500
|
}
|
|
2558
2501
|
: {
|
|
2559
2502
|
status: "PASSED",
|
|
@@ -2565,27 +2508,7 @@ class StableBrowser {
|
|
|
2565
2508
|
}
|
|
2566
2509
|
}
|
|
2567
2510
|
async _replaceWithLocalData(value, world, _decrypt = true, totpWait = true) {
|
|
2568
|
-
|
|
2569
|
-
return value;
|
|
2570
|
-
}
|
|
2571
|
-
// find all the accurance of {{(.*?)}} and replace with the value
|
|
2572
|
-
let regex = /{{(.*?)}}/g;
|
|
2573
|
-
let matches = value.match(regex);
|
|
2574
|
-
if (matches) {
|
|
2575
|
-
const testData = this.getTestData(world);
|
|
2576
|
-
for (let i = 0; i < matches.length; i++) {
|
|
2577
|
-
let match = matches[i];
|
|
2578
|
-
let key = match.substring(2, match.length - 2);
|
|
2579
|
-
let newValue = objectPath.get(testData, key, null);
|
|
2580
|
-
if (newValue !== null) {
|
|
2581
|
-
value = value.replace(match, newValue);
|
|
2582
|
-
}
|
|
2583
|
-
}
|
|
2584
|
-
}
|
|
2585
|
-
if ((value.startsWith("secret:") || value.startsWith("totp:")) && _decrypt) {
|
|
2586
|
-
return await decrypt(value, null, totpWait);
|
|
2587
|
-
}
|
|
2588
|
-
return value;
|
|
2511
|
+
return await replaceWithLocalTestData(value, world, _decrypt, totpWait, this.context, this);
|
|
2589
2512
|
}
|
|
2590
2513
|
_getLoadTimeout(options) {
|
|
2591
2514
|
let timeout = 15000;
|
|
@@ -2645,7 +2568,7 @@ class StableBrowser {
|
|
|
2645
2568
|
status: "FAILED",
|
|
2646
2569
|
startTime,
|
|
2647
2570
|
endTime,
|
|
2648
|
-
message: error
|
|
2571
|
+
message: error?.message,
|
|
2649
2572
|
}
|
|
2650
2573
|
: {
|
|
2651
2574
|
status: "PASSED",
|
|
@@ -2680,7 +2603,7 @@ class StableBrowser {
|
|
|
2680
2603
|
status: "FAILED",
|
|
2681
2604
|
startTime,
|
|
2682
2605
|
endTime,
|
|
2683
|
-
message: error
|
|
2606
|
+
message: error?.message,
|
|
2684
2607
|
}
|
|
2685
2608
|
: {
|
|
2686
2609
|
status: "PASSED",
|
|
@@ -2722,7 +2645,7 @@ class StableBrowser {
|
|
|
2722
2645
|
status: "FAILED",
|
|
2723
2646
|
startTime,
|
|
2724
2647
|
endTime,
|
|
2725
|
-
message: error
|
|
2648
|
+
message: error?.message,
|
|
2726
2649
|
}
|
|
2727
2650
|
: {
|
|
2728
2651
|
status: "PASSED",
|
|
@@ -2758,7 +2681,7 @@ class StableBrowser {
|
|
|
2758
2681
|
status: "FAILED",
|
|
2759
2682
|
startTime,
|
|
2760
2683
|
endTime,
|
|
2761
|
-
message: error
|
|
2684
|
+
message: error?.message,
|
|
2762
2685
|
}
|
|
2763
2686
|
: {
|
|
2764
2687
|
status: "PASSED",
|
|
@@ -2943,5 +2866,10 @@ const KEYBOARD_EVENTS = [
|
|
|
2943
2866
|
"TVAntennaCable",
|
|
2944
2867
|
"TVAudioDescription",
|
|
2945
2868
|
];
|
|
2869
|
+
function unEscapeString(str) {
|
|
2870
|
+
const placeholder = "__NEWLINE__";
|
|
2871
|
+
str = str.replace(new RegExp(placeholder, "g"), "\n");
|
|
2872
|
+
return str;
|
|
2873
|
+
}
|
|
2946
2874
|
export { StableBrowser };
|
|
2947
2875
|
//# sourceMappingURL=stable_browser.js.map
|