automation_model 1.0.632-dev → 1.0.632-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 +16 -16
- package/lib/analyze_helper.js.map +1 -1
- package/lib/api.d.ts +0 -1
- package/lib/api.js +35 -21
- package/lib/api.js.map +1 -1
- package/lib/auto_page.d.ts +1 -1
- package/lib/auto_page.js +99 -28
- package/lib/auto_page.js.map +1 -1
- package/lib/browser_manager.js +38 -9
- package/lib/browser_manager.js.map +1 -1
- package/lib/bruno.d.ts +2 -0
- package/lib/bruno.js +381 -0
- package/lib/bruno.js.map +1 -0
- package/lib/command_common.d.ts +4 -4
- package/lib/command_common.js +36 -16
- package/lib/command_common.js.map +1 -1
- package/lib/date_time.js.map +1 -1
- package/lib/drawRect.js.map +1 -1
- package/lib/environment.d.ts +1 -0
- package/lib/environment.js +1 -0
- package/lib/environment.js.map +1 -1
- package/lib/error-messages.js.map +1 -1
- package/lib/file_checker.d.ts +1 -0
- package/lib/file_checker.js +61 -0
- package/lib/file_checker.js.map +1 -0
- package/lib/find_function.js.map +1 -1
- package/lib/index.d.ts +2 -0
- package/lib/index.js +2 -0
- package/lib/index.js.map +1 -1
- package/lib/init_browser.d.ts +2 -2
- package/lib/init_browser.js +33 -27
- 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/locator.js +1 -1
- package/lib/locator.js.map +1 -1
- package/lib/locator_log.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/snapshot_validation.d.ts +37 -0
- package/lib/snapshot_validation.js +357 -0
- package/lib/snapshot_validation.js.map +1 -0
- package/lib/stable_browser.d.ts +31 -23
- package/lib/stable_browser.js +614 -167
- package/lib/stable_browser.js.map +1 -1
- package/lib/table.js.map +1 -1
- package/lib/table_analyze.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 +2 -0
- package/lib/test_context.js +2 -0
- package/lib/test_context.js.map +1 -1
- package/lib/utils.d.ts +8 -4
- package/lib/utils.js +221 -21
- package/lib/utils.js.map +1 -1
- package/package.json +11 -6
package/lib/stable_browser.js
CHANGED
|
@@ -10,33 +10,37 @@ import { getDateTimeValue } from "./date_time.js";
|
|
|
10
10
|
import drawRectangle from "./drawRect.js";
|
|
11
11
|
//import { closeUnexpectedPopups } from "./popups.js";
|
|
12
12
|
import { getTableCells, getTableData } from "./table_analyze.js";
|
|
13
|
-
import { _convertToRegexQuery, _copyContext, _fixLocatorUsingParams, _fixUsingParams, _getServerUrl, extractStepExampleParameters, KEYBOARD_EVENTS, maskValue, replaceWithLocalTestData, scrollPageToLoadLazyElements, unEscapeString, _getDataFile, } 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
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";
|
|
23
24
|
import axios from "axios";
|
|
25
|
+
import { _findCellArea, findElementsInArea } from "./table_helper.js";
|
|
26
|
+
import { snapshotValidation } from "./snapshot_validation.js";
|
|
27
|
+
import { loadBrunoParams } from "./bruno.js";
|
|
24
28
|
export const Types = {
|
|
25
29
|
CLICK: "click_element",
|
|
26
30
|
WAIT_ELEMENT: "wait_element",
|
|
27
|
-
NAVIGATE: "navigate",
|
|
31
|
+
NAVIGATE: "navigate", ///
|
|
28
32
|
FILL: "fill_element",
|
|
29
|
-
EXECUTE: "execute_page_method",
|
|
30
|
-
OPEN: "open_environment",
|
|
33
|
+
EXECUTE: "execute_page_method", //
|
|
34
|
+
OPEN: "open_environment", //
|
|
31
35
|
COMPLETE: "step_complete",
|
|
32
36
|
ASK: "information_needed",
|
|
33
|
-
GET_PAGE_STATUS: "get_page_status",
|
|
34
|
-
CLICK_ROW_ACTION: "click_row_action",
|
|
37
|
+
GET_PAGE_STATUS: "get_page_status", ///
|
|
38
|
+
CLICK_ROW_ACTION: "click_row_action", //
|
|
35
39
|
VERIFY_ELEMENT_CONTAINS_TEXT: "verify_element_contains_text",
|
|
36
40
|
VERIFY_PAGE_CONTAINS_TEXT: "verify_page_contains_text",
|
|
37
41
|
VERIFY_PAGE_CONTAINS_NO_TEXT: "verify_page_contains_no_text",
|
|
38
42
|
ANALYZE_TABLE: "analyze_table",
|
|
39
|
-
SELECT: "select_combobox",
|
|
43
|
+
SELECT: "select_combobox", //
|
|
40
44
|
VERIFY_PAGE_PATH: "verify_page_path",
|
|
41
45
|
TYPE_PRESS: "type_press",
|
|
42
46
|
PRESS: "press_key",
|
|
@@ -45,6 +49,7 @@ export const Types = {
|
|
|
45
49
|
UNCHECK: "uncheck_element",
|
|
46
50
|
EXTRACT: "extract_attribute",
|
|
47
51
|
CLOSE_PAGE: "close_page",
|
|
52
|
+
TABLE_OPERATION: "table_operation",
|
|
48
53
|
SET_DATE_TIME: "set_date_time",
|
|
49
54
|
SET_VIEWPORT: "set_viewport",
|
|
50
55
|
VERIFY_VISUAL: "verify_visual",
|
|
@@ -53,6 +58,11 @@ export const Types = {
|
|
|
53
58
|
WAIT_FOR_TEXT_TO_DISAPPEAR: "wait_for_text_to_disappear",
|
|
54
59
|
VERIFY_ATTRIBUTE: "verify_element_attribute",
|
|
55
60
|
VERIFY_TEXT_WITH_RELATION: "verify_text_with_relation",
|
|
61
|
+
BRUNO: "bruno",
|
|
62
|
+
VERIFY_FILE_EXISTS: "verify_file_exists",
|
|
63
|
+
SET_INPUT_FILES: "set_input_files",
|
|
64
|
+
SNAPSHOT_VALIDATION: "snapshot_validation",
|
|
65
|
+
REPORT_COMMAND: "report_command",
|
|
56
66
|
};
|
|
57
67
|
export const apps = {};
|
|
58
68
|
const formatElementName = (elementName) => {
|
|
@@ -178,6 +188,30 @@ class StableBrowser {
|
|
|
178
188
|
await this.waitForPageLoad();
|
|
179
189
|
}
|
|
180
190
|
}
|
|
191
|
+
async switchTab(tabTitleOrIndex) {
|
|
192
|
+
// first check if the tabNameOrIndex is a number
|
|
193
|
+
let index = parseInt(tabTitleOrIndex);
|
|
194
|
+
if (!isNaN(index)) {
|
|
195
|
+
if (index >= 0 && index < this.context.pages.length) {
|
|
196
|
+
this.page = this.context.pages[index];
|
|
197
|
+
this.context.page = this.page;
|
|
198
|
+
await this.page.bringToFront();
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
// if the tabNameOrIndex is a string, find the tab by name
|
|
203
|
+
for (let i = 0; i < this.context.pages.length; i++) {
|
|
204
|
+
let page = this.context.pages[i];
|
|
205
|
+
let title = await page.title();
|
|
206
|
+
if (title.includes(tabTitleOrIndex)) {
|
|
207
|
+
this.page = page;
|
|
208
|
+
this.context.page = this.page;
|
|
209
|
+
await this.page.bringToFront();
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
throw new Error("Tab not found: " + tabTitleOrIndex);
|
|
214
|
+
}
|
|
181
215
|
registerConsoleLogListener(page, context) {
|
|
182
216
|
if (!this.context.webLogger) {
|
|
183
217
|
this.context.webLogger = [];
|
|
@@ -245,6 +279,7 @@ class StableBrowser {
|
|
|
245
279
|
if (!url) {
|
|
246
280
|
throw new Error("url is null, verify that the environment file is correct");
|
|
247
281
|
}
|
|
282
|
+
url = await this._replaceWithLocalData(url, this.world);
|
|
248
283
|
if (!url.startsWith("http")) {
|
|
249
284
|
url = "https://" + url;
|
|
250
285
|
}
|
|
@@ -273,7 +308,7 @@ class StableBrowser {
|
|
|
273
308
|
_commandError(state, error, this);
|
|
274
309
|
}
|
|
275
310
|
finally {
|
|
276
|
-
_commandFinally(state, this);
|
|
311
|
+
await _commandFinally(state, this);
|
|
277
312
|
}
|
|
278
313
|
}
|
|
279
314
|
async _getLocator(locator, scope, _params) {
|
|
@@ -374,6 +409,12 @@ class StableBrowser {
|
|
|
374
409
|
if (!el.setAttribute) {
|
|
375
410
|
el = el.parentElement;
|
|
376
411
|
}
|
|
412
|
+
// remove any attributes start with data-blinq-id
|
|
413
|
+
// for (let i = 0; i < el.attributes.length; i++) {
|
|
414
|
+
// if (el.attributes[i].name.startsWith("data-blinq-id")) {
|
|
415
|
+
// el.removeAttribute(el.attributes[i].name);
|
|
416
|
+
// }
|
|
417
|
+
// }
|
|
377
418
|
el.setAttribute("data-blinq-id-" + randomToken, "");
|
|
378
419
|
return true;
|
|
379
420
|
}, [tag1, randomToken]))) {
|
|
@@ -383,7 +424,7 @@ class StableBrowser {
|
|
|
383
424
|
}
|
|
384
425
|
return { elementCount: tagCount, randomToken };
|
|
385
426
|
}
|
|
386
|
-
async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true, allowDisabled = false, element_name = null) {
|
|
427
|
+
async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true, allowDisabled = false, element_name = null, logErrors = false) {
|
|
387
428
|
if (!info) {
|
|
388
429
|
info = {};
|
|
389
430
|
}
|
|
@@ -450,7 +491,7 @@ class StableBrowser {
|
|
|
450
491
|
}
|
|
451
492
|
return;
|
|
452
493
|
}
|
|
453
|
-
if (info.locatorLog && count === 0) {
|
|
494
|
+
if (info.locatorLog && count === 0 && logErrors) {
|
|
454
495
|
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "NOT_FOUND");
|
|
455
496
|
}
|
|
456
497
|
for (let j = 0; j < count; j++) {
|
|
@@ -465,7 +506,7 @@ class StableBrowser {
|
|
|
465
506
|
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
|
|
466
507
|
}
|
|
467
508
|
}
|
|
468
|
-
else {
|
|
509
|
+
else if (logErrors) {
|
|
469
510
|
info.failCause.visible = visible;
|
|
470
511
|
info.failCause.enabled = enabled;
|
|
471
512
|
if (!info.printMessages) {
|
|
@@ -560,12 +601,24 @@ class StableBrowser {
|
|
|
560
601
|
element.evaluate((el, randomToken) => {
|
|
561
602
|
el.setAttribute("data-blinq-id-" + randomToken, "");
|
|
562
603
|
}, randomToken);
|
|
563
|
-
if (element._frame) {
|
|
564
|
-
|
|
565
|
-
}
|
|
566
|
-
const scope = element.page();
|
|
567
|
-
|
|
568
|
-
|
|
604
|
+
// if (element._frame) {
|
|
605
|
+
// return element;
|
|
606
|
+
// }
|
|
607
|
+
const scope = element._frame ?? element.page();
|
|
608
|
+
let newElementSelector = "[data-blinq-id-" + randomToken + "]";
|
|
609
|
+
let prefixSelector = "";
|
|
610
|
+
const frameControlSelector = " >> internal:control=enter-frame";
|
|
611
|
+
const frameSelectorIndex = element._selector.lastIndexOf(frameControlSelector);
|
|
612
|
+
if (frameSelectorIndex !== -1) {
|
|
613
|
+
// remove everything after the >> internal:control=enter-frame
|
|
614
|
+
const frameSelector = element._selector.substring(0, frameSelectorIndex);
|
|
615
|
+
prefixSelector = frameSelector + " >> internal:control=enter-frame >>";
|
|
616
|
+
}
|
|
617
|
+
// if (element?._frame?._selector) {
|
|
618
|
+
// prefixSelector = element._frame._selector + " >> " + prefixSelector;
|
|
619
|
+
// }
|
|
620
|
+
const newSelector = prefixSelector + newElementSelector;
|
|
621
|
+
return scope.locator(newSelector);
|
|
569
622
|
}
|
|
570
623
|
}
|
|
571
624
|
throw new Error("unable to locate element " + JSON.stringify(selectors));
|
|
@@ -717,14 +770,9 @@ class StableBrowser {
|
|
|
717
770
|
// info.log += "scanning locators in priority 2" + "\n";
|
|
718
771
|
result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
719
772
|
}
|
|
720
|
-
if (result.foundElements.length === 0 && onlyPriority3) {
|
|
773
|
+
if (result.foundElements.length === 0 && (onlyPriority3 || !highPriorityOnly)) {
|
|
721
774
|
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
722
775
|
}
|
|
723
|
-
else {
|
|
724
|
-
if (result.foundElements.length === 0 && !highPriorityOnly) {
|
|
725
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
726
|
-
}
|
|
727
|
-
}
|
|
728
776
|
let foundElements = result.foundElements;
|
|
729
777
|
if (foundElements.length === 1 && foundElements[0].unique) {
|
|
730
778
|
info.box = foundElements[0].box;
|
|
@@ -779,6 +827,11 @@ class StableBrowser {
|
|
|
779
827
|
visibleOnly = false;
|
|
780
828
|
}
|
|
781
829
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
830
|
+
// sheck of more of half of the timeout has passed
|
|
831
|
+
if (Date.now() - startTime > timeout / 2) {
|
|
832
|
+
highPriorityOnly = false;
|
|
833
|
+
visibleOnly = false;
|
|
834
|
+
}
|
|
782
835
|
}
|
|
783
836
|
this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
|
|
784
837
|
// if (info.locatorLog) {
|
|
@@ -794,7 +847,7 @@ class StableBrowser {
|
|
|
794
847
|
}
|
|
795
848
|
throw new Error("failed to locate first element no elements found, " + info.log);
|
|
796
849
|
}
|
|
797
|
-
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false, element_name) {
|
|
850
|
+
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false, element_name, logErrors = false) {
|
|
798
851
|
let foundElements = [];
|
|
799
852
|
const result = {
|
|
800
853
|
foundElements: foundElements,
|
|
@@ -813,7 +866,9 @@ class StableBrowser {
|
|
|
813
866
|
await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
|
|
814
867
|
}
|
|
815
868
|
catch (e) {
|
|
816
|
-
|
|
869
|
+
if (logErrors) {
|
|
870
|
+
this.logger.info("unable to use locator (second try) " + JSON.stringify(locatorsGroup[i]));
|
|
871
|
+
}
|
|
817
872
|
}
|
|
818
873
|
}
|
|
819
874
|
if (foundLocators.length === 1) {
|
|
@@ -825,9 +880,40 @@ class StableBrowser {
|
|
|
825
880
|
result.locatorIndex = i;
|
|
826
881
|
}
|
|
827
882
|
if (foundLocators.length > 1) {
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
883
|
+
// remove elements that consume the same space with 10 pixels tolerance
|
|
884
|
+
const boxes = [];
|
|
885
|
+
for (let j = 0; j < foundLocators.length; j++) {
|
|
886
|
+
boxes.push({ box: await foundLocators[j].boundingBox(), locator: foundLocators[j] });
|
|
887
|
+
}
|
|
888
|
+
for (let j = 0; j < boxes.length; j++) {
|
|
889
|
+
for (let k = 0; k < boxes.length; k++) {
|
|
890
|
+
if (j === k) {
|
|
891
|
+
continue;
|
|
892
|
+
}
|
|
893
|
+
// check if x, y, width, height are the same with 10 pixels tolerance
|
|
894
|
+
if (Math.abs(boxes[j].box.x - boxes[k].box.x) < 10 &&
|
|
895
|
+
Math.abs(boxes[j].box.y - boxes[k].box.y) < 10 &&
|
|
896
|
+
Math.abs(boxes[j].box.width - boxes[k].box.width) < 10 &&
|
|
897
|
+
Math.abs(boxes[j].box.height - boxes[k].box.height) < 10) {
|
|
898
|
+
// as the element is not unique, will remove it
|
|
899
|
+
boxes.splice(k, 1);
|
|
900
|
+
k--;
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
if (boxes.length === 1) {
|
|
905
|
+
result.foundElements.push({
|
|
906
|
+
locator: boxes[0].locator.first(),
|
|
907
|
+
box: boxes[0].box,
|
|
908
|
+
unique: true,
|
|
909
|
+
});
|
|
910
|
+
result.locatorIndex = i;
|
|
911
|
+
}
|
|
912
|
+
else if (logErrors) {
|
|
913
|
+
info.failCause.foundMultiple = true;
|
|
914
|
+
if (info.locatorLog) {
|
|
915
|
+
info.locatorLog.setLocatorSearchStatus(JSON.stringify(locatorsGroup[i]), "FOUND_NOT_UNIQUE");
|
|
916
|
+
}
|
|
831
917
|
}
|
|
832
918
|
}
|
|
833
919
|
}
|
|
@@ -875,7 +961,7 @@ class StableBrowser {
|
|
|
875
961
|
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
876
962
|
}
|
|
877
963
|
finally {
|
|
878
|
-
_commandFinally(state, this);
|
|
964
|
+
await _commandFinally(state, this);
|
|
879
965
|
}
|
|
880
966
|
}
|
|
881
967
|
}
|
|
@@ -924,7 +1010,7 @@ class StableBrowser {
|
|
|
924
1010
|
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
925
1011
|
}
|
|
926
1012
|
finally {
|
|
927
|
-
_commandFinally(state, this);
|
|
1013
|
+
await _commandFinally(state, this);
|
|
928
1014
|
}
|
|
929
1015
|
}
|
|
930
1016
|
}
|
|
@@ -945,19 +1031,7 @@ class StableBrowser {
|
|
|
945
1031
|
};
|
|
946
1032
|
try {
|
|
947
1033
|
await _preCommand(state, this);
|
|
948
|
-
|
|
949
|
-
// state.selectors.locators[0].text = state.options.context;
|
|
950
|
-
// }
|
|
951
|
-
try {
|
|
952
|
-
await state.element.click();
|
|
953
|
-
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
954
|
-
}
|
|
955
|
-
catch (e) {
|
|
956
|
-
// await this.closeUnexpectedPopups();
|
|
957
|
-
state.element = await this._locate(selectors, state.info, _params);
|
|
958
|
-
await state.element.dispatchEvent("click");
|
|
959
|
-
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
960
|
-
}
|
|
1034
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
961
1035
|
await this.waitForPageLoad();
|
|
962
1036
|
return state.info;
|
|
963
1037
|
}
|
|
@@ -965,7 +1039,7 @@ class StableBrowser {
|
|
|
965
1039
|
await _commandError(state, e, this);
|
|
966
1040
|
}
|
|
967
1041
|
finally {
|
|
968
|
-
_commandFinally(state, this);
|
|
1042
|
+
await _commandFinally(state, this);
|
|
969
1043
|
}
|
|
970
1044
|
}
|
|
971
1045
|
async waitForElement(selectors, _params, options = {}, world = null) {
|
|
@@ -996,7 +1070,7 @@ class StableBrowser {
|
|
|
996
1070
|
// await _commandError(state, e, this);
|
|
997
1071
|
}
|
|
998
1072
|
finally {
|
|
999
|
-
_commandFinally(state, this);
|
|
1073
|
+
await _commandFinally(state, this);
|
|
1000
1074
|
}
|
|
1001
1075
|
return found;
|
|
1002
1076
|
}
|
|
@@ -1020,7 +1094,7 @@ class StableBrowser {
|
|
|
1020
1094
|
try {
|
|
1021
1095
|
// if (world && world.screenshot && !world.screenshotPath) {
|
|
1022
1096
|
// console.log(`Highlighting while running from recorder`);
|
|
1023
|
-
await this._highlightElements(element);
|
|
1097
|
+
await this._highlightElements(state.element);
|
|
1024
1098
|
await state.element.setChecked(checked);
|
|
1025
1099
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1026
1100
|
// await this._unHighlightElements(element);
|
|
@@ -1047,7 +1121,7 @@ class StableBrowser {
|
|
|
1047
1121
|
await _commandError(state, e, this);
|
|
1048
1122
|
}
|
|
1049
1123
|
finally {
|
|
1050
|
-
_commandFinally(state, this);
|
|
1124
|
+
await _commandFinally(state, this);
|
|
1051
1125
|
}
|
|
1052
1126
|
}
|
|
1053
1127
|
async hover(selectors, _params, options = {}, world = null) {
|
|
@@ -1064,19 +1138,7 @@ class StableBrowser {
|
|
|
1064
1138
|
};
|
|
1065
1139
|
try {
|
|
1066
1140
|
await _preCommand(state, this);
|
|
1067
|
-
|
|
1068
|
-
await state.element.hover();
|
|
1069
|
-
// await _screenshot(state, this);
|
|
1070
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1071
|
-
}
|
|
1072
|
-
catch (e) {
|
|
1073
|
-
//await this.closeUnexpectedPopups();
|
|
1074
|
-
state.info.log += "hover failed, will try again" + "\n";
|
|
1075
|
-
state.element = await this._locate(selectors, state.info, _params);
|
|
1076
|
-
await state.element.hover({ timeout: 10000 });
|
|
1077
|
-
// await _screenshot(state, this);
|
|
1078
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1079
|
-
}
|
|
1141
|
+
await performAction("hover", state.element, options, this, state, _params);
|
|
1080
1142
|
await _screenshot(state, this);
|
|
1081
1143
|
await this.waitForPageLoad();
|
|
1082
1144
|
return state.info;
|
|
@@ -1085,7 +1147,7 @@ class StableBrowser {
|
|
|
1085
1147
|
await _commandError(state, e, this);
|
|
1086
1148
|
}
|
|
1087
1149
|
finally {
|
|
1088
|
-
_commandFinally(state, this);
|
|
1150
|
+
await _commandFinally(state, this);
|
|
1089
1151
|
}
|
|
1090
1152
|
}
|
|
1091
1153
|
async selectOption(selectors, values, _params = null, options = {}, world = null) {
|
|
@@ -1121,7 +1183,7 @@ class StableBrowser {
|
|
|
1121
1183
|
await _commandError(state, e, this);
|
|
1122
1184
|
}
|
|
1123
1185
|
finally {
|
|
1124
|
-
_commandFinally(state, this);
|
|
1186
|
+
await _commandFinally(state, this);
|
|
1125
1187
|
}
|
|
1126
1188
|
}
|
|
1127
1189
|
async type(_value, _params = null, options = {}, world = null) {
|
|
@@ -1167,7 +1229,7 @@ class StableBrowser {
|
|
|
1167
1229
|
await _commandError(state, e, this);
|
|
1168
1230
|
}
|
|
1169
1231
|
finally {
|
|
1170
|
-
_commandFinally(state, this);
|
|
1232
|
+
await _commandFinally(state, this);
|
|
1171
1233
|
}
|
|
1172
1234
|
}
|
|
1173
1235
|
async setInputValue(selectors, value, _params = null, options = {}, world = null) {
|
|
@@ -1203,7 +1265,7 @@ class StableBrowser {
|
|
|
1203
1265
|
await _commandError(state, e, this);
|
|
1204
1266
|
}
|
|
1205
1267
|
finally {
|
|
1206
|
-
_commandFinally(state, this);
|
|
1268
|
+
await _commandFinally(state, this);
|
|
1207
1269
|
}
|
|
1208
1270
|
}
|
|
1209
1271
|
async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
|
|
@@ -1223,7 +1285,7 @@ class StableBrowser {
|
|
|
1223
1285
|
try {
|
|
1224
1286
|
await _preCommand(state, this);
|
|
1225
1287
|
try {
|
|
1226
|
-
await state.element
|
|
1288
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
1227
1289
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1228
1290
|
if (format) {
|
|
1229
1291
|
state.value = dayjs(state.value).format(format);
|
|
@@ -1272,7 +1334,7 @@ class StableBrowser {
|
|
|
1272
1334
|
await _commandError(state, e, this);
|
|
1273
1335
|
}
|
|
1274
1336
|
finally {
|
|
1275
|
-
_commandFinally(state, this);
|
|
1337
|
+
await _commandFinally(state, this);
|
|
1276
1338
|
}
|
|
1277
1339
|
}
|
|
1278
1340
|
async clickType(selectors, _value, enter = false, _params = null, options = {}, world = null) {
|
|
@@ -1291,6 +1353,9 @@ class StableBrowser {
|
|
|
1291
1353
|
operation: "clickType",
|
|
1292
1354
|
log: "***** clickType on " + selectors.element_name + " with value " + maskValue(_value) + "*****\n",
|
|
1293
1355
|
};
|
|
1356
|
+
if (!options) {
|
|
1357
|
+
options = {};
|
|
1358
|
+
}
|
|
1294
1359
|
if (newValue !== _value) {
|
|
1295
1360
|
//this.logger.info(_value + "=" + newValue);
|
|
1296
1361
|
_value = newValue;
|
|
@@ -1298,7 +1363,7 @@ class StableBrowser {
|
|
|
1298
1363
|
try {
|
|
1299
1364
|
await _preCommand(state, this);
|
|
1300
1365
|
state.info.value = _value;
|
|
1301
|
-
if (
|
|
1366
|
+
if (!options.press) {
|
|
1302
1367
|
try {
|
|
1303
1368
|
let currentValue = await state.element.inputValue();
|
|
1304
1369
|
if (currentValue) {
|
|
@@ -1309,13 +1374,9 @@ class StableBrowser {
|
|
|
1309
1374
|
this.logger.info("unable to clear input value");
|
|
1310
1375
|
}
|
|
1311
1376
|
}
|
|
1312
|
-
if (options
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
}
|
|
1316
|
-
catch (e) {
|
|
1317
|
-
await state.element.dispatchEvent("click");
|
|
1318
|
-
}
|
|
1377
|
+
if (options.press) {
|
|
1378
|
+
options.timeout = 5000;
|
|
1379
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
1319
1380
|
}
|
|
1320
1381
|
else {
|
|
1321
1382
|
try {
|
|
@@ -1373,7 +1434,7 @@ class StableBrowser {
|
|
|
1373
1434
|
await _commandError(state, e, this);
|
|
1374
1435
|
}
|
|
1375
1436
|
finally {
|
|
1376
|
-
_commandFinally(state, this);
|
|
1437
|
+
await _commandFinally(state, this);
|
|
1377
1438
|
}
|
|
1378
1439
|
}
|
|
1379
1440
|
async fill(selectors, value, enter = false, _params = null, options = {}, world = null) {
|
|
@@ -1403,7 +1464,42 @@ class StableBrowser {
|
|
|
1403
1464
|
await _commandError(state, e, this);
|
|
1404
1465
|
}
|
|
1405
1466
|
finally {
|
|
1406
|
-
_commandFinally(state, this);
|
|
1467
|
+
await _commandFinally(state, this);
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
async setInputFiles(selectors, files, _params = null, options = {}, world = null) {
|
|
1471
|
+
const state = {
|
|
1472
|
+
selectors,
|
|
1473
|
+
_params,
|
|
1474
|
+
files,
|
|
1475
|
+
value: '"' + files.join('", "') + '"',
|
|
1476
|
+
options,
|
|
1477
|
+
world,
|
|
1478
|
+
type: Types.SET_INPUT_FILES,
|
|
1479
|
+
text: `Set input files`,
|
|
1480
|
+
_text: `Set input files on ${selectors.element_name}`,
|
|
1481
|
+
operation: "setInputFiles",
|
|
1482
|
+
log: "***** set input files " + selectors.element_name + " *****\n",
|
|
1483
|
+
};
|
|
1484
|
+
const uploadsFolder = this.configuration.uploadsFolder ?? "data/uploads";
|
|
1485
|
+
try {
|
|
1486
|
+
await _preCommand(state, this);
|
|
1487
|
+
for (let i = 0; i < files.length; i++) {
|
|
1488
|
+
const file = files[i];
|
|
1489
|
+
const filePath = path.join(uploadsFolder, file);
|
|
1490
|
+
if (!fs.existsSync(filePath)) {
|
|
1491
|
+
throw new Error(`File not found: ${filePath}`);
|
|
1492
|
+
}
|
|
1493
|
+
state.files[i] = filePath;
|
|
1494
|
+
}
|
|
1495
|
+
await state.element.setInputFiles(files);
|
|
1496
|
+
return state.info;
|
|
1497
|
+
}
|
|
1498
|
+
catch (e) {
|
|
1499
|
+
await _commandError(state, e, this);
|
|
1500
|
+
}
|
|
1501
|
+
finally {
|
|
1502
|
+
await _commandFinally(state, this);
|
|
1407
1503
|
}
|
|
1408
1504
|
}
|
|
1409
1505
|
async getText(selectors, _params = null, options = {}, info = {}, world = null) {
|
|
@@ -1519,7 +1615,7 @@ class StableBrowser {
|
|
|
1519
1615
|
await _commandError(state, e, this);
|
|
1520
1616
|
}
|
|
1521
1617
|
finally {
|
|
1522
|
-
_commandFinally(state, this);
|
|
1618
|
+
await _commandFinally(state, this);
|
|
1523
1619
|
}
|
|
1524
1620
|
}
|
|
1525
1621
|
async containsText(selectors, text, climb, _params = null, options = {}, world = null) {
|
|
@@ -1554,7 +1650,7 @@ class StableBrowser {
|
|
|
1554
1650
|
while (Date.now() - startTime < timeout) {
|
|
1555
1651
|
try {
|
|
1556
1652
|
await _preCommand(state, this);
|
|
1557
|
-
foundObj = await this._getText(selectors, climb, _params, { timeout:
|
|
1653
|
+
foundObj = await this._getText(selectors, climb, _params, { timeout: 3000 }, state.info, world);
|
|
1558
1654
|
if (foundObj && foundObj.element) {
|
|
1559
1655
|
await this.scrollIfNeeded(foundObj.element, state.info);
|
|
1560
1656
|
}
|
|
@@ -1596,7 +1692,79 @@ class StableBrowser {
|
|
|
1596
1692
|
throw e;
|
|
1597
1693
|
}
|
|
1598
1694
|
finally {
|
|
1599
|
-
_commandFinally(state, this);
|
|
1695
|
+
await _commandFinally(state, this);
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
async snapshotValidation(frameSelectors, referanceSnapshot, _params = null, options = {}, world = null) {
|
|
1699
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1700
|
+
const startTime = Date.now();
|
|
1701
|
+
const state = {
|
|
1702
|
+
_params,
|
|
1703
|
+
value: referanceSnapshot,
|
|
1704
|
+
options,
|
|
1705
|
+
world,
|
|
1706
|
+
locate: false,
|
|
1707
|
+
scroll: false,
|
|
1708
|
+
screenshot: true,
|
|
1709
|
+
highlight: false,
|
|
1710
|
+
type: Types.SNAPSHOT_VALIDATION,
|
|
1711
|
+
text: `verify snapshot: ${referanceSnapshot}`,
|
|
1712
|
+
operation: "snapshotValidation",
|
|
1713
|
+
log: "***** verify snapshot *****\n",
|
|
1714
|
+
};
|
|
1715
|
+
if (!referanceSnapshot) {
|
|
1716
|
+
throw new Error("referanceSnapshot is null");
|
|
1717
|
+
}
|
|
1718
|
+
let text = null;
|
|
1719
|
+
if (fs.existsSync(path.join(this.project_path, "data", "snapshots", this.context.environment.name, referanceSnapshot + ".yml"))) {
|
|
1720
|
+
text = fs.readFileSync(path.join(this.project_path, "data", "snapshots", this.context.environment.name, referanceSnapshot + ".yml"), "utf8");
|
|
1721
|
+
}
|
|
1722
|
+
else if (fs.existsSync(path.join(this.project_path, "data", "snapshots", this.context.environment.name, referanceSnapshot + ".yaml"))) {
|
|
1723
|
+
text = fs.readFileSync(path.join(this.project_path, "data", "snapshots", this.context.environment.name, referanceSnapshot + ".yaml"), "utf8");
|
|
1724
|
+
}
|
|
1725
|
+
else if (referanceSnapshot.startsWith("yaml:")) {
|
|
1726
|
+
text = referanceSnapshot.substring(5);
|
|
1727
|
+
}
|
|
1728
|
+
else {
|
|
1729
|
+
throw new Error("referenceSnapshot file not found: " + referanceSnapshot);
|
|
1730
|
+
}
|
|
1731
|
+
state.text = text;
|
|
1732
|
+
const newValue = await this._replaceWithLocalData(text, world);
|
|
1733
|
+
await _preCommand(state, this);
|
|
1734
|
+
let foundObj = null;
|
|
1735
|
+
try {
|
|
1736
|
+
let matchResult = null;
|
|
1737
|
+
while (Date.now() - startTime < timeout) {
|
|
1738
|
+
try {
|
|
1739
|
+
let scope = null;
|
|
1740
|
+
if (!frameSelectors) {
|
|
1741
|
+
scope = this.page;
|
|
1742
|
+
}
|
|
1743
|
+
else {
|
|
1744
|
+
scope = await this._findFrameScope(frameSelectors, timeout, state.info);
|
|
1745
|
+
}
|
|
1746
|
+
const snapshot = await scope.locator("body").ariaSnapshot({ timeout });
|
|
1747
|
+
matchResult = snapshotValidation(snapshot, newValue, referanceSnapshot);
|
|
1748
|
+
if (matchResult.errorLine !== -1) {
|
|
1749
|
+
throw new Error("Snapshot validation failed at line " + matchResult.errorLineText);
|
|
1750
|
+
}
|
|
1751
|
+
// highlight and screenshot
|
|
1752
|
+
return state.info;
|
|
1753
|
+
}
|
|
1754
|
+
catch (e) {
|
|
1755
|
+
// Log error but continue retrying until timeout is reached
|
|
1756
|
+
//this.logger.warn("Retrying snapshot validation due to: " + e.message);
|
|
1757
|
+
}
|
|
1758
|
+
await new Promise((resolve) => setTimeout(resolve, 2000)); // Wait 1 second before retrying
|
|
1759
|
+
}
|
|
1760
|
+
throw new Error("No snapshot match " + matchResult?.errorLineText);
|
|
1761
|
+
}
|
|
1762
|
+
catch (e) {
|
|
1763
|
+
await _commandError(state, e, this);
|
|
1764
|
+
throw e;
|
|
1765
|
+
}
|
|
1766
|
+
finally {
|
|
1767
|
+
await _commandFinally(state, this);
|
|
1600
1768
|
}
|
|
1601
1769
|
}
|
|
1602
1770
|
async waitForUserInput(message, world = null) {
|
|
@@ -1634,6 +1802,15 @@ class StableBrowser {
|
|
|
1634
1802
|
// save the data to the file
|
|
1635
1803
|
fs.writeFileSync(dataFile, JSON.stringify(data, null, 2));
|
|
1636
1804
|
}
|
|
1805
|
+
overwriteTestData(testData, world = null) {
|
|
1806
|
+
if (!testData) {
|
|
1807
|
+
return;
|
|
1808
|
+
}
|
|
1809
|
+
// if data file exists, load it
|
|
1810
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1811
|
+
// save the data to the file
|
|
1812
|
+
fs.writeFileSync(dataFile, JSON.stringify(testData, null, 2));
|
|
1813
|
+
}
|
|
1637
1814
|
_getDataFilePath(fileName) {
|
|
1638
1815
|
let dataFile = path.join(this.project_path, "data", fileName);
|
|
1639
1816
|
if (fs.existsSync(dataFile)) {
|
|
@@ -1886,7 +2063,7 @@ class StableBrowser {
|
|
|
1886
2063
|
await _commandError(state, e, this);
|
|
1887
2064
|
}
|
|
1888
2065
|
finally {
|
|
1889
|
-
_commandFinally(state, this);
|
|
2066
|
+
await _commandFinally(state, this);
|
|
1890
2067
|
}
|
|
1891
2068
|
}
|
|
1892
2069
|
async extractAttribute(selectors, attribute, variable, _params = null, options = {}, world = null) {
|
|
@@ -1917,10 +2094,31 @@ class StableBrowser {
|
|
|
1917
2094
|
case "value":
|
|
1918
2095
|
state.value = await state.element.inputValue();
|
|
1919
2096
|
break;
|
|
2097
|
+
case "text":
|
|
2098
|
+
state.value = await state.element.textContent();
|
|
2099
|
+
break;
|
|
1920
2100
|
default:
|
|
1921
2101
|
state.value = await state.element.getAttribute(attribute);
|
|
1922
2102
|
break;
|
|
1923
2103
|
}
|
|
2104
|
+
if (options !== null) {
|
|
2105
|
+
if (options.regex && options.regex !== "") {
|
|
2106
|
+
// Construct a regex pattern from the provided string
|
|
2107
|
+
const regex = options.regex.slice(1, -1);
|
|
2108
|
+
const regexPattern = new RegExp(regex, "g");
|
|
2109
|
+
const matches = state.value.match(regexPattern);
|
|
2110
|
+
if (matches) {
|
|
2111
|
+
let newValue = "";
|
|
2112
|
+
for (const match of matches) {
|
|
2113
|
+
newValue += match;
|
|
2114
|
+
}
|
|
2115
|
+
state.value = newValue;
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2118
|
+
if (options.trimSpaces && options.trimSpaces === true) {
|
|
2119
|
+
state.value = state.value.trim();
|
|
2120
|
+
}
|
|
2121
|
+
}
|
|
1924
2122
|
state.info.value = state.value;
|
|
1925
2123
|
this.setTestData({ [variable]: state.value }, world);
|
|
1926
2124
|
this.logger.info("set test data: " + variable + "=" + state.value);
|
|
@@ -1931,7 +2129,7 @@ class StableBrowser {
|
|
|
1931
2129
|
await _commandError(state, e, this);
|
|
1932
2130
|
}
|
|
1933
2131
|
finally {
|
|
1934
|
-
_commandFinally(state, this);
|
|
2132
|
+
await _commandFinally(state, this);
|
|
1935
2133
|
}
|
|
1936
2134
|
}
|
|
1937
2135
|
async verifyAttribute(selectors, attribute, value, _params = null, options = {}, world = null) {
|
|
@@ -1956,12 +2154,15 @@ class StableBrowser {
|
|
|
1956
2154
|
let expectedValue;
|
|
1957
2155
|
try {
|
|
1958
2156
|
await _preCommand(state, this);
|
|
1959
|
-
expectedValue = state.value;
|
|
2157
|
+
expectedValue = await replaceWithLocalTestData(state.value, world);
|
|
1960
2158
|
state.info.expectedValue = expectedValue;
|
|
1961
2159
|
switch (attribute) {
|
|
1962
2160
|
case "innerText":
|
|
1963
2161
|
val = String(await state.element.innerText());
|
|
1964
2162
|
break;
|
|
2163
|
+
case "text":
|
|
2164
|
+
val = String(await state.element.textContent());
|
|
2165
|
+
break;
|
|
1965
2166
|
case "value":
|
|
1966
2167
|
val = String(await state.element.inputValue());
|
|
1967
2168
|
break;
|
|
@@ -1983,17 +2184,42 @@ class StableBrowser {
|
|
|
1983
2184
|
let regex;
|
|
1984
2185
|
if (expectedValue.startsWith("/") && expectedValue.endsWith("/")) {
|
|
1985
2186
|
const patternBody = expectedValue.slice(1, -1);
|
|
1986
|
-
|
|
2187
|
+
const processedPattern = patternBody.replace(/\n/g, ".*");
|
|
2188
|
+
regex = new RegExp(processedPattern, "gs");
|
|
2189
|
+
state.info.regex = true;
|
|
1987
2190
|
}
|
|
1988
2191
|
else {
|
|
1989
2192
|
const escapedPattern = expectedValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1990
2193
|
regex = new RegExp(escapedPattern, "g");
|
|
1991
2194
|
}
|
|
1992
|
-
if (
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
2195
|
+
if (attribute === "innerText") {
|
|
2196
|
+
if (state.info.regex) {
|
|
2197
|
+
if (!regex.test(val)) {
|
|
2198
|
+
let errorMessage = `The ${attribute} attribute has a value of "${val}", but the expected value is "${expectedValue}"`;
|
|
2199
|
+
state.info.failCause.assertionFailed = true;
|
|
2200
|
+
state.info.failCause.lastError = errorMessage;
|
|
2201
|
+
throw new Error(errorMessage);
|
|
2202
|
+
}
|
|
2203
|
+
}
|
|
2204
|
+
else {
|
|
2205
|
+
const valLines = val.split("\n");
|
|
2206
|
+
const expectedLines = expectedValue.split("\n");
|
|
2207
|
+
const isPart = expectedLines.every((expectedLine) => valLines.some((valLine) => valLine === expectedLine));
|
|
2208
|
+
if (!isPart) {
|
|
2209
|
+
let errorMessage = `The ${attribute} attribute has a value of "${val}", but the expected value is "${expectedValue}"`;
|
|
2210
|
+
state.info.failCause.assertionFailed = true;
|
|
2211
|
+
state.info.failCause.lastError = errorMessage;
|
|
2212
|
+
throw new Error(errorMessage);
|
|
2213
|
+
}
|
|
2214
|
+
}
|
|
2215
|
+
}
|
|
2216
|
+
else {
|
|
2217
|
+
if (!val.match(regex)) {
|
|
2218
|
+
let errorMessage = `The ${attribute} attribute has a value of "${val}", but the expected value is "${expectedValue}"`;
|
|
2219
|
+
state.info.failCause.assertionFailed = true;
|
|
2220
|
+
state.info.failCause.lastError = errorMessage;
|
|
2221
|
+
throw new Error(errorMessage);
|
|
2222
|
+
}
|
|
1997
2223
|
}
|
|
1998
2224
|
return state.info;
|
|
1999
2225
|
}
|
|
@@ -2001,7 +2227,7 @@ class StableBrowser {
|
|
|
2001
2227
|
await _commandError(state, e, this);
|
|
2002
2228
|
}
|
|
2003
2229
|
finally {
|
|
2004
|
-
_commandFinally(state, this);
|
|
2230
|
+
await _commandFinally(state, this);
|
|
2005
2231
|
}
|
|
2006
2232
|
}
|
|
2007
2233
|
async extractEmailData(emailAddress, options, world) {
|
|
@@ -2161,54 +2387,6 @@ class StableBrowser {
|
|
|
2161
2387
|
console.debug(error);
|
|
2162
2388
|
}
|
|
2163
2389
|
}
|
|
2164
|
-
// async _unhighlightElements(scope, css) {
|
|
2165
|
-
// try {
|
|
2166
|
-
// if (!scope) {
|
|
2167
|
-
// return;
|
|
2168
|
-
// }
|
|
2169
|
-
// if (!css) {
|
|
2170
|
-
// scope
|
|
2171
|
-
// .evaluate((node) => {
|
|
2172
|
-
// if (node && node.style) {
|
|
2173
|
-
// if (!node.__previousOutline) {
|
|
2174
|
-
// node.style.outline = "";
|
|
2175
|
-
// } else {
|
|
2176
|
-
// node.style.outline = node.__previousOutline;
|
|
2177
|
-
// }
|
|
2178
|
-
// }
|
|
2179
|
-
// })
|
|
2180
|
-
// .then(() => {})
|
|
2181
|
-
// .catch((e) => {
|
|
2182
|
-
// // console.log(`Error while unhighlighting node ${JSON.stringify(scope)}: ${e}`);
|
|
2183
|
-
// });
|
|
2184
|
-
// } else {
|
|
2185
|
-
// scope
|
|
2186
|
-
// .evaluate(([css]) => {
|
|
2187
|
-
// if (!css) {
|
|
2188
|
-
// return;
|
|
2189
|
-
// }
|
|
2190
|
-
// let elements = Array.from(document.querySelectorAll(css));
|
|
2191
|
-
// for (i = 0; i < elements.length; i++) {
|
|
2192
|
-
// let element = elements[i];
|
|
2193
|
-
// if (!element.style) {
|
|
2194
|
-
// return;
|
|
2195
|
-
// }
|
|
2196
|
-
// if (!element.__previousOutline) {
|
|
2197
|
-
// element.style.outline = "";
|
|
2198
|
-
// } else {
|
|
2199
|
-
// element.style.outline = element.__previousOutline;
|
|
2200
|
-
// }
|
|
2201
|
-
// }
|
|
2202
|
-
// })
|
|
2203
|
-
// .then(() => {})
|
|
2204
|
-
// .catch((e) => {
|
|
2205
|
-
// // console.error(`Error while unhighlighting element in css: ${e}`);
|
|
2206
|
-
// });
|
|
2207
|
-
// }
|
|
2208
|
-
// } catch (error) {
|
|
2209
|
-
// // console.debug(error);
|
|
2210
|
-
// }
|
|
2211
|
-
// }
|
|
2212
2390
|
async verifyPagePath(pathPart, options = {}, world = null) {
|
|
2213
2391
|
const startTime = Date.now();
|
|
2214
2392
|
let error = null;
|
|
@@ -2246,7 +2424,7 @@ class StableBrowser {
|
|
|
2246
2424
|
Object.assign(e, { info: info });
|
|
2247
2425
|
error = e;
|
|
2248
2426
|
// throw e;
|
|
2249
|
-
await _commandError({ text: "verifyPagePath", operation: "verifyPagePath", pathPart, info }, e, this);
|
|
2427
|
+
await _commandError({ text: "verifyPagePath", operation: "verifyPagePath", pathPart, info, throwError: true }, e, this);
|
|
2250
2428
|
}
|
|
2251
2429
|
finally {
|
|
2252
2430
|
const endTime = Date.now();
|
|
@@ -2271,27 +2449,89 @@ class StableBrowser {
|
|
|
2271
2449
|
});
|
|
2272
2450
|
}
|
|
2273
2451
|
}
|
|
2274
|
-
async
|
|
2452
|
+
async verifyPageTitle(title, options = {}, world = null) {
|
|
2453
|
+
const startTime = Date.now();
|
|
2454
|
+
let error = null;
|
|
2455
|
+
let screenshotId = null;
|
|
2456
|
+
let screenshotPath = null;
|
|
2457
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2458
|
+
const info = {};
|
|
2459
|
+
info.log = "***** verify page title " + title + " *****\n";
|
|
2460
|
+
info.operation = "verifyPageTitle";
|
|
2461
|
+
const newValue = await this._replaceWithLocalData(title, world);
|
|
2462
|
+
if (newValue !== title) {
|
|
2463
|
+
this.logger.info(title + "=" + newValue);
|
|
2464
|
+
title = newValue;
|
|
2465
|
+
}
|
|
2466
|
+
info.title = title;
|
|
2467
|
+
try {
|
|
2468
|
+
for (let i = 0; i < 30; i++) {
|
|
2469
|
+
const foundTitle = await this.page.title();
|
|
2470
|
+
if (!foundTitle.includes(title)) {
|
|
2471
|
+
if (i === 29) {
|
|
2472
|
+
throw new Error(`url ${foundTitle} doesn't contain ${title}`);
|
|
2473
|
+
}
|
|
2474
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2475
|
+
continue;
|
|
2476
|
+
}
|
|
2477
|
+
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2478
|
+
return info;
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2481
|
+
catch (e) {
|
|
2482
|
+
//await this.closeUnexpectedPopups();
|
|
2483
|
+
this.logger.error("verify page title failed " + info.log);
|
|
2484
|
+
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2485
|
+
info.screenshotPath = screenshotPath;
|
|
2486
|
+
Object.assign(e, { info: info });
|
|
2487
|
+
error = e;
|
|
2488
|
+
// throw e;
|
|
2489
|
+
await _commandError({ text: "verifyPageTitle", operation: "verifyPageTitle", title, info, throwError: true }, e, this);
|
|
2490
|
+
}
|
|
2491
|
+
finally {
|
|
2492
|
+
const endTime = Date.now();
|
|
2493
|
+
_reportToWorld(world, {
|
|
2494
|
+
type: Types.VERIFY_PAGE_PATH,
|
|
2495
|
+
text: "Verify page title",
|
|
2496
|
+
_text: "Verify the page title contains " + title,
|
|
2497
|
+
screenshotId,
|
|
2498
|
+
result: error
|
|
2499
|
+
? {
|
|
2500
|
+
status: "FAILED",
|
|
2501
|
+
startTime,
|
|
2502
|
+
endTime,
|
|
2503
|
+
message: error?.message,
|
|
2504
|
+
}
|
|
2505
|
+
: {
|
|
2506
|
+
status: "PASSED",
|
|
2507
|
+
startTime,
|
|
2508
|
+
endTime,
|
|
2509
|
+
},
|
|
2510
|
+
info: info,
|
|
2511
|
+
});
|
|
2512
|
+
}
|
|
2513
|
+
}
|
|
2514
|
+
async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state, partial = true, ignoreCase = false) {
|
|
2275
2515
|
const frames = this.page.frames();
|
|
2276
2516
|
let results = [];
|
|
2277
|
-
let ignoreCase = false;
|
|
2517
|
+
// let ignoreCase = false;
|
|
2278
2518
|
for (let i = 0; i < frames.length; i++) {
|
|
2279
2519
|
if (dateAlternatives.date) {
|
|
2280
2520
|
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2281
|
-
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false,
|
|
2521
|
+
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, partial, ignoreCase, {});
|
|
2282
2522
|
result.frame = frames[i];
|
|
2283
2523
|
results.push(result);
|
|
2284
2524
|
}
|
|
2285
2525
|
}
|
|
2286
2526
|
else if (numberAlternatives.number) {
|
|
2287
2527
|
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2288
|
-
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false,
|
|
2528
|
+
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, partial, ignoreCase, {});
|
|
2289
2529
|
result.frame = frames[i];
|
|
2290
2530
|
results.push(result);
|
|
2291
2531
|
}
|
|
2292
2532
|
}
|
|
2293
2533
|
else {
|
|
2294
|
-
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false,
|
|
2534
|
+
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, partial, ignoreCase, {});
|
|
2295
2535
|
result.frame = frames[i];
|
|
2296
2536
|
results.push(result);
|
|
2297
2537
|
}
|
|
@@ -2310,11 +2550,14 @@ class StableBrowser {
|
|
|
2310
2550
|
scroll: false,
|
|
2311
2551
|
highlight: false,
|
|
2312
2552
|
type: Types.VERIFY_PAGE_CONTAINS_TEXT,
|
|
2313
|
-
text: `Verify text exists in page`,
|
|
2553
|
+
text: `Verify the text '${maskValue(text)}' exists in page`,
|
|
2314
2554
|
_text: `Verify the text '${text}' exists in page`,
|
|
2315
2555
|
operation: "verifyTextExistInPage",
|
|
2316
2556
|
log: "***** verify text " + text + " exists in page *****\n",
|
|
2317
2557
|
};
|
|
2558
|
+
if (testForRegex(text)) {
|
|
2559
|
+
text = text.replace(/\\"/g, '"');
|
|
2560
|
+
}
|
|
2318
2561
|
const timeout = this._getFindElementTimeout(options);
|
|
2319
2562
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2320
2563
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
@@ -2385,7 +2628,7 @@ class StableBrowser {
|
|
|
2385
2628
|
await _commandError(state, e, this);
|
|
2386
2629
|
}
|
|
2387
2630
|
finally {
|
|
2388
|
-
_commandFinally(state, this);
|
|
2631
|
+
await _commandFinally(state, this);
|
|
2389
2632
|
}
|
|
2390
2633
|
}
|
|
2391
2634
|
async waitForTextToDisappear(text, options = {}, world = null) {
|
|
@@ -2398,11 +2641,14 @@ class StableBrowser {
|
|
|
2398
2641
|
scroll: false,
|
|
2399
2642
|
highlight: false,
|
|
2400
2643
|
type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
|
|
2401
|
-
text: `Verify text does not exist in page`,
|
|
2644
|
+
text: `Verify the text '${maskValue(text)}' does not exist in page`,
|
|
2402
2645
|
_text: `Verify the text '${text}' does not exist in page`,
|
|
2403
2646
|
operation: "verifyTextNotExistInPage",
|
|
2404
2647
|
log: "***** verify text " + text + " does not exist in page *****\n",
|
|
2405
2648
|
};
|
|
2649
|
+
if (testForRegex(text)) {
|
|
2650
|
+
text = text.replace(/\\"/g, '"');
|
|
2651
|
+
}
|
|
2406
2652
|
const timeout = this._getFindElementTimeout(options);
|
|
2407
2653
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2408
2654
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
@@ -2439,7 +2685,7 @@ class StableBrowser {
|
|
|
2439
2685
|
await _commandError(state, e, this);
|
|
2440
2686
|
}
|
|
2441
2687
|
finally {
|
|
2442
|
-
_commandFinally(state, this);
|
|
2688
|
+
await _commandFinally(state, this);
|
|
2443
2689
|
}
|
|
2444
2690
|
}
|
|
2445
2691
|
async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
|
|
@@ -2481,7 +2727,7 @@ class StableBrowser {
|
|
|
2481
2727
|
};
|
|
2482
2728
|
while (true) {
|
|
2483
2729
|
try {
|
|
2484
|
-
resultWithElementsFound = await this.findTextInAllFrames(
|
|
2730
|
+
resultWithElementsFound = await this.findTextInAllFrames(findDateAlternatives(textAnchor), findNumberAlternatives(textAnchor), textAnchor, state, false);
|
|
2485
2731
|
}
|
|
2486
2732
|
catch (error) {
|
|
2487
2733
|
// ignore
|
|
@@ -2509,7 +2755,7 @@ class StableBrowser {
|
|
|
2509
2755
|
const count = await frame.locator(css).count();
|
|
2510
2756
|
for (let j = 0; j < count; j++) {
|
|
2511
2757
|
const continer = await frame.locator(css).nth(j);
|
|
2512
|
-
const result = await this._locateElementByText(continer, textToVerify, "
|
|
2758
|
+
const result = await this._locateElementByText(continer, textToVerify, "*:not(script, style, head)", false, true, true, {});
|
|
2513
2759
|
if (result.elementCount > 0) {
|
|
2514
2760
|
const dataAttribute = "[data-blinq-id-" + result.randomToken + "]";
|
|
2515
2761
|
await this._highlightElements(frame, dataAttribute);
|
|
@@ -2550,8 +2796,32 @@ class StableBrowser {
|
|
|
2550
2796
|
await _commandError(state, e, this);
|
|
2551
2797
|
}
|
|
2552
2798
|
finally {
|
|
2553
|
-
_commandFinally(state, this);
|
|
2799
|
+
await _commandFinally(state, this);
|
|
2800
|
+
}
|
|
2801
|
+
}
|
|
2802
|
+
async findRelatedTextInAllFrames(textAnchor, climb, textToVerify, params = {}, options = {}, world = null) {
|
|
2803
|
+
const frames = this.page.frames();
|
|
2804
|
+
let results = [];
|
|
2805
|
+
let ignoreCase = false;
|
|
2806
|
+
for (let i = 0; i < frames.length; i++) {
|
|
2807
|
+
const result = await this._locateElementByText(frames[i], textAnchor, "*:not(script, style, head)", false, true, ignoreCase, {});
|
|
2808
|
+
result.frame = frames[i];
|
|
2809
|
+
const climbArray = [];
|
|
2810
|
+
for (let i = 0; i < climb; i++) {
|
|
2811
|
+
climbArray.push("..");
|
|
2812
|
+
}
|
|
2813
|
+
let climbXpath = "xpath=" + climbArray.join("/");
|
|
2814
|
+
const newLocator = `[data-blinq-id-${result.randomToken}] ${climb > 0 ? ">> " + climbXpath : ""} >> internal:text=${testForRegex(textToVerify) ? textToVerify : unEscapeString(textToVerify)}`;
|
|
2815
|
+
const count = await frames[i].locator(newLocator).count();
|
|
2816
|
+
if (count > 0) {
|
|
2817
|
+
result.elementCount = count;
|
|
2818
|
+
result.locator = newLocator;
|
|
2819
|
+
results.push(result);
|
|
2820
|
+
}
|
|
2554
2821
|
}
|
|
2822
|
+
// state.info.results = results;
|
|
2823
|
+
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2824
|
+
return resultWithElementsFound;
|
|
2555
2825
|
}
|
|
2556
2826
|
async visualVerification(text, options = {}, world = null) {
|
|
2557
2827
|
const startTime = Date.now();
|
|
@@ -2869,7 +3139,13 @@ class StableBrowser {
|
|
|
2869
3139
|
}
|
|
2870
3140
|
}
|
|
2871
3141
|
async _replaceWithLocalData(value, world, _decrypt = true, totpWait = true) {
|
|
2872
|
-
|
|
3142
|
+
try {
|
|
3143
|
+
return await replaceWithLocalTestData(value, world, _decrypt, totpWait, this.context, this);
|
|
3144
|
+
}
|
|
3145
|
+
catch (error) {
|
|
3146
|
+
this.logger.debug(error);
|
|
3147
|
+
throw error;
|
|
3148
|
+
}
|
|
2873
3149
|
}
|
|
2874
3150
|
_getLoadTimeout(options) {
|
|
2875
3151
|
let timeout = 15000;
|
|
@@ -2892,6 +3168,7 @@ class StableBrowser {
|
|
|
2892
3168
|
}
|
|
2893
3169
|
async saveStoreState(path = null, world = null) {
|
|
2894
3170
|
const storageState = await this.page.context().storageState();
|
|
3171
|
+
path = await this._replaceWithLocalData(path, this.world);
|
|
2895
3172
|
//const testDataFile = _getDataFile(world, this.context, this);
|
|
2896
3173
|
if (path) {
|
|
2897
3174
|
// save { storageState: storageState } into the path
|
|
@@ -2902,10 +3179,14 @@ class StableBrowser {
|
|
|
2902
3179
|
}
|
|
2903
3180
|
}
|
|
2904
3181
|
async restoreSaveState(path = null, world = null) {
|
|
3182
|
+
path = await this._replaceWithLocalData(path, this.world);
|
|
2905
3183
|
await refreshBrowser(this, path, world);
|
|
2906
3184
|
this.registerEventListeners(this.context);
|
|
2907
3185
|
registerNetworkEvents(this.world, this, this.context, this.page);
|
|
2908
3186
|
registerDownloadEvent(this.page, this.world, this.context);
|
|
3187
|
+
if (this.onRestoreSaveState) {
|
|
3188
|
+
this.onRestoreSaveState(path);
|
|
3189
|
+
}
|
|
2909
3190
|
}
|
|
2910
3191
|
async waitForPageLoad(options = {}, world = null) {
|
|
2911
3192
|
let timeout = this._getLoadTimeout(options);
|
|
@@ -2988,11 +3269,98 @@ class StableBrowser {
|
|
|
2988
3269
|
await _commandError(state, e, this);
|
|
2989
3270
|
}
|
|
2990
3271
|
finally {
|
|
2991
|
-
_commandFinally(state, this);
|
|
3272
|
+
await _commandFinally(state, this);
|
|
3273
|
+
}
|
|
3274
|
+
}
|
|
3275
|
+
async tableCellOperation(headerText, rowText, options, _params, world = null) {
|
|
3276
|
+
let operation = null;
|
|
3277
|
+
if (!options || !options.operation) {
|
|
3278
|
+
throw new Error("operation is not defined");
|
|
3279
|
+
}
|
|
3280
|
+
operation = options.operation;
|
|
3281
|
+
// validate operation is one of the supported operations
|
|
3282
|
+
if (operation != "click" && operation != "hover+click") {
|
|
3283
|
+
throw new Error("operation is not supported");
|
|
3284
|
+
}
|
|
3285
|
+
const state = {
|
|
3286
|
+
options,
|
|
3287
|
+
world,
|
|
3288
|
+
locate: false,
|
|
3289
|
+
scroll: false,
|
|
3290
|
+
highlight: false,
|
|
3291
|
+
type: Types.TABLE_OPERATION,
|
|
3292
|
+
text: `Table operation`,
|
|
3293
|
+
_text: `Table ${operation} operation`,
|
|
3294
|
+
operation: operation,
|
|
3295
|
+
log: "***** Table operation *****\n",
|
|
3296
|
+
};
|
|
3297
|
+
const timeout = this._getFindElementTimeout(options);
|
|
3298
|
+
try {
|
|
3299
|
+
await _preCommand(state, this);
|
|
3300
|
+
const start = Date.now();
|
|
3301
|
+
let cellArea = null;
|
|
3302
|
+
while (true) {
|
|
3303
|
+
try {
|
|
3304
|
+
cellArea = await _findCellArea(headerText, rowText, this, state);
|
|
3305
|
+
if (cellArea) {
|
|
3306
|
+
break;
|
|
3307
|
+
}
|
|
3308
|
+
}
|
|
3309
|
+
catch (e) {
|
|
3310
|
+
// ignore
|
|
3311
|
+
}
|
|
3312
|
+
if (Date.now() - start > timeout) {
|
|
3313
|
+
throw new Error(`Cell not found in table`);
|
|
3314
|
+
}
|
|
3315
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
3316
|
+
}
|
|
3317
|
+
switch (operation) {
|
|
3318
|
+
case "click":
|
|
3319
|
+
if (!options.css) {
|
|
3320
|
+
// will click in the center of the cell
|
|
3321
|
+
let xOffset = 0;
|
|
3322
|
+
let yOffset = 0;
|
|
3323
|
+
if (options.xOffset) {
|
|
3324
|
+
xOffset = options.xOffset;
|
|
3325
|
+
}
|
|
3326
|
+
if (options.yOffset) {
|
|
3327
|
+
yOffset = options.yOffset;
|
|
3328
|
+
}
|
|
3329
|
+
await this.page.mouse.click(cellArea.x + cellArea.width / 2 + xOffset, cellArea.y + cellArea.height / 2 + yOffset);
|
|
3330
|
+
}
|
|
3331
|
+
else {
|
|
3332
|
+
const results = await findElementsInArea(options.css, cellArea, this, options);
|
|
3333
|
+
if (results.length === 0) {
|
|
3334
|
+
throw new Error(`Element not found in cell area`);
|
|
3335
|
+
}
|
|
3336
|
+
state.element = results[0];
|
|
3337
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
3338
|
+
}
|
|
3339
|
+
break;
|
|
3340
|
+
case "hover+click":
|
|
3341
|
+
if (!options.css) {
|
|
3342
|
+
throw new Error("css is not defined");
|
|
3343
|
+
}
|
|
3344
|
+
const results = await findElementsInArea(options.css, cellArea, this, options);
|
|
3345
|
+
if (results.length === 0) {
|
|
3346
|
+
throw new Error(`Element not found in cell area`);
|
|
3347
|
+
}
|
|
3348
|
+
state.element = results[0];
|
|
3349
|
+
await performAction("hover+click", state.element, options, this, state, _params);
|
|
3350
|
+
break;
|
|
3351
|
+
default:
|
|
3352
|
+
throw new Error("operation is not supported");
|
|
3353
|
+
}
|
|
3354
|
+
}
|
|
3355
|
+
catch (e) {
|
|
3356
|
+
await _commandError(state, e, this);
|
|
3357
|
+
}
|
|
3358
|
+
finally {
|
|
3359
|
+
await _commandFinally(state, this);
|
|
2992
3360
|
}
|
|
2993
3361
|
}
|
|
2994
3362
|
saveTestDataAsGlobal(options, world) {
|
|
2995
|
-
const dataFile =
|
|
3363
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
2996
3364
|
process.env.GLOBAL_TEST_DATA_FILE = dataFile;
|
|
2997
3365
|
this.logger.info("Save the scenario test data as global for the following scenarios.");
|
|
2998
3366
|
}
|
|
@@ -3093,7 +3461,39 @@ class StableBrowser {
|
|
|
3093
3461
|
console.log("#-#");
|
|
3094
3462
|
}
|
|
3095
3463
|
}
|
|
3464
|
+
async beforeScenario(world, scenario) {
|
|
3465
|
+
this.beforeScenarioCalled = true;
|
|
3466
|
+
if (scenario && scenario.pickle && scenario.pickle.name) {
|
|
3467
|
+
this.scenarioName = scenario.pickle.name;
|
|
3468
|
+
}
|
|
3469
|
+
if (scenario && scenario.gherkinDocument && scenario.gherkinDocument.feature) {
|
|
3470
|
+
this.featureName = scenario.gherkinDocument.feature.name;
|
|
3471
|
+
}
|
|
3472
|
+
if (this.context) {
|
|
3473
|
+
this.context.examplesRow = extractStepExampleParameters(scenario);
|
|
3474
|
+
}
|
|
3475
|
+
if (this.tags === null && scenario && scenario.pickle && scenario.pickle.tags) {
|
|
3476
|
+
this.tags = scenario.pickle.tags.map((tag) => tag.name);
|
|
3477
|
+
// check if @global_test_data tag is present
|
|
3478
|
+
if (this.tags.includes("@global_test_data")) {
|
|
3479
|
+
this.saveTestDataAsGlobal({}, world);
|
|
3480
|
+
}
|
|
3481
|
+
}
|
|
3482
|
+
// update test data based on feature/scenario
|
|
3483
|
+
let envName = null;
|
|
3484
|
+
if (this.context && this.context.environment) {
|
|
3485
|
+
envName = this.context.environment.name;
|
|
3486
|
+
}
|
|
3487
|
+
if (!process.env.TEMP_RUN) {
|
|
3488
|
+
await getTestData(envName, world, undefined, this.featureName, this.scenarioName);
|
|
3489
|
+
}
|
|
3490
|
+
await loadBrunoParams(this.context, this.context.environment.name);
|
|
3491
|
+
}
|
|
3492
|
+
async afterScenario(world, scenario) { }
|
|
3096
3493
|
async beforeStep(world, step) {
|
|
3494
|
+
if (!this.beforeScenarioCalled) {
|
|
3495
|
+
this.beforeScenario(world, step);
|
|
3496
|
+
}
|
|
3097
3497
|
if (this.stepIndex === undefined) {
|
|
3098
3498
|
this.stepIndex = 0;
|
|
3099
3499
|
}
|
|
@@ -3110,21 +3510,11 @@ class StableBrowser {
|
|
|
3110
3510
|
else {
|
|
3111
3511
|
this.stepName = "step " + this.stepIndex;
|
|
3112
3512
|
}
|
|
3113
|
-
if (this.context) {
|
|
3114
|
-
this.context.examplesRow = extractStepExampleParameters(step);
|
|
3115
|
-
}
|
|
3116
3513
|
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
3117
3514
|
if (this.context.browserObject.context) {
|
|
3118
3515
|
await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
|
|
3119
3516
|
}
|
|
3120
3517
|
}
|
|
3121
|
-
if (this.tags === null && step && step.pickle && step.pickle.tags) {
|
|
3122
|
-
this.tags = step.pickle.tags.map((tag) => tag.name);
|
|
3123
|
-
// check if @global_test_data tag is present
|
|
3124
|
-
if (this.tags.includes("@global_test_data")) {
|
|
3125
|
-
this.saveTestDataAsGlobal({}, world);
|
|
3126
|
-
}
|
|
3127
|
-
}
|
|
3128
3518
|
if (this.initSnapshotTaken === false) {
|
|
3129
3519
|
this.initSnapshotTaken = true;
|
|
3130
3520
|
if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
|
|
@@ -3149,18 +3539,68 @@ class StableBrowser {
|
|
|
3149
3539
|
const content = [`- path: ${path}`, `- title: ${title}`];
|
|
3150
3540
|
const timeout = this.configuration.ariaSnapshotTimeout ? this.configuration.ariaSnapshotTimeout : 3000;
|
|
3151
3541
|
for (let i = 0; i < frames.length; i++) {
|
|
3152
|
-
content.push(`- frame: ${i}`);
|
|
3153
3542
|
const frame = frames[i];
|
|
3154
|
-
|
|
3155
|
-
|
|
3543
|
+
try {
|
|
3544
|
+
// Ensure frame is attached and has body
|
|
3545
|
+
const body = frame.locator("body");
|
|
3546
|
+
await body.waitFor({ timeout: 200 }); // wait explicitly
|
|
3547
|
+
const snapshot = await body.ariaSnapshot({ timeout });
|
|
3548
|
+
content.push(`- frame: ${i}`);
|
|
3549
|
+
content.push(snapshot);
|
|
3550
|
+
}
|
|
3551
|
+
catch (innerErr) { }
|
|
3156
3552
|
}
|
|
3157
3553
|
return content.join("\n");
|
|
3158
3554
|
}
|
|
3159
3555
|
catch (e) {
|
|
3160
|
-
console.
|
|
3556
|
+
console.log("Error in getAriaSnapshot");
|
|
3557
|
+
//console.debug(e);
|
|
3161
3558
|
}
|
|
3162
3559
|
return null;
|
|
3163
3560
|
}
|
|
3561
|
+
/**
|
|
3562
|
+
* Sends command with custom payload to report.
|
|
3563
|
+
* @param commandText - Title of the command to be shown in the report.
|
|
3564
|
+
* @param commandStatus - Status of the command (e.g. "PASSED", "FAILED").
|
|
3565
|
+
* @param content - Content of the command to be shown in the report.
|
|
3566
|
+
* @param options - Options for the command. Example: { type: "json", screenshot: true }
|
|
3567
|
+
* @param world - Optional world context.
|
|
3568
|
+
* @public
|
|
3569
|
+
*/
|
|
3570
|
+
async addCommandToReport(commandText, commandStatus, content, options = {}, world = null) {
|
|
3571
|
+
const state = {
|
|
3572
|
+
options,
|
|
3573
|
+
world,
|
|
3574
|
+
locate: false,
|
|
3575
|
+
scroll: false,
|
|
3576
|
+
screenshot: options.screenshot ?? false,
|
|
3577
|
+
highlight: options.highlight ?? false,
|
|
3578
|
+
type: Types.REPORT_COMMAND,
|
|
3579
|
+
text: commandText,
|
|
3580
|
+
_text: commandText,
|
|
3581
|
+
operation: "report_command",
|
|
3582
|
+
log: "***** " + commandText + " *****\n",
|
|
3583
|
+
};
|
|
3584
|
+
try {
|
|
3585
|
+
await _preCommand(state, this);
|
|
3586
|
+
const payload = {
|
|
3587
|
+
type: options.type ?? "text",
|
|
3588
|
+
content: content,
|
|
3589
|
+
screenshotId: null,
|
|
3590
|
+
};
|
|
3591
|
+
state.payload = payload;
|
|
3592
|
+
if (commandStatus === "FAILED") {
|
|
3593
|
+
state.throwError = true;
|
|
3594
|
+
throw new Error("Command failed");
|
|
3595
|
+
}
|
|
3596
|
+
}
|
|
3597
|
+
catch (e) {
|
|
3598
|
+
await _commandError(state, e, this);
|
|
3599
|
+
}
|
|
3600
|
+
finally {
|
|
3601
|
+
await _commandFinally(state, this);
|
|
3602
|
+
}
|
|
3603
|
+
}
|
|
3164
3604
|
async afterStep(world, step) {
|
|
3165
3605
|
this.stepName = null;
|
|
3166
3606
|
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
@@ -3168,6 +3608,13 @@ class StableBrowser {
|
|
|
3168
3608
|
await this.context.browserObject.context.tracing.stopChunk({
|
|
3169
3609
|
path: path.join(this.context.browserObject.traceFolder, `trace-${this.stepIndex}.zip`),
|
|
3170
3610
|
});
|
|
3611
|
+
if (world && world.attach) {
|
|
3612
|
+
await world.attach(JSON.stringify({
|
|
3613
|
+
type: "trace",
|
|
3614
|
+
traceFilePath: `trace-${this.stepIndex}.zip`,
|
|
3615
|
+
}), "application/json+trace");
|
|
3616
|
+
}
|
|
3617
|
+
// console.log("trace file created", `trace-${this.stepIndex}.zip`);
|
|
3171
3618
|
}
|
|
3172
3619
|
}
|
|
3173
3620
|
if (this.context) {
|