automation_model 1.0.560-dev → 1.0.560-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/README.md +130 -0
- package/lib/api.js +3 -0
- package/lib/api.js.map +1 -1
- package/lib/auto_page.d.ts +4 -2
- package/lib/auto_page.js +105 -8
- package/lib/auto_page.js.map +1 -1
- package/lib/browser_manager.js +49 -45
- package/lib/browser_manager.js.map +1 -1
- package/lib/command_common.d.ts +4 -4
- package/lib/command_common.js +42 -20
- package/lib/command_common.js.map +1 -1
- package/lib/error-messages.js +18 -0
- package/lib/error-messages.js.map +1 -1
- package/lib/init_browser.d.ts +3 -2
- package/lib/init_browser.js +64 -11
- package/lib/init_browser.js.map +1 -1
- package/lib/locate_element.js +2 -2
- package/lib/locate_element.js.map +1 -1
- package/lib/network.d.ts +1 -1
- package/lib/network.js +46 -18
- package/lib/network.js.map +1 -1
- package/lib/stable_browser.d.ts +27 -9
- package/lib/stable_browser.js +713 -234
- package/lib/stable_browser.js.map +1 -1
- package/lib/table_helper.d.ts +19 -0
- package/lib/table_helper.js +101 -0
- package/lib/table_helper.js.map +1 -0
- package/lib/test_context.d.ts +3 -0
- package/lib/test_context.js +2 -0
- package/lib/test_context.js.map +1 -1
- package/lib/utils.d.ts +7 -2
- package/lib/utils.js +307 -12
- package/lib/utils.js.map +1 -1
- package/package.json +7 -8
- package/lib/scripts/find_text.js +0 -125
package/lib/stable_browser.js
CHANGED
|
@@ -10,18 +10,21 @@ 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 { _copyContext, _fixLocatorUsingParams, _fixUsingParams, _getServerUrl, KEYBOARD_EVENTS, maskValue, replaceWithLocalTestData, scrollPageToLoadLazyElements, unEscapeString, } from "./utils.js";
|
|
13
|
+
import { _convertToRegexQuery, _copyContext, _fixLocatorUsingParams, _fixUsingParams, _getServerUrl, extractStepExampleParameters, KEYBOARD_EVENTS, maskValue, replaceWithLocalTestData, scrollPageToLoadLazyElements, unEscapeString, _getDataFile, testForRegex, performAction, } from "./utils.js";
|
|
14
14
|
import csv from "csv-parser";
|
|
15
15
|
import { Readable } from "node:stream";
|
|
16
16
|
import readline from "readline";
|
|
17
|
-
import { getContext } from "./init_browser.js";
|
|
17
|
+
import { getContext, refreshBrowser } from "./init_browser.js";
|
|
18
18
|
import { locate_element } from "./locate_element.js";
|
|
19
19
|
import { randomUUID } from "crypto";
|
|
20
20
|
import { _commandError, _commandFinally, _preCommand, _validateSelectors, _screenshot, _reportToWorld, } from "./command_common.js";
|
|
21
21
|
import { registerDownloadEvent, registerNetworkEvents } from "./network.js";
|
|
22
22
|
import { LocatorLog } from "./locator_log.js";
|
|
23
|
+
import axios from "axios";
|
|
24
|
+
import { _findCellArea, findElementsInArea } from "./table_helper.js";
|
|
23
25
|
export const Types = {
|
|
24
26
|
CLICK: "click_element",
|
|
27
|
+
WAIT_ELEMENT: "wait_element",
|
|
25
28
|
NAVIGATE: "navigate",
|
|
26
29
|
FILL: "fill_element",
|
|
27
30
|
EXECUTE: "execute_page_method",
|
|
@@ -43,6 +46,7 @@ export const Types = {
|
|
|
43
46
|
UNCHECK: "uncheck_element",
|
|
44
47
|
EXTRACT: "extract_attribute",
|
|
45
48
|
CLOSE_PAGE: "close_page",
|
|
49
|
+
TABLE_OPERATION: "table_operation",
|
|
46
50
|
SET_DATE_TIME: "set_date_time",
|
|
47
51
|
SET_VIEWPORT: "set_viewport",
|
|
48
52
|
VERIFY_VISUAL: "verify_visual",
|
|
@@ -53,6 +57,9 @@ export const Types = {
|
|
|
53
57
|
VERIFY_TEXT_WITH_RELATION: "verify_text_with_relation",
|
|
54
58
|
};
|
|
55
59
|
export const apps = {};
|
|
60
|
+
const formatElementName = (elementName) => {
|
|
61
|
+
return elementName ? JSON.stringify(elementName) : "element";
|
|
62
|
+
};
|
|
56
63
|
class StableBrowser {
|
|
57
64
|
browser;
|
|
58
65
|
page;
|
|
@@ -66,6 +73,7 @@ class StableBrowser {
|
|
|
66
73
|
appName = "main";
|
|
67
74
|
tags = null;
|
|
68
75
|
isRecording = false;
|
|
76
|
+
initSnapshotTaken = false;
|
|
69
77
|
constructor(browser, page, logger = null, context = null, world = null) {
|
|
70
78
|
this.browser = browser;
|
|
71
79
|
this.page = page;
|
|
@@ -235,16 +243,51 @@ class StableBrowser {
|
|
|
235
243
|
// async closeUnexpectedPopups() {
|
|
236
244
|
// await closeUnexpectedPopups(this.page);
|
|
237
245
|
// }
|
|
238
|
-
async goto(url) {
|
|
246
|
+
async goto(url, world = null) {
|
|
247
|
+
if (!url) {
|
|
248
|
+
throw new Error("url is null, verify that the environment file is correct");
|
|
249
|
+
}
|
|
239
250
|
if (!url.startsWith("http")) {
|
|
240
251
|
url = "https://" + url;
|
|
241
252
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
253
|
+
const state = {
|
|
254
|
+
value: url,
|
|
255
|
+
world: world,
|
|
256
|
+
type: Types.NAVIGATE,
|
|
257
|
+
text: `Navigate Page to: ${url}`,
|
|
258
|
+
operation: "goto",
|
|
259
|
+
log: "***** navigate page to " + url + " *****\n",
|
|
260
|
+
info: {},
|
|
261
|
+
locate: false,
|
|
262
|
+
scroll: false,
|
|
263
|
+
screenshot: false,
|
|
264
|
+
highlight: false,
|
|
265
|
+
};
|
|
266
|
+
try {
|
|
267
|
+
await _preCommand(state, this);
|
|
268
|
+
await this.page.goto(url, {
|
|
269
|
+
timeout: 60000,
|
|
270
|
+
});
|
|
271
|
+
await _screenshot(state, this);
|
|
272
|
+
}
|
|
273
|
+
catch (error) {
|
|
274
|
+
console.error("Error on goto", error);
|
|
275
|
+
_commandError(state, error, this);
|
|
276
|
+
}
|
|
277
|
+
finally {
|
|
278
|
+
_commandFinally(state, this);
|
|
279
|
+
}
|
|
245
280
|
}
|
|
246
|
-
_getLocator(locator, scope, _params) {
|
|
281
|
+
async _getLocator(locator, scope, _params) {
|
|
247
282
|
locator = _fixLocatorUsingParams(locator, _params);
|
|
283
|
+
// locator = await this._replaceWithLocalData(locator);
|
|
284
|
+
for (let key in locator) {
|
|
285
|
+
if (typeof locator[key] !== "string")
|
|
286
|
+
continue;
|
|
287
|
+
if (locator[key].includes("{{") && locator[key].includes("}}")) {
|
|
288
|
+
locator[key] = await this._replaceWithLocalData(locator[key], this.world);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
248
291
|
let locatorReturn;
|
|
249
292
|
if (locator.role) {
|
|
250
293
|
if (locator.role[1].nameReg) {
|
|
@@ -295,52 +338,60 @@ class StableBrowser {
|
|
|
295
338
|
if (css && css.locator) {
|
|
296
339
|
css = css.locator;
|
|
297
340
|
}
|
|
298
|
-
let result = await this._locateElementByText(scope, _fixUsingParams(text, _params), "*:not(script, style, head)", false, false, _params);
|
|
341
|
+
let result = await this._locateElementByText(scope, _fixUsingParams(text, _params), "*:not(script, style, head)", false, false, true, _params);
|
|
299
342
|
if (result.elementCount === 0) {
|
|
300
343
|
return;
|
|
301
344
|
}
|
|
302
|
-
let textElementCss = "[data-blinq-id
|
|
345
|
+
let textElementCss = "[data-blinq-id-" + result.randomToken + "]";
|
|
303
346
|
// css climb to parent element
|
|
304
347
|
const climbArray = [];
|
|
305
348
|
for (let i = 0; i < climb; i++) {
|
|
306
349
|
climbArray.push("..");
|
|
307
350
|
}
|
|
308
351
|
let climbXpath = "xpath=" + climbArray.join("/");
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
return
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
for (let i = 0; i < elements.length; i++) {
|
|
332
|
-
if (randomToken === null) {
|
|
333
|
-
randomToken = Math.random().toString(36).substring(7);
|
|
352
|
+
let resultCss = textElementCss + " >> " + climbXpath;
|
|
353
|
+
if (css) {
|
|
354
|
+
resultCss = resultCss + " >> " + css;
|
|
355
|
+
}
|
|
356
|
+
return resultCss;
|
|
357
|
+
}
|
|
358
|
+
async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, ignoreCase = true, _params) {
|
|
359
|
+
const query = `${_convertToRegexQuery(text1, regex1, !partial1, ignoreCase)}`;
|
|
360
|
+
const locator = scope.locator(query);
|
|
361
|
+
const count = await locator.count();
|
|
362
|
+
if (!tag1) {
|
|
363
|
+
tag1 = "*";
|
|
364
|
+
}
|
|
365
|
+
const randomToken = Math.random().toString(36).substring(7);
|
|
366
|
+
let tagCount = 0;
|
|
367
|
+
for (let i = 0; i < count; i++) {
|
|
368
|
+
const element = locator.nth(i);
|
|
369
|
+
// check if the tag matches
|
|
370
|
+
if (!(await element.evaluate((el, [tag, randomToken]) => {
|
|
371
|
+
if (!tag.startsWith("*")) {
|
|
372
|
+
if (el.tagName.toLowerCase() !== tag) {
|
|
373
|
+
return false;
|
|
334
374
|
}
|
|
335
|
-
let element = elements[i];
|
|
336
|
-
element.setAttribute("data-blinq-id", "blinq-id-" + randomToken);
|
|
337
|
-
elementCount++;
|
|
338
375
|
}
|
|
376
|
+
if (!el.setAttribute) {
|
|
377
|
+
el = el.parentElement;
|
|
378
|
+
}
|
|
379
|
+
// remove any attributes start with data-blinq-id
|
|
380
|
+
// for (let i = 0; i < el.attributes.length; i++) {
|
|
381
|
+
// if (el.attributes[i].name.startsWith("data-blinq-id")) {
|
|
382
|
+
// el.removeAttribute(el.attributes[i].name);
|
|
383
|
+
// }
|
|
384
|
+
// }
|
|
385
|
+
el.setAttribute("data-blinq-id-" + randomToken, "");
|
|
386
|
+
return true;
|
|
387
|
+
}, [tag1, randomToken]))) {
|
|
388
|
+
continue;
|
|
339
389
|
}
|
|
340
|
-
|
|
341
|
-
}
|
|
390
|
+
tagCount++;
|
|
391
|
+
}
|
|
392
|
+
return { elementCount: tagCount, randomToken };
|
|
342
393
|
}
|
|
343
|
-
async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true) {
|
|
394
|
+
async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true, allowDisabled = false, element_name = null) {
|
|
344
395
|
if (!info) {
|
|
345
396
|
info = {};
|
|
346
397
|
}
|
|
@@ -363,30 +414,31 @@ class StableBrowser {
|
|
|
363
414
|
//info.log += "searching for locator " + JSON.stringify(locatorSearch) + "\n";
|
|
364
415
|
let locator = null;
|
|
365
416
|
if (locatorSearch.climb && locatorSearch.climb >= 0) {
|
|
366
|
-
|
|
417
|
+
const replacedText = await this._replaceWithLocalData(locatorSearch.text, this.world);
|
|
418
|
+
let locatorString = await this._locateElmentByTextClimbCss(scope, replacedText, locatorSearch.climb, locatorSearch.css, _params);
|
|
367
419
|
if (!locatorString) {
|
|
368
420
|
info.failCause.textNotFound = true;
|
|
369
|
-
info.failCause.lastError =
|
|
421
|
+
info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${locatorSearch.text}`;
|
|
370
422
|
return;
|
|
371
423
|
}
|
|
372
|
-
locator = this._getLocator({ css: locatorString }, scope, _params);
|
|
424
|
+
locator = await this._getLocator({ css: locatorString }, scope, _params);
|
|
373
425
|
}
|
|
374
426
|
else if (locatorSearch.text) {
|
|
375
427
|
let text = _fixUsingParams(locatorSearch.text, _params);
|
|
376
|
-
let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, _params);
|
|
428
|
+
let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, true, _params);
|
|
377
429
|
if (result.elementCount === 0) {
|
|
378
430
|
info.failCause.textNotFound = true;
|
|
379
|
-
info.failCause.lastError =
|
|
431
|
+
info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${text}`;
|
|
380
432
|
return;
|
|
381
433
|
}
|
|
382
|
-
locatorSearch.css = "[data-blinq-id
|
|
434
|
+
locatorSearch.css = "[data-blinq-id-" + result.randomToken + "]";
|
|
383
435
|
if (locatorSearch.childCss) {
|
|
384
436
|
locatorSearch.css = locatorSearch.css + " " + locatorSearch.childCss;
|
|
385
437
|
}
|
|
386
|
-
locator = this._getLocator(locatorSearch, scope, _params);
|
|
438
|
+
locator = await this._getLocator(locatorSearch, scope, _params);
|
|
387
439
|
}
|
|
388
440
|
else {
|
|
389
|
-
locator = this._getLocator(locatorSearch, scope, _params);
|
|
441
|
+
locator = await this._getLocator(locatorSearch, scope, _params);
|
|
390
442
|
}
|
|
391
443
|
// let cssHref = false;
|
|
392
444
|
// if (locatorSearch.css && locatorSearch.css.includes("href=")) {
|
|
@@ -415,7 +467,7 @@ class StableBrowser {
|
|
|
415
467
|
if (!visibleOnly) {
|
|
416
468
|
visible = true;
|
|
417
469
|
}
|
|
418
|
-
if (visible && enabled) {
|
|
470
|
+
if (visible && (allowDisabled || enabled)) {
|
|
419
471
|
foundLocators.push(locator.nth(j));
|
|
420
472
|
if (info.locatorLog) {
|
|
421
473
|
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
|
|
@@ -428,9 +480,11 @@ class StableBrowser {
|
|
|
428
480
|
info.printMessages = {};
|
|
429
481
|
}
|
|
430
482
|
if (info.locatorLog && !visible) {
|
|
483
|
+
info.failCause.lastError = `${formatElementName(element_name)} is not visible, searching for ${originalLocatorSearch}`;
|
|
431
484
|
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_VISIBLE");
|
|
432
485
|
}
|
|
433
486
|
if (info.locatorLog && !enabled) {
|
|
487
|
+
info.failCause.lastError = `${formatElementName(element_name)} is disabled, searching for ${originalLocatorSearch}`;
|
|
434
488
|
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_ENABLED");
|
|
435
489
|
}
|
|
436
490
|
if (!info.printMessages[j.toString()]) {
|
|
@@ -498,7 +552,7 @@ class StableBrowser {
|
|
|
498
552
|
}
|
|
499
553
|
return { rerun: false };
|
|
500
554
|
}
|
|
501
|
-
async _locate(selectors, info, _params, timeout) {
|
|
555
|
+
async _locate(selectors, info, _params, timeout, allowDisabled = false) {
|
|
502
556
|
if (!timeout) {
|
|
503
557
|
timeout = 30000;
|
|
504
558
|
}
|
|
@@ -508,9 +562,18 @@ class StableBrowser {
|
|
|
508
562
|
let selector = selectors.locators[j];
|
|
509
563
|
info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
|
|
510
564
|
}
|
|
511
|
-
let element = await this._locate_internal(selectors, info, _params, timeout);
|
|
565
|
+
let element = await this._locate_internal(selectors, info, _params, timeout, allowDisabled);
|
|
512
566
|
if (!element.rerun) {
|
|
513
|
-
|
|
567
|
+
const randomToken = Math.random().toString(36).substring(7);
|
|
568
|
+
element.evaluate((el, randomToken) => {
|
|
569
|
+
el.setAttribute("data-blinq-id-" + randomToken, "");
|
|
570
|
+
}, randomToken);
|
|
571
|
+
if (element._frame) {
|
|
572
|
+
return element;
|
|
573
|
+
}
|
|
574
|
+
const scope = element.page();
|
|
575
|
+
const newSelector = scope.locator("[data-blinq-id-" + randomToken + "]");
|
|
576
|
+
return newSelector;
|
|
514
577
|
}
|
|
515
578
|
}
|
|
516
579
|
throw new Error("unable to locate element " + JSON.stringify(selectors));
|
|
@@ -578,19 +641,19 @@ class StableBrowser {
|
|
|
578
641
|
}
|
|
579
642
|
if (!scope) {
|
|
580
643
|
if (info && info.locatorLog) {
|
|
581
|
-
info.locatorLog.
|
|
644
|
+
info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "NOT_FOUND");
|
|
582
645
|
}
|
|
583
646
|
//info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
|
|
584
647
|
if (Date.now() - startTime > timeout) {
|
|
585
648
|
info.failCause.iframeNotFound = true;
|
|
586
|
-
info.failCause.lastError =
|
|
649
|
+
info.failCause.lastError = `unable to locate iframe "${selectors.iframe_src}"`;
|
|
587
650
|
throw new Error("unable to locate iframe " + selectors.iframe_src);
|
|
588
651
|
}
|
|
589
652
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
590
653
|
}
|
|
591
654
|
else {
|
|
592
655
|
if (info && info.locatorLog) {
|
|
593
|
-
info.locatorLog.
|
|
656
|
+
info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "FOUND");
|
|
594
657
|
}
|
|
595
658
|
break;
|
|
596
659
|
}
|
|
@@ -608,7 +671,7 @@ class StableBrowser {
|
|
|
608
671
|
return bodyContent;
|
|
609
672
|
});
|
|
610
673
|
}
|
|
611
|
-
async _locate_internal(selectors, info, _params, timeout = 30000) {
|
|
674
|
+
async _locate_internal(selectors, info, _params, timeout = 30000, allowDisabled = false) {
|
|
612
675
|
if (!info) {
|
|
613
676
|
info = {};
|
|
614
677
|
info.failCause = {};
|
|
@@ -657,17 +720,17 @@ class StableBrowser {
|
|
|
657
720
|
}
|
|
658
721
|
// info.log += "scanning locators in priority 1" + "\n";
|
|
659
722
|
let onlyPriority3 = selectorsLocators[0].priority === 3;
|
|
660
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly);
|
|
723
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
661
724
|
if (result.foundElements.length === 0) {
|
|
662
725
|
// info.log += "scanning locators in priority 2" + "\n";
|
|
663
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly);
|
|
726
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
664
727
|
}
|
|
665
728
|
if (result.foundElements.length === 0 && onlyPriority3) {
|
|
666
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
|
|
729
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
667
730
|
}
|
|
668
731
|
else {
|
|
669
732
|
if (result.foundElements.length === 0 && !highPriorityOnly) {
|
|
670
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
|
|
733
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
671
734
|
}
|
|
672
735
|
}
|
|
673
736
|
let foundElements = result.foundElements;
|
|
@@ -712,7 +775,7 @@ class StableBrowser {
|
|
|
712
775
|
break;
|
|
713
776
|
}
|
|
714
777
|
if (Date.now() - startTime > highPriorityTimeout) {
|
|
715
|
-
info.log += "high priority timeout, will try all elements" + "\n";
|
|
778
|
+
//info.log += "high priority timeout, will try all elements" + "\n";
|
|
716
779
|
highPriorityOnly = false;
|
|
717
780
|
if (this.configuration && this.configuration.load_all_lazy === true && !lazy_scroll) {
|
|
718
781
|
lazy_scroll = true;
|
|
@@ -720,7 +783,7 @@ class StableBrowser {
|
|
|
720
783
|
}
|
|
721
784
|
}
|
|
722
785
|
if (Date.now() - startTime > visibleOnlyTimeout) {
|
|
723
|
-
info.log += "visible only timeout, will try all elements" + "\n";
|
|
786
|
+
//info.log += "visible only timeout, will try all elements" + "\n";
|
|
724
787
|
visibleOnly = false;
|
|
725
788
|
}
|
|
726
789
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -734,10 +797,12 @@ class StableBrowser {
|
|
|
734
797
|
// }
|
|
735
798
|
//info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
|
|
736
799
|
info.failCause.locatorNotFound = true;
|
|
737
|
-
info
|
|
800
|
+
if (!info?.failCause?.lastError) {
|
|
801
|
+
info.failCause.lastError = `failed to locate ${formatElementName(selectors.element_name)}, ${locatorsCount > 0 ? `${locatorsCount} matching elements found` : "no matching elements found"}`;
|
|
802
|
+
}
|
|
738
803
|
throw new Error("failed to locate first element no elements found, " + info.log);
|
|
739
804
|
}
|
|
740
|
-
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
|
|
805
|
+
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false, element_name) {
|
|
741
806
|
let foundElements = [];
|
|
742
807
|
const result = {
|
|
743
808
|
foundElements: foundElements,
|
|
@@ -745,14 +810,15 @@ class StableBrowser {
|
|
|
745
810
|
for (let i = 0; i < locatorsGroup.length; i++) {
|
|
746
811
|
let foundLocators = [];
|
|
747
812
|
try {
|
|
748
|
-
await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly);
|
|
813
|
+
await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
|
|
749
814
|
}
|
|
750
815
|
catch (e) {
|
|
751
|
-
this
|
|
752
|
-
this.logger.debug(
|
|
816
|
+
// this call can fail it the browser is navigating
|
|
817
|
+
// this.logger.debug("unable to use locator " + JSON.stringify(locatorsGroup[i]));
|
|
818
|
+
// this.logger.debug(e);
|
|
753
819
|
foundLocators = [];
|
|
754
820
|
try {
|
|
755
|
-
await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly);
|
|
821
|
+
await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
|
|
756
822
|
}
|
|
757
823
|
catch (e) {
|
|
758
824
|
this.logger.info("unable to use locator (second try) " + JSON.stringify(locatorsGroup[i]));
|
|
@@ -767,9 +833,40 @@ class StableBrowser {
|
|
|
767
833
|
result.locatorIndex = i;
|
|
768
834
|
}
|
|
769
835
|
if (foundLocators.length > 1) {
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
836
|
+
// remove elements that consume the same space with 10 pixels tolerance
|
|
837
|
+
const boxes = [];
|
|
838
|
+
for (let j = 0; j < foundLocators.length; j++) {
|
|
839
|
+
boxes.push({ box: await foundLocators[j].boundingBox(), locator: foundLocators[j] });
|
|
840
|
+
}
|
|
841
|
+
for (let j = 0; j < boxes.length; j++) {
|
|
842
|
+
for (let k = 0; k < boxes.length; k++) {
|
|
843
|
+
if (j === k) {
|
|
844
|
+
continue;
|
|
845
|
+
}
|
|
846
|
+
// check if x, y, width, height are the same with 10 pixels tolerance
|
|
847
|
+
if (Math.abs(boxes[j].box.x - boxes[k].box.x) < 10 &&
|
|
848
|
+
Math.abs(boxes[j].box.y - boxes[k].box.y) < 10 &&
|
|
849
|
+
Math.abs(boxes[j].box.width - boxes[k].box.width) < 10 &&
|
|
850
|
+
Math.abs(boxes[j].box.height - boxes[k].box.height) < 10) {
|
|
851
|
+
// as the element is not unique, will remove it
|
|
852
|
+
boxes.splice(k, 1);
|
|
853
|
+
k--;
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
if (boxes.length === 1) {
|
|
858
|
+
result.foundElements.push({
|
|
859
|
+
locator: boxes[0].locator.first(),
|
|
860
|
+
box: boxes[0].box,
|
|
861
|
+
unique: true,
|
|
862
|
+
});
|
|
863
|
+
result.locatorIndex = i;
|
|
864
|
+
}
|
|
865
|
+
else {
|
|
866
|
+
info.failCause.foundMultiple = true;
|
|
867
|
+
if (info.locatorLog) {
|
|
868
|
+
info.locatorLog.setLocatorSearchStatus(JSON.stringify(locatorsGroup[i]), "FOUND_NOT_UNIQUE");
|
|
869
|
+
}
|
|
773
870
|
}
|
|
774
871
|
}
|
|
775
872
|
}
|
|
@@ -880,25 +977,14 @@ class StableBrowser {
|
|
|
880
977
|
options,
|
|
881
978
|
world,
|
|
882
979
|
text: "Click element",
|
|
980
|
+
_text: "Click on " + selectors.element_name,
|
|
883
981
|
type: Types.CLICK,
|
|
884
982
|
operation: "click",
|
|
885
983
|
log: "***** click on " + selectors.element_name + " *****\n",
|
|
886
984
|
};
|
|
887
985
|
try {
|
|
888
986
|
await _preCommand(state, this);
|
|
889
|
-
|
|
890
|
-
state.selectors.locators[0].text = state.options.context;
|
|
891
|
-
}
|
|
892
|
-
try {
|
|
893
|
-
await state.element.click();
|
|
894
|
-
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
895
|
-
}
|
|
896
|
-
catch (e) {
|
|
897
|
-
// await this.closeUnexpectedPopups();
|
|
898
|
-
state.element = await this._locate(selectors, state.info, _params);
|
|
899
|
-
await state.element.dispatchEvent("click");
|
|
900
|
-
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
901
|
-
}
|
|
987
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
902
988
|
await this.waitForPageLoad();
|
|
903
989
|
return state.info;
|
|
904
990
|
}
|
|
@@ -909,6 +995,38 @@ class StableBrowser {
|
|
|
909
995
|
_commandFinally(state, this);
|
|
910
996
|
}
|
|
911
997
|
}
|
|
998
|
+
async waitForElement(selectors, _params, options = {}, world = null) {
|
|
999
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1000
|
+
const state = {
|
|
1001
|
+
selectors,
|
|
1002
|
+
_params,
|
|
1003
|
+
options,
|
|
1004
|
+
world,
|
|
1005
|
+
text: "Wait for element",
|
|
1006
|
+
_text: "Wait for " + selectors.element_name,
|
|
1007
|
+
type: Types.WAIT_ELEMENT,
|
|
1008
|
+
operation: "waitForElement",
|
|
1009
|
+
log: "***** wait for " + selectors.element_name + " *****\n",
|
|
1010
|
+
};
|
|
1011
|
+
let found = false;
|
|
1012
|
+
try {
|
|
1013
|
+
await _preCommand(state, this);
|
|
1014
|
+
// if (state.options && state.options.context) {
|
|
1015
|
+
// state.selectors.locators[0].text = state.options.context;
|
|
1016
|
+
// }
|
|
1017
|
+
await state.element.waitFor({ timeout: timeout });
|
|
1018
|
+
found = true;
|
|
1019
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1020
|
+
}
|
|
1021
|
+
catch (e) {
|
|
1022
|
+
console.error("Error on waitForElement", e);
|
|
1023
|
+
// await _commandError(state, e, this);
|
|
1024
|
+
}
|
|
1025
|
+
finally {
|
|
1026
|
+
_commandFinally(state, this);
|
|
1027
|
+
}
|
|
1028
|
+
return found;
|
|
1029
|
+
}
|
|
912
1030
|
async setCheck(selectors, checked = true, _params, options = {}, world = null) {
|
|
913
1031
|
const state = {
|
|
914
1032
|
selectors,
|
|
@@ -917,6 +1035,7 @@ class StableBrowser {
|
|
|
917
1035
|
world,
|
|
918
1036
|
type: checked ? Types.CHECK : Types.UNCHECK,
|
|
919
1037
|
text: checked ? `Check element` : `Uncheck element`,
|
|
1038
|
+
_text: checked ? `Check ${selectors.element_name}` : `Uncheck ${selectors.element_name}`,
|
|
920
1039
|
operation: "setCheck",
|
|
921
1040
|
log: "***** check " + selectors.element_name + " *****\n",
|
|
922
1041
|
};
|
|
@@ -926,9 +1045,15 @@ class StableBrowser {
|
|
|
926
1045
|
// let element = await this._locate(selectors, info, _params);
|
|
927
1046
|
// ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
928
1047
|
try {
|
|
929
|
-
//
|
|
1048
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
1049
|
+
// console.log(`Highlighting while running from recorder`);
|
|
1050
|
+
await this._highlightElements(element);
|
|
930
1051
|
await state.element.setChecked(checked);
|
|
931
1052
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1053
|
+
// await this._unHighlightElements(element);
|
|
1054
|
+
// }
|
|
1055
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1056
|
+
// await this._unHighlightElements(element);
|
|
932
1057
|
}
|
|
933
1058
|
catch (e) {
|
|
934
1059
|
if (e.message && e.message.includes("did not change its state")) {
|
|
@@ -960,22 +1085,13 @@ class StableBrowser {
|
|
|
960
1085
|
world,
|
|
961
1086
|
type: Types.HOVER,
|
|
962
1087
|
text: `Hover element`,
|
|
1088
|
+
_text: `Hover on ${selectors.element_name}`,
|
|
963
1089
|
operation: "hover",
|
|
964
1090
|
log: "***** hover " + selectors.element_name + " *****\n",
|
|
965
1091
|
};
|
|
966
1092
|
try {
|
|
967
1093
|
await _preCommand(state, this);
|
|
968
|
-
|
|
969
|
-
await state.element.hover();
|
|
970
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
971
|
-
}
|
|
972
|
-
catch (e) {
|
|
973
|
-
//await this.closeUnexpectedPopups();
|
|
974
|
-
state.info.log += "hover failed, will try again" + "\n";
|
|
975
|
-
state.element = await this._locate(selectors, state.info, _params);
|
|
976
|
-
await state.element.hover({ timeout: 10000 });
|
|
977
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
978
|
-
}
|
|
1094
|
+
await performAction("hover", state.element, options, this, state, _params);
|
|
979
1095
|
await _screenshot(state, this);
|
|
980
1096
|
await this.waitForPageLoad();
|
|
981
1097
|
return state.info;
|
|
@@ -999,6 +1115,7 @@ class StableBrowser {
|
|
|
999
1115
|
value: values.toString(),
|
|
1000
1116
|
type: Types.SELECT,
|
|
1001
1117
|
text: `Select option: ${values}`,
|
|
1118
|
+
_text: `Select option: ${values} on ${selectors.element_name}`,
|
|
1002
1119
|
operation: "selectOption",
|
|
1003
1120
|
log: "***** select option " + selectors.element_name + " *****\n",
|
|
1004
1121
|
};
|
|
@@ -1033,6 +1150,7 @@ class StableBrowser {
|
|
|
1033
1150
|
highlight: false,
|
|
1034
1151
|
type: Types.TYPE_PRESS,
|
|
1035
1152
|
text: `Type value: ${_value}`,
|
|
1153
|
+
_text: `Type value: ${_value}`,
|
|
1036
1154
|
operation: "type",
|
|
1037
1155
|
log: "",
|
|
1038
1156
|
};
|
|
@@ -1112,6 +1230,7 @@ class StableBrowser {
|
|
|
1112
1230
|
world,
|
|
1113
1231
|
type: Types.SET_DATE_TIME,
|
|
1114
1232
|
text: `Set date time value: ${value}`,
|
|
1233
|
+
_text: `Set date time value: ${value} on ${selectors.element_name}`,
|
|
1115
1234
|
operation: "setDateTime",
|
|
1116
1235
|
log: "***** set date time value " + selectors.element_name + " *****\n",
|
|
1117
1236
|
throwError: false,
|
|
@@ -1119,7 +1238,7 @@ class StableBrowser {
|
|
|
1119
1238
|
try {
|
|
1120
1239
|
await _preCommand(state, this);
|
|
1121
1240
|
try {
|
|
1122
|
-
await state.element
|
|
1241
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
1123
1242
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1124
1243
|
if (format) {
|
|
1125
1244
|
state.value = dayjs(state.value).format(format);
|
|
@@ -1183,9 +1302,13 @@ class StableBrowser {
|
|
|
1183
1302
|
world,
|
|
1184
1303
|
type: Types.FILL,
|
|
1185
1304
|
text: `Click type input with value: ${_value}`,
|
|
1305
|
+
_text: "Fill " + selectors.element_name + " with value " + maskValue(_value),
|
|
1186
1306
|
operation: "clickType",
|
|
1187
1307
|
log: "***** clickType on " + selectors.element_name + " with value " + maskValue(_value) + "*****\n",
|
|
1188
1308
|
};
|
|
1309
|
+
if (!options) {
|
|
1310
|
+
options = {};
|
|
1311
|
+
}
|
|
1189
1312
|
if (newValue !== _value) {
|
|
1190
1313
|
//this.logger.info(_value + "=" + newValue);
|
|
1191
1314
|
_value = newValue;
|
|
@@ -1193,7 +1316,7 @@ class StableBrowser {
|
|
|
1193
1316
|
try {
|
|
1194
1317
|
await _preCommand(state, this);
|
|
1195
1318
|
state.info.value = _value;
|
|
1196
|
-
if (
|
|
1319
|
+
if (!options.press) {
|
|
1197
1320
|
try {
|
|
1198
1321
|
let currentValue = await state.element.inputValue();
|
|
1199
1322
|
if (currentValue) {
|
|
@@ -1204,13 +1327,9 @@ class StableBrowser {
|
|
|
1204
1327
|
this.logger.info("unable to clear input value");
|
|
1205
1328
|
}
|
|
1206
1329
|
}
|
|
1207
|
-
if (options
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
}
|
|
1211
|
-
catch (e) {
|
|
1212
|
-
await state.element.dispatchEvent("click");
|
|
1213
|
-
}
|
|
1330
|
+
if (options.press) {
|
|
1331
|
+
options.timeout = 5000;
|
|
1332
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
1214
1333
|
}
|
|
1215
1334
|
else {
|
|
1216
1335
|
try {
|
|
@@ -1248,7 +1367,12 @@ class StableBrowser {
|
|
|
1248
1367
|
await this.waitForPageLoad();
|
|
1249
1368
|
}
|
|
1250
1369
|
else if (enter === false) {
|
|
1251
|
-
|
|
1370
|
+
try {
|
|
1371
|
+
await state.element.dispatchEvent("change", null, { timeout: 5000 });
|
|
1372
|
+
}
|
|
1373
|
+
catch (e) {
|
|
1374
|
+
// ignore
|
|
1375
|
+
}
|
|
1252
1376
|
//await this.page.keyboard.press("Tab");
|
|
1253
1377
|
}
|
|
1254
1378
|
else {
|
|
@@ -1300,6 +1424,7 @@ class StableBrowser {
|
|
|
1300
1424
|
return await this._getText(selectors, 0, _params, options, info, world);
|
|
1301
1425
|
}
|
|
1302
1426
|
async _getText(selectors, climb, _params = null, options = {}, info = {}, world = null) {
|
|
1427
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1303
1428
|
_validateSelectors(selectors);
|
|
1304
1429
|
let screenshotId = null;
|
|
1305
1430
|
let screenshotPath = null;
|
|
@@ -1309,7 +1434,7 @@ class StableBrowser {
|
|
|
1309
1434
|
}
|
|
1310
1435
|
info.operation = "getText";
|
|
1311
1436
|
info.selectors = selectors;
|
|
1312
|
-
let element = await this._locate(selectors, info, _params);
|
|
1437
|
+
let element = await this._locate(selectors, info, _params, timeout);
|
|
1313
1438
|
if (climb > 0) {
|
|
1314
1439
|
const climbArray = [];
|
|
1315
1440
|
for (let i = 0; i < climb; i++) {
|
|
@@ -1328,6 +1453,18 @@ class StableBrowser {
|
|
|
1328
1453
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1329
1454
|
try {
|
|
1330
1455
|
await this._highlightElements(element);
|
|
1456
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
1457
|
+
// // console.log(`Highlighting for get text while running from recorder`);
|
|
1458
|
+
// this._highlightElements(element)
|
|
1459
|
+
// .then(async () => {
|
|
1460
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1461
|
+
// this._unhighlightElements(element).then(
|
|
1462
|
+
// () => {}
|
|
1463
|
+
// // console.log(`Unhighlighting vrtr in recorder is successful`)
|
|
1464
|
+
// );
|
|
1465
|
+
// })
|
|
1466
|
+
// .catch(e);
|
|
1467
|
+
// }
|
|
1331
1468
|
const elementText = await element.innerText();
|
|
1332
1469
|
return {
|
|
1333
1470
|
text: elementText,
|
|
@@ -1339,7 +1476,7 @@ class StableBrowser {
|
|
|
1339
1476
|
}
|
|
1340
1477
|
catch (e) {
|
|
1341
1478
|
//await this.closeUnexpectedPopups();
|
|
1342
|
-
this.logger.info("no innerText will use textContent");
|
|
1479
|
+
this.logger.info("no innerText, will use textContent");
|
|
1343
1480
|
const elementText = await element.textContent();
|
|
1344
1481
|
return { text: elementText, screenshotId, screenshotPath, value: value };
|
|
1345
1482
|
}
|
|
@@ -1364,6 +1501,7 @@ class StableBrowser {
|
|
|
1364
1501
|
highlight: false,
|
|
1365
1502
|
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1366
1503
|
text: `Verify element contains pattern: ${pattern}`,
|
|
1504
|
+
_text: "Verify element " + selectors.element_name + " contains pattern " + pattern,
|
|
1367
1505
|
operation: "containsPattern",
|
|
1368
1506
|
log: "***** verify element " + selectors.element_name + " contains pattern " + pattern + " *****\n",
|
|
1369
1507
|
};
|
|
@@ -1399,6 +1537,8 @@ class StableBrowser {
|
|
|
1399
1537
|
}
|
|
1400
1538
|
}
|
|
1401
1539
|
async containsText(selectors, text, climb, _params = null, options = {}, world = null) {
|
|
1540
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1541
|
+
const startTime = Date.now();
|
|
1402
1542
|
const state = {
|
|
1403
1543
|
selectors,
|
|
1404
1544
|
_params,
|
|
@@ -1425,62 +1565,54 @@ class StableBrowser {
|
|
|
1425
1565
|
}
|
|
1426
1566
|
let foundObj = null;
|
|
1427
1567
|
try {
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
const dateAlternatives = findDateAlternatives(text);
|
|
1435
|
-
const numberAlternatives = findNumberAlternatives(text);
|
|
1436
|
-
if (dateAlternatives.date) {
|
|
1437
|
-
for (let i = 0; i < dateAlternatives.dates.length; i++) {
|
|
1438
|
-
if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
|
|
1439
|
-
foundObj?.value?.includes(dateAlternatives.dates[i])) {
|
|
1440
|
-
return state.info;
|
|
1568
|
+
while (Date.now() - startTime < timeout) {
|
|
1569
|
+
try {
|
|
1570
|
+
await _preCommand(state, this);
|
|
1571
|
+
foundObj = await this._getText(selectors, climb, _params, { timeout: 2000 }, state.info, world);
|
|
1572
|
+
if (foundObj && foundObj.element) {
|
|
1573
|
+
await this.scrollIfNeeded(foundObj.element, state.info);
|
|
1441
1574
|
}
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1575
|
+
await _screenshot(state, this);
|
|
1576
|
+
const dateAlternatives = findDateAlternatives(text);
|
|
1577
|
+
const numberAlternatives = findNumberAlternatives(text);
|
|
1578
|
+
if (dateAlternatives.date) {
|
|
1579
|
+
for (let i = 0; i < dateAlternatives.dates.length; i++) {
|
|
1580
|
+
if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
|
|
1581
|
+
foundObj?.value?.includes(dateAlternatives.dates[i])) {
|
|
1582
|
+
return state.info;
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
else if (numberAlternatives.number) {
|
|
1587
|
+
for (let i = 0; i < numberAlternatives.numbers.length; i++) {
|
|
1588
|
+
if (foundObj?.text.includes(numberAlternatives.numbers[i]) ||
|
|
1589
|
+
foundObj?.value?.includes(numberAlternatives.numbers[i])) {
|
|
1590
|
+
return state.info;
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
else if (foundObj?.text.includes(text) || foundObj?.value?.includes(text)) {
|
|
1449
1595
|
return state.info;
|
|
1450
1596
|
}
|
|
1451
1597
|
}
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
throw new Error("element doesn't contain text " + text);
|
|
1598
|
+
catch (e) {
|
|
1599
|
+
// Log error but continue retrying until timeout is reached
|
|
1600
|
+
this.logger.warn("Retrying containsText due to: " + e.message);
|
|
1601
|
+
}
|
|
1602
|
+
await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second before retrying
|
|
1458
1603
|
}
|
|
1459
|
-
|
|
1604
|
+
state.info.foundText = foundObj?.text;
|
|
1605
|
+
state.info.value = foundObj?.value;
|
|
1606
|
+
throw new Error("element doesn't contain text " + text);
|
|
1460
1607
|
}
|
|
1461
1608
|
catch (e) {
|
|
1462
1609
|
await _commandError(state, e, this);
|
|
1610
|
+
throw e;
|
|
1463
1611
|
}
|
|
1464
1612
|
finally {
|
|
1465
1613
|
_commandFinally(state, this);
|
|
1466
1614
|
}
|
|
1467
1615
|
}
|
|
1468
|
-
_getDataFile(world = null) {
|
|
1469
|
-
let dataFile = null;
|
|
1470
|
-
if (world && world.reportFolder) {
|
|
1471
|
-
dataFile = path.join(world.reportFolder, "data.json");
|
|
1472
|
-
}
|
|
1473
|
-
else if (this.reportFolder) {
|
|
1474
|
-
dataFile = path.join(this.reportFolder, "data.json");
|
|
1475
|
-
}
|
|
1476
|
-
else if (this.context && this.context.reportFolder) {
|
|
1477
|
-
dataFile = path.join(this.context.reportFolder, "data.json");
|
|
1478
|
-
}
|
|
1479
|
-
else {
|
|
1480
|
-
dataFile = "data.json";
|
|
1481
|
-
}
|
|
1482
|
-
return dataFile;
|
|
1483
|
-
}
|
|
1484
1616
|
async waitForUserInput(message, world = null) {
|
|
1485
1617
|
if (!message) {
|
|
1486
1618
|
message = "# Wait for user input. Press any key to continue";
|
|
@@ -1509,7 +1641,7 @@ class StableBrowser {
|
|
|
1509
1641
|
return;
|
|
1510
1642
|
}
|
|
1511
1643
|
// if data file exists, load it
|
|
1512
|
-
const dataFile =
|
|
1644
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1513
1645
|
let data = this.getTestData(world);
|
|
1514
1646
|
// merge the testData with the existing data
|
|
1515
1647
|
Object.assign(data, testData);
|
|
@@ -1612,7 +1744,7 @@ class StableBrowser {
|
|
|
1612
1744
|
}
|
|
1613
1745
|
}
|
|
1614
1746
|
getTestData(world = null) {
|
|
1615
|
-
const dataFile =
|
|
1747
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1616
1748
|
let data = {};
|
|
1617
1749
|
if (fs.existsSync(dataFile)) {
|
|
1618
1750
|
data = JSON.parse(fs.readFileSync(dataFile, "utf8"));
|
|
@@ -1699,6 +1831,15 @@ class StableBrowser {
|
|
|
1699
1831
|
document.documentElement.clientWidth,
|
|
1700
1832
|
])));
|
|
1701
1833
|
let screenshotBuffer = null;
|
|
1834
|
+
// if (focusedElement) {
|
|
1835
|
+
// // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
|
|
1836
|
+
// await this._unhighlightElements(focusedElement);
|
|
1837
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1838
|
+
// console.log(`Unhighlighted previous element`);
|
|
1839
|
+
// }
|
|
1840
|
+
// if (focusedElement) {
|
|
1841
|
+
// await this._highlightElements(focusedElement);
|
|
1842
|
+
// }
|
|
1702
1843
|
if (this.context.browserName === "chromium") {
|
|
1703
1844
|
const client = await playContext.newCDPSession(this.page);
|
|
1704
1845
|
const { data } = await client.send("Page.captureScreenshot", {
|
|
@@ -1720,6 +1861,10 @@ class StableBrowser {
|
|
|
1720
1861
|
else {
|
|
1721
1862
|
screenshotBuffer = await this.page.screenshot();
|
|
1722
1863
|
}
|
|
1864
|
+
// if (focusedElement) {
|
|
1865
|
+
// // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
|
|
1866
|
+
// await this._unhighlightElements(focusedElement);
|
|
1867
|
+
// }
|
|
1723
1868
|
let image = await Jimp.read(screenshotBuffer);
|
|
1724
1869
|
// Get the image dimensions
|
|
1725
1870
|
const { width, height } = image.bitmap;
|
|
@@ -1732,6 +1877,7 @@ class StableBrowser {
|
|
|
1732
1877
|
else {
|
|
1733
1878
|
fs.writeFileSync(screenshotPath, screenshotBuffer);
|
|
1734
1879
|
}
|
|
1880
|
+
return screenshotBuffer;
|
|
1735
1881
|
}
|
|
1736
1882
|
async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
|
|
1737
1883
|
const state = {
|
|
@@ -1767,8 +1913,10 @@ class StableBrowser {
|
|
|
1767
1913
|
world,
|
|
1768
1914
|
type: Types.EXTRACT,
|
|
1769
1915
|
text: `Extract attribute from element`,
|
|
1916
|
+
_text: `Extract attribute ${attribute} from ${selectors.element_name}`,
|
|
1770
1917
|
operation: "extractAttribute",
|
|
1771
1918
|
log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1919
|
+
allowDisabled: true,
|
|
1772
1920
|
};
|
|
1773
1921
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1774
1922
|
try {
|
|
@@ -1790,6 +1938,7 @@ class StableBrowser {
|
|
|
1790
1938
|
state.info.value = state.value;
|
|
1791
1939
|
this.setTestData({ [variable]: state.value }, world);
|
|
1792
1940
|
this.logger.info("set test data: " + variable + "=" + state.value);
|
|
1941
|
+
// await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1793
1942
|
return state.info;
|
|
1794
1943
|
}
|
|
1795
1944
|
catch (e) {
|
|
@@ -1808,14 +1957,21 @@ class StableBrowser {
|
|
|
1808
1957
|
options,
|
|
1809
1958
|
world,
|
|
1810
1959
|
type: Types.VERIFY_ATTRIBUTE,
|
|
1960
|
+
highlight: true,
|
|
1961
|
+
screenshot: true,
|
|
1811
1962
|
text: `Verify element attribute`,
|
|
1963
|
+
_text: `Verify attribute ${attribute} from ${selectors.element_name} is ${value}`,
|
|
1812
1964
|
operation: "verifyAttribute",
|
|
1813
1965
|
log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1966
|
+
allowDisabled: true,
|
|
1814
1967
|
};
|
|
1815
1968
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1816
1969
|
let val;
|
|
1970
|
+
let expectedValue;
|
|
1817
1971
|
try {
|
|
1818
1972
|
await _preCommand(state, this);
|
|
1973
|
+
expectedValue = state.value;
|
|
1974
|
+
state.info.expectedValue = expectedValue;
|
|
1819
1975
|
switch (attribute) {
|
|
1820
1976
|
case "innerText":
|
|
1821
1977
|
val = String(await state.element.innerText());
|
|
@@ -1829,23 +1985,30 @@ class StableBrowser {
|
|
|
1829
1985
|
case "disabled":
|
|
1830
1986
|
val = String(await state.element.isDisabled());
|
|
1831
1987
|
break;
|
|
1988
|
+
case "readOnly":
|
|
1989
|
+
const isEditable = await state.element.isEditable();
|
|
1990
|
+
val = String(!isEditable);
|
|
1991
|
+
break;
|
|
1832
1992
|
default:
|
|
1833
1993
|
val = String(await state.element.getAttribute(attribute));
|
|
1834
1994
|
break;
|
|
1835
1995
|
}
|
|
1996
|
+
state.info.value = val;
|
|
1836
1997
|
let regex;
|
|
1837
|
-
if (
|
|
1838
|
-
const patternBody =
|
|
1998
|
+
if (expectedValue.startsWith("/") && expectedValue.endsWith("/")) {
|
|
1999
|
+
const patternBody = expectedValue.slice(1, -1);
|
|
1839
2000
|
regex = new RegExp(patternBody, "g");
|
|
1840
2001
|
}
|
|
1841
2002
|
else {
|
|
1842
|
-
const escapedPattern =
|
|
2003
|
+
const escapedPattern = expectedValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1843
2004
|
regex = new RegExp(escapedPattern, "g");
|
|
1844
2005
|
}
|
|
1845
2006
|
if (!val.match(regex)) {
|
|
1846
|
-
|
|
2007
|
+
let errorMessage = `The ${attribute} attribute has a value of "${val}", but the expected value is "${expectedValue}"`;
|
|
2008
|
+
state.info.failCause.assertionFailed = true;
|
|
2009
|
+
state.info.failCause.lastError = errorMessage;
|
|
2010
|
+
throw new Error(errorMessage);
|
|
1847
2011
|
}
|
|
1848
|
-
state.info.value = val;
|
|
1849
2012
|
return state.info;
|
|
1850
2013
|
}
|
|
1851
2014
|
catch (e) {
|
|
@@ -1944,27 +2107,32 @@ class StableBrowser {
|
|
|
1944
2107
|
async _highlightElements(scope, css) {
|
|
1945
2108
|
try {
|
|
1946
2109
|
if (!scope) {
|
|
2110
|
+
// console.log(`Scope is not defined`);
|
|
1947
2111
|
return;
|
|
1948
2112
|
}
|
|
1949
2113
|
if (!css) {
|
|
1950
2114
|
scope
|
|
1951
2115
|
.evaluate((node) => {
|
|
1952
2116
|
if (node && node.style) {
|
|
1953
|
-
let
|
|
1954
|
-
|
|
2117
|
+
let originalOutline = node.style.outline;
|
|
2118
|
+
// console.log(`Original outline was: ${originalOutline}`);
|
|
2119
|
+
// node.__previousOutline = originalOutline;
|
|
2120
|
+
node.style.outline = "2px solid red";
|
|
2121
|
+
// console.log(`New outline is: ${node.style.outline}`);
|
|
1955
2122
|
if (window) {
|
|
1956
2123
|
window.addEventListener("beforeunload", function (e) {
|
|
1957
|
-
node.style.
|
|
2124
|
+
node.style.outline = originalOutline;
|
|
1958
2125
|
});
|
|
1959
2126
|
}
|
|
1960
2127
|
setTimeout(function () {
|
|
1961
|
-
node.style.
|
|
2128
|
+
node.style.outline = originalOutline;
|
|
1962
2129
|
}, 2000);
|
|
1963
2130
|
}
|
|
1964
2131
|
})
|
|
1965
2132
|
.then(() => { })
|
|
1966
2133
|
.catch((e) => {
|
|
1967
2134
|
// ignore
|
|
2135
|
+
// console.error(`Could not highlight node : ${e}`);
|
|
1968
2136
|
});
|
|
1969
2137
|
}
|
|
1970
2138
|
else {
|
|
@@ -1980,17 +2148,18 @@ class StableBrowser {
|
|
|
1980
2148
|
if (!element.style) {
|
|
1981
2149
|
return;
|
|
1982
2150
|
}
|
|
1983
|
-
|
|
2151
|
+
let originalOutline = element.style.outline;
|
|
2152
|
+
element.__previousOutline = originalOutline;
|
|
1984
2153
|
// Set the new border to be red and 2px solid
|
|
1985
|
-
element.style.
|
|
2154
|
+
element.style.outline = "2px solid red";
|
|
1986
2155
|
if (window) {
|
|
1987
2156
|
window.addEventListener("beforeunload", function (e) {
|
|
1988
|
-
element.style.
|
|
2157
|
+
element.style.outline = originalOutline;
|
|
1989
2158
|
});
|
|
1990
2159
|
}
|
|
1991
2160
|
// Set a timeout to revert to the original border after 2 seconds
|
|
1992
2161
|
setTimeout(function () {
|
|
1993
|
-
element.style.
|
|
2162
|
+
element.style.outline = originalOutline;
|
|
1994
2163
|
}, 2000);
|
|
1995
2164
|
}
|
|
1996
2165
|
return;
|
|
@@ -1998,6 +2167,7 @@ class StableBrowser {
|
|
|
1998
2167
|
.then(() => { })
|
|
1999
2168
|
.catch((e) => {
|
|
2000
2169
|
// ignore
|
|
2170
|
+
// console.error(`Could not highlight css: ${e}`);
|
|
2001
2171
|
});
|
|
2002
2172
|
}
|
|
2003
2173
|
}
|
|
@@ -2005,6 +2175,54 @@ class StableBrowser {
|
|
|
2005
2175
|
console.debug(error);
|
|
2006
2176
|
}
|
|
2007
2177
|
}
|
|
2178
|
+
// async _unhighlightElements(scope, css) {
|
|
2179
|
+
// try {
|
|
2180
|
+
// if (!scope) {
|
|
2181
|
+
// return;
|
|
2182
|
+
// }
|
|
2183
|
+
// if (!css) {
|
|
2184
|
+
// scope
|
|
2185
|
+
// .evaluate((node) => {
|
|
2186
|
+
// if (node && node.style) {
|
|
2187
|
+
// if (!node.__previousOutline) {
|
|
2188
|
+
// node.style.outline = "";
|
|
2189
|
+
// } else {
|
|
2190
|
+
// node.style.outline = node.__previousOutline;
|
|
2191
|
+
// }
|
|
2192
|
+
// }
|
|
2193
|
+
// })
|
|
2194
|
+
// .then(() => {})
|
|
2195
|
+
// .catch((e) => {
|
|
2196
|
+
// // console.log(`Error while unhighlighting node ${JSON.stringify(scope)}: ${e}`);
|
|
2197
|
+
// });
|
|
2198
|
+
// } else {
|
|
2199
|
+
// scope
|
|
2200
|
+
// .evaluate(([css]) => {
|
|
2201
|
+
// if (!css) {
|
|
2202
|
+
// return;
|
|
2203
|
+
// }
|
|
2204
|
+
// let elements = Array.from(document.querySelectorAll(css));
|
|
2205
|
+
// for (i = 0; i < elements.length; i++) {
|
|
2206
|
+
// let element = elements[i];
|
|
2207
|
+
// if (!element.style) {
|
|
2208
|
+
// return;
|
|
2209
|
+
// }
|
|
2210
|
+
// if (!element.__previousOutline) {
|
|
2211
|
+
// element.style.outline = "";
|
|
2212
|
+
// } else {
|
|
2213
|
+
// element.style.outline = element.__previousOutline;
|
|
2214
|
+
// }
|
|
2215
|
+
// }
|
|
2216
|
+
// })
|
|
2217
|
+
// .then(() => {})
|
|
2218
|
+
// .catch((e) => {
|
|
2219
|
+
// // console.error(`Error while unhighlighting element in css: ${e}`);
|
|
2220
|
+
// });
|
|
2221
|
+
// }
|
|
2222
|
+
// } catch (error) {
|
|
2223
|
+
// // console.debug(error);
|
|
2224
|
+
// }
|
|
2225
|
+
// }
|
|
2008
2226
|
async verifyPagePath(pathPart, options = {}, world = null) {
|
|
2009
2227
|
const startTime = Date.now();
|
|
2010
2228
|
let error = null;
|
|
@@ -2049,6 +2267,7 @@ class StableBrowser {
|
|
|
2049
2267
|
_reportToWorld(world, {
|
|
2050
2268
|
type: Types.VERIFY_PAGE_PATH,
|
|
2051
2269
|
text: "Verify page path",
|
|
2270
|
+
_text: "Verify the page path contains " + pathPart,
|
|
2052
2271
|
screenshotId,
|
|
2053
2272
|
result: error
|
|
2054
2273
|
? {
|
|
@@ -2066,26 +2285,27 @@ class StableBrowser {
|
|
|
2066
2285
|
});
|
|
2067
2286
|
}
|
|
2068
2287
|
}
|
|
2069
|
-
async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state) {
|
|
2288
|
+
async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state, partial = true, ignoreCase = false) {
|
|
2070
2289
|
const frames = this.page.frames();
|
|
2071
2290
|
let results = [];
|
|
2291
|
+
// let ignoreCase = false;
|
|
2072
2292
|
for (let i = 0; i < frames.length; i++) {
|
|
2073
2293
|
if (dateAlternatives.date) {
|
|
2074
2294
|
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2075
|
-
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false,
|
|
2295
|
+
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, partial, ignoreCase, {});
|
|
2076
2296
|
result.frame = frames[i];
|
|
2077
2297
|
results.push(result);
|
|
2078
2298
|
}
|
|
2079
2299
|
}
|
|
2080
2300
|
else if (numberAlternatives.number) {
|
|
2081
2301
|
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2082
|
-
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false,
|
|
2302
|
+
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, partial, ignoreCase, {});
|
|
2083
2303
|
result.frame = frames[i];
|
|
2084
2304
|
results.push(result);
|
|
2085
2305
|
}
|
|
2086
2306
|
}
|
|
2087
2307
|
else {
|
|
2088
|
-
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false,
|
|
2308
|
+
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, partial, ignoreCase, {});
|
|
2089
2309
|
result.frame = frames[i];
|
|
2090
2310
|
results.push(result);
|
|
2091
2311
|
}
|
|
@@ -2104,11 +2324,15 @@ class StableBrowser {
|
|
|
2104
2324
|
scroll: false,
|
|
2105
2325
|
highlight: false,
|
|
2106
2326
|
type: Types.VERIFY_PAGE_CONTAINS_TEXT,
|
|
2107
|
-
text: `Verify text exists in page`,
|
|
2327
|
+
text: `Verify the text '${text}' exists in page`,
|
|
2328
|
+
_text: `Verify the text '${text}' exists in page`,
|
|
2108
2329
|
operation: "verifyTextExistInPage",
|
|
2109
2330
|
log: "***** verify text " + text + " exists in page *****\n",
|
|
2110
2331
|
};
|
|
2111
|
-
|
|
2332
|
+
if (testForRegex(text)) {
|
|
2333
|
+
text = text.replace(/\\"/g, '"');
|
|
2334
|
+
}
|
|
2335
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2112
2336
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2113
2337
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2114
2338
|
if (newValue !== text) {
|
|
@@ -2121,7 +2345,15 @@ class StableBrowser {
|
|
|
2121
2345
|
await _preCommand(state, this);
|
|
2122
2346
|
state.info.text = text;
|
|
2123
2347
|
while (true) {
|
|
2124
|
-
|
|
2348
|
+
let resultWithElementsFound = {
|
|
2349
|
+
length: 0,
|
|
2350
|
+
};
|
|
2351
|
+
try {
|
|
2352
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2353
|
+
}
|
|
2354
|
+
catch (error) {
|
|
2355
|
+
// ignore
|
|
2356
|
+
}
|
|
2125
2357
|
if (resultWithElementsFound.length === 0) {
|
|
2126
2358
|
if (Date.now() - state.startTime > timeout) {
|
|
2127
2359
|
throw new Error(`Text ${text} not found in page`);
|
|
@@ -2129,18 +2361,40 @@ class StableBrowser {
|
|
|
2129
2361
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2130
2362
|
continue;
|
|
2131
2363
|
}
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2364
|
+
try {
|
|
2365
|
+
if (resultWithElementsFound[0].randomToken) {
|
|
2366
|
+
const frame = resultWithElementsFound[0].frame;
|
|
2367
|
+
const dataAttribute = `[data-blinq-id-${resultWithElementsFound[0].randomToken}]`;
|
|
2368
|
+
await this._highlightElements(frame, dataAttribute);
|
|
2369
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
2370
|
+
// console.log(`Highlighting for verify text is found while running from recorder`);
|
|
2371
|
+
// this._highlightElements(frame, dataAttribute).then(async () => {
|
|
2372
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2373
|
+
// this._unhighlightElements(frame, dataAttribute)
|
|
2374
|
+
// .then(async () => {
|
|
2375
|
+
// console.log(`Unhighlighted frame dataAttribute successfully`);
|
|
2376
|
+
// })
|
|
2377
|
+
// .catch(
|
|
2378
|
+
// (e) => {}
|
|
2379
|
+
// console.error(e)
|
|
2380
|
+
// );
|
|
2381
|
+
// });
|
|
2382
|
+
// }
|
|
2383
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2384
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2385
|
+
// await this._unhighlightElements(frame, dataAttribute);
|
|
2386
|
+
if (element) {
|
|
2387
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2388
|
+
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2389
|
+
// await _screenshot(state, this, element);
|
|
2390
|
+
}
|
|
2140
2391
|
}
|
|
2392
|
+
await _screenshot(state, this);
|
|
2393
|
+
return state.info;
|
|
2394
|
+
}
|
|
2395
|
+
catch (error) {
|
|
2396
|
+
console.error(error);
|
|
2141
2397
|
}
|
|
2142
|
-
await _screenshot(state, this);
|
|
2143
|
-
return state.info;
|
|
2144
2398
|
}
|
|
2145
2399
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2146
2400
|
}
|
|
@@ -2161,11 +2415,15 @@ class StableBrowser {
|
|
|
2161
2415
|
scroll: false,
|
|
2162
2416
|
highlight: false,
|
|
2163
2417
|
type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
|
|
2164
|
-
text: `Verify text does not exist in page`,
|
|
2418
|
+
text: `Verify the text '${text}' does not exist in page`,
|
|
2419
|
+
_text: `Verify the text '${text}' does not exist in page`,
|
|
2165
2420
|
operation: "verifyTextNotExistInPage",
|
|
2166
2421
|
log: "***** verify text " + text + " does not exist in page *****\n",
|
|
2167
2422
|
};
|
|
2168
|
-
|
|
2423
|
+
if (testForRegex(text)) {
|
|
2424
|
+
text = text.replace(/\\"/g, '"');
|
|
2425
|
+
}
|
|
2426
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2169
2427
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2170
2428
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2171
2429
|
if (newValue !== text) {
|
|
@@ -2177,8 +2435,16 @@ class StableBrowser {
|
|
|
2177
2435
|
try {
|
|
2178
2436
|
await _preCommand(state, this);
|
|
2179
2437
|
state.info.text = text;
|
|
2438
|
+
let resultWithElementsFound = {
|
|
2439
|
+
length: null, // initial cannot be 0
|
|
2440
|
+
};
|
|
2180
2441
|
while (true) {
|
|
2181
|
-
|
|
2442
|
+
try {
|
|
2443
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2444
|
+
}
|
|
2445
|
+
catch (error) {
|
|
2446
|
+
// ignore
|
|
2447
|
+
}
|
|
2182
2448
|
if (resultWithElementsFound.length === 0) {
|
|
2183
2449
|
await _screenshot(state, this);
|
|
2184
2450
|
return state.info;
|
|
@@ -2208,10 +2474,11 @@ class StableBrowser {
|
|
|
2208
2474
|
highlight: false,
|
|
2209
2475
|
type: Types.VERIFY_TEXT_WITH_RELATION,
|
|
2210
2476
|
text: `Verify text with relation to another text`,
|
|
2477
|
+
_text: "Search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found",
|
|
2211
2478
|
operation: "verify_text_with_relation",
|
|
2212
2479
|
log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
|
|
2213
2480
|
};
|
|
2214
|
-
const timeout = this.
|
|
2481
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2215
2482
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2216
2483
|
let newValue = await this._replaceWithLocalData(textAnchor, world);
|
|
2217
2484
|
if (newValue !== textAnchor) {
|
|
@@ -2229,8 +2496,16 @@ class StableBrowser {
|
|
|
2229
2496
|
try {
|
|
2230
2497
|
await _preCommand(state, this);
|
|
2231
2498
|
state.info.text = textToVerify;
|
|
2499
|
+
let resultWithElementsFound = {
|
|
2500
|
+
length: 0,
|
|
2501
|
+
};
|
|
2232
2502
|
while (true) {
|
|
2233
|
-
|
|
2503
|
+
try {
|
|
2504
|
+
resultWithElementsFound = await this.findTextInAllFrames(findDateAlternatives(textAnchor), findNumberAlternatives(textAnchor), textAnchor, state, false);
|
|
2505
|
+
}
|
|
2506
|
+
catch (error) {
|
|
2507
|
+
// ignore
|
|
2508
|
+
}
|
|
2234
2509
|
if (resultWithElementsFound.length === 0) {
|
|
2235
2510
|
if (Date.now() - state.startTime > timeout) {
|
|
2236
2511
|
throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
|
|
@@ -2238,51 +2513,56 @@ class StableBrowser {
|
|
|
2238
2513
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2239
2514
|
continue;
|
|
2240
2515
|
}
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
const
|
|
2249
|
-
for (let i = 0; i <
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2516
|
+
try {
|
|
2517
|
+
for (let i = 0; i < resultWithElementsFound.length; i++) {
|
|
2518
|
+
foundAncore = true;
|
|
2519
|
+
const result = resultWithElementsFound[i];
|
|
2520
|
+
const token = result.randomToken;
|
|
2521
|
+
const frame = result.frame;
|
|
2522
|
+
let css = `[data-blinq-id-${token}]`;
|
|
2523
|
+
const climbArray1 = [];
|
|
2524
|
+
for (let i = 0; i < climb; i++) {
|
|
2525
|
+
climbArray1.push("..");
|
|
2526
|
+
}
|
|
2527
|
+
let climbXpath = "xpath=" + climbArray1.join("/");
|
|
2528
|
+
css = css + " >> " + climbXpath;
|
|
2529
|
+
const count = await frame.locator(css).count();
|
|
2530
|
+
for (let j = 0; j < count; j++) {
|
|
2531
|
+
const continer = await frame.locator(css).nth(j);
|
|
2532
|
+
const result = await this._locateElementByText(continer, textToVerify, "*:not(script, style, head)", false, false, true, {});
|
|
2533
|
+
if (result.elementCount > 0) {
|
|
2534
|
+
const dataAttribute = "[data-blinq-id-" + result.randomToken + "]";
|
|
2535
|
+
await this._highlightElements(frame, dataAttribute);
|
|
2536
|
+
//const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
|
|
2537
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
2538
|
+
// console.log(`Highlighting for vtrt while running from recorder`);
|
|
2539
|
+
// this._highlightElements(frame, dataAttribute)
|
|
2540
|
+
// .then(async () => {
|
|
2541
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2542
|
+
// this._unhighlightElements(frame, dataAttribute).then(
|
|
2543
|
+
// () => {}
|
|
2544
|
+
// console.log(`Unhighlighting vrtr in recorder is successful`)
|
|
2545
|
+
// );
|
|
2546
|
+
// })
|
|
2547
|
+
// .catch(e);
|
|
2548
|
+
// }
|
|
2549
|
+
//await this._highlightElements(frame, cssAnchor);
|
|
2550
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2551
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2552
|
+
// await this._unhighlightElements(frame, dataAttribute);
|
|
2553
|
+
if (element) {
|
|
2554
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2555
|
+
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2256
2556
|
}
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
continue;
|
|
2260
|
-
}
|
|
2261
|
-
const foundElements = window.findMatchingElements(textToVerify, {}, climbParent);
|
|
2262
|
-
if (foundElements.length > 0) {
|
|
2263
|
-
// set the container element attribute
|
|
2264
|
-
element.setAttribute("data-blinq-id", `blinq-id-${token}-anchor`);
|
|
2265
|
-
climbParent.setAttribute("data-blinq-id", `blinq-id-${token}-container`);
|
|
2266
|
-
foundElements[0].setAttribute("data-blinq-id", `blinq-id-${token}-verify`);
|
|
2267
|
-
return { found: true };
|
|
2557
|
+
await _screenshot(state, this);
|
|
2558
|
+
return state.info;
|
|
2268
2559
|
}
|
|
2269
2560
|
}
|
|
2270
|
-
return { found: false };
|
|
2271
|
-
}, [css, climb, textToVerify, result.randomToken]);
|
|
2272
|
-
if (findResult.found === true) {
|
|
2273
|
-
const dataAttribute = `[data-blinq-id="blinq-id-${token}-verify"]`;
|
|
2274
|
-
const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
|
|
2275
|
-
await this._highlightElements(frame, dataAttribute);
|
|
2276
|
-
await this._highlightElements(frame, cssAnchor);
|
|
2277
|
-
const element = await frame.$(dataAttribute);
|
|
2278
|
-
if (element) {
|
|
2279
|
-
await this.scrollIfNeeded(element, state.info);
|
|
2280
|
-
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2281
|
-
}
|
|
2282
|
-
await _screenshot(state, this);
|
|
2283
|
-
return state.info;
|
|
2284
2561
|
}
|
|
2285
2562
|
}
|
|
2563
|
+
catch (error) {
|
|
2564
|
+
console.error(error);
|
|
2565
|
+
}
|
|
2286
2566
|
}
|
|
2287
2567
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2288
2568
|
}
|
|
@@ -2293,6 +2573,30 @@ class StableBrowser {
|
|
|
2293
2573
|
_commandFinally(state, this);
|
|
2294
2574
|
}
|
|
2295
2575
|
}
|
|
2576
|
+
async findRelatedTextInAllFrames(textAnchor, climb, textToVerify, params = {}, options = {}, world = null) {
|
|
2577
|
+
const frames = this.page.frames();
|
|
2578
|
+
let results = [];
|
|
2579
|
+
let ignoreCase = false;
|
|
2580
|
+
for (let i = 0; i < frames.length; i++) {
|
|
2581
|
+
const result = await this._locateElementByText(frames[i], textAnchor, "*:not(script, style, head)", false, true, ignoreCase, {});
|
|
2582
|
+
result.frame = frames[i];
|
|
2583
|
+
const climbArray = [];
|
|
2584
|
+
for (let i = 0; i < climb; i++) {
|
|
2585
|
+
climbArray.push("..");
|
|
2586
|
+
}
|
|
2587
|
+
let climbXpath = "xpath=" + climbArray.join("/");
|
|
2588
|
+
const newLocator = `[data-blinq-id-${result.randomToken}] ${climb > 0 ? ">> " + climbXpath : ""} >> internal:text=${testForRegex(textToVerify) ? textToVerify : unEscapeString(textToVerify)}`;
|
|
2589
|
+
const count = await frames[i].locator(newLocator).count();
|
|
2590
|
+
if (count > 0) {
|
|
2591
|
+
result.elementCount = count;
|
|
2592
|
+
result.locator = newLocator;
|
|
2593
|
+
results.push(result);
|
|
2594
|
+
}
|
|
2595
|
+
}
|
|
2596
|
+
// state.info.results = results;
|
|
2597
|
+
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2598
|
+
return resultWithElementsFound;
|
|
2599
|
+
}
|
|
2296
2600
|
async visualVerification(text, options = {}, world = null) {
|
|
2297
2601
|
const startTime = Date.now();
|
|
2298
2602
|
let error = null;
|
|
@@ -2311,10 +2615,13 @@ class StableBrowser {
|
|
|
2311
2615
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2312
2616
|
info.screenshotPath = screenshotPath;
|
|
2313
2617
|
const screenshot = await this.takeScreenshot();
|
|
2314
|
-
|
|
2315
|
-
method: "
|
|
2618
|
+
let request = {
|
|
2619
|
+
method: "post",
|
|
2620
|
+
maxBodyLength: Infinity,
|
|
2316
2621
|
url: `${serviceUrl}/api/runs/screenshots/validate-screenshot`,
|
|
2317
2622
|
headers: {
|
|
2623
|
+
"x-bvt-project-id": path.basename(this.project_path),
|
|
2624
|
+
"x-source": "aaa",
|
|
2318
2625
|
"Content-Type": "application/json",
|
|
2319
2626
|
Authorization: `Bearer ${process.env.TOKEN}`,
|
|
2320
2627
|
},
|
|
@@ -2323,7 +2630,7 @@ class StableBrowser {
|
|
|
2323
2630
|
screenshot: screenshot,
|
|
2324
2631
|
}),
|
|
2325
2632
|
};
|
|
2326
|
-
|
|
2633
|
+
const result = await axios.request(request);
|
|
2327
2634
|
if (result.data.status !== true) {
|
|
2328
2635
|
throw new Error("Visual validation failed");
|
|
2329
2636
|
}
|
|
@@ -2351,6 +2658,7 @@ class StableBrowser {
|
|
|
2351
2658
|
_reportToWorld(world, {
|
|
2352
2659
|
type: Types.VERIFY_VISUAL,
|
|
2353
2660
|
text: "Visual verification",
|
|
2661
|
+
_text: "Visual verification of " + text,
|
|
2354
2662
|
screenshotId,
|
|
2355
2663
|
result: error
|
|
2356
2664
|
? {
|
|
@@ -2617,6 +2925,32 @@ class StableBrowser {
|
|
|
2617
2925
|
}
|
|
2618
2926
|
return timeout;
|
|
2619
2927
|
}
|
|
2928
|
+
_getFindElementTimeout(options) {
|
|
2929
|
+
if (options && options.timeout) {
|
|
2930
|
+
return options.timeout;
|
|
2931
|
+
}
|
|
2932
|
+
if (this.configuration.find_element_timeout) {
|
|
2933
|
+
return this.configuration.find_element_timeout;
|
|
2934
|
+
}
|
|
2935
|
+
return 30000;
|
|
2936
|
+
}
|
|
2937
|
+
async saveStoreState(path = null, world = null) {
|
|
2938
|
+
const storageState = await this.page.context().storageState();
|
|
2939
|
+
//const testDataFile = _getDataFile(world, this.context, this);
|
|
2940
|
+
if (path) {
|
|
2941
|
+
// save { storageState: storageState } into the path
|
|
2942
|
+
fs.writeFileSync(path, JSON.stringify({ storageState: storageState }, null, 2));
|
|
2943
|
+
}
|
|
2944
|
+
else {
|
|
2945
|
+
await this.setTestData({ storageState: storageState }, world);
|
|
2946
|
+
}
|
|
2947
|
+
}
|
|
2948
|
+
async restoreSaveState(path = null, world = null) {
|
|
2949
|
+
await refreshBrowser(this, path, world);
|
|
2950
|
+
this.registerEventListeners(this.context);
|
|
2951
|
+
registerNetworkEvents(this.world, this, this.context, this.page);
|
|
2952
|
+
registerDownloadEvent(this.page, this.world, this.context);
|
|
2953
|
+
}
|
|
2620
2954
|
async waitForPageLoad(options = {}, world = null) {
|
|
2621
2955
|
let timeout = this._getLoadTimeout(options);
|
|
2622
2956
|
const promiseArray = [];
|
|
@@ -2684,6 +3018,7 @@ class StableBrowser {
|
|
|
2684
3018
|
highlight: false,
|
|
2685
3019
|
type: Types.CLOSE_PAGE,
|
|
2686
3020
|
text: `Close page`,
|
|
3021
|
+
_text: `Close the page`,
|
|
2687
3022
|
operation: "closePage",
|
|
2688
3023
|
log: "***** close page *****\n",
|
|
2689
3024
|
throwError: false,
|
|
@@ -2700,8 +3035,95 @@ class StableBrowser {
|
|
|
2700
3035
|
_commandFinally(state, this);
|
|
2701
3036
|
}
|
|
2702
3037
|
}
|
|
3038
|
+
async tableCellOperation(headerText, rowText, options, _params, world = null) {
|
|
3039
|
+
let operation = null;
|
|
3040
|
+
if (!options || !options.operation) {
|
|
3041
|
+
throw new Error("operation is not defined");
|
|
3042
|
+
}
|
|
3043
|
+
operation = options.operation;
|
|
3044
|
+
// validate operation is one of the supported operations
|
|
3045
|
+
if (operation != "click" && operation != "hover+click") {
|
|
3046
|
+
throw new Error("operation is not supported");
|
|
3047
|
+
}
|
|
3048
|
+
const state = {
|
|
3049
|
+
options,
|
|
3050
|
+
world,
|
|
3051
|
+
locate: false,
|
|
3052
|
+
scroll: false,
|
|
3053
|
+
highlight: false,
|
|
3054
|
+
type: Types.TABLE_OPERATION,
|
|
3055
|
+
text: `Table operation`,
|
|
3056
|
+
_text: `Table ${operation} operation`,
|
|
3057
|
+
operation: operation,
|
|
3058
|
+
log: "***** Table operation *****\n",
|
|
3059
|
+
};
|
|
3060
|
+
const timeout = this._getFindElementTimeout(options);
|
|
3061
|
+
try {
|
|
3062
|
+
await _preCommand(state, this);
|
|
3063
|
+
const start = Date.now();
|
|
3064
|
+
let cellArea = null;
|
|
3065
|
+
while (true) {
|
|
3066
|
+
try {
|
|
3067
|
+
cellArea = await _findCellArea(headerText, rowText, this, state);
|
|
3068
|
+
if (cellArea) {
|
|
3069
|
+
break;
|
|
3070
|
+
}
|
|
3071
|
+
}
|
|
3072
|
+
catch (e) {
|
|
3073
|
+
// ignore
|
|
3074
|
+
}
|
|
3075
|
+
if (Date.now() - start > timeout) {
|
|
3076
|
+
throw new Error(`Cell not found in table`);
|
|
3077
|
+
}
|
|
3078
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
3079
|
+
}
|
|
3080
|
+
switch (operation) {
|
|
3081
|
+
case "click":
|
|
3082
|
+
if (!options.css) {
|
|
3083
|
+
// will click in the center of the cell
|
|
3084
|
+
let xOffset = 0;
|
|
3085
|
+
let yOffset = 0;
|
|
3086
|
+
if (options.xOffset) {
|
|
3087
|
+
xOffset = options.xOffset;
|
|
3088
|
+
}
|
|
3089
|
+
if (options.yOffset) {
|
|
3090
|
+
yOffset = options.yOffset;
|
|
3091
|
+
}
|
|
3092
|
+
await this.page.mouse.click(cellArea.x + cellArea.width / 2 + xOffset, cellArea.y + cellArea.height / 2 + yOffset);
|
|
3093
|
+
}
|
|
3094
|
+
else {
|
|
3095
|
+
const results = await findElementsInArea(options.css, cellArea, this, options);
|
|
3096
|
+
if (results.length === 0) {
|
|
3097
|
+
throw new Error(`Element not found in cell area`);
|
|
3098
|
+
}
|
|
3099
|
+
state.element = results[0];
|
|
3100
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
3101
|
+
}
|
|
3102
|
+
break;
|
|
3103
|
+
case "hover+click":
|
|
3104
|
+
if (!options.css) {
|
|
3105
|
+
throw new Error("css is not defined");
|
|
3106
|
+
}
|
|
3107
|
+
const results = await findElementsInArea(options.css, cellArea, this, options);
|
|
3108
|
+
if (results.length === 0) {
|
|
3109
|
+
throw new Error(`Element not found in cell area`);
|
|
3110
|
+
}
|
|
3111
|
+
state.element = results[0];
|
|
3112
|
+
await performAction("hover+click", state.element, options, this, state, _params);
|
|
3113
|
+
break;
|
|
3114
|
+
default:
|
|
3115
|
+
throw new Error("operation is not supported");
|
|
3116
|
+
}
|
|
3117
|
+
}
|
|
3118
|
+
catch (e) {
|
|
3119
|
+
await _commandError(state, e, this);
|
|
3120
|
+
}
|
|
3121
|
+
finally {
|
|
3122
|
+
_commandFinally(state, this);
|
|
3123
|
+
}
|
|
3124
|
+
}
|
|
2703
3125
|
saveTestDataAsGlobal(options, world) {
|
|
2704
|
-
const dataFile =
|
|
3126
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
2705
3127
|
process.env.GLOBAL_TEST_DATA_FILE = dataFile;
|
|
2706
3128
|
this.logger.info("Save the scenario test data as global for the following scenarios.");
|
|
2707
3129
|
}
|
|
@@ -2731,6 +3153,7 @@ class StableBrowser {
|
|
|
2731
3153
|
_reportToWorld(world, {
|
|
2732
3154
|
type: Types.SET_VIEWPORT,
|
|
2733
3155
|
text: "set viewport size to " + width + "x" + hight,
|
|
3156
|
+
_text: "Set the viewport size to " + width + "x" + hight,
|
|
2734
3157
|
screenshotId,
|
|
2735
3158
|
result: error
|
|
2736
3159
|
? {
|
|
@@ -2802,14 +3225,25 @@ class StableBrowser {
|
|
|
2802
3225
|
}
|
|
2803
3226
|
}
|
|
2804
3227
|
async beforeStep(world, step) {
|
|
2805
|
-
this.stepName = step.pickleStep.text;
|
|
2806
|
-
this.logger.info("step: " + this.stepName);
|
|
2807
3228
|
if (this.stepIndex === undefined) {
|
|
2808
3229
|
this.stepIndex = 0;
|
|
2809
3230
|
}
|
|
2810
3231
|
else {
|
|
2811
3232
|
this.stepIndex++;
|
|
2812
3233
|
}
|
|
3234
|
+
if (step && step.pickleStep && step.pickleStep.text) {
|
|
3235
|
+
this.stepName = step.pickleStep.text;
|
|
3236
|
+
this.logger.info("step: " + this.stepName);
|
|
3237
|
+
}
|
|
3238
|
+
else if (step && step.text) {
|
|
3239
|
+
this.stepName = step.text;
|
|
3240
|
+
}
|
|
3241
|
+
else {
|
|
3242
|
+
this.stepName = "step " + this.stepIndex;
|
|
3243
|
+
}
|
|
3244
|
+
if (this.context) {
|
|
3245
|
+
this.context.examplesRow = extractStepExampleParameters(step);
|
|
3246
|
+
}
|
|
2813
3247
|
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
2814
3248
|
if (this.context.browserObject.context) {
|
|
2815
3249
|
await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
|
|
@@ -2822,6 +3256,41 @@ class StableBrowser {
|
|
|
2822
3256
|
this.saveTestDataAsGlobal({}, world);
|
|
2823
3257
|
}
|
|
2824
3258
|
}
|
|
3259
|
+
if (this.initSnapshotTaken === false) {
|
|
3260
|
+
this.initSnapshotTaken = true;
|
|
3261
|
+
if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
|
|
3262
|
+
const snapshot = await this.getAriaSnapshot();
|
|
3263
|
+
if (snapshot) {
|
|
3264
|
+
await world.attach(JSON.stringify(snapshot), "application/json+snapshot-before");
|
|
3265
|
+
}
|
|
3266
|
+
}
|
|
3267
|
+
}
|
|
3268
|
+
}
|
|
3269
|
+
async getAriaSnapshot() {
|
|
3270
|
+
try {
|
|
3271
|
+
// find the page url
|
|
3272
|
+
const url = await this.page.url();
|
|
3273
|
+
// extract the path from the url
|
|
3274
|
+
const path = new URL(url).pathname;
|
|
3275
|
+
// get the page title
|
|
3276
|
+
const title = await this.page.title();
|
|
3277
|
+
// go over other frams
|
|
3278
|
+
const frames = this.page.frames();
|
|
3279
|
+
const snapshots = [];
|
|
3280
|
+
const content = [`- path: ${path}`, `- title: ${title}`];
|
|
3281
|
+
const timeout = this.configuration.ariaSnapshotTimeout ? this.configuration.ariaSnapshotTimeout : 3000;
|
|
3282
|
+
for (let i = 0; i < frames.length; i++) {
|
|
3283
|
+
content.push(`- frame: ${i}`);
|
|
3284
|
+
const frame = frames[i];
|
|
3285
|
+
const snapshot = await frame.locator("body").ariaSnapshot({ timeout });
|
|
3286
|
+
content.push(snapshot);
|
|
3287
|
+
}
|
|
3288
|
+
return content.join("\n");
|
|
3289
|
+
}
|
|
3290
|
+
catch (e) {
|
|
3291
|
+
console.error(e);
|
|
3292
|
+
}
|
|
3293
|
+
return null;
|
|
2825
3294
|
}
|
|
2826
3295
|
async afterStep(world, step) {
|
|
2827
3296
|
this.stepName = null;
|
|
@@ -2832,6 +3301,16 @@ class StableBrowser {
|
|
|
2832
3301
|
});
|
|
2833
3302
|
}
|
|
2834
3303
|
}
|
|
3304
|
+
if (this.context) {
|
|
3305
|
+
this.context.examplesRow = null;
|
|
3306
|
+
}
|
|
3307
|
+
if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
|
|
3308
|
+
const snapshot = await this.getAriaSnapshot();
|
|
3309
|
+
if (snapshot) {
|
|
3310
|
+
const obj = {};
|
|
3311
|
+
await world.attach(JSON.stringify(snapshot), "application/json+snapshot-after");
|
|
3312
|
+
}
|
|
3313
|
+
}
|
|
2835
3314
|
}
|
|
2836
3315
|
}
|
|
2837
3316
|
function createTimedPromise(promise, label) {
|