automation_model 1.0.595-dev → 1.0.595-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 +47 -16
- 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 +22 -4
- package/lib/stable_browser.js +770 -202
- 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
|
@@ -10,18 +10,22 @@ import { getDateTimeValue } from "./date_time.js";
|
|
|
10
10
|
import drawRectangle from "./drawRect.js";
|
|
11
11
|
//import { closeUnexpectedPopups } from "./popups.js";
|
|
12
12
|
import { getTableCells, getTableData } from "./table_analyze.js";
|
|
13
|
-
import { _convertToRegexQuery, _copyContext, _fixLocatorUsingParams, _fixUsingParams, _getServerUrl, 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";
|
|
14
14
|
import csv from "csv-parser";
|
|
15
15
|
import { Readable } from "node:stream";
|
|
16
16
|
import readline from "readline";
|
|
17
|
-
import { getContext } from "./init_browser.js";
|
|
17
|
+
import { getContext, refreshBrowser } from "./init_browser.js";
|
|
18
|
+
import { getTestData } from "./auto_page.js";
|
|
18
19
|
import { locate_element } from "./locate_element.js";
|
|
19
20
|
import { randomUUID } from "crypto";
|
|
20
21
|
import { _commandError, _commandFinally, _preCommand, _validateSelectors, _screenshot, _reportToWorld, } from "./command_common.js";
|
|
21
22
|
import { registerDownloadEvent, registerNetworkEvents } from "./network.js";
|
|
22
23
|
import { LocatorLog } from "./locator_log.js";
|
|
24
|
+
import axios from "axios";
|
|
25
|
+
import { _findCellArea, findElementsInArea } from "./table_helper.js";
|
|
23
26
|
export const Types = {
|
|
24
27
|
CLICK: "click_element",
|
|
28
|
+
WAIT_ELEMENT: "wait_element",
|
|
25
29
|
NAVIGATE: "navigate",
|
|
26
30
|
FILL: "fill_element",
|
|
27
31
|
EXECUTE: "execute_page_method",
|
|
@@ -43,6 +47,7 @@ export const Types = {
|
|
|
43
47
|
UNCHECK: "uncheck_element",
|
|
44
48
|
EXTRACT: "extract_attribute",
|
|
45
49
|
CLOSE_PAGE: "close_page",
|
|
50
|
+
TABLE_OPERATION: "table_operation",
|
|
46
51
|
SET_DATE_TIME: "set_date_time",
|
|
47
52
|
SET_VIEWPORT: "set_viewport",
|
|
48
53
|
VERIFY_VISUAL: "verify_visual",
|
|
@@ -51,8 +56,12 @@ export const Types = {
|
|
|
51
56
|
WAIT_FOR_TEXT_TO_DISAPPEAR: "wait_for_text_to_disappear",
|
|
52
57
|
VERIFY_ATTRIBUTE: "verify_element_attribute",
|
|
53
58
|
VERIFY_TEXT_WITH_RELATION: "verify_text_with_relation",
|
|
59
|
+
BRUNO: "bruno",
|
|
54
60
|
};
|
|
55
61
|
export const apps = {};
|
|
62
|
+
const formatElementName = (elementName) => {
|
|
63
|
+
return elementName ? JSON.stringify(elementName) : "element";
|
|
64
|
+
};
|
|
56
65
|
class StableBrowser {
|
|
57
66
|
browser;
|
|
58
67
|
page;
|
|
@@ -66,6 +75,7 @@ class StableBrowser {
|
|
|
66
75
|
appName = "main";
|
|
67
76
|
tags = null;
|
|
68
77
|
isRecording = false;
|
|
78
|
+
initSnapshotTaken = false;
|
|
69
79
|
constructor(browser, page, logger = null, context = null, world = null) {
|
|
70
80
|
this.browser = browser;
|
|
71
81
|
this.page = page;
|
|
@@ -172,6 +182,30 @@ class StableBrowser {
|
|
|
172
182
|
await this.waitForPageLoad();
|
|
173
183
|
}
|
|
174
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
|
+
}
|
|
175
209
|
registerConsoleLogListener(page, context) {
|
|
176
210
|
if (!this.context.webLogger) {
|
|
177
211
|
this.context.webLogger = [];
|
|
@@ -236,6 +270,9 @@ class StableBrowser {
|
|
|
236
270
|
// await closeUnexpectedPopups(this.page);
|
|
237
271
|
// }
|
|
238
272
|
async goto(url, world = null) {
|
|
273
|
+
if (!url) {
|
|
274
|
+
throw new Error("url is null, verify that the environment file is correct");
|
|
275
|
+
}
|
|
239
276
|
if (!url.startsWith("http")) {
|
|
240
277
|
url = "https://" + url;
|
|
241
278
|
}
|
|
@@ -264,7 +301,7 @@ class StableBrowser {
|
|
|
264
301
|
_commandError(state, error, this);
|
|
265
302
|
}
|
|
266
303
|
finally {
|
|
267
|
-
_commandFinally(state, this);
|
|
304
|
+
await _commandFinally(state, this);
|
|
268
305
|
}
|
|
269
306
|
}
|
|
270
307
|
async _getLocator(locator, scope, _params) {
|
|
@@ -345,7 +382,7 @@ class StableBrowser {
|
|
|
345
382
|
return resultCss;
|
|
346
383
|
}
|
|
347
384
|
async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, ignoreCase = true, _params) {
|
|
348
|
-
const query = _convertToRegexQuery(text1, regex1, !partial1, ignoreCase)
|
|
385
|
+
const query = `${_convertToRegexQuery(text1, regex1, !partial1, ignoreCase)}`;
|
|
349
386
|
const locator = scope.locator(query);
|
|
350
387
|
const count = await locator.count();
|
|
351
388
|
if (!tag1) {
|
|
@@ -365,6 +402,12 @@ class StableBrowser {
|
|
|
365
402
|
if (!el.setAttribute) {
|
|
366
403
|
el = el.parentElement;
|
|
367
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
|
+
// }
|
|
368
411
|
el.setAttribute("data-blinq-id-" + randomToken, "");
|
|
369
412
|
return true;
|
|
370
413
|
}, [tag1, randomToken]))) {
|
|
@@ -374,7 +417,7 @@ class StableBrowser {
|
|
|
374
417
|
}
|
|
375
418
|
return { elementCount: tagCount, randomToken };
|
|
376
419
|
}
|
|
377
|
-
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) {
|
|
378
421
|
if (!info) {
|
|
379
422
|
info = {};
|
|
380
423
|
}
|
|
@@ -397,10 +440,11 @@ class StableBrowser {
|
|
|
397
440
|
//info.log += "searching for locator " + JSON.stringify(locatorSearch) + "\n";
|
|
398
441
|
let locator = null;
|
|
399
442
|
if (locatorSearch.climb && locatorSearch.climb >= 0) {
|
|
400
|
-
|
|
443
|
+
const replacedText = await this._replaceWithLocalData(locatorSearch.text, this.world);
|
|
444
|
+
let locatorString = await this._locateElmentByTextClimbCss(scope, replacedText, locatorSearch.climb, locatorSearch.css, _params);
|
|
401
445
|
if (!locatorString) {
|
|
402
446
|
info.failCause.textNotFound = true;
|
|
403
|
-
info.failCause.lastError =
|
|
447
|
+
info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${locatorSearch.text}`;
|
|
404
448
|
return;
|
|
405
449
|
}
|
|
406
450
|
locator = await this._getLocator({ css: locatorString }, scope, _params);
|
|
@@ -410,7 +454,7 @@ class StableBrowser {
|
|
|
410
454
|
let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, true, _params);
|
|
411
455
|
if (result.elementCount === 0) {
|
|
412
456
|
info.failCause.textNotFound = true;
|
|
413
|
-
info.failCause.lastError =
|
|
457
|
+
info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${text}`;
|
|
414
458
|
return;
|
|
415
459
|
}
|
|
416
460
|
locatorSearch.css = "[data-blinq-id-" + result.randomToken + "]";
|
|
@@ -462,11 +506,11 @@ class StableBrowser {
|
|
|
462
506
|
info.printMessages = {};
|
|
463
507
|
}
|
|
464
508
|
if (info.locatorLog && !visible) {
|
|
465
|
-
info.failCause.lastError =
|
|
509
|
+
info.failCause.lastError = `${formatElementName(element_name)} is not visible, searching for ${originalLocatorSearch}`;
|
|
466
510
|
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_VISIBLE");
|
|
467
511
|
}
|
|
468
512
|
if (info.locatorLog && !enabled) {
|
|
469
|
-
info.failCause.lastError =
|
|
513
|
+
info.failCause.lastError = `${formatElementName(element_name)} is disabled, searching for ${originalLocatorSearch}`;
|
|
470
514
|
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_ENABLED");
|
|
471
515
|
}
|
|
472
516
|
if (!info.printMessages[j.toString()]) {
|
|
@@ -546,7 +590,28 @@ class StableBrowser {
|
|
|
546
590
|
}
|
|
547
591
|
let element = await this._locate_internal(selectors, info, _params, timeout, allowDisabled);
|
|
548
592
|
if (!element.rerun) {
|
|
549
|
-
|
|
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);
|
|
550
615
|
}
|
|
551
616
|
}
|
|
552
617
|
throw new Error("unable to locate element " + JSON.stringify(selectors));
|
|
@@ -619,7 +684,7 @@ class StableBrowser {
|
|
|
619
684
|
//info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
|
|
620
685
|
if (Date.now() - startTime > timeout) {
|
|
621
686
|
info.failCause.iframeNotFound = true;
|
|
622
|
-
info.failCause.lastError =
|
|
687
|
+
info.failCause.lastError = `unable to locate iframe "${selectors.iframe_src}"`;
|
|
623
688
|
throw new Error("unable to locate iframe " + selectors.iframe_src);
|
|
624
689
|
}
|
|
625
690
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -693,18 +758,13 @@ class StableBrowser {
|
|
|
693
758
|
}
|
|
694
759
|
// info.log += "scanning locators in priority 1" + "\n";
|
|
695
760
|
let onlyPriority3 = selectorsLocators[0].priority === 3;
|
|
696
|
-
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);
|
|
697
762
|
if (result.foundElements.length === 0) {
|
|
698
763
|
// info.log += "scanning locators in priority 2" + "\n";
|
|
699
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly, allowDisabled);
|
|
764
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
700
765
|
}
|
|
701
|
-
if (result.foundElements.length === 0 && onlyPriority3) {
|
|
702
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled);
|
|
703
|
-
}
|
|
704
|
-
else {
|
|
705
|
-
if (result.foundElements.length === 0 && !highPriorityOnly) {
|
|
706
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled);
|
|
707
|
-
}
|
|
766
|
+
if (result.foundElements.length === 0 && (onlyPriority3 || !highPriorityOnly)) {
|
|
767
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
708
768
|
}
|
|
709
769
|
let foundElements = result.foundElements;
|
|
710
770
|
if (foundElements.length === 1 && foundElements[0].unique) {
|
|
@@ -760,6 +820,11 @@ class StableBrowser {
|
|
|
760
820
|
visibleOnly = false;
|
|
761
821
|
}
|
|
762
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
|
+
}
|
|
763
828
|
}
|
|
764
829
|
this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
|
|
765
830
|
// if (info.locatorLog) {
|
|
@@ -771,11 +836,11 @@ class StableBrowser {
|
|
|
771
836
|
//info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
|
|
772
837
|
info.failCause.locatorNotFound = true;
|
|
773
838
|
if (!info?.failCause?.lastError) {
|
|
774
|
-
info.failCause.lastError =
|
|
839
|
+
info.failCause.lastError = `failed to locate ${formatElementName(selectors.element_name)}, ${locatorsCount > 0 ? `${locatorsCount} matching elements found` : "no matching elements found"}`;
|
|
775
840
|
}
|
|
776
841
|
throw new Error("failed to locate first element no elements found, " + info.log);
|
|
777
842
|
}
|
|
778
|
-
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false) {
|
|
843
|
+
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false, element_name) {
|
|
779
844
|
let foundElements = [];
|
|
780
845
|
const result = {
|
|
781
846
|
foundElements: foundElements,
|
|
@@ -783,7 +848,7 @@ class StableBrowser {
|
|
|
783
848
|
for (let i = 0; i < locatorsGroup.length; i++) {
|
|
784
849
|
let foundLocators = [];
|
|
785
850
|
try {
|
|
786
|
-
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);
|
|
787
852
|
}
|
|
788
853
|
catch (e) {
|
|
789
854
|
// this call can fail it the browser is navigating
|
|
@@ -791,7 +856,7 @@ class StableBrowser {
|
|
|
791
856
|
// this.logger.debug(e);
|
|
792
857
|
foundLocators = [];
|
|
793
858
|
try {
|
|
794
|
-
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);
|
|
795
860
|
}
|
|
796
861
|
catch (e) {
|
|
797
862
|
this.logger.info("unable to use locator (second try) " + JSON.stringify(locatorsGroup[i]));
|
|
@@ -806,9 +871,40 @@ class StableBrowser {
|
|
|
806
871
|
result.locatorIndex = i;
|
|
807
872
|
}
|
|
808
873
|
if (foundLocators.length > 1) {
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
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
|
+
}
|
|
812
908
|
}
|
|
813
909
|
}
|
|
814
910
|
}
|
|
@@ -856,7 +952,7 @@ class StableBrowser {
|
|
|
856
952
|
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
857
953
|
}
|
|
858
954
|
finally {
|
|
859
|
-
_commandFinally(state, this);
|
|
955
|
+
await _commandFinally(state, this);
|
|
860
956
|
}
|
|
861
957
|
}
|
|
862
958
|
}
|
|
@@ -905,7 +1001,7 @@ class StableBrowser {
|
|
|
905
1001
|
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
906
1002
|
}
|
|
907
1003
|
finally {
|
|
908
|
-
_commandFinally(state, this);
|
|
1004
|
+
await _commandFinally(state, this);
|
|
909
1005
|
}
|
|
910
1006
|
}
|
|
911
1007
|
}
|
|
@@ -919,25 +1015,14 @@ class StableBrowser {
|
|
|
919
1015
|
options,
|
|
920
1016
|
world,
|
|
921
1017
|
text: "Click element",
|
|
1018
|
+
_text: "Click on " + selectors.element_name,
|
|
922
1019
|
type: Types.CLICK,
|
|
923
1020
|
operation: "click",
|
|
924
1021
|
log: "***** click on " + selectors.element_name + " *****\n",
|
|
925
1022
|
};
|
|
926
1023
|
try {
|
|
927
1024
|
await _preCommand(state, this);
|
|
928
|
-
|
|
929
|
-
// state.selectors.locators[0].text = state.options.context;
|
|
930
|
-
// }
|
|
931
|
-
try {
|
|
932
|
-
await state.element.click();
|
|
933
|
-
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
934
|
-
}
|
|
935
|
-
catch (e) {
|
|
936
|
-
// await this.closeUnexpectedPopups();
|
|
937
|
-
state.element = await this._locate(selectors, state.info, _params);
|
|
938
|
-
await state.element.dispatchEvent("click");
|
|
939
|
-
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
940
|
-
}
|
|
1025
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
941
1026
|
await this.waitForPageLoad();
|
|
942
1027
|
return state.info;
|
|
943
1028
|
}
|
|
@@ -945,8 +1030,40 @@ class StableBrowser {
|
|
|
945
1030
|
await _commandError(state, e, this);
|
|
946
1031
|
}
|
|
947
1032
|
finally {
|
|
948
|
-
_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);
|
|
949
1065
|
}
|
|
1066
|
+
return found;
|
|
950
1067
|
}
|
|
951
1068
|
async setCheck(selectors, checked = true, _params, options = {}, world = null) {
|
|
952
1069
|
const state = {
|
|
@@ -956,6 +1073,7 @@ class StableBrowser {
|
|
|
956
1073
|
world,
|
|
957
1074
|
type: checked ? Types.CHECK : Types.UNCHECK,
|
|
958
1075
|
text: checked ? `Check element` : `Uncheck element`,
|
|
1076
|
+
_text: checked ? `Check ${selectors.element_name}` : `Uncheck ${selectors.element_name}`,
|
|
959
1077
|
operation: "setCheck",
|
|
960
1078
|
log: "***** check " + selectors.element_name + " *****\n",
|
|
961
1079
|
};
|
|
@@ -965,9 +1083,15 @@ class StableBrowser {
|
|
|
965
1083
|
// let element = await this._locate(selectors, info, _params);
|
|
966
1084
|
// ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
967
1085
|
try {
|
|
968
|
-
//
|
|
1086
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
1087
|
+
// console.log(`Highlighting while running from recorder`);
|
|
1088
|
+
await this._highlightElements(state.element);
|
|
969
1089
|
await state.element.setChecked(checked);
|
|
970
1090
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1091
|
+
// await this._unHighlightElements(element);
|
|
1092
|
+
// }
|
|
1093
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1094
|
+
// await this._unHighlightElements(element);
|
|
971
1095
|
}
|
|
972
1096
|
catch (e) {
|
|
973
1097
|
if (e.message && e.message.includes("did not change its state")) {
|
|
@@ -988,7 +1112,7 @@ class StableBrowser {
|
|
|
988
1112
|
await _commandError(state, e, this);
|
|
989
1113
|
}
|
|
990
1114
|
finally {
|
|
991
|
-
_commandFinally(state, this);
|
|
1115
|
+
await _commandFinally(state, this);
|
|
992
1116
|
}
|
|
993
1117
|
}
|
|
994
1118
|
async hover(selectors, _params, options = {}, world = null) {
|
|
@@ -999,22 +1123,13 @@ class StableBrowser {
|
|
|
999
1123
|
world,
|
|
1000
1124
|
type: Types.HOVER,
|
|
1001
1125
|
text: `Hover element`,
|
|
1126
|
+
_text: `Hover on ${selectors.element_name}`,
|
|
1002
1127
|
operation: "hover",
|
|
1003
1128
|
log: "***** hover " + selectors.element_name + " *****\n",
|
|
1004
1129
|
};
|
|
1005
1130
|
try {
|
|
1006
1131
|
await _preCommand(state, this);
|
|
1007
|
-
|
|
1008
|
-
await state.element.hover();
|
|
1009
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1010
|
-
}
|
|
1011
|
-
catch (e) {
|
|
1012
|
-
//await this.closeUnexpectedPopups();
|
|
1013
|
-
state.info.log += "hover failed, will try again" + "\n";
|
|
1014
|
-
state.element = await this._locate(selectors, state.info, _params);
|
|
1015
|
-
await state.element.hover({ timeout: 10000 });
|
|
1016
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1017
|
-
}
|
|
1132
|
+
await performAction("hover", state.element, options, this, state, _params);
|
|
1018
1133
|
await _screenshot(state, this);
|
|
1019
1134
|
await this.waitForPageLoad();
|
|
1020
1135
|
return state.info;
|
|
@@ -1023,7 +1138,7 @@ class StableBrowser {
|
|
|
1023
1138
|
await _commandError(state, e, this);
|
|
1024
1139
|
}
|
|
1025
1140
|
finally {
|
|
1026
|
-
_commandFinally(state, this);
|
|
1141
|
+
await _commandFinally(state, this);
|
|
1027
1142
|
}
|
|
1028
1143
|
}
|
|
1029
1144
|
async selectOption(selectors, values, _params = null, options = {}, world = null) {
|
|
@@ -1038,6 +1153,7 @@ class StableBrowser {
|
|
|
1038
1153
|
value: values.toString(),
|
|
1039
1154
|
type: Types.SELECT,
|
|
1040
1155
|
text: `Select option: ${values}`,
|
|
1156
|
+
_text: `Select option: ${values} on ${selectors.element_name}`,
|
|
1041
1157
|
operation: "selectOption",
|
|
1042
1158
|
log: "***** select option " + selectors.element_name + " *****\n",
|
|
1043
1159
|
};
|
|
@@ -1058,7 +1174,7 @@ class StableBrowser {
|
|
|
1058
1174
|
await _commandError(state, e, this);
|
|
1059
1175
|
}
|
|
1060
1176
|
finally {
|
|
1061
|
-
_commandFinally(state, this);
|
|
1177
|
+
await _commandFinally(state, this);
|
|
1062
1178
|
}
|
|
1063
1179
|
}
|
|
1064
1180
|
async type(_value, _params = null, options = {}, world = null) {
|
|
@@ -1072,6 +1188,7 @@ class StableBrowser {
|
|
|
1072
1188
|
highlight: false,
|
|
1073
1189
|
type: Types.TYPE_PRESS,
|
|
1074
1190
|
text: `Type value: ${_value}`,
|
|
1191
|
+
_text: `Type value: ${_value}`,
|
|
1075
1192
|
operation: "type",
|
|
1076
1193
|
log: "",
|
|
1077
1194
|
};
|
|
@@ -1103,7 +1220,7 @@ class StableBrowser {
|
|
|
1103
1220
|
await _commandError(state, e, this);
|
|
1104
1221
|
}
|
|
1105
1222
|
finally {
|
|
1106
|
-
_commandFinally(state, this);
|
|
1223
|
+
await _commandFinally(state, this);
|
|
1107
1224
|
}
|
|
1108
1225
|
}
|
|
1109
1226
|
async setInputValue(selectors, value, _params = null, options = {}, world = null) {
|
|
@@ -1139,7 +1256,7 @@ class StableBrowser {
|
|
|
1139
1256
|
await _commandError(state, e, this);
|
|
1140
1257
|
}
|
|
1141
1258
|
finally {
|
|
1142
|
-
_commandFinally(state, this);
|
|
1259
|
+
await _commandFinally(state, this);
|
|
1143
1260
|
}
|
|
1144
1261
|
}
|
|
1145
1262
|
async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
|
|
@@ -1151,6 +1268,7 @@ class StableBrowser {
|
|
|
1151
1268
|
world,
|
|
1152
1269
|
type: Types.SET_DATE_TIME,
|
|
1153
1270
|
text: `Set date time value: ${value}`,
|
|
1271
|
+
_text: `Set date time value: ${value} on ${selectors.element_name}`,
|
|
1154
1272
|
operation: "setDateTime",
|
|
1155
1273
|
log: "***** set date time value " + selectors.element_name + " *****\n",
|
|
1156
1274
|
throwError: false,
|
|
@@ -1158,7 +1276,7 @@ class StableBrowser {
|
|
|
1158
1276
|
try {
|
|
1159
1277
|
await _preCommand(state, this);
|
|
1160
1278
|
try {
|
|
1161
|
-
await state.element
|
|
1279
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
1162
1280
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1163
1281
|
if (format) {
|
|
1164
1282
|
state.value = dayjs(state.value).format(format);
|
|
@@ -1207,7 +1325,7 @@ class StableBrowser {
|
|
|
1207
1325
|
await _commandError(state, e, this);
|
|
1208
1326
|
}
|
|
1209
1327
|
finally {
|
|
1210
|
-
_commandFinally(state, this);
|
|
1328
|
+
await _commandFinally(state, this);
|
|
1211
1329
|
}
|
|
1212
1330
|
}
|
|
1213
1331
|
async clickType(selectors, _value, enter = false, _params = null, options = {}, world = null) {
|
|
@@ -1222,9 +1340,13 @@ class StableBrowser {
|
|
|
1222
1340
|
world,
|
|
1223
1341
|
type: Types.FILL,
|
|
1224
1342
|
text: `Click type input with value: ${_value}`,
|
|
1343
|
+
_text: "Fill " + selectors.element_name + " with value " + maskValue(_value),
|
|
1225
1344
|
operation: "clickType",
|
|
1226
1345
|
log: "***** clickType on " + selectors.element_name + " with value " + maskValue(_value) + "*****\n",
|
|
1227
1346
|
};
|
|
1347
|
+
if (!options) {
|
|
1348
|
+
options = {};
|
|
1349
|
+
}
|
|
1228
1350
|
if (newValue !== _value) {
|
|
1229
1351
|
//this.logger.info(_value + "=" + newValue);
|
|
1230
1352
|
_value = newValue;
|
|
@@ -1232,7 +1354,7 @@ class StableBrowser {
|
|
|
1232
1354
|
try {
|
|
1233
1355
|
await _preCommand(state, this);
|
|
1234
1356
|
state.info.value = _value;
|
|
1235
|
-
if (
|
|
1357
|
+
if (!options.press) {
|
|
1236
1358
|
try {
|
|
1237
1359
|
let currentValue = await state.element.inputValue();
|
|
1238
1360
|
if (currentValue) {
|
|
@@ -1243,13 +1365,9 @@ class StableBrowser {
|
|
|
1243
1365
|
this.logger.info("unable to clear input value");
|
|
1244
1366
|
}
|
|
1245
1367
|
}
|
|
1246
|
-
if (options
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
}
|
|
1250
|
-
catch (e) {
|
|
1251
|
-
await state.element.dispatchEvent("click");
|
|
1252
|
-
}
|
|
1368
|
+
if (options.press) {
|
|
1369
|
+
options.timeout = 5000;
|
|
1370
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
1253
1371
|
}
|
|
1254
1372
|
else {
|
|
1255
1373
|
try {
|
|
@@ -1307,7 +1425,7 @@ class StableBrowser {
|
|
|
1307
1425
|
await _commandError(state, e, this);
|
|
1308
1426
|
}
|
|
1309
1427
|
finally {
|
|
1310
|
-
_commandFinally(state, this);
|
|
1428
|
+
await _commandFinally(state, this);
|
|
1311
1429
|
}
|
|
1312
1430
|
}
|
|
1313
1431
|
async fill(selectors, value, enter = false, _params = null, options = {}, world = null) {
|
|
@@ -1337,13 +1455,14 @@ class StableBrowser {
|
|
|
1337
1455
|
await _commandError(state, e, this);
|
|
1338
1456
|
}
|
|
1339
1457
|
finally {
|
|
1340
|
-
_commandFinally(state, this);
|
|
1458
|
+
await _commandFinally(state, this);
|
|
1341
1459
|
}
|
|
1342
1460
|
}
|
|
1343
1461
|
async getText(selectors, _params = null, options = {}, info = {}, world = null) {
|
|
1344
1462
|
return await this._getText(selectors, 0, _params, options, info, world);
|
|
1345
1463
|
}
|
|
1346
1464
|
async _getText(selectors, climb, _params = null, options = {}, info = {}, world = null) {
|
|
1465
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1347
1466
|
_validateSelectors(selectors);
|
|
1348
1467
|
let screenshotId = null;
|
|
1349
1468
|
let screenshotPath = null;
|
|
@@ -1353,7 +1472,7 @@ class StableBrowser {
|
|
|
1353
1472
|
}
|
|
1354
1473
|
info.operation = "getText";
|
|
1355
1474
|
info.selectors = selectors;
|
|
1356
|
-
let element = await this._locate(selectors, info, _params);
|
|
1475
|
+
let element = await this._locate(selectors, info, _params, timeout);
|
|
1357
1476
|
if (climb > 0) {
|
|
1358
1477
|
const climbArray = [];
|
|
1359
1478
|
for (let i = 0; i < climb; i++) {
|
|
@@ -1372,6 +1491,18 @@ class StableBrowser {
|
|
|
1372
1491
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1373
1492
|
try {
|
|
1374
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
|
+
// }
|
|
1375
1506
|
const elementText = await element.innerText();
|
|
1376
1507
|
return {
|
|
1377
1508
|
text: elementText,
|
|
@@ -1383,7 +1514,7 @@ class StableBrowser {
|
|
|
1383
1514
|
}
|
|
1384
1515
|
catch (e) {
|
|
1385
1516
|
//await this.closeUnexpectedPopups();
|
|
1386
|
-
this.logger.info("no innerText will use textContent");
|
|
1517
|
+
this.logger.info("no innerText, will use textContent");
|
|
1387
1518
|
const elementText = await element.textContent();
|
|
1388
1519
|
return { text: elementText, screenshotId, screenshotPath, value: value };
|
|
1389
1520
|
}
|
|
@@ -1408,6 +1539,7 @@ class StableBrowser {
|
|
|
1408
1539
|
highlight: false,
|
|
1409
1540
|
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1410
1541
|
text: `Verify element contains pattern: ${pattern}`,
|
|
1542
|
+
_text: "Verify element " + selectors.element_name + " contains pattern " + pattern,
|
|
1411
1543
|
operation: "containsPattern",
|
|
1412
1544
|
log: "***** verify element " + selectors.element_name + " contains pattern " + pattern + " *****\n",
|
|
1413
1545
|
};
|
|
@@ -1439,10 +1571,12 @@ class StableBrowser {
|
|
|
1439
1571
|
await _commandError(state, e, this);
|
|
1440
1572
|
}
|
|
1441
1573
|
finally {
|
|
1442
|
-
_commandFinally(state, this);
|
|
1574
|
+
await _commandFinally(state, this);
|
|
1443
1575
|
}
|
|
1444
1576
|
}
|
|
1445
1577
|
async containsText(selectors, text, climb, _params = null, options = {}, world = null) {
|
|
1578
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1579
|
+
const startTime = Date.now();
|
|
1446
1580
|
const state = {
|
|
1447
1581
|
selectors,
|
|
1448
1582
|
_params,
|
|
@@ -1469,61 +1603,53 @@ class StableBrowser {
|
|
|
1469
1603
|
}
|
|
1470
1604
|
let foundObj = null;
|
|
1471
1605
|
try {
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
const dateAlternatives = findDateAlternatives(text);
|
|
1479
|
-
const numberAlternatives = findNumberAlternatives(text);
|
|
1480
|
-
if (dateAlternatives.date) {
|
|
1481
|
-
for (let i = 0; i < dateAlternatives.dates.length; i++) {
|
|
1482
|
-
if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
|
|
1483
|
-
foundObj?.value?.includes(dateAlternatives.dates[i])) {
|
|
1484
|
-
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);
|
|
1485
1612
|
}
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
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)) {
|
|
1493
1633
|
return state.info;
|
|
1494
1634
|
}
|
|
1495
1635
|
}
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
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
|
|
1502
1641
|
}
|
|
1503
|
-
|
|
1642
|
+
state.info.foundText = foundObj?.text;
|
|
1643
|
+
state.info.value = foundObj?.value;
|
|
1644
|
+
throw new Error("element doesn't contain text " + text);
|
|
1504
1645
|
}
|
|
1505
1646
|
catch (e) {
|
|
1506
1647
|
await _commandError(state, e, this);
|
|
1648
|
+
throw e;
|
|
1507
1649
|
}
|
|
1508
1650
|
finally {
|
|
1509
|
-
_commandFinally(state, this);
|
|
1510
|
-
}
|
|
1511
|
-
}
|
|
1512
|
-
_getDataFile(world = null) {
|
|
1513
|
-
let dataFile = null;
|
|
1514
|
-
if (world && world.reportFolder) {
|
|
1515
|
-
dataFile = path.join(world.reportFolder, "data.json");
|
|
1516
|
-
}
|
|
1517
|
-
else if (this.reportFolder) {
|
|
1518
|
-
dataFile = path.join(this.reportFolder, "data.json");
|
|
1519
|
-
}
|
|
1520
|
-
else if (this.context && this.context.reportFolder) {
|
|
1521
|
-
dataFile = path.join(this.context.reportFolder, "data.json");
|
|
1522
|
-
}
|
|
1523
|
-
else {
|
|
1524
|
-
dataFile = "data.json";
|
|
1651
|
+
await _commandFinally(state, this);
|
|
1525
1652
|
}
|
|
1526
|
-
return dataFile;
|
|
1527
1653
|
}
|
|
1528
1654
|
async waitForUserInput(message, world = null) {
|
|
1529
1655
|
if (!message) {
|
|
@@ -1553,13 +1679,22 @@ class StableBrowser {
|
|
|
1553
1679
|
return;
|
|
1554
1680
|
}
|
|
1555
1681
|
// if data file exists, load it
|
|
1556
|
-
const dataFile =
|
|
1682
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1557
1683
|
let data = this.getTestData(world);
|
|
1558
1684
|
// merge the testData with the existing data
|
|
1559
1685
|
Object.assign(data, testData);
|
|
1560
1686
|
// save the data to the file
|
|
1561
1687
|
fs.writeFileSync(dataFile, JSON.stringify(data, null, 2));
|
|
1562
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
|
+
}
|
|
1563
1698
|
_getDataFilePath(fileName) {
|
|
1564
1699
|
let dataFile = path.join(this.project_path, "data", fileName);
|
|
1565
1700
|
if (fs.existsSync(dataFile)) {
|
|
@@ -1656,7 +1791,7 @@ class StableBrowser {
|
|
|
1656
1791
|
}
|
|
1657
1792
|
}
|
|
1658
1793
|
getTestData(world = null) {
|
|
1659
|
-
const dataFile =
|
|
1794
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1660
1795
|
let data = {};
|
|
1661
1796
|
if (fs.existsSync(dataFile)) {
|
|
1662
1797
|
data = JSON.parse(fs.readFileSync(dataFile, "utf8"));
|
|
@@ -1743,6 +1878,15 @@ class StableBrowser {
|
|
|
1743
1878
|
document.documentElement.clientWidth,
|
|
1744
1879
|
])));
|
|
1745
1880
|
let screenshotBuffer = null;
|
|
1881
|
+
// if (focusedElement) {
|
|
1882
|
+
// // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
|
|
1883
|
+
// await this._unhighlightElements(focusedElement);
|
|
1884
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1885
|
+
// console.log(`Unhighlighted previous element`);
|
|
1886
|
+
// }
|
|
1887
|
+
// if (focusedElement) {
|
|
1888
|
+
// await this._highlightElements(focusedElement);
|
|
1889
|
+
// }
|
|
1746
1890
|
if (this.context.browserName === "chromium") {
|
|
1747
1891
|
const client = await playContext.newCDPSession(this.page);
|
|
1748
1892
|
const { data } = await client.send("Page.captureScreenshot", {
|
|
@@ -1764,6 +1908,10 @@ class StableBrowser {
|
|
|
1764
1908
|
else {
|
|
1765
1909
|
screenshotBuffer = await this.page.screenshot();
|
|
1766
1910
|
}
|
|
1911
|
+
// if (focusedElement) {
|
|
1912
|
+
// // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
|
|
1913
|
+
// await this._unhighlightElements(focusedElement);
|
|
1914
|
+
// }
|
|
1767
1915
|
let image = await Jimp.read(screenshotBuffer);
|
|
1768
1916
|
// Get the image dimensions
|
|
1769
1917
|
const { width, height } = image.bitmap;
|
|
@@ -1776,6 +1924,7 @@ class StableBrowser {
|
|
|
1776
1924
|
else {
|
|
1777
1925
|
fs.writeFileSync(screenshotPath, screenshotBuffer);
|
|
1778
1926
|
}
|
|
1927
|
+
return screenshotBuffer;
|
|
1779
1928
|
}
|
|
1780
1929
|
async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
|
|
1781
1930
|
const state = {
|
|
@@ -1798,7 +1947,7 @@ class StableBrowser {
|
|
|
1798
1947
|
await _commandError(state, e, this);
|
|
1799
1948
|
}
|
|
1800
1949
|
finally {
|
|
1801
|
-
_commandFinally(state, this);
|
|
1950
|
+
await _commandFinally(state, this);
|
|
1802
1951
|
}
|
|
1803
1952
|
}
|
|
1804
1953
|
async extractAttribute(selectors, attribute, variable, _params = null, options = {}, world = null) {
|
|
@@ -1811,8 +1960,10 @@ class StableBrowser {
|
|
|
1811
1960
|
world,
|
|
1812
1961
|
type: Types.EXTRACT,
|
|
1813
1962
|
text: `Extract attribute from element`,
|
|
1963
|
+
_text: `Extract attribute ${attribute} from ${selectors.element_name}`,
|
|
1814
1964
|
operation: "extractAttribute",
|
|
1815
1965
|
log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1966
|
+
allowDisabled: true,
|
|
1816
1967
|
};
|
|
1817
1968
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1818
1969
|
try {
|
|
@@ -1827,6 +1978,9 @@ class StableBrowser {
|
|
|
1827
1978
|
case "value":
|
|
1828
1979
|
state.value = await state.element.inputValue();
|
|
1829
1980
|
break;
|
|
1981
|
+
case "text":
|
|
1982
|
+
state.value = await state.element.textContent();
|
|
1983
|
+
break;
|
|
1830
1984
|
default:
|
|
1831
1985
|
state.value = await state.element.getAttribute(attribute);
|
|
1832
1986
|
break;
|
|
@@ -1834,13 +1988,14 @@ class StableBrowser {
|
|
|
1834
1988
|
state.info.value = state.value;
|
|
1835
1989
|
this.setTestData({ [variable]: state.value }, world);
|
|
1836
1990
|
this.logger.info("set test data: " + variable + "=" + state.value);
|
|
1991
|
+
// await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1837
1992
|
return state.info;
|
|
1838
1993
|
}
|
|
1839
1994
|
catch (e) {
|
|
1840
1995
|
await _commandError(state, e, this);
|
|
1841
1996
|
}
|
|
1842
1997
|
finally {
|
|
1843
|
-
_commandFinally(state, this);
|
|
1998
|
+
await _commandFinally(state, this);
|
|
1844
1999
|
}
|
|
1845
2000
|
}
|
|
1846
2001
|
async verifyAttribute(selectors, attribute, value, _params = null, options = {}, world = null) {
|
|
@@ -1855,18 +2010,25 @@ class StableBrowser {
|
|
|
1855
2010
|
highlight: true,
|
|
1856
2011
|
screenshot: true,
|
|
1857
2012
|
text: `Verify element attribute`,
|
|
2013
|
+
_text: `Verify attribute ${attribute} from ${selectors.element_name} is ${value}`,
|
|
1858
2014
|
operation: "verifyAttribute",
|
|
1859
2015
|
log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1860
2016
|
allowDisabled: true,
|
|
1861
2017
|
};
|
|
1862
2018
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1863
2019
|
let val;
|
|
2020
|
+
let expectedValue;
|
|
1864
2021
|
try {
|
|
1865
2022
|
await _preCommand(state, this);
|
|
2023
|
+
expectedValue = await replaceWithLocalTestData(state.value, world);
|
|
2024
|
+
state.info.expectedValue = expectedValue;
|
|
1866
2025
|
switch (attribute) {
|
|
1867
2026
|
case "innerText":
|
|
1868
2027
|
val = String(await state.element.innerText());
|
|
1869
2028
|
break;
|
|
2029
|
+
case "text":
|
|
2030
|
+
val = String(await state.element.textContent());
|
|
2031
|
+
break;
|
|
1870
2032
|
case "value":
|
|
1871
2033
|
val = String(await state.element.inputValue());
|
|
1872
2034
|
break;
|
|
@@ -1884,26 +2046,29 @@ class StableBrowser {
|
|
|
1884
2046
|
val = String(await state.element.getAttribute(attribute));
|
|
1885
2047
|
break;
|
|
1886
2048
|
}
|
|
2049
|
+
state.info.value = val;
|
|
1887
2050
|
let regex;
|
|
1888
|
-
if (
|
|
1889
|
-
const patternBody =
|
|
2051
|
+
if (expectedValue.startsWith("/") && expectedValue.endsWith("/")) {
|
|
2052
|
+
const patternBody = expectedValue.slice(1, -1);
|
|
1890
2053
|
regex = new RegExp(patternBody, "g");
|
|
1891
2054
|
}
|
|
1892
2055
|
else {
|
|
1893
|
-
const escapedPattern =
|
|
2056
|
+
const escapedPattern = expectedValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1894
2057
|
regex = new RegExp(escapedPattern, "g");
|
|
1895
2058
|
}
|
|
1896
2059
|
if (!val.match(regex)) {
|
|
1897
|
-
|
|
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);
|
|
1898
2064
|
}
|
|
1899
|
-
state.info.expectedValue = val;
|
|
1900
2065
|
return state.info;
|
|
1901
2066
|
}
|
|
1902
2067
|
catch (e) {
|
|
1903
2068
|
await _commandError(state, e, this);
|
|
1904
2069
|
}
|
|
1905
2070
|
finally {
|
|
1906
|
-
_commandFinally(state, this);
|
|
2071
|
+
await _commandFinally(state, this);
|
|
1907
2072
|
}
|
|
1908
2073
|
}
|
|
1909
2074
|
async extractEmailData(emailAddress, options, world) {
|
|
@@ -1995,27 +2160,32 @@ class StableBrowser {
|
|
|
1995
2160
|
async _highlightElements(scope, css) {
|
|
1996
2161
|
try {
|
|
1997
2162
|
if (!scope) {
|
|
2163
|
+
// console.log(`Scope is not defined`);
|
|
1998
2164
|
return;
|
|
1999
2165
|
}
|
|
2000
2166
|
if (!css) {
|
|
2001
2167
|
scope
|
|
2002
2168
|
.evaluate((node) => {
|
|
2003
2169
|
if (node && node.style) {
|
|
2004
|
-
let
|
|
2170
|
+
let originalOutline = node.style.outline;
|
|
2171
|
+
// console.log(`Original outline was: ${originalOutline}`);
|
|
2172
|
+
// node.__previousOutline = originalOutline;
|
|
2005
2173
|
node.style.outline = "2px solid red";
|
|
2174
|
+
// console.log(`New outline is: ${node.style.outline}`);
|
|
2006
2175
|
if (window) {
|
|
2007
2176
|
window.addEventListener("beforeunload", function (e) {
|
|
2008
|
-
node.style.outline =
|
|
2177
|
+
node.style.outline = originalOutline;
|
|
2009
2178
|
});
|
|
2010
2179
|
}
|
|
2011
2180
|
setTimeout(function () {
|
|
2012
|
-
node.style.outline =
|
|
2181
|
+
node.style.outline = originalOutline;
|
|
2013
2182
|
}, 2000);
|
|
2014
2183
|
}
|
|
2015
2184
|
})
|
|
2016
2185
|
.then(() => { })
|
|
2017
2186
|
.catch((e) => {
|
|
2018
2187
|
// ignore
|
|
2188
|
+
// console.error(`Could not highlight node : ${e}`);
|
|
2019
2189
|
});
|
|
2020
2190
|
}
|
|
2021
2191
|
else {
|
|
@@ -2031,17 +2201,18 @@ class StableBrowser {
|
|
|
2031
2201
|
if (!element.style) {
|
|
2032
2202
|
return;
|
|
2033
2203
|
}
|
|
2034
|
-
|
|
2204
|
+
let originalOutline = element.style.outline;
|
|
2205
|
+
element.__previousOutline = originalOutline;
|
|
2035
2206
|
// Set the new border to be red and 2px solid
|
|
2036
2207
|
element.style.outline = "2px solid red";
|
|
2037
2208
|
if (window) {
|
|
2038
2209
|
window.addEventListener("beforeunload", function (e) {
|
|
2039
|
-
element.style.outline =
|
|
2210
|
+
element.style.outline = originalOutline;
|
|
2040
2211
|
});
|
|
2041
2212
|
}
|
|
2042
2213
|
// Set a timeout to revert to the original border after 2 seconds
|
|
2043
2214
|
setTimeout(function () {
|
|
2044
|
-
element.style.outline =
|
|
2215
|
+
element.style.outline = originalOutline;
|
|
2045
2216
|
}, 2000);
|
|
2046
2217
|
}
|
|
2047
2218
|
return;
|
|
@@ -2049,6 +2220,7 @@ class StableBrowser {
|
|
|
2049
2220
|
.then(() => { })
|
|
2050
2221
|
.catch((e) => {
|
|
2051
2222
|
// ignore
|
|
2223
|
+
// console.error(`Could not highlight css: ${e}`);
|
|
2052
2224
|
});
|
|
2053
2225
|
}
|
|
2054
2226
|
}
|
|
@@ -2056,6 +2228,54 @@ class StableBrowser {
|
|
|
2056
2228
|
console.debug(error);
|
|
2057
2229
|
}
|
|
2058
2230
|
}
|
|
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
|
+
// }
|
|
2059
2279
|
async verifyPagePath(pathPart, options = {}, world = null) {
|
|
2060
2280
|
const startTime = Date.now();
|
|
2061
2281
|
let error = null;
|
|
@@ -2100,6 +2320,69 @@ class StableBrowser {
|
|
|
2100
2320
|
_reportToWorld(world, {
|
|
2101
2321
|
type: Types.VERIFY_PAGE_PATH,
|
|
2102
2322
|
text: "Verify page path",
|
|
2323
|
+
_text: "Verify the page path contains " + pathPart,
|
|
2324
|
+
screenshotId,
|
|
2325
|
+
result: error
|
|
2326
|
+
? {
|
|
2327
|
+
status: "FAILED",
|
|
2328
|
+
startTime,
|
|
2329
|
+
endTime,
|
|
2330
|
+
message: error?.message,
|
|
2331
|
+
}
|
|
2332
|
+
: {
|
|
2333
|
+
status: "PASSED",
|
|
2334
|
+
startTime,
|
|
2335
|
+
endTime,
|
|
2336
|
+
},
|
|
2337
|
+
info: info,
|
|
2338
|
+
});
|
|
2339
|
+
}
|
|
2340
|
+
}
|
|
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,
|
|
2103
2386
|
screenshotId,
|
|
2104
2387
|
result: error
|
|
2105
2388
|
? {
|
|
@@ -2117,27 +2400,27 @@ class StableBrowser {
|
|
|
2117
2400
|
});
|
|
2118
2401
|
}
|
|
2119
2402
|
}
|
|
2120
|
-
async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state) {
|
|
2403
|
+
async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state, partial = true, ignoreCase = false) {
|
|
2121
2404
|
const frames = this.page.frames();
|
|
2122
2405
|
let results = [];
|
|
2123
|
-
let ignoreCase = false;
|
|
2406
|
+
// let ignoreCase = false;
|
|
2124
2407
|
for (let i = 0; i < frames.length; i++) {
|
|
2125
2408
|
if (dateAlternatives.date) {
|
|
2126
2409
|
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2127
|
-
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, {});
|
|
2128
2411
|
result.frame = frames[i];
|
|
2129
2412
|
results.push(result);
|
|
2130
2413
|
}
|
|
2131
2414
|
}
|
|
2132
2415
|
else if (numberAlternatives.number) {
|
|
2133
2416
|
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2134
|
-
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, {});
|
|
2135
2418
|
result.frame = frames[i];
|
|
2136
2419
|
results.push(result);
|
|
2137
2420
|
}
|
|
2138
2421
|
}
|
|
2139
2422
|
else {
|
|
2140
|
-
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, {});
|
|
2141
2424
|
result.frame = frames[i];
|
|
2142
2425
|
results.push(result);
|
|
2143
2426
|
}
|
|
@@ -2156,11 +2439,15 @@ class StableBrowser {
|
|
|
2156
2439
|
scroll: false,
|
|
2157
2440
|
highlight: false,
|
|
2158
2441
|
type: Types.VERIFY_PAGE_CONTAINS_TEXT,
|
|
2159
|
-
text: `Verify text exists in page`,
|
|
2442
|
+
text: `Verify the text '${text}' exists in page`,
|
|
2443
|
+
_text: `Verify the text '${text}' exists in page`,
|
|
2160
2444
|
operation: "verifyTextExistInPage",
|
|
2161
2445
|
log: "***** verify text " + text + " exists in page *****\n",
|
|
2162
2446
|
};
|
|
2163
|
-
|
|
2447
|
+
if (testForRegex(text)) {
|
|
2448
|
+
text = text.replace(/\\"/g, '"');
|
|
2449
|
+
}
|
|
2450
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2164
2451
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2165
2452
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2166
2453
|
if (newValue !== text) {
|
|
@@ -2173,7 +2460,15 @@ class StableBrowser {
|
|
|
2173
2460
|
await _preCommand(state, this);
|
|
2174
2461
|
state.info.text = text;
|
|
2175
2462
|
while (true) {
|
|
2176
|
-
|
|
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
|
+
}
|
|
2177
2472
|
if (resultWithElementsFound.length === 0) {
|
|
2178
2473
|
if (Date.now() - state.startTime > timeout) {
|
|
2179
2474
|
throw new Error(`Text ${text} not found in page`);
|
|
@@ -2181,18 +2476,40 @@ class StableBrowser {
|
|
|
2181
2476
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2182
2477
|
continue;
|
|
2183
2478
|
}
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
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) {
|
|
2485
|
+
// console.log(`Highlighting for verify text is found while running from recorder`);
|
|
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
|
+
}
|
|
2192
2506
|
}
|
|
2507
|
+
await _screenshot(state, this);
|
|
2508
|
+
return state.info;
|
|
2509
|
+
}
|
|
2510
|
+
catch (error) {
|
|
2511
|
+
console.error(error);
|
|
2193
2512
|
}
|
|
2194
|
-
await _screenshot(state, this);
|
|
2195
|
-
return state.info;
|
|
2196
2513
|
}
|
|
2197
2514
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2198
2515
|
}
|
|
@@ -2200,7 +2517,7 @@ class StableBrowser {
|
|
|
2200
2517
|
await _commandError(state, e, this);
|
|
2201
2518
|
}
|
|
2202
2519
|
finally {
|
|
2203
|
-
_commandFinally(state, this);
|
|
2520
|
+
await _commandFinally(state, this);
|
|
2204
2521
|
}
|
|
2205
2522
|
}
|
|
2206
2523
|
async waitForTextToDisappear(text, options = {}, world = null) {
|
|
@@ -2213,11 +2530,15 @@ class StableBrowser {
|
|
|
2213
2530
|
scroll: false,
|
|
2214
2531
|
highlight: false,
|
|
2215
2532
|
type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
|
|
2216
|
-
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`,
|
|
2217
2535
|
operation: "verifyTextNotExistInPage",
|
|
2218
2536
|
log: "***** verify text " + text + " does not exist in page *****\n",
|
|
2219
2537
|
};
|
|
2220
|
-
|
|
2538
|
+
if (testForRegex(text)) {
|
|
2539
|
+
text = text.replace(/\\"/g, '"');
|
|
2540
|
+
}
|
|
2541
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2221
2542
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2222
2543
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2223
2544
|
if (newValue !== text) {
|
|
@@ -2229,8 +2550,16 @@ class StableBrowser {
|
|
|
2229
2550
|
try {
|
|
2230
2551
|
await _preCommand(state, this);
|
|
2231
2552
|
state.info.text = text;
|
|
2553
|
+
let resultWithElementsFound = {
|
|
2554
|
+
length: null, // initial cannot be 0
|
|
2555
|
+
};
|
|
2232
2556
|
while (true) {
|
|
2233
|
-
|
|
2557
|
+
try {
|
|
2558
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2559
|
+
}
|
|
2560
|
+
catch (error) {
|
|
2561
|
+
// ignore
|
|
2562
|
+
}
|
|
2234
2563
|
if (resultWithElementsFound.length === 0) {
|
|
2235
2564
|
await _screenshot(state, this);
|
|
2236
2565
|
return state.info;
|
|
@@ -2245,7 +2574,7 @@ class StableBrowser {
|
|
|
2245
2574
|
await _commandError(state, e, this);
|
|
2246
2575
|
}
|
|
2247
2576
|
finally {
|
|
2248
|
-
_commandFinally(state, this);
|
|
2577
|
+
await _commandFinally(state, this);
|
|
2249
2578
|
}
|
|
2250
2579
|
}
|
|
2251
2580
|
async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
|
|
@@ -2260,10 +2589,11 @@ class StableBrowser {
|
|
|
2260
2589
|
highlight: false,
|
|
2261
2590
|
type: Types.VERIFY_TEXT_WITH_RELATION,
|
|
2262
2591
|
text: `Verify text with relation to another text`,
|
|
2592
|
+
_text: "Search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found",
|
|
2263
2593
|
operation: "verify_text_with_relation",
|
|
2264
2594
|
log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
|
|
2265
2595
|
};
|
|
2266
|
-
const timeout = this.
|
|
2596
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2267
2597
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2268
2598
|
let newValue = await this._replaceWithLocalData(textAnchor, world);
|
|
2269
2599
|
if (newValue !== textAnchor) {
|
|
@@ -2281,8 +2611,16 @@ class StableBrowser {
|
|
|
2281
2611
|
try {
|
|
2282
2612
|
await _preCommand(state, this);
|
|
2283
2613
|
state.info.text = textToVerify;
|
|
2614
|
+
let resultWithElementsFound = {
|
|
2615
|
+
length: 0,
|
|
2616
|
+
};
|
|
2284
2617
|
while (true) {
|
|
2285
|
-
|
|
2618
|
+
try {
|
|
2619
|
+
resultWithElementsFound = await this.findTextInAllFrames(findDateAlternatives(textAnchor), findNumberAlternatives(textAnchor), textAnchor, state, false);
|
|
2620
|
+
}
|
|
2621
|
+
catch (error) {
|
|
2622
|
+
// ignore
|
|
2623
|
+
}
|
|
2286
2624
|
if (resultWithElementsFound.length === 0) {
|
|
2287
2625
|
if (Date.now() - state.startTime > timeout) {
|
|
2288
2626
|
throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
|
|
@@ -2290,37 +2628,56 @@ class StableBrowser {
|
|
|
2290
2628
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2291
2629
|
continue;
|
|
2292
2630
|
}
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
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) {
|
|
2653
|
+
// console.log(`Highlighting for vtrt while running from recorder`);
|
|
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
|
+
}
|
|
2672
|
+
await _screenshot(state, this);
|
|
2673
|
+
return state.info;
|
|
2318
2674
|
}
|
|
2319
|
-
await _screenshot(state, this);
|
|
2320
|
-
return state.info;
|
|
2321
2675
|
}
|
|
2322
2676
|
}
|
|
2323
2677
|
}
|
|
2678
|
+
catch (error) {
|
|
2679
|
+
console.error(error);
|
|
2680
|
+
}
|
|
2324
2681
|
}
|
|
2325
2682
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2326
2683
|
}
|
|
@@ -2328,9 +2685,33 @@ class StableBrowser {
|
|
|
2328
2685
|
await _commandError(state, e, this);
|
|
2329
2686
|
}
|
|
2330
2687
|
finally {
|
|
2331
|
-
_commandFinally(state, this);
|
|
2688
|
+
await _commandFinally(state, this);
|
|
2332
2689
|
}
|
|
2333
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
|
+
}
|
|
2334
2715
|
async visualVerification(text, options = {}, world = null) {
|
|
2335
2716
|
const startTime = Date.now();
|
|
2336
2717
|
let error = null;
|
|
@@ -2349,10 +2730,13 @@ class StableBrowser {
|
|
|
2349
2730
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2350
2731
|
info.screenshotPath = screenshotPath;
|
|
2351
2732
|
const screenshot = await this.takeScreenshot();
|
|
2352
|
-
|
|
2353
|
-
method: "
|
|
2733
|
+
let request = {
|
|
2734
|
+
method: "post",
|
|
2735
|
+
maxBodyLength: Infinity,
|
|
2354
2736
|
url: `${serviceUrl}/api/runs/screenshots/validate-screenshot`,
|
|
2355
2737
|
headers: {
|
|
2738
|
+
"x-bvt-project-id": path.basename(this.project_path),
|
|
2739
|
+
"x-source": "aaa",
|
|
2356
2740
|
"Content-Type": "application/json",
|
|
2357
2741
|
Authorization: `Bearer ${process.env.TOKEN}`,
|
|
2358
2742
|
},
|
|
@@ -2361,7 +2745,7 @@ class StableBrowser {
|
|
|
2361
2745
|
screenshot: screenshot,
|
|
2362
2746
|
}),
|
|
2363
2747
|
};
|
|
2364
|
-
|
|
2748
|
+
const result = await axios.request(request);
|
|
2365
2749
|
if (result.data.status !== true) {
|
|
2366
2750
|
throw new Error("Visual validation failed");
|
|
2367
2751
|
}
|
|
@@ -2389,6 +2773,7 @@ class StableBrowser {
|
|
|
2389
2773
|
_reportToWorld(world, {
|
|
2390
2774
|
type: Types.VERIFY_VISUAL,
|
|
2391
2775
|
text: "Visual verification",
|
|
2776
|
+
_text: "Visual verification of " + text,
|
|
2392
2777
|
screenshotId,
|
|
2393
2778
|
result: error
|
|
2394
2779
|
? {
|
|
@@ -2655,6 +3040,32 @@ class StableBrowser {
|
|
|
2655
3040
|
}
|
|
2656
3041
|
return timeout;
|
|
2657
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
|
+
}
|
|
2658
3069
|
async waitForPageLoad(options = {}, world = null) {
|
|
2659
3070
|
let timeout = this._getLoadTimeout(options);
|
|
2660
3071
|
const promiseArray = [];
|
|
@@ -2722,6 +3133,7 @@ class StableBrowser {
|
|
|
2722
3133
|
highlight: false,
|
|
2723
3134
|
type: Types.CLOSE_PAGE,
|
|
2724
3135
|
text: `Close page`,
|
|
3136
|
+
_text: `Close the page`,
|
|
2725
3137
|
operation: "closePage",
|
|
2726
3138
|
log: "***** close page *****\n",
|
|
2727
3139
|
throwError: false,
|
|
@@ -2735,11 +3147,98 @@ class StableBrowser {
|
|
|
2735
3147
|
await _commandError(state, e, this);
|
|
2736
3148
|
}
|
|
2737
3149
|
finally {
|
|
2738
|
-
_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);
|
|
2739
3238
|
}
|
|
2740
3239
|
}
|
|
2741
3240
|
saveTestDataAsGlobal(options, world) {
|
|
2742
|
-
const dataFile =
|
|
3241
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
2743
3242
|
process.env.GLOBAL_TEST_DATA_FILE = dataFile;
|
|
2744
3243
|
this.logger.info("Save the scenario test data as global for the following scenarios.");
|
|
2745
3244
|
}
|
|
@@ -2769,6 +3268,7 @@ class StableBrowser {
|
|
|
2769
3268
|
_reportToWorld(world, {
|
|
2770
3269
|
type: Types.SET_VIEWPORT,
|
|
2771
3270
|
text: "set viewport size to " + width + "x" + hight,
|
|
3271
|
+
_text: "Set the viewport size to " + width + "x" + hight,
|
|
2772
3272
|
screenshotId,
|
|
2773
3273
|
result: error
|
|
2774
3274
|
? {
|
|
@@ -2839,7 +3339,36 @@ class StableBrowser {
|
|
|
2839
3339
|
console.log("#-#");
|
|
2840
3340
|
}
|
|
2841
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
|
+
await await getTestData(envName, world, undefined, this.featureName, this.scenarioName);
|
|
3366
|
+
}
|
|
3367
|
+
async afterScenario(world, scenario) { }
|
|
2842
3368
|
async beforeStep(world, step) {
|
|
3369
|
+
if (!this.beforeScenarioCalled) {
|
|
3370
|
+
this.beforeScenario(world, step);
|
|
3371
|
+
}
|
|
2843
3372
|
if (this.stepIndex === undefined) {
|
|
2844
3373
|
this.stepIndex = 0;
|
|
2845
3374
|
}
|
|
@@ -2856,22 +3385,47 @@ class StableBrowser {
|
|
|
2856
3385
|
else {
|
|
2857
3386
|
this.stepName = "step " + this.stepIndex;
|
|
2858
3387
|
}
|
|
2859
|
-
if (this.context) {
|
|
2860
|
-
this.context.examplesRow = extractStepExampleParameters(step);
|
|
2861
|
-
}
|
|
2862
3388
|
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
2863
3389
|
if (this.context.browserObject.context) {
|
|
2864
3390
|
await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
|
|
2865
3391
|
}
|
|
2866
3392
|
}
|
|
2867
|
-
if (this.
|
|
2868
|
-
this.
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
3393
|
+
if (this.initSnapshotTaken === false) {
|
|
3394
|
+
this.initSnapshotTaken = true;
|
|
3395
|
+
if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
|
|
3396
|
+
const snapshot = await this.getAriaSnapshot();
|
|
3397
|
+
if (snapshot) {
|
|
3398
|
+
await world.attach(JSON.stringify(snapshot), "application/json+snapshot-before");
|
|
3399
|
+
}
|
|
2872
3400
|
}
|
|
2873
3401
|
}
|
|
2874
3402
|
}
|
|
3403
|
+
async getAriaSnapshot() {
|
|
3404
|
+
try {
|
|
3405
|
+
// find the page url
|
|
3406
|
+
const url = await this.page.url();
|
|
3407
|
+
// extract the path from the url
|
|
3408
|
+
const path = new URL(url).pathname;
|
|
3409
|
+
// get the page title
|
|
3410
|
+
const title = await this.page.title();
|
|
3411
|
+
// go over other frams
|
|
3412
|
+
const frames = this.page.frames();
|
|
3413
|
+
const snapshots = [];
|
|
3414
|
+
const content = [`- path: ${path}`, `- title: ${title}`];
|
|
3415
|
+
const timeout = this.configuration.ariaSnapshotTimeout ? this.configuration.ariaSnapshotTimeout : 3000;
|
|
3416
|
+
for (let i = 0; i < frames.length; i++) {
|
|
3417
|
+
content.push(`- frame: ${i}`);
|
|
3418
|
+
const frame = frames[i];
|
|
3419
|
+
const snapshot = await frame.locator("body").ariaSnapshot({ timeout });
|
|
3420
|
+
content.push(snapshot);
|
|
3421
|
+
}
|
|
3422
|
+
return content.join("\n");
|
|
3423
|
+
}
|
|
3424
|
+
catch (e) {
|
|
3425
|
+
console.error(e);
|
|
3426
|
+
}
|
|
3427
|
+
return null;
|
|
3428
|
+
}
|
|
2875
3429
|
async afterStep(world, step) {
|
|
2876
3430
|
this.stepName = null;
|
|
2877
3431
|
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
@@ -2879,11 +3433,25 @@ class StableBrowser {
|
|
|
2879
3433
|
await this.context.browserObject.context.tracing.stopChunk({
|
|
2880
3434
|
path: path.join(this.context.browserObject.traceFolder, `trace-${this.stepIndex}.zip`),
|
|
2881
3435
|
});
|
|
3436
|
+
if (world && world.attach) {
|
|
3437
|
+
await world.attach(JSON.stringify({
|
|
3438
|
+
type: "trace",
|
|
3439
|
+
traceFilePath: `trace-${this.stepIndex}.zip`,
|
|
3440
|
+
}), "application/json+trace");
|
|
3441
|
+
}
|
|
3442
|
+
// console.log("trace file created", `trace-${this.stepIndex}.zip`);
|
|
2882
3443
|
}
|
|
2883
3444
|
}
|
|
2884
3445
|
if (this.context) {
|
|
2885
3446
|
this.context.examplesRow = null;
|
|
2886
3447
|
}
|
|
3448
|
+
if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
|
|
3449
|
+
const snapshot = await this.getAriaSnapshot();
|
|
3450
|
+
if (snapshot) {
|
|
3451
|
+
const obj = {};
|
|
3452
|
+
await world.attach(JSON.stringify(snapshot), "application/json+snapshot-after");
|
|
3453
|
+
}
|
|
3454
|
+
}
|
|
2887
3455
|
}
|
|
2888
3456
|
}
|
|
2889
3457
|
function createTimedPromise(promise, label) {
|