automation_model 1.0.596-dev → 1.0.596-stage
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +130 -0
- package/lib/api.js +35 -21
- package/lib/api.js.map +1 -1
- package/lib/auto_page.d.ts +4 -2
- package/lib/auto_page.js +140 -8
- package/lib/auto_page.js.map +1 -1
- package/lib/browser_manager.js +57 -16
- package/lib/browser_manager.js.map +1 -1
- package/lib/bruno.d.ts +1 -0
- package/lib/bruno.js +301 -0
- package/lib/bruno.js.map +1 -0
- package/lib/command_common.d.ts +4 -4
- package/lib/command_common.js +51 -37
- package/lib/command_common.js.map +1 -1
- package/lib/error-messages.js +6 -0
- package/lib/error-messages.js.map +1 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/lib/init_browser.d.ts +3 -2
- package/lib/init_browser.js +64 -11
- package/lib/init_browser.js.map +1 -1
- package/lib/locate_element.js +2 -2
- package/lib/locate_element.js.map +1 -1
- package/lib/network.d.ts +1 -1
- package/lib/network.js +5 -5
- package/lib/network.js.map +1 -1
- package/lib/stable_browser.d.ts +24 -7
- package/lib/stable_browser.js +799 -355
- package/lib/stable_browser.js.map +1 -1
- package/lib/table_helper.d.ts +19 -0
- package/lib/table_helper.js +116 -0
- package/lib/table_helper.js.map +1 -0
- package/lib/test_context.d.ts +3 -0
- package/lib/test_context.js +2 -0
- package/lib/test_context.js.map +1 -1
- package/lib/utils.d.ts +8 -3
- package/lib/utils.js +209 -19
- package/lib/utils.js.map +1 -1
- package/package.json +8 -8
package/lib/stable_browser.js
CHANGED
|
@@ -7,20 +7,25 @@ import path from "path";
|
|
|
7
7
|
import reg_parser from "regex-parser";
|
|
8
8
|
import { findDateAlternatives, findNumberAlternatives } from "./analyze_helper.js";
|
|
9
9
|
import { getDateTimeValue } from "./date_time.js";
|
|
10
|
+
import drawRectangle from "./drawRect.js";
|
|
10
11
|
//import { closeUnexpectedPopups } from "./popups.js";
|
|
11
12
|
import { getTableCells, getTableData } from "./table_analyze.js";
|
|
12
|
-
import { _convertToRegexQuery, _copyContext, _fixLocatorUsingParams, _fixUsingParams, _getServerUrl, extractStepExampleParameters, KEYBOARD_EVENTS, maskValue, replaceWithLocalTestData, scrollPageToLoadLazyElements, unEscapeString, } from "./utils.js";
|
|
13
|
+
import { _convertToRegexQuery, _copyContext, _fixLocatorUsingParams, _fixUsingParams, _getServerUrl, extractStepExampleParameters, KEYBOARD_EVENTS, maskValue, replaceWithLocalTestData, scrollPageToLoadLazyElements, unEscapeString, _getDataFile, testForRegex, performAction, } from "./utils.js";
|
|
13
14
|
import csv from "csv-parser";
|
|
14
15
|
import { Readable } from "node:stream";
|
|
15
16
|
import readline from "readline";
|
|
16
|
-
import { getContext } from "./init_browser.js";
|
|
17
|
+
import { getContext, refreshBrowser } from "./init_browser.js";
|
|
18
|
+
import { getTestData } from "./auto_page.js";
|
|
17
19
|
import { locate_element } from "./locate_element.js";
|
|
18
20
|
import { randomUUID } from "crypto";
|
|
19
21
|
import { _commandError, _commandFinally, _preCommand, _validateSelectors, _screenshot, _reportToWorld, } from "./command_common.js";
|
|
20
22
|
import { registerDownloadEvent, registerNetworkEvents } from "./network.js";
|
|
21
23
|
import { LocatorLog } from "./locator_log.js";
|
|
24
|
+
import axios from "axios";
|
|
25
|
+
import { _findCellArea, findElementsInArea } from "./table_helper.js";
|
|
22
26
|
export const Types = {
|
|
23
27
|
CLICK: "click_element",
|
|
28
|
+
WAIT_ELEMENT: "wait_element",
|
|
24
29
|
NAVIGATE: "navigate",
|
|
25
30
|
FILL: "fill_element",
|
|
26
31
|
EXECUTE: "execute_page_method",
|
|
@@ -42,6 +47,7 @@ export const Types = {
|
|
|
42
47
|
UNCHECK: "uncheck_element",
|
|
43
48
|
EXTRACT: "extract_attribute",
|
|
44
49
|
CLOSE_PAGE: "close_page",
|
|
50
|
+
TABLE_OPERATION: "table_operation",
|
|
45
51
|
SET_DATE_TIME: "set_date_time",
|
|
46
52
|
SET_VIEWPORT: "set_viewport",
|
|
47
53
|
VERIFY_VISUAL: "verify_visual",
|
|
@@ -50,8 +56,12 @@ export const Types = {
|
|
|
50
56
|
WAIT_FOR_TEXT_TO_DISAPPEAR: "wait_for_text_to_disappear",
|
|
51
57
|
VERIFY_ATTRIBUTE: "verify_element_attribute",
|
|
52
58
|
VERIFY_TEXT_WITH_RELATION: "verify_text_with_relation",
|
|
59
|
+
BRUNO: "bruno",
|
|
53
60
|
};
|
|
54
61
|
export const apps = {};
|
|
62
|
+
const formatElementName = (elementName) => {
|
|
63
|
+
return elementName ? JSON.stringify(elementName) : "element";
|
|
64
|
+
};
|
|
55
65
|
class StableBrowser {
|
|
56
66
|
browser;
|
|
57
67
|
page;
|
|
@@ -65,6 +75,7 @@ class StableBrowser {
|
|
|
65
75
|
appName = "main";
|
|
66
76
|
tags = null;
|
|
67
77
|
isRecording = false;
|
|
78
|
+
initSnapshotTaken = false;
|
|
68
79
|
constructor(browser, page, logger = null, context = null, world = null) {
|
|
69
80
|
this.browser = browser;
|
|
70
81
|
this.page = page;
|
|
@@ -171,6 +182,30 @@ class StableBrowser {
|
|
|
171
182
|
await this.waitForPageLoad();
|
|
172
183
|
}
|
|
173
184
|
}
|
|
185
|
+
async switchTab(tabTitleOrIndex) {
|
|
186
|
+
// first check if the tabNameOrIndex is a number
|
|
187
|
+
let index = parseInt(tabTitleOrIndex);
|
|
188
|
+
if (!isNaN(index)) {
|
|
189
|
+
if (index >= 0 && index < this.context.pages.length) {
|
|
190
|
+
this.page = this.context.pages[index];
|
|
191
|
+
this.context.page = this.page;
|
|
192
|
+
await this.page.bringToFront();
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
// if the tabNameOrIndex is a string, find the tab by name
|
|
197
|
+
for (let i = 0; i < this.context.pages.length; i++) {
|
|
198
|
+
let page = this.context.pages[i];
|
|
199
|
+
let title = await page.title();
|
|
200
|
+
if (title.includes(tabTitleOrIndex)) {
|
|
201
|
+
this.page = page;
|
|
202
|
+
this.context.page = this.page;
|
|
203
|
+
await this.page.bringToFront();
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
throw new Error("Tab not found: " + tabTitleOrIndex);
|
|
208
|
+
}
|
|
174
209
|
registerConsoleLogListener(page, context) {
|
|
175
210
|
if (!this.context.webLogger) {
|
|
176
211
|
this.context.webLogger = [];
|
|
@@ -235,6 +270,9 @@ class StableBrowser {
|
|
|
235
270
|
// await closeUnexpectedPopups(this.page);
|
|
236
271
|
// }
|
|
237
272
|
async goto(url, world = null) {
|
|
273
|
+
if (!url) {
|
|
274
|
+
throw new Error("url is null, verify that the environment file is correct");
|
|
275
|
+
}
|
|
238
276
|
if (!url.startsWith("http")) {
|
|
239
277
|
url = "https://" + url;
|
|
240
278
|
}
|
|
@@ -252,7 +290,7 @@ class StableBrowser {
|
|
|
252
290
|
highlight: false,
|
|
253
291
|
};
|
|
254
292
|
try {
|
|
255
|
-
await _preCommand(state, this
|
|
293
|
+
await _preCommand(state, this);
|
|
256
294
|
await this.page.goto(url, {
|
|
257
295
|
timeout: 60000,
|
|
258
296
|
});
|
|
@@ -263,7 +301,7 @@ class StableBrowser {
|
|
|
263
301
|
_commandError(state, error, this);
|
|
264
302
|
}
|
|
265
303
|
finally {
|
|
266
|
-
_commandFinally(state, this);
|
|
304
|
+
await _commandFinally(state, this);
|
|
267
305
|
}
|
|
268
306
|
}
|
|
269
307
|
async _getLocator(locator, scope, _params) {
|
|
@@ -344,7 +382,7 @@ class StableBrowser {
|
|
|
344
382
|
return resultCss;
|
|
345
383
|
}
|
|
346
384
|
async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, ignoreCase = true, _params) {
|
|
347
|
-
const query = _convertToRegexQuery(text1, regex1, !partial1, ignoreCase)
|
|
385
|
+
const query = `${_convertToRegexQuery(text1, regex1, !partial1, ignoreCase)}`;
|
|
348
386
|
const locator = scope.locator(query);
|
|
349
387
|
const count = await locator.count();
|
|
350
388
|
if (!tag1) {
|
|
@@ -364,6 +402,12 @@ class StableBrowser {
|
|
|
364
402
|
if (!el.setAttribute) {
|
|
365
403
|
el = el.parentElement;
|
|
366
404
|
}
|
|
405
|
+
// remove any attributes start with data-blinq-id
|
|
406
|
+
// for (let i = 0; i < el.attributes.length; i++) {
|
|
407
|
+
// if (el.attributes[i].name.startsWith("data-blinq-id")) {
|
|
408
|
+
// el.removeAttribute(el.attributes[i].name);
|
|
409
|
+
// }
|
|
410
|
+
// }
|
|
367
411
|
el.setAttribute("data-blinq-id-" + randomToken, "");
|
|
368
412
|
return true;
|
|
369
413
|
}, [tag1, randomToken]))) {
|
|
@@ -373,7 +417,7 @@ class StableBrowser {
|
|
|
373
417
|
}
|
|
374
418
|
return { elementCount: tagCount, randomToken };
|
|
375
419
|
}
|
|
376
|
-
async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true, allowDisabled = false) {
|
|
420
|
+
async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true, allowDisabled = false, element_name = null) {
|
|
377
421
|
if (!info) {
|
|
378
422
|
info = {};
|
|
379
423
|
}
|
|
@@ -396,10 +440,11 @@ class StableBrowser {
|
|
|
396
440
|
//info.log += "searching for locator " + JSON.stringify(locatorSearch) + "\n";
|
|
397
441
|
let locator = null;
|
|
398
442
|
if (locatorSearch.climb && locatorSearch.climb >= 0) {
|
|
399
|
-
|
|
443
|
+
const replacedText = await this._replaceWithLocalData(locatorSearch.text, this.world);
|
|
444
|
+
let locatorString = await this._locateElmentByTextClimbCss(scope, replacedText, locatorSearch.climb, locatorSearch.css, _params);
|
|
400
445
|
if (!locatorString) {
|
|
401
446
|
info.failCause.textNotFound = true;
|
|
402
|
-
info.failCause.lastError =
|
|
447
|
+
info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${locatorSearch.text}`;
|
|
403
448
|
return;
|
|
404
449
|
}
|
|
405
450
|
locator = await this._getLocator({ css: locatorString }, scope, _params);
|
|
@@ -409,7 +454,7 @@ class StableBrowser {
|
|
|
409
454
|
let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, true, _params);
|
|
410
455
|
if (result.elementCount === 0) {
|
|
411
456
|
info.failCause.textNotFound = true;
|
|
412
|
-
info.failCause.lastError =
|
|
457
|
+
info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${text}`;
|
|
413
458
|
return;
|
|
414
459
|
}
|
|
415
460
|
locatorSearch.css = "[data-blinq-id-" + result.randomToken + "]";
|
|
@@ -461,11 +506,11 @@ class StableBrowser {
|
|
|
461
506
|
info.printMessages = {};
|
|
462
507
|
}
|
|
463
508
|
if (info.locatorLog && !visible) {
|
|
464
|
-
info.failCause.lastError =
|
|
509
|
+
info.failCause.lastError = `${formatElementName(element_name)} is not visible, searching for ${originalLocatorSearch}`;
|
|
465
510
|
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_VISIBLE");
|
|
466
511
|
}
|
|
467
512
|
if (info.locatorLog && !enabled) {
|
|
468
|
-
info.failCause.lastError =
|
|
513
|
+
info.failCause.lastError = `${formatElementName(element_name)} is disabled, searching for ${originalLocatorSearch}`;
|
|
469
514
|
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_ENABLED");
|
|
470
515
|
}
|
|
471
516
|
if (!info.printMessages[j.toString()]) {
|
|
@@ -545,7 +590,28 @@ class StableBrowser {
|
|
|
545
590
|
}
|
|
546
591
|
let element = await this._locate_internal(selectors, info, _params, timeout, allowDisabled);
|
|
547
592
|
if (!element.rerun) {
|
|
548
|
-
|
|
593
|
+
const randomToken = Math.random().toString(36).substring(7);
|
|
594
|
+
element.evaluate((el, randomToken) => {
|
|
595
|
+
el.setAttribute("data-blinq-id-" + randomToken, "");
|
|
596
|
+
}, randomToken);
|
|
597
|
+
// if (element._frame) {
|
|
598
|
+
// return element;
|
|
599
|
+
// }
|
|
600
|
+
const scope = element._frame ?? element.page();
|
|
601
|
+
let newElementSelector = "[data-blinq-id-" + randomToken + "]";
|
|
602
|
+
let prefixSelector = "";
|
|
603
|
+
const frameControlSelector = " >> internal:control=enter-frame";
|
|
604
|
+
const frameSelectorIndex = element._selector.lastIndexOf(frameControlSelector);
|
|
605
|
+
if (frameSelectorIndex !== -1) {
|
|
606
|
+
// remove everything after the >> internal:control=enter-frame
|
|
607
|
+
const frameSelector = element._selector.substring(0, frameSelectorIndex);
|
|
608
|
+
prefixSelector = frameSelector + " >> internal:control=enter-frame >>";
|
|
609
|
+
}
|
|
610
|
+
// if (element?._frame?._selector) {
|
|
611
|
+
// prefixSelector = element._frame._selector + " >> " + prefixSelector;
|
|
612
|
+
// }
|
|
613
|
+
const newSelector = prefixSelector + newElementSelector;
|
|
614
|
+
return scope.locator(newSelector);
|
|
549
615
|
}
|
|
550
616
|
}
|
|
551
617
|
throw new Error("unable to locate element " + JSON.stringify(selectors));
|
|
@@ -618,7 +684,7 @@ class StableBrowser {
|
|
|
618
684
|
//info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
|
|
619
685
|
if (Date.now() - startTime > timeout) {
|
|
620
686
|
info.failCause.iframeNotFound = true;
|
|
621
|
-
info.failCause.lastError =
|
|
687
|
+
info.failCause.lastError = `unable to locate iframe "${selectors.iframe_src}"`;
|
|
622
688
|
throw new Error("unable to locate iframe " + selectors.iframe_src);
|
|
623
689
|
}
|
|
624
690
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -692,18 +758,13 @@ class StableBrowser {
|
|
|
692
758
|
}
|
|
693
759
|
// info.log += "scanning locators in priority 1" + "\n";
|
|
694
760
|
let onlyPriority3 = selectorsLocators[0].priority === 3;
|
|
695
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly, allowDisabled);
|
|
761
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
696
762
|
if (result.foundElements.length === 0) {
|
|
697
763
|
// info.log += "scanning locators in priority 2" + "\n";
|
|
698
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly, allowDisabled);
|
|
699
|
-
}
|
|
700
|
-
if (result.foundElements.length === 0 && onlyPriority3) {
|
|
701
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled);
|
|
764
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
702
765
|
}
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled);
|
|
706
|
-
}
|
|
766
|
+
if (result.foundElements.length === 0 && (onlyPriority3 || !highPriorityOnly)) {
|
|
767
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
707
768
|
}
|
|
708
769
|
let foundElements = result.foundElements;
|
|
709
770
|
if (foundElements.length === 1 && foundElements[0].unique) {
|
|
@@ -759,6 +820,11 @@ class StableBrowser {
|
|
|
759
820
|
visibleOnly = false;
|
|
760
821
|
}
|
|
761
822
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
823
|
+
// sheck of more of half of the timeout has passed
|
|
824
|
+
if (Date.now() - startTime > timeout / 2) {
|
|
825
|
+
highPriorityOnly = false;
|
|
826
|
+
visibleOnly = false;
|
|
827
|
+
}
|
|
762
828
|
}
|
|
763
829
|
this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
|
|
764
830
|
// if (info.locatorLog) {
|
|
@@ -770,11 +836,11 @@ class StableBrowser {
|
|
|
770
836
|
//info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
|
|
771
837
|
info.failCause.locatorNotFound = true;
|
|
772
838
|
if (!info?.failCause?.lastError) {
|
|
773
|
-
info.failCause.lastError =
|
|
839
|
+
info.failCause.lastError = `failed to locate ${formatElementName(selectors.element_name)}, ${locatorsCount > 0 ? `${locatorsCount} matching elements found` : "no matching elements found"}`;
|
|
774
840
|
}
|
|
775
841
|
throw new Error("failed to locate first element no elements found, " + info.log);
|
|
776
842
|
}
|
|
777
|
-
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false) {
|
|
843
|
+
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false, element_name) {
|
|
778
844
|
let foundElements = [];
|
|
779
845
|
const result = {
|
|
780
846
|
foundElements: foundElements,
|
|
@@ -782,7 +848,7 @@ class StableBrowser {
|
|
|
782
848
|
for (let i = 0; i < locatorsGroup.length; i++) {
|
|
783
849
|
let foundLocators = [];
|
|
784
850
|
try {
|
|
785
|
-
await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly, allowDisabled);
|
|
851
|
+
await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
|
|
786
852
|
}
|
|
787
853
|
catch (e) {
|
|
788
854
|
// this call can fail it the browser is navigating
|
|
@@ -790,7 +856,7 @@ class StableBrowser {
|
|
|
790
856
|
// this.logger.debug(e);
|
|
791
857
|
foundLocators = [];
|
|
792
858
|
try {
|
|
793
|
-
await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly, allowDisabled);
|
|
859
|
+
await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
|
|
794
860
|
}
|
|
795
861
|
catch (e) {
|
|
796
862
|
this.logger.info("unable to use locator (second try) " + JSON.stringify(locatorsGroup[i]));
|
|
@@ -805,9 +871,40 @@ class StableBrowser {
|
|
|
805
871
|
result.locatorIndex = i;
|
|
806
872
|
}
|
|
807
873
|
if (foundLocators.length > 1) {
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
874
|
+
// remove elements that consume the same space with 10 pixels tolerance
|
|
875
|
+
const boxes = [];
|
|
876
|
+
for (let j = 0; j < foundLocators.length; j++) {
|
|
877
|
+
boxes.push({ box: await foundLocators[j].boundingBox(), locator: foundLocators[j] });
|
|
878
|
+
}
|
|
879
|
+
for (let j = 0; j < boxes.length; j++) {
|
|
880
|
+
for (let k = 0; k < boxes.length; k++) {
|
|
881
|
+
if (j === k) {
|
|
882
|
+
continue;
|
|
883
|
+
}
|
|
884
|
+
// check if x, y, width, height are the same with 10 pixels tolerance
|
|
885
|
+
if (Math.abs(boxes[j].box.x - boxes[k].box.x) < 10 &&
|
|
886
|
+
Math.abs(boxes[j].box.y - boxes[k].box.y) < 10 &&
|
|
887
|
+
Math.abs(boxes[j].box.width - boxes[k].box.width) < 10 &&
|
|
888
|
+
Math.abs(boxes[j].box.height - boxes[k].box.height) < 10) {
|
|
889
|
+
// as the element is not unique, will remove it
|
|
890
|
+
boxes.splice(k, 1);
|
|
891
|
+
k--;
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
if (boxes.length === 1) {
|
|
896
|
+
result.foundElements.push({
|
|
897
|
+
locator: boxes[0].locator.first(),
|
|
898
|
+
box: boxes[0].box,
|
|
899
|
+
unique: true,
|
|
900
|
+
});
|
|
901
|
+
result.locatorIndex = i;
|
|
902
|
+
}
|
|
903
|
+
else {
|
|
904
|
+
info.failCause.foundMultiple = true;
|
|
905
|
+
if (info.locatorLog) {
|
|
906
|
+
info.locatorLog.setLocatorSearchStatus(JSON.stringify(locatorsGroup[i]), "FOUND_NOT_UNIQUE");
|
|
907
|
+
}
|
|
811
908
|
}
|
|
812
909
|
}
|
|
813
910
|
}
|
|
@@ -826,7 +923,7 @@ class StableBrowser {
|
|
|
826
923
|
operation: "simpleClick",
|
|
827
924
|
log: "***** click on " + elementDescription + " *****\n",
|
|
828
925
|
};
|
|
829
|
-
_preCommand(state, this
|
|
926
|
+
_preCommand(state, this);
|
|
830
927
|
const startTime = Date.now();
|
|
831
928
|
let timeout = 30000;
|
|
832
929
|
if (options && options.timeout) {
|
|
@@ -855,7 +952,7 @@ class StableBrowser {
|
|
|
855
952
|
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
856
953
|
}
|
|
857
954
|
finally {
|
|
858
|
-
_commandFinally(state, this);
|
|
955
|
+
await _commandFinally(state, this);
|
|
859
956
|
}
|
|
860
957
|
}
|
|
861
958
|
}
|
|
@@ -875,7 +972,7 @@ class StableBrowser {
|
|
|
875
972
|
operation: "simpleClickType",
|
|
876
973
|
log: "***** click type on " + elementDescription + " *****\n",
|
|
877
974
|
};
|
|
878
|
-
_preCommand(state, this
|
|
975
|
+
_preCommand(state, this);
|
|
879
976
|
const startTime = Date.now();
|
|
880
977
|
let timeout = 30000;
|
|
881
978
|
if (options && options.timeout) {
|
|
@@ -904,7 +1001,7 @@ class StableBrowser {
|
|
|
904
1001
|
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
905
1002
|
}
|
|
906
1003
|
finally {
|
|
907
|
-
_commandFinally(state, this);
|
|
1004
|
+
await _commandFinally(state, this);
|
|
908
1005
|
}
|
|
909
1006
|
}
|
|
910
1007
|
}
|
|
@@ -918,25 +1015,14 @@ class StableBrowser {
|
|
|
918
1015
|
options,
|
|
919
1016
|
world,
|
|
920
1017
|
text: "Click element",
|
|
1018
|
+
_text: "Click on " + selectors.element_name,
|
|
921
1019
|
type: Types.CLICK,
|
|
922
1020
|
operation: "click",
|
|
923
1021
|
log: "***** click on " + selectors.element_name + " *****\n",
|
|
924
1022
|
};
|
|
925
1023
|
try {
|
|
926
|
-
await _preCommand(state, this
|
|
927
|
-
|
|
928
|
-
// state.selectors.locators[0].text = state.options.context;
|
|
929
|
-
// }
|
|
930
|
-
try {
|
|
931
|
-
await state.element.click();
|
|
932
|
-
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
933
|
-
}
|
|
934
|
-
catch (e) {
|
|
935
|
-
// await this.closeUnexpectedPopups();
|
|
936
|
-
state.element = await this._locate(selectors, state.info, _params);
|
|
937
|
-
await state.element.dispatchEvent("click");
|
|
938
|
-
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
939
|
-
}
|
|
1024
|
+
await _preCommand(state, this);
|
|
1025
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
940
1026
|
await this.waitForPageLoad();
|
|
941
1027
|
return state.info;
|
|
942
1028
|
}
|
|
@@ -944,8 +1030,40 @@ class StableBrowser {
|
|
|
944
1030
|
await _commandError(state, e, this);
|
|
945
1031
|
}
|
|
946
1032
|
finally {
|
|
947
|
-
_commandFinally(state, this);
|
|
1033
|
+
await _commandFinally(state, this);
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
async waitForElement(selectors, _params, options = {}, world = null) {
|
|
1037
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1038
|
+
const state = {
|
|
1039
|
+
selectors,
|
|
1040
|
+
_params,
|
|
1041
|
+
options,
|
|
1042
|
+
world,
|
|
1043
|
+
text: "Wait for element",
|
|
1044
|
+
_text: "Wait for " + selectors.element_name,
|
|
1045
|
+
type: Types.WAIT_ELEMENT,
|
|
1046
|
+
operation: "waitForElement",
|
|
1047
|
+
log: "***** wait for " + selectors.element_name + " *****\n",
|
|
1048
|
+
};
|
|
1049
|
+
let found = false;
|
|
1050
|
+
try {
|
|
1051
|
+
await _preCommand(state, this);
|
|
1052
|
+
// if (state.options && state.options.context) {
|
|
1053
|
+
// state.selectors.locators[0].text = state.options.context;
|
|
1054
|
+
// }
|
|
1055
|
+
await state.element.waitFor({ timeout: timeout });
|
|
1056
|
+
found = true;
|
|
1057
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1058
|
+
}
|
|
1059
|
+
catch (e) {
|
|
1060
|
+
console.error("Error on waitForElement", e);
|
|
1061
|
+
// await _commandError(state, e, this);
|
|
1062
|
+
}
|
|
1063
|
+
finally {
|
|
1064
|
+
await _commandFinally(state, this);
|
|
948
1065
|
}
|
|
1066
|
+
return found;
|
|
949
1067
|
}
|
|
950
1068
|
async setCheck(selectors, checked = true, _params, options = {}, world = null) {
|
|
951
1069
|
const state = {
|
|
@@ -955,22 +1073,23 @@ class StableBrowser {
|
|
|
955
1073
|
world,
|
|
956
1074
|
type: checked ? Types.CHECK : Types.UNCHECK,
|
|
957
1075
|
text: checked ? `Check element` : `Uncheck element`,
|
|
1076
|
+
_text: checked ? `Check ${selectors.element_name}` : `Uncheck ${selectors.element_name}`,
|
|
958
1077
|
operation: "setCheck",
|
|
959
1078
|
log: "***** check " + selectors.element_name + " *****\n",
|
|
960
1079
|
};
|
|
961
1080
|
try {
|
|
962
|
-
await _preCommand(state, this
|
|
1081
|
+
await _preCommand(state, this);
|
|
963
1082
|
state.info.checked = checked;
|
|
964
1083
|
// let element = await this._locate(selectors, info, _params);
|
|
965
1084
|
// ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
966
1085
|
try {
|
|
967
|
-
if (world && world.screenshot && !world.screenshotPath) {
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
971
|
-
await this._unHighlightElements(element);
|
|
972
|
-
}
|
|
1086
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
1087
|
+
// console.log(`Highlighting while running from recorder`);
|
|
1088
|
+
await this._highlightElements(state.element);
|
|
973
1089
|
await state.element.setChecked(checked);
|
|
1090
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1091
|
+
// await this._unHighlightElements(element);
|
|
1092
|
+
// }
|
|
974
1093
|
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
975
1094
|
// await this._unHighlightElements(element);
|
|
976
1095
|
}
|
|
@@ -993,7 +1112,7 @@ class StableBrowser {
|
|
|
993
1112
|
await _commandError(state, e, this);
|
|
994
1113
|
}
|
|
995
1114
|
finally {
|
|
996
|
-
_commandFinally(state, this);
|
|
1115
|
+
await _commandFinally(state, this);
|
|
997
1116
|
}
|
|
998
1117
|
}
|
|
999
1118
|
async hover(selectors, _params, options = {}, world = null) {
|
|
@@ -1004,25 +1123,14 @@ class StableBrowser {
|
|
|
1004
1123
|
world,
|
|
1005
1124
|
type: Types.HOVER,
|
|
1006
1125
|
text: `Hover element`,
|
|
1126
|
+
_text: `Hover on ${selectors.element_name}`,
|
|
1007
1127
|
operation: "hover",
|
|
1008
1128
|
log: "***** hover " + selectors.element_name + " *****\n",
|
|
1009
1129
|
};
|
|
1010
1130
|
try {
|
|
1011
|
-
await _preCommand(state, this
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
await _screenshot(state, this);
|
|
1015
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1016
|
-
}
|
|
1017
|
-
catch (e) {
|
|
1018
|
-
//await this.closeUnexpectedPopups();
|
|
1019
|
-
state.info.log += "hover failed, will try again" + "\n";
|
|
1020
|
-
state.element = await this._locate(selectors, state.info, _params);
|
|
1021
|
-
await state.element.hover({ timeout: 10000 });
|
|
1022
|
-
await _screenshot(state, this);
|
|
1023
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1024
|
-
}
|
|
1025
|
-
// await _screenshot(state, this);
|
|
1131
|
+
await _preCommand(state, this);
|
|
1132
|
+
await performAction("hover", state.element, options, this, state, _params);
|
|
1133
|
+
await _screenshot(state, this);
|
|
1026
1134
|
await this.waitForPageLoad();
|
|
1027
1135
|
return state.info;
|
|
1028
1136
|
}
|
|
@@ -1030,7 +1138,7 @@ class StableBrowser {
|
|
|
1030
1138
|
await _commandError(state, e, this);
|
|
1031
1139
|
}
|
|
1032
1140
|
finally {
|
|
1033
|
-
_commandFinally(state, this);
|
|
1141
|
+
await _commandFinally(state, this);
|
|
1034
1142
|
}
|
|
1035
1143
|
}
|
|
1036
1144
|
async selectOption(selectors, values, _params = null, options = {}, world = null) {
|
|
@@ -1045,11 +1153,12 @@ class StableBrowser {
|
|
|
1045
1153
|
value: values.toString(),
|
|
1046
1154
|
type: Types.SELECT,
|
|
1047
1155
|
text: `Select option: ${values}`,
|
|
1156
|
+
_text: `Select option: ${values} on ${selectors.element_name}`,
|
|
1048
1157
|
operation: "selectOption",
|
|
1049
1158
|
log: "***** select option " + selectors.element_name + " *****\n",
|
|
1050
1159
|
};
|
|
1051
1160
|
try {
|
|
1052
|
-
await _preCommand(state, this
|
|
1161
|
+
await _preCommand(state, this);
|
|
1053
1162
|
try {
|
|
1054
1163
|
await state.element.selectOption(values);
|
|
1055
1164
|
}
|
|
@@ -1065,7 +1174,7 @@ class StableBrowser {
|
|
|
1065
1174
|
await _commandError(state, e, this);
|
|
1066
1175
|
}
|
|
1067
1176
|
finally {
|
|
1068
|
-
_commandFinally(state, this);
|
|
1177
|
+
await _commandFinally(state, this);
|
|
1069
1178
|
}
|
|
1070
1179
|
}
|
|
1071
1180
|
async type(_value, _params = null, options = {}, world = null) {
|
|
@@ -1079,11 +1188,12 @@ class StableBrowser {
|
|
|
1079
1188
|
highlight: false,
|
|
1080
1189
|
type: Types.TYPE_PRESS,
|
|
1081
1190
|
text: `Type value: ${_value}`,
|
|
1191
|
+
_text: `Type value: ${_value}`,
|
|
1082
1192
|
operation: "type",
|
|
1083
1193
|
log: "",
|
|
1084
1194
|
};
|
|
1085
1195
|
try {
|
|
1086
|
-
await _preCommand(state, this
|
|
1196
|
+
await _preCommand(state, this);
|
|
1087
1197
|
const valueSegment = state.value.split("&&");
|
|
1088
1198
|
for (let i = 0; i < valueSegment.length; i++) {
|
|
1089
1199
|
if (i > 0) {
|
|
@@ -1110,7 +1220,7 @@ class StableBrowser {
|
|
|
1110
1220
|
await _commandError(state, e, this);
|
|
1111
1221
|
}
|
|
1112
1222
|
finally {
|
|
1113
|
-
_commandFinally(state, this);
|
|
1223
|
+
await _commandFinally(state, this);
|
|
1114
1224
|
}
|
|
1115
1225
|
}
|
|
1116
1226
|
async setInputValue(selectors, value, _params = null, options = {}, world = null) {
|
|
@@ -1126,7 +1236,7 @@ class StableBrowser {
|
|
|
1126
1236
|
log: "***** set input value " + selectors.element_name + " *****\n",
|
|
1127
1237
|
};
|
|
1128
1238
|
try {
|
|
1129
|
-
await _preCommand(state, this
|
|
1239
|
+
await _preCommand(state, this);
|
|
1130
1240
|
let value = await this._replaceWithLocalData(state.value, this);
|
|
1131
1241
|
try {
|
|
1132
1242
|
await state.element.evaluateHandle((el, value) => {
|
|
@@ -1146,7 +1256,7 @@ class StableBrowser {
|
|
|
1146
1256
|
await _commandError(state, e, this);
|
|
1147
1257
|
}
|
|
1148
1258
|
finally {
|
|
1149
|
-
_commandFinally(state, this);
|
|
1259
|
+
await _commandFinally(state, this);
|
|
1150
1260
|
}
|
|
1151
1261
|
}
|
|
1152
1262
|
async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
|
|
@@ -1158,14 +1268,15 @@ class StableBrowser {
|
|
|
1158
1268
|
world,
|
|
1159
1269
|
type: Types.SET_DATE_TIME,
|
|
1160
1270
|
text: `Set date time value: ${value}`,
|
|
1271
|
+
_text: `Set date time value: ${value} on ${selectors.element_name}`,
|
|
1161
1272
|
operation: "setDateTime",
|
|
1162
1273
|
log: "***** set date time value " + selectors.element_name + " *****\n",
|
|
1163
1274
|
throwError: false,
|
|
1164
1275
|
};
|
|
1165
1276
|
try {
|
|
1166
|
-
await _preCommand(state, this
|
|
1277
|
+
await _preCommand(state, this);
|
|
1167
1278
|
try {
|
|
1168
|
-
await state.element
|
|
1279
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
1169
1280
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1170
1281
|
if (format) {
|
|
1171
1282
|
state.value = dayjs(state.value).format(format);
|
|
@@ -1214,7 +1325,7 @@ class StableBrowser {
|
|
|
1214
1325
|
await _commandError(state, e, this);
|
|
1215
1326
|
}
|
|
1216
1327
|
finally {
|
|
1217
|
-
_commandFinally(state, this);
|
|
1328
|
+
await _commandFinally(state, this);
|
|
1218
1329
|
}
|
|
1219
1330
|
}
|
|
1220
1331
|
async clickType(selectors, _value, enter = false, _params = null, options = {}, world = null) {
|
|
@@ -1229,17 +1340,21 @@ class StableBrowser {
|
|
|
1229
1340
|
world,
|
|
1230
1341
|
type: Types.FILL,
|
|
1231
1342
|
text: `Click type input with value: ${_value}`,
|
|
1343
|
+
_text: "Fill " + selectors.element_name + " with value " + maskValue(_value),
|
|
1232
1344
|
operation: "clickType",
|
|
1233
1345
|
log: "***** clickType on " + selectors.element_name + " with value " + maskValue(_value) + "*****\n",
|
|
1234
1346
|
};
|
|
1347
|
+
if (!options) {
|
|
1348
|
+
options = {};
|
|
1349
|
+
}
|
|
1235
1350
|
if (newValue !== _value) {
|
|
1236
1351
|
//this.logger.info(_value + "=" + newValue);
|
|
1237
1352
|
_value = newValue;
|
|
1238
1353
|
}
|
|
1239
1354
|
try {
|
|
1240
|
-
await _preCommand(state, this
|
|
1355
|
+
await _preCommand(state, this);
|
|
1241
1356
|
state.info.value = _value;
|
|
1242
|
-
if (
|
|
1357
|
+
if (!options.press) {
|
|
1243
1358
|
try {
|
|
1244
1359
|
let currentValue = await state.element.inputValue();
|
|
1245
1360
|
if (currentValue) {
|
|
@@ -1250,13 +1365,9 @@ class StableBrowser {
|
|
|
1250
1365
|
this.logger.info("unable to clear input value");
|
|
1251
1366
|
}
|
|
1252
1367
|
}
|
|
1253
|
-
if (options
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
}
|
|
1257
|
-
catch (e) {
|
|
1258
|
-
await state.element.dispatchEvent("click");
|
|
1259
|
-
}
|
|
1368
|
+
if (options.press) {
|
|
1369
|
+
options.timeout = 5000;
|
|
1370
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
1260
1371
|
}
|
|
1261
1372
|
else {
|
|
1262
1373
|
try {
|
|
@@ -1314,7 +1425,7 @@ class StableBrowser {
|
|
|
1314
1425
|
await _commandError(state, e, this);
|
|
1315
1426
|
}
|
|
1316
1427
|
finally {
|
|
1317
|
-
_commandFinally(state, this);
|
|
1428
|
+
await _commandFinally(state, this);
|
|
1318
1429
|
}
|
|
1319
1430
|
}
|
|
1320
1431
|
async fill(selectors, value, enter = false, _params = null, options = {}, world = null) {
|
|
@@ -1330,7 +1441,7 @@ class StableBrowser {
|
|
|
1330
1441
|
log: "***** fill on " + selectors.element_name + " with value " + value + "*****\n",
|
|
1331
1442
|
};
|
|
1332
1443
|
try {
|
|
1333
|
-
await _preCommand(state, this
|
|
1444
|
+
await _preCommand(state, this);
|
|
1334
1445
|
await state.element.fill(value);
|
|
1335
1446
|
await state.element.dispatchEvent("change");
|
|
1336
1447
|
if (enter) {
|
|
@@ -1344,13 +1455,14 @@ class StableBrowser {
|
|
|
1344
1455
|
await _commandError(state, e, this);
|
|
1345
1456
|
}
|
|
1346
1457
|
finally {
|
|
1347
|
-
_commandFinally(state, this);
|
|
1458
|
+
await _commandFinally(state, this);
|
|
1348
1459
|
}
|
|
1349
1460
|
}
|
|
1350
1461
|
async getText(selectors, _params = null, options = {}, info = {}, world = null) {
|
|
1351
1462
|
return await this._getText(selectors, 0, _params, options, info, world);
|
|
1352
1463
|
}
|
|
1353
1464
|
async _getText(selectors, climb, _params = null, options = {}, info = {}, world = null) {
|
|
1465
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1354
1466
|
_validateSelectors(selectors);
|
|
1355
1467
|
let screenshotId = null;
|
|
1356
1468
|
let screenshotPath = null;
|
|
@@ -1360,7 +1472,7 @@ class StableBrowser {
|
|
|
1360
1472
|
}
|
|
1361
1473
|
info.operation = "getText";
|
|
1362
1474
|
info.selectors = selectors;
|
|
1363
|
-
let element = await this._locate(selectors, info, _params);
|
|
1475
|
+
let element = await this._locate(selectors, info, _params, timeout);
|
|
1364
1476
|
if (climb > 0) {
|
|
1365
1477
|
const climbArray = [];
|
|
1366
1478
|
for (let i = 0; i < climb; i++) {
|
|
@@ -1376,19 +1488,21 @@ class StableBrowser {
|
|
|
1376
1488
|
catch (e) {
|
|
1377
1489
|
//ignore
|
|
1378
1490
|
}
|
|
1379
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info
|
|
1491
|
+
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1380
1492
|
try {
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
}
|
|
1493
|
+
await this._highlightElements(element);
|
|
1494
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
1495
|
+
// // console.log(`Highlighting for get text while running from recorder`);
|
|
1496
|
+
// this._highlightElements(element)
|
|
1497
|
+
// .then(async () => {
|
|
1498
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1499
|
+
// this._unhighlightElements(element).then(
|
|
1500
|
+
// () => {}
|
|
1501
|
+
// // console.log(`Unhighlighting vrtr in recorder is successful`)
|
|
1502
|
+
// );
|
|
1503
|
+
// })
|
|
1504
|
+
// .catch(e);
|
|
1505
|
+
// }
|
|
1392
1506
|
const elementText = await element.innerText();
|
|
1393
1507
|
return {
|
|
1394
1508
|
text: elementText,
|
|
@@ -1425,6 +1539,7 @@ class StableBrowser {
|
|
|
1425
1539
|
highlight: false,
|
|
1426
1540
|
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1427
1541
|
text: `Verify element contains pattern: ${pattern}`,
|
|
1542
|
+
_text: "Verify element " + selectors.element_name + " contains pattern " + pattern,
|
|
1428
1543
|
operation: "containsPattern",
|
|
1429
1544
|
log: "***** verify element " + selectors.element_name + " contains pattern " + pattern + " *****\n",
|
|
1430
1545
|
};
|
|
@@ -1435,13 +1550,13 @@ class StableBrowser {
|
|
|
1435
1550
|
}
|
|
1436
1551
|
let foundObj = null;
|
|
1437
1552
|
try {
|
|
1438
|
-
await _preCommand(state, this
|
|
1553
|
+
await _preCommand(state, this);
|
|
1439
1554
|
state.info.pattern = pattern;
|
|
1440
1555
|
foundObj = await this._getText(selectors, 0, _params, options, state.info, world);
|
|
1441
1556
|
if (foundObj && foundObj.element) {
|
|
1442
1557
|
await this.scrollIfNeeded(foundObj.element, state.info);
|
|
1443
1558
|
}
|
|
1444
|
-
await _screenshot(state, this
|
|
1559
|
+
await _screenshot(state, this);
|
|
1445
1560
|
let escapedText = text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
|
|
1446
1561
|
pattern = pattern.replace("{text}", escapedText);
|
|
1447
1562
|
let regex = new RegExp(pattern, "im");
|
|
@@ -1456,10 +1571,12 @@ class StableBrowser {
|
|
|
1456
1571
|
await _commandError(state, e, this);
|
|
1457
1572
|
}
|
|
1458
1573
|
finally {
|
|
1459
|
-
_commandFinally(state, this);
|
|
1574
|
+
await _commandFinally(state, this);
|
|
1460
1575
|
}
|
|
1461
1576
|
}
|
|
1462
1577
|
async containsText(selectors, text, climb, _params = null, options = {}, world = null) {
|
|
1578
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1579
|
+
const startTime = Date.now();
|
|
1463
1580
|
const state = {
|
|
1464
1581
|
selectors,
|
|
1465
1582
|
_params,
|
|
@@ -1486,61 +1603,53 @@ class StableBrowser {
|
|
|
1486
1603
|
}
|
|
1487
1604
|
let foundObj = null;
|
|
1488
1605
|
try {
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
const dateAlternatives = findDateAlternatives(text);
|
|
1496
|
-
const numberAlternatives = findNumberAlternatives(text);
|
|
1497
|
-
if (dateAlternatives.date) {
|
|
1498
|
-
for (let i = 0; i < dateAlternatives.dates.length; i++) {
|
|
1499
|
-
if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
|
|
1500
|
-
foundObj?.value?.includes(dateAlternatives.dates[i])) {
|
|
1501
|
-
return state.info;
|
|
1606
|
+
while (Date.now() - startTime < timeout) {
|
|
1607
|
+
try {
|
|
1608
|
+
await _preCommand(state, this);
|
|
1609
|
+
foundObj = await this._getText(selectors, climb, _params, { timeout: 3000 }, state.info, world);
|
|
1610
|
+
if (foundObj && foundObj.element) {
|
|
1611
|
+
await this.scrollIfNeeded(foundObj.element, state.info);
|
|
1502
1612
|
}
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1613
|
+
await _screenshot(state, this);
|
|
1614
|
+
const dateAlternatives = findDateAlternatives(text);
|
|
1615
|
+
const numberAlternatives = findNumberAlternatives(text);
|
|
1616
|
+
if (dateAlternatives.date) {
|
|
1617
|
+
for (let i = 0; i < dateAlternatives.dates.length; i++) {
|
|
1618
|
+
if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
|
|
1619
|
+
foundObj?.value?.includes(dateAlternatives.dates[i])) {
|
|
1620
|
+
return state.info;
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1624
|
+
else if (numberAlternatives.number) {
|
|
1625
|
+
for (let i = 0; i < numberAlternatives.numbers.length; i++) {
|
|
1626
|
+
if (foundObj?.text.includes(numberAlternatives.numbers[i]) ||
|
|
1627
|
+
foundObj?.value?.includes(numberAlternatives.numbers[i])) {
|
|
1628
|
+
return state.info;
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
else if (foundObj?.text.includes(text) || foundObj?.value?.includes(text)) {
|
|
1510
1633
|
return state.info;
|
|
1511
1634
|
}
|
|
1512
1635
|
}
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
throw new Error("element doesn't contain text " + text);
|
|
1636
|
+
catch (e) {
|
|
1637
|
+
// Log error but continue retrying until timeout is reached
|
|
1638
|
+
this.logger.warn("Retrying containsText due to: " + e.message);
|
|
1639
|
+
}
|
|
1640
|
+
await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second before retrying
|
|
1519
1641
|
}
|
|
1520
|
-
|
|
1642
|
+
state.info.foundText = foundObj?.text;
|
|
1643
|
+
state.info.value = foundObj?.value;
|
|
1644
|
+
throw new Error("element doesn't contain text " + text);
|
|
1521
1645
|
}
|
|
1522
1646
|
catch (e) {
|
|
1523
1647
|
await _commandError(state, e, this);
|
|
1648
|
+
throw e;
|
|
1524
1649
|
}
|
|
1525
1650
|
finally {
|
|
1526
|
-
_commandFinally(state, this);
|
|
1527
|
-
}
|
|
1528
|
-
}
|
|
1529
|
-
_getDataFile(world = null) {
|
|
1530
|
-
let dataFile = null;
|
|
1531
|
-
if (world && world.reportFolder) {
|
|
1532
|
-
dataFile = path.join(world.reportFolder, "data.json");
|
|
1533
|
-
}
|
|
1534
|
-
else if (this.reportFolder) {
|
|
1535
|
-
dataFile = path.join(this.reportFolder, "data.json");
|
|
1536
|
-
}
|
|
1537
|
-
else if (this.context && this.context.reportFolder) {
|
|
1538
|
-
dataFile = path.join(this.context.reportFolder, "data.json");
|
|
1651
|
+
await _commandFinally(state, this);
|
|
1539
1652
|
}
|
|
1540
|
-
else {
|
|
1541
|
-
dataFile = "data.json";
|
|
1542
|
-
}
|
|
1543
|
-
return dataFile;
|
|
1544
1653
|
}
|
|
1545
1654
|
async waitForUserInput(message, world = null) {
|
|
1546
1655
|
if (!message) {
|
|
@@ -1570,13 +1679,22 @@ class StableBrowser {
|
|
|
1570
1679
|
return;
|
|
1571
1680
|
}
|
|
1572
1681
|
// if data file exists, load it
|
|
1573
|
-
const dataFile =
|
|
1682
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1574
1683
|
let data = this.getTestData(world);
|
|
1575
1684
|
// merge the testData with the existing data
|
|
1576
1685
|
Object.assign(data, testData);
|
|
1577
1686
|
// save the data to the file
|
|
1578
1687
|
fs.writeFileSync(dataFile, JSON.stringify(data, null, 2));
|
|
1579
1688
|
}
|
|
1689
|
+
overwriteTestData(testData, world = null) {
|
|
1690
|
+
if (!testData) {
|
|
1691
|
+
return;
|
|
1692
|
+
}
|
|
1693
|
+
// if data file exists, load it
|
|
1694
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1695
|
+
// save the data to the file
|
|
1696
|
+
fs.writeFileSync(dataFile, JSON.stringify(testData, null, 2));
|
|
1697
|
+
}
|
|
1580
1698
|
_getDataFilePath(fileName) {
|
|
1581
1699
|
let dataFile = path.join(this.project_path, "data", fileName);
|
|
1582
1700
|
if (fs.existsSync(dataFile)) {
|
|
@@ -1673,14 +1791,14 @@ class StableBrowser {
|
|
|
1673
1791
|
}
|
|
1674
1792
|
}
|
|
1675
1793
|
getTestData(world = null) {
|
|
1676
|
-
const dataFile =
|
|
1794
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1677
1795
|
let data = {};
|
|
1678
1796
|
if (fs.existsSync(dataFile)) {
|
|
1679
1797
|
data = JSON.parse(fs.readFileSync(dataFile, "utf8"));
|
|
1680
1798
|
}
|
|
1681
1799
|
return data;
|
|
1682
1800
|
}
|
|
1683
|
-
async _screenShot(options = {}, world = null, info = null
|
|
1801
|
+
async _screenShot(options = {}, world = null, info = null) {
|
|
1684
1802
|
// collect url/path/title
|
|
1685
1803
|
if (info) {
|
|
1686
1804
|
if (!info.title) {
|
|
@@ -1709,7 +1827,7 @@ class StableBrowser {
|
|
|
1709
1827
|
const uuidStr = "id_" + randomUUID();
|
|
1710
1828
|
const screenshotPath = path.join(world.screenshotPath, uuidStr + ".png");
|
|
1711
1829
|
try {
|
|
1712
|
-
await this.takeScreenshot(screenshotPath
|
|
1830
|
+
await this.takeScreenshot(screenshotPath);
|
|
1713
1831
|
// let buffer = await this.page.screenshot({ timeout: 4000 });
|
|
1714
1832
|
// // save the buffer to the screenshot path asynchrously
|
|
1715
1833
|
// fs.writeFile(screenshotPath, buffer, (err) => {
|
|
@@ -1720,7 +1838,7 @@ class StableBrowser {
|
|
|
1720
1838
|
result.screenshotId = uuidStr;
|
|
1721
1839
|
result.screenshotPath = screenshotPath;
|
|
1722
1840
|
if (info && info.box) {
|
|
1723
|
-
|
|
1841
|
+
await drawRectangle(screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
|
|
1724
1842
|
}
|
|
1725
1843
|
}
|
|
1726
1844
|
catch (e) {
|
|
@@ -1730,7 +1848,7 @@ class StableBrowser {
|
|
|
1730
1848
|
else if (options && options.screenshot) {
|
|
1731
1849
|
result.screenshotPath = options.screenshotPath;
|
|
1732
1850
|
try {
|
|
1733
|
-
await this.takeScreenshot(options.screenshotPath
|
|
1851
|
+
await this.takeScreenshot(options.screenshotPath);
|
|
1734
1852
|
// let buffer = await this.page.screenshot({ timeout: 4000 });
|
|
1735
1853
|
// // save the buffer to the screenshot path asynchrously
|
|
1736
1854
|
// fs.writeFile(options.screenshotPath, buffer, (err) => {
|
|
@@ -1743,12 +1861,12 @@ class StableBrowser {
|
|
|
1743
1861
|
this.logger.info("unable to take screenshot, ignored");
|
|
1744
1862
|
}
|
|
1745
1863
|
if (info && info.box) {
|
|
1746
|
-
|
|
1864
|
+
await drawRectangle(options.screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
|
|
1747
1865
|
}
|
|
1748
1866
|
}
|
|
1749
1867
|
return result;
|
|
1750
1868
|
}
|
|
1751
|
-
async takeScreenshot(screenshotPath
|
|
1869
|
+
async takeScreenshot(screenshotPath) {
|
|
1752
1870
|
const playContext = this.context.playContext;
|
|
1753
1871
|
// Using CDP to capture the screenshot
|
|
1754
1872
|
const viewportWidth = Math.max(...(await this.page.evaluate(() => [
|
|
@@ -1766,9 +1884,9 @@ class StableBrowser {
|
|
|
1766
1884
|
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1767
1885
|
// console.log(`Unhighlighted previous element`);
|
|
1768
1886
|
// }
|
|
1769
|
-
if (focusedElement) {
|
|
1770
|
-
|
|
1771
|
-
}
|
|
1887
|
+
// if (focusedElement) {
|
|
1888
|
+
// await this._highlightElements(focusedElement);
|
|
1889
|
+
// }
|
|
1772
1890
|
if (this.context.browserName === "chromium") {
|
|
1773
1891
|
const client = await playContext.newCDPSession(this.page);
|
|
1774
1892
|
const { data } = await client.send("Page.captureScreenshot", {
|
|
@@ -1790,10 +1908,10 @@ class StableBrowser {
|
|
|
1790
1908
|
else {
|
|
1791
1909
|
screenshotBuffer = await this.page.screenshot();
|
|
1792
1910
|
}
|
|
1793
|
-
if (focusedElement) {
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
}
|
|
1911
|
+
// if (focusedElement) {
|
|
1912
|
+
// // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
|
|
1913
|
+
// await this._unhighlightElements(focusedElement);
|
|
1914
|
+
// }
|
|
1797
1915
|
let image = await Jimp.read(screenshotBuffer);
|
|
1798
1916
|
// Get the image dimensions
|
|
1799
1917
|
const { width, height } = image.bitmap;
|
|
@@ -1806,6 +1924,7 @@ class StableBrowser {
|
|
|
1806
1924
|
else {
|
|
1807
1925
|
fs.writeFileSync(screenshotPath, screenshotBuffer);
|
|
1808
1926
|
}
|
|
1927
|
+
return screenshotBuffer;
|
|
1809
1928
|
}
|
|
1810
1929
|
async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
|
|
1811
1930
|
const state = {
|
|
@@ -1820,7 +1939,7 @@ class StableBrowser {
|
|
|
1820
1939
|
};
|
|
1821
1940
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1822
1941
|
try {
|
|
1823
|
-
await _preCommand(state, this
|
|
1942
|
+
await _preCommand(state, this);
|
|
1824
1943
|
await expect(state.element).toHaveCount(1, { timeout: 10000 });
|
|
1825
1944
|
return state.info;
|
|
1826
1945
|
}
|
|
@@ -1828,7 +1947,7 @@ class StableBrowser {
|
|
|
1828
1947
|
await _commandError(state, e, this);
|
|
1829
1948
|
}
|
|
1830
1949
|
finally {
|
|
1831
|
-
_commandFinally(state, this);
|
|
1950
|
+
await _commandFinally(state, this);
|
|
1832
1951
|
}
|
|
1833
1952
|
}
|
|
1834
1953
|
async extractAttribute(selectors, attribute, variable, _params = null, options = {}, world = null) {
|
|
@@ -1841,12 +1960,14 @@ class StableBrowser {
|
|
|
1841
1960
|
world,
|
|
1842
1961
|
type: Types.EXTRACT,
|
|
1843
1962
|
text: `Extract attribute from element`,
|
|
1963
|
+
_text: `Extract attribute ${attribute} from ${selectors.element_name}`,
|
|
1844
1964
|
operation: "extractAttribute",
|
|
1845
1965
|
log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1966
|
+
allowDisabled: true,
|
|
1846
1967
|
};
|
|
1847
1968
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1848
1969
|
try {
|
|
1849
|
-
await _preCommand(state, this
|
|
1970
|
+
await _preCommand(state, this);
|
|
1850
1971
|
switch (attribute) {
|
|
1851
1972
|
case "inner_text":
|
|
1852
1973
|
state.value = await state.element.innerText();
|
|
@@ -1857,6 +1978,9 @@ class StableBrowser {
|
|
|
1857
1978
|
case "value":
|
|
1858
1979
|
state.value = await state.element.inputValue();
|
|
1859
1980
|
break;
|
|
1981
|
+
case "text":
|
|
1982
|
+
state.value = await state.element.textContent();
|
|
1983
|
+
break;
|
|
1860
1984
|
default:
|
|
1861
1985
|
state.value = await state.element.getAttribute(attribute);
|
|
1862
1986
|
break;
|
|
@@ -1864,14 +1988,14 @@ class StableBrowser {
|
|
|
1864
1988
|
state.info.value = state.value;
|
|
1865
1989
|
this.setTestData({ [variable]: state.value }, world);
|
|
1866
1990
|
this.logger.info("set test data: " + variable + "=" + state.value);
|
|
1867
|
-
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1991
|
+
// await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1868
1992
|
return state.info;
|
|
1869
1993
|
}
|
|
1870
1994
|
catch (e) {
|
|
1871
1995
|
await _commandError(state, e, this);
|
|
1872
1996
|
}
|
|
1873
1997
|
finally {
|
|
1874
|
-
_commandFinally(state, this);
|
|
1998
|
+
await _commandFinally(state, this);
|
|
1875
1999
|
}
|
|
1876
2000
|
}
|
|
1877
2001
|
async verifyAttribute(selectors, attribute, value, _params = null, options = {}, world = null) {
|
|
@@ -1886,18 +2010,25 @@ class StableBrowser {
|
|
|
1886
2010
|
highlight: true,
|
|
1887
2011
|
screenshot: true,
|
|
1888
2012
|
text: `Verify element attribute`,
|
|
2013
|
+
_text: `Verify attribute ${attribute} from ${selectors.element_name} is ${value}`,
|
|
1889
2014
|
operation: "verifyAttribute",
|
|
1890
2015
|
log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1891
2016
|
allowDisabled: true,
|
|
1892
2017
|
};
|
|
1893
2018
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1894
2019
|
let val;
|
|
2020
|
+
let expectedValue;
|
|
1895
2021
|
try {
|
|
1896
|
-
await _preCommand(state, this
|
|
2022
|
+
await _preCommand(state, this);
|
|
2023
|
+
expectedValue = await replaceWithLocalTestData(state.value, world);
|
|
2024
|
+
state.info.expectedValue = expectedValue;
|
|
1897
2025
|
switch (attribute) {
|
|
1898
2026
|
case "innerText":
|
|
1899
2027
|
val = String(await state.element.innerText());
|
|
1900
2028
|
break;
|
|
2029
|
+
case "text":
|
|
2030
|
+
val = String(await state.element.textContent());
|
|
2031
|
+
break;
|
|
1901
2032
|
case "value":
|
|
1902
2033
|
val = String(await state.element.inputValue());
|
|
1903
2034
|
break;
|
|
@@ -1915,26 +2046,29 @@ class StableBrowser {
|
|
|
1915
2046
|
val = String(await state.element.getAttribute(attribute));
|
|
1916
2047
|
break;
|
|
1917
2048
|
}
|
|
2049
|
+
state.info.value = val;
|
|
1918
2050
|
let regex;
|
|
1919
|
-
if (
|
|
1920
|
-
const patternBody =
|
|
2051
|
+
if (expectedValue.startsWith("/") && expectedValue.endsWith("/")) {
|
|
2052
|
+
const patternBody = expectedValue.slice(1, -1);
|
|
1921
2053
|
regex = new RegExp(patternBody, "g");
|
|
1922
2054
|
}
|
|
1923
2055
|
else {
|
|
1924
|
-
const escapedPattern =
|
|
2056
|
+
const escapedPattern = expectedValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1925
2057
|
regex = new RegExp(escapedPattern, "g");
|
|
1926
2058
|
}
|
|
1927
2059
|
if (!val.match(regex)) {
|
|
1928
|
-
|
|
2060
|
+
let errorMessage = `The ${attribute} attribute has a value of "${val}", but the expected value is "${expectedValue}"`;
|
|
2061
|
+
state.info.failCause.assertionFailed = true;
|
|
2062
|
+
state.info.failCause.lastError = errorMessage;
|
|
2063
|
+
throw new Error(errorMessage);
|
|
1929
2064
|
}
|
|
1930
|
-
state.info.expectedValue = val;
|
|
1931
2065
|
return state.info;
|
|
1932
2066
|
}
|
|
1933
2067
|
catch (e) {
|
|
1934
2068
|
await _commandError(state, e, this);
|
|
1935
2069
|
}
|
|
1936
2070
|
finally {
|
|
1937
|
-
_commandFinally(state, this);
|
|
2071
|
+
await _commandFinally(state, this);
|
|
1938
2072
|
}
|
|
1939
2073
|
}
|
|
1940
2074
|
async extractEmailData(emailAddress, options, world) {
|
|
@@ -2035,21 +2169,20 @@ class StableBrowser {
|
|
|
2035
2169
|
if (node && node.style) {
|
|
2036
2170
|
let originalOutline = node.style.outline;
|
|
2037
2171
|
// console.log(`Original outline was: ${originalOutline}`);
|
|
2038
|
-
node.__previousOutline = originalOutline;
|
|
2172
|
+
// node.__previousOutline = originalOutline;
|
|
2039
2173
|
node.style.outline = "2px solid red";
|
|
2040
2174
|
// console.log(`New outline is: ${node.style.outline}`);
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2175
|
+
if (window) {
|
|
2176
|
+
window.addEventListener("beforeunload", function (e) {
|
|
2177
|
+
node.style.outline = originalOutline;
|
|
2178
|
+
});
|
|
2179
|
+
}
|
|
2180
|
+
setTimeout(function () {
|
|
2181
|
+
node.style.outline = originalOutline;
|
|
2182
|
+
}, 2000);
|
|
2049
2183
|
}
|
|
2050
2184
|
})
|
|
2051
|
-
.then(() => {
|
|
2052
|
-
})
|
|
2185
|
+
.then(() => { })
|
|
2053
2186
|
.catch((e) => {
|
|
2054
2187
|
// ignore
|
|
2055
2188
|
// console.error(`Could not highlight node : ${e}`);
|
|
@@ -2072,20 +2205,19 @@ class StableBrowser {
|
|
|
2072
2205
|
element.__previousOutline = originalOutline;
|
|
2073
2206
|
// Set the new border to be red and 2px solid
|
|
2074
2207
|
element.style.outline = "2px solid red";
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2208
|
+
if (window) {
|
|
2209
|
+
window.addEventListener("beforeunload", function (e) {
|
|
2210
|
+
element.style.outline = originalOutline;
|
|
2211
|
+
});
|
|
2212
|
+
}
|
|
2080
2213
|
// Set a timeout to revert to the original border after 2 seconds
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2214
|
+
setTimeout(function () {
|
|
2215
|
+
element.style.outline = originalOutline;
|
|
2216
|
+
}, 2000);
|
|
2084
2217
|
}
|
|
2085
2218
|
return;
|
|
2086
2219
|
}, [css])
|
|
2087
|
-
.then(() => {
|
|
2088
|
-
})
|
|
2220
|
+
.then(() => { })
|
|
2089
2221
|
.catch((e) => {
|
|
2090
2222
|
// ignore
|
|
2091
2223
|
// console.error(`Could not highlight css: ${e}`);
|
|
@@ -2096,60 +2228,54 @@ class StableBrowser {
|
|
|
2096
2228
|
console.debug(error);
|
|
2097
2229
|
}
|
|
2098
2230
|
}
|
|
2099
|
-
async _unhighlightElements(scope, css) {
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
}
|
|
2148
|
-
}
|
|
2149
|
-
catch (error) {
|
|
2150
|
-
// console.debug(error);
|
|
2151
|
-
}
|
|
2152
|
-
}
|
|
2231
|
+
// async _unhighlightElements(scope, css) {
|
|
2232
|
+
// try {
|
|
2233
|
+
// if (!scope) {
|
|
2234
|
+
// return;
|
|
2235
|
+
// }
|
|
2236
|
+
// if (!css) {
|
|
2237
|
+
// scope
|
|
2238
|
+
// .evaluate((node) => {
|
|
2239
|
+
// if (node && node.style) {
|
|
2240
|
+
// if (!node.__previousOutline) {
|
|
2241
|
+
// node.style.outline = "";
|
|
2242
|
+
// } else {
|
|
2243
|
+
// node.style.outline = node.__previousOutline;
|
|
2244
|
+
// }
|
|
2245
|
+
// }
|
|
2246
|
+
// })
|
|
2247
|
+
// .then(() => {})
|
|
2248
|
+
// .catch((e) => {
|
|
2249
|
+
// // console.log(`Error while unhighlighting node ${JSON.stringify(scope)}: ${e}`);
|
|
2250
|
+
// });
|
|
2251
|
+
// } else {
|
|
2252
|
+
// scope
|
|
2253
|
+
// .evaluate(([css]) => {
|
|
2254
|
+
// if (!css) {
|
|
2255
|
+
// return;
|
|
2256
|
+
// }
|
|
2257
|
+
// let elements = Array.from(document.querySelectorAll(css));
|
|
2258
|
+
// for (i = 0; i < elements.length; i++) {
|
|
2259
|
+
// let element = elements[i];
|
|
2260
|
+
// if (!element.style) {
|
|
2261
|
+
// return;
|
|
2262
|
+
// }
|
|
2263
|
+
// if (!element.__previousOutline) {
|
|
2264
|
+
// element.style.outline = "";
|
|
2265
|
+
// } else {
|
|
2266
|
+
// element.style.outline = element.__previousOutline;
|
|
2267
|
+
// }
|
|
2268
|
+
// }
|
|
2269
|
+
// })
|
|
2270
|
+
// .then(() => {})
|
|
2271
|
+
// .catch((e) => {
|
|
2272
|
+
// // console.error(`Error while unhighlighting element in css: ${e}`);
|
|
2273
|
+
// });
|
|
2274
|
+
// }
|
|
2275
|
+
// } catch (error) {
|
|
2276
|
+
// // console.debug(error);
|
|
2277
|
+
// }
|
|
2278
|
+
// }
|
|
2153
2279
|
async verifyPagePath(pathPart, options = {}, world = null) {
|
|
2154
2280
|
const startTime = Date.now();
|
|
2155
2281
|
let error = null;
|
|
@@ -2194,6 +2320,7 @@ class StableBrowser {
|
|
|
2194
2320
|
_reportToWorld(world, {
|
|
2195
2321
|
type: Types.VERIFY_PAGE_PATH,
|
|
2196
2322
|
text: "Verify page path",
|
|
2323
|
+
_text: "Verify the page path contains " + pathPart,
|
|
2197
2324
|
screenshotId,
|
|
2198
2325
|
result: error
|
|
2199
2326
|
? {
|
|
@@ -2211,27 +2338,89 @@ class StableBrowser {
|
|
|
2211
2338
|
});
|
|
2212
2339
|
}
|
|
2213
2340
|
}
|
|
2214
|
-
async
|
|
2341
|
+
async verifyPageTitle(title, options = {}, world = null) {
|
|
2342
|
+
const startTime = Date.now();
|
|
2343
|
+
let error = null;
|
|
2344
|
+
let screenshotId = null;
|
|
2345
|
+
let screenshotPath = null;
|
|
2346
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2347
|
+
const info = {};
|
|
2348
|
+
info.log = "***** verify page title " + title + " *****\n";
|
|
2349
|
+
info.operation = "verifyPageTitle";
|
|
2350
|
+
const newValue = await this._replaceWithLocalData(title, world);
|
|
2351
|
+
if (newValue !== title) {
|
|
2352
|
+
this.logger.info(title + "=" + newValue);
|
|
2353
|
+
title = newValue;
|
|
2354
|
+
}
|
|
2355
|
+
info.title = title;
|
|
2356
|
+
try {
|
|
2357
|
+
for (let i = 0; i < 30; i++) {
|
|
2358
|
+
const foundTitle = await this.page.title();
|
|
2359
|
+
if (!foundTitle.includes(title)) {
|
|
2360
|
+
if (i === 29) {
|
|
2361
|
+
throw new Error(`url ${foundTitle} doesn't contain ${title}`);
|
|
2362
|
+
}
|
|
2363
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2364
|
+
continue;
|
|
2365
|
+
}
|
|
2366
|
+
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2367
|
+
return info;
|
|
2368
|
+
}
|
|
2369
|
+
}
|
|
2370
|
+
catch (e) {
|
|
2371
|
+
//await this.closeUnexpectedPopups();
|
|
2372
|
+
this.logger.error("verify page title failed " + info.log);
|
|
2373
|
+
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2374
|
+
info.screenshotPath = screenshotPath;
|
|
2375
|
+
Object.assign(e, { info: info });
|
|
2376
|
+
error = e;
|
|
2377
|
+
// throw e;
|
|
2378
|
+
await _commandError({ text: "verifyPageTitle", operation: "verifyPageTitle", title, info, throwError: true }, e, this);
|
|
2379
|
+
}
|
|
2380
|
+
finally {
|
|
2381
|
+
const endTime = Date.now();
|
|
2382
|
+
_reportToWorld(world, {
|
|
2383
|
+
type: Types.VERIFY_PAGE_PATH,
|
|
2384
|
+
text: "Verify page title",
|
|
2385
|
+
_text: "Verify the page title contains " + title,
|
|
2386
|
+
screenshotId,
|
|
2387
|
+
result: error
|
|
2388
|
+
? {
|
|
2389
|
+
status: "FAILED",
|
|
2390
|
+
startTime,
|
|
2391
|
+
endTime,
|
|
2392
|
+
message: error?.message,
|
|
2393
|
+
}
|
|
2394
|
+
: {
|
|
2395
|
+
status: "PASSED",
|
|
2396
|
+
startTime,
|
|
2397
|
+
endTime,
|
|
2398
|
+
},
|
|
2399
|
+
info: info,
|
|
2400
|
+
});
|
|
2401
|
+
}
|
|
2402
|
+
}
|
|
2403
|
+
async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state, partial = true, ignoreCase = false) {
|
|
2215
2404
|
const frames = this.page.frames();
|
|
2216
2405
|
let results = [];
|
|
2217
|
-
let ignoreCase = false;
|
|
2406
|
+
// let ignoreCase = false;
|
|
2218
2407
|
for (let i = 0; i < frames.length; i++) {
|
|
2219
2408
|
if (dateAlternatives.date) {
|
|
2220
2409
|
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2221
|
-
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false,
|
|
2410
|
+
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, partial, ignoreCase, {});
|
|
2222
2411
|
result.frame = frames[i];
|
|
2223
2412
|
results.push(result);
|
|
2224
2413
|
}
|
|
2225
2414
|
}
|
|
2226
2415
|
else if (numberAlternatives.number) {
|
|
2227
2416
|
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2228
|
-
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false,
|
|
2417
|
+
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, partial, ignoreCase, {});
|
|
2229
2418
|
result.frame = frames[i];
|
|
2230
2419
|
results.push(result);
|
|
2231
2420
|
}
|
|
2232
2421
|
}
|
|
2233
2422
|
else {
|
|
2234
|
-
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false,
|
|
2423
|
+
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, partial, ignoreCase, {});
|
|
2235
2424
|
result.frame = frames[i];
|
|
2236
2425
|
results.push(result);
|
|
2237
2426
|
}
|
|
@@ -2250,11 +2439,15 @@ class StableBrowser {
|
|
|
2250
2439
|
scroll: false,
|
|
2251
2440
|
highlight: false,
|
|
2252
2441
|
type: Types.VERIFY_PAGE_CONTAINS_TEXT,
|
|
2253
|
-
text: `Verify text exists in page`,
|
|
2442
|
+
text: `Verify the text '${text}' exists in page`,
|
|
2443
|
+
_text: `Verify the text '${text}' exists in page`,
|
|
2254
2444
|
operation: "verifyTextExistInPage",
|
|
2255
2445
|
log: "***** verify text " + text + " exists in page *****\n",
|
|
2256
2446
|
};
|
|
2257
|
-
|
|
2447
|
+
if (testForRegex(text)) {
|
|
2448
|
+
text = text.replace(/\\"/g, '"');
|
|
2449
|
+
}
|
|
2450
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2258
2451
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2259
2452
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2260
2453
|
if (newValue !== text) {
|
|
@@ -2264,10 +2457,18 @@ class StableBrowser {
|
|
|
2264
2457
|
let dateAlternatives = findDateAlternatives(text);
|
|
2265
2458
|
let numberAlternatives = findNumberAlternatives(text);
|
|
2266
2459
|
try {
|
|
2267
|
-
await _preCommand(state, this
|
|
2460
|
+
await _preCommand(state, this);
|
|
2268
2461
|
state.info.text = text;
|
|
2269
2462
|
while (true) {
|
|
2270
|
-
|
|
2463
|
+
let resultWithElementsFound = {
|
|
2464
|
+
length: 0,
|
|
2465
|
+
};
|
|
2466
|
+
try {
|
|
2467
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2468
|
+
}
|
|
2469
|
+
catch (error) {
|
|
2470
|
+
// ignore
|
|
2471
|
+
}
|
|
2271
2472
|
if (resultWithElementsFound.length === 0) {
|
|
2272
2473
|
if (Date.now() - state.startTime > timeout) {
|
|
2273
2474
|
throw new Error(`Text ${text} not found in page`);
|
|
@@ -2275,35 +2476,40 @@ class StableBrowser {
|
|
|
2275
2476
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2276
2477
|
continue;
|
|
2277
2478
|
}
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2479
|
+
try {
|
|
2480
|
+
if (resultWithElementsFound[0].randomToken) {
|
|
2481
|
+
const frame = resultWithElementsFound[0].frame;
|
|
2482
|
+
const dataAttribute = `[data-blinq-id-${resultWithElementsFound[0].randomToken}]`;
|
|
2483
|
+
await this._highlightElements(frame, dataAttribute);
|
|
2484
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
2282
2485
|
// console.log(`Highlighting for verify text is found while running from recorder`);
|
|
2283
|
-
this._highlightElements(frame, dataAttribute).then(async () => {
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2486
|
+
// this._highlightElements(frame, dataAttribute).then(async () => {
|
|
2487
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2488
|
+
// this._unhighlightElements(frame, dataAttribute)
|
|
2489
|
+
// .then(async () => {
|
|
2490
|
+
// console.log(`Unhighlighted frame dataAttribute successfully`);
|
|
2491
|
+
// })
|
|
2492
|
+
// .catch(
|
|
2493
|
+
// (e) => {}
|
|
2494
|
+
// console.error(e)
|
|
2495
|
+
// );
|
|
2496
|
+
// });
|
|
2497
|
+
// }
|
|
2498
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2499
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2500
|
+
// await this._unhighlightElements(frame, dataAttribute);
|
|
2501
|
+
if (element) {
|
|
2502
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2503
|
+
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2504
|
+
// await _screenshot(state, this, element);
|
|
2505
|
+
}
|
|
2301
2506
|
}
|
|
2302
|
-
}
|
|
2303
|
-
else {
|
|
2304
2507
|
await _screenshot(state, this);
|
|
2508
|
+
return state.info;
|
|
2509
|
+
}
|
|
2510
|
+
catch (error) {
|
|
2511
|
+
console.error(error);
|
|
2305
2512
|
}
|
|
2306
|
-
return state.info;
|
|
2307
2513
|
}
|
|
2308
2514
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2309
2515
|
}
|
|
@@ -2311,7 +2517,7 @@ class StableBrowser {
|
|
|
2311
2517
|
await _commandError(state, e, this);
|
|
2312
2518
|
}
|
|
2313
2519
|
finally {
|
|
2314
|
-
_commandFinally(state, this);
|
|
2520
|
+
await _commandFinally(state, this);
|
|
2315
2521
|
}
|
|
2316
2522
|
}
|
|
2317
2523
|
async waitForTextToDisappear(text, options = {}, world = null) {
|
|
@@ -2324,11 +2530,15 @@ class StableBrowser {
|
|
|
2324
2530
|
scroll: false,
|
|
2325
2531
|
highlight: false,
|
|
2326
2532
|
type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
|
|
2327
|
-
text: `Verify text does not exist in page`,
|
|
2533
|
+
text: `Verify the text '${text}' does not exist in page`,
|
|
2534
|
+
_text: `Verify the text '${text}' does not exist in page`,
|
|
2328
2535
|
operation: "verifyTextNotExistInPage",
|
|
2329
2536
|
log: "***** verify text " + text + " does not exist in page *****\n",
|
|
2330
2537
|
};
|
|
2331
|
-
|
|
2538
|
+
if (testForRegex(text)) {
|
|
2539
|
+
text = text.replace(/\\"/g, '"');
|
|
2540
|
+
}
|
|
2541
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2332
2542
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2333
2543
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2334
2544
|
if (newValue !== text) {
|
|
@@ -2338,10 +2548,18 @@ class StableBrowser {
|
|
|
2338
2548
|
let dateAlternatives = findDateAlternatives(text);
|
|
2339
2549
|
let numberAlternatives = findNumberAlternatives(text);
|
|
2340
2550
|
try {
|
|
2341
|
-
await _preCommand(state, this
|
|
2551
|
+
await _preCommand(state, this);
|
|
2342
2552
|
state.info.text = text;
|
|
2553
|
+
let resultWithElementsFound = {
|
|
2554
|
+
length: null, // initial cannot be 0
|
|
2555
|
+
};
|
|
2343
2556
|
while (true) {
|
|
2344
|
-
|
|
2557
|
+
try {
|
|
2558
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2559
|
+
}
|
|
2560
|
+
catch (error) {
|
|
2561
|
+
// ignore
|
|
2562
|
+
}
|
|
2345
2563
|
if (resultWithElementsFound.length === 0) {
|
|
2346
2564
|
await _screenshot(state, this);
|
|
2347
2565
|
return state.info;
|
|
@@ -2356,7 +2574,7 @@ class StableBrowser {
|
|
|
2356
2574
|
await _commandError(state, e, this);
|
|
2357
2575
|
}
|
|
2358
2576
|
finally {
|
|
2359
|
-
_commandFinally(state, this);
|
|
2577
|
+
await _commandFinally(state, this);
|
|
2360
2578
|
}
|
|
2361
2579
|
}
|
|
2362
2580
|
async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
|
|
@@ -2371,10 +2589,11 @@ class StableBrowser {
|
|
|
2371
2589
|
highlight: false,
|
|
2372
2590
|
type: Types.VERIFY_TEXT_WITH_RELATION,
|
|
2373
2591
|
text: `Verify text with relation to another text`,
|
|
2592
|
+
_text: "Search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found",
|
|
2374
2593
|
operation: "verify_text_with_relation",
|
|
2375
2594
|
log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
|
|
2376
2595
|
};
|
|
2377
|
-
const timeout = this.
|
|
2596
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2378
2597
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2379
2598
|
let newValue = await this._replaceWithLocalData(textAnchor, world);
|
|
2380
2599
|
if (newValue !== textAnchor) {
|
|
@@ -2390,10 +2609,18 @@ class StableBrowser {
|
|
|
2390
2609
|
let numberAlternatives = findNumberAlternatives(textToVerify);
|
|
2391
2610
|
let foundAncore = false;
|
|
2392
2611
|
try {
|
|
2393
|
-
await _preCommand(state, this
|
|
2612
|
+
await _preCommand(state, this);
|
|
2394
2613
|
state.info.text = textToVerify;
|
|
2614
|
+
let resultWithElementsFound = {
|
|
2615
|
+
length: 0,
|
|
2616
|
+
};
|
|
2395
2617
|
while (true) {
|
|
2396
|
-
|
|
2618
|
+
try {
|
|
2619
|
+
resultWithElementsFound = await this.findTextInAllFrames(findDateAlternatives(textAnchor), findNumberAlternatives(textAnchor), textAnchor, state, false);
|
|
2620
|
+
}
|
|
2621
|
+
catch (error) {
|
|
2622
|
+
// ignore
|
|
2623
|
+
}
|
|
2397
2624
|
if (resultWithElementsFound.length === 0) {
|
|
2398
2625
|
if (Date.now() - state.startTime > timeout) {
|
|
2399
2626
|
throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
|
|
@@ -2401,52 +2628,56 @@ class StableBrowser {
|
|
|
2401
2628
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2402
2629
|
continue;
|
|
2403
2630
|
}
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2631
|
+
try {
|
|
2632
|
+
for (let i = 0; i < resultWithElementsFound.length; i++) {
|
|
2633
|
+
foundAncore = true;
|
|
2634
|
+
const result = resultWithElementsFound[i];
|
|
2635
|
+
const token = result.randomToken;
|
|
2636
|
+
const frame = result.frame;
|
|
2637
|
+
let css = `[data-blinq-id-${token}]`;
|
|
2638
|
+
const climbArray1 = [];
|
|
2639
|
+
for (let i = 0; i < climb; i++) {
|
|
2640
|
+
climbArray1.push("..");
|
|
2641
|
+
}
|
|
2642
|
+
let climbXpath = "xpath=" + climbArray1.join("/");
|
|
2643
|
+
css = css + " >> " + climbXpath;
|
|
2644
|
+
const count = await frame.locator(css).count();
|
|
2645
|
+
for (let j = 0; j < count; j++) {
|
|
2646
|
+
const continer = await frame.locator(css).nth(j);
|
|
2647
|
+
const result = await this._locateElementByText(continer, textToVerify, "*:not(script, style, head)", false, true, true, {});
|
|
2648
|
+
if (result.elementCount > 0) {
|
|
2649
|
+
const dataAttribute = "[data-blinq-id-" + result.randomToken + "]";
|
|
2650
|
+
await this._highlightElements(frame, dataAttribute);
|
|
2651
|
+
//const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
|
|
2652
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
2424
2653
|
// console.log(`Highlighting for vtrt while running from recorder`);
|
|
2425
|
-
this._highlightElements(frame, dataAttribute)
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
else {
|
|
2654
|
+
// this._highlightElements(frame, dataAttribute)
|
|
2655
|
+
// .then(async () => {
|
|
2656
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2657
|
+
// this._unhighlightElements(frame, dataAttribute).then(
|
|
2658
|
+
// () => {}
|
|
2659
|
+
// console.log(`Unhighlighting vrtr in recorder is successful`)
|
|
2660
|
+
// );
|
|
2661
|
+
// })
|
|
2662
|
+
// .catch(e);
|
|
2663
|
+
// }
|
|
2664
|
+
//await this._highlightElements(frame, cssAnchor);
|
|
2665
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2666
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2667
|
+
// await this._unhighlightElements(frame, dataAttribute);
|
|
2668
|
+
if (element) {
|
|
2669
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2670
|
+
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2671
|
+
}
|
|
2444
2672
|
await _screenshot(state, this);
|
|
2673
|
+
return state.info;
|
|
2445
2674
|
}
|
|
2446
|
-
return state.info;
|
|
2447
2675
|
}
|
|
2448
2676
|
}
|
|
2449
2677
|
}
|
|
2678
|
+
catch (error) {
|
|
2679
|
+
console.error(error);
|
|
2680
|
+
}
|
|
2450
2681
|
}
|
|
2451
2682
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2452
2683
|
}
|
|
@@ -2454,9 +2685,33 @@ class StableBrowser {
|
|
|
2454
2685
|
await _commandError(state, e, this);
|
|
2455
2686
|
}
|
|
2456
2687
|
finally {
|
|
2457
|
-
_commandFinally(state, this);
|
|
2688
|
+
await _commandFinally(state, this);
|
|
2458
2689
|
}
|
|
2459
2690
|
}
|
|
2691
|
+
async findRelatedTextInAllFrames(textAnchor, climb, textToVerify, params = {}, options = {}, world = null) {
|
|
2692
|
+
const frames = this.page.frames();
|
|
2693
|
+
let results = [];
|
|
2694
|
+
let ignoreCase = false;
|
|
2695
|
+
for (let i = 0; i < frames.length; i++) {
|
|
2696
|
+
const result = await this._locateElementByText(frames[i], textAnchor, "*:not(script, style, head)", false, true, ignoreCase, {});
|
|
2697
|
+
result.frame = frames[i];
|
|
2698
|
+
const climbArray = [];
|
|
2699
|
+
for (let i = 0; i < climb; i++) {
|
|
2700
|
+
climbArray.push("..");
|
|
2701
|
+
}
|
|
2702
|
+
let climbXpath = "xpath=" + climbArray.join("/");
|
|
2703
|
+
const newLocator = `[data-blinq-id-${result.randomToken}] ${climb > 0 ? ">> " + climbXpath : ""} >> internal:text=${testForRegex(textToVerify) ? textToVerify : unEscapeString(textToVerify)}`;
|
|
2704
|
+
const count = await frames[i].locator(newLocator).count();
|
|
2705
|
+
if (count > 0) {
|
|
2706
|
+
result.elementCount = count;
|
|
2707
|
+
result.locator = newLocator;
|
|
2708
|
+
results.push(result);
|
|
2709
|
+
}
|
|
2710
|
+
}
|
|
2711
|
+
// state.info.results = results;
|
|
2712
|
+
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2713
|
+
return resultWithElementsFound;
|
|
2714
|
+
}
|
|
2460
2715
|
async visualVerification(text, options = {}, world = null) {
|
|
2461
2716
|
const startTime = Date.now();
|
|
2462
2717
|
let error = null;
|
|
@@ -2475,10 +2730,13 @@ class StableBrowser {
|
|
|
2475
2730
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2476
2731
|
info.screenshotPath = screenshotPath;
|
|
2477
2732
|
const screenshot = await this.takeScreenshot();
|
|
2478
|
-
|
|
2479
|
-
method: "
|
|
2733
|
+
let request = {
|
|
2734
|
+
method: "post",
|
|
2735
|
+
maxBodyLength: Infinity,
|
|
2480
2736
|
url: `${serviceUrl}/api/runs/screenshots/validate-screenshot`,
|
|
2481
2737
|
headers: {
|
|
2738
|
+
"x-bvt-project-id": path.basename(this.project_path),
|
|
2739
|
+
"x-source": "aaa",
|
|
2482
2740
|
"Content-Type": "application/json",
|
|
2483
2741
|
Authorization: `Bearer ${process.env.TOKEN}`,
|
|
2484
2742
|
},
|
|
@@ -2487,7 +2745,7 @@ class StableBrowser {
|
|
|
2487
2745
|
screenshot: screenshot,
|
|
2488
2746
|
}),
|
|
2489
2747
|
};
|
|
2490
|
-
|
|
2748
|
+
const result = await axios.request(request);
|
|
2491
2749
|
if (result.data.status !== true) {
|
|
2492
2750
|
throw new Error("Visual validation failed");
|
|
2493
2751
|
}
|
|
@@ -2515,6 +2773,7 @@ class StableBrowser {
|
|
|
2515
2773
|
_reportToWorld(world, {
|
|
2516
2774
|
type: Types.VERIFY_VISUAL,
|
|
2517
2775
|
text: "Visual verification",
|
|
2776
|
+
_text: "Visual verification of " + text,
|
|
2518
2777
|
screenshotId,
|
|
2519
2778
|
result: error
|
|
2520
2779
|
? {
|
|
@@ -2565,14 +2824,14 @@ class StableBrowser {
|
|
|
2565
2824
|
info.selectors = selectors;
|
|
2566
2825
|
try {
|
|
2567
2826
|
let table = await this._locate(selectors, info, _params);
|
|
2568
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info
|
|
2827
|
+
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2569
2828
|
const tableData = await getTableData(this.page, table);
|
|
2570
2829
|
return tableData;
|
|
2571
2830
|
}
|
|
2572
2831
|
catch (e) {
|
|
2573
2832
|
this.logger.error("getTableData failed " + info.log);
|
|
2574
2833
|
this.logger.error(e);
|
|
2575
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info
|
|
2834
|
+
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2576
2835
|
info.screenshotPath = screenshotPath;
|
|
2577
2836
|
Object.assign(e, { info: info });
|
|
2578
2837
|
error = e;
|
|
@@ -2781,6 +3040,32 @@ class StableBrowser {
|
|
|
2781
3040
|
}
|
|
2782
3041
|
return timeout;
|
|
2783
3042
|
}
|
|
3043
|
+
_getFindElementTimeout(options) {
|
|
3044
|
+
if (options && options.timeout) {
|
|
3045
|
+
return options.timeout;
|
|
3046
|
+
}
|
|
3047
|
+
if (this.configuration.find_element_timeout) {
|
|
3048
|
+
return this.configuration.find_element_timeout;
|
|
3049
|
+
}
|
|
3050
|
+
return 30000;
|
|
3051
|
+
}
|
|
3052
|
+
async saveStoreState(path = null, world = null) {
|
|
3053
|
+
const storageState = await this.page.context().storageState();
|
|
3054
|
+
//const testDataFile = _getDataFile(world, this.context, this);
|
|
3055
|
+
if (path) {
|
|
3056
|
+
// save { storageState: storageState } into the path
|
|
3057
|
+
fs.writeFileSync(path, JSON.stringify({ storageState: storageState }, null, 2));
|
|
3058
|
+
}
|
|
3059
|
+
else {
|
|
3060
|
+
await this.setTestData({ storageState: storageState }, world);
|
|
3061
|
+
}
|
|
3062
|
+
}
|
|
3063
|
+
async restoreSaveState(path = null, world = null) {
|
|
3064
|
+
await refreshBrowser(this, path, world);
|
|
3065
|
+
this.registerEventListeners(this.context);
|
|
3066
|
+
registerNetworkEvents(this.world, this, this.context, this.page);
|
|
3067
|
+
registerDownloadEvent(this.page, this.world, this.context);
|
|
3068
|
+
}
|
|
2784
3069
|
async waitForPageLoad(options = {}, world = null) {
|
|
2785
3070
|
let timeout = this._getLoadTimeout(options);
|
|
2786
3071
|
const promiseArray = [];
|
|
@@ -2848,12 +3133,13 @@ class StableBrowser {
|
|
|
2848
3133
|
highlight: false,
|
|
2849
3134
|
type: Types.CLOSE_PAGE,
|
|
2850
3135
|
text: `Close page`,
|
|
3136
|
+
_text: `Close the page`,
|
|
2851
3137
|
operation: "closePage",
|
|
2852
3138
|
log: "***** close page *****\n",
|
|
2853
3139
|
throwError: false,
|
|
2854
3140
|
};
|
|
2855
3141
|
try {
|
|
2856
|
-
await _preCommand(state, this
|
|
3142
|
+
await _preCommand(state, this);
|
|
2857
3143
|
await this.page.close();
|
|
2858
3144
|
}
|
|
2859
3145
|
catch (e) {
|
|
@@ -2861,11 +3147,98 @@ class StableBrowser {
|
|
|
2861
3147
|
await _commandError(state, e, this);
|
|
2862
3148
|
}
|
|
2863
3149
|
finally {
|
|
2864
|
-
_commandFinally(state, this);
|
|
3150
|
+
await _commandFinally(state, this);
|
|
3151
|
+
}
|
|
3152
|
+
}
|
|
3153
|
+
async tableCellOperation(headerText, rowText, options, _params, world = null) {
|
|
3154
|
+
let operation = null;
|
|
3155
|
+
if (!options || !options.operation) {
|
|
3156
|
+
throw new Error("operation is not defined");
|
|
3157
|
+
}
|
|
3158
|
+
operation = options.operation;
|
|
3159
|
+
// validate operation is one of the supported operations
|
|
3160
|
+
if (operation != "click" && operation != "hover+click") {
|
|
3161
|
+
throw new Error("operation is not supported");
|
|
3162
|
+
}
|
|
3163
|
+
const state = {
|
|
3164
|
+
options,
|
|
3165
|
+
world,
|
|
3166
|
+
locate: false,
|
|
3167
|
+
scroll: false,
|
|
3168
|
+
highlight: false,
|
|
3169
|
+
type: Types.TABLE_OPERATION,
|
|
3170
|
+
text: `Table operation`,
|
|
3171
|
+
_text: `Table ${operation} operation`,
|
|
3172
|
+
operation: operation,
|
|
3173
|
+
log: "***** Table operation *****\n",
|
|
3174
|
+
};
|
|
3175
|
+
const timeout = this._getFindElementTimeout(options);
|
|
3176
|
+
try {
|
|
3177
|
+
await _preCommand(state, this);
|
|
3178
|
+
const start = Date.now();
|
|
3179
|
+
let cellArea = null;
|
|
3180
|
+
while (true) {
|
|
3181
|
+
try {
|
|
3182
|
+
cellArea = await _findCellArea(headerText, rowText, this, state);
|
|
3183
|
+
if (cellArea) {
|
|
3184
|
+
break;
|
|
3185
|
+
}
|
|
3186
|
+
}
|
|
3187
|
+
catch (e) {
|
|
3188
|
+
// ignore
|
|
3189
|
+
}
|
|
3190
|
+
if (Date.now() - start > timeout) {
|
|
3191
|
+
throw new Error(`Cell not found in table`);
|
|
3192
|
+
}
|
|
3193
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
3194
|
+
}
|
|
3195
|
+
switch (operation) {
|
|
3196
|
+
case "click":
|
|
3197
|
+
if (!options.css) {
|
|
3198
|
+
// will click in the center of the cell
|
|
3199
|
+
let xOffset = 0;
|
|
3200
|
+
let yOffset = 0;
|
|
3201
|
+
if (options.xOffset) {
|
|
3202
|
+
xOffset = options.xOffset;
|
|
3203
|
+
}
|
|
3204
|
+
if (options.yOffset) {
|
|
3205
|
+
yOffset = options.yOffset;
|
|
3206
|
+
}
|
|
3207
|
+
await this.page.mouse.click(cellArea.x + cellArea.width / 2 + xOffset, cellArea.y + cellArea.height / 2 + yOffset);
|
|
3208
|
+
}
|
|
3209
|
+
else {
|
|
3210
|
+
const results = await findElementsInArea(options.css, cellArea, this, options);
|
|
3211
|
+
if (results.length === 0) {
|
|
3212
|
+
throw new Error(`Element not found in cell area`);
|
|
3213
|
+
}
|
|
3214
|
+
state.element = results[0];
|
|
3215
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
3216
|
+
}
|
|
3217
|
+
break;
|
|
3218
|
+
case "hover+click":
|
|
3219
|
+
if (!options.css) {
|
|
3220
|
+
throw new Error("css is not defined");
|
|
3221
|
+
}
|
|
3222
|
+
const results = await findElementsInArea(options.css, cellArea, this, options);
|
|
3223
|
+
if (results.length === 0) {
|
|
3224
|
+
throw new Error(`Element not found in cell area`);
|
|
3225
|
+
}
|
|
3226
|
+
state.element = results[0];
|
|
3227
|
+
await performAction("hover+click", state.element, options, this, state, _params);
|
|
3228
|
+
break;
|
|
3229
|
+
default:
|
|
3230
|
+
throw new Error("operation is not supported");
|
|
3231
|
+
}
|
|
3232
|
+
}
|
|
3233
|
+
catch (e) {
|
|
3234
|
+
await _commandError(state, e, this);
|
|
3235
|
+
}
|
|
3236
|
+
finally {
|
|
3237
|
+
await _commandFinally(state, this);
|
|
2865
3238
|
}
|
|
2866
3239
|
}
|
|
2867
3240
|
saveTestDataAsGlobal(options, world) {
|
|
2868
|
-
const dataFile =
|
|
3241
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
2869
3242
|
process.env.GLOBAL_TEST_DATA_FILE = dataFile;
|
|
2870
3243
|
this.logger.info("Save the scenario test data as global for the following scenarios.");
|
|
2871
3244
|
}
|
|
@@ -2895,6 +3268,7 @@ class StableBrowser {
|
|
|
2895
3268
|
_reportToWorld(world, {
|
|
2896
3269
|
type: Types.SET_VIEWPORT,
|
|
2897
3270
|
text: "set viewport size to " + width + "x" + hight,
|
|
3271
|
+
_text: "Set the viewport size to " + width + "x" + hight,
|
|
2898
3272
|
screenshotId,
|
|
2899
3273
|
result: error
|
|
2900
3274
|
? {
|
|
@@ -2965,7 +3339,38 @@ class StableBrowser {
|
|
|
2965
3339
|
console.log("#-#");
|
|
2966
3340
|
}
|
|
2967
3341
|
}
|
|
3342
|
+
async beforeScenario(world, scenario) {
|
|
3343
|
+
this.beforeScenarioCalled = true;
|
|
3344
|
+
if (scenario && scenario.pickle && scenario.pickle.name) {
|
|
3345
|
+
this.scenarioName = scenario.pickle.name;
|
|
3346
|
+
}
|
|
3347
|
+
if (scenario && scenario.gherkinDocument && scenario.gherkinDocument.feature) {
|
|
3348
|
+
this.featureName = scenario.gherkinDocument.feature.name;
|
|
3349
|
+
}
|
|
3350
|
+
if (this.context) {
|
|
3351
|
+
this.context.examplesRow = extractStepExampleParameters(scenario);
|
|
3352
|
+
}
|
|
3353
|
+
if (this.tags === null && scenario && scenario.pickle && scenario.pickle.tags) {
|
|
3354
|
+
this.tags = scenario.pickle.tags.map((tag) => tag.name);
|
|
3355
|
+
// check if @global_test_data tag is present
|
|
3356
|
+
if (this.tags.includes("@global_test_data")) {
|
|
3357
|
+
this.saveTestDataAsGlobal({}, world);
|
|
3358
|
+
}
|
|
3359
|
+
}
|
|
3360
|
+
// update test data based on feature/scenario
|
|
3361
|
+
let envName = null;
|
|
3362
|
+
if (this.context && this.context.environment) {
|
|
3363
|
+
envName = this.context.environment.name;
|
|
3364
|
+
}
|
|
3365
|
+
if (!process.env.TEMP_RUN) {
|
|
3366
|
+
await getTestData(envName, world, undefined, this.featureName, this.scenarioName);
|
|
3367
|
+
}
|
|
3368
|
+
}
|
|
3369
|
+
async afterScenario(world, scenario) { }
|
|
2968
3370
|
async beforeStep(world, step) {
|
|
3371
|
+
if (!this.beforeScenarioCalled) {
|
|
3372
|
+
this.beforeScenario(world, step);
|
|
3373
|
+
}
|
|
2969
3374
|
if (this.stepIndex === undefined) {
|
|
2970
3375
|
this.stepIndex = 0;
|
|
2971
3376
|
}
|
|
@@ -2982,22 +3387,47 @@ class StableBrowser {
|
|
|
2982
3387
|
else {
|
|
2983
3388
|
this.stepName = "step " + this.stepIndex;
|
|
2984
3389
|
}
|
|
2985
|
-
if (this.context) {
|
|
2986
|
-
this.context.examplesRow = extractStepExampleParameters(step);
|
|
2987
|
-
}
|
|
2988
3390
|
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
2989
3391
|
if (this.context.browserObject.context) {
|
|
2990
3392
|
await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
|
|
2991
3393
|
}
|
|
2992
3394
|
}
|
|
2993
|
-
if (this.
|
|
2994
|
-
this.
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
3395
|
+
if (this.initSnapshotTaken === false) {
|
|
3396
|
+
this.initSnapshotTaken = true;
|
|
3397
|
+
if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
|
|
3398
|
+
const snapshot = await this.getAriaSnapshot();
|
|
3399
|
+
if (snapshot) {
|
|
3400
|
+
await world.attach(JSON.stringify(snapshot), "application/json+snapshot-before");
|
|
3401
|
+
}
|
|
2998
3402
|
}
|
|
2999
3403
|
}
|
|
3000
3404
|
}
|
|
3405
|
+
async getAriaSnapshot() {
|
|
3406
|
+
try {
|
|
3407
|
+
// find the page url
|
|
3408
|
+
const url = await this.page.url();
|
|
3409
|
+
// extract the path from the url
|
|
3410
|
+
const path = new URL(url).pathname;
|
|
3411
|
+
// get the page title
|
|
3412
|
+
const title = await this.page.title();
|
|
3413
|
+
// go over other frams
|
|
3414
|
+
const frames = this.page.frames();
|
|
3415
|
+
const snapshots = [];
|
|
3416
|
+
const content = [`- path: ${path}`, `- title: ${title}`];
|
|
3417
|
+
const timeout = this.configuration.ariaSnapshotTimeout ? this.configuration.ariaSnapshotTimeout : 3000;
|
|
3418
|
+
for (let i = 0; i < frames.length; i++) {
|
|
3419
|
+
content.push(`- frame: ${i}`);
|
|
3420
|
+
const frame = frames[i];
|
|
3421
|
+
const snapshot = await frame.locator("body").ariaSnapshot({ timeout });
|
|
3422
|
+
content.push(snapshot);
|
|
3423
|
+
}
|
|
3424
|
+
return content.join("\n");
|
|
3425
|
+
}
|
|
3426
|
+
catch (e) {
|
|
3427
|
+
console.error(e);
|
|
3428
|
+
}
|
|
3429
|
+
return null;
|
|
3430
|
+
}
|
|
3001
3431
|
async afterStep(world, step) {
|
|
3002
3432
|
this.stepName = null;
|
|
3003
3433
|
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
@@ -3005,11 +3435,25 @@ class StableBrowser {
|
|
|
3005
3435
|
await this.context.browserObject.context.tracing.stopChunk({
|
|
3006
3436
|
path: path.join(this.context.browserObject.traceFolder, `trace-${this.stepIndex}.zip`),
|
|
3007
3437
|
});
|
|
3438
|
+
if (world && world.attach) {
|
|
3439
|
+
await world.attach(JSON.stringify({
|
|
3440
|
+
type: "trace",
|
|
3441
|
+
traceFilePath: `trace-${this.stepIndex}.zip`,
|
|
3442
|
+
}), "application/json+trace");
|
|
3443
|
+
}
|
|
3444
|
+
// console.log("trace file created", `trace-${this.stepIndex}.zip`);
|
|
3008
3445
|
}
|
|
3009
3446
|
}
|
|
3010
3447
|
if (this.context) {
|
|
3011
3448
|
this.context.examplesRow = null;
|
|
3012
3449
|
}
|
|
3450
|
+
if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
|
|
3451
|
+
const snapshot = await this.getAriaSnapshot();
|
|
3452
|
+
if (snapshot) {
|
|
3453
|
+
const obj = {};
|
|
3454
|
+
await world.attach(JSON.stringify(snapshot), "application/json+snapshot-after");
|
|
3455
|
+
}
|
|
3456
|
+
}
|
|
3013
3457
|
}
|
|
3014
3458
|
}
|
|
3015
3459
|
function createTimedPromise(promise, label) {
|