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