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