automation_model 1.0.806-dev → 1.0.806-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 +40 -12
- package/lib/api.js.map +1 -1
- package/lib/auto_page.d.ts +1 -1
- package/lib/auto_page.js +104 -58
- package/lib/auto_page.js.map +1 -1
- package/lib/browser_manager.d.ts +2 -7
- package/lib/browser_manager.js +116 -102
- 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 +42 -24
- package/lib/command_common.js.map +1 -1
- package/lib/constants.d.ts +4 -0
- package/lib/constants.js +2 -0
- package/lib/constants.js.map +1 -0
- 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 +137 -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 +183 -120
- package/lib/network.js.map +1 -1
- package/lib/route.d.ts +64 -2
- package/lib/route.js +496 -251
- 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 +12 -8
- package/lib/stable_browser.js +355 -96
- 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 +7 -4
- package/lib/utils.js +157 -34
- package/lib/utils.js.map +1 -1
- package/package.json +20 -12
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";
|
|
@@ -28,6 +29,7 @@ import { highlightSnapshot, snapshotValidation } from "./snapshot_validation.js"
|
|
|
28
29
|
import { loadBrunoParams } from "./bruno.js";
|
|
29
30
|
import { registerAfterStepRoutes, registerBeforeStepRoutes } from "./route.js";
|
|
30
31
|
import { existsSync } from "node:fs";
|
|
32
|
+
import _ from "lodash";
|
|
31
33
|
export const Types = {
|
|
32
34
|
CLICK: "click_element",
|
|
33
35
|
WAIT_ELEMENT: "wait_element",
|
|
@@ -46,6 +48,7 @@ export const Types = {
|
|
|
46
48
|
VERIFY_PAGE_CONTAINS_NO_TEXT: "verify_page_contains_no_text",
|
|
47
49
|
ANALYZE_TABLE: "analyze_table",
|
|
48
50
|
SELECT: "select_combobox", //
|
|
51
|
+
VERIFY_PROPERTY: "verify_element_property",
|
|
49
52
|
VERIFY_PAGE_PATH: "verify_page_path",
|
|
50
53
|
VERIFY_PAGE_TITLE: "verify_page_title",
|
|
51
54
|
TYPE_PRESS: "type_press",
|
|
@@ -64,12 +67,11 @@ export const Types = {
|
|
|
64
67
|
SET_INPUT: "set_input",
|
|
65
68
|
WAIT_FOR_TEXT_TO_DISAPPEAR: "wait_for_text_to_disappear",
|
|
66
69
|
VERIFY_ATTRIBUTE: "verify_element_attribute",
|
|
67
|
-
VERIFY_PROPERTY: "verify_element_property",
|
|
68
70
|
VERIFY_TEXT_WITH_RELATION: "verify_text_with_relation",
|
|
69
71
|
BRUNO: "bruno",
|
|
70
|
-
SNAPSHOT_VALIDATION: "snapshot_validation",
|
|
71
72
|
VERIFY_FILE_EXISTS: "verify_file_exists",
|
|
72
73
|
SET_INPUT_FILES: "set_input_files",
|
|
74
|
+
SNAPSHOT_VALIDATION: "snapshot_validation",
|
|
73
75
|
REPORT_COMMAND: "report_command",
|
|
74
76
|
STEP_COMPLETE: "step_complete",
|
|
75
77
|
SLEEP: "sleep",
|
|
@@ -86,6 +88,7 @@ class StableBrowser {
|
|
|
86
88
|
context;
|
|
87
89
|
world;
|
|
88
90
|
fastMode;
|
|
91
|
+
stepTags;
|
|
89
92
|
project_path = null;
|
|
90
93
|
webLogFile = null;
|
|
91
94
|
networkLogger = null;
|
|
@@ -94,14 +97,17 @@ class StableBrowser {
|
|
|
94
97
|
tags = null;
|
|
95
98
|
isRecording = false;
|
|
96
99
|
initSnapshotTaken = false;
|
|
97
|
-
|
|
98
|
-
|
|
100
|
+
onlyFailuresScreenshot = process.env.SCREENSHOT_ON_FAILURE_ONLY === "true";
|
|
101
|
+
// set to true if the step issue a report
|
|
102
|
+
inStepReport = false;
|
|
103
|
+
constructor(browser, page, logger = null, context = null, world = null, fastMode = false, stepTags = []) {
|
|
99
104
|
this.browser = browser;
|
|
100
105
|
this.page = page;
|
|
101
106
|
this.logger = logger;
|
|
102
107
|
this.context = context;
|
|
103
108
|
this.world = world;
|
|
104
109
|
this.fastMode = fastMode;
|
|
110
|
+
this.stepTags = stepTags;
|
|
105
111
|
if (!this.logger) {
|
|
106
112
|
this.logger = console;
|
|
107
113
|
}
|
|
@@ -134,7 +140,7 @@ class StableBrowser {
|
|
|
134
140
|
this.fastMode = true;
|
|
135
141
|
}
|
|
136
142
|
if (process.env.FAST_MODE === "true") {
|
|
137
|
-
console.log("Fast mode enabled from environment variable");
|
|
143
|
+
// console.log("Fast mode enabled from environment variable");
|
|
138
144
|
this.fastMode = true;
|
|
139
145
|
}
|
|
140
146
|
if (process.env.FAST_MODE === "false") {
|
|
@@ -177,6 +183,7 @@ class StableBrowser {
|
|
|
177
183
|
registerNetworkEvents(this.world, this, context, this.page);
|
|
178
184
|
registerDownloadEvent(this.page, this.world, context);
|
|
179
185
|
page.on("close", async () => {
|
|
186
|
+
// return if browser context is already closed
|
|
180
187
|
if (this.context && this.context.pages && this.context.pages.length > 1) {
|
|
181
188
|
this.context.pages.pop();
|
|
182
189
|
this.page = this.context.pages[this.context.pages.length - 1];
|
|
@@ -186,7 +193,12 @@ class StableBrowser {
|
|
|
186
193
|
console.log("Switched to page " + title);
|
|
187
194
|
}
|
|
188
195
|
catch (error) {
|
|
189
|
-
|
|
196
|
+
if (error?.message?.includes("Target page, context or browser has been closed")) {
|
|
197
|
+
// Ignore this error
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
console.error("Error on page close", error);
|
|
201
|
+
}
|
|
190
202
|
}
|
|
191
203
|
}
|
|
192
204
|
});
|
|
@@ -195,7 +207,12 @@ class StableBrowser {
|
|
|
195
207
|
console.log("Switch page: " + (await page.title()));
|
|
196
208
|
}
|
|
197
209
|
catch (e) {
|
|
198
|
-
|
|
210
|
+
if (e?.message?.includes("Target page, context or browser has been closed")) {
|
|
211
|
+
// Ignore this error
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
this.logger.error("error on page load " + e);
|
|
215
|
+
}
|
|
199
216
|
}
|
|
200
217
|
context.pageLoading.status = false;
|
|
201
218
|
}.bind(this));
|
|
@@ -207,7 +224,7 @@ class StableBrowser {
|
|
|
207
224
|
}
|
|
208
225
|
let newContextCreated = false;
|
|
209
226
|
if (!apps[appName]) {
|
|
210
|
-
let newContext = await getContext(null, this.context.headless ? this.context.headless : false, this, this.logger, appName, false, this, -1, this.context.reportFolder);
|
|
227
|
+
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);
|
|
211
228
|
newContextCreated = true;
|
|
212
229
|
apps[appName] = {
|
|
213
230
|
context: newContext,
|
|
@@ -223,7 +240,7 @@ class StableBrowser {
|
|
|
223
240
|
if (newContextCreated) {
|
|
224
241
|
this.registerEventListeners(this.context);
|
|
225
242
|
await this.goto(this.context.environment.baseUrl);
|
|
226
|
-
if (!this.fastMode) {
|
|
243
|
+
if (!this.fastMode && !this.stepTags.includes("fast-mode")) {
|
|
227
244
|
await this.waitForPageLoad();
|
|
228
245
|
}
|
|
229
246
|
}
|
|
@@ -315,7 +332,7 @@ class StableBrowser {
|
|
|
315
332
|
// async closeUnexpectedPopups() {
|
|
316
333
|
// await closeUnexpectedPopups(this.page);
|
|
317
334
|
// }
|
|
318
|
-
async goto(url, world = null) {
|
|
335
|
+
async goto(url, world = null, options = {}) {
|
|
319
336
|
if (!url) {
|
|
320
337
|
throw new Error("url is null, verify that the environment file is correct");
|
|
321
338
|
}
|
|
@@ -336,16 +353,23 @@ class StableBrowser {
|
|
|
336
353
|
screenshot: false,
|
|
337
354
|
highlight: false,
|
|
338
355
|
};
|
|
356
|
+
let timeout = 60000;
|
|
357
|
+
if (this.configuration && this.configuration.page_timeout) {
|
|
358
|
+
timeout = this.configuration.page_timeout;
|
|
359
|
+
}
|
|
360
|
+
if (options && options["timeout"]) {
|
|
361
|
+
timeout = options["timeout"];
|
|
362
|
+
}
|
|
339
363
|
try {
|
|
340
364
|
await _preCommand(state, this);
|
|
341
365
|
await this.page.goto(url, {
|
|
342
|
-
timeout:
|
|
366
|
+
timeout: timeout,
|
|
343
367
|
});
|
|
344
368
|
await _screenshot(state, this);
|
|
345
369
|
}
|
|
346
370
|
catch (error) {
|
|
347
371
|
console.error("Error on goto", error);
|
|
348
|
-
_commandError(state, error, this);
|
|
372
|
+
await _commandError(state, error, this);
|
|
349
373
|
}
|
|
350
374
|
finally {
|
|
351
375
|
await _commandFinally(state, this);
|
|
@@ -374,7 +398,7 @@ class StableBrowser {
|
|
|
374
398
|
}
|
|
375
399
|
catch (error) {
|
|
376
400
|
console.error("Error on goBack", error);
|
|
377
|
-
_commandError(state, error, this);
|
|
401
|
+
await _commandError(state, error, this);
|
|
378
402
|
}
|
|
379
403
|
finally {
|
|
380
404
|
await _commandFinally(state, this);
|
|
@@ -403,7 +427,7 @@ class StableBrowser {
|
|
|
403
427
|
}
|
|
404
428
|
catch (error) {
|
|
405
429
|
console.error("Error on goForward", error);
|
|
406
|
-
_commandError(state, error, this);
|
|
430
|
+
await _commandError(state, error, this);
|
|
407
431
|
}
|
|
408
432
|
finally {
|
|
409
433
|
await _commandFinally(state, this);
|
|
@@ -507,12 +531,6 @@ class StableBrowser {
|
|
|
507
531
|
if (!el.setAttribute) {
|
|
508
532
|
el = el.parentElement;
|
|
509
533
|
}
|
|
510
|
-
// remove any attributes start with data-blinq-id
|
|
511
|
-
// for (let i = 0; i < el.attributes.length; i++) {
|
|
512
|
-
// if (el.attributes[i].name.startsWith("data-blinq-id")) {
|
|
513
|
-
// el.removeAttribute(el.attributes[i].name);
|
|
514
|
-
// }
|
|
515
|
-
// }
|
|
516
534
|
el.setAttribute("data-blinq-id-" + randomToken, "");
|
|
517
535
|
return true;
|
|
518
536
|
}, [tag1, randomToken]))) {
|
|
@@ -684,8 +702,8 @@ class StableBrowser {
|
|
|
684
702
|
}
|
|
685
703
|
getFilePath() {
|
|
686
704
|
const stackFrames = errorStackParser.parse(new Error());
|
|
687
|
-
const
|
|
688
|
-
|
|
705
|
+
const mjsFrames = stackFrames.filter((frame) => frame.fileName && frame.fileName.endsWith(".mjs"));
|
|
706
|
+
const stackFrame = mjsFrames[mjsFrames.length - 2];
|
|
689
707
|
const filepath = stackFrame?.fileName;
|
|
690
708
|
if (filepath) {
|
|
691
709
|
let jsonFilePath = filepath.replace(".mjs", ".json");
|
|
@@ -807,29 +825,61 @@ class StableBrowser {
|
|
|
807
825
|
element = await this._locate_internal(selectors, info, _params, timeout, allowDisabled);
|
|
808
826
|
}
|
|
809
827
|
if (!element.rerun) {
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
828
|
+
let newElementSelector = "";
|
|
829
|
+
if (this.configuration && this.configuration.stableLocatorStrategy === "csschain") {
|
|
830
|
+
const cssSelector = await element.evaluate((el) => {
|
|
831
|
+
function getCssSelector(el) {
|
|
832
|
+
if (!el || el.nodeType !== 1 || el === document.body)
|
|
833
|
+
return el.tagName.toLowerCase();
|
|
834
|
+
const parent = el.parentElement;
|
|
835
|
+
const tag = el.tagName.toLowerCase();
|
|
836
|
+
// Find the index of the element among its siblings of the same tag
|
|
837
|
+
let index = 1;
|
|
838
|
+
for (let sibling = el.previousElementSibling; sibling; sibling = sibling.previousElementSibling) {
|
|
839
|
+
if (sibling.tagName === el.tagName) {
|
|
840
|
+
index++;
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
// Use nth-child if necessary (i.e., if there's more than one of the same tag)
|
|
844
|
+
const siblings = Array.from(parent.children).filter((child) => child.tagName === el.tagName);
|
|
845
|
+
const needsNthChild = siblings.length > 1;
|
|
846
|
+
const selector = needsNthChild ? `${tag}:nth-child(${[...parent.children].indexOf(el) + 1})` : tag;
|
|
847
|
+
return getCssSelector(parent) + " > " + selector;
|
|
848
|
+
}
|
|
849
|
+
const cssSelector = getCssSelector(el);
|
|
850
|
+
return cssSelector;
|
|
851
|
+
});
|
|
852
|
+
newElementSelector = cssSelector;
|
|
853
|
+
}
|
|
854
|
+
else {
|
|
855
|
+
const randomToken = "blinq_" + Math.random().toString(36).substring(7);
|
|
856
|
+
if (this.configuration && this.configuration.stableLocatorStrategy === "data-attribute") {
|
|
857
|
+
const dataAttribute = "data-blinq-id";
|
|
858
|
+
await element.evaluate((el, [dataAttribute, randomToken]) => {
|
|
859
|
+
el.setAttribute(dataAttribute, randomToken);
|
|
860
|
+
}, [dataAttribute, randomToken]);
|
|
861
|
+
newElementSelector = `[${dataAttribute}="${randomToken}"]`;
|
|
862
|
+
}
|
|
863
|
+
else {
|
|
864
|
+
// the default case just return the located element
|
|
865
|
+
// will not work for click and type if the locator is placeholder and the placeholder change due to the click event
|
|
866
|
+
return element;
|
|
867
|
+
}
|
|
868
|
+
}
|
|
818
869
|
const scope = element._frame ?? element.page();
|
|
819
|
-
let newElementSelector = "[data-blinq-id-" + randomToken + "]";
|
|
820
870
|
let prefixSelector = "";
|
|
821
871
|
const frameControlSelector = " >> internal:control=enter-frame";
|
|
822
872
|
const frameSelectorIndex = element._selector.lastIndexOf(frameControlSelector);
|
|
823
873
|
if (frameSelectorIndex !== -1) {
|
|
824
874
|
// remove everything after the >> internal:control=enter-frame
|
|
825
875
|
const frameSelector = element._selector.substring(0, frameSelectorIndex);
|
|
826
|
-
prefixSelector = frameSelector + " >> internal:control=enter-frame >>";
|
|
876
|
+
prefixSelector = frameSelector + " >> internal:control=enter-frame >> ";
|
|
827
877
|
}
|
|
828
878
|
// if (element?._frame?._selector) {
|
|
829
879
|
// prefixSelector = element._frame._selector + " >> " + prefixSelector;
|
|
830
880
|
// }
|
|
831
881
|
const newSelector = prefixSelector + newElementSelector;
|
|
832
|
-
return scope.locator(newSelector);
|
|
882
|
+
return scope.locator(newSelector).first();
|
|
833
883
|
}
|
|
834
884
|
}
|
|
835
885
|
throw new Error("unable to locate element " + JSON.stringify(selectors));
|
|
@@ -850,7 +900,7 @@ class StableBrowser {
|
|
|
850
900
|
for (let i = 0; i < frame.selectors.length; i++) {
|
|
851
901
|
let frameLocator = frame.selectors[i];
|
|
852
902
|
if (frameLocator.css) {
|
|
853
|
-
let testframescope = framescope.frameLocator(frameLocator.css);
|
|
903
|
+
let testframescope = framescope.frameLocator(`${frameLocator.css} >> visible=true`);
|
|
854
904
|
if (frameLocator.index) {
|
|
855
905
|
testframescope = framescope.nth(frameLocator.index);
|
|
856
906
|
}
|
|
@@ -928,6 +978,15 @@ class StableBrowser {
|
|
|
928
978
|
});
|
|
929
979
|
}
|
|
930
980
|
async _locate_internal(selectors, info, _params, timeout = 30000, allowDisabled = false) {
|
|
981
|
+
if (selectors.locators && Array.isArray(selectors.locators)) {
|
|
982
|
+
selectors.locators.forEach((locator) => {
|
|
983
|
+
locator.index = locator.index ?? 0;
|
|
984
|
+
locator.visible = locator.visible ?? true;
|
|
985
|
+
if (locator.visible && locator.css && !locator.css.endsWith(">> visible=true")) {
|
|
986
|
+
locator.css = locator.css + " >> visible=true";
|
|
987
|
+
}
|
|
988
|
+
});
|
|
989
|
+
}
|
|
931
990
|
if (!info) {
|
|
932
991
|
info = {};
|
|
933
992
|
info.failCause = {};
|
|
@@ -1083,9 +1142,13 @@ class StableBrowser {
|
|
|
1083
1142
|
}
|
|
1084
1143
|
}
|
|
1085
1144
|
if (foundLocators.length === 1) {
|
|
1145
|
+
let box = null;
|
|
1146
|
+
if (!this.onlyFailuresScreenshot) {
|
|
1147
|
+
box = await foundLocators[0].boundingBox();
|
|
1148
|
+
}
|
|
1086
1149
|
result.foundElements.push({
|
|
1087
1150
|
locator: foundLocators[0],
|
|
1088
|
-
box:
|
|
1151
|
+
box: box,
|
|
1089
1152
|
unique: true,
|
|
1090
1153
|
});
|
|
1091
1154
|
result.locatorIndex = i;
|
|
@@ -1143,7 +1206,7 @@ class StableBrowser {
|
|
|
1143
1206
|
operation: "simpleClick",
|
|
1144
1207
|
log: "***** click on " + elementDescription + " *****\n",
|
|
1145
1208
|
};
|
|
1146
|
-
_preCommand(state, this);
|
|
1209
|
+
await _preCommand(state, this);
|
|
1147
1210
|
const startTime = Date.now();
|
|
1148
1211
|
let timeout = 30000;
|
|
1149
1212
|
if (options && options.timeout) {
|
|
@@ -1192,7 +1255,7 @@ class StableBrowser {
|
|
|
1192
1255
|
operation: "simpleClickType",
|
|
1193
1256
|
log: "***** click type on " + elementDescription + " *****\n",
|
|
1194
1257
|
};
|
|
1195
|
-
_preCommand(state, this);
|
|
1258
|
+
await _preCommand(state, this);
|
|
1196
1259
|
const startTime = Date.now();
|
|
1197
1260
|
let timeout = 30000;
|
|
1198
1261
|
if (options && options.timeout) {
|
|
@@ -1240,11 +1303,22 @@ class StableBrowser {
|
|
|
1240
1303
|
operation: "click",
|
|
1241
1304
|
log: "***** click on " + selectors.element_name + " *****\n",
|
|
1242
1305
|
};
|
|
1306
|
+
check_performance("click_all ***", this.context, true);
|
|
1307
|
+
let stepFastMode = this.stepTags.includes("fast-mode");
|
|
1308
|
+
if (stepFastMode) {
|
|
1309
|
+
state.onlyFailuresScreenshot = true;
|
|
1310
|
+
state.scroll = false;
|
|
1311
|
+
state.highlight = false;
|
|
1312
|
+
}
|
|
1243
1313
|
try {
|
|
1314
|
+
check_performance("click_preCommand", this.context, true);
|
|
1244
1315
|
await _preCommand(state, this);
|
|
1316
|
+
check_performance("click_preCommand", this.context, false);
|
|
1245
1317
|
await performAction("click", state.element, options, this, state, _params);
|
|
1246
|
-
if (!this.fastMode) {
|
|
1247
|
-
|
|
1318
|
+
if (!this.fastMode && !this.stepTags.includes("fast-mode")) {
|
|
1319
|
+
check_performance("click_waitForPageLoad", this.context, true);
|
|
1320
|
+
await this.waitForPageLoad({ noSleep: true });
|
|
1321
|
+
check_performance("click_waitForPageLoad", this.context, false);
|
|
1248
1322
|
}
|
|
1249
1323
|
return state.info;
|
|
1250
1324
|
}
|
|
@@ -1252,7 +1326,13 @@ class StableBrowser {
|
|
|
1252
1326
|
await _commandError(state, e, this);
|
|
1253
1327
|
}
|
|
1254
1328
|
finally {
|
|
1329
|
+
check_performance("click_commandFinally", this.context, true);
|
|
1255
1330
|
await _commandFinally(state, this);
|
|
1331
|
+
check_performance("click_commandFinally", this.context, false);
|
|
1332
|
+
check_performance("click_all ***", this.context, false);
|
|
1333
|
+
if (this.context.profile) {
|
|
1334
|
+
console.log(JSON.stringify(this.context.profile, null, 2));
|
|
1335
|
+
}
|
|
1256
1336
|
}
|
|
1257
1337
|
}
|
|
1258
1338
|
async waitForElement(selectors, _params, options = {}, world = null) {
|
|
@@ -1344,7 +1424,7 @@ class StableBrowser {
|
|
|
1344
1424
|
}
|
|
1345
1425
|
}
|
|
1346
1426
|
}
|
|
1347
|
-
await this.waitForPageLoad();
|
|
1427
|
+
//await this.waitForPageLoad();
|
|
1348
1428
|
return state.info;
|
|
1349
1429
|
}
|
|
1350
1430
|
catch (e) {
|
|
@@ -1370,7 +1450,7 @@ class StableBrowser {
|
|
|
1370
1450
|
await _preCommand(state, this);
|
|
1371
1451
|
await performAction("hover", state.element, options, this, state, _params);
|
|
1372
1452
|
await _screenshot(state, this);
|
|
1373
|
-
await this.waitForPageLoad();
|
|
1453
|
+
//await this.waitForPageLoad();
|
|
1374
1454
|
return state.info;
|
|
1375
1455
|
}
|
|
1376
1456
|
catch (e) {
|
|
@@ -1381,9 +1461,10 @@ class StableBrowser {
|
|
|
1381
1461
|
}
|
|
1382
1462
|
}
|
|
1383
1463
|
async selectOption(selectors, values, _params = null, options = {}, world = null) {
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1464
|
+
// commented out this condition as some select elements have NULL values by default.
|
|
1465
|
+
// if (!values) {
|
|
1466
|
+
// throw new Error("values is null");
|
|
1467
|
+
// }
|
|
1387
1468
|
const state = {
|
|
1388
1469
|
selectors,
|
|
1389
1470
|
_params,
|
|
@@ -1406,7 +1487,7 @@ class StableBrowser {
|
|
|
1406
1487
|
state.info.log += "selectOption failed, will try force" + "\n";
|
|
1407
1488
|
await state.element.selectOption(values, { timeout: 10000, force: true });
|
|
1408
1489
|
}
|
|
1409
|
-
await this.waitForPageLoad();
|
|
1490
|
+
//await this.waitForPageLoad();
|
|
1410
1491
|
return state.info;
|
|
1411
1492
|
}
|
|
1412
1493
|
catch (e) {
|
|
@@ -1510,7 +1591,7 @@ class StableBrowser {
|
|
|
1510
1591
|
_text: `Set date time value: ${value} on ${selectors.element_name}`,
|
|
1511
1592
|
operation: "setDateTime",
|
|
1512
1593
|
log: "***** set date time value " + selectors.element_name + " *****\n",
|
|
1513
|
-
throwError: false,
|
|
1594
|
+
// throwError: false,
|
|
1514
1595
|
};
|
|
1515
1596
|
try {
|
|
1516
1597
|
await _preCommand(state, this);
|
|
@@ -1592,6 +1673,14 @@ class StableBrowser {
|
|
|
1592
1673
|
}
|
|
1593
1674
|
try {
|
|
1594
1675
|
await _preCommand(state, this);
|
|
1676
|
+
const randomToken = "blinq_" + Math.random().toString(36).substring(7);
|
|
1677
|
+
// tag the element
|
|
1678
|
+
let newElementSelector = await state.element.evaluate((el, token) => {
|
|
1679
|
+
// use attribute and not id
|
|
1680
|
+
const attrName = `data-blinq-id-${token}`;
|
|
1681
|
+
el.setAttribute(attrName, "");
|
|
1682
|
+
return `[${attrName}]`;
|
|
1683
|
+
}, randomToken);
|
|
1595
1684
|
state.info.value = _value;
|
|
1596
1685
|
if (!options.press) {
|
|
1597
1686
|
try {
|
|
@@ -1617,6 +1706,25 @@ class StableBrowser {
|
|
|
1617
1706
|
}
|
|
1618
1707
|
}
|
|
1619
1708
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1709
|
+
// check if the element exist after the click (no wait)
|
|
1710
|
+
const count = await state.element.count({ timeout: 0 });
|
|
1711
|
+
if (count === 0) {
|
|
1712
|
+
// the locator changed after the click (placeholder) we need to locate the element using the data-blinq-id
|
|
1713
|
+
const scope = state.element._frame ?? element.page();
|
|
1714
|
+
let prefixSelector = "";
|
|
1715
|
+
const frameControlSelector = " >> internal:control=enter-frame";
|
|
1716
|
+
const frameSelectorIndex = state.element._selector.lastIndexOf(frameControlSelector);
|
|
1717
|
+
if (frameSelectorIndex !== -1) {
|
|
1718
|
+
// remove everything after the >> internal:control=enter-frame
|
|
1719
|
+
const frameSelector = state.element._selector.substring(0, frameSelectorIndex);
|
|
1720
|
+
prefixSelector = frameSelector + " >> internal:control=enter-frame >> ";
|
|
1721
|
+
}
|
|
1722
|
+
// if (element?._frame?._selector) {
|
|
1723
|
+
// prefixSelector = element._frame._selector + " >> " + prefixSelector;
|
|
1724
|
+
// }
|
|
1725
|
+
const newSelector = prefixSelector + newElementSelector;
|
|
1726
|
+
state.element = scope.locator(newSelector).first();
|
|
1727
|
+
}
|
|
1620
1728
|
const valueSegment = state.value.split("&&");
|
|
1621
1729
|
for (let i = 0; i < valueSegment.length; i++) {
|
|
1622
1730
|
if (i > 0) {
|
|
@@ -1688,8 +1796,8 @@ class StableBrowser {
|
|
|
1688
1796
|
if (enter) {
|
|
1689
1797
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1690
1798
|
await this.page.keyboard.press("Enter");
|
|
1799
|
+
await this.waitForPageLoad();
|
|
1691
1800
|
}
|
|
1692
|
-
await this.waitForPageLoad();
|
|
1693
1801
|
return state.info;
|
|
1694
1802
|
}
|
|
1695
1803
|
catch (e) {
|
|
@@ -1948,11 +2056,12 @@ class StableBrowser {
|
|
|
1948
2056
|
throw new Error("referanceSnapshot is null");
|
|
1949
2057
|
}
|
|
1950
2058
|
let text = null;
|
|
1951
|
-
|
|
1952
|
-
|
|
2059
|
+
const snapshotsFolder = process.env.BVT_TEMP_SNAPSHOTS_FOLDER ?? this.context.snapshotFolder; //path .join(this.project_path, "data", "snapshots");
|
|
2060
|
+
if (fs.existsSync(path.join(snapshotsFolder, referanceSnapshot + ".yml"))) {
|
|
2061
|
+
text = fs.readFileSync(path.join(snapshotsFolder, referanceSnapshot + ".yml"), "utf8");
|
|
1953
2062
|
}
|
|
1954
|
-
else if (fs.existsSync(path.join(
|
|
1955
|
-
text = fs.readFileSync(path.join(
|
|
2063
|
+
else if (fs.existsSync(path.join(snapshotsFolder, referanceSnapshot + ".yaml"))) {
|
|
2064
|
+
text = fs.readFileSync(path.join(snapshotsFolder, referanceSnapshot + ".yaml"), "utf8");
|
|
1956
2065
|
}
|
|
1957
2066
|
else if (referanceSnapshot.startsWith("yaml:")) {
|
|
1958
2067
|
text = referanceSnapshot.substring(5);
|
|
@@ -1976,7 +2085,13 @@ class StableBrowser {
|
|
|
1976
2085
|
scope = await this._findFrameScope(frameSelectors, timeout, state.info);
|
|
1977
2086
|
}
|
|
1978
2087
|
const snapshot = await scope.locator("body").ariaSnapshot({ timeout });
|
|
2088
|
+
if (snapshot && snapshot.length <= 10) {
|
|
2089
|
+
console.log("Page snapshot length is suspiciously small:", snapshot);
|
|
2090
|
+
}
|
|
1979
2091
|
matchResult = snapshotValidation(snapshot, newValue, referanceSnapshot);
|
|
2092
|
+
if (matchResult === undefined) {
|
|
2093
|
+
console.log("snapshotValidation returned undefined");
|
|
2094
|
+
}
|
|
1980
2095
|
if (matchResult.errorLine !== -1) {
|
|
1981
2096
|
throw new Error("Snapshot validation failed at line " + matchResult.errorLineText);
|
|
1982
2097
|
}
|
|
@@ -2147,6 +2262,9 @@ class StableBrowser {
|
|
|
2147
2262
|
return _getTestData(world, this.context, this);
|
|
2148
2263
|
}
|
|
2149
2264
|
async _screenShot(options = {}, world = null, info = null) {
|
|
2265
|
+
if (!options) {
|
|
2266
|
+
options = {};
|
|
2267
|
+
}
|
|
2150
2268
|
// collect url/path/title
|
|
2151
2269
|
if (info) {
|
|
2152
2270
|
if (!info.title) {
|
|
@@ -2175,7 +2293,7 @@ class StableBrowser {
|
|
|
2175
2293
|
const uuidStr = "id_" + randomUUID();
|
|
2176
2294
|
const screenshotPath = path.join(world.screenshotPath, uuidStr + ".png");
|
|
2177
2295
|
try {
|
|
2178
|
-
await this.takeScreenshot(screenshotPath);
|
|
2296
|
+
await this.takeScreenshot(screenshotPath, options.fullPage === true);
|
|
2179
2297
|
// let buffer = await this.page.screenshot({ timeout: 4000 });
|
|
2180
2298
|
// // save the buffer to the screenshot path asynchrously
|
|
2181
2299
|
// fs.writeFile(screenshotPath, buffer, (err) => {
|
|
@@ -2196,7 +2314,7 @@ class StableBrowser {
|
|
|
2196
2314
|
else if (options && options.screenshot) {
|
|
2197
2315
|
result.screenshotPath = options.screenshotPath;
|
|
2198
2316
|
try {
|
|
2199
|
-
await this.takeScreenshot(options.screenshotPath);
|
|
2317
|
+
await this.takeScreenshot(options.screenshotPath, options.fullPage === true);
|
|
2200
2318
|
// let buffer = await this.page.screenshot({ timeout: 4000 });
|
|
2201
2319
|
// // save the buffer to the screenshot path asynchrously
|
|
2202
2320
|
// fs.writeFile(options.screenshotPath, buffer, (err) => {
|
|
@@ -2214,7 +2332,7 @@ class StableBrowser {
|
|
|
2214
2332
|
}
|
|
2215
2333
|
return result;
|
|
2216
2334
|
}
|
|
2217
|
-
async takeScreenshot(screenshotPath) {
|
|
2335
|
+
async takeScreenshot(screenshotPath, fullPage = false) {
|
|
2218
2336
|
const playContext = this.context.playContext;
|
|
2219
2337
|
// Using CDP to capture the screenshot
|
|
2220
2338
|
const viewportWidth = Math.max(...(await this.page.evaluate(() => [
|
|
@@ -2239,13 +2357,7 @@ class StableBrowser {
|
|
|
2239
2357
|
const client = await playContext.newCDPSession(this.page);
|
|
2240
2358
|
const { data } = await client.send("Page.captureScreenshot", {
|
|
2241
2359
|
format: "png",
|
|
2242
|
-
|
|
2243
|
-
// x: 0,
|
|
2244
|
-
// y: 0,
|
|
2245
|
-
// width: viewportWidth,
|
|
2246
|
-
// height: viewportHeight,
|
|
2247
|
-
// scale: 1,
|
|
2248
|
-
// },
|
|
2360
|
+
captureBeyondViewport: fullPage,
|
|
2249
2361
|
});
|
|
2250
2362
|
await client.detach();
|
|
2251
2363
|
if (!screenshotPath) {
|
|
@@ -2254,7 +2366,7 @@ class StableBrowser {
|
|
|
2254
2366
|
screenshotBuffer = Buffer.from(data, "base64");
|
|
2255
2367
|
}
|
|
2256
2368
|
else {
|
|
2257
|
-
screenshotBuffer = await this.page.screenshot();
|
|
2369
|
+
screenshotBuffer = await this.page.screenshot({ fullPage: fullPage });
|
|
2258
2370
|
}
|
|
2259
2371
|
// if (focusedElement) {
|
|
2260
2372
|
// // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
|
|
@@ -2354,6 +2466,12 @@ class StableBrowser {
|
|
|
2354
2466
|
state.info.value = state.value;
|
|
2355
2467
|
this.setTestData({ [variable]: state.value }, world);
|
|
2356
2468
|
this.logger.info("set test data: " + variable + "=" + state.value);
|
|
2469
|
+
if (process.env.MODE === "executions") {
|
|
2470
|
+
const globalDataFile = "global_test_data.json";
|
|
2471
|
+
if (existsSync(globalDataFile)) {
|
|
2472
|
+
this.saveTestDataAsGlobal({}, world);
|
|
2473
|
+
}
|
|
2474
|
+
}
|
|
2357
2475
|
// await new Promise((resolve) => setTimeout(resolve, 500));
|
|
2358
2476
|
return state.info;
|
|
2359
2477
|
}
|
|
@@ -2425,6 +2543,12 @@ class StableBrowser {
|
|
|
2425
2543
|
state.info.value = state.value;
|
|
2426
2544
|
this.setTestData({ [variable]: state.value }, world);
|
|
2427
2545
|
this.logger.info("set test data: " + variable + "=" + state.value);
|
|
2546
|
+
if (process.env.MODE === "executions") {
|
|
2547
|
+
const globalDataFile = "global_test_data.json";
|
|
2548
|
+
if (existsSync(globalDataFile)) {
|
|
2549
|
+
this.saveTestDataAsGlobal({}, world);
|
|
2550
|
+
}
|
|
2551
|
+
}
|
|
2428
2552
|
// await new Promise((resolve) => setTimeout(resolve, 500));
|
|
2429
2553
|
return state.info;
|
|
2430
2554
|
}
|
|
@@ -2555,7 +2679,7 @@ class StableBrowser {
|
|
|
2555
2679
|
let expectedValue;
|
|
2556
2680
|
try {
|
|
2557
2681
|
await _preCommand(state, this);
|
|
2558
|
-
expectedValue = await
|
|
2682
|
+
expectedValue = await this._replaceWithLocalData(value, world);
|
|
2559
2683
|
state.info.expectedValue = expectedValue;
|
|
2560
2684
|
switch (property) {
|
|
2561
2685
|
case "innerText":
|
|
@@ -2681,6 +2805,7 @@ class StableBrowser {
|
|
|
2681
2805
|
allowDisabled: true,
|
|
2682
2806
|
info: {},
|
|
2683
2807
|
};
|
|
2808
|
+
state.options ??= { timeout: timeoutMs };
|
|
2684
2809
|
// Initialize startTime outside try block to ensure it's always accessible
|
|
2685
2810
|
const startTime = Date.now();
|
|
2686
2811
|
let conditionMet = false;
|
|
@@ -2850,6 +2975,12 @@ class StableBrowser {
|
|
|
2850
2975
|
emailUrl = url;
|
|
2851
2976
|
codeOrUrlFound = true;
|
|
2852
2977
|
}
|
|
2978
|
+
if (process.env.MODE === "executions") {
|
|
2979
|
+
const globalDataFile = "global_test_data.json";
|
|
2980
|
+
if (existsSync(globalDataFile)) {
|
|
2981
|
+
this.saveTestDataAsGlobal({}, world);
|
|
2982
|
+
}
|
|
2983
|
+
}
|
|
2853
2984
|
if (codeOrUrlFound) {
|
|
2854
2985
|
return { emailUrl, emailCode };
|
|
2855
2986
|
}
|
|
@@ -3256,7 +3387,16 @@ class StableBrowser {
|
|
|
3256
3387
|
text = text.replace(/\\"/g, '"');
|
|
3257
3388
|
}
|
|
3258
3389
|
const timeout = this._getFindElementTimeout(options);
|
|
3259
|
-
|
|
3390
|
+
//if (!this.fastMode && !this.stepTags.includes("fast-mode")) {
|
|
3391
|
+
let stepFastMode = this.stepTags.includes("fast-mode");
|
|
3392
|
+
if (!stepFastMode) {
|
|
3393
|
+
if (!this.fastMode) {
|
|
3394
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
3395
|
+
}
|
|
3396
|
+
else {
|
|
3397
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
3398
|
+
}
|
|
3399
|
+
}
|
|
3260
3400
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
3261
3401
|
if (newValue !== text) {
|
|
3262
3402
|
this.logger.info(text + "=" + newValue);
|
|
@@ -3264,6 +3404,11 @@ class StableBrowser {
|
|
|
3264
3404
|
}
|
|
3265
3405
|
let dateAlternatives = findDateAlternatives(text);
|
|
3266
3406
|
let numberAlternatives = findNumberAlternatives(text);
|
|
3407
|
+
if (stepFastMode) {
|
|
3408
|
+
state.onlyFailuresScreenshot = true;
|
|
3409
|
+
state.scroll = false;
|
|
3410
|
+
state.highlight = false;
|
|
3411
|
+
}
|
|
3267
3412
|
try {
|
|
3268
3413
|
await _preCommand(state, this);
|
|
3269
3414
|
state.info.text = text;
|
|
@@ -3383,6 +3528,8 @@ class StableBrowser {
|
|
|
3383
3528
|
operation: "verify_text_with_relation",
|
|
3384
3529
|
log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
|
|
3385
3530
|
};
|
|
3531
|
+
const cmdStartTime = Date.now();
|
|
3532
|
+
let cmdEndTime = null;
|
|
3386
3533
|
const timeout = this._getFindElementTimeout(options);
|
|
3387
3534
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
3388
3535
|
let newValue = await this._replaceWithLocalData(textAnchor, world);
|
|
@@ -3418,6 +3565,17 @@ class StableBrowser {
|
|
|
3418
3565
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
3419
3566
|
continue;
|
|
3420
3567
|
}
|
|
3568
|
+
else {
|
|
3569
|
+
cmdEndTime = Date.now();
|
|
3570
|
+
if (cmdEndTime - cmdStartTime > 55000) {
|
|
3571
|
+
if (foundAncore) {
|
|
3572
|
+
throw new Error(`Text ${textToVerify} not found in page`);
|
|
3573
|
+
}
|
|
3574
|
+
else {
|
|
3575
|
+
throw new Error(`Text ${textAnchor} not found in page`);
|
|
3576
|
+
}
|
|
3577
|
+
}
|
|
3578
|
+
}
|
|
3421
3579
|
try {
|
|
3422
3580
|
for (let i = 0; i < resultWithElementsFound.length; i++) {
|
|
3423
3581
|
foundAncore = true;
|
|
@@ -3430,7 +3588,9 @@ class StableBrowser {
|
|
|
3430
3588
|
climbArray1.push("..");
|
|
3431
3589
|
}
|
|
3432
3590
|
let climbXpath = "xpath=" + climbArray1.join("/");
|
|
3433
|
-
|
|
3591
|
+
if (Number(climb) > 0) {
|
|
3592
|
+
css = css + " >> " + climbXpath;
|
|
3593
|
+
}
|
|
3434
3594
|
const count = await frame.locator(css).count();
|
|
3435
3595
|
for (let j = 0; j < count; j++) {
|
|
3436
3596
|
const continer = await frame.locator(css).nth(j);
|
|
@@ -3556,7 +3716,7 @@ class StableBrowser {
|
|
|
3556
3716
|
Object.assign(e, { info: info });
|
|
3557
3717
|
error = e;
|
|
3558
3718
|
// throw e;
|
|
3559
|
-
await _commandError({ text: "visualVerification", operation: "visualVerification",
|
|
3719
|
+
await _commandError({ text: "visualVerification", operation: "visualVerification", info }, e, this);
|
|
3560
3720
|
}
|
|
3561
3721
|
finally {
|
|
3562
3722
|
const endTime = Date.now();
|
|
@@ -3901,10 +4061,26 @@ class StableBrowser {
|
|
|
3901
4061
|
registerNetworkEvents(this.world, this, this.context, this.page);
|
|
3902
4062
|
registerDownloadEvent(this.page, this.world, this.context);
|
|
3903
4063
|
if (this.onRestoreSaveState) {
|
|
3904
|
-
this.onRestoreSaveState(path);
|
|
4064
|
+
await this.onRestoreSaveState(path);
|
|
3905
4065
|
}
|
|
3906
4066
|
}
|
|
3907
4067
|
async waitForPageLoad(options = {}, world = null) {
|
|
4068
|
+
// try {
|
|
4069
|
+
// let currentPagePath = null;
|
|
4070
|
+
// currentPagePath = new URL(this.page.url()).pathname;
|
|
4071
|
+
// if (this.latestPagePath) {
|
|
4072
|
+
// // get the currect page path and compare with the latest page path
|
|
4073
|
+
// if (this.latestPagePath === currentPagePath) {
|
|
4074
|
+
// // if the page path is the same, do not wait for page load
|
|
4075
|
+
// console.log("No page change: " + currentPagePath);
|
|
4076
|
+
// return;
|
|
4077
|
+
// }
|
|
4078
|
+
// }
|
|
4079
|
+
// this.latestPagePath = currentPagePath;
|
|
4080
|
+
// } catch (e) {
|
|
4081
|
+
// console.debug("Error getting current page path: ", e);
|
|
4082
|
+
// }
|
|
4083
|
+
//console.log("Waiting for page load");
|
|
3908
4084
|
let timeout = this._getLoadTimeout(options);
|
|
3909
4085
|
const promiseArray = [];
|
|
3910
4086
|
// let waitForNetworkIdle = true;
|
|
@@ -3939,7 +4115,10 @@ class StableBrowser {
|
|
|
3939
4115
|
}
|
|
3940
4116
|
}
|
|
3941
4117
|
finally {
|
|
3942
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
4118
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
4119
|
+
if (options && !options.noSleep) {
|
|
4120
|
+
await new Promise((resolve) => setTimeout(resolve, 1500));
|
|
4121
|
+
}
|
|
3943
4122
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world));
|
|
3944
4123
|
const endTime = Date.now();
|
|
3945
4124
|
_reportToWorld(world, {
|
|
@@ -3973,7 +4152,7 @@ class StableBrowser {
|
|
|
3973
4152
|
_text: `Close the page`,
|
|
3974
4153
|
operation: "closePage",
|
|
3975
4154
|
log: "***** close page *****\n",
|
|
3976
|
-
throwError: false,
|
|
4155
|
+
// throwError: false,
|
|
3977
4156
|
};
|
|
3978
4157
|
try {
|
|
3979
4158
|
await _preCommand(state, this);
|
|
@@ -3993,7 +4172,7 @@ class StableBrowser {
|
|
|
3993
4172
|
}
|
|
3994
4173
|
operation = options.operation;
|
|
3995
4174
|
// validate operation is one of the supported operations
|
|
3996
|
-
if (operation != "click" && operation != "hover+click") {
|
|
4175
|
+
if (operation != "click" && operation != "hover+click" && operation != "hover") {
|
|
3997
4176
|
throw new Error("operation is not supported");
|
|
3998
4177
|
}
|
|
3999
4178
|
const state = {
|
|
@@ -4062,6 +4241,17 @@ class StableBrowser {
|
|
|
4062
4241
|
state.element = results[0];
|
|
4063
4242
|
await performAction("hover+click", state.element, options, this, state, _params);
|
|
4064
4243
|
break;
|
|
4244
|
+
case "hover":
|
|
4245
|
+
if (!options.css) {
|
|
4246
|
+
throw new Error("css is not defined");
|
|
4247
|
+
}
|
|
4248
|
+
const result1 = await findElementsInArea(options.css, cellArea, this, options);
|
|
4249
|
+
if (result1.length === 0) {
|
|
4250
|
+
throw new Error(`Element not found in cell area`);
|
|
4251
|
+
}
|
|
4252
|
+
state.element = result1[0];
|
|
4253
|
+
await performAction("hover", state.element, options, this, state, _params);
|
|
4254
|
+
break;
|
|
4065
4255
|
default:
|
|
4066
4256
|
throw new Error("operation is not supported");
|
|
4067
4257
|
}
|
|
@@ -4075,6 +4265,16 @@ class StableBrowser {
|
|
|
4075
4265
|
}
|
|
4076
4266
|
saveTestDataAsGlobal(options, world) {
|
|
4077
4267
|
const dataFile = _getDataFile(world, this.context, this);
|
|
4268
|
+
if (process.env.MODE === "executions") {
|
|
4269
|
+
const globalDataFile = path.join(this.project_path, "global_test_data.json");
|
|
4270
|
+
const dataFileContents = fs.existsSync(dataFile) ? JSON.parse(fs.readFileSync(dataFile)) : {};
|
|
4271
|
+
const globalDataFileContents = fs.existsSync(globalDataFile) ? JSON.parse(fs.readFileSync(globalDataFile)) : {};
|
|
4272
|
+
const mergedData = JSON.stringify(_.merge({}, dataFileContents, globalDataFileContents), null, 2);
|
|
4273
|
+
fs.writeFileSync(dataFile, mergedData);
|
|
4274
|
+
fs.writeFileSync(globalDataFile, mergedData);
|
|
4275
|
+
this.logger.info("Save the scenario test data to " + dataFile + " as global for the following scenarios.");
|
|
4276
|
+
return;
|
|
4277
|
+
}
|
|
4078
4278
|
process.env.GLOBAL_TEST_DATA_FILE = dataFile;
|
|
4079
4279
|
this.logger.info("Save the scenario test data as global for the following scenarios.");
|
|
4080
4280
|
}
|
|
@@ -4177,6 +4377,7 @@ class StableBrowser {
|
|
|
4177
4377
|
if (world && world.attach) {
|
|
4178
4378
|
world.attach(this.context.reportFolder, { mediaType: "text/plain" });
|
|
4179
4379
|
}
|
|
4380
|
+
this.context.loadedRoutes = null;
|
|
4180
4381
|
this.beforeScenarioCalled = true;
|
|
4181
4382
|
if (scenario && scenario.pickle && scenario.pickle.name) {
|
|
4182
4383
|
this.scenarioName = scenario.pickle.name;
|
|
@@ -4190,9 +4391,12 @@ class StableBrowser {
|
|
|
4190
4391
|
if (this.tags === null && scenario && scenario.pickle && scenario.pickle.tags) {
|
|
4191
4392
|
this.tags = scenario.pickle.tags.map((tag) => tag.name);
|
|
4192
4393
|
// check if @global_test_data tag is present
|
|
4193
|
-
if (this.tags.includes("@global_test_data")) {
|
|
4394
|
+
if (this.tags.includes("@global_test_data" /* TAG_CONSTANTS.GLOBAL_TEST_DATA */)) {
|
|
4194
4395
|
this.saveTestDataAsGlobal({}, world);
|
|
4195
4396
|
}
|
|
4397
|
+
if (this.tags.includes("@fast_mode" /* TAG_CONSTANTS.FAST_MODE */)) {
|
|
4398
|
+
this.fastMode = true;
|
|
4399
|
+
}
|
|
4196
4400
|
}
|
|
4197
4401
|
// update test data based on feature/scenario
|
|
4198
4402
|
let envName = null;
|
|
@@ -4203,14 +4407,51 @@ class StableBrowser {
|
|
|
4203
4407
|
await getTestData(envName, world, undefined, this.featureName, this.scenarioName, this.context);
|
|
4204
4408
|
}
|
|
4205
4409
|
await loadBrunoParams(this.context, this.context.environment.name);
|
|
4410
|
+
if ((process.env.TRACE === "true" || this.configuration.trace === true) && this.context) {
|
|
4411
|
+
this.trace = true;
|
|
4412
|
+
const traceFolder = path.join(this.context.reportFolder, "trace");
|
|
4413
|
+
if (!fs.existsSync(traceFolder)) {
|
|
4414
|
+
fs.mkdirSync(traceFolder, { recursive: true });
|
|
4415
|
+
}
|
|
4416
|
+
this.traceFolder = traceFolder;
|
|
4417
|
+
await this.context.playContext.tracing.start({ screenshots: true, snapshots: true });
|
|
4418
|
+
}
|
|
4419
|
+
}
|
|
4420
|
+
async afterScenario(world, scenario) {
|
|
4421
|
+
const id = scenario.testCaseStartedId;
|
|
4422
|
+
if (this.trace) {
|
|
4423
|
+
await this.context.playContext.tracing.stop({
|
|
4424
|
+
path: path.join(this.traceFolder, `trace-${id}.zip`),
|
|
4425
|
+
});
|
|
4426
|
+
}
|
|
4427
|
+
}
|
|
4428
|
+
getGherkinKeyword(step) {
|
|
4429
|
+
if (!step?.type) {
|
|
4430
|
+
return "";
|
|
4431
|
+
}
|
|
4432
|
+
switch (step.type) {
|
|
4433
|
+
case "Context":
|
|
4434
|
+
return "Given";
|
|
4435
|
+
case "Action":
|
|
4436
|
+
return "When";
|
|
4437
|
+
case "Outcome":
|
|
4438
|
+
return "Then";
|
|
4439
|
+
case "Conjunction":
|
|
4440
|
+
return "And";
|
|
4441
|
+
default:
|
|
4442
|
+
return "";
|
|
4443
|
+
}
|
|
4206
4444
|
}
|
|
4207
|
-
async afterScenario(world, scenario) { }
|
|
4208
4445
|
async beforeStep(world, step) {
|
|
4209
|
-
if (this.
|
|
4210
|
-
|
|
4446
|
+
if (step?.pickleStep && this.trace) {
|
|
4447
|
+
const keyword = this.getGherkinKeyword(step.pickleStep);
|
|
4448
|
+
this.traceGroupName = `${keyword} ${step.pickleStep.text}`;
|
|
4449
|
+
await this.context.playContext.tracing.group(this.traceGroupName);
|
|
4211
4450
|
}
|
|
4451
|
+
this.stepTags = [];
|
|
4212
4452
|
if (!this.beforeScenarioCalled) {
|
|
4213
4453
|
this.beforeScenario(world, step);
|
|
4454
|
+
this.context.loadedRoutes = null;
|
|
4214
4455
|
}
|
|
4215
4456
|
if (this.stepIndex === undefined) {
|
|
4216
4457
|
this.stepIndex = 0;
|
|
@@ -4220,7 +4461,12 @@ class StableBrowser {
|
|
|
4220
4461
|
}
|
|
4221
4462
|
if (step && step.pickleStep && step.pickleStep.text) {
|
|
4222
4463
|
this.stepName = step.pickleStep.text;
|
|
4223
|
-
|
|
4464
|
+
let printableStepName = this.stepName;
|
|
4465
|
+
// take the printableStepName and replace quated value with \x1b[33m and \x1b[0m
|
|
4466
|
+
printableStepName = printableStepName.replace(/"([^"]*)"/g, (match, p1) => {
|
|
4467
|
+
return `\x1b[33m"${p1}"\x1b[0m`;
|
|
4468
|
+
});
|
|
4469
|
+
this.logger.info("\x1b[38;5;208mstep:\x1b[0m " + printableStepName);
|
|
4224
4470
|
}
|
|
4225
4471
|
else if (step && step.text) {
|
|
4226
4472
|
this.stepName = step.text;
|
|
@@ -4235,7 +4481,10 @@ class StableBrowser {
|
|
|
4235
4481
|
}
|
|
4236
4482
|
if (this.initSnapshotTaken === false) {
|
|
4237
4483
|
this.initSnapshotTaken = true;
|
|
4238
|
-
if (world &&
|
|
4484
|
+
if (world &&
|
|
4485
|
+
world.attach &&
|
|
4486
|
+
!process.env.DISABLE_SNAPSHOT &&
|
|
4487
|
+
(!this.fastMode || this.stepTags.includes("fast-mode"))) {
|
|
4239
4488
|
const snapshot = await this.getAriaSnapshot();
|
|
4240
4489
|
if (snapshot) {
|
|
4241
4490
|
await world.attach(JSON.stringify(snapshot), "application/json+snapshot-before");
|
|
@@ -4245,7 +4494,11 @@ class StableBrowser {
|
|
|
4245
4494
|
this.context.routeResults = null;
|
|
4246
4495
|
this.context.loadedRoutes = null;
|
|
4247
4496
|
await registerBeforeStepRoutes(this.context, this.stepName, world);
|
|
4248
|
-
networkBeforeStep(this.stepName);
|
|
4497
|
+
networkBeforeStep(this.stepName, this.context);
|
|
4498
|
+
this.inStepReport = false;
|
|
4499
|
+
}
|
|
4500
|
+
setStepTags(tags) {
|
|
4501
|
+
this.stepTags = tags;
|
|
4249
4502
|
}
|
|
4250
4503
|
async getAriaSnapshot() {
|
|
4251
4504
|
try {
|
|
@@ -4319,7 +4572,7 @@ class StableBrowser {
|
|
|
4319
4572
|
state.payload = payload;
|
|
4320
4573
|
if (commandStatus === "FAILED") {
|
|
4321
4574
|
state.throwError = true;
|
|
4322
|
-
throw new Error(
|
|
4575
|
+
throw new Error(commandText);
|
|
4323
4576
|
}
|
|
4324
4577
|
}
|
|
4325
4578
|
catch (e) {
|
|
@@ -4329,26 +4582,22 @@ class StableBrowser {
|
|
|
4329
4582
|
await _commandFinally(state, this);
|
|
4330
4583
|
}
|
|
4331
4584
|
}
|
|
4332
|
-
async afterStep(world, step) {
|
|
4585
|
+
async afterStep(world, step, result) {
|
|
4333
4586
|
this.stepName = null;
|
|
4334
|
-
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
4335
|
-
if (this.context.browserObject.context) {
|
|
4336
|
-
await this.context.browserObject.context.tracing.stopChunk({
|
|
4337
|
-
path: path.join(this.context.browserObject.traceFolder, `trace-${this.stepIndex}.zip`),
|
|
4338
|
-
});
|
|
4339
|
-
if (world && world.attach) {
|
|
4340
|
-
await world.attach(JSON.stringify({
|
|
4341
|
-
type: "trace",
|
|
4342
|
-
traceFilePath: `trace-${this.stepIndex}.zip`,
|
|
4343
|
-
}), "application/json+trace");
|
|
4344
|
-
}
|
|
4345
|
-
// console.log("trace file created", `trace-${this.stepIndex}.zip`);
|
|
4346
|
-
}
|
|
4347
|
-
}
|
|
4348
4587
|
if (this.context) {
|
|
4349
4588
|
this.context.examplesRow = null;
|
|
4350
4589
|
}
|
|
4351
|
-
if (
|
|
4590
|
+
if (!this.inStepReport) {
|
|
4591
|
+
// check the step result
|
|
4592
|
+
if (result && result.status === "FAILED" && world && world.attach) {
|
|
4593
|
+
await this.addCommandToReport(result.message ? result.message : "Step failed", "FAILED", `${result.message}`, { type: "text", screenshot: true }, world);
|
|
4594
|
+
}
|
|
4595
|
+
}
|
|
4596
|
+
if (world &&
|
|
4597
|
+
world.attach &&
|
|
4598
|
+
!process.env.DISABLE_SNAPSHOT &&
|
|
4599
|
+
!this.fastMode &&
|
|
4600
|
+
!this.stepTags.includes("fast-mode")) {
|
|
4352
4601
|
const snapshot = await this.getAriaSnapshot();
|
|
4353
4602
|
if (snapshot) {
|
|
4354
4603
|
const obj = {};
|
|
@@ -4356,6 +4605,11 @@ class StableBrowser {
|
|
|
4356
4605
|
}
|
|
4357
4606
|
}
|
|
4358
4607
|
this.context.routeResults = await registerAfterStepRoutes(this.context, world);
|
|
4608
|
+
if (this.context.routeResults) {
|
|
4609
|
+
if (world && world.attach) {
|
|
4610
|
+
await world.attach(JSON.stringify(this.context.routeResults), "application/json+intercept-results");
|
|
4611
|
+
}
|
|
4612
|
+
}
|
|
4359
4613
|
if (!process.env.TEMP_RUN) {
|
|
4360
4614
|
const state = {
|
|
4361
4615
|
world,
|
|
@@ -4379,10 +4633,15 @@ class StableBrowser {
|
|
|
4379
4633
|
await _commandFinally(state, this);
|
|
4380
4634
|
}
|
|
4381
4635
|
}
|
|
4382
|
-
networkAfterStep(this.stepName);
|
|
4636
|
+
networkAfterStep(this.stepName, this.context);
|
|
4383
4637
|
if (process.env.TEMP_RUN === "true") {
|
|
4384
4638
|
// Put a sleep for some time to allow the browser to finish processing
|
|
4385
|
-
|
|
4639
|
+
if (!this.stepTags.includes("fast-mode")) {
|
|
4640
|
+
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
4641
|
+
}
|
|
4642
|
+
}
|
|
4643
|
+
if (this.trace) {
|
|
4644
|
+
await this.context.playContext.tracing.groupEnd();
|
|
4386
4645
|
}
|
|
4387
4646
|
}
|
|
4388
4647
|
}
|