@dev-blinq/cucumber_client 1.0.1446-dev → 1.0.1446-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 (50) hide show
  1. package/bin/assets/bundled_scripts/recorder.js +73 -73
  2. package/bin/assets/preload/css_gen.js +10 -10
  3. package/bin/assets/preload/toolbar.js +27 -29
  4. package/bin/assets/preload/unique_locators.js +1 -1
  5. package/bin/assets/preload/yaml.js +288 -275
  6. package/bin/assets/scripts/aria_snapshot.js +223 -220
  7. package/bin/assets/scripts/dom_attr.js +329 -329
  8. package/bin/assets/scripts/dom_parent.js +169 -174
  9. package/bin/assets/scripts/event_utils.js +94 -94
  10. package/bin/assets/scripts/pw.js +2050 -1949
  11. package/bin/assets/scripts/recorder.js +70 -45
  12. package/bin/assets/scripts/snapshot_capturer.js +147 -147
  13. package/bin/assets/scripts/unique_locators.js +170 -49
  14. package/bin/assets/scripts/yaml.js +796 -783
  15. package/bin/assets/templates/_hooks_template.txt +6 -2
  16. package/bin/assets/templates/utils_template.txt +16 -16
  17. package/bin/client/code_cleanup/find_step_definition_references.js +0 -1
  18. package/bin/client/code_cleanup/utils.js +16 -7
  19. package/bin/client/code_gen/api_codegen.js +2 -2
  20. package/bin/client/code_gen/code_inversion.js +119 -2
  21. package/bin/client/code_gen/duplication_analysis.js +2 -1
  22. package/bin/client/code_gen/function_signature.js +4 -0
  23. package/bin/client/code_gen/page_reflection.js +52 -11
  24. package/bin/client/code_gen/playwright_codeget.js +163 -75
  25. package/bin/client/cucumber/feature.js +4 -17
  26. package/bin/client/cucumber/feature_data.js +2 -2
  27. package/bin/client/cucumber/project_to_document.js +8 -2
  28. package/bin/client/cucumber/steps_definitions.js +19 -3
  29. package/bin/client/local_agent.js +1 -0
  30. package/bin/client/parse_feature_file.js +23 -26
  31. package/bin/client/playground/projects/env.json +2 -2
  32. package/bin/client/recorderv3/bvt_init.js +305 -0
  33. package/bin/client/recorderv3/bvt_recorder.js +1024 -58
  34. package/bin/client/recorderv3/implemented_steps.js +2 -0
  35. package/bin/client/recorderv3/index.js +3 -283
  36. package/bin/client/recorderv3/services.js +818 -142
  37. package/bin/client/recorderv3/step_runner.js +25 -8
  38. package/bin/client/recorderv3/step_utils.js +572 -76
  39. package/bin/client/recorderv3/update_feature.js +87 -39
  40. package/bin/client/recorderv3/wbr_entry.js +61 -0
  41. package/bin/client/recording.js +1 -0
  42. package/bin/client/upload-service.js +4 -2
  43. package/bin/client/utils/app_dir.js +21 -0
  44. package/bin/client/utils/socket_logger.js +87 -125
  45. package/bin/index.js +4 -1
  46. package/package.json +11 -5
  47. package/bin/client/recorderv3/app_dir.js +0 -23
  48. package/bin/client/recorderv3/network.js +0 -299
  49. package/bin/client/recorderv3/scriptTest.js +0 -5
  50. package/bin/client/recorderv3/ws_server.js +0 -72
@@ -1,7 +1,6 @@
1
- import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
2
- import path from "path";
3
- import url from "url";
4
- import logger from "../../logger.js";
1
+ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
2
+ import path from "node:path";
3
+ import url from "node:url";
5
4
  import { CodePage, getAiConfig } from "../code_gen/page_reflection.js";
6
5
  import { generateCode, generatePageName } from "../code_gen/playwright_codeget.js";
