automation_model 1.0.805-dev → 1.0.805-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");
|
|
@@ -788,6 +806,7 @@ class StableBrowser {
|
|
|
788
806
|
info.log += "using strategy " + key + " with locators " + JSON.stringify(strategyLocators) + "\n";
|
|
789
807
|
selectors.locators = strategyLocators;
|
|
790
808
|
element = await this._locate_internal(selectors, info, _params, 10_000, allowDisabled);
|
|
809
|
+
err = null;
|
|
791
810
|
info.selectedStrategy = key;
|
|
792
811
|
info.log += "element found using strategy " + key + "\n";
|
|
793
812
|
break;
|
|
@@ -797,38 +816,70 @@ class StableBrowser {
|
|
|
797
816
|
}
|
|
798
817
|
}
|
|
799
818
|
}
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
819
|
+
}
|
|
820
|
+
if (err) {
|
|
821
|
+
throw err;
|
|
803
822
|
}
|
|
804
823
|
}
|
|
805
824
|
else {
|
|
806
825
|
element = await this._locate_internal(selectors, info, _params, timeout, allowDisabled);
|
|
807
826
|
}
|
|
808
827
|
if (!element.rerun) {
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
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
|
+
}
|
|
817
869
|
const scope = element._frame ?? element.page();
|
|
818
|
-
let newElementSelector = "[data-blinq-id-" + randomToken + "]";
|
|
819
870
|
let prefixSelector = "";
|
|
820
871
|
const frameControlSelector = " >> internal:control=enter-frame";
|
|
821
872
|
const frameSelectorIndex = element._selector.lastIndexOf(frameControlSelector);
|
|
822
873
|
if (frameSelectorIndex !== -1) {
|
|
823
874
|
// remove everything after the >> internal:control=enter-frame
|
|
824
875
|
const frameSelector = element._selector.substring(0, frameSelectorIndex);
|
|
825
|
-
prefixSelector = frameSelector + " >> internal:control=enter-frame >>";
|
|
876
|
+
prefixSelector = frameSelector + " >> internal:control=enter-frame >> ";
|
|
826
877
|
}
|
|
827
878
|
// if (element?._frame?._selector) {
|
|
828
879
|
// prefixSelector = element._frame._selector + " >> " + prefixSelector;
|
|
829
880
|
// }
|
|
830
881
|
const newSelector = prefixSelector + newElementSelector;
|
|
831
|
-
return scope.locator(newSelector);
|
|
882
|
+
return scope.locator(newSelector).first();
|
|
832
883
|
}
|
|
833
884
|
}
|
|
834
885
|
throw new Error("unable to locate element " + JSON.stringify(selectors));
|
|
@@ -849,7 +900,7 @@ class StableBrowser {
|
|
|
849
900
|
for (let i = 0; i < frame.selectors.length; i++) {
|
|
850
901
|
let frameLocator = frame.selectors[i];
|
|
851
902
|
if (frameLocator.css) {
|
|
852
|
-
let testframescope = framescope.frameLocator(frameLocator.css);
|
|
903
|
+
let testframescope = framescope.frameLocator(`${frameLocator.css} >> visible=true`);
|
|
853
904
|
if (frameLocator.index) {
|
|
854
905
|
testframescope = framescope.nth(frameLocator.index);
|
|
855
906
|
}
|
|
@@ -927,6 +978,15 @@ class StableBrowser {
|
|
|
927
978
|
});
|
|
928
979
|
}
|
|
929
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
|
+
}
|
|
930
990
|
if (!info) {
|
|
931
991
|
info = {};
|
|
932
992
|
info.failCause = {};
|
|
@@ -1082,9 +1142,13 @@ class StableBrowser {
|
|
|
1082
1142
|
}
|
|
1083
1143
|
}
|
|
1084
1144
|
if (foundLocators.length === 1) {
|
|
1145
|
+
let box = null;
|
|
1146
|
+
if (!this.onlyFailuresScreenshot) {
|
|
1147
|
+
box = await foundLocators[0].boundingBox();
|
|
1148
|
+
}
|
|
1085
1149
|
result.foundElements.push({
|
|
1086
1150
|
locator: foundLocators[0],
|
|
1087
|
-
box:
|
|
1151
|
+
box: box,
|
|
1088
1152
|
unique: true,
|
|
1089
1153
|
});
|
|
1090
1154
|
result.locatorIndex = i;
|
|
@@ -1142,7 +1206,7 @@ class StableBrowser {
|
|
|
1142
1206
|
operation: "simpleClick",
|
|
1143
1207
|
log: "***** click on " + elementDescription + " *****\n",
|
|
1144
1208
|
};
|
|
1145
|
-
_preCommand(state, this);
|
|
1209
|
+
await _preCommand(state, this);
|
|
1146
1210
|
const startTime = Date.now();
|
|
1147
1211
|
let timeout = 30000;
|
|
1148
1212
|
if (options && options.timeout) {
|
|
@@ -1191,7 +1255,7 @@ class StableBrowser {
|
|
|
1191
1255
|
operation: "simpleClickType",
|
|
1192
1256
|
log: "***** click type on " + elementDescription + " *****\n",
|
|
1193
1257
|
};
|
|
1194
|
-
_preCommand(state, this);
|
|
1258
|
+
await _preCommand(state, this);
|
|
1195
1259
|
const startTime = Date.now();
|
|
1196
1260
|
let timeout = 30000;
|
|
1197
1261
|
if (options && options.timeout) {
|
|
@@ -1239,11 +1303,22 @@ class StableBrowser {
|
|
|
1239
1303
|
operation: "click",
|
|
1240
1304
|
log: "***** click on " + selectors.element_name + " *****\n",
|
|
1241
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
|
+
}
|
|
1242
1313
|
try {
|
|
1314
|
+
check_performance("click_preCommand", this.context, true);
|
|
1243
1315
|
await _preCommand(state, this);
|
|
1316
|
+
check_performance("click_preCommand", this.context, false);
|
|
1244
1317
|
await performAction("click", state.element, options, this, state, _params);
|
|
1245
|
-
if (!this.fastMode) {
|
|
1246
|
-
|
|
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);
|
|
1247
1322
|
}
|
|
1248
1323
|
return state.info;
|
|
1249
1324
|
}
|
|
@@ -1251,7 +1326,13 @@ class StableBrowser {
|
|
|
1251
1326
|
await _commandError(state, e, this);
|
|
1252
1327
|
}
|
|
1253
1328
|
finally {
|
|
1329
|
+
check_performance("click_commandFinally", this.context, true);
|
|
1254
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
|
+
}
|
|
1255
1336
|
}
|
|
1256
1337
|
}
|
|
1257
1338
|
async waitForElement(selectors, _params, options = {}, world = null) {
|
|
@@ -1343,7 +1424,7 @@ class StableBrowser {
|
|
|
1343
1424
|
}
|
|
1344
1425
|
}
|
|
1345
1426
|
}
|
|
1346
|
-
await this.waitForPageLoad();
|
|
1427
|
+
//await this.waitForPageLoad();
|
|
1347
1428
|
return state.info;
|
|
1348
1429
|
}
|
|
1349
1430
|
catch (e) {
|
|
@@ -1369,7 +1450,7 @@ class StableBrowser {
|
|
|
1369
1450
|
await _preCommand(state, this);
|
|
1370
1451
|
await performAction("hover", state.element, options, this, state, _params);
|
|
1371
1452
|
await _screenshot(state, this);
|
|
1372
|
-
await this.waitForPageLoad();
|
|
1453
|
+
//await this.waitForPageLoad();
|
|
1373
1454
|
return state.info;
|
|
1374
1455
|
}
|
|
1375
1456
|
catch (e) {
|
|
@@ -1405,7 +1486,7 @@ class StableBrowser {
|
|
|
1405
1486
|
state.info.log += "selectOption failed, will try force" + "\n";
|
|
1406
1487
|
await state.element.selectOption(values, { timeout: 10000, force: true });
|
|
1407
1488
|
}
|
|
1408
|
-
await this.waitForPageLoad();
|
|
1489
|
+
//await this.waitForPageLoad();
|
|
1409
1490
|
return state.info;
|
|
1410
1491
|
}
|
|
1411
1492
|
catch (e) {
|
|
@@ -1509,7 +1590,7 @@ class StableBrowser {
|
|
|
1509
1590
|
_text: `Set date time value: ${value} on ${selectors.element_name}`,
|
|
1510
1591
|
operation: "setDateTime",
|
|
1511
1592
|
log: "***** set date time value " + selectors.element_name + " *****\n",
|
|
1512
|
-
throwError: false,
|
|
1593
|
+
// throwError: false,
|
|
1513
1594
|
};
|
|
1514
1595
|
try {
|
|
1515
1596
|
await _preCommand(state, this);
|
|
@@ -1591,6 +1672,14 @@ class StableBrowser {
|
|
|
1591
1672
|
}
|
|
1592
1673
|
try {
|
|
1593
1674
|
await _preCommand(state, this);
|
|
1675
|
+
const randomToken = "blinq_" + Math.random().toString(36).substring(7);
|
|
1676
|
+
// tag the element
|
|
1677
|
+
let newElementSelector = await state.element.evaluate((el, token) => {
|
|
1678
|
+
// use attribute and not id
|
|
1679
|
+
const attrName = `data-blinq-id-${token}`;
|
|
1680
|
+
el.setAttribute(attrName, "");
|
|
1681
|
+
return `[${attrName}]`;
|
|
1682
|
+
}, randomToken);
|
|
1594
1683
|
state.info.value = _value;
|
|
1595
1684
|
if (!options.press) {
|
|
1596
1685
|
try {
|
|
@@ -1616,6 +1705,25 @@ class StableBrowser {
|
|
|
1616
1705
|
}
|
|
1617
1706
|
}
|
|
1618
1707
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1708
|
+
// check if the element exist after the click (no wait)
|
|
1709
|
+
const count = await state.element.count({ timeout: 0 });
|
|
1710
|
+
if (count === 0) {
|
|
1711
|
+
// the locator changed after the click (placeholder) we need to locate the element using the data-blinq-id
|
|
1712
|
+
const scope = state.element._frame ?? element.page();
|
|
1713
|
+
let prefixSelector = "";
|
|
1714
|
+
const frameControlSelector = " >> internal:control=enter-frame";
|
|
1715
|
+
const frameSelectorIndex = state.element._selector.lastIndexOf(frameControlSelector);
|
|
1716
|
+
if (frameSelectorIndex !== -1) {
|
|
1717
|
+
// remove everything after the >> internal:control=enter-frame
|
|
1718
|
+
const frameSelector = state.element._selector.substring(0, frameSelectorIndex);
|
|
1719
|
+
prefixSelector = frameSelector + " >> internal:control=enter-frame >> ";
|
|
1720
|
+
}
|
|
1721
|
+
// if (element?._frame?._selector) {
|
|
1722
|
+
// prefixSelector = element._frame._selector + " >> " + prefixSelector;
|
|
1723
|
+
// }
|
|
1724
|
+
const newSelector = prefixSelector + newElementSelector;
|
|
1725
|
+
state.element = scope.locator(newSelector).first();
|
|
1726
|
+
}
|
|
1619
1727
|
const valueSegment = state.value.split("&&");
|
|
1620
1728
|
for (let i = 0; i < valueSegment.length; i++) {
|
|
1621
1729
|
if (i > 0) {
|
|
@@ -1687,8 +1795,8 @@ class StableBrowser {
|
|
|
1687
1795
|
if (enter) {
|
|
1688
1796
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1689
1797
|
await this.page.keyboard.press("Enter");
|
|
1798
|
+
await this.waitForPageLoad();
|
|
1690
1799
|
}
|
|
1691
|
-
await this.waitForPageLoad();
|
|
1692
1800
|
return state.info;
|
|
1693
1801
|
}
|
|
1694
1802
|
catch (e) {
|
|
@@ -1947,11 +2055,12 @@ class StableBrowser {
|
|
|
1947
2055
|
throw new Error("referanceSnapshot is null");
|
|
1948
2056
|
}
|
|
1949
2057
|
let text = null;
|
|
1950
|
-
|
|
1951
|
-
|
|
2058
|
+
const snapshotsFolder = process.env.BVT_TEMP_SNAPSHOTS_FOLDER ?? this.context.snapshotFolder; //path .join(this.project_path, "data", "snapshots");
|
|
2059
|
+
if (fs.existsSync(path.join(snapshotsFolder, referanceSnapshot + ".yml"))) {
|
|
2060
|
+
text = fs.readFileSync(path.join(snapshotsFolder, referanceSnapshot + ".yml"), "utf8");
|
|
1952
2061
|
}
|
|
1953
|
-
else if (fs.existsSync(path.join(
|
|
1954
|
-
text = fs.readFileSync(path.join(
|
|
2062
|
+
else if (fs.existsSync(path.join(snapshotsFolder, referanceSnapshot + ".yaml"))) {
|
|
2063
|
+
text = fs.readFileSync(path.join(snapshotsFolder, referanceSnapshot + ".yaml"), "utf8");
|
|
1955
2064
|
}
|
|
1956
2065
|
else if (referanceSnapshot.startsWith("yaml:")) {
|
|
1957
2066
|
text = referanceSnapshot.substring(5);
|
|
@@ -1975,7 +2084,13 @@ class StableBrowser {
|
|
|
1975
2084
|
scope = await this._findFrameScope(frameSelectors, timeout, state.info);
|
|
1976
2085
|
}
|
|
1977
2086
|
const snapshot = await scope.locator("body").ariaSnapshot({ timeout });
|
|
2087
|
+
if (snapshot && snapshot.length <= 10) {
|
|
2088
|
+
console.log("Page snapshot length is suspiciously small:", snapshot);
|
|
2089
|
+
}
|
|
1978
2090
|
matchResult = snapshotValidation(snapshot, newValue, referanceSnapshot);
|
|
2091
|
+
if (matchResult === undefined) {
|
|
2092
|
+
console.log("snapshotValidation returned undefined");
|
|
2093
|
+
}
|
|
1979
2094
|
if (matchResult.errorLine !== -1) {
|
|
1980
2095
|
throw new Error("Snapshot validation failed at line " + matchResult.errorLineText);
|
|
1981
2096
|
}
|
|
@@ -2146,6 +2261,9 @@ class StableBrowser {
|
|
|
2146
2261
|
return _getTestData(world, this.context, this);
|
|
2147
2262
|
}
|
|
2148
2263
|
async _screenShot(options = {}, world = null, info = null) {
|
|
2264
|
+
if (!options) {
|
|
2265
|
+
options = {};
|
|
2266
|
+
}
|
|
2149
2267
|
// collect url/path/title
|
|
2150
2268
|
if (info) {
|
|
2151
2269
|
if (!info.title) {
|
|
@@ -2174,7 +2292,7 @@ class StableBrowser {
|
|
|
2174
2292
|
const uuidStr = "id_" + randomUUID();
|
|
2175
2293
|
const screenshotPath = path.join(world.screenshotPath, uuidStr + ".png");
|
|
2176
2294
|
try {
|
|
2177
|
-
await this.takeScreenshot(screenshotPath);
|
|
2295
|
+
await this.takeScreenshot(screenshotPath, options.fullPage === true);
|
|
2178
2296
|
// let buffer = await this.page.screenshot({ timeout: 4000 });
|
|
2179
2297
|
// // save the buffer to the screenshot path asynchrously
|
|
2180
2298
|
// fs.writeFile(screenshotPath, buffer, (err) => {
|
|
@@ -2195,7 +2313,7 @@ class StableBrowser {
|
|
|
2195
2313
|
else if (options && options.screenshot) {
|
|
2196
2314
|
result.screenshotPath = options.screenshotPath;
|
|
2197
2315
|
try {
|
|
2198
|
-
await this.takeScreenshot(options.screenshotPath);
|
|
2316
|
+
await this.takeScreenshot(options.screenshotPath, options.fullPage === true);
|
|
2199
2317
|
// let buffer = await this.page.screenshot({ timeout: 4000 });
|
|
2200
2318
|
// // save the buffer to the screenshot path asynchrously
|
|
2201
2319
|
// fs.writeFile(options.screenshotPath, buffer, (err) => {
|
|
@@ -2213,7 +2331,7 @@ class StableBrowser {
|
|
|
2213
2331
|
}
|
|
2214
2332
|
return result;
|
|
2215
2333
|
}
|
|
2216
|
-
async takeScreenshot(screenshotPath) {
|
|
2334
|
+
async takeScreenshot(screenshotPath, fullPage = false) {
|
|
2217
2335
|
const playContext = this.context.playContext;
|
|
2218
2336
|
// Using CDP to capture the screenshot
|
|
2219
2337
|
const viewportWidth = Math.max(...(await this.page.evaluate(() => [
|
|
@@ -2238,13 +2356,7 @@ class StableBrowser {
|
|
|
2238
2356
|
const client = await playContext.newCDPSession(this.page);
|
|
2239
2357
|
const { data } = await client.send("Page.captureScreenshot", {
|
|
2240
2358
|
format: "png",
|
|
2241
|
-
|
|
2242
|
-
// x: 0,
|
|
2243
|
-
// y: 0,
|
|
2244
|
-
// width: viewportWidth,
|
|
2245
|
-
// height: viewportHeight,
|
|
2246
|
-
// scale: 1,
|
|
2247
|
-
// },
|
|
2359
|
+
captureBeyondViewport: fullPage,
|
|
2248
2360
|
});
|
|
2249
2361
|
await client.detach();
|
|
2250
2362
|
if (!screenshotPath) {
|
|
@@ -2253,7 +2365,7 @@ class StableBrowser {
|
|
|
2253
2365
|
screenshotBuffer = Buffer.from(data, "base64");
|
|
2254
2366
|
}
|
|
2255
2367
|
else {
|
|
2256
|
-
screenshotBuffer = await this.page.screenshot();
|
|
2368
|
+
screenshotBuffer = await this.page.screenshot({ fullPage: fullPage });
|
|
2257
2369
|
}
|
|
2258
2370
|
// if (focusedElement) {
|
|
2259
2371
|
// // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
|
|
@@ -2353,6 +2465,12 @@ class StableBrowser {
|
|
|
2353
2465
|
state.info.value = state.value;
|
|
2354
2466
|
this.setTestData({ [variable]: state.value }, world);
|
|
2355
2467
|
this.logger.info("set test data: " + variable + "=" + state.value);
|
|
2468
|
+
if (process.env.MODE === "executions") {
|
|
2469
|
+
const globalDataFile = "global_test_data.json";
|
|
2470
|
+
if (existsSync(globalDataFile)) {
|
|
2471
|
+
this.saveTestDataAsGlobal({}, world);
|
|
2472
|
+
}
|
|
2473
|
+
}
|
|
2356
2474
|
// await new Promise((resolve) => setTimeout(resolve, 500));
|
|
2357
2475
|
return state.info;
|
|
2358
2476
|
}
|
|
@@ -2424,6 +2542,12 @@ class StableBrowser {
|
|
|
2424
2542
|
state.info.value = state.value;
|
|
2425
2543
|
this.setTestData({ [variable]: state.value }, world);
|
|
2426
2544
|
this.logger.info("set test data: " + variable + "=" + state.value);
|
|
2545
|
+
if (process.env.MODE === "executions") {
|
|
2546
|
+
const globalDataFile = "global_test_data.json";
|
|
2547
|
+
if (existsSync(globalDataFile)) {
|
|
2548
|
+
this.saveTestDataAsGlobal({}, world);
|
|
2549
|
+
}
|
|
2550
|
+
}
|
|
2427
2551
|
// await new Promise((resolve) => setTimeout(resolve, 500));
|
|
2428
2552
|
return state.info;
|
|
2429
2553
|
}
|
|
@@ -2554,7 +2678,7 @@ class StableBrowser {
|
|
|
2554
2678
|
let expectedValue;
|
|
2555
2679
|
try {
|
|
2556
2680
|
await _preCommand(state, this);
|
|
2557
|
-
expectedValue = await
|
|
2681
|
+
expectedValue = await this._replaceWithLocalData(value, world);
|
|
2558
2682
|
state.info.expectedValue = expectedValue;
|
|
2559
2683
|
switch (property) {
|
|
2560
2684
|
case "innerText":
|
|
@@ -2680,6 +2804,7 @@ class StableBrowser {
|
|
|
2680
2804
|
allowDisabled: true,
|
|
2681
2805
|
info: {},
|
|
2682
2806
|
};
|
|
2807
|
+
state.options ??= { timeout: timeoutMs };
|
|
2683
2808
|
// Initialize startTime outside try block to ensure it's always accessible
|
|
2684
2809
|
const startTime = Date.now();
|
|
2685
2810
|
let conditionMet = false;
|
|
@@ -2849,6 +2974,12 @@ class StableBrowser {
|
|
|
2849
2974
|
emailUrl = url;
|
|
2850
2975
|
codeOrUrlFound = true;
|
|
2851
2976
|
}
|
|
2977
|
+
if (process.env.MODE === "executions") {
|
|
2978
|
+
const globalDataFile = "global_test_data.json";
|
|
2979
|
+
if (existsSync(globalDataFile)) {
|
|
2980
|
+
this.saveTestDataAsGlobal({}, world);
|
|
2981
|
+
}
|
|
2982
|
+
}
|
|
2852
2983
|
if (codeOrUrlFound) {
|
|
2853
2984
|
return { emailUrl, emailCode };
|
|
2854
2985
|
}
|
|
@@ -3255,7 +3386,16 @@ class StableBrowser {
|
|
|
3255
3386
|
text = text.replace(/\\"/g, '"');
|
|
3256
3387
|
}
|
|
3257
3388
|
const timeout = this._getFindElementTimeout(options);
|
|
3258
|
-
|
|
3389
|
+
//if (!this.fastMode && !this.stepTags.includes("fast-mode")) {
|
|
3390
|
+
let stepFastMode = this.stepTags.includes("fast-mode");
|
|
3391
|
+
if (!stepFastMode) {
|
|
3392
|
+
if (!this.fastMode) {
|
|
3393
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
3394
|
+
}
|
|
3395
|
+
else {
|
|
3396
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
3397
|
+
}
|
|
3398
|
+
}
|
|
3259
3399
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
3260
3400
|
if (newValue !== text) {
|
|
3261
3401
|
this.logger.info(text + "=" + newValue);
|
|
@@ -3263,6 +3403,11 @@ class StableBrowser {
|
|
|
3263
3403
|
}
|
|
3264
3404
|
let dateAlternatives = findDateAlternatives(text);
|
|
3265
3405
|
let numberAlternatives = findNumberAlternatives(text);
|
|
3406
|
+
if (stepFastMode) {
|
|
3407
|
+
state.onlyFailuresScreenshot = true;
|
|
3408
|
+
state.scroll = false;
|
|
3409
|
+
state.highlight = false;
|
|
3410
|
+
}
|
|
3266
3411
|
try {
|
|
3267
3412
|
await _preCommand(state, this);
|
|
3268
3413
|
state.info.text = text;
|
|
@@ -3382,6 +3527,8 @@ class StableBrowser {
|
|
|
3382
3527
|
operation: "verify_text_with_relation",
|
|
3383
3528
|
log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
|
|
3384
3529
|
};
|
|
3530
|
+
const cmdStartTime = Date.now();
|
|
3531
|
+
let cmdEndTime = null;
|
|
3385
3532
|
const timeout = this._getFindElementTimeout(options);
|
|
3386
3533
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
3387
3534
|
let newValue = await this._replaceWithLocalData(textAnchor, world);
|
|
@@ -3417,6 +3564,17 @@ class StableBrowser {
|
|
|
3417
3564
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
3418
3565
|
continue;
|
|
3419
3566
|
}
|
|
3567
|
+
else {
|
|
3568
|
+
cmdEndTime = Date.now();
|
|
3569
|
+
if (cmdEndTime - cmdStartTime > 55000) {
|
|
3570
|
+
if (foundAncore) {
|
|
3571
|
+
throw new Error(`Text ${textToVerify} not found in page`);
|
|
3572
|
+
}
|
|
3573
|
+
else {
|
|
3574
|
+
throw new Error(`Text ${textAnchor} not found in page`);
|
|
3575
|
+
}
|
|
3576
|
+
}
|
|
3577
|
+
}
|
|
3420
3578
|
try {
|
|
3421
3579
|
for (let i = 0; i < resultWithElementsFound.length; i++) {
|
|
3422
3580
|
foundAncore = true;
|
|
@@ -3429,7 +3587,9 @@ class StableBrowser {
|
|
|
3429
3587
|
climbArray1.push("..");
|
|
3430
3588
|
}
|
|
3431
3589
|
let climbXpath = "xpath=" + climbArray1.join("/");
|
|
3432
|
-
|
|
3590
|
+
if (Number(climb) > 0) {
|
|
3591
|
+
css = css + " >> " + climbXpath;
|
|
3592
|
+
}
|
|
3433
3593
|
const count = await frame.locator(css).count();
|
|
3434
3594
|
for (let j = 0; j < count; j++) {
|
|
3435
3595
|
const continer = await frame.locator(css).nth(j);
|
|
@@ -3555,7 +3715,7 @@ class StableBrowser {
|
|
|
3555
3715
|
Object.assign(e, { info: info });
|
|
3556
3716
|
error = e;
|
|
3557
3717
|
// throw e;
|
|
3558
|
-
await _commandError({ text: "visualVerification", operation: "visualVerification",
|
|
3718
|
+
await _commandError({ text: "visualVerification", operation: "visualVerification", info }, e, this);
|
|
3559
3719
|
}
|
|
3560
3720
|
finally {
|
|
3561
3721
|
const endTime = Date.now();
|
|
@@ -3900,10 +4060,26 @@ class StableBrowser {
|
|
|
3900
4060
|
registerNetworkEvents(this.world, this, this.context, this.page);
|
|
3901
4061
|
registerDownloadEvent(this.page, this.world, this.context);
|
|
3902
4062
|
if (this.onRestoreSaveState) {
|
|
3903
|
-
this.onRestoreSaveState(path);
|
|
4063
|
+
await this.onRestoreSaveState(path);
|
|
3904
4064
|
}
|
|
3905
4065
|
}
|
|
3906
4066
|
async waitForPageLoad(options = {}, world = null) {
|
|
4067
|
+
// try {
|
|
4068
|
+
// let currentPagePath = null;
|
|
4069
|
+
// currentPagePath = new URL(this.page.url()).pathname;
|
|
4070
|
+
// if (this.latestPagePath) {
|
|
4071
|
+
// // get the currect page path and compare with the latest page path
|
|
4072
|
+
// if (this.latestPagePath === currentPagePath) {
|
|
4073
|
+
// // if the page path is the same, do not wait for page load
|
|
4074
|
+
// console.log("No page change: " + currentPagePath);
|
|
4075
|
+
// return;
|
|
4076
|
+
// }
|
|
4077
|
+
// }
|
|
4078
|
+
// this.latestPagePath = currentPagePath;
|
|
4079
|
+
// } catch (e) {
|
|
4080
|
+
// console.debug("Error getting current page path: ", e);
|
|
4081
|
+
// }
|
|
4082
|
+
//console.log("Waiting for page load");
|
|
3907
4083
|
let timeout = this._getLoadTimeout(options);
|
|
3908
4084
|
const promiseArray = [];
|
|
3909
4085
|
// let waitForNetworkIdle = true;
|
|
@@ -3938,7 +4114,10 @@ class StableBrowser {
|
|
|
3938
4114
|
}
|
|
3939
4115
|
}
|
|
3940
4116
|
finally {
|
|
3941
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
4117
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
4118
|
+
if (options && !options.noSleep) {
|
|
4119
|
+
await new Promise((resolve) => setTimeout(resolve, 1500));
|
|
4120
|
+
}
|
|
3942
4121
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world));
|
|
3943
4122
|
const endTime = Date.now();
|
|
3944
4123
|
_reportToWorld(world, {
|
|
@@ -3972,7 +4151,7 @@ class StableBrowser {
|
|
|
3972
4151
|
_text: `Close the page`,
|
|
3973
4152
|
operation: "closePage",
|
|
3974
4153
|
log: "***** close page *****\n",
|
|
3975
|
-
throwError: false,
|
|
4154
|
+
// throwError: false,
|
|
3976
4155
|
};
|
|
3977
4156
|
try {
|
|
3978
4157
|
await _preCommand(state, this);
|
|
@@ -3992,7 +4171,7 @@ class StableBrowser {
|
|
|
3992
4171
|
}
|
|
3993
4172
|
operation = options.operation;
|
|
3994
4173
|
// validate operation is one of the supported operations
|
|
3995
|
-
if (operation != "click" && operation != "hover+click") {
|
|
4174
|
+
if (operation != "click" && operation != "hover+click" && operation != "hover") {
|
|
3996
4175
|
throw new Error("operation is not supported");
|
|
3997
4176
|
}
|
|
3998
4177
|
const state = {
|
|
@@ -4061,6 +4240,17 @@ class StableBrowser {
|
|
|
4061
4240
|
state.element = results[0];
|
|
4062
4241
|
await performAction("hover+click", state.element, options, this, state, _params);
|
|
4063
4242
|
break;
|
|
4243
|
+
case "hover":
|
|
4244
|
+
if (!options.css) {
|
|
4245
|
+
throw new Error("css is not defined");
|
|
4246
|
+
}
|
|
4247
|
+
const result1 = await findElementsInArea(options.css, cellArea, this, options);
|
|
4248
|
+
if (result1.length === 0) {
|
|
4249
|
+
throw new Error(`Element not found in cell area`);
|
|
4250
|
+
}
|
|
4251
|
+
state.element = result1[0];
|
|
4252
|
+
await performAction("hover", state.element, options, this, state, _params);
|
|
4253
|
+
break;
|
|
4064
4254
|
default:
|
|
4065
4255
|
throw new Error("operation is not supported");
|
|
4066
4256
|
}
|
|
@@ -4074,6 +4264,16 @@ class StableBrowser {
|
|
|
4074
4264
|
}
|
|
4075
4265
|
saveTestDataAsGlobal(options, world) {
|
|
4076
4266
|
const dataFile = _getDataFile(world, this.context, this);
|
|
4267
|
+
if (process.env.MODE === "executions") {
|
|
4268
|
+
const globalDataFile = path.join(this.project_path, "global_test_data.json");
|
|
4269
|
+
const dataFileContents = fs.existsSync(dataFile) ? JSON.parse(fs.readFileSync(dataFile)) : {};
|
|
4270
|
+
const globalDataFileContents = fs.existsSync(globalDataFile) ? JSON.parse(fs.readFileSync(globalDataFile)) : {};
|
|
4271
|
+
const mergedData = JSON.stringify(_.merge({}, dataFileContents, globalDataFileContents), null, 2);
|
|
4272
|
+
fs.writeFileSync(dataFile, mergedData);
|
|
4273
|
+
fs.writeFileSync(globalDataFile, mergedData);
|
|
4274
|
+
this.logger.info("Save the scenario test data to " + dataFile + " as global for the following scenarios.");
|
|
4275
|
+
return;
|
|
4276
|
+
}
|
|
4077
4277
|
process.env.GLOBAL_TEST_DATA_FILE = dataFile;
|
|
4078
4278
|
this.logger.info("Save the scenario test data as global for the following scenarios.");
|
|
4079
4279
|
}
|
|
@@ -4176,6 +4376,7 @@ class StableBrowser {
|
|
|
4176
4376
|
if (world && world.attach) {
|
|
4177
4377
|
world.attach(this.context.reportFolder, { mediaType: "text/plain" });
|
|
4178
4378
|
}
|
|
4379
|
+
this.context.loadedRoutes = null;
|
|
4179
4380
|
this.beforeScenarioCalled = true;
|
|
4180
4381
|
if (scenario && scenario.pickle && scenario.pickle.name) {
|
|
4181
4382
|
this.scenarioName = scenario.pickle.name;
|
|
@@ -4189,9 +4390,12 @@ class StableBrowser {
|
|
|
4189
4390
|
if (this.tags === null && scenario && scenario.pickle && scenario.pickle.tags) {
|
|
4190
4391
|
this.tags = scenario.pickle.tags.map((tag) => tag.name);
|
|
4191
4392
|
// check if @global_test_data tag is present
|
|
4192
|
-
if (this.tags.includes("@global_test_data")) {
|
|
4393
|
+
if (this.tags.includes("@global_test_data" /* TAG_CONSTANTS.GLOBAL_TEST_DATA */)) {
|
|
4193
4394
|
this.saveTestDataAsGlobal({}, world);
|
|
4194
4395
|
}
|
|
4396
|
+
if (this.tags.includes("@fast_mode" /* TAG_CONSTANTS.FAST_MODE */)) {
|
|
4397
|
+
this.fastMode = true;
|
|
4398
|
+
}
|
|
4195
4399
|
}
|
|
4196
4400
|
// update test data based on feature/scenario
|
|
4197
4401
|
let envName = null;
|
|
@@ -4202,14 +4406,51 @@ class StableBrowser {
|
|
|
4202
4406
|
await getTestData(envName, world, undefined, this.featureName, this.scenarioName, this.context);
|
|
4203
4407
|
}
|
|
4204
4408
|
await loadBrunoParams(this.context, this.context.environment.name);
|
|
4409
|
+
if ((process.env.TRACE === "true" || this.configuration.trace === true) && this.context) {
|
|
4410
|
+
this.trace = true;
|
|
4411
|
+
const traceFolder = path.join(this.context.reportFolder, "trace");
|
|
4412
|
+
if (!fs.existsSync(traceFolder)) {
|
|
4413
|
+
fs.mkdirSync(traceFolder, { recursive: true });
|
|
4414
|
+
}
|
|
4415
|
+
this.traceFolder = traceFolder;
|
|
4416
|
+
await this.context.playContext.tracing.start({ screenshots: true, snapshots: true });
|
|
4417
|
+
}
|
|
4418
|
+
}
|
|
4419
|
+
async afterScenario(world, scenario) {
|
|
4420
|
+
const id = scenario.testCaseStartedId;
|
|
4421
|
+
if (this.trace) {
|
|
4422
|
+
await this.context.playContext.tracing.stop({
|
|
4423
|
+
path: path.join(this.traceFolder, `trace-${id}.zip`),
|
|
4424
|
+
});
|
|
4425
|
+
}
|
|
4426
|
+
}
|
|
4427
|
+
getGherkinKeyword(step) {
|
|
4428
|
+
if (!step?.type) {
|
|
4429
|
+
return "";
|
|
4430
|
+
}
|
|
4431
|
+
switch (step.type) {
|
|
4432
|
+
case "Context":
|
|
4433
|
+
return "Given";
|
|
4434
|
+
case "Action":
|
|
4435
|
+
return "When";
|
|
4436
|
+
case "Outcome":
|
|
4437
|
+
return "Then";
|
|
4438
|
+
case "Conjunction":
|
|
4439
|
+
return "And";
|
|
4440
|
+
default:
|
|
4441
|
+
return "";
|
|
4442
|
+
}
|
|
4205
4443
|
}
|
|
4206
|
-
async afterScenario(world, scenario) { }
|
|
4207
4444
|
async beforeStep(world, step) {
|
|
4208
|
-
if (this.
|
|
4209
|
-
|
|
4445
|
+
if (step?.pickleStep && this.trace) {
|
|
4446
|
+
const keyword = this.getGherkinKeyword(step.pickleStep);
|
|
4447
|
+
this.traceGroupName = `${keyword} ${step.pickleStep.text}`;
|
|
4448
|
+
await this.context.playContext.tracing.group(this.traceGroupName);
|
|
4210
4449
|
}
|
|
4450
|
+
this.stepTags = [];
|
|
4211
4451
|
if (!this.beforeScenarioCalled) {
|
|
4212
4452
|
this.beforeScenario(world, step);
|
|
4453
|
+
this.context.loadedRoutes = null;
|
|
4213
4454
|
}
|
|
4214
4455
|
if (this.stepIndex === undefined) {
|
|
4215
4456
|
this.stepIndex = 0;
|
|
@@ -4219,7 +4460,12 @@ class StableBrowser {
|
|
|
4219
4460
|
}
|
|
4220
4461
|
if (step && step.pickleStep && step.pickleStep.text) {
|
|
4221
4462
|
this.stepName = step.pickleStep.text;
|
|
4222
|
-
|
|
4463
|
+
let printableStepName = this.stepName;
|
|
4464
|
+
// take the printableStepName and replace quated value with \x1b[33m and \x1b[0m
|
|
4465
|
+
printableStepName = printableStepName.replace(/"([^"]*)"/g, (match, p1) => {
|
|
4466
|
+
return `\x1b[33m"${p1}"\x1b[0m`;
|
|
4467
|
+
});
|
|
4468
|
+
this.logger.info("\x1b[38;5;208mstep:\x1b[0m " + printableStepName);
|
|
4223
4469
|
}
|
|
4224
4470
|
else if (step && step.text) {
|
|
4225
4471
|
this.stepName = step.text;
|
|
@@ -4234,7 +4480,10 @@ class StableBrowser {
|
|
|
4234
4480
|
}
|
|
4235
4481
|
if (this.initSnapshotTaken === false) {
|
|
4236
4482
|
this.initSnapshotTaken = true;
|
|
4237
|
-
if (world &&
|
|
4483
|
+
if (world &&
|
|
4484
|
+
world.attach &&
|
|
4485
|
+
!process.env.DISABLE_SNAPSHOT &&
|
|
4486
|
+
(!this.fastMode || this.stepTags.includes("fast-mode"))) {
|
|
4238
4487
|
const snapshot = await this.getAriaSnapshot();
|
|
4239
4488
|
if (snapshot) {
|
|
4240
4489
|
await world.attach(JSON.stringify(snapshot), "application/json+snapshot-before");
|
|
@@ -4244,7 +4493,11 @@ class StableBrowser {
|
|
|
4244
4493
|
this.context.routeResults = null;
|
|
4245
4494
|
this.context.loadedRoutes = null;
|
|
4246
4495
|
await registerBeforeStepRoutes(this.context, this.stepName, world);
|
|
4247
|
-
networkBeforeStep(this.stepName);
|
|
4496
|
+
networkBeforeStep(this.stepName, this.context);
|
|
4497
|
+
this.inStepReport = false;
|
|
4498
|
+
}
|
|
4499
|
+
setStepTags(tags) {
|
|
4500
|
+
this.stepTags = tags;
|
|
4248
4501
|
}
|
|
4249
4502
|
async getAriaSnapshot() {
|
|
4250
4503
|
try {
|
|
@@ -4318,7 +4571,7 @@ class StableBrowser {
|
|
|
4318
4571
|
state.payload = payload;
|
|
4319
4572
|
if (commandStatus === "FAILED") {
|
|
4320
4573
|
state.throwError = true;
|
|
4321
|
-
throw new Error(
|
|
4574
|
+
throw new Error(commandText);
|
|
4322
4575
|
}
|
|
4323
4576
|
}
|
|
4324
4577
|
catch (e) {
|
|
@@ -4328,26 +4581,22 @@ class StableBrowser {
|
|
|
4328
4581
|
await _commandFinally(state, this);
|
|
4329
4582
|
}
|
|
4330
4583
|
}
|
|
4331
|
-
async afterStep(world, step) {
|
|
4584
|
+
async afterStep(world, step, result) {
|
|
4332
4585
|
this.stepName = null;
|
|
4333
|
-
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
4334
|
-
if (this.context.browserObject.context) {
|
|
4335
|
-
await this.context.browserObject.context.tracing.stopChunk({
|
|
4336
|
-
path: path.join(this.context.browserObject.traceFolder, `trace-${this.stepIndex}.zip`),
|
|
4337
|
-
});
|
|
4338
|
-
if (world && world.attach) {
|
|
4339
|
-
await world.attach(JSON.stringify({
|
|
4340
|
-
type: "trace",
|
|
4341
|
-
traceFilePath: `trace-${this.stepIndex}.zip`,
|
|
4342
|
-
}), "application/json+trace");
|
|
4343
|
-
}
|
|
4344
|
-
// console.log("trace file created", `trace-${this.stepIndex}.zip`);
|
|
4345
|
-
}
|
|
4346
|
-
}
|
|
4347
4586
|
if (this.context) {
|
|
4348
4587
|
this.context.examplesRow = null;
|
|
4349
4588
|
}
|
|
4350
|
-
if (
|
|
4589
|
+
if (!this.inStepReport) {
|
|
4590
|
+
// check the step result
|
|
4591
|
+
if (result && result.status === "FAILED" && world && world.attach) {
|
|
4592
|
+
await this.addCommandToReport(result.message ? result.message : "Step failed", "FAILED", `${result.message}`, { type: "text", screenshot: true }, world);
|
|
4593
|
+
}
|
|
4594
|
+
}
|
|
4595
|
+
if (world &&
|
|
4596
|
+
world.attach &&
|
|
4597
|
+
!process.env.DISABLE_SNAPSHOT &&
|
|
4598
|
+
!this.fastMode &&
|
|
4599
|
+
!this.stepTags.includes("fast-mode")) {
|
|
4351
4600
|
const snapshot = await this.getAriaSnapshot();
|
|
4352
4601
|
if (snapshot) {
|
|
4353
4602
|
const obj = {};
|
|
@@ -4355,6 +4604,11 @@ class StableBrowser {
|
|
|
4355
4604
|
}
|
|
4356
4605
|
}
|
|
4357
4606
|
this.context.routeResults = await registerAfterStepRoutes(this.context, world);
|
|
4607
|
+
if (this.context.routeResults) {
|
|
4608
|
+
if (world && world.attach) {
|
|
4609
|
+
await world.attach(JSON.stringify(this.context.routeResults), "application/json+intercept-results");
|
|
4610
|
+
}
|
|
4611
|
+
}
|
|
4358
4612
|
if (!process.env.TEMP_RUN) {
|
|
4359
4613
|
const state = {
|
|
4360
4614
|
world,
|
|
@@ -4378,10 +4632,15 @@ class StableBrowser {
|
|
|
4378
4632
|
await _commandFinally(state, this);
|
|
4379
4633
|
}
|
|
4380
4634
|
}
|
|
4381
|
-
networkAfterStep(this.stepName);
|
|
4635
|
+
networkAfterStep(this.stepName, this.context);
|
|
4382
4636
|
if (process.env.TEMP_RUN === "true") {
|
|
4383
4637
|
// Put a sleep for some time to allow the browser to finish processing
|
|
4384
|
-
|
|
4638
|
+
if (!this.stepTags.includes("fast-mode")) {
|
|
4639
|
+
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
4640
|
+
}
|
|
4641
|
+
}
|
|
4642
|
+
if (this.trace) {
|
|
4643
|
+
await this.context.playContext.tracing.groupEnd();
|
|
4385
4644
|
}
|
|
4386
4645
|
}
|
|
4387
4646
|
}
|