automation_model 1.0.592-dev → 1.0.592-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 +136 -8
- package/lib/auto_page.js.map +1 -1
- package/lib/browser_manager.js +53 -16
- package/lib/browser_manager.js.map +1 -1
- package/lib/bruno.d.ts +1 -0
- package/lib/bruno.js +284 -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 +18 -0
- package/lib/error-messages.js.map +1 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/lib/init_browser.d.ts +3 -2
- package/lib/init_browser.js +64 -11
- package/lib/init_browser.js.map +1 -1
- package/lib/locate_element.js +2 -2
- package/lib/locate_element.js.map +1 -1
- package/lib/network.d.ts +1 -1
- package/lib/network.js +5 -5
- package/lib/network.js.map +1 -1
- package/lib/stable_browser.d.ts +22 -4
- package/lib/stable_browser.js +775 -197
- package/lib/stable_browser.js.map +1 -1
- package/lib/table_helper.d.ts +19 -0
- package/lib/table_helper.js +116 -0
- package/lib/table_helper.js.map +1 -0
- package/lib/test_context.d.ts +3 -0
- package/lib/test_context.js +2 -0
- package/lib/test_context.js.map +1 -1
- package/lib/utils.d.ts +9 -3
- package/lib/utils.js +309 -21
- 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, 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,9 +506,11 @@ class StableBrowser {
|
|
|
462
506
|
info.printMessages = {};
|
|
463
507
|
}
|
|
464
508
|
if (info.locatorLog && !visible) {
|
|
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) {
|
|
513
|
+
info.failCause.lastError = `${formatElementName(element_name)} is disabled, searching for ${originalLocatorSearch}`;
|
|
468
514
|
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_ENABLED");
|
|
469
515
|
}
|
|
470
516
|
if (!info.printMessages[j.toString()]) {
|
|
@@ -544,7 +590,28 @@ class StableBrowser {
|
|
|
544
590
|
}
|
|
545
591
|
let element = await this._locate_internal(selectors, info, _params, timeout, allowDisabled);
|
|
546
592
|
if (!element.rerun) {
|
|
547
|
-
|
|
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);
|
|
548
615
|
}
|
|
549
616
|
}
|
|
550
617
|
throw new Error("unable to locate element " + JSON.stringify(selectors));
|
|
@@ -617,7 +684,7 @@ class StableBrowser {
|
|
|
617
684
|
//info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
|
|
618
685
|
if (Date.now() - startTime > timeout) {
|
|
619
686
|
info.failCause.iframeNotFound = true;
|
|
620
|
-
info.failCause.lastError =
|
|
687
|
+
info.failCause.lastError = `unable to locate iframe "${selectors.iframe_src}"`;
|
|
621
688
|
throw new Error("unable to locate iframe " + selectors.iframe_src);
|
|
622
689
|
}
|
|
623
690
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -691,18 +758,13 @@ class StableBrowser {
|
|
|
691
758
|
}
|
|
692
759
|
// info.log += "scanning locators in priority 1" + "\n";
|
|
693
760
|
let onlyPriority3 = selectorsLocators[0].priority === 3;
|
|
694
|
-
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);
|
|
695
762
|
if (result.foundElements.length === 0) {
|
|
696
763
|
// info.log += "scanning locators in priority 2" + "\n";
|
|
697
|
-
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);
|
|
698
765
|
}
|
|
699
|
-
if (result.foundElements.length === 0 && onlyPriority3) {
|
|
700
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled);
|
|
701
|
-
}
|
|
702
|
-
else {
|
|
703
|
-
if (result.foundElements.length === 0 && !highPriorityOnly) {
|
|
704
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled);
|
|
705
|
-
}
|
|
766
|
+
if (result.foundElements.length === 0 && (onlyPriority3 || !highPriorityOnly)) {
|
|
767
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
706
768
|
}
|
|
707
769
|
let foundElements = result.foundElements;
|
|
708
770
|
if (foundElements.length === 1 && foundElements[0].unique) {
|
|
@@ -758,6 +820,11 @@ class StableBrowser {
|
|
|
758
820
|
visibleOnly = false;
|
|
759
821
|
}
|
|
760
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
|
+
}
|
|
761
828
|
}
|
|
762
829
|
this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
|
|
763
830
|
// if (info.locatorLog) {
|
|
@@ -768,10 +835,12 @@ class StableBrowser {
|
|
|
768
835
|
// }
|
|
769
836
|
//info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
|
|
770
837
|
info.failCause.locatorNotFound = true;
|
|
771
|
-
info
|
|
838
|
+
if (!info?.failCause?.lastError) {
|
|
839
|
+
info.failCause.lastError = `failed to locate ${formatElementName(selectors.element_name)}, ${locatorsCount > 0 ? `${locatorsCount} matching elements found` : "no matching elements found"}`;
|
|
840
|
+
}
|
|
772
841
|
throw new Error("failed to locate first element no elements found, " + info.log);
|
|
773
842
|
}
|
|
774
|
-
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false) {
|
|
843
|
+
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false, element_name) {
|
|
775
844
|
let foundElements = [];
|
|
776
845
|
const result = {
|
|
777
846
|
foundElements: foundElements,
|
|
@@ -779,7 +848,7 @@ class StableBrowser {
|
|
|
779
848
|
for (let i = 0; i < locatorsGroup.length; i++) {
|
|
780
849
|
let foundLocators = [];
|
|
781
850
|
try {
|
|
782
|
-
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);
|
|
783
852
|
}
|
|
784
853
|
catch (e) {
|
|
785
854
|
// this call can fail it the browser is navigating
|
|
@@ -787,7 +856,7 @@ class StableBrowser {
|
|
|
787
856
|
// this.logger.debug(e);
|
|
788
857
|
foundLocators = [];
|
|
789
858
|
try {
|
|
790
|
-
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);
|
|
791
860
|
}
|
|
792
861
|
catch (e) {
|
|
793
862
|
this.logger.info("unable to use locator (second try) " + JSON.stringify(locatorsGroup[i]));
|
|
@@ -802,9 +871,40 @@ class StableBrowser {
|
|
|
802
871
|
result.locatorIndex = i;
|
|
803
872
|
}
|
|
804
873
|
if (foundLocators.length > 1) {
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
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
|
+
}
|
|
808
908
|
}
|
|
809
909
|
}
|
|
810
910
|
}
|
|
@@ -852,7 +952,7 @@ class StableBrowser {
|
|
|
852
952
|
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
853
953
|
}
|
|
854
954
|
finally {
|
|
855
|
-
_commandFinally(state, this);
|
|
955
|
+
await _commandFinally(state, this);
|
|
856
956
|
}
|
|
857
957
|
}
|
|
858
958
|
}
|
|
@@ -901,7 +1001,7 @@ class StableBrowser {
|
|
|
901
1001
|
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
902
1002
|
}
|
|
903
1003
|
finally {
|
|
904
|
-
_commandFinally(state, this);
|
|
1004
|
+
await _commandFinally(state, this);
|
|
905
1005
|
}
|
|
906
1006
|
}
|
|
907
1007
|
}
|
|
@@ -915,25 +1015,14 @@ class StableBrowser {
|
|
|
915
1015
|
options,
|
|
916
1016
|
world,
|
|
917
1017
|
text: "Click element",
|
|
1018
|
+
_text: "Click on " + selectors.element_name,
|
|
918
1019
|
type: Types.CLICK,
|
|
919
1020
|
operation: "click",
|
|
920
1021
|
log: "***** click on " + selectors.element_name + " *****\n",
|
|
921
1022
|
};
|
|
922
1023
|
try {
|
|
923
1024
|
await _preCommand(state, this);
|
|
924
|
-
|
|
925
|
-
// state.selectors.locators[0].text = state.options.context;
|
|
926
|
-
// }
|
|
927
|
-
try {
|
|
928
|
-
await state.element.click();
|
|
929
|
-
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
930
|
-
}
|
|
931
|
-
catch (e) {
|
|
932
|
-
// await this.closeUnexpectedPopups();
|
|
933
|
-
state.element = await this._locate(selectors, state.info, _params);
|
|
934
|
-
await state.element.dispatchEvent("click");
|
|
935
|
-
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
936
|
-
}
|
|
1025
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
937
1026
|
await this.waitForPageLoad();
|
|
938
1027
|
return state.info;
|
|
939
1028
|
}
|
|
@@ -941,8 +1030,40 @@ class StableBrowser {
|
|
|
941
1030
|
await _commandError(state, e, this);
|
|
942
1031
|
}
|
|
943
1032
|
finally {
|
|
944
|
-
_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);
|
|
945
1065
|
}
|
|
1066
|
+
return found;
|
|
946
1067
|
}
|
|
947
1068
|
async setCheck(selectors, checked = true, _params, options = {}, world = null) {
|
|
948
1069
|
const state = {
|
|
@@ -952,6 +1073,7 @@ class StableBrowser {
|
|
|
952
1073
|
world,
|
|
953
1074
|
type: checked ? Types.CHECK : Types.UNCHECK,
|
|
954
1075
|
text: checked ? `Check element` : `Uncheck element`,
|
|
1076
|
+
_text: checked ? `Check ${selectors.element_name}` : `Uncheck ${selectors.element_name}`,
|
|
955
1077
|
operation: "setCheck",
|
|
956
1078
|
log: "***** check " + selectors.element_name + " *****\n",
|
|
957
1079
|
};
|
|
@@ -961,9 +1083,15 @@ class StableBrowser {
|
|
|
961
1083
|
// let element = await this._locate(selectors, info, _params);
|
|
962
1084
|
// ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
963
1085
|
try {
|
|
964
|
-
//
|
|
1086
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
1087
|
+
// console.log(`Highlighting while running from recorder`);
|
|
1088
|
+
await this._highlightElements(state.element);
|
|
965
1089
|
await state.element.setChecked(checked);
|
|
966
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);
|
|
967
1095
|
}
|
|
968
1096
|
catch (e) {
|
|
969
1097
|
if (e.message && e.message.includes("did not change its state")) {
|
|
@@ -984,7 +1112,7 @@ class StableBrowser {
|
|
|
984
1112
|
await _commandError(state, e, this);
|
|
985
1113
|
}
|
|
986
1114
|
finally {
|
|
987
|
-
_commandFinally(state, this);
|
|
1115
|
+
await _commandFinally(state, this);
|
|
988
1116
|
}
|
|
989
1117
|
}
|
|
990
1118
|
async hover(selectors, _params, options = {}, world = null) {
|
|
@@ -995,22 +1123,13 @@ class StableBrowser {
|
|
|
995
1123
|
world,
|
|
996
1124
|
type: Types.HOVER,
|
|
997
1125
|
text: `Hover element`,
|
|
1126
|
+
_text: `Hover on ${selectors.element_name}`,
|
|
998
1127
|
operation: "hover",
|
|
999
1128
|
log: "***** hover " + selectors.element_name + " *****\n",
|
|
1000
1129
|
};
|
|
1001
1130
|
try {
|
|
1002
1131
|
await _preCommand(state, this);
|
|
1003
|
-
|
|
1004
|
-
await state.element.hover();
|
|
1005
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1006
|
-
}
|
|
1007
|
-
catch (e) {
|
|
1008
|
-
//await this.closeUnexpectedPopups();
|
|
1009
|
-
state.info.log += "hover failed, will try again" + "\n";
|
|
1010
|
-
state.element = await this._locate(selectors, state.info, _params);
|
|
1011
|
-
await state.element.hover({ timeout: 10000 });
|
|
1012
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1013
|
-
}
|
|
1132
|
+
await performAction("hover", state.element, options, this, state, _params);
|
|
1014
1133
|
await _screenshot(state, this);
|
|
1015
1134
|
await this.waitForPageLoad();
|
|
1016
1135
|
return state.info;
|
|
@@ -1019,7 +1138,7 @@ class StableBrowser {
|
|
|
1019
1138
|
await _commandError(state, e, this);
|
|
1020
1139
|
}
|
|
1021
1140
|
finally {
|
|
1022
|
-
_commandFinally(state, this);
|
|
1141
|
+
await _commandFinally(state, this);
|
|
1023
1142
|
}
|
|
1024
1143
|
}
|
|
1025
1144
|
async selectOption(selectors, values, _params = null, options = {}, world = null) {
|
|
@@ -1034,6 +1153,7 @@ class StableBrowser {
|
|
|
1034
1153
|
value: values.toString(),
|
|
1035
1154
|
type: Types.SELECT,
|
|
1036
1155
|
text: `Select option: ${values}`,
|
|
1156
|
+
_text: `Select option: ${values} on ${selectors.element_name}`,
|
|
1037
1157
|
operation: "selectOption",
|
|
1038
1158
|
log: "***** select option " + selectors.element_name + " *****\n",
|
|
1039
1159
|
};
|
|
@@ -1054,7 +1174,7 @@ class StableBrowser {
|
|
|
1054
1174
|
await _commandError(state, e, this);
|
|
1055
1175
|
}
|
|
1056
1176
|
finally {
|
|
1057
|
-
_commandFinally(state, this);
|
|
1177
|
+
await _commandFinally(state, this);
|
|
1058
1178
|
}
|
|
1059
1179
|
}
|
|
1060
1180
|
async type(_value, _params = null, options = {}, world = null) {
|
|
@@ -1068,6 +1188,7 @@ class StableBrowser {
|
|
|
1068
1188
|
highlight: false,
|
|
1069
1189
|
type: Types.TYPE_PRESS,
|
|
1070
1190
|
text: `Type value: ${_value}`,
|
|
1191
|
+
_text: `Type value: ${_value}`,
|
|
1071
1192
|
operation: "type",
|
|
1072
1193
|
log: "",
|
|
1073
1194
|
};
|
|
@@ -1099,7 +1220,7 @@ class StableBrowser {
|
|
|
1099
1220
|
await _commandError(state, e, this);
|
|
1100
1221
|
}
|
|
1101
1222
|
finally {
|
|
1102
|
-
_commandFinally(state, this);
|
|
1223
|
+
await _commandFinally(state, this);
|
|
1103
1224
|
}
|
|
1104
1225
|
}
|
|
1105
1226
|
async setInputValue(selectors, value, _params = null, options = {}, world = null) {
|
|
@@ -1135,7 +1256,7 @@ class StableBrowser {
|
|
|
1135
1256
|
await _commandError(state, e, this);
|
|
1136
1257
|
}
|
|
1137
1258
|
finally {
|
|
1138
|
-
_commandFinally(state, this);
|
|
1259
|
+
await _commandFinally(state, this);
|
|
1139
1260
|
}
|
|
1140
1261
|
}
|
|
1141
1262
|
async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
|
|
@@ -1147,6 +1268,7 @@ class StableBrowser {
|
|
|
1147
1268
|
world,
|
|
1148
1269
|
type: Types.SET_DATE_TIME,
|
|
1149
1270
|
text: `Set date time value: ${value}`,
|
|
1271
|
+
_text: `Set date time value: ${value} on ${selectors.element_name}`,
|
|
1150
1272
|
operation: "setDateTime",
|
|
1151
1273
|
log: "***** set date time value " + selectors.element_name + " *****\n",
|
|
1152
1274
|
throwError: false,
|
|
@@ -1154,7 +1276,7 @@ class StableBrowser {
|
|
|
1154
1276
|
try {
|
|
1155
1277
|
await _preCommand(state, this);
|
|
1156
1278
|
try {
|
|
1157
|
-
await state.element
|
|
1279
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
1158
1280
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1159
1281
|
if (format) {
|
|
1160
1282
|
state.value = dayjs(state.value).format(format);
|
|
@@ -1203,7 +1325,7 @@ class StableBrowser {
|
|
|
1203
1325
|
await _commandError(state, e, this);
|
|
1204
1326
|
}
|
|
1205
1327
|
finally {
|
|
1206
|
-
_commandFinally(state, this);
|
|
1328
|
+
await _commandFinally(state, this);
|
|
1207
1329
|
}
|
|
1208
1330
|
}
|
|
1209
1331
|
async clickType(selectors, _value, enter = false, _params = null, options = {}, world = null) {
|
|
@@ -1218,9 +1340,13 @@ class StableBrowser {
|
|
|
1218
1340
|
world,
|
|
1219
1341
|
type: Types.FILL,
|
|
1220
1342
|
text: `Click type input with value: ${_value}`,
|
|
1343
|
+
_text: "Fill " + selectors.element_name + " with value " + maskValue(_value),
|
|
1221
1344
|
operation: "clickType",
|
|
1222
1345
|
log: "***** clickType on " + selectors.element_name + " with value " + maskValue(_value) + "*****\n",
|
|
1223
1346
|
};
|
|
1347
|
+
if (!options) {
|
|
1348
|
+
options = {};
|
|
1349
|
+
}
|
|
1224
1350
|
if (newValue !== _value) {
|
|
1225
1351
|
//this.logger.info(_value + "=" + newValue);
|
|
1226
1352
|
_value = newValue;
|
|
@@ -1228,7 +1354,7 @@ class StableBrowser {
|
|
|
1228
1354
|
try {
|
|
1229
1355
|
await _preCommand(state, this);
|
|
1230
1356
|
state.info.value = _value;
|
|
1231
|
-
if (
|
|
1357
|
+
if (!options.press) {
|
|
1232
1358
|
try {
|
|
1233
1359
|
let currentValue = await state.element.inputValue();
|
|
1234
1360
|
if (currentValue) {
|
|
@@ -1239,13 +1365,9 @@ class StableBrowser {
|
|
|
1239
1365
|
this.logger.info("unable to clear input value");
|
|
1240
1366
|
}
|
|
1241
1367
|
}
|
|
1242
|
-
if (options
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
}
|
|
1246
|
-
catch (e) {
|
|
1247
|
-
await state.element.dispatchEvent("click");
|
|
1248
|
-
}
|
|
1368
|
+
if (options.press) {
|
|
1369
|
+
options.timeout = 5000;
|
|
1370
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
1249
1371
|
}
|
|
1250
1372
|
else {
|
|
1251
1373
|
try {
|
|
@@ -1303,7 +1425,7 @@ class StableBrowser {
|
|
|
1303
1425
|
await _commandError(state, e, this);
|
|
1304
1426
|
}
|
|
1305
1427
|
finally {
|
|
1306
|
-
_commandFinally(state, this);
|
|
1428
|
+
await _commandFinally(state, this);
|
|
1307
1429
|
}
|
|
1308
1430
|
}
|
|
1309
1431
|
async fill(selectors, value, enter = false, _params = null, options = {}, world = null) {
|
|
@@ -1333,13 +1455,14 @@ class StableBrowser {
|
|
|
1333
1455
|
await _commandError(state, e, this);
|
|
1334
1456
|
}
|
|
1335
1457
|
finally {
|
|
1336
|
-
_commandFinally(state, this);
|
|
1458
|
+
await _commandFinally(state, this);
|
|
1337
1459
|
}
|
|
1338
1460
|
}
|
|
1339
1461
|
async getText(selectors, _params = null, options = {}, info = {}, world = null) {
|
|
1340
1462
|
return await this._getText(selectors, 0, _params, options, info, world);
|
|
1341
1463
|
}
|
|
1342
1464
|
async _getText(selectors, climb, _params = null, options = {}, info = {}, world = null) {
|
|
1465
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1343
1466
|
_validateSelectors(selectors);
|
|
1344
1467
|
let screenshotId = null;
|
|
1345
1468
|
let screenshotPath = null;
|
|
@@ -1349,7 +1472,7 @@ class StableBrowser {
|
|
|
1349
1472
|
}
|
|
1350
1473
|
info.operation = "getText";
|
|
1351
1474
|
info.selectors = selectors;
|
|
1352
|
-
let element = await this._locate(selectors, info, _params);
|
|
1475
|
+
let element = await this._locate(selectors, info, _params, timeout);
|
|
1353
1476
|
if (climb > 0) {
|
|
1354
1477
|
const climbArray = [];
|
|
1355
1478
|
for (let i = 0; i < climb; i++) {
|
|
@@ -1368,6 +1491,18 @@ class StableBrowser {
|
|
|
1368
1491
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1369
1492
|
try {
|
|
1370
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
|
+
// }
|
|
1371
1506
|
const elementText = await element.innerText();
|
|
1372
1507
|
return {
|
|
1373
1508
|
text: elementText,
|
|
@@ -1379,7 +1514,7 @@ class StableBrowser {
|
|
|
1379
1514
|
}
|
|
1380
1515
|
catch (e) {
|
|
1381
1516
|
//await this.closeUnexpectedPopups();
|
|
1382
|
-
this.logger.info("no innerText will use textContent");
|
|
1517
|
+
this.logger.info("no innerText, will use textContent");
|
|
1383
1518
|
const elementText = await element.textContent();
|
|
1384
1519
|
return { text: elementText, screenshotId, screenshotPath, value: value };
|
|
1385
1520
|
}
|
|
@@ -1404,6 +1539,7 @@ class StableBrowser {
|
|
|
1404
1539
|
highlight: false,
|
|
1405
1540
|
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1406
1541
|
text: `Verify element contains pattern: ${pattern}`,
|
|
1542
|
+
_text: "Verify element " + selectors.element_name + " contains pattern " + pattern,
|
|
1407
1543
|
operation: "containsPattern",
|
|
1408
1544
|
log: "***** verify element " + selectors.element_name + " contains pattern " + pattern + " *****\n",
|
|
1409
1545
|
};
|
|
@@ -1435,10 +1571,12 @@ class StableBrowser {
|
|
|
1435
1571
|
await _commandError(state, e, this);
|
|
1436
1572
|
}
|
|
1437
1573
|
finally {
|
|
1438
|
-
_commandFinally(state, this);
|
|
1574
|
+
await _commandFinally(state, this);
|
|
1439
1575
|
}
|
|
1440
1576
|
}
|
|
1441
1577
|
async containsText(selectors, text, climb, _params = null, options = {}, world = null) {
|
|
1578
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1579
|
+
const startTime = Date.now();
|
|
1442
1580
|
const state = {
|
|
1443
1581
|
selectors,
|
|
1444
1582
|
_params,
|
|
@@ -1465,62 +1603,54 @@ class StableBrowser {
|
|
|
1465
1603
|
}
|
|
1466
1604
|
let foundObj = null;
|
|
1467
1605
|
try {
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
const dateAlternatives = findDateAlternatives(text);
|
|
1475
|
-
const numberAlternatives = findNumberAlternatives(text);
|
|
1476
|
-
if (dateAlternatives.date) {
|
|
1477
|
-
for (let i = 0; i < dateAlternatives.dates.length; i++) {
|
|
1478
|
-
if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
|
|
1479
|
-
foundObj?.value?.includes(dateAlternatives.dates[i])) {
|
|
1480
|
-
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);
|
|
1481
1612
|
}
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
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)) {
|
|
1489
1633
|
return state.info;
|
|
1490
1634
|
}
|
|
1491
1635
|
}
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
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
|
|
1498
1641
|
}
|
|
1499
|
-
|
|
1642
|
+
state.info.foundText = foundObj?.text;
|
|
1643
|
+
state.info.value = foundObj?.value;
|
|
1644
|
+
throw new Error("element doesn't contain text " + text);
|
|
1500
1645
|
}
|
|
1501
1646
|
catch (e) {
|
|
1502
1647
|
await _commandError(state, e, this);
|
|
1648
|
+
throw e;
|
|
1503
1649
|
}
|
|
1504
1650
|
finally {
|
|
1505
|
-
_commandFinally(state, this);
|
|
1651
|
+
await _commandFinally(state, this);
|
|
1506
1652
|
}
|
|
1507
1653
|
}
|
|
1508
|
-
_getDataFile(world = null) {
|
|
1509
|
-
let dataFile = null;
|
|
1510
|
-
if (world && world.reportFolder) {
|
|
1511
|
-
dataFile = path.join(world.reportFolder, "data.json");
|
|
1512
|
-
}
|
|
1513
|
-
else if (this.reportFolder) {
|
|
1514
|
-
dataFile = path.join(this.reportFolder, "data.json");
|
|
1515
|
-
}
|
|
1516
|
-
else if (this.context && this.context.reportFolder) {
|
|
1517
|
-
dataFile = path.join(this.context.reportFolder, "data.json");
|
|
1518
|
-
}
|
|
1519
|
-
else {
|
|
1520
|
-
dataFile = "data.json";
|
|
1521
|
-
}
|
|
1522
|
-
return dataFile;
|
|
1523
|
-
}
|
|
1524
1654
|
async waitForUserInput(message, world = null) {
|
|
1525
1655
|
if (!message) {
|
|
1526
1656
|
message = "# Wait for user input. Press any key to continue";
|
|
@@ -1549,13 +1679,22 @@ class StableBrowser {
|
|
|
1549
1679
|
return;
|
|
1550
1680
|
}
|
|
1551
1681
|
// if data file exists, load it
|
|
1552
|
-
const dataFile =
|
|
1682
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1553
1683
|
let data = this.getTestData(world);
|
|
1554
1684
|
// merge the testData with the existing data
|
|
1555
1685
|
Object.assign(data, testData);
|
|
1556
1686
|
// save the data to the file
|
|
1557
1687
|
fs.writeFileSync(dataFile, JSON.stringify(data, null, 2));
|
|
1558
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
|
+
}
|
|
1559
1698
|
_getDataFilePath(fileName) {
|
|
1560
1699
|
let dataFile = path.join(this.project_path, "data", fileName);
|
|
1561
1700
|
if (fs.existsSync(dataFile)) {
|
|
@@ -1652,7 +1791,7 @@ class StableBrowser {
|
|
|
1652
1791
|
}
|
|
1653
1792
|
}
|
|
1654
1793
|
getTestData(world = null) {
|
|
1655
|
-
const dataFile =
|
|
1794
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1656
1795
|
let data = {};
|
|
1657
1796
|
if (fs.existsSync(dataFile)) {
|
|
1658
1797
|
data = JSON.parse(fs.readFileSync(dataFile, "utf8"));
|
|
@@ -1739,6 +1878,15 @@ class StableBrowser {
|
|
|
1739
1878
|
document.documentElement.clientWidth,
|
|
1740
1879
|
])));
|
|
1741
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
|
+
// }
|
|
1742
1890
|
if (this.context.browserName === "chromium") {
|
|
1743
1891
|
const client = await playContext.newCDPSession(this.page);
|
|
1744
1892
|
const { data } = await client.send("Page.captureScreenshot", {
|
|
@@ -1760,6 +1908,10 @@ class StableBrowser {
|
|
|
1760
1908
|
else {
|
|
1761
1909
|
screenshotBuffer = await this.page.screenshot();
|
|
1762
1910
|
}
|
|
1911
|
+
// if (focusedElement) {
|
|
1912
|
+
// // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
|
|
1913
|
+
// await this._unhighlightElements(focusedElement);
|
|
1914
|
+
// }
|
|
1763
1915
|
let image = await Jimp.read(screenshotBuffer);
|
|
1764
1916
|
// Get the image dimensions
|
|
1765
1917
|
const { width, height } = image.bitmap;
|
|
@@ -1772,6 +1924,7 @@ class StableBrowser {
|
|
|
1772
1924
|
else {
|
|
1773
1925
|
fs.writeFileSync(screenshotPath, screenshotBuffer);
|
|
1774
1926
|
}
|
|
1927
|
+
return screenshotBuffer;
|
|
1775
1928
|
}
|
|
1776
1929
|
async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
|
|
1777
1930
|
const state = {
|
|
@@ -1794,7 +1947,7 @@ class StableBrowser {
|
|
|
1794
1947
|
await _commandError(state, e, this);
|
|
1795
1948
|
}
|
|
1796
1949
|
finally {
|
|
1797
|
-
_commandFinally(state, this);
|
|
1950
|
+
await _commandFinally(state, this);
|
|
1798
1951
|
}
|
|
1799
1952
|
}
|
|
1800
1953
|
async extractAttribute(selectors, attribute, variable, _params = null, options = {}, world = null) {
|
|
@@ -1807,8 +1960,10 @@ class StableBrowser {
|
|
|
1807
1960
|
world,
|
|
1808
1961
|
type: Types.EXTRACT,
|
|
1809
1962
|
text: `Extract attribute from element`,
|
|
1963
|
+
_text: `Extract attribute ${attribute} from ${selectors.element_name}`,
|
|
1810
1964
|
operation: "extractAttribute",
|
|
1811
1965
|
log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1966
|
+
allowDisabled: true,
|
|
1812
1967
|
};
|
|
1813
1968
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1814
1969
|
try {
|
|
@@ -1823,6 +1978,9 @@ class StableBrowser {
|
|
|
1823
1978
|
case "value":
|
|
1824
1979
|
state.value = await state.element.inputValue();
|
|
1825
1980
|
break;
|
|
1981
|
+
case "text":
|
|
1982
|
+
state.value = await state.element.textContent();
|
|
1983
|
+
break;
|
|
1826
1984
|
default:
|
|
1827
1985
|
state.value = await state.element.getAttribute(attribute);
|
|
1828
1986
|
break;
|
|
@@ -1830,13 +1988,14 @@ class StableBrowser {
|
|
|
1830
1988
|
state.info.value = state.value;
|
|
1831
1989
|
this.setTestData({ [variable]: state.value }, world);
|
|
1832
1990
|
this.logger.info("set test data: " + variable + "=" + state.value);
|
|
1991
|
+
// await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1833
1992
|
return state.info;
|
|
1834
1993
|
}
|
|
1835
1994
|
catch (e) {
|
|
1836
1995
|
await _commandError(state, e, this);
|
|
1837
1996
|
}
|
|
1838
1997
|
finally {
|
|
1839
|
-
_commandFinally(state, this);
|
|
1998
|
+
await _commandFinally(state, this);
|
|
1840
1999
|
}
|
|
1841
2000
|
}
|
|
1842
2001
|
async verifyAttribute(selectors, attribute, value, _params = null, options = {}, world = null) {
|
|
@@ -1851,18 +2010,25 @@ class StableBrowser {
|
|
|
1851
2010
|
highlight: true,
|
|
1852
2011
|
screenshot: true,
|
|
1853
2012
|
text: `Verify element attribute`,
|
|
2013
|
+
_text: `Verify attribute ${attribute} from ${selectors.element_name} is ${value}`,
|
|
1854
2014
|
operation: "verifyAttribute",
|
|
1855
2015
|
log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1856
2016
|
allowDisabled: true,
|
|
1857
2017
|
};
|
|
1858
2018
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1859
2019
|
let val;
|
|
2020
|
+
let expectedValue;
|
|
1860
2021
|
try {
|
|
1861
2022
|
await _preCommand(state, this);
|
|
2023
|
+
expectedValue = await replaceWithLocalTestData(state.value, world);
|
|
2024
|
+
state.info.expectedValue = expectedValue;
|
|
1862
2025
|
switch (attribute) {
|
|
1863
2026
|
case "innerText":
|
|
1864
2027
|
val = String(await state.element.innerText());
|
|
1865
2028
|
break;
|
|
2029
|
+
case "text":
|
|
2030
|
+
val = String(await state.element.textContent());
|
|
2031
|
+
break;
|
|
1866
2032
|
case "value":
|
|
1867
2033
|
val = String(await state.element.inputValue());
|
|
1868
2034
|
break;
|
|
@@ -1880,26 +2046,29 @@ class StableBrowser {
|
|
|
1880
2046
|
val = String(await state.element.getAttribute(attribute));
|
|
1881
2047
|
break;
|
|
1882
2048
|
}
|
|
2049
|
+
state.info.value = val;
|
|
1883
2050
|
let regex;
|
|
1884
|
-
if (
|
|
1885
|
-
const patternBody =
|
|
2051
|
+
if (expectedValue.startsWith("/") && expectedValue.endsWith("/")) {
|
|
2052
|
+
const patternBody = expectedValue.slice(1, -1);
|
|
1886
2053
|
regex = new RegExp(patternBody, "g");
|
|
1887
2054
|
}
|
|
1888
2055
|
else {
|
|
1889
|
-
const escapedPattern =
|
|
2056
|
+
const escapedPattern = expectedValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1890
2057
|
regex = new RegExp(escapedPattern, "g");
|
|
1891
2058
|
}
|
|
1892
2059
|
if (!val.match(regex)) {
|
|
1893
|
-
|
|
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);
|
|
1894
2064
|
}
|
|
1895
|
-
state.info.expectedValue = val;
|
|
1896
2065
|
return state.info;
|
|
1897
2066
|
}
|
|
1898
2067
|
catch (e) {
|
|
1899
2068
|
await _commandError(state, e, this);
|
|
1900
2069
|
}
|
|
1901
2070
|
finally {
|
|
1902
|
-
_commandFinally(state, this);
|
|
2071
|
+
await _commandFinally(state, this);
|
|
1903
2072
|
}
|
|
1904
2073
|
}
|
|
1905
2074
|
async extractEmailData(emailAddress, options, world) {
|
|
@@ -1991,27 +2160,32 @@ class StableBrowser {
|
|
|
1991
2160
|
async _highlightElements(scope, css) {
|
|
1992
2161
|
try {
|
|
1993
2162
|
if (!scope) {
|
|
2163
|
+
// console.log(`Scope is not defined`);
|
|
1994
2164
|
return;
|
|
1995
2165
|
}
|
|
1996
2166
|
if (!css) {
|
|
1997
2167
|
scope
|
|
1998
2168
|
.evaluate((node) => {
|
|
1999
2169
|
if (node && node.style) {
|
|
2000
|
-
let
|
|
2170
|
+
let originalOutline = node.style.outline;
|
|
2171
|
+
// console.log(`Original outline was: ${originalOutline}`);
|
|
2172
|
+
// node.__previousOutline = originalOutline;
|
|
2001
2173
|
node.style.outline = "2px solid red";
|
|
2174
|
+
// console.log(`New outline is: ${node.style.outline}`);
|
|
2002
2175
|
if (window) {
|
|
2003
2176
|
window.addEventListener("beforeunload", function (e) {
|
|
2004
|
-
node.style.outline =
|
|
2177
|
+
node.style.outline = originalOutline;
|
|
2005
2178
|
});
|
|
2006
2179
|
}
|
|
2007
2180
|
setTimeout(function () {
|
|
2008
|
-
node.style.outline =
|
|
2181
|
+
node.style.outline = originalOutline;
|
|
2009
2182
|
}, 2000);
|
|
2010
2183
|
}
|
|
2011
2184
|
})
|
|
2012
2185
|
.then(() => { })
|
|
2013
2186
|
.catch((e) => {
|
|
2014
2187
|
// ignore
|
|
2188
|
+
// console.error(`Could not highlight node : ${e}`);
|
|
2015
2189
|
});
|
|
2016
2190
|
}
|
|
2017
2191
|
else {
|
|
@@ -2027,17 +2201,18 @@ class StableBrowser {
|
|
|
2027
2201
|
if (!element.style) {
|
|
2028
2202
|
return;
|
|
2029
2203
|
}
|
|
2030
|
-
|
|
2204
|
+
let originalOutline = element.style.outline;
|
|
2205
|
+
element.__previousOutline = originalOutline;
|
|
2031
2206
|
// Set the new border to be red and 2px solid
|
|
2032
2207
|
element.style.outline = "2px solid red";
|
|
2033
2208
|
if (window) {
|
|
2034
2209
|
window.addEventListener("beforeunload", function (e) {
|
|
2035
|
-
element.style.outline =
|
|
2210
|
+
element.style.outline = originalOutline;
|
|
2036
2211
|
});
|
|
2037
2212
|
}
|
|
2038
2213
|
// Set a timeout to revert to the original border after 2 seconds
|
|
2039
2214
|
setTimeout(function () {
|
|
2040
|
-
element.style.outline =
|
|
2215
|
+
element.style.outline = originalOutline;
|
|
2041
2216
|
}, 2000);
|
|
2042
2217
|
}
|
|
2043
2218
|
return;
|
|
@@ -2045,6 +2220,7 @@ class StableBrowser {
|
|
|
2045
2220
|
.then(() => { })
|
|
2046
2221
|
.catch((e) => {
|
|
2047
2222
|
// ignore
|
|
2223
|
+
// console.error(`Could not highlight css: ${e}`);
|
|
2048
2224
|
});
|
|
2049
2225
|
}
|
|
2050
2226
|
}
|
|
@@ -2052,6 +2228,54 @@ class StableBrowser {
|
|
|
2052
2228
|
console.debug(error);
|
|
2053
2229
|
}
|
|
2054
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
|
+
// }
|
|
2055
2279
|
async verifyPagePath(pathPart, options = {}, world = null) {
|
|
2056
2280
|
const startTime = Date.now();
|
|
2057
2281
|
let error = null;
|
|
@@ -2096,6 +2320,7 @@ class StableBrowser {
|
|
|
2096
2320
|
_reportToWorld(world, {
|
|
2097
2321
|
type: Types.VERIFY_PAGE_PATH,
|
|
2098
2322
|
text: "Verify page path",
|
|
2323
|
+
_text: "Verify the page path contains " + pathPart,
|
|
2099
2324
|
screenshotId,
|
|
2100
2325
|
result: error
|
|
2101
2326
|
? {
|
|
@@ -2113,27 +2338,89 @@ class StableBrowser {
|
|
|
2113
2338
|
});
|
|
2114
2339
|
}
|
|
2115
2340
|
}
|
|
2116
|
-
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) {
|
|
2117
2404
|
const frames = this.page.frames();
|
|
2118
2405
|
let results = [];
|
|
2119
|
-
let ignoreCase = false;
|
|
2406
|
+
// let ignoreCase = false;
|
|
2120
2407
|
for (let i = 0; i < frames.length; i++) {
|
|
2121
2408
|
if (dateAlternatives.date) {
|
|
2122
2409
|
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2123
|
-
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, {});
|
|
2124
2411
|
result.frame = frames[i];
|
|
2125
2412
|
results.push(result);
|
|
2126
2413
|
}
|
|
2127
2414
|
}
|
|
2128
2415
|
else if (numberAlternatives.number) {
|
|
2129
2416
|
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2130
|
-
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, {});
|
|
2131
2418
|
result.frame = frames[i];
|
|
2132
2419
|
results.push(result);
|
|
2133
2420
|
}
|
|
2134
2421
|
}
|
|
2135
2422
|
else {
|
|
2136
|
-
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, {});
|
|
2137
2424
|
result.frame = frames[i];
|
|
2138
2425
|
results.push(result);
|
|
2139
2426
|
}
|
|
@@ -2152,11 +2439,15 @@ class StableBrowser {
|
|
|
2152
2439
|
scroll: false,
|
|
2153
2440
|
highlight: false,
|
|
2154
2441
|
type: Types.VERIFY_PAGE_CONTAINS_TEXT,
|
|
2155
|
-
text: `Verify text exists in page`,
|
|
2442
|
+
text: `Verify the text '${text}' exists in page`,
|
|
2443
|
+
_text: `Verify the text '${text}' exists in page`,
|
|
2156
2444
|
operation: "verifyTextExistInPage",
|
|
2157
2445
|
log: "***** verify text " + text + " exists in page *****\n",
|
|
2158
2446
|
};
|
|
2159
|
-
|
|
2447
|
+
if (testForRegex(text)) {
|
|
2448
|
+
text = text.replace(/\\"/g, '"');
|
|
2449
|
+
}
|
|
2450
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2160
2451
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2161
2452
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2162
2453
|
if (newValue !== text) {
|
|
@@ -2169,7 +2460,15 @@ class StableBrowser {
|
|
|
2169
2460
|
await _preCommand(state, this);
|
|
2170
2461
|
state.info.text = text;
|
|
2171
2462
|
while (true) {
|
|
2172
|
-
|
|
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
|
+
}
|
|
2173
2472
|
if (resultWithElementsFound.length === 0) {
|
|
2174
2473
|
if (Date.now() - state.startTime > timeout) {
|
|
2175
2474
|
throw new Error(`Text ${text} not found in page`);
|
|
@@ -2177,18 +2476,40 @@ class StableBrowser {
|
|
|
2177
2476
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2178
2477
|
continue;
|
|
2179
2478
|
}
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
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
|
+
}
|
|
2188
2506
|
}
|
|
2507
|
+
await _screenshot(state, this);
|
|
2508
|
+
return state.info;
|
|
2509
|
+
}
|
|
2510
|
+
catch (error) {
|
|
2511
|
+
console.error(error);
|
|
2189
2512
|
}
|
|
2190
|
-
await _screenshot(state, this);
|
|
2191
|
-
return state.info;
|
|
2192
2513
|
}
|
|
2193
2514
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2194
2515
|
}
|
|
@@ -2196,7 +2517,7 @@ class StableBrowser {
|
|
|
2196
2517
|
await _commandError(state, e, this);
|
|
2197
2518
|
}
|
|
2198
2519
|
finally {
|
|
2199
|
-
_commandFinally(state, this);
|
|
2520
|
+
await _commandFinally(state, this);
|
|
2200
2521
|
}
|
|
2201
2522
|
}
|
|
2202
2523
|
async waitForTextToDisappear(text, options = {}, world = null) {
|
|
@@ -2209,11 +2530,15 @@ class StableBrowser {
|
|
|
2209
2530
|
scroll: false,
|
|
2210
2531
|
highlight: false,
|
|
2211
2532
|
type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
|
|
2212
|
-
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`,
|
|
2213
2535
|
operation: "verifyTextNotExistInPage",
|
|
2214
2536
|
log: "***** verify text " + text + " does not exist in page *****\n",
|
|
2215
2537
|
};
|
|
2216
|
-
|
|
2538
|
+
if (testForRegex(text)) {
|
|
2539
|
+
text = text.replace(/\\"/g, '"');
|
|
2540
|
+
}
|
|
2541
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2217
2542
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2218
2543
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2219
2544
|
if (newValue !== text) {
|
|
@@ -2225,8 +2550,16 @@ class StableBrowser {
|
|
|
2225
2550
|
try {
|
|
2226
2551
|
await _preCommand(state, this);
|
|
2227
2552
|
state.info.text = text;
|
|
2553
|
+
let resultWithElementsFound = {
|
|
2554
|
+
length: null, // initial cannot be 0
|
|
2555
|
+
};
|
|
2228
2556
|
while (true) {
|
|
2229
|
-
|
|
2557
|
+
try {
|
|
2558
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2559
|
+
}
|
|
2560
|
+
catch (error) {
|
|
2561
|
+
// ignore
|
|
2562
|
+
}
|
|
2230
2563
|
if (resultWithElementsFound.length === 0) {
|
|
2231
2564
|
await _screenshot(state, this);
|
|
2232
2565
|
return state.info;
|
|
@@ -2241,7 +2574,7 @@ class StableBrowser {
|
|
|
2241
2574
|
await _commandError(state, e, this);
|
|
2242
2575
|
}
|
|
2243
2576
|
finally {
|
|
2244
|
-
_commandFinally(state, this);
|
|
2577
|
+
await _commandFinally(state, this);
|
|
2245
2578
|
}
|
|
2246
2579
|
}
|
|
2247
2580
|
async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
|
|
@@ -2256,10 +2589,11 @@ class StableBrowser {
|
|
|
2256
2589
|
highlight: false,
|
|
2257
2590
|
type: Types.VERIFY_TEXT_WITH_RELATION,
|
|
2258
2591
|
text: `Verify text with relation to another text`,
|
|
2592
|
+
_text: "Search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found",
|
|
2259
2593
|
operation: "verify_text_with_relation",
|
|
2260
2594
|
log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
|
|
2261
2595
|
};
|
|
2262
|
-
const timeout = this.
|
|
2596
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2263
2597
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2264
2598
|
let newValue = await this._replaceWithLocalData(textAnchor, world);
|
|
2265
2599
|
if (newValue !== textAnchor) {
|
|
@@ -2277,8 +2611,16 @@ class StableBrowser {
|
|
|
2277
2611
|
try {
|
|
2278
2612
|
await _preCommand(state, this);
|
|
2279
2613
|
state.info.text = textToVerify;
|
|
2614
|
+
let resultWithElementsFound = {
|
|
2615
|
+
length: 0,
|
|
2616
|
+
};
|
|
2280
2617
|
while (true) {
|
|
2281
|
-
|
|
2618
|
+
try {
|
|
2619
|
+
resultWithElementsFound = await this.findTextInAllFrames(findDateAlternatives(textAnchor), findNumberAlternatives(textAnchor), textAnchor, state, false);
|
|
2620
|
+
}
|
|
2621
|
+
catch (error) {
|
|
2622
|
+
// ignore
|
|
2623
|
+
}
|
|
2282
2624
|
if (resultWithElementsFound.length === 0) {
|
|
2283
2625
|
if (Date.now() - state.startTime > timeout) {
|
|
2284
2626
|
throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
|
|
@@ -2286,37 +2628,56 @@ class StableBrowser {
|
|
|
2286
2628
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2287
2629
|
continue;
|
|
2288
2630
|
}
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
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;
|
|
2314
2674
|
}
|
|
2315
|
-
await _screenshot(state, this);
|
|
2316
|
-
return state.info;
|
|
2317
2675
|
}
|
|
2318
2676
|
}
|
|
2319
2677
|
}
|
|
2678
|
+
catch (error) {
|
|
2679
|
+
console.error(error);
|
|
2680
|
+
}
|
|
2320
2681
|
}
|
|
2321
2682
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2322
2683
|
}
|
|
@@ -2324,8 +2685,32 @@ class StableBrowser {
|
|
|
2324
2685
|
await _commandError(state, e, this);
|
|
2325
2686
|
}
|
|
2326
2687
|
finally {
|
|
2327
|
-
_commandFinally(state, this);
|
|
2688
|
+
await _commandFinally(state, this);
|
|
2689
|
+
}
|
|
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
|
+
}
|
|
2328
2710
|
}
|
|
2711
|
+
// state.info.results = results;
|
|
2712
|
+
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2713
|
+
return resultWithElementsFound;
|
|
2329
2714
|
}
|
|
2330
2715
|
async visualVerification(text, options = {}, world = null) {
|
|
2331
2716
|
const startTime = Date.now();
|
|
@@ -2345,10 +2730,13 @@ class StableBrowser {
|
|
|
2345
2730
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2346
2731
|
info.screenshotPath = screenshotPath;
|
|
2347
2732
|
const screenshot = await this.takeScreenshot();
|
|
2348
|
-
|
|
2349
|
-
method: "
|
|
2733
|
+
let request = {
|
|
2734
|
+
method: "post",
|
|
2735
|
+
maxBodyLength: Infinity,
|
|
2350
2736
|
url: `${serviceUrl}/api/runs/screenshots/validate-screenshot`,
|
|
2351
2737
|
headers: {
|
|
2738
|
+
"x-bvt-project-id": path.basename(this.project_path),
|
|
2739
|
+
"x-source": "aaa",
|
|
2352
2740
|
"Content-Type": "application/json",
|
|
2353
2741
|
Authorization: `Bearer ${process.env.TOKEN}`,
|
|
2354
2742
|
},
|
|
@@ -2357,7 +2745,7 @@ class StableBrowser {
|
|
|
2357
2745
|
screenshot: screenshot,
|
|
2358
2746
|
}),
|
|
2359
2747
|
};
|
|
2360
|
-
|
|
2748
|
+
const result = await axios.request(request);
|
|
2361
2749
|
if (result.data.status !== true) {
|
|
2362
2750
|
throw new Error("Visual validation failed");
|
|
2363
2751
|
}
|
|
@@ -2385,6 +2773,7 @@ class StableBrowser {
|
|
|
2385
2773
|
_reportToWorld(world, {
|
|
2386
2774
|
type: Types.VERIFY_VISUAL,
|
|
2387
2775
|
text: "Visual verification",
|
|
2776
|
+
_text: "Visual verification of " + text,
|
|
2388
2777
|
screenshotId,
|
|
2389
2778
|
result: error
|
|
2390
2779
|
? {
|
|
@@ -2651,6 +3040,32 @@ class StableBrowser {
|
|
|
2651
3040
|
}
|
|
2652
3041
|
return timeout;
|
|
2653
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
|
+
}
|
|
2654
3069
|
async waitForPageLoad(options = {}, world = null) {
|
|
2655
3070
|
let timeout = this._getLoadTimeout(options);
|
|
2656
3071
|
const promiseArray = [];
|
|
@@ -2718,6 +3133,7 @@ class StableBrowser {
|
|
|
2718
3133
|
highlight: false,
|
|
2719
3134
|
type: Types.CLOSE_PAGE,
|
|
2720
3135
|
text: `Close page`,
|
|
3136
|
+
_text: `Close the page`,
|
|
2721
3137
|
operation: "closePage",
|
|
2722
3138
|
log: "***** close page *****\n",
|
|
2723
3139
|
throwError: false,
|
|
@@ -2731,11 +3147,98 @@ class StableBrowser {
|
|
|
2731
3147
|
await _commandError(state, e, this);
|
|
2732
3148
|
}
|
|
2733
3149
|
finally {
|
|
2734
|
-
_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);
|
|
2735
3238
|
}
|
|
2736
3239
|
}
|
|
2737
3240
|
saveTestDataAsGlobal(options, world) {
|
|
2738
|
-
const dataFile =
|
|
3241
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
2739
3242
|
process.env.GLOBAL_TEST_DATA_FILE = dataFile;
|
|
2740
3243
|
this.logger.info("Save the scenario test data as global for the following scenarios.");
|
|
2741
3244
|
}
|
|
@@ -2765,6 +3268,7 @@ class StableBrowser {
|
|
|
2765
3268
|
_reportToWorld(world, {
|
|
2766
3269
|
type: Types.SET_VIEWPORT,
|
|
2767
3270
|
text: "set viewport size to " + width + "x" + hight,
|
|
3271
|
+
_text: "Set the viewport size to " + width + "x" + hight,
|
|
2768
3272
|
screenshotId,
|
|
2769
3273
|
result: error
|
|
2770
3274
|
? {
|
|
@@ -2835,7 +3339,36 @@ class StableBrowser {
|
|
|
2835
3339
|
console.log("#-#");
|
|
2836
3340
|
}
|
|
2837
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) { }
|
|
2838
3368
|
async beforeStep(world, step) {
|
|
3369
|
+
if (!this.beforeScenarioCalled) {
|
|
3370
|
+
this.beforeScenario(world, step);
|
|
3371
|
+
}
|
|
2839
3372
|
if (this.stepIndex === undefined) {
|
|
2840
3373
|
this.stepIndex = 0;
|
|
2841
3374
|
}
|
|
@@ -2857,14 +3390,42 @@ class StableBrowser {
|
|
|
2857
3390
|
await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
|
|
2858
3391
|
}
|
|
2859
3392
|
}
|
|
2860
|
-
if (this.
|
|
2861
|
-
this.
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
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
|
+
}
|
|
2865
3400
|
}
|
|
2866
3401
|
}
|
|
2867
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
|
+
}
|
|
2868
3429
|
async afterStep(world, step) {
|
|
2869
3430
|
this.stepName = null;
|
|
2870
3431
|
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
@@ -2872,6 +3433,23 @@ class StableBrowser {
|
|
|
2872
3433
|
await this.context.browserObject.context.tracing.stopChunk({
|
|
2873
3434
|
path: path.join(this.context.browserObject.traceFolder, `trace-${this.stepIndex}.zip`),
|
|
2874
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`);
|
|
3443
|
+
}
|
|
3444
|
+
}
|
|
3445
|
+
if (this.context) {
|
|
3446
|
+
this.context.examplesRow = null;
|
|
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");
|
|
2875
3453
|
}
|
|
2876
3454
|
}
|
|
2877
3455
|
}
|