@dev-blinq/cucumber_client 1.0.1267-dev → 1.0.1267-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 +159 -14224
- package/bin/assets/preload/recorderv3.js +3 -1
- package/bin/assets/scripts/dom_parent.js +4 -0
- package/bin/assets/scripts/recorder.js +8 -4
- package/bin/assets/scripts/unique_locators.js +847 -693
- 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 +1 -46
- package/bin/client/apiTest/apiTest.js +6 -0
- package/bin/client/cli_helpers.js +11 -13
- package/bin/client/code_cleanup/utils.js +5 -1
- package/bin/client/code_gen/code_inversion.js +53 -4
- package/bin/client/code_gen/page_reflection.js +838 -899
- package/bin/client/code_gen/playwright_codeget.js +52 -13
- package/bin/client/cucumber/feature.js +89 -27
- package/bin/client/cucumber/project_to_document.js +1 -1
- package/bin/client/cucumber/steps_definitions.js +84 -76
- package/bin/client/cucumber_selector.js +17 -1
- package/bin/client/local_agent.js +5 -4
- package/bin/client/project.js +186 -196
- package/bin/client/recorderv3/bvt_recorder.js +160 -61
- package/bin/client/recorderv3/implemented_steps.js +74 -16
- package/bin/client/recorderv3/index.js +47 -25
- package/bin/client/recorderv3/network.js +299 -0
- package/bin/client/recorderv3/services.js +4 -16
- package/bin/client/recorderv3/step_runner.js +331 -67
- package/bin/client/recorderv3/step_utils.js +155 -5
- package/bin/client/recorderv3/update_feature.js +32 -30
- package/bin/client/recording.js +1 -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/client/utils/socket_logger.js +132 -0
- package/bin/index.js +1 -0
- package/bin/logger.js +3 -2
- package/bin/min/consoleApi.min.cjs +2 -3
- package/bin/min/injectedScript.min.cjs +16 -16
- package/package.json +24 -14
|
@@ -3,7 +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 { getImplementedSteps,
|
|
6
|
+
import { getImplementedSteps, parseRouteFiles } from "./implemented_steps.js";
|
|
7
7
|
import { NamesService } from "./services.js";
|
|
8
8
|
import { BVTStepRunner } from "./step_runner.js";
|
|
9
9
|
import { readFile, writeFile } from "fs/promises";
|
|
@@ -12,10 +12,9 @@ import { updateFeatureFile } from "./update_feature.js";
|
|
|
12
12
|
import { parseStepTextParameters } from "../cucumber/utils.js";
|
|
13
13
|
import { AstBuilder, GherkinClassicTokenMatcher, Parser } from "@cucumber/gherkin";
|
|
14
14
|
import chokidar from "chokidar";
|
|
15
|
-
import logger from "../../logger.js";
|
|
16
15
|
import { unEscapeNonPrintables } from "../cucumber/utils.js";
|
|
17
16
|
import { findAvailablePort } from "../utils/index.js";
|
|
18
|
-
|
|
17
|
+
import socketLogger from "../utils/socket_logger.js";
|
|
19
18
|
const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
|
|
20
19
|
|
|
21
20
|
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -44,15 +43,15 @@ async function evaluate(frame, script) {
|
|
|
44
43
|
async function findNestedFrameSelector(frame, obj) {
|
|
45
44
|
try {
|
|
46
45
|
const parent = frame.parentFrame();
|
|
47
|
-
if (parent) console.log(`Parent frame: ${JSON.stringify(parent)}`);
|
|
48
46
|
if (!parent) return { children: obj };
|
|
49
47
|
const frameElement = await frame.frameElement();
|
|
50
48
|
if (!frameElement) return;
|
|
51
49
|
const selectors = await parent.evaluate((element) => {
|
|
52
|
-
return window.
|
|
50
|
+
return window.__bvt_Recorder.locatorGenerator.getElementLocators(element, { excludeText: true }).locators;
|
|
53
51
|
}, frameElement);
|
|
54
52
|
return findNestedFrameSelector(parent, { children: obj, selectors });
|
|
55
53
|
} catch (e) {
|
|
54
|
+
socketLogger.error(`Error in findNestedFrameSelector: ${e}`);
|
|
56
55
|
console.error(e);
|
|
57
56
|
}
|
|
58
57
|
}
|
|
@@ -149,6 +148,7 @@ const transformAction = (action, el, isVerify, isPopupCloseClick, isInHoverMode,
|
|
|
149
148
|
};
|
|
150
149
|
}
|
|
151
150
|
default: {
|
|
151
|
+
socketLogger.error(`Action not supported: ${action.name}`);
|
|
152
152
|
console.log("action not supported", action);
|
|
153
153
|
throw new Error("action not supported");
|
|
154
154
|
}
|
|
@@ -160,6 +160,7 @@ const transformAction = (action, el, isVerify, isPopupCloseClick, isInHoverMode,
|
|
|
160
160
|
* @property {string} projectDir
|
|
161
161
|
* @property {string} TOKEN
|
|
162
162
|
* @property {(name:string, data:any)=> void} sendEvent
|
|
163
|
+
* @property {Object} logger
|
|
163
164
|
*/
|
|
164
165
|
export class BVTRecorder {
|
|
165
166
|
#currentURL = "";
|
|
@@ -173,7 +174,6 @@ export class BVTRecorder {
|
|
|
173
174
|
*/
|
|
174
175
|
constructor(initialState) {
|
|
175
176
|
Object.assign(this, initialState);
|
|
176
|
-
this.logger = logger;
|
|
177
177
|
this.screenshotMap = new Map();
|
|
178
178
|
this.snapshotMap = new Map();
|
|
179
179
|
this.scenariosStepsMap = new Map();
|
|
@@ -183,13 +183,9 @@ export class BVTRecorder {
|
|
|
183
183
|
projectDir: this.projectDir,
|
|
184
184
|
logger: this.logger,
|
|
185
185
|
});
|
|
186
|
-
this.stepRunner = new BVTStepRunner({
|
|
187
|
-
projectDir: this.projectDir,
|
|
188
|
-
});
|
|
189
186
|
this.pageSet = new Set();
|
|
190
|
-
|
|
187
|
+
this.pageMetaDataSet = new Set();
|
|
191
188
|
this.lastKnownUrlPath = "";
|
|
192
|
-
// TODO: what is world?
|
|
193
189
|
this.world = { attach: () => {} };
|
|
194
190
|
this.shouldTakeScreenshot = true;
|
|
195
191
|
this.watcher = null;
|
|
@@ -203,12 +199,16 @@ export class BVTRecorder {
|
|
|
203
199
|
onStepDetails: "BVTRecorder.onStepDetails",
|
|
204
200
|
getTestData: "BVTRecorder.getTestData",
|
|
205
201
|
onGoto: "BVTRecorder.onGoto",
|
|
202
|
+
cmdExecutionStart: "BVTRecorder.cmdExecutionStart",
|
|
203
|
+
cmdExecutionSuccess: "BVTRecorder.cmdExecutionSuccess",
|
|
204
|
+
cmdExecutionError: "BVTRecorder.cmdExecutionError",
|
|
205
|
+
interceptResults: "BVTRecorder.interceptResults",
|
|
206
206
|
};
|
|
207
207
|
bindings = {
|
|
208
208
|
__bvt_recordCommand: async ({ frame, page, context }, event) => {
|
|
209
209
|
this.#activeFrame = frame;
|
|
210
210
|
const nestFrmLoc = await findNestedFrameSelector(frame);
|
|
211
|
-
|
|
211
|
+
this.logger.info(`Time taken for action: ${event.statistics.time}`);
|
|
212
212
|
await this.onAction({ ...event, nestFrmLoc });
|
|
213
213
|
},
|
|
214
214
|
__bvt_getMode: async () => {
|
|
@@ -227,8 +227,7 @@ export class BVTRecorder {
|
|
|
227
227
|
await this.onClosePopup();
|
|
228
228
|
},
|
|
229
229
|
__bvt_log: async (src, message) => {
|
|
230
|
-
|
|
231
|
-
console.log(`Inside Browser: ${message}`);
|
|
230
|
+
this.logger.info(`Inside Browser: ${message}`);
|
|
232
231
|
},
|
|
233
232
|
__bvt_getObject: (_src, obj) => {
|
|
234
233
|
this.processObject(obj);
|
|
@@ -293,7 +292,7 @@ export class BVTRecorder {
|
|
|
293
292
|
this.#remoteDebuggerPort = await findAvailablePort();
|
|
294
293
|
process.env.CDP_LISTEN_PORT = this.#remoteDebuggerPort;
|
|
295
294
|
|
|
296
|
-
this.stepRunner.setRemoteDebugPort(this.#remoteDebuggerPort);
|
|
295
|
+
// this.stepRunner.setRemoteDebugPort(this.#remoteDebuggerPort);
|
|
297
296
|
this.world = { attach: () => {} };
|
|
298
297
|
|
|
299
298
|
const ai_config_file = path.join(this.projectDir, "ai_config.json");
|
|
@@ -302,9 +301,10 @@ export class BVTRecorder {
|
|
|
302
301
|
try {
|
|
303
302
|
ai_config = JSON.parse(readFileSync(ai_config_file, "utf8"));
|
|
304
303
|
} catch (error) {
|
|
305
|
-
|
|
304
|
+
this.logger.error("Error reading ai_config.json", error);
|
|
306
305
|
}
|
|
307
306
|
}
|
|
307
|
+
this.config = ai_config;
|
|
308
308
|
const initScripts = {
|
|
309
309
|
// recorderCjs: injectedScriptSource,
|
|
310
310
|
scripts: [
|
|
@@ -313,16 +313,37 @@ export class BVTRecorder {
|
|
|
313
313
|
],
|
|
314
314
|
};
|
|
315
315
|
|
|
316
|
-
let startTime = Date.now();
|
|
317
316
|
const bvtContext = await initContext(url, false, false, this.world, 450, initScripts, this.envName);
|
|
318
|
-
let stopTime = Date.now();
|
|
319
|
-
this.logger.info(`Browser launched in ${(stopTime - startTime) / 1000} s`);
|
|
320
317
|
this.bvtContext = bvtContext;
|
|
318
|
+
this.stepRunner = new BVTStepRunner({
|
|
319
|
+
projectDir: this.projectDir,
|
|
320
|
+
sendExecutionStatus: (data) => {
|
|
321
|
+
if (data && data.type) {
|
|
322
|
+
switch (data.type) {
|
|
323
|
+
case "cmdExecutionStart":
|
|
324
|
+
this.sendEvent(this.events.cmdExecutionStart, data);
|
|
325
|
+
break;
|
|
326
|
+
case "cmdExecutionSuccess":
|
|
327
|
+
this.sendEvent(this.events.cmdExecutionSuccess, data);
|
|
328
|
+
break;
|
|
329
|
+
case "cmdExecutionError":
|
|
330
|
+
this.sendEvent(this.events.cmdExecutionError, data);
|
|
331
|
+
break;
|
|
332
|
+
case "interceptResults":
|
|
333
|
+
this.sendEvent(this.events.interceptResults, data);
|
|
334
|
+
break;
|
|
335
|
+
default:
|
|
336
|
+
break;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
},
|
|
340
|
+
bvtContext: this.bvtContext,
|
|
341
|
+
});
|
|
321
342
|
const context = bvtContext.playContext;
|
|
322
343
|
this.context = context;
|
|
323
344
|
this.web = bvtContext.stable || bvtContext.web;
|
|
345
|
+
this.web.tryAllStrategies = true;
|
|
324
346
|
this.page = bvtContext.page;
|
|
325
|
-
|
|
326
347
|
this.pageSet.add(this.page);
|
|
327
348
|
this.lastKnownUrlPath = this._updateUrlPath();
|
|
328
349
|
const browser = await this.context.browser();
|
|
@@ -350,13 +371,15 @@ export class BVTRecorder {
|
|
|
350
371
|
}
|
|
351
372
|
return;
|
|
352
373
|
} catch (error) {
|
|
353
|
-
console.error("Error evaluting in context:", error);
|
|
374
|
+
// console.error("Error evaluting in context:", error);
|
|
375
|
+
this.logger.error("Error evaluating in context:", error);
|
|
354
376
|
}
|
|
355
377
|
}
|
|
356
378
|
}
|
|
357
379
|
|
|
358
380
|
getMode() {
|
|
359
|
-
console.log("getMode", this.#mode);
|
|
381
|
+
// console.log("getMode", this.#mode);
|
|
382
|
+
this.logger.info("Current mode:", this.#mode);
|
|
360
383
|
return this.#mode;
|
|
361
384
|
}
|
|
362
385
|
|
|
@@ -398,6 +421,8 @@ export class BVTRecorder {
|
|
|
398
421
|
this.sendEvent(this.events.onBrowserClose);
|
|
399
422
|
}
|
|
400
423
|
} catch (error) {
|
|
424
|
+
this.logger.error("Error in page close event");
|
|
425
|
+
this.logger.error(error);
|
|
401
426
|
console.error("Error in page close event");
|
|
402
427
|
console.error(error);
|
|
403
428
|
}
|
|
@@ -408,8 +433,10 @@ export class BVTRecorder {
|
|
|
408
433
|
if (frame !== page.mainFrame()) return;
|
|
409
434
|
this.handlePageTransition();
|
|
410
435
|
} catch (error) {
|
|
436
|
+
this.logger.error("Error in handlePageTransition event");
|
|
437
|
+
this.logger.error(error);
|
|
411
438
|
console.error("Error in handlePageTransition event");
|
|
412
|
-
|
|
439
|
+
console.error(error);
|
|
413
440
|
}
|
|
414
441
|
try {
|
|
415
442
|
if (frame !== this.#activeFrame) return;
|
|
@@ -428,6 +455,8 @@ export class BVTRecorder {
|
|
|
428
455
|
// await this._setRecordingMode(frame);
|
|
429
456
|
// await this._initPage(page);
|
|
430
457
|
} catch (error) {
|
|
458
|
+
this.logger.error("Error in frame navigate event");
|
|
459
|
+
this.logger.error(error);
|
|
431
460
|
console.error("Error in frame navigate event");
|
|
432
461
|
// console.error(error);
|
|
433
462
|
}
|
|
@@ -510,13 +539,9 @@ export class BVTRecorder {
|
|
|
510
539
|
|
|
511
540
|
try {
|
|
512
541
|
const result = await client.send("Page.getNavigationHistory");
|
|
513
|
-
// console.log("Navigation History:", result);
|
|
514
542
|
const entries = result.entries;
|
|
515
543
|
const currentIndex = result.currentIndex;
|
|
516
544
|
|
|
517
|
-
// ignore if currentIndex is not the last entry
|
|
518
|
-
// if (currentIndex !== entries.length - 1) return;
|
|
519
|
-
|
|
520
545
|
const currentEntry = entries[currentIndex];
|
|
521
546
|
const transitionInfo = this.analyzeTransitionType(entries, currentIndex, currentEntry);
|
|
522
547
|
this.previousIndex = currentIndex;
|
|
@@ -529,6 +554,8 @@ export class BVTRecorder {
|
|
|
529
554
|
navigationAction: transitionInfo.action,
|
|
530
555
|
};
|
|
531
556
|
} catch (error) {
|
|
557
|
+
this.logger.error("Error in getCurrentTransition event");
|
|
558
|
+
this.logger.error(error);
|
|
532
559
|
console.error("Error in getTransistionType event", error);
|
|
533
560
|
} finally {
|
|
534
561
|
await client.detach();
|
|
@@ -597,6 +624,8 @@ export class BVTRecorder {
|
|
|
597
624
|
// add listener for frame navigation on new tab
|
|
598
625
|
this._addFrameNavigateListener(page);
|
|
599
626
|
} catch (error) {
|
|
627
|
+
this.logger.error("Error in page event");
|
|
628
|
+
this.logger.error(error);
|
|
600
629
|
console.error("Error in page event");
|
|
601
630
|
console.error(error);
|
|
602
631
|
}
|
|
@@ -638,6 +667,7 @@ export class BVTRecorder {
|
|
|
638
667
|
const { data } = await client.send("Page.captureScreenshot", { format: "png" });
|
|
639
668
|
return data;
|
|
640
669
|
} catch (error) {
|
|
670
|
+
this.logger.error("Error in taking browser screenshot");
|
|
641
671
|
console.error("Error in taking browser screenshot", error);
|
|
642
672
|
} finally {
|
|
643
673
|
await client.detach();
|
|
@@ -701,7 +731,6 @@ export class BVTRecorder {
|
|
|
701
731
|
async closeBrowser() {
|
|
702
732
|
delete process.env.TEMP_RUN;
|
|
703
733
|
await this.watcher.close().then(() => {});
|
|
704
|
-
|
|
705
734
|
this.watcher = null;
|
|
706
735
|
this.previousIndex = null;
|
|
707
736
|
this.previousHistoryLength = null;
|
|
@@ -784,40 +813,65 @@ export class BVTRecorder {
|
|
|
784
813
|
async abortExecution() {
|
|
785
814
|
await this.stepRunner.abortExecution();
|
|
786
815
|
}
|
|
816
|
+
|
|
817
|
+
async pauseExecution({ cmdId }) {
|
|
818
|
+
await this.stepRunner.pauseExecution(cmdId);
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
async resumeExecution({ cmdId }) {
|
|
822
|
+
await this.stepRunner.resumeExecution(cmdId);
|
|
823
|
+
}
|
|
824
|
+
|
|
787
825
|
async dealyedRevertMode() {
|
|
788
826
|
const timerId = setTimeout(async () => {
|
|
789
827
|
await this.revertMode();
|
|
790
828
|
}, 100);
|
|
791
829
|
this.timerId = timerId;
|
|
792
830
|
}
|
|
793
|
-
async runStep({ step, parametersMap }, options) {
|
|
831
|
+
async runStep({ step, parametersMap, tags, isFirstStep, listenNetwork }, options) {
|
|
832
|
+
const { skipAfter = true, skipBefore = !isFirstStep } = options || {};
|
|
794
833
|
const _env = {
|
|
795
834
|
TOKEN: this.TOKEN,
|
|
796
835
|
TEMP_RUN: true,
|
|
797
836
|
REPORT_FOLDER: this.bvtContext.reportFolder,
|
|
798
837
|
BLINQ_ENV: this.envName,
|
|
838
|
+
DEBUG: "blinq:route",
|
|
799
839
|
};
|
|
800
840
|
|
|
801
841
|
this.bvtContext.navigate = true;
|
|
842
|
+
this.bvtContext.loadedRoutes = null;
|
|
843
|
+
if (listenNetwork) {
|
|
844
|
+
process.env.STORE_DETAILED_NETWORK_DATA = "true";
|
|
845
|
+
} else {
|
|
846
|
+
process.env.STORE_DETAILED_NETWORK_DATA = "false";
|
|
847
|
+
}
|
|
802
848
|
for (const [key, value] of Object.entries(_env)) {
|
|
803
849
|
process.env[key] = value;
|
|
804
850
|
}
|
|
851
|
+
|
|
805
852
|
if (this.timerId) {
|
|
806
853
|
clearTimeout(this.timerId);
|
|
807
854
|
this.timerId = null;
|
|
808
855
|
}
|
|
809
856
|
await this.setMode("running");
|
|
857
|
+
|
|
810
858
|
try {
|
|
811
|
-
await this.stepRunner.runStep(
|
|
859
|
+
const { result, info } = await this.stepRunner.runStep(
|
|
812
860
|
{
|
|
813
861
|
step,
|
|
814
862
|
parametersMap,
|
|
815
863
|
envPath: this.envName,
|
|
864
|
+
tags,
|
|
865
|
+
config: this.config,
|
|
816
866
|
},
|
|
817
867
|
this.bvtContext,
|
|
818
|
-
|
|
868
|
+
{
|
|
869
|
+
skipAfter,
|
|
870
|
+
skipBefore,
|
|
871
|
+
}
|
|
819
872
|
);
|
|
820
873
|
await this.revertMode();
|
|
874
|
+
return { info };
|
|
821
875
|
} catch (error) {
|
|
822
876
|
await this.revertMode();
|
|
823
877
|
throw error;
|
|
@@ -828,15 +882,10 @@ export class BVTRecorder {
|
|
|
828
882
|
this.bvtContext.navigate = false;
|
|
829
883
|
}
|
|
830
884
|
}
|
|
831
|
-
async runScenario({ steps, parametersMap }) {
|
|
832
|
-
for (const step of steps) {
|
|
833
|
-
await this.runStep({ step, parametersMap });
|
|
834
|
-
}
|
|
835
|
-
}
|
|
836
885
|
async saveScenario({ scenario, featureName, override, isSingleStep }) {
|
|
837
886
|
await updateStepDefinitions({ scenario, featureName, projectDir: this.projectDir }); // updates mjs files
|
|
838
887
|
if (!isSingleStep) await updateFeatureFile({ featureName, scenario, override, projectDir: this.projectDir }); // updates gherkin files
|
|
839
|
-
await this.cleanup();
|
|
888
|
+
await this.cleanup({ tags: scenario.tags });
|
|
840
889
|
}
|
|
841
890
|
async getImplementedSteps() {
|
|
842
891
|
const stepsAndScenarios = await getImplementedSteps(this.projectDir);
|
|
@@ -860,6 +909,7 @@ export class BVTRecorder {
|
|
|
860
909
|
step.commands = [];
|
|
861
910
|
}
|
|
862
911
|
}
|
|
912
|
+
return steps;
|
|
863
913
|
// return getStepsAndCommandsForScenario({
|
|
864
914
|
// name,
|
|
865
915
|
// featureName,
|
|
@@ -867,6 +917,7 @@ export class BVTRecorder {
|
|
|
867
917
|
// map: this.scenariosStepsMap,
|
|
868
918
|
// });
|
|
869
919
|
}
|
|
920
|
+
|
|
870
921
|
async generateStepName({ commands, stepsNames, parameters, map }) {
|
|
871
922
|
return await this.namesService.generateStepName({ commands, stepsNames, parameters, map });
|
|
872
923
|
}
|
|
@@ -918,10 +969,11 @@ export class BVTRecorder {
|
|
|
918
969
|
if (existsSync(_getDataFile(this.world, this.bvtContext, this.web))) {
|
|
919
970
|
try {
|
|
920
971
|
const testData = JSON.parse(readFileSync(_getDataFile(this.world, this.bvtContext, this.web), "utf8"));
|
|
921
|
-
this.logger.info("Test data", testData);
|
|
972
|
+
// this.logger.info("Test data", testData);
|
|
922
973
|
this.sendEvent(this.events.getTestData, testData);
|
|
923
974
|
} catch (e) {
|
|
924
|
-
this.logger.error("Error reading test data file", e);
|
|
975
|
+
// this.logger.error("Error reading test data file", e);
|
|
976
|
+
console.log("Error reading test data file", e);
|
|
925
977
|
}
|
|
926
978
|
}
|
|
927
979
|
|
|
@@ -930,10 +982,12 @@ export class BVTRecorder {
|
|
|
930
982
|
this.watcher.on("all", async (event, path) => {
|
|
931
983
|
try {
|
|
932
984
|
const testData = JSON.parse(await readFile(_getDataFile(this.world, this.bvtContext, this.web), "utf8"));
|
|
933
|
-
this.logger.info("Test data", testData);
|
|
985
|
+
// this.logger.info("Test data", testData);
|
|
986
|
+
console.log("Test data changed", testData);
|
|
934
987
|
this.sendEvent(this.events.getTestData, testData);
|
|
935
988
|
} catch (e) {
|
|
936
|
-
this.logger.error("Error reading test data file", e);
|
|
989
|
+
// this.logger.error("Error reading test data file", e);
|
|
990
|
+
console.log("Error reading test data file", e);
|
|
937
991
|
}
|
|
938
992
|
});
|
|
939
993
|
}
|
|
@@ -949,9 +1003,9 @@ export class BVTRecorder {
|
|
|
949
1003
|
}
|
|
950
1004
|
}
|
|
951
1005
|
|
|
952
|
-
async discardTestData() {
|
|
1006
|
+
async discardTestData({ tags }) {
|
|
953
1007
|
resetTestData(this.envName, this.world);
|
|
954
|
-
await this.cleanup();
|
|
1008
|
+
await this.cleanup({ tags });
|
|
955
1009
|
}
|
|
956
1010
|
async addToTestData(obj) {
|
|
957
1011
|
if (!existsSync(_getDataFile(this.world, this.bvtContext, this.web))) {
|
|
@@ -966,7 +1020,7 @@ export class BVTRecorder {
|
|
|
966
1020
|
.filter((file) => file.endsWith(".feature"))
|
|
967
1021
|
.map((file) => path.join(this.projectDir, "features", file));
|
|
968
1022
|
try {
|
|
969
|
-
const parsedFiles = featureFiles.map((file) => parseFeatureFile(file));
|
|
1023
|
+
const parsedFiles = featureFiles.map((file) => this.parseFeatureFile(file));
|
|
970
1024
|
const output = {};
|
|
971
1025
|
parsedFiles.forEach((file) => {
|
|
972
1026
|
if (!file.feature) return;
|
|
@@ -990,10 +1044,11 @@ export class BVTRecorder {
|
|
|
990
1044
|
const stepParams = parseStepTextParameters(stepName);
|
|
991
1045
|
return getCommandsForImplementedStep(stepName, step_definitions, stepParams).commands;
|
|
992
1046
|
}
|
|
1047
|
+
|
|
993
1048
|
loadExistingScenario({ featureName, scenarioName }) {
|
|
994
1049
|
const step_definitions = loadStepDefinitions(this.projectDir);
|
|
995
1050
|
const featureFilePath = path.join(this.projectDir, "features", featureName);
|
|
996
|
-
const gherkinDoc = parseFeatureFile(featureFilePath);
|
|
1051
|
+
const gherkinDoc = this.parseFeatureFile(featureFilePath);
|
|
997
1052
|
const scenario = gherkinDoc.feature.children.find((child) => child.scenario.name === scenarioName)?.scenario;
|
|
998
1053
|
|
|
999
1054
|
const steps = [];
|
|
@@ -1018,6 +1073,7 @@ export class BVTRecorder {
|
|
|
1018
1073
|
..._s,
|
|
1019
1074
|
keyword: step.keyword.trim(),
|
|
1020
1075
|
};
|
|
1076
|
+
parseRouteFiles(this.projectDir, _step);
|
|
1021
1077
|
steps.push(_step);
|
|
1022
1078
|
}
|
|
1023
1079
|
return {
|
|
@@ -1057,7 +1113,7 @@ export class BVTRecorder {
|
|
|
1057
1113
|
}
|
|
1058
1114
|
return result;
|
|
1059
1115
|
}
|
|
1060
|
-
async cleanup() {
|
|
1116
|
+
async cleanup({ tags }) {
|
|
1061
1117
|
const noopStep = {
|
|
1062
1118
|
text: "Noop",
|
|
1063
1119
|
isImplemented: true,
|
|
@@ -1071,6 +1127,7 @@ export class BVTRecorder {
|
|
|
1071
1127
|
{
|
|
1072
1128
|
step: noopStep,
|
|
1073
1129
|
parametersMap: {},
|
|
1130
|
+
tags: tags || [],
|
|
1074
1131
|
},
|
|
1075
1132
|
{
|
|
1076
1133
|
skipAfter: false,
|
|
@@ -1109,20 +1166,62 @@ export class BVTRecorder {
|
|
|
1109
1166
|
return false;
|
|
1110
1167
|
}
|
|
1111
1168
|
}
|
|
1112
|
-
}
|
|
1169
|
+
async initExecution({ tags = [] }) {
|
|
1170
|
+
// run before hooks
|
|
1171
|
+
const noopStep = {
|
|
1172
|
+
text: "Noop",
|
|
1173
|
+
isImplemented: true,
|
|
1174
|
+
};
|
|
1175
|
+
await this.runStep(
|
|
1176
|
+
{
|
|
1177
|
+
step: noopStep,
|
|
1178
|
+
parametersMap: {},
|
|
1179
|
+
tags,
|
|
1180
|
+
},
|
|
1181
|
+
{
|
|
1182
|
+
skipBefore: false,
|
|
1183
|
+
skipAfter: true,
|
|
1184
|
+
}
|
|
1185
|
+
);
|
|
1186
|
+
}
|
|
1187
|
+
async cleanupExecution({ tags = [] }) {
|
|
1188
|
+
// run after hooks
|
|
1189
|
+
const noopStep = {
|
|
1190
|
+
text: "Noop",
|
|
1191
|
+
isImplemented: true,
|
|
1192
|
+
};
|
|
1193
|
+
await this.runStep(
|
|
1194
|
+
{
|
|
1195
|
+
step: noopStep,
|
|
1196
|
+
parametersMap: {},
|
|
1197
|
+
tags,
|
|
1198
|
+
},
|
|
1199
|
+
{
|
|
1200
|
+
skipBefore: true,
|
|
1201
|
+
skipAfter: false,
|
|
1202
|
+
}
|
|
1203
|
+
);
|
|
1204
|
+
}
|
|
1205
|
+
async resetExecution({ tags = [] }) {
|
|
1206
|
+
// run after hooks followed by before hooks
|
|
1207
|
+
await this.cleanupExecution({ tags });
|
|
1208
|
+
await this.initExecution({ tags });
|
|
1209
|
+
}
|
|
1113
1210
|
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1211
|
+
parseFeatureFile(featureFilePath) {
|
|
1212
|
+
try {
|
|
1213
|
+
let id = 0;
|
|
1214
|
+
const uuidFn = () => (++id).toString(16);
|
|
1215
|
+
const builder = new AstBuilder(uuidFn);
|
|
1216
|
+
const matcher = new GherkinClassicTokenMatcher();
|
|
1217
|
+
const parser = new Parser(builder, matcher);
|
|
1218
|
+
const source = readFileSync(featureFilePath, "utf8");
|
|
1219
|
+
const gherkinDocument = parser.parse(source);
|
|
1220
|
+
return gherkinDocument;
|
|
1221
|
+
} catch (e) {
|
|
1222
|
+
this.logger.error(`Error parsing feature file: ${featureFilePath}`);
|
|
1223
|
+
console.log(e);
|
|
1224
|
+
}
|
|
1225
|
+
return {};
|
|
1126
1226
|
}
|
|
1127
|
-
|
|
1128
|
-
};
|
|
1227
|
+
}
|
|
@@ -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,17 +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
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
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
|
+
}
|
|
250
308
|
}
|
|
251
309
|
if (foundErrors.length > 0) {
|
|
252
310
|
console.log("foundErrors", foundErrors);
|