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