automation_model 1.0.587-dev → 1.0.587-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 +108 -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 +282 -0
- package/lib/bruno.js.map +1 -0
- package/lib/command_common.d.ts +4 -4
- package/lib/command_common.js +57 -23
- 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 +45 -19
- package/lib/network.js.map +1 -1
- package/lib/stable_browser.d.ts +20 -4
- package/lib/stable_browser.js +757 -194
- 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 +302 -21
- package/lib/utils.js.map +1 -1
- package/package.json +8 -8
package/lib/stable_browser.js
CHANGED
|
@@ -10,18 +10,21 @@ 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
18
|
import { locate_element } from "./locate_element.js";
|
|
19
19
|
import { randomUUID } from "crypto";
|
|
20
20
|
import { _commandError, _commandFinally, _preCommand, _validateSelectors, _screenshot, _reportToWorld, } from "./command_common.js";
|
|
21
21
|
import { registerDownloadEvent, registerNetworkEvents } from "./network.js";
|
|
22
22
|
import { LocatorLog } from "./locator_log.js";
|
|
23
|
+
import axios from "axios";
|
|
24
|
+
import { _findCellArea, findElementsInArea } from "./table_helper.js";
|
|
23
25
|
export const Types = {
|
|
24
26
|
CLICK: "click_element",
|
|
27
|
+
WAIT_ELEMENT: "wait_element",
|
|
25
28
|
NAVIGATE: "navigate",
|
|
26
29
|
FILL: "fill_element",
|
|
27
30
|
EXECUTE: "execute_page_method",
|
|
@@ -43,6 +46,7 @@ export const Types = {
|
|
|
43
46
|
UNCHECK: "uncheck_element",
|
|
44
47
|
EXTRACT: "extract_attribute",
|
|
45
48
|
CLOSE_PAGE: "close_page",
|
|
49
|
+
TABLE_OPERATION: "table_operation",
|
|
46
50
|
SET_DATE_TIME: "set_date_time",
|
|
47
51
|
SET_VIEWPORT: "set_viewport",
|
|
48
52
|
VERIFY_VISUAL: "verify_visual",
|
|
@@ -51,8 +55,12 @@ export const Types = {
|
|
|
51
55
|
WAIT_FOR_TEXT_TO_DISAPPEAR: "wait_for_text_to_disappear",
|
|
52
56
|
VERIFY_ATTRIBUTE: "verify_element_attribute",
|
|
53
57
|
VERIFY_TEXT_WITH_RELATION: "verify_text_with_relation",
|
|
58
|
+
BRUNO: "bruno",
|
|
54
59
|
};
|
|
55
60
|
export const apps = {};
|
|
61
|
+
const formatElementName = (elementName) => {
|
|
62
|
+
return elementName ? JSON.stringify(elementName) : "element";
|
|
63
|
+
};
|
|
56
64
|
class StableBrowser {
|
|
57
65
|
browser;
|
|
58
66
|
page;
|
|
@@ -66,6 +74,7 @@ class StableBrowser {
|
|
|
66
74
|
appName = "main";
|
|
67
75
|
tags = null;
|
|
68
76
|
isRecording = false;
|
|
77
|
+
initSnapshotTaken = false;
|
|
69
78
|
constructor(browser, page, logger = null, context = null, world = null) {
|
|
70
79
|
this.browser = browser;
|
|
71
80
|
this.page = page;
|
|
@@ -172,6 +181,30 @@ class StableBrowser {
|
|
|
172
181
|
await this.waitForPageLoad();
|
|
173
182
|
}
|
|
174
183
|
}
|
|
184
|
+
async switchTab(tabTitleOrIndex) {
|
|
185
|
+
// first check if the tabNameOrIndex is a number
|
|
186
|
+
let index = parseInt(tabTitleOrIndex);
|
|
187
|
+
if (!isNaN(index)) {
|
|
188
|
+
if (index >= 0 && index < this.context.pages.length) {
|
|
189
|
+
this.page = this.context.pages[index];
|
|
190
|
+
this.context.page = this.page;
|
|
191
|
+
await this.page.bringToFront();
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
// if the tabNameOrIndex is a string, find the tab by name
|
|
196
|
+
for (let i = 0; i < this.context.pages.length; i++) {
|
|
197
|
+
let page = this.context.pages[i];
|
|
198
|
+
let title = await page.title();
|
|
199
|
+
if (title.includes(tabTitleOrIndex)) {
|
|
200
|
+
this.page = page;
|
|
201
|
+
this.context.page = this.page;
|
|
202
|
+
await this.page.bringToFront();
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
throw new Error("Tab not found: " + tabTitleOrIndex);
|
|
207
|
+
}
|
|
175
208
|
registerConsoleLogListener(page, context) {
|
|
176
209
|
if (!this.context.webLogger) {
|
|
177
210
|
this.context.webLogger = [];
|
|
@@ -236,6 +269,9 @@ class StableBrowser {
|
|
|
236
269
|
// await closeUnexpectedPopups(this.page);
|
|
237
270
|
// }
|
|
238
271
|
async goto(url, world = null) {
|
|
272
|
+
if (!url) {
|
|
273
|
+
throw new Error("url is null, verify that the environment file is correct");
|
|
274
|
+
}
|
|
239
275
|
if (!url.startsWith("http")) {
|
|
240
276
|
url = "https://" + url;
|
|
241
277
|
}
|
|
@@ -264,7 +300,7 @@ class StableBrowser {
|
|
|
264
300
|
_commandError(state, error, this);
|
|
265
301
|
}
|
|
266
302
|
finally {
|
|
267
|
-
_commandFinally(state, this);
|
|
303
|
+
await _commandFinally(state, this);
|
|
268
304
|
}
|
|
269
305
|
}
|
|
270
306
|
async _getLocator(locator, scope, _params) {
|
|
@@ -345,7 +381,7 @@ class StableBrowser {
|
|
|
345
381
|
return resultCss;
|
|
346
382
|
}
|
|
347
383
|
async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, ignoreCase = true, _params) {
|
|
348
|
-
const query = _convertToRegexQuery(text1, regex1, !partial1, ignoreCase)
|
|
384
|
+
const query = `${_convertToRegexQuery(text1, regex1, !partial1, ignoreCase)}`;
|
|
349
385
|
const locator = scope.locator(query);
|
|
350
386
|
const count = await locator.count();
|
|
351
387
|
if (!tag1) {
|
|
@@ -365,6 +401,12 @@ class StableBrowser {
|
|
|
365
401
|
if (!el.setAttribute) {
|
|
366
402
|
el = el.parentElement;
|
|
367
403
|
}
|
|
404
|
+
// remove any attributes start with data-blinq-id
|
|
405
|
+
// for (let i = 0; i < el.attributes.length; i++) {
|
|
406
|
+
// if (el.attributes[i].name.startsWith("data-blinq-id")) {
|
|
407
|
+
// el.removeAttribute(el.attributes[i].name);
|
|
408
|
+
// }
|
|
409
|
+
// }
|
|
368
410
|
el.setAttribute("data-blinq-id-" + randomToken, "");
|
|
369
411
|
return true;
|
|
370
412
|
}, [tag1, randomToken]))) {
|
|
@@ -374,7 +416,7 @@ class StableBrowser {
|
|
|
374
416
|
}
|
|
375
417
|
return { elementCount: tagCount, randomToken };
|
|
376
418
|
}
|
|
377
|
-
async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true, allowDisabled = false) {
|
|
419
|
+
async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true, allowDisabled = false, element_name = null) {
|
|
378
420
|
if (!info) {
|
|
379
421
|
info = {};
|
|
380
422
|
}
|
|
@@ -397,10 +439,11 @@ class StableBrowser {
|
|
|
397
439
|
//info.log += "searching for locator " + JSON.stringify(locatorSearch) + "\n";
|
|
398
440
|
let locator = null;
|
|
399
441
|
if (locatorSearch.climb && locatorSearch.climb >= 0) {
|
|
400
|
-
|
|
442
|
+
const replacedText = await this._replaceWithLocalData(locatorSearch.text, this.world);
|
|
443
|
+
let locatorString = await this._locateElmentByTextClimbCss(scope, replacedText, locatorSearch.climb, locatorSearch.css, _params);
|
|
401
444
|
if (!locatorString) {
|
|
402
445
|
info.failCause.textNotFound = true;
|
|
403
|
-
info.failCause.lastError =
|
|
446
|
+
info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${locatorSearch.text}`;
|
|
404
447
|
return;
|
|
405
448
|
}
|
|
406
449
|
locator = await this._getLocator({ css: locatorString }, scope, _params);
|
|
@@ -410,7 +453,7 @@ class StableBrowser {
|
|
|
410
453
|
let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, true, _params);
|
|
411
454
|
if (result.elementCount === 0) {
|
|
412
455
|
info.failCause.textNotFound = true;
|
|
413
|
-
info.failCause.lastError =
|
|
456
|
+
info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${text}`;
|
|
414
457
|
return;
|
|
415
458
|
}
|
|
416
459
|
locatorSearch.css = "[data-blinq-id-" + result.randomToken + "]";
|
|
@@ -462,9 +505,11 @@ class StableBrowser {
|
|
|
462
505
|
info.printMessages = {};
|
|
463
506
|
}
|
|
464
507
|
if (info.locatorLog && !visible) {
|
|
508
|
+
info.failCause.lastError = `${formatElementName(element_name)} is not visible, searching for ${originalLocatorSearch}`;
|
|
465
509
|
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_VISIBLE");
|
|
466
510
|
}
|
|
467
511
|
if (info.locatorLog && !enabled) {
|
|
512
|
+
info.failCause.lastError = `${formatElementName(element_name)} is disabled, searching for ${originalLocatorSearch}`;
|
|
468
513
|
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_ENABLED");
|
|
469
514
|
}
|
|
470
515
|
if (!info.printMessages[j.toString()]) {
|
|
@@ -544,7 +589,28 @@ class StableBrowser {
|
|
|
544
589
|
}
|
|
545
590
|
let element = await this._locate_internal(selectors, info, _params, timeout, allowDisabled);
|
|
546
591
|
if (!element.rerun) {
|
|
547
|
-
|
|
592
|
+
const randomToken = Math.random().toString(36).substring(7);
|
|
593
|
+
element.evaluate((el, randomToken) => {
|
|
594
|
+
el.setAttribute("data-blinq-id-" + randomToken, "");
|
|
595
|
+
}, randomToken);
|
|
596
|
+
// if (element._frame) {
|
|
597
|
+
// return element;
|
|
598
|
+
// }
|
|
599
|
+
const scope = element._frame ?? element.page();
|
|
600
|
+
let newElementSelector = "[data-blinq-id-" + randomToken + "]";
|
|
601
|
+
let prefixSelector = "";
|
|
602
|
+
const frameControlSelector = " >> internal:control=enter-frame";
|
|
603
|
+
const frameSelectorIndex = element._selector.lastIndexOf(frameControlSelector);
|
|
604
|
+
if (frameSelectorIndex !== -1) {
|
|
605
|
+
// remove everything after the >> internal:control=enter-frame
|
|
606
|
+
const frameSelector = element._selector.substring(0, frameSelectorIndex);
|
|
607
|
+
prefixSelector = frameSelector + " >> internal:control=enter-frame >>";
|
|
608
|
+
}
|
|
609
|
+
// if (element?._frame?._selector) {
|
|
610
|
+
// prefixSelector = element._frame._selector + " >> " + prefixSelector;
|
|
611
|
+
// }
|
|
612
|
+
const newSelector = prefixSelector + newElementSelector;
|
|
613
|
+
return scope.locator(newSelector);
|
|
548
614
|
}
|
|
549
615
|
}
|
|
550
616
|
throw new Error("unable to locate element " + JSON.stringify(selectors));
|
|
@@ -617,7 +683,7 @@ class StableBrowser {
|
|
|
617
683
|
//info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
|
|
618
684
|
if (Date.now() - startTime > timeout) {
|
|
619
685
|
info.failCause.iframeNotFound = true;
|
|
620
|
-
info.failCause.lastError =
|
|
686
|
+
info.failCause.lastError = `unable to locate iframe "${selectors.iframe_src}"`;
|
|
621
687
|
throw new Error("unable to locate iframe " + selectors.iframe_src);
|
|
622
688
|
}
|
|
623
689
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -691,18 +757,13 @@ class StableBrowser {
|
|
|
691
757
|
}
|
|
692
758
|
// info.log += "scanning locators in priority 1" + "\n";
|
|
693
759
|
let onlyPriority3 = selectorsLocators[0].priority === 3;
|
|
694
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly, allowDisabled);
|
|
760
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
695
761
|
if (result.foundElements.length === 0) {
|
|
696
762
|
// info.log += "scanning locators in priority 2" + "\n";
|
|
697
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly, allowDisabled);
|
|
698
|
-
}
|
|
699
|
-
if (result.foundElements.length === 0 && onlyPriority3) {
|
|
700
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled);
|
|
763
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
701
764
|
}
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled);
|
|
705
|
-
}
|
|
765
|
+
if (result.foundElements.length === 0 && (onlyPriority3 || !highPriorityOnly)) {
|
|
766
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
706
767
|
}
|
|
707
768
|
let foundElements = result.foundElements;
|
|
708
769
|
if (foundElements.length === 1 && foundElements[0].unique) {
|
|
@@ -758,6 +819,11 @@ class StableBrowser {
|
|
|
758
819
|
visibleOnly = false;
|
|
759
820
|
}
|
|
760
821
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
822
|
+
// sheck of more of half of the timeout has passed
|
|
823
|
+
if (Date.now() - startTime > timeout / 2) {
|
|
824
|
+
highPriorityOnly = false;
|
|
825
|
+
visibleOnly = false;
|
|
826
|
+
}
|
|
761
827
|
}
|
|
762
828
|
this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
|
|
763
829
|
// if (info.locatorLog) {
|
|
@@ -768,10 +834,12 @@ class StableBrowser {
|
|
|
768
834
|
// }
|
|
769
835
|
//info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
|
|
770
836
|
info.failCause.locatorNotFound = true;
|
|
771
|
-
info
|
|
837
|
+
if (!info?.failCause?.lastError) {
|
|
838
|
+
info.failCause.lastError = `failed to locate ${formatElementName(selectors.element_name)}, ${locatorsCount > 0 ? `${locatorsCount} matching elements found` : "no matching elements found"}`;
|
|
839
|
+
}
|
|
772
840
|
throw new Error("failed to locate first element no elements found, " + info.log);
|
|
773
841
|
}
|
|
774
|
-
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false) {
|
|
842
|
+
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false, element_name) {
|
|
775
843
|
let foundElements = [];
|
|
776
844
|
const result = {
|
|
777
845
|
foundElements: foundElements,
|
|
@@ -779,7 +847,7 @@ class StableBrowser {
|
|
|
779
847
|
for (let i = 0; i < locatorsGroup.length; i++) {
|
|
780
848
|
let foundLocators = [];
|
|
781
849
|
try {
|
|
782
|
-
await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly, allowDisabled);
|
|
850
|
+
await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
|
|
783
851
|
}
|
|
784
852
|
catch (e) {
|
|
785
853
|
// this call can fail it the browser is navigating
|
|
@@ -787,7 +855,7 @@ class StableBrowser {
|
|
|
787
855
|
// this.logger.debug(e);
|
|
788
856
|
foundLocators = [];
|
|
789
857
|
try {
|
|
790
|
-
await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly, allowDisabled);
|
|
858
|
+
await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
|
|
791
859
|
}
|
|
792
860
|
catch (e) {
|
|
793
861
|
this.logger.info("unable to use locator (second try) " + JSON.stringify(locatorsGroup[i]));
|
|
@@ -802,9 +870,40 @@ class StableBrowser {
|
|
|
802
870
|
result.locatorIndex = i;
|
|
803
871
|
}
|
|
804
872
|
if (foundLocators.length > 1) {
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
873
|
+
// remove elements that consume the same space with 10 pixels tolerance
|
|
874
|
+
const boxes = [];
|
|
875
|
+
for (let j = 0; j < foundLocators.length; j++) {
|
|
876
|
+
boxes.push({ box: await foundLocators[j].boundingBox(), locator: foundLocators[j] });
|
|
877
|
+
}
|
|
878
|
+
for (let j = 0; j < boxes.length; j++) {
|
|
879
|
+
for (let k = 0; k < boxes.length; k++) {
|
|
880
|
+
if (j === k) {
|
|
881
|
+
continue;
|
|
882
|
+
}
|
|
883
|
+
// check if x, y, width, height are the same with 10 pixels tolerance
|
|
884
|
+
if (Math.abs(boxes[j].box.x - boxes[k].box.x) < 10 &&
|
|
885
|
+
Math.abs(boxes[j].box.y - boxes[k].box.y) < 10 &&
|
|
886
|
+
Math.abs(boxes[j].box.width - boxes[k].box.width) < 10 &&
|
|
887
|
+
Math.abs(boxes[j].box.height - boxes[k].box.height) < 10) {
|
|
888
|
+
// as the element is not unique, will remove it
|
|
889
|
+
boxes.splice(k, 1);
|
|
890
|
+
k--;
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
if (boxes.length === 1) {
|
|
895
|
+
result.foundElements.push({
|
|
896
|
+
locator: boxes[0].locator.first(),
|
|
897
|
+
box: boxes[0].box,
|
|
898
|
+
unique: true,
|
|
899
|
+
});
|
|
900
|
+
result.locatorIndex = i;
|
|
901
|
+
}
|
|
902
|
+
else {
|
|
903
|
+
info.failCause.foundMultiple = true;
|
|
904
|
+
if (info.locatorLog) {
|
|
905
|
+
info.locatorLog.setLocatorSearchStatus(JSON.stringify(locatorsGroup[i]), "FOUND_NOT_UNIQUE");
|
|
906
|
+
}
|
|
808
907
|
}
|
|
809
908
|
}
|
|
810
909
|
}
|
|
@@ -852,7 +951,7 @@ class StableBrowser {
|
|
|
852
951
|
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
853
952
|
}
|
|
854
953
|
finally {
|
|
855
|
-
_commandFinally(state, this);
|
|
954
|
+
await _commandFinally(state, this);
|
|
856
955
|
}
|
|
857
956
|
}
|
|
858
957
|
}
|
|
@@ -901,7 +1000,7 @@ class StableBrowser {
|
|
|
901
1000
|
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
902
1001
|
}
|
|
903
1002
|
finally {
|
|
904
|
-
_commandFinally(state, this);
|
|
1003
|
+
await _commandFinally(state, this);
|
|
905
1004
|
}
|
|
906
1005
|
}
|
|
907
1006
|
}
|
|
@@ -915,25 +1014,14 @@ class StableBrowser {
|
|
|
915
1014
|
options,
|
|
916
1015
|
world,
|
|
917
1016
|
text: "Click element",
|
|
1017
|
+
_text: "Click on " + selectors.element_name,
|
|
918
1018
|
type: Types.CLICK,
|
|
919
1019
|
operation: "click",
|
|
920
1020
|
log: "***** click on " + selectors.element_name + " *****\n",
|
|
921
1021
|
};
|
|
922
1022
|
try {
|
|
923
1023
|
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
|
-
}
|
|
1024
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
937
1025
|
await this.waitForPageLoad();
|
|
938
1026
|
return state.info;
|
|
939
1027
|
}
|
|
@@ -941,9 +1029,41 @@ class StableBrowser {
|
|
|
941
1029
|
await _commandError(state, e, this);
|
|
942
1030
|
}
|
|
943
1031
|
finally {
|
|
944
|
-
_commandFinally(state, this);
|
|
1032
|
+
await _commandFinally(state, this);
|
|
945
1033
|
}
|
|
946
1034
|
}
|
|
1035
|
+
async waitForElement(selectors, _params, options = {}, world = null) {
|
|
1036
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1037
|
+
const state = {
|
|
1038
|
+
selectors,
|
|
1039
|
+
_params,
|
|
1040
|
+
options,
|
|
1041
|
+
world,
|
|
1042
|
+
text: "Wait for element",
|
|
1043
|
+
_text: "Wait for " + selectors.element_name,
|
|
1044
|
+
type: Types.WAIT_ELEMENT,
|
|
1045
|
+
operation: "waitForElement",
|
|
1046
|
+
log: "***** wait for " + selectors.element_name + " *****\n",
|
|
1047
|
+
};
|
|
1048
|
+
let found = false;
|
|
1049
|
+
try {
|
|
1050
|
+
await _preCommand(state, this);
|
|
1051
|
+
// if (state.options && state.options.context) {
|
|
1052
|
+
// state.selectors.locators[0].text = state.options.context;
|
|
1053
|
+
// }
|
|
1054
|
+
await state.element.waitFor({ timeout: timeout });
|
|
1055
|
+
found = true;
|
|
1056
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1057
|
+
}
|
|
1058
|
+
catch (e) {
|
|
1059
|
+
console.error("Error on waitForElement", e);
|
|
1060
|
+
// await _commandError(state, e, this);
|
|
1061
|
+
}
|
|
1062
|
+
finally {
|
|
1063
|
+
await _commandFinally(state, this);
|
|
1064
|
+
}
|
|
1065
|
+
return found;
|
|
1066
|
+
}
|
|
947
1067
|
async setCheck(selectors, checked = true, _params, options = {}, world = null) {
|
|
948
1068
|
const state = {
|
|
949
1069
|
selectors,
|
|
@@ -952,6 +1072,7 @@ class StableBrowser {
|
|
|
952
1072
|
world,
|
|
953
1073
|
type: checked ? Types.CHECK : Types.UNCHECK,
|
|
954
1074
|
text: checked ? `Check element` : `Uncheck element`,
|
|
1075
|
+
_text: checked ? `Check ${selectors.element_name}` : `Uncheck ${selectors.element_name}`,
|
|
955
1076
|
operation: "setCheck",
|
|
956
1077
|
log: "***** check " + selectors.element_name + " *****\n",
|
|
957
1078
|
};
|
|
@@ -961,9 +1082,15 @@ class StableBrowser {
|
|
|
961
1082
|
// let element = await this._locate(selectors, info, _params);
|
|
962
1083
|
// ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
963
1084
|
try {
|
|
964
|
-
//
|
|
1085
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
1086
|
+
// console.log(`Highlighting while running from recorder`);
|
|
1087
|
+
await this._highlightElements(state.element);
|
|
965
1088
|
await state.element.setChecked(checked);
|
|
966
1089
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1090
|
+
// await this._unHighlightElements(element);
|
|
1091
|
+
// }
|
|
1092
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1093
|
+
// await this._unHighlightElements(element);
|
|
967
1094
|
}
|
|
968
1095
|
catch (e) {
|
|
969
1096
|
if (e.message && e.message.includes("did not change its state")) {
|
|
@@ -984,7 +1111,7 @@ class StableBrowser {
|
|
|
984
1111
|
await _commandError(state, e, this);
|
|
985
1112
|
}
|
|
986
1113
|
finally {
|
|
987
|
-
_commandFinally(state, this);
|
|
1114
|
+
await _commandFinally(state, this);
|
|
988
1115
|
}
|
|
989
1116
|
}
|
|
990
1117
|
async hover(selectors, _params, options = {}, world = null) {
|
|
@@ -995,22 +1122,13 @@ class StableBrowser {
|
|
|
995
1122
|
world,
|
|
996
1123
|
type: Types.HOVER,
|
|
997
1124
|
text: `Hover element`,
|
|
1125
|
+
_text: `Hover on ${selectors.element_name}`,
|
|
998
1126
|
operation: "hover",
|
|
999
1127
|
log: "***** hover " + selectors.element_name + " *****\n",
|
|
1000
1128
|
};
|
|
1001
1129
|
try {
|
|
1002
1130
|
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
|
-
}
|
|
1131
|
+
await performAction("hover", state.element, options, this, state, _params);
|
|
1014
1132
|
await _screenshot(state, this);
|
|
1015
1133
|
await this.waitForPageLoad();
|
|
1016
1134
|
return state.info;
|
|
@@ -1019,7 +1137,7 @@ class StableBrowser {
|
|
|
1019
1137
|
await _commandError(state, e, this);
|
|
1020
1138
|
}
|
|
1021
1139
|
finally {
|
|
1022
|
-
_commandFinally(state, this);
|
|
1140
|
+
await _commandFinally(state, this);
|
|
1023
1141
|
}
|
|
1024
1142
|
}
|
|
1025
1143
|
async selectOption(selectors, values, _params = null, options = {}, world = null) {
|
|
@@ -1034,6 +1152,7 @@ class StableBrowser {
|
|
|
1034
1152
|
value: values.toString(),
|
|
1035
1153
|
type: Types.SELECT,
|
|
1036
1154
|
text: `Select option: ${values}`,
|
|
1155
|
+
_text: `Select option: ${values} on ${selectors.element_name}`,
|
|
1037
1156
|
operation: "selectOption",
|
|
1038
1157
|
log: "***** select option " + selectors.element_name + " *****\n",
|
|
1039
1158
|
};
|
|
@@ -1054,7 +1173,7 @@ class StableBrowser {
|
|
|
1054
1173
|
await _commandError(state, e, this);
|
|
1055
1174
|
}
|
|
1056
1175
|
finally {
|
|
1057
|
-
_commandFinally(state, this);
|
|
1176
|
+
await _commandFinally(state, this);
|
|
1058
1177
|
}
|
|
1059
1178
|
}
|
|
1060
1179
|
async type(_value, _params = null, options = {}, world = null) {
|
|
@@ -1068,6 +1187,7 @@ class StableBrowser {
|
|
|
1068
1187
|
highlight: false,
|
|
1069
1188
|
type: Types.TYPE_PRESS,
|
|
1070
1189
|
text: `Type value: ${_value}`,
|
|
1190
|
+
_text: `Type value: ${_value}`,
|
|
1071
1191
|
operation: "type",
|
|
1072
1192
|
log: "",
|
|
1073
1193
|
};
|
|
@@ -1099,7 +1219,7 @@ class StableBrowser {
|
|
|
1099
1219
|
await _commandError(state, e, this);
|
|
1100
1220
|
}
|
|
1101
1221
|
finally {
|
|
1102
|
-
_commandFinally(state, this);
|
|
1222
|
+
await _commandFinally(state, this);
|
|
1103
1223
|
}
|
|
1104
1224
|
}
|
|
1105
1225
|
async setInputValue(selectors, value, _params = null, options = {}, world = null) {
|
|
@@ -1135,7 +1255,7 @@ class StableBrowser {
|
|
|
1135
1255
|
await _commandError(state, e, this);
|
|
1136
1256
|
}
|
|
1137
1257
|
finally {
|
|
1138
|
-
_commandFinally(state, this);
|
|
1258
|
+
await _commandFinally(state, this);
|
|
1139
1259
|
}
|
|
1140
1260
|
}
|
|
1141
1261
|
async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
|
|
@@ -1147,6 +1267,7 @@ class StableBrowser {
|
|
|
1147
1267
|
world,
|
|
1148
1268
|
type: Types.SET_DATE_TIME,
|
|
1149
1269
|
text: `Set date time value: ${value}`,
|
|
1270
|
+
_text: `Set date time value: ${value} on ${selectors.element_name}`,
|
|
1150
1271
|
operation: "setDateTime",
|
|
1151
1272
|
log: "***** set date time value " + selectors.element_name + " *****\n",
|
|
1152
1273
|
throwError: false,
|
|
@@ -1154,7 +1275,7 @@ class StableBrowser {
|
|
|
1154
1275
|
try {
|
|
1155
1276
|
await _preCommand(state, this);
|
|
1156
1277
|
try {
|
|
1157
|
-
await state.element
|
|
1278
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
1158
1279
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1159
1280
|
if (format) {
|
|
1160
1281
|
state.value = dayjs(state.value).format(format);
|
|
@@ -1203,7 +1324,7 @@ class StableBrowser {
|
|
|
1203
1324
|
await _commandError(state, e, this);
|
|
1204
1325
|
}
|
|
1205
1326
|
finally {
|
|
1206
|
-
_commandFinally(state, this);
|
|
1327
|
+
await _commandFinally(state, this);
|
|
1207
1328
|
}
|
|
1208
1329
|
}
|
|
1209
1330
|
async clickType(selectors, _value, enter = false, _params = null, options = {}, world = null) {
|
|
@@ -1218,9 +1339,13 @@ class StableBrowser {
|
|
|
1218
1339
|
world,
|
|
1219
1340
|
type: Types.FILL,
|
|
1220
1341
|
text: `Click type input with value: ${_value}`,
|
|
1342
|
+
_text: "Fill " + selectors.element_name + " with value " + maskValue(_value),
|
|
1221
1343
|
operation: "clickType",
|
|
1222
1344
|
log: "***** clickType on " + selectors.element_name + " with value " + maskValue(_value) + "*****\n",
|
|
1223
1345
|
};
|
|
1346
|
+
if (!options) {
|
|
1347
|
+
options = {};
|
|
1348
|
+
}
|
|
1224
1349
|
if (newValue !== _value) {
|
|
1225
1350
|
//this.logger.info(_value + "=" + newValue);
|
|
1226
1351
|
_value = newValue;
|
|
@@ -1228,7 +1353,7 @@ class StableBrowser {
|
|
|
1228
1353
|
try {
|
|
1229
1354
|
await _preCommand(state, this);
|
|
1230
1355
|
state.info.value = _value;
|
|
1231
|
-
if (
|
|
1356
|
+
if (!options.press) {
|
|
1232
1357
|
try {
|
|
1233
1358
|
let currentValue = await state.element.inputValue();
|
|
1234
1359
|
if (currentValue) {
|
|
@@ -1239,13 +1364,9 @@ class StableBrowser {
|
|
|
1239
1364
|
this.logger.info("unable to clear input value");
|
|
1240
1365
|
}
|
|
1241
1366
|
}
|
|
1242
|
-
if (options
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
}
|
|
1246
|
-
catch (e) {
|
|
1247
|
-
await state.element.dispatchEvent("click");
|
|
1248
|
-
}
|
|
1367
|
+
if (options.press) {
|
|
1368
|
+
options.timeout = 5000;
|
|
1369
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
1249
1370
|
}
|
|
1250
1371
|
else {
|
|
1251
1372
|
try {
|
|
@@ -1283,7 +1404,12 @@ class StableBrowser {
|
|
|
1283
1404
|
await this.waitForPageLoad();
|
|
1284
1405
|
}
|
|
1285
1406
|
else if (enter === false) {
|
|
1286
|
-
|
|
1407
|
+
try {
|
|
1408
|
+
await state.element.dispatchEvent("change", null, { timeout: 5000 });
|
|
1409
|
+
}
|
|
1410
|
+
catch (e) {
|
|
1411
|
+
// ignore
|
|
1412
|
+
}
|
|
1287
1413
|
//await this.page.keyboard.press("Tab");
|
|
1288
1414
|
}
|
|
1289
1415
|
else {
|
|
@@ -1298,7 +1424,7 @@ class StableBrowser {
|
|
|
1298
1424
|
await _commandError(state, e, this);
|
|
1299
1425
|
}
|
|
1300
1426
|
finally {
|
|
1301
|
-
_commandFinally(state, this);
|
|
1427
|
+
await _commandFinally(state, this);
|
|
1302
1428
|
}
|
|
1303
1429
|
}
|
|
1304
1430
|
async fill(selectors, value, enter = false, _params = null, options = {}, world = null) {
|
|
@@ -1328,13 +1454,14 @@ class StableBrowser {
|
|
|
1328
1454
|
await _commandError(state, e, this);
|
|
1329
1455
|
}
|
|
1330
1456
|
finally {
|
|
1331
|
-
_commandFinally(state, this);
|
|
1457
|
+
await _commandFinally(state, this);
|
|
1332
1458
|
}
|
|
1333
1459
|
}
|
|
1334
1460
|
async getText(selectors, _params = null, options = {}, info = {}, world = null) {
|
|
1335
1461
|
return await this._getText(selectors, 0, _params, options, info, world);
|
|
1336
1462
|
}
|
|
1337
1463
|
async _getText(selectors, climb, _params = null, options = {}, info = {}, world = null) {
|
|
1464
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1338
1465
|
_validateSelectors(selectors);
|
|
1339
1466
|
let screenshotId = null;
|
|
1340
1467
|
let screenshotPath = null;
|
|
@@ -1344,7 +1471,7 @@ class StableBrowser {
|
|
|
1344
1471
|
}
|
|
1345
1472
|
info.operation = "getText";
|
|
1346
1473
|
info.selectors = selectors;
|
|
1347
|
-
let element = await this._locate(selectors, info, _params);
|
|
1474
|
+
let element = await this._locate(selectors, info, _params, timeout);
|
|
1348
1475
|
if (climb > 0) {
|
|
1349
1476
|
const climbArray = [];
|
|
1350
1477
|
for (let i = 0; i < climb; i++) {
|
|
@@ -1363,6 +1490,18 @@ class StableBrowser {
|
|
|
1363
1490
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1364
1491
|
try {
|
|
1365
1492
|
await this._highlightElements(element);
|
|
1493
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
1494
|
+
// // console.log(`Highlighting for get text while running from recorder`);
|
|
1495
|
+
// this._highlightElements(element)
|
|
1496
|
+
// .then(async () => {
|
|
1497
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1498
|
+
// this._unhighlightElements(element).then(
|
|
1499
|
+
// () => {}
|
|
1500
|
+
// // console.log(`Unhighlighting vrtr in recorder is successful`)
|
|
1501
|
+
// );
|
|
1502
|
+
// })
|
|
1503
|
+
// .catch(e);
|
|
1504
|
+
// }
|
|
1366
1505
|
const elementText = await element.innerText();
|
|
1367
1506
|
return {
|
|
1368
1507
|
text: elementText,
|
|
@@ -1374,7 +1513,7 @@ class StableBrowser {
|
|
|
1374
1513
|
}
|
|
1375
1514
|
catch (e) {
|
|
1376
1515
|
//await this.closeUnexpectedPopups();
|
|
1377
|
-
this.logger.info("no innerText will use textContent");
|
|
1516
|
+
this.logger.info("no innerText, will use textContent");
|
|
1378
1517
|
const elementText = await element.textContent();
|
|
1379
1518
|
return { text: elementText, screenshotId, screenshotPath, value: value };
|
|
1380
1519
|
}
|
|
@@ -1399,6 +1538,7 @@ class StableBrowser {
|
|
|
1399
1538
|
highlight: false,
|
|
1400
1539
|
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1401
1540
|
text: `Verify element contains pattern: ${pattern}`,
|
|
1541
|
+
_text: "Verify element " + selectors.element_name + " contains pattern " + pattern,
|
|
1402
1542
|
operation: "containsPattern",
|
|
1403
1543
|
log: "***** verify element " + selectors.element_name + " contains pattern " + pattern + " *****\n",
|
|
1404
1544
|
};
|
|
@@ -1430,10 +1570,12 @@ class StableBrowser {
|
|
|
1430
1570
|
await _commandError(state, e, this);
|
|
1431
1571
|
}
|
|
1432
1572
|
finally {
|
|
1433
|
-
_commandFinally(state, this);
|
|
1573
|
+
await _commandFinally(state, this);
|
|
1434
1574
|
}
|
|
1435
1575
|
}
|
|
1436
1576
|
async containsText(selectors, text, climb, _params = null, options = {}, world = null) {
|
|
1577
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1578
|
+
const startTime = Date.now();
|
|
1437
1579
|
const state = {
|
|
1438
1580
|
selectors,
|
|
1439
1581
|
_params,
|
|
@@ -1460,62 +1602,54 @@ class StableBrowser {
|
|
|
1460
1602
|
}
|
|
1461
1603
|
let foundObj = null;
|
|
1462
1604
|
try {
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
const dateAlternatives = findDateAlternatives(text);
|
|
1470
|
-
const numberAlternatives = findNumberAlternatives(text);
|
|
1471
|
-
if (dateAlternatives.date) {
|
|
1472
|
-
for (let i = 0; i < dateAlternatives.dates.length; i++) {
|
|
1473
|
-
if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
|
|
1474
|
-
foundObj?.value?.includes(dateAlternatives.dates[i])) {
|
|
1475
|
-
return state.info;
|
|
1605
|
+
while (Date.now() - startTime < timeout) {
|
|
1606
|
+
try {
|
|
1607
|
+
await _preCommand(state, this);
|
|
1608
|
+
foundObj = await this._getText(selectors, climb, _params, { timeout: 3000 }, state.info, world);
|
|
1609
|
+
if (foundObj && foundObj.element) {
|
|
1610
|
+
await this.scrollIfNeeded(foundObj.element, state.info);
|
|
1476
1611
|
}
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1612
|
+
await _screenshot(state, this);
|
|
1613
|
+
const dateAlternatives = findDateAlternatives(text);
|
|
1614
|
+
const numberAlternatives = findNumberAlternatives(text);
|
|
1615
|
+
if (dateAlternatives.date) {
|
|
1616
|
+
for (let i = 0; i < dateAlternatives.dates.length; i++) {
|
|
1617
|
+
if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
|
|
1618
|
+
foundObj?.value?.includes(dateAlternatives.dates[i])) {
|
|
1619
|
+
return state.info;
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
else if (numberAlternatives.number) {
|
|
1624
|
+
for (let i = 0; i < numberAlternatives.numbers.length; i++) {
|
|
1625
|
+
if (foundObj?.text.includes(numberAlternatives.numbers[i]) ||
|
|
1626
|
+
foundObj?.value?.includes(numberAlternatives.numbers[i])) {
|
|
1627
|
+
return state.info;
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
else if (foundObj?.text.includes(text) || foundObj?.value?.includes(text)) {
|
|
1484
1632
|
return state.info;
|
|
1485
1633
|
}
|
|
1486
1634
|
}
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
throw new Error("element doesn't contain text " + text);
|
|
1635
|
+
catch (e) {
|
|
1636
|
+
// Log error but continue retrying until timeout is reached
|
|
1637
|
+
this.logger.warn("Retrying containsText due to: " + e.message);
|
|
1638
|
+
}
|
|
1639
|
+
await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second before retrying
|
|
1493
1640
|
}
|
|
1494
|
-
|
|
1641
|
+
state.info.foundText = foundObj?.text;
|
|
1642
|
+
state.info.value = foundObj?.value;
|
|
1643
|
+
throw new Error("element doesn't contain text " + text);
|
|
1495
1644
|
}
|
|
1496
1645
|
catch (e) {
|
|
1497
1646
|
await _commandError(state, e, this);
|
|
1647
|
+
throw e;
|
|
1498
1648
|
}
|
|
1499
1649
|
finally {
|
|
1500
|
-
_commandFinally(state, this);
|
|
1650
|
+
await _commandFinally(state, this);
|
|
1501
1651
|
}
|
|
1502
1652
|
}
|
|
1503
|
-
_getDataFile(world = null) {
|
|
1504
|
-
let dataFile = null;
|
|
1505
|
-
if (world && world.reportFolder) {
|
|
1506
|
-
dataFile = path.join(world.reportFolder, "data.json");
|
|
1507
|
-
}
|
|
1508
|
-
else if (this.reportFolder) {
|
|
1509
|
-
dataFile = path.join(this.reportFolder, "data.json");
|
|
1510
|
-
}
|
|
1511
|
-
else if (this.context && this.context.reportFolder) {
|
|
1512
|
-
dataFile = path.join(this.context.reportFolder, "data.json");
|
|
1513
|
-
}
|
|
1514
|
-
else {
|
|
1515
|
-
dataFile = "data.json";
|
|
1516
|
-
}
|
|
1517
|
-
return dataFile;
|
|
1518
|
-
}
|
|
1519
1653
|
async waitForUserInput(message, world = null) {
|
|
1520
1654
|
if (!message) {
|
|
1521
1655
|
message = "# Wait for user input. Press any key to continue";
|
|
@@ -1544,13 +1678,22 @@ class StableBrowser {
|
|
|
1544
1678
|
return;
|
|
1545
1679
|
}
|
|
1546
1680
|
// if data file exists, load it
|
|
1547
|
-
const dataFile =
|
|
1681
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1548
1682
|
let data = this.getTestData(world);
|
|
1549
1683
|
// merge the testData with the existing data
|
|
1550
1684
|
Object.assign(data, testData);
|
|
1551
1685
|
// save the data to the file
|
|
1552
1686
|
fs.writeFileSync(dataFile, JSON.stringify(data, null, 2));
|
|
1553
1687
|
}
|
|
1688
|
+
overwriteTestData(testData, world = null) {
|
|
1689
|
+
if (!testData) {
|
|
1690
|
+
return;
|
|
1691
|
+
}
|
|
1692
|
+
// if data file exists, load it
|
|
1693
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1694
|
+
// save the data to the file
|
|
1695
|
+
fs.writeFileSync(dataFile, JSON.stringify(testData, null, 2));
|
|
1696
|
+
}
|
|
1554
1697
|
_getDataFilePath(fileName) {
|
|
1555
1698
|
let dataFile = path.join(this.project_path, "data", fileName);
|
|
1556
1699
|
if (fs.existsSync(dataFile)) {
|
|
@@ -1647,7 +1790,7 @@ class StableBrowser {
|
|
|
1647
1790
|
}
|
|
1648
1791
|
}
|
|
1649
1792
|
getTestData(world = null) {
|
|
1650
|
-
const dataFile =
|
|
1793
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1651
1794
|
let data = {};
|
|
1652
1795
|
if (fs.existsSync(dataFile)) {
|
|
1653
1796
|
data = JSON.parse(fs.readFileSync(dataFile, "utf8"));
|
|
@@ -1734,6 +1877,15 @@ class StableBrowser {
|
|
|
1734
1877
|
document.documentElement.clientWidth,
|
|
1735
1878
|
])));
|
|
1736
1879
|
let screenshotBuffer = null;
|
|
1880
|
+
// if (focusedElement) {
|
|
1881
|
+
// // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
|
|
1882
|
+
// await this._unhighlightElements(focusedElement);
|
|
1883
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1884
|
+
// console.log(`Unhighlighted previous element`);
|
|
1885
|
+
// }
|
|
1886
|
+
// if (focusedElement) {
|
|
1887
|
+
// await this._highlightElements(focusedElement);
|
|
1888
|
+
// }
|
|
1737
1889
|
if (this.context.browserName === "chromium") {
|
|
1738
1890
|
const client = await playContext.newCDPSession(this.page);
|
|
1739
1891
|
const { data } = await client.send("Page.captureScreenshot", {
|
|
@@ -1755,6 +1907,10 @@ class StableBrowser {
|
|
|
1755
1907
|
else {
|
|
1756
1908
|
screenshotBuffer = await this.page.screenshot();
|
|
1757
1909
|
}
|
|
1910
|
+
// if (focusedElement) {
|
|
1911
|
+
// // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
|
|
1912
|
+
// await this._unhighlightElements(focusedElement);
|
|
1913
|
+
// }
|
|
1758
1914
|
let image = await Jimp.read(screenshotBuffer);
|
|
1759
1915
|
// Get the image dimensions
|
|
1760
1916
|
const { width, height } = image.bitmap;
|
|
@@ -1767,6 +1923,7 @@ class StableBrowser {
|
|
|
1767
1923
|
else {
|
|
1768
1924
|
fs.writeFileSync(screenshotPath, screenshotBuffer);
|
|
1769
1925
|
}
|
|
1926
|
+
return screenshotBuffer;
|
|
1770
1927
|
}
|
|
1771
1928
|
async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
|
|
1772
1929
|
const state = {
|
|
@@ -1789,7 +1946,7 @@ class StableBrowser {
|
|
|
1789
1946
|
await _commandError(state, e, this);
|
|
1790
1947
|
}
|
|
1791
1948
|
finally {
|
|
1792
|
-
_commandFinally(state, this);
|
|
1949
|
+
await _commandFinally(state, this);
|
|
1793
1950
|
}
|
|
1794
1951
|
}
|
|
1795
1952
|
async extractAttribute(selectors, attribute, variable, _params = null, options = {}, world = null) {
|
|
@@ -1802,8 +1959,10 @@ class StableBrowser {
|
|
|
1802
1959
|
world,
|
|
1803
1960
|
type: Types.EXTRACT,
|
|
1804
1961
|
text: `Extract attribute from element`,
|
|
1962
|
+
_text: `Extract attribute ${attribute} from ${selectors.element_name}`,
|
|
1805
1963
|
operation: "extractAttribute",
|
|
1806
1964
|
log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1965
|
+
allowDisabled: true,
|
|
1807
1966
|
};
|
|
1808
1967
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1809
1968
|
try {
|
|
@@ -1818,6 +1977,9 @@ class StableBrowser {
|
|
|
1818
1977
|
case "value":
|
|
1819
1978
|
state.value = await state.element.inputValue();
|
|
1820
1979
|
break;
|
|
1980
|
+
case "text":
|
|
1981
|
+
state.value = await state.element.textContent();
|
|
1982
|
+
break;
|
|
1821
1983
|
default:
|
|
1822
1984
|
state.value = await state.element.getAttribute(attribute);
|
|
1823
1985
|
break;
|
|
@@ -1825,13 +1987,14 @@ class StableBrowser {
|
|
|
1825
1987
|
state.info.value = state.value;
|
|
1826
1988
|
this.setTestData({ [variable]: state.value }, world);
|
|
1827
1989
|
this.logger.info("set test data: " + variable + "=" + state.value);
|
|
1990
|
+
// await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1828
1991
|
return state.info;
|
|
1829
1992
|
}
|
|
1830
1993
|
catch (e) {
|
|
1831
1994
|
await _commandError(state, e, this);
|
|
1832
1995
|
}
|
|
1833
1996
|
finally {
|
|
1834
|
-
_commandFinally(state, this);
|
|
1997
|
+
await _commandFinally(state, this);
|
|
1835
1998
|
}
|
|
1836
1999
|
}
|
|
1837
2000
|
async verifyAttribute(selectors, attribute, value, _params = null, options = {}, world = null) {
|
|
@@ -1846,18 +2009,25 @@ class StableBrowser {
|
|
|
1846
2009
|
highlight: true,
|
|
1847
2010
|
screenshot: true,
|
|
1848
2011
|
text: `Verify element attribute`,
|
|
2012
|
+
_text: `Verify attribute ${attribute} from ${selectors.element_name} is ${value}`,
|
|
1849
2013
|
operation: "verifyAttribute",
|
|
1850
2014
|
log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1851
|
-
allowDisabled: true
|
|
2015
|
+
allowDisabled: true,
|
|
1852
2016
|
};
|
|
1853
2017
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1854
2018
|
let val;
|
|
2019
|
+
let expectedValue;
|
|
1855
2020
|
try {
|
|
1856
2021
|
await _preCommand(state, this);
|
|
2022
|
+
expectedValue = await replaceWithLocalTestData(state.value, world);
|
|
2023
|
+
state.info.expectedValue = expectedValue;
|
|
1857
2024
|
switch (attribute) {
|
|
1858
2025
|
case "innerText":
|
|
1859
2026
|
val = String(await state.element.innerText());
|
|
1860
2027
|
break;
|
|
2028
|
+
case "text":
|
|
2029
|
+
val = String(await state.element.textContent());
|
|
2030
|
+
break;
|
|
1861
2031
|
case "value":
|
|
1862
2032
|
val = String(await state.element.inputValue());
|
|
1863
2033
|
break;
|
|
@@ -1875,26 +2045,29 @@ class StableBrowser {
|
|
|
1875
2045
|
val = String(await state.element.getAttribute(attribute));
|
|
1876
2046
|
break;
|
|
1877
2047
|
}
|
|
2048
|
+
state.info.value = val;
|
|
1878
2049
|
let regex;
|
|
1879
|
-
if (
|
|
1880
|
-
const patternBody =
|
|
2050
|
+
if (expectedValue.startsWith("/") && expectedValue.endsWith("/")) {
|
|
2051
|
+
const patternBody = expectedValue.slice(1, -1);
|
|
1881
2052
|
regex = new RegExp(patternBody, "g");
|
|
1882
2053
|
}
|
|
1883
2054
|
else {
|
|
1884
|
-
const escapedPattern =
|
|
2055
|
+
const escapedPattern = expectedValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1885
2056
|
regex = new RegExp(escapedPattern, "g");
|
|
1886
2057
|
}
|
|
1887
2058
|
if (!val.match(regex)) {
|
|
1888
|
-
|
|
2059
|
+
let errorMessage = `The ${attribute} attribute has a value of "${val}", but the expected value is "${expectedValue}"`;
|
|
2060
|
+
state.info.failCause.assertionFailed = true;
|
|
2061
|
+
state.info.failCause.lastError = errorMessage;
|
|
2062
|
+
throw new Error(errorMessage);
|
|
1889
2063
|
}
|
|
1890
|
-
state.info.expectedValue = val;
|
|
1891
2064
|
return state.info;
|
|
1892
2065
|
}
|
|
1893
2066
|
catch (e) {
|
|
1894
2067
|
await _commandError(state, e, this);
|
|
1895
2068
|
}
|
|
1896
2069
|
finally {
|
|
1897
|
-
_commandFinally(state, this);
|
|
2070
|
+
await _commandFinally(state, this);
|
|
1898
2071
|
}
|
|
1899
2072
|
}
|
|
1900
2073
|
async extractEmailData(emailAddress, options, world) {
|
|
@@ -1986,27 +2159,32 @@ class StableBrowser {
|
|
|
1986
2159
|
async _highlightElements(scope, css) {
|
|
1987
2160
|
try {
|
|
1988
2161
|
if (!scope) {
|
|
2162
|
+
// console.log(`Scope is not defined`);
|
|
1989
2163
|
return;
|
|
1990
2164
|
}
|
|
1991
2165
|
if (!css) {
|
|
1992
2166
|
scope
|
|
1993
2167
|
.evaluate((node) => {
|
|
1994
2168
|
if (node && node.style) {
|
|
1995
|
-
let
|
|
2169
|
+
let originalOutline = node.style.outline;
|
|
2170
|
+
// console.log(`Original outline was: ${originalOutline}`);
|
|
2171
|
+
// node.__previousOutline = originalOutline;
|
|
1996
2172
|
node.style.outline = "2px solid red";
|
|
2173
|
+
// console.log(`New outline is: ${node.style.outline}`);
|
|
1997
2174
|
if (window) {
|
|
1998
2175
|
window.addEventListener("beforeunload", function (e) {
|
|
1999
|
-
node.style.outline =
|
|
2176
|
+
node.style.outline = originalOutline;
|
|
2000
2177
|
});
|
|
2001
2178
|
}
|
|
2002
2179
|
setTimeout(function () {
|
|
2003
|
-
node.style.outline =
|
|
2180
|
+
node.style.outline = originalOutline;
|
|
2004
2181
|
}, 2000);
|
|
2005
2182
|
}
|
|
2006
2183
|
})
|
|
2007
2184
|
.then(() => { })
|
|
2008
2185
|
.catch((e) => {
|
|
2009
2186
|
// ignore
|
|
2187
|
+
// console.error(`Could not highlight node : ${e}`);
|
|
2010
2188
|
});
|
|
2011
2189
|
}
|
|
2012
2190
|
else {
|
|
@@ -2022,17 +2200,18 @@ class StableBrowser {
|
|
|
2022
2200
|
if (!element.style) {
|
|
2023
2201
|
return;
|
|
2024
2202
|
}
|
|
2025
|
-
|
|
2203
|
+
let originalOutline = element.style.outline;
|
|
2204
|
+
element.__previousOutline = originalOutline;
|
|
2026
2205
|
// Set the new border to be red and 2px solid
|
|
2027
2206
|
element.style.outline = "2px solid red";
|
|
2028
2207
|
if (window) {
|
|
2029
2208
|
window.addEventListener("beforeunload", function (e) {
|
|
2030
|
-
element.style.outline =
|
|
2209
|
+
element.style.outline = originalOutline;
|
|
2031
2210
|
});
|
|
2032
2211
|
}
|
|
2033
2212
|
// Set a timeout to revert to the original border after 2 seconds
|
|
2034
2213
|
setTimeout(function () {
|
|
2035
|
-
element.style.outline =
|
|
2214
|
+
element.style.outline = originalOutline;
|
|
2036
2215
|
}, 2000);
|
|
2037
2216
|
}
|
|
2038
2217
|
return;
|
|
@@ -2040,6 +2219,7 @@ class StableBrowser {
|
|
|
2040
2219
|
.then(() => { })
|
|
2041
2220
|
.catch((e) => {
|
|
2042
2221
|
// ignore
|
|
2222
|
+
// console.error(`Could not highlight css: ${e}`);
|
|
2043
2223
|
});
|
|
2044
2224
|
}
|
|
2045
2225
|
}
|
|
@@ -2047,6 +2227,54 @@ class StableBrowser {
|
|
|
2047
2227
|
console.debug(error);
|
|
2048
2228
|
}
|
|
2049
2229
|
}
|
|
2230
|
+
// async _unhighlightElements(scope, css) {
|
|
2231
|
+
// try {
|
|
2232
|
+
// if (!scope) {
|
|
2233
|
+
// return;
|
|
2234
|
+
// }
|
|
2235
|
+
// if (!css) {
|
|
2236
|
+
// scope
|
|
2237
|
+
// .evaluate((node) => {
|
|
2238
|
+
// if (node && node.style) {
|
|
2239
|
+
// if (!node.__previousOutline) {
|
|
2240
|
+
// node.style.outline = "";
|
|
2241
|
+
// } else {
|
|
2242
|
+
// node.style.outline = node.__previousOutline;
|
|
2243
|
+
// }
|
|
2244
|
+
// }
|
|
2245
|
+
// })
|
|
2246
|
+
// .then(() => {})
|
|
2247
|
+
// .catch((e) => {
|
|
2248
|
+
// // console.log(`Error while unhighlighting node ${JSON.stringify(scope)}: ${e}`);
|
|
2249
|
+
// });
|
|
2250
|
+
// } else {
|
|
2251
|
+
// scope
|
|
2252
|
+
// .evaluate(([css]) => {
|
|
2253
|
+
// if (!css) {
|
|
2254
|
+
// return;
|
|
2255
|
+
// }
|
|
2256
|
+
// let elements = Array.from(document.querySelectorAll(css));
|
|
2257
|
+
// for (i = 0; i < elements.length; i++) {
|
|
2258
|
+
// let element = elements[i];
|
|
2259
|
+
// if (!element.style) {
|
|
2260
|
+
// return;
|
|
2261
|
+
// }
|
|
2262
|
+
// if (!element.__previousOutline) {
|
|
2263
|
+
// element.style.outline = "";
|
|
2264
|
+
// } else {
|
|
2265
|
+
// element.style.outline = element.__previousOutline;
|
|
2266
|
+
// }
|
|
2267
|
+
// }
|
|
2268
|
+
// })
|
|
2269
|
+
// .then(() => {})
|
|
2270
|
+
// .catch((e) => {
|
|
2271
|
+
// // console.error(`Error while unhighlighting element in css: ${e}`);
|
|
2272
|
+
// });
|
|
2273
|
+
// }
|
|
2274
|
+
// } catch (error) {
|
|
2275
|
+
// // console.debug(error);
|
|
2276
|
+
// }
|
|
2277
|
+
// }
|
|
2050
2278
|
async verifyPagePath(pathPart, options = {}, world = null) {
|
|
2051
2279
|
const startTime = Date.now();
|
|
2052
2280
|
let error = null;
|
|
@@ -2091,6 +2319,69 @@ class StableBrowser {
|
|
|
2091
2319
|
_reportToWorld(world, {
|
|
2092
2320
|
type: Types.VERIFY_PAGE_PATH,
|
|
2093
2321
|
text: "Verify page path",
|
|
2322
|
+
_text: "Verify the page path contains " + pathPart,
|
|
2323
|
+
screenshotId,
|
|
2324
|
+
result: error
|
|
2325
|
+
? {
|
|
2326
|
+
status: "FAILED",
|
|
2327
|
+
startTime,
|
|
2328
|
+
endTime,
|
|
2329
|
+
message: error?.message,
|
|
2330
|
+
}
|
|
2331
|
+
: {
|
|
2332
|
+
status: "PASSED",
|
|
2333
|
+
startTime,
|
|
2334
|
+
endTime,
|
|
2335
|
+
},
|
|
2336
|
+
info: info,
|
|
2337
|
+
});
|
|
2338
|
+
}
|
|
2339
|
+
}
|
|
2340
|
+
async verifyPageTitle(title, options = {}, world = null) {
|
|
2341
|
+
const startTime = Date.now();
|
|
2342
|
+
let error = null;
|
|
2343
|
+
let screenshotId = null;
|
|
2344
|
+
let screenshotPath = null;
|
|
2345
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2346
|
+
const info = {};
|
|
2347
|
+
info.log = "***** verify page title " + title + " *****\n";
|
|
2348
|
+
info.operation = "verifyPageTitle";
|
|
2349
|
+
const newValue = await this._replaceWithLocalData(title, world);
|
|
2350
|
+
if (newValue !== title) {
|
|
2351
|
+
this.logger.info(title + "=" + newValue);
|
|
2352
|
+
title = newValue;
|
|
2353
|
+
}
|
|
2354
|
+
info.title = title;
|
|
2355
|
+
try {
|
|
2356
|
+
for (let i = 0; i < 30; i++) {
|
|
2357
|
+
const foundTitle = await this.page.title();
|
|
2358
|
+
if (!foundTitle.includes(title)) {
|
|
2359
|
+
if (i === 29) {
|
|
2360
|
+
throw new Error(`url ${foundTitle} doesn't contain ${title}`);
|
|
2361
|
+
}
|
|
2362
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2363
|
+
continue;
|
|
2364
|
+
}
|
|
2365
|
+
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2366
|
+
return info;
|
|
2367
|
+
}
|
|
2368
|
+
}
|
|
2369
|
+
catch (e) {
|
|
2370
|
+
//await this.closeUnexpectedPopups();
|
|
2371
|
+
this.logger.error("verify page title failed " + info.log);
|
|
2372
|
+
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2373
|
+
info.screenshotPath = screenshotPath;
|
|
2374
|
+
Object.assign(e, { info: info });
|
|
2375
|
+
error = e;
|
|
2376
|
+
// throw e;
|
|
2377
|
+
await _commandError({ text: "verifyPageTitle", operation: "verifyPageTitle", title, info, throwError: true }, e, this);
|
|
2378
|
+
}
|
|
2379
|
+
finally {
|
|
2380
|
+
const endTime = Date.now();
|
|
2381
|
+
_reportToWorld(world, {
|
|
2382
|
+
type: Types.VERIFY_PAGE_PATH,
|
|
2383
|
+
text: "Verify page title",
|
|
2384
|
+
_text: "Verify the page title contains " + title,
|
|
2094
2385
|
screenshotId,
|
|
2095
2386
|
result: error
|
|
2096
2387
|
? {
|
|
@@ -2108,27 +2399,27 @@ class StableBrowser {
|
|
|
2108
2399
|
});
|
|
2109
2400
|
}
|
|
2110
2401
|
}
|
|
2111
|
-
async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state) {
|
|
2402
|
+
async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state, partial = true, ignoreCase = false) {
|
|
2112
2403
|
const frames = this.page.frames();
|
|
2113
2404
|
let results = [];
|
|
2114
|
-
let ignoreCase = false;
|
|
2405
|
+
// let ignoreCase = false;
|
|
2115
2406
|
for (let i = 0; i < frames.length; i++) {
|
|
2116
2407
|
if (dateAlternatives.date) {
|
|
2117
2408
|
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2118
|
-
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false,
|
|
2409
|
+
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, partial, ignoreCase, {});
|
|
2119
2410
|
result.frame = frames[i];
|
|
2120
2411
|
results.push(result);
|
|
2121
2412
|
}
|
|
2122
2413
|
}
|
|
2123
2414
|
else if (numberAlternatives.number) {
|
|
2124
2415
|
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2125
|
-
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false,
|
|
2416
|
+
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, partial, ignoreCase, {});
|
|
2126
2417
|
result.frame = frames[i];
|
|
2127
2418
|
results.push(result);
|
|
2128
2419
|
}
|
|
2129
2420
|
}
|
|
2130
2421
|
else {
|
|
2131
|
-
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false,
|
|
2422
|
+
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, partial, ignoreCase, {});
|
|
2132
2423
|
result.frame = frames[i];
|
|
2133
2424
|
results.push(result);
|
|
2134
2425
|
}
|
|
@@ -2147,11 +2438,15 @@ class StableBrowser {
|
|
|
2147
2438
|
scroll: false,
|
|
2148
2439
|
highlight: false,
|
|
2149
2440
|
type: Types.VERIFY_PAGE_CONTAINS_TEXT,
|
|
2150
|
-
text: `Verify text exists in page`,
|
|
2441
|
+
text: `Verify the text '${text}' exists in page`,
|
|
2442
|
+
_text: `Verify the text '${text}' exists in page`,
|
|
2151
2443
|
operation: "verifyTextExistInPage",
|
|
2152
2444
|
log: "***** verify text " + text + " exists in page *****\n",
|
|
2153
2445
|
};
|
|
2154
|
-
|
|
2446
|
+
if (testForRegex(text)) {
|
|
2447
|
+
text = text.replace(/\\"/g, '"');
|
|
2448
|
+
}
|
|
2449
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2155
2450
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2156
2451
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2157
2452
|
if (newValue !== text) {
|
|
@@ -2164,7 +2459,15 @@ class StableBrowser {
|
|
|
2164
2459
|
await _preCommand(state, this);
|
|
2165
2460
|
state.info.text = text;
|
|
2166
2461
|
while (true) {
|
|
2167
|
-
|
|
2462
|
+
let resultWithElementsFound = {
|
|
2463
|
+
length: 0,
|
|
2464
|
+
};
|
|
2465
|
+
try {
|
|
2466
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2467
|
+
}
|
|
2468
|
+
catch (error) {
|
|
2469
|
+
// ignore
|
|
2470
|
+
}
|
|
2168
2471
|
if (resultWithElementsFound.length === 0) {
|
|
2169
2472
|
if (Date.now() - state.startTime > timeout) {
|
|
2170
2473
|
throw new Error(`Text ${text} not found in page`);
|
|
@@ -2172,18 +2475,40 @@ class StableBrowser {
|
|
|
2172
2475
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2173
2476
|
continue;
|
|
2174
2477
|
}
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2478
|
+
try {
|
|
2479
|
+
if (resultWithElementsFound[0].randomToken) {
|
|
2480
|
+
const frame = resultWithElementsFound[0].frame;
|
|
2481
|
+
const dataAttribute = `[data-blinq-id-${resultWithElementsFound[0].randomToken}]`;
|
|
2482
|
+
await this._highlightElements(frame, dataAttribute);
|
|
2483
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
2484
|
+
// console.log(`Highlighting for verify text is found while running from recorder`);
|
|
2485
|
+
// this._highlightElements(frame, dataAttribute).then(async () => {
|
|
2486
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2487
|
+
// this._unhighlightElements(frame, dataAttribute)
|
|
2488
|
+
// .then(async () => {
|
|
2489
|
+
// console.log(`Unhighlighted frame dataAttribute successfully`);
|
|
2490
|
+
// })
|
|
2491
|
+
// .catch(
|
|
2492
|
+
// (e) => {}
|
|
2493
|
+
// console.error(e)
|
|
2494
|
+
// );
|
|
2495
|
+
// });
|
|
2496
|
+
// }
|
|
2497
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2498
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2499
|
+
// await this._unhighlightElements(frame, dataAttribute);
|
|
2500
|
+
if (element) {
|
|
2501
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2502
|
+
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2503
|
+
// await _screenshot(state, this, element);
|
|
2504
|
+
}
|
|
2183
2505
|
}
|
|
2506
|
+
await _screenshot(state, this);
|
|
2507
|
+
return state.info;
|
|
2508
|
+
}
|
|
2509
|
+
catch (error) {
|
|
2510
|
+
console.error(error);
|
|
2184
2511
|
}
|
|
2185
|
-
await _screenshot(state, this);
|
|
2186
|
-
return state.info;
|
|
2187
2512
|
}
|
|
2188
2513
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2189
2514
|
}
|
|
@@ -2191,7 +2516,7 @@ class StableBrowser {
|
|
|
2191
2516
|
await _commandError(state, e, this);
|
|
2192
2517
|
}
|
|
2193
2518
|
finally {
|
|
2194
|
-
_commandFinally(state, this);
|
|
2519
|
+
await _commandFinally(state, this);
|
|
2195
2520
|
}
|
|
2196
2521
|
}
|
|
2197
2522
|
async waitForTextToDisappear(text, options = {}, world = null) {
|
|
@@ -2204,11 +2529,15 @@ class StableBrowser {
|
|
|
2204
2529
|
scroll: false,
|
|
2205
2530
|
highlight: false,
|
|
2206
2531
|
type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
|
|
2207
|
-
text: `Verify text does not exist in page`,
|
|
2532
|
+
text: `Verify the text '${text}' does not exist in page`,
|
|
2533
|
+
_text: `Verify the text '${text}' does not exist in page`,
|
|
2208
2534
|
operation: "verifyTextNotExistInPage",
|
|
2209
2535
|
log: "***** verify text " + text + " does not exist in page *****\n",
|
|
2210
2536
|
};
|
|
2211
|
-
|
|
2537
|
+
if (testForRegex(text)) {
|
|
2538
|
+
text = text.replace(/\\"/g, '"');
|
|
2539
|
+
}
|
|
2540
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2212
2541
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2213
2542
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2214
2543
|
if (newValue !== text) {
|
|
@@ -2220,8 +2549,16 @@ class StableBrowser {
|
|
|
2220
2549
|
try {
|
|
2221
2550
|
await _preCommand(state, this);
|
|
2222
2551
|
state.info.text = text;
|
|
2552
|
+
let resultWithElementsFound = {
|
|
2553
|
+
length: null, // initial cannot be 0
|
|
2554
|
+
};
|
|
2223
2555
|
while (true) {
|
|
2224
|
-
|
|
2556
|
+
try {
|
|
2557
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2558
|
+
}
|
|
2559
|
+
catch (error) {
|
|
2560
|
+
// ignore
|
|
2561
|
+
}
|
|
2225
2562
|
if (resultWithElementsFound.length === 0) {
|
|
2226
2563
|
await _screenshot(state, this);
|
|
2227
2564
|
return state.info;
|
|
@@ -2236,7 +2573,7 @@ class StableBrowser {
|
|
|
2236
2573
|
await _commandError(state, e, this);
|
|
2237
2574
|
}
|
|
2238
2575
|
finally {
|
|
2239
|
-
_commandFinally(state, this);
|
|
2576
|
+
await _commandFinally(state, this);
|
|
2240
2577
|
}
|
|
2241
2578
|
}
|
|
2242
2579
|
async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
|
|
@@ -2251,10 +2588,11 @@ class StableBrowser {
|
|
|
2251
2588
|
highlight: false,
|
|
2252
2589
|
type: Types.VERIFY_TEXT_WITH_RELATION,
|
|
2253
2590
|
text: `Verify text with relation to another text`,
|
|
2591
|
+
_text: "Search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found",
|
|
2254
2592
|
operation: "verify_text_with_relation",
|
|
2255
2593
|
log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
|
|
2256
2594
|
};
|
|
2257
|
-
const timeout = this.
|
|
2595
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2258
2596
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2259
2597
|
let newValue = await this._replaceWithLocalData(textAnchor, world);
|
|
2260
2598
|
if (newValue !== textAnchor) {
|
|
@@ -2272,8 +2610,16 @@ class StableBrowser {
|
|
|
2272
2610
|
try {
|
|
2273
2611
|
await _preCommand(state, this);
|
|
2274
2612
|
state.info.text = textToVerify;
|
|
2613
|
+
let resultWithElementsFound = {
|
|
2614
|
+
length: 0,
|
|
2615
|
+
};
|
|
2275
2616
|
while (true) {
|
|
2276
|
-
|
|
2617
|
+
try {
|
|
2618
|
+
resultWithElementsFound = await this.findTextInAllFrames(findDateAlternatives(textAnchor), findNumberAlternatives(textAnchor), textAnchor, state, false);
|
|
2619
|
+
}
|
|
2620
|
+
catch (error) {
|
|
2621
|
+
// ignore
|
|
2622
|
+
}
|
|
2277
2623
|
if (resultWithElementsFound.length === 0) {
|
|
2278
2624
|
if (Date.now() - state.startTime > timeout) {
|
|
2279
2625
|
throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
|
|
@@ -2281,37 +2627,56 @@ class StableBrowser {
|
|
|
2281
2627
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2282
2628
|
continue;
|
|
2283
2629
|
}
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2630
|
+
try {
|
|
2631
|
+
for (let i = 0; i < resultWithElementsFound.length; i++) {
|
|
2632
|
+
foundAncore = true;
|
|
2633
|
+
const result = resultWithElementsFound[i];
|
|
2634
|
+
const token = result.randomToken;
|
|
2635
|
+
const frame = result.frame;
|
|
2636
|
+
let css = `[data-blinq-id-${token}]`;
|
|
2637
|
+
const climbArray1 = [];
|
|
2638
|
+
for (let i = 0; i < climb; i++) {
|
|
2639
|
+
climbArray1.push("..");
|
|
2640
|
+
}
|
|
2641
|
+
let climbXpath = "xpath=" + climbArray1.join("/");
|
|
2642
|
+
css = css + " >> " + climbXpath;
|
|
2643
|
+
const count = await frame.locator(css).count();
|
|
2644
|
+
for (let j = 0; j < count; j++) {
|
|
2645
|
+
const continer = await frame.locator(css).nth(j);
|
|
2646
|
+
const result = await this._locateElementByText(continer, textToVerify, "*:not(script, style, head)", false, true, true, {});
|
|
2647
|
+
if (result.elementCount > 0) {
|
|
2648
|
+
const dataAttribute = "[data-blinq-id-" + result.randomToken + "]";
|
|
2649
|
+
await this._highlightElements(frame, dataAttribute);
|
|
2650
|
+
//const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
|
|
2651
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
2652
|
+
// console.log(`Highlighting for vtrt while running from recorder`);
|
|
2653
|
+
// this._highlightElements(frame, dataAttribute)
|
|
2654
|
+
// .then(async () => {
|
|
2655
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2656
|
+
// this._unhighlightElements(frame, dataAttribute).then(
|
|
2657
|
+
// () => {}
|
|
2658
|
+
// console.log(`Unhighlighting vrtr in recorder is successful`)
|
|
2659
|
+
// );
|
|
2660
|
+
// })
|
|
2661
|
+
// .catch(e);
|
|
2662
|
+
// }
|
|
2663
|
+
//await this._highlightElements(frame, cssAnchor);
|
|
2664
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2665
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2666
|
+
// await this._unhighlightElements(frame, dataAttribute);
|
|
2667
|
+
if (element) {
|
|
2668
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2669
|
+
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2670
|
+
}
|
|
2671
|
+
await _screenshot(state, this);
|
|
2672
|
+
return state.info;
|
|
2309
2673
|
}
|
|
2310
|
-
await _screenshot(state, this);
|
|
2311
|
-
return state.info;
|
|
2312
2674
|
}
|
|
2313
2675
|
}
|
|
2314
2676
|
}
|
|
2677
|
+
catch (error) {
|
|
2678
|
+
console.error(error);
|
|
2679
|
+
}
|
|
2315
2680
|
}
|
|
2316
2681
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2317
2682
|
}
|
|
@@ -2319,9 +2684,33 @@ class StableBrowser {
|
|
|
2319
2684
|
await _commandError(state, e, this);
|
|
2320
2685
|
}
|
|
2321
2686
|
finally {
|
|
2322
|
-
_commandFinally(state, this);
|
|
2687
|
+
await _commandFinally(state, this);
|
|
2323
2688
|
}
|
|
2324
2689
|
}
|
|
2690
|
+
async findRelatedTextInAllFrames(textAnchor, climb, textToVerify, params = {}, options = {}, world = null) {
|
|
2691
|
+
const frames = this.page.frames();
|
|
2692
|
+
let results = [];
|
|
2693
|
+
let ignoreCase = false;
|
|
2694
|
+
for (let i = 0; i < frames.length; i++) {
|
|
2695
|
+
const result = await this._locateElementByText(frames[i], textAnchor, "*:not(script, style, head)", false, true, ignoreCase, {});
|
|
2696
|
+
result.frame = frames[i];
|
|
2697
|
+
const climbArray = [];
|
|
2698
|
+
for (let i = 0; i < climb; i++) {
|
|
2699
|
+
climbArray.push("..");
|
|
2700
|
+
}
|
|
2701
|
+
let climbXpath = "xpath=" + climbArray.join("/");
|
|
2702
|
+
const newLocator = `[data-blinq-id-${result.randomToken}] ${climb > 0 ? ">> " + climbXpath : ""} >> internal:text=${testForRegex(textToVerify) ? textToVerify : unEscapeString(textToVerify)}`;
|
|
2703
|
+
const count = await frames[i].locator(newLocator).count();
|
|
2704
|
+
if (count > 0) {
|
|
2705
|
+
result.elementCount = count;
|
|
2706
|
+
result.locator = newLocator;
|
|
2707
|
+
results.push(result);
|
|
2708
|
+
}
|
|
2709
|
+
}
|
|
2710
|
+
// state.info.results = results;
|
|
2711
|
+
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2712
|
+
return resultWithElementsFound;
|
|
2713
|
+
}
|
|
2325
2714
|
async visualVerification(text, options = {}, world = null) {
|
|
2326
2715
|
const startTime = Date.now();
|
|
2327
2716
|
let error = null;
|
|
@@ -2340,10 +2729,13 @@ class StableBrowser {
|
|
|
2340
2729
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2341
2730
|
info.screenshotPath = screenshotPath;
|
|
2342
2731
|
const screenshot = await this.takeScreenshot();
|
|
2343
|
-
|
|
2344
|
-
method: "
|
|
2732
|
+
let request = {
|
|
2733
|
+
method: "post",
|
|
2734
|
+
maxBodyLength: Infinity,
|
|
2345
2735
|
url: `${serviceUrl}/api/runs/screenshots/validate-screenshot`,
|
|
2346
2736
|
headers: {
|
|
2737
|
+
"x-bvt-project-id": path.basename(this.project_path),
|
|
2738
|
+
"x-source": "aaa",
|
|
2347
2739
|
"Content-Type": "application/json",
|
|
2348
2740
|
Authorization: `Bearer ${process.env.TOKEN}`,
|
|
2349
2741
|
},
|
|
@@ -2352,7 +2744,7 @@ class StableBrowser {
|
|
|
2352
2744
|
screenshot: screenshot,
|
|
2353
2745
|
}),
|
|
2354
2746
|
};
|
|
2355
|
-
|
|
2747
|
+
const result = await axios.request(request);
|
|
2356
2748
|
if (result.data.status !== true) {
|
|
2357
2749
|
throw new Error("Visual validation failed");
|
|
2358
2750
|
}
|
|
@@ -2380,6 +2772,7 @@ class StableBrowser {
|
|
|
2380
2772
|
_reportToWorld(world, {
|
|
2381
2773
|
type: Types.VERIFY_VISUAL,
|
|
2382
2774
|
text: "Visual verification",
|
|
2775
|
+
_text: "Visual verification of " + text,
|
|
2383
2776
|
screenshotId,
|
|
2384
2777
|
result: error
|
|
2385
2778
|
? {
|
|
@@ -2646,6 +3039,32 @@ class StableBrowser {
|
|
|
2646
3039
|
}
|
|
2647
3040
|
return timeout;
|
|
2648
3041
|
}
|
|
3042
|
+
_getFindElementTimeout(options) {
|
|
3043
|
+
if (options && options.timeout) {
|
|
3044
|
+
return options.timeout;
|
|
3045
|
+
}
|
|
3046
|
+
if (this.configuration.find_element_timeout) {
|
|
3047
|
+
return this.configuration.find_element_timeout;
|
|
3048
|
+
}
|
|
3049
|
+
return 30000;
|
|
3050
|
+
}
|
|
3051
|
+
async saveStoreState(path = null, world = null) {
|
|
3052
|
+
const storageState = await this.page.context().storageState();
|
|
3053
|
+
//const testDataFile = _getDataFile(world, this.context, this);
|
|
3054
|
+
if (path) {
|
|
3055
|
+
// save { storageState: storageState } into the path
|
|
3056
|
+
fs.writeFileSync(path, JSON.stringify({ storageState: storageState }, null, 2));
|
|
3057
|
+
}
|
|
3058
|
+
else {
|
|
3059
|
+
await this.setTestData({ storageState: storageState }, world);
|
|
3060
|
+
}
|
|
3061
|
+
}
|
|
3062
|
+
async restoreSaveState(path = null, world = null) {
|
|
3063
|
+
await refreshBrowser(this, path, world);
|
|
3064
|
+
this.registerEventListeners(this.context);
|
|
3065
|
+
registerNetworkEvents(this.world, this, this.context, this.page);
|
|
3066
|
+
registerDownloadEvent(this.page, this.world, this.context);
|
|
3067
|
+
}
|
|
2649
3068
|
async waitForPageLoad(options = {}, world = null) {
|
|
2650
3069
|
let timeout = this._getLoadTimeout(options);
|
|
2651
3070
|
const promiseArray = [];
|
|
@@ -2713,6 +3132,7 @@ class StableBrowser {
|
|
|
2713
3132
|
highlight: false,
|
|
2714
3133
|
type: Types.CLOSE_PAGE,
|
|
2715
3134
|
text: `Close page`,
|
|
3135
|
+
_text: `Close the page`,
|
|
2716
3136
|
operation: "closePage",
|
|
2717
3137
|
log: "***** close page *****\n",
|
|
2718
3138
|
throwError: false,
|
|
@@ -2726,11 +3146,98 @@ class StableBrowser {
|
|
|
2726
3146
|
await _commandError(state, e, this);
|
|
2727
3147
|
}
|
|
2728
3148
|
finally {
|
|
2729
|
-
_commandFinally(state, this);
|
|
3149
|
+
await _commandFinally(state, this);
|
|
3150
|
+
}
|
|
3151
|
+
}
|
|
3152
|
+
async tableCellOperation(headerText, rowText, options, _params, world = null) {
|
|
3153
|
+
let operation = null;
|
|
3154
|
+
if (!options || !options.operation) {
|
|
3155
|
+
throw new Error("operation is not defined");
|
|
3156
|
+
}
|
|
3157
|
+
operation = options.operation;
|
|
3158
|
+
// validate operation is one of the supported operations
|
|
3159
|
+
if (operation != "click" && operation != "hover+click") {
|
|
3160
|
+
throw new Error("operation is not supported");
|
|
3161
|
+
}
|
|
3162
|
+
const state = {
|
|
3163
|
+
options,
|
|
3164
|
+
world,
|
|
3165
|
+
locate: false,
|
|
3166
|
+
scroll: false,
|
|
3167
|
+
highlight: false,
|
|
3168
|
+
type: Types.TABLE_OPERATION,
|
|
3169
|
+
text: `Table operation`,
|
|
3170
|
+
_text: `Table ${operation} operation`,
|
|
3171
|
+
operation: operation,
|
|
3172
|
+
log: "***** Table operation *****\n",
|
|
3173
|
+
};
|
|
3174
|
+
const timeout = this._getFindElementTimeout(options);
|
|
3175
|
+
try {
|
|
3176
|
+
await _preCommand(state, this);
|
|
3177
|
+
const start = Date.now();
|
|
3178
|
+
let cellArea = null;
|
|
3179
|
+
while (true) {
|
|
3180
|
+
try {
|
|
3181
|
+
cellArea = await _findCellArea(headerText, rowText, this, state);
|
|
3182
|
+
if (cellArea) {
|
|
3183
|
+
break;
|
|
3184
|
+
}
|
|
3185
|
+
}
|
|
3186
|
+
catch (e) {
|
|
3187
|
+
// ignore
|
|
3188
|
+
}
|
|
3189
|
+
if (Date.now() - start > timeout) {
|
|
3190
|
+
throw new Error(`Cell not found in table`);
|
|
3191
|
+
}
|
|
3192
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
3193
|
+
}
|
|
3194
|
+
switch (operation) {
|
|
3195
|
+
case "click":
|
|
3196
|
+
if (!options.css) {
|
|
3197
|
+
// will click in the center of the cell
|
|
3198
|
+
let xOffset = 0;
|
|
3199
|
+
let yOffset = 0;
|
|
3200
|
+
if (options.xOffset) {
|
|
3201
|
+
xOffset = options.xOffset;
|
|
3202
|
+
}
|
|
3203
|
+
if (options.yOffset) {
|
|
3204
|
+
yOffset = options.yOffset;
|
|
3205
|
+
}
|
|
3206
|
+
await this.page.mouse.click(cellArea.x + cellArea.width / 2 + xOffset, cellArea.y + cellArea.height / 2 + yOffset);
|
|
3207
|
+
}
|
|
3208
|
+
else {
|
|
3209
|
+
const results = await findElementsInArea(options.css, cellArea, this, options);
|
|
3210
|
+
if (results.length === 0) {
|
|
3211
|
+
throw new Error(`Element not found in cell area`);
|
|
3212
|
+
}
|
|
3213
|
+
state.element = results[0];
|
|
3214
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
3215
|
+
}
|
|
3216
|
+
break;
|
|
3217
|
+
case "hover+click":
|
|
3218
|
+
if (!options.css) {
|
|
3219
|
+
throw new Error("css is not defined");
|
|
3220
|
+
}
|
|
3221
|
+
const results = await findElementsInArea(options.css, cellArea, this, options);
|
|
3222
|
+
if (results.length === 0) {
|
|
3223
|
+
throw new Error(`Element not found in cell area`);
|
|
3224
|
+
}
|
|
3225
|
+
state.element = results[0];
|
|
3226
|
+
await performAction("hover+click", state.element, options, this, state, _params);
|
|
3227
|
+
break;
|
|
3228
|
+
default:
|
|
3229
|
+
throw new Error("operation is not supported");
|
|
3230
|
+
}
|
|
3231
|
+
}
|
|
3232
|
+
catch (e) {
|
|
3233
|
+
await _commandError(state, e, this);
|
|
3234
|
+
}
|
|
3235
|
+
finally {
|
|
3236
|
+
await _commandFinally(state, this);
|
|
2730
3237
|
}
|
|
2731
3238
|
}
|
|
2732
3239
|
saveTestDataAsGlobal(options, world) {
|
|
2733
|
-
const dataFile =
|
|
3240
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
2734
3241
|
process.env.GLOBAL_TEST_DATA_FILE = dataFile;
|
|
2735
3242
|
this.logger.info("Save the scenario test data as global for the following scenarios.");
|
|
2736
3243
|
}
|
|
@@ -2760,6 +3267,7 @@ class StableBrowser {
|
|
|
2760
3267
|
_reportToWorld(world, {
|
|
2761
3268
|
type: Types.SET_VIEWPORT,
|
|
2762
3269
|
text: "set viewport size to " + width + "x" + hight,
|
|
3270
|
+
_text: "Set the viewport size to " + width + "x" + hight,
|
|
2763
3271
|
screenshotId,
|
|
2764
3272
|
result: error
|
|
2765
3273
|
? {
|
|
@@ -2847,6 +3355,9 @@ class StableBrowser {
|
|
|
2847
3355
|
else {
|
|
2848
3356
|
this.stepName = "step " + this.stepIndex;
|
|
2849
3357
|
}
|
|
3358
|
+
if (this.context) {
|
|
3359
|
+
this.context.examplesRow = extractStepExampleParameters(step);
|
|
3360
|
+
}
|
|
2850
3361
|
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
2851
3362
|
if (this.context.browserObject.context) {
|
|
2852
3363
|
await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
|
|
@@ -2859,6 +3370,41 @@ class StableBrowser {
|
|
|
2859
3370
|
this.saveTestDataAsGlobal({}, world);
|
|
2860
3371
|
}
|
|
2861
3372
|
}
|
|
3373
|
+
if (this.initSnapshotTaken === false) {
|
|
3374
|
+
this.initSnapshotTaken = true;
|
|
3375
|
+
if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
|
|
3376
|
+
const snapshot = await this.getAriaSnapshot();
|
|
3377
|
+
if (snapshot) {
|
|
3378
|
+
await world.attach(JSON.stringify(snapshot), "application/json+snapshot-before");
|
|
3379
|
+
}
|
|
3380
|
+
}
|
|
3381
|
+
}
|
|
3382
|
+
}
|
|
3383
|
+
async getAriaSnapshot() {
|
|
3384
|
+
try {
|
|
3385
|
+
// find the page url
|
|
3386
|
+
const url = await this.page.url();
|
|
3387
|
+
// extract the path from the url
|
|
3388
|
+
const path = new URL(url).pathname;
|
|
3389
|
+
// get the page title
|
|
3390
|
+
const title = await this.page.title();
|
|
3391
|
+
// go over other frams
|
|
3392
|
+
const frames = this.page.frames();
|
|
3393
|
+
const snapshots = [];
|
|
3394
|
+
const content = [`- path: ${path}`, `- title: ${title}`];
|
|
3395
|
+
const timeout = this.configuration.ariaSnapshotTimeout ? this.configuration.ariaSnapshotTimeout : 3000;
|
|
3396
|
+
for (let i = 0; i < frames.length; i++) {
|
|
3397
|
+
content.push(`- frame: ${i}`);
|
|
3398
|
+
const frame = frames[i];
|
|
3399
|
+
const snapshot = await frame.locator("body").ariaSnapshot({ timeout });
|
|
3400
|
+
content.push(snapshot);
|
|
3401
|
+
}
|
|
3402
|
+
return content.join("\n");
|
|
3403
|
+
}
|
|
3404
|
+
catch (e) {
|
|
3405
|
+
console.error(e);
|
|
3406
|
+
}
|
|
3407
|
+
return null;
|
|
2862
3408
|
}
|
|
2863
3409
|
async afterStep(world, step) {
|
|
2864
3410
|
this.stepName = null;
|
|
@@ -2867,6 +3413,23 @@ class StableBrowser {
|
|
|
2867
3413
|
await this.context.browserObject.context.tracing.stopChunk({
|
|
2868
3414
|
path: path.join(this.context.browserObject.traceFolder, `trace-${this.stepIndex}.zip`),
|
|
2869
3415
|
});
|
|
3416
|
+
if (world && world.attach) {
|
|
3417
|
+
await world.attach(JSON.stringify({
|
|
3418
|
+
type: "trace",
|
|
3419
|
+
traceFilePath: `trace-${this.stepIndex}.zip`,
|
|
3420
|
+
}), "application/json+trace");
|
|
3421
|
+
}
|
|
3422
|
+
// console.log("trace file created", `trace-${this.stepIndex}.zip`);
|
|
3423
|
+
}
|
|
3424
|
+
}
|
|
3425
|
+
if (this.context) {
|
|
3426
|
+
this.context.examplesRow = null;
|
|
3427
|
+
}
|
|
3428
|
+
if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
|
|
3429
|
+
const snapshot = await this.getAriaSnapshot();
|
|
3430
|
+
if (snapshot) {
|
|
3431
|
+
const obj = {};
|
|
3432
|
+
await world.attach(JSON.stringify(snapshot), "application/json+snapshot-after");
|
|
2870
3433
|
}
|
|
2871
3434
|
}
|
|
2872
3435
|
}
|