@dev-blinq/cucumber_client 1.0.1445-dev → 1.0.1445-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 +571 -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,62 @@ 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
+ if (!step.renamedText || step.renamedText === step.text) {
557
+ return;
558
+ }
559
+ step.text = step.text.trim();
560
+ const { functionName } = stepsDefinitions.findMatchingStep(step.renamedText);
561
+ step.renamedText = functionName;
562
+ const newImportLine = `import { ${functionName} } from "./utils.mjs";\n`;
563
+
564
+ if (!codePage.fileContent.includes(newImportLine)) {
565
+ codePage.fileContent = newImportLine + codePage.fileContent;
566
+ }
567
+ }
111
568
  }
112
569
  }
113
570
 
571
+ const renamedUtil = step.renamedText && isUtilStep;
572
+
573
+ routesPath = path.join(tmpdir(), "blinq_temp_routes");
114
574
  if (process.env.TEMP_RUN === "true") {
115
- console.log("Save routes in temp folder for running:", routesPath);
116
575
  if (existsSync(routesPath)) {
117
- console.log("Removing existing temp_routes_folder:", routesPath);
118
576
  rmSync(routesPath, { recursive: true });
119
577
  }
120
578
  mkdirSync(routesPath, { recursive: true });
121
- console.log("Created temp_routes_folder:", routesPath);
122
- saveRoutes({ step, folderPath: routesPath });
579
+ saveRoutes({ step, folderPath: routesPath }, logger);
123
580
  } else {
124
- console.log("Saving routes in project directory:", projectDir);
125
581
  if (existsSync(routesPath)) {
126
- // remove the folder
127
582
  try {
128
583
  rmSync(routesPath, { recursive: true });
129
- console.log("Removed temp_routes_folder:", routesPath);
130
584
  } catch (error) {
131
- console.error("Error removing temp_routes folder", error);
585
+ logger.error(`Error removing temp routes folder: ${getErrorMessage(error)}`);
132
586
  }
133
587
  }
134
588
  routesPath = path.join(projectDir, "data", "routes");
135
- console.log("Saving routes to:", routesPath);
136
589
  if (!existsSync(routesPath)) {
137
590
  mkdirSync(routesPath, { recursive: true });
138
591
  }
139
- saveRoutes({ step, folderPath: routesPath });
592
+ saveRoutes({ step, folderPath: routesPath }, logger);
140
593
  }
141
594
 
142
595
  cucumberStep.text = step.text;
143
596
  const recording = new Recording();
597
+
144
598
  const steps = step.commands;
145
599
 
146
600
  recording.loadFromObject({ steps, step: cucumberStep });
@@ -191,7 +645,7 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
191
645
  stepsDefinitions
192
646
  );
193
647
 
