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