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