automation_model 1.0.479-dev → 1.0.479-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 +1 -1
- package/lib/api.js +91 -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 +60 -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 +484 -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;
|
|
@@ -985,6 +1151,7 @@ class StableBrowser {
|
|
|
985
1151
|
_params,
|
|
986
1152
|
options,
|
|
987
1153
|
world,
|
|
1154
|
+
value: values.toString(),
|
|
988
1155
|
type: Types.SELECT,
|
|
989
1156
|
text: `Select option: ${values}`,
|
|
990
1157
|
operation: "selectOption",
|
|
@@ -1092,33 +1259,30 @@ class StableBrowser {
|
|
|
1092
1259
|
}
|
|
1093
1260
|
}
|
|
1094
1261
|
async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
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
|
+
};
|
|
1105
1274
|
try {
|
|
1106
|
-
|
|
1107
|
-
let element = await this._locate(selectors, info, _params);
|
|
1108
|
-
//insert red border around the element
|
|
1109
|
-
await this.scrollIfNeeded(element, info);
|
|
1110
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1111
|
-
await this._highlightElements(element);
|
|
1275
|
+
await _preCommand(state, this);
|
|
1112
1276
|
try {
|
|
1113
|
-
await element.click();
|
|
1277
|
+
await state.element.click();
|
|
1114
1278
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1115
1279
|
if (format) {
|
|
1116
|
-
value = dayjs(value).format(format);
|
|
1117
|
-
await element.fill(value);
|
|
1280
|
+
state.value = dayjs(state.value).format(format);
|
|
1281
|
+
await state.element.fill(state.value);
|
|
1118
1282
|
}
|
|
1119
1283
|
else {
|
|
1120
|
-
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
1121
|
-
await element.evaluateHandle((el, dateTimeValue) => {
|
|
1284
|
+
const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
|
|
1285
|
+
await state.element.evaluateHandle((el, dateTimeValue) => {
|
|
1122
1286
|
el.value = ""; // clear input
|
|
1123
1287
|
el.value = dateTimeValue;
|
|
1124
1288
|
}, dateTimeValue);
|
|
@@ -1131,20 +1295,19 @@ class StableBrowser {
|
|
|
1131
1295
|
}
|
|
1132
1296
|
catch (err) {
|
|
1133
1297
|
//await this.closeUnexpectedPopups();
|
|
1134
|
-
this.logger.error("setting date time input failed " + JSON.stringify(info));
|
|
1298
|
+
this.logger.error("setting date time input failed " + JSON.stringify(state.info));
|
|
1135
1299
|
this.logger.info("Trying again");
|
|
1136
|
-
|
|
1137
|
-
info.
|
|
1138
|
-
Object.assign(err, { info: info });
|
|
1300
|
+
await _screenshot(state, this);
|
|
1301
|
+
Object.assign(err, { info: state.info });
|
|
1139
1302
|
await element.click();
|
|
1140
1303
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1141
1304
|
if (format) {
|
|
1142
|
-
value = dayjs(value).format(format);
|
|
1143
|
-
await element.fill(value);
|
|
1305
|
+
state.value = dayjs(state.value).format(format);
|
|
1306
|
+
await state.element.fill(state.value);
|
|
1144
1307
|
}
|
|
1145
1308
|
else {
|
|
1146
|
-
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
1147
|
-
await element.evaluateHandle((el, dateTimeValue) => {
|
|
1309
|
+
const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
|
|
1310
|
+
await state.element.evaluateHandle((el, dateTimeValue) => {
|
|
1148
1311
|
el.value = ""; // clear input
|
|
1149
1312
|
el.value = dateTimeValue;
|
|
1150
1313
|
}, dateTimeValue);
|
|
@@ -1157,46 +1320,27 @@ class StableBrowser {
|
|
|
1157
1320
|
}
|
|
1158
1321
|
}
|
|
1159
1322
|
catch (e) {
|
|
1160
|
-
|
|
1161
|
-
throw e;
|
|
1323
|
+
await _commandError(state, e, this);
|
|
1162
1324
|
}
|
|
1163
1325
|
finally {
|
|
1164
|
-
|
|
1165
|
-
this._reportToWorld(world, {
|
|
1166
|
-
element_name: selectors.element_name,
|
|
1167
|
-
type: Types.SET_DATE_TIME,
|
|
1168
|
-
screenshotId,
|
|
1169
|
-
value: value,
|
|
1170
|
-
text: `setDateTime input with value: ${value}`,
|
|
1171
|
-
result: error
|
|
1172
|
-
? {
|
|
1173
|
-
status: "FAILED",
|
|
1174
|
-
startTime,
|
|
1175
|
-
endTime,
|
|
1176
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1177
|
-
}
|
|
1178
|
-
: {
|
|
1179
|
-
status: "PASSED",
|
|
1180
|
-
startTime,
|
|
1181
|
-
endTime,
|
|
1182
|
-
},
|
|
1183
|
-
info: info,
|
|
1184
|
-
});
|
|
1326
|
+
_commandFinally(state, this);
|
|
1185
1327
|
}
|
|
1186
1328
|
}
|
|
1187
1329
|
async clickType(selectors, _value, enter = false, _params = null, options = {}, world = null) {
|
|
1330
|
+
_value = unEscapeString(_value);
|
|
1331
|
+
const newValue = await this._replaceWithLocalData(_value, world);
|
|
1188
1332
|
const state = {
|
|
1189
1333
|
selectors,
|
|
1190
1334
|
_params,
|
|
1191
|
-
value:
|
|
1335
|
+
value: newValue,
|
|
1336
|
+
originalValue: _value,
|
|
1192
1337
|
options,
|
|
1193
1338
|
world,
|
|
1194
1339
|
type: Types.FILL,
|
|
1195
1340
|
text: `Click type input with value: ${_value}`,
|
|
1196
1341
|
operation: "clickType",
|
|
1197
|
-
log: "***** clickType on " + selectors.element_name + " with value " + _value + "*****\n",
|
|
1342
|
+
log: "***** clickType on " + selectors.element_name + " with value " + maskValue(_value) + "*****\n",
|
|
1198
1343
|
};
|
|
1199
|
-
const newValue = await this._replaceWithLocalData(state.value, world);
|
|
1200
1344
|
if (newValue !== _value) {
|
|
1201
1345
|
//this.logger.info(_value + "=" + newValue);
|
|
1202
1346
|
_value = newValue;
|
|
@@ -1208,7 +1352,7 @@ class StableBrowser {
|
|
|
1208
1352
|
try {
|
|
1209
1353
|
let currentValue = await state.element.inputValue();
|
|
1210
1354
|
if (currentValue) {
|
|
1211
|
-
await element.fill("");
|
|
1355
|
+
await state.element.fill("");
|
|
1212
1356
|
}
|
|
1213
1357
|
}
|
|
1214
1358
|
catch (e) {
|
|
@@ -1316,6 +1460,7 @@ class StableBrowser {
|
|
|
1316
1460
|
let screenshotPath = null;
|
|
1317
1461
|
if (!info.log) {
|
|
1318
1462
|
info.log = "";
|
|
1463
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
1319
1464
|
}
|
|
1320
1465
|
info.operation = "getText";
|
|
1321
1466
|
info.selectors = selectors;
|
|
@@ -1365,7 +1510,7 @@ class StableBrowser {
|
|
|
1365
1510
|
selectors,
|
|
1366
1511
|
_params,
|
|
1367
1512
|
pattern,
|
|
1368
|
-
value:
|
|
1513
|
+
value: pattern,
|
|
1369
1514
|
options,
|
|
1370
1515
|
world,
|
|
1371
1516
|
locate: false,
|
|
@@ -1654,11 +1799,9 @@ class StableBrowser {
|
|
|
1654
1799
|
if (!fs.existsSync(world.screenshotPath)) {
|
|
1655
1800
|
fs.mkdirSync(world.screenshotPath, { recursive: true });
|
|
1656
1801
|
}
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
}
|
|
1661
|
-
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");
|
|
1662
1805
|
try {
|
|
1663
1806
|
await this.takeScreenshot(screenshotPath);
|
|
1664
1807
|
// let buffer = await this.page.screenshot({ timeout: 4000 });
|
|
@@ -1672,7 +1815,7 @@ class StableBrowser {
|
|
|
1672
1815
|
catch (e) {
|
|
1673
1816
|
this.logger.info("unable to take screenshot, ignored");
|
|
1674
1817
|
}
|
|
1675
|
-
result.screenshotId =
|
|
1818
|
+
result.screenshotId = uuidStr;
|
|
1676
1819
|
result.screenshotPath = screenshotPath;
|
|
1677
1820
|
if (info && info.box) {
|
|
1678
1821
|
await drawRectangle(screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
|
|
@@ -1770,74 +1913,101 @@ class StableBrowser {
|
|
|
1770
1913
|
}
|
|
1771
1914
|
}
|
|
1772
1915
|
async extractAttribute(selectors, attribute, variable, _params = null, options = {}, world = null) {
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
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
|
+
};
|
|
1778
1928
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1779
|
-
const info = {};
|
|
1780
|
-
info.log = "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n";
|
|
1781
|
-
info.operation = "extract";
|
|
1782
|
-
info.selectors = selectors;
|
|
1783
1929
|
try {
|
|
1784
|
-
|
|
1785
|
-
await this._highlightElements(element);
|
|
1786
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1930
|
+
await _preCommand(state, this);
|
|
1787
1931
|
switch (attribute) {
|
|
1788
1932
|
case "inner_text":
|
|
1789
|
-
|
|
1933
|
+
state.value = await state.element.innerText();
|
|
1790
1934
|
break;
|
|
1791
1935
|
case "href":
|
|
1792
|
-
|
|
1936
|
+
state.value = await state.element.getAttribute("href");
|
|
1793
1937
|
break;
|
|
1794
1938
|
case "value":
|
|
1795
|
-
|
|
1939
|
+
state.value = await state.element.inputValue();
|
|
1796
1940
|
break;
|
|
1797
1941
|
default:
|
|
1798
|
-
|
|
1942
|
+
state.value = await state.element.getAttribute(attribute);
|
|
1799
1943
|
break;
|
|
1800
1944
|
}
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
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;
|
|
1804
1990
|
}
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
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;
|
|
1808
2005
|
}
|
|
1809
2006
|
catch (e) {
|
|
1810
|
-
|
|
1811
|
-
this.logger.error("extract failed " + info.log);
|
|
1812
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1813
|
-
info.screenshotPath = screenshotPath;
|
|
1814
|
-
Object.assign(e, { info: info });
|
|
1815
|
-
error = e;
|
|
1816
|
-
throw e;
|
|
2007
|
+
await _commandError(state, e, this);
|
|
1817
2008
|
}
|
|
1818
2009
|
finally {
|
|
1819
|
-
|
|
1820
|
-
this._reportToWorld(world, {
|
|
1821
|
-
element_name: selectors.element_name,
|
|
1822
|
-
type: Types.EXTRACT_ATTRIBUTE,
|
|
1823
|
-
variable: variable,
|
|
1824
|
-
value: info.value,
|
|
1825
|
-
text: "Extract attribute from element",
|
|
1826
|
-
screenshotId,
|
|
1827
|
-
result: error
|
|
1828
|
-
? {
|
|
1829
|
-
status: "FAILED",
|
|
1830
|
-
startTime,
|
|
1831
|
-
endTime,
|
|
1832
|
-
message: error?.message,
|
|
1833
|
-
}
|
|
1834
|
-
: {
|
|
1835
|
-
status: "PASSED",
|
|
1836
|
-
startTime,
|
|
1837
|
-
endTime,
|
|
1838
|
-
},
|
|
1839
|
-
info: info,
|
|
1840
|
-
});
|
|
2010
|
+
_commandFinally(state, this);
|
|
1841
2011
|
}
|
|
1842
2012
|
}
|
|
1843
2013
|
async extractEmailData(emailAddress, options, world) {
|
|
@@ -1914,7 +2084,8 @@ class StableBrowser {
|
|
|
1914
2084
|
catch (e) {
|
|
1915
2085
|
errorCount++;
|
|
1916
2086
|
if (errorCount > 3) {
|
|
1917
|
-
throw e;
|
|
2087
|
+
// throw e;
|
|
2088
|
+
await _commandError({ text: "extractEmailData", operation: "extractEmailData", emailAddress, info: {} }, e, this);
|
|
1918
2089
|
}
|
|
1919
2090
|
// ignore
|
|
1920
2091
|
}
|
|
@@ -2025,11 +2196,12 @@ class StableBrowser {
|
|
|
2025
2196
|
info.screenshotPath = screenshotPath;
|
|
2026
2197
|
Object.assign(e, { info: info });
|
|
2027
2198
|
error = e;
|
|
2028
|
-
throw e;
|
|
2199
|
+
// throw e;
|
|
2200
|
+
await _commandError({ text: "verifyPagePath", operation: "verifyPagePath", pathPart, info }, e, this);
|
|
2029
2201
|
}
|
|
2030
2202
|
finally {
|
|
2031
2203
|
const endTime = Date.now();
|
|
2032
|
-
|
|
2204
|
+
_reportToWorld(world, {
|
|
2033
2205
|
type: Types.VERIFY_PAGE_PATH,
|
|
2034
2206
|
text: "Verify page path",
|
|
2035
2207
|
screenshotId,
|
|
@@ -2051,52 +2223,58 @@ class StableBrowser {
|
|
|
2051
2223
|
}
|
|
2052
2224
|
async verifyTextExistInPage(text, options = {}, world = null) {
|
|
2053
2225
|
text = unEscapeString(text);
|
|
2054
|
-
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
|
+
};
|
|
2055
2238
|
const timeout = this._getLoadTimeout(options);
|
|
2056
|
-
let error = null;
|
|
2057
|
-
let screenshotId = null;
|
|
2058
|
-
let screenshotPath = null;
|
|
2059
2239
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2060
|
-
const info = {};
|
|
2061
|
-
info.log = "***** verify text " + text + " exists in page *****\n";
|
|
2062
|
-
info.operation = "verifyTextExistInPage";
|
|
2063
2240
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2064
2241
|
if (newValue !== text) {
|
|
2065
2242
|
this.logger.info(text + "=" + newValue);
|
|
2066
2243
|
text = newValue;
|
|
2067
2244
|
}
|
|
2068
|
-
info.text = text;
|
|
2069
2245
|
let dateAlternatives = findDateAlternatives(text);
|
|
2070
2246
|
let numberAlternatives = findNumberAlternatives(text);
|
|
2071
2247
|
try {
|
|
2248
|
+
await _preCommand(state, this);
|
|
2249
|
+
state.info.text = text;
|
|
2072
2250
|
while (true) {
|
|
2073
2251
|
const frames = this.page.frames();
|
|
2074
2252
|
let results = [];
|
|
2075
2253
|
for (let i = 0; i < frames.length; i++) {
|
|
2076
2254
|
if (dateAlternatives.date) {
|
|
2077
2255
|
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2078
|
-
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, {});
|
|
2079
2257
|
result.frame = frames[i];
|
|
2080
2258
|
results.push(result);
|
|
2081
2259
|
}
|
|
2082
2260
|
}
|
|
2083
2261
|
else if (numberAlternatives.number) {
|
|
2084
2262
|
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2085
|
-
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, {});
|
|
2086
2264
|
result.frame = frames[i];
|
|
2087
2265
|
results.push(result);
|
|
2088
2266
|
}
|
|
2089
2267
|
}
|
|
2090
2268
|
else {
|
|
2091
|
-
const result = await this._locateElementByText(frames[i], text, "
|
|
2269
|
+
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
|
|
2092
2270
|
result.frame = frames[i];
|
|
2093
2271
|
results.push(result);
|
|
2094
2272
|
}
|
|
2095
2273
|
}
|
|
2096
|
-
info.results = results;
|
|
2274
|
+
state.info.results = results;
|
|
2097
2275
|
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2098
2276
|
if (resultWithElementsFound.length === 0) {
|
|
2099
|
-
if (Date.now() - startTime > timeout) {
|
|
2277
|
+
if (Date.now() - state.startTime > timeout) {
|
|
2100
2278
|
throw new Error(`Text ${text} not found in page`);
|
|
2101
2279
|
}
|
|
2102
2280
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -2108,44 +2286,89 @@ class StableBrowser {
|
|
|
2108
2286
|
await this._highlightElements(frame, dataAttribute);
|
|
2109
2287
|
const element = await frame.$(dataAttribute);
|
|
2110
2288
|
if (element) {
|
|
2111
|
-
await this.scrollIfNeeded(element, info);
|
|
2289
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2112
2290
|
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2113
2291
|
}
|
|
2114
2292
|
}
|
|
2115
|
-
|
|
2116
|
-
return info;
|
|
2293
|
+
await _screenshot(state, this);
|
|
2294
|
+
return state.info;
|
|
2117
2295
|
}
|
|
2118
2296
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2119
2297
|
}
|
|
2120
2298
|
catch (e) {
|
|
2121
|
-
|
|
2122
|
-
this.logger.error("verify text exist in page failed " + info.log);
|
|
2123
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2124
|
-
info.screenshotPath = screenshotPath;
|
|
2125
|
-
Object.assign(e, { info: info });
|
|
2126
|
-
error = e;
|
|
2127
|
-
throw e;
|
|
2299
|
+
await _commandError(state, e, this);
|
|
2128
2300
|
}
|
|
2129
2301
|
finally {
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
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
|
+
}
|
|
2141
2341
|
}
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
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);
|
|
2149
2372
|
}
|
|
2150
2373
|
}
|
|
2151
2374
|
_getServerUrl() {
|
|
@@ -2208,11 +2431,12 @@ class StableBrowser {
|
|
|
2208
2431
|
info.screenshotPath = screenshotPath;
|
|
2209
2432
|
Object.assign(e, { info: info });
|
|
2210
2433
|
error = e;
|
|
2211
|
-
throw e;
|
|
2434
|
+
// throw e;
|
|
2435
|
+
await _commandError({ text: "visualVerification", operation: "visualVerification", text, info }, e, this);
|
|
2212
2436
|
}
|
|
2213
2437
|
finally {
|
|
2214
2438
|
const endTime = Date.now();
|
|
2215
|
-
|
|
2439
|
+
_reportToWorld(world, {
|
|
2216
2440
|
type: Types.VERIFY_VISUAL,
|
|
2217
2441
|
text: "Visual verification",
|
|
2218
2442
|
screenshotId,
|
|
@@ -2260,6 +2484,7 @@ class StableBrowser {
|
|
|
2260
2484
|
let screenshotPath = null;
|
|
2261
2485
|
const info = {};
|
|
2262
2486
|
info.log = "";
|
|
2487
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
2263
2488
|
info.operation = "getTableData";
|
|
2264
2489
|
info.selectors = selectors;
|
|
2265
2490
|
try {
|
|
@@ -2275,11 +2500,12 @@ class StableBrowser {
|
|
|
2275
2500
|
info.screenshotPath = screenshotPath;
|
|
2276
2501
|
Object.assign(e, { info: info });
|
|
2277
2502
|
error = e;
|
|
2278
|
-
throw e;
|
|
2503
|
+
// throw e;
|
|
2504
|
+
await _commandError({ text: "getTableData", operation: "getTableData", selectors, info }, e, this);
|
|
2279
2505
|
}
|
|
2280
2506
|
finally {
|
|
2281
2507
|
const endTime = Date.now();
|
|
2282
|
-
|
|
2508
|
+
_reportToWorld(world, {
|
|
2283
2509
|
element_name: selectors.element_name,
|
|
2284
2510
|
type: Types.GET_TABLE_DATA,
|
|
2285
2511
|
text: "Get table data",
|
|
@@ -2440,11 +2666,12 @@ class StableBrowser {
|
|
|
2440
2666
|
info.screenshotPath = screenshotPath;
|
|
2441
2667
|
Object.assign(e, { info: info });
|
|
2442
2668
|
error = e;
|
|
2443
|
-
throw e;
|
|
2669
|
+
// throw e;
|
|
2670
|
+
await _commandError({ text: "analyzeTable", operation: "analyzeTable", selectors, query, operator, value }, e, this);
|
|
2444
2671
|
}
|
|
2445
2672
|
finally {
|
|
2446
2673
|
const endTime = Date.now();
|
|
2447
|
-
|
|
2674
|
+
_reportToWorld(world, {
|
|
2448
2675
|
element_name: selectors.element_name,
|
|
2449
2676
|
type: Types.ANALYZE_TABLE,
|
|
2450
2677
|
text: "Analyze table",
|
|
@@ -2466,27 +2693,7 @@ class StableBrowser {
|
|
|
2466
2693
|
}
|
|
2467
2694
|
}
|
|
2468
2695
|
async _replaceWithLocalData(value, world, _decrypt = true, totpWait = true) {
|
|
2469
|
-
|
|
2470
|
-
return value;
|
|
2471
|
-
}
|
|
2472
|
-
// find all the accurance of {{(.*?)}} and replace with the value
|
|
2473
|
-
let regex = /{{(.*?)}}/g;
|
|
2474
|
-
let matches = value.match(regex);
|
|
2475
|
-
if (matches) {
|
|
2476
|
-
const testData = this.getTestData(world);
|
|
2477
|
-
for (let i = 0; i < matches.length; i++) {
|
|
2478
|
-
let match = matches[i];
|
|
2479
|
-
let key = match.substring(2, match.length - 2);
|
|
2480
|
-
let newValue = objectPath.get(testData, key, null);
|
|
2481
|
-
if (newValue !== null) {
|
|
2482
|
-
value = value.replace(match, newValue);
|
|
2483
|
-
}
|
|
2484
|
-
}
|
|
2485
|
-
}
|
|
2486
|
-
if ((value.startsWith("secret:") || value.startsWith("totp:")) && _decrypt) {
|
|
2487
|
-
return await decrypt(value, null, totpWait);
|
|
2488
|
-
}
|
|
2489
|
-
return value;
|
|
2696
|
+
return await replaceWithLocalTestData(value, world, _decrypt, totpWait, this.context, this);
|
|
2490
2697
|
}
|
|
2491
2698
|
_getLoadTimeout(options) {
|
|
2492
2699
|
let timeout = 15000;
|
|
@@ -2537,7 +2744,7 @@ class StableBrowser {
|
|
|
2537
2744
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2538
2745
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world));
|
|
2539
2746
|
const endTime = Date.now();
|
|
2540
|
-
|
|
2747
|
+
_reportToWorld(world, {
|
|
2541
2748
|
type: Types.GET_PAGE_STATUS,
|
|
2542
2749
|
text: "Wait for page load",
|
|
2543
2750
|
screenshotId,
|
|
@@ -2557,41 +2764,35 @@ class StableBrowser {
|
|
|
2557
2764
|
}
|
|
2558
2765
|
}
|
|
2559
2766
|
async closePage(options = {}, world = null) {
|
|
2560
|
-
const
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
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
|
+
};
|
|
2565
2779
|
try {
|
|
2780
|
+
await _preCommand(state, this);
|
|
2566
2781
|
await this.page.close();
|
|
2567
2782
|
}
|
|
2568
2783
|
catch (e) {
|
|
2569
2784
|
console.log(".");
|
|
2785
|
+
await _commandError(state, e, this);
|
|
2570
2786
|
}
|
|
2571
2787
|
finally {
|
|
2572
|
-
|
|
2573
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world));
|
|
2574
|
-
const endTime = Date.now();
|
|
2575
|
-
this._reportToWorld(world, {
|
|
2576
|
-
type: Types.CLOSE_PAGE,
|
|
2577
|
-
text: "close page",
|
|
2578
|
-
screenshotId,
|
|
2579
|
-
result: error
|
|
2580
|
-
? {
|
|
2581
|
-
status: "FAILED",
|
|
2582
|
-
startTime,
|
|
2583
|
-
endTime,
|
|
2584
|
-
message: error?.message,
|
|
2585
|
-
}
|
|
2586
|
-
: {
|
|
2587
|
-
status: "PASSED",
|
|
2588
|
-
startTime,
|
|
2589
|
-
endTime,
|
|
2590
|
-
},
|
|
2591
|
-
info: info,
|
|
2592
|
-
});
|
|
2788
|
+
_commandFinally(state, this);
|
|
2593
2789
|
}
|
|
2594
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
|
+
}
|
|
2595
2796
|
async setViewportSize(width, hight, options = {}, world = null) {
|
|
2596
2797
|
const startTime = Date.now();
|
|
2597
2798
|
let error = null;
|
|
@@ -2609,12 +2810,13 @@ class StableBrowser {
|
|
|
2609
2810
|
}
|
|
2610
2811
|
catch (e) {
|
|
2611
2812
|
console.log(".");
|
|
2813
|
+
await _commandError({ text: "setViewportSize", operation: "setViewportSize", width, hight, info }, e, this);
|
|
2612
2814
|
}
|
|
2613
2815
|
finally {
|
|
2614
2816
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2615
2817
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world));
|
|
2616
2818
|
const endTime = Date.now();
|
|
2617
|
-
|
|
2819
|
+
_reportToWorld(world, {
|
|
2618
2820
|
type: Types.SET_VIEWPORT,
|
|
2619
2821
|
text: "set viewport size to " + width + "x" + hight,
|
|
2620
2822
|
screenshotId,
|
|
@@ -2645,12 +2847,13 @@ class StableBrowser {
|
|
|
2645
2847
|
}
|
|
2646
2848
|
catch (e) {
|
|
2647
2849
|
console.log(".");
|
|
2850
|
+
await _commandError({ text: "reloadPage", operation: "reloadPage", info }, e, this);
|
|
2648
2851
|
}
|
|
2649
2852
|
finally {
|
|
2650
2853
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2651
2854
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2652
2855
|
const endTime = Date.now();
|
|
2653
|
-
|
|
2856
|
+
_reportToWorld(world, {
|
|
2654
2857
|
type: Types.GET_PAGE_STATUS,
|
|
2655
2858
|
text: "page relaod",
|
|
2656
2859
|
screenshotId,
|
|
@@ -2686,11 +2889,37 @@ class StableBrowser {
|
|
|
2686
2889
|
console.log("#-#");
|
|
2687
2890
|
}
|
|
2688
2891
|
}
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
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
|
+
}
|
|
2692
2922
|
}
|
|
2693
|
-
world.attach(JSON.stringify(properties), { mediaType: "application/json" });
|
|
2694
2923
|
}
|
|
2695
2924
|
}
|
|
2696
2925
|
function createTimedPromise(promise, label) {
|