automation_model 1.0.481-dev → 1.0.481-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 +2 -1
- package/lib/api.js +95 -98
- package/lib/api.js.map +1 -1
- package/lib/auto_page.d.ts +2 -1
- package/lib/auto_page.js +39 -17
- package/lib/auto_page.js.map +1 -1
- package/lib/browser_manager.d.ts +6 -3
- package/lib/browser_manager.js +110 -16
- package/lib/browser_manager.js.map +1 -1
- package/lib/command_common.d.ts +1 -0
- package/lib/command_common.js +59 -6
- package/lib/command_common.js.map +1 -1
- package/lib/error-messages.d.ts +6 -0
- package/lib/error-messages.js +188 -0
- package/lib/error-messages.js.map +1 -0
- package/lib/generation_scripts.d.ts +4 -0
- package/lib/generation_scripts.js +2 -0
- package/lib/generation_scripts.js.map +1 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/lib/init_browser.d.ts +2 -1
- package/lib/init_browser.js +31 -4
- package/lib/init_browser.js.map +1 -1
- package/lib/locate_element.js +15 -13
- package/lib/locate_element.js.map +1 -1
- package/lib/locator.d.ts +36 -0
- package/lib/locator.js +165 -0
- package/lib/locator.js.map +1 -1
- package/lib/locator_log.d.ts +26 -0
- package/lib/locator_log.js +69 -0
- package/lib/locator_log.js.map +1 -0
- package/lib/network.d.ts +3 -0
- package/lib/network.js +155 -0
- package/lib/network.js.map +1 -0
- package/lib/scripts/find_text.js +126 -0
- package/lib/stable_browser.d.ts +49 -17
- package/lib/stable_browser.js +583 -599
- package/lib/stable_browser.js.map +1 -1
- package/lib/table.d.ts +13 -0
- package/lib/table.js +187 -0
- package/lib/table.js.map +1 -0
- package/lib/test_context.d.ts +1 -0
- package/lib/test_context.js +1 -0
- package/lib/test_context.js.map +1 -1
- package/lib/utils.d.ts +14 -1
- package/lib/utils.js +302 -3
- package/lib/utils.js.map +1 -1
- package/package.json +7 -5
- /package/lib/{axe → scripts}/axe.mini.js +0 -0
package/lib/stable_browser.js
CHANGED
|
@@ -10,15 +10,17 @@ 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 { _copyContext, _fixLocatorUsingParams, _fixUsingParams, _getServerUrl, KEYBOARD_EVENTS, maskValue, replaceWithLocalTestData, scrollPageToLoadLazyElements, unEscapeString, } from "./utils.js";
|
|
15
14
|
import csv from "csv-parser";
|
|
16
15
|
import { Readable } from "node:stream";
|
|
17
16
|
import readline from "readline";
|
|
18
17
|
import { getContext } from "./init_browser.js";
|
|
19
18
|
import { locate_element } from "./locate_element.js";
|
|
20
|
-
import {
|
|
21
|
-
|
|
19
|
+
import { randomUUID } from "crypto";
|
|
20
|
+
import { _commandError, _commandFinally, _preCommand, _validateSelectors, _screenshot, _reportToWorld, } from "./command_common.js";
|
|
21
|
+
import { registerDownloadEvent, registerNetworkEvents } from "./network.js";
|
|
22
|
+
import { LocatorLog } from "./locator_log.js";
|
|
23
|
+
export const Types = {
|
|
22
24
|
CLICK: "click_element",
|
|
23
25
|
NAVIGATE: "navigate",
|
|
24
26
|
FILL: "fill_element",
|
|
@@ -29,6 +31,8 @@ const Types = {
|
|
|
29
31
|
GET_PAGE_STATUS: "get_page_status",
|
|
30
32
|
CLICK_ROW_ACTION: "click_row_action",
|
|
31
33
|
VERIFY_ELEMENT_CONTAINS_TEXT: "verify_element_contains_text",
|
|
34
|
+
VERIFY_PAGE_CONTAINS_TEXT: "verify_page_contains_text",
|
|
35
|
+
VERIFY_PAGE_CONTAINS_NO_TEXT: "verify_page_contains_no_text",
|
|
32
36
|
ANALYZE_TABLE: "analyze_table",
|
|
33
37
|
SELECT: "select_combobox",
|
|
34
38
|
VERIFY_PAGE_PATH: "verify_page_path",
|
|
@@ -44,6 +48,9 @@ const Types = {
|
|
|
44
48
|
VERIFY_VISUAL: "verify_visual",
|
|
45
49
|
LOAD_DATA: "load_data",
|
|
46
50
|
SET_INPUT: "set_input",
|
|
51
|
+
WAIT_FOR_TEXT_TO_DISAPPEAR: "wait_for_text_to_disappear",
|
|
52
|
+
VERIFY_ATTRIBUTE: "verify_element_attribute",
|
|
53
|
+
VERIFY_TEXT_WITH_RELATION: "verify_text_with_relation",
|
|
47
54
|
};
|
|
48
55
|
export const apps = {};
|
|
49
56
|
class StableBrowser {
|
|
@@ -57,6 +64,7 @@ class StableBrowser {
|
|
|
57
64
|
networkLogger = null;
|
|
58
65
|
configuration = null;
|
|
59
66
|
appName = "main";
|
|
67
|
+
tags = null;
|
|
60
68
|
constructor(browser, page, logger = null, context = null, world = null) {
|
|
61
69
|
this.browser = browser;
|
|
62
70
|
this.page = page;
|
|
@@ -87,23 +95,32 @@ class StableBrowser {
|
|
|
87
95
|
catch (e) {
|
|
88
96
|
this.logger.error("unable to read ai_config.json");
|
|
89
97
|
}
|
|
90
|
-
context.pageLoading = { status: false };
|
|
91
|
-
context.pages = [this.page];
|
|
92
98
|
const logFolder = path.join(this.project_path, "logs", "web");
|
|
93
99
|
this.world = world;
|
|
100
|
+
context.pages = [this.page];
|
|
101
|
+
context.pageLoading = { status: false };
|
|
94
102
|
this.registerEventListeners(this.context);
|
|
103
|
+
registerNetworkEvents(this.world, this, this.context, this.page);
|
|
104
|
+
registerDownloadEvent(this.page, this.world, this.context);
|
|
95
105
|
}
|
|
96
106
|
registerEventListeners(context) {
|
|
97
107
|
this.registerConsoleLogListener(this.page, context);
|
|
98
|
-
this.registerRequestListener(this.page, context, this.webLogFile);
|
|
108
|
+
// this.registerRequestListener(this.page, context, this.webLogFile);
|
|
99
109
|
if (!context.pageLoading) {
|
|
100
110
|
context.pageLoading = { status: false };
|
|
101
111
|
}
|
|
102
112
|
context.playContext.on("page", async function (page) {
|
|
113
|
+
if (this.configuration && this.configuration.closePopups === true) {
|
|
114
|
+
console.log("close unexpected popups");
|
|
115
|
+
await page.close();
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
103
118
|
context.pageLoading.status = true;
|
|
104
119
|
this.page = page;
|
|
105
120
|
context.page = page;
|
|
106
121
|
context.pages.push(page);
|
|
122
|
+
registerNetworkEvents(this.world, this, context, this.page);
|
|
123
|
+
registerDownloadEvent(this.page, this.world, context);
|
|
107
124
|
page.on("close", async () => {
|
|
108
125
|
if (this.context && this.context.pages && this.context.pages.length > 1) {
|
|
109
126
|
this.context.pages.pop();
|
|
@@ -133,9 +150,9 @@ class StableBrowser {
|
|
|
133
150
|
if (this.appName === appName) {
|
|
134
151
|
return;
|
|
135
152
|
}
|
|
136
|
-
let
|
|
153
|
+
let navigate = false;
|
|
137
154
|
if (!apps[appName]) {
|
|
138
|
-
let newContext = await getContext(null, this.context.headless ? this.context.headless : false, this, this.logger, appName, false, this);
|
|
155
|
+
let newContext = await getContext(null, this.context.headless ? this.context.headless : false, this, this.logger, appName, false, this, -1, this.context.reportFolder);
|
|
139
156
|
newContextCreated = true;
|
|
140
157
|
apps[appName] = {
|
|
141
158
|
context: newContext,
|
|
@@ -144,32 +161,15 @@ class StableBrowser {
|
|
|
144
161
|
};
|
|
145
162
|
}
|
|
146
163
|
const tempContext = {};
|
|
147
|
-
|
|
148
|
-
|
|
164
|
+
_copyContext(this, tempContext);
|
|
165
|
+
_copyContext(apps[appName], this);
|
|
149
166
|
apps[this.appName] = tempContext;
|
|
150
167
|
this.appName = appName;
|
|
151
|
-
if (
|
|
152
|
-
this.registerEventListeners(this.context);
|
|
168
|
+
if (navigate) {
|
|
153
169
|
await this.goto(this.context.environment.baseUrl);
|
|
154
170
|
await this.waitForPageLoad();
|
|
155
171
|
}
|
|
156
172
|
}
|
|
157
|
-
_copyContext(from, to) {
|
|
158
|
-
to.browser = from.browser;
|
|
159
|
-
to.page = from.page;
|
|
160
|
-
to.context = from.context;
|
|
161
|
-
}
|
|
162
|
-
getWebLogFile(logFolder) {
|
|
163
|
-
if (!fs.existsSync(logFolder)) {
|
|
164
|
-
fs.mkdirSync(logFolder, { recursive: true });
|
|
165
|
-
}
|
|
166
|
-
let nextIndex = 1;
|
|
167
|
-
while (fs.existsSync(path.join(logFolder, nextIndex.toString() + ".json"))) {
|
|
168
|
-
nextIndex++;
|
|
169
|
-
}
|
|
170
|
-
const fileName = nextIndex + ".json";
|
|
171
|
-
return path.join(logFolder, fileName);
|
|
172
|
-
}
|
|
173
173
|
registerConsoleLogListener(page, context) {
|
|
174
174
|
if (!this.context.webLogger) {
|
|
175
175
|
this.context.webLogger = [];
|
|
@@ -219,7 +219,7 @@ class StableBrowser {
|
|
|
219
219
|
this.world?.attach(JSON.stringify(obj), { mediaType: "application/json+network" });
|
|
220
220
|
}
|
|
221
221
|
catch (error) {
|
|
222
|
-
console.error("Error in request listener", error);
|
|
222
|
+
// console.error("Error in request listener", error);
|
|
223
223
|
context.networkLogger.push({
|
|
224
224
|
error: "not able to listen",
|
|
225
225
|
message: error.message,
|
|
@@ -241,47 +241,8 @@ class StableBrowser {
|
|
|
241
241
|
timeout: 60000,
|
|
242
242
|
});
|
|
243
243
|
}
|
|
244
|
-
_fixUsingParams(text, _params) {
|
|
245
|
-
if (!_params || typeof text !== "string") {
|
|
246
|
-
return text;
|
|
247
|
-
}
|
|
248
|
-
for (let key in _params) {
|
|
249
|
-
let regValue = key;
|
|
250
|
-
if (key.startsWith("_")) {
|
|
251
|
-
// remove the _ prefix
|
|
252
|
-
regValue = key.substring(1);
|
|
253
|
-
}
|
|
254
|
-
text = text.replaceAll(new RegExp("{" + regValue + "}", "g"), _params[key]);
|
|
255
|
-
}
|
|
256
|
-
return text;
|
|
257
|
-
}
|
|
258
|
-
_fixLocatorUsingParams(locator, _params) {
|
|
259
|
-
// check if not null
|
|
260
|
-
if (!locator) {
|
|
261
|
-
return locator;
|
|
262
|
-
}
|
|
263
|
-
// clone the locator
|
|
264
|
-
locator = JSON.parse(JSON.stringify(locator));
|
|
265
|
-
this.scanAndManipulate(locator, _params);
|
|
266
|
-
return locator;
|
|
267
|
-
}
|
|
268
|
-
_isObject(value) {
|
|
269
|
-
return value && typeof value === "object" && value.constructor === Object;
|
|
270
|
-
}
|
|
271
|
-
scanAndManipulate(currentObj, _params) {
|
|
272
|
-
for (const key in currentObj) {
|
|
273
|
-
if (typeof currentObj[key] === "string") {
|
|
274
|
-
// Perform string manipulation
|
|
275
|
-
currentObj[key] = this._fixUsingParams(currentObj[key], _params);
|
|
276
|
-
}
|
|
277
|
-
else if (this._isObject(currentObj[key])) {
|
|
278
|
-
// Recursively scan nested objects
|
|
279
|
-
this.scanAndManipulate(currentObj[key], _params);
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
244
|
_getLocator(locator, scope, _params) {
|
|
284
|
-
locator =
|
|
245
|
+
locator = _fixLocatorUsingParams(locator, _params);
|
|
285
246
|
let locatorReturn;
|
|
286
247
|
if (locator.role) {
|
|
287
248
|
if (locator.role[1].nameReg) {
|
|
@@ -289,7 +250,7 @@ class StableBrowser {
|
|
|
289
250
|
delete locator.role[1].nameReg;
|
|
290
251
|
}
|
|
291
252
|
// if (locator.role[1].name) {
|
|
292
|
-
// locator.role[1].name =
|
|
253
|
+
// locator.role[1].name = _fixUsingParams(locator.role[1].name, _params);
|
|
293
254
|
// }
|
|
294
255
|
locatorReturn = scope.getByRole(locator.role[0], locator.role[1]);
|
|
295
256
|
}
|
|
@@ -332,7 +293,7 @@ class StableBrowser {
|
|
|
332
293
|
if (css && css.locator) {
|
|
333
294
|
css = css.locator;
|
|
334
295
|
}
|
|
335
|
-
let result = await this._locateElementByText(scope,
|
|
296
|
+
let result = await this._locateElementByText(scope, _fixUsingParams(text, _params), "*:not(script, style, head)", false, false, _params);
|
|
336
297
|
if (result.elementCount === 0) {
|
|
337
298
|
return;
|
|
338
299
|
}
|
|
@@ -348,116 +309,28 @@ class StableBrowser {
|
|
|
348
309
|
async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, _params) {
|
|
349
310
|
//const stringifyText = JSON.stringify(text);
|
|
350
311
|
return await scope.locator(":root").evaluate((_node, [text, tag, regex, partial]) => {
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
if (currentNode === parent) {
|
|
355
|
-
return true;
|
|
356
|
-
}
|
|
357
|
-
currentNode = currentNode.parentNode;
|
|
358
|
-
}
|
|
359
|
-
return false;
|
|
360
|
-
}
|
|
361
|
-
document.isParent = isParent;
|
|
362
|
-
function getRegex(str) {
|
|
363
|
-
const match = str.match(/^\/(.*?)\/([gimuy]*)$/);
|
|
364
|
-
if (!match) {
|
|
365
|
-
return null;
|
|
366
|
-
}
|
|
367
|
-
let [_, pattern, flags] = match;
|
|
368
|
-
return new RegExp(pattern, flags);
|
|
369
|
-
}
|
|
370
|
-
document.getRegex = getRegex;
|
|
371
|
-
function collectAllShadowDomElements(element, result = []) {
|
|
372
|
-
// Check and add the element if it has a shadow root
|
|
373
|
-
if (element.shadowRoot) {
|
|
374
|
-
result.push(element);
|
|
375
|
-
// Also search within the shadow root
|
|
376
|
-
document.collectAllShadowDomElements(element.shadowRoot, result);
|
|
377
|
-
}
|
|
378
|
-
// Iterate over child nodes
|
|
379
|
-
element.childNodes.forEach((child) => {
|
|
380
|
-
// Recursively call the function for each child node
|
|
381
|
-
document.collectAllShadowDomElements(child, result);
|
|
382
|
-
});
|
|
383
|
-
return result;
|
|
384
|
-
}
|
|
385
|
-
document.collectAllShadowDomElements = collectAllShadowDomElements;
|
|
386
|
-
if (!tag) {
|
|
387
|
-
tag = "*";
|
|
388
|
-
}
|
|
389
|
-
let regexpSearch = document.getRegex(text);
|
|
390
|
-
if (regexpSearch) {
|
|
391
|
-
regex = true;
|
|
392
|
-
}
|
|
393
|
-
let elements = Array.from(document.querySelectorAll(tag));
|
|
394
|
-
let shadowHosts = [];
|
|
395
|
-
document.collectAllShadowDomElements(document, shadowHosts);
|
|
396
|
-
for (let i = 0; i < shadowHosts.length; i++) {
|
|
397
|
-
let shadowElement = shadowHosts[i].shadowRoot;
|
|
398
|
-
if (!shadowElement) {
|
|
399
|
-
console.log("shadowElement is null, for host " + shadowHosts[i]);
|
|
400
|
-
continue;
|
|
401
|
-
}
|
|
402
|
-
let shadowElements = Array.from(shadowElement.querySelectorAll(tag));
|
|
403
|
-
elements = elements.concat(shadowElements);
|
|
404
|
-
}
|
|
405
|
-
let randomToken = null;
|
|
406
|
-
const foundElements = [];
|
|
312
|
+
const options = {
|
|
313
|
+
innerText: true,
|
|
314
|
+
};
|
|
407
315
|
if (regex) {
|
|
408
|
-
|
|
409
|
-
regexpSearch = new RegExp(text, "im");
|
|
410
|
-
}
|
|
411
|
-
for (let i = 0; i < elements.length; i++) {
|
|
412
|
-
const element = elements[i];
|
|
413
|
-
if ((element.innerText && regexpSearch.test(element.innerText)) ||
|
|
414
|
-
(element.value && regexpSearch.test(element.value))) {
|
|
415
|
-
foundElements.push(element);
|
|
416
|
-
}
|
|
417
|
-
}
|
|
316
|
+
options.singleRegex = true;
|
|
418
317
|
}
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
for (let i = 0; i < elements.length; i++) {
|
|
422
|
-
const element = elements[i];
|
|
423
|
-
if (partial) {
|
|
424
|
-
if ((element.innerText && element.innerText.toLowerCase().trim().includes(text.toLowerCase())) ||
|
|
425
|
-
(element.value && element.value.toLowerCase().includes(text.toLowerCase()))) {
|
|
426
|
-
foundElements.push(element);
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
else {
|
|
430
|
-
if ((element.innerText && element.innerText.trim() === text) ||
|
|
431
|
-
(element.value && element.value === text)) {
|
|
432
|
-
foundElements.push(element);
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
}
|
|
318
|
+
if (tag) {
|
|
319
|
+
options.tag = tag;
|
|
436
320
|
}
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
let element = foundElements[i];
|
|
440
|
-
let hasChild = false;
|
|
441
|
-
for (let j = 0; j < foundElements.length; j++) {
|
|
442
|
-
if (i === j) {
|
|
443
|
-
continue;
|
|
444
|
-
}
|
|
445
|
-
if (isParent(element, foundElements[j])) {
|
|
446
|
-
hasChild = true;
|
|
447
|
-
break;
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
if (!hasChild) {
|
|
451
|
-
noChildElements.push(element);
|
|
452
|
-
}
|
|
321
|
+
if (!(partial === true)) {
|
|
322
|
+
options.exactMatch = true;
|
|
453
323
|
}
|
|
324
|
+
const elements = window.findMatchingElements(text, options);
|
|
325
|
+
let randomToken = null;
|
|
326
|
+
const foundElements = [];
|
|
454
327
|
let elementCount = 0;
|
|
455
|
-
if (
|
|
456
|
-
for (let i = 0; i <
|
|
328
|
+
if (elements.length > 0) {
|
|
329
|
+
for (let i = 0; i < elements.length; i++) {
|
|
457
330
|
if (randomToken === null) {
|
|
458
331
|
randomToken = Math.random().toString(36).substring(7);
|
|
459
332
|
}
|
|
460
|
-
let element =
|
|
333
|
+
let element = elements[i];
|
|
461
334
|
element.setAttribute("data-blinq-id", "blinq-id-" + randomToken);
|
|
462
335
|
elementCount++;
|
|
463
336
|
}
|
|
@@ -466,9 +339,21 @@ class StableBrowser {
|
|
|
466
339
|
}, [text1, tag1, regex1, partial1]);
|
|
467
340
|
}
|
|
468
341
|
async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true) {
|
|
342
|
+
if (!info) {
|
|
343
|
+
info = {};
|
|
344
|
+
}
|
|
345
|
+
if (!info.failCause) {
|
|
346
|
+
info.failCause = {};
|
|
347
|
+
}
|
|
348
|
+
if (!info.log) {
|
|
349
|
+
info.log = "";
|
|
350
|
+
info.locatorLog = new LocatorLog(selectorHierarchy);
|
|
351
|
+
}
|
|
469
352
|
let locatorSearch = selectorHierarchy[index];
|
|
353
|
+
let originalLocatorSearch = "";
|
|
470
354
|
try {
|
|
471
|
-
|
|
355
|
+
originalLocatorSearch = _fixUsingParams(JSON.stringify(locatorSearch), _params);
|
|
356
|
+
locatorSearch = JSON.parse(originalLocatorSearch);
|
|
472
357
|
}
|
|
473
358
|
catch (e) {
|
|
474
359
|
console.error(e);
|
|
@@ -478,13 +363,18 @@ class StableBrowser {
|
|
|
478
363
|
if (locatorSearch.climb && locatorSearch.climb >= 0) {
|
|
479
364
|
let locatorString = await this._locateElmentByTextClimbCss(scope, locatorSearch.text, locatorSearch.climb, locatorSearch.css, _params);
|
|
480
365
|
if (!locatorString) {
|
|
366
|
+
info.failCause.textNotFound = true;
|
|
367
|
+
info.failCause.lastError = "failed to locate element by text: " + locatorSearch.text;
|
|
481
368
|
return;
|
|
482
369
|
}
|
|
483
370
|
locator = this._getLocator({ css: locatorString }, scope, _params);
|
|
484
371
|
}
|
|
485
372
|
else if (locatorSearch.text) {
|
|
486
|
-
let
|
|
373
|
+
let text = _fixUsingParams(locatorSearch.text, _params);
|
|
374
|
+
let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, _params);
|
|
487
375
|
if (result.elementCount === 0) {
|
|
376
|
+
info.failCause.textNotFound = true;
|
|
377
|
+
info.failCause.lastError = "failed to locate element by text: " + text;
|
|
488
378
|
return;
|
|
489
379
|
}
|
|
490
380
|
locatorSearch.css = "[data-blinq-id='blinq-id-" + result.randomToken + "']";
|
|
@@ -501,13 +391,22 @@ class StableBrowser {
|
|
|
501
391
|
// cssHref = true;
|
|
502
392
|
// }
|
|
503
393
|
let count = await locator.count();
|
|
394
|
+
if (count > 0 && !info.failCause.count) {
|
|
395
|
+
info.failCause.count = count;
|
|
396
|
+
}
|
|
504
397
|
//info.log += "total elements found " + count + "\n";
|
|
505
398
|
//let visibleCount = 0;
|
|
506
399
|
let visibleLocator = null;
|
|
507
|
-
if (locatorSearch.index && locatorSearch.index < count) {
|
|
400
|
+
if (typeof locatorSearch.index === "number" && locatorSearch.index < count) {
|
|
508
401
|
foundLocators.push(locator.nth(locatorSearch.index));
|
|
402
|
+
if (info.locatorLog) {
|
|
403
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
|
|
404
|
+
}
|
|
509
405
|
return;
|
|
510
406
|
}
|
|
407
|
+
if (info.locatorLog && count === 0) {
|
|
408
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "NOT_FOUND");
|
|
409
|
+
}
|
|
511
410
|
for (let j = 0; j < count; j++) {
|
|
512
411
|
let visible = await locator.nth(j).isVisible();
|
|
513
412
|
const enabled = await locator.nth(j).isEnabled();
|
|
@@ -516,24 +415,40 @@ class StableBrowser {
|
|
|
516
415
|
}
|
|
517
416
|
if (visible && enabled) {
|
|
518
417
|
foundLocators.push(locator.nth(j));
|
|
418
|
+
if (info.locatorLog) {
|
|
419
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
|
|
420
|
+
}
|
|
519
421
|
}
|
|
520
422
|
else {
|
|
423
|
+
info.failCause.visible = visible;
|
|
424
|
+
info.failCause.enabled = enabled;
|
|
521
425
|
if (!info.printMessages) {
|
|
522
426
|
info.printMessages = {};
|
|
523
427
|
}
|
|
428
|
+
if (info.locatorLog && !visible) {
|
|
429
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_VISIBLE");
|
|
430
|
+
}
|
|
431
|
+
if (info.locatorLog && !enabled) {
|
|
432
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_ENABLED");
|
|
433
|
+
}
|
|
524
434
|
if (!info.printMessages[j.toString()]) {
|
|
525
|
-
info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
|
|
435
|
+
//info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
|
|
526
436
|
info.printMessages[j.toString()] = true;
|
|
527
437
|
}
|
|
528
438
|
}
|
|
529
439
|
}
|
|
530
440
|
}
|
|
531
441
|
async closeUnexpectedPopups(info, _params) {
|
|
442
|
+
if (!info) {
|
|
443
|
+
info = {};
|
|
444
|
+
info.failCause = {};
|
|
445
|
+
info.log = "";
|
|
446
|
+
}
|
|
532
447
|
if (this.configuration.popupHandlers && this.configuration.popupHandlers.length > 0) {
|
|
533
448
|
if (!info) {
|
|
534
449
|
info = {};
|
|
535
450
|
}
|
|
536
|
-
info.log += "scan for popup handlers" + "\n";
|
|
451
|
+
//info.log += "scan for popup handlers" + "\n";
|
|
537
452
|
const handlerGroup = [];
|
|
538
453
|
for (let i = 0; i < this.configuration.popupHandlers.length; i++) {
|
|
539
454
|
handlerGroup.push(this.configuration.popupHandlers[i].locator);
|
|
@@ -560,9 +475,21 @@ class StableBrowser {
|
|
|
560
475
|
}
|
|
561
476
|
if (result.foundElements.length > 0) {
|
|
562
477
|
let dialogCloseLocator = result.foundElements[0].locator;
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
478
|
+
try {
|
|
479
|
+
await scope?.evaluate(() => {
|
|
480
|
+
window.__isClosingPopups = true;
|
|
481
|
+
});
|
|
482
|
+
await dialogCloseLocator.click();
|
|
483
|
+
// wait for the dialog to close
|
|
484
|
+
await dialogCloseLocator.waitFor({ state: "hidden" });
|
|
485
|
+
}
|
|
486
|
+
catch (e) {
|
|
487
|
+
}
|
|
488
|
+
finally {
|
|
489
|
+
await scope?.evaluate(() => {
|
|
490
|
+
window.__isClosingPopups = false;
|
|
491
|
+
});
|
|
492
|
+
}
|
|
566
493
|
return { rerun: true };
|
|
567
494
|
}
|
|
568
495
|
}
|
|
@@ -586,7 +513,13 @@ class StableBrowser {
|
|
|
586
513
|
}
|
|
587
514
|
throw new Error("unable to locate element " + JSON.stringify(selectors));
|
|
588
515
|
}
|
|
589
|
-
async _findFrameScope(selectors, timeout = 30000) {
|
|
516
|
+
async _findFrameScope(selectors, timeout = 30000, info) {
|
|
517
|
+
if (!info) {
|
|
518
|
+
info = {};
|
|
519
|
+
info.failCause = {};
|
|
520
|
+
info.log = "";
|
|
521
|
+
}
|
|
522
|
+
let startTime = Date.now();
|
|
590
523
|
let scope = this.page;
|
|
591
524
|
if (selectors.frame) {
|
|
592
525
|
return selectors.frame;
|
|
@@ -617,9 +550,11 @@ class StableBrowser {
|
|
|
617
550
|
}
|
|
618
551
|
return framescope;
|
|
619
552
|
};
|
|
553
|
+
let fLocator = null;
|
|
620
554
|
while (true) {
|
|
621
555
|
let frameFound = false;
|
|
622
556
|
if (selectors.nestFrmLoc) {
|
|
557
|
+
fLocator = selectors.nestFrmLoc;
|
|
623
558
|
scope = await findFrame(selectors.nestFrmLoc, scope);
|
|
624
559
|
frameFound = true;
|
|
625
560
|
break;
|
|
@@ -628,6 +563,7 @@ class StableBrowser {
|
|
|
628
563
|
for (let i = 0; i < selectors.frameLocators.length; i++) {
|
|
629
564
|
let frameLocator = selectors.frameLocators[i];
|
|
630
565
|
if (frameLocator.css) {
|
|
566
|
+
fLocator = frameLocator.css;
|
|
631
567
|
scope = scope.frameLocator(frameLocator.css);
|
|
632
568
|
frameFound = true;
|
|
633
569
|
break;
|
|
@@ -635,16 +571,25 @@ class StableBrowser {
|
|
|
635
571
|
}
|
|
636
572
|
}
|
|
637
573
|
if (!frameFound && selectors.iframe_src) {
|
|
574
|
+
fLocator = selectors.iframe_src;
|
|
638
575
|
scope = this.page.frame({ url: selectors.iframe_src });
|
|
639
576
|
}
|
|
640
577
|
if (!scope) {
|
|
641
|
-
info
|
|
642
|
-
|
|
578
|
+
if (info && info.locatorLog) {
|
|
579
|
+
info.locatorLog.setFrameSearchStatus("frame-" + fLocator, "NOT_FOUND");
|
|
580
|
+
}
|
|
581
|
+
//info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
|
|
582
|
+
if (Date.now() - startTime > timeout) {
|
|
583
|
+
info.failCause.iframeNotFound = true;
|
|
584
|
+
info.failCause.lastError = "unable to locate iframe " + selectors.iframe_src;
|
|
643
585
|
throw new Error("unable to locate iframe " + selectors.iframe_src);
|
|
644
586
|
}
|
|
645
587
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
646
588
|
}
|
|
647
589
|
else {
|
|
590
|
+
if (info && info.locatorLog) {
|
|
591
|
+
info.locatorLog.setFrameSearchStatus("frame-" + fLocator, "FOUND");
|
|
592
|
+
}
|
|
648
593
|
break;
|
|
649
594
|
}
|
|
650
595
|
}
|
|
@@ -654,20 +599,27 @@ class StableBrowser {
|
|
|
654
599
|
}
|
|
655
600
|
return scope;
|
|
656
601
|
}
|
|
657
|
-
async _getDocumentBody(selectors, timeout = 30000) {
|
|
658
|
-
let scope = await this._findFrameScope(selectors, timeout);
|
|
602
|
+
async _getDocumentBody(selectors, timeout = 30000, info) {
|
|
603
|
+
let scope = await this._findFrameScope(selectors, timeout, info);
|
|
659
604
|
return scope.evaluate(() => {
|
|
660
605
|
var bodyContent = document.body.innerHTML;
|
|
661
606
|
return bodyContent;
|
|
662
607
|
});
|
|
663
608
|
}
|
|
664
609
|
async _locate_internal(selectors, info, _params, timeout = 30000) {
|
|
610
|
+
if (!info) {
|
|
611
|
+
info = {};
|
|
612
|
+
info.failCause = {};
|
|
613
|
+
info.log = "";
|
|
614
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
615
|
+
}
|
|
665
616
|
let highPriorityTimeout = 5000;
|
|
666
617
|
let visibleOnlyTimeout = 6000;
|
|
667
|
-
let startTime =
|
|
618
|
+
let startTime = Date.now();
|
|
668
619
|
let locatorsCount = 0;
|
|
620
|
+
let lazy_scroll = false;
|
|
669
621
|
//let arrayMode = Array.isArray(selectors);
|
|
670
|
-
let scope = await this._findFrameScope(selectors, timeout);
|
|
622
|
+
let scope = await this._findFrameScope(selectors, timeout, info);
|
|
671
623
|
let selectorsLocators = null;
|
|
672
624
|
selectorsLocators = selectors.locators;
|
|
673
625
|
// group selectors by priority
|
|
@@ -754,21 +706,33 @@ class StableBrowser {
|
|
|
754
706
|
return maxCountElement.locator;
|
|
755
707
|
}
|
|
756
708
|
}
|
|
757
|
-
if (
|
|
709
|
+
if (Date.now() - startTime > timeout) {
|
|
758
710
|
break;
|
|
759
711
|
}
|
|
760
|
-
if (
|
|
712
|
+
if (Date.now() - startTime > highPriorityTimeout) {
|
|
761
713
|
info.log += "high priority timeout, will try all elements" + "\n";
|
|
762
714
|
highPriorityOnly = false;
|
|
715
|
+
if (this.configuration && this.configuration.load_all_lazy === true && !lazy_scroll) {
|
|
716
|
+
lazy_scroll = true;
|
|
717
|
+
await scrollPageToLoadLazyElements(this.page);
|
|
718
|
+
}
|
|
763
719
|
}
|
|
764
|
-
if (
|
|
720
|
+
if (Date.now() - startTime > visibleOnlyTimeout) {
|
|
765
721
|
info.log += "visible only timeout, will try all elements" + "\n";
|
|
766
722
|
visibleOnly = false;
|
|
767
723
|
}
|
|
768
724
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
769
725
|
}
|
|
770
726
|
this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
|
|
771
|
-
info.
|
|
727
|
+
// if (info.locatorLog) {
|
|
728
|
+
// const lines = info.locatorLog.toString().split("\n");
|
|
729
|
+
// for (let line of lines) {
|
|
730
|
+
// this.logger.debug(line);
|
|
731
|
+
// }
|
|
732
|
+
// }
|
|
733
|
+
//info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
|
|
734
|
+
info.failCause.locatorNotFound = true;
|
|
735
|
+
info.failCause.lastError = "failed to locate unique element";
|
|
772
736
|
throw new Error("failed to locate first element no elements found, " + info.log);
|
|
773
737
|
}
|
|
774
738
|
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
|
|
@@ -800,10 +764,29 @@ class StableBrowser {
|
|
|
800
764
|
});
|
|
801
765
|
result.locatorIndex = i;
|
|
802
766
|
}
|
|
767
|
+
if (foundLocators.length > 1) {
|
|
768
|
+
info.failCause.foundMultiple = true;
|
|
769
|
+
if (info.locatorLog) {
|
|
770
|
+
info.locatorLog.setLocatorSearchStatus(locatorsGroup[i], "FOUND_NOT_UNIQUE");
|
|
771
|
+
}
|
|
772
|
+
}
|
|
803
773
|
}
|
|
804
774
|
return result;
|
|
805
775
|
}
|
|
806
776
|
async simpleClick(elementDescription, _params, options = {}, world = null) {
|
|
777
|
+
const state = {
|
|
778
|
+
locate: false,
|
|
779
|
+
scroll: false,
|
|
780
|
+
highlight: false,
|
|
781
|
+
_params,
|
|
782
|
+
options,
|
|
783
|
+
world,
|
|
784
|
+
type: Types.CLICK,
|
|
785
|
+
text: "Click element",
|
|
786
|
+
operation: "simpleClick",
|
|
787
|
+
log: "***** click on " + elementDescription + " *****\n",
|
|
788
|
+
};
|
|
789
|
+
_preCommand(state, this);
|
|
807
790
|
const startTime = Date.now();
|
|
808
791
|
let timeout = 30000;
|
|
809
792
|
if (options && options.timeout) {
|
|
@@ -827,13 +810,32 @@ class StableBrowser {
|
|
|
827
810
|
}
|
|
828
811
|
catch (e) {
|
|
829
812
|
if (performance.now() - startTime > timeout) {
|
|
830
|
-
throw e;
|
|
813
|
+
// throw e;
|
|
814
|
+
try {
|
|
815
|
+
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
816
|
+
}
|
|
817
|
+
finally {
|
|
818
|
+
_commandFinally(state, this);
|
|
819
|
+
}
|
|
831
820
|
}
|
|
832
821
|
}
|
|
833
822
|
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
834
823
|
}
|
|
835
824
|
}
|
|
836
825
|
async simpleClickType(elementDescription, value, _params, options = {}, world = null) {
|
|
826
|
+
const state = {
|
|
827
|
+
locate: false,
|
|
828
|
+
scroll: false,
|
|
829
|
+
highlight: false,
|
|
830
|
+
_params,
|
|
831
|
+
options,
|
|
832
|
+
world,
|
|
833
|
+
type: Types.FILL,
|
|
834
|
+
text: "Fill element",
|
|
835
|
+
operation: "simpleClickType",
|
|
836
|
+
log: "***** click type on " + elementDescription + " *****\n",
|
|
837
|
+
};
|
|
838
|
+
_preCommand(state, this);
|
|
837
839
|
const startTime = Date.now();
|
|
838
840
|
let timeout = 30000;
|
|
839
841
|
if (options && options.timeout) {
|
|
@@ -857,7 +859,13 @@ class StableBrowser {
|
|
|
857
859
|
}
|
|
858
860
|
catch (e) {
|
|
859
861
|
if (performance.now() - startTime > timeout) {
|
|
860
|
-
throw e;
|
|
862
|
+
// throw e;
|
|
863
|
+
try {
|
|
864
|
+
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
865
|
+
}
|
|
866
|
+
finally {
|
|
867
|
+
_commandFinally(state, this);
|
|
868
|
+
}
|
|
861
869
|
}
|
|
862
870
|
}
|
|
863
871
|
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
@@ -881,13 +889,13 @@ class StableBrowser {
|
|
|
881
889
|
}
|
|
882
890
|
try {
|
|
883
891
|
await state.element.click();
|
|
884
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
892
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
885
893
|
}
|
|
886
894
|
catch (e) {
|
|
887
895
|
// await this.closeUnexpectedPopups();
|
|
888
896
|
state.element = await this._locate(selectors, state.info, _params);
|
|
889
897
|
await state.element.dispatchEvent("click");
|
|
890
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
898
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
891
899
|
}
|
|
892
900
|
await this.waitForPageLoad();
|
|
893
901
|
return state.info;
|
|
@@ -966,7 +974,6 @@ class StableBrowser {
|
|
|
966
974
|
await state.element.hover({ timeout: 10000 });
|
|
967
975
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
968
976
|
}
|
|
969
|
-
await _screenshot(state, this);
|
|
970
977
|
await this.waitForPageLoad();
|
|
971
978
|
return state.info;
|
|
972
979
|
}
|
|
@@ -1094,33 +1101,30 @@ class StableBrowser {
|
|
|
1094
1101
|
}
|
|
1095
1102
|
}
|
|
1096
1103
|
async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1104
|
+
const state = {
|
|
1105
|
+
selectors,
|
|
1106
|
+
_params,
|
|
1107
|
+
value: await this._replaceWithLocalData(value, this),
|
|
1108
|
+
options,
|
|
1109
|
+
world,
|
|
1110
|
+
type: Types.SET_DATE_TIME,
|
|
1111
|
+
text: `Set date time value: ${value}`,
|
|
1112
|
+
operation: "setDateTime",
|
|
1113
|
+
log: "***** set date time value " + selectors.element_name + " *****\n",
|
|
1114
|
+
throwError: false,
|
|
1115
|
+
};
|
|
1107
1116
|
try {
|
|
1108
|
-
|
|
1109
|
-
let element = await this._locate(selectors, info, _params);
|
|
1110
|
-
//insert red border around the element
|
|
1111
|
-
await this.scrollIfNeeded(element, info);
|
|
1112
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1113
|
-
await this._highlightElements(element);
|
|
1117
|
+
await _preCommand(state, this);
|
|
1114
1118
|
try {
|
|
1115
|
-
await element.click();
|
|
1119
|
+
await state.element.click();
|
|
1116
1120
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1117
1121
|
if (format) {
|
|
1118
|
-
value = dayjs(value).format(format);
|
|
1119
|
-
await element.fill(value);
|
|
1122
|
+
state.value = dayjs(state.value).format(format);
|
|
1123
|
+
await state.element.fill(state.value);
|
|
1120
1124
|
}
|
|
1121
1125
|
else {
|
|
1122
|
-
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
1123
|
-
await element.evaluateHandle((el, dateTimeValue) => {
|
|
1126
|
+
const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
|
|
1127
|
+
await state.element.evaluateHandle((el, dateTimeValue) => {
|
|
1124
1128
|
el.value = ""; // clear input
|
|
1125
1129
|
el.value = dateTimeValue;
|
|
1126
1130
|
}, dateTimeValue);
|
|
@@ -1133,20 +1137,19 @@ class StableBrowser {
|
|
|
1133
1137
|
}
|
|
1134
1138
|
catch (err) {
|
|
1135
1139
|
//await this.closeUnexpectedPopups();
|
|
1136
|
-
this.logger.error("setting date time input failed " + JSON.stringify(info));
|
|
1140
|
+
this.logger.error("setting date time input failed " + JSON.stringify(state.info));
|
|
1137
1141
|
this.logger.info("Trying again");
|
|
1138
|
-
|
|
1139
|
-
info.
|
|
1140
|
-
Object.assign(err, { info: info });
|
|
1142
|
+
await _screenshot(state, this);
|
|
1143
|
+
Object.assign(err, { info: state.info });
|
|
1141
1144
|
await element.click();
|
|
1142
1145
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1143
1146
|
if (format) {
|
|
1144
|
-
value = dayjs(value).format(format);
|
|
1145
|
-
await element.fill(value);
|
|
1147
|
+
state.value = dayjs(state.value).format(format);
|
|
1148
|
+
await state.element.fill(state.value);
|
|
1146
1149
|
}
|
|
1147
1150
|
else {
|
|
1148
|
-
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
1149
|
-
await element.evaluateHandle((el, dateTimeValue) => {
|
|
1151
|
+
const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
|
|
1152
|
+
await state.element.evaluateHandle((el, dateTimeValue) => {
|
|
1150
1153
|
el.value = ""; // clear input
|
|
1151
1154
|
el.value = dateTimeValue;
|
|
1152
1155
|
}, dateTimeValue);
|
|
@@ -1159,50 +1162,30 @@ class StableBrowser {
|
|
|
1159
1162
|
}
|
|
1160
1163
|
}
|
|
1161
1164
|
catch (e) {
|
|
1162
|
-
|
|
1163
|
-
throw e;
|
|
1165
|
+
await _commandError(state, e, this);
|
|
1164
1166
|
}
|
|
1165
1167
|
finally {
|
|
1166
|
-
|
|
1167
|
-
this._reportToWorld(world, {
|
|
1168
|
-
element_name: selectors.element_name,
|
|
1169
|
-
type: Types.SET_DATE_TIME,
|
|
1170
|
-
screenshotId,
|
|
1171
|
-
value: value,
|
|
1172
|
-
text: `setDateTime input with value: ${value}`,
|
|
1173
|
-
result: error
|
|
1174
|
-
? {
|
|
1175
|
-
status: "FAILED",
|
|
1176
|
-
startTime,
|
|
1177
|
-
endTime,
|
|
1178
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1179
|
-
}
|
|
1180
|
-
: {
|
|
1181
|
-
status: "PASSED",
|
|
1182
|
-
startTime,
|
|
1183
|
-
endTime,
|
|
1184
|
-
},
|
|
1185
|
-
info: info,
|
|
1186
|
-
});
|
|
1168
|
+
_commandFinally(state, this);
|
|
1187
1169
|
}
|
|
1188
1170
|
}
|
|
1189
1171
|
async clickType(selectors, _value, enter = false, _params = null, options = {}, world = null) {
|
|
1172
|
+
_value = unEscapeString(_value);
|
|
1173
|
+
const newValue = await this._replaceWithLocalData(_value, world);
|
|
1190
1174
|
const state = {
|
|
1191
1175
|
selectors,
|
|
1192
1176
|
_params,
|
|
1193
|
-
value:
|
|
1177
|
+
value: newValue,
|
|
1178
|
+
originalValue: _value,
|
|
1194
1179
|
options,
|
|
1195
1180
|
world,
|
|
1196
1181
|
type: Types.FILL,
|
|
1197
1182
|
text: `Click type input with value: ${_value}`,
|
|
1198
1183
|
operation: "clickType",
|
|
1199
|
-
log: "***** clickType on " + selectors.element_name + " with value " + _value + "*****\n",
|
|
1184
|
+
log: "***** clickType on " + selectors.element_name + " with value " + maskValue(_value) + "*****\n",
|
|
1200
1185
|
};
|
|
1201
|
-
const newValue = await this._replaceWithLocalData(state.value, world);
|
|
1202
1186
|
if (newValue !== _value) {
|
|
1203
1187
|
//this.logger.info(_value + "=" + newValue);
|
|
1204
1188
|
_value = newValue;
|
|
1205
|
-
state.value = newValue;
|
|
1206
1189
|
}
|
|
1207
1190
|
try {
|
|
1208
1191
|
await _preCommand(state, this);
|
|
@@ -1211,7 +1194,7 @@ class StableBrowser {
|
|
|
1211
1194
|
try {
|
|
1212
1195
|
let currentValue = await state.element.inputValue();
|
|
1213
1196
|
if (currentValue) {
|
|
1214
|
-
await element.fill("");
|
|
1197
|
+
await state.element.fill("");
|
|
1215
1198
|
}
|
|
1216
1199
|
}
|
|
1217
1200
|
catch (e) {
|
|
@@ -1319,6 +1302,7 @@ class StableBrowser {
|
|
|
1319
1302
|
let screenshotPath = null;
|
|
1320
1303
|
if (!info.log) {
|
|
1321
1304
|
info.log = "";
|
|
1305
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
1322
1306
|
}
|
|
1323
1307
|
info.operation = "getText";
|
|
1324
1308
|
info.selectors = selectors;
|
|
@@ -1657,11 +1641,9 @@ class StableBrowser {
|
|
|
1657
1641
|
if (!fs.existsSync(world.screenshotPath)) {
|
|
1658
1642
|
fs.mkdirSync(world.screenshotPath, { recursive: true });
|
|
1659
1643
|
}
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
}
|
|
1664
|
-
const screenshotPath = path.join(world.screenshotPath, nextIndex + ".png");
|
|
1644
|
+
// to make sure the path doesn't start with -
|
|
1645
|
+
const uuidStr = "id_" + randomUUID();
|
|
1646
|
+
const screenshotPath = path.join(world.screenshotPath, uuidStr + ".png");
|
|
1665
1647
|
try {
|
|
1666
1648
|
await this.takeScreenshot(screenshotPath);
|
|
1667
1649
|
// let buffer = await this.page.screenshot({ timeout: 4000 });
|
|
@@ -1675,7 +1657,7 @@ class StableBrowser {
|
|
|
1675
1657
|
catch (e) {
|
|
1676
1658
|
this.logger.info("unable to take screenshot, ignored");
|
|
1677
1659
|
}
|
|
1678
|
-
result.screenshotId =
|
|
1660
|
+
result.screenshotId = uuidStr;
|
|
1679
1661
|
result.screenshotPath = screenshotPath;
|
|
1680
1662
|
if (info && info.box) {
|
|
1681
1663
|
await drawRectangle(screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
|
|
@@ -1773,74 +1755,101 @@ class StableBrowser {
|
|
|
1773
1755
|
}
|
|
1774
1756
|
}
|
|
1775
1757
|
async extractAttribute(selectors, attribute, variable, _params = null, options = {}, world = null) {
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1758
|
+
const state = {
|
|
1759
|
+
selectors,
|
|
1760
|
+
_params,
|
|
1761
|
+
attribute,
|
|
1762
|
+
variable,
|
|
1763
|
+
options,
|
|
1764
|
+
world,
|
|
1765
|
+
type: Types.EXTRACT,
|
|
1766
|
+
text: `Extract attribute from element`,
|
|
1767
|
+
operation: "extractAttribute",
|
|
1768
|
+
log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1769
|
+
};
|
|
1781
1770
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1782
|
-
const info = {};
|
|
1783
|
-
info.log = "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n";
|
|
1784
|
-
info.operation = "extract";
|
|
1785
|
-
info.selectors = selectors;
|
|
1786
1771
|
try {
|
|
1787
|
-
|
|
1788
|
-
await this._highlightElements(element);
|
|
1789
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1772
|
+
await _preCommand(state, this);
|
|
1790
1773
|
switch (attribute) {
|
|
1791
1774
|
case "inner_text":
|
|
1792
|
-
|
|
1775
|
+
state.value = await state.element.innerText();
|
|
1793
1776
|
break;
|
|
1794
1777
|
case "href":
|
|
1795
|
-
|
|
1778
|
+
state.value = await state.element.getAttribute("href");
|
|
1796
1779
|
break;
|
|
1797
1780
|
case "value":
|
|
1798
|
-
|
|
1781
|
+
state.value = await state.element.inputValue();
|
|
1799
1782
|
break;
|
|
1800
1783
|
default:
|
|
1801
|
-
|
|
1784
|
+
state.value = await state.element.getAttribute(attribute);
|
|
1802
1785
|
break;
|
|
1803
1786
|
}
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1787
|
+
state.info.value = state.value;
|
|
1788
|
+
this.setTestData({ [variable]: state.value }, world);
|
|
1789
|
+
this.logger.info("set test data: " + variable + "=" + state.value);
|
|
1790
|
+
return state.info;
|
|
1791
|
+
}
|
|
1792
|
+
catch (e) {
|
|
1793
|
+
await _commandError(state, e, this);
|
|
1794
|
+
}
|
|
1795
|
+
finally {
|
|
1796
|
+
_commandFinally(state, this);
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
async verifyAttribute(selectors, attribute, value, _params = null, options = {}, world = null) {
|
|
1800
|
+
const state = {
|
|
1801
|
+
selectors,
|
|
1802
|
+
_params,
|
|
1803
|
+
attribute,
|
|
1804
|
+
value,
|
|
1805
|
+
options,
|
|
1806
|
+
world,
|
|
1807
|
+
type: Types.VERIFY_ATTRIBUTE,
|
|
1808
|
+
text: `Verify element attribute`,
|
|
1809
|
+
operation: "verifyAttribute",
|
|
1810
|
+
log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1811
|
+
};
|
|
1812
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1813
|
+
let val;
|
|
1814
|
+
try {
|
|
1815
|
+
await _preCommand(state, this);
|
|
1816
|
+
switch (attribute) {
|
|
1817
|
+
case "innerText":
|
|
1818
|
+
val = String(await state.element.innerText());
|
|
1819
|
+
break;
|
|
1820
|
+
case "value":
|
|
1821
|
+
val = String(await state.element.inputValue());
|
|
1822
|
+
break;
|
|
1823
|
+
case "checked":
|
|
1824
|
+
val = String(await state.element.isChecked());
|
|
1825
|
+
break;
|
|
1826
|
+
case "disabled":
|
|
1827
|
+
val = String(await state.element.isDisabled());
|
|
1828
|
+
break;
|
|
1829
|
+
default:
|
|
1830
|
+
val = String(await state.element.getAttribute(attribute));
|
|
1831
|
+
break;
|
|
1807
1832
|
}
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1833
|
+
let regex;
|
|
1834
|
+
if (value.startsWith("/") && value.endsWith("/")) {
|
|
1835
|
+
const patternBody = value.slice(1, -1);
|
|
1836
|
+
regex = new RegExp(patternBody, "g");
|
|
1837
|
+
}
|
|
1838
|
+
else {
|
|
1839
|
+
const escapedPattern = value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1840
|
+
regex = new RegExp(escapedPattern, "g");
|
|
1841
|
+
}
|
|
1842
|
+
if (!val.match(regex)) {
|
|
1843
|
+
throw new Error(`The ${attribute} attribute has a value of "${val}", but the expected value is "${value}"`);
|
|
1844
|
+
}
|
|
1845
|
+
state.info.value = val;
|
|
1846
|
+
return state.info;
|
|
1811
1847
|
}
|
|
1812
1848
|
catch (e) {
|
|
1813
|
-
|
|
1814
|
-
this.logger.error("extract failed " + info.log);
|
|
1815
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1816
|
-
info.screenshotPath = screenshotPath;
|
|
1817
|
-
Object.assign(e, { info: info });
|
|
1818
|
-
error = e;
|
|
1819
|
-
throw e;
|
|
1849
|
+
await _commandError(state, e, this);
|
|
1820
1850
|
}
|
|
1821
1851
|
finally {
|
|
1822
|
-
|
|
1823
|
-
this._reportToWorld(world, {
|
|
1824
|
-
element_name: selectors.element_name,
|
|
1825
|
-
type: Types.EXTRACT_ATTRIBUTE,
|
|
1826
|
-
variable: variable,
|
|
1827
|
-
value: info.value,
|
|
1828
|
-
text: "Extract attribute from element",
|
|
1829
|
-
screenshotId,
|
|
1830
|
-
result: error
|
|
1831
|
-
? {
|
|
1832
|
-
status: "FAILED",
|
|
1833
|
-
startTime,
|
|
1834
|
-
endTime,
|
|
1835
|
-
message: error?.message,
|
|
1836
|
-
}
|
|
1837
|
-
: {
|
|
1838
|
-
status: "PASSED",
|
|
1839
|
-
startTime,
|
|
1840
|
-
endTime,
|
|
1841
|
-
},
|
|
1842
|
-
info: info,
|
|
1843
|
-
});
|
|
1852
|
+
_commandFinally(state, this);
|
|
1844
1853
|
}
|
|
1845
1854
|
}
|
|
1846
1855
|
async extractEmailData(emailAddress, options, world) {
|
|
@@ -1861,7 +1870,7 @@ class StableBrowser {
|
|
|
1861
1870
|
if (options && options.timeout) {
|
|
1862
1871
|
timeout = options.timeout;
|
|
1863
1872
|
}
|
|
1864
|
-
const serviceUrl =
|
|
1873
|
+
const serviceUrl = _getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
|
|
1865
1874
|
const request = {
|
|
1866
1875
|
method: "POST",
|
|
1867
1876
|
url: serviceUrl,
|
|
@@ -1917,7 +1926,8 @@ class StableBrowser {
|
|
|
1917
1926
|
catch (e) {
|
|
1918
1927
|
errorCount++;
|
|
1919
1928
|
if (errorCount > 3) {
|
|
1920
|
-
throw e;
|
|
1929
|
+
// throw e;
|
|
1930
|
+
await _commandError({ text: "extractEmailData", operation: "extractEmailData", emailAddress, info: {} }, e, this);
|
|
1921
1931
|
}
|
|
1922
1932
|
// ignore
|
|
1923
1933
|
}
|
|
@@ -2028,11 +2038,12 @@ class StableBrowser {
|
|
|
2028
2038
|
info.screenshotPath = screenshotPath;
|
|
2029
2039
|
Object.assign(e, { info: info });
|
|
2030
2040
|
error = e;
|
|
2031
|
-
throw e;
|
|
2041
|
+
// throw e;
|
|
2042
|
+
await _commandError({ text: "verifyPagePath", operation: "verifyPagePath", pathPart, info }, e, this);
|
|
2032
2043
|
}
|
|
2033
2044
|
finally {
|
|
2034
2045
|
const endTime = Date.now();
|
|
2035
|
-
|
|
2046
|
+
_reportToWorld(world, {
|
|
2036
2047
|
type: Types.VERIFY_PAGE_PATH,
|
|
2037
2048
|
text: "Verify page path",
|
|
2038
2049
|
screenshotId,
|
|
@@ -2052,54 +2063,64 @@ class StableBrowser {
|
|
|
2052
2063
|
});
|
|
2053
2064
|
}
|
|
2054
2065
|
}
|
|
2066
|
+
async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state) {
|
|
2067
|
+
const frames = this.page.frames();
|
|
2068
|
+
let results = [];
|
|
2069
|
+
for (let i = 0; i < frames.length; i++) {
|
|
2070
|
+
if (dateAlternatives.date) {
|
|
2071
|
+
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2072
|
+
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, true, {});
|
|
2073
|
+
result.frame = frames[i];
|
|
2074
|
+
results.push(result);
|
|
2075
|
+
}
|
|
2076
|
+
}
|
|
2077
|
+
else if (numberAlternatives.number) {
|
|
2078
|
+
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2079
|
+
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, true, {});
|
|
2080
|
+
result.frame = frames[i];
|
|
2081
|
+
results.push(result);
|
|
2082
|
+
}
|
|
2083
|
+
}
|
|
2084
|
+
else {
|
|
2085
|
+
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, true, {});
|
|
2086
|
+
result.frame = frames[i];
|
|
2087
|
+
results.push(result);
|
|
2088
|
+
}
|
|
2089
|
+
}
|
|
2090
|
+
state.info.results = results;
|
|
2091
|
+
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2092
|
+
return resultWithElementsFound;
|
|
2093
|
+
}
|
|
2055
2094
|
async verifyTextExistInPage(text, options = {}, world = null) {
|
|
2056
2095
|
text = unEscapeString(text);
|
|
2057
|
-
const
|
|
2096
|
+
const state = {
|
|
2097
|
+
text_search: text,
|
|
2098
|
+
options,
|
|
2099
|
+
world,
|
|
2100
|
+
locate: false,
|
|
2101
|
+
scroll: false,
|
|
2102
|
+
highlight: false,
|
|
2103
|
+
type: Types.VERIFY_PAGE_CONTAINS_TEXT,
|
|
2104
|
+
text: `Verify text exists in page`,
|
|
2105
|
+
operation: "verifyTextExistInPage",
|
|
2106
|
+
log: "***** verify text " + text + " exists in page *****\n",
|
|
2107
|
+
};
|
|
2058
2108
|
const timeout = this._getLoadTimeout(options);
|
|
2059
|
-
let error = null;
|
|
2060
|
-
let screenshotId = null;
|
|
2061
|
-
let screenshotPath = null;
|
|
2062
2109
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2063
|
-
const info = {};
|
|
2064
|
-
info.log = "***** verify text " + text + " exists in page *****\n";
|
|
2065
|
-
info.operation = "verifyTextExistInPage";
|
|
2066
2110
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2067
2111
|
if (newValue !== text) {
|
|
2068
2112
|
this.logger.info(text + "=" + newValue);
|
|
2069
2113
|
text = newValue;
|
|
2070
2114
|
}
|
|
2071
|
-
info.text = text;
|
|
2072
2115
|
let dateAlternatives = findDateAlternatives(text);
|
|
2073
2116
|
let numberAlternatives = findNumberAlternatives(text);
|
|
2074
2117
|
try {
|
|
2118
|
+
await _preCommand(state, this);
|
|
2119
|
+
state.info.text = text;
|
|
2075
2120
|
while (true) {
|
|
2076
|
-
const
|
|
2077
|
-
let results = [];
|
|
2078
|
-
for (let i = 0; i < frames.length; i++) {
|
|
2079
|
-
if (dateAlternatives.date) {
|
|
2080
|
-
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2081
|
-
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*", true, true, {});
|
|
2082
|
-
result.frame = frames[i];
|
|
2083
|
-
results.push(result);
|
|
2084
|
-
}
|
|
2085
|
-
}
|
|
2086
|
-
else if (numberAlternatives.number) {
|
|
2087
|
-
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2088
|
-
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*", true, true, {});
|
|
2089
|
-
result.frame = frames[i];
|
|
2090
|
-
results.push(result);
|
|
2091
|
-
}
|
|
2092
|
-
}
|
|
2093
|
-
else {
|
|
2094
|
-
const result = await this._locateElementByText(frames[i], text, "*", true, true, {});
|
|
2095
|
-
result.frame = frames[i];
|
|
2096
|
-
results.push(result);
|
|
2097
|
-
}
|
|
2098
|
-
}
|
|
2099
|
-
info.results = results;
|
|
2100
|
-
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2121
|
+
const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2101
2122
|
if (resultWithElementsFound.length === 0) {
|
|
2102
|
-
if (Date.now() - startTime > timeout) {
|
|
2123
|
+
if (Date.now() - state.startTime > timeout) {
|
|
2103
2124
|
throw new Error(`Text ${text} not found in page`);
|
|
2104
2125
|
}
|
|
2105
2126
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -2111,55 +2132,163 @@ class StableBrowser {
|
|
|
2111
2132
|
await this._highlightElements(frame, dataAttribute);
|
|
2112
2133
|
const element = await frame.$(dataAttribute);
|
|
2113
2134
|
if (element) {
|
|
2114
|
-
await this.scrollIfNeeded(element, info);
|
|
2135
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2115
2136
|
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2116
2137
|
}
|
|
2117
2138
|
}
|
|
2118
|
-
|
|
2119
|
-
return info;
|
|
2139
|
+
await _screenshot(state, this);
|
|
2140
|
+
return state.info;
|
|
2120
2141
|
}
|
|
2121
2142
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2122
2143
|
}
|
|
2123
2144
|
catch (e) {
|
|
2124
|
-
|
|
2125
|
-
this.logger.error("verify text exist in page failed " + info.log);
|
|
2126
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2127
|
-
info.screenshotPath = screenshotPath;
|
|
2128
|
-
Object.assign(e, { info: info });
|
|
2129
|
-
error = e;
|
|
2130
|
-
throw e;
|
|
2145
|
+
await _commandError(state, e, this);
|
|
2131
2146
|
}
|
|
2132
2147
|
finally {
|
|
2133
|
-
|
|
2134
|
-
this._reportToWorld(world, {
|
|
2135
|
-
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
2136
|
-
text: "Verify text exists in page",
|
|
2137
|
-
screenshotId,
|
|
2138
|
-
result: error
|
|
2139
|
-
? {
|
|
2140
|
-
status: "FAILED",
|
|
2141
|
-
startTime,
|
|
2142
|
-
endTime,
|
|
2143
|
-
message: error?.message,
|
|
2144
|
-
}
|
|
2145
|
-
: {
|
|
2146
|
-
status: "PASSED",
|
|
2147
|
-
startTime,
|
|
2148
|
-
endTime,
|
|
2149
|
-
},
|
|
2150
|
-
info: info,
|
|
2151
|
-
});
|
|
2148
|
+
_commandFinally(state, this);
|
|
2152
2149
|
}
|
|
2153
2150
|
}
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2151
|
+
async waitForTextToDisappear(text, options = {}, world = null) {
|
|
2152
|
+
text = unEscapeString(text);
|
|
2153
|
+
const state = {
|
|
2154
|
+
text_search: text,
|
|
2155
|
+
options,
|
|
2156
|
+
world,
|
|
2157
|
+
locate: false,
|
|
2158
|
+
scroll: false,
|
|
2159
|
+
highlight: false,
|
|
2160
|
+
type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
|
|
2161
|
+
text: `Verify text does not exist in page`,
|
|
2162
|
+
operation: "verifyTextNotExistInPage",
|
|
2163
|
+
log: "***** verify text " + text + " does not exist in page *****\n",
|
|
2164
|
+
};
|
|
2165
|
+
const timeout = this._getLoadTimeout(options);
|
|
2166
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2167
|
+
const newValue = await this._replaceWithLocalData(text, world);
|
|
2168
|
+
if (newValue !== text) {
|
|
2169
|
+
this.logger.info(text + "=" + newValue);
|
|
2170
|
+
text = newValue;
|
|
2158
2171
|
}
|
|
2159
|
-
|
|
2160
|
-
|
|
2172
|
+
let dateAlternatives = findDateAlternatives(text);
|
|
2173
|
+
let numberAlternatives = findNumberAlternatives(text);
|
|
2174
|
+
try {
|
|
2175
|
+
await _preCommand(state, this);
|
|
2176
|
+
state.info.text = text;
|
|
2177
|
+
while (true) {
|
|
2178
|
+
const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2179
|
+
if (resultWithElementsFound.length === 0) {
|
|
2180
|
+
await _screenshot(state, this);
|
|
2181
|
+
return state.info;
|
|
2182
|
+
}
|
|
2183
|
+
if (Date.now() - state.startTime > timeout) {
|
|
2184
|
+
throw new Error(`Text ${text} found in page`);
|
|
2185
|
+
}
|
|
2186
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2187
|
+
}
|
|
2188
|
+
}
|
|
2189
|
+
catch (e) {
|
|
2190
|
+
await _commandError(state, e, this);
|
|
2191
|
+
}
|
|
2192
|
+
finally {
|
|
2193
|
+
_commandFinally(state, this);
|
|
2194
|
+
}
|
|
2195
|
+
}
|
|
2196
|
+
async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
|
|
2197
|
+
textAnchor = unEscapeString(textAnchor);
|
|
2198
|
+
textToVerify = unEscapeString(textToVerify);
|
|
2199
|
+
const state = {
|
|
2200
|
+
text_search: textToVerify,
|
|
2201
|
+
options,
|
|
2202
|
+
world,
|
|
2203
|
+
locate: false,
|
|
2204
|
+
scroll: false,
|
|
2205
|
+
highlight: false,
|
|
2206
|
+
type: Types.VERIFY_TEXT_WITH_RELATION,
|
|
2207
|
+
text: `Verify text with relation to another text`,
|
|
2208
|
+
operation: "verify_text_with_relation",
|
|
2209
|
+
log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
|
|
2210
|
+
};
|
|
2211
|
+
const timeout = this._getLoadTimeout(options);
|
|
2212
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2213
|
+
let newValue = await this._replaceWithLocalData(textAnchor, world);
|
|
2214
|
+
if (newValue !== textAnchor) {
|
|
2215
|
+
this.logger.info(textAnchor + "=" + newValue);
|
|
2216
|
+
textAnchor = newValue;
|
|
2217
|
+
}
|
|
2218
|
+
newValue = await this._replaceWithLocalData(textToVerify, world);
|
|
2219
|
+
if (newValue !== textToVerify) {
|
|
2220
|
+
this.logger.info(textToVerify + "=" + newValue);
|
|
2221
|
+
textToVerify = newValue;
|
|
2222
|
+
}
|
|
2223
|
+
let dateAlternatives = findDateAlternatives(textToVerify);
|
|
2224
|
+
let numberAlternatives = findNumberAlternatives(textToVerify);
|
|
2225
|
+
let foundAncore = false;
|
|
2226
|
+
try {
|
|
2227
|
+
await _preCommand(state, this);
|
|
2228
|
+
state.info.text = textToVerify;
|
|
2229
|
+
while (true) {
|
|
2230
|
+
const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, textAnchor, state);
|
|
2231
|
+
if (resultWithElementsFound.length === 0) {
|
|
2232
|
+
if (Date.now() - state.startTime > timeout) {
|
|
2233
|
+
throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
|
|
2234
|
+
}
|
|
2235
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2236
|
+
continue;
|
|
2237
|
+
}
|
|
2238
|
+
for (let i = 0; i < resultWithElementsFound.length; i++) {
|
|
2239
|
+
foundAncore = true;
|
|
2240
|
+
const result = resultWithElementsFound[i];
|
|
2241
|
+
const token = result.randomToken;
|
|
2242
|
+
const frame = result.frame;
|
|
2243
|
+
const css = `[data-blinq-id="blinq-id-${token}"]`;
|
|
2244
|
+
const findResult = await frame.evaluate(([css, climb, textToVerify, token]) => {
|
|
2245
|
+
const elements = Array.from(document.querySelectorAll(css));
|
|
2246
|
+
for (let i = 0; i < elements.length; i++) {
|
|
2247
|
+
const element = elements[i];
|
|
2248
|
+
let climbParent = element;
|
|
2249
|
+
for (let j = 0; j < climb; j++) {
|
|
2250
|
+
climbParent = climbParent.parentElement;
|
|
2251
|
+
if (!climbParent) {
|
|
2252
|
+
break;
|
|
2253
|
+
}
|
|
2254
|
+
}
|
|
2255
|
+
if (!climbParent) {
|
|
2256
|
+
continue;
|
|
2257
|
+
}
|
|
2258
|
+
const foundElements = window.findMatchingElements(textToVerify, {}, climbParent);
|
|
2259
|
+
if (foundElements.length > 0) {
|
|
2260
|
+
// set the container element attribute
|
|
2261
|
+
element.setAttribute("data-blinq-id", `blinq-id-${token}-anchor`);
|
|
2262
|
+
climbParent.setAttribute("data-blinq-id", `blinq-id-${token}-container`);
|
|
2263
|
+
foundElements[0].setAttribute("data-blinq-id", `blinq-id-${token}-verify`);
|
|
2264
|
+
return { found: true };
|
|
2265
|
+
}
|
|
2266
|
+
}
|
|
2267
|
+
return { found: false };
|
|
2268
|
+
}, [css, climb, textToVerify, result.randomToken]);
|
|
2269
|
+
if (findResult.found === true) {
|
|
2270
|
+
const dataAttribute = `[data-blinq-id="blinq-id-${token}-verify"]`;
|
|
2271
|
+
const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
|
|
2272
|
+
await this._highlightElements(frame, dataAttribute);
|
|
2273
|
+
await this._highlightElements(frame, cssAnchor);
|
|
2274
|
+
const element = await frame.$(dataAttribute);
|
|
2275
|
+
if (element) {
|
|
2276
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2277
|
+
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2278
|
+
}
|
|
2279
|
+
await _screenshot(state, this);
|
|
2280
|
+
return state.info;
|
|
2281
|
+
}
|
|
2282
|
+
}
|
|
2283
|
+
}
|
|
2284
|
+
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2285
|
+
}
|
|
2286
|
+
catch (e) {
|
|
2287
|
+
await _commandError(state, e, this);
|
|
2288
|
+
}
|
|
2289
|
+
finally {
|
|
2290
|
+
_commandFinally(state, this);
|
|
2161
2291
|
}
|
|
2162
|
-
return serviceUrl;
|
|
2163
2292
|
}
|
|
2164
2293
|
async visualVerification(text, options = {}, world = null) {
|
|
2165
2294
|
const startTime = Date.now();
|
|
@@ -2175,7 +2304,7 @@ class StableBrowser {
|
|
|
2175
2304
|
throw new Error("TOKEN is not set");
|
|
2176
2305
|
}
|
|
2177
2306
|
try {
|
|
2178
|
-
let serviceUrl =
|
|
2307
|
+
let serviceUrl = _getServerUrl();
|
|
2179
2308
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2180
2309
|
info.screenshotPath = screenshotPath;
|
|
2181
2310
|
const screenshot = await this.takeScreenshot();
|
|
@@ -2211,11 +2340,12 @@ class StableBrowser {
|
|
|
2211
2340
|
info.screenshotPath = screenshotPath;
|
|
2212
2341
|
Object.assign(e, { info: info });
|
|
2213
2342
|
error = e;
|
|
2214
|
-
throw e;
|
|
2343
|
+
// throw e;
|
|
2344
|
+
await _commandError({ text: "visualVerification", operation: "visualVerification", text, info }, e, this);
|
|
2215
2345
|
}
|
|
2216
2346
|
finally {
|
|
2217
2347
|
const endTime = Date.now();
|
|
2218
|
-
|
|
2348
|
+
_reportToWorld(world, {
|
|
2219
2349
|
type: Types.VERIFY_VISUAL,
|
|
2220
2350
|
text: "Visual verification",
|
|
2221
2351
|
screenshotId,
|
|
@@ -2263,6 +2393,7 @@ class StableBrowser {
|
|
|
2263
2393
|
let screenshotPath = null;
|
|
2264
2394
|
const info = {};
|
|
2265
2395
|
info.log = "";
|
|
2396
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
2266
2397
|
info.operation = "getTableData";
|
|
2267
2398
|
info.selectors = selectors;
|
|
2268
2399
|
try {
|
|
@@ -2278,11 +2409,12 @@ class StableBrowser {
|
|
|
2278
2409
|
info.screenshotPath = screenshotPath;
|
|
2279
2410
|
Object.assign(e, { info: info });
|
|
2280
2411
|
error = e;
|
|
2281
|
-
throw e;
|
|
2412
|
+
// throw e;
|
|
2413
|
+
await _commandError({ text: "getTableData", operation: "getTableData", selectors, info }, e, this);
|
|
2282
2414
|
}
|
|
2283
2415
|
finally {
|
|
2284
2416
|
const endTime = Date.now();
|
|
2285
|
-
|
|
2417
|
+
_reportToWorld(world, {
|
|
2286
2418
|
element_name: selectors.element_name,
|
|
2287
2419
|
type: Types.GET_TABLE_DATA,
|
|
2288
2420
|
text: "Get table data",
|
|
@@ -2337,7 +2469,7 @@ class StableBrowser {
|
|
|
2337
2469
|
info.operation = "analyzeTable";
|
|
2338
2470
|
info.selectors = selectors;
|
|
2339
2471
|
info.query = query;
|
|
2340
|
-
query =
|
|
2472
|
+
query = _fixUsingParams(query, _params);
|
|
2341
2473
|
info.query_fixed = query;
|
|
2342
2474
|
info.operator = operator;
|
|
2343
2475
|
info.value = value;
|
|
@@ -2443,11 +2575,12 @@ class StableBrowser {
|
|
|
2443
2575
|
info.screenshotPath = screenshotPath;
|
|
2444
2576
|
Object.assign(e, { info: info });
|
|
2445
2577
|
error = e;
|
|
2446
|
-
throw e;
|
|
2578
|
+
// throw e;
|
|
2579
|
+
await _commandError({ text: "analyzeTable", operation: "analyzeTable", selectors, query, operator, value }, e, this);
|
|
2447
2580
|
}
|
|
2448
2581
|
finally {
|
|
2449
2582
|
const endTime = Date.now();
|
|
2450
|
-
|
|
2583
|
+
_reportToWorld(world, {
|
|
2451
2584
|
element_name: selectors.element_name,
|
|
2452
2585
|
type: Types.ANALYZE_TABLE,
|
|
2453
2586
|
text: "Analyze table",
|
|
@@ -2469,27 +2602,7 @@ class StableBrowser {
|
|
|
2469
2602
|
}
|
|
2470
2603
|
}
|
|
2471
2604
|
async _replaceWithLocalData(value, world, _decrypt = true, totpWait = true) {
|
|
2472
|
-
|
|
2473
|
-
return value;
|
|
2474
|
-
}
|
|
2475
|
-
// find all the accurance of {{(.*?)}} and replace with the value
|
|
2476
|
-
let regex = /{{(.*?)}}/g;
|
|
2477
|
-
let matches = value.match(regex);
|
|
2478
|
-
if (matches) {
|
|
2479
|
-
const testData = this.getTestData(world);
|
|
2480
|
-
for (let i = 0; i < matches.length; i++) {
|
|
2481
|
-
let match = matches[i];
|
|
2482
|
-
let key = match.substring(2, match.length - 2);
|
|
2483
|
-
let newValue = objectPath.get(testData, key, null);
|
|
2484
|
-
if (newValue !== null) {
|
|
2485
|
-
value = value.replace(match, newValue);
|
|
2486
|
-
}
|
|
2487
|
-
}
|
|
2488
|
-
}
|
|
2489
|
-
if ((value.startsWith("secret:") || value.startsWith("totp:")) && _decrypt) {
|
|
2490
|
-
return await decrypt(value, null, totpWait);
|
|
2491
|
-
}
|
|
2492
|
-
return value;
|
|
2605
|
+
return await replaceWithLocalTestData(value, world, _decrypt, totpWait, this.context, this);
|
|
2493
2606
|
}
|
|
2494
2607
|
_getLoadTimeout(options) {
|
|
2495
2608
|
let timeout = 15000;
|
|
@@ -2540,7 +2653,7 @@ class StableBrowser {
|
|
|
2540
2653
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2541
2654
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world));
|
|
2542
2655
|
const endTime = Date.now();
|
|
2543
|
-
|
|
2656
|
+
_reportToWorld(world, {
|
|
2544
2657
|
type: Types.GET_PAGE_STATUS,
|
|
2545
2658
|
text: "Wait for page load",
|
|
2546
2659
|
screenshotId,
|
|
@@ -2560,41 +2673,35 @@ class StableBrowser {
|
|
|
2560
2673
|
}
|
|
2561
2674
|
}
|
|
2562
2675
|
async closePage(options = {}, world = null) {
|
|
2563
|
-
const
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2676
|
+
const state = {
|
|
2677
|
+
options,
|
|
2678
|
+
world,
|
|
2679
|
+
locate: false,
|
|
2680
|
+
scroll: false,
|
|
2681
|
+
highlight: false,
|
|
2682
|
+
type: Types.CLOSE_PAGE,
|
|
2683
|
+
text: `Close page`,
|
|
2684
|
+
operation: "closePage",
|
|
2685
|
+
log: "***** close page *****\n",
|
|
2686
|
+
throwError: false,
|
|
2687
|
+
};
|
|
2568
2688
|
try {
|
|
2689
|
+
await _preCommand(state, this);
|
|
2569
2690
|
await this.page.close();
|
|
2570
2691
|
}
|
|
2571
2692
|
catch (e) {
|
|
2572
2693
|
console.log(".");
|
|
2694
|
+
await _commandError(state, e, this);
|
|
2573
2695
|
}
|
|
2574
2696
|
finally {
|
|
2575
|
-
|
|
2576
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world));
|
|
2577
|
-
const endTime = Date.now();
|
|
2578
|
-
this._reportToWorld(world, {
|
|
2579
|
-
type: Types.CLOSE_PAGE,
|
|
2580
|
-
text: "close page",
|
|
2581
|
-
screenshotId,
|
|
2582
|
-
result: error
|
|
2583
|
-
? {
|
|
2584
|
-
status: "FAILED",
|
|
2585
|
-
startTime,
|
|
2586
|
-
endTime,
|
|
2587
|
-
message: error?.message,
|
|
2588
|
-
}
|
|
2589
|
-
: {
|
|
2590
|
-
status: "PASSED",
|
|
2591
|
-
startTime,
|
|
2592
|
-
endTime,
|
|
2593
|
-
},
|
|
2594
|
-
info: info,
|
|
2595
|
-
});
|
|
2697
|
+
_commandFinally(state, this);
|
|
2596
2698
|
}
|
|
2597
2699
|
}
|
|
2700
|
+
saveTestDataAsGlobal(options, world) {
|
|
2701
|
+
const dataFile = this._getDataFile(world);
|
|
2702
|
+
process.env.GLOBAL_TEST_DATA_FILE = dataFile;
|
|
2703
|
+
this.logger.info("Save the scenario test data as global for the following scenarios.");
|
|
2704
|
+
}
|
|
2598
2705
|
async setViewportSize(width, hight, options = {}, world = null) {
|
|
2599
2706
|
const startTime = Date.now();
|
|
2600
2707
|
let error = null;
|
|
@@ -2612,12 +2719,13 @@ class StableBrowser {
|
|
|
2612
2719
|
}
|
|
2613
2720
|
catch (e) {
|
|
2614
2721
|
console.log(".");
|
|
2722
|
+
await _commandError({ text: "setViewportSize", operation: "setViewportSize", width, hight, info }, e, this);
|
|
2615
2723
|
}
|
|
2616
2724
|
finally {
|
|
2617
2725
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2618
2726
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world));
|
|
2619
2727
|
const endTime = Date.now();
|
|
2620
|
-
|
|
2728
|
+
_reportToWorld(world, {
|
|
2621
2729
|
type: Types.SET_VIEWPORT,
|
|
2622
2730
|
text: "set viewport size to " + width + "x" + hight,
|
|
2623
2731
|
screenshotId,
|
|
@@ -2648,12 +2756,13 @@ class StableBrowser {
|
|
|
2648
2756
|
}
|
|
2649
2757
|
catch (e) {
|
|
2650
2758
|
console.log(".");
|
|
2759
|
+
await _commandError({ text: "reloadPage", operation: "reloadPage", info }, e, this);
|
|
2651
2760
|
}
|
|
2652
2761
|
finally {
|
|
2653
2762
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2654
2763
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2655
2764
|
const endTime = Date.now();
|
|
2656
|
-
|
|
2765
|
+
_reportToWorld(world, {
|
|
2657
2766
|
type: Types.GET_PAGE_STATUS,
|
|
2658
2767
|
text: "page relaod",
|
|
2659
2768
|
screenshotId,
|
|
@@ -2689,11 +2798,37 @@ class StableBrowser {
|
|
|
2689
2798
|
console.log("#-#");
|
|
2690
2799
|
}
|
|
2691
2800
|
}
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2801
|
+
async beforeStep(world, step) {
|
|
2802
|
+
this.stepName = step.pickleStep.text;
|
|
2803
|
+
this.logger.info("step: " + this.stepName);
|
|
2804
|
+
if (this.stepIndex === undefined) {
|
|
2805
|
+
this.stepIndex = 0;
|
|
2806
|
+
}
|
|
2807
|
+
else {
|
|
2808
|
+
this.stepIndex++;
|
|
2809
|
+
}
|
|
2810
|
+
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
2811
|
+
if (this.context.browserObject.context) {
|
|
2812
|
+
await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
|
|
2813
|
+
}
|
|
2814
|
+
}
|
|
2815
|
+
if (this.tags === null && step && step.pickle && step.pickle.tags) {
|
|
2816
|
+
this.tags = step.pickle.tags.map((tag) => tag.name);
|
|
2817
|
+
// check if @global_test_data tag is present
|
|
2818
|
+
if (this.tags.includes("@global_test_data")) {
|
|
2819
|
+
this.saveTestDataAsGlobal({}, world);
|
|
2820
|
+
}
|
|
2821
|
+
}
|
|
2822
|
+
}
|
|
2823
|
+
async afterStep(world, step) {
|
|
2824
|
+
this.stepName = null;
|
|
2825
|
+
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
2826
|
+
if (this.context.browserObject.context) {
|
|
2827
|
+
await this.context.browserObject.context.tracing.stopChunk({
|
|
2828
|
+
path: path.join(this.context.browserObject.traceFolder, `trace-${this.stepIndex}.zip`),
|
|
2829
|
+
});
|
|
2830
|
+
}
|
|
2695
2831
|
}
|
|
2696
|
-
world.attach(JSON.stringify(properties), { mediaType: "application/json" });
|
|
2697
2832
|
}
|
|
2698
2833
|
}
|
|
2699
2834
|
function createTimedPromise(promise, label) {
|
|
@@ -2701,156 +2836,5 @@ function createTimedPromise(promise, label) {
|
|
|
2701
2836
|
.then((result) => ({ status: "fulfilled", label, result }))
|
|
2702
2837
|
.catch((error) => Promise.reject({ status: "rejected", label, error }));
|
|
2703
2838
|
}
|
|
2704
|
-
const KEYBOARD_EVENTS = [
|
|
2705
|
-
"ALT",
|
|
2706
|
-
"AltGraph",
|
|
2707
|
-
"CapsLock",
|
|
2708
|
-
"Control",
|
|
2709
|
-
"Fn",
|
|
2710
|
-
"FnLock",
|
|
2711
|
-
"Hyper",
|
|
2712
|
-
"Meta",
|
|
2713
|
-
"NumLock",
|
|
2714
|
-
"ScrollLock",
|
|
2715
|
-
"Shift",
|
|
2716
|
-
"Super",
|
|
2717
|
-
"Symbol",
|
|
2718
|
-
"SymbolLock",
|
|
2719
|
-
"Enter",
|
|
2720
|
-
"Tab",
|
|
2721
|
-
"ArrowDown",
|
|
2722
|
-
"ArrowLeft",
|
|
2723
|
-
"ArrowRight",
|
|
2724
|
-
"ArrowUp",
|
|
2725
|
-
"End",
|
|
2726
|
-
"Home",
|
|
2727
|
-
"PageDown",
|
|
2728
|
-
"PageUp",
|
|
2729
|
-
"Backspace",
|
|
2730
|
-
"Clear",
|
|
2731
|
-
"Copy",
|
|
2732
|
-
"CrSel",
|
|
2733
|
-
"Cut",
|
|
2734
|
-
"Delete",
|
|
2735
|
-
"EraseEof",
|
|
2736
|
-
"ExSel",
|
|
2737
|
-
"Insert",
|
|
2738
|
-
"Paste",
|
|
2739
|
-
"Redo",
|
|
2740
|
-
"Undo",
|
|
2741
|
-
"Accept",
|
|
2742
|
-
"Again",
|
|
2743
|
-
"Attn",
|
|
2744
|
-
"Cancel",
|
|
2745
|
-
"ContextMenu",
|
|
2746
|
-
"Escape",
|
|
2747
|
-
"Execute",
|
|
2748
|
-
"Find",
|
|
2749
|
-
"Finish",
|
|
2750
|
-
"Help",
|
|
2751
|
-
"Pause",
|
|
2752
|
-
"Play",
|
|
2753
|
-
"Props",
|
|
2754
|
-
"Select",
|
|
2755
|
-
"ZoomIn",
|
|
2756
|
-
"ZoomOut",
|
|
2757
|
-
"BrightnessDown",
|
|
2758
|
-
"BrightnessUp",
|
|
2759
|
-
"Eject",
|
|
2760
|
-
"LogOff",
|
|
2761
|
-
"Power",
|
|
2762
|
-
"PowerOff",
|
|
2763
|
-
"PrintScreen",
|
|
2764
|
-
"Hibernate",
|
|
2765
|
-
"Standby",
|
|
2766
|
-
"WakeUp",
|
|
2767
|
-
"AllCandidates",
|
|
2768
|
-
"Alphanumeric",
|
|
2769
|
-
"CodeInput",
|
|
2770
|
-
"Compose",
|
|
2771
|
-
"Convert",
|
|
2772
|
-
"Dead",
|
|
2773
|
-
"FinalMode",
|
|
2774
|
-
"GroupFirst",
|
|
2775
|
-
"GroupLast",
|
|
2776
|
-
"GroupNext",
|
|
2777
|
-
"GroupPrevious",
|
|
2778
|
-
"ModeChange",
|
|
2779
|
-
"NextCandidate",
|
|
2780
|
-
"NonConvert",
|
|
2781
|
-
"PreviousCandidate",
|
|
2782
|
-
"Process",
|
|
2783
|
-
"SingleCandidate",
|
|
2784
|
-
"HangulMode",
|
|
2785
|
-
"HanjaMode",
|
|
2786
|
-
"JunjaMode",
|
|
2787
|
-
"Eisu",
|
|
2788
|
-
"Hankaku",
|
|
2789
|
-
"Hiragana",
|
|
2790
|
-
"HiraganaKatakana",
|
|
2791
|
-
"KanaMode",
|
|
2792
|
-
"KanjiMode",
|
|
2793
|
-
"Katakana",
|
|
2794
|
-
"Romaji",
|
|
2795
|
-
"Zenkaku",
|
|
2796
|
-
"ZenkakuHanaku",
|
|
2797
|
-
"F1",
|
|
2798
|
-
"F2",
|
|
2799
|
-
"F3",
|
|
2800
|
-
"F4",
|
|
2801
|
-
"F5",
|
|
2802
|
-
"F6",
|
|
2803
|
-
"F7",
|
|
2804
|
-
"F8",
|
|
2805
|
-
"F9",
|
|
2806
|
-
"F10",
|
|
2807
|
-
"F11",
|
|
2808
|
-
"F12",
|
|
2809
|
-
"Soft1",
|
|
2810
|
-
"Soft2",
|
|
2811
|
-
"Soft3",
|
|
2812
|
-
"Soft4",
|
|
2813
|
-
"ChannelDown",
|
|
2814
|
-
"ChannelUp",
|
|
2815
|
-
"Close",
|
|
2816
|
-
"MailForward",
|
|
2817
|
-
"MailReply",
|
|
2818
|
-
"MailSend",
|
|
2819
|
-
"MediaFastForward",
|
|
2820
|
-
"MediaPause",
|
|
2821
|
-
"MediaPlay",
|
|
2822
|
-
"MediaPlayPause",
|
|
2823
|
-
"MediaRecord",
|
|
2824
|
-
"MediaRewind",
|
|
2825
|
-
"MediaStop",
|
|
2826
|
-
"MediaTrackNext",
|
|
2827
|
-
"MediaTrackPrevious",
|
|
2828
|
-
"AudioBalanceLeft",
|
|
2829
|
-
"AudioBalanceRight",
|
|
2830
|
-
"AudioBassBoostDown",
|
|
2831
|
-
"AudioBassBoostToggle",
|
|
2832
|
-
"AudioBassBoostUp",
|
|
2833
|
-
"AudioFaderFront",
|
|
2834
|
-
"AudioFaderRear",
|
|
2835
|
-
"AudioSurroundModeNext",
|
|
2836
|
-
"AudioTrebleDown",
|
|
2837
|
-
"AudioTrebleUp",
|
|
2838
|
-
"AudioVolumeDown",
|
|
2839
|
-
"AudioVolumeMute",
|
|
2840
|
-
"AudioVolumeUp",
|
|
2841
|
-
"MicrophoneToggle",
|
|
2842
|
-
"MicrophoneVolumeDown",
|
|
2843
|
-
"MicrophoneVolumeMute",
|
|
2844
|
-
"MicrophoneVolumeUp",
|
|
2845
|
-
"TV",
|
|
2846
|
-
"TV3DMode",
|
|
2847
|
-
"TVAntennaCable",
|
|
2848
|
-
"TVAudioDescription",
|
|
2849
|
-
];
|
|
2850
|
-
function unEscapeString(str) {
|
|
2851
|
-
const placeholder = "__NEWLINE__";
|
|
2852
|
-
str = str.replace(new RegExp(placeholder, "g"), "\n");
|
|
2853
|
-
return str;
|
|
2854
|
-
}
|
|
2855
2839
|
export { StableBrowser };
|
|
2856
2840
|
//# sourceMappingURL=stable_browser.js.map
|