automation_model 1.0.779-dev → 1.0.779-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 +51 -15
- package/lib/auto_page.js.map +1 -1
- package/lib/browser_manager.js +69 -48
- 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.d.ts +1 -1
- package/lib/command_common.js +26 -16
- package/lib/command_common.js.map +1 -1
- package/lib/file_checker.js +51 -4
- 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 -2
- package/lib/network.js +341 -178
- package/lib/network.js.map +1 -1
- package/lib/route.d.ts +64 -15
- package/lib/route.js +524 -196
- 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 +13 -7
- package/lib/stable_browser.js +439 -116
- 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";
|
|
@@ -26,6 +28,7 @@ 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,7 +139,7 @@ class StableBrowser {
|
|
|
131
139
|
this.fastMode = true;
|
|
132
140
|
}
|
|
133
141
|
if (process.env.FAST_MODE === "true") {
|
|
134
|
-
console.log("Fast mode enabled from environment variable");
|
|
142
|
+
// console.log("Fast mode enabled from environment variable");
|
|
135
143
|
this.fastMode = true;
|
|
136
144
|
}
|
|
137
145
|
if (process.env.FAST_MODE === "false") {
|
|
@@ -174,6 +182,7 @@ class StableBrowser {
|
|
|
174
182
|
registerNetworkEvents(this.world, this, context, this.page);
|
|
175
183
|
registerDownloadEvent(this.page, this.world, context);
|
|
176
184
|
page.on("close", async () => {
|
|
185
|
+
// return if browser context is already closed
|
|
177
186
|
if (this.context && this.context.pages && this.context.pages.length > 1) {
|
|
178
187
|
this.context.pages.pop();
|
|
179
188
|
this.page = this.context.pages[this.context.pages.length - 1];
|
|
@@ -183,7 +192,12 @@ class StableBrowser {
|
|
|
183
192
|
console.log("Switched to page " + title);
|
|
184
193
|
}
|
|
185
194
|
catch (error) {
|
|
186
|
-
|
|
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
|
+
}
|
|
187
201
|
}
|
|
188
202
|
}
|
|
189
203
|
});
|
|
@@ -192,7 +206,12 @@ class StableBrowser {
|
|
|
192
206
|
console.log("Switch page: " + (await page.title()));
|
|
193
207
|
}
|
|
194
208
|
catch (e) {
|
|
195
|
-
|
|
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
|
+
}
|
|
196
215
|
}
|
|
197
216
|
context.pageLoading.status = false;
|
|
198
217
|
}.bind(this));
|
|
@@ -220,7 +239,7 @@ class StableBrowser {
|
|
|
220
239
|
if (newContextCreated) {
|
|
221
240
|
this.registerEventListeners(this.context);
|
|
222
241
|
await this.goto(this.context.environment.baseUrl);
|
|
223
|
-
if (!this.fastMode) {
|
|
242
|
+
if (!this.fastMode && !this.stepTags.includes("fast-mode")) {
|
|
224
243
|
await this.waitForPageLoad();
|
|
225
244
|
}
|
|
226
245
|
}
|
|
@@ -312,7 +331,7 @@ class StableBrowser {
|
|
|
312
331
|
// async closeUnexpectedPopups() {
|
|
313
332
|
// await closeUnexpectedPopups(this.page);
|
|
314
333
|
// }
|
|
315
|
-
async goto(url, world = null) {
|
|
334
|
+
async goto(url, world = null, options = {}) {
|
|
316
335
|
if (!url) {
|
|
317
336
|
throw new Error("url is null, verify that the environment file is correct");
|
|
318
337
|
}
|
|
@@ -333,10 +352,14 @@ class StableBrowser {
|
|
|
333
352
|
screenshot: false,
|
|
334
353
|
highlight: false,
|
|
335
354
|
};
|
|
355
|
+
let timeout = 60000;
|
|
356
|
+
if (options && options["timeout"]) {
|
|
357
|
+
timeout = options["timeout"];
|
|
358
|
+
}
|
|
336
359
|
try {
|
|
337
360
|
await _preCommand(state, this);
|
|
338
361
|
await this.page.goto(url, {
|
|
339
|
-
timeout:
|
|
362
|
+
timeout: timeout,
|
|
340
363
|
});
|
|
341
364
|
await _screenshot(state, this);
|
|
342
365
|
}
|
|
@@ -504,12 +527,6 @@ class StableBrowser {
|
|
|
504
527
|
if (!el.setAttribute) {
|
|
505
528
|
el = el.parentElement;
|
|
506
529
|
}
|
|
507
|
-
// remove any attributes start with data-blinq-id
|
|
508
|
-
// for (let i = 0; i < el.attributes.length; i++) {
|
|
509
|
-
// if (el.attributes[i].name.startsWith("data-blinq-id")) {
|
|
510
|
-
// el.removeAttribute(el.attributes[i].name);
|
|
511
|
-
// }
|
|
512
|
-
// }
|
|
513
530
|
el.setAttribute("data-blinq-id-" + randomToken, "");
|
|
514
531
|
return true;
|
|
515
532
|
}, [tag1, randomToken]))) {
|
|
@@ -679,40 +696,186 @@ class StableBrowser {
|
|
|
679
696
|
}
|
|
680
697
|
return { rerun: false };
|
|
681
698
|
}
|
|
699
|
+
getFilePath() {
|
|
700
|
+
const stackFrames = errorStackParser.parse(new Error());
|
|
701
|
+
const stackFrame = stackFrames.findLast((frame) => frame.fileName && frame.fileName.endsWith(".mjs"));
|
|
702
|
+
// return stackFrame?.fileName || null;
|
|
703
|
+
const filepath = stackFrame?.fileName;
|
|
704
|
+
if (filepath) {
|
|
705
|
+
let jsonFilePath = filepath.replace(".mjs", ".json");
|
|
706
|
+
if (existsSync(jsonFilePath)) {
|
|
707
|
+
return jsonFilePath;
|
|
708
|
+
}
|
|
709
|
+
const config = this.configuration ?? {};
|
|
710
|
+
if (!config?.locatorsMetadataDir) {
|
|
711
|
+
config.locatorsMetadataDir = "features/step_definitions/locators";
|
|
712
|
+
}
|
|
713
|
+
if (config && config.locatorsMetadataDir) {
|
|
714
|
+
jsonFilePath = path.join(config.locatorsMetadataDir, path.basename(jsonFilePath));
|
|
715
|
+
}
|
|
716
|
+
if (existsSync(jsonFilePath)) {
|
|
717
|
+
return jsonFilePath;
|
|
718
|
+
}
|
|
719
|
+
return null;
|
|
720
|
+
}
|
|
721
|
+
return null;
|
|
722
|
+
}
|
|
723
|
+
getFullElementLocators(selectors, filePath) {
|
|
724
|
+
if (!filePath || !existsSync(filePath)) {
|
|
725
|
+
return null;
|
|
726
|
+
}
|
|
727
|
+
const content = fs.readFileSync(filePath, "utf8");
|
|
728
|
+
try {
|
|
729
|
+
const allElements = JSON.parse(content);
|
|
730
|
+
const element_key = selectors?.element_key;
|
|
731
|
+
if (element_key && allElements[element_key]) {
|
|
732
|
+
return allElements[element_key];
|
|
733
|
+
}
|
|
734
|
+
for (const elementKey in allElements) {
|
|
735
|
+
const element = allElements[elementKey];
|
|
736
|
+
let foundStrategy = null;
|
|
737
|
+
for (const key in element) {
|
|
738
|
+
if (key === "strategy") {
|
|
739
|
+
continue;
|
|
740
|
+
}
|
|
741
|
+
const locators = element[key];
|
|
742
|
+
if (!locators || !locators.length) {
|
|
743
|
+
continue;
|
|
744
|
+
}
|
|
745
|
+
for (const locator of locators) {
|
|
746
|
+
delete locator.score;
|
|
747
|
+
}
|
|
748
|
+
if (JSON.stringify(locators) === JSON.stringify(selectors.locators)) {
|
|
749
|
+
foundStrategy = key;
|
|
750
|
+
break;
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
if (foundStrategy) {
|
|
754
|
+
return element;
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
catch (error) {
|
|
759
|
+
console.error("Error parsing locators from file: " + filePath, error);
|
|
760
|
+
}
|
|
761
|
+
return null;
|
|
762
|
+
}
|
|
682
763
|
async _locate(selectors, info, _params, timeout, allowDisabled = false) {
|
|
683
764
|
if (!timeout) {
|
|
684
765
|
timeout = 30000;
|
|
685
766
|
}
|
|
767
|
+
let element = null;
|
|
768
|
+
let allStrategyLocators = null;
|
|
769
|
+
let selectedStrategy = null;
|
|
770
|
+
if (this.tryAllStrategies) {
|
|
771
|
+
allStrategyLocators = this.getFullElementLocators(selectors, this.getFilePath());
|
|
772
|
+
selectedStrategy = allStrategyLocators?.strategy;
|
|
773
|
+
}
|
|
686
774
|
for (let i = 0; i < 3; i++) {
|
|
687
775
|
info.log += "attempt " + i + ": total locators " + selectors.locators.length + "\n";
|
|
688
776
|
for (let j = 0; j < selectors.locators.length; j++) {
|
|
689
777
|
let selector = selectors.locators[j];
|
|
690
778
|
info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
|
|
691
779
|
}
|
|
692
|
-
|
|
780
|
+
if (this.tryAllStrategies && selectedStrategy) {
|
|
781
|
+
const strategyLocators = allStrategyLocators[selectedStrategy];
|
|
782
|
+
let err;
|
|
783
|
+
if (strategyLocators && strategyLocators.length) {
|
|
784
|
+
try {
|
|
785
|
+
selectors.locators = strategyLocators;
|
|
786
|
+
element = await this._locate_internal(selectors, info, _params, 10_000, allowDisabled);
|
|
787
|
+
info.selectedStrategy = selectedStrategy;
|
|
788
|
+
info.log += "element found using strategy " + selectedStrategy + "\n";
|
|
789
|
+
}
|
|
790
|
+
catch (error) {
|
|
791
|
+
err = error;
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
if (!element) {
|
|
795
|
+
for (const key in allStrategyLocators) {
|
|
796
|
+
if (key === "strategy" || key === selectedStrategy) {
|
|
797
|
+
continue;
|
|
798
|
+
}
|
|
799
|
+
const strategyLocators = allStrategyLocators[key];
|
|
800
|
+
if (strategyLocators && strategyLocators.length) {
|
|
801
|
+
try {
|
|
802
|
+
info.log += "using strategy " + key + " with locators " + JSON.stringify(strategyLocators) + "\n";
|
|
803
|
+
selectors.locators = strategyLocators;
|
|
804
|
+
element = await this._locate_internal(selectors, info, _params, 10_000, allowDisabled);
|
|
805
|
+
err = null;
|
|
806
|
+
info.selectedStrategy = key;
|
|
807
|
+
info.log += "element found using strategy " + key + "\n";
|
|
808
|
+
break;
|
|
809
|
+
}
|
|
810
|
+
catch (error) {
|
|
811
|
+
err = error;
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
if (err) {
|
|
817
|
+
throw err;
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
else {
|
|
821
|
+
element = await this._locate_internal(selectors, info, _params, timeout, allowDisabled);
|
|
822
|
+
}
|
|
693
823
|
if (!element.rerun) {
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
824
|
+
let newElementSelector = "";
|
|
825
|
+
if (this.configuration && this.configuration.stableLocatorStrategy === "csschain") {
|
|
826
|
+
const cssSelector = await element.evaluate((el) => {
|
|
827
|
+
function getCssSelector(el) {
|
|
828
|
+
if (!el || el.nodeType !== 1 || el === document.body)
|
|
829
|
+
return el.tagName.toLowerCase();
|
|
830
|
+
const parent = el.parentElement;
|
|
831
|
+
const tag = el.tagName.toLowerCase();
|
|
832
|
+
// Find the index of the element among its siblings of the same tag
|
|
833
|
+
let index = 1;
|
|
834
|
+
for (let sibling = el.previousElementSibling; sibling; sibling = sibling.previousElementSibling) {
|
|
835
|
+
if (sibling.tagName === el.tagName) {
|
|
836
|
+
index++;
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
// Use nth-child if necessary (i.e., if there's more than one of the same tag)
|
|
840
|
+
const siblings = Array.from(parent.children).filter((child) => child.tagName === el.tagName);
|
|
841
|
+
const needsNthChild = siblings.length > 1;
|
|
842
|
+
const selector = needsNthChild ? `${tag}:nth-child(${[...parent.children].indexOf(el) + 1})` : tag;
|
|
843
|
+
return getCssSelector(parent) + " > " + selector;
|
|
844
|
+
}
|
|
845
|
+
const cssSelector = getCssSelector(el);
|
|
846
|
+
return cssSelector;
|
|
847
|
+
});
|
|
848
|
+
newElementSelector = cssSelector;
|
|
849
|
+
}
|
|
850
|
+
else {
|
|
851
|
+
const randomToken = "blinq_" + Math.random().toString(36).substring(7);
|
|
852
|
+
if (this.configuration && this.configuration.stableLocatorStrategy === "data-attribute") {
|
|
853
|
+
const dataAttribute = "data-blinq-id";
|
|
854
|
+
await element.evaluate((el, [dataAttribute, randomToken]) => {
|
|
855
|
+
el.setAttribute(dataAttribute, randomToken);
|
|
856
|
+
}, [dataAttribute, randomToken]);
|
|
857
|
+
newElementSelector = `[${dataAttribute}="${randomToken}"]`;
|
|
858
|
+
}
|
|
859
|
+
else {
|
|
860
|
+
// the default case just return the located element
|
|
861
|
+
// will not work for click and type if the locator is placeholder and the placeholder change due to the click event
|
|
862
|
+
return element;
|
|
863
|
+
}
|
|
864
|
+
}
|
|
701
865
|
const scope = element._frame ?? element.page();
|
|
702
|
-
let newElementSelector = "[data-blinq-id-" + randomToken + "]";
|
|
703
866
|
let prefixSelector = "";
|
|
704
867
|
const frameControlSelector = " >> internal:control=enter-frame";
|
|
705
868
|
const frameSelectorIndex = element._selector.lastIndexOf(frameControlSelector);
|
|
706
869
|
if (frameSelectorIndex !== -1) {
|
|
707
870
|
// remove everything after the >> internal:control=enter-frame
|
|
708
871
|
const frameSelector = element._selector.substring(0, frameSelectorIndex);
|
|
709
|
-
prefixSelector = frameSelector + " >> internal:control=enter-frame >>";
|
|
872
|
+
prefixSelector = frameSelector + " >> internal:control=enter-frame >> ";
|
|
710
873
|
}
|
|
711
874
|
// if (element?._frame?._selector) {
|
|
712
875
|
// prefixSelector = element._frame._selector + " >> " + prefixSelector;
|
|
713
876
|
// }
|
|
714
877
|
const newSelector = prefixSelector + newElementSelector;
|
|
715
|
-
return scope.locator(newSelector);
|
|
878
|
+
return scope.locator(newSelector).first();
|
|
716
879
|
}
|
|
717
880
|
}
|
|
718
881
|
throw new Error("unable to locate element " + JSON.stringify(selectors));
|
|
@@ -733,7 +896,7 @@ class StableBrowser {
|
|
|
733
896
|
for (let i = 0; i < frame.selectors.length; i++) {
|
|
734
897
|
let frameLocator = frame.selectors[i];
|
|
735
898
|
if (frameLocator.css) {
|
|
736
|
-
let testframescope = framescope.frameLocator(frameLocator.css);
|
|
899
|
+
let testframescope = framescope.frameLocator(`${frameLocator.css} >> visible=true`);
|
|
737
900
|
if (frameLocator.index) {
|
|
738
901
|
testframescope = framescope.nth(frameLocator.index);
|
|
739
902
|
}
|
|
@@ -811,6 +974,15 @@ class StableBrowser {
|
|
|
811
974
|
});
|
|
812
975
|
}
|
|
813
976
|
async _locate_internal(selectors, info, _params, timeout = 30000, allowDisabled = false) {
|
|
977
|
+
if (selectors.locators && Array.isArray(selectors.locators)) {
|
|
978
|
+
selectors.locators.forEach((locator) => {
|
|
979
|
+
locator.index = locator.index ?? 0;
|
|
980
|
+
locator.visible = locator.visible ?? true;
|
|
981
|
+
if (locator.visible && locator.css && !locator.css.endsWith(">> visible=true")) {
|
|
982
|
+
locator.css = locator.css + " >> visible=true";
|
|
983
|
+
}
|
|
984
|
+
});
|
|
985
|
+
}
|
|
814
986
|
if (!info) {
|
|
815
987
|
info = {};
|
|
816
988
|
info.failCause = {};
|
|
@@ -823,7 +995,6 @@ class StableBrowser {
|
|
|
823
995
|
let locatorsCount = 0;
|
|
824
996
|
let lazy_scroll = false;
|
|
825
997
|
//let arrayMode = Array.isArray(selectors);
|
|
826
|
-
let scope = await this._findFrameScope(selectors, timeout, info);
|
|
827
998
|
let selectorsLocators = null;
|
|
828
999
|
selectorsLocators = selectors.locators;
|
|
829
1000
|
// group selectors by priority
|
|
@@ -851,6 +1022,7 @@ class StableBrowser {
|
|
|
851
1022
|
let highPriorityOnly = true;
|
|
852
1023
|
let visibleOnly = true;
|
|
853
1024
|
while (true) {
|
|
1025
|
+
let scope = await this._findFrameScope(selectors, timeout, info);
|
|
854
1026
|
locatorsCount = 0;
|
|
855
1027
|
let result = [];
|
|
856
1028
|
let popupResult = await this.closeUnexpectedPopups(info, _params);
|
|
@@ -966,9 +1138,13 @@ class StableBrowser {
|
|
|
966
1138
|
}
|
|
967
1139
|
}
|
|
968
1140
|
if (foundLocators.length === 1) {
|
|
1141
|
+
let box = null;
|
|
1142
|
+
if (!this.onlyFailuresScreenshot) {
|
|
1143
|
+
box = await foundLocators[0].boundingBox();
|
|
1144
|
+
}
|
|
969
1145
|
result.foundElements.push({
|
|
970
1146
|
locator: foundLocators[0],
|
|
971
|
-
box:
|
|
1147
|
+
box: box,
|
|
972
1148
|
unique: true,
|
|
973
1149
|
});
|
|
974
1150
|
result.locatorIndex = i;
|
|
@@ -1123,11 +1299,22 @@ class StableBrowser {
|
|
|
1123
1299
|
operation: "click",
|
|
1124
1300
|
log: "***** click on " + selectors.element_name + " *****\n",
|
|
1125
1301
|
};
|
|
1302
|
+
check_performance("click_all ***", this.context, true);
|
|
1303
|
+
let stepFastMode = this.stepTags.includes("fast-mode");
|
|
1304
|
+
if (stepFastMode) {
|
|
1305
|
+
state.onlyFailuresScreenshot = true;
|
|
1306
|
+
state.scroll = false;
|
|
1307
|
+
state.highlight = false;
|
|
1308
|
+
}
|
|
1126
1309
|
try {
|
|
1310
|
+
check_performance("click_preCommand", this.context, true);
|
|
1127
1311
|
await _preCommand(state, this);
|
|
1312
|
+
check_performance("click_preCommand", this.context, false);
|
|
1128
1313
|
await performAction("click", state.element, options, this, state, _params);
|
|
1129
|
-
if (!this.fastMode) {
|
|
1130
|
-
|
|
1314
|
+
if (!this.fastMode && !this.stepTags.includes("fast-mode")) {
|
|
1315
|
+
check_performance("click_waitForPageLoad", this.context, true);
|
|
1316
|
+
await this.waitForPageLoad({ noSleep: true });
|
|
1317
|
+
check_performance("click_waitForPageLoad", this.context, false);
|
|
1131
1318
|
}
|
|
1132
1319
|
return state.info;
|
|
1133
1320
|
}
|
|
@@ -1135,7 +1322,13 @@ class StableBrowser {
|
|
|
1135
1322
|
await _commandError(state, e, this);
|
|
1136
1323
|
}
|
|
1137
1324
|
finally {
|
|
1325
|
+
check_performance("click_commandFinally", this.context, true);
|
|
1138
1326
|
await _commandFinally(state, this);
|
|
1327
|
+
check_performance("click_commandFinally", this.context, false);
|
|
1328
|
+
check_performance("click_all ***", this.context, false);
|
|
1329
|
+
if (this.context.profile) {
|
|
1330
|
+
console.log(JSON.stringify(this.context.profile, null, 2));
|
|
1331
|
+
}
|
|
1139
1332
|
}
|
|
1140
1333
|
}
|
|
1141
1334
|
async waitForElement(selectors, _params, options = {}, world = null) {
|
|
@@ -1227,7 +1420,7 @@ class StableBrowser {
|
|
|
1227
1420
|
}
|
|
1228
1421
|
}
|
|
1229
1422
|
}
|
|
1230
|
-
await this.waitForPageLoad();
|
|
1423
|
+
//await this.waitForPageLoad();
|
|
1231
1424
|
return state.info;
|
|
1232
1425
|
}
|
|
1233
1426
|
catch (e) {
|
|
@@ -1253,7 +1446,7 @@ class StableBrowser {
|
|
|
1253
1446
|
await _preCommand(state, this);
|
|
1254
1447
|
await performAction("hover", state.element, options, this, state, _params);
|
|
1255
1448
|
await _screenshot(state, this);
|
|
1256
|
-
await this.waitForPageLoad();
|
|
1449
|
+
//await this.waitForPageLoad();
|
|
1257
1450
|
return state.info;
|
|
1258
1451
|
}
|
|
1259
1452
|
catch (e) {
|
|
@@ -1289,7 +1482,7 @@ class StableBrowser {
|
|
|
1289
1482
|
state.info.log += "selectOption failed, will try force" + "\n";
|
|
1290
1483
|
await state.element.selectOption(values, { timeout: 10000, force: true });
|
|
1291
1484
|
}
|
|
1292
|
-
await this.waitForPageLoad();
|
|
1485
|
+
//await this.waitForPageLoad();
|
|
1293
1486
|
return state.info;
|
|
1294
1487
|
}
|
|
1295
1488
|
catch (e) {
|
|
@@ -1475,6 +1668,14 @@ class StableBrowser {
|
|
|
1475
1668
|
}
|
|
1476
1669
|
try {
|
|
1477
1670
|
await _preCommand(state, this);
|
|
1671
|
+
const randomToken = "blinq_" + Math.random().toString(36).substring(7);
|
|
1672
|
+
// tag the element
|
|
1673
|
+
let newElementSelector = await state.element.evaluate((el, token) => {
|
|
1674
|
+
// use attribute and not id
|
|
1675
|
+
const attrName = `data-blinq-id-${token}`;
|
|
1676
|
+
el.setAttribute(attrName, "");
|
|
1677
|
+
return `[${attrName}]`;
|
|
1678
|
+
}, randomToken);
|
|
1478
1679
|
state.info.value = _value;
|
|
1479
1680
|
if (!options.press) {
|
|
1480
1681
|
try {
|
|
@@ -1500,6 +1701,25 @@ class StableBrowser {
|
|
|
1500
1701
|
}
|
|
1501
1702
|
}
|
|
1502
1703
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1704
|
+
// check if the element exist after the click (no wait)
|
|
1705
|
+
const count = await state.element.count({ timeout: 0 });
|
|
1706
|
+
if (count === 0) {
|
|
1707
|
+
// the locator changed after the click (placeholder) we need to locate the element using the data-blinq-id
|
|
1708
|
+
const scope = state.element._frame ?? element.page();
|
|
1709
|
+
let prefixSelector = "";
|
|
1710
|
+
const frameControlSelector = " >> internal:control=enter-frame";
|
|
1711
|
+
const frameSelectorIndex = state.element._selector.lastIndexOf(frameControlSelector);
|
|
1712
|
+
if (frameSelectorIndex !== -1) {
|
|
1713
|
+
// remove everything after the >> internal:control=enter-frame
|
|
1714
|
+
const frameSelector = state.element._selector.substring(0, frameSelectorIndex);
|
|
1715
|
+
prefixSelector = frameSelector + " >> internal:control=enter-frame >> ";
|
|
1716
|
+
}
|
|
1717
|
+
// if (element?._frame?._selector) {
|
|
1718
|
+
// prefixSelector = element._frame._selector + " >> " + prefixSelector;
|
|
1719
|
+
// }
|
|
1720
|
+
const newSelector = prefixSelector + newElementSelector;
|
|
1721
|
+
state.element = scope.locator(newSelector).first();
|
|
1722
|
+
}
|
|
1503
1723
|
const valueSegment = state.value.split("&&");
|
|
1504
1724
|
for (let i = 0; i < valueSegment.length; i++) {
|
|
1505
1725
|
if (i > 0) {
|
|
@@ -1571,8 +1791,8 @@ class StableBrowser {
|
|
|
1571
1791
|
if (enter) {
|
|
1572
1792
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1573
1793
|
await this.page.keyboard.press("Enter");
|
|
1794
|
+
await this.waitForPageLoad();
|
|
1574
1795
|
}
|
|
1575
|
-
await this.waitForPageLoad();
|
|
1576
1796
|
return state.info;
|
|
1577
1797
|
}
|
|
1578
1798
|
catch (e) {
|
|
@@ -1831,11 +2051,12 @@ class StableBrowser {
|
|
|
1831
2051
|
throw new Error("referanceSnapshot is null");
|
|
1832
2052
|
}
|
|
1833
2053
|
let text = null;
|
|
1834
|
-
|
|
1835
|
-
|
|
2054
|
+
const snapshotsFolder = process.env.BVT_TEMP_SNAPSHOTS_FOLDER ?? this.context.snapshotFolder; //path .join(this.project_path, "data", "snapshots");
|
|
2055
|
+
if (fs.existsSync(path.join(snapshotsFolder, referanceSnapshot + ".yml"))) {
|
|
2056
|
+
text = fs.readFileSync(path.join(snapshotsFolder, referanceSnapshot + ".yml"), "utf8");
|
|
1836
2057
|
}
|
|
1837
|
-
else if (fs.existsSync(path.join(
|
|
1838
|
-
text = fs.readFileSync(path.join(
|
|
2058
|
+
else if (fs.existsSync(path.join(snapshotsFolder, referanceSnapshot + ".yaml"))) {
|
|
2059
|
+
text = fs.readFileSync(path.join(snapshotsFolder, referanceSnapshot + ".yaml"), "utf8");
|
|
1839
2060
|
}
|
|
1840
2061
|
else if (referanceSnapshot.startsWith("yaml:")) {
|
|
1841
2062
|
text = referanceSnapshot.substring(5);
|
|
@@ -2030,6 +2251,9 @@ class StableBrowser {
|
|
|
2030
2251
|
return _getTestData(world, this.context, this);
|
|
2031
2252
|
}
|
|
2032
2253
|
async _screenShot(options = {}, world = null, info = null) {
|
|
2254
|
+
if (!options) {
|
|
2255
|
+
options = {};
|
|
2256
|
+
}
|
|
2033
2257
|
// collect url/path/title
|
|
2034
2258
|
if (info) {
|
|
2035
2259
|
if (!info.title) {
|
|
@@ -2058,7 +2282,7 @@ class StableBrowser {
|
|
|
2058
2282
|
const uuidStr = "id_" + randomUUID();
|
|
2059
2283
|
const screenshotPath = path.join(world.screenshotPath, uuidStr + ".png");
|
|
2060
2284
|
try {
|
|
2061
|
-
await this.takeScreenshot(screenshotPath);
|
|
2285
|
+
await this.takeScreenshot(screenshotPath, options.fullPage === true);
|
|
2062
2286
|
// let buffer = await this.page.screenshot({ timeout: 4000 });
|
|
2063
2287
|
// // save the buffer to the screenshot path asynchrously
|
|
2064
2288
|
// fs.writeFile(screenshotPath, buffer, (err) => {
|
|
@@ -2079,7 +2303,7 @@ class StableBrowser {
|
|
|
2079
2303
|
else if (options && options.screenshot) {
|
|
2080
2304
|
result.screenshotPath = options.screenshotPath;
|
|
2081
2305
|
try {
|
|
2082
|
-
await this.takeScreenshot(options.screenshotPath);
|
|
2306
|
+
await this.takeScreenshot(options.screenshotPath, options.fullPage === true);
|
|
2083
2307
|
// let buffer = await this.page.screenshot({ timeout: 4000 });
|
|
2084
2308
|
// // save the buffer to the screenshot path asynchrously
|
|
2085
2309
|
// fs.writeFile(options.screenshotPath, buffer, (err) => {
|
|
@@ -2097,7 +2321,7 @@ class StableBrowser {
|
|
|
2097
2321
|
}
|
|
2098
2322
|
return result;
|
|
2099
2323
|
}
|
|
2100
|
-
async takeScreenshot(screenshotPath) {
|
|
2324
|
+
async takeScreenshot(screenshotPath, fullPage = false) {
|
|
2101
2325
|
const playContext = this.context.playContext;
|
|
2102
2326
|
// Using CDP to capture the screenshot
|
|
2103
2327
|
const viewportWidth = Math.max(...(await this.page.evaluate(() => [
|
|
@@ -2122,13 +2346,7 @@ class StableBrowser {
|
|
|
2122
2346
|
const client = await playContext.newCDPSession(this.page);
|
|
2123
2347
|
const { data } = await client.send("Page.captureScreenshot", {
|
|
2124
2348
|
format: "png",
|
|
2125
|
-
|
|
2126
|
-
// x: 0,
|
|
2127
|
-
// y: 0,
|
|
2128
|
-
// width: viewportWidth,
|
|
2129
|
-
// height: viewportHeight,
|
|
2130
|
-
// scale: 1,
|
|
2131
|
-
// },
|
|
2349
|
+
captureBeyondViewport: fullPage,
|
|
2132
2350
|
});
|
|
2133
2351
|
await client.detach();
|
|
2134
2352
|
if (!screenshotPath) {
|
|
@@ -2137,7 +2355,7 @@ class StableBrowser {
|
|
|
2137
2355
|
screenshotBuffer = Buffer.from(data, "base64");
|
|
2138
2356
|
}
|
|
2139
2357
|
else {
|
|
2140
|
-
screenshotBuffer = await this.page.screenshot();
|
|
2358
|
+
screenshotBuffer = await this.page.screenshot({ fullPage: fullPage });
|
|
2141
2359
|
}
|
|
2142
2360
|
// if (focusedElement) {
|
|
2143
2361
|
// // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
|
|
@@ -2438,7 +2656,7 @@ class StableBrowser {
|
|
|
2438
2656
|
let expectedValue;
|
|
2439
2657
|
try {
|
|
2440
2658
|
await _preCommand(state, this);
|
|
2441
|
-
expectedValue = await
|
|
2659
|
+
expectedValue = await this._replaceWithLocalData(value, world);
|
|
2442
2660
|
state.info.expectedValue = expectedValue;
|
|
2443
2661
|
switch (property) {
|
|
2444
2662
|
case "innerText":
|
|
@@ -2486,47 +2704,54 @@ class StableBrowser {
|
|
|
2486
2704
|
}
|
|
2487
2705
|
state.info.value = val;
|
|
2488
2706
|
let regex;
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
const
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
state.info.failCause.assertionFailed = true;
|
|
2504
|
-
state.info.failCause.lastError = errorMessage;
|
|
2505
|
-
throw new Error(errorMessage);
|
|
2506
|
-
}
|
|
2707
|
+
state.info.value = val;
|
|
2708
|
+
const isRegex = expectedValue.startsWith("regex:");
|
|
2709
|
+
const isContains = expectedValue.startsWith("contains:");
|
|
2710
|
+
const isExact = expectedValue.startsWith("exact:");
|
|
2711
|
+
let matchPassed = false;
|
|
2712
|
+
if (isRegex) {
|
|
2713
|
+
const rawPattern = expectedValue.slice(6); // remove "regex:"
|
|
2714
|
+
const lastSlashIndex = rawPattern.lastIndexOf("/");
|
|
2715
|
+
if (rawPattern.startsWith("/") && lastSlashIndex > 0) {
|
|
2716
|
+
const patternBody = rawPattern.slice(1, lastSlashIndex).replace(/\n/g, ".*");
|
|
2717
|
+
const flags = rawPattern.slice(lastSlashIndex + 1) || "gs";
|
|
2718
|
+
const regex = new RegExp(patternBody, flags);
|
|
2719
|
+
state.info.regex = true;
|
|
2720
|
+
matchPassed = regex.test(val);
|
|
2507
2721
|
}
|
|
2508
2722
|
else {
|
|
2509
|
-
//
|
|
2510
|
-
const
|
|
2511
|
-
const
|
|
2512
|
-
|
|
2513
|
-
// Check if all expected lines are present in the actual lines
|
|
2514
|
-
const isPart = expectedLines.every((expectedLine) => valLines.some((valLine) => valLine.trim() === expectedLine.trim()));
|
|
2515
|
-
if (!isPart) {
|
|
2516
|
-
let errorMessage = `The ${property} property has a value of "${val}", but the expected value is "${expectedValue}"`;
|
|
2517
|
-
state.info.failCause.assertionFailed = true;
|
|
2518
|
-
state.info.failCause.lastError = errorMessage;
|
|
2519
|
-
throw new Error(errorMessage);
|
|
2520
|
-
}
|
|
2723
|
+
// Fallback: treat as literal
|
|
2724
|
+
const escapedPattern = rawPattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2725
|
+
const regex = new RegExp(escapedPattern, "g");
|
|
2726
|
+
matchPassed = regex.test(val);
|
|
2521
2727
|
}
|
|
2522
2728
|
}
|
|
2729
|
+
else if (isContains) {
|
|
2730
|
+
const containsValue = expectedValue.slice(9); // remove "contains:"
|
|
2731
|
+
matchPassed = val.includes(containsValue);
|
|
2732
|
+
}
|
|
2733
|
+
else if (isExact) {
|
|
2734
|
+
const exactValue = expectedValue.slice(6); // remove "exact:"
|
|
2735
|
+
matchPassed = val === exactValue;
|
|
2736
|
+
}
|
|
2737
|
+
else if (property === "innerText") {
|
|
2738
|
+
// Default innerText logic
|
|
2739
|
+
const normalizedExpectedValue = expectedValue.replace(/\\n/g, "\n");
|
|
2740
|
+
const valLines = val.split("\n");
|
|
2741
|
+
const expectedLines = normalizedExpectedValue.split("\n");
|
|
2742
|
+
matchPassed = expectedLines.every((expectedLine) => valLines.some((valLine) => valLine.trim() === expectedLine.trim()));
|
|
2743
|
+
}
|
|
2523
2744
|
else {
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2745
|
+
// Fallback exact or loose match
|
|
2746
|
+
const escapedPattern = expectedValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2747
|
+
const regex = new RegExp(escapedPattern, "g");
|
|
2748
|
+
matchPassed = regex.test(val);
|
|
2749
|
+
}
|
|
2750
|
+
if (!matchPassed) {
|
|
2751
|
+
let errorMessage = `The ${property} property has a value of "${val}", but the expected value is "${expectedValue}"`;
|
|
2752
|
+
state.info.failCause.assertionFailed = true;
|
|
2753
|
+
state.info.failCause.lastError = errorMessage;
|
|
2754
|
+
throw new Error(errorMessage);
|
|
2530
2755
|
}
|
|
2531
2756
|
return state.info;
|
|
2532
2757
|
}
|
|
@@ -2557,6 +2782,7 @@ class StableBrowser {
|
|
|
2557
2782
|
allowDisabled: true,
|
|
2558
2783
|
info: {},
|
|
2559
2784
|
};
|
|
2785
|
+
state.options ??= { timeout: timeoutMs };
|
|
2560
2786
|
// Initialize startTime outside try block to ensure it's always accessible
|
|
2561
2787
|
const startTime = Date.now();
|
|
2562
2788
|
let conditionMet = false;
|
|
@@ -3132,7 +3358,16 @@ class StableBrowser {
|
|
|
3132
3358
|
text = text.replace(/\\"/g, '"');
|
|
3133
3359
|
}
|
|
3134
3360
|
const timeout = this._getFindElementTimeout(options);
|
|
3135
|
-
|
|
3361
|
+
//if (!this.fastMode && !this.stepTags.includes("fast-mode")) {
|
|
3362
|
+
let stepFastMode = this.stepTags.includes("fast-mode");
|
|
3363
|
+
if (!stepFastMode) {
|
|
3364
|
+
if (!this.fastMode) {
|
|
3365
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
3366
|
+
}
|
|
3367
|
+
else {
|
|
3368
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
3369
|
+
}
|
|
3370
|
+
}
|
|
3136
3371
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
3137
3372
|
if (newValue !== text) {
|
|
3138
3373
|
this.logger.info(text + "=" + newValue);
|
|
@@ -3140,6 +3375,11 @@ class StableBrowser {
|
|
|
3140
3375
|
}
|
|
3141
3376
|
let dateAlternatives = findDateAlternatives(text);
|
|
3142
3377
|
let numberAlternatives = findNumberAlternatives(text);
|
|
3378
|
+
if (stepFastMode) {
|
|
3379
|
+
state.onlyFailuresScreenshot = true;
|
|
3380
|
+
state.scroll = false;
|
|
3381
|
+
state.highlight = false;
|
|
3382
|
+
}
|
|
3143
3383
|
try {
|
|
3144
3384
|
await _preCommand(state, this);
|
|
3145
3385
|
state.info.text = text;
|
|
@@ -3259,6 +3499,8 @@ class StableBrowser {
|
|
|
3259
3499
|
operation: "verify_text_with_relation",
|
|
3260
3500
|
log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
|
|
3261
3501
|
};
|
|
3502
|
+
const cmdStartTime = Date.now();
|
|
3503
|
+
let cmdEndTime = null;
|
|
3262
3504
|
const timeout = this._getFindElementTimeout(options);
|
|
3263
3505
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
3264
3506
|
let newValue = await this._replaceWithLocalData(textAnchor, world);
|
|
@@ -3294,6 +3536,17 @@ class StableBrowser {
|
|
|
3294
3536
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
3295
3537
|
continue;
|
|
3296
3538
|
}
|
|
3539
|
+
else {
|
|
3540
|
+
cmdEndTime = Date.now();
|
|
3541
|
+
if (cmdEndTime - cmdStartTime > 55000) {
|
|
3542
|
+
if (foundAncore) {
|
|
3543
|
+
throw new Error(`Text ${textToVerify} not found in page`);
|
|
3544
|
+
}
|
|
3545
|
+
else {
|
|
3546
|
+
throw new Error(`Text ${textAnchor} not found in page`);
|
|
3547
|
+
}
|
|
3548
|
+
}
|
|
3549
|
+
}
|
|
3297
3550
|
try {
|
|
3298
3551
|
for (let i = 0; i < resultWithElementsFound.length; i++) {
|
|
3299
3552
|
foundAncore = true;
|
|
@@ -3432,7 +3685,7 @@ class StableBrowser {
|
|
|
3432
3685
|
Object.assign(e, { info: info });
|
|
3433
3686
|
error = e;
|
|
3434
3687
|
// throw e;
|
|
3435
|
-
await _commandError({ text: "visualVerification", operation: "visualVerification",
|
|
3688
|
+
await _commandError({ text: "visualVerification", operation: "visualVerification", info }, e, this);
|
|
3436
3689
|
}
|
|
3437
3690
|
finally {
|
|
3438
3691
|
const endTime = Date.now();
|
|
@@ -3781,6 +4034,22 @@ class StableBrowser {
|
|
|
3781
4034
|
}
|
|
3782
4035
|
}
|
|
3783
4036
|
async waitForPageLoad(options = {}, world = null) {
|
|
4037
|
+
// try {
|
|
4038
|
+
// let currentPagePath = null;
|
|
4039
|
+
// currentPagePath = new URL(this.page.url()).pathname;
|
|
4040
|
+
// if (this.latestPagePath) {
|
|
4041
|
+
// // get the currect page path and compare with the latest page path
|
|
4042
|
+
// if (this.latestPagePath === currentPagePath) {
|
|
4043
|
+
// // if the page path is the same, do not wait for page load
|
|
4044
|
+
// console.log("No page change: " + currentPagePath);
|
|
4045
|
+
// return;
|
|
4046
|
+
// }
|
|
4047
|
+
// }
|
|
4048
|
+
// this.latestPagePath = currentPagePath;
|
|
4049
|
+
// } catch (e) {
|
|
4050
|
+
// console.debug("Error getting current page path: ", e);
|
|
4051
|
+
// }
|
|
4052
|
+
//console.log("Waiting for page load");
|
|
3784
4053
|
let timeout = this._getLoadTimeout(options);
|
|
3785
4054
|
const promiseArray = [];
|
|
3786
4055
|
// let waitForNetworkIdle = true;
|
|
@@ -3815,7 +4084,10 @@ class StableBrowser {
|
|
|
3815
4084
|
}
|
|
3816
4085
|
}
|
|
3817
4086
|
finally {
|
|
3818
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
4087
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
4088
|
+
if (options && !options.noSleep) {
|
|
4089
|
+
await new Promise((resolve) => setTimeout(resolve, 1500));
|
|
4090
|
+
}
|
|
3819
4091
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world));
|
|
3820
4092
|
const endTime = Date.now();
|
|
3821
4093
|
_reportToWorld(world, {
|
|
@@ -3869,7 +4141,7 @@ class StableBrowser {
|
|
|
3869
4141
|
}
|
|
3870
4142
|
operation = options.operation;
|
|
3871
4143
|
// validate operation is one of the supported operations
|
|
3872
|
-
if (operation != "click" && operation != "hover+click") {
|
|
4144
|
+
if (operation != "click" && operation != "hover+click" && operation != "hover") {
|
|
3873
4145
|
throw new Error("operation is not supported");
|
|
3874
4146
|
}
|
|
3875
4147
|
const state = {
|
|
@@ -3938,6 +4210,17 @@ class StableBrowser {
|
|
|
3938
4210
|
state.element = results[0];
|
|
3939
4211
|
await performAction("hover+click", state.element, options, this, state, _params);
|
|
3940
4212
|
break;
|
|
4213
|
+
case "hover":
|
|
4214
|
+
if (!options.css) {
|
|
4215
|
+
throw new Error("css is not defined");
|
|
4216
|
+
}
|
|
4217
|
+
const result1 = await findElementsInArea(options.css, cellArea, this, options);
|
|
4218
|
+
if (result1.length === 0) {
|
|
4219
|
+
throw new Error(`Element not found in cell area`);
|
|
4220
|
+
}
|
|
4221
|
+
state.element = result1[0];
|
|
4222
|
+
await performAction("hover", state.element, options, this, state, _params);
|
|
4223
|
+
break;
|
|
3941
4224
|
default:
|
|
3942
4225
|
throw new Error("operation is not supported");
|
|
3943
4226
|
}
|
|
@@ -3951,6 +4234,12 @@ class StableBrowser {
|
|
|
3951
4234
|
}
|
|
3952
4235
|
saveTestDataAsGlobal(options, world) {
|
|
3953
4236
|
const dataFile = _getDataFile(world, this.context, this);
|
|
4237
|
+
if (process.env.MODE === "executions") {
|
|
4238
|
+
const globalDataFile = path.join(this.project_path, "global_test_data.json");
|
|
4239
|
+
fs.copyFileSync(dataFile, globalDataFile);
|
|
4240
|
+
this.logger.info("Save the scenario test data to " + globalDataFile + " as global for the following scenarios.");
|
|
4241
|
+
return;
|
|
4242
|
+
}
|
|
3954
4243
|
process.env.GLOBAL_TEST_DATA_FILE = dataFile;
|
|
3955
4244
|
this.logger.info("Save the scenario test data as global for the following scenarios.");
|
|
3956
4245
|
}
|
|
@@ -4053,6 +4342,7 @@ class StableBrowser {
|
|
|
4053
4342
|
if (world && world.attach) {
|
|
4054
4343
|
world.attach(this.context.reportFolder, { mediaType: "text/plain" });
|
|
4055
4344
|
}
|
|
4345
|
+
this.context.loadedRoutes = null;
|
|
4056
4346
|
this.beforeScenarioCalled = true;
|
|
4057
4347
|
if (scenario && scenario.pickle && scenario.pickle.name) {
|
|
4058
4348
|
this.scenarioName = scenario.pickle.name;
|
|
@@ -4079,12 +4369,30 @@ class StableBrowser {
|
|
|
4079
4369
|
await getTestData(envName, world, undefined, this.featureName, this.scenarioName, this.context);
|
|
4080
4370
|
}
|
|
4081
4371
|
await loadBrunoParams(this.context, this.context.environment.name);
|
|
4372
|
+
if ((process.env.TRACE === "true" || this.configuration.trace === true) && this.context) {
|
|
4373
|
+
this.trace = true;
|
|
4374
|
+
const traceFolder = path.join(this.context.reportFolder, "trace");
|
|
4375
|
+
if (!fs.existsSync(traceFolder)) {
|
|
4376
|
+
fs.mkdirSync(traceFolder, { recursive: true });
|
|
4377
|
+
}
|
|
4378
|
+
this.traceFolder = traceFolder;
|
|
4379
|
+
await this.context.playContext.tracing.start({ screenshots: true, snapshots: true });
|
|
4380
|
+
}
|
|
4381
|
+
}
|
|
4382
|
+
async afterScenario(world, scenario) {
|
|
4383
|
+
const id = scenario.testCaseStartedId;
|
|
4384
|
+
await this.context.playContext.tracing.stop({
|
|
4385
|
+
path: path.join(this.traceFolder, `trace-${id}.zip`),
|
|
4386
|
+
});
|
|
4082
4387
|
}
|
|
4083
|
-
async afterScenario(world, scenario) { }
|
|
4084
4388
|
async beforeStep(world, step) {
|
|
4085
|
-
|
|
4389
|
+
if (step?.pickleStep) {
|
|
4390
|
+
await this.context.playContext.tracing.group(`Step: ${step.pickleStep.text}`);
|
|
4391
|
+
}
|
|
4392
|
+
this.stepTags = [];
|
|
4086
4393
|
if (!this.beforeScenarioCalled) {
|
|
4087
4394
|
this.beforeScenario(world, step);
|
|
4395
|
+
this.context.loadedRoutes = null;
|
|
4088
4396
|
}
|
|
4089
4397
|
if (this.stepIndex === undefined) {
|
|
4090
4398
|
this.stepIndex = 0;
|
|
@@ -4094,7 +4402,12 @@ class StableBrowser {
|
|
|
4094
4402
|
}
|
|
4095
4403
|
if (step && step.pickleStep && step.pickleStep.text) {
|
|
4096
4404
|
this.stepName = step.pickleStep.text;
|
|
4097
|
-
|
|
4405
|
+
let printableStepName = this.stepName;
|
|
4406
|
+
// take the printableStepName and replace quated value with \x1b[33m and \x1b[0m
|
|
4407
|
+
printableStepName = printableStepName.replace(/"([^"]*)"/g, (match, p1) => {
|
|
4408
|
+
return `\x1b[33m"${p1}"\x1b[0m`;
|
|
4409
|
+
});
|
|
4410
|
+
this.logger.info("\x1b[38;5;208mstep:\x1b[0m " + printableStepName);
|
|
4098
4411
|
}
|
|
4099
4412
|
else if (step && step.text) {
|
|
4100
4413
|
this.stepName = step.text;
|
|
@@ -4109,7 +4422,10 @@ class StableBrowser {
|
|
|
4109
4422
|
}
|
|
4110
4423
|
if (this.initSnapshotTaken === false) {
|
|
4111
4424
|
this.initSnapshotTaken = true;
|
|
4112
|
-
if (world &&
|
|
4425
|
+
if (world &&
|
|
4426
|
+
world.attach &&
|
|
4427
|
+
!process.env.DISABLE_SNAPSHOT &&
|
|
4428
|
+
(!this.fastMode || this.stepTags.includes("fast-mode"))) {
|
|
4113
4429
|
const snapshot = await this.getAriaSnapshot();
|
|
4114
4430
|
if (snapshot) {
|
|
4115
4431
|
await world.attach(JSON.stringify(snapshot), "application/json+snapshot-before");
|
|
@@ -4117,8 +4433,13 @@ class StableBrowser {
|
|
|
4117
4433
|
}
|
|
4118
4434
|
}
|
|
4119
4435
|
this.context.routeResults = null;
|
|
4120
|
-
|
|
4121
|
-
|
|
4436
|
+
this.context.loadedRoutes = null;
|
|
4437
|
+
await registerBeforeStepRoutes(this.context, this.stepName, world);
|
|
4438
|
+
networkBeforeStep(this.stepName, this.context);
|
|
4439
|
+
this.inStepReport = false;
|
|
4440
|
+
}
|
|
4441
|
+
setStepTags(tags) {
|
|
4442
|
+
this.stepTags = tags;
|
|
4122
4443
|
}
|
|
4123
4444
|
async getAriaSnapshot() {
|
|
4124
4445
|
try {
|
|
@@ -4192,7 +4513,7 @@ class StableBrowser {
|
|
|
4192
4513
|
state.payload = payload;
|
|
4193
4514
|
if (commandStatus === "FAILED") {
|
|
4194
4515
|
state.throwError = true;
|
|
4195
|
-
throw new Error(
|
|
4516
|
+
throw new Error(commandText);
|
|
4196
4517
|
}
|
|
4197
4518
|
}
|
|
4198
4519
|
catch (e) {
|
|
@@ -4202,26 +4523,22 @@ class StableBrowser {
|
|
|
4202
4523
|
await _commandFinally(state, this);
|
|
4203
4524
|
}
|
|
4204
4525
|
}
|
|
4205
|
-
async afterStep(world, step) {
|
|
4526
|
+
async afterStep(world, step, result) {
|
|
4206
4527
|
this.stepName = null;
|
|
4207
|
-
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
4208
|
-
if (this.context.browserObject.context) {
|
|
4209
|
-
await this.context.browserObject.context.tracing.stopChunk({
|
|
4210
|
-
path: path.join(this.context.browserObject.traceFolder, `trace-${this.stepIndex}.zip`),
|
|
4211
|
-
});
|
|
4212
|
-
if (world && world.attach) {
|
|
4213
|
-
await world.attach(JSON.stringify({
|
|
4214
|
-
type: "trace",
|
|
4215
|
-
traceFilePath: `trace-${this.stepIndex}.zip`,
|
|
4216
|
-
}), "application/json+trace");
|
|
4217
|
-
}
|
|
4218
|
-
// console.log("trace file created", `trace-${this.stepIndex}.zip`);
|
|
4219
|
-
}
|
|
4220
|
-
}
|
|
4221
4528
|
if (this.context) {
|
|
4222
4529
|
this.context.examplesRow = null;
|
|
4223
4530
|
}
|
|
4224
|
-
if (
|
|
4531
|
+
if (!this.inStepReport) {
|
|
4532
|
+
// check the step result
|
|
4533
|
+
if (result && result.status === "FAILED" && world && world.attach) {
|
|
4534
|
+
await this.addCommandToReport(result.message ? result.message : "Step failed", "FAILED", `${result.message}`, { type: "text", screenshot: true }, world);
|
|
4535
|
+
}
|
|
4536
|
+
}
|
|
4537
|
+
if (world &&
|
|
4538
|
+
world.attach &&
|
|
4539
|
+
!process.env.DISABLE_SNAPSHOT &&
|
|
4540
|
+
!this.fastMode &&
|
|
4541
|
+
!this.stepTags.includes("fast-mode")) {
|
|
4225
4542
|
const snapshot = await this.getAriaSnapshot();
|
|
4226
4543
|
if (snapshot) {
|
|
4227
4544
|
const obj = {};
|
|
@@ -4230,7 +4547,6 @@ class StableBrowser {
|
|
|
4230
4547
|
}
|
|
4231
4548
|
this.context.routeResults = await registerAfterStepRoutes(this.context, world);
|
|
4232
4549
|
if (this.context.routeResults) {
|
|
4233
|
-
this.logger.info("Route results after step: " + JSON.stringify(this.context.routeResults));
|
|
4234
4550
|
if (world && world.attach) {
|
|
4235
4551
|
await world.attach(JSON.stringify(this.context.routeResults), "application/json+intercept-results");
|
|
4236
4552
|
}
|
|
@@ -4258,7 +4574,14 @@ class StableBrowser {
|
|
|
4258
4574
|
await _commandFinally(state, this);
|
|
4259
4575
|
}
|
|
4260
4576
|
}
|
|
4261
|
-
networkAfterStep(this.stepName);
|
|
4577
|
+
networkAfterStep(this.stepName, this.context);
|
|
4578
|
+
if (process.env.TEMP_RUN === "true") {
|
|
4579
|
+
// Put a sleep for some time to allow the browser to finish processing
|
|
4580
|
+
if (!this.stepTags.includes("fast-mode")) {
|
|
4581
|
+
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
4582
|
+
}
|
|
4583
|
+
}
|
|
4584
|
+
await this.context.playContext.tracing.groupEnd();
|
|
4262
4585
|
}
|
|
4263
4586
|
}
|
|
4264
4587
|
function createTimedPromise(promise, label) {
|