automation_model 1.0.770-dev → 1.0.770-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/lib/api.js +28 -11
- package/lib/api.js.map +1 -1
- package/lib/auto_page.js +80 -23
- package/lib/auto_page.js.map +1 -1
- package/lib/browser_manager.js +56 -34
- package/lib/browser_manager.js.map +1 -1
- package/lib/bruno.js.map +1 -1
- package/lib/check_performance.d.ts +1 -0
- package/lib/check_performance.js +57 -0
- package/lib/check_performance.js.map +1 -0
- package/lib/command_common.js +18 -1
- package/lib/command_common.js.map +1 -1
- package/lib/file_checker.js +136 -25
- package/lib/file_checker.js.map +1 -1
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/lib/init_browser.d.ts +1 -2
- package/lib/init_browser.js +122 -126
- package/lib/init_browser.js.map +1 -1
- package/lib/locator_log.js.map +1 -1
- package/lib/network.d.ts +2 -0
- package/lib/network.js +398 -87
- package/lib/network.js.map +1 -1
- package/lib/route.d.ts +65 -15
- package/lib/route.js +530 -189
- package/lib/route.js.map +1 -1
- package/lib/scripts/axe.mini.js +23994 -1
- package/lib/snapshot_validation.js.map +1 -1
- package/lib/stable_browser.d.ts +11 -5
- package/lib/stable_browser.js +408 -81
- package/lib/stable_browser.js.map +1 -1
- package/lib/table_helper.js +14 -0
- package/lib/table_helper.js.map +1 -1
- package/lib/test_context.d.ts +1 -0
- package/lib/test_context.js +1 -0
- package/lib/test_context.js.map +1 -1
- package/lib/utils.d.ts +6 -2
- package/lib/utils.js +121 -14
- package/lib/utils.js.map +1 -1
- package/package.json +17 -10
package/lib/stable_browser.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// @ts-nocheck
|
|
2
|
+
import { check_performance } from "./check_performance.js";
|
|
2
3
|
import { expect } from "@playwright/test";
|
|
3
4
|
import dayjs from "dayjs";
|
|
4
5
|
import fs from "fs";
|
|
@@ -10,6 +11,7 @@ import { getDateTimeValue } from "./date_time.js";
|
|
|
10
11
|
import drawRectangle from "./drawRect.js";
|
|
11
12
|
//import { closeUnexpectedPopups } from "./popups.js";
|
|
12
13
|
import { getTableCells, getTableData } from "./table_analyze.js";
|
|
14
|
+
import errorStackParser from "error-stack-parser";
|
|
13
15
|
import { _convertToRegexQuery, _copyContext, _fixLocatorUsingParams, _fixUsingParams, _getServerUrl, extractStepExampleParameters, KEYBOARD_EVENTS, maskValue, replaceWithLocalTestData, scrollPageToLoadLazyElements, unEscapeString, _getDataFile, testForRegex, performAction, _getTestData, } from "./utils.js";
|
|
14
16
|
import csv from "csv-parser";
|
|
15
17
|
import { Readable } from "node:stream";
|
|
@@ -19,13 +21,14 @@ import { getTestData } from "./auto_page.js";
|
|
|
19
21
|
import { locate_element } from "./locate_element.js";
|
|
20
22
|
import { randomUUID } from "crypto";
|
|
21
23
|
import { _commandError, _commandFinally, _preCommand, _validateSelectors, _screenshot, _reportToWorld, } from "./command_common.js";
|
|
22
|
-
import { registerDownloadEvent, registerNetworkEvents } from "./network.js";
|
|
24
|
+
import { networkAfterStep, networkBeforeStep, registerDownloadEvent, registerNetworkEvents } from "./network.js";
|
|
23
25
|
import { LocatorLog } from "./locator_log.js";
|
|
24
26
|
import axios from "axios";
|
|
25
27
|
import { _findCellArea, findElementsInArea } from "./table_helper.js";
|
|
26
28
|
import { highlightSnapshot, snapshotValidation } from "./snapshot_validation.js";
|
|
27
29
|
import { loadBrunoParams } from "./bruno.js";
|
|
28
30
|
import { registerAfterStepRoutes, registerBeforeStepRoutes } from "./route.js";
|
|
31
|
+
import { existsSync } from "node:fs";
|
|
29
32
|
export const Types = {
|
|
30
33
|
CLICK: "click_element",
|
|
31
34
|
WAIT_ELEMENT: "wait_element",
|
|
@@ -44,6 +47,7 @@ export const Types = {
|
|
|
44
47
|
VERIFY_PAGE_CONTAINS_NO_TEXT: "verify_page_contains_no_text",
|
|
45
48
|
ANALYZE_TABLE: "analyze_table",
|
|
46
49
|
SELECT: "select_combobox", //
|
|
50
|
+
VERIFY_PROPERTY: "verify_element_property",
|
|
47
51
|
VERIFY_PAGE_PATH: "verify_page_path",
|
|
48
52
|
VERIFY_PAGE_TITLE: "verify_page_title",
|
|
49
53
|
TYPE_PRESS: "type_press",
|
|
@@ -62,12 +66,11 @@ export const Types = {
|
|
|
62
66
|
SET_INPUT: "set_input",
|
|
63
67
|
WAIT_FOR_TEXT_TO_DISAPPEAR: "wait_for_text_to_disappear",
|
|
64
68
|
VERIFY_ATTRIBUTE: "verify_element_attribute",
|
|
65
|
-
VERIFY_PROPERTY: "verify_element_property",
|
|
66
69
|
VERIFY_TEXT_WITH_RELATION: "verify_text_with_relation",
|
|
67
70
|
BRUNO: "bruno",
|
|
68
|
-
SNAPSHOT_VALIDATION: "snapshot_validation",
|
|
69
71
|
VERIFY_FILE_EXISTS: "verify_file_exists",
|
|
70
72
|
SET_INPUT_FILES: "set_input_files",
|
|
73
|
+
SNAPSHOT_VALIDATION: "snapshot_validation",
|
|
71
74
|
REPORT_COMMAND: "report_command",
|
|
72
75
|
STEP_COMPLETE: "step_complete",
|
|
73
76
|
SLEEP: "sleep",
|
|
@@ -84,6 +87,7 @@ class StableBrowser {
|
|
|
84
87
|
context;
|
|
85
88
|
world;
|
|
86
89
|
fastMode;
|
|
90
|
+
stepTags;
|
|
87
91
|
project_path = null;
|
|
88
92
|
webLogFile = null;
|
|
89
93
|
networkLogger = null;
|
|
@@ -92,13 +96,17 @@ class StableBrowser {
|
|
|
92
96
|
tags = null;
|
|
93
97
|
isRecording = false;
|
|
94
98
|
initSnapshotTaken = false;
|
|
95
|
-
|
|
99
|
+
onlyFailuresScreenshot = process.env.SCREENSHOT_ON_FAILURE_ONLY === "true";
|
|
100
|
+
// set to true if the step issue a report
|
|
101
|
+
inStepReport = false;
|
|
102
|
+
constructor(browser, page, logger = null, context = null, world = null, fastMode = false, stepTags = []) {
|
|
96
103
|
this.browser = browser;
|
|
97
104
|
this.page = page;
|
|
98
105
|
this.logger = logger;
|
|
99
106
|
this.context = context;
|
|
100
107
|
this.world = world;
|
|
101
108
|
this.fastMode = fastMode;
|
|
109
|
+
this.stepTags = stepTags;
|
|
102
110
|
if (!this.logger) {
|
|
103
111
|
this.logger = console;
|
|
104
112
|
}
|
|
@@ -131,6 +139,7 @@ class StableBrowser {
|
|
|
131
139
|
this.fastMode = true;
|
|
132
140
|
}
|
|
133
141
|
if (process.env.FAST_MODE === "true") {
|
|
142
|
+
// console.log("Fast mode enabled from environment variable");
|
|
134
143
|
this.fastMode = true;
|
|
135
144
|
}
|
|
136
145
|
if (process.env.FAST_MODE === "false") {
|
|
@@ -173,6 +182,7 @@ class StableBrowser {
|
|
|
173
182
|
registerNetworkEvents(this.world, this, context, this.page);
|
|
174
183
|
registerDownloadEvent(this.page, this.world, context);
|
|
175
184
|
page.on("close", async () => {
|
|
185
|
+
// return if browser context is already closed
|
|
176
186
|
if (this.context && this.context.pages && this.context.pages.length > 1) {
|
|
177
187
|
this.context.pages.pop();
|
|
178
188
|
this.page = this.context.pages[this.context.pages.length - 1];
|
|
@@ -182,7 +192,12 @@ class StableBrowser {
|
|
|
182
192
|
console.log("Switched to page " + title);
|
|
183
193
|
}
|
|
184
194
|
catch (error) {
|
|
185
|
-
|
|
195
|
+
if (error?.message?.includes("Target page, context or browser has been closed")) {
|
|
196
|
+
// Ignore this error
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
console.error("Error on page close", error);
|
|
200
|
+
}
|
|
186
201
|
}
|
|
187
202
|
}
|
|
188
203
|
});
|
|
@@ -191,7 +206,12 @@ class StableBrowser {
|
|
|
191
206
|
console.log("Switch page: " + (await page.title()));
|
|
192
207
|
}
|
|
193
208
|
catch (e) {
|
|
194
|
-
|
|
209
|
+
if (e?.message?.includes("Target page, context or browser has been closed")) {
|
|
210
|
+
// Ignore this error
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
this.logger.error("error on page load " + e);
|
|
214
|
+
}
|
|
195
215
|
}
|
|
196
216
|
context.pageLoading.status = false;
|
|
197
217
|
}.bind(this));
|
|
@@ -219,7 +239,7 @@ class StableBrowser {
|
|
|
219
239
|
if (newContextCreated) {
|
|
220
240
|
this.registerEventListeners(this.context);
|
|
221
241
|
await this.goto(this.context.environment.baseUrl);
|
|
222
|
-
if (!this.fastMode) {
|
|
242
|
+
if (!this.fastMode && !this.stepTags.includes("fast-mode")) {
|
|
223
243
|
await this.waitForPageLoad();
|
|
224
244
|
}
|
|
225
245
|
}
|
|
@@ -503,12 +523,6 @@ class StableBrowser {
|
|
|
503
523
|
if (!el.setAttribute) {
|
|
504
524
|
el = el.parentElement;
|
|
505
525
|
}
|
|
506
|
-
// remove any attributes start with data-blinq-id
|
|
507
|
-
// for (let i = 0; i < el.attributes.length; i++) {
|
|
508
|
-
// if (el.attributes[i].name.startsWith("data-blinq-id")) {
|
|
509
|
-
// el.removeAttribute(el.attributes[i].name);
|
|
510
|
-
// }
|
|
511
|
-
// }
|
|
512
526
|
el.setAttribute("data-blinq-id-" + randomToken, "");
|
|
513
527
|
return true;
|
|
514
528
|
}, [tag1, randomToken]))) {
|
|
@@ -678,40 +692,186 @@ class StableBrowser {
|
|
|
678
692
|
}
|
|
679
693
|
return { rerun: false };
|
|
680
694
|
}
|
|
695
|
+
getFilePath() {
|
|
696
|
+
const stackFrames = errorStackParser.parse(new Error());
|
|
697
|
+
const stackFrame = stackFrames.findLast((frame) => frame.fileName && frame.fileName.endsWith(".mjs"));
|
|
698
|
+
// return stackFrame?.fileName || null;
|
|
699
|
+
const filepath = stackFrame?.fileName;
|
|
700
|
+
if (filepath) {
|
|
701
|
+
let jsonFilePath = filepath.replace(".mjs", ".json");
|
|
702
|
+
if (existsSync(jsonFilePath)) {
|
|
703
|
+
return jsonFilePath;
|
|
704
|
+
}
|
|
705
|
+
const config = this.configuration ?? {};
|
|
706
|
+
if (!config?.locatorsMetadataDir) {
|
|
707
|
+
config.locatorsMetadataDir = "features/step_definitions/locators";
|
|
708
|
+
}
|
|
709
|
+
if (config && config.locatorsMetadataDir) {
|
|
710
|
+
jsonFilePath = path.join(config.locatorsMetadataDir, path.basename(jsonFilePath));
|
|
711
|
+
}
|
|
712
|
+
if (existsSync(jsonFilePath)) {
|
|
713
|
+
return jsonFilePath;
|
|
714
|
+
}
|
|
715
|
+
return null;
|
|
716
|
+
}
|
|
717
|
+
return null;
|
|
718
|
+
}
|
|
719
|
+
getFullElementLocators(selectors, filePath) {
|
|
720
|
+
if (!filePath || !existsSync(filePath)) {
|
|
721
|
+
return null;
|
|
722
|
+
}
|
|
723
|
+
const content = fs.readFileSync(filePath, "utf8");
|
|
724
|
+
try {
|
|
725
|
+
const allElements = JSON.parse(content);
|
|
726
|
+
const element_key = selectors?.element_key;
|
|
727
|
+
if (element_key && allElements[element_key]) {
|
|
728
|
+
return allElements[element_key];
|
|
729
|
+
}
|
|
730
|
+
for (const elementKey in allElements) {
|
|
731
|
+
const element = allElements[elementKey];
|
|
732
|
+
let foundStrategy = null;
|
|
733
|
+
for (const key in element) {
|
|
734
|
+
if (key === "strategy") {
|
|
735
|
+
continue;
|
|
736
|
+
}
|
|
737
|
+
const locators = element[key];
|
|
738
|
+
if (!locators || !locators.length) {
|
|
739
|
+
continue;
|
|
740
|
+
}
|
|
741
|
+
for (const locator of locators) {
|
|
742
|
+
delete locator.score;
|
|
743
|
+
}
|
|
744
|
+
if (JSON.stringify(locators) === JSON.stringify(selectors.locators)) {
|
|
745
|
+
foundStrategy = key;
|
|
746
|
+
break;
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
if (foundStrategy) {
|
|
750
|
+
return element;
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
catch (error) {
|
|
755
|
+
console.error("Error parsing locators from file: " + filePath, error);
|
|
756
|
+
}
|
|
757
|
+
return null;
|
|
758
|
+
}
|
|
681
759
|
async _locate(selectors, info, _params, timeout, allowDisabled = false) {
|
|
682
760
|
if (!timeout) {
|
|
683
761
|
timeout = 30000;
|
|
684
762
|
}
|
|
763
|
+
let element = null;
|
|
764
|
+
let allStrategyLocators = null;
|
|
765
|
+
let selectedStrategy = null;
|
|
766
|
+
if (this.tryAllStrategies) {
|
|
767
|
+
allStrategyLocators = this.getFullElementLocators(selectors, this.getFilePath());
|
|
768
|
+
selectedStrategy = allStrategyLocators?.strategy;
|
|
769
|
+
}
|
|
685
770
|
for (let i = 0; i < 3; i++) {
|
|
686
771
|
info.log += "attempt " + i + ": total locators " + selectors.locators.length + "\n";
|
|
687
772
|
for (let j = 0; j < selectors.locators.length; j++) {
|
|
688
773
|
let selector = selectors.locators[j];
|
|
689
774
|
info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
|
|
690
775
|
}
|
|
691
|
-
|
|
776
|
+
if (this.tryAllStrategies && selectedStrategy) {
|
|
777
|
+
const strategyLocators = allStrategyLocators[selectedStrategy];
|
|
778
|
+
let err;
|
|
779
|
+
if (strategyLocators && strategyLocators.length) {
|
|
780
|
+
try {
|
|
781
|
+
selectors.locators = strategyLocators;
|
|
782
|
+
element = await this._locate_internal(selectors, info, _params, 10_000, allowDisabled);
|
|
783
|
+
info.selectedStrategy = selectedStrategy;
|
|
784
|
+
info.log += "element found using strategy " + selectedStrategy + "\n";
|
|
785
|
+
}
|
|
786
|
+
catch (error) {
|
|
787
|
+
err = error;
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
if (!element) {
|
|
791
|
+
for (const key in allStrategyLocators) {
|
|
792
|
+
if (key === "strategy" || key === selectedStrategy) {
|
|
793
|
+
continue;
|
|
794
|
+
}
|
|
795
|
+
const strategyLocators = allStrategyLocators[key];
|
|
796
|
+
if (strategyLocators && strategyLocators.length) {
|
|
797
|
+
try {
|
|
798
|
+
info.log += "using strategy " + key + " with locators " + JSON.stringify(strategyLocators) + "\n";
|
|
799
|
+
selectors.locators = strategyLocators;
|
|
800
|
+
element = await this._locate_internal(selectors, info, _params, 10_000, allowDisabled);
|
|
801
|
+
err = null;
|
|
802
|
+
info.selectedStrategy = key;
|
|
803
|
+
info.log += "element found using strategy " + key + "\n";
|
|
804
|
+
break;
|
|
805
|
+
}
|
|
806
|
+
catch (error) {
|
|
807
|
+
err = error;
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
if (err) {
|
|
813
|
+
throw err;
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
else {
|
|
817
|
+
element = await this._locate_internal(selectors, info, _params, timeout, allowDisabled);
|
|
818
|
+
}
|
|
692
819
|
if (!element.rerun) {
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
820
|
+
let newElementSelector = "";
|
|
821
|
+
if (this.configuration && this.configuration.stableLocatorStrategy === "csschain") {
|
|
822
|
+
const cssSelector = await element.evaluate((el) => {
|
|
823
|
+
function getCssSelector(el) {
|
|
824
|
+
if (!el || el.nodeType !== 1 || el === document.body)
|
|
825
|
+
return el.tagName.toLowerCase();
|
|
826
|
+
const parent = el.parentElement;
|
|
827
|
+
const tag = el.tagName.toLowerCase();
|
|
828
|
+
// Find the index of the element among its siblings of the same tag
|
|
829
|
+
let index = 1;
|
|
830
|
+
for (let sibling = el.previousElementSibling; sibling; sibling = sibling.previousElementSibling) {
|
|
831
|
+
if (sibling.tagName === el.tagName) {
|
|
832
|
+
index++;
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
// Use nth-child if necessary (i.e., if there's more than one of the same tag)
|
|
836
|
+
const siblings = Array.from(parent.children).filter((child) => child.tagName === el.tagName);
|
|
837
|
+
const needsNthChild = siblings.length > 1;
|
|
838
|
+
const selector = needsNthChild ? `${tag}:nth-child(${[...parent.children].indexOf(el) + 1})` : tag;
|
|
839
|
+
return getCssSelector(parent) + " > " + selector;
|
|
840
|
+
}
|
|
841
|
+
const cssSelector = getCssSelector(el);
|
|
842
|
+
return cssSelector;
|
|
843
|
+
});
|
|
844
|
+
newElementSelector = cssSelector;
|
|
845
|
+
}
|
|
846
|
+
else {
|
|
847
|
+
const randomToken = "blinq_" + Math.random().toString(36).substring(7);
|
|
848
|
+
if (this.configuration && this.configuration.stableLocatorStrategy === "data-attribute") {
|
|
849
|
+
const dataAttribute = "data-blinq-id";
|
|
850
|
+
await element.evaluate((el, [dataAttribute, randomToken]) => {
|
|
851
|
+
el.setAttribute(dataAttribute, randomToken);
|
|
852
|
+
}, [dataAttribute, randomToken]);
|
|
853
|
+
newElementSelector = `[${dataAttribute}="${randomToken}"]`;
|
|
854
|
+
}
|
|
855
|
+
else {
|
|
856
|
+
// the default case just return the located element
|
|
857
|
+
// will not work for click and type if the locator is placeholder and the placeholder change due to the click event
|
|
858
|
+
return element;
|
|
859
|
+
}
|
|
860
|
+
}
|
|
700
861
|
const scope = element._frame ?? element.page();
|
|
701
|
-
let newElementSelector = "[data-blinq-id-" + randomToken + "]";
|
|
702
862
|
let prefixSelector = "";
|
|
703
863
|
const frameControlSelector = " >> internal:control=enter-frame";
|
|
704
864
|
const frameSelectorIndex = element._selector.lastIndexOf(frameControlSelector);
|
|
705
865
|
if (frameSelectorIndex !== -1) {
|
|
706
866
|
// remove everything after the >> internal:control=enter-frame
|
|
707
867
|
const frameSelector = element._selector.substring(0, frameSelectorIndex);
|
|
708
|
-
prefixSelector = frameSelector + " >> internal:control=enter-frame >>";
|
|
868
|
+
prefixSelector = frameSelector + " >> internal:control=enter-frame >> ";
|
|
709
869
|
}
|
|
710
870
|
// if (element?._frame?._selector) {
|
|
711
871
|
// prefixSelector = element._frame._selector + " >> " + prefixSelector;
|
|
712
872
|
// }
|
|
713
873
|
const newSelector = prefixSelector + newElementSelector;
|
|
714
|
-
return scope.locator(newSelector);
|
|
874
|
+
return scope.locator(newSelector).first();
|
|
715
875
|
}
|
|
716
876
|
}
|
|
717
877
|
throw new Error("unable to locate element " + JSON.stringify(selectors));
|
|
@@ -732,7 +892,7 @@ class StableBrowser {
|
|
|
732
892
|
for (let i = 0; i < frame.selectors.length; i++) {
|
|
733
893
|
let frameLocator = frame.selectors[i];
|
|
734
894
|
if (frameLocator.css) {
|
|
735
|
-
let testframescope = framescope.frameLocator(frameLocator.css);
|
|
895
|
+
let testframescope = framescope.frameLocator(`${frameLocator.css} >> visible=true`);
|
|
736
896
|
if (frameLocator.index) {
|
|
737
897
|
testframescope = framescope.nth(frameLocator.index);
|
|
738
898
|
}
|
|
@@ -744,7 +904,7 @@ class StableBrowser {
|
|
|
744
904
|
break;
|
|
745
905
|
}
|
|
746
906
|
catch (error) {
|
|
747
|
-
console.error("frame not found " + frameLocator.css);
|
|
907
|
+
// console.error("frame not found " + frameLocator.css);
|
|
748
908
|
}
|
|
749
909
|
}
|
|
750
910
|
}
|
|
@@ -810,6 +970,15 @@ class StableBrowser {
|
|
|
810
970
|
});
|
|
811
971
|
}
|
|
812
972
|
async _locate_internal(selectors, info, _params, timeout = 30000, allowDisabled = false) {
|
|
973
|
+
if (selectors.locators && Array.isArray(selectors.locators)) {
|
|
974
|
+
selectors.locators.forEach((locator) => {
|
|
975
|
+
locator.index = locator.index ?? 0;
|
|
976
|
+
locator.visible = locator.visible ?? true;
|
|
977
|
+
if (locator.visible && locator.css && !locator.css.endsWith(">> visible=true")) {
|
|
978
|
+
locator.css = locator.css + " >> visible=true";
|
|
979
|
+
}
|
|
980
|
+
});
|
|
981
|
+
}
|
|
813
982
|
if (!info) {
|
|
814
983
|
info = {};
|
|
815
984
|
info.failCause = {};
|
|
@@ -822,7 +991,6 @@ class StableBrowser {
|
|
|
822
991
|
let locatorsCount = 0;
|
|
823
992
|
let lazy_scroll = false;
|
|
824
993
|
//let arrayMode = Array.isArray(selectors);
|
|
825
|
-
let scope = await this._findFrameScope(selectors, timeout, info);
|
|
826
994
|
let selectorsLocators = null;
|
|
827
995
|
selectorsLocators = selectors.locators;
|
|
828
996
|
// group selectors by priority
|
|
@@ -850,6 +1018,7 @@ class StableBrowser {
|
|
|
850
1018
|
let highPriorityOnly = true;
|
|
851
1019
|
let visibleOnly = true;
|
|
852
1020
|
while (true) {
|
|
1021
|
+
let scope = await this._findFrameScope(selectors, timeout, info);
|
|
853
1022
|
locatorsCount = 0;
|
|
854
1023
|
let result = [];
|
|
855
1024
|
let popupResult = await this.closeUnexpectedPopups(info, _params);
|
|
@@ -965,9 +1134,13 @@ class StableBrowser {
|
|
|
965
1134
|
}
|
|
966
1135
|
}
|
|
967
1136
|
if (foundLocators.length === 1) {
|
|
1137
|
+
let box = null;
|
|
1138
|
+
if (!this.onlyFailuresScreenshot) {
|
|
1139
|
+
box = await foundLocators[0].boundingBox();
|
|
1140
|
+
}
|
|
968
1141
|
result.foundElements.push({
|
|
969
1142
|
locator: foundLocators[0],
|
|
970
|
-
box:
|
|
1143
|
+
box: box,
|
|
971
1144
|
unique: true,
|
|
972
1145
|
});
|
|
973
1146
|
result.locatorIndex = i;
|
|
@@ -1122,11 +1295,22 @@ class StableBrowser {
|
|
|
1122
1295
|
operation: "click",
|
|
1123
1296
|
log: "***** click on " + selectors.element_name + " *****\n",
|
|
1124
1297
|
};
|
|
1298
|
+
check_performance("click_all ***", this.context, true);
|
|
1299
|
+
let stepFastMode = this.stepTags.includes("fast-mode");
|
|
1300
|
+
if (stepFastMode) {
|
|
1301
|
+
state.onlyFailuresScreenshot = true;
|
|
1302
|
+
state.scroll = false;
|
|
1303
|
+
state.highlight = false;
|
|
1304
|
+
}
|
|
1125
1305
|
try {
|
|
1306
|
+
check_performance("click_preCommand", this.context, true);
|
|
1126
1307
|
await _preCommand(state, this);
|
|
1308
|
+
check_performance("click_preCommand", this.context, false);
|
|
1127
1309
|
await performAction("click", state.element, options, this, state, _params);
|
|
1128
|
-
if (!this.fastMode) {
|
|
1129
|
-
|
|
1310
|
+
if (!this.fastMode && !this.stepTags.includes("fast-mode")) {
|
|
1311
|
+
check_performance("click_waitForPageLoad", this.context, true);
|
|
1312
|
+
await this.waitForPageLoad({ noSleep: true });
|
|
1313
|
+
check_performance("click_waitForPageLoad", this.context, false);
|
|
1130
1314
|
}
|
|
1131
1315
|
return state.info;
|
|
1132
1316
|
}
|
|
@@ -1134,7 +1318,13 @@ class StableBrowser {
|
|
|
1134
1318
|
await _commandError(state, e, this);
|
|
1135
1319
|
}
|
|
1136
1320
|
finally {
|
|
1321
|
+
check_performance("click_commandFinally", this.context, true);
|
|
1137
1322
|
await _commandFinally(state, this);
|
|
1323
|
+
check_performance("click_commandFinally", this.context, false);
|
|
1324
|
+
check_performance("click_all ***", this.context, false);
|
|
1325
|
+
if (this.context.profile) {
|
|
1326
|
+
console.log(JSON.stringify(this.context.profile, null, 2));
|
|
1327
|
+
}
|
|
1138
1328
|
}
|
|
1139
1329
|
}
|
|
1140
1330
|
async waitForElement(selectors, _params, options = {}, world = null) {
|
|
@@ -1226,7 +1416,7 @@ class StableBrowser {
|
|
|
1226
1416
|
}
|
|
1227
1417
|
}
|
|
1228
1418
|
}
|
|
1229
|
-
await this.waitForPageLoad();
|
|
1419
|
+
//await this.waitForPageLoad();
|
|
1230
1420
|
return state.info;
|
|
1231
1421
|
}
|
|
1232
1422
|
catch (e) {
|
|
@@ -1252,7 +1442,7 @@ class StableBrowser {
|
|
|
1252
1442
|
await _preCommand(state, this);
|
|
1253
1443
|
await performAction("hover", state.element, options, this, state, _params);
|
|
1254
1444
|
await _screenshot(state, this);
|
|
1255
|
-
await this.waitForPageLoad();
|
|
1445
|
+
//await this.waitForPageLoad();
|
|
1256
1446
|
return state.info;
|
|
1257
1447
|
}
|
|
1258
1448
|
catch (e) {
|
|
@@ -1288,7 +1478,7 @@ class StableBrowser {
|
|
|
1288
1478
|
state.info.log += "selectOption failed, will try force" + "\n";
|
|
1289
1479
|
await state.element.selectOption(values, { timeout: 10000, force: true });
|
|
1290
1480
|
}
|
|
1291
|
-
await this.waitForPageLoad();
|
|
1481
|
+
//await this.waitForPageLoad();
|
|
1292
1482
|
return state.info;
|
|
1293
1483
|
}
|
|
1294
1484
|
catch (e) {
|
|
@@ -1474,6 +1664,14 @@ class StableBrowser {
|
|
|
1474
1664
|
}
|
|
1475
1665
|
try {
|
|
1476
1666
|
await _preCommand(state, this);
|
|
1667
|
+
const randomToken = "blinq_" + Math.random().toString(36).substring(7);
|
|
1668
|
+
// tag the element
|
|
1669
|
+
let newElementSelector = await state.element.evaluate((el, token) => {
|
|
1670
|
+
// use attribute and not id
|
|
1671
|
+
const attrName = `data-blinq-id-${token}`;
|
|
1672
|
+
el.setAttribute(attrName, "");
|
|
1673
|
+
return `[${attrName}]`;
|
|
1674
|
+
}, randomToken);
|
|
1477
1675
|
state.info.value = _value;
|
|
1478
1676
|
if (!options.press) {
|
|
1479
1677
|
try {
|
|
@@ -1499,6 +1697,25 @@ class StableBrowser {
|
|
|
1499
1697
|
}
|
|
1500
1698
|
}
|
|
1501
1699
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1700
|
+
// check if the element exist after the click (no wait)
|
|
1701
|
+
const count = await state.element.count({ timeout: 0 });
|
|
1702
|
+
if (count === 0) {
|
|
1703
|
+
// the locator changed after the click (placeholder) we need to locate the element using the data-blinq-id
|
|
1704
|
+
const scope = state.element._frame ?? element.page();
|
|
1705
|
+
let prefixSelector = "";
|
|
1706
|
+
const frameControlSelector = " >> internal:control=enter-frame";
|
|
1707
|
+
const frameSelectorIndex = state.element._selector.lastIndexOf(frameControlSelector);
|
|
1708
|
+
if (frameSelectorIndex !== -1) {
|
|
1709
|
+
// remove everything after the >> internal:control=enter-frame
|
|
1710
|
+
const frameSelector = state.element._selector.substring(0, frameSelectorIndex);
|
|
1711
|
+
prefixSelector = frameSelector + " >> internal:control=enter-frame >> ";
|
|
1712
|
+
}
|
|
1713
|
+
// if (element?._frame?._selector) {
|
|
1714
|
+
// prefixSelector = element._frame._selector + " >> " + prefixSelector;
|
|
1715
|
+
// }
|
|
1716
|
+
const newSelector = prefixSelector + newElementSelector;
|
|
1717
|
+
state.element = scope.locator(newSelector).first();
|
|
1718
|
+
}
|
|
1502
1719
|
const valueSegment = state.value.split("&&");
|
|
1503
1720
|
for (let i = 0; i < valueSegment.length; i++) {
|
|
1504
1721
|
if (i > 0) {
|
|
@@ -1570,8 +1787,8 @@ class StableBrowser {
|
|
|
1570
1787
|
if (enter) {
|
|
1571
1788
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1572
1789
|
await this.page.keyboard.press("Enter");
|
|
1790
|
+
await this.waitForPageLoad();
|
|
1573
1791
|
}
|
|
1574
|
-
await this.waitForPageLoad();
|
|
1575
1792
|
return state.info;
|
|
1576
1793
|
}
|
|
1577
1794
|
catch (e) {
|
|
@@ -2437,7 +2654,7 @@ class StableBrowser {
|
|
|
2437
2654
|
let expectedValue;
|
|
2438
2655
|
try {
|
|
2439
2656
|
await _preCommand(state, this);
|
|
2440
|
-
expectedValue = await
|
|
2657
|
+
expectedValue = await this._replaceWithLocalData(value, world);
|
|
2441
2658
|
state.info.expectedValue = expectedValue;
|
|
2442
2659
|
switch (property) {
|
|
2443
2660
|
case "innerText":
|
|
@@ -2485,47 +2702,54 @@ class StableBrowser {
|
|
|
2485
2702
|
}
|
|
2486
2703
|
state.info.value = val;
|
|
2487
2704
|
let regex;
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
const
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
state.info.failCause.assertionFailed = true;
|
|
2503
|
-
state.info.failCause.lastError = errorMessage;
|
|
2504
|
-
throw new Error(errorMessage);
|
|
2505
|
-
}
|
|
2705
|
+
state.info.value = val;
|
|
2706
|
+
const isRegex = expectedValue.startsWith("regex:");
|
|
2707
|
+
const isContains = expectedValue.startsWith("contains:");
|
|
2708
|
+
const isExact = expectedValue.startsWith("exact:");
|
|
2709
|
+
let matchPassed = false;
|
|
2710
|
+
if (isRegex) {
|
|
2711
|
+
const rawPattern = expectedValue.slice(6); // remove "regex:"
|
|
2712
|
+
const lastSlashIndex = rawPattern.lastIndexOf("/");
|
|
2713
|
+
if (rawPattern.startsWith("/") && lastSlashIndex > 0) {
|
|
2714
|
+
const patternBody = rawPattern.slice(1, lastSlashIndex).replace(/\n/g, ".*");
|
|
2715
|
+
const flags = rawPattern.slice(lastSlashIndex + 1) || "gs";
|
|
2716
|
+
const regex = new RegExp(patternBody, flags);
|
|
2717
|
+
state.info.regex = true;
|
|
2718
|
+
matchPassed = regex.test(val);
|
|
2506
2719
|
}
|
|
2507
2720
|
else {
|
|
2508
|
-
//
|
|
2509
|
-
const
|
|
2510
|
-
const
|
|
2511
|
-
|
|
2512
|
-
// Check if all expected lines are present in the actual lines
|
|
2513
|
-
const isPart = expectedLines.every((expectedLine) => valLines.some((valLine) => valLine.trim() === expectedLine.trim()));
|
|
2514
|
-
if (!isPart) {
|
|
2515
|
-
let errorMessage = `The ${property} property has a value of "${val}", but the expected value is "${expectedValue}"`;
|
|
2516
|
-
state.info.failCause.assertionFailed = true;
|
|
2517
|
-
state.info.failCause.lastError = errorMessage;
|
|
2518
|
-
throw new Error(errorMessage);
|
|
2519
|
-
}
|
|
2721
|
+
// Fallback: treat as literal
|
|
2722
|
+
const escapedPattern = rawPattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2723
|
+
const regex = new RegExp(escapedPattern, "g");
|
|
2724
|
+
matchPassed = regex.test(val);
|
|
2520
2725
|
}
|
|
2521
2726
|
}
|
|
2727
|
+
else if (isContains) {
|
|
2728
|
+
const containsValue = expectedValue.slice(9); // remove "contains:"
|
|
2729
|
+
matchPassed = val.includes(containsValue);
|
|
2730
|
+
}
|
|
2731
|
+
else if (isExact) {
|
|
2732
|
+
const exactValue = expectedValue.slice(6); // remove "exact:"
|
|
2733
|
+
matchPassed = val === exactValue;
|
|
2734
|
+
}
|
|
2735
|
+
else if (property === "innerText") {
|
|
2736
|
+
// Default innerText logic
|
|
2737
|
+
const normalizedExpectedValue = expectedValue.replace(/\\n/g, "\n");
|
|
2738
|
+
const valLines = val.split("\n");
|
|
2739
|
+
const expectedLines = normalizedExpectedValue.split("\n");
|
|
2740
|
+
matchPassed = expectedLines.every((expectedLine) => valLines.some((valLine) => valLine.trim() === expectedLine.trim()));
|
|
2741
|
+
}
|
|
2522
2742
|
else {
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2743
|
+
// Fallback exact or loose match
|
|
2744
|
+
const escapedPattern = expectedValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2745
|
+
const regex = new RegExp(escapedPattern, "g");
|
|
2746
|
+
matchPassed = regex.test(val);
|
|
2747
|
+
}
|
|
2748
|
+
if (!matchPassed) {
|
|
2749
|
+
let errorMessage = `The ${property} property has a value of "${val}", but the expected value is "${expectedValue}"`;
|
|
2750
|
+
state.info.failCause.assertionFailed = true;
|
|
2751
|
+
state.info.failCause.lastError = errorMessage;
|
|
2752
|
+
throw new Error(errorMessage);
|
|
2529
2753
|
}
|
|
2530
2754
|
return state.info;
|
|
2531
2755
|
}
|
|
@@ -2556,6 +2780,7 @@ class StableBrowser {
|
|
|
2556
2780
|
allowDisabled: true,
|
|
2557
2781
|
info: {},
|
|
2558
2782
|
};
|
|
2783
|
+
state.options ??= { timeout: timeoutMs };
|
|
2559
2784
|
// Initialize startTime outside try block to ensure it's always accessible
|
|
2560
2785
|
const startTime = Date.now();
|
|
2561
2786
|
let conditionMet = false;
|
|
@@ -3131,7 +3356,16 @@ class StableBrowser {
|
|
|
3131
3356
|
text = text.replace(/\\"/g, '"');
|
|
3132
3357
|
}
|
|
3133
3358
|
const timeout = this._getFindElementTimeout(options);
|
|
3134
|
-
|
|
3359
|
+
//if (!this.fastMode && !this.stepTags.includes("fast-mode")) {
|
|
3360
|
+
let stepFastMode = this.stepTags.includes("fast-mode");
|
|
3361
|
+
if (!stepFastMode) {
|
|
3362
|
+
if (!this.fastMode) {
|
|
3363
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
3364
|
+
}
|
|
3365
|
+
else {
|
|
3366
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
3367
|
+
}
|
|
3368
|
+
}
|
|
3135
3369
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
3136
3370
|
if (newValue !== text) {
|
|
3137
3371
|
this.logger.info(text + "=" + newValue);
|
|
@@ -3139,6 +3373,11 @@ class StableBrowser {
|
|
|
3139
3373
|
}
|
|
3140
3374
|
let dateAlternatives = findDateAlternatives(text);
|
|
3141
3375
|
let numberAlternatives = findNumberAlternatives(text);
|
|
3376
|
+
if (stepFastMode) {
|
|
3377
|
+
state.onlyFailuresScreenshot = true;
|
|
3378
|
+
state.scroll = false;
|
|
3379
|
+
state.highlight = false;
|
|
3380
|
+
}
|
|
3142
3381
|
try {
|
|
3143
3382
|
await _preCommand(state, this);
|
|
3144
3383
|
state.info.text = text;
|
|
@@ -3258,6 +3497,8 @@ class StableBrowser {
|
|
|
3258
3497
|
operation: "verify_text_with_relation",
|
|
3259
3498
|
log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
|
|
3260
3499
|
};
|
|
3500
|
+
const cmdStartTime = Date.now();
|
|
3501
|
+
let cmdEndTime = null;
|
|
3261
3502
|
const timeout = this._getFindElementTimeout(options);
|
|
3262
3503
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
3263
3504
|
let newValue = await this._replaceWithLocalData(textAnchor, world);
|
|
@@ -3293,6 +3534,17 @@ class StableBrowser {
|
|
|
3293
3534
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
3294
3535
|
continue;
|
|
3295
3536
|
}
|
|
3537
|
+
else {
|
|
3538
|
+
cmdEndTime = Date.now();
|
|
3539
|
+
if (cmdEndTime - cmdStartTime > 55000) {
|
|
3540
|
+
if (foundAncore) {
|
|
3541
|
+
throw new Error(`Text ${textToVerify} not found in page`);
|
|
3542
|
+
}
|
|
3543
|
+
else {
|
|
3544
|
+
throw new Error(`Text ${textAnchor} not found in page`);
|
|
3545
|
+
}
|
|
3546
|
+
}
|
|
3547
|
+
}
|
|
3296
3548
|
try {
|
|
3297
3549
|
for (let i = 0; i < resultWithElementsFound.length; i++) {
|
|
3298
3550
|
foundAncore = true;
|
|
@@ -3431,7 +3683,7 @@ class StableBrowser {
|
|
|
3431
3683
|
Object.assign(e, { info: info });
|
|
3432
3684
|
error = e;
|
|
3433
3685
|
// throw e;
|
|
3434
|
-
await _commandError({ text: "visualVerification", operation: "visualVerification",
|
|
3686
|
+
await _commandError({ text: "visualVerification", operation: "visualVerification", info }, e, this);
|
|
3435
3687
|
}
|
|
3436
3688
|
finally {
|
|
3437
3689
|
const endTime = Date.now();
|
|
@@ -3780,6 +4032,22 @@ class StableBrowser {
|
|
|
3780
4032
|
}
|
|
3781
4033
|
}
|
|
3782
4034
|
async waitForPageLoad(options = {}, world = null) {
|
|
4035
|
+
// try {
|
|
4036
|
+
// let currentPagePath = null;
|
|
4037
|
+
// currentPagePath = new URL(this.page.url()).pathname;
|
|
4038
|
+
// if (this.latestPagePath) {
|
|
4039
|
+
// // get the currect page path and compare with the latest page path
|
|
4040
|
+
// if (this.latestPagePath === currentPagePath) {
|
|
4041
|
+
// // if the page path is the same, do not wait for page load
|
|
4042
|
+
// console.log("No page change: " + currentPagePath);
|
|
4043
|
+
// return;
|
|
4044
|
+
// }
|
|
4045
|
+
// }
|
|
4046
|
+
// this.latestPagePath = currentPagePath;
|
|
4047
|
+
// } catch (e) {
|
|
4048
|
+
// console.debug("Error getting current page path: ", e);
|
|
4049
|
+
// }
|
|
4050
|
+
//console.log("Waiting for page load");
|
|
3783
4051
|
let timeout = this._getLoadTimeout(options);
|
|
3784
4052
|
const promiseArray = [];
|
|
3785
4053
|
// let waitForNetworkIdle = true;
|
|
@@ -3814,7 +4082,10 @@ class StableBrowser {
|
|
|
3814
4082
|
}
|
|
3815
4083
|
}
|
|
3816
4084
|
finally {
|
|
3817
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
4085
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
4086
|
+
if (options && !options.noSleep) {
|
|
4087
|
+
await new Promise((resolve) => setTimeout(resolve, 1500));
|
|
4088
|
+
}
|
|
3818
4089
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world));
|
|
3819
4090
|
const endTime = Date.now();
|
|
3820
4091
|
_reportToWorld(world, {
|
|
@@ -3868,7 +4139,7 @@ class StableBrowser {
|
|
|
3868
4139
|
}
|
|
3869
4140
|
operation = options.operation;
|
|
3870
4141
|
// validate operation is one of the supported operations
|
|
3871
|
-
if (operation != "click" && operation != "hover+click") {
|
|
4142
|
+
if (operation != "click" && operation != "hover+click" && operation != "hover") {
|
|
3872
4143
|
throw new Error("operation is not supported");
|
|
3873
4144
|
}
|
|
3874
4145
|
const state = {
|
|
@@ -3937,6 +4208,17 @@ class StableBrowser {
|
|
|
3937
4208
|
state.element = results[0];
|
|
3938
4209
|
await performAction("hover+click", state.element, options, this, state, _params);
|
|
3939
4210
|
break;
|
|
4211
|
+
case "hover":
|
|
4212
|
+
if (!options.css) {
|
|
4213
|
+
throw new Error("css is not defined");
|
|
4214
|
+
}
|
|
4215
|
+
const result1 = await findElementsInArea(options.css, cellArea, this, options);
|
|
4216
|
+
if (result1.length === 0) {
|
|
4217
|
+
throw new Error(`Element not found in cell area`);
|
|
4218
|
+
}
|
|
4219
|
+
state.element = result1[0];
|
|
4220
|
+
await performAction("hover", state.element, options, this, state, _params);
|
|
4221
|
+
break;
|
|
3940
4222
|
default:
|
|
3941
4223
|
throw new Error("operation is not supported");
|
|
3942
4224
|
}
|
|
@@ -3950,6 +4232,12 @@ class StableBrowser {
|
|
|
3950
4232
|
}
|
|
3951
4233
|
saveTestDataAsGlobal(options, world) {
|
|
3952
4234
|
const dataFile = _getDataFile(world, this.context, this);
|
|
4235
|
+
if (process.env.MODE === "executions") {
|
|
4236
|
+
const globalDataFile = path.join(this.project_path, "global_test_data.json");
|
|
4237
|
+
fs.copyFileSync(dataFile, globalDataFile);
|
|
4238
|
+
this.logger.info("Save the scenario test data to " + globalDataFile + " as global for the following scenarios.");
|
|
4239
|
+
return;
|
|
4240
|
+
}
|
|
3953
4241
|
process.env.GLOBAL_TEST_DATA_FILE = dataFile;
|
|
3954
4242
|
this.logger.info("Save the scenario test data as global for the following scenarios.");
|
|
3955
4243
|
}
|
|
@@ -4052,6 +4340,7 @@ class StableBrowser {
|
|
|
4052
4340
|
if (world && world.attach) {
|
|
4053
4341
|
world.attach(this.context.reportFolder, { mediaType: "text/plain" });
|
|
4054
4342
|
}
|
|
4343
|
+
this.context.loadedRoutes = null;
|
|
4055
4344
|
this.beforeScenarioCalled = true;
|
|
4056
4345
|
if (scenario && scenario.pickle && scenario.pickle.name) {
|
|
4057
4346
|
this.scenarioName = scenario.pickle.name;
|
|
@@ -4081,8 +4370,10 @@ class StableBrowser {
|
|
|
4081
4370
|
}
|
|
4082
4371
|
async afterScenario(world, scenario) { }
|
|
4083
4372
|
async beforeStep(world, step) {
|
|
4373
|
+
this.stepTags = [];
|
|
4084
4374
|
if (!this.beforeScenarioCalled) {
|
|
4085
4375
|
this.beforeScenario(world, step);
|
|
4376
|
+
this.context.loadedRoutes = null;
|
|
4086
4377
|
}
|
|
4087
4378
|
if (this.stepIndex === undefined) {
|
|
4088
4379
|
this.stepIndex = 0;
|
|
@@ -4092,7 +4383,12 @@ class StableBrowser {
|
|
|
4092
4383
|
}
|
|
4093
4384
|
if (step && step.pickleStep && step.pickleStep.text) {
|
|
4094
4385
|
this.stepName = step.pickleStep.text;
|
|
4095
|
-
|
|
4386
|
+
let printableStepName = this.stepName;
|
|
4387
|
+
// take the printableStepName and replace quated value with \x1b[33m and \x1b[0m
|
|
4388
|
+
printableStepName = printableStepName.replace(/"([^"]*)"/g, (match, p1) => {
|
|
4389
|
+
return `\x1b[33m"${p1}"\x1b[0m`;
|
|
4390
|
+
});
|
|
4391
|
+
this.logger.info("\x1b[38;5;208mstep:\x1b[0m " + printableStepName);
|
|
4096
4392
|
}
|
|
4097
4393
|
else if (step && step.text) {
|
|
4098
4394
|
this.stepName = step.text;
|
|
@@ -4107,7 +4403,10 @@ class StableBrowser {
|
|
|
4107
4403
|
}
|
|
4108
4404
|
if (this.initSnapshotTaken === false) {
|
|
4109
4405
|
this.initSnapshotTaken = true;
|
|
4110
|
-
if (world &&
|
|
4406
|
+
if (world &&
|
|
4407
|
+
world.attach &&
|
|
4408
|
+
!process.env.DISABLE_SNAPSHOT &&
|
|
4409
|
+
(!this.fastMode || this.stepTags.includes("fast-mode"))) {
|
|
4111
4410
|
const snapshot = await this.getAriaSnapshot();
|
|
4112
4411
|
if (snapshot) {
|
|
4113
4412
|
await world.attach(JSON.stringify(snapshot), "application/json+snapshot-before");
|
|
@@ -4115,7 +4414,13 @@ class StableBrowser {
|
|
|
4115
4414
|
}
|
|
4116
4415
|
}
|
|
4117
4416
|
this.context.routeResults = null;
|
|
4118
|
-
|
|
4417
|
+
this.context.loadedRoutes = null;
|
|
4418
|
+
await registerBeforeStepRoutes(this.context, this.stepName, world);
|
|
4419
|
+
networkBeforeStep(this.stepName, this.context);
|
|
4420
|
+
this.inStepReport = false;
|
|
4421
|
+
}
|
|
4422
|
+
setStepTags(tags) {
|
|
4423
|
+
this.stepTags = tags;
|
|
4119
4424
|
}
|
|
4120
4425
|
async getAriaSnapshot() {
|
|
4121
4426
|
try {
|
|
@@ -4189,7 +4494,7 @@ class StableBrowser {
|
|
|
4189
4494
|
state.payload = payload;
|
|
4190
4495
|
if (commandStatus === "FAILED") {
|
|
4191
4496
|
state.throwError = true;
|
|
4192
|
-
throw new Error(
|
|
4497
|
+
throw new Error(commandText);
|
|
4193
4498
|
}
|
|
4194
4499
|
}
|
|
4195
4500
|
catch (e) {
|
|
@@ -4199,7 +4504,7 @@ class StableBrowser {
|
|
|
4199
4504
|
await _commandFinally(state, this);
|
|
4200
4505
|
}
|
|
4201
4506
|
}
|
|
4202
|
-
async afterStep(world, step) {
|
|
4507
|
+
async afterStep(world, step, result) {
|
|
4203
4508
|
this.stepName = null;
|
|
4204
4509
|
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
4205
4510
|
if (this.context.browserObject.context) {
|
|
@@ -4218,7 +4523,17 @@ class StableBrowser {
|
|
|
4218
4523
|
if (this.context) {
|
|
4219
4524
|
this.context.examplesRow = null;
|
|
4220
4525
|
}
|
|
4221
|
-
if (
|
|
4526
|
+
if (!this.inStepReport) {
|
|
4527
|
+
// check the step result
|
|
4528
|
+
if (result && result.status === "FAILED" && world && world.attach) {
|
|
4529
|
+
await this.addCommandToReport(result.message ? result.message : "Step failed", "FAILED", `${result.message}`, { type: "text", screenshot: true }, world);
|
|
4530
|
+
}
|
|
4531
|
+
}
|
|
4532
|
+
if (world &&
|
|
4533
|
+
world.attach &&
|
|
4534
|
+
!process.env.DISABLE_SNAPSHOT &&
|
|
4535
|
+
!this.fastMode &&
|
|
4536
|
+
!this.stepTags.includes("fast-mode")) {
|
|
4222
4537
|
const snapshot = await this.getAriaSnapshot();
|
|
4223
4538
|
if (snapshot) {
|
|
4224
4539
|
const obj = {};
|
|
@@ -4226,6 +4541,11 @@ class StableBrowser {
|
|
|
4226
4541
|
}
|
|
4227
4542
|
}
|
|
4228
4543
|
this.context.routeResults = await registerAfterStepRoutes(this.context, world);
|
|
4544
|
+
if (this.context.routeResults) {
|
|
4545
|
+
if (world && world.attach) {
|
|
4546
|
+
await world.attach(JSON.stringify(this.context.routeResults), "application/json+intercept-results");
|
|
4547
|
+
}
|
|
4548
|
+
}
|
|
4229
4549
|
if (!process.env.TEMP_RUN) {
|
|
4230
4550
|
const state = {
|
|
4231
4551
|
world,
|
|
@@ -4249,6 +4569,13 @@ class StableBrowser {
|
|
|
4249
4569
|
await _commandFinally(state, this);
|
|
4250
4570
|
}
|
|
4251
4571
|
}
|
|
4572
|
+
networkAfterStep(this.stepName, this.context);
|
|
4573
|
+
if (process.env.TEMP_RUN === "true") {
|
|
4574
|
+
// Put a sleep for some time to allow the browser to finish processing
|
|
4575
|
+
if (!this.stepTags.includes("fast-mode")) {
|
|
4576
|
+
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
4577
|
+
}
|
|
4578
|
+
}
|
|
4252
4579
|
}
|
|
4253
4580
|
}
|
|
4254
4581
|
function createTimedPromise(promise, label) {
|