@dev-blinq/cucumber_client 1.0.1438-dev → 1.0.1438-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 +63 -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 +46 -28
  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 +3 -2
  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 +1025 -57
  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 +21 -7
  38. package/bin/client/recorderv3/step_utils.js +540 -72
  39. package/bin/client/recorderv3/update_feature.js +86 -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,437 @@ 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
+ };
377
+ Object.assign(step, cmdID);
378
+
379
+ const locatorsObject = JSON.parse(JSON.stringify(cmd.locators ?? null));
380
+
381
+ if (!locatorsObject) return step;
382
+
383
+ const element_name = cmd?.locators?.element_name ?? `${cmd.label} ${cmd.role ?? "Text"}`;
384
+ locatorsObject.element_name = element_name;
385
+
386
+ const isValueVariable = isVariable(cmd.value);
387
+ const isTargetValueVariable = isVariable(cmd.targetValue);
388
+ const allStrategyLocators = JSON.parse(JSON.stringify(cmd?.allStrategyLocators ?? null));
389
+ step.locators = locatorsObject;
390
+ step.allStrategyLocators = allStrategyLocators;
391
+ step.isLocatorsAssigned = true;
392
+
393
+ if (!isValueVariable && !isTargetValueVariable) {
394
+ return step;
395
+ }
396
+
397
+ if (isValueVariable) {
398
+ step.dataSource = "parameters";
399
+ step.dataKey = convertToIdentifier(cmd.value.slice(1, -1));
400
+ }
401
+
402
+ if (!allStrategyLocators) {
403
+ let locs = locatorsObject.locators;
404
+ locs = parameterizeLocators({
405
+ cmd,
406
+ locs,
407
+ isValueVariable,
408
+ isTargetValueVariable,
409
+ parametersMap,
410
+ });
411
+ locatorsObject.locators = locs;
412
+ return {
413
+ ...step,
414
+ locators: locatorsObject,
415
+ };
416
+ }
417
+
418
+ for (const key in allStrategyLocators) {
419
+ if (key === "strategy") continue;
420
+ if (key === "no_text" || key === "custom") continue;
421
+ const locators = allStrategyLocators[key];
422
+ if (locators.length === 0) continue;
423
+ parameterizeLocators({
424
+ cmd,
425
+ locs: locators,
426
+ isValueVariable,
427
+ isTargetValueVariable,
428
+ parametersMap,
429
+ });
430
+ }
431
+
432
+ locatorsObject.locators = allStrategyLocators[allStrategyLocators.strategy] ?? locatorsObject.locators;
433
+
434
+ return {
435
+ ...step,
436
+ locators: locatorsObject,
437
+ allStrategyLocators,
438
+ isLocatorsAssigned: true,
439
+ };
440
+ };
441
+
17
442
  export const toMethodName = (str) => {
18
443
  // Remove any non-word characters (excluding underscore) and trim spaces
19
444
  let cleanStr = str.trim().replace(/[^\w\s]/gi, "");
@@ -36,7 +461,7 @@ export function getCodePage(stepDefsFilePath) {
36
461
  export function getCucumberStep({ step }) {
37
462
  const cucumberStep = new Step();
38
463
  cucumberStep.loadFromJson({
39
- text: step.text,
464
+ text: step.renamedText ? step.renamedText : step.text,
40
465
  keyword: step.keyword,
41
466
  keywordType: step.keywordType,
42
467
  parameters: [],
@@ -54,7 +479,7 @@ export function getCucumberStep({ step }) {
54
479
 
55
480
  function makeStepTextUnique(step, stepsDefinitions) {
56
481
  // const utilsFilePath = path.join("features", "step_definitions", "utils.mjs");
57
- let stepText = step.text;
482
+ let stepText = step.renamedText ? step.renamedText : step.text;
58
483
  let stepIndex = 1;
59
484
  // console.log("makeStepTextUnique", step.text);
60
485
  let stepDef = stepsDefinitions.findMatchingStep(stepText);
@@ -70,29 +495,38 @@ function makeStepTextUnique(step, stepsDefinitions) {
70
495
  step.text = stepText;
71
496
  }
72
497
 
73
- export async function saveRecording({ step, cucumberStep, codePage, projectDir, stepsDefinitions }) {
498
+ export async function saveRecording({
499
+ step,
500
+ cucumberStep,
501
+ codePage,
502
+ projectDir,
503
+ stepsDefinitions,
504
+ parametersMap,
505
+ logger,
506
+ }) {
507
+ if (step.commands && Array.isArray(step.commands)) {
508
+ step.commands = step.commands.map((cmd) => toRecordingStep(cmd, parametersMap, step.text));
509
+ }
74
510
  let routesPath = path.join(tmpdir(), "blinq_temp_routes");
75
511
 
76
- if (process.env.TEMP_RUN) {
512
+ if (process.env.TEMP_RUN === "true") {
77
513
  if (existsSync(routesPath)) {
78
514
  rmSync(routesPath, { recursive: true });
79
515
  }
80
516
  mkdirSync(routesPath, { recursive: true });
81
- saveRoutes({ step, folderPath: routesPath });
517
+ saveRoutes({ step, folderPath: routesPath }, logger);
82
518
  } else {
83
519
  if (existsSync(routesPath)) {
84
- // remove the folder
85
520
  try {
86
521
  rmSync(routesPath, { recursive: true });
87
- console.log("Removed temp_routes_folder:", routesPath);
88
522
  } catch (error) {
89
- console.error("Error removing temp_routes folder", error);
523
+ logger.error(`Error removing temp routes folder: ${getErrorMessage(error)}`);
90
524
  }
91
525
  routesPath = path.join(projectDir, "data", "routes");
92
526
  if (!existsSync(routesPath)) {
93
527
  mkdirSync(routesPath, { recursive: true });
94
528
  }
95
- saveRoutes({ step, folderPath: routesPath });
529
+ saveRoutes({ step, folderPath: routesPath }, logger);
96
530
  }
97
531
  }
98
532
 
@@ -101,46 +535,51 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
101
535
  }
102
536
 
103
537
  if (step.isImplemented && step.shouldOverride) {
104
- let stepDef = stepsDefinitions.findMatchingStep(step.text);
538
+ const stepDef = stepsDefinitions.findMatchingStep(step.text);
105
539
  codePage = getCodePage(stepDef.file);
106
540
  } else {
107
541
  const isUtilStep = makeStepTextUnique(step, stepsDefinitions);
108
542
 
109
543
  if (isUtilStep) {
110
- return;
544
+ if (!step.renamedText) {
545
+ return;
546
+ }
547
+ step.text = step.text.trim();
548
+ const { functionName } = stepsDefinitions.findMatchingStep(step.renamedText);
549
+ step.renamedText = functionName;
550
+ const newImportLine = `import { ${functionName} } from "./utils.mjs";\n`;
551
+
552
+ if (!codePage.fileContent.includes(newImportLine)) {
553
+ codePage.fileContent = newImportLine + codePage.fileContent;
554
+ }
111
555
  }
112
556
  }
113
557
 
558
+ routesPath = path.join(tmpdir(), "blinq_temp_routes");
114
559
  if (process.env.TEMP_RUN === "true") {
115
- console.log("Save routes in temp folder for running:", routesPath);
116
560
  if (existsSync(routesPath)) {
117
- console.log("Removing existing temp_routes_folder:", routesPath);
118
561
  rmSync(routesPath, { recursive: true });
119
562
  }
120
563
  mkdirSync(routesPath, { recursive: true });
121
- console.log("Created temp_routes_folder:", routesPath);
122
- saveRoutes({ step, folderPath: routesPath });
564
+ saveRoutes({ step, folderPath: routesPath }, logger);
123
565
  } else {
124
- console.log("Saving routes in project directory:", projectDir);
125
566
  if (existsSync(routesPath)) {
126
- // remove the folder
127
567
  try {
128
568
  rmSync(routesPath, { recursive: true });
129
- console.log("Removed temp_routes_folder:", routesPath);
130
569
  } catch (error) {
131
- console.error("Error removing temp_routes folder", error);
570
+ logger.error(`Error removing temp routes folder: ${getErrorMessage(error)}`);
132
571
  }
133
572
  }
134
573
  routesPath = path.join(projectDir, "data", "routes");
135
- console.log("Saving routes to:", routesPath);
136
574
  if (!existsSync(routesPath)) {
137
575
  mkdirSync(routesPath, { recursive: true });
138
576
  }
139
- saveRoutes({ step, folderPath: routesPath });
577
+ saveRoutes({ step, folderPath: routesPath }, logger);
140
578
  }
141
579
 
142
580
  cucumberStep.text = step.text;
143
581
  const recording = new Recording();
582
+
144
583
  const steps = step.commands;
145
584
 
146
585
  recording.loadFromObject({ steps, step: cucumberStep });
@@ -191,7 +630,7 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
191
630
  stepsDefinitions
192
631
  );
193
632
 
194
- if (!step.isImplemented) {
633
+ if (!step.renamedText && !(step.isImplemented && step.shouldOverride)) {
195
634
  stepsDefinitions.addStep({
196
635
  name: step.text,
197
636
  file: result.codePage.sourceFileName,
@@ -204,12 +643,14 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
204
643
  } else {
205
644
  const generateCodeResult = generateCode(recording, codePage, userData, projectDir, methodName);
206
645
  if (generateCodeResult.noCode === true) {
207
- logger.log("No code generated for step: " + step.text);
208
646
  return generateCodeResult.page;
209
647
  }
210
648
  codePage = generateCodeResult.page;
211
649
  methodName = generateCodeResult.methodName;
212
- codePage.insertElements(generateCodeResult.elements);
650
+
651
+ if (!step.renamedText) {
652
+ codePage.insertElements(generateCodeResult.elements);
653
+ }
213
654
 
214
655
  const description = cucumberStep.text;
215
656
  let path = null;
@@ -222,15 +663,32 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
222
663
  protect = true;
223
664
  }
224
665
  }
225
- const infraResult = codePage.addInfraCommand(
226
- methodName,
227
- description,
228
- cucumberStep.getVariablesList(),
229
- generateCodeResult.codeLines,
230
- protect,
231
- "recorder",
232
- path
233
- );
666
+
667
+ if (step.renamedText) {
668
+ codePage.addInfraCommandUtil(
669
+ methodName,
670
+ description,
671
+ cucumberStep.parameters,
672
+ generateCodeResult.codeLines,
673
+ step.renamedText,
674
+ step.text,
675
+ parametersMap,
676
+ protect,
677
+ "recorder",
678
+ path
679
+ );
680
+ } else {
681
+ codePage.addInfraCommand(
682
+ methodName,
683
+ description,
684
+ cucumberStep.getVariablesList(),
685
+ generateCodeResult.codeLines,
686
+ protect,
687
+ "recorder",
688
+ path
689
+ );
690
+ }
691
+
234
692
  const keyword = (cucumberStep.keywordAlias ?? cucumberStep.keyword).trim();
235
693
  const stepResult = codePage.addCucumberStep(
236
694
  keyword,
@@ -240,7 +698,7 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
240
698
  step.finalTimeout
241
699
  );
242
700
 
243
- if (!step.isImplemented) {
701
+ if (!step.renamedText && !(step.isImplemented && step.shouldOverride)) {
244
702
  stepsDefinitions.addStep({
245
703
  name: step.text,
246
704
  file: codePage.sourceFileName,
@@ -249,6 +707,7 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
249
707
  }
250
708
 
251
709
  codePage.removeUnusedElements();
710
+ codePage.mergeSimilarElements();
252
711
  cucumberStep.methodName = methodName;
253
712
  if (generateCodeResult.locatorsMetadata) {
254
713
  codePage.addLocatorsMetadata(generateCodeResult.locatorsMetadata);
@@ -292,7 +751,7 @@ export const getCommandsForImplementedStep = (stepName, stepsDefinitions, stepPa
292
751
  const file = step?.file;
293
752
  const locatorsJson = getLocatorsJson(file);
294
753
  if (!step) {
295
- throw new Error("Step definition not found" + stepName);
754
+ throw new Error(`Step definition not found: ${stepName}`);
296
755
  }
297
756
  isImplemented = true;
298
757
  const { codeCommands, codePage, elements, parametersNames, error } =
@@ -300,26 +759,35 @@ export const getCommandsForImplementedStep = (stepName, stepsDefinitions, stepPa
300
759
  if (error) {
301
760
  throw new Error(error);
302
761
  }
762
+ isUtilStep = codePage.sourceFileName.endsWith("utils.mjs");
303
763
 
304
764
  if (parametersNames.length !== stepParams.length) {
305
765
  // console.log("Parameters mismatch", parametersNames, stepParams);
306
766
  throw new Error("Parameters mismatch");
307
767
  }
308
- for (let i = 0; i < parametersNames.length; i++) {
309
- stepParams[i].argumentName = parametersNames[i];
310
- }
311
768
 
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];
769
+ const pattern = step.name;
770
+ if (isUtilStep && pattern === "Verify the file {string} exists") {
771
+ commands.push({
772
+ type: "verify_file_exists",
773
+ parameters: [stepParams[0].text],
774
+ });
775
+ } else {
776
+ for (let i = 0; i < parametersNames.length; i++) {
777
+ stepParams[i].argumentName = parametersNames[i];
778
+ }
779
+
780
+ for (const { code } of codeCommands) {
781
+ const command = invertCodeToCommand(code, elements, stepParams, stepsDefinitions, codePage, stepName)[0];
782
+ if (command === undefined || command.type === null) continue;
783
+ if (command.element) {
784
+ const key = command.element.key;
785
+ if (key && locatorsJson[key]) {
786
+ command.allStrategyLocators = locatorsJson[key];
787
+ }
320
788
  }
789
+ commands.push(command);
321
790
  }
322
- commands.push(command);
323
791
  }
324
792
  } catch (error) {
325
793
  console.error(error);
@@ -367,7 +835,7 @@ export async function executeStep({ stepsDefinitions, cucumberStep, context, cod
367
835
  }
368
836
  }
369
837
 
370
- export async function updateStepDefinitions({ scenario, featureName, projectDir }) {
838
+ export async function updateStepDefinitions({ scenario, featureName, projectDir, logger }) {
371
839
  // set the candidate step definition file name
372
840
  // set the utils file path
373
841
  const utilsFilePath = path.join(projectDir, "features", "step_definitions", "utils.mjs");
@@ -401,40 +869,43 @@ export async function updateStepDefinitions({ scenario, featureName, projectDir
401
869
  if ((step.isImplemented && !step.shouldOverride) || step.commands.length === 0) {
402
870
  let routesPath = path.join(tmpdir(), `blinq_temp_routes`);
403
871
  if (process.env.TEMP_RUN === "true") {
404
- console.log("Save routes in temp folder for running:", routesPath);
405
872
  if (existsSync(routesPath)) {
406
- console.log("Removing existing temp_routes_folder:", routesPath);
873
+ routesPath = path.join(tmpdir(), `blinq_temp_routes`);
407
874
  rmSync(routesPath, { recursive: true });
408
875
  }
409
876
  mkdirSync(routesPath, { recursive: true });
410
- console.log("Created temp_routes_folder:", routesPath);
411
- saveRoutes({ step, folderPath: routesPath });
877
+ saveRoutes({ step, folderPath: routesPath }, logger);
412
878
  } else {
413
- console.log("Saving routes in project directory:", projectDir);
414
879
  if (existsSync(routesPath)) {
415
- // remove the folder
416
880
  try {
417
881
  rmSync(routesPath, { recursive: true });
418
- console.log("Removed temp_routes_folder:", routesPath);
419
882
  } catch (error) {
420
- console.error("Error removing temp_routes folder", error);
883
+ logger.error(`Error removing temp routes folder: ${getErrorMessage(error)}`);
421
884
  }
422
885
  }
423
886
  routesPath = path.join(projectDir, "data", "routes");
424
- console.log("Saving routes to:", routesPath);
425
887
  if (!existsSync(routesPath)) {
426
888
  mkdirSync(routesPath, { recursive: true });
427
889
  }
428
- saveRoutes({ step, folderPath: routesPath });
890
+ saveRoutes({ step, folderPath: routesPath }, logger);
891
+ }
892
+ if (step.commands && Array.isArray(step.commands)) {
893
+ step.commands = step.commands.map((cmd) => toRecordingStep(cmd, scenario.parametersMap));
429
894
  }
430
895
  continue;
431
896
  }
432
897
  const cucumberStep = getCucumberStep({ step });
433
898
  const pageName = generatePageName(step.startFrame?.url ?? "default");
434
899
  const stepDefsFilePath = locateDefinitionPath(featureFolder, pageName);
435
- // path.join(stepDefinitionFolderPath, pageName + "_page.mjs");
436
900
  let codePage = getCodePage(stepDefsFilePath);
437
- codePage = await saveRecording({ step, cucumberStep, codePage, projectDir, stepsDefinitions });
901
+ codePage = await saveRecording({
902
+ step,
903
+ cucumberStep,
904
+ codePage,
905
+ projectDir,
906
+ stepsDefinitions,
907
+ parametersMap: scenario.parametersMap,
908
+ });
438
909
  if (!codePage) {
439
910
  continue;
440
911
  }
@@ -446,7 +917,7 @@ export async function updateStepDefinitions({ scenario, featureName, projectDir
446
917
  writeFileSync(utilsFilePath, utilsContent, "utf8");
447
918
  }
448
919
 
449
- export function saveRoutes({ step, folderPath }) {
920
+ export function saveRoutes({ step, folderPath }, logger) {
450
921
  const routeItems = step.routeItems;
451
922
  if (!routeItems || routeItems.length === 0) {
452
923
  return;
@@ -469,21 +940,18 @@ export function saveRoutes({ step, folderPath }) {
469
940
  };
470
941
  });
471
942
 
472
- const routesFilePath = path.join(folderPath, stepNameHash + ".json");
473
- console.log("Routes file path:", routesFilePath);
943
+ const routesFilePath = path.join(folderPath, `${stepNameHash}.json`);
474
944
  const routesData = {
475
945
  template,
476
946
  routes: routeItemsWithFilters,
477
947
  };
478
- console.log("Routes data to save:", routesData);
479
948
 
480
949
  if (!existsSync(folderPath)) {
481
950
  mkdirSync(folderPath, { recursive: true });
482
951
  }
483
952
  try {
484
953
  writeFileSync(routesFilePath, JSON.stringify(routesData, null, 2), "utf8");
485
- console.log("Saved routes to", routesFilePath);
486
954
  } catch (error) {
487
- console.error("Failed to save routes to", routesFilePath, "Error:", error);
955
+ logger.error(`Error saving routes to ${routesFilePath}: ${getErrorMessage(error)}`);
488
956
  }
489
957
  }