194
- if (!step.isImplemented) {
648
+ if (!renamedUtil && !(step.isImplemented && step.shouldOverride)) {
195
649
  stepsDefinitions.addStep({
196
650
  name: step.text,
197
651
  file: result.codePage.sourceFileName,
@@ -203,13 +657,16 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
203
657
  return result.codePage;
204
658
  } else {
205
659
  const generateCodeResult = generateCode(recording, codePage, userData, projectDir, methodName);
206
- if (generateCodeResult.noCode === true) {
207
- logger.log("No code generated for step: " + step.text);
660
+ console.log("Generated code for step:", step.text);
661
+ if (!isUtilStep && generateCodeResult.noCode === true) {
208
662
  return generateCodeResult.page;
209
663
  }
210
664
  codePage = generateCodeResult.page;
211
665
  methodName = generateCodeResult.methodName;
212
- codePage.insertElements(generateCodeResult.elements);
666
+
667
+ if (!renamedUtil) {
668
+ codePage.insertElements(generateCodeResult.elements);
669
+ }
213
670
 
214
671
  const description = cucumberStep.text;
215
672
  let path = null;
@@ -222,15 +679,43 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
222
679
  protect = true;
223
680
  }
224
681
  }
225
- const infraResult = codePage.addInfraCommand(
226
- methodName,
227
- description,
228
- cucumberStep.getVariablesList(),
229
- generateCodeResult.codeLines,
230
- protect,
231
- "recorder",
232
- path
233
- );
682
+
683
+ if (renamedUtil) {
684
+ if (isChangedUtilStepName) {
685
+ const newFileContent = codePage.fileContent
686
+ .replace(`async function ${step.renamedText}`, `async function ${methodName}`)
687
+ .replace(`Then("${step.renamedText}"`, `// Then("${step.renamedText}"`)
688
+ .replace(`When("${step.renamedText}"`, `// When("${step.renamedText}"`)
689
+ .replace(`Given("${step.renamedText}"`, `// Given("${step.renamedText}"`);
690
+
691
+ codePage._init();
692
+ codePage.generateModel(newFileContent);
693
+ } else {
694
+ codePage.addInfraCommandUtil(
695
+ methodName,
696
+ description,
697
+ cucumberStep.parameters,
698
+ generateCodeResult.codeLines,
699
+ step.renamedText,
700
+ step.text,
701
+ parametersMap,
702
+ protect,
703
+ "recorder",
704
+ path
705
+ );
706
+ }
707
+ } else {
708
+ codePage.addInfraCommand(
709
+ methodName,
710
+ description,
711
+ cucumberStep.getVariablesList(),
712
+ generateCodeResult.codeLines,
713
+ protect,
714
+ "recorder",
715
+ path
716
+ );
717
+ }
718
+
234
719
  const keyword = (cucumberStep.keywordAlias ?? cucumberStep.keyword).trim();
235
720
  const stepResult = codePage.addCucumberStep(
236
721
  keyword,
@@ -240,7 +725,7 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
240
725
  step.finalTimeout
241
726
  );
242
727
 
243
- if (!step.isImplemented) {
728
+ if (!renamedUtil && !(step.isImplemented && step.shouldOverride)) {
244
729
  stepsDefinitions.addStep({
245
730
  name: step.text,
246
731
  file: codePage.sourceFileName,
@@ -249,6 +734,7 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
249
734
  }
250
735
 
251
736
  codePage.removeUnusedElements();
737
+ codePage.mergeSimilarElements();
252
738
  cucumberStep.methodName = methodName;
253
739
  if (generateCodeResult.locatorsMetadata) {
254
740
  codePage.addLocatorsMetadata(generateCodeResult.locatorsMetadata);
@@ -292,7 +778,7 @@ export const getCommandsForImplementedStep = (stepName, stepsDefinitions, stepPa
292
778
  const file = step?.file;
293
779
  const locatorsJson = getLocatorsJson(file);
294
780
  if (!step) {
295
- throw new Error("Step definition not found" + stepName);
781
+ throw new Error(`Step definition not found: ${stepName}`);
296
782
  }
297
783
  isImplemented = true;
298
784
  const { codeCommands, codePage, elements, parametersNames, error } =
@@ -300,26 +786,35 @@ export const getCommandsForImplementedStep = (stepName, stepsDefinitions, stepPa
300
786
  if (error) {
301
787
  throw new Error(error);
302
788
  }
789
+ isUtilStep = codePage.sourceFileName.endsWith("utils.mjs") || codePage.sourceFileName.endsWith("default_page.mjs");
303
790
 
304
791
  if (parametersNames.length !== stepParams.length) {
305
792
  // console.log("Parameters mismatch", parametersNames, stepParams);
306
793
  throw new Error("Parameters mismatch");
307
794
  }
308
- for (let i = 0; i < parametersNames.length; i++) {
309
- stepParams[i].argumentName = parametersNames[i];
310
- }
311
795
 
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];
796
+ const pattern = step.name;
797
+ if (isUtilStep && pattern === "Verify the file {string} exists") {
798
+ commands.push({
799
+ type: "verify_file_exists",
800
+ parameters: [stepParams[0].text],
801
+ });
802
+ } else {
803
+ for (let i = 0; i < parametersNames.length; i++) {
804
+ stepParams[i].argumentName = parametersNames[i];
805
+ }
806
+
807
+ for (const { code } of codeCommands) {
808
+ const command = invertCodeToCommand(code, elements, stepParams, stepsDefinitions, codePage, stepName)[0];
809
+ if (command === undefined || command.type === null) continue;
810
+ if (command.element) {
811
+ const key = command.element.key;
812
+ if (key && locatorsJson[key]) {
813
+ command.allStrategyLocators = locatorsJson[key];
814
+ }
320
815
  }
816
+ commands.push(command);
321
817
  }
322
- commands.push(command);
323
818
  }
324
819
  } catch (error) {
325
820
  console.error(error);
@@ -367,7 +862,7 @@ export async function executeStep({ stepsDefinitions, cucumberStep, context, cod
367
862
  }
368
863
  }
369
864
 
370
- export async function updateStepDefinitions({ scenario, featureName, projectDir }) {
865
+ export async function updateStepDefinitions({ scenario, featureName, projectDir, logger }) {
371
866
  // set the candidate step definition file name
372
867
  // set the utils file path
373
868
  const utilsFilePath = path.join(projectDir, "features", "step_definitions", "utils.mjs");
@@ -398,43 +893,46 @@ export async function updateStepDefinitions({ scenario, featureName, projectDir
398
893
  step.isImplementedWhileRecording = true;
399
894
  }
400
895
  }
401
- if ((step.isImplemented && !step.shouldOverride) || step.commands.length === 0) {
896
+ if (!step.isUtilStep && ((step.isImplemented && !step.shouldOverride) || step.commands.length === 0)) {
402
897
  let routesPath = path.join(tmpdir(), `blinq_temp_routes`);
403
898
  if (process.env.TEMP_RUN === "true") {
404
- console.log("Save routes in temp folder for running:", routesPath);
405
899
  if (existsSync(routesPath)) {
406
- console.log("Removing existing temp_routes_folder:", routesPath);
900
+ routesPath = path.join(tmpdir(), `blinq_temp_routes`);
407
901
  rmSync(routesPath, { recursive: true });
408
902
  }
409
903
  mkdirSync(routesPath, { recursive: true });
410
- console.log("Created temp_routes_folder:", routesPath);
411
- saveRoutes({ step, folderPath: routesPath });
904
+ saveRoutes({ step, folderPath: routesPath }, logger);
412
905
  } else {
413
- console.log("Saving routes in project directory:", projectDir);
414
906
  if (existsSync(routesPath)) {
415
- // remove the folder
416
907
  try {
417
908
  rmSync(routesPath, { recursive: true });
418
- console.log("Removed temp_routes_folder:", routesPath);
419
909
  } catch (error) {
420
- console.error("Error removing temp_routes folder", error);
910
+ logger.error(`Error removing temp routes folder: ${getErrorMessage(error)}`);
421
911
  }
422
912
  }
423
913
  routesPath = path.join(projectDir, "data", "routes");
424
- console.log("Saving routes to:", routesPath);
425
914
  if (!existsSync(routesPath)) {
426
915
  mkdirSync(routesPath, { recursive: true });
427
916
  }
428
- saveRoutes({ step, folderPath: routesPath });
917
+ saveRoutes({ step, folderPath: routesPath }, logger);
918
+ }
919
+ if (step.commands && Array.isArray(step.commands)) {
920
+ step.commands = step.commands.map((cmd) => toRecordingStep(cmd, scenario.parametersMap));
429
921
  }
430
922
  continue;
431
923
  }
432
924
  const cucumberStep = getCucumberStep({ step });
433
925
  const pageName = generatePageName(step.startFrame?.url ?? "default");
434
926
  const stepDefsFilePath = locateDefinitionPath(featureFolder, pageName);
435
- // path.join(stepDefinitionFolderPath, pageName + "_page.mjs");
436
927
  let codePage = getCodePage(stepDefsFilePath);
437
- codePage = await saveRecording({ step, cucumberStep, codePage, projectDir, stepsDefinitions });
928
+ codePage = await saveRecording({
929
+ step,
930
+ cucumberStep,
931
+ codePage,
932
+ projectDir,
933
+ stepsDefinitions,
934
+ parametersMap: scenario.parametersMap,
935
+ });
438
936
  if (!codePage) {
439
937
  continue;
440
938
  }
@@ -446,7 +944,7 @@ export async function updateStepDefinitions({ scenario, featureName, projectDir
446
944
  writeFileSync(utilsFilePath, utilsContent, "utf8");
447
945
  }
448
946
 
449
- export function saveRoutes({ step, folderPath }) {
947
+ export function saveRoutes({ step, folderPath }, logger) {
450
948
  const routeItems = step.routeItems;
451
949
  if (!routeItems || routeItems.length === 0) {
452
950
  return;
@@ -469,21 +967,18 @@ export function saveRoutes({ step, folderPath }) {
469
967
  };
470
968
  });
471
969
 
472
- const routesFilePath = path.join(folderPath, stepNameHash + ".json");
473
- console.log("Routes file path:", routesFilePath);
970
+ const routesFilePath = path.join(folderPath, `${stepNameHash}.json`);
474
971
  const routesData = {
475
972
  template,
476
973
  routes: routeItemsWithFilters,
477
974
  };
478
- console.log("Routes data to save:", routesData);
479
975
 
480
976
  if (!existsSync(folderPath)) {
481
977
  mkdirSync(folderPath, { recursive: true });
482
978
  }
483
979
  try {
484
980
  writeFileSync(routesFilePath, JSON.stringify(routesData, null, 2), "utf8");
485
- console.log("Saved routes to", routesFilePath);
486
981
  } catch (error) {
487
- console.error("Failed to save routes to", routesFilePath, "Error:", error);
982
+ logger.error(`Error saving routes to ${routesFilePath}: ${getErrorMessage(error)}`);
488
983
  }
489
984
  }