@dev-blinq/cucumber_client 1.0.1237-dev → 1.0.1237-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/bin/assets/bundled_scripts/recorder.js +220 -0
- package/bin/assets/preload/recorderv3.js +5 -3
- package/bin/assets/preload/unique_locators.js +1 -1
- package/bin/assets/scripts/aria_snapshot.js +235 -0
- package/bin/assets/scripts/dom_attr.js +372 -0
- package/bin/assets/scripts/dom_element.js +0 -0
- package/bin/assets/scripts/dom_parent.js +185 -0
- package/bin/assets/scripts/event_utils.js +105 -0
- package/bin/assets/scripts/pw.js +7886 -0
- package/bin/assets/scripts/recorder.js +1147 -0
- package/bin/assets/scripts/snapshot_capturer.js +155 -0
- package/bin/assets/scripts/unique_locators.js +852 -0
- package/bin/assets/scripts/yaml.js +4770 -0
- package/bin/assets/templates/_hooks_template.txt +37 -0
- package/bin/assets/templates/page_template.txt +2 -16
- package/bin/assets/templates/utils_template.txt +44 -71
- package/bin/client/apiTest/apiTest.js +6 -0
- package/bin/client/cli_helpers.js +11 -13
- package/bin/client/code_cleanup/utils.js +36 -13
- package/bin/client/code_gen/code_inversion.js +68 -10
- package/bin/client/code_gen/page_reflection.js +12 -15
- package/bin/client/code_gen/playwright_codeget.js +127 -34
- package/bin/client/cucumber/feature.js +85 -27
- package/bin/client/cucumber/steps_definitions.js +84 -76
- package/bin/client/cucumber_selector.js +13 -1
- package/bin/client/local_agent.js +3 -3
- package/bin/client/project.js +7 -1
- package/bin/client/recorderv3/bvt_recorder.js +267 -87
- package/bin/client/recorderv3/implemented_steps.js +74 -12
- package/bin/client/recorderv3/index.js +58 -8
- package/bin/client/recorderv3/network.js +299 -0
- package/bin/client/recorderv3/step_runner.js +319 -67
- package/bin/client/recorderv3/step_utils.js +152 -5
- package/bin/client/recorderv3/update_feature.js +58 -30
- package/bin/client/recording.js +5 -0
- package/bin/client/run_cucumber.js +5 -1
- package/bin/client/scenario_report.js +0 -5
- package/bin/client/test_scenario.js +0 -1
- package/bin/index.js +1 -0
- package/package.json +17 -9
|
@@ -3,8 +3,7 @@ import { closeContext, initContext, _getDataFile, resetTestData } from "automati
|
|
|
3
3
|
import { existsSync, readdirSync, readFileSync, rmSync } from "fs";
|
|
4
4
|
import path from "path";
|
|
5
5
|
import url from "url";
|
|
6
|
-
import
|
|
7
|
-
import { getImplementedSteps, getStepsAndCommandsForScenario } from "./implemented_steps.js";
|
|
6
|
+
import { getImplementedSteps, parseRouteFiles } from "./implemented_steps.js";
|
|
8
7
|
import { NamesService } from "./services.js";
|
|
9
8
|
import { BVTStepRunner } from "./step_runner.js";
|
|
10
9
|
import { readFile, writeFile } from "fs/promises";
|
|
@@ -16,42 +15,21 @@ import chokidar from "chokidar";
|
|
|
16
15
|
import logger from "../../logger.js";
|
|
17
16
|
import { unEscapeNonPrintables } from "../cucumber/utils.js";
|
|
18
17
|
import { findAvailablePort } from "../utils/index.js";
|
|
19
|
-
import {
|
|
18
|
+
import { Step } from "../cucumber/feature.js";
|
|
19
|
+
|
|
20
20
|
const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
|
|
21
|
-
|
|
21
|
+
|
|
22
22
|
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
23
|
-
export function getInitScript(config) {
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
window.__bvt_Recorder.setPopupHandlers(${JSON.stringify(popupHandlers)});
|
|
32
|
-
window.__bvt_Recorder.setDisableHighlight(${JSON.stringify(disableHighlight)});
|
|
33
|
-
window.__bvt_Recorder.setImproviseLocators(${JSON.stringify(!disableMultipleLocators)});`;
|
|
34
|
-
return (
|
|
35
|
-
[
|
|
36
|
-
path.join(__dirname, "..", "..", "assets", "preload", "accessibility.js"),
|
|
37
|
-
path.join(__dirname, "..", "..", "assets", "preload", "dom-utils.js"),
|
|
38
|
-
path.join(__dirname, "..", "..", "assets", "preload", "generateSelector.js"),
|
|
39
|
-
path.join(__dirname, "..", "..", "assets", "preload", "locators.js"),
|
|
40
|
-
path.join(__dirname, "..", "..", "assets", "preload", "unique_locators.js"),
|
|
41
|
-
// path.join(__dirname, "..", "..", "assets", "preload", "pw_locators.js"),
|
|
42
|
-
path.join(__dirname, "..", "..", "assets", "preload", "climb.js"),
|
|
43
|
-
path.join(__dirname, "..", "..", "assets", "preload", "text-locator.js"),
|
|
44
|
-
path.join(__dirname, "..", "..", "assets", "preload", "toolbar.js"),
|
|
45
|
-
path.join(__dirname, "..", "..", "assets", "preload", "recording-tool.js"),
|
|
46
|
-
path.join(__dirname, "..", "..", "assets", "preload", "find_context.js"),
|
|
47
|
-
path.join(__dirname, "..", "..", "assets", "preload", "recorderv3.js"),
|
|
48
|
-
path.join(__dirname, "..", "..", "assets", "preload", "findElementText.js"),
|
|
49
|
-
path.join(__dirname, "..", "..", "assets", "preload", "css_gen.js"),
|
|
50
|
-
path.join(__dirname, "..", "..", "assets", "preload", "yaml.js"),
|
|
51
|
-
]
|
|
52
|
-
.map((filePath) => readFileSync(filePath, "utf8"))
|
|
53
|
-
.join("\n") + popupScript
|
|
23
|
+
export function getInitScript(config, options) {
|
|
24
|
+
const preScript = `
|
|
25
|
+
window.__bvt_Recorder_config = ${JSON.stringify(config ?? null)};
|
|
26
|
+
window.__PW_options = ${JSON.stringify(options ?? null)};
|
|
27
|
+
`;
|
|
28
|
+
const recorderScript = readFileSync(
|
|
29
|
+
path.join(__dirname, "..", "..", "assets", "bundled_scripts", "recorder.js"),
|
|
30
|
+
"utf8"
|
|
54
31
|
);
|
|
32
|
+
return preScript + recorderScript;
|
|
55
33
|
}
|
|
56
34
|
|
|
57
35
|
async function evaluate(frame, script) {
|
|
@@ -72,7 +50,7 @@ async function findNestedFrameSelector(frame, obj) {
|
|
|
72
50
|
const frameElement = await frame.frameElement();
|
|
73
51
|
if (!frameElement) return;
|
|
74
52
|
const selectors = await parent.evaluate((element) => {
|
|
75
|
-
return window.
|
|
53
|
+
return window.__bvt_Recorder.locatorGenerator.getElementLocators(element, { excludeText: true }).locators;
|
|
76
54
|
}, frameElement);
|
|
77
55
|
return findNestedFrameSelector(parent, { children: obj, selectors });
|
|
78
56
|
} catch (e) {
|
|
@@ -206,11 +184,8 @@ export class BVTRecorder {
|
|
|
206
184
|
projectDir: this.projectDir,
|
|
207
185
|
logger: this.logger,
|
|
208
186
|
});
|
|
209
|
-
this.stepRunner = new BVTStepRunner({
|
|
210
|
-
projectDir: this.projectDir,
|
|
211
|
-
});
|
|
212
187
|
this.pageSet = new Set();
|
|
213
|
-
|
|
188
|
+
this.pageMetaDataSet = new Set();
|
|
214
189
|
this.lastKnownUrlPath = "";
|
|
215
190
|
// TODO: what is world?
|
|
216
191
|
this.world = { attach: () => {} };
|
|
@@ -226,6 +201,10 @@ export class BVTRecorder {
|
|
|
226
201
|
onStepDetails: "BVTRecorder.onStepDetails",
|
|
227
202
|
getTestData: "BVTRecorder.getTestData",
|
|
228
203
|
onGoto: "BVTRecorder.onGoto",
|
|
204
|
+
cmdExecutionStart: "BVTRecorder.cmdExecutionStart",
|
|
205
|
+
cmdExecutionSuccess: "BVTRecorder.cmdExecutionSuccess",
|
|
206
|
+
cmdExecutionError: "BVTRecorder.cmdExecutionError",
|
|
207
|
+
interceptResults: "BVTRecorder.interceptResults",
|
|
229
208
|
};
|
|
230
209
|
bindings = {
|
|
231
210
|
__bvt_recordCommand: async ({ frame, page, context }, event) => {
|
|
@@ -301,29 +280,22 @@ export class BVTRecorder {
|
|
|
301
280
|
return result;
|
|
302
281
|
}
|
|
303
282
|
getInitScripts(config) {
|
|
304
|
-
return getInitScript(config
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
// const inlineScript = `
|
|
314
|
-
// window.__bvt_Recorder.setPopupHandlers(${JSON.stringify(popupHandlers)});
|
|
315
|
-
// window.__bvt_Recorder.setDisableHighlight(${JSON.stringify(disableHighlight)});
|
|
316
|
-
// window.__bvt_Recorder.setImproviseLocators(${JSON.stringify(!disableMultipleLocators)});
|
|
317
|
-
// `
|
|
318
|
-
// scripts.push(inlineScript);
|
|
319
|
-
// return scripts;
|
|
283
|
+
return getInitScript(config, {
|
|
284
|
+
sdkLanguage: "javascript",
|
|
285
|
+
testIdAttributeName: "blinq-test-id",
|
|
286
|
+
stableRafCount: 0,
|
|
287
|
+
browserName: this.browser?.browserType().name(),
|
|
288
|
+
inputFileRoleTextbox: false,
|
|
289
|
+
customEngines: [],
|
|
290
|
+
isUnderTest: true,
|
|
291
|
+
});
|
|
320
292
|
}
|
|
321
293
|
|
|
322
294
|
async _initBrowser({ url }) {
|
|
323
295
|
this.#remoteDebuggerPort = await findAvailablePort();
|
|
324
296
|
process.env.CDP_LISTEN_PORT = this.#remoteDebuggerPort;
|
|
325
297
|
|
|
326
|
-
this.stepRunner.setRemoteDebugPort(this.#remoteDebuggerPort);
|
|
298
|
+
// this.stepRunner.setRemoteDebugPort(this.#remoteDebuggerPort);
|
|
327
299
|
this.world = { attach: () => {} };
|
|
328
300
|
|
|
329
301
|
const ai_config_file = path.join(this.projectDir, "ai_config.json");
|
|
@@ -335,8 +307,9 @@ export class BVTRecorder {
|
|
|
335
307
|
console.error("Error reading ai_config.json", error);
|
|
336
308
|
}
|
|
337
309
|
}
|
|
310
|
+
this.config = ai_config;
|
|
338
311
|
const initScripts = {
|
|
339
|
-
recorderCjs: injectedScriptSource,
|
|
312
|
+
// recorderCjs: injectedScriptSource,
|
|
340
313
|
scripts: [
|
|
341
314
|
this.getInitScripts(ai_config),
|
|
342
315
|
`\ndelete Object.getPrototypeOf(navigator).webdriver;${process.env.WINDOW_DEBUGGER ? "window.debug=true;\n" : ""}`,
|
|
@@ -348,11 +321,40 @@ export class BVTRecorder {
|
|
|
348
321
|
let stopTime = Date.now();
|
|
349
322
|
this.logger.info(`Browser launched in ${(stopTime - startTime) / 1000} s`);
|
|
350
323
|
this.bvtContext = bvtContext;
|
|
324
|
+
this.stepRunner = new BVTStepRunner({
|
|
325
|
+
projectDir: this.projectDir,
|
|
326
|
+
sendExecutionStatus: (data) => {
|
|
327
|
+
if (data && data.type) {
|
|
328
|
+
switch (data.type) {
|
|
329
|
+
case "cmdExecutionStart":
|
|
330
|
+
console.log("Sending cmdExecutionStart event for cmdId:", data);
|
|
331
|
+
this.sendEvent(this.events.cmdExecutionStart, data);
|
|
332
|
+
break;
|
|
333
|
+
case "cmdExecutionSuccess":
|
|
334
|
+
console.log("Sending cmdExecutionSuccess event for cmdId:", data);
|
|
335
|
+
this.sendEvent(this.events.cmdExecutionSuccess, data);
|
|
336
|
+
break;
|
|
337
|
+
case "cmdExecutionError":
|
|
338
|
+
console.log("Sending cmdExecutionError event for cmdId:", data);
|
|
339
|
+
this.sendEvent(this.events.cmdExecutionError, data);
|
|
340
|
+
break;
|
|
341
|
+
case "interceptResults":
|
|
342
|
+
console.log("Sending interceptResults event");
|
|
343
|
+
this.sendEvent(this.events.interceptResults, data);
|
|
344
|
+
break;
|
|
345
|
+
default:
|
|
346
|
+
console.warn("Unknown command execution status type:", data.type);
|
|
347
|
+
break;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
},
|
|
351
|
+
bvtContext: this.bvtContext,
|
|
352
|
+
});
|
|
351
353
|
const context = bvtContext.playContext;
|
|
352
354
|
this.context = context;
|
|
353
355
|
this.web = bvtContext.stable || bvtContext.web;
|
|
356
|
+
this.web.tryAllStrategies = true;
|
|
354
357
|
this.page = bvtContext.page;
|
|
355
|
-
|
|
356
358
|
this.pageSet.add(this.page);
|
|
357
359
|
this.lastKnownUrlPath = this._updateUrlPath();
|
|
358
360
|
const browser = await this.context.browser();
|
|
@@ -463,6 +465,75 @@ export class BVTRecorder {
|
|
|
463
465
|
}
|
|
464
466
|
});
|
|
465
467
|
}
|
|
468
|
+
|
|
469
|
+
hasHistoryReplacementAtIndex(previousEntries, currentEntries, index) {
|
|
470
|
+
if (!previousEntries || !currentEntries) return false;
|
|
471
|
+
if (index >= previousEntries.length || index >= currentEntries.length) return false;
|
|
472
|
+
|
|
473
|
+
const prevEntry = previousEntries[index];
|
|
474
|
+
// console.log("prevEntry", prevEntry);
|
|
475
|
+
const currEntry = currentEntries[index];
|
|
476
|
+
// console.log("currEntry", currEntry);
|
|
477
|
+
|
|
478
|
+
// Check if the entry at this index has been replaced
|
|
479
|
+
return prevEntry.id !== currEntry.id;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// Even simpler approach for your specific case
|
|
483
|
+
analyzeTransitionType(entries, currentIndex, currentEntry) {
|
|
484
|
+
// console.log("Analyzing transition type");
|
|
485
|
+
// console.log("===========================");
|
|
486
|
+
// console.log("Current Index:", currentIndex);
|
|
487
|
+
// console.log("Current Entry:", currentEntry);
|
|
488
|
+
// console.log("Current Entries:", entries);
|
|
489
|
+
// console.log("Current entries length:", entries.length);
|
|
490
|
+
// console.log("===========================");
|
|
491
|
+
// console.log("Previous Index:", this.previousIndex);
|
|
492
|
+
// // console.log("Previous Entry:", this.previousEntries[this.previousIndex]);
|
|
493
|
+
// console.log("Previous Entries:", this.previousEntries);
|
|
494
|
+
// console.log("Previous entries length:", this.previousHistoryLength);
|
|
495
|
+
|
|
496
|
+
if (this.previousIndex === null || this.previousHistoryLength === null || !this.previousEntries) {
|
|
497
|
+
return {
|
|
498
|
+
action: "initial",
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
const indexDiff = currentIndex - this.previousIndex;
|
|
503
|
+
const lengthDiff = entries.length - this.previousHistoryLength;
|
|
504
|
+
|
|
505
|
+
// Backward navigation
|
|
506
|
+
if (indexDiff < 0) {
|
|
507
|
+
return { action: "back" };
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// Forward navigation
|
|
511
|
+
if (indexDiff > 0 && lengthDiff === 0) {
|
|
512
|
+
// Check if the entry at current index is the same as before
|
|
513
|
+
const entryReplaced = this.hasHistoryReplacementAtIndex(this.previousEntries, entries, currentIndex);
|
|
514
|
+
|
|
515
|
+
if (entryReplaced) {
|
|
516
|
+
return { action: "navigate" }; // New navigation that replaced forward history
|
|
517
|
+
} else {
|
|
518
|
+
return { action: "forward" }; // True forward navigation
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// New navigation (history grew)
|
|
523
|
+
if (lengthDiff > 0) {
|
|
524
|
+
return { action: "navigate" };
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// Same position, same length
|
|
528
|
+
if (lengthDiff <= 0) {
|
|
529
|
+
const entryReplaced = this.hasHistoryReplacementAtIndex(this.previousEntries, entries, currentIndex);
|
|
530
|
+
|
|
531
|
+
return entryReplaced ? { action: "navigate" } : { action: "reload" };
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
return { action: "unknown" };
|
|
535
|
+
}
|
|
536
|
+
|
|
466
537
|
async getCurrentTransition() {
|
|
467
538
|
if (this?.web?.browser?._name !== "chromium") {
|
|
468
539
|
return;
|
|
@@ -471,35 +542,70 @@ export class BVTRecorder {
|
|
|
471
542
|
|
|
472
543
|
try {
|
|
473
544
|
const result = await client.send("Page.getNavigationHistory");
|
|
474
|
-
// console.log("
|
|
545
|
+
// console.log("Navigation History:", result);
|
|
475
546
|
const entries = result.entries;
|
|
476
547
|
const currentIndex = result.currentIndex;
|
|
477
548
|
|
|
478
549
|
// ignore if currentIndex is not the last entry
|
|
479
|
-
if (currentIndex !== entries.length - 1) return;
|
|
550
|
+
// if (currentIndex !== entries.length - 1) return;
|
|
480
551
|
|
|
481
552
|
const currentEntry = entries[currentIndex];
|
|
482
|
-
|
|
553
|
+
const transitionInfo = this.analyzeTransitionType(entries, currentIndex, currentEntry);
|
|
554
|
+
this.previousIndex = currentIndex;
|
|
555
|
+
this.previousHistoryLength = entries.length;
|
|
556
|
+
this.previousUrl = currentEntry.url;
|
|
557
|
+
this.previousEntries = [...entries]; // Store a copy of current entries
|
|
558
|
+
|
|
559
|
+
return {
|
|
560
|
+
currentEntry,
|
|
561
|
+
navigationAction: transitionInfo.action,
|
|
562
|
+
};
|
|
483
563
|
} catch (error) {
|
|
484
|
-
console.error("Error in getTransistionType event");
|
|
564
|
+
console.error("Error in getTransistionType event", error);
|
|
485
565
|
} finally {
|
|
486
566
|
await client.detach();
|
|
487
567
|
}
|
|
488
568
|
}
|
|
489
|
-
|
|
569
|
+
userInitiatedTransitionTypes = ["typed", "address_bar"];
|
|
490
570
|
async handlePageTransition() {
|
|
491
571
|
const transition = await this.getCurrentTransition();
|
|
492
572
|
if (!transition) return;
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
573
|
+
|
|
574
|
+
const { currentEntry, navigationAction } = transition;
|
|
575
|
+
|
|
576
|
+
switch (navigationAction) {
|
|
577
|
+
case "initial":
|
|
578
|
+
// console.log("Initial navigation, no action taken");
|
|
579
|
+
return;
|
|
580
|
+
case "navigate":
|
|
581
|
+
// console.log("transitionType", transition.transitionType);
|
|
582
|
+
// console.log("sending onGoto event", { url: currentEntry.url,
|
|
583
|
+
// type: "navigate", });
|
|
584
|
+
if (this.userInitiatedTransitionTypes.includes(currentEntry.transitionType)) {
|
|
585
|
+
const env = JSON.parse(readFileSync(this.envName), "utf8");
|
|
586
|
+
const baseUrl = env.baseUrl;
|
|
587
|
+
let url = currentEntry.userTypedURL;
|
|
588
|
+
if (baseUrl && url.startsWith(baseUrl)) {
|
|
589
|
+
url = url.replace(baseUrl, "{{env.baseUrl}}");
|
|
590
|
+
}
|
|
591
|
+
// console.log("User initiated transition");
|
|
592
|
+
this.sendEvent(this.events.onGoto, { url, type: "navigate" });
|
|
593
|
+
}
|
|
594
|
+
return;
|
|
595
|
+
case "back":
|
|
596
|
+
// console.log("User navigated back");
|
|
597
|
+
// console.log("sending onGoto event", {
|
|
598
|
+
// type: "back",
|
|
599
|
+
// });
|
|
600
|
+
this.sendEvent(this.events.onGoto, { type: "back" });
|
|
601
|
+
return;
|
|
602
|
+
case "forward":
|
|
603
|
+
// console.log("User navigated forward"); console.log("sending onGoto event", { type: "forward", });
|
|
604
|
+
this.sendEvent(this.events.onGoto, { type: "forward" });
|
|
605
|
+
return;
|
|
606
|
+
default:
|
|
607
|
+
this.sendEvent(this.events.onGoto, { type: "unknown" });
|
|
608
|
+
return;
|
|
503
609
|
}
|
|
504
610
|
}
|
|
505
611
|
|
|
@@ -628,6 +734,11 @@ export class BVTRecorder {
|
|
|
628
734
|
delete process.env.TEMP_RUN;
|
|
629
735
|
await this.watcher.close().then(() => {});
|
|
630
736
|
this.watcher = null;
|
|
737
|
+
this.previousIndex = null;
|
|
738
|
+
this.previousHistoryLength = null;
|
|
739
|
+
this.previousUrl = null;
|
|
740
|
+
this.previousEntries = null;
|
|
741
|
+
|
|
631
742
|
await closeContext();
|
|
632
743
|
this.pageSet.clear();
|
|
633
744
|
}
|
|
@@ -704,40 +815,61 @@ export class BVTRecorder {
|
|
|
704
815
|
async abortExecution() {
|
|
705
816
|
await this.stepRunner.abortExecution();
|
|
706
817
|
}
|
|
818
|
+
|
|
819
|
+
async pauseExecution({ cmdId }) {
|
|
820
|
+
await this.stepRunner.pauseExecution(cmdId);
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
async resumeExecution({ cmdId }) {
|
|
824
|
+
await this.stepRunner.resumeExecution(cmdId);
|
|
825
|
+
}
|
|
826
|
+
|
|
707
827
|
async dealyedRevertMode() {
|
|
708
828
|
const timerId = setTimeout(async () => {
|
|
709
829
|
await this.revertMode();
|
|
710
830
|
}, 100);
|
|
711
831
|
this.timerId = timerId;
|
|
712
832
|
}
|
|
713
|
-
async runStep({ step, parametersMap }, options) {
|
|
833
|
+
async runStep({ step, parametersMap, tags, isFirstStep, listenNetwork }, options) {
|
|
834
|
+
const { skipAfter = true, skipBefore = !isFirstStep } = options || {};
|
|
714
835
|
const _env = {
|
|
715
836
|
TOKEN: this.TOKEN,
|
|
716
837
|
TEMP_RUN: true,
|
|
717
838
|
REPORT_FOLDER: this.bvtContext.reportFolder,
|
|
718
839
|
BLINQ_ENV: this.envName,
|
|
840
|
+
STORE_DETAILED_NETWORK_DATA: listenNetwork ? "true" : "false",
|
|
841
|
+
CURRENT_STEP_ID: step.id,
|
|
719
842
|
};
|
|
720
843
|
|
|
721
844
|
this.bvtContext.navigate = true;
|
|
845
|
+
this.bvtContext.loadedRoutes = null;
|
|
722
846
|
for (const [key, value] of Object.entries(_env)) {
|
|
723
847
|
process.env[key] = value;
|
|
724
848
|
}
|
|
849
|
+
|
|
725
850
|
if (this.timerId) {
|
|
726
851
|
clearTimeout(this.timerId);
|
|
727
852
|
this.timerId = null;
|
|
728
853
|
}
|
|
729
854
|
await this.setMode("running");
|
|
855
|
+
|
|
730
856
|
try {
|
|
731
|
-
await this.stepRunner.runStep(
|
|
857
|
+
const { result, info } = await this.stepRunner.runStep(
|
|
732
858
|
{
|
|
733
859
|
step,
|
|
734
860
|
parametersMap,
|
|
735
861
|
envPath: this.envName,
|
|
862
|
+
tags,
|
|
863
|
+
config: this.config,
|
|
736
864
|
},
|
|
737
865
|
this.bvtContext,
|
|
738
|
-
|
|
866
|
+
{
|
|
867
|
+
skipAfter,
|
|
868
|
+
skipBefore,
|
|
869
|
+
}
|
|
739
870
|
);
|
|
740
871
|
await this.revertMode();
|
|
872
|
+
return { info };
|
|
741
873
|
} catch (error) {
|
|
742
874
|
await this.revertMode();
|
|
743
875
|
throw error;
|
|
@@ -748,15 +880,10 @@ export class BVTRecorder {
|
|
|
748
880
|
this.bvtContext.navigate = false;
|
|
749
881
|
}
|
|
750
882
|
}
|
|
751
|
-
async runScenario({ steps, parametersMap }) {
|
|
752
|
-
for (const step of steps) {
|
|
753
|
-
await this.runStep({ step, parametersMap });
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
883
|
async saveScenario({ scenario, featureName, override, isSingleStep }) {
|
|
757
884
|
await updateStepDefinitions({ scenario, featureName, projectDir: this.projectDir }); // updates mjs files
|
|
758
885
|
if (!isSingleStep) await updateFeatureFile({ featureName, scenario, override, projectDir: this.projectDir }); // updates gherkin files
|
|
759
|
-
await this.cleanup();
|
|
886
|
+
await this.cleanup({ tags: scenario.tags });
|
|
760
887
|
}
|
|
761
888
|
async getImplementedSteps() {
|
|
762
889
|
const stepsAndScenarios = await getImplementedSteps(this.projectDir);
|
|
@@ -772,7 +899,15 @@ export class BVTRecorder {
|
|
|
772
899
|
};
|
|
773
900
|
}
|
|
774
901
|
async getStepsAndCommandsForScenario({ name, featureName }) {
|
|
775
|
-
|
|
902
|
+
const steps = this.scenariosStepsMap.get(name) || [];
|
|
903
|
+
for (const step of steps) {
|
|
904
|
+
if (step.isImplemented) {
|
|
905
|
+
step.commands = this.getCommandsForImplementedStep({ stepName: step.text });
|
|
906
|
+
} else {
|
|
907
|
+
step.commands = [];
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
return steps;
|
|
776
911
|
// return getStepsAndCommandsForScenario({
|
|
777
912
|
// name,
|
|
778
913
|
// featureName,
|
|
@@ -780,6 +915,7 @@ export class BVTRecorder {
|
|
|
780
915
|
// map: this.scenariosStepsMap,
|
|
781
916
|
// });
|
|
782
917
|
}
|
|
918
|
+
|
|
783
919
|
async generateStepName({ commands, stepsNames, parameters, map }) {
|
|
784
920
|
return await this.namesService.generateStepName({ commands, stepsNames, parameters, map });
|
|
785
921
|
}
|
|
@@ -862,9 +998,9 @@ export class BVTRecorder {
|
|
|
862
998
|
}
|
|
863
999
|
}
|
|
864
1000
|
|
|
865
|
-
async discardTestData() {
|
|
1001
|
+
async discardTestData({ tags }) {
|
|
866
1002
|
resetTestData(this.envName, this.world);
|
|
867
|
-
await this.cleanup();
|
|
1003
|
+
await this.cleanup({ tags });
|
|
868
1004
|
}
|
|
869
1005
|
async addToTestData(obj) {
|
|
870
1006
|
if (!existsSync(_getDataFile(this.world, this.bvtContext, this.web))) {
|
|
@@ -903,6 +1039,7 @@ export class BVTRecorder {
|
|
|
903
1039
|
const stepParams = parseStepTextParameters(stepName);
|
|
904
1040
|
return getCommandsForImplementedStep(stepName, step_definitions, stepParams).commands;
|
|
905
1041
|
}
|
|
1042
|
+
|
|
906
1043
|
loadExistingScenario({ featureName, scenarioName }) {
|
|
907
1044
|
const step_definitions = loadStepDefinitions(this.projectDir);
|
|
908
1045
|
const featureFilePath = path.join(this.projectDir, "features", featureName);
|
|
@@ -931,6 +1068,7 @@ export class BVTRecorder {
|
|
|
931
1068
|
..._s,
|
|
932
1069
|
keyword: step.keyword.trim(),
|
|
933
1070
|
};
|
|
1071
|
+
parseRouteFiles(this.projectDir, _step);
|
|
934
1072
|
steps.push(_step);
|
|
935
1073
|
}
|
|
936
1074
|
return {
|
|
@@ -970,7 +1108,7 @@ export class BVTRecorder {
|
|
|
970
1108
|
}
|
|
971
1109
|
return result;
|
|
972
1110
|
}
|
|
973
|
-
async cleanup() {
|
|
1111
|
+
async cleanup({ tags }) {
|
|
974
1112
|
const noopStep = {
|
|
975
1113
|
text: "Noop",
|
|
976
1114
|
isImplemented: true,
|
|
@@ -984,6 +1122,7 @@ export class BVTRecorder {
|
|
|
984
1122
|
{
|
|
985
1123
|
step: noopStep,
|
|
986
1124
|
parametersMap: {},
|
|
1125
|
+
tags: tags || [],
|
|
987
1126
|
},
|
|
988
1127
|
{
|
|
989
1128
|
skipAfter: false,
|
|
@@ -1022,6 +1161,47 @@ export class BVTRecorder {
|
|
|
1022
1161
|
return false;
|
|
1023
1162
|
}
|
|
1024
1163
|
}
|
|
1164
|
+
async initExecution({ tags = [] }) {
|
|
1165
|
+
// run before hooks
|
|
1166
|
+
const noopStep = {
|
|
1167
|
+
text: "Noop",
|
|
1168
|
+
isImplemented: true,
|
|
1169
|
+
};
|
|
1170
|
+
await this.runStep(
|
|
1171
|
+
{
|
|
1172
|
+
step: noopStep,
|
|
1173
|
+
parametersMap: {},
|
|
1174
|
+
tags,
|
|
1175
|
+
},
|
|
1176
|
+
{
|
|
1177
|
+
skipBefore: false,
|
|
1178
|
+
skipAfter: true,
|
|
1179
|
+
}
|
|
1180
|
+
);
|
|
1181
|
+
}
|
|
1182
|
+
async cleanupExecution({ tags = [] }) {
|
|
1183
|
+
// run after hooks
|
|
1184
|
+
const noopStep = {
|
|
1185
|
+
text: "Noop",
|
|
1186
|
+
isImplemented: true,
|
|
1187
|
+
};
|
|
1188
|
+
await this.runStep(
|
|
1189
|
+
{
|
|
1190
|
+
step: noopStep,
|
|
1191
|
+
parametersMap: {},
|
|
1192
|
+
tags,
|
|
1193
|
+
},
|
|
1194
|
+
{
|
|
1195
|
+
skipBefore: true,
|
|
1196
|
+
skipAfter: false,
|
|
1197
|
+
}
|
|
1198
|
+
);
|
|
1199
|
+
}
|
|
1200
|
+
async resetExecution({ tags = [] }) {
|
|
1201
|
+
// run after hooks followed by before hooks
|
|
1202
|
+
await this.cleanupExecution({ tags });
|
|
1203
|
+
await this.initExecution({ tags });
|
|
1204
|
+
}
|
|
1025
1205
|
}
|
|
1026
1206
|
|
|
1027
1207
|
const parseFeatureFile = (featureFilePath) => {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { AstBuilder, GherkinClassicTokenMatcher, Parser } from "@cucumber/gherkin";
|
|
2
|
-
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
|
|
2
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync } from "fs";
|
|
3
3
|
import path from "path";
|
|
4
4
|
import url from "url";
|
|
5
5
|
import { findFilesWithExtension, StepsDefinitions } from "../cucumber/steps_definitions.js";
|
|
6
|
-
import { Feature } from "../cucumber/feature.js";
|
|
6
|
+
import { Feature, Step } from "../cucumber/feature.js";
|
|
7
7
|
import { CodePage } from "../code_gen/page_reflection.js";
|
|
8
8
|
import { getCommandsForImplementedStep, loadStepDefinitions } from "./step_utils.js";
|
|
9
9
|
import { parseStepTextParameters } from "../cucumber/utils.js";
|
|
@@ -13,7 +13,6 @@ const uuidFn = () => (++id).toString(16);
|
|
|
13
13
|
const builder = new AstBuilder(uuidFn);
|
|
14
14
|
const matcher = new GherkinClassicTokenMatcher();
|
|
15
15
|
const parser = new Parser(builder, matcher);
|
|
16
|
-
|
|
17
16
|
let i = 0;
|
|
18
17
|
const getImplId = () => {
|
|
19
18
|
return `I-${i++}`;
|
|
@@ -57,6 +56,53 @@ function memorySizeOf(obj) {
|
|
|
57
56
|
return sizeOf(obj);
|
|
58
57
|
}
|
|
59
58
|
|
|
59
|
+
export function parseRouteFiles(projectDir, step) {
|
|
60
|
+
const routeFolder = path.join(projectDir, "data", "routes");
|
|
61
|
+
const templateRouteMap = new Map();
|
|
62
|
+
|
|
63
|
+
if (!existsSync(routeFolder)) {
|
|
64
|
+
step.routeItems = null;
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Go over all the files in the route folder and parse them
|
|
69
|
+
const routeFiles = readdirSync(routeFolder).filter((file) => file.endsWith(".json"));
|
|
70
|
+
for (const file of routeFiles) {
|
|
71
|
+
const filePath = path.join(routeFolder, file);
|
|
72
|
+
const routeData = JSON.parse(readFileSync(filePath, "utf8"));
|
|
73
|
+
if (routeData && routeData.template) {
|
|
74
|
+
const template = routeData.template;
|
|
75
|
+
const routes = routeData.routes;
|
|
76
|
+
|
|
77
|
+
templateRouteMap.set(template, routes);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (!existsSync(routeFolder)) {
|
|
82
|
+
return null;
|
|
83
|
+
} else if (step && step.text) {
|
|
84
|
+
// Convert the step text to cucumber template
|
|
85
|
+
const cucumberStep = new Step();
|
|
86
|
+
cucumberStep.text = step.text;
|
|
87
|
+
const template = cucumberStep.getTemplate();
|
|
88
|
+
if (templateRouteMap.has(template)) {
|
|
89
|
+
const routeItems = templateRouteMap.get(template);
|
|
90
|
+
routeItems.forEach((item) => {
|
|
91
|
+
const filters = item.filters || {};
|
|
92
|
+
const queryParams = filters?.queryParams || {};
|
|
93
|
+
const queryParamsArray = Object.keys(queryParams).map((key) => ({
|
|
94
|
+
key: key,
|
|
95
|
+
value: queryParams[key],
|
|
96
|
+
}));
|
|
97
|
+
filters.queryParams = queryParamsArray || [];
|
|
98
|
+
});
|
|
99
|
+
step.routeItems = routeItems;
|
|
100
|
+
} else {
|
|
101
|
+
step.routeItems = null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
60
106
|
export const getImplementedSteps = async (projectDir) => {
|
|
61
107
|
const foundErrors = [];
|
|
62
108
|
try {
|
|
@@ -70,6 +116,12 @@ export const getImplementedSteps = async (projectDir) => {
|
|
|
70
116
|
const utilsContent = readFileSync(utilsTemplateFilePath, "utf8");
|
|
71
117
|
//console.log({ utilsContent });
|
|
72
118
|
writeFileSync(utilsFilePath, utilsContent, "utf8");
|
|
119
|
+
const hooksTemplateFilePath = path.join(__dirname, "../../assets", "templates", "_hooks_template.txt");
|
|
120
|
+
if (existsSync(hooksTemplateFilePath)) {
|
|
121
|
+
const hooksFilePath = path.join(stepDefinitionFolderPath, "_hooks.mjs");
|
|
122
|
+
const hooksContent = readFileSync(hooksTemplateFilePath, "utf8");
|
|
123
|
+
writeFileSync(hooksFilePath, hooksContent, "utf8");
|
|
124
|
+
}
|
|
73
125
|
} catch (error) {
|
|
74
126
|
foundErrors.push({ error });
|
|
75
127
|
}
|
|
@@ -168,7 +220,10 @@ export const getImplementedSteps = async (projectDir) => {
|
|
|
168
220
|
}
|
|
169
221
|
stepLineSet.add(stepLine);
|
|
170
222
|
step.templateIndex = implementedSteps.length;
|
|
171
|
-
|
|
223
|
+
|
|
224
|
+
parseRouteFiles(projectDir, step);
|
|
225
|
+
|
|
226
|
+
const implementedStep = {
|
|
172
227
|
keyword: step.keyword.trim(),
|
|
173
228
|
keywordAlias: step.keywordAlias?.trim(),
|
|
174
229
|
text: updateStepText(template.pattern, step.parameters),
|
|
@@ -179,7 +234,10 @@ export const getImplementedSteps = async (projectDir) => {
|
|
|
179
234
|
templateIndex: step.templateIndex,
|
|
180
235
|
pattern: template.pattern,
|
|
181
236
|
paths: template.paths,
|
|
182
|
-
|
|
237
|
+
routeItems: step.routeItems,
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
implementedSteps.push(implementedStep);
|
|
183
241
|
}
|
|
184
242
|
}
|
|
185
243
|
|
|
@@ -236,13 +294,17 @@ export const getImplementedSteps = async (projectDir) => {
|
|
|
236
294
|
for (const tag of scenario.tags) {
|
|
237
295
|
delete tag.location;
|
|
238
296
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
297
|
+
for (const scenario of scenarios) {
|
|
298
|
+
for (const step of scenario.steps) {
|
|
299
|
+
if (step.templateIndex === undefined) {
|
|
300
|
+
const cleanStepName = stepsDefinitions._stepNameToTemplate(step.text);
|
|
301
|
+
const index = implementedSteps.findIndex((istep) => {
|
|
302
|
+
return cleanStepName === istep.pattern;
|
|
303
|
+
});
|
|
304
|
+
step.templateIndex = index;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
246
308
|
}
|
|
247
309
|
if (foundErrors.length > 0) {
|
|
248
310
|
console.log("foundErrors", foundErrors);
|