automation_model 1.0.581-dev → 1.0.581-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 +33 -19
- package/lib/api.js.map +1 -1
- package/lib/auto_page.d.ts +4 -2
- package/lib/auto_page.js +108 -8
- package/lib/auto_page.js.map +1 -1
- package/lib/browser_manager.js +53 -16
- package/lib/browser_manager.js.map +1 -1
- package/lib/bruno.d.ts +1 -0
- package/lib/bruno.js +241 -0
- package/lib/bruno.js.map +1 -0
- package/lib/command_common.d.ts +4 -4
- package/lib/command_common.js +43 -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/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/index.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 +45 -19
- package/lib/network.js.map +1 -1
- package/lib/stable_browser.d.ts +21 -6
- package/lib/stable_browser.js +726 -181
- package/lib/stable_browser.js.map +1 -1
- package/lib/table_helper.d.ts +19 -0
- package/lib/table_helper.js +116 -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 +9 -3
- package/lib/utils.js +328 -24
- package/lib/utils.js.map +1 -1
- package/package.json +8 -8
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 { _convertToRegexQuery, _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",
|
|
@@ -51,8 +55,12 @@ export const Types = {
|
|
|
51
55
|
WAIT_FOR_TEXT_TO_DISAPPEAR: "wait_for_text_to_disappear",
|
|
52
56
|
VERIFY_ATTRIBUTE: "verify_element_attribute",
|
|
53
57
|
VERIFY_TEXT_WITH_RELATION: "verify_text_with_relation",
|
|
58
|
+
BRUNO: "bruno",
|
|
54
59
|
};
|
|
55
60
|
export const apps = {};
|
|
61
|
+
const formatElementName = (elementName) => {
|
|
62
|
+
return elementName ? JSON.stringify(elementName) : "element";
|
|
63
|
+
};
|
|
56
64
|
class StableBrowser {
|
|
57
65
|
browser;
|
|
58
66
|
page;
|
|
@@ -66,6 +74,7 @@ class StableBrowser {
|
|
|
66
74
|
appName = "main";
|
|
67
75
|
tags = null;
|
|
68
76
|
isRecording = false;
|
|
77
|
+
initSnapshotTaken = false;
|
|
69
78
|
constructor(browser, page, logger = null, context = null, world = null) {
|
|
70
79
|
this.browser = browser;
|
|
71
80
|
this.page = page;
|
|
@@ -236,6 +245,9 @@ class StableBrowser {
|
|
|
236
245
|
// await closeUnexpectedPopups(this.page);
|
|
237
246
|
// }
|
|
238
247
|
async goto(url, world = null) {
|
|
248
|
+
if (!url) {
|
|
249
|
+
throw new Error("url is null, verify that the environment file is correct");
|
|
250
|
+
}
|
|
239
251
|
if (!url.startsWith("http")) {
|
|
240
252
|
url = "https://" + url;
|
|
241
253
|
}
|
|
@@ -331,7 +343,7 @@ class StableBrowser {
|
|
|
331
343
|
if (result.elementCount === 0) {
|
|
332
344
|
return;
|
|
333
345
|
}
|
|
334
|
-
let textElementCss = "[data-blinq-id
|
|
346
|
+
let textElementCss = "[data-blinq-id-" + result.randomToken + "]";
|
|
335
347
|
// css climb to parent element
|
|
336
348
|
const climbArray = [];
|
|
337
349
|
for (let i = 0; i < climb; i++) {
|
|
@@ -345,9 +357,12 @@ class StableBrowser {
|
|
|
345
357
|
return resultCss;
|
|
346
358
|
}
|
|
347
359
|
async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, ignoreCase = true, _params) {
|
|
348
|
-
const query = _convertToRegexQuery(text1, regex1, !partial1, ignoreCase)
|
|
360
|
+
const query = `${_convertToRegexQuery(text1, regex1, !partial1, ignoreCase)}`;
|
|
349
361
|
const locator = scope.locator(query);
|
|
350
362
|
const count = await locator.count();
|
|
363
|
+
if (!tag1) {
|
|
364
|
+
tag1 = "*";
|
|
365
|
+
}
|
|
351
366
|
const randomToken = Math.random().toString(36).substring(7);
|
|
352
367
|
let tagCount = 0;
|
|
353
368
|
for (let i = 0; i < count; i++) {
|
|
@@ -362,7 +377,13 @@ class StableBrowser {
|
|
|
362
377
|
if (!el.setAttribute) {
|
|
363
378
|
el = el.parentElement;
|
|
364
379
|
}
|
|
365
|
-
|
|
380
|
+
// remove any attributes start with data-blinq-id
|
|
381
|
+
// for (let i = 0; i < el.attributes.length; i++) {
|
|
382
|
+
// if (el.attributes[i].name.startsWith("data-blinq-id")) {
|
|
383
|
+
// el.removeAttribute(el.attributes[i].name);
|
|
384
|
+
// }
|
|
385
|
+
// }
|
|
386
|
+
el.setAttribute("data-blinq-id-" + randomToken, "");
|
|
366
387
|
return true;
|
|
367
388
|
}, [tag1, randomToken]))) {
|
|
368
389
|
continue;
|
|
@@ -371,7 +392,7 @@ class StableBrowser {
|
|
|
371
392
|
}
|
|
372
393
|
return { elementCount: tagCount, randomToken };
|
|
373
394
|
}
|
|
374
|
-
async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true) {
|
|
395
|
+
async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true, allowDisabled = false, element_name = null) {
|
|
375
396
|
if (!info) {
|
|
376
397
|
info = {};
|
|
377
398
|
}
|
|
@@ -394,10 +415,11 @@ class StableBrowser {
|
|
|
394
415
|
//info.log += "searching for locator " + JSON.stringify(locatorSearch) + "\n";
|
|
395
416
|
let locator = null;
|
|
396
417
|
if (locatorSearch.climb && locatorSearch.climb >= 0) {
|
|
397
|
-
|
|
418
|
+
const replacedText = await this._replaceWithLocalData(locatorSearch.text, this.world);
|
|
419
|
+
let locatorString = await this._locateElmentByTextClimbCss(scope, replacedText, locatorSearch.climb, locatorSearch.css, _params);
|
|
398
420
|
if (!locatorString) {
|
|
399
421
|
info.failCause.textNotFound = true;
|
|
400
|
-
info.failCause.lastError =
|
|
422
|
+
info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${locatorSearch.text}`;
|
|
401
423
|
return;
|
|
402
424
|
}
|
|
403
425
|
locator = await this._getLocator({ css: locatorString }, scope, _params);
|
|
@@ -407,10 +429,10 @@ class StableBrowser {
|
|
|
407
429
|
let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, true, _params);
|
|
408
430
|
if (result.elementCount === 0) {
|
|
409
431
|
info.failCause.textNotFound = true;
|
|
410
|
-
info.failCause.lastError =
|
|
432
|
+
info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${text}`;
|
|
411
433
|
return;
|
|
412
434
|
}
|
|
413
|
-
locatorSearch.css = "[data-blinq-id
|
|
435
|
+
locatorSearch.css = "[data-blinq-id-" + result.randomToken + "]";
|
|
414
436
|
if (locatorSearch.childCss) {
|
|
415
437
|
locatorSearch.css = locatorSearch.css + " " + locatorSearch.childCss;
|
|
416
438
|
}
|
|
@@ -446,7 +468,7 @@ class StableBrowser {
|
|
|
446
468
|
if (!visibleOnly) {
|
|
447
469
|
visible = true;
|
|
448
470
|
}
|
|
449
|
-
if (visible && enabled) {
|
|
471
|
+
if (visible && (allowDisabled || enabled)) {
|
|
450
472
|
foundLocators.push(locator.nth(j));
|
|
451
473
|
if (info.locatorLog) {
|
|
452
474
|
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
|
|
@@ -459,9 +481,11 @@ class StableBrowser {
|
|
|
459
481
|
info.printMessages = {};
|
|
460
482
|
}
|
|
461
483
|
if (info.locatorLog && !visible) {
|
|
484
|
+
info.failCause.lastError = `${formatElementName(element_name)} is not visible, searching for ${originalLocatorSearch}`;
|
|
462
485
|
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_VISIBLE");
|
|
463
486
|
}
|
|
464
487
|
if (info.locatorLog && !enabled) {
|
|
488
|
+
info.failCause.lastError = `${formatElementName(element_name)} is disabled, searching for ${originalLocatorSearch}`;
|
|
465
489
|
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_ENABLED");
|
|
466
490
|
}
|
|
467
491
|
if (!info.printMessages[j.toString()]) {
|
|
@@ -529,7 +553,7 @@ class StableBrowser {
|
|
|
529
553
|
}
|
|
530
554
|
return { rerun: false };
|
|
531
555
|
}
|
|
532
|
-
async _locate(selectors, info, _params, timeout) {
|
|
556
|
+
async _locate(selectors, info, _params, timeout, allowDisabled = false) {
|
|
533
557
|
if (!timeout) {
|
|
534
558
|
timeout = 30000;
|
|
535
559
|
}
|
|
@@ -539,9 +563,30 @@ class StableBrowser {
|
|
|
539
563
|
let selector = selectors.locators[j];
|
|
540
564
|
info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
|
|
541
565
|
}
|
|
542
|
-
let element = await this._locate_internal(selectors, info, _params, timeout);
|
|
566
|
+
let element = await this._locate_internal(selectors, info, _params, timeout, allowDisabled);
|
|
543
567
|
if (!element.rerun) {
|
|
544
|
-
|
|
568
|
+
const randomToken = Math.random().toString(36).substring(7);
|
|
569
|
+
element.evaluate((el, randomToken) => {
|
|
570
|
+
el.setAttribute("data-blinq-id-" + randomToken, "");
|
|
571
|
+
}, randomToken);
|
|
572
|
+
// if (element._frame) {
|
|
573
|
+
// return element;
|
|
574
|
+
// }
|
|
575
|
+
const scope = element._frame ?? element.page();
|
|
576
|
+
let newElementSelector = "[data-blinq-id-" + randomToken + "]";
|
|
577
|
+
let prefixSelector = "";
|
|
578
|
+
const frameControlSelector = " >> internal:control=enter-frame";
|
|
579
|
+
const frameSelectorIndex = element._selector.lastIndexOf(frameControlSelector);
|
|
580
|
+
if (frameSelectorIndex !== -1) {
|
|
581
|
+
// remove everything after the >> internal:control=enter-frame
|
|
582
|
+
const frameSelector = element._selector.substring(0, frameSelectorIndex);
|
|
583
|
+
prefixSelector = frameSelector + " >> internal:control=enter-frame >>";
|
|
584
|
+
}
|
|
585
|
+
// if (element?._frame?._selector) {
|
|
586
|
+
// prefixSelector = element._frame._selector + " >> " + prefixSelector;
|
|
587
|
+
// }
|
|
588
|
+
const newSelector = prefixSelector + newElementSelector;
|
|
589
|
+
return scope.locator(newSelector);
|
|
545
590
|
}
|
|
546
591
|
}
|
|
547
592
|
throw new Error("unable to locate element " + JSON.stringify(selectors));
|
|
@@ -614,7 +659,7 @@ class StableBrowser {
|
|
|
614
659
|
//info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
|
|
615
660
|
if (Date.now() - startTime > timeout) {
|
|
616
661
|
info.failCause.iframeNotFound = true;
|
|
617
|
-
info.failCause.lastError =
|
|
662
|
+
info.failCause.lastError = `unable to locate iframe "${selectors.iframe_src}"`;
|
|
618
663
|
throw new Error("unable to locate iframe " + selectors.iframe_src);
|
|
619
664
|
}
|
|
620
665
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -639,7 +684,7 @@ class StableBrowser {
|
|
|
639
684
|
return bodyContent;
|
|
640
685
|
});
|
|
641
686
|
}
|
|
642
|
-
async _locate_internal(selectors, info, _params, timeout = 30000) {
|
|
687
|
+
async _locate_internal(selectors, info, _params, timeout = 30000, allowDisabled = false) {
|
|
643
688
|
if (!info) {
|
|
644
689
|
info = {};
|
|
645
690
|
info.failCause = {};
|
|
@@ -688,18 +733,13 @@ class StableBrowser {
|
|
|
688
733
|
}
|
|
689
734
|
// info.log += "scanning locators in priority 1" + "\n";
|
|
690
735
|
let onlyPriority3 = selectorsLocators[0].priority === 3;
|
|
691
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly);
|
|
736
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
692
737
|
if (result.foundElements.length === 0) {
|
|
693
738
|
// info.log += "scanning locators in priority 2" + "\n";
|
|
694
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly);
|
|
739
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
695
740
|
}
|
|
696
|
-
if (result.foundElements.length === 0 && onlyPriority3) {
|
|
697
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
|
|
698
|
-
}
|
|
699
|
-
else {
|
|
700
|
-
if (result.foundElements.length === 0 && !highPriorityOnly) {
|
|
701
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
|
|
702
|
-
}
|
|
741
|
+
if (result.foundElements.length === 0 && (onlyPriority3 || !highPriorityOnly)) {
|
|
742
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
703
743
|
}
|
|
704
744
|
let foundElements = result.foundElements;
|
|
705
745
|
if (foundElements.length === 1 && foundElements[0].unique) {
|
|
@@ -755,6 +795,11 @@ class StableBrowser {
|
|
|
755
795
|
visibleOnly = false;
|
|
756
796
|
}
|
|
757
797
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
798
|
+
// sheck of more of half of the timeout has passed
|
|
799
|
+
if (Date.now() - startTime > timeout / 2) {
|
|
800
|
+
highPriorityOnly = false;
|
|
801
|
+
visibleOnly = false;
|
|
802
|
+
}
|
|
758
803
|
}
|
|
759
804
|
this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
|
|
760
805
|
// if (info.locatorLog) {
|
|
@@ -765,10 +810,12 @@ class StableBrowser {
|
|
|
765
810
|
// }
|
|
766
811
|
//info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
|
|
767
812
|
info.failCause.locatorNotFound = true;
|
|
768
|
-
info
|
|
813
|
+
if (!info?.failCause?.lastError) {
|
|
814
|
+
info.failCause.lastError = `failed to locate ${formatElementName(selectors.element_name)}, ${locatorsCount > 0 ? `${locatorsCount} matching elements found` : "no matching elements found"}`;
|
|
815
|
+
}
|
|
769
816
|
throw new Error("failed to locate first element no elements found, " + info.log);
|
|
770
817
|
}
|
|
771
|
-
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
|
|
818
|
+
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false, element_name) {
|
|
772
819
|
let foundElements = [];
|
|
773
820
|
const result = {
|
|
774
821
|
foundElements: foundElements,
|
|
@@ -776,7 +823,7 @@ class StableBrowser {
|
|
|
776
823
|
for (let i = 0; i < locatorsGroup.length; i++) {
|
|
777
824
|
let foundLocators = [];
|
|
778
825
|
try {
|
|
779
|
-
await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly);
|
|
826
|
+
await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
|
|
780
827
|
}
|
|
781
828
|
catch (e) {
|
|
782
829
|
// this call can fail it the browser is navigating
|
|
@@ -784,7 +831,7 @@ class StableBrowser {
|
|
|
784
831
|
// this.logger.debug(e);
|
|
785
832
|
foundLocators = [];
|
|
786
833
|
try {
|
|
787
|
-
await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly);
|
|
834
|
+
await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
|
|
788
835
|
}
|
|
789
836
|
catch (e) {
|
|
790
837
|
this.logger.info("unable to use locator (second try) " + JSON.stringify(locatorsGroup[i]));
|
|
@@ -799,9 +846,40 @@ class StableBrowser {
|
|
|
799
846
|
result.locatorIndex = i;
|
|
800
847
|
}
|
|
801
848
|
if (foundLocators.length > 1) {
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
849
|
+
// remove elements that consume the same space with 10 pixels tolerance
|
|
850
|
+
const boxes = [];
|
|
851
|
+
for (let j = 0; j < foundLocators.length; j++) {
|
|
852
|
+
boxes.push({ box: await foundLocators[j].boundingBox(), locator: foundLocators[j] });
|
|
853
|
+
}
|
|
854
|
+
for (let j = 0; j < boxes.length; j++) {
|
|
855
|
+
for (let k = 0; k < boxes.length; k++) {
|
|
856
|
+
if (j === k) {
|
|
857
|
+
continue;
|
|
858
|
+
}
|
|
859
|
+
// check if x, y, width, height are the same with 10 pixels tolerance
|
|
860
|
+
if (Math.abs(boxes[j].box.x - boxes[k].box.x) < 10 &&
|
|
861
|
+
Math.abs(boxes[j].box.y - boxes[k].box.y) < 10 &&
|
|
862
|
+
Math.abs(boxes[j].box.width - boxes[k].box.width) < 10 &&
|
|
863
|
+
Math.abs(boxes[j].box.height - boxes[k].box.height) < 10) {
|
|
864
|
+
// as the element is not unique, will remove it
|
|
865
|
+
boxes.splice(k, 1);
|
|
866
|
+
k--;
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
if (boxes.length === 1) {
|
|
871
|
+
result.foundElements.push({
|
|
872
|
+
locator: boxes[0].locator.first(),
|
|
873
|
+
box: boxes[0].box,
|
|
874
|
+
unique: true,
|
|
875
|
+
});
|
|
876
|
+
result.locatorIndex = i;
|
|
877
|
+
}
|
|
878
|
+
else {
|
|
879
|
+
info.failCause.foundMultiple = true;
|
|
880
|
+
if (info.locatorLog) {
|
|
881
|
+
info.locatorLog.setLocatorSearchStatus(JSON.stringify(locatorsGroup[i]), "FOUND_NOT_UNIQUE");
|
|
882
|
+
}
|
|
805
883
|
}
|
|
806
884
|
}
|
|
807
885
|
}
|
|
@@ -912,25 +990,14 @@ class StableBrowser {
|
|
|
912
990
|
options,
|
|
913
991
|
world,
|
|
914
992
|
text: "Click element",
|
|
993
|
+
_text: "Click on " + selectors.element_name,
|
|
915
994
|
type: Types.CLICK,
|
|
916
995
|
operation: "click",
|
|
917
996
|
log: "***** click on " + selectors.element_name + " *****\n",
|
|
918
997
|
};
|
|
919
998
|
try {
|
|
920
999
|
await _preCommand(state, this);
|
|
921
|
-
|
|
922
|
-
// state.selectors.locators[0].text = state.options.context;
|
|
923
|
-
// }
|
|
924
|
-
try {
|
|
925
|
-
await state.element.click();
|
|
926
|
-
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
927
|
-
}
|
|
928
|
-
catch (e) {
|
|
929
|
-
// await this.closeUnexpectedPopups();
|
|
930
|
-
state.element = await this._locate(selectors, state.info, _params);
|
|
931
|
-
await state.element.dispatchEvent("click");
|
|
932
|
-
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
933
|
-
}
|
|
1000
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
934
1001
|
await this.waitForPageLoad();
|
|
935
1002
|
return state.info;
|
|
936
1003
|
}
|
|
@@ -941,6 +1008,38 @@ class StableBrowser {
|
|
|
941
1008
|
_commandFinally(state, this);
|
|
942
1009
|
}
|
|
943
1010
|
}
|
|
1011
|
+
async waitForElement(selectors, _params, options = {}, world = null) {
|
|
1012
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1013
|
+
const state = {
|
|
1014
|
+
selectors,
|
|
1015
|
+
_params,
|
|
1016
|
+
options,
|
|
1017
|
+
world,
|
|
1018
|
+
text: "Wait for element",
|
|
1019
|
+
_text: "Wait for " + selectors.element_name,
|
|
1020
|
+
type: Types.WAIT_ELEMENT,
|
|
1021
|
+
operation: "waitForElement",
|
|
1022
|
+
log: "***** wait for " + selectors.element_name + " *****\n",
|
|
1023
|
+
};
|
|
1024
|
+
let found = false;
|
|
1025
|
+
try {
|
|
1026
|
+
await _preCommand(state, this);
|
|
1027
|
+
// if (state.options && state.options.context) {
|
|
1028
|
+
// state.selectors.locators[0].text = state.options.context;
|
|
1029
|
+
// }
|
|
1030
|
+
await state.element.waitFor({ timeout: timeout });
|
|
1031
|
+
found = true;
|
|
1032
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1033
|
+
}
|
|
1034
|
+
catch (e) {
|
|
1035
|
+
console.error("Error on waitForElement", e);
|
|
1036
|
+
// await _commandError(state, e, this);
|
|
1037
|
+
}
|
|
1038
|
+
finally {
|
|
1039
|
+
_commandFinally(state, this);
|
|
1040
|
+
}
|
|
1041
|
+
return found;
|
|
1042
|
+
}
|
|
944
1043
|
async setCheck(selectors, checked = true, _params, options = {}, world = null) {
|
|
945
1044
|
const state = {
|
|
946
1045
|
selectors,
|
|
@@ -949,6 +1048,7 @@ class StableBrowser {
|
|
|
949
1048
|
world,
|
|
950
1049
|
type: checked ? Types.CHECK : Types.UNCHECK,
|
|
951
1050
|
text: checked ? `Check element` : `Uncheck element`,
|
|
1051
|
+
_text: checked ? `Check ${selectors.element_name}` : `Uncheck ${selectors.element_name}`,
|
|
952
1052
|
operation: "setCheck",
|
|
953
1053
|
log: "***** check " + selectors.element_name + " *****\n",
|
|
954
1054
|
};
|
|
@@ -958,9 +1058,15 @@ class StableBrowser {
|
|
|
958
1058
|
// let element = await this._locate(selectors, info, _params);
|
|
959
1059
|
// ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
960
1060
|
try {
|
|
961
|
-
//
|
|
1061
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
1062
|
+
// console.log(`Highlighting while running from recorder`);
|
|
1063
|
+
await this._highlightElements(state.element);
|
|
962
1064
|
await state.element.setChecked(checked);
|
|
963
1065
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1066
|
+
// await this._unHighlightElements(element);
|
|
1067
|
+
// }
|
|
1068
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1069
|
+
// await this._unHighlightElements(element);
|
|
964
1070
|
}
|
|
965
1071
|
catch (e) {
|
|
966
1072
|
if (e.message && e.message.includes("did not change its state")) {
|
|
@@ -992,22 +1098,13 @@ class StableBrowser {
|
|
|
992
1098
|
world,
|
|
993
1099
|
type: Types.HOVER,
|
|
994
1100
|
text: `Hover element`,
|
|
1101
|
+
_text: `Hover on ${selectors.element_name}`,
|
|
995
1102
|
operation: "hover",
|
|
996
1103
|
log: "***** hover " + selectors.element_name + " *****\n",
|
|
997
1104
|
};
|
|
998
1105
|
try {
|
|
999
1106
|
await _preCommand(state, this);
|
|
1000
|
-
|
|
1001
|
-
await state.element.hover();
|
|
1002
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1003
|
-
}
|
|
1004
|
-
catch (e) {
|
|
1005
|
-
//await this.closeUnexpectedPopups();
|
|
1006
|
-
state.info.log += "hover failed, will try again" + "\n";
|
|
1007
|
-
state.element = await this._locate(selectors, state.info, _params);
|
|
1008
|
-
await state.element.hover({ timeout: 10000 });
|
|
1009
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1010
|
-
}
|
|
1107
|
+
await performAction("hover", state.element, options, this, state, _params);
|
|
1011
1108
|
await _screenshot(state, this);
|
|
1012
1109
|
await this.waitForPageLoad();
|
|
1013
1110
|
return state.info;
|
|
@@ -1031,6 +1128,7 @@ class StableBrowser {
|
|
|
1031
1128
|
value: values.toString(),
|
|
1032
1129
|
type: Types.SELECT,
|
|
1033
1130
|
text: `Select option: ${values}`,
|
|
1131
|
+
_text: `Select option: ${values} on ${selectors.element_name}`,
|
|
1034
1132
|
operation: "selectOption",
|
|
1035
1133
|
log: "***** select option " + selectors.element_name + " *****\n",
|
|
1036
1134
|
};
|
|
@@ -1065,6 +1163,7 @@ class StableBrowser {
|
|
|
1065
1163
|
highlight: false,
|
|
1066
1164
|
type: Types.TYPE_PRESS,
|
|
1067
1165
|
text: `Type value: ${_value}`,
|
|
1166
|
+
_text: `Type value: ${_value}`,
|
|
1068
1167
|
operation: "type",
|
|
1069
1168
|
log: "",
|
|
1070
1169
|
};
|
|
@@ -1144,6 +1243,7 @@ class StableBrowser {
|
|
|
1144
1243
|
world,
|
|
1145
1244
|
type: Types.SET_DATE_TIME,
|
|
1146
1245
|
text: `Set date time value: ${value}`,
|
|
1246
|
+
_text: `Set date time value: ${value} on ${selectors.element_name}`,
|
|
1147
1247
|
operation: "setDateTime",
|
|
1148
1248
|
log: "***** set date time value " + selectors.element_name + " *****\n",
|
|
1149
1249
|
throwError: false,
|
|
@@ -1151,7 +1251,7 @@ class StableBrowser {
|
|
|
1151
1251
|
try {
|
|
1152
1252
|
await _preCommand(state, this);
|
|
1153
1253
|
try {
|
|
1154
|
-
await state.element
|
|
1254
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
1155
1255
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1156
1256
|
if (format) {
|
|
1157
1257
|
state.value = dayjs(state.value).format(format);
|
|
@@ -1215,9 +1315,13 @@ class StableBrowser {
|
|
|
1215
1315
|
world,
|
|
1216
1316
|
type: Types.FILL,
|
|
1217
1317
|
text: `Click type input with value: ${_value}`,
|
|
1318
|
+
_text: "Fill " + selectors.element_name + " with value " + maskValue(_value),
|
|
1218
1319
|
operation: "clickType",
|
|
1219
1320
|
log: "***** clickType on " + selectors.element_name + " with value " + maskValue(_value) + "*****\n",
|
|
1220
1321
|
};
|
|
1322
|
+
if (!options) {
|
|
1323
|
+
options = {};
|
|
1324
|
+
}
|
|
1221
1325
|
if (newValue !== _value) {
|
|
1222
1326
|
//this.logger.info(_value + "=" + newValue);
|
|
1223
1327
|
_value = newValue;
|
|
@@ -1225,7 +1329,7 @@ class StableBrowser {
|
|
|
1225
1329
|
try {
|
|
1226
1330
|
await _preCommand(state, this);
|
|
1227
1331
|
state.info.value = _value;
|
|
1228
|
-
if (
|
|
1332
|
+
if (!options.press) {
|
|
1229
1333
|
try {
|
|
1230
1334
|
let currentValue = await state.element.inputValue();
|
|
1231
1335
|
if (currentValue) {
|
|
@@ -1236,13 +1340,9 @@ class StableBrowser {
|
|
|
1236
1340
|
this.logger.info("unable to clear input value");
|
|
1237
1341
|
}
|
|
1238
1342
|
}
|
|
1239
|
-
if (options
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
}
|
|
1243
|
-
catch (e) {
|
|
1244
|
-
await state.element.dispatchEvent("click");
|
|
1245
|
-
}
|
|
1343
|
+
if (options.press) {
|
|
1344
|
+
options.timeout = 5000;
|
|
1345
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
1246
1346
|
}
|
|
1247
1347
|
else {
|
|
1248
1348
|
try {
|
|
@@ -1280,7 +1380,12 @@ class StableBrowser {
|
|
|
1280
1380
|
await this.waitForPageLoad();
|
|
1281
1381
|
}
|
|
1282
1382
|
else if (enter === false) {
|
|
1283
|
-
|
|
1383
|
+
try {
|
|
1384
|
+
await state.element.dispatchEvent("change", null, { timeout: 5000 });
|
|
1385
|
+
}
|
|
1386
|
+
catch (e) {
|
|
1387
|
+
// ignore
|
|
1388
|
+
}
|
|
1284
1389
|
//await this.page.keyboard.press("Tab");
|
|
1285
1390
|
}
|
|
1286
1391
|
else {
|
|
@@ -1332,6 +1437,7 @@ class StableBrowser {
|
|
|
1332
1437
|
return await this._getText(selectors, 0, _params, options, info, world);
|
|
1333
1438
|
}
|
|
1334
1439
|
async _getText(selectors, climb, _params = null, options = {}, info = {}, world = null) {
|
|
1440
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1335
1441
|
_validateSelectors(selectors);
|
|
1336
1442
|
let screenshotId = null;
|
|
1337
1443
|
let screenshotPath = null;
|
|
@@ -1341,7 +1447,7 @@ class StableBrowser {
|
|
|
1341
1447
|
}
|
|
1342
1448
|
info.operation = "getText";
|
|
1343
1449
|
info.selectors = selectors;
|
|
1344
|
-
let element = await this._locate(selectors, info, _params);
|
|
1450
|
+
let element = await this._locate(selectors, info, _params, timeout);
|
|
1345
1451
|
if (climb > 0) {
|
|
1346
1452
|
const climbArray = [];
|
|
1347
1453
|
for (let i = 0; i < climb; i++) {
|
|
@@ -1360,6 +1466,18 @@ class StableBrowser {
|
|
|
1360
1466
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1361
1467
|
try {
|
|
1362
1468
|
await this._highlightElements(element);
|
|
1469
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
1470
|
+
// // console.log(`Highlighting for get text while running from recorder`);
|
|
1471
|
+
// this._highlightElements(element)
|
|
1472
|
+
// .then(async () => {
|
|
1473
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1474
|
+
// this._unhighlightElements(element).then(
|
|
1475
|
+
// () => {}
|
|
1476
|
+
// // console.log(`Unhighlighting vrtr in recorder is successful`)
|
|
1477
|
+
// );
|
|
1478
|
+
// })
|
|
1479
|
+
// .catch(e);
|
|
1480
|
+
// }
|
|
1363
1481
|
const elementText = await element.innerText();
|
|
1364
1482
|
return {
|
|
1365
1483
|
text: elementText,
|
|
@@ -1371,7 +1489,7 @@ class StableBrowser {
|
|
|
1371
1489
|
}
|
|
1372
1490
|
catch (e) {
|
|
1373
1491
|
//await this.closeUnexpectedPopups();
|
|
1374
|
-
this.logger.info("no innerText will use textContent");
|
|
1492
|
+
this.logger.info("no innerText, will use textContent");
|
|
1375
1493
|
const elementText = await element.textContent();
|
|
1376
1494
|
return { text: elementText, screenshotId, screenshotPath, value: value };
|
|
1377
1495
|
}
|
|
@@ -1396,6 +1514,7 @@ class StableBrowser {
|
|
|
1396
1514
|
highlight: false,
|
|
1397
1515
|
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1398
1516
|
text: `Verify element contains pattern: ${pattern}`,
|
|
1517
|
+
_text: "Verify element " + selectors.element_name + " contains pattern " + pattern,
|
|
1399
1518
|
operation: "containsPattern",
|
|
1400
1519
|
log: "***** verify element " + selectors.element_name + " contains pattern " + pattern + " *****\n",
|
|
1401
1520
|
};
|
|
@@ -1431,6 +1550,8 @@ class StableBrowser {
|
|
|
1431
1550
|
}
|
|
1432
1551
|
}
|
|
1433
1552
|
async containsText(selectors, text, climb, _params = null, options = {}, world = null) {
|
|
1553
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1554
|
+
const startTime = Date.now();
|
|
1434
1555
|
const state = {
|
|
1435
1556
|
selectors,
|
|
1436
1557
|
_params,
|
|
@@ -1457,62 +1578,54 @@ class StableBrowser {
|
|
|
1457
1578
|
}
|
|
1458
1579
|
let foundObj = null;
|
|
1459
1580
|
try {
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
const dateAlternatives = findDateAlternatives(text);
|
|
1467
|
-
const numberAlternatives = findNumberAlternatives(text);
|
|
1468
|
-
if (dateAlternatives.date) {
|
|
1469
|
-
for (let i = 0; i < dateAlternatives.dates.length; i++) {
|
|
1470
|
-
if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
|
|
1471
|
-
foundObj?.value?.includes(dateAlternatives.dates[i])) {
|
|
1472
|
-
return state.info;
|
|
1581
|
+
while (Date.now() - startTime < timeout) {
|
|
1582
|
+
try {
|
|
1583
|
+
await _preCommand(state, this);
|
|
1584
|
+
foundObj = await this._getText(selectors, climb, _params, { timeout: 3000 }, state.info, world);
|
|
1585
|
+
if (foundObj && foundObj.element) {
|
|
1586
|
+
await this.scrollIfNeeded(foundObj.element, state.info);
|
|
1473
1587
|
}
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1588
|
+
await _screenshot(state, this);
|
|
1589
|
+
const dateAlternatives = findDateAlternatives(text);
|
|
1590
|
+
const numberAlternatives = findNumberAlternatives(text);
|
|
1591
|
+
if (dateAlternatives.date) {
|
|
1592
|
+
for (let i = 0; i < dateAlternatives.dates.length; i++) {
|
|
1593
|
+
if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
|
|
1594
|
+
foundObj?.value?.includes(dateAlternatives.dates[i])) {
|
|
1595
|
+
return state.info;
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
else if (numberAlternatives.number) {
|
|
1600
|
+
for (let i = 0; i < numberAlternatives.numbers.length; i++) {
|
|
1601
|
+
if (foundObj?.text.includes(numberAlternatives.numbers[i]) ||
|
|
1602
|
+
foundObj?.value?.includes(numberAlternatives.numbers[i])) {
|
|
1603
|
+
return state.info;
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
else if (foundObj?.text.includes(text) || foundObj?.value?.includes(text)) {
|
|
1481
1608
|
return state.info;
|
|
1482
1609
|
}
|
|
1483
1610
|
}
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
throw new Error("element doesn't contain text " + text);
|
|
1611
|
+
catch (e) {
|
|
1612
|
+
// Log error but continue retrying until timeout is reached
|
|
1613
|
+
this.logger.warn("Retrying containsText due to: " + e.message);
|
|
1614
|
+
}
|
|
1615
|
+
await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second before retrying
|
|
1490
1616
|
}
|
|
1491
|
-
|
|
1617
|
+
state.info.foundText = foundObj?.text;
|
|
1618
|
+
state.info.value = foundObj?.value;
|
|
1619
|
+
throw new Error("element doesn't contain text " + text);
|
|
1492
1620
|
}
|
|
1493
1621
|
catch (e) {
|
|
1494
1622
|
await _commandError(state, e, this);
|
|
1623
|
+
throw e;
|
|
1495
1624
|
}
|
|
1496
1625
|
finally {
|
|
1497
1626
|
_commandFinally(state, this);
|
|
1498
1627
|
}
|
|
1499
1628
|
}
|
|
1500
|
-
_getDataFile(world = null) {
|
|
1501
|
-
let dataFile = null;
|
|
1502
|
-
if (world && world.reportFolder) {
|
|
1503
|
-
dataFile = path.join(world.reportFolder, "data.json");
|
|
1504
|
-
}
|
|
1505
|
-
else if (this.reportFolder) {
|
|
1506
|
-
dataFile = path.join(this.reportFolder, "data.json");
|
|
1507
|
-
}
|
|
1508
|
-
else if (this.context && this.context.reportFolder) {
|
|
1509
|
-
dataFile = path.join(this.context.reportFolder, "data.json");
|
|
1510
|
-
}
|
|
1511
|
-
else {
|
|
1512
|
-
dataFile = "data.json";
|
|
1513
|
-
}
|
|
1514
|
-
return dataFile;
|
|
1515
|
-
}
|
|
1516
1629
|
async waitForUserInput(message, world = null) {
|
|
1517
1630
|
if (!message) {
|
|
1518
1631
|
message = "# Wait for user input. Press any key to continue";
|
|
@@ -1541,13 +1654,22 @@ class StableBrowser {
|
|
|
1541
1654
|
return;
|
|
1542
1655
|
}
|
|
1543
1656
|
// if data file exists, load it
|
|
1544
|
-
const dataFile =
|
|
1657
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1545
1658
|
let data = this.getTestData(world);
|
|
1546
1659
|
// merge the testData with the existing data
|
|
1547
1660
|
Object.assign(data, testData);
|
|
1548
1661
|
// save the data to the file
|
|
1549
1662
|
fs.writeFileSync(dataFile, JSON.stringify(data, null, 2));
|
|
1550
1663
|
}
|
|
1664
|
+
overwriteTestData(testData, world = null) {
|
|
1665
|
+
if (!testData) {
|
|
1666
|
+
return;
|
|
1667
|
+
}
|
|
1668
|
+
// if data file exists, load it
|
|
1669
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1670
|
+
// save the data to the file
|
|
1671
|
+
fs.writeFileSync(dataFile, JSON.stringify(testData, null, 2));
|
|
1672
|
+
}
|
|
1551
1673
|
_getDataFilePath(fileName) {
|
|
1552
1674
|
let dataFile = path.join(this.project_path, "data", fileName);
|
|
1553
1675
|
if (fs.existsSync(dataFile)) {
|
|
@@ -1644,7 +1766,7 @@ class StableBrowser {
|
|
|
1644
1766
|
}
|
|
1645
1767
|
}
|
|
1646
1768
|
getTestData(world = null) {
|
|
1647
|
-
const dataFile =
|
|
1769
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1648
1770
|
let data = {};
|
|
1649
1771
|
if (fs.existsSync(dataFile)) {
|
|
1650
1772
|
data = JSON.parse(fs.readFileSync(dataFile, "utf8"));
|
|
@@ -1731,6 +1853,15 @@ class StableBrowser {
|
|
|
1731
1853
|
document.documentElement.clientWidth,
|
|
1732
1854
|
])));
|
|
1733
1855
|
let screenshotBuffer = null;
|
|
1856
|
+
// if (focusedElement) {
|
|
1857
|
+
// // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
|
|
1858
|
+
// await this._unhighlightElements(focusedElement);
|
|
1859
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1860
|
+
// console.log(`Unhighlighted previous element`);
|
|
1861
|
+
// }
|
|
1862
|
+
// if (focusedElement) {
|
|
1863
|
+
// await this._highlightElements(focusedElement);
|
|
1864
|
+
// }
|
|
1734
1865
|
if (this.context.browserName === "chromium") {
|
|
1735
1866
|
const client = await playContext.newCDPSession(this.page);
|
|
1736
1867
|
const { data } = await client.send("Page.captureScreenshot", {
|
|
@@ -1752,6 +1883,10 @@ class StableBrowser {
|
|
|
1752
1883
|
else {
|
|
1753
1884
|
screenshotBuffer = await this.page.screenshot();
|
|
1754
1885
|
}
|
|
1886
|
+
// if (focusedElement) {
|
|
1887
|
+
// // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
|
|
1888
|
+
// await this._unhighlightElements(focusedElement);
|
|
1889
|
+
// }
|
|
1755
1890
|
let image = await Jimp.read(screenshotBuffer);
|
|
1756
1891
|
// Get the image dimensions
|
|
1757
1892
|
const { width, height } = image.bitmap;
|
|
@@ -1764,6 +1899,7 @@ class StableBrowser {
|
|
|
1764
1899
|
else {
|
|
1765
1900
|
fs.writeFileSync(screenshotPath, screenshotBuffer);
|
|
1766
1901
|
}
|
|
1902
|
+
return screenshotBuffer;
|
|
1767
1903
|
}
|
|
1768
1904
|
async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
|
|
1769
1905
|
const state = {
|
|
@@ -1799,8 +1935,10 @@ class StableBrowser {
|
|
|
1799
1935
|
world,
|
|
1800
1936
|
type: Types.EXTRACT,
|
|
1801
1937
|
text: `Extract attribute from element`,
|
|
1938
|
+
_text: `Extract attribute ${attribute} from ${selectors.element_name}`,
|
|
1802
1939
|
operation: "extractAttribute",
|
|
1803
1940
|
log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1941
|
+
allowDisabled: true,
|
|
1804
1942
|
};
|
|
1805
1943
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1806
1944
|
try {
|
|
@@ -1815,6 +1953,9 @@ class StableBrowser {
|
|
|
1815
1953
|
case "value":
|
|
1816
1954
|
state.value = await state.element.inputValue();
|
|
1817
1955
|
break;
|
|
1956
|
+
case "text":
|
|
1957
|
+
state.value = await state.element.textContent();
|
|
1958
|
+
break;
|
|
1818
1959
|
default:
|
|
1819
1960
|
state.value = await state.element.getAttribute(attribute);
|
|
1820
1961
|
break;
|
|
@@ -1822,6 +1963,7 @@ class StableBrowser {
|
|
|
1822
1963
|
state.info.value = state.value;
|
|
1823
1964
|
this.setTestData({ [variable]: state.value }, world);
|
|
1824
1965
|
this.logger.info("set test data: " + variable + "=" + state.value);
|
|
1966
|
+
// await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1825
1967
|
return state.info;
|
|
1826
1968
|
}
|
|
1827
1969
|
catch (e) {
|
|
@@ -1840,18 +1982,28 @@ class StableBrowser {
|
|
|
1840
1982
|
options,
|
|
1841
1983
|
world,
|
|
1842
1984
|
type: Types.VERIFY_ATTRIBUTE,
|
|
1985
|
+
highlight: true,
|
|
1986
|
+
screenshot: true,
|
|
1843
1987
|
text: `Verify element attribute`,
|
|
1988
|
+
_text: `Verify attribute ${attribute} from ${selectors.element_name} is ${value}`,
|
|
1844
1989
|
operation: "verifyAttribute",
|
|
1845
1990
|
log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1991
|
+
allowDisabled: true,
|
|
1846
1992
|
};
|
|
1847
1993
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1848
1994
|
let val;
|
|
1995
|
+
let expectedValue;
|
|
1849
1996
|
try {
|
|
1850
1997
|
await _preCommand(state, this);
|
|
1998
|
+
expectedValue = await replaceWithLocalTestData(state.value, world);
|
|
1999
|
+
state.info.expectedValue = expectedValue;
|
|
1851
2000
|
switch (attribute) {
|
|
1852
2001
|
case "innerText":
|
|
1853
2002
|
val = String(await state.element.innerText());
|
|
1854
2003
|
break;
|
|
2004
|
+
case "text":
|
|
2005
|
+
val = String(await state.element.textContent());
|
|
2006
|
+
break;
|
|
1855
2007
|
case "value":
|
|
1856
2008
|
val = String(await state.element.inputValue());
|
|
1857
2009
|
break;
|
|
@@ -1869,19 +2021,22 @@ class StableBrowser {
|
|
|
1869
2021
|
val = String(await state.element.getAttribute(attribute));
|
|
1870
2022
|
break;
|
|
1871
2023
|
}
|
|
2024
|
+
state.info.value = val;
|
|
1872
2025
|
let regex;
|
|
1873
|
-
if (
|
|
1874
|
-
const patternBody =
|
|
2026
|
+
if (expectedValue.startsWith("/") && expectedValue.endsWith("/")) {
|
|
2027
|
+
const patternBody = expectedValue.slice(1, -1);
|
|
1875
2028
|
regex = new RegExp(patternBody, "g");
|
|
1876
2029
|
}
|
|
1877
2030
|
else {
|
|
1878
|
-
const escapedPattern =
|
|
2031
|
+
const escapedPattern = expectedValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1879
2032
|
regex = new RegExp(escapedPattern, "g");
|
|
1880
2033
|
}
|
|
1881
2034
|
if (!val.match(regex)) {
|
|
1882
|
-
|
|
2035
|
+
let errorMessage = `The ${attribute} attribute has a value of "${val}", but the expected value is "${expectedValue}"`;
|
|
2036
|
+
state.info.failCause.assertionFailed = true;
|
|
2037
|
+
state.info.failCause.lastError = errorMessage;
|
|
2038
|
+
throw new Error(errorMessage);
|
|
1883
2039
|
}
|
|
1884
|
-
state.info.expectedValue = val;
|
|
1885
2040
|
return state.info;
|
|
1886
2041
|
}
|
|
1887
2042
|
catch (e) {
|
|
@@ -1980,27 +2135,32 @@ class StableBrowser {
|
|
|
1980
2135
|
async _highlightElements(scope, css) {
|
|
1981
2136
|
try {
|
|
1982
2137
|
if (!scope) {
|
|
2138
|
+
// console.log(`Scope is not defined`);
|
|
1983
2139
|
return;
|
|
1984
2140
|
}
|
|
1985
2141
|
if (!css) {
|
|
1986
2142
|
scope
|
|
1987
2143
|
.evaluate((node) => {
|
|
1988
2144
|
if (node && node.style) {
|
|
1989
|
-
let
|
|
1990
|
-
|
|
2145
|
+
let originalOutline = node.style.outline;
|
|
2146
|
+
// console.log(`Original outline was: ${originalOutline}`);
|
|
2147
|
+
// node.__previousOutline = originalOutline;
|
|
2148
|
+
node.style.outline = "2px solid red";
|
|
2149
|
+
// console.log(`New outline is: ${node.style.outline}`);
|
|
1991
2150
|
if (window) {
|
|
1992
2151
|
window.addEventListener("beforeunload", function (e) {
|
|
1993
|
-
node.style.
|
|
2152
|
+
node.style.outline = originalOutline;
|
|
1994
2153
|
});
|
|
1995
2154
|
}
|
|
1996
2155
|
setTimeout(function () {
|
|
1997
|
-
node.style.
|
|
2156
|
+
node.style.outline = originalOutline;
|
|
1998
2157
|
}, 2000);
|
|
1999
2158
|
}
|
|
2000
2159
|
})
|
|
2001
2160
|
.then(() => { })
|
|
2002
2161
|
.catch((e) => {
|
|
2003
2162
|
// ignore
|
|
2163
|
+
// console.error(`Could not highlight node : ${e}`);
|
|
2004
2164
|
});
|
|
2005
2165
|
}
|
|
2006
2166
|
else {
|
|
@@ -2016,17 +2176,18 @@ class StableBrowser {
|
|
|
2016
2176
|
if (!element.style) {
|
|
2017
2177
|
return;
|
|
2018
2178
|
}
|
|
2019
|
-
|
|
2179
|
+
let originalOutline = element.style.outline;
|
|
2180
|
+
element.__previousOutline = originalOutline;
|
|
2020
2181
|
// Set the new border to be red and 2px solid
|
|
2021
|
-
element.style.
|
|
2182
|
+
element.style.outline = "2px solid red";
|
|
2022
2183
|
if (window) {
|
|
2023
2184
|
window.addEventListener("beforeunload", function (e) {
|
|
2024
|
-
element.style.
|
|
2185
|
+
element.style.outline = originalOutline;
|
|
2025
2186
|
});
|
|
2026
2187
|
}
|
|
2027
2188
|
// Set a timeout to revert to the original border after 2 seconds
|
|
2028
2189
|
setTimeout(function () {
|
|
2029
|
-
element.style.
|
|
2190
|
+
element.style.outline = originalOutline;
|
|
2030
2191
|
}, 2000);
|
|
2031
2192
|
}
|
|
2032
2193
|
return;
|
|
@@ -2034,6 +2195,7 @@ class StableBrowser {
|
|
|
2034
2195
|
.then(() => { })
|
|
2035
2196
|
.catch((e) => {
|
|
2036
2197
|
// ignore
|
|
2198
|
+
// console.error(`Could not highlight css: ${e}`);
|
|
2037
2199
|
});
|
|
2038
2200
|
}
|
|
2039
2201
|
}
|
|
@@ -2041,6 +2203,54 @@ class StableBrowser {
|
|
|
2041
2203
|
console.debug(error);
|
|
2042
2204
|
}
|
|
2043
2205
|
}
|
|
2206
|
+
// async _unhighlightElements(scope, css) {
|
|
2207
|
+
// try {
|
|
2208
|
+
// if (!scope) {
|
|
2209
|
+
// return;
|
|
2210
|
+
// }
|
|
2211
|
+
// if (!css) {
|
|
2212
|
+
// scope
|
|
2213
|
+
// .evaluate((node) => {
|
|
2214
|
+
// if (node && node.style) {
|
|
2215
|
+
// if (!node.__previousOutline) {
|
|
2216
|
+
// node.style.outline = "";
|
|
2217
|
+
// } else {
|
|
2218
|
+
// node.style.outline = node.__previousOutline;
|
|
2219
|
+
// }
|
|
2220
|
+
// }
|
|
2221
|
+
// })
|
|
2222
|
+
// .then(() => {})
|
|
2223
|
+
// .catch((e) => {
|
|
2224
|
+
// // console.log(`Error while unhighlighting node ${JSON.stringify(scope)}: ${e}`);
|
|
2225
|
+
// });
|
|
2226
|
+
// } else {
|
|
2227
|
+
// scope
|
|
2228
|
+
// .evaluate(([css]) => {
|
|
2229
|
+
// if (!css) {
|
|
2230
|
+
// return;
|
|
2231
|
+
// }
|
|
2232
|
+
// let elements = Array.from(document.querySelectorAll(css));
|
|
2233
|
+
// for (i = 0; i < elements.length; i++) {
|
|
2234
|
+
// let element = elements[i];
|
|
2235
|
+
// if (!element.style) {
|
|
2236
|
+
// return;
|
|
2237
|
+
// }
|
|
2238
|
+
// if (!element.__previousOutline) {
|
|
2239
|
+
// element.style.outline = "";
|
|
2240
|
+
// } else {
|
|
2241
|
+
// element.style.outline = element.__previousOutline;
|
|
2242
|
+
// }
|
|
2243
|
+
// }
|
|
2244
|
+
// })
|
|
2245
|
+
// .then(() => {})
|
|
2246
|
+
// .catch((e) => {
|
|
2247
|
+
// // console.error(`Error while unhighlighting element in css: ${e}`);
|
|
2248
|
+
// });
|
|
2249
|
+
// }
|
|
2250
|
+
// } catch (error) {
|
|
2251
|
+
// // console.debug(error);
|
|
2252
|
+
// }
|
|
2253
|
+
// }
|
|
2044
2254
|
async verifyPagePath(pathPart, options = {}, world = null) {
|
|
2045
2255
|
const startTime = Date.now();
|
|
2046
2256
|
let error = null;
|
|
@@ -2085,6 +2295,69 @@ class StableBrowser {
|
|
|
2085
2295
|
_reportToWorld(world, {
|
|
2086
2296
|
type: Types.VERIFY_PAGE_PATH,
|
|
2087
2297
|
text: "Verify page path",
|
|
2298
|
+
_text: "Verify the page path contains " + pathPart,
|
|
2299
|
+
screenshotId,
|
|
2300
|
+
result: error
|
|
2301
|
+
? {
|
|
2302
|
+
status: "FAILED",
|
|
2303
|
+
startTime,
|
|
2304
|
+
endTime,
|
|
2305
|
+
message: error?.message,
|
|
2306
|
+
}
|
|
2307
|
+
: {
|
|
2308
|
+
status: "PASSED",
|
|
2309
|
+
startTime,
|
|
2310
|
+
endTime,
|
|
2311
|
+
},
|
|
2312
|
+
info: info,
|
|
2313
|
+
});
|
|
2314
|
+
}
|
|
2315
|
+
}
|
|
2316
|
+
async verifyPageTitle(title, options = {}, world = null) {
|
|
2317
|
+
const startTime = Date.now();
|
|
2318
|
+
let error = null;
|
|
2319
|
+
let screenshotId = null;
|
|
2320
|
+
let screenshotPath = null;
|
|
2321
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2322
|
+
const info = {};
|
|
2323
|
+
info.log = "***** verify page title " + title + " *****\n";
|
|
2324
|
+
info.operation = "verifyPageTitle";
|
|
2325
|
+
const newValue = await this._replaceWithLocalData(title, world);
|
|
2326
|
+
if (newValue !== title) {
|
|
2327
|
+
this.logger.info(title + "=" + newValue);
|
|
2328
|
+
title = newValue;
|
|
2329
|
+
}
|
|
2330
|
+
info.title = title;
|
|
2331
|
+
try {
|
|
2332
|
+
for (let i = 0; i < 30; i++) {
|
|
2333
|
+
const foundTitle = await this.page.title();
|
|
2334
|
+
if (!foundTitle.includes(title)) {
|
|
2335
|
+
if (i === 29) {
|
|
2336
|
+
throw new Error(`url ${foundTitle} doesn't contain ${title}`);
|
|
2337
|
+
}
|
|
2338
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2339
|
+
continue;
|
|
2340
|
+
}
|
|
2341
|
+
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2342
|
+
return info;
|
|
2343
|
+
}
|
|
2344
|
+
}
|
|
2345
|
+
catch (e) {
|
|
2346
|
+
//await this.closeUnexpectedPopups();
|
|
2347
|
+
this.logger.error("verify page title failed " + info.log);
|
|
2348
|
+
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2349
|
+
info.screenshotPath = screenshotPath;
|
|
2350
|
+
Object.assign(e, { info: info });
|
|
2351
|
+
error = e;
|
|
2352
|
+
// throw e;
|
|
2353
|
+
await _commandError({ text: "verifyPageTitle", operation: "verifyPageTitle", title, info, throwError: true }, e, this);
|
|
2354
|
+
}
|
|
2355
|
+
finally {
|
|
2356
|
+
const endTime = Date.now();
|
|
2357
|
+
_reportToWorld(world, {
|
|
2358
|
+
type: Types.VERIFY_PAGE_PATH,
|
|
2359
|
+
text: "Verify page title",
|
|
2360
|
+
_text: "Verify the page title contains " + title,
|
|
2088
2361
|
screenshotId,
|
|
2089
2362
|
result: error
|
|
2090
2363
|
? {
|
|
@@ -2102,27 +2375,27 @@ class StableBrowser {
|
|
|
2102
2375
|
});
|
|
2103
2376
|
}
|
|
2104
2377
|
}
|
|
2105
|
-
async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state) {
|
|
2378
|
+
async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state, partial = true, ignoreCase = false) {
|
|
2106
2379
|
const frames = this.page.frames();
|
|
2107
2380
|
let results = [];
|
|
2108
|
-
let ignoreCase =
|
|
2381
|
+
// let ignoreCase = false;
|
|
2109
2382
|
for (let i = 0; i < frames.length; i++) {
|
|
2110
2383
|
if (dateAlternatives.date) {
|
|
2111
2384
|
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2112
|
-
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false,
|
|
2385
|
+
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, partial, ignoreCase, {});
|
|
2113
2386
|
result.frame = frames[i];
|
|
2114
2387
|
results.push(result);
|
|
2115
2388
|
}
|
|
2116
2389
|
}
|
|
2117
2390
|
else if (numberAlternatives.number) {
|
|
2118
2391
|
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2119
|
-
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false,
|
|
2392
|
+
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, partial, ignoreCase, {});
|
|
2120
2393
|
result.frame = frames[i];
|
|
2121
2394
|
results.push(result);
|
|
2122
2395
|
}
|
|
2123
2396
|
}
|
|
2124
2397
|
else {
|
|
2125
|
-
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false,
|
|
2398
|
+
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, partial, ignoreCase, {});
|
|
2126
2399
|
result.frame = frames[i];
|
|
2127
2400
|
results.push(result);
|
|
2128
2401
|
}
|
|
@@ -2141,11 +2414,15 @@ class StableBrowser {
|
|
|
2141
2414
|
scroll: false,
|
|
2142
2415
|
highlight: false,
|
|
2143
2416
|
type: Types.VERIFY_PAGE_CONTAINS_TEXT,
|
|
2144
|
-
text: `Verify text exists in page`,
|
|
2417
|
+
text: `Verify the text '${text}' exists in page`,
|
|
2418
|
+
_text: `Verify the text '${text}' exists in page`,
|
|
2145
2419
|
operation: "verifyTextExistInPage",
|
|
2146
2420
|
log: "***** verify text " + text + " exists in page *****\n",
|
|
2147
2421
|
};
|
|
2148
|
-
|
|
2422
|
+
if (testForRegex(text)) {
|
|
2423
|
+
text = text.replace(/\\"/g, '"');
|
|
2424
|
+
}
|
|
2425
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2149
2426
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2150
2427
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2151
2428
|
if (newValue !== text) {
|
|
@@ -2158,7 +2435,15 @@ class StableBrowser {
|
|
|
2158
2435
|
await _preCommand(state, this);
|
|
2159
2436
|
state.info.text = text;
|
|
2160
2437
|
while (true) {
|
|
2161
|
-
|
|
2438
|
+
let resultWithElementsFound = {
|
|
2439
|
+
length: 0,
|
|
2440
|
+
};
|
|
2441
|
+
try {
|
|
2442
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2443
|
+
}
|
|
2444
|
+
catch (error) {
|
|
2445
|
+
// ignore
|
|
2446
|
+
}
|
|
2162
2447
|
if (resultWithElementsFound.length === 0) {
|
|
2163
2448
|
if (Date.now() - state.startTime > timeout) {
|
|
2164
2449
|
throw new Error(`Text ${text} not found in page`);
|
|
@@ -2166,18 +2451,40 @@ class StableBrowser {
|
|
|
2166
2451
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2167
2452
|
continue;
|
|
2168
2453
|
}
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2454
|
+
try {
|
|
2455
|
+
if (resultWithElementsFound[0].randomToken) {
|
|
2456
|
+
const frame = resultWithElementsFound[0].frame;
|
|
2457
|
+
const dataAttribute = `[data-blinq-id-${resultWithElementsFound[0].randomToken}]`;
|
|
2458
|
+
await this._highlightElements(frame, dataAttribute);
|
|
2459
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
2460
|
+
// console.log(`Highlighting for verify text is found while running from recorder`);
|
|
2461
|
+
// this._highlightElements(frame, dataAttribute).then(async () => {
|
|
2462
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2463
|
+
// this._unhighlightElements(frame, dataAttribute)
|
|
2464
|
+
// .then(async () => {
|
|
2465
|
+
// console.log(`Unhighlighted frame dataAttribute successfully`);
|
|
2466
|
+
// })
|
|
2467
|
+
// .catch(
|
|
2468
|
+
// (e) => {}
|
|
2469
|
+
// console.error(e)
|
|
2470
|
+
// );
|
|
2471
|
+
// });
|
|
2472
|
+
// }
|
|
2473
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2474
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2475
|
+
// await this._unhighlightElements(frame, dataAttribute);
|
|
2476
|
+
if (element) {
|
|
2477
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2478
|
+
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2479
|
+
// await _screenshot(state, this, element);
|
|
2480
|
+
}
|
|
2177
2481
|
}
|
|
2482
|
+
await _screenshot(state, this);
|
|
2483
|
+
return state.info;
|
|
2484
|
+
}
|
|
2485
|
+
catch (error) {
|
|
2486
|
+
console.error(error);
|
|
2178
2487
|
}
|
|
2179
|
-
await _screenshot(state, this);
|
|
2180
|
-
return state.info;
|
|
2181
2488
|
}
|
|
2182
2489
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2183
2490
|
}
|
|
@@ -2198,11 +2505,15 @@ class StableBrowser {
|
|
|
2198
2505
|
scroll: false,
|
|
2199
2506
|
highlight: false,
|
|
2200
2507
|
type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
|
|
2201
|
-
text: `Verify text does not exist in page`,
|
|
2508
|
+
text: `Verify the text '${text}' does not exist in page`,
|
|
2509
|
+
_text: `Verify the text '${text}' does not exist in page`,
|
|
2202
2510
|
operation: "verifyTextNotExistInPage",
|
|
2203
2511
|
log: "***** verify text " + text + " does not exist in page *****\n",
|
|
2204
2512
|
};
|
|
2205
|
-
|
|
2513
|
+
if (testForRegex(text)) {
|
|
2514
|
+
text = text.replace(/\\"/g, '"');
|
|
2515
|
+
}
|
|
2516
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2206
2517
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2207
2518
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2208
2519
|
if (newValue !== text) {
|
|
@@ -2214,8 +2525,16 @@ class StableBrowser {
|
|
|
2214
2525
|
try {
|
|
2215
2526
|
await _preCommand(state, this);
|
|
2216
2527
|
state.info.text = text;
|
|
2528
|
+
let resultWithElementsFound = {
|
|
2529
|
+
length: null, // initial cannot be 0
|
|
2530
|
+
};
|
|
2217
2531
|
while (true) {
|
|
2218
|
-
|
|
2532
|
+
try {
|
|
2533
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2534
|
+
}
|
|
2535
|
+
catch (error) {
|
|
2536
|
+
// ignore
|
|
2537
|
+
}
|
|
2219
2538
|
if (resultWithElementsFound.length === 0) {
|
|
2220
2539
|
await _screenshot(state, this);
|
|
2221
2540
|
return state.info;
|
|
@@ -2245,10 +2564,11 @@ class StableBrowser {
|
|
|
2245
2564
|
highlight: false,
|
|
2246
2565
|
type: Types.VERIFY_TEXT_WITH_RELATION,
|
|
2247
2566
|
text: `Verify text with relation to another text`,
|
|
2567
|
+
_text: "Search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found",
|
|
2248
2568
|
operation: "verify_text_with_relation",
|
|
2249
2569
|
log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
|
|
2250
2570
|
};
|
|
2251
|
-
const timeout = this.
|
|
2571
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2252
2572
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2253
2573
|
let newValue = await this._replaceWithLocalData(textAnchor, world);
|
|
2254
2574
|
if (newValue !== textAnchor) {
|
|
@@ -2266,8 +2586,16 @@ class StableBrowser {
|
|
|
2266
2586
|
try {
|
|
2267
2587
|
await _preCommand(state, this);
|
|
2268
2588
|
state.info.text = textToVerify;
|
|
2589
|
+
let resultWithElementsFound = {
|
|
2590
|
+
length: 0,
|
|
2591
|
+
};
|
|
2269
2592
|
while (true) {
|
|
2270
|
-
|
|
2593
|
+
try {
|
|
2594
|
+
resultWithElementsFound = await this.findTextInAllFrames(findDateAlternatives(textAnchor), findNumberAlternatives(textAnchor), textAnchor, state, false);
|
|
2595
|
+
}
|
|
2596
|
+
catch (error) {
|
|
2597
|
+
// ignore
|
|
2598
|
+
}
|
|
2271
2599
|
if (resultWithElementsFound.length === 0) {
|
|
2272
2600
|
if (Date.now() - state.startTime > timeout) {
|
|
2273
2601
|
throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
|
|
@@ -2275,37 +2603,56 @@ class StableBrowser {
|
|
|
2275
2603
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2276
2604
|
continue;
|
|
2277
2605
|
}
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2606
|
+
try {
|
|
2607
|
+
for (let i = 0; i < resultWithElementsFound.length; i++) {
|
|
2608
|
+
foundAncore = true;
|
|
2609
|
+
const result = resultWithElementsFound[i];
|
|
2610
|
+
const token = result.randomToken;
|
|
2611
|
+
const frame = result.frame;
|
|
2612
|
+
let css = `[data-blinq-id-${token}]`;
|
|
2613
|
+
const climbArray1 = [];
|
|
2614
|
+
for (let i = 0; i < climb; i++) {
|
|
2615
|
+
climbArray1.push("..");
|
|
2616
|
+
}
|
|
2617
|
+
let climbXpath = "xpath=" + climbArray1.join("/");
|
|
2618
|
+
css = css + " >> " + climbXpath;
|
|
2619
|
+
const count = await frame.locator(css).count();
|
|
2620
|
+
for (let j = 0; j < count; j++) {
|
|
2621
|
+
const continer = await frame.locator(css).nth(j);
|
|
2622
|
+
const result = await this._locateElementByText(continer, textToVerify, "*:not(script, style, head)", false, true, true, {});
|
|
2623
|
+
if (result.elementCount > 0) {
|
|
2624
|
+
const dataAttribute = "[data-blinq-id-" + result.randomToken + "]";
|
|
2625
|
+
await this._highlightElements(frame, dataAttribute);
|
|
2626
|
+
//const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
|
|
2627
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
2628
|
+
// console.log(`Highlighting for vtrt while running from recorder`);
|
|
2629
|
+
// this._highlightElements(frame, dataAttribute)
|
|
2630
|
+
// .then(async () => {
|
|
2631
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2632
|
+
// this._unhighlightElements(frame, dataAttribute).then(
|
|
2633
|
+
// () => {}
|
|
2634
|
+
// console.log(`Unhighlighting vrtr in recorder is successful`)
|
|
2635
|
+
// );
|
|
2636
|
+
// })
|
|
2637
|
+
// .catch(e);
|
|
2638
|
+
// }
|
|
2639
|
+
//await this._highlightElements(frame, cssAnchor);
|
|
2640
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2641
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2642
|
+
// await this._unhighlightElements(frame, dataAttribute);
|
|
2643
|
+
if (element) {
|
|
2644
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2645
|
+
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2646
|
+
}
|
|
2647
|
+
await _screenshot(state, this);
|
|
2648
|
+
return state.info;
|
|
2303
2649
|
}
|
|
2304
|
-
await _screenshot(state, this);
|
|
2305
|
-
return state.info;
|
|
2306
2650
|
}
|
|
2307
2651
|
}
|
|
2308
2652
|
}
|
|
2653
|
+
catch (error) {
|
|
2654
|
+
console.error(error);
|
|
2655
|
+
}
|
|
2309
2656
|
}
|
|
2310
2657
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2311
2658
|
}
|
|
@@ -2316,6 +2663,30 @@ class StableBrowser {
|
|
|
2316
2663
|
_commandFinally(state, this);
|
|
2317
2664
|
}
|
|
2318
2665
|
}
|
|
2666
|
+
async findRelatedTextInAllFrames(textAnchor, climb, textToVerify, params = {}, options = {}, world = null) {
|
|
2667
|
+
const frames = this.page.frames();
|
|
2668
|
+
let results = [];
|
|
2669
|
+
let ignoreCase = false;
|
|
2670
|
+
for (let i = 0; i < frames.length; i++) {
|
|
2671
|
+
const result = await this._locateElementByText(frames[i], textAnchor, "*:not(script, style, head)", false, true, ignoreCase, {});
|
|
2672
|
+
result.frame = frames[i];
|
|
2673
|
+
const climbArray = [];
|
|
2674
|
+
for (let i = 0; i < climb; i++) {
|
|
2675
|
+
climbArray.push("..");
|
|
2676
|
+
}
|
|
2677
|
+
let climbXpath = "xpath=" + climbArray.join("/");
|
|
2678
|
+
const newLocator = `[data-blinq-id-${result.randomToken}] ${climb > 0 ? ">> " + climbXpath : ""} >> internal:text=${testForRegex(textToVerify) ? textToVerify : unEscapeString(textToVerify)}`;
|
|
2679
|
+
const count = await frames[i].locator(newLocator).count();
|
|
2680
|
+
if (count > 0) {
|
|
2681
|
+
result.elementCount = count;
|
|
2682
|
+
result.locator = newLocator;
|
|
2683
|
+
results.push(result);
|
|
2684
|
+
}
|
|
2685
|
+
}
|
|
2686
|
+
// state.info.results = results;
|
|
2687
|
+
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2688
|
+
return resultWithElementsFound;
|
|
2689
|
+
}
|
|
2319
2690
|
async visualVerification(text, options = {}, world = null) {
|
|
2320
2691
|
const startTime = Date.now();
|
|
2321
2692
|
let error = null;
|
|
@@ -2334,10 +2705,13 @@ class StableBrowser {
|
|
|
2334
2705
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2335
2706
|
info.screenshotPath = screenshotPath;
|
|
2336
2707
|
const screenshot = await this.takeScreenshot();
|
|
2337
|
-
|
|
2338
|
-
method: "
|
|
2708
|
+
let request = {
|
|
2709
|
+
method: "post",
|
|
2710
|
+
maxBodyLength: Infinity,
|
|
2339
2711
|
url: `${serviceUrl}/api/runs/screenshots/validate-screenshot`,
|
|
2340
2712
|
headers: {
|
|
2713
|
+
"x-bvt-project-id": path.basename(this.project_path),
|
|
2714
|
+
"x-source": "aaa",
|
|
2341
2715
|
"Content-Type": "application/json",
|
|
2342
2716
|
Authorization: `Bearer ${process.env.TOKEN}`,
|
|
2343
2717
|
},
|
|
@@ -2346,7 +2720,7 @@ class StableBrowser {
|
|
|
2346
2720
|
screenshot: screenshot,
|
|
2347
2721
|
}),
|
|
2348
2722
|
};
|
|
2349
|
-
|
|
2723
|
+
const result = await axios.request(request);
|
|
2350
2724
|
if (result.data.status !== true) {
|
|
2351
2725
|
throw new Error("Visual validation failed");
|
|
2352
2726
|
}
|
|
@@ -2374,6 +2748,7 @@ class StableBrowser {
|
|
|
2374
2748
|
_reportToWorld(world, {
|
|
2375
2749
|
type: Types.VERIFY_VISUAL,
|
|
2376
2750
|
text: "Visual verification",
|
|
2751
|
+
_text: "Visual verification of " + text,
|
|
2377
2752
|
screenshotId,
|
|
2378
2753
|
result: error
|
|
2379
2754
|
? {
|
|
@@ -2640,6 +3015,32 @@ class StableBrowser {
|
|
|
2640
3015
|
}
|
|
2641
3016
|
return timeout;
|
|
2642
3017
|
}
|
|
3018
|
+
_getFindElementTimeout(options) {
|
|
3019
|
+
if (options && options.timeout) {
|
|
3020
|
+
return options.timeout;
|
|
3021
|
+
}
|
|
3022
|
+
if (this.configuration.find_element_timeout) {
|
|
3023
|
+
return this.configuration.find_element_timeout;
|
|
3024
|
+
}
|
|
3025
|
+
return 30000;
|
|
3026
|
+
}
|
|
3027
|
+
async saveStoreState(path = null, world = null) {
|
|
3028
|
+
const storageState = await this.page.context().storageState();
|
|
3029
|
+
//const testDataFile = _getDataFile(world, this.context, this);
|
|
3030
|
+
if (path) {
|
|
3031
|
+
// save { storageState: storageState } into the path
|
|
3032
|
+
fs.writeFileSync(path, JSON.stringify({ storageState: storageState }, null, 2));
|
|
3033
|
+
}
|
|
3034
|
+
else {
|
|
3035
|
+
await this.setTestData({ storageState: storageState }, world);
|
|
3036
|
+
}
|
|
3037
|
+
}
|
|
3038
|
+
async restoreSaveState(path = null, world = null) {
|
|
3039
|
+
await refreshBrowser(this, path, world);
|
|
3040
|
+
this.registerEventListeners(this.context);
|
|
3041
|
+
registerNetworkEvents(this.world, this, this.context, this.page);
|
|
3042
|
+
registerDownloadEvent(this.page, this.world, this.context);
|
|
3043
|
+
}
|
|
2643
3044
|
async waitForPageLoad(options = {}, world = null) {
|
|
2644
3045
|
let timeout = this._getLoadTimeout(options);
|
|
2645
3046
|
const promiseArray = [];
|
|
@@ -2707,6 +3108,7 @@ class StableBrowser {
|
|
|
2707
3108
|
highlight: false,
|
|
2708
3109
|
type: Types.CLOSE_PAGE,
|
|
2709
3110
|
text: `Close page`,
|
|
3111
|
+
_text: `Close the page`,
|
|
2710
3112
|
operation: "closePage",
|
|
2711
3113
|
log: "***** close page *****\n",
|
|
2712
3114
|
throwError: false,
|
|
@@ -2723,8 +3125,95 @@ class StableBrowser {
|
|
|
2723
3125
|
_commandFinally(state, this);
|
|
2724
3126
|
}
|
|
2725
3127
|
}
|
|
3128
|
+
async tableCellOperation(headerText, rowText, options, _params, world = null) {
|
|
3129
|
+
let operation = null;
|
|
3130
|
+
if (!options || !options.operation) {
|
|
3131
|
+
throw new Error("operation is not defined");
|
|
3132
|
+
}
|
|
3133
|
+
operation = options.operation;
|
|
3134
|
+
// validate operation is one of the supported operations
|
|
3135
|
+
if (operation != "click" && operation != "hover+click") {
|
|
3136
|
+
throw new Error("operation is not supported");
|
|
3137
|
+
}
|
|
3138
|
+
const state = {
|
|
3139
|
+
options,
|
|
3140
|
+
world,
|
|
3141
|
+
locate: false,
|
|
3142
|
+
scroll: false,
|
|
3143
|
+
highlight: false,
|
|
3144
|
+
type: Types.TABLE_OPERATION,
|
|
3145
|
+
text: `Table operation`,
|
|
3146
|
+
_text: `Table ${operation} operation`,
|
|
3147
|
+
operation: operation,
|
|
3148
|
+
log: "***** Table operation *****\n",
|
|
3149
|
+
};
|
|
3150
|
+
const timeout = this._getFindElementTimeout(options);
|
|
3151
|
+
try {
|
|
3152
|
+
await _preCommand(state, this);
|
|
3153
|
+
const start = Date.now();
|
|
3154
|
+
let cellArea = null;
|
|
3155
|
+
while (true) {
|
|
3156
|
+
try {
|
|
3157
|
+
cellArea = await _findCellArea(headerText, rowText, this, state);
|
|
3158
|
+
if (cellArea) {
|
|
3159
|
+
break;
|
|
3160
|
+
}
|
|
3161
|
+
}
|
|
3162
|
+
catch (e) {
|
|
3163
|
+
// ignore
|
|
3164
|
+
}
|
|
3165
|
+
if (Date.now() - start > timeout) {
|
|
3166
|
+
throw new Error(`Cell not found in table`);
|
|
3167
|
+
}
|
|
3168
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
3169
|
+
}
|
|
3170
|
+
switch (operation) {
|
|
3171
|
+
case "click":
|
|
3172
|
+
if (!options.css) {
|
|
3173
|
+
// will click in the center of the cell
|
|
3174
|
+
let xOffset = 0;
|
|
3175
|
+
let yOffset = 0;
|
|
3176
|
+
if (options.xOffset) {
|
|
3177
|
+
xOffset = options.xOffset;
|
|
3178
|
+
}
|
|
3179
|
+
if (options.yOffset) {
|
|
3180
|
+
yOffset = options.yOffset;
|
|
3181
|
+
}
|
|
3182
|
+
await this.page.mouse.click(cellArea.x + cellArea.width / 2 + xOffset, cellArea.y + cellArea.height / 2 + yOffset);
|
|
3183
|
+
}
|
|
3184
|
+
else {
|
|
3185
|
+
const results = await findElementsInArea(options.css, cellArea, this, options);
|
|
3186
|
+
if (results.length === 0) {
|
|
3187
|
+
throw new Error(`Element not found in cell area`);
|
|
3188
|
+
}
|
|
3189
|
+
state.element = results[0];
|
|
3190
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
3191
|
+
}
|
|
3192
|
+
break;
|
|
3193
|
+
case "hover+click":
|
|
3194
|
+
if (!options.css) {
|
|
3195
|
+
throw new Error("css is not defined");
|
|
3196
|
+
}
|
|
3197
|
+
const results = await findElementsInArea(options.css, cellArea, this, options);
|
|
3198
|
+
if (results.length === 0) {
|
|
3199
|
+
throw new Error(`Element not found in cell area`);
|
|
3200
|
+
}
|
|
3201
|
+
state.element = results[0];
|
|
3202
|
+
await performAction("hover+click", state.element, options, this, state, _params);
|
|
3203
|
+
break;
|
|
3204
|
+
default:
|
|
3205
|
+
throw new Error("operation is not supported");
|
|
3206
|
+
}
|
|
3207
|
+
}
|
|
3208
|
+
catch (e) {
|
|
3209
|
+
await _commandError(state, e, this);
|
|
3210
|
+
}
|
|
3211
|
+
finally {
|
|
3212
|
+
_commandFinally(state, this);
|
|
3213
|
+
}
|
|
3214
|
+
}
|
|
2726
3215
|
saveTestDataAsGlobal(options, world) {
|
|
2727
|
-
const dataFile =
|
|
3216
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
2728
3217
|
process.env.GLOBAL_TEST_DATA_FILE = dataFile;
|
|
2729
3218
|
this.logger.info("Save the scenario test data as global for the following scenarios.");
|
|
2730
3219
|
}
|
|
@@ -2754,6 +3243,7 @@ class StableBrowser {
|
|
|
2754
3243
|
_reportToWorld(world, {
|
|
2755
3244
|
type: Types.SET_VIEWPORT,
|
|
2756
3245
|
text: "set viewport size to " + width + "x" + hight,
|
|
3246
|
+
_text: "Set the viewport size to " + width + "x" + hight,
|
|
2757
3247
|
screenshotId,
|
|
2758
3248
|
result: error
|
|
2759
3249
|
? {
|
|
@@ -2841,6 +3331,9 @@ class StableBrowser {
|
|
|
2841
3331
|
else {
|
|
2842
3332
|
this.stepName = "step " + this.stepIndex;
|
|
2843
3333
|
}
|
|
3334
|
+
if (this.context) {
|
|
3335
|
+
this.context.examplesRow = extractStepExampleParameters(step);
|
|
3336
|
+
}
|
|
2844
3337
|
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
2845
3338
|
if (this.context.browserObject.context) {
|
|
2846
3339
|
await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
|
|
@@ -2853,6 +3346,41 @@ class StableBrowser {
|
|
|
2853
3346
|
this.saveTestDataAsGlobal({}, world);
|
|
2854
3347
|
}
|
|
2855
3348
|
}
|
|
3349
|
+
if (this.initSnapshotTaken === false) {
|
|
3350
|
+
this.initSnapshotTaken = true;
|
|
3351
|
+
if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
|
|
3352
|
+
const snapshot = await this.getAriaSnapshot();
|
|
3353
|
+
if (snapshot) {
|
|
3354
|
+
await world.attach(JSON.stringify(snapshot), "application/json+snapshot-before");
|
|
3355
|
+
}
|
|
3356
|
+
}
|
|
3357
|
+
}
|
|
3358
|
+
}
|
|
3359
|
+
async getAriaSnapshot() {
|
|
3360
|
+
try {
|
|
3361
|
+
// find the page url
|
|
3362
|
+
const url = await this.page.url();
|
|
3363
|
+
// extract the path from the url
|
|
3364
|
+
const path = new URL(url).pathname;
|
|
3365
|
+
// get the page title
|
|
3366
|
+
const title = await this.page.title();
|
|
3367
|
+
// go over other frams
|
|
3368
|
+
const frames = this.page.frames();
|
|
3369
|
+
const snapshots = [];
|
|
3370
|
+
const content = [`- path: ${path}`, `- title: ${title}`];
|
|
3371
|
+
const timeout = this.configuration.ariaSnapshotTimeout ? this.configuration.ariaSnapshotTimeout : 3000;
|
|
3372
|
+
for (let i = 0; i < frames.length; i++) {
|
|
3373
|
+
content.push(`- frame: ${i}`);
|
|
3374
|
+
const frame = frames[i];
|
|
3375
|
+
const snapshot = await frame.locator("body").ariaSnapshot({ timeout });
|
|
3376
|
+
content.push(snapshot);
|
|
3377
|
+
}
|
|
3378
|
+
return content.join("\n");
|
|
3379
|
+
}
|
|
3380
|
+
catch (e) {
|
|
3381
|
+
console.error(e);
|
|
3382
|
+
}
|
|
3383
|
+
return null;
|
|
2856
3384
|
}
|
|
2857
3385
|
async afterStep(world, step) {
|
|
2858
3386
|
this.stepName = null;
|
|
@@ -2861,6 +3389,23 @@ class StableBrowser {
|
|
|
2861
3389
|
await this.context.browserObject.context.tracing.stopChunk({
|
|
2862
3390
|
path: path.join(this.context.browserObject.traceFolder, `trace-${this.stepIndex}.zip`),
|
|
2863
3391
|
});
|
|
3392
|
+
if (world && world.attach) {
|
|
3393
|
+
await world.attach(JSON.stringify({
|
|
3394
|
+
type: "trace",
|
|
3395
|
+
traceFilePath: `trace-${this.stepIndex}.zip`,
|
|
3396
|
+
}), "application/json+trace");
|
|
3397
|
+
}
|
|
3398
|
+
// console.log("trace file created", `trace-${this.stepIndex}.zip`);
|
|
3399
|
+
}
|
|
3400
|
+
}
|
|
3401
|
+
if (this.context) {
|
|
3402
|
+
this.context.examplesRow = null;
|
|
3403
|
+
}
|
|
3404
|
+
if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
|
|
3405
|
+
const snapshot = await this.getAriaSnapshot();
|
|
3406
|
+
if (snapshot) {
|
|
3407
|
+
const obj = {};
|
|
3408
|
+
await world.attach(JSON.stringify(snapshot), "application/json+snapshot-after");
|
|
2864
3409
|
}
|
|
2865
3410
|
}
|
|
2866
3411
|
}
|