7
6
  import { invertCodeToCommand } from "../code_gen/code_inversion.js";
@@ -9,11 +8,438 @@ import { Step } from "../cucumber/feature.js";
9
8
  import { locateDefinitionPath, StepsDefinitions } from "../cucumber/steps_definitions.js";
10
9
  import { Recording } from "../recording.js";
11
10
  import { generateApiCode } from "../code_gen/api_codegen.js";
12
- import { tmpdir } from "os";
13
- import { createHash } from "crypto";
11
+ import { tmpdir } from "node:os";
12
+ import { createHash } from "node:crypto";
13
+ import { getErrorMessage } from "../utils/socket_logger.js";
14
14
 
15
15
  const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
16
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, stepText) => {
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
+ valueInStepText: stepText && typeof stepText === "string" ? stepText.includes(`"${cmd.value}"`) : false,
254
+ };
255
+ }
256
+ case "conditional_wait": {
257
+ return {
258
+ type: "conditional_wait",
259
+ element: {
260
+ role: cmd.role,
261
+ name: cmd.label,
262
+ },
263
+ parameters: [cmd.timeout, cmd.selectedField, cmd.value],
264
+ lastKnownUrlPath: cmd.lastKnownUrlPath,
265
+ valueInStepText: stepText && typeof stepText === "string" ? stepText.includes(`"${cmd.value}"`) : false,
266
+ };
267
+ }
268
+ case "navigate": {
269
+ return {
270
+ type: "navigate",
271
+ parameters: [cmd.value],
272
+ lastKnownUrlPath: cmd.lastKnownUrlPath,
273
+ };
274
+ }
275
+ case "browser_go_back": {
276
+ return {
277
+ type: "browser_go_back",
278
+ };
279
+ }
280
+ case "browser_go_forward": {
281
+ return {
282
+ type: "browser_go_forward",
283
+ };
284
+ }
285
+ case "set_input_files": {
286
+ return {
287
+ type: "set_input_files",
288
+ element: {
289
+ role: cmd.role,
290
+ name: cmd.label,
291
+ },
292
+ parameters: [cmd.files],
293
+ lastKnownUrlPath: cmd.lastKnownUrlPath,
294
+ };
295
+ }
296
+ case "verify_page_snapshot": {
297
+ return {
298
+ type: "verify_page_snapshot",
299
+ parameters: [cmd.value],
300
+ selectors: cmd.selectors,
301
+ data: cmd.data,
302
+ valueInStepText: stepText && typeof stepText === "string" ? stepText.includes(`"${cmd.value}"`) : false,
303
+ };
304
+ }
305
+ default: {
306
+ return {
307
+ type: cmd.type,
308
+ parameters: [cmd.value],
309
+ lastKnownUrlPath: cmd.lastKnownUrlPath,
310
+ };
311
+ }
312
+ }
313
+ };
314
+
315
+ function getBestStrategy(allStrategyLocators) {
316
+ const orderedPriorities = ["custom", "context", "basic", "text_with_index", "ignore_digit", "no_text"];
317
+ for (const strategy of orderedPriorities) {
318
+ if (allStrategyLocators[strategy] && allStrategyLocators[strategy].length > 0) {
319
+ return strategy;
320
+ }
321
+ }
322
+ return null;
323
+ }
324
+
325
+ const _parameterizeLocators = (locators, replacementFromValue, replacementToValue) => {
326
+ for (const loc of locators) {
327
+ if (loc?.css?.includes(replacementFromValue)) {
328
+ loc.css = loc.css.replaceAll(replacementFromValue, replacementToValue);
329
+ }
330
+ if (loc?.text?.includes(replacementFromValue)) {
331
+ loc.text = loc.text.replaceAll(replacementFromValue, replacementToValue);
332
+ }
333
+ if (loc?.climb && typeof loc.climb === "string" && loc.climb?.includes(replacementFromValue)) {
334
+ loc.climb = loc.climb.replaceAll(replacementFromValue, replacementToValue);
335
+ }
336
+ }
337
+ return locators;
338
+ };
339
+ const parameterizeLocators = ({ cmd, locs, isValueVariable, isTargetValueVariable, parametersMap }) => {
340
+ if (isValueVariable) {
341
+ const variable = cmd.value.slice(1, -1);
342
+ // const val = parametersMap[variable];
343
+ if (typeof cmd.text === "string") {
344
+ const replacementFromValue = cmd.text.trim().replace(/\s+/g, " ") ?? ""; // val.trim();
345
+ if (replacementFromValue.length > 0) {
346
+ const replacementToValue = `{${variable}}`;
347
+ locs = _parameterizeLocators(locs, replacementFromValue, replacementToValue);
348
+ }
349
+ }
350
+ }
351
+ if (isTargetValueVariable) {
352
+ const variable = cmd.targetValue.slice(1, -1);
353
+ // const val = parametersMap[variable];
354
+ if (typeof cmd.targetText === "string") {
355
+ const replacementFromValue = cmd.targetText.trim().replace(/\s+/g, " ") ?? ""; // val.trim();
356
+ if (replacementFromValue.length > 0) {
357
+ const replacementToValue = `{${variable}}`;
358
+ locs = _parameterizeLocators(locs, replacementFromValue, replacementToValue);
359
+ }
360
+ }
361
+ }
362
+ return locs;
363
+ };
364
+
365
+ //TODO: IMPORTAN
366
+ export const toRecordingStep = (cmd, parametersMap, stepText) => {
367
+ if (cmd.type === "api") {
368
+ return {
369
+ type: "api",
370
+ value: cmd.value,
371
+ };
372
+ }
373
+ const step = _toRecordingStep(cmd, stepText);
374
+ const cmdID = {
375
+ cmdId: cmd.id,
376
+ options: cmd.options ?? null,
377
+ };
378
+ Object.assign(step, cmdID);
379
+
380
+ const locatorsObject = JSON.parse(JSON.stringify(cmd.locators ?? null));
381
+
382
+ if (!locatorsObject) return step;
383
+
384
+ const element_name = cmd?.locators?.element_name ?? `${cmd.label} ${cmd.role ?? "Text"}`;
385
+ locatorsObject.element_name = element_name;
386
+
387
+ const isValueVariable = isVariable(cmd.value);
388
+ const isTargetValueVariable = isVariable(cmd.targetValue);
389
+ const allStrategyLocators = JSON.parse(JSON.stringify(cmd?.allStrategyLocators ?? null));
390
+ step.locators = locatorsObject;
391
+ step.allStrategyLocators = allStrategyLocators;
392
+ step.isLocatorsAssigned = true;
393
+
394
+ if (!isValueVariable && !isTargetValueVariable) {
395
+ return step;
396
+ }
397
+
398
+ if (isValueVariable) {
399
+ step.dataSource = "parameters";
400
+ step.dataKey = convertToIdentifier(cmd.value.slice(1, -1));
401
+ }
402
+
403
+ if (!allStrategyLocators) {
404
+ let locs = locatorsObject.locators;
405
+ locs = parameterizeLocators({
406
+ cmd,
407
+ locs,
408
+ isValueVariable,
409
+ isTargetValueVariable,
410
+ parametersMap,
411
+ });
412
+ locatorsObject.locators = locs;
413
+ return {
414
+ ...step,
415
+ locators: locatorsObject,
416
+ };
417
+ }
418
+
419
+ for (const key in allStrategyLocators) {
420
+ if (key === "strategy") continue;
421
+ if (key === "no_text" || key === "custom") continue;
422
+ const locators = allStrategyLocators[key];
423
+ if (locators.length === 0) continue;
424
+ parameterizeLocators({
425
+ cmd,
426
+ locs: locators,
427
+ isValueVariable,
428
+ isTargetValueVariable,
429
+ parametersMap,
430
+ });
431
+ }
432
+
433
+ locatorsObject.locators = allStrategyLocators[allStrategyLocators.strategy] ?? locatorsObject.locators;
434
+
435
+ return {
436
+ ...step,
437
+ locators: locatorsObject,
438
+ allStrategyLocators,
439
+ isLocatorsAssigned: true,
440
+ };
441
+ };
442
+
17
443
  export const toMethodName = (str) => {
18
444
  // Remove any non-word characters (excluding underscore) and trim spaces
19
445
  let cleanStr = str.trim().replace(/[^\w\s]/gi, "");
@@ -36,7 +462,7 @@ export function getCodePage(stepDefsFilePath) {
36
462
  export function getCucumberStep({ step }) {
37
463
  const cucumberStep = new Step();
38
464
  cucumberStep.loadFromJson({
39
- text: step.text,
465
+ text: step.renamedText ? step.renamedText : step.text,
40
466
  keyword: step.keyword,
41
467
  keywordType: step.keywordType,
42
468
  parameters: [],
@@ -54,12 +480,16 @@ export function getCucumberStep({ step }) {
54
480
 
55
481
  function makeStepTextUnique(step, stepsDefinitions) {
56
482
  // const utilsFilePath = path.join("features", "step_definitions", "utils.mjs");
57
- let stepText = step.text;
483
+ let stepText = step.renamedText ? step.renamedText : step.text;
58
484
  let stepIndex = 1;
59
485
  // console.log("makeStepTextUnique", step.text);
60
486
  let stepDef = stepsDefinitions.findMatchingStep(stepText);
61
487
  // console.log({ stepDef });
62
- if (stepDef && stepDef?.file.endsWith("utils.mjs")) {
488
+ if (
489
+ stepDef &&
490
+ (stepDef?.file.endsWith("utils.mjs") || stepDef?.file.endsWith("default_page.mjs")) &&
491
+ !step.shouldMakeStepTextUnique
492
+ ) {
63
493
  return true;
64
494
  }
65
495
  while (stepDef) {
@@ -70,29 +500,38 @@ function makeStepTextUnique(step, stepsDefinitions) {
70
500
  step.text = stepText;
71
501
  }
72
502
 
73
- export async function saveRecording({ step, cucumberStep, codePage, projectDir, stepsDefinitions }) {
503
+ export async function saveRecording({
504
+ step,
505
+ cucumberStep,
506
+ codePage,
507
+ projectDir,
508
+ stepsDefinitions,
509
+ parametersMap,
510
+ logger,
511
+ }) {
512
+ if (step.commands && Array.isArray(step.commands)) {
513
+ step.commands = step.commands.map((cmd) => toRecordingStep(cmd, parametersMap, step.text));
514
+ }
74
515
  let routesPath = path.join(tmpdir(), "blinq_temp_routes");
75
516
 
76
- if (process.env.TEMP_RUN) {
517
+ if (process.env.TEMP_RUN === "true") {
77
518
  if (existsSync(routesPath)) {
78
519
  rmSync(routesPath, { recursive: true });
79
520
  }
80
521
  mkdirSync(routesPath, { recursive: true });
81
- saveRoutes({ step, folderPath: routesPath });
522
+ saveRoutes({ step, folderPath: routesPath }, logger);
82
523
  } else {
83
524
  if (existsSync(routesPath)) {
84
- // remove the folder
85
525
  try {
86
526
  rmSync(routesPath, { recursive: true });
87
- console.log("Removed temp_routes_folder:", routesPath);
88
527
  } catch (error) {
89
- console.error("Error removing temp_routes folder", error);
528
+ logger.error(`Error removing temp routes folder: ${getErrorMessage(error)}`);
90
529
  }
91
530
  routesPath = path.join(projectDir, "data", "routes");
92
531
  if (!existsSync(routesPath)) {
93
532
  mkdirSync(routesPath, { recursive: true });
94
533
  }
95
- saveRoutes({ step, folderPath: routesPath });
534
+ saveRoutes({ step, folderPath: routesPath }, logger);
96
535
  }
97
536
  }
98
537
 
@@ -100,47 +539,63 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
100
539
  return;
101
540
  }
102
541
 
542
+ let isUtilStep = false;
543
+ let isChangedUtilStepName = false;
544
+
103
545
  if (step.isImplemented && step.shouldOverride) {
104
- let stepDef = stepsDefinitions.findMatchingStep(step.text);
546
+ const stepDef = stepsDefinitions.findMatchingStep(step.text);
105
547
  codePage = getCodePage(stepDef.file);
106
548
  } else {
107
- const isUtilStep = makeStepTextUnique(step, stepsDefinitions);
549
+ isUtilStep = makeStepTextUnique(step, stepsDefinitions);
108
550
 
109
551
  if (isUtilStep) {
110
- return;
552
+ isChangedUtilStepName =
553
+ step.renamedText && stepsDefinitions.findMatchingStep(step.renamedText)?.file.endsWith("default_page.mjs");
554
+
555
+ if (!isChangedUtilStepName) {
556
+ const isUtilStep = stepsDefinitions.findMatchingStep(step.text)?.file.endsWith("utils.mjs");
557
+ if (!step.renamedText || step.renamedText === step.text || isUtilStep) {
558
+ return;
559
+ }
560
+ step.text = step.text.trim();
561
+ const { functionName } = stepsDefinitions.findMatchingStep(step.renamedText);
562
+ step.renamedText = functionName;
563
+ const newImportLine = `import { ${functionName} } from "./utils.mjs";\n`;
564
+
565
+ if (!codePage.fileContent.includes(newImportLine)) {
566
+ codePage.fileContent = newImportLine + codePage.fileContent;
567
+ }
568
+ }
111
569
  }
112
570
  }
113
571
 
572
+ const renamedUtil = step.renamedText && isUtilStep;
573
+
574
+ routesPath = path.join(tmpdir(), "blinq_temp_routes");
114
575
  if (process.env.TEMP_RUN === "true") {
115
- console.log("Save routes in temp folder for running:", routesPath);
116
576
  if (existsSync(routesPath)) {
117
- console.log("Removing existing temp_routes_folder:", routesPath);
118
577
  rmSync(routesPath, { recursive: true });
119
578
  }
120
579
  mkdirSync(routesPath, { recursive: true });
121
- console.log("Created temp_routes_folder:", routesPath);
122
- saveRoutes({ step, folderPath: routesPath });
580
+ saveRoutes({ step, folderPath: routesPath }, logger);
123
581
  } else {
124
- console.log("Saving routes in project directory:", projectDir);
125
582
  if (existsSync(routesPath)) {
126
- // remove the folder
127
583
  try {
128
584
  rmSync(routesPath, { recursive: true });
129
- console.log("Removed temp_routes_folder:", routesPath);
130
585
  } catch (error) {
131
- console.error("Error removing temp_routes folder", error);
586
+ logger.error(`Error removing temp routes folder: ${getErrorMessage(error)}`);
132
587
  }
133
588
  }
134
589
  routesPath = path.join(projectDir, "data", "routes");
135
- console.log("Saving routes to:", routesPath);
136
590
  if (!existsSync(routesPath)) {
137
591
  mkdirSync(routesPath, { recursive: true });
138
592
  }
139
- saveRoutes({ step, folderPath: routesPath });
593
+ saveRoutes({ step, folderPath: routesPath }, logger);
140
594
  }
141
595
 
142
596
  cucumberStep.text = step.text;
143
597
  const recording = new Recording();
598
+
144
599
  const steps = step.commands;
145
600
 
146
601
  recording.loadFromObject({ steps, step: cucumberStep });
@@ -191,7 +646,7 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
191
646
  stepsDefinitions
192
647
  );
193
648
 
194
- if (!step.isImplemented) {
649
+ if (!renamedUtil && !(step.isImplemented && step.shouldOverride)) {
195
650
  stepsDefinitions.addStep({
196
651
  name: step.text,
197
652
  file: result.codePage.sourceFileName,
@@ -203,13 +658,16 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
203
658
  return result.codePage;
204
659
  } else {
205
660
  const generateCodeResult = generateCode(recording, codePage, userData, projectDir, methodName);
206
- if (generateCodeResult.noCode === true) {
207
- logger.log("No code generated for step: " + step.text);
661
+ console.log("Generated code for step:", step.text);
662
+ if (!isUtilStep && generateCodeResult.noCode === true) {
208
663
  return generateCodeResult.page;
209
664
  }
210
665
  codePage = generateCodeResult.page;
211
666
  methodName = generateCodeResult.methodName;
212
- codePage.insertElements(generateCodeResult.elements);
667
+
668
+ if (!renamedUtil) {
669
+ codePage.insertElements(generateCodeResult.elements);
670
+ }
213
671
 
214
672
  const description = cucumberStep.text;
215
673
  let path = null;
@@ -222,15 +680,43 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
222
680
  protect = true;
223
681
  }
224
682
  }
225
- const infraResult = codePage.addInfraCommand(
226
- methodName,
227
- description,
228
- cucumberStep.getVariablesList(),
229
- generateCodeResult.codeLines,
230
- protect,
231
- "recorder",
232
- path
233
- );
683
+
684
+ if (renamedUtil) {
685
+ if (isChangedUtilStepName) {
686
+ const newFileContent = codePage.fileContent
687
+ .replace(`async function ${step.renamedText}`, `async function ${methodName}`)
688
+ .replace(`Then("${step.renamedText}"`, `// Then("${step.renamedText}"`)
689
+ .replace(`When("${step.renamedText}"`, `// When("${step.renamedText}"`)
690
+ .replace(`Given("${step.renamedText}"`, `// Given("${step.renamedText}"`);
691
+
692
+ codePage._init();
693
+ codePage.generateModel(newFileContent);
694
+ } else {
695
+ codePage.addInfraCommandUtil(
696
+ methodName,
697
+ description,
698
+ cucumberStep.parameters,
699
+ generateCodeResult.codeLines,
700
+ step.renamedText,
701
+ step.text,
702
+ parametersMap,
703
+ protect,
704
+ "recorder",
705
+ path
706
+ );
707
+ }
708
+ } else {
709
+ codePage.addInfraCommand(
710
+ methodName,
711
+ description,
712
+ cucumberStep.getVariablesList(),
713
+ generateCodeResult.codeLines,
714
+ protect,
715
+ "recorder",
716
+ path
717
+ );
718
+ }
719
+
234
720
  const keyword = (cucumberStep.keywordAlias ?? cucumberStep.keyword).trim();
235
721
  const stepResult = codePage.addCucumberStep(
236
722
  keyword,
@@ -240,7 +726,7 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
240
726
  step.finalTimeout
241
727
  );
242
728
 
243
- if (!step.isImplemented) {
729
+ if (!renamedUtil && !(step.isImplemented && step.shouldOverride)) {
244
730
  stepsDefinitions.addStep({
245
731
  name: step.text,
246
732
  file: codePage.sourceFileName,
@@ -249,6 +735,7 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
249
735
  }
250
736
 
251
737
  codePage.removeUnusedElements();
738
+ codePage.mergeSimilarElements();
252
739
  cucumberStep.methodName = methodName;
253
740
  if (generateCodeResult.locatorsMetadata) {
254
741
  codePage.addLocatorsMetadata(generateCodeResult.locatorsMetadata);
@@ -292,7 +779,7 @@ export const getCommandsForImplementedStep = (stepName, stepsDefinitions, stepPa
292
779
  const file = step?.file;
293
780
  const locatorsJson = getLocatorsJson(file);
294
781
  if (!step) {
295
- throw new Error("Step definition not found" + stepName);
782
+ throw new Error(`Step definition not found: ${stepName}`);
296
783
  }
297
784
  isImplemented = true;
298
785
  const { codeCommands, codePage, elements, parametersNames, error } =
@@ -300,26 +787,35 @@ export const getCommandsForImplementedStep = (stepName, stepsDefinitions, stepPa
300
787
  if (error) {
301
788
  throw new Error(error);
302
789
  }
790
+ isUtilStep = codePage.sourceFileName.endsWith("utils.mjs") || codePage.sourceFileName.endsWith("default_page.mjs");
303
791
 
304
792
  if (parametersNames.length !== stepParams.length) {
305
793
  // console.log("Parameters mismatch", parametersNames, stepParams);
306
794
  throw new Error("Parameters mismatch");
307
795
  }
308
- for (let i = 0; i < parametersNames.length; i++) {
309
- stepParams[i].argumentName = parametersNames[i];
310
- }
311
796
 
312
- isUtilStep = codePage.sourceFileName.endsWith("utils.mjs");
313
- for (const { code } of codeCommands) {
314
- const command = invertCodeToCommand(code, elements, stepParams, stepsDefinitions, codePage, stepName)[0];
315
- if (command === undefined || command.type === null) continue;
316
- if (command.element) {
317
- const key = command.element.key;
318
- if (key && locatorsJson[key]) {
319
- command.allStrategyLocators = locatorsJson[key];
797
+ const pattern = step.name;
798
+ if (isUtilStep && pattern === "Verify the file {string} exists") {
799
+ commands.push({
800
+ type: "verify_file_exists",
801
+ parameters: [stepParams[0].text],
802
+ });
803
+ } else {
804
+ for (let i = 0; i < parametersNames.length; i++) {
805
+ stepParams[i].argumentName = parametersNames[i];
806
+ }
807
+
808
+ for (const { code } of codeCommands) {
809
+ const command = invertCodeToCommand(code, elements, stepParams, stepsDefinitions, codePage, stepName)[0];
810
+ if (command === undefined || command.type === null) continue;
811
+ if (command.element) {
812
+ const key = command.element.key;
813
+ if (key && locatorsJson[key]) {
814
+ command.allStrategyLocators = locatorsJson[key];
815
+ }
320
816
  }
817
+ commands.push(command);
321
818
  }
322
- commands.push(command);
323
819
  }
324
820
  } catch (error) {
325
821
  console.error(error);
@@ -367,7 +863,7 @@ export async function executeStep({ stepsDefinitions, cucumberStep, context, cod
367
863
  }
368
864
  }
369
865
 
370
- export async function updateStepDefinitions({ scenario, featureName, projectDir }) {
866
+ export async function updateStepDefinitions({ scenario, featureName, projectDir, logger }) {
371
867
  // set the candidate step definition file name
372
868
  // set the utils file path
373
869
  const utilsFilePath = path.join(projectDir, "features", "step_definitions", "utils.mjs");
@@ -398,43 +894,46 @@ export async function updateStepDefinitions({ scenario, featureName, projectDir
398
894
  step.isImplementedWhileRecording = true;
399
895
  }
400
896
  }
401
- if ((step.isImplemented && !step.shouldOverride) || step.commands.length === 0) {
897
+ if (!step.isUtilStep && ((step.isImplemented && !step.shouldOverride) || step.commands.length === 0)) {
402
898
  let routesPath = path.join(tmpdir(), `blinq_temp_routes`);
403
899
  if (process.env.TEMP_RUN === "true") {
404
- console.log("Save routes in temp folder for running:", routesPath);
405
900
  if (existsSync(routesPath)) {
406
- console.log("Removing existing temp_routes_folder:", routesPath);
901
+ routesPath = path.join(tmpdir(), `blinq_temp_routes`);
407
902
  rmSync(routesPath, { recursive: true });
408
903
  }
409
904
  mkdirSync(routesPath, { recursive: true });
410
- console.log("Created temp_routes_folder:", routesPath);
411
- saveRoutes({ step, folderPath: routesPath });
905
+ saveRoutes({ step, folderPath: routesPath }, logger);
412
906
  } else {
413
- console.log("Saving routes in project directory:", projectDir);
414
907
  if (existsSync(routesPath)) {
415
- // remove the folder
416
908
  try {
417
909
  rmSync(routesPath, { recursive: true });
418
- console.log("Removed temp_routes_folder:", routesPath);
419
910
  } catch (error) {
420
- console.error("Error removing temp_routes folder", error);
911
+ logger.error(`Error removing temp routes folder: ${getErrorMessage(error)}`);
421
912
  }
422
913
  }
423
914
  routesPath = path.join(projectDir, "data", "routes");
424
- console.log("Saving routes to:", routesPath);
425
915
  if (!existsSync(routesPath)) {
426
916
  mkdirSync(routesPath, { recursive: true });
427
917
  }
428
- saveRoutes({ step, folderPath: routesPath });
918
+ saveRoutes({ step, folderPath: routesPath }, logger);
919
+ }
920
+ if (step.commands && Array.isArray(step.commands)) {
921
+ step.commands = step.commands.map((cmd) => toRecordingStep(cmd, scenario.parametersMap));
429
922
  }
430
923
  continue;
431
924
  }
432
925
  const cucumberStep = getCucumberStep({ step });
433
926
  const pageName = generatePageName(step.startFrame?.url ?? "default");
434
927
  const stepDefsFilePath = locateDefinitionPath(featureFolder, pageName);
435
- // path.join(stepDefinitionFolderPath, pageName + "_page.mjs");
436
928
  let codePage = getCodePage(stepDefsFilePath);
437
- codePage = await saveRecording({ step, cucumberStep, codePage, projectDir, stepsDefinitions });
929
+ codePage = await saveRecording({
930
+ step,
931
+ cucumberStep,
932
+ codePage,
933
+ projectDir,
934
+ stepsDefinitions,
935
+ parametersMap: scenario.parametersMap,
936
+ });
438
937
  if (!codePage) {
439
938
  continue;
440
939
  }
@@ -446,7 +945,7 @@ export async function updateStepDefinitions({ scenario, featureName, projectDir
446
945
  writeFileSync(utilsFilePath, utilsContent, "utf8");
447
946
  }
448
947
 
449
- export function saveRoutes({ step, folderPath }) {
948
+ export function saveRoutes({ step, folderPath }, logger) {
450
949
  const routeItems = step.routeItems;
451
950
  if (!routeItems || routeItems.length === 0) {
452
951
  return;
@@ -469,21 +968,18 @@ export function saveRoutes({ step, folderPath }) {
469
968
  };
470
969
  });
471
970
 
472
- const routesFilePath = path.join(folderPath, stepNameHash + ".json");
473
- console.log("Routes file path:", routesFilePath);
971
+ const routesFilePath = path.join(folderPath, `${stepNameHash}.json`);
474
972
  const routesData = {
475
973
  template,
476
974
  routes: routeItemsWithFilters,
477
975
  };
478
- console.log("Routes data to save:", routesData);
479
976
 
480
977
  if (!existsSync(folderPath)) {
481
978
  mkdirSync(folderPath, { recursive: true });
482
979
  }
483
980
  try {
484
981
  writeFileSync(routesFilePath, JSON.stringify(routesData, null, 2), "utf8");
485
- console.log("Saved routes to", routesFilePath);
486
982
  } catch (error) {
487
- console.error("Failed to save routes to", routesFilePath, "Error:", error);
983
+ logger.error(`Error saving routes to ${routesFilePath}: ${getErrorMessage(error)}`);
488
984
  }
489
985
  }