automation_model 1.0.786-dev → 1.0.786-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.d.ts +1 -1
- package/lib/auto_page.js +59 -18
- package/lib/auto_page.js.map +1 -1
- package/lib/browser_manager.d.ts +2 -2
- package/lib/browser_manager.js +102 -52
- 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 +2 -2
- package/lib/command_common.js +26 -25
- package/lib/command_common.js.map +1 -1
- package/lib/file_checker.js +7 -0
- 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 +124 -128
- 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 -2
- package/lib/route.js +493 -192
- package/lib/route.js.map +1 -1
- package/lib/scripts/axe.mini.js +23978 -1
- package/lib/snapshot_validation.js +3 -0
- package/lib/snapshot_validation.js.map +1 -1
- package/lib/stable_browser.d.ts +13 -7
- package/lib/stable_browser.js +427 -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 -1
- package/lib/test_context.js +1 -1
- 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 +18 -11
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));
|
|
@@ -204,7 +223,7 @@ class StableBrowser {
|
|
|
204
223
|
}
|
|
205
224
|
let newContextCreated = false;
|
|
206
225
|
if (!apps[appName]) {
|
|
207
|
-
let newContext = await getContext(null, this.context.headless ? this.context.headless : false, this, this.logger, appName, false, this, -1, this.context.reportFolder);
|
|
226
|
+
let newContext = await getContext(null, this.context.headless ? this.context.headless : false, this, this.logger, appName, false, this, -1, this.context.reportFolder, null, null, this.tags);
|
|
208
227
|
newContextCreated = true;
|
|
209
228
|
apps[appName] = {
|
|
210
229
|
context: newContext,
|
|
@@ -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,17 @@ class StableBrowser {
|
|
|
333
352
|
screenshot: false,
|
|
334
353
|
highlight: false,
|
|
335
354
|
};
|
|
355
|
+
let timeout = 60000;
|
|
356
|
+
if (this.configuration && this.configuration.page_timeout) {
|
|
357
|
+
timeout = this.configuration.page_timeout;
|
|
358
|
+
}
|
|
359
|
+
if (options && options["timeout"]) {
|
|
360
|
+
timeout = options["timeout"];
|
|
361
|
+
}
|
|
336
362
|
try {
|
|
337
363
|
await _preCommand(state, this);
|
|
338
364
|
await this.page.goto(url, {
|
|
339
|
-
timeout:
|
|
365
|
+
timeout: timeout,
|
|
340
366
|
});
|
|
341
367
|
await _screenshot(state, this);
|
|
342
368
|
}
|
|
@@ -504,12 +530,6 @@ class StableBrowser {
|
|
|
504
530
|
if (!el.setAttribute) {
|
|
505
531
|
el = el.parentElement;
|
|
506
532
|
}
|
|
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
533
|
el.setAttribute("data-blinq-id-" + randomToken, "");
|
|
514
534
|
return true;
|
|
515
535
|
}, [tag1, randomToken]))) {
|
|
@@ -679,40 +699,186 @@ class StableBrowser {
|
|
|
679
699
|
}
|
|
680
700
|
return { rerun: false };
|
|
681
701
|
}
|
|
702
|
+
getFilePath() {
|
|
703
|
+
const stackFrames = errorStackParser.parse(new Error());
|
|
704
|
+
const mjsFrames = stackFrames.filter((frame) => frame.fileName && frame.fileName.endsWith(".mjs"));
|
|
705
|
+
const stackFrame = mjsFrames[mjsFrames.length - 2];
|
|
706
|
+
const filepath = stackFrame?.fileName;
|
|
707
|
+
if (filepath) {
|
|
708
|
+
let jsonFilePath = filepath.replace(".mjs", ".json");
|
|
709
|
+
if (existsSync(jsonFilePath)) {
|
|
710
|
+
return jsonFilePath;
|
|
711
|
+
}
|
|
712
|
+
const config = this.configuration ?? {};
|
|
713
|
+
if (!config?.locatorsMetadataDir) {
|
|
714
|
+
config.locatorsMetadataDir = "features/step_definitions/locators";
|
|
715
|
+
}
|
|
716
|
+
if (config && config.locatorsMetadataDir) {
|
|
717
|
+
jsonFilePath = path.join(config.locatorsMetadataDir, path.basename(jsonFilePath));
|
|
718
|
+
}
|
|
719
|
+
if (existsSync(jsonFilePath)) {
|
|
720
|
+
return jsonFilePath;
|
|
721
|
+
}
|
|
722
|
+
return null;
|
|
723
|
+
}
|
|
724
|
+
return null;
|
|
725
|
+
}
|
|
726
|
+
getFullElementLocators(selectors, filePath) {
|
|
727
|
+
if (!filePath || !existsSync(filePath)) {
|
|
728
|
+
return null;
|
|
729
|
+
}
|
|
730
|
+
const content = fs.readFileSync(filePath, "utf8");
|
|
731
|
+
try {
|
|
732
|
+
const allElements = JSON.parse(content);
|
|
733
|
+
const element_key = selectors?.element_key;
|
|
734
|
+
if (element_key && allElements[element_key]) {
|
|
735
|
+
return allElements[element_key];
|
|
736
|
+
}
|
|
737
|
+
for (const elementKey in allElements) {
|
|
738
|
+
const element = allElements[elementKey];
|
|
739
|
+
let foundStrategy = null;
|
|
740
|
+
for (const key in element) {
|
|
741
|
+
if (key === "strategy") {
|
|
742
|
+
continue;
|
|
743
|
+
}
|
|
744
|
+
const locators = element[key];
|
|
745
|
+
if (!locators || !locators.length) {
|
|
746
|
+
continue;
|
|
747
|
+
}
|
|
748
|
+
for (const locator of locators) {
|
|
749
|
+
delete locator.score;
|
|
750
|
+
}
|
|
751
|
+
if (JSON.stringify(locators) === JSON.stringify(selectors.locators)) {
|
|
752
|
+
foundStrategy = key;
|
|
753
|
+
break;
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
if (foundStrategy) {
|
|
757
|
+
return element;
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
catch (error) {
|
|
762
|
+
console.error("Error parsing locators from file: " + filePath, error);
|
|
763
|
+
}
|
|
764
|
+
return null;
|
|
765
|
+
}
|
|
682
766
|
async _locate(selectors, info, _params, timeout, allowDisabled = false) {
|
|
683
767
|
if (!timeout) {
|
|
684
768
|
timeout = 30000;
|
|
685
769
|
}
|
|
770
|
+
let element = null;
|
|
771
|
+
let allStrategyLocators = null;
|
|
772
|
+
let selectedStrategy = null;
|
|
773
|
+
if (this.tryAllStrategies) {
|
|
774
|
+
allStrategyLocators = this.getFullElementLocators(selectors, this.getFilePath());
|
|
775
|
+
selectedStrategy = allStrategyLocators?.strategy;
|
|
776
|
+
}
|
|
686
777
|
for (let i = 0; i < 3; i++) {
|
|
687
778
|
info.log += "attempt " + i + ": total locators " + selectors.locators.length + "\n";
|
|
688
779
|
for (let j = 0; j < selectors.locators.length; j++) {
|
|
689
780
|
let selector = selectors.locators[j];
|
|
690
781
|
info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
|
|
691
782
|
}
|
|
692
|
-
|
|
783
|
+
if (this.tryAllStrategies && selectedStrategy) {
|
|
784
|
+
const strategyLocators = allStrategyLocators[selectedStrategy];
|
|
785
|
+
let err;
|
|
786
|
+
if (strategyLocators && strategyLocators.length) {
|
|
787
|
+
try {
|
|
788
|
+
selectors.locators = strategyLocators;
|
|
789
|
+
element = await this._locate_internal(selectors, info, _params, 10_000, allowDisabled);
|
|
790
|
+
info.selectedStrategy = selectedStrategy;
|
|
791
|
+
info.log += "element found using strategy " + selectedStrategy + "\n";
|
|
792
|
+
}
|
|
793
|
+
catch (error) {
|
|
794
|
+
err = error;
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
if (!element) {
|
|
798
|
+
for (const key in allStrategyLocators) {
|
|
799
|
+
if (key === "strategy" || key === selectedStrategy) {
|
|
800
|
+
continue;
|
|
801
|
+
}
|
|
802
|
+
const strategyLocators = allStrategyLocators[key];
|
|
803
|
+
if (strategyLocators && strategyLocators.length) {
|
|
804
|
+
try {
|
|
805
|
+
info.log += "using strategy " + key + " with locators " + JSON.stringify(strategyLocators) + "\n";
|
|
806
|
+
selectors.locators = strategyLocators;
|
|
807
|
+
element = await this._locate_internal(selectors, info, _params, 10_000, allowDisabled);
|
|
808
|
+
err = null;
|
|
809
|
+
info.selectedStrategy = key;
|
|
810
|
+
info.log += "element found using strategy " + key + "\n";
|
|
811
|
+
break;
|
|
812
|
+
}
|
|
813
|
+
catch (error) {
|
|
814
|
+
err = error;
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
if (err) {
|
|
820
|
+
throw err;
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
else {
|
|
824
|
+
element = await this._locate_internal(selectors, info, _params, timeout, allowDisabled);
|
|
825
|
+
}
|
|
693
826
|
if (!element.rerun) {
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
827
|
+
let newElementSelector = "";
|
|
828
|
+
if (this.configuration && this.configuration.stableLocatorStrategy === "csschain") {
|
|
829
|
+
const cssSelector = await element.evaluate((el) => {
|
|
830
|
+
function getCssSelector(el) {
|
|
831
|
+
if (!el || el.nodeType !== 1 || el === document.body)
|
|
832
|
+
return el.tagName.toLowerCase();
|
|
833
|
+
const parent = el.parentElement;
|
|
834
|
+
const tag = el.tagName.toLowerCase();
|
|
835
|
+
// Find the index of the element among its siblings of the same tag
|
|
836
|
+
let index = 1;
|
|
837
|
+
for (let sibling = el.previousElementSibling; sibling; sibling = sibling.previousElementSibling) {
|
|
838
|
+
if (sibling.tagName === el.tagName) {
|
|
839
|
+
index++;
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
// Use nth-child if necessary (i.e., if there's more than one of the same tag)
|
|
843
|
+
const siblings = Array.from(parent.children).filter((child) => child.tagName === el.tagName);
|
|
844
|
+
const needsNthChild = siblings.length > 1;
|
|
845
|
+
const selector = needsNthChild ? `${tag}:nth-child(${[...parent.children].indexOf(el) + 1})` : tag;
|
|
846
|
+
return getCssSelector(parent) + " > " + selector;
|
|
847
|
+
}
|
|
848
|
+
const cssSelector = getCssSelector(el);
|
|
849
|
+
return cssSelector;
|
|
850
|
+
});
|
|
851
|
+
newElementSelector = cssSelector;
|
|
852
|
+
}
|
|
853
|
+
else {
|
|
854
|
+
const randomToken = "blinq_" + Math.random().toString(36).substring(7);
|
|
855
|
+
if (this.configuration && this.configuration.stableLocatorStrategy === "data-attribute") {
|
|
856
|
+
const dataAttribute = "data-blinq-id";
|
|
857
|
+
await element.evaluate((el, [dataAttribute, randomToken]) => {
|
|
858
|
+
el.setAttribute(dataAttribute, randomToken);
|
|
859
|
+
}, [dataAttribute, randomToken]);
|
|
860
|
+
newElementSelector = `[${dataAttribute}="${randomToken}"]`;
|
|
861
|
+
}
|
|
862
|
+
else {
|
|
863
|
+
// the default case just return the located element
|
|
864
|
+
// will not work for click and type if the locator is placeholder and the placeholder change due to the click event
|
|
865
|
+
return element;
|
|
866
|
+
}
|
|
867
|
+
}
|
|
701
868
|
const scope = element._frame ?? element.page();
|
|
702
|
-
let newElementSelector = "[data-blinq-id-" + randomToken + "]";
|
|
703
869
|
let prefixSelector = "";
|
|
704
870
|
const frameControlSelector = " >> internal:control=enter-frame";
|
|
705
871
|
const frameSelectorIndex = element._selector.lastIndexOf(frameControlSelector);
|
|
706
872
|
if (frameSelectorIndex !== -1) {
|
|
707
873
|
// remove everything after the >> internal:control=enter-frame
|
|
708
874
|
const frameSelector = element._selector.substring(0, frameSelectorIndex);
|
|
709
|
-
prefixSelector = frameSelector + " >> internal:control=enter-frame >>";
|
|
875
|
+
prefixSelector = frameSelector + " >> internal:control=enter-frame >> ";
|
|
710
876
|
}
|
|
711
877
|
// if (element?._frame?._selector) {
|
|
712
878
|
// prefixSelector = element._frame._selector + " >> " + prefixSelector;
|
|
713
879
|
// }
|
|
714
880
|
const newSelector = prefixSelector + newElementSelector;
|
|
715
|
-
return scope.locator(newSelector);
|
|
881
|
+
return scope.locator(newSelector).first();
|
|
716
882
|
}
|
|
717
883
|
}
|
|
718
884
|
throw new Error("unable to locate element " + JSON.stringify(selectors));
|
|
@@ -733,7 +899,7 @@ class StableBrowser {
|
|
|
733
899
|
for (let i = 0; i < frame.selectors.length; i++) {
|
|
734
900
|
let frameLocator = frame.selectors[i];
|
|
735
901
|
if (frameLocator.css) {
|
|
736
|
-
let testframescope = framescope.frameLocator(frameLocator.css);
|
|
902
|
+
let testframescope = framescope.frameLocator(`${frameLocator.css} >> visible=true`);
|
|
737
903
|
if (frameLocator.index) {
|
|
738
904
|
testframescope = framescope.nth(frameLocator.index);
|
|
739
905
|
}
|
|
@@ -811,6 +977,15 @@ class StableBrowser {
|
|
|
811
977
|
});
|
|
812
978
|
}
|
|
813
979
|
async _locate_internal(selectors, info, _params, timeout = 30000, allowDisabled = false) {
|
|
980
|
+
if (selectors.locators && Array.isArray(selectors.locators)) {
|
|
981
|
+
selectors.locators.forEach((locator) => {
|
|
982
|
+
locator.index = locator.index ?? 0;
|
|
983
|
+
locator.visible = locator.visible ?? true;
|
|
984
|
+
if (locator.visible && locator.css && !locator.css.endsWith(">> visible=true")) {
|
|
985
|
+
locator.css = locator.css + " >> visible=true";
|
|
986
|
+
}
|
|
987
|
+
});
|
|
988
|
+
}
|
|
814
989
|
if (!info) {
|
|
815
990
|
info = {};
|
|
816
991
|
info.failCause = {};
|
|
@@ -823,7 +998,6 @@ class StableBrowser {
|
|
|
823
998
|
let locatorsCount = 0;
|
|
824
999
|
let lazy_scroll = false;
|
|
825
1000
|
//let arrayMode = Array.isArray(selectors);
|
|
826
|
-
let scope = await this._findFrameScope(selectors, timeout, info);
|
|
827
1001
|
let selectorsLocators = null;
|
|
828
1002
|
selectorsLocators = selectors.locators;
|
|
829
1003
|
// group selectors by priority
|
|
@@ -851,6 +1025,7 @@ class StableBrowser {
|
|
|
851
1025
|
let highPriorityOnly = true;
|
|
852
1026
|
let visibleOnly = true;
|
|
853
1027
|
while (true) {
|
|
1028
|
+
let scope = await this._findFrameScope(selectors, timeout, info);
|
|
854
1029
|
locatorsCount = 0;
|
|
855
1030
|
let result = [];
|
|
856
1031
|
let popupResult = await this.closeUnexpectedPopups(info, _params);
|
|
@@ -966,9 +1141,13 @@ class StableBrowser {
|
|
|
966
1141
|
}
|
|
967
1142
|
}
|
|
968
1143
|
if (foundLocators.length === 1) {
|
|
1144
|
+
let box = null;
|
|
1145
|
+
if (!this.onlyFailuresScreenshot) {
|
|
1146
|
+
box = await foundLocators[0].boundingBox();
|
|
1147
|
+
}
|
|
969
1148
|
result.foundElements.push({
|
|
970
1149
|
locator: foundLocators[0],
|
|
971
|
-
box:
|
|
1150
|
+
box: box,
|
|
972
1151
|
unique: true,
|
|
973
1152
|
});
|
|
974
1153
|
result.locatorIndex = i;
|
|
@@ -1123,11 +1302,22 @@ class StableBrowser {
|
|
|
1123
1302
|
operation: "click",
|
|
1124
1303
|
log: "***** click on " + selectors.element_name + " *****\n",
|
|
1125
1304
|
};
|
|
1305
|
+
check_performance("click_all ***", this.context, true);
|
|
1306
|
+
let stepFastMode = this.stepTags.includes("fast-mode");
|
|
1307
|
+
if (stepFastMode) {
|
|
1308
|
+
state.onlyFailuresScreenshot = true;
|
|
1309
|
+
state.scroll = false;
|
|
1310
|
+
state.highlight = false;
|
|
1311
|
+
}
|
|
1126
1312
|
try {
|
|
1313
|
+
check_performance("click_preCommand", this.context, true);
|
|
1127
1314
|
await _preCommand(state, this);
|
|
1315
|
+
check_performance("click_preCommand", this.context, false);
|
|
1128
1316
|
await performAction("click", state.element, options, this, state, _params);
|
|
1129
|
-
if (!this.fastMode) {
|
|
1130
|
-
|
|
1317
|
+
if (!this.fastMode && !this.stepTags.includes("fast-mode")) {
|
|
1318
|
+
check_performance("click_waitForPageLoad", this.context, true);
|
|
1319
|
+
await this.waitForPageLoad({ noSleep: true });
|
|
1320
|
+
check_performance("click_waitForPageLoad", this.context, false);
|
|
1131
1321
|
}
|
|
1132
1322
|
return state.info;
|
|
1133
1323
|
}
|
|
@@ -1135,7 +1325,13 @@ class StableBrowser {
|
|
|
1135
1325
|
await _commandError(state, e, this);
|
|
1136
1326
|
}
|
|
1137
1327
|
finally {
|
|
1328
|
+
check_performance("click_commandFinally", this.context, true);
|
|
1138
1329
|
await _commandFinally(state, this);
|
|
1330
|
+
check_performance("click_commandFinally", this.context, false);
|
|
1331
|
+
check_performance("click_all ***", this.context, false);
|
|
1332
|
+
if (this.context.profile) {
|
|
1333
|
+
console.log(JSON.stringify(this.context.profile, null, 2));
|
|
1334
|
+
}
|
|
1139
1335
|
}
|
|
1140
1336
|
}
|
|
1141
1337
|
async waitForElement(selectors, _params, options = {}, world = null) {
|
|
@@ -1227,7 +1423,7 @@ class StableBrowser {
|
|
|
1227
1423
|
}
|
|
1228
1424
|
}
|
|
1229
1425
|
}
|
|
1230
|
-
await this.waitForPageLoad();
|
|
1426
|
+
//await this.waitForPageLoad();
|
|
1231
1427
|
return state.info;
|
|
1232
1428
|
}
|
|
1233
1429
|
catch (e) {
|
|
@@ -1253,7 +1449,7 @@ class StableBrowser {
|
|
|
1253
1449
|
await _preCommand(state, this);
|
|
1254
1450
|
await performAction("hover", state.element, options, this, state, _params);
|
|
1255
1451
|
await _screenshot(state, this);
|
|
1256
|
-
await this.waitForPageLoad();
|
|
1452
|
+
//await this.waitForPageLoad();
|
|
1257
1453
|
return state.info;
|
|
1258
1454
|
}
|
|
1259
1455
|
catch (e) {
|
|
@@ -1289,7 +1485,7 @@ class StableBrowser {
|
|
|
1289
1485
|
state.info.log += "selectOption failed, will try force" + "\n";
|
|
1290
1486
|
await state.element.selectOption(values, { timeout: 10000, force: true });
|
|
1291
1487
|
}
|
|
1292
|
-
await this.waitForPageLoad();
|
|
1488
|
+
//await this.waitForPageLoad();
|
|
1293
1489
|
return state.info;
|
|
1294
1490
|
}
|
|
1295
1491
|
catch (e) {
|
|
@@ -1475,6 +1671,14 @@ class StableBrowser {
|
|
|
1475
1671
|
}
|
|
1476
1672
|
try {
|
|
1477
1673
|
await _preCommand(state, this);
|
|
1674
|
+
const randomToken = "blinq_" + Math.random().toString(36).substring(7);
|
|
1675
|
+
// tag the element
|
|
1676
|
+
let newElementSelector = await state.element.evaluate((el, token) => {
|
|
1677
|
+
// use attribute and not id
|
|
1678
|
+
const attrName = `data-blinq-id-${token}`;
|
|
1679
|
+
el.setAttribute(attrName, "");
|
|
1680
|
+
return `[${attrName}]`;
|
|
1681
|
+
}, randomToken);
|
|
1478
1682
|
state.info.value = _value;
|
|
1479
1683
|
if (!options.press) {
|
|
1480
1684
|
try {
|
|
@@ -1500,6 +1704,25 @@ class StableBrowser {
|
|
|
1500
1704
|
}
|
|
1501
1705
|
}
|
|
1502
1706
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1707
|
+
// check if the element exist after the click (no wait)
|
|
1708
|
+
const count = await state.element.count({ timeout: 0 });
|
|
1709
|
+
if (count === 0) {
|
|
1710
|
+
// the locator changed after the click (placeholder) we need to locate the element using the data-blinq-id
|
|
1711
|
+
const scope = state.element._frame ?? element.page();
|
|
1712
|
+
let prefixSelector = "";
|
|
1713
|
+
const frameControlSelector = " >> internal:control=enter-frame";
|
|
1714
|
+
const frameSelectorIndex = state.element._selector.lastIndexOf(frameControlSelector);
|
|
1715
|
+
if (frameSelectorIndex !== -1) {
|
|
1716
|
+
// remove everything after the >> internal:control=enter-frame
|
|
1717
|
+
const frameSelector = state.element._selector.substring(0, frameSelectorIndex);
|
|
1718
|
+
prefixSelector = frameSelector + " >> internal:control=enter-frame >> ";
|
|
1719
|
+
}
|
|
1720
|
+
// if (element?._frame?._selector) {
|
|
1721
|
+
// prefixSelector = element._frame._selector + " >> " + prefixSelector;
|
|
1722
|
+
// }
|
|
1723
|
+
const newSelector = prefixSelector + newElementSelector;
|
|
1724
|
+
state.element = scope.locator(newSelector).first();
|
|
1725
|
+
}
|
|
1503
1726
|
const valueSegment = state.value.split("&&");
|
|
1504
1727
|
for (let i = 0; i < valueSegment.length; i++) {
|
|
1505
1728
|
if (i > 0) {
|
|
@@ -1571,8 +1794,8 @@ class StableBrowser {
|
|
|
1571
1794
|
if (enter) {
|
|
1572
1795
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1573
1796
|
await this.page.keyboard.press("Enter");
|
|
1797
|
+
await this.waitForPageLoad();
|
|
1574
1798
|
}
|
|
1575
|
-
await this.waitForPageLoad();
|
|
1576
1799
|
return state.info;
|
|
1577
1800
|
}
|
|
1578
1801
|
catch (e) {
|
|
@@ -1831,11 +2054,12 @@ class StableBrowser {
|
|
|
1831
2054
|
throw new Error("referanceSnapshot is null");
|
|
1832
2055
|
}
|
|
1833
2056
|
let text = null;
|
|
1834
|
-
|
|
1835
|
-
|
|
2057
|
+
const snapshotsFolder = process.env.BVT_TEMP_SNAPSHOTS_FOLDER ?? this.context.snapshotFolder; //path .join(this.project_path, "data", "snapshots");
|
|
2058
|
+
if (fs.existsSync(path.join(snapshotsFolder, referanceSnapshot + ".yml"))) {
|
|
2059
|
+
text = fs.readFileSync(path.join(snapshotsFolder, referanceSnapshot + ".yml"), "utf8");
|
|
1836
2060
|
}
|
|
1837
|
-
else if (fs.existsSync(path.join(
|
|
1838
|
-
text = fs.readFileSync(path.join(
|
|
2061
|
+
else if (fs.existsSync(path.join(snapshotsFolder, referanceSnapshot + ".yaml"))) {
|
|
2062
|
+
text = fs.readFileSync(path.join(snapshotsFolder, referanceSnapshot + ".yaml"), "utf8");
|
|
1839
2063
|
}
|
|
1840
2064
|
else if (referanceSnapshot.startsWith("yaml:")) {
|
|
1841
2065
|
text = referanceSnapshot.substring(5);
|
|
@@ -1859,7 +2083,13 @@ class StableBrowser {
|
|
|
1859
2083
|
scope = await this._findFrameScope(frameSelectors, timeout, state.info);
|
|
1860
2084
|
}
|
|
1861
2085
|
const snapshot = await scope.locator("body").ariaSnapshot({ timeout });
|
|
2086
|
+
if (snapshot && snapshot.length <= 10) {
|
|
2087
|
+
console.log("Page snapshot length is suspiciously small:", snapshot);
|
|
2088
|
+
}
|
|
1862
2089
|
matchResult = snapshotValidation(snapshot, newValue, referanceSnapshot);
|
|
2090
|
+
if (matchResult === undefined) {
|
|
2091
|
+
console.log("snapshotValidation returned undefined");
|
|
2092
|
+
}
|
|
1863
2093
|
if (matchResult.errorLine !== -1) {
|
|
1864
2094
|
throw new Error("Snapshot validation failed at line " + matchResult.errorLineText);
|
|
1865
2095
|
}
|
|
@@ -2030,6 +2260,9 @@ class StableBrowser {
|
|
|
2030
2260
|
return _getTestData(world, this.context, this);
|
|
2031
2261
|
}
|
|
2032
2262
|
async _screenShot(options = {}, world = null, info = null) {
|
|
2263
|
+
if (!options) {
|
|
2264
|
+
options = {};
|
|
2265
|
+
}
|
|
2033
2266
|
// collect url/path/title
|
|
2034
2267
|
if (info) {
|
|
2035
2268
|
if (!info.title) {
|
|
@@ -2058,7 +2291,7 @@ class StableBrowser {
|
|
|
2058
2291
|
const uuidStr = "id_" + randomUUID();
|
|
2059
2292
|
const screenshotPath = path.join(world.screenshotPath, uuidStr + ".png");
|
|
2060
2293
|
try {
|
|
2061
|
-
await this.takeScreenshot(screenshotPath);
|
|
2294
|
+
await this.takeScreenshot(screenshotPath, options.fullPage === true);
|
|
2062
2295
|
// let buffer = await this.page.screenshot({ timeout: 4000 });
|
|
2063
2296
|
// // save the buffer to the screenshot path asynchrously
|
|
2064
2297
|
// fs.writeFile(screenshotPath, buffer, (err) => {
|
|
@@ -2079,7 +2312,7 @@ class StableBrowser {
|
|
|
2079
2312
|
else if (options && options.screenshot) {
|
|
2080
2313
|
result.screenshotPath = options.screenshotPath;
|
|
2081
2314
|
try {
|
|
2082
|
-
await this.takeScreenshot(options.screenshotPath);
|
|
2315
|
+
await this.takeScreenshot(options.screenshotPath, options.fullPage === true);
|
|
2083
2316
|
// let buffer = await this.page.screenshot({ timeout: 4000 });
|
|
2084
2317
|
// // save the buffer to the screenshot path asynchrously
|
|
2085
2318
|
// fs.writeFile(options.screenshotPath, buffer, (err) => {
|
|
@@ -2097,7 +2330,7 @@ class StableBrowser {
|
|
|
2097
2330
|
}
|
|
2098
2331
|
return result;
|
|
2099
2332
|
}
|
|
2100
|
-
async takeScreenshot(screenshotPath) {
|
|
2333
|
+
async takeScreenshot(screenshotPath, fullPage = false) {
|
|
2101
2334
|
const playContext = this.context.playContext;
|
|
2102
2335
|
// Using CDP to capture the screenshot
|
|
2103
2336
|
const viewportWidth = Math.max(...(await this.page.evaluate(() => [
|
|
@@ -2122,13 +2355,7 @@ class StableBrowser {
|
|
|
2122
2355
|
const client = await playContext.newCDPSession(this.page);
|
|
2123
2356
|
const { data } = await client.send("Page.captureScreenshot", {
|
|
2124
2357
|
format: "png",
|
|
2125
|
-
|
|
2126
|
-
// x: 0,
|
|
2127
|
-
// y: 0,
|
|
2128
|
-
// width: viewportWidth,
|
|
2129
|
-
// height: viewportHeight,
|
|
2130
|
-
// scale: 1,
|
|
2131
|
-
// },
|
|
2358
|
+
captureBeyondViewport: fullPage,
|
|
2132
2359
|
});
|
|
2133
2360
|
await client.detach();
|
|
2134
2361
|
if (!screenshotPath) {
|
|
@@ -2137,7 +2364,7 @@ class StableBrowser {
|
|
|
2137
2364
|
screenshotBuffer = Buffer.from(data, "base64");
|
|
2138
2365
|
}
|
|
2139
2366
|
else {
|
|
2140
|
-
screenshotBuffer = await this.page.screenshot();
|
|
2367
|
+
screenshotBuffer = await this.page.screenshot({ fullPage: fullPage });
|
|
2141
2368
|
}
|
|
2142
2369
|
// if (focusedElement) {
|
|
2143
2370
|
// // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
|
|
@@ -2237,6 +2464,12 @@ class StableBrowser {
|
|
|
2237
2464
|
state.info.value = state.value;
|
|
2238
2465
|
this.setTestData({ [variable]: state.value }, world);
|
|
2239
2466
|
this.logger.info("set test data: " + variable + "=" + state.value);
|
|
2467
|
+
if (process.env.MODE === "executions") {
|
|
2468
|
+
const globalDataFile = "global_test_data.json";
|
|
2469
|
+
if (existsSync(globalDataFile)) {
|
|
2470
|
+
this.saveTestDataAsGlobal({}, world);
|
|
2471
|
+
}
|
|
2472
|
+
}
|
|
2240
2473
|
// await new Promise((resolve) => setTimeout(resolve, 500));
|
|
2241
2474
|
return state.info;
|
|
2242
2475
|
}
|
|
@@ -2308,6 +2541,12 @@ class StableBrowser {
|
|
|
2308
2541
|
state.info.value = state.value;
|
|
2309
2542
|
this.setTestData({ [variable]: state.value }, world);
|
|
2310
2543
|
this.logger.info("set test data: " + variable + "=" + state.value);
|
|
2544
|
+
if (process.env.MODE === "executions") {
|
|
2545
|
+
const globalDataFile = "global_test_data.json";
|
|
2546
|
+
if (existsSync(globalDataFile)) {
|
|
2547
|
+
this.saveTestDataAsGlobal({}, world);
|
|
2548
|
+
}
|
|
2549
|
+
}
|
|
2311
2550
|
// await new Promise((resolve) => setTimeout(resolve, 500));
|
|
2312
2551
|
return state.info;
|
|
2313
2552
|
}
|
|
@@ -2438,7 +2677,7 @@ class StableBrowser {
|
|
|
2438
2677
|
let expectedValue;
|
|
2439
2678
|
try {
|
|
2440
2679
|
await _preCommand(state, this);
|
|
2441
|
-
expectedValue = await
|
|
2680
|
+
expectedValue = await this._replaceWithLocalData(value, world);
|
|
2442
2681
|
state.info.expectedValue = expectedValue;
|
|
2443
2682
|
switch (property) {
|
|
2444
2683
|
case "innerText":
|
|
@@ -2564,6 +2803,7 @@ class StableBrowser {
|
|
|
2564
2803
|
allowDisabled: true,
|
|
2565
2804
|
info: {},
|
|
2566
2805
|
};
|
|
2806
|
+
state.options ??= { timeout: timeoutMs };
|
|
2567
2807
|
// Initialize startTime outside try block to ensure it's always accessible
|
|
2568
2808
|
const startTime = Date.now();
|
|
2569
2809
|
let conditionMet = false;
|
|
@@ -2733,6 +2973,12 @@ class StableBrowser {
|
|
|
2733
2973
|
emailUrl = url;
|
|
2734
2974
|
codeOrUrlFound = true;
|
|
2735
2975
|
}
|
|
2976
|
+
if (process.env.MODE === "executions") {
|
|
2977
|
+
const globalDataFile = "global_test_data.json";
|
|
2978
|
+
if (existsSync(globalDataFile)) {
|
|
2979
|
+
this.saveTestDataAsGlobal({}, world);
|
|
2980
|
+
}
|
|
2981
|
+
}
|
|
2736
2982
|
if (codeOrUrlFound) {
|
|
2737
2983
|
return { emailUrl, emailCode };
|
|
2738
2984
|
}
|
|
@@ -3139,7 +3385,16 @@ class StableBrowser {
|
|
|
3139
3385
|
text = text.replace(/\\"/g, '"');
|
|
3140
3386
|
}
|
|
3141
3387
|
const timeout = this._getFindElementTimeout(options);
|
|
3142
|
-
|
|
3388
|
+
//if (!this.fastMode && !this.stepTags.includes("fast-mode")) {
|
|
3389
|
+
let stepFastMode = this.stepTags.includes("fast-mode");
|
|
3390
|
+
if (!stepFastMode) {
|
|
3391
|
+
if (!this.fastMode) {
|
|
3392
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
3393
|
+
}
|
|
3394
|
+
else {
|
|
3395
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
3396
|
+
}
|
|
3397
|
+
}
|
|
3143
3398
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
3144
3399
|
if (newValue !== text) {
|
|
3145
3400
|
this.logger.info(text + "=" + newValue);
|
|
@@ -3147,6 +3402,11 @@ class StableBrowser {
|
|
|
3147
3402
|
}
|
|
3148
3403
|
let dateAlternatives = findDateAlternatives(text);
|
|
3149
3404
|
let numberAlternatives = findNumberAlternatives(text);
|
|
3405
|
+
if (stepFastMode) {
|
|
3406
|
+
state.onlyFailuresScreenshot = true;
|
|
3407
|
+
state.scroll = false;
|
|
3408
|
+
state.highlight = false;
|
|
3409
|
+
}
|
|
3150
3410
|
try {
|
|
3151
3411
|
await _preCommand(state, this);
|
|
3152
3412
|
state.info.text = text;
|
|
@@ -3266,6 +3526,8 @@ class StableBrowser {
|
|
|
3266
3526
|
operation: "verify_text_with_relation",
|
|
3267
3527
|
log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
|
|
3268
3528
|
};
|
|
3529
|
+
const cmdStartTime = Date.now();
|
|
3530
|
+
let cmdEndTime = null;
|
|
3269
3531
|
const timeout = this._getFindElementTimeout(options);
|
|
3270
3532
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
3271
3533
|
let newValue = await this._replaceWithLocalData(textAnchor, world);
|
|
@@ -3301,6 +3563,17 @@ class StableBrowser {
|
|
|
3301
3563
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
3302
3564
|
continue;
|
|
3303
3565
|
}
|
|
3566
|
+
else {
|
|
3567
|
+
cmdEndTime = Date.now();
|
|
3568
|
+
if (cmdEndTime - cmdStartTime > 55000) {
|
|
3569
|
+
if (foundAncore) {
|
|
3570
|
+
throw new Error(`Text ${textToVerify} not found in page`);
|
|
3571
|
+
}
|
|
3572
|
+
else {
|
|
3573
|
+
throw new Error(`Text ${textAnchor} not found in page`);
|
|
3574
|
+
}
|
|
3575
|
+
}
|
|
3576
|
+
}
|
|
3304
3577
|
try {
|
|
3305
3578
|
for (let i = 0; i < resultWithElementsFound.length; i++) {
|
|
3306
3579
|
foundAncore = true;
|
|
@@ -3439,7 +3712,7 @@ class StableBrowser {
|
|
|
3439
3712
|
Object.assign(e, { info: info });
|
|
3440
3713
|
error = e;
|
|
3441
3714
|
// throw e;
|
|
3442
|
-
await _commandError({ text: "visualVerification", operation: "visualVerification",
|
|
3715
|
+
await _commandError({ text: "visualVerification", operation: "visualVerification", info }, e, this);
|
|
3443
3716
|
}
|
|
3444
3717
|
finally {
|
|
3445
3718
|
const endTime = Date.now();
|
|
@@ -3788,6 +4061,22 @@ class StableBrowser {
|
|
|
3788
4061
|
}
|
|
3789
4062
|
}
|
|
3790
4063
|
async waitForPageLoad(options = {}, world = null) {
|
|
4064
|
+
// try {
|
|
4065
|
+
// let currentPagePath = null;
|
|
4066
|
+
// currentPagePath = new URL(this.page.url()).pathname;
|
|
4067
|
+
// if (this.latestPagePath) {
|
|
4068
|
+
// // get the currect page path and compare with the latest page path
|
|
4069
|
+
// if (this.latestPagePath === currentPagePath) {
|
|
4070
|
+
// // if the page path is the same, do not wait for page load
|
|
4071
|
+
// console.log("No page change: " + currentPagePath);
|
|
4072
|
+
// return;
|
|
4073
|
+
// }
|
|
4074
|
+
// }
|
|
4075
|
+
// this.latestPagePath = currentPagePath;
|
|
4076
|
+
// } catch (e) {
|
|
4077
|
+
// console.debug("Error getting current page path: ", e);
|
|
4078
|
+
// }
|
|
4079
|
+
//console.log("Waiting for page load");
|
|
3791
4080
|
let timeout = this._getLoadTimeout(options);
|
|
3792
4081
|
const promiseArray = [];
|
|
3793
4082
|
// let waitForNetworkIdle = true;
|
|
@@ -3822,7 +4111,10 @@ class StableBrowser {
|
|
|
3822
4111
|
}
|
|
3823
4112
|
}
|
|
3824
4113
|
finally {
|
|
3825
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
4114
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
4115
|
+
if (options && !options.noSleep) {
|
|
4116
|
+
await new Promise((resolve) => setTimeout(resolve, 1500));
|
|
4117
|
+
}
|
|
3826
4118
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world));
|
|
3827
4119
|
const endTime = Date.now();
|
|
3828
4120
|
_reportToWorld(world, {
|
|
@@ -3876,7 +4168,7 @@ class StableBrowser {
|
|
|
3876
4168
|
}
|
|
3877
4169
|
operation = options.operation;
|
|
3878
4170
|
// validate operation is one of the supported operations
|
|
3879
|
-
if (operation != "click" && operation != "hover+click") {
|
|
4171
|
+
if (operation != "click" && operation != "hover+click" && operation != "hover") {
|
|
3880
4172
|
throw new Error("operation is not supported");
|
|
3881
4173
|
}
|
|
3882
4174
|
const state = {
|
|
@@ -3945,6 +4237,17 @@ class StableBrowser {
|
|
|
3945
4237
|
state.element = results[0];
|
|
3946
4238
|
await performAction("hover+click", state.element, options, this, state, _params);
|
|
3947
4239
|
break;
|
|
4240
|
+
case "hover":
|
|
4241
|
+
if (!options.css) {
|
|
4242
|
+
throw new Error("css is not defined");
|
|
4243
|
+
}
|
|
4244
|
+
const result1 = await findElementsInArea(options.css, cellArea, this, options);
|
|
4245
|
+
if (result1.length === 0) {
|
|
4246
|
+
throw new Error(`Element not found in cell area`);
|
|
4247
|
+
}
|
|
4248
|
+
state.element = result1[0];
|
|
4249
|
+
await performAction("hover", state.element, options, this, state, _params);
|
|
4250
|
+
break;
|
|
3948
4251
|
default:
|
|
3949
4252
|
throw new Error("operation is not supported");
|
|
3950
4253
|
}
|
|
@@ -3958,6 +4261,12 @@ class StableBrowser {
|
|
|
3958
4261
|
}
|
|
3959
4262
|
saveTestDataAsGlobal(options, world) {
|
|
3960
4263
|
const dataFile = _getDataFile(world, this.context, this);
|
|
4264
|
+
if (process.env.MODE === "executions") {
|
|
4265
|
+
const globalDataFile = path.join(this.project_path, "global_test_data.json");
|
|
4266
|
+
fs.copyFileSync(dataFile, globalDataFile);
|
|
4267
|
+
this.logger.info("Save the scenario test data to " + globalDataFile + " as global for the following scenarios.");
|
|
4268
|
+
return;
|
|
4269
|
+
}
|
|
3961
4270
|
process.env.GLOBAL_TEST_DATA_FILE = dataFile;
|
|
3962
4271
|
this.logger.info("Save the scenario test data as global for the following scenarios.");
|
|
3963
4272
|
}
|
|
@@ -4060,6 +4369,7 @@ class StableBrowser {
|
|
|
4060
4369
|
if (world && world.attach) {
|
|
4061
4370
|
world.attach(this.context.reportFolder, { mediaType: "text/plain" });
|
|
4062
4371
|
}
|
|
4372
|
+
this.context.loadedRoutes = null;
|
|
4063
4373
|
this.beforeScenarioCalled = true;
|
|
4064
4374
|
if (scenario && scenario.pickle && scenario.pickle.name) {
|
|
4065
4375
|
this.scenarioName = scenario.pickle.name;
|
|
@@ -4086,14 +4396,32 @@ class StableBrowser {
|
|
|
4086
4396
|
await getTestData(envName, world, undefined, this.featureName, this.scenarioName, this.context);
|
|
4087
4397
|
}
|
|
4088
4398
|
await loadBrunoParams(this.context, this.context.environment.name);
|
|
4399
|
+
if ((process.env.TRACE === "true" || this.configuration.trace === true) && this.context) {
|
|
4400
|
+
this.trace = true;
|
|
4401
|
+
const traceFolder = path.join(this.context.reportFolder, "trace");
|
|
4402
|
+
if (!fs.existsSync(traceFolder)) {
|
|
4403
|
+
fs.mkdirSync(traceFolder, { recursive: true });
|
|
4404
|
+
}
|
|
4405
|
+
this.traceFolder = traceFolder;
|
|
4406
|
+
await this.context.playContext.tracing.start({ screenshots: true, snapshots: true });
|
|
4407
|
+
}
|
|
4408
|
+
}
|
|
4409
|
+
async afterScenario(world, scenario) {
|
|
4410
|
+
const id = scenario.testCaseStartedId;
|
|
4411
|
+
if (this.trace) {
|
|
4412
|
+
await this.context.playContext.tracing.stop({
|
|
4413
|
+
path: path.join(this.traceFolder, `trace-${id}.zip`),
|
|
4414
|
+
});
|
|
4415
|
+
}
|
|
4089
4416
|
}
|
|
4090
|
-
async afterScenario(world, scenario) { }
|
|
4091
4417
|
async beforeStep(world, step) {
|
|
4092
|
-
if (this.
|
|
4093
|
-
|
|
4418
|
+
if (step?.pickleStep && this.trace) {
|
|
4419
|
+
await this.context.playContext.tracing.group(`Step: ${step.pickleStep.text}`);
|
|
4094
4420
|
}
|
|
4421
|
+
this.stepTags = [];
|
|
4095
4422
|
if (!this.beforeScenarioCalled) {
|
|
4096
4423
|
this.beforeScenario(world, step);
|
|
4424
|
+
this.context.loadedRoutes = null;
|
|
4097
4425
|
}
|
|
4098
4426
|
if (this.stepIndex === undefined) {
|
|
4099
4427
|
this.stepIndex = 0;
|
|
@@ -4103,7 +4431,12 @@ class StableBrowser {
|
|
|
4103
4431
|
}
|
|
4104
4432
|
if (step && step.pickleStep && step.pickleStep.text) {
|
|
4105
4433
|
this.stepName = step.pickleStep.text;
|
|
4106
|
-
|
|
4434
|
+
let printableStepName = this.stepName;
|
|
4435
|
+
// take the printableStepName and replace quated value with \x1b[33m and \x1b[0m
|
|
4436
|
+
printableStepName = printableStepName.replace(/"([^"]*)"/g, (match, p1) => {
|
|
4437
|
+
return `\x1b[33m"${p1}"\x1b[0m`;
|
|
4438
|
+
});
|
|
4439
|
+
this.logger.info("\x1b[38;5;208mstep:\x1b[0m " + printableStepName);
|
|
4107
4440
|
}
|
|
4108
4441
|
else if (step && step.text) {
|
|
4109
4442
|
this.stepName = step.text;
|
|
@@ -4118,7 +4451,10 @@ class StableBrowser {
|
|
|
4118
4451
|
}
|
|
4119
4452
|
if (this.initSnapshotTaken === false) {
|
|
4120
4453
|
this.initSnapshotTaken = true;
|
|
4121
|
-
if (world &&
|
|
4454
|
+
if (world &&
|
|
4455
|
+
world.attach &&
|
|
4456
|
+
!process.env.DISABLE_SNAPSHOT &&
|
|
4457
|
+
(!this.fastMode || this.stepTags.includes("fast-mode"))) {
|
|
4122
4458
|
const snapshot = await this.getAriaSnapshot();
|
|
4123
4459
|
if (snapshot) {
|
|
4124
4460
|
await world.attach(JSON.stringify(snapshot), "application/json+snapshot-before");
|
|
@@ -4128,7 +4464,11 @@ class StableBrowser {
|
|
|
4128
4464
|
this.context.routeResults = null;
|
|
4129
4465
|
this.context.loadedRoutes = null;
|
|
4130
4466
|
await registerBeforeStepRoutes(this.context, this.stepName, world);
|
|
4131
|
-
networkBeforeStep(this.stepName);
|
|
4467
|
+
networkBeforeStep(this.stepName, this.context);
|
|
4468
|
+
this.inStepReport = false;
|
|
4469
|
+
}
|
|
4470
|
+
setStepTags(tags) {
|
|
4471
|
+
this.stepTags = tags;
|
|
4132
4472
|
}
|
|
4133
4473
|
async getAriaSnapshot() {
|
|
4134
4474
|
try {
|
|
@@ -4202,7 +4542,7 @@ class StableBrowser {
|
|
|
4202
4542
|
state.payload = payload;
|
|
4203
4543
|
if (commandStatus === "FAILED") {
|
|
4204
4544
|
state.throwError = true;
|
|
4205
|
-
throw new Error(
|
|
4545
|
+
throw new Error(commandText);
|
|
4206
4546
|
}
|
|
4207
4547
|
}
|
|
4208
4548
|
catch (e) {
|
|
@@ -4212,26 +4552,22 @@ class StableBrowser {
|
|
|
4212
4552
|
await _commandFinally(state, this);
|
|
4213
4553
|
}
|
|
4214
4554
|
}
|
|
4215
|
-
async afterStep(world, step) {
|
|
4555
|
+
async afterStep(world, step, result) {
|
|
4216
4556
|
this.stepName = null;
|
|
4217
|
-
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
4218
|
-
if (this.context.browserObject.context) {
|
|
4219
|
-
await this.context.browserObject.context.tracing.stopChunk({
|
|
4220
|
-
path: path.join(this.context.browserObject.traceFolder, `trace-${this.stepIndex}.zip`),
|
|
4221
|
-
});
|
|
4222
|
-
if (world && world.attach) {
|
|
4223
|
-
await world.attach(JSON.stringify({
|
|
4224
|
-
type: "trace",
|
|
4225
|
-
traceFilePath: `trace-${this.stepIndex}.zip`,
|
|
4226
|
-
}), "application/json+trace");
|
|
4227
|
-
}
|
|
4228
|
-
// console.log("trace file created", `trace-${this.stepIndex}.zip`);
|
|
4229
|
-
}
|
|
4230
|
-
}
|
|
4231
4557
|
if (this.context) {
|
|
4232
4558
|
this.context.examplesRow = null;
|
|
4233
4559
|
}
|
|
4234
|
-
if (
|
|
4560
|
+
if (!this.inStepReport) {
|
|
4561
|
+
// check the step result
|
|
4562
|
+
if (result && result.status === "FAILED" && world && world.attach) {
|
|
4563
|
+
await this.addCommandToReport(result.message ? result.message : "Step failed", "FAILED", `${result.message}`, { type: "text", screenshot: true }, world);
|
|
4564
|
+
}
|
|
4565
|
+
}
|
|
4566
|
+
if (world &&
|
|
4567
|
+
world.attach &&
|
|
4568
|
+
!process.env.DISABLE_SNAPSHOT &&
|
|
4569
|
+
!this.fastMode &&
|
|
4570
|
+
!this.stepTags.includes("fast-mode")) {
|
|
4235
4571
|
const snapshot = await this.getAriaSnapshot();
|
|
4236
4572
|
if (snapshot) {
|
|
4237
4573
|
const obj = {};
|
|
@@ -4239,6 +4575,11 @@ class StableBrowser {
|
|
|
4239
4575
|
}
|
|
4240
4576
|
}
|
|
4241
4577
|
this.context.routeResults = await registerAfterStepRoutes(this.context, world);
|
|
4578
|
+
if (this.context.routeResults) {
|
|
4579
|
+
if (world && world.attach) {
|
|
4580
|
+
await world.attach(JSON.stringify(this.context.routeResults), "application/json+intercept-results");
|
|
4581
|
+
}
|
|
4582
|
+
}
|
|
4242
4583
|
if (!process.env.TEMP_RUN) {
|
|
4243
4584
|
const state = {
|
|
4244
4585
|
world,
|
|
@@ -4262,10 +4603,15 @@ class StableBrowser {
|
|
|
4262
4603
|
await _commandFinally(state, this);
|
|
4263
4604
|
}
|
|
4264
4605
|
}
|
|
4265
|
-
networkAfterStep(this.stepName);
|
|
4606
|
+
networkAfterStep(this.stepName, this.context);
|
|
4266
4607
|
if (process.env.TEMP_RUN === "true") {
|
|
4267
4608
|
// Put a sleep for some time to allow the browser to finish processing
|
|
4268
|
-
|
|
4609
|
+
if (!this.stepTags.includes("fast-mode")) {
|
|
4610
|
+
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
4611
|
+
}
|
|
4612
|
+
}
|
|
4613
|
+
if (this.trace) {
|
|
4614
|
+
await this.context.playContext.tracing.groupEnd();
|
|
4269
4615
|
}
|
|
4270
4616
|
}
|
|
4271
4617
|
}
|