automation_model 1.0.489-dev → 1.0.489-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 +55 -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 +2 -1
- package/lib/network.js +33 -1
- package/lib/network.js.map +1 -1
- package/lib/scripts/find_text.js +126 -0
- package/lib/stable_browser.d.ts +49 -17
- package/lib/stable_browser.js +602 -576
- 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 +12 -1
- package/lib/utils.js +237 -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,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 { maskValue, 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
|
-
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,25 +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);
|
|
95
103
|
registerNetworkEvents(this.world, this, this.context, this.page);
|
|
104
|
+
registerDownloadEvent(this.page, this.world, this.context);
|
|
96
105
|
}
|
|
97
106
|
registerEventListeners(context) {
|
|
98
107
|
this.registerConsoleLogListener(this.page, context);
|
|
99
|
-
this.registerRequestListener(this.page, context, this.webLogFile);
|
|
108
|
+
// this.registerRequestListener(this.page, context, this.webLogFile);
|
|
100
109
|
if (!context.pageLoading) {
|
|
101
110
|
context.pageLoading = { status: false };
|
|
102
111
|
}
|
|
103
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
|
+
}
|
|
104
118
|
context.pageLoading.status = true;
|
|
105
119
|
this.page = page;
|
|
106
120
|
context.page = page;
|
|
107
121
|
context.pages.push(page);
|
|
108
122
|
registerNetworkEvents(this.world, this, context, this.page);
|
|
123
|
+
registerDownloadEvent(this.page, this.world, context);
|
|
109
124
|
page.on("close", async () => {
|
|
110
125
|
if (this.context && this.context.pages && this.context.pages.length > 1) {
|
|
111
126
|
this.context.pages.pop();
|
|
@@ -135,9 +150,9 @@ class StableBrowser {
|
|
|
135
150
|
if (this.appName === appName) {
|
|
136
151
|
return;
|
|
137
152
|
}
|
|
138
|
-
let
|
|
153
|
+
let navigate = false;
|
|
139
154
|
if (!apps[appName]) {
|
|
140
|
-
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);
|
|
141
156
|
newContextCreated = true;
|
|
142
157
|
apps[appName] = {
|
|
143
158
|
context: newContext,
|
|
@@ -146,32 +161,15 @@ class StableBrowser {
|
|
|
146
161
|
};
|
|
147
162
|
}
|
|
148
163
|
const tempContext = {};
|
|
149
|
-
|
|
150
|
-
|
|
164
|
+
_copyContext(this, tempContext);
|
|
165
|
+
_copyContext(apps[appName], this);
|
|
151
166
|
apps[this.appName] = tempContext;
|
|
152
167
|
this.appName = appName;
|
|
153
|
-
if (
|
|
154
|
-
this.registerEventListeners(this.context);
|
|
168
|
+
if (navigate) {
|
|
155
169
|
await this.goto(this.context.environment.baseUrl);
|
|
156
170
|
await this.waitForPageLoad();
|
|
157
171
|
}
|
|
158
172
|
}
|
|
159
|
-
_copyContext(from, to) {
|
|
160
|
-
to.browser = from.browser;
|
|
161
|
-
to.page = from.page;
|
|
162
|
-
to.context = from.context;
|
|
163
|
-
}
|
|
164
|
-
getWebLogFile(logFolder) {
|
|
165
|
-
if (!fs.existsSync(logFolder)) {
|
|
166
|
-
fs.mkdirSync(logFolder, { recursive: true });
|
|
167
|
-
}
|
|
168
|
-
let nextIndex = 1;
|
|
169
|
-
while (fs.existsSync(path.join(logFolder, nextIndex.toString() + ".json"))) {
|
|
170
|
-
nextIndex++;
|
|
171
|
-
}
|
|
172
|
-
const fileName = nextIndex + ".json";
|
|
173
|
-
return path.join(logFolder, fileName);
|
|
174
|
-
}
|
|
175
173
|
registerConsoleLogListener(page, context) {
|
|
176
174
|
if (!this.context.webLogger) {
|
|
177
175
|
this.context.webLogger = [];
|
|
@@ -221,7 +219,7 @@ class StableBrowser {
|
|
|
221
219
|
this.world?.attach(JSON.stringify(obj), { mediaType: "application/json+network" });
|
|
222
220
|
}
|
|
223
221
|
catch (error) {
|
|
224
|
-
console.error("Error in request listener", error);
|
|
222
|
+
// console.error("Error in request listener", error);
|
|
225
223
|
context.networkLogger.push({
|
|
226
224
|
error: "not able to listen",
|
|
227
225
|
message: error.message,
|
|
@@ -235,55 +233,40 @@ class StableBrowser {
|
|
|
235
233
|
// async closeUnexpectedPopups() {
|
|
236
234
|
// await closeUnexpectedPopups(this.page);
|
|
237
235
|
// }
|
|
238
|
-
async goto(url) {
|
|
236
|
+
async goto(url, world = null) {
|
|
239
237
|
if (!url.startsWith("http")) {
|
|
240
238
|
url = "https://" + url;
|
|
241
239
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
240
|
+
const state = {
|
|
241
|
+
value: url,
|
|
242
|
+
world: world,
|
|
243
|
+
type: Types.NAVIGATE,
|
|
244
|
+
text: `Navigate Page to: ${url}`,
|
|
245
|
+
operation: "goto",
|
|
246
|
+
log: "***** navigate page to " + url + " *****\n",
|
|
247
|
+
info: {},
|
|
248
|
+
locate: false,
|
|
249
|
+
scroll: false,
|
|
250
|
+
screenshot: false,
|
|
251
|
+
highlight: false,
|
|
252
|
+
};
|
|
253
|
+
try {
|
|
254
|
+
await _preCommand(state, this);
|
|
255
|
+
await this.page.goto(url, {
|
|
256
|
+
timeout: 60000,
|
|
257
|
+
});
|
|
258
|
+
await _screenshot(state, this);
|
|
257
259
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
// check if not null
|
|
262
|
-
if (!locator) {
|
|
263
|
-
return locator;
|
|
260
|
+
catch (error) {
|
|
261
|
+
console.error("Error on goto", error);
|
|
262
|
+
_commandError(state, error, this);
|
|
264
263
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
this.scanAndManipulate(locator, _params);
|
|
268
|
-
return locator;
|
|
269
|
-
}
|
|
270
|
-
_isObject(value) {
|
|
271
|
-
return value && typeof value === "object" && value.constructor === Object;
|
|
272
|
-
}
|
|
273
|
-
scanAndManipulate(currentObj, _params) {
|
|
274
|
-
for (const key in currentObj) {
|
|
275
|
-
if (typeof currentObj[key] === "string") {
|
|
276
|
-
// Perform string manipulation
|
|
277
|
-
currentObj[key] = this._fixUsingParams(currentObj[key], _params);
|
|
278
|
-
}
|
|
279
|
-
else if (this._isObject(currentObj[key])) {
|
|
280
|
-
// Recursively scan nested objects
|
|
281
|
-
this.scanAndManipulate(currentObj[key], _params);
|
|
282
|
-
}
|
|
264
|
+
finally {
|
|
265
|
+
_commandFinally(state, this);
|
|
283
266
|
}
|
|
284
267
|
}
|
|
285
268
|
_getLocator(locator, scope, _params) {
|
|
286
|
-
locator =
|
|
269
|
+
locator = _fixLocatorUsingParams(locator, _params);
|
|
287
270
|
let locatorReturn;
|
|
288
271
|
if (locator.role) {
|
|
289
272
|
if (locator.role[1].nameReg) {
|
|
@@ -291,7 +274,7 @@ class StableBrowser {
|
|
|
291
274
|
delete locator.role[1].nameReg;
|
|
292
275
|
}
|
|
293
276
|
// if (locator.role[1].name) {
|
|
294
|
-
// locator.role[1].name =
|
|
277
|
+
// locator.role[1].name = _fixUsingParams(locator.role[1].name, _params);
|
|
295
278
|
// }
|
|
296
279
|
locatorReturn = scope.getByRole(locator.role[0], locator.role[1]);
|
|
297
280
|
}
|
|
@@ -334,7 +317,7 @@ class StableBrowser {
|
|
|
334
317
|
if (css && css.locator) {
|
|
335
318
|
css = css.locator;
|
|
336
319
|
}
|
|
337
|
-
let result = await this._locateElementByText(scope,
|
|
320
|
+
let result = await this._locateElementByText(scope, _fixUsingParams(text, _params), "*:not(script, style, head)", false, false, _params);
|
|
338
321
|
if (result.elementCount === 0) {
|
|
339
322
|
return;
|
|
340
323
|
}
|
|
@@ -350,116 +333,28 @@ class StableBrowser {
|
|
|
350
333
|
async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, _params) {
|
|
351
334
|
//const stringifyText = JSON.stringify(text);
|
|
352
335
|
return await scope.locator(":root").evaluate((_node, [text, tag, regex, partial]) => {
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
if (currentNode === parent) {
|
|
357
|
-
return true;
|
|
358
|
-
}
|
|
359
|
-
currentNode = currentNode.parentNode;
|
|
360
|
-
}
|
|
361
|
-
return false;
|
|
362
|
-
}
|
|
363
|
-
document.isParent = isParent;
|
|
364
|
-
function getRegex(str) {
|
|
365
|
-
const match = str.match(/^\/(.*?)\/([gimuy]*)$/);
|
|
366
|
-
if (!match) {
|
|
367
|
-
return null;
|
|
368
|
-
}
|
|
369
|
-
let [_, pattern, flags] = match;
|
|
370
|
-
return new RegExp(pattern, flags);
|
|
371
|
-
}
|
|
372
|
-
document.getRegex = getRegex;
|
|
373
|
-
function collectAllShadowDomElements(element, result = []) {
|
|
374
|
-
// Check and add the element if it has a shadow root
|
|
375
|
-
if (element.shadowRoot) {
|
|
376
|
-
result.push(element);
|
|
377
|
-
// Also search within the shadow root
|
|
378
|
-
document.collectAllShadowDomElements(element.shadowRoot, result);
|
|
379
|
-
}
|
|
380
|
-
// Iterate over child nodes
|
|
381
|
-
element.childNodes.forEach((child) => {
|
|
382
|
-
// Recursively call the function for each child node
|
|
383
|
-
document.collectAllShadowDomElements(child, result);
|
|
384
|
-
});
|
|
385
|
-
return result;
|
|
386
|
-
}
|
|
387
|
-
document.collectAllShadowDomElements = collectAllShadowDomElements;
|
|
388
|
-
if (!tag) {
|
|
389
|
-
tag = "*";
|
|
390
|
-
}
|
|
391
|
-
let regexpSearch = document.getRegex(text);
|
|
392
|
-
if (regexpSearch) {
|
|
393
|
-
regex = true;
|
|
394
|
-
}
|
|
395
|
-
let elements = Array.from(document.querySelectorAll(tag));
|
|
396
|
-
let shadowHosts = [];
|
|
397
|
-
document.collectAllShadowDomElements(document, shadowHosts);
|
|
398
|
-
for (let i = 0; i < shadowHosts.length; i++) {
|
|
399
|
-
let shadowElement = shadowHosts[i].shadowRoot;
|
|
400
|
-
if (!shadowElement) {
|
|
401
|
-
console.log("shadowElement is null, for host " + shadowHosts[i]);
|
|
402
|
-
continue;
|
|
403
|
-
}
|
|
404
|
-
let shadowElements = Array.from(shadowElement.querySelectorAll(tag));
|
|
405
|
-
elements = elements.concat(shadowElements);
|
|
406
|
-
}
|
|
407
|
-
let randomToken = null;
|
|
408
|
-
const foundElements = [];
|
|
336
|
+
const options = {
|
|
337
|
+
innerText: true,
|
|
338
|
+
};
|
|
409
339
|
if (regex) {
|
|
410
|
-
|
|
411
|
-
regexpSearch = new RegExp(text, "im");
|
|
412
|
-
}
|
|
413
|
-
for (let i = 0; i < elements.length; i++) {
|
|
414
|
-
const element = elements[i];
|
|
415
|
-
if ((element.innerText && regexpSearch.test(element.innerText)) ||
|
|
416
|
-
(element.value && regexpSearch.test(element.value))) {
|
|
417
|
-
foundElements.push(element);
|
|
418
|
-
}
|
|
419
|
-
}
|
|
340
|
+
options.singleRegex = true;
|
|
420
341
|
}
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
for (let i = 0; i < elements.length; i++) {
|
|
424
|
-
const element = elements[i];
|
|
425
|
-
if (partial) {
|
|
426
|
-
if ((element.innerText && element.innerText.toLowerCase().trim().includes(text.toLowerCase())) ||
|
|
427
|
-
(element.value && element.value.toLowerCase().includes(text.toLowerCase()))) {
|
|
428
|
-
foundElements.push(element);
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
else {
|
|
432
|
-
if ((element.innerText && element.innerText.trim() === text) ||
|
|
433
|
-
(element.value && element.value === text)) {
|
|
434
|
-
foundElements.push(element);
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
}
|
|
342
|
+
if (tag) {
|
|
343
|
+
options.tag = tag;
|
|
438
344
|
}
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
let element = foundElements[i];
|
|
442
|
-
let hasChild = false;
|
|
443
|
-
for (let j = 0; j < foundElements.length; j++) {
|
|
444
|
-
if (i === j) {
|
|
445
|
-
continue;
|
|
446
|
-
}
|
|
447
|
-
if (isParent(element, foundElements[j])) {
|
|
448
|
-
hasChild = true;
|
|
449
|
-
break;
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
if (!hasChild) {
|
|
453
|
-
noChildElements.push(element);
|
|
454
|
-
}
|
|
345
|
+
if (!(partial === true)) {
|
|
346
|
+
options.exactMatch = true;
|
|
455
347
|
}
|
|
348
|
+
const elements = window.findMatchingElements(text, options);
|
|
349
|
+
let randomToken = null;
|
|
350
|
+
const foundElements = [];
|
|
456
351
|
let elementCount = 0;
|
|
457
|
-
if (
|
|
458
|
-
for (let i = 0; i <
|
|
352
|
+
if (elements.length > 0) {
|
|
353
|
+
for (let i = 0; i < elements.length; i++) {
|
|
459
354
|
if (randomToken === null) {
|
|
460
355
|
randomToken = Math.random().toString(36).substring(7);
|
|
461
356
|
}
|
|
462
|
-
let element =
|
|
357
|
+
let element = elements[i];
|
|
463
358
|
element.setAttribute("data-blinq-id", "blinq-id-" + randomToken);
|
|
464
359
|
elementCount++;
|
|
465
360
|
}
|
|
@@ -468,9 +363,21 @@ class StableBrowser {
|
|
|
468
363
|
}, [text1, tag1, regex1, partial1]);
|
|
469
364
|
}
|
|
470
365
|
async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true) {
|
|
366
|
+
if (!info) {
|
|
367
|
+
info = {};
|
|
368
|
+
}
|
|
369
|
+
if (!info.failCause) {
|
|
370
|
+
info.failCause = {};
|
|
371
|
+
}
|
|
372
|
+
if (!info.log) {
|
|
373
|
+
info.log = "";
|
|
374
|
+
info.locatorLog = new LocatorLog(selectorHierarchy);
|
|
375
|
+
}
|
|
471
376
|
let locatorSearch = selectorHierarchy[index];
|
|
377
|
+
let originalLocatorSearch = "";
|
|
472
378
|
try {
|
|
473
|
-
|
|
379
|
+
originalLocatorSearch = _fixUsingParams(JSON.stringify(locatorSearch), _params);
|
|
380
|
+
locatorSearch = JSON.parse(originalLocatorSearch);
|
|
474
381
|
}
|
|
475
382
|
catch (e) {
|
|
476
383
|
console.error(e);
|
|
@@ -480,13 +387,18 @@ class StableBrowser {
|
|
|
480
387
|
if (locatorSearch.climb && locatorSearch.climb >= 0) {
|
|
481
388
|
let locatorString = await this._locateElmentByTextClimbCss(scope, locatorSearch.text, locatorSearch.climb, locatorSearch.css, _params);
|
|
482
389
|
if (!locatorString) {
|
|
390
|
+
info.failCause.textNotFound = true;
|
|
391
|
+
info.failCause.lastError = "failed to locate element by text: " + locatorSearch.text;
|
|
483
392
|
return;
|
|
484
393
|
}
|
|
485
394
|
locator = this._getLocator({ css: locatorString }, scope, _params);
|
|
486
395
|
}
|
|
487
396
|
else if (locatorSearch.text) {
|
|
488
|
-
let
|
|
397
|
+
let text = _fixUsingParams(locatorSearch.text, _params);
|
|
398
|
+
let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, _params);
|
|
489
399
|
if (result.elementCount === 0) {
|
|
400
|
+
info.failCause.textNotFound = true;
|
|
401
|
+
info.failCause.lastError = "failed to locate element by text: " + text;
|
|
490
402
|
return;
|
|
491
403
|
}
|
|
492
404
|
locatorSearch.css = "[data-blinq-id='blinq-id-" + result.randomToken + "']";
|
|
@@ -503,13 +415,22 @@ class StableBrowser {
|
|
|
503
415
|
// cssHref = true;
|
|
504
416
|
// }
|
|
505
417
|
let count = await locator.count();
|
|
418
|
+
if (count > 0 && !info.failCause.count) {
|
|
419
|
+
info.failCause.count = count;
|
|
420
|
+
}
|
|
506
421
|
//info.log += "total elements found " + count + "\n";
|
|
507
422
|
//let visibleCount = 0;
|
|
508
423
|
let visibleLocator = null;
|
|
509
|
-
if (locatorSearch.index && locatorSearch.index < count) {
|
|
424
|
+
if (typeof locatorSearch.index === "number" && locatorSearch.index < count) {
|
|
510
425
|
foundLocators.push(locator.nth(locatorSearch.index));
|
|
426
|
+
if (info.locatorLog) {
|
|
427
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
|
|
428
|
+
}
|
|
511
429
|
return;
|
|
512
430
|
}
|
|
431
|
+
if (info.locatorLog && count === 0) {
|
|
432
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "NOT_FOUND");
|
|
433
|
+
}
|
|
513
434
|
for (let j = 0; j < count; j++) {
|
|
514
435
|
let visible = await locator.nth(j).isVisible();
|
|
515
436
|
const enabled = await locator.nth(j).isEnabled();
|
|
@@ -518,24 +439,40 @@ class StableBrowser {
|
|
|
518
439
|
}
|
|
519
440
|
if (visible && enabled) {
|
|
520
441
|
foundLocators.push(locator.nth(j));
|
|
442
|
+
if (info.locatorLog) {
|
|
443
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
|
|
444
|
+
}
|
|
521
445
|
}
|
|
522
446
|
else {
|
|
447
|
+
info.failCause.visible = visible;
|
|
448
|
+
info.failCause.enabled = enabled;
|
|
523
449
|
if (!info.printMessages) {
|
|
524
450
|
info.printMessages = {};
|
|
525
451
|
}
|
|
452
|
+
if (info.locatorLog && !visible) {
|
|
453
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_VISIBLE");
|
|
454
|
+
}
|
|
455
|
+
if (info.locatorLog && !enabled) {
|
|
456
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_ENABLED");
|
|
457
|
+
}
|
|
526
458
|
if (!info.printMessages[j.toString()]) {
|
|
527
|
-
info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
|
|
459
|
+
//info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
|
|
528
460
|
info.printMessages[j.toString()] = true;
|
|
529
461
|
}
|
|
530
462
|
}
|
|
531
463
|
}
|
|
532
464
|
}
|
|
533
465
|
async closeUnexpectedPopups(info, _params) {
|
|
466
|
+
if (!info) {
|
|
467
|
+
info = {};
|
|
468
|
+
info.failCause = {};
|
|
469
|
+
info.log = "";
|
|
470
|
+
}
|
|
534
471
|
if (this.configuration.popupHandlers && this.configuration.popupHandlers.length > 0) {
|
|
535
472
|
if (!info) {
|
|
536
473
|
info = {};
|
|
537
474
|
}
|
|
538
|
-
info.log += "scan for popup handlers" + "\n";
|
|
475
|
+
//info.log += "scan for popup handlers" + "\n";
|
|
539
476
|
const handlerGroup = [];
|
|
540
477
|
for (let i = 0; i < this.configuration.popupHandlers.length; i++) {
|
|
541
478
|
handlerGroup.push(this.configuration.popupHandlers[i].locator);
|
|
@@ -562,9 +499,21 @@ class StableBrowser {
|
|
|
562
499
|
}
|
|
563
500
|
if (result.foundElements.length > 0) {
|
|
564
501
|
let dialogCloseLocator = result.foundElements[0].locator;
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
502
|
+
try {
|
|
503
|
+
await scope?.evaluate(() => {
|
|
504
|
+
window.__isClosingPopups = true;
|
|
505
|
+
});
|
|
506
|
+
await dialogCloseLocator.click();
|
|
507
|
+
// wait for the dialog to close
|
|
508
|
+
await dialogCloseLocator.waitFor({ state: "hidden" });
|
|
509
|
+
}
|
|
510
|
+
catch (e) {
|
|
511
|
+
}
|
|
512
|
+
finally {
|
|
513
|
+
await scope?.evaluate(() => {
|
|
514
|
+
window.__isClosingPopups = false;
|
|
515
|
+
});
|
|
516
|
+
}
|
|
568
517
|
return { rerun: true };
|
|
569
518
|
}
|
|
570
519
|
}
|
|
@@ -588,7 +537,13 @@ class StableBrowser {
|
|
|
588
537
|
}
|
|
589
538
|
throw new Error("unable to locate element " + JSON.stringify(selectors));
|
|
590
539
|
}
|
|
591
|
-
async _findFrameScope(selectors, timeout = 30000) {
|
|
540
|
+
async _findFrameScope(selectors, timeout = 30000, info) {
|
|
541
|
+
if (!info) {
|
|
542
|
+
info = {};
|
|
543
|
+
info.failCause = {};
|
|
544
|
+
info.log = "";
|
|
545
|
+
}
|
|
546
|
+
let startTime = Date.now();
|
|
592
547
|
let scope = this.page;
|
|
593
548
|
if (selectors.frame) {
|
|
594
549
|
return selectors.frame;
|
|
@@ -619,9 +574,11 @@ class StableBrowser {
|
|
|
619
574
|
}
|
|
620
575
|
return framescope;
|
|
621
576
|
};
|
|
577
|
+
let fLocator = null;
|
|
622
578
|
while (true) {
|
|
623
579
|
let frameFound = false;
|
|
624
580
|
if (selectors.nestFrmLoc) {
|
|
581
|
+
fLocator = selectors.nestFrmLoc;
|
|
625
582
|
scope = await findFrame(selectors.nestFrmLoc, scope);
|
|
626
583
|
frameFound = true;
|
|
627
584
|
break;
|
|
@@ -630,6 +587,7 @@ class StableBrowser {
|
|
|
630
587
|
for (let i = 0; i < selectors.frameLocators.length; i++) {
|
|
631
588
|
let frameLocator = selectors.frameLocators[i];
|
|
632
589
|
if (frameLocator.css) {
|
|
590
|
+
fLocator = frameLocator.css;
|
|
633
591
|
scope = scope.frameLocator(frameLocator.css);
|
|
634
592
|
frameFound = true;
|
|
635
593
|
break;
|
|
@@ -637,16 +595,25 @@ class StableBrowser {
|
|
|
637
595
|
}
|
|
638
596
|
}
|
|
639
597
|
if (!frameFound && selectors.iframe_src) {
|
|
598
|
+
fLocator = selectors.iframe_src;
|
|
640
599
|
scope = this.page.frame({ url: selectors.iframe_src });
|
|
641
600
|
}
|
|
642
601
|
if (!scope) {
|
|
643
|
-
info
|
|
644
|
-
|
|
602
|
+
if (info && info.locatorLog) {
|
|
603
|
+
info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "NOT_FOUND");
|
|
604
|
+
}
|
|
605
|
+
//info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
|
|
606
|
+
if (Date.now() - startTime > timeout) {
|
|
607
|
+
info.failCause.iframeNotFound = true;
|
|
608
|
+
info.failCause.lastError = "unable to locate iframe " + selectors.iframe_src;
|
|
645
609
|
throw new Error("unable to locate iframe " + selectors.iframe_src);
|
|
646
610
|
}
|
|
647
611
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
648
612
|
}
|
|
649
613
|
else {
|
|
614
|
+
if (info && info.locatorLog) {
|
|
615
|
+
info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "FOUND");
|
|
616
|
+
}
|
|
650
617
|
break;
|
|
651
618
|
}
|
|
652
619
|
}
|
|
@@ -656,20 +623,27 @@ class StableBrowser {
|
|
|
656
623
|
}
|
|
657
624
|
return scope;
|
|
658
625
|
}
|
|
659
|
-
async _getDocumentBody(selectors, timeout = 30000) {
|
|
660
|
-
let scope = await this._findFrameScope(selectors, timeout);
|
|
626
|
+
async _getDocumentBody(selectors, timeout = 30000, info) {
|
|
627
|
+
let scope = await this._findFrameScope(selectors, timeout, info);
|
|
661
628
|
return scope.evaluate(() => {
|
|
662
629
|
var bodyContent = document.body.innerHTML;
|
|
663
630
|
return bodyContent;
|
|
664
631
|
});
|
|
665
632
|
}
|
|
666
633
|
async _locate_internal(selectors, info, _params, timeout = 30000) {
|
|
634
|
+
if (!info) {
|
|
635
|
+
info = {};
|
|
636
|
+
info.failCause = {};
|
|
637
|
+
info.log = "";
|
|
638
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
639
|
+
}
|
|
667
640
|
let highPriorityTimeout = 5000;
|
|
668
641
|
let visibleOnlyTimeout = 6000;
|
|
669
|
-
let startTime =
|
|
642
|
+
let startTime = Date.now();
|
|
670
643
|
let locatorsCount = 0;
|
|
644
|
+
let lazy_scroll = false;
|
|
671
645
|
//let arrayMode = Array.isArray(selectors);
|
|
672
|
-
let scope = await this._findFrameScope(selectors, timeout);
|
|
646
|
+
let scope = await this._findFrameScope(selectors, timeout, info);
|
|
673
647
|
let selectorsLocators = null;
|
|
674
648
|
selectorsLocators = selectors.locators;
|
|
675
649
|
// group selectors by priority
|
|
@@ -756,21 +730,33 @@ class StableBrowser {
|
|
|
756
730
|
return maxCountElement.locator;
|
|
757
731
|
}
|
|
758
732
|
}
|
|
759
|
-
if (
|
|
733
|
+
if (Date.now() - startTime > timeout) {
|
|
760
734
|
break;
|
|
761
735
|
}
|
|
762
|
-
if (
|
|
736
|
+
if (Date.now() - startTime > highPriorityTimeout) {
|
|
763
737
|
info.log += "high priority timeout, will try all elements" + "\n";
|
|
764
738
|
highPriorityOnly = false;
|
|
739
|
+
if (this.configuration && this.configuration.load_all_lazy === true && !lazy_scroll) {
|
|
740
|
+
lazy_scroll = true;
|
|
741
|
+
await scrollPageToLoadLazyElements(this.page);
|
|
742
|
+
}
|
|
765
743
|
}
|
|
766
|
-
if (
|
|
744
|
+
if (Date.now() - startTime > visibleOnlyTimeout) {
|
|
767
745
|
info.log += "visible only timeout, will try all elements" + "\n";
|
|
768
746
|
visibleOnly = false;
|
|
769
747
|
}
|
|
770
748
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
771
749
|
}
|
|
772
750
|
this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
|
|
773
|
-
info.
|
|
751
|
+
// if (info.locatorLog) {
|
|
752
|
+
// const lines = info.locatorLog.toString().split("\n");
|
|
753
|
+
// for (let line of lines) {
|
|
754
|
+
// this.logger.debug(line);
|
|
755
|
+
// }
|
|
756
|
+
// }
|
|
757
|
+
//info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
|
|
758
|
+
info.failCause.locatorNotFound = true;
|
|
759
|
+
info.failCause.lastError = "failed to locate unique element";
|
|
774
760
|
throw new Error("failed to locate first element no elements found, " + info.log);
|
|
775
761
|
}
|
|
776
762
|
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
|
|
@@ -784,8 +770,9 @@ class StableBrowser {
|
|
|
784
770
|
await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly);
|
|
785
771
|
}
|
|
786
772
|
catch (e) {
|
|
787
|
-
this
|
|
788
|
-
this.logger.debug(
|
|
773
|
+
// this call can fail it the browser is navigating
|
|
774
|
+
// this.logger.debug("unable to use locator " + JSON.stringify(locatorsGroup[i]));
|
|
775
|
+
// this.logger.debug(e);
|
|
789
776
|
foundLocators = [];
|
|
790
777
|
try {
|
|
791
778
|
await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly);
|
|
@@ -802,10 +789,29 @@ class StableBrowser {
|
|
|
802
789
|
});
|
|
803
790
|
result.locatorIndex = i;
|
|
804
791
|
}
|
|
792
|
+
if (foundLocators.length > 1) {
|
|
793
|
+
info.failCause.foundMultiple = true;
|
|
794
|
+
if (info.locatorLog) {
|
|
795
|
+
info.locatorLog.setLocatorSearchStatus(locatorsGroup[i], "FOUND_NOT_UNIQUE");
|
|
796
|
+
}
|
|
797
|
+
}
|
|
805
798
|
}
|
|
806
799
|
return result;
|
|
807
800
|
}
|
|
808
801
|
async simpleClick(elementDescription, _params, options = {}, world = null) {
|
|
802
|
+
const state = {
|
|
803
|
+
locate: false,
|
|
804
|
+
scroll: false,
|
|
805
|
+
highlight: false,
|
|
806
|
+
_params,
|
|
807
|
+
options,
|
|
808
|
+
world,
|
|
809
|
+
type: Types.CLICK,
|
|
810
|
+
text: "Click element",
|
|
811
|
+
operation: "simpleClick",
|
|
812
|
+
log: "***** click on " + elementDescription + " *****\n",
|
|
813
|
+
};
|
|
814
|
+
_preCommand(state, this);
|
|
809
815
|
const startTime = Date.now();
|
|
810
816
|
let timeout = 30000;
|
|
811
817
|
if (options && options.timeout) {
|
|
@@ -829,13 +835,32 @@ class StableBrowser {
|
|
|
829
835
|
}
|
|
830
836
|
catch (e) {
|
|
831
837
|
if (performance.now() - startTime > timeout) {
|
|
832
|
-
throw e;
|
|
838
|
+
// throw e;
|
|
839
|
+
try {
|
|
840
|
+
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
841
|
+
}
|
|
842
|
+
finally {
|
|
843
|
+
_commandFinally(state, this);
|
|
844
|
+
}
|
|
833
845
|
}
|
|
834
846
|
}
|
|
835
847
|
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
836
848
|
}
|
|
837
849
|
}
|
|
838
850
|
async simpleClickType(elementDescription, value, _params, options = {}, world = null) {
|
|
851
|
+
const state = {
|
|
852
|
+
locate: false,
|
|
853
|
+
scroll: false,
|
|
854
|
+
highlight: false,
|
|
855
|
+
_params,
|
|
856
|
+
options,
|
|
857
|
+
world,
|
|
858
|
+
type: Types.FILL,
|
|
859
|
+
text: "Fill element",
|
|
860
|
+
operation: "simpleClickType",
|
|
861
|
+
log: "***** click type on " + elementDescription + " *****\n",
|
|
862
|
+
};
|
|
863
|
+
_preCommand(state, this);
|
|
839
864
|
const startTime = Date.now();
|
|
840
865
|
let timeout = 30000;
|
|
841
866
|
if (options && options.timeout) {
|
|
@@ -859,7 +884,13 @@ class StableBrowser {
|
|
|
859
884
|
}
|
|
860
885
|
catch (e) {
|
|
861
886
|
if (performance.now() - startTime > timeout) {
|
|
862
|
-
throw e;
|
|
887
|
+
// throw e;
|
|
888
|
+
try {
|
|
889
|
+
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
890
|
+
}
|
|
891
|
+
finally {
|
|
892
|
+
_commandFinally(state, this);
|
|
893
|
+
}
|
|
863
894
|
}
|
|
864
895
|
}
|
|
865
896
|
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
@@ -883,13 +914,13 @@ class StableBrowser {
|
|
|
883
914
|
}
|
|
884
915
|
try {
|
|
885
916
|
await state.element.click();
|
|
886
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
917
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
887
918
|
}
|
|
888
919
|
catch (e) {
|
|
889
920
|
// await this.closeUnexpectedPopups();
|
|
890
921
|
state.element = await this._locate(selectors, state.info, _params);
|
|
891
922
|
await state.element.dispatchEvent("click");
|
|
892
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
923
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
893
924
|
}
|
|
894
925
|
await this.waitForPageLoad();
|
|
895
926
|
return state.info;
|
|
@@ -968,7 +999,6 @@ class StableBrowser {
|
|
|
968
999
|
await state.element.hover({ timeout: 10000 });
|
|
969
1000
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
970
1001
|
}
|
|
971
|
-
await _screenshot(state, this);
|
|
972
1002
|
await this.waitForPageLoad();
|
|
973
1003
|
return state.info;
|
|
974
1004
|
}
|
|
@@ -1096,33 +1126,30 @@ class StableBrowser {
|
|
|
1096
1126
|
}
|
|
1097
1127
|
}
|
|
1098
1128
|
async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1129
|
+
const state = {
|
|
1130
|
+
selectors,
|
|
1131
|
+
_params,
|
|
1132
|
+
value: await this._replaceWithLocalData(value, this),
|
|
1133
|
+
options,
|
|
1134
|
+
world,
|
|
1135
|
+
type: Types.SET_DATE_TIME,
|
|
1136
|
+
text: `Set date time value: ${value}`,
|
|
1137
|
+
operation: "setDateTime",
|
|
1138
|
+
log: "***** set date time value " + selectors.element_name + " *****\n",
|
|
1139
|
+
throwError: false,
|
|
1140
|
+
};
|
|
1109
1141
|
try {
|
|
1110
|
-
|
|
1111
|
-
let element = await this._locate(selectors, info, _params);
|
|
1112
|
-
//insert red border around the element
|
|
1113
|
-
await this.scrollIfNeeded(element, info);
|
|
1114
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1115
|
-
await this._highlightElements(element);
|
|
1142
|
+
await _preCommand(state, this);
|
|
1116
1143
|
try {
|
|
1117
|
-
await element.click();
|
|
1144
|
+
await state.element.click();
|
|
1118
1145
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1119
1146
|
if (format) {
|
|
1120
|
-
value = dayjs(value).format(format);
|
|
1121
|
-
await element.fill(value);
|
|
1147
|
+
state.value = dayjs(state.value).format(format);
|
|
1148
|
+
await state.element.fill(state.value);
|
|
1122
1149
|
}
|
|
1123
1150
|
else {
|
|
1124
|
-
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
1125
|
-
await element.evaluateHandle((el, dateTimeValue) => {
|
|
1151
|
+
const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
|
|
1152
|
+
await state.element.evaluateHandle((el, dateTimeValue) => {
|
|
1126
1153
|
el.value = ""; // clear input
|
|
1127
1154
|
el.value = dateTimeValue;
|
|
1128
1155
|
}, dateTimeValue);
|
|
@@ -1135,20 +1162,19 @@ class StableBrowser {
|
|
|
1135
1162
|
}
|
|
1136
1163
|
catch (err) {
|
|
1137
1164
|
//await this.closeUnexpectedPopups();
|
|
1138
|
-
this.logger.error("setting date time input failed " + JSON.stringify(info));
|
|
1165
|
+
this.logger.error("setting date time input failed " + JSON.stringify(state.info));
|
|
1139
1166
|
this.logger.info("Trying again");
|
|
1140
|
-
|
|
1141
|
-
info.
|
|
1142
|
-
Object.assign(err, { info: info });
|
|
1167
|
+
await _screenshot(state, this);
|
|
1168
|
+
Object.assign(err, { info: state.info });
|
|
1143
1169
|
await element.click();
|
|
1144
1170
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1145
1171
|
if (format) {
|
|
1146
|
-
value = dayjs(value).format(format);
|
|
1147
|
-
await element.fill(value);
|
|
1172
|
+
state.value = dayjs(state.value).format(format);
|
|
1173
|
+
await state.element.fill(state.value);
|
|
1148
1174
|
}
|
|
1149
1175
|
else {
|
|
1150
|
-
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
1151
|
-
await element.evaluateHandle((el, dateTimeValue) => {
|
|
1176
|
+
const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
|
|
1177
|
+
await state.element.evaluateHandle((el, dateTimeValue) => {
|
|
1152
1178
|
el.value = ""; // clear input
|
|
1153
1179
|
el.value = dateTimeValue;
|
|
1154
1180
|
}, dateTimeValue);
|
|
@@ -1161,31 +1187,10 @@ class StableBrowser {
|
|
|
1161
1187
|
}
|
|
1162
1188
|
}
|
|
1163
1189
|
catch (e) {
|
|
1164
|
-
|
|
1165
|
-
throw e;
|
|
1190
|
+
await _commandError(state, e, this);
|
|
1166
1191
|
}
|
|
1167
1192
|
finally {
|
|
1168
|
-
|
|
1169
|
-
this._reportToWorld(world, {
|
|
1170
|
-
element_name: selectors.element_name,
|
|
1171
|
-
type: Types.SET_DATE_TIME,
|
|
1172
|
-
screenshotId,
|
|
1173
|
-
value: value,
|
|
1174
|
-
text: `setDateTime input with value: ${value}`,
|
|
1175
|
-
result: error
|
|
1176
|
-
? {
|
|
1177
|
-
status: "FAILED",
|
|
1178
|
-
startTime,
|
|
1179
|
-
endTime,
|
|
1180
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1181
|
-
}
|
|
1182
|
-
: {
|
|
1183
|
-
status: "PASSED",
|
|
1184
|
-
startTime,
|
|
1185
|
-
endTime,
|
|
1186
|
-
},
|
|
1187
|
-
info: info,
|
|
1188
|
-
});
|
|
1193
|
+
_commandFinally(state, this);
|
|
1189
1194
|
}
|
|
1190
1195
|
}
|
|
1191
1196
|
async clickType(selectors, _value, enter = false, _params = null, options = {}, world = null) {
|
|
@@ -1322,6 +1327,7 @@ class StableBrowser {
|
|
|
1322
1327
|
let screenshotPath = null;
|
|
1323
1328
|
if (!info.log) {
|
|
1324
1329
|
info.log = "";
|
|
1330
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
1325
1331
|
}
|
|
1326
1332
|
info.operation = "getText";
|
|
1327
1333
|
info.selectors = selectors;
|
|
@@ -1660,11 +1666,9 @@ class StableBrowser {
|
|
|
1660
1666
|
if (!fs.existsSync(world.screenshotPath)) {
|
|
1661
1667
|
fs.mkdirSync(world.screenshotPath, { recursive: true });
|
|
1662
1668
|
}
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
}
|
|
1667
|
-
const screenshotPath = path.join(world.screenshotPath, nextIndex + ".png");
|
|
1669
|
+
// to make sure the path doesn't start with -
|
|
1670
|
+
const uuidStr = "id_" + randomUUID();
|
|
1671
|
+
const screenshotPath = path.join(world.screenshotPath, uuidStr + ".png");
|
|
1668
1672
|
try {
|
|
1669
1673
|
await this.takeScreenshot(screenshotPath);
|
|
1670
1674
|
// let buffer = await this.page.screenshot({ timeout: 4000 });
|
|
@@ -1678,7 +1682,7 @@ class StableBrowser {
|
|
|
1678
1682
|
catch (e) {
|
|
1679
1683
|
this.logger.info("unable to take screenshot, ignored");
|
|
1680
1684
|
}
|
|
1681
|
-
result.screenshotId =
|
|
1685
|
+
result.screenshotId = uuidStr;
|
|
1682
1686
|
result.screenshotPath = screenshotPath;
|
|
1683
1687
|
if (info && info.box) {
|
|
1684
1688
|
await drawRectangle(screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
|
|
@@ -1776,74 +1780,101 @@ class StableBrowser {
|
|
|
1776
1780
|
}
|
|
1777
1781
|
}
|
|
1778
1782
|
async extractAttribute(selectors, attribute, variable, _params = null, options = {}, world = null) {
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1783
|
+
const state = {
|
|
1784
|
+
selectors,
|
|
1785
|
+
_params,
|
|
1786
|
+
attribute,
|
|
1787
|
+
variable,
|
|
1788
|
+
options,
|
|
1789
|
+
world,
|
|
1790
|
+
type: Types.EXTRACT,
|
|
1791
|
+
text: `Extract attribute from element`,
|
|
1792
|
+
operation: "extractAttribute",
|
|
1793
|
+
log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1794
|
+
};
|
|
1784
1795
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1785
|
-
const info = {};
|
|
1786
|
-
info.log = "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n";
|
|
1787
|
-
info.operation = "extract";
|
|
1788
|
-
info.selectors = selectors;
|
|
1789
1796
|
try {
|
|
1790
|
-
|
|
1791
|
-
await this._highlightElements(element);
|
|
1792
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1797
|
+
await _preCommand(state, this);
|
|
1793
1798
|
switch (attribute) {
|
|
1794
1799
|
case "inner_text":
|
|
1795
|
-
|
|
1800
|
+
state.value = await state.element.innerText();
|
|
1796
1801
|
break;
|
|
1797
1802
|
case "href":
|
|
1798
|
-
|
|
1803
|
+
state.value = await state.element.getAttribute("href");
|
|
1804
|
+
break;
|
|
1805
|
+
case "value":
|
|
1806
|
+
state.value = await state.element.inputValue();
|
|
1807
|
+
break;
|
|
1808
|
+
default:
|
|
1809
|
+
state.value = await state.element.getAttribute(attribute);
|
|
1810
|
+
break;
|
|
1811
|
+
}
|
|
1812
|
+
state.info.value = state.value;
|
|
1813
|
+
this.setTestData({ [variable]: state.value }, world);
|
|
1814
|
+
this.logger.info("set test data: " + variable + "=" + state.value);
|
|
1815
|
+
return state.info;
|
|
1816
|
+
}
|
|
1817
|
+
catch (e) {
|
|
1818
|
+
await _commandError(state, e, this);
|
|
1819
|
+
}
|
|
1820
|
+
finally {
|
|
1821
|
+
_commandFinally(state, this);
|
|
1822
|
+
}
|
|
1823
|
+
}
|
|
1824
|
+
async verifyAttribute(selectors, attribute, value, _params = null, options = {}, world = null) {
|
|
1825
|
+
const state = {
|
|
1826
|
+
selectors,
|
|
1827
|
+
_params,
|
|
1828
|
+
attribute,
|
|
1829
|
+
value,
|
|
1830
|
+
options,
|
|
1831
|
+
world,
|
|
1832
|
+
type: Types.VERIFY_ATTRIBUTE,
|
|
1833
|
+
text: `Verify element attribute`,
|
|
1834
|
+
operation: "verifyAttribute",
|
|
1835
|
+
log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1836
|
+
};
|
|
1837
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1838
|
+
let val;
|
|
1839
|
+
try {
|
|
1840
|
+
await _preCommand(state, this);
|
|
1841
|
+
switch (attribute) {
|
|
1842
|
+
case "innerText":
|
|
1843
|
+
val = String(await state.element.innerText());
|
|
1799
1844
|
break;
|
|
1800
1845
|
case "value":
|
|
1801
|
-
|
|
1846
|
+
val = String(await state.element.inputValue());
|
|
1847
|
+
break;
|
|
1848
|
+
case "checked":
|
|
1849
|
+
val = String(await state.element.isChecked());
|
|
1850
|
+
break;
|
|
1851
|
+
case "disabled":
|
|
1852
|
+
val = String(await state.element.isDisabled());
|
|
1802
1853
|
break;
|
|
1803
1854
|
default:
|
|
1804
|
-
|
|
1855
|
+
val = String(await state.element.getAttribute(attribute));
|
|
1805
1856
|
break;
|
|
1806
1857
|
}
|
|
1807
|
-
|
|
1808
|
-
if (
|
|
1809
|
-
|
|
1858
|
+
let regex;
|
|
1859
|
+
if (value.startsWith("/") && value.endsWith("/")) {
|
|
1860
|
+
const patternBody = value.slice(1, -1);
|
|
1861
|
+
regex = new RegExp(patternBody, "g");
|
|
1810
1862
|
}
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1863
|
+
else {
|
|
1864
|
+
const escapedPattern = value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1865
|
+
regex = new RegExp(escapedPattern, "g");
|
|
1866
|
+
}
|
|
1867
|
+
if (!val.match(regex)) {
|
|
1868
|
+
throw new Error(`The ${attribute} attribute has a value of "${val}", but the expected value is "${value}"`);
|
|
1869
|
+
}
|
|
1870
|
+
state.info.value = val;
|
|
1871
|
+
return state.info;
|
|
1814
1872
|
}
|
|
1815
1873
|
catch (e) {
|
|
1816
|
-
|
|
1817
|
-
this.logger.error("extract failed " + info.log);
|
|
1818
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1819
|
-
info.screenshotPath = screenshotPath;
|
|
1820
|
-
Object.assign(e, { info: info });
|
|
1821
|
-
error = e;
|
|
1822
|
-
throw e;
|
|
1874
|
+
await _commandError(state, e, this);
|
|
1823
1875
|
}
|
|
1824
1876
|
finally {
|
|
1825
|
-
|
|
1826
|
-
this._reportToWorld(world, {
|
|
1827
|
-
element_name: selectors.element_name,
|
|
1828
|
-
type: Types.EXTRACT_ATTRIBUTE,
|
|
1829
|
-
variable: variable,
|
|
1830
|
-
value: info.value,
|
|
1831
|
-
text: "Extract attribute from element",
|
|
1832
|
-
screenshotId,
|
|
1833
|
-
result: error
|
|
1834
|
-
? {
|
|
1835
|
-
status: "FAILED",
|
|
1836
|
-
startTime,
|
|
1837
|
-
endTime,
|
|
1838
|
-
message: error?.message,
|
|
1839
|
-
}
|
|
1840
|
-
: {
|
|
1841
|
-
status: "PASSED",
|
|
1842
|
-
startTime,
|
|
1843
|
-
endTime,
|
|
1844
|
-
},
|
|
1845
|
-
info: info,
|
|
1846
|
-
});
|
|
1877
|
+
_commandFinally(state, this);
|
|
1847
1878
|
}
|
|
1848
1879
|
}
|
|
1849
1880
|
async extractEmailData(emailAddress, options, world) {
|
|
@@ -1864,7 +1895,7 @@ class StableBrowser {
|
|
|
1864
1895
|
if (options && options.timeout) {
|
|
1865
1896
|
timeout = options.timeout;
|
|
1866
1897
|
}
|
|
1867
|
-
const serviceUrl =
|
|
1898
|
+
const serviceUrl = _getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
|
|
1868
1899
|
const request = {
|
|
1869
1900
|
method: "POST",
|
|
1870
1901
|
url: serviceUrl,
|
|
@@ -1920,7 +1951,8 @@ class StableBrowser {
|
|
|
1920
1951
|
catch (e) {
|
|
1921
1952
|
errorCount++;
|
|
1922
1953
|
if (errorCount > 3) {
|
|
1923
|
-
throw e;
|
|
1954
|
+
// throw e;
|
|
1955
|
+
await _commandError({ text: "extractEmailData", operation: "extractEmailData", emailAddress, info: {} }, e, this);
|
|
1924
1956
|
}
|
|
1925
1957
|
// ignore
|
|
1926
1958
|
}
|
|
@@ -2031,11 +2063,12 @@ class StableBrowser {
|
|
|
2031
2063
|
info.screenshotPath = screenshotPath;
|
|
2032
2064
|
Object.assign(e, { info: info });
|
|
2033
2065
|
error = e;
|
|
2034
|
-
throw e;
|
|
2066
|
+
// throw e;
|
|
2067
|
+
await _commandError({ text: "verifyPagePath", operation: "verifyPagePath", pathPart, info }, e, this);
|
|
2035
2068
|
}
|
|
2036
2069
|
finally {
|
|
2037
2070
|
const endTime = Date.now();
|
|
2038
|
-
|
|
2071
|
+
_reportToWorld(world, {
|
|
2039
2072
|
type: Types.VERIFY_PAGE_PATH,
|
|
2040
2073
|
text: "Verify page path",
|
|
2041
2074
|
screenshotId,
|
|
@@ -2055,54 +2088,64 @@ class StableBrowser {
|
|
|
2055
2088
|
});
|
|
2056
2089
|
}
|
|
2057
2090
|
}
|
|
2091
|
+
async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state) {
|
|
2092
|
+
const frames = this.page.frames();
|
|
2093
|
+
let results = [];
|
|
2094
|
+
for (let i = 0; i < frames.length; i++) {
|
|
2095
|
+
if (dateAlternatives.date) {
|
|
2096
|
+
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2097
|
+
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, true, {});
|
|
2098
|
+
result.frame = frames[i];
|
|
2099
|
+
results.push(result);
|
|
2100
|
+
}
|
|
2101
|
+
}
|
|
2102
|
+
else if (numberAlternatives.number) {
|
|
2103
|
+
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2104
|
+
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, true, {});
|
|
2105
|
+
result.frame = frames[i];
|
|
2106
|
+
results.push(result);
|
|
2107
|
+
}
|
|
2108
|
+
}
|
|
2109
|
+
else {
|
|
2110
|
+
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, true, {});
|
|
2111
|
+
result.frame = frames[i];
|
|
2112
|
+
results.push(result);
|
|
2113
|
+
}
|
|
2114
|
+
}
|
|
2115
|
+
state.info.results = results;
|
|
2116
|
+
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2117
|
+
return resultWithElementsFound;
|
|
2118
|
+
}
|
|
2058
2119
|
async verifyTextExistInPage(text, options = {}, world = null) {
|
|
2059
2120
|
text = unEscapeString(text);
|
|
2060
|
-
const
|
|
2121
|
+
const state = {
|
|
2122
|
+
text_search: text,
|
|
2123
|
+
options,
|
|
2124
|
+
world,
|
|
2125
|
+
locate: false,
|
|
2126
|
+
scroll: false,
|
|
2127
|
+
highlight: false,
|
|
2128
|
+
type: Types.VERIFY_PAGE_CONTAINS_TEXT,
|
|
2129
|
+
text: `Verify text exists in page`,
|
|
2130
|
+
operation: "verifyTextExistInPage",
|
|
2131
|
+
log: "***** verify text " + text + " exists in page *****\n",
|
|
2132
|
+
};
|
|
2061
2133
|
const timeout = this._getLoadTimeout(options);
|
|
2062
|
-
let error = null;
|
|
2063
|
-
let screenshotId = null;
|
|
2064
|
-
let screenshotPath = null;
|
|
2065
2134
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2066
|
-
const info = {};
|
|
2067
|
-
info.log = "***** verify text " + text + " exists in page *****\n";
|
|
2068
|
-
info.operation = "verifyTextExistInPage";
|
|
2069
2135
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2070
2136
|
if (newValue !== text) {
|
|
2071
2137
|
this.logger.info(text + "=" + newValue);
|
|
2072
2138
|
text = newValue;
|
|
2073
2139
|
}
|
|
2074
|
-
info.text = text;
|
|
2075
2140
|
let dateAlternatives = findDateAlternatives(text);
|
|
2076
2141
|
let numberAlternatives = findNumberAlternatives(text);
|
|
2077
2142
|
try {
|
|
2143
|
+
await _preCommand(state, this);
|
|
2144
|
+
state.info.text = text;
|
|
2078
2145
|
while (true) {
|
|
2079
|
-
const
|
|
2080
|
-
let results = [];
|
|
2081
|
-
for (let i = 0; i < frames.length; i++) {
|
|
2082
|
-
if (dateAlternatives.date) {
|
|
2083
|
-
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2084
|
-
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*", true, true, {});
|
|
2085
|
-
result.frame = frames[i];
|
|
2086
|
-
results.push(result);
|
|
2087
|
-
}
|
|
2088
|
-
}
|
|
2089
|
-
else if (numberAlternatives.number) {
|
|
2090
|
-
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2091
|
-
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*", true, true, {});
|
|
2092
|
-
result.frame = frames[i];
|
|
2093
|
-
results.push(result);
|
|
2094
|
-
}
|
|
2095
|
-
}
|
|
2096
|
-
else {
|
|
2097
|
-
const result = await this._locateElementByText(frames[i], text, "*", true, true, {});
|
|
2098
|
-
result.frame = frames[i];
|
|
2099
|
-
results.push(result);
|
|
2100
|
-
}
|
|
2101
|
-
}
|
|
2102
|
-
info.results = results;
|
|
2103
|
-
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2146
|
+
const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2104
2147
|
if (resultWithElementsFound.length === 0) {
|
|
2105
|
-
if (Date.now() - startTime > timeout) {
|
|
2148
|
+
if (Date.now() - state.startTime > timeout) {
|
|
2106
2149
|
throw new Error(`Text ${text} not found in page`);
|
|
2107
2150
|
}
|
|
2108
2151
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -2114,55 +2157,163 @@ class StableBrowser {
|
|
|
2114
2157
|
await this._highlightElements(frame, dataAttribute);
|
|
2115
2158
|
const element = await frame.$(dataAttribute);
|
|
2116
2159
|
if (element) {
|
|
2117
|
-
await this.scrollIfNeeded(element, info);
|
|
2160
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2118
2161
|
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2119
2162
|
}
|
|
2120
2163
|
}
|
|
2121
|
-
|
|
2122
|
-
return info;
|
|
2164
|
+
await _screenshot(state, this);
|
|
2165
|
+
return state.info;
|
|
2123
2166
|
}
|
|
2124
2167
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2125
2168
|
}
|
|
2126
2169
|
catch (e) {
|
|
2127
|
-
|
|
2128
|
-
this.logger.error("verify text exist in page failed " + info.log);
|
|
2129
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2130
|
-
info.screenshotPath = screenshotPath;
|
|
2131
|
-
Object.assign(e, { info: info });
|
|
2132
|
-
error = e;
|
|
2133
|
-
throw e;
|
|
2170
|
+
await _commandError(state, e, this);
|
|
2134
2171
|
}
|
|
2135
2172
|
finally {
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2173
|
+
_commandFinally(state, this);
|
|
2174
|
+
}
|
|
2175
|
+
}
|
|
2176
|
+
async waitForTextToDisappear(text, options = {}, world = null) {
|
|
2177
|
+
text = unEscapeString(text);
|
|
2178
|
+
const state = {
|
|
2179
|
+
text_search: text,
|
|
2180
|
+
options,
|
|
2181
|
+
world,
|
|
2182
|
+
locate: false,
|
|
2183
|
+
scroll: false,
|
|
2184
|
+
highlight: false,
|
|
2185
|
+
type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
|
|
2186
|
+
text: `Verify text does not exist in page`,
|
|
2187
|
+
operation: "verifyTextNotExistInPage",
|
|
2188
|
+
log: "***** verify text " + text + " does not exist in page *****\n",
|
|
2189
|
+
};
|
|
2190
|
+
const timeout = this._getLoadTimeout(options);
|
|
2191
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2192
|
+
const newValue = await this._replaceWithLocalData(text, world);
|
|
2193
|
+
if (newValue !== text) {
|
|
2194
|
+
this.logger.info(text + "=" + newValue);
|
|
2195
|
+
text = newValue;
|
|
2196
|
+
}
|
|
2197
|
+
let dateAlternatives = findDateAlternatives(text);
|
|
2198
|
+
let numberAlternatives = findNumberAlternatives(text);
|
|
2199
|
+
try {
|
|
2200
|
+
await _preCommand(state, this);
|
|
2201
|
+
state.info.text = text;
|
|
2202
|
+
while (true) {
|
|
2203
|
+
const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2204
|
+
if (resultWithElementsFound.length === 0) {
|
|
2205
|
+
await _screenshot(state, this);
|
|
2206
|
+
return state.info;
|
|
2207
|
+
}
|
|
2208
|
+
if (Date.now() - state.startTime > timeout) {
|
|
2209
|
+
throw new Error(`Text ${text} found in page`);
|
|
2210
|
+
}
|
|
2211
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
catch (e) {
|
|
2215
|
+
await _commandError(state, e, this);
|
|
2216
|
+
}
|
|
2217
|
+
finally {
|
|
2218
|
+
_commandFinally(state, this);
|
|
2155
2219
|
}
|
|
2156
2220
|
}
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2221
|
+
async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
|
|
2222
|
+
textAnchor = unEscapeString(textAnchor);
|
|
2223
|
+
textToVerify = unEscapeString(textToVerify);
|
|
2224
|
+
const state = {
|
|
2225
|
+
text_search: textToVerify,
|
|
2226
|
+
options,
|
|
2227
|
+
world,
|
|
2228
|
+
locate: false,
|
|
2229
|
+
scroll: false,
|
|
2230
|
+
highlight: false,
|
|
2231
|
+
type: Types.VERIFY_TEXT_WITH_RELATION,
|
|
2232
|
+
text: `Verify text with relation to another text`,
|
|
2233
|
+
operation: "verify_text_with_relation",
|
|
2234
|
+
log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
|
|
2235
|
+
};
|
|
2236
|
+
const timeout = this._getLoadTimeout(options);
|
|
2237
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2238
|
+
let newValue = await this._replaceWithLocalData(textAnchor, world);
|
|
2239
|
+
if (newValue !== textAnchor) {
|
|
2240
|
+
this.logger.info(textAnchor + "=" + newValue);
|
|
2241
|
+
textAnchor = newValue;
|
|
2242
|
+
}
|
|
2243
|
+
newValue = await this._replaceWithLocalData(textToVerify, world);
|
|
2244
|
+
if (newValue !== textToVerify) {
|
|
2245
|
+
this.logger.info(textToVerify + "=" + newValue);
|
|
2246
|
+
textToVerify = newValue;
|
|
2247
|
+
}
|
|
2248
|
+
let dateAlternatives = findDateAlternatives(textToVerify);
|
|
2249
|
+
let numberAlternatives = findNumberAlternatives(textToVerify);
|
|
2250
|
+
let foundAncore = false;
|
|
2251
|
+
try {
|
|
2252
|
+
await _preCommand(state, this);
|
|
2253
|
+
state.info.text = textToVerify;
|
|
2254
|
+
while (true) {
|
|
2255
|
+
const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, textAnchor, state);
|
|
2256
|
+
if (resultWithElementsFound.length === 0) {
|
|
2257
|
+
if (Date.now() - state.startTime > timeout) {
|
|
2258
|
+
throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
|
|
2259
|
+
}
|
|
2260
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2261
|
+
continue;
|
|
2262
|
+
}
|
|
2263
|
+
for (let i = 0; i < resultWithElementsFound.length; i++) {
|
|
2264
|
+
foundAncore = true;
|
|
2265
|
+
const result = resultWithElementsFound[i];
|
|
2266
|
+
const token = result.randomToken;
|
|
2267
|
+
const frame = result.frame;
|
|
2268
|
+
const css = `[data-blinq-id="blinq-id-${token}"]`;
|
|
2269
|
+
const findResult = await frame.evaluate(([css, climb, textToVerify, token]) => {
|
|
2270
|
+
const elements = Array.from(document.querySelectorAll(css));
|
|
2271
|
+
for (let i = 0; i < elements.length; i++) {
|
|
2272
|
+
const element = elements[i];
|
|
2273
|
+
let climbParent = element;
|
|
2274
|
+
for (let j = 0; j < climb; j++) {
|
|
2275
|
+
climbParent = climbParent.parentElement;
|
|
2276
|
+
if (!climbParent) {
|
|
2277
|
+
break;
|
|
2278
|
+
}
|
|
2279
|
+
}
|
|
2280
|
+
if (!climbParent) {
|
|
2281
|
+
continue;
|
|
2282
|
+
}
|
|
2283
|
+
const foundElements = window.findMatchingElements(textToVerify, {}, climbParent);
|
|
2284
|
+
if (foundElements.length > 0) {
|
|
2285
|
+
// set the container element attribute
|
|
2286
|
+
element.setAttribute("data-blinq-id", `blinq-id-${token}-anchor`);
|
|
2287
|
+
climbParent.setAttribute("data-blinq-id", `blinq-id-${token}-container`);
|
|
2288
|
+
foundElements[0].setAttribute("data-blinq-id", `blinq-id-${token}-verify`);
|
|
2289
|
+
return { found: true };
|
|
2290
|
+
}
|
|
2291
|
+
}
|
|
2292
|
+
return { found: false };
|
|
2293
|
+
}, [css, climb, textToVerify, result.randomToken]);
|
|
2294
|
+
if (findResult.found === true) {
|
|
2295
|
+
const dataAttribute = `[data-blinq-id="blinq-id-${token}-verify"]`;
|
|
2296
|
+
const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
|
|
2297
|
+
await this._highlightElements(frame, dataAttribute);
|
|
2298
|
+
await this._highlightElements(frame, cssAnchor);
|
|
2299
|
+
const element = await frame.$(dataAttribute);
|
|
2300
|
+
if (element) {
|
|
2301
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2302
|
+
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2303
|
+
}
|
|
2304
|
+
await _screenshot(state, this);
|
|
2305
|
+
return state.info;
|
|
2306
|
+
}
|
|
2307
|
+
}
|
|
2308
|
+
}
|
|
2309
|
+
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2310
|
+
}
|
|
2311
|
+
catch (e) {
|
|
2312
|
+
await _commandError(state, e, this);
|
|
2161
2313
|
}
|
|
2162
|
-
|
|
2163
|
-
|
|
2314
|
+
finally {
|
|
2315
|
+
_commandFinally(state, this);
|
|
2164
2316
|
}
|
|
2165
|
-
return serviceUrl;
|
|
2166
2317
|
}
|
|
2167
2318
|
async visualVerification(text, options = {}, world = null) {
|
|
2168
2319
|
const startTime = Date.now();
|
|
@@ -2178,7 +2329,7 @@ class StableBrowser {
|
|
|
2178
2329
|
throw new Error("TOKEN is not set");
|
|
2179
2330
|
}
|
|
2180
2331
|
try {
|
|
2181
|
-
let serviceUrl =
|
|
2332
|
+
let serviceUrl = _getServerUrl();
|
|
2182
2333
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2183
2334
|
info.screenshotPath = screenshotPath;
|
|
2184
2335
|
const screenshot = await this.takeScreenshot();
|
|
@@ -2214,11 +2365,12 @@ class StableBrowser {
|
|
|
2214
2365
|
info.screenshotPath = screenshotPath;
|
|
2215
2366
|
Object.assign(e, { info: info });
|
|
2216
2367
|
error = e;
|
|
2217
|
-
throw e;
|
|
2368
|
+
// throw e;
|
|
2369
|
+
await _commandError({ text: "visualVerification", operation: "visualVerification", text, info }, e, this);
|
|
2218
2370
|
}
|
|
2219
2371
|
finally {
|
|
2220
2372
|
const endTime = Date.now();
|
|
2221
|
-
|
|
2373
|
+
_reportToWorld(world, {
|
|
2222
2374
|
type: Types.VERIFY_VISUAL,
|
|
2223
2375
|
text: "Visual verification",
|
|
2224
2376
|
screenshotId,
|
|
@@ -2266,6 +2418,7 @@ class StableBrowser {
|
|
|
2266
2418
|
let screenshotPath = null;
|
|
2267
2419
|
const info = {};
|
|
2268
2420
|
info.log = "";
|
|
2421
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
2269
2422
|
info.operation = "getTableData";
|
|
2270
2423
|
info.selectors = selectors;
|
|
2271
2424
|
try {
|
|
@@ -2281,11 +2434,12 @@ class StableBrowser {
|
|
|
2281
2434
|
info.screenshotPath = screenshotPath;
|
|
2282
2435
|
Object.assign(e, { info: info });
|
|
2283
2436
|
error = e;
|
|
2284
|
-
throw e;
|
|
2437
|
+
// throw e;
|
|
2438
|
+
await _commandError({ text: "getTableData", operation: "getTableData", selectors, info }, e, this);
|
|
2285
2439
|
}
|
|
2286
2440
|
finally {
|
|
2287
2441
|
const endTime = Date.now();
|
|
2288
|
-
|
|
2442
|
+
_reportToWorld(world, {
|
|
2289
2443
|
element_name: selectors.element_name,
|
|
2290
2444
|
type: Types.GET_TABLE_DATA,
|
|
2291
2445
|
text: "Get table data",
|
|
@@ -2340,7 +2494,7 @@ class StableBrowser {
|
|
|
2340
2494
|
info.operation = "analyzeTable";
|
|
2341
2495
|
info.selectors = selectors;
|
|
2342
2496
|
info.query = query;
|
|
2343
|
-
query =
|
|
2497
|
+
query = _fixUsingParams(query, _params);
|
|
2344
2498
|
info.query_fixed = query;
|
|
2345
2499
|
info.operator = operator;
|
|
2346
2500
|
info.value = value;
|
|
@@ -2446,11 +2600,12 @@ class StableBrowser {
|
|
|
2446
2600
|
info.screenshotPath = screenshotPath;
|
|
2447
2601
|
Object.assign(e, { info: info });
|
|
2448
2602
|
error = e;
|
|
2449
|
-
throw e;
|
|
2603
|
+
// throw e;
|
|
2604
|
+
await _commandError({ text: "analyzeTable", operation: "analyzeTable", selectors, query, operator, value }, e, this);
|
|
2450
2605
|
}
|
|
2451
2606
|
finally {
|
|
2452
2607
|
const endTime = Date.now();
|
|
2453
|
-
|
|
2608
|
+
_reportToWorld(world, {
|
|
2454
2609
|
element_name: selectors.element_name,
|
|
2455
2610
|
type: Types.ANALYZE_TABLE,
|
|
2456
2611
|
text: "Analyze table",
|
|
@@ -2523,7 +2678,7 @@ class StableBrowser {
|
|
|
2523
2678
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2524
2679
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world));
|
|
2525
2680
|
const endTime = Date.now();
|
|
2526
|
-
|
|
2681
|
+
_reportToWorld(world, {
|
|
2527
2682
|
type: Types.GET_PAGE_STATUS,
|
|
2528
2683
|
text: "Wait for page load",
|
|
2529
2684
|
screenshotId,
|
|
@@ -2543,41 +2698,35 @@ class StableBrowser {
|
|
|
2543
2698
|
}
|
|
2544
2699
|
}
|
|
2545
2700
|
async closePage(options = {}, world = null) {
|
|
2546
|
-
const
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2701
|
+
const state = {
|
|
2702
|
+
options,
|
|
2703
|
+
world,
|
|
2704
|
+
locate: false,
|
|
2705
|
+
scroll: false,
|
|
2706
|
+
highlight: false,
|
|
2707
|
+
type: Types.CLOSE_PAGE,
|
|
2708
|
+
text: `Close page`,
|
|
2709
|
+
operation: "closePage",
|
|
2710
|
+
log: "***** close page *****\n",
|
|
2711
|
+
throwError: false,
|
|
2712
|
+
};
|
|
2551
2713
|
try {
|
|
2714
|
+
await _preCommand(state, this);
|
|
2552
2715
|
await this.page.close();
|
|
2553
2716
|
}
|
|
2554
2717
|
catch (e) {
|
|
2555
2718
|
console.log(".");
|
|
2719
|
+
await _commandError(state, e, this);
|
|
2556
2720
|
}
|
|
2557
2721
|
finally {
|
|
2558
|
-
|
|
2559
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world));
|
|
2560
|
-
const endTime = Date.now();
|
|
2561
|
-
this._reportToWorld(world, {
|
|
2562
|
-
type: Types.CLOSE_PAGE,
|
|
2563
|
-
text: "close page",
|
|
2564
|
-
screenshotId,
|
|
2565
|
-
result: error
|
|
2566
|
-
? {
|
|
2567
|
-
status: "FAILED",
|
|
2568
|
-
startTime,
|
|
2569
|
-
endTime,
|
|
2570
|
-
message: error?.message,
|
|
2571
|
-
}
|
|
2572
|
-
: {
|
|
2573
|
-
status: "PASSED",
|
|
2574
|
-
startTime,
|
|
2575
|
-
endTime,
|
|
2576
|
-
},
|
|
2577
|
-
info: info,
|
|
2578
|
-
});
|
|
2722
|
+
_commandFinally(state, this);
|
|
2579
2723
|
}
|
|
2580
2724
|
}
|
|
2725
|
+
saveTestDataAsGlobal(options, world) {
|
|
2726
|
+
const dataFile = this._getDataFile(world);
|
|
2727
|
+
process.env.GLOBAL_TEST_DATA_FILE = dataFile;
|
|
2728
|
+
this.logger.info("Save the scenario test data as global for the following scenarios.");
|
|
2729
|
+
}
|
|
2581
2730
|
async setViewportSize(width, hight, options = {}, world = null) {
|
|
2582
2731
|
const startTime = Date.now();
|
|
2583
2732
|
let error = null;
|
|
@@ -2595,12 +2744,13 @@ class StableBrowser {
|
|
|
2595
2744
|
}
|
|
2596
2745
|
catch (e) {
|
|
2597
2746
|
console.log(".");
|
|
2747
|
+
await _commandError({ text: "setViewportSize", operation: "setViewportSize", width, hight, info }, e, this);
|
|
2598
2748
|
}
|
|
2599
2749
|
finally {
|
|
2600
2750
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2601
2751
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world));
|
|
2602
2752
|
const endTime = Date.now();
|
|
2603
|
-
|
|
2753
|
+
_reportToWorld(world, {
|
|
2604
2754
|
type: Types.SET_VIEWPORT,
|
|
2605
2755
|
text: "set viewport size to " + width + "x" + hight,
|
|
2606
2756
|
screenshotId,
|
|
@@ -2631,12 +2781,13 @@ class StableBrowser {
|
|
|
2631
2781
|
}
|
|
2632
2782
|
catch (e) {
|
|
2633
2783
|
console.log(".");
|
|
2784
|
+
await _commandError({ text: "reloadPage", operation: "reloadPage", info }, e, this);
|
|
2634
2785
|
}
|
|
2635
2786
|
finally {
|
|
2636
2787
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2637
2788
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2638
2789
|
const endTime = Date.now();
|
|
2639
|
-
|
|
2790
|
+
_reportToWorld(world, {
|
|
2640
2791
|
type: Types.GET_PAGE_STATUS,
|
|
2641
2792
|
text: "page relaod",
|
|
2642
2793
|
screenshotId,
|
|
@@ -2672,11 +2823,37 @@ class StableBrowser {
|
|
|
2672
2823
|
console.log("#-#");
|
|
2673
2824
|
}
|
|
2674
2825
|
}
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2826
|
+
async beforeStep(world, step) {
|
|
2827
|
+
this.stepName = step.pickleStep.text;
|
|
2828
|
+
this.logger.info("step: " + this.stepName);
|
|
2829
|
+
if (this.stepIndex === undefined) {
|
|
2830
|
+
this.stepIndex = 0;
|
|
2831
|
+
}
|
|
2832
|
+
else {
|
|
2833
|
+
this.stepIndex++;
|
|
2834
|
+
}
|
|
2835
|
+
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
2836
|
+
if (this.context.browserObject.context) {
|
|
2837
|
+
await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
|
|
2838
|
+
}
|
|
2839
|
+
}
|
|
2840
|
+
if (this.tags === null && step && step.pickle && step.pickle.tags) {
|
|
2841
|
+
this.tags = step.pickle.tags.map((tag) => tag.name);
|
|
2842
|
+
// check if @global_test_data tag is present
|
|
2843
|
+
if (this.tags.includes("@global_test_data")) {
|
|
2844
|
+
this.saveTestDataAsGlobal({}, world);
|
|
2845
|
+
}
|
|
2846
|
+
}
|
|
2847
|
+
}
|
|
2848
|
+
async afterStep(world, step) {
|
|
2849
|
+
this.stepName = null;
|
|
2850
|
+
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
2851
|
+
if (this.context.browserObject.context) {
|
|
2852
|
+
await this.context.browserObject.context.tracing.stopChunk({
|
|
2853
|
+
path: path.join(this.context.browserObject.traceFolder, `trace-${this.stepIndex}.zip`),
|
|
2854
|
+
});
|
|
2855
|
+
}
|
|
2678
2856
|
}
|
|
2679
|
-
world.attach(JSON.stringify(properties), { mediaType: "application/json" });
|
|
2680
2857
|
}
|
|
2681
2858
|
}
|
|
2682
2859
|
function createTimedPromise(promise, label) {
|
|
@@ -2684,156 +2861,5 @@ function createTimedPromise(promise, label) {
|
|
|
2684
2861
|
.then((result) => ({ status: "fulfilled", label, result }))
|
|
2685
2862
|
.catch((error) => Promise.reject({ status: "rejected", label, error }));
|
|
2686
2863
|
}
|
|
2687
|
-
const KEYBOARD_EVENTS = [
|
|
2688
|
-
"ALT",
|
|
2689
|
-
"AltGraph",
|
|
2690
|
-
"CapsLock",
|
|
2691
|
-
"Control",
|
|
2692
|
-
"Fn",
|
|
2693
|
-
"FnLock",
|
|
2694
|
-
"Hyper",
|
|
2695
|
-
"Meta",
|
|
2696
|
-
"NumLock",
|
|
2697
|
-
"ScrollLock",
|
|
2698
|
-
"Shift",
|
|
2699
|
-
"Super",
|
|
2700
|
-
"Symbol",
|
|
2701
|
-
"SymbolLock",
|
|
2702
|
-
"Enter",
|
|
2703
|
-
"Tab",
|
|
2704
|
-
"ArrowDown",
|
|
2705
|
-
"ArrowLeft",
|
|
2706
|
-
"ArrowRight",
|
|
2707
|
-
"ArrowUp",
|
|
2708
|
-
"End",
|
|
2709
|
-
"Home",
|
|
2710
|
-
"PageDown",
|
|
2711
|
-
"PageUp",
|
|
2712
|
-
"Backspace",
|
|
2713
|
-
"Clear",
|
|
2714
|
-
"Copy",
|
|
2715
|
-
"CrSel",
|
|
2716
|
-
"Cut",
|
|
2717
|
-
"Delete",
|
|
2718
|
-
"EraseEof",
|
|
2719
|
-
"ExSel",
|
|
2720
|
-
"Insert",
|
|
2721
|
-
"Paste",
|
|
2722
|
-
"Redo",
|
|
2723
|
-
"Undo",
|
|
2724
|
-
"Accept",
|
|
2725
|
-
"Again",
|
|
2726
|
-
"Attn",
|
|
2727
|
-
"Cancel",
|
|
2728
|
-
"ContextMenu",
|
|
2729
|
-
"Escape",
|
|
2730
|
-
"Execute",
|
|
2731
|
-
"Find",
|
|
2732
|
-
"Finish",
|
|
2733
|
-
"Help",
|
|
2734
|
-
"Pause",
|
|
2735
|
-
"Play",
|
|
2736
|
-
"Props",
|
|
2737
|
-
"Select",
|
|
2738
|
-
"ZoomIn",
|
|
2739
|
-
"ZoomOut",
|
|
2740
|
-
"BrightnessDown",
|
|
2741
|
-
"BrightnessUp",
|
|
2742
|
-
"Eject",
|
|
2743
|
-
"LogOff",
|
|
2744
|
-
"Power",
|
|
2745
|
-
"PowerOff",
|
|
2746
|
-
"PrintScreen",
|
|
2747
|
-
"Hibernate",
|
|
2748
|
-
"Standby",
|
|
2749
|
-
"WakeUp",
|
|
2750
|
-
"AllCandidates",
|
|
2751
|
-
"Alphanumeric",
|
|
2752
|
-
"CodeInput",
|
|
2753
|
-
"Compose",
|
|
2754
|
-
"Convert",
|
|
2755
|
-
"Dead",
|
|
2756
|
-
"FinalMode",
|
|
2757
|
-
"GroupFirst",
|
|
2758
|
-
"GroupLast",
|
|
2759
|
-
"GroupNext",
|
|
2760
|
-
"GroupPrevious",
|
|
2761
|
-
"ModeChange",
|
|
2762
|
-
"NextCandidate",
|
|
2763
|
-
"NonConvert",
|
|
2764
|
-
"PreviousCandidate",
|
|
2765
|
-
"Process",
|
|
2766
|
-
"SingleCandidate",
|
|
2767
|
-
"HangulMode",
|
|
2768
|
-
"HanjaMode",
|
|
2769
|
-
"JunjaMode",
|
|
2770
|
-
"Eisu",
|
|
2771
|
-
"Hankaku",
|
|
2772
|
-
"Hiragana",
|
|
2773
|
-
"HiraganaKatakana",
|
|
2774
|
-
"KanaMode",
|
|
2775
|
-
"KanjiMode",
|
|
2776
|
-
"Katakana",
|
|
2777
|
-
"Romaji",
|
|
2778
|
-
"Zenkaku",
|
|
2779
|
-
"ZenkakuHanaku",
|
|
2780
|
-
"F1",
|
|
2781
|
-
"F2",
|
|
2782
|
-
"F3",
|
|
2783
|
-
"F4",
|
|
2784
|
-
"F5",
|
|
2785
|
-
"F6",
|
|
2786
|
-
"F7",
|
|
2787
|
-
"F8",
|
|
2788
|
-
"F9",
|
|
2789
|
-
"F10",
|
|
2790
|
-
"F11",
|
|
2791
|
-
"F12",
|
|
2792
|
-
"Soft1",
|
|
2793
|
-
"Soft2",
|
|
2794
|
-
"Soft3",
|
|
2795
|
-
"Soft4",
|
|
2796
|
-
"ChannelDown",
|
|
2797
|
-
"ChannelUp",
|
|
2798
|
-
"Close",
|
|
2799
|
-
"MailForward",
|
|
2800
|
-
"MailReply",
|
|
2801
|
-
"MailSend",
|
|
2802
|
-
"MediaFastForward",
|
|
2803
|
-
"MediaPause",
|
|
2804
|
-
"MediaPlay",
|
|
2805
|
-
"MediaPlayPause",
|
|
2806
|
-
"MediaRecord",
|
|
2807
|
-
"MediaRewind",
|
|
2808
|
-
"MediaStop",
|
|
2809
|
-
"MediaTrackNext",
|
|
2810
|
-
"MediaTrackPrevious",
|
|
2811
|
-
"AudioBalanceLeft",
|
|
2812
|
-
"AudioBalanceRight",
|
|
2813
|
-
"AudioBassBoostDown",
|
|
2814
|
-
"AudioBassBoostToggle",
|
|
2815
|
-
"AudioBassBoostUp",
|
|
2816
|
-
"AudioFaderFront",
|
|
2817
|
-
"AudioFaderRear",
|
|
2818
|
-
"AudioSurroundModeNext",
|
|
2819
|
-
"AudioTrebleDown",
|
|
2820
|
-
"AudioTrebleUp",
|
|
2821
|
-
"AudioVolumeDown",
|
|
2822
|
-
"AudioVolumeMute",
|
|
2823
|
-
"AudioVolumeUp",
|
|
2824
|
-
"MicrophoneToggle",
|
|
2825
|
-
"MicrophoneVolumeDown",
|
|
2826
|
-
"MicrophoneVolumeMute",
|
|
2827
|
-
"MicrophoneVolumeUp",
|
|
2828
|
-
"TV",
|
|
2829
|
-
"TV3DMode",
|
|
2830
|
-
"TVAntennaCable",
|
|
2831
|
-
"TVAudioDescription",
|
|
2832
|
-
];
|
|
2833
|
-
function unEscapeString(str) {
|
|
2834
|
-
const placeholder = "__NEWLINE__";
|
|
2835
|
-
str = str.replace(new RegExp(placeholder, "g"), "\n");
|
|
2836
|
-
return str;
|
|
2837
|
-
}
|
|
2838
2864
|
export { StableBrowser };
|
|
2839
2865
|
//# sourceMappingURL=stable_browser.js.map
|