@dev-blinq/cucumber_client 1.0.1283-dev → 1.0.1283-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.
Files changed (53) hide show
  1. package/bin/assets/bundled_scripts/recorder.js +106 -106
  2. package/bin/assets/preload/css_gen.js +10 -10
  3. package/bin/assets/preload/recorderv3.js +3 -1
  4. package/bin/assets/preload/toolbar.js +27 -29
  5. package/bin/assets/preload/unique_locators.js +1 -1
  6. package/bin/assets/preload/yaml.js +288 -275
  7. package/bin/assets/scripts/aria_snapshot.js +223 -220
  8. package/bin/assets/scripts/dom_attr.js +329 -329
  9. package/bin/assets/scripts/dom_parent.js +169 -170
  10. package/bin/assets/scripts/event_utils.js +94 -94
  11. package/bin/assets/scripts/pw.js +2050 -1949
  12. package/bin/assets/scripts/recorder.js +7 -4
  13. package/bin/assets/scripts/snapshot_capturer.js +153 -146
  14. package/bin/assets/scripts/unique_locators.js +830 -815
  15. package/bin/assets/scripts/yaml.js +796 -783
  16. package/bin/assets/templates/_hooks_template.txt +37 -0
  17. package/bin/assets/templates/page_template.txt +2 -16
  18. package/bin/assets/templates/utils_template.txt +1 -46
  19. package/bin/client/apiTest/apiTest.js +6 -0
  20. package/bin/client/cli_helpers.js +11 -13
  21. package/bin/client/code_cleanup/utils.js +5 -1
  22. package/bin/client/code_gen/api_codegen.js +2 -2
  23. package/bin/client/code_gen/code_inversion.js +53 -4
  24. package/bin/client/code_gen/page_reflection.js +839 -902
  25. package/bin/client/code_gen/playwright_codeget.js +43 -12
  26. package/bin/client/cucumber/feature.js +89 -27
  27. package/bin/client/cucumber/feature_data.js +2 -2
  28. package/bin/client/cucumber/project_to_document.js +9 -3
  29. package/bin/client/cucumber/steps_definitions.js +90 -84
  30. package/bin/client/cucumber_selector.js +17 -1
  31. package/bin/client/local_agent.js +6 -5
  32. package/bin/client/parse_feature_file.js +23 -26
  33. package/bin/client/playground/projects/env.json +2 -2
  34. package/bin/client/project.js +186 -196
  35. package/bin/client/recorderv3/bvt_recorder.js +170 -60
  36. package/bin/client/recorderv3/implemented_steps.js +74 -16
  37. package/bin/client/recorderv3/index.js +57 -25
  38. package/bin/client/recorderv3/network.js +299 -0
  39. package/bin/client/recorderv3/scriptTest.js +1 -1
  40. package/bin/client/recorderv3/services.js +4 -16
  41. package/bin/client/recorderv3/step_runner.js +331 -68
  42. package/bin/client/recorderv3/step_utils.js +574 -7
  43. package/bin/client/recorderv3/update_feature.js +32 -30
  44. package/bin/client/run_cucumber.js +5 -1
  45. package/bin/client/scenario_report.js +0 -5
  46. package/bin/client/test_scenario.js +0 -1
  47. package/bin/client/upload-service.js +2 -2
  48. package/bin/client/utils/socket_logger.js +132 -0
  49. package/bin/index.js +1 -0
  50. package/bin/logger.js +3 -2
  51. package/bin/min/consoleApi.min.cjs +2 -3
  52. package/bin/min/injectedScript.min.cjs +16 -16
  53. package/package.json +22 -13
@@ -1,4 +1,4 @@
1
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
1
+ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
2
2
  import path from "path";
3
3
  import url from "url";
4
4
  import logger from "../../logger.js";
@@ -9,9 +9,420 @@ import { Step } from "../cucumber/feature.js";
9
9
  import { locateDefinitionPath, StepsDefinitions } from "../cucumber/steps_definitions.js";
10
10
  import { Recording } from "../recording.js";
11
11
  import { generateApiCode } from "../code_gen/api_codegen.js";
12
+ import { tmpdir } from "os";
13
+ import { createHash } from "crypto";
12
14
 
13
15
  const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
14
16
 
17
+ const convertToIdentifier = (text) => {
18
+ // replace all invalid characters with _
19
+ return text.replace(/[^a-zA-Z0-9_]/g, "_");
20
+ };
21
+
22
+ export const isVariable = (text) => {
23
+ if (typeof text !== "string") return false;
24
+ const isParametric = text.startsWith("<") && text.endsWith(">");
25
+ if (!isParametric) return false;
26
+ const l = text.length;
27
+ if (l < 2) return false;
28
+ const leftindex = text.indexOf("<");
29
+ const rightindex = text.indexOf(">");
30
+ return leftindex === 0 && rightindex === l - 1;
31
+ };
32
+
33
+ export const extractQuotes = (text) => {
34
+ const stringRegex = /"([^"]*)"/g;
35
+ const matches = text.match(stringRegex);
36
+ if (!matches) return [];
37
+ const quotes = [];
38
+ for (const match of matches) {
39
+ const value = match.slice(1, -1);
40
+ quotes.push(value);
41
+ }
42
+ return quotes;
43
+ };
44
+
45
+ const replaceLastOccurence = (str, search, replacement) => {
46
+ const lastIndex = str.lastIndexOf(search);
47
+ if (lastIndex === -1) return str;
48
+ return str.substring(0, lastIndex) + replacement + str.substring(lastIndex + search.length);
49
+ };
50
+
51
+ const _toRecordingStep = (cmd) => {
52
+ switch (cmd.type) {
53
+ case "hover_element": {
54
+ return {
55
+ type: "hover_element",
56
+ element: {
57
+ role: cmd.role,
58
+ name: cmd.label,
59
+ },
60
+ lastKnownUrlPath: cmd.lastKnownUrlPath,
61
+ };
62
+ }
63
+ case "click_element": {
64
+ return {
65
+ type: "click_element",
66
+ element: {
67
+ role: cmd.role,
68
+ name: cmd.label,
69
+ },
70
+ lastKnownUrlPath: cmd.lastKnownUrlPath,
71
+ count: cmd.count ?? 1,
72
+ };
73
+ }
74
+ case "context_click": {
75
+ return {
76
+ type: "context_click",
77
+ element: {
78
+ role: cmd.role,
79
+ name: cmd.label,
80
+ },
81
+ label: cmd.label,
82
+ value: cmd.value,
83
+ lastKnownUrlPath: cmd.lastKnownUrlPath,
84
+ text: cmd.text,
85
+ count: cmd.count ?? 1,
86
+ };
87
+ }
88
+ case "parameterized_click": {
89
+ return {
90
+ type: "parameterized_click",
91
+ element: {
92
+ role: cmd.role,
93
+ name: cmd.label,
94
+ },
95
+ label: cmd.label,
96
+ value: cmd.value,
97
+ lastKnownUrlPath: cmd.lastKnownUrlPath,
98
+ count: cmd.count ?? 1,
99
+ };
100
+ }
101
+ case "fill_element": {
102
+ return {
103
+ type: "fill_element",
104
+ element: {
105
+ role: cmd.role,
106
+ name: cmd.label,
107
+ },
108
+ parameters: [cmd.value, cmd.enter ?? false],
109
+ lastKnownUrlPath: cmd.lastKnownUrlPath,
110
+ };
111
+ }
112
+ case "select_combobox": {
113
+ return {
114
+ type: "select_combobox",
115
+ element: {
116
+ role: "combobox",
117
+ name: cmd.label,
118
+ },
119
+ selectMode: "select",
120
+ parameters: [cmd.value],
121
+ lastKnownUrlPath: cmd.lastKnownUrlPath,
122
+ };
123
+ }
124
+ case "verify_page_contains_text": {
125
+ return {
126
+ type: "verify_page_contains_text",
127
+ parameters: [cmd.value, cmd.isRegex],
128
+ lastKnownUrlPath: cmd.lastKnownUrlPath,
129
+ };
130
+ }
131
+ case "verify_element_contains_text": {
132
+ return {
133
+ type: "verify_element_contains_text",
134
+ element: {
135
+ role: cmd.role,
136
+ name: cmd.label,
137
+ },
138
+ parameters: [cmd.value, cmd.climb],
139
+ lastKnownUrlPath: cmd.lastKnownUrlPath,
140
+ };
141
+ }
142
+ case "close_page": {
143
+ return {
144
+ type: "close_page",
145
+ lastKnownUrlPath: cmd.lastKnownUrlPath,
146
+ };
147
+ }
148
+ case "check_element": {
149
+ return {
150
+ type: "check_element",
151
+ element: {
152
+ role: cmd.role,
153
+ name: cmd.label,
154
+ },
155
+ check: cmd.check,
156
+ lastKnownUrlPath: cmd.lastKnownUrlPath,
157
+ };
158
+ }
159
+ case "press_key": {
160
+ return {
161
+ type: "press_key",
162
+ element: {
163
+ role: cmd.role,
164
+ name: cmd.label,
165
+ },
166
+ key: cmd.value,
167
+ lastKnownUrlPath: cmd.lastKnownUrlPath,
168
+ };
169
+ }
170
+ case "load_user": {
171
+ return {
172
+ type: "load_data",
173
+ parameters: ["users", cmd.value],
174
+ lastKnownUrlPath: cmd.lastKnownUrlPath,
175
+ };
176
+ }
177
+ case "load_csv": {
178
+ return {
179
+ type: "load_data",
180
+ parameters: ["csv", `${cmd.label}:${cmd.value}`],
181
+ lastKnownUrlPath: cmd.lastKnownUrlPath,
182
+ };
183
+ }
184
+ case "set_date_time": {
185
+ return {
186
+ type: "set_date_time",
187
+ element: {
188
+ role: cmd.role,
189
+ name: cmd.label,
190
+ },
191
+ parameters: [cmd.value],
192
+ lastKnownUrlPath: cmd.lastKnownUrlPath,
193
+ };
194
+ }
195
+ case "set_input": {
196
+ return {
197
+ type: "set_input",
198
+ element: {
199
+ role: cmd.role,
200
+ name: cmd.label,
201
+ },
202
+ value: cmd.value,
203
+ parameters: [cmd.value],
204
+ lastKnownUrlPath: cmd.lastKnownUrlPath,
205
+ };
206
+ }
207
+ case "extract_attribute": {
208
+ return {
209
+ type: "extract_attribute",
210
+ element: {
211
+ role: cmd.role,
212
+ name: cmd.label,
213
+ },
214
+ parameters: [cmd.selectedField, cmd.variableName],
215
+ lastKnownUrlPath: cmd.lastKnownUrlPath,
216
+ regex: cmd.regex,
217
+ trimSpaces: cmd.trimSpaces,
218
+ };
219
+ }
220
+ case "extract_property": {
221
+ return {
222
+ type: "extract_property",
223
+ element: {
224
+ role: cmd.role,
225
+ name: cmd.label,
226
+ },
227
+ parameters: [cmd.selectedField, cmd.variableName],
228
+ lastKnownUrlPath: cmd.lastKnownUrlPath,
229
+ regex: cmd.regex,
230
+ trimSpaces: cmd.trimSpaces,
231
+ };
232
+ }
233
+ case "verify_element_attribute": {
234
+ return {
235
+ type: "verify_element_attribute",
236
+ element: {
237
+ role: cmd.role,
238
+ name: cmd.label,
239
+ },
240
+ parameters: [cmd.selectedField, cmd.value],
241
+ lastKnownUrlPath: cmd.lastKnownUrlPath,
242
+ };
243
+ }
244
+ case "verify_element_property": {
245
+ return {
246
+ type: "verify_element_property",
247
+ element: {
248
+ role: cmd.role,
249
+ name: cmd.label,
250
+ },
251
+ parameters: [cmd.selectedField, cmd.value],
252
+ lastKnownUrlPath: cmd.lastKnownUrlPath,
253
+ };
254
+ }
255
+ case "conditional_wait": {
256
+ return {
257
+ type: "conditional_wait",
258
+ element: {
259
+ role: cmd.role,
260
+ name: cmd.label,
261
+ },
262
+ parameters: [cmd.timeout, cmd.selectedField, cmd.value],
263
+ lastKnownUrlPath: cmd.lastKnownUrlPath,
264
+ };
265
+ }
266
+ case "navigate": {
267
+ return {
268
+ type: "navigate",
269
+ parameters: [cmd.value],
270
+ lastKnownUrlPath: cmd.lastKnownUrlPath,
271
+ };
272
+ }
273
+ case "browser_go_back": {
274
+ return {
275
+ type: "browser_go_back",
276
+ };
277
+ }
278
+ case "browser_go_forward": {
279
+ return {
280
+ type: "browser_go_forward",
281
+ };
282
+ }
283
+ case "set_input_files": {
284
+ return {
285
+ type: "set_input_files",
286
+ element: {
287
+ role: cmd.role,
288
+ name: cmd.label,
289
+ },
290
+ parameters: [cmd.files],
291
+ lastKnownUrlPath: cmd.lastKnownUrlPath,
292
+ };
293
+ }
294
+ case "verify_page_snapshot": {
295
+ return {
296
+ type: "verify_page_snapshot",
297
+ parameters: [cmd.value],
298
+ selectors: cmd.selectors,
299
+ };
300
+ }
301
+ default: {
302
+ return {
303
+ type: cmd.type,
304
+ parameters: [cmd.value],
305
+ lastKnownUrlPath: cmd.lastKnownUrlPath,
306
+ };
307
+ }
308
+ }
309
+ };
310
+
311
+ function getBestStrategy(allStrategyLocators) {
312
+ const orderedPriorities = ["custom", "context", "basic", "text_with_index", "ignore_digit", "no_text"];
313
+ for (const strategy of orderedPriorities) {
314
+ if (allStrategyLocators[strategy] && allStrategyLocators[strategy].length > 0) {
315
+ return strategy;
316
+ }
317
+ }
318
+ return null;
319
+ }
320
+
321
+ const _parameterizeLocators = (locators, replacementFromValue, replacementToValue) => {
322
+ for (const loc of locators) {
323
+ if (loc?.css?.includes(replacementFromValue)) {
324
+ loc.css = loc.css.replaceAll(replacementFromValue, replacementToValue);
325
+ }
326
+ if (loc?.text?.includes(replacementFromValue)) {
327
+ loc.text = loc.text.replaceAll(replacementFromValue, replacementToValue);
328
+ }
329
+ if (loc?.climb && typeof loc.climb === "string" && loc.climb?.includes(replacementFromValue)) {
330
+ loc.climb = loc.climb.replaceAll(replacementFromValue, replacementToValue);
331
+ }
332
+ }
333
+ return locators;
334
+ };
335
+ const parameterizeLocators = ({ cmd, locs, isValueVariable, isTextVariable, parametersMap }) => {
336
+ if (isValueVariable) {
337
+ const variable = cmd.value.slice(1, -1);
338
+ const val = parametersMap[variable];
339
+ const replacementFromValue = val.trim();
340
+ const replacementToValue = `{${variable}}`;
341
+ locs = _parameterizeLocators(locs, replacementFromValue, replacementToValue);
342
+ }
343
+ if (isTextVariable) {
344
+ const variable = cmd.text.slice(1, -1);
345
+ const val = parametersMap[variable];
346
+ const replacementFromValue = val.trim();
347
+ const replacementToValue = `{${variable}}`;
348
+ locs = _parameterizeLocators(locs, replacementFromValue, replacementToValue);
349
+ }
350
+ return locs;
351
+ };
352
+
353
+ //TODO: IMPORTAN
354
+ export const toRecordingStep = (cmd, parametersMap) => {
355
+ if (cmd.type === "api") {
356
+ return {
357
+ type: "api",
358
+ value: cmd.value,
359
+ };
360
+ }
361
+ const step = _toRecordingStep(cmd);
362
+ const cmdID = {
363
+ cmdId: cmd.id,
364
+ };
365
+ Object.assign(step, cmdID);
366
+
367
+ const locatorsObject = JSON.parse(JSON.stringify(cmd.locators ?? null));
368
+
369
+ if (!locatorsObject) return step;
370
+ const isValueVariable = isVariable(cmd.value);
371
+ const isTextVariable = isVariable(cmd.text);
372
+ const allStrategyLocators = JSON.parse(JSON.stringify(cmd?.allStrategyLocators ?? null));
373
+ step.locators = locatorsObject;
374
+ step.allStrategyLocators = allStrategyLocators;
375
+ step.isLocatorsAssigned = true;
376
+
377
+ if (!isValueVariable && !isTextVariable) {
378
+ return step;
379
+ }
380
+
381
+ if (isValueVariable) {
382
+ step.dataSource = "parameters";
383
+ step.dataKey = convertToIdentifier(cmd.value.slice(1, -1));
384
+ }
385
+
386
+ if (!allStrategyLocators) {
387
+ let locs = locatorsObject.locators;
388
+ locs = parameterizeLocators({
389
+ cmd,
390
+ locs,
391
+ isValueVariable,
392
+ isTextVariable,
393
+ parametersMap,
394
+ });
395
+ locatorsObject.locators = locs;
396
+ return {
397
+ ...step,
398
+ locators: locatorsObject,
399
+ };
400
+ }
401
+
402
+ for (const key in allStrategyLocators) {
403
+ if (key === "strategy") continue;
404
+ if (key === "no_text" || key === "custom") continue;
405
+ const locators = allStrategyLocators[key];
406
+ if (locators.length === 0) continue;
407
+ parameterizeLocators({
408
+ cmd,
409
+ locs: locators,
410
+ isValueVariable,
411
+ isTextVariable,
412
+ parametersMap,
413
+ });
414
+ }
415
+
416
+ locatorsObject.locators = allStrategyLocators[allStrategyLocators.strategy] ?? locatorsObject.locators;
417
+
418
+ return {
419
+ ...step,
420
+ locators: locatorsObject,
421
+ allStrategyLocators,
422
+ isLocatorsAssigned: true,
423
+ };
424
+ };
425
+
15
426
  export const toMethodName = (str) => {
16
427
  // Remove any non-word characters (excluding underscore) and trim spaces
17
428
  let cleanStr = str.trim().replace(/[^\w\s]/gi, "");
@@ -68,22 +479,79 @@ function makeStepTextUnique(step, stepsDefinitions) {
68
479
  step.text = stepText;
69
480
  }
70
481
 
71
- export async function saveRecording({ step, cucumberStep, codePage, projectDir, stepsDefinitions }) {
72
- // console.log("saveRecording", step.text);
482
+ export async function saveRecording({ step, cucumberStep, codePage, projectDir, stepsDefinitions, parametersMap }) {
483
+ let routesPath = path.join(tmpdir(), "blinq_temp_routes");
484
+
485
+ if (process.env.TEMP_RUN === "true") {
486
+ if (existsSync(routesPath)) {
487
+ rmSync(routesPath, { recursive: true });
488
+ }
489
+ mkdirSync(routesPath, { recursive: true });
490
+ saveRoutes({ step, folderPath: routesPath });
491
+ } else {
492
+ if (existsSync(routesPath)) {
493
+ // remove the folder
494
+ try {
495
+ rmSync(routesPath, { recursive: true });
496
+ console.log("Removed temp_routes_folder:", routesPath);
497
+ } catch (error) {
498
+ console.error("Error removing temp_routes folder", error);
499
+ }
500
+ routesPath = path.join(projectDir, "data", "routes");
501
+ if (!existsSync(routesPath)) {
502
+ mkdirSync(routesPath, { recursive: true });
503
+ }
504
+ saveRoutes({ step, folderPath: routesPath });
505
+ }
506
+ }
507
+
73
508
  if (step.isImplementedWhileRecording && !process.env.TEMP_RUN) {
74
509
  return;
75
510
  }
511
+
76
512
  if (step.isImplemented && step.shouldOverride) {
77
513
  let stepDef = stepsDefinitions.findMatchingStep(step.text);
78
514
  codePage = getCodePage(stepDef.file);
79
515
  } else {
80
516
  const isUtilStep = makeStepTextUnique(step, stepsDefinitions);
517
+
81
518
  if (isUtilStep) {
82
519
  return;
83
520
  }
84
521
  }
522
+
523
+ routesPath = path.join(tmpdir(), "blinq_temp_routes");
524
+ if (process.env.TEMP_RUN === "true") {
525
+ console.log("Save routes in temp folder for running:", routesPath);
526
+ if (existsSync(routesPath)) {
527
+ console.log("Removing existing temp_routes_folder:", routesPath);
528
+ rmSync(routesPath, { recursive: true });
529
+ }
530
+ mkdirSync(routesPath, { recursive: true });
531
+ console.log("Created temp_routes_folder:", routesPath);
532
+ saveRoutes({ step, folderPath: routesPath });
533
+ } else {
534
+ console.log("Saving routes in project directory:", projectDir);
535
+ if (existsSync(routesPath)) {
536
+ // remove the folder
537
+ try {
538
+ rmSync(routesPath, { recursive: true });
539
+ console.log("Removed temp_routes_folder:", routesPath);
540
+ } catch (error) {
541
+ console.error("Error removing temp_routes folder", error);
542
+ }
543
+ }
544
+ routesPath = path.join(projectDir, "data", "routes");
545
+ console.log("Saving routes to:", routesPath);
546
+ if (!existsSync(routesPath)) {
547
+ mkdirSync(routesPath, { recursive: true });
548
+ }
549
+ saveRoutes({ step, folderPath: routesPath });
550
+ }
551
+
85
552
  cucumberStep.text = step.text;
86
553
  const recording = new Recording();
554
+ step.commands = step.commands.map((cmd) => toRecordingStep(cmd, parametersMap));
87
555
  const steps = step.commands;
88
556
 
89
557
  recording.loadFromObject({ steps, step: cucumberStep });
@@ -108,6 +576,7 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
108
576
  isStaticToken,
109
577
  status,
110
578
  } = step.commands[0].value;
579
+
111
580
  const result = await generateApiCode(
112
581
  {
113
582
  url,
@@ -132,6 +601,7 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
132
601
  step.keyword,
133
602
  stepsDefinitions
134
603
  );
604
+
135
605
  if (!step.isImplemented) {
136
606
  stepsDefinitions.addStep({
137
607
  name: step.text,
@@ -139,6 +609,7 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
139
609
  source: "recorder",
140
610
  });
141
611
  }
612
+
142
613
  cucumberStep.methodName = result.methodName;
143
614
  return result.codePage;
144
615
  } else {
@@ -156,17 +627,29 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
156
627
  if (step.commands && step.commands.length > 0 && step.commands[0]) {
157
628
  path = step.commands[0].lastKnownUrlPath;
158
629
  }
630
+ let protect = false;
631
+ if (step.commands && step.commands.length > 0 && step.commands[0].type) {
632
+ if (step.commands[0].type === "verify_element_property" || step.commands[0].type === "conditional_wait") {
633
+ protect = true;
634
+ }
635
+ }
159
636
  const infraResult = codePage.addInfraCommand(
160
637
  methodName,
161
638
  description,
162
639
  cucumberStep.getVariablesList(),
163
640
  generateCodeResult.codeLines,
164
- false,
641
+ protect,
165
642
  "recorder",
166
643
  path
167
644
  );
168
645
  const keyword = (cucumberStep.keywordAlias ?? cucumberStep.keyword).trim();
169
- const stepResult = codePage.addCucumberStep(keyword, cucumberStep.getTemplate(), methodName, steps.length);
646
+ const stepResult = codePage.addCucumberStep(
647
+ keyword,
648
+ cucumberStep.getTemplate(),
649
+ methodName,
650
+ steps.length,
651
+ step.finalTimeout
652
+ );
170
653
 
171
654
  if (!step.isImplemented) {
172
655
  stepsDefinitions.addStep({
@@ -177,6 +660,7 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
177
660
  }
178
661
 
179
662
  codePage.removeUnusedElements();
663
+ codePage.mergeSimilarElements();
180
664
  cucumberStep.methodName = methodName;
181
665
  if (generateCodeResult.locatorsMetadata) {
182
666
  codePage.addLocatorsMetadata(generateCodeResult.locatorsMetadata);
@@ -306,6 +790,12 @@ export async function updateStepDefinitions({ scenario, featureName, projectDir
306
790
  const utilsTemplateFilePath = path.join(__dirname, "../../assets", "templates", "utils_template.txt");
307
791
  const utilsContent = readFileSync(utilsTemplateFilePath, "utf8");
308
792
  writeFileSync(utilsFilePath, utilsContent, "utf8");
793
+ const hooksTemplateFilePath = path.join(__dirname, "../../assets", "templates", "_hooks_template.txt");
794
+ if (existsSync(hooksTemplateFilePath)) {
795
+ const hooksFilePath = path.join(stepDefinitionFolderPath, "_hooks.mjs");
796
+ const hooksContent = readFileSync(hooksTemplateFilePath, "utf8");
797
+ writeFileSync(hooksFilePath, hooksContent, "utf8");
798
+ }
309
799
  const steps = scenario.steps;
310
800
 
311
801
  const stepsDefinitions = new StepsDefinitions(projectDir);
@@ -321,6 +811,35 @@ export async function updateStepDefinitions({ scenario, featureName, projectDir
321
811
  }
322
812
  }
323
813
  if ((step.isImplemented && !step.shouldOverride) || step.commands.length === 0) {
814
+ let routesPath = path.join(tmpdir(), `blinq_temp_routes`);
815
+ if (process.env.TEMP_RUN === "true") {
816
+ console.log("Save routes in temp folder for running:", routesPath);
817
+ if (existsSync(routesPath)) {
818
+ console.log("Removing existing temp_routes_folder:", routesPath);
819
+ routesPath = path.join(tmpdir(), `blinq_temp_routes`);
820
+ rmSync(routesPath, { recursive: true });
821
+ }
822
+ mkdirSync(routesPath, { recursive: true });
823
+ console.log("Created temp_routes_folder:", routesPath);
824
+ saveRoutes({ step, folderPath: routesPath });
825
+ } else {
826
+ console.log("Saving routes in project directory:", projectDir);
827
+ if (existsSync(routesPath)) {
828
+ // remove the folder
829
+ try {
830
+ rmSync(routesPath, { recursive: true });
831
+ console.log("Removed temp_routes_folder:", routesPath);
832
+ } catch (error) {
833
+ console.error("Error removing temp_routes folder", error);
834
+ }
835
+ }
836
+ routesPath = path.join(projectDir, "data", "routes");
837
+ console.log("Saving routes to:", routesPath);
838
+ if (!existsSync(routesPath)) {
839
+ mkdirSync(routesPath, { recursive: true });
840
+ }
841
+ saveRoutes({ step, folderPath: routesPath });
842
+ }
324
843
  continue;
325
844
  }
326
845
  const cucumberStep = getCucumberStep({ step });
@@ -328,8 +847,14 @@ export async function updateStepDefinitions({ scenario, featureName, projectDir
328
847
  const stepDefsFilePath = locateDefinitionPath(featureFolder, pageName);
329
848
  // path.join(stepDefinitionFolderPath, pageName + "_page.mjs");
330
849
  let codePage = getCodePage(stepDefsFilePath);
331
-
332
- codePage = await saveRecording({ step, cucumberStep, codePage, projectDir, stepsDefinitions });
850
+ codePage = await saveRecording({
851
+ step,
852
+ cucumberStep,
853
+ codePage,
854
+ projectDir,
855
+ stepsDefinitions,
856
+ parametersMap: scenario.parametersMap,
857
+ });
333
858
  if (!codePage) {
334
859
  continue;
335
860
  }
@@ -340,3 +865,45 @@ export async function updateStepDefinitions({ scenario, featureName, projectDir
340
865
  }
341
866
  writeFileSync(utilsFilePath, utilsContent, "utf8");
342
867
  }
868
+
869
+ export function saveRoutes({ step, folderPath }) {
870
+ const routeItems = step.routeItems;
871
+ if (!routeItems || routeItems.length === 0) {
872
+ return;
873
+ }
874
+ const cucumberStep = getCucumberStep({ step });
875
+ const template = cucumberStep.getTemplate();
876
+ const stepNameHash = createHash("sha256").update(template).digest("hex");
877
+ console.log("Saving routes for step:", step.text, "with hash:", stepNameHash);
878
+
879
+ const routeItemsWithFilters = routeItems.map((routeItem) => {
880
+ const oldFilters = routeItem.filters;
881
+ const queryParamsObject = {};
882
+ oldFilters.queryParams.forEach((queryParam) => {
883
+ queryParamsObject[queryParam.key] = queryParam.value;
884
+ });
885
+ const newFilters = { path: oldFilters.path, method: oldFilters.method, queryParams: queryParamsObject };
886
+ return {
887
+ ...routeItem,
888
+ filters: newFilters,
889
+ };
890
+ });
891
+
892
+ const routesFilePath = path.join(folderPath, stepNameHash + ".json");
893
+ console.log("Routes file path:", routesFilePath);
894
+ const routesData = {
895
+ template,
896
+ routes: routeItemsWithFilters,
897
+ };
898
+ console.log("Routes data to save:", routesData);
899
+
900
+ if (!existsSync(folderPath)) {
901
+ mkdirSync(folderPath, { recursive: true });
902
+ }
903
+ try {
904
+ writeFileSync(routesFilePath, JSON.stringify(routesData, null, 2), "utf8");
905
+ console.log("Saved routes to", routesFilePath);
906
+ } catch (error) {
907
+ console.error("Failed to save routes to", routesFilePath, "Error:", error);
908
+ }
909
+ }