automation_model 1.0.480-dev → 1.0.480-stage
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/api.d.ts +2 -1
- package/lib/api.js +92 -92
- package/lib/api.js.map +1 -1
- package/lib/auto_page.d.ts +2 -1
- package/lib/auto_page.js +39 -17
- package/lib/auto_page.js.map +1 -1
- package/lib/browser_manager.d.ts +6 -3
- package/lib/browser_manager.js +86 -16
- package/lib/browser_manager.js.map +1 -1
- package/lib/command_common.d.ts +1 -0
- package/lib/command_common.js +58 -5
- package/lib/command_common.js.map +1 -1
- package/lib/error-messages.d.ts +6 -0
- package/lib/error-messages.js +188 -0
- package/lib/error-messages.js.map +1 -0
- package/lib/generation_scripts.d.ts +4 -0
- package/lib/generation_scripts.js +2 -0
- package/lib/generation_scripts.js.map +1 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/lib/init_browser.d.ts +2 -1
- package/lib/init_browser.js +31 -4
- package/lib/init_browser.js.map +1 -1
- package/lib/locate_element.js +5 -3
- package/lib/locate_element.js.map +1 -1
- package/lib/locator.d.ts +36 -0
- package/lib/locator.js +165 -0
- package/lib/locator.js.map +1 -1
- package/lib/locator_log.d.ts +26 -0
- package/lib/locator_log.js +69 -0
- package/lib/locator_log.js.map +1 -0
- package/lib/network.d.ts +3 -0
- package/lib/network.js +155 -0
- package/lib/network.js.map +1 -0
- package/lib/stable_browser.d.ts +44 -9
- package/lib/stable_browser.js +482 -255
- package/lib/stable_browser.js.map +1 -1
- package/lib/table.d.ts +13 -0
- package/lib/table.js +187 -0
- package/lib/table.js.map +1 -0
- package/lib/test_context.d.ts +1 -0
- package/lib/test_context.js +1 -0
- package/lib/test_context.js.map +1 -1
- package/lib/utils.d.ts +3 -1
- package/lib/utils.js +68 -1
- package/lib/utils.js.map +1 -1
- package/package.json +5 -4
package/lib/stable_browser.js
CHANGED
|
@@ -10,15 +10,17 @@ import { getDateTimeValue } from "./date_time.js";
|
|
|
10
10
|
import drawRectangle from "./drawRect.js";
|
|
11
11
|
//import { closeUnexpectedPopups } from "./popups.js";
|
|
12
12
|
import { getTableCells, getTableData } from "./table_analyze.js";
|
|
13
|
-
import
|
|
14
|
-
import { decrypt } from "./utils.js";
|
|
13
|
+
import { maskValue, replaceWithLocalTestData } from "./utils.js";
|
|
15
14
|
import csv from "csv-parser";
|
|
16
15
|
import { Readable } from "node:stream";
|
|
17
16
|
import readline from "readline";
|
|
18
17
|
import { getContext } from "./init_browser.js";
|
|
19
18
|
import { locate_element } from "./locate_element.js";
|
|
20
|
-
import {
|
|
21
|
-
|
|
19
|
+
import { randomUUID } from "crypto";
|
|
20
|
+
import { _commandError, _commandFinally, _preCommand, _validateSelectors, _screenshot, _reportToWorld, } from "./command_common.js";
|
|
21
|
+
import { registerDownloadEvent, registerNetworkEvents } from "./network.js";
|
|
22
|
+
import { LocatorLog } from "./locator_log.js";
|
|
23
|
+
export const Types = {
|
|
22
24
|
CLICK: "click_element",
|
|
23
25
|
NAVIGATE: "navigate",
|
|
24
26
|
FILL: "fill_element",
|
|
@@ -44,6 +46,8 @@ const Types = {
|
|
|
44
46
|
VERIFY_VISUAL: "verify_visual",
|
|
45
47
|
LOAD_DATA: "load_data",
|
|
46
48
|
SET_INPUT: "set_input",
|
|
49
|
+
WAIT_FOR_TEXT_TO_DISAPPEAR: "wait_for_text_to_disappear",
|
|
50
|
+
VERIFY_ATTRIBUTE: "verify_element_attribute",
|
|
47
51
|
};
|
|
48
52
|
export const apps = {};
|
|
49
53
|
class StableBrowser {
|
|
@@ -57,6 +61,7 @@ class StableBrowser {
|
|
|
57
61
|
networkLogger = null;
|
|
58
62
|
configuration = null;
|
|
59
63
|
appName = "main";
|
|
64
|
+
tags = null;
|
|
60
65
|
constructor(browser, page, logger = null, context = null, world = null) {
|
|
61
66
|
this.browser = browser;
|
|
62
67
|
this.page = page;
|
|
@@ -87,23 +92,50 @@ class StableBrowser {
|
|
|
87
92
|
catch (e) {
|
|
88
93
|
this.logger.error("unable to read ai_config.json");
|
|
89
94
|
}
|
|
90
|
-
context.pageLoading = { status: false };
|
|
91
|
-
context.pages = [this.page];
|
|
92
95
|
const logFolder = path.join(this.project_path, "logs", "web");
|
|
93
96
|
this.world = world;
|
|
97
|
+
context.pages = [this.page];
|
|
98
|
+
context.pageLoading = { status: false };
|
|
94
99
|
this.registerEventListeners(this.context);
|
|
100
|
+
registerNetworkEvents(this.world, this, this.context, this.page);
|
|
101
|
+
registerDownloadEvent(this.page, this.world, this.context);
|
|
102
|
+
}
|
|
103
|
+
async scrollPageToLoadLazyElements() {
|
|
104
|
+
let lastHeight = await this.page.evaluate(() => document.body.scrollHeight);
|
|
105
|
+
let retry = 0;
|
|
106
|
+
while (true) {
|
|
107
|
+
await this.page.evaluate(() => window.scrollBy(0, window.innerHeight));
|
|
108
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
109
|
+
let newHeight = await this.page.evaluate(() => document.body.scrollHeight);
|
|
110
|
+
if (newHeight === lastHeight) {
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
lastHeight = newHeight;
|
|
114
|
+
retry++;
|
|
115
|
+
if (retry > 10) {
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
await this.page.evaluate(() => window.scrollTo(0, 0));
|
|
95
120
|
}
|
|
96
121
|
registerEventListeners(context) {
|
|
97
122
|
this.registerConsoleLogListener(this.page, context);
|
|
98
|
-
this.registerRequestListener(this.page, context, this.webLogFile);
|
|
123
|
+
// this.registerRequestListener(this.page, context, this.webLogFile);
|
|
99
124
|
if (!context.pageLoading) {
|
|
100
125
|
context.pageLoading = { status: false };
|
|
101
126
|
}
|
|
102
127
|
context.playContext.on("page", async function (page) {
|
|
128
|
+
if (this.configuration && this.configuration.closePopups === true) {
|
|
129
|
+
console.log("close unexpected popups");
|
|
130
|
+
await page.close();
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
103
133
|
context.pageLoading.status = true;
|
|
104
134
|
this.page = page;
|
|
105
135
|
context.page = page;
|
|
106
136
|
context.pages.push(page);
|
|
137
|
+
registerNetworkEvents(this.world, this, context, this.page);
|
|
138
|
+
registerDownloadEvent(this.page, this.world, context);
|
|
107
139
|
page.on("close", async () => {
|
|
108
140
|
if (this.context && this.context.pages && this.context.pages.length > 1) {
|
|
109
141
|
this.context.pages.pop();
|
|
@@ -133,9 +165,9 @@ class StableBrowser {
|
|
|
133
165
|
if (this.appName === appName) {
|
|
134
166
|
return;
|
|
135
167
|
}
|
|
136
|
-
let
|
|
168
|
+
let navigate = false;
|
|
137
169
|
if (!apps[appName]) {
|
|
138
|
-
let newContext = await getContext(null, this.context.headless ? this.context.headless : false, this, this.logger, appName, false, this);
|
|
170
|
+
let newContext = await getContext(null, this.context.headless ? this.context.headless : false, this, this.logger, appName, false, this, -1, this.context.reportFolder);
|
|
139
171
|
newContextCreated = true;
|
|
140
172
|
apps[appName] = {
|
|
141
173
|
context: newContext,
|
|
@@ -148,8 +180,7 @@ class StableBrowser {
|
|
|
148
180
|
this._copyContext(apps[appName], this);
|
|
149
181
|
apps[this.appName] = tempContext;
|
|
150
182
|
this.appName = appName;
|
|
151
|
-
if (
|
|
152
|
-
this.registerEventListeners(this.context);
|
|
183
|
+
if (navigate) {
|
|
153
184
|
await this.goto(this.context.environment.baseUrl);
|
|
154
185
|
await this.waitForPageLoad();
|
|
155
186
|
}
|
|
@@ -219,7 +250,7 @@ class StableBrowser {
|
|
|
219
250
|
this.world?.attach(JSON.stringify(obj), { mediaType: "application/json+network" });
|
|
220
251
|
}
|
|
221
252
|
catch (error) {
|
|
222
|
-
console.error("Error in request listener", error);
|
|
253
|
+
// console.error("Error in request listener", error);
|
|
223
254
|
context.networkLogger.push({
|
|
224
255
|
error: "not able to listen",
|
|
225
256
|
message: error.message,
|
|
@@ -332,7 +363,7 @@ class StableBrowser {
|
|
|
332
363
|
if (css && css.locator) {
|
|
333
364
|
css = css.locator;
|
|
334
365
|
}
|
|
335
|
-
let result = await this._locateElementByText(scope, this._fixUsingParams(text, _params), "
|
|
366
|
+
let result = await this._locateElementByText(scope, this._fixUsingParams(text, _params), "*:not(script, style, head)", false, false, _params);
|
|
336
367
|
if (result.elementCount === 0) {
|
|
337
368
|
return;
|
|
338
369
|
}
|
|
@@ -384,7 +415,7 @@ class StableBrowser {
|
|
|
384
415
|
}
|
|
385
416
|
document.collectAllShadowDomElements = collectAllShadowDomElements;
|
|
386
417
|
if (!tag) {
|
|
387
|
-
tag = "
|
|
418
|
+
tag = "*:not(script, style, head)";
|
|
388
419
|
}
|
|
389
420
|
let regexpSearch = document.getRegex(text);
|
|
390
421
|
if (regexpSearch) {
|
|
@@ -466,9 +497,21 @@ class StableBrowser {
|
|
|
466
497
|
}, [text1, tag1, regex1, partial1]);
|
|
467
498
|
}
|
|
468
499
|
async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true) {
|
|
500
|
+
if (!info) {
|
|
501
|
+
info = {};
|
|
502
|
+
}
|
|
503
|
+
if (!info.failCause) {
|
|
504
|
+
info.failCause = {};
|
|
505
|
+
}
|
|
506
|
+
if (!info.log) {
|
|
507
|
+
info.log = "";
|
|
508
|
+
info.locatorLog = new LocatorLog(selectorHierarchy);
|
|
509
|
+
}
|
|
469
510
|
let locatorSearch = selectorHierarchy[index];
|
|
511
|
+
let originalLocatorSearch = "";
|
|
470
512
|
try {
|
|
471
|
-
|
|
513
|
+
originalLocatorSearch = this._fixUsingParams(JSON.stringify(locatorSearch), _params);
|
|
514
|
+
locatorSearch = JSON.parse(originalLocatorSearch);
|
|
472
515
|
}
|
|
473
516
|
catch (e) {
|
|
474
517
|
console.error(e);
|
|
@@ -478,13 +521,18 @@ class StableBrowser {
|
|
|
478
521
|
if (locatorSearch.climb && locatorSearch.climb >= 0) {
|
|
479
522
|
let locatorString = await this._locateElmentByTextClimbCss(scope, locatorSearch.text, locatorSearch.climb, locatorSearch.css, _params);
|
|
480
523
|
if (!locatorString) {
|
|
524
|
+
info.failCause.textNotFound = true;
|
|
525
|
+
info.failCause.lastError = "failed to locate element by text: " + locatorSearch.text;
|
|
481
526
|
return;
|
|
482
527
|
}
|
|
483
528
|
locator = this._getLocator({ css: locatorString }, scope, _params);
|
|
484
529
|
}
|
|
485
530
|
else if (locatorSearch.text) {
|
|
486
|
-
let
|
|
531
|
+
let text = this._fixUsingParams(locatorSearch.text, _params);
|
|
532
|
+
let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, _params);
|
|
487
533
|
if (result.elementCount === 0) {
|
|
534
|
+
info.failCause.textNotFound = true;
|
|
535
|
+
info.failCause.lastError = "failed to locate element by text: " + text;
|
|
488
536
|
return;
|
|
489
537
|
}
|
|
490
538
|
locatorSearch.css = "[data-blinq-id='blinq-id-" + result.randomToken + "']";
|
|
@@ -501,13 +549,22 @@ class StableBrowser {
|
|
|
501
549
|
// cssHref = true;
|
|
502
550
|
// }
|
|
503
551
|
let count = await locator.count();
|
|
552
|
+
if (count > 0 && !info.failCause.count) {
|
|
553
|
+
info.failCause.count = count;
|
|
554
|
+
}
|
|
504
555
|
//info.log += "total elements found " + count + "\n";
|
|
505
556
|
//let visibleCount = 0;
|
|
506
557
|
let visibleLocator = null;
|
|
507
|
-
if (locatorSearch.index && locatorSearch.index < count) {
|
|
558
|
+
if (typeof locatorSearch.index === "number" && locatorSearch.index < count) {
|
|
508
559
|
foundLocators.push(locator.nth(locatorSearch.index));
|
|
560
|
+
if (info.locatorLog) {
|
|
561
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
|
|
562
|
+
}
|
|
509
563
|
return;
|
|
510
564
|
}
|
|
565
|
+
if (info.locatorLog && count === 0) {
|
|
566
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "NOT_FOUND");
|
|
567
|
+
}
|
|
511
568
|
for (let j = 0; j < count; j++) {
|
|
512
569
|
let visible = await locator.nth(j).isVisible();
|
|
513
570
|
const enabled = await locator.nth(j).isEnabled();
|
|
@@ -516,24 +573,40 @@ class StableBrowser {
|
|
|
516
573
|
}
|
|
517
574
|
if (visible && enabled) {
|
|
518
575
|
foundLocators.push(locator.nth(j));
|
|
576
|
+
if (info.locatorLog) {
|
|
577
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
|
|
578
|
+
}
|
|
519
579
|
}
|
|
520
580
|
else {
|
|
581
|
+
info.failCause.visible = visible;
|
|
582
|
+
info.failCause.enabled = enabled;
|
|
521
583
|
if (!info.printMessages) {
|
|
522
584
|
info.printMessages = {};
|
|
523
585
|
}
|
|
586
|
+
if (info.locatorLog && !visible) {
|
|
587
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_VISIBLE");
|
|
588
|
+
}
|
|
589
|
+
if (info.locatorLog && !enabled) {
|
|
590
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_ENABLED");
|
|
591
|
+
}
|
|
524
592
|
if (!info.printMessages[j.toString()]) {
|
|
525
|
-
info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
|
|
593
|
+
//info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
|
|
526
594
|
info.printMessages[j.toString()] = true;
|
|
527
595
|
}
|
|
528
596
|
}
|
|
529
597
|
}
|
|
530
598
|
}
|
|
531
599
|
async closeUnexpectedPopups(info, _params) {
|
|
600
|
+
if (!info) {
|
|
601
|
+
info = {};
|
|
602
|
+
info.failCause = {};
|
|
603
|
+
info.log = "";
|
|
604
|
+
}
|
|
532
605
|
if (this.configuration.popupHandlers && this.configuration.popupHandlers.length > 0) {
|
|
533
606
|
if (!info) {
|
|
534
607
|
info = {};
|
|
535
608
|
}
|
|
536
|
-
info.log += "scan for popup handlers" + "\n";
|
|
609
|
+
//info.log += "scan for popup handlers" + "\n";
|
|
537
610
|
const handlerGroup = [];
|
|
538
611
|
for (let i = 0; i < this.configuration.popupHandlers.length; i++) {
|
|
539
612
|
handlerGroup.push(this.configuration.popupHandlers[i].locator);
|
|
@@ -560,9 +633,21 @@ class StableBrowser {
|
|
|
560
633
|
}
|
|
561
634
|
if (result.foundElements.length > 0) {
|
|
562
635
|
let dialogCloseLocator = result.foundElements[0].locator;
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
636
|
+
try {
|
|
637
|
+
await scope?.evaluate(() => {
|
|
638
|
+
window.__isClosingPopups = true;
|
|
639
|
+
});
|
|
640
|
+
await dialogCloseLocator.click();
|
|
641
|
+
// wait for the dialog to close
|
|
642
|
+
await dialogCloseLocator.waitFor({ state: "hidden" });
|
|
643
|
+
}
|
|
644
|
+
catch (e) {
|
|
645
|
+
}
|
|
646
|
+
finally {
|
|
647
|
+
await scope?.evaluate(() => {
|
|
648
|
+
window.__isClosingPopups = false;
|
|
649
|
+
});
|
|
650
|
+
}
|
|
566
651
|
return { rerun: true };
|
|
567
652
|
}
|
|
568
653
|
}
|
|
@@ -586,7 +671,13 @@ class StableBrowser {
|
|
|
586
671
|
}
|
|
587
672
|
throw new Error("unable to locate element " + JSON.stringify(selectors));
|
|
588
673
|
}
|
|
589
|
-
async _findFrameScope(selectors, timeout = 30000) {
|
|
674
|
+
async _findFrameScope(selectors, timeout = 30000, info) {
|
|
675
|
+
if (!info) {
|
|
676
|
+
info = {};
|
|
677
|
+
info.failCause = {};
|
|
678
|
+
info.log = "";
|
|
679
|
+
}
|
|
680
|
+
let startTime = Date.now();
|
|
590
681
|
let scope = this.page;
|
|
591
682
|
if (selectors.frame) {
|
|
592
683
|
return selectors.frame;
|
|
@@ -617,9 +708,11 @@ class StableBrowser {
|
|
|
617
708
|
}
|
|
618
709
|
return framescope;
|
|
619
710
|
};
|
|
711
|
+
let fLocator = null;
|
|
620
712
|
while (true) {
|
|
621
713
|
let frameFound = false;
|
|
622
714
|
if (selectors.nestFrmLoc) {
|
|
715
|
+
fLocator = selectors.nestFrmLoc;
|
|
623
716
|
scope = await findFrame(selectors.nestFrmLoc, scope);
|
|
624
717
|
frameFound = true;
|
|
625
718
|
break;
|
|
@@ -628,6 +721,7 @@ class StableBrowser {
|
|
|
628
721
|
for (let i = 0; i < selectors.frameLocators.length; i++) {
|
|
629
722
|
let frameLocator = selectors.frameLocators[i];
|
|
630
723
|
if (frameLocator.css) {
|
|
724
|
+
fLocator = frameLocator.css;
|
|
631
725
|
scope = scope.frameLocator(frameLocator.css);
|
|
632
726
|
frameFound = true;
|
|
633
727
|
break;
|
|
@@ -635,16 +729,25 @@ class StableBrowser {
|
|
|
635
729
|
}
|
|
636
730
|
}
|
|
637
731
|
if (!frameFound && selectors.iframe_src) {
|
|
732
|
+
fLocator = selectors.iframe_src;
|
|
638
733
|
scope = this.page.frame({ url: selectors.iframe_src });
|
|
639
734
|
}
|
|
640
735
|
if (!scope) {
|
|
641
|
-
info
|
|
642
|
-
|
|
736
|
+
if (info && info.locatorLog) {
|
|
737
|
+
info.locatorLog.setFrameSearchStatus("frame-" + fLocator, "NOT_FOUND");
|
|
738
|
+
}
|
|
739
|
+
//info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
|
|
740
|
+
if (Date.now() - startTime > timeout) {
|
|
741
|
+
info.failCause.iframeNotFound = true;
|
|
742
|
+
info.failCause.lastError = "unable to locate iframe " + selectors.iframe_src;
|
|
643
743
|
throw new Error("unable to locate iframe " + selectors.iframe_src);
|
|
644
744
|
}
|
|
645
745
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
646
746
|
}
|
|
647
747
|
else {
|
|
748
|
+
if (info && info.locatorLog) {
|
|
749
|
+
info.locatorLog.setFrameSearchStatus("frame-" + fLocator, "FOUND");
|
|
750
|
+
}
|
|
648
751
|
break;
|
|
649
752
|
}
|
|
650
753
|
}
|
|
@@ -654,20 +757,27 @@ class StableBrowser {
|
|
|
654
757
|
}
|
|
655
758
|
return scope;
|
|
656
759
|
}
|
|
657
|
-
async _getDocumentBody(selectors, timeout = 30000) {
|
|
658
|
-
let scope = await this._findFrameScope(selectors, timeout);
|
|
760
|
+
async _getDocumentBody(selectors, timeout = 30000, info) {
|
|
761
|
+
let scope = await this._findFrameScope(selectors, timeout, info);
|
|
659
762
|
return scope.evaluate(() => {
|
|
660
763
|
var bodyContent = document.body.innerHTML;
|
|
661
764
|
return bodyContent;
|
|
662
765
|
});
|
|
663
766
|
}
|
|
664
767
|
async _locate_internal(selectors, info, _params, timeout = 30000) {
|
|
768
|
+
if (!info) {
|
|
769
|
+
info = {};
|
|
770
|
+
info.failCause = {};
|
|
771
|
+
info.log = "";
|
|
772
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
773
|
+
}
|
|
665
774
|
let highPriorityTimeout = 5000;
|
|
666
775
|
let visibleOnlyTimeout = 6000;
|
|
667
|
-
let startTime =
|
|
776
|
+
let startTime = Date.now();
|
|
668
777
|
let locatorsCount = 0;
|
|
778
|
+
let lazy_scroll = false;
|
|
669
779
|
//let arrayMode = Array.isArray(selectors);
|
|
670
|
-
let scope = await this._findFrameScope(selectors, timeout);
|
|
780
|
+
let scope = await this._findFrameScope(selectors, timeout, info);
|
|
671
781
|
let selectorsLocators = null;
|
|
672
782
|
selectorsLocators = selectors.locators;
|
|
673
783
|
// group selectors by priority
|
|
@@ -754,21 +864,33 @@ class StableBrowser {
|
|
|
754
864
|
return maxCountElement.locator;
|
|
755
865
|
}
|
|
756
866
|
}
|
|
757
|
-
if (
|
|
867
|
+
if (Date.now() - startTime > timeout) {
|
|
758
868
|
break;
|
|
759
869
|
}
|
|
760
|
-
if (
|
|
870
|
+
if (Date.now() - startTime > highPriorityTimeout) {
|
|
761
871
|
info.log += "high priority timeout, will try all elements" + "\n";
|
|
762
872
|
highPriorityOnly = false;
|
|
873
|
+
if (this.configuration && this.configuration.load_all_lazy === true && !lazy_scroll) {
|
|
874
|
+
lazy_scroll = true;
|
|
875
|
+
await this.scrollPageToLoadLazyElements();
|
|
876
|
+
}
|
|
763
877
|
}
|
|
764
|
-
if (
|
|
878
|
+
if (Date.now() - startTime > visibleOnlyTimeout) {
|
|
765
879
|
info.log += "visible only timeout, will try all elements" + "\n";
|
|
766
880
|
visibleOnly = false;
|
|
767
881
|
}
|
|
768
882
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
769
883
|
}
|
|
770
884
|
this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
|
|
771
|
-
info.
|
|
885
|
+
// if (info.locatorLog) {
|
|
886
|
+
// const lines = info.locatorLog.toString().split("\n");
|
|
887
|
+
// for (let line of lines) {
|
|
888
|
+
// this.logger.debug(line);
|
|
889
|
+
// }
|
|
890
|
+
// }
|
|
891
|
+
//info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
|
|
892
|
+
info.failCause.locatorNotFound = true;
|
|
893
|
+
info.failCause.lastError = "failed to locate unique element";
|
|
772
894
|
throw new Error("failed to locate first element no elements found, " + info.log);
|
|
773
895
|
}
|
|
774
896
|
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
|
|
@@ -800,10 +922,29 @@ class StableBrowser {
|
|
|
800
922
|
});
|
|
801
923
|
result.locatorIndex = i;
|
|
802
924
|
}
|
|
925
|
+
if (foundLocators.length > 1) {
|
|
926
|
+
info.failCause.foundMultiple = true;
|
|
927
|
+
if (info.locatorLog) {
|
|
928
|
+
info.locatorLog.setLocatorSearchStatus(locatorsGroup[i], "FOUND_NOT_UNIQUE");
|
|
929
|
+
}
|
|
930
|
+
}
|
|
803
931
|
}
|
|
804
932
|
return result;
|
|
805
933
|
}
|
|
806
934
|
async simpleClick(elementDescription, _params, options = {}, world = null) {
|
|
935
|
+
const state = {
|
|
936
|
+
locate: false,
|
|
937
|
+
scroll: false,
|
|
938
|
+
highlight: false,
|
|
939
|
+
_params,
|
|
940
|
+
options,
|
|
941
|
+
world,
|
|
942
|
+
type: Types.CLICK,
|
|
943
|
+
text: "Click element",
|
|
944
|
+
operation: "simpleClick",
|
|
945
|
+
log: "***** click on " + elementDescription + " *****\n",
|
|
946
|
+
};
|
|
947
|
+
_preCommand(state, this);
|
|
807
948
|
const startTime = Date.now();
|
|
808
949
|
let timeout = 30000;
|
|
809
950
|
if (options && options.timeout) {
|
|
@@ -827,13 +968,32 @@ class StableBrowser {
|
|
|
827
968
|
}
|
|
828
969
|
catch (e) {
|
|
829
970
|
if (performance.now() - startTime > timeout) {
|
|
830
|
-
throw e;
|
|
971
|
+
// throw e;
|
|
972
|
+
try {
|
|
973
|
+
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
974
|
+
}
|
|
975
|
+
finally {
|
|
976
|
+
_commandFinally(state, this);
|
|
977
|
+
}
|
|
831
978
|
}
|
|
832
979
|
}
|
|
833
980
|
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
834
981
|
}
|
|
835
982
|
}
|
|
836
983
|
async simpleClickType(elementDescription, value, _params, options = {}, world = null) {
|
|
984
|
+
const state = {
|
|
985
|
+
locate: false,
|
|
986
|
+
scroll: false,
|
|
987
|
+
highlight: false,
|
|
988
|
+
_params,
|
|
989
|
+
options,
|
|
990
|
+
world,
|
|
991
|
+
type: Types.FILL,
|
|
992
|
+
text: "Fill element",
|
|
993
|
+
operation: "simpleClickType",
|
|
994
|
+
log: "***** click type on " + elementDescription + " *****\n",
|
|
995
|
+
};
|
|
996
|
+
_preCommand(state, this);
|
|
837
997
|
const startTime = Date.now();
|
|
838
998
|
let timeout = 30000;
|
|
839
999
|
if (options && options.timeout) {
|
|
@@ -857,7 +1017,13 @@ class StableBrowser {
|
|
|
857
1017
|
}
|
|
858
1018
|
catch (e) {
|
|
859
1019
|
if (performance.now() - startTime > timeout) {
|
|
860
|
-
throw e;
|
|
1020
|
+
// throw e;
|
|
1021
|
+
try {
|
|
1022
|
+
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
1023
|
+
}
|
|
1024
|
+
finally {
|
|
1025
|
+
_commandFinally(state, this);
|
|
1026
|
+
}
|
|
861
1027
|
}
|
|
862
1028
|
}
|
|
863
1029
|
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
@@ -881,13 +1047,13 @@ class StableBrowser {
|
|
|
881
1047
|
}
|
|
882
1048
|
try {
|
|
883
1049
|
await state.element.click();
|
|
884
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1050
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
885
1051
|
}
|
|
886
1052
|
catch (e) {
|
|
887
1053
|
// await this.closeUnexpectedPopups();
|
|
888
1054
|
state.element = await this._locate(selectors, state.info, _params);
|
|
889
1055
|
await state.element.dispatchEvent("click");
|
|
890
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1056
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
891
1057
|
}
|
|
892
1058
|
await this.waitForPageLoad();
|
|
893
1059
|
return state.info;
|
|
@@ -1093,33 +1259,30 @@ class StableBrowser {
|
|
|
1093
1259
|
}
|
|
1094
1260
|
}
|
|
1095
1261
|
async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1262
|
+
const state = {
|
|
1263
|
+
selectors,
|
|
1264
|
+
_params,
|
|
1265
|
+
value: await this._replaceWithLocalData(value, this),
|
|
1266
|
+
options,
|
|
1267
|
+
world,
|
|
1268
|
+
type: Types.SET_DATE_TIME,
|
|
1269
|
+
text: `Set date time value: ${value}`,
|
|
1270
|
+
operation: "setDateTime",
|
|
1271
|
+
log: "***** set date time value " + selectors.element_name + " *****\n",
|
|
1272
|
+
throwError: false,
|
|
1273
|
+
};
|
|
1106
1274
|
try {
|
|
1107
|
-
|
|
1108
|
-
let element = await this._locate(selectors, info, _params);
|
|
1109
|
-
//insert red border around the element
|
|
1110
|
-
await this.scrollIfNeeded(element, info);
|
|
1111
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1112
|
-
await this._highlightElements(element);
|
|
1275
|
+
await _preCommand(state, this);
|
|
1113
1276
|
try {
|
|
1114
|
-
await element.click();
|
|
1277
|
+
await state.element.click();
|
|
1115
1278
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1116
1279
|
if (format) {
|
|
1117
|
-
value = dayjs(value).format(format);
|
|
1118
|
-
await element.fill(value);
|
|
1280
|
+
state.value = dayjs(state.value).format(format);
|
|
1281
|
+
await state.element.fill(state.value);
|
|
1119
1282
|
}
|
|
1120
1283
|
else {
|
|
1121
|
-
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
1122
|
-
await element.evaluateHandle((el, dateTimeValue) => {
|
|
1284
|
+
const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
|
|
1285
|
+
await state.element.evaluateHandle((el, dateTimeValue) => {
|
|
1123
1286
|
el.value = ""; // clear input
|
|
1124
1287
|
el.value = dateTimeValue;
|
|
1125
1288
|
}, dateTimeValue);
|
|
@@ -1132,20 +1295,19 @@ class StableBrowser {
|
|
|
1132
1295
|
}
|
|
1133
1296
|
catch (err) {
|
|
1134
1297
|
//await this.closeUnexpectedPopups();
|
|
1135
|
-
this.logger.error("setting date time input failed " + JSON.stringify(info));
|
|
1298
|
+
this.logger.error("setting date time input failed " + JSON.stringify(state.info));
|
|
1136
1299
|
this.logger.info("Trying again");
|
|
1137
|
-
|
|
1138
|
-
info.
|
|
1139
|
-
Object.assign(err, { info: info });
|
|
1300
|
+
await _screenshot(state, this);
|
|
1301
|
+
Object.assign(err, { info: state.info });
|
|
1140
1302
|
await element.click();
|
|
1141
1303
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1142
1304
|
if (format) {
|
|
1143
|
-
value = dayjs(value).format(format);
|
|
1144
|
-
await element.fill(value);
|
|
1305
|
+
state.value = dayjs(state.value).format(format);
|
|
1306
|
+
await state.element.fill(state.value);
|
|
1145
1307
|
}
|
|
1146
1308
|
else {
|
|
1147
|
-
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
1148
|
-
await element.evaluateHandle((el, dateTimeValue) => {
|
|
1309
|
+
const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
|
|
1310
|
+
await state.element.evaluateHandle((el, dateTimeValue) => {
|
|
1149
1311
|
el.value = ""; // clear input
|
|
1150
1312
|
el.value = dateTimeValue;
|
|
1151
1313
|
}, dateTimeValue);
|
|
@@ -1158,50 +1320,30 @@ class StableBrowser {
|
|
|
1158
1320
|
}
|
|
1159
1321
|
}
|
|
1160
1322
|
catch (e) {
|
|
1161
|
-
|
|
1162
|
-
throw e;
|
|
1323
|
+
await _commandError(state, e, this);
|
|
1163
1324
|
}
|
|
1164
1325
|
finally {
|
|
1165
|
-
|
|
1166
|
-
this._reportToWorld(world, {
|
|
1167
|
-
element_name: selectors.element_name,
|
|
1168
|
-
type: Types.SET_DATE_TIME,
|
|
1169
|
-
screenshotId,
|
|
1170
|
-
value: value,
|
|
1171
|
-
text: `setDateTime input with value: ${value}`,
|
|
1172
|
-
result: error
|
|
1173
|
-
? {
|
|
1174
|
-
status: "FAILED",
|
|
1175
|
-
startTime,
|
|
1176
|
-
endTime,
|
|
1177
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1178
|
-
}
|
|
1179
|
-
: {
|
|
1180
|
-
status: "PASSED",
|
|
1181
|
-
startTime,
|
|
1182
|
-
endTime,
|
|
1183
|
-
},
|
|
1184
|
-
info: info,
|
|
1185
|
-
});
|
|
1326
|
+
_commandFinally(state, this);
|
|
1186
1327
|
}
|
|
1187
1328
|
}
|
|
1188
1329
|
async clickType(selectors, _value, enter = false, _params = null, options = {}, world = null) {
|
|
1330
|
+
_value = unEscapeString(_value);
|
|
1331
|
+
const newValue = await this._replaceWithLocalData(_value, world);
|
|
1189
1332
|
const state = {
|
|
1190
1333
|
selectors,
|
|
1191
1334
|
_params,
|
|
1192
|
-
value:
|
|
1335
|
+
value: newValue,
|
|
1336
|
+
originalValue: _value,
|
|
1193
1337
|
options,
|
|
1194
1338
|
world,
|
|
1195
1339
|
type: Types.FILL,
|
|
1196
1340
|
text: `Click type input with value: ${_value}`,
|
|
1197
1341
|
operation: "clickType",
|
|
1198
|
-
log: "***** clickType on " + selectors.element_name + " with value " + _value + "*****\n",
|
|
1342
|
+
log: "***** clickType on " + selectors.element_name + " with value " + maskValue(_value) + "*****\n",
|
|
1199
1343
|
};
|
|
1200
|
-
const newValue = await this._replaceWithLocalData(state.value, world);
|
|
1201
1344
|
if (newValue !== _value) {
|
|
1202
1345
|
//this.logger.info(_value + "=" + newValue);
|
|
1203
1346
|
_value = newValue;
|
|
1204
|
-
state.value = newValue;
|
|
1205
1347
|
}
|
|
1206
1348
|
try {
|
|
1207
1349
|
await _preCommand(state, this);
|
|
@@ -1210,7 +1352,7 @@ class StableBrowser {
|
|
|
1210
1352
|
try {
|
|
1211
1353
|
let currentValue = await state.element.inputValue();
|
|
1212
1354
|
if (currentValue) {
|
|
1213
|
-
await element.fill("");
|
|
1355
|
+
await state.element.fill("");
|
|
1214
1356
|
}
|
|
1215
1357
|
}
|
|
1216
1358
|
catch (e) {
|
|
@@ -1318,6 +1460,7 @@ class StableBrowser {
|
|
|
1318
1460
|
let screenshotPath = null;
|
|
1319
1461
|
if (!info.log) {
|
|
1320
1462
|
info.log = "";
|
|
1463
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
1321
1464
|
}
|
|
1322
1465
|
info.operation = "getText";
|
|
1323
1466
|
info.selectors = selectors;
|
|
@@ -1656,11 +1799,9 @@ class StableBrowser {
|
|
|
1656
1799
|
if (!fs.existsSync(world.screenshotPath)) {
|
|
1657
1800
|
fs.mkdirSync(world.screenshotPath, { recursive: true });
|
|
1658
1801
|
}
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
}
|
|
1663
|
-
const screenshotPath = path.join(world.screenshotPath, nextIndex + ".png");
|
|
1802
|
+
// to make sure the path doesn't start with -
|
|
1803
|
+
const uuidStr = "id_" + randomUUID();
|
|
1804
|
+
const screenshotPath = path.join(world.screenshotPath, uuidStr + ".png");
|
|
1664
1805
|
try {
|
|
1665
1806
|
await this.takeScreenshot(screenshotPath);
|
|
1666
1807
|
// let buffer = await this.page.screenshot({ timeout: 4000 });
|
|
@@ -1674,7 +1815,7 @@ class StableBrowser {
|
|
|
1674
1815
|
catch (e) {
|
|
1675
1816
|
this.logger.info("unable to take screenshot, ignored");
|
|
1676
1817
|
}
|
|
1677
|
-
result.screenshotId =
|
|
1818
|
+
result.screenshotId = uuidStr;
|
|
1678
1819
|
result.screenshotPath = screenshotPath;
|
|
1679
1820
|
if (info && info.box) {
|
|
1680
1821
|
await drawRectangle(screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
|
|
@@ -1772,74 +1913,101 @@ class StableBrowser {
|
|
|
1772
1913
|
}
|
|
1773
1914
|
}
|
|
1774
1915
|
async extractAttribute(selectors, attribute, variable, _params = null, options = {}, world = null) {
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1916
|
+
const state = {
|
|
1917
|
+
selectors,
|
|
1918
|
+
_params,
|
|
1919
|
+
attribute,
|
|
1920
|
+
variable,
|
|
1921
|
+
options,
|
|
1922
|
+
world,
|
|
1923
|
+
type: Types.EXTRACT,
|
|
1924
|
+
text: `Extract attribute from element`,
|
|
1925
|
+
operation: "extractAttribute",
|
|
1926
|
+
log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1927
|
+
};
|
|
1780
1928
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1781
|
-
const info = {};
|
|
1782
|
-
info.log = "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n";
|
|
1783
|
-
info.operation = "extract";
|
|
1784
|
-
info.selectors = selectors;
|
|
1785
1929
|
try {
|
|
1786
|
-
|
|
1787
|
-
await this._highlightElements(element);
|
|
1788
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1930
|
+
await _preCommand(state, this);
|
|
1789
1931
|
switch (attribute) {
|
|
1790
1932
|
case "inner_text":
|
|
1791
|
-
|
|
1933
|
+
state.value = await state.element.innerText();
|
|
1792
1934
|
break;
|
|
1793
1935
|
case "href":
|
|
1794
|
-
|
|
1936
|
+
state.value = await state.element.getAttribute("href");
|
|
1795
1937
|
break;
|
|
1796
1938
|
case "value":
|
|
1797
|
-
|
|
1939
|
+
state.value = await state.element.inputValue();
|
|
1798
1940
|
break;
|
|
1799
1941
|
default:
|
|
1800
|
-
|
|
1942
|
+
state.value = await state.element.getAttribute(attribute);
|
|
1801
1943
|
break;
|
|
1802
1944
|
}
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1945
|
+
state.info.value = state.value;
|
|
1946
|
+
this.setTestData({ [variable]: state.value }, world);
|
|
1947
|
+
this.logger.info("set test data: " + variable + "=" + state.value);
|
|
1948
|
+
return state.info;
|
|
1949
|
+
}
|
|
1950
|
+
catch (e) {
|
|
1951
|
+
await _commandError(state, e, this);
|
|
1952
|
+
}
|
|
1953
|
+
finally {
|
|
1954
|
+
_commandFinally(state, this);
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
1957
|
+
async verifyAttribute(selectors, attribute, value, _params = null, options = {}, world = null) {
|
|
1958
|
+
const state = {
|
|
1959
|
+
selectors,
|
|
1960
|
+
_params,
|
|
1961
|
+
attribute,
|
|
1962
|
+
value,
|
|
1963
|
+
options,
|
|
1964
|
+
world,
|
|
1965
|
+
type: Types.VERIFY_ATTRIBUTE,
|
|
1966
|
+
text: `Verify element attribute`,
|
|
1967
|
+
operation: "verifyAttribute",
|
|
1968
|
+
log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1969
|
+
};
|
|
1970
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1971
|
+
let val;
|
|
1972
|
+
try {
|
|
1973
|
+
await _preCommand(state, this);
|
|
1974
|
+
switch (attribute) {
|
|
1975
|
+
case "innerText":
|
|
1976
|
+
val = String(await state.element.innerText());
|
|
1977
|
+
break;
|
|
1978
|
+
case "value":
|
|
1979
|
+
val = String(await state.element.inputValue());
|
|
1980
|
+
break;
|
|
1981
|
+
case "checked":
|
|
1982
|
+
val = String(await state.element.isChecked());
|
|
1983
|
+
break;
|
|
1984
|
+
case "disabled":
|
|
1985
|
+
val = String(await state.element.isDisabled());
|
|
1986
|
+
break;
|
|
1987
|
+
default:
|
|
1988
|
+
val = String(await state.element.getAttribute(attribute));
|
|
1989
|
+
break;
|
|
1806
1990
|
}
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1991
|
+
let regex;
|
|
1992
|
+
if (value.startsWith("/") && value.endsWith("/")) {
|
|
1993
|
+
const patternBody = value.slice(1, -1);
|
|
1994
|
+
regex = new RegExp(patternBody, "g");
|
|
1995
|
+
}
|
|
1996
|
+
else {
|
|
1997
|
+
const escapedPattern = value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1998
|
+
regex = new RegExp(escapedPattern, "g");
|
|
1999
|
+
}
|
|
2000
|
+
if (!val.match(regex)) {
|
|
2001
|
+
throw new Error(`The ${attribute} attribute has a value of "${val}", but the expected value is "${value}"`);
|
|
2002
|
+
}
|
|
2003
|
+
state.info.value = val;
|
|
2004
|
+
return state.info;
|
|
1810
2005
|
}
|
|
1811
2006
|
catch (e) {
|
|
1812
|
-
|
|
1813
|
-
this.logger.error("extract failed " + info.log);
|
|
1814
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1815
|
-
info.screenshotPath = screenshotPath;
|
|
1816
|
-
Object.assign(e, { info: info });
|
|
1817
|
-
error = e;
|
|
1818
|
-
throw e;
|
|
2007
|
+
await _commandError(state, e, this);
|
|
1819
2008
|
}
|
|
1820
2009
|
finally {
|
|
1821
|
-
|
|
1822
|
-
this._reportToWorld(world, {
|
|
1823
|
-
element_name: selectors.element_name,
|
|
1824
|
-
type: Types.EXTRACT_ATTRIBUTE,
|
|
1825
|
-
variable: variable,
|
|
1826
|
-
value: info.value,
|
|
1827
|
-
text: "Extract attribute from element",
|
|
1828
|
-
screenshotId,
|
|
1829
|
-
result: error
|
|
1830
|
-
? {
|
|
1831
|
-
status: "FAILED",
|
|
1832
|
-
startTime,
|
|
1833
|
-
endTime,
|
|
1834
|
-
message: error?.message,
|
|
1835
|
-
}
|
|
1836
|
-
: {
|
|
1837
|
-
status: "PASSED",
|
|
1838
|
-
startTime,
|
|
1839
|
-
endTime,
|
|
1840
|
-
},
|
|
1841
|
-
info: info,
|
|
1842
|
-
});
|
|
2010
|
+
_commandFinally(state, this);
|
|
1843
2011
|
}
|
|
1844
2012
|
}
|
|
1845
2013
|
async extractEmailData(emailAddress, options, world) {
|
|
@@ -1916,7 +2084,8 @@ class StableBrowser {
|
|
|
1916
2084
|
catch (e) {
|
|
1917
2085
|
errorCount++;
|
|
1918
2086
|
if (errorCount > 3) {
|
|
1919
|
-
throw e;
|
|
2087
|
+
// throw e;
|
|
2088
|
+
await _commandError({ text: "extractEmailData", operation: "extractEmailData", emailAddress, info: {} }, e, this);
|
|
1920
2089
|
}
|
|
1921
2090
|
// ignore
|
|
1922
2091
|
}
|
|
@@ -2027,11 +2196,12 @@ class StableBrowser {
|
|
|
2027
2196
|
info.screenshotPath = screenshotPath;
|
|
2028
2197
|
Object.assign(e, { info: info });
|
|
2029
2198
|
error = e;
|
|
2030
|
-
throw e;
|
|
2199
|
+
// throw e;
|
|
2200
|
+
await _commandError({ text: "verifyPagePath", operation: "verifyPagePath", pathPart, info }, e, this);
|
|
2031
2201
|
}
|
|
2032
2202
|
finally {
|
|
2033
2203
|
const endTime = Date.now();
|
|
2034
|
-
|
|
2204
|
+
_reportToWorld(world, {
|
|
2035
2205
|
type: Types.VERIFY_PAGE_PATH,
|
|
2036
2206
|
text: "Verify page path",
|
|
2037
2207
|
screenshotId,
|
|
@@ -2053,52 +2223,58 @@ class StableBrowser {
|
|
|
2053
2223
|
}
|
|
2054
2224
|
async verifyTextExistInPage(text, options = {}, world = null) {
|
|
2055
2225
|
text = unEscapeString(text);
|
|
2056
|
-
const
|
|
2226
|
+
const state = {
|
|
2227
|
+
text_search: text,
|
|
2228
|
+
options,
|
|
2229
|
+
world,
|
|
2230
|
+
locate: false,
|
|
2231
|
+
scroll: false,
|
|
2232
|
+
highlight: false,
|
|
2233
|
+
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
2234
|
+
text: `Verify text exists in page`,
|
|
2235
|
+
operation: "verifyTextExistInPage",
|
|
2236
|
+
log: "***** verify text " + text + " exists in page *****\n",
|
|
2237
|
+
};
|
|
2057
2238
|
const timeout = this._getLoadTimeout(options);
|
|
2058
|
-
let error = null;
|
|
2059
|
-
let screenshotId = null;
|
|
2060
|
-
let screenshotPath = null;
|
|
2061
2239
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2062
|
-
const info = {};
|
|
2063
|
-
info.log = "***** verify text " + text + " exists in page *****\n";
|
|
2064
|
-
info.operation = "verifyTextExistInPage";
|
|
2065
2240
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2066
2241
|
if (newValue !== text) {
|
|
2067
2242
|
this.logger.info(text + "=" + newValue);
|
|
2068
2243
|
text = newValue;
|
|
2069
2244
|
}
|
|
2070
|
-
info.text = text;
|
|
2071
2245
|
let dateAlternatives = findDateAlternatives(text);
|
|
2072
2246
|
let numberAlternatives = findNumberAlternatives(text);
|
|
2073
2247
|
try {
|
|
2248
|
+
await _preCommand(state, this);
|
|
2249
|
+
state.info.text = text;
|
|
2074
2250
|
while (true) {
|
|
2075
2251
|
const frames = this.page.frames();
|
|
2076
2252
|
let results = [];
|
|
2077
2253
|
for (let i = 0; i < frames.length; i++) {
|
|
2078
2254
|
if (dateAlternatives.date) {
|
|
2079
2255
|
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2080
|
-
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "
|
|
2256
|
+
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", true, true, {});
|
|
2081
2257
|
result.frame = frames[i];
|
|
2082
2258
|
results.push(result);
|
|
2083
2259
|
}
|
|
2084
2260
|
}
|
|
2085
2261
|
else if (numberAlternatives.number) {
|
|
2086
2262
|
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2087
|
-
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "
|
|
2263
|
+
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
|
|
2088
2264
|
result.frame = frames[i];
|
|
2089
2265
|
results.push(result);
|
|
2090
2266
|
}
|
|
2091
2267
|
}
|
|
2092
2268
|
else {
|
|
2093
|
-
const result = await this._locateElementByText(frames[i], text, "
|
|
2269
|
+
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
|
|
2094
2270
|
result.frame = frames[i];
|
|
2095
2271
|
results.push(result);
|
|
2096
2272
|
}
|
|
2097
2273
|
}
|
|
2098
|
-
info.results = results;
|
|
2274
|
+
state.info.results = results;
|
|
2099
2275
|
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2100
2276
|
if (resultWithElementsFound.length === 0) {
|
|
2101
|
-
if (Date.now() - startTime > timeout) {
|
|
2277
|
+
if (Date.now() - state.startTime > timeout) {
|
|
2102
2278
|
throw new Error(`Text ${text} not found in page`);
|
|
2103
2279
|
}
|
|
2104
2280
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -2110,44 +2286,89 @@ class StableBrowser {
|
|
|
2110
2286
|
await this._highlightElements(frame, dataAttribute);
|
|
2111
2287
|
const element = await frame.$(dataAttribute);
|
|
2112
2288
|
if (element) {
|
|
2113
|
-
await this.scrollIfNeeded(element, info);
|
|
2289
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2114
2290
|
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2115
2291
|
}
|
|
2116
2292
|
}
|
|
2117
|
-
|
|
2118
|
-
return info;
|
|
2293
|
+
await _screenshot(state, this);
|
|
2294
|
+
return state.info;
|
|
2119
2295
|
}
|
|
2120
2296
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2121
2297
|
}
|
|
2122
2298
|
catch (e) {
|
|
2123
|
-
|
|
2124
|
-
this.logger.error("verify text exist in page failed " + info.log);
|
|
2125
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2126
|
-
info.screenshotPath = screenshotPath;
|
|
2127
|
-
Object.assign(e, { info: info });
|
|
2128
|
-
error = e;
|
|
2129
|
-
throw e;
|
|
2299
|
+
await _commandError(state, e, this);
|
|
2130
2300
|
}
|
|
2131
2301
|
finally {
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2302
|
+
_commandFinally(state, this);
|
|
2303
|
+
}
|
|
2304
|
+
}
|
|
2305
|
+
async waitForTextToDisappear(text, options = {}, world = null) {
|
|
2306
|
+
text = unEscapeString(text);
|
|
2307
|
+
const state = {
|
|
2308
|
+
text_search: text,
|
|
2309
|
+
options,
|
|
2310
|
+
world,
|
|
2311
|
+
locate: false,
|
|
2312
|
+
scroll: false,
|
|
2313
|
+
highlight: false,
|
|
2314
|
+
type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
|
|
2315
|
+
text: `Verify text does not exist in page`,
|
|
2316
|
+
operation: "verifyTextNotExistInPage",
|
|
2317
|
+
log: "***** verify text " + text + " does not exist in page *****\n",
|
|
2318
|
+
};
|
|
2319
|
+
const timeout = this._getLoadTimeout(options);
|
|
2320
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2321
|
+
const newValue = await this._replaceWithLocalData(text, world);
|
|
2322
|
+
if (newValue !== text) {
|
|
2323
|
+
this.logger.info(text + "=" + newValue);
|
|
2324
|
+
text = newValue;
|
|
2325
|
+
}
|
|
2326
|
+
let dateAlternatives = findDateAlternatives(text);
|
|
2327
|
+
let numberAlternatives = findNumberAlternatives(text);
|
|
2328
|
+
try {
|
|
2329
|
+
await _preCommand(state, this);
|
|
2330
|
+
state.info.text = text;
|
|
2331
|
+
while (true) {
|
|
2332
|
+
const frames = this.page.frames();
|
|
2333
|
+
let results = [];
|
|
2334
|
+
for (let i = 0; i < frames.length; i++) {
|
|
2335
|
+
if (dateAlternatives.date) {
|
|
2336
|
+
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2337
|
+
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", true, true, {});
|
|
2338
|
+
result.frame = frames[i];
|
|
2339
|
+
results.push(result);
|
|
2340
|
+
}
|
|
2143
2341
|
}
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2342
|
+
else if (numberAlternatives.number) {
|
|
2343
|
+
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2344
|
+
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
|
|
2345
|
+
result.frame = frames[i];
|
|
2346
|
+
results.push(result);
|
|
2347
|
+
}
|
|
2348
|
+
}
|
|
2349
|
+
else {
|
|
2350
|
+
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
|
|
2351
|
+
result.frame = frames[i];
|
|
2352
|
+
results.push(result);
|
|
2353
|
+
}
|
|
2354
|
+
}
|
|
2355
|
+
state.info.results = results;
|
|
2356
|
+
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2357
|
+
if (resultWithElementsFound.length === 0) {
|
|
2358
|
+
await _screenshot(state, this);
|
|
2359
|
+
return state.info;
|
|
2360
|
+
}
|
|
2361
|
+
if (Date.now() - state.startTime > timeout) {
|
|
2362
|
+
throw new Error(`Text ${text} found in page`);
|
|
2363
|
+
}
|
|
2364
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2365
|
+
}
|
|
2366
|
+
}
|
|
2367
|
+
catch (e) {
|
|
2368
|
+
await _commandError(state, e, this);
|
|
2369
|
+
}
|
|
2370
|
+
finally {
|
|
2371
|
+
_commandFinally(state, this);
|
|
2151
2372
|
}
|
|
2152
2373
|
}
|
|
2153
2374
|
_getServerUrl() {
|
|
@@ -2210,11 +2431,12 @@ class StableBrowser {
|
|
|
2210
2431
|
info.screenshotPath = screenshotPath;
|
|
2211
2432
|
Object.assign(e, { info: info });
|
|
2212
2433
|
error = e;
|
|
2213
|
-
throw e;
|
|
2434
|
+
// throw e;
|
|
2435
|
+
await _commandError({ text: "visualVerification", operation: "visualVerification", text, info }, e, this);
|
|
2214
2436
|
}
|
|
2215
2437
|
finally {
|
|
2216
2438
|
const endTime = Date.now();
|
|
2217
|
-
|
|
2439
|
+
_reportToWorld(world, {
|
|
2218
2440
|
type: Types.VERIFY_VISUAL,
|
|
2219
2441
|
text: "Visual verification",
|
|
2220
2442
|
screenshotId,
|
|
@@ -2262,6 +2484,7 @@ class StableBrowser {
|
|
|
2262
2484
|
let screenshotPath = null;
|
|
2263
2485
|
const info = {};
|
|
2264
2486
|
info.log = "";
|
|
2487
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
2265
2488
|
info.operation = "getTableData";
|
|
2266
2489
|
info.selectors = selectors;
|
|
2267
2490
|
try {
|
|
@@ -2277,11 +2500,12 @@ class StableBrowser {
|
|
|
2277
2500
|
info.screenshotPath = screenshotPath;
|
|
2278
2501
|
Object.assign(e, { info: info });
|
|
2279
2502
|
error = e;
|
|
2280
|
-
throw e;
|
|
2503
|
+
// throw e;
|
|
2504
|
+
await _commandError({ text: "getTableData", operation: "getTableData", selectors, info }, e, this);
|
|
2281
2505
|
}
|
|
2282
2506
|
finally {
|
|
2283
2507
|
const endTime = Date.now();
|
|
2284
|
-
|
|
2508
|
+
_reportToWorld(world, {
|
|
2285
2509
|
element_name: selectors.element_name,
|
|
2286
2510
|
type: Types.GET_TABLE_DATA,
|
|
2287
2511
|
text: "Get table data",
|
|
@@ -2442,11 +2666,12 @@ class StableBrowser {
|
|
|
2442
2666
|
info.screenshotPath = screenshotPath;
|
|
2443
2667
|
Object.assign(e, { info: info });
|
|
2444
2668
|
error = e;
|
|
2445
|
-
throw e;
|
|
2669
|
+
// throw e;
|
|
2670
|
+
await _commandError({ text: "analyzeTable", operation: "analyzeTable", selectors, query, operator, value }, e, this);
|
|
2446
2671
|
}
|
|
2447
2672
|
finally {
|
|
2448
2673
|
const endTime = Date.now();
|
|
2449
|
-
|
|
2674
|
+
_reportToWorld(world, {
|
|
2450
2675
|
element_name: selectors.element_name,
|
|
2451
2676
|
type: Types.ANALYZE_TABLE,
|
|
2452
2677
|
text: "Analyze table",
|
|
@@ -2468,27 +2693,7 @@ class StableBrowser {
|
|
|
2468
2693
|
}
|
|
2469
2694
|
}
|
|
2470
2695
|
async _replaceWithLocalData(value, world, _decrypt = true, totpWait = true) {
|
|
2471
|
-
|
|
2472
|
-
return value;
|
|
2473
|
-
}
|
|
2474
|
-
// find all the accurance of {{(.*?)}} and replace with the value
|
|
2475
|
-
let regex = /{{(.*?)}}/g;
|
|
2476
|
-
let matches = value.match(regex);
|
|
2477
|
-
if (matches) {
|
|
2478
|
-
const testData = this.getTestData(world);
|
|
2479
|
-
for (let i = 0; i < matches.length; i++) {
|
|
2480
|
-
let match = matches[i];
|
|
2481
|
-
let key = match.substring(2, match.length - 2);
|
|
2482
|
-
let newValue = objectPath.get(testData, key, null);
|
|
2483
|
-
if (newValue !== null) {
|
|
2484
|
-
value = value.replace(match, newValue);
|
|
2485
|
-
}
|
|
2486
|
-
}
|
|
2487
|
-
}
|
|
2488
|
-
if ((value.startsWith("secret:") || value.startsWith("totp:")) && _decrypt) {
|
|
2489
|
-
return await decrypt(value, null, totpWait);
|
|
2490
|
-
}
|
|
2491
|
-
return value;
|
|
2696
|
+
return await replaceWithLocalTestData(value, world, _decrypt, totpWait, this.context, this);
|
|
2492
2697
|
}
|
|
2493
2698
|
_getLoadTimeout(options) {
|
|
2494
2699
|
let timeout = 15000;
|
|
@@ -2539,7 +2744,7 @@ class StableBrowser {
|
|
|
2539
2744
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2540
2745
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world));
|
|
2541
2746
|
const endTime = Date.now();
|
|
2542
|
-
|
|
2747
|
+
_reportToWorld(world, {
|
|
2543
2748
|
type: Types.GET_PAGE_STATUS,
|
|
2544
2749
|
text: "Wait for page load",
|
|
2545
2750
|
screenshotId,
|
|
@@ -2559,41 +2764,35 @@ class StableBrowser {
|
|
|
2559
2764
|
}
|
|
2560
2765
|
}
|
|
2561
2766
|
async closePage(options = {}, world = null) {
|
|
2562
|
-
const
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2767
|
+
const state = {
|
|
2768
|
+
options,
|
|
2769
|
+
world,
|
|
2770
|
+
locate: false,
|
|
2771
|
+
scroll: false,
|
|
2772
|
+
highlight: false,
|
|
2773
|
+
type: Types.CLOSE_PAGE,
|
|
2774
|
+
text: `Close page`,
|
|
2775
|
+
operation: "closePage",
|
|
2776
|
+
log: "***** close page *****\n",
|
|
2777
|
+
throwError: false,
|
|
2778
|
+
};
|
|
2567
2779
|
try {
|
|
2780
|
+
await _preCommand(state, this);
|
|
2568
2781
|
await this.page.close();
|
|
2569
2782
|
}
|
|
2570
2783
|
catch (e) {
|
|
2571
2784
|
console.log(".");
|
|
2785
|
+
await _commandError(state, e, this);
|
|
2572
2786
|
}
|
|
2573
2787
|
finally {
|
|
2574
|
-
|
|
2575
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world));
|
|
2576
|
-
const endTime = Date.now();
|
|
2577
|
-
this._reportToWorld(world, {
|
|
2578
|
-
type: Types.CLOSE_PAGE,
|
|
2579
|
-
text: "close page",
|
|
2580
|
-
screenshotId,
|
|
2581
|
-
result: error
|
|
2582
|
-
? {
|
|
2583
|
-
status: "FAILED",
|
|
2584
|
-
startTime,
|
|
2585
|
-
endTime,
|
|
2586
|
-
message: error?.message,
|
|
2587
|
-
}
|
|
2588
|
-
: {
|
|
2589
|
-
status: "PASSED",
|
|
2590
|
-
startTime,
|
|
2591
|
-
endTime,
|
|
2592
|
-
},
|
|
2593
|
-
info: info,
|
|
2594
|
-
});
|
|
2788
|
+
_commandFinally(state, this);
|
|
2595
2789
|
}
|
|
2596
2790
|
}
|
|
2791
|
+
saveTestDataAsGlobal(options, world) {
|
|
2792
|
+
const dataFile = this._getDataFile(world);
|
|
2793
|
+
process.env.GLOBAL_TEST_DATA_FILE = dataFile;
|
|
2794
|
+
this.logger.info("Save the scenario test data as global for the following scenarios.");
|
|
2795
|
+
}
|
|
2597
2796
|
async setViewportSize(width, hight, options = {}, world = null) {
|
|
2598
2797
|
const startTime = Date.now();
|
|
2599
2798
|
let error = null;
|
|
@@ -2611,12 +2810,13 @@ class StableBrowser {
|
|
|
2611
2810
|
}
|
|
2612
2811
|
catch (e) {
|
|
2613
2812
|
console.log(".");
|
|
2813
|
+
await _commandError({ text: "setViewportSize", operation: "setViewportSize", width, hight, info }, e, this);
|
|
2614
2814
|
}
|
|
2615
2815
|
finally {
|
|
2616
2816
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2617
2817
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world));
|
|
2618
2818
|
const endTime = Date.now();
|
|
2619
|
-
|
|
2819
|
+
_reportToWorld(world, {
|
|
2620
2820
|
type: Types.SET_VIEWPORT,
|
|
2621
2821
|
text: "set viewport size to " + width + "x" + hight,
|
|
2622
2822
|
screenshotId,
|
|
@@ -2647,12 +2847,13 @@ class StableBrowser {
|
|
|
2647
2847
|
}
|
|
2648
2848
|
catch (e) {
|
|
2649
2849
|
console.log(".");
|
|
2850
|
+
await _commandError({ text: "reloadPage", operation: "reloadPage", info }, e, this);
|
|
2650
2851
|
}
|
|
2651
2852
|
finally {
|
|
2652
2853
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2653
2854
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2654
2855
|
const endTime = Date.now();
|
|
2655
|
-
|
|
2856
|
+
_reportToWorld(world, {
|
|
2656
2857
|
type: Types.GET_PAGE_STATUS,
|
|
2657
2858
|
text: "page relaod",
|
|
2658
2859
|
screenshotId,
|
|
@@ -2688,11 +2889,37 @@ class StableBrowser {
|
|
|
2688
2889
|
console.log("#-#");
|
|
2689
2890
|
}
|
|
2690
2891
|
}
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2892
|
+
async beforeStep(world, step) {
|
|
2893
|
+
this.stepName = step.pickleStep.text;
|
|
2894
|
+
this.logger.info("step: " + this.stepName);
|
|
2895
|
+
if (this.stepIndex === undefined) {
|
|
2896
|
+
this.stepIndex = 0;
|
|
2897
|
+
}
|
|
2898
|
+
else {
|
|
2899
|
+
this.stepIndex++;
|
|
2900
|
+
}
|
|
2901
|
+
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
2902
|
+
if (this.context.browserObject.context) {
|
|
2903
|
+
await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
|
|
2904
|
+
}
|
|
2905
|
+
}
|
|
2906
|
+
if (this.tags === null && step && step.pickle && step.pickle.tags) {
|
|
2907
|
+
this.tags = step.pickle.tags.map((tag) => tag.name);
|
|
2908
|
+
// check if @global_test_data tag is present
|
|
2909
|
+
if (this.tags.includes("@global_test_data")) {
|
|
2910
|
+
this.saveTestDataAsGlobal({}, world);
|
|
2911
|
+
}
|
|
2912
|
+
}
|
|
2913
|
+
}
|
|
2914
|
+
async afterStep(world, step) {
|
|
2915
|
+
this.stepName = null;
|
|
2916
|
+
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
2917
|
+
if (this.context.browserObject.context) {
|
|
2918
|
+
await this.context.browserObject.context.tracing.stopChunk({
|
|
2919
|
+
path: path.join(this.context.browserObject.traceFolder, `trace-${this.stepIndex}.zip`),
|
|
2920
|
+
});
|
|
2921
|
+
}
|
|
2694
2922
|
}
|
|
2695
|
-
world.attach(JSON.stringify(properties), { mediaType: "application/json" });
|
|
2696
2923
|
}
|
|
2697
2924
|
}
|
|
2698
2925
|
function createTimedPromise(promise, label) {
|