automation_model 1.0.765-dev → 1.0.765-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 +17 -10
- package/lib/api.js.map +1 -1
- package/lib/auto_page.js +66 -18
- package/lib/auto_page.js.map +1 -1
- package/lib/browser_manager.js +56 -32
- 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 +17 -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 +541 -175
- 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 +9 -4
- package/lib/stable_browser.js +401 -83
- 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,15 @@ 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
|
+
constructor(browser, page, logger = null, context = null, world = null, fastMode = false, stepTags = []) {
|
|
96
101
|
this.browser = browser;
|
|
97
102
|
this.page = page;
|
|
98
103
|
this.logger = logger;
|
|
99
104
|
this.context = context;
|
|
100
105
|
this.world = world;
|
|
101
106
|
this.fastMode = fastMode;
|
|
107
|
+
this.stepTags = stepTags;
|
|
102
108
|
if (!this.logger) {
|
|
103
109
|
this.logger = console;
|
|
104
110
|
}
|
|
@@ -131,6 +137,7 @@ class StableBrowser {
|
|
|
131
137
|
this.fastMode = true;
|
|
132
138
|
}
|
|
133
139
|
if (process.env.FAST_MODE === "true") {
|
|
140
|
+
// console.log("Fast mode enabled from environment variable");
|
|
134
141
|
this.fastMode = true;
|
|
135
142
|
}
|
|
136
143
|
if (process.env.FAST_MODE === "false") {
|
|
@@ -173,6 +180,7 @@ class StableBrowser {
|
|
|
173
180
|
registerNetworkEvents(this.world, this, context, this.page);
|
|
174
181
|
registerDownloadEvent(this.page, this.world, context);
|
|
175
182
|
page.on("close", async () => {
|
|
183
|
+
// return if browser context is already closed
|
|
176
184
|
if (this.context && this.context.pages && this.context.pages.length > 1) {
|
|
177
185
|
this.context.pages.pop();
|
|
178
186
|
this.page = this.context.pages[this.context.pages.length - 1];
|
|
@@ -182,7 +190,12 @@ class StableBrowser {
|
|
|
182
190
|
console.log("Switched to page " + title);
|
|
183
191
|
}
|
|
184
192
|
catch (error) {
|
|
185
|
-
|
|
193
|
+
if (error?.message?.includes("Target page, context or browser has been closed")) {
|
|
194
|
+
// Ignore this error
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
console.error("Error on page close", error);
|
|
198
|
+
}
|
|
186
199
|
}
|
|
187
200
|
}
|
|
188
201
|
});
|
|
@@ -191,7 +204,12 @@ class StableBrowser {
|
|
|
191
204
|
console.log("Switch page: " + (await page.title()));
|
|
192
205
|
}
|
|
193
206
|
catch (e) {
|
|
194
|
-
|
|
207
|
+
if (e?.message?.includes("Target page, context or browser has been closed")) {
|
|
208
|
+
// Ignore this error
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
this.logger.error("error on page load " + e);
|
|
212
|
+
}
|
|
195
213
|
}
|
|
196
214
|
context.pageLoading.status = false;
|
|
197
215
|
}.bind(this));
|
|
@@ -219,7 +237,7 @@ class StableBrowser {
|
|
|
219
237
|
if (newContextCreated) {
|
|
220
238
|
this.registerEventListeners(this.context);
|
|
221
239
|
await this.goto(this.context.environment.baseUrl);
|
|
222
|
-
if (!this.fastMode) {
|
|
240
|
+
if (!this.fastMode && !this.stepTags.includes("fast-mode")) {
|
|
223
241
|
await this.waitForPageLoad();
|
|
224
242
|
}
|
|
225
243
|
}
|
|
@@ -503,12 +521,6 @@ class StableBrowser {
|
|
|
503
521
|
if (!el.setAttribute) {
|
|
504
522
|
el = el.parentElement;
|
|
505
523
|
}
|
|
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
524
|
el.setAttribute("data-blinq-id-" + randomToken, "");
|
|
513
525
|
return true;
|
|
514
526
|
}, [tag1, randomToken]))) {
|
|
@@ -678,40 +690,186 @@ class StableBrowser {
|
|
|
678
690
|
}
|
|
679
691
|
return { rerun: false };
|
|
680
692
|
}
|
|
693
|
+
getFilePath() {
|
|
694
|
+
const stackFrames = errorStackParser.parse(new Error());
|
|
695
|
+
const stackFrame = stackFrames.findLast((frame) => frame.fileName && frame.fileName.endsWith(".mjs"));
|
|
696
|
+
// return stackFrame?.fileName || null;
|
|
697
|
+
const filepath = stackFrame?.fileName;
|
|
698
|
+
if (filepath) {
|
|
699
|
+
let jsonFilePath = filepath.replace(".mjs", ".json");
|
|
700
|
+
if (existsSync(jsonFilePath)) {
|
|
701
|
+
return jsonFilePath;
|
|
702
|
+
}
|
|
703
|
+
const config = this.configuration ?? {};
|
|
704
|
+
if (!config?.locatorsMetadataDir) {
|
|
705
|
+
config.locatorsMetadataDir = "features/step_definitions/locators";
|
|
706
|
+
}
|
|
707
|
+
if (config && config.locatorsMetadataDir) {
|
|
708
|
+
jsonFilePath = path.join(config.locatorsMetadataDir, path.basename(jsonFilePath));
|
|
709
|
+
}
|
|
710
|
+
if (existsSync(jsonFilePath)) {
|
|
711
|
+
return jsonFilePath;
|
|
712
|
+
}
|
|
713
|
+
return null;
|
|
714
|
+
}
|
|
715
|
+
return null;
|
|
716
|
+
}
|
|
717
|
+
getFullElementLocators(selectors, filePath) {
|
|
718
|
+
if (!filePath || !existsSync(filePath)) {
|
|
719
|
+
return null;
|
|
720
|
+
}
|
|
721
|
+
const content = fs.readFileSync(filePath, "utf8");
|
|
722
|
+
try {
|
|
723
|
+
const allElements = JSON.parse(content);
|
|
724
|
+
const element_key = selectors?.element_key;
|
|
725
|
+
if (element_key && allElements[element_key]) {
|
|
726
|
+
return allElements[element_key];
|
|
727
|
+
}
|
|
728
|
+
for (const elementKey in allElements) {
|
|
729
|
+
const element = allElements[elementKey];
|
|
730
|
+
let foundStrategy = null;
|
|
731
|
+
for (const key in element) {
|
|
732
|
+
if (key === "strategy") {
|
|
733
|
+
continue;
|
|
734
|
+
}
|
|
735
|
+
const locators = element[key];
|
|
736
|
+
if (!locators || !locators.length) {
|
|
737
|
+
continue;
|
|
738
|
+
}
|
|
739
|
+
for (const locator of locators) {
|
|
740
|
+
delete locator.score;
|
|
741
|
+
}
|
|
742
|
+
if (JSON.stringify(locators) === JSON.stringify(selectors.locators)) {
|
|
743
|
+
foundStrategy = key;
|
|
744
|
+
break;
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
if (foundStrategy) {
|
|
748
|
+
return element;
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
catch (error) {
|
|
753
|
+
console.error("Error parsing locators from file: " + filePath, error);
|
|
754
|
+
}
|
|
755
|
+
return null;
|
|
756
|
+
}
|
|
681
757
|
async _locate(selectors, info, _params, timeout, allowDisabled = false) {
|
|
682
758
|
if (!timeout) {
|
|
683
759
|
timeout = 30000;
|
|
684
760
|
}
|
|
761
|
+
let element = null;
|
|
762
|
+
let allStrategyLocators = null;
|
|
763
|
+
let selectedStrategy = null;
|
|
764
|
+
if (this.tryAllStrategies) {
|
|
765
|
+
allStrategyLocators = this.getFullElementLocators(selectors, this.getFilePath());
|
|
766
|
+
selectedStrategy = allStrategyLocators?.strategy;
|
|
767
|
+
}
|
|
685
768
|
for (let i = 0; i < 3; i++) {
|
|
686
769
|
info.log += "attempt " + i + ": total locators " + selectors.locators.length + "\n";
|
|
687
770
|
for (let j = 0; j < selectors.locators.length; j++) {
|
|
688
771
|
let selector = selectors.locators[j];
|
|
689
772
|
info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
|
|
690
773
|
}
|
|
691
|
-
|
|
774
|
+
if (this.tryAllStrategies && selectedStrategy) {
|
|
775
|
+
const strategyLocators = allStrategyLocators[selectedStrategy];
|
|
776
|
+
let err;
|
|
777
|
+
if (strategyLocators && strategyLocators.length) {
|
|
778
|
+
try {
|
|
779
|
+
selectors.locators = strategyLocators;
|
|
780
|
+
element = await this._locate_internal(selectors, info, _params, 10_000, allowDisabled);
|
|
781
|
+
info.selectedStrategy = selectedStrategy;
|
|
782
|
+
info.log += "element found using strategy " + selectedStrategy + "\n";
|
|
783
|
+
}
|
|
784
|
+
catch (error) {
|
|
785
|
+
err = error;
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
if (!element) {
|
|
789
|
+
for (const key in allStrategyLocators) {
|
|
790
|
+
if (key === "strategy" || key === selectedStrategy) {
|
|
791
|
+
continue;
|
|
792
|
+
}
|
|
793
|
+
const strategyLocators = allStrategyLocators[key];
|
|
794
|
+
if (strategyLocators && strategyLocators.length) {
|
|
795
|
+
try {
|
|
796
|
+
info.log += "using strategy " + key + " with locators " + JSON.stringify(strategyLocators) + "\n";
|
|
797
|
+
selectors.locators = strategyLocators;
|
|
798
|
+
element = await this._locate_internal(selectors, info, _params, 10_000, allowDisabled);
|
|
799
|
+
err = null;
|
|
800
|
+
info.selectedStrategy = key;
|
|
801
|
+
info.log += "element found using strategy " + key + "\n";
|
|
802
|
+
break;
|
|
803
|
+
}
|
|
804
|
+
catch (error) {
|
|
805
|
+
err = error;
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
if (err) {
|
|
811
|
+
throw err;
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
else {
|
|
815
|
+
element = await this._locate_internal(selectors, info, _params, timeout, allowDisabled);
|
|
816
|
+
}
|
|
692
817
|
if (!element.rerun) {
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
818
|
+
let newElementSelector = "";
|
|
819
|
+
if (this.configuration && this.configuration.stableLocatorStrategy === "csschain") {
|
|
820
|
+
const cssSelector = await element.evaluate((el) => {
|
|
821
|
+
function getCssSelector(el) {
|
|
822
|
+
if (!el || el.nodeType !== 1 || el === document.body)
|
|
823
|
+
return el.tagName.toLowerCase();
|
|
824
|
+
const parent = el.parentElement;
|
|
825
|
+
const tag = el.tagName.toLowerCase();
|
|
826
|
+
// Find the index of the element among its siblings of the same tag
|
|
827
|
+
let index = 1;
|
|
828
|
+
for (let sibling = el.previousElementSibling; sibling; sibling = sibling.previousElementSibling) {
|
|
829
|
+
if (sibling.tagName === el.tagName) {
|
|
830
|
+
index++;
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
// Use nth-child if necessary (i.e., if there's more than one of the same tag)
|
|
834
|
+
const siblings = Array.from(parent.children).filter((child) => child.tagName === el.tagName);
|
|
835
|
+
const needsNthChild = siblings.length > 1;
|
|
836
|
+
const selector = needsNthChild ? `${tag}:nth-child(${[...parent.children].indexOf(el) + 1})` : tag;
|
|
837
|
+
return getCssSelector(parent) + " > " + selector;
|
|
838
|
+
}
|
|
839
|
+
const cssSelector = getCssSelector(el);
|
|
840
|
+
return cssSelector;
|
|
841
|
+
});
|
|
842
|
+
newElementSelector = cssSelector;
|
|
843
|
+
}
|
|
844
|
+
else {
|
|
845
|
+
const randomToken = "blinq_" + Math.random().toString(36).substring(7);
|
|
846
|
+
if (this.configuration && this.configuration.stableLocatorStrategy === "data-attribute") {
|
|
847
|
+
const dataAttribute = "data-blinq-id";
|
|
848
|
+
await element.evaluate((el, [dataAttribute, randomToken]) => {
|
|
849
|
+
el.setAttribute(dataAttribute, randomToken);
|
|
850
|
+
}, [dataAttribute, randomToken]);
|
|
851
|
+
newElementSelector = `[${dataAttribute}="${randomToken}"]`;
|
|
852
|
+
}
|
|
853
|
+
else {
|
|
854
|
+
// the default case just return the located element
|
|
855
|
+
// will not work for click and type if the locator is placeholder and the placeholder change due to the click event
|
|
856
|
+
return element;
|
|
857
|
+
}
|
|
858
|
+
}
|
|
700
859
|
const scope = element._frame ?? element.page();
|
|
701
|
-
let newElementSelector = "[data-blinq-id-" + randomToken + "]";
|
|
702
860
|
let prefixSelector = "";
|
|
703
861
|
const frameControlSelector = " >> internal:control=enter-frame";
|
|
704
862
|
const frameSelectorIndex = element._selector.lastIndexOf(frameControlSelector);
|
|
705
863
|
if (frameSelectorIndex !== -1) {
|
|
706
864
|
// remove everything after the >> internal:control=enter-frame
|
|
707
865
|
const frameSelector = element._selector.substring(0, frameSelectorIndex);
|
|
708
|
-
prefixSelector = frameSelector + " >> internal:control=enter-frame >>";
|
|
866
|
+
prefixSelector = frameSelector + " >> internal:control=enter-frame >> ";
|
|
709
867
|
}
|
|
710
868
|
// if (element?._frame?._selector) {
|
|
711
869
|
// prefixSelector = element._frame._selector + " >> " + prefixSelector;
|
|
712
870
|
// }
|
|
713
871
|
const newSelector = prefixSelector + newElementSelector;
|
|
714
|
-
return scope.locator(newSelector);
|
|
872
|
+
return scope.locator(newSelector).first();
|
|
715
873
|
}
|
|
716
874
|
}
|
|
717
875
|
throw new Error("unable to locate element " + JSON.stringify(selectors));
|
|
@@ -732,7 +890,7 @@ class StableBrowser {
|
|
|
732
890
|
for (let i = 0; i < frame.selectors.length; i++) {
|
|
733
891
|
let frameLocator = frame.selectors[i];
|
|
734
892
|
if (frameLocator.css) {
|
|
735
|
-
let testframescope = framescope.frameLocator(frameLocator.css);
|
|
893
|
+
let testframescope = framescope.frameLocator(`${frameLocator.css} >> visible=true`);
|
|
736
894
|
if (frameLocator.index) {
|
|
737
895
|
testframescope = framescope.nth(frameLocator.index);
|
|
738
896
|
}
|
|
@@ -744,7 +902,7 @@ class StableBrowser {
|
|
|
744
902
|
break;
|
|
745
903
|
}
|
|
746
904
|
catch (error) {
|
|
747
|
-
console.error("frame not found " + frameLocator.css);
|
|
905
|
+
// console.error("frame not found " + frameLocator.css);
|
|
748
906
|
}
|
|
749
907
|
}
|
|
750
908
|
}
|
|
@@ -810,6 +968,15 @@ class StableBrowser {
|
|
|
810
968
|
});
|
|
811
969
|
}
|
|
812
970
|
async _locate_internal(selectors, info, _params, timeout = 30000, allowDisabled = false) {
|
|
971
|
+
if (selectors.locators && Array.isArray(selectors.locators)) {
|
|
972
|
+
selectors.locators.forEach((locator) => {
|
|
973
|
+
locator.index = locator.index ?? 0;
|
|
974
|
+
locator.visible = locator.visible ?? true;
|
|
975
|
+
if (locator.visible && locator.css && !locator.css.endsWith(">> visible=true")) {
|
|
976
|
+
locator.css = locator.css + " >> visible=true";
|
|
977
|
+
}
|
|
978
|
+
});
|
|
979
|
+
}
|
|
813
980
|
if (!info) {
|
|
814
981
|
info = {};
|
|
815
982
|
info.failCause = {};
|
|
@@ -822,7 +989,6 @@ class StableBrowser {
|
|
|
822
989
|
let locatorsCount = 0;
|
|
823
990
|
let lazy_scroll = false;
|
|
824
991
|
//let arrayMode = Array.isArray(selectors);
|
|
825
|
-
let scope = await this._findFrameScope(selectors, timeout, info);
|
|
826
992
|
let selectorsLocators = null;
|
|
827
993
|
selectorsLocators = selectors.locators;
|
|
828
994
|
// group selectors by priority
|
|
@@ -850,6 +1016,7 @@ class StableBrowser {
|
|
|
850
1016
|
let highPriorityOnly = true;
|
|
851
1017
|
let visibleOnly = true;
|
|
852
1018
|
while (true) {
|
|
1019
|
+
let scope = await this._findFrameScope(selectors, timeout, info);
|
|
853
1020
|
locatorsCount = 0;
|
|
854
1021
|
let result = [];
|
|
855
1022
|
let popupResult = await this.closeUnexpectedPopups(info, _params);
|
|
@@ -965,9 +1132,13 @@ class StableBrowser {
|
|
|
965
1132
|
}
|
|
966
1133
|
}
|
|
967
1134
|
if (foundLocators.length === 1) {
|
|
1135
|
+
let box = null;
|
|
1136
|
+
if (!this.onlyFailuresScreenshot) {
|
|
1137
|
+
box = await foundLocators[0].boundingBox();
|
|
1138
|
+
}
|
|
968
1139
|
result.foundElements.push({
|
|
969
1140
|
locator: foundLocators[0],
|
|
970
|
-
box:
|
|
1141
|
+
box: box,
|
|
971
1142
|
unique: true,
|
|
972
1143
|
});
|
|
973
1144
|
result.locatorIndex = i;
|
|
@@ -1122,11 +1293,22 @@ class StableBrowser {
|
|
|
1122
1293
|
operation: "click",
|
|
1123
1294
|
log: "***** click on " + selectors.element_name + " *****\n",
|
|
1124
1295
|
};
|
|
1296
|
+
check_performance("click_all ***", this.context, true);
|
|
1297
|
+
let stepFastMode = this.stepTags.includes("fast-mode");
|
|
1298
|
+
if (stepFastMode) {
|
|
1299
|
+
state.onlyFailuresScreenshot = true;
|
|
1300
|
+
state.scroll = false;
|
|
1301
|
+
state.highlight = false;
|
|
1302
|
+
}
|
|
1125
1303
|
try {
|
|
1304
|
+
check_performance("click_preCommand", this.context, true);
|
|
1126
1305
|
await _preCommand(state, this);
|
|
1306
|
+
check_performance("click_preCommand", this.context, false);
|
|
1127
1307
|
await performAction("click", state.element, options, this, state, _params);
|
|
1128
|
-
if (!this.fastMode) {
|
|
1129
|
-
|
|
1308
|
+
if (!this.fastMode && !this.stepTags.includes("fast-mode")) {
|
|
1309
|
+
check_performance("click_waitForPageLoad", this.context, true);
|
|
1310
|
+
await this.waitForPageLoad({ noSleep: true });
|
|
1311
|
+
check_performance("click_waitForPageLoad", this.context, false);
|
|
1130
1312
|
}
|
|
1131
1313
|
return state.info;
|
|
1132
1314
|
}
|
|
@@ -1134,7 +1316,13 @@ class StableBrowser {
|
|
|
1134
1316
|
await _commandError(state, e, this);
|
|
1135
1317
|
}
|
|
1136
1318
|
finally {
|
|
1319
|
+
check_performance("click_commandFinally", this.context, true);
|
|
1137
1320
|
await _commandFinally(state, this);
|
|
1321
|
+
check_performance("click_commandFinally", this.context, false);
|
|
1322
|
+
check_performance("click_all ***", this.context, false);
|
|
1323
|
+
if (this.context.profile) {
|
|
1324
|
+
console.log(JSON.stringify(this.context.profile, null, 2));
|
|
1325
|
+
}
|
|
1138
1326
|
}
|
|
1139
1327
|
}
|
|
1140
1328
|
async waitForElement(selectors, _params, options = {}, world = null) {
|
|
@@ -1226,7 +1414,7 @@ class StableBrowser {
|
|
|
1226
1414
|
}
|
|
1227
1415
|
}
|
|
1228
1416
|
}
|
|
1229
|
-
await this.waitForPageLoad();
|
|
1417
|
+
//await this.waitForPageLoad();
|
|
1230
1418
|
return state.info;
|
|
1231
1419
|
}
|
|
1232
1420
|
catch (e) {
|
|
@@ -1252,7 +1440,7 @@ class StableBrowser {
|
|
|
1252
1440
|
await _preCommand(state, this);
|
|
1253
1441
|
await performAction("hover", state.element, options, this, state, _params);
|
|
1254
1442
|
await _screenshot(state, this);
|
|
1255
|
-
await this.waitForPageLoad();
|
|
1443
|
+
//await this.waitForPageLoad();
|
|
1256
1444
|
return state.info;
|
|
1257
1445
|
}
|
|
1258
1446
|
catch (e) {
|
|
@@ -1288,7 +1476,7 @@ class StableBrowser {
|
|
|
1288
1476
|
state.info.log += "selectOption failed, will try force" + "\n";
|
|
1289
1477
|
await state.element.selectOption(values, { timeout: 10000, force: true });
|
|
1290
1478
|
}
|
|
1291
|
-
await this.waitForPageLoad();
|
|
1479
|
+
//await this.waitForPageLoad();
|
|
1292
1480
|
return state.info;
|
|
1293
1481
|
}
|
|
1294
1482
|
catch (e) {
|
|
@@ -1474,6 +1662,14 @@ class StableBrowser {
|
|
|
1474
1662
|
}
|
|
1475
1663
|
try {
|
|
1476
1664
|
await _preCommand(state, this);
|
|
1665
|
+
const randomToken = "blinq_" + Math.random().toString(36).substring(7);
|
|
1666
|
+
// tag the element
|
|
1667
|
+
let newElementSelector = await state.element.evaluate((el, token) => {
|
|
1668
|
+
// use attribute and not id
|
|
1669
|
+
const attrName = `data-blinq-id-${token}`;
|
|
1670
|
+
el.setAttribute(attrName, "");
|
|
1671
|
+
return `[${attrName}]`;
|
|
1672
|
+
}, randomToken);
|
|
1477
1673
|
state.info.value = _value;
|
|
1478
1674
|
if (!options.press) {
|
|
1479
1675
|
try {
|
|
@@ -1499,6 +1695,25 @@ class StableBrowser {
|
|
|
1499
1695
|
}
|
|
1500
1696
|
}
|
|
1501
1697
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1698
|
+
// check if the element exist after the click (no wait)
|
|
1699
|
+
const count = await state.element.count({ timeout: 0 });
|
|
1700
|
+
if (count === 0) {
|
|
1701
|
+
// the locator changed after the click (placeholder) we need to locate the element using the data-blinq-id
|
|
1702
|
+
const scope = state.element._frame ?? element.page();
|
|
1703
|
+
let prefixSelector = "";
|
|
1704
|
+
const frameControlSelector = " >> internal:control=enter-frame";
|
|
1705
|
+
const frameSelectorIndex = state.element._selector.lastIndexOf(frameControlSelector);
|
|
1706
|
+
if (frameSelectorIndex !== -1) {
|
|
1707
|
+
// remove everything after the >> internal:control=enter-frame
|
|
1708
|
+
const frameSelector = state.element._selector.substring(0, frameSelectorIndex);
|
|
1709
|
+
prefixSelector = frameSelector + " >> internal:control=enter-frame >> ";
|
|
1710
|
+
}
|
|
1711
|
+
// if (element?._frame?._selector) {
|
|
1712
|
+
// prefixSelector = element._frame._selector + " >> " + prefixSelector;
|
|
1713
|
+
// }
|
|
1714
|
+
const newSelector = prefixSelector + newElementSelector;
|
|
1715
|
+
state.element = scope.locator(newSelector).first();
|
|
1716
|
+
}
|
|
1502
1717
|
const valueSegment = state.value.split("&&");
|
|
1503
1718
|
for (let i = 0; i < valueSegment.length; i++) {
|
|
1504
1719
|
if (i > 0) {
|
|
@@ -1570,8 +1785,8 @@ class StableBrowser {
|
|
|
1570
1785
|
if (enter) {
|
|
1571
1786
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1572
1787
|
await this.page.keyboard.press("Enter");
|
|
1788
|
+
await this.waitForPageLoad();
|
|
1573
1789
|
}
|
|
1574
|
-
await this.waitForPageLoad();
|
|
1575
1790
|
return state.info;
|
|
1576
1791
|
}
|
|
1577
1792
|
catch (e) {
|
|
@@ -2437,7 +2652,7 @@ class StableBrowser {
|
|
|
2437
2652
|
let expectedValue;
|
|
2438
2653
|
try {
|
|
2439
2654
|
await _preCommand(state, this);
|
|
2440
|
-
expectedValue = await
|
|
2655
|
+
expectedValue = await this._replaceWithLocalData(value, world);
|
|
2441
2656
|
state.info.expectedValue = expectedValue;
|
|
2442
2657
|
switch (property) {
|
|
2443
2658
|
case "innerText":
|
|
@@ -2485,47 +2700,54 @@ class StableBrowser {
|
|
|
2485
2700
|
}
|
|
2486
2701
|
state.info.value = val;
|
|
2487
2702
|
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
|
-
}
|
|
2703
|
+
state.info.value = val;
|
|
2704
|
+
const isRegex = expectedValue.startsWith("regex:");
|
|
2705
|
+
const isContains = expectedValue.startsWith("contains:");
|
|
2706
|
+
const isExact = expectedValue.startsWith("exact:");
|
|
2707
|
+
let matchPassed = false;
|
|
2708
|
+
if (isRegex) {
|
|
2709
|
+
const rawPattern = expectedValue.slice(6); // remove "regex:"
|
|
2710
|
+
const lastSlashIndex = rawPattern.lastIndexOf("/");
|
|
2711
|
+
if (rawPattern.startsWith("/") && lastSlashIndex > 0) {
|
|
2712
|
+
const patternBody = rawPattern.slice(1, lastSlashIndex).replace(/\n/g, ".*");
|
|
2713
|
+
const flags = rawPattern.slice(lastSlashIndex + 1) || "gs";
|
|
2714
|
+
const regex = new RegExp(patternBody, flags);
|
|
2715
|
+
state.info.regex = true;
|
|
2716
|
+
matchPassed = regex.test(val);
|
|
2506
2717
|
}
|
|
2507
2718
|
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
|
-
}
|
|
2719
|
+
// Fallback: treat as literal
|
|
2720
|
+
const escapedPattern = rawPattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2721
|
+
const regex = new RegExp(escapedPattern, "g");
|
|
2722
|
+
matchPassed = regex.test(val);
|
|
2520
2723
|
}
|
|
2521
2724
|
}
|
|
2725
|
+
else if (isContains) {
|
|
2726
|
+
const containsValue = expectedValue.slice(9); // remove "contains:"
|
|
2727
|
+
matchPassed = val.includes(containsValue);
|
|
2728
|
+
}
|
|
2729
|
+
else if (isExact) {
|
|
2730
|
+
const exactValue = expectedValue.slice(6); // remove "exact:"
|
|
2731
|
+
matchPassed = val === exactValue;
|
|
2732
|
+
}
|
|
2733
|
+
else if (property === "innerText") {
|
|
2734
|
+
// Default innerText logic
|
|
2735
|
+
const normalizedExpectedValue = expectedValue.replace(/\\n/g, "\n");
|
|
2736
|
+
const valLines = val.split("\n");
|
|
2737
|
+
const expectedLines = normalizedExpectedValue.split("\n");
|
|
2738
|
+
matchPassed = expectedLines.every((expectedLine) => valLines.some((valLine) => valLine.trim() === expectedLine.trim()));
|
|
2739
|
+
}
|
|
2522
2740
|
else {
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2741
|
+
// Fallback exact or loose match
|
|
2742
|
+
const escapedPattern = expectedValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2743
|
+
const regex = new RegExp(escapedPattern, "g");
|
|
2744
|
+
matchPassed = regex.test(val);
|
|
2745
|
+
}
|
|
2746
|
+
if (!matchPassed) {
|
|
2747
|
+
let errorMessage = `The ${property} property has a value of "${val}", but the expected value is "${expectedValue}"`;
|
|
2748
|
+
state.info.failCause.assertionFailed = true;
|
|
2749
|
+
state.info.failCause.lastError = errorMessage;
|
|
2750
|
+
throw new Error(errorMessage);
|
|
2529
2751
|
}
|
|
2530
2752
|
return state.info;
|
|
2531
2753
|
}
|
|
@@ -2556,6 +2778,7 @@ class StableBrowser {
|
|
|
2556
2778
|
allowDisabled: true,
|
|
2557
2779
|
info: {},
|
|
2558
2780
|
};
|
|
2781
|
+
state.options ??= { timeout: timeoutMs };
|
|
2559
2782
|
// Initialize startTime outside try block to ensure it's always accessible
|
|
2560
2783
|
const startTime = Date.now();
|
|
2561
2784
|
let conditionMet = false;
|
|
@@ -2614,7 +2837,7 @@ class StableBrowser {
|
|
|
2614
2837
|
}
|
|
2615
2838
|
// Check if we still have time for another attempt
|
|
2616
2839
|
if (Date.now() - startTime + 50 < timeoutMs) {
|
|
2617
|
-
await new Promise(res => setTimeout(res, 50));
|
|
2840
|
+
await new Promise((res) => setTimeout(res, 50));
|
|
2618
2841
|
}
|
|
2619
2842
|
else {
|
|
2620
2843
|
break;
|
|
@@ -2633,7 +2856,7 @@ class StableBrowser {
|
|
|
2633
2856
|
const timeLeft = timeoutMs - currentElapsedTime;
|
|
2634
2857
|
// Check if we have enough time left to retry
|
|
2635
2858
|
if (timeLeft > 100) {
|
|
2636
|
-
await new Promise(resolve => setTimeout(resolve, 50));
|
|
2859
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
2637
2860
|
}
|
|
2638
2861
|
else {
|
|
2639
2862
|
break;
|
|
@@ -3131,7 +3354,16 @@ class StableBrowser {
|
|
|
3131
3354
|
text = text.replace(/\\"/g, '"');
|
|
3132
3355
|
}
|
|
3133
3356
|
const timeout = this._getFindElementTimeout(options);
|
|
3134
|
-
|
|
3357
|
+
//if (!this.fastMode && !this.stepTags.includes("fast-mode")) {
|
|
3358
|
+
let stepFastMode = this.stepTags.includes("fast-mode");
|
|
3359
|
+
if (!stepFastMode) {
|
|
3360
|
+
if (!this.fastMode) {
|
|
3361
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
3362
|
+
}
|
|
3363
|
+
else {
|
|
3364
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
3365
|
+
}
|
|
3366
|
+
}
|
|
3135
3367
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
3136
3368
|
if (newValue !== text) {
|
|
3137
3369
|
this.logger.info(text + "=" + newValue);
|
|
@@ -3139,6 +3371,11 @@ class StableBrowser {
|
|
|
3139
3371
|
}
|
|
3140
3372
|
let dateAlternatives = findDateAlternatives(text);
|
|
3141
3373
|
let numberAlternatives = findNumberAlternatives(text);
|
|
3374
|
+
if (stepFastMode) {
|
|
3375
|
+
state.onlyFailuresScreenshot = true;
|
|
3376
|
+
state.scroll = false;
|
|
3377
|
+
state.highlight = false;
|
|
3378
|
+
}
|
|
3142
3379
|
try {
|
|
3143
3380
|
await _preCommand(state, this);
|
|
3144
3381
|
state.info.text = text;
|
|
@@ -3258,6 +3495,8 @@ class StableBrowser {
|
|
|
3258
3495
|
operation: "verify_text_with_relation",
|
|
3259
3496
|
log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
|
|
3260
3497
|
};
|
|
3498
|
+
const cmdStartTime = Date.now();
|
|
3499
|
+
let cmdEndTime = null;
|
|
3261
3500
|
const timeout = this._getFindElementTimeout(options);
|
|
3262
3501
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
3263
3502
|
let newValue = await this._replaceWithLocalData(textAnchor, world);
|
|
@@ -3293,6 +3532,17 @@ class StableBrowser {
|
|
|
3293
3532
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
3294
3533
|
continue;
|
|
3295
3534
|
}
|
|
3535
|
+
else {
|
|
3536
|
+
cmdEndTime = Date.now();
|
|
3537
|
+
if (cmdEndTime - cmdStartTime > 55000) {
|
|
3538
|
+
if (foundAncore) {
|
|
3539
|
+
throw new Error(`Text ${textToVerify} not found in page`);
|
|
3540
|
+
}
|
|
3541
|
+
else {
|
|
3542
|
+
throw new Error(`Text ${textAnchor} not found in page`);
|
|
3543
|
+
}
|
|
3544
|
+
}
|
|
3545
|
+
}
|
|
3296
3546
|
try {
|
|
3297
3547
|
for (let i = 0; i < resultWithElementsFound.length; i++) {
|
|
3298
3548
|
foundAncore = true;
|
|
@@ -3431,7 +3681,7 @@ class StableBrowser {
|
|
|
3431
3681
|
Object.assign(e, { info: info });
|
|
3432
3682
|
error = e;
|
|
3433
3683
|
// throw e;
|
|
3434
|
-
await _commandError({ text: "visualVerification", operation: "visualVerification",
|
|
3684
|
+
await _commandError({ text: "visualVerification", operation: "visualVerification", info }, e, this);
|
|
3435
3685
|
}
|
|
3436
3686
|
finally {
|
|
3437
3687
|
const endTime = Date.now();
|
|
@@ -3780,6 +4030,22 @@ class StableBrowser {
|
|
|
3780
4030
|
}
|
|
3781
4031
|
}
|
|
3782
4032
|
async waitForPageLoad(options = {}, world = null) {
|
|
4033
|
+
// try {
|
|
4034
|
+
// let currentPagePath = null;
|
|
4035
|
+
// currentPagePath = new URL(this.page.url()).pathname;
|
|
4036
|
+
// if (this.latestPagePath) {
|
|
4037
|
+
// // get the currect page path and compare with the latest page path
|
|
4038
|
+
// if (this.latestPagePath === currentPagePath) {
|
|
4039
|
+
// // if the page path is the same, do not wait for page load
|
|
4040
|
+
// console.log("No page change: " + currentPagePath);
|
|
4041
|
+
// return;
|
|
4042
|
+
// }
|
|
4043
|
+
// }
|
|
4044
|
+
// this.latestPagePath = currentPagePath;
|
|
4045
|
+
// } catch (e) {
|
|
4046
|
+
// console.debug("Error getting current page path: ", e);
|
|
4047
|
+
// }
|
|
4048
|
+
//console.log("Waiting for page load");
|
|
3783
4049
|
let timeout = this._getLoadTimeout(options);
|
|
3784
4050
|
const promiseArray = [];
|
|
3785
4051
|
// let waitForNetworkIdle = true;
|
|
@@ -3814,7 +4080,10 @@ class StableBrowser {
|
|
|
3814
4080
|
}
|
|
3815
4081
|
}
|
|
3816
4082
|
finally {
|
|
3817
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
4083
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
4084
|
+
if (options && !options.noSleep) {
|
|
4085
|
+
await new Promise((resolve) => setTimeout(resolve, 1500));
|
|
4086
|
+
}
|
|
3818
4087
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world));
|
|
3819
4088
|
const endTime = Date.now();
|
|
3820
4089
|
_reportToWorld(world, {
|
|
@@ -3868,7 +4137,7 @@ class StableBrowser {
|
|
|
3868
4137
|
}
|
|
3869
4138
|
operation = options.operation;
|
|
3870
4139
|
// validate operation is one of the supported operations
|
|
3871
|
-
if (operation != "click" && operation != "hover+click") {
|
|
4140
|
+
if (operation != "click" && operation != "hover+click" && operation != "hover") {
|
|
3872
4141
|
throw new Error("operation is not supported");
|
|
3873
4142
|
}
|
|
3874
4143
|
const state = {
|
|
@@ -3937,6 +4206,17 @@ class StableBrowser {
|
|
|
3937
4206
|
state.element = results[0];
|
|
3938
4207
|
await performAction("hover+click", state.element, options, this, state, _params);
|
|
3939
4208
|
break;
|
|
4209
|
+
case "hover":
|
|
4210
|
+
if (!options.css) {
|
|
4211
|
+
throw new Error("css is not defined");
|
|
4212
|
+
}
|
|
4213
|
+
const result1 = await findElementsInArea(options.css, cellArea, this, options);
|
|
4214
|
+
if (result1.length === 0) {
|
|
4215
|
+
throw new Error(`Element not found in cell area`);
|
|
4216
|
+
}
|
|
4217
|
+
state.element = result1[0];
|
|
4218
|
+
await performAction("hover", state.element, options, this, state, _params);
|
|
4219
|
+
break;
|
|
3940
4220
|
default:
|
|
3941
4221
|
throw new Error("operation is not supported");
|
|
3942
4222
|
}
|
|
@@ -4052,6 +4332,7 @@ class StableBrowser {
|
|
|
4052
4332
|
if (world && world.attach) {
|
|
4053
4333
|
world.attach(this.context.reportFolder, { mediaType: "text/plain" });
|
|
4054
4334
|
}
|
|
4335
|
+
this.context.loadedRoutes = null;
|
|
4055
4336
|
this.beforeScenarioCalled = true;
|
|
4056
4337
|
if (scenario && scenario.pickle && scenario.pickle.name) {
|
|
4057
4338
|
this.scenarioName = scenario.pickle.name;
|
|
@@ -4081,8 +4362,10 @@ class StableBrowser {
|
|
|
4081
4362
|
}
|
|
4082
4363
|
async afterScenario(world, scenario) { }
|
|
4083
4364
|
async beforeStep(world, step) {
|
|
4365
|
+
this.stepTags = [];
|
|
4084
4366
|
if (!this.beforeScenarioCalled) {
|
|
4085
4367
|
this.beforeScenario(world, step);
|
|
4368
|
+
this.context.loadedRoutes = null;
|
|
4086
4369
|
}
|
|
4087
4370
|
if (this.stepIndex === undefined) {
|
|
4088
4371
|
this.stepIndex = 0;
|
|
@@ -4092,7 +4375,12 @@ class StableBrowser {
|
|
|
4092
4375
|
}
|
|
4093
4376
|
if (step && step.pickleStep && step.pickleStep.text) {
|
|
4094
4377
|
this.stepName = step.pickleStep.text;
|
|
4095
|
-
|
|
4378
|
+
let printableStepName = this.stepName;
|
|
4379
|
+
// take the printableStepName and replace quated value with \x1b[33m and \x1b[0m
|
|
4380
|
+
printableStepName = printableStepName.replace(/"([^"]*)"/g, (match, p1) => {
|
|
4381
|
+
return `\x1b[33m"${p1}"\x1b[0m`;
|
|
4382
|
+
});
|
|
4383
|
+
this.logger.info("\x1b[38;5;208mstep:\x1b[0m " + printableStepName);
|
|
4096
4384
|
}
|
|
4097
4385
|
else if (step && step.text) {
|
|
4098
4386
|
this.stepName = step.text;
|
|
@@ -4107,7 +4395,10 @@ class StableBrowser {
|
|
|
4107
4395
|
}
|
|
4108
4396
|
if (this.initSnapshotTaken === false) {
|
|
4109
4397
|
this.initSnapshotTaken = true;
|
|
4110
|
-
if (world &&
|
|
4398
|
+
if (world &&
|
|
4399
|
+
world.attach &&
|
|
4400
|
+
!process.env.DISABLE_SNAPSHOT &&
|
|
4401
|
+
(!this.fastMode || this.stepTags.includes("fast-mode"))) {
|
|
4111
4402
|
const snapshot = await this.getAriaSnapshot();
|
|
4112
4403
|
if (snapshot) {
|
|
4113
4404
|
await world.attach(JSON.stringify(snapshot), "application/json+snapshot-before");
|
|
@@ -4115,7 +4406,12 @@ class StableBrowser {
|
|
|
4115
4406
|
}
|
|
4116
4407
|
}
|
|
4117
4408
|
this.context.routeResults = null;
|
|
4118
|
-
|
|
4409
|
+
this.context.loadedRoutes = null;
|
|
4410
|
+
await registerBeforeStepRoutes(this.context, this.stepName, world);
|
|
4411
|
+
networkBeforeStep(this.stepName, this.context);
|
|
4412
|
+
}
|
|
4413
|
+
setStepTags(tags) {
|
|
4414
|
+
this.stepTags = tags;
|
|
4119
4415
|
}
|
|
4120
4416
|
async getAriaSnapshot() {
|
|
4121
4417
|
try {
|
|
@@ -4135,12 +4431,18 @@ class StableBrowser {
|
|
|
4135
4431
|
try {
|
|
4136
4432
|
// Ensure frame is attached and has body
|
|
4137
4433
|
const body = frame.locator("body");
|
|
4138
|
-
await body.waitFor({ timeout:
|
|
4434
|
+
//await body.waitFor({ timeout: 2000 }); // wait explicitly
|
|
4139
4435
|
const snapshot = await body.ariaSnapshot({ timeout });
|
|
4436
|
+
if (!snapshot) {
|
|
4437
|
+
continue;
|
|
4438
|
+
}
|
|
4140
4439
|
content.push(`- frame: ${i}`);
|
|
4141
4440
|
content.push(snapshot);
|
|
4142
4441
|
}
|
|
4143
|
-
catch (innerErr) {
|
|
4442
|
+
catch (innerErr) {
|
|
4443
|
+
console.warn(`Frame ${i} snapshot failed:`, innerErr);
|
|
4444
|
+
content.push(`- frame: ${i} - error: ${innerErr.message}`);
|
|
4445
|
+
}
|
|
4144
4446
|
}
|
|
4145
4447
|
return content.join("\n");
|
|
4146
4448
|
}
|
|
@@ -4212,7 +4514,11 @@ class StableBrowser {
|
|
|
4212
4514
|
if (this.context) {
|
|
4213
4515
|
this.context.examplesRow = null;
|
|
4214
4516
|
}
|
|
4215
|
-
if (world &&
|
|
4517
|
+
if (world &&
|
|
4518
|
+
world.attach &&
|
|
4519
|
+
!process.env.DISABLE_SNAPSHOT &&
|
|
4520
|
+
!this.fastMode &&
|
|
4521
|
+
!this.stepTags.includes("fast-mode")) {
|
|
4216
4522
|
const snapshot = await this.getAriaSnapshot();
|
|
4217
4523
|
if (snapshot) {
|
|
4218
4524
|
const obj = {};
|
|
@@ -4220,6 +4526,11 @@ class StableBrowser {
|
|
|
4220
4526
|
}
|
|
4221
4527
|
}
|
|
4222
4528
|
this.context.routeResults = await registerAfterStepRoutes(this.context, world);
|
|
4529
|
+
if (this.context.routeResults) {
|
|
4530
|
+
if (world && world.attach) {
|
|
4531
|
+
await world.attach(JSON.stringify(this.context.routeResults), "application/json+intercept-results");
|
|
4532
|
+
}
|
|
4533
|
+
}
|
|
4223
4534
|
if (!process.env.TEMP_RUN) {
|
|
4224
4535
|
const state = {
|
|
4225
4536
|
world,
|
|
@@ -4243,6 +4554,13 @@ class StableBrowser {
|
|
|
4243
4554
|
await _commandFinally(state, this);
|
|
4244
4555
|
}
|
|
4245
4556
|
}
|
|
4557
|
+
networkAfterStep(this.stepName, this.context);
|
|
4558
|
+
if (process.env.TEMP_RUN === "true") {
|
|
4559
|
+
// Put a sleep for some time to allow the browser to finish processing
|
|
4560
|
+
if (!this.stepTags.includes("fast-mode")) {
|
|
4561
|
+
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
4562
|
+
}
|
|
4563
|
+
}
|
|
4246
4564
|
}
|
|
4247
4565
|
}
|
|
4248
4566
|
function createTimedPromise(promise, label) {
|