@dev-blinq/cucumber_client 1.0.1456-dev → 1.0.1456-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 (34) hide show
  1. package/bin/assets/bundled_scripts/recorder.js +73 -73
  2. package/bin/assets/scripts/recorder.js +87 -49
  3. package/bin/assets/scripts/snapshot_capturer.js +10 -17
  4. package/bin/assets/scripts/unique_locators.js +169 -47
  5. package/bin/assets/templates/_hooks_template.txt +6 -2
  6. package/bin/assets/templates/utils_template.txt +16 -16
  7. package/bin/client/code_cleanup/utils.js +16 -7
  8. package/bin/client/code_gen/code_inversion.js +115 -0
  9. package/bin/client/code_gen/duplication_analysis.js +2 -1
  10. package/bin/client/code_gen/function_signature.js +4 -0
  11. package/bin/client/code_gen/page_reflection.js +52 -11
  12. package/bin/client/code_gen/playwright_codeget.js +164 -75
  13. package/bin/client/cucumber/feature.js +4 -17
  14. package/bin/client/cucumber/steps_definitions.js +13 -0
  15. package/bin/client/local_agent.js +1 -0
  16. package/bin/client/recorderv3/bvt_init.js +305 -0
  17. package/bin/client/recorderv3/bvt_recorder.js +1031 -61
  18. package/bin/client/recorderv3/implemented_steps.js +2 -0
  19. package/bin/client/recorderv3/index.js +3 -286
  20. package/bin/client/recorderv3/services.js +818 -142
  21. package/bin/client/recorderv3/step_runner.js +21 -4
  22. package/bin/client/recorderv3/step_utils.js +194 -118
  23. package/bin/client/recorderv3/update_feature.js +87 -39
  24. package/bin/client/recorderv3/wbr_entry.js +61 -0
  25. package/bin/client/recording.js +1 -0
  26. package/bin/client/upload-service.js +2 -0
  27. package/bin/client/utils/app_dir.js +21 -0
  28. package/bin/client/utils/socket_logger.js +87 -125
  29. package/bin/index.js +4 -1
  30. package/package.json +11 -5
  31. package/bin/client/recorderv3/app_dir.js +0 -23
  32. package/bin/client/recorderv3/network.js +0 -299
  33. package/bin/client/recorderv3/scriptTest.js +0 -5
  34. package/bin/client/recorderv3/ws_server.js +0 -72
@@ -12,7 +12,7 @@ import path from "path";
12
12
  * @param {string} text the text to verify exists in page
13
13
  * @protect
14
14
  */
15
- async function verifyTextExistsInPage(text) {
15
+ export async function verifyTextExistsInPage(text) {
16
16
  await context.web.verifyTextExistInPage(text, null, this);
17
17
  }
18
18
  Then("Verify the text {string} can be found in the page", verifyTextExistsInPage);
@@ -22,7 +22,7 @@ Then("Verify the text {string} can be found in the page", verifyTextExistsInPage
22
22
  * @param {string} elementDescription element description
23
23
  * @protect
24
24
  */
25
- async function clickOnElement(elementDescription) {
25
+ export async function clickOnElement(elementDescription) {
26
26
  await context.web.simpleClick(elementDescription, null, null, this);
27
27
  }
28
28
  When("click on {string}", clickOnElement);
@@ -36,7 +36,7 @@ When("Click {string}", clickOnElement);
36
36
  * @param {string} value value to fill the element with
37
37
  * @protect
38
38
  */
39
- async function fillElement(elementDescription, value) {
39
+ export async function fillElement(elementDescription, value) {
40
40
  await context.web.simpleClickType(elementDescription, value, null, null, this);
41
41
  }
42
42
  When("fill {string} with {string}", fillElement);
@@ -47,7 +47,7 @@ When("Fill {string} with {string}", fillElement);
47
47
  * @param {string} text the text to verify does not exist in page
48
48
  * @protect
49
49
  */
50
- async function verifyTextNotExistsInPage(text) {
50
+ export async function verifyTextNotExistsInPage(text) {
51
51
  await context.web.waitForTextToDisappear(text, null, this);
52
52
  }
53
53
  Then("Verify the text {string} cannot be found in the page", verifyTextNotExistsInPage);
@@ -57,7 +57,7 @@ Then("Verify the text {string} cannot be found in the page", verifyTextNotExists
57
57
  * @param {string} url URL to navigate
58
58
  * @protect
59
59
  */
60
- async function navigateTo(url) {
60
+ export async function navigateTo(url) {
61
61
  await context.web.goto(url, this);
62
62
  }
63
63
  When("Navigate to {string}", navigateTo);
@@ -66,7 +66,7 @@ When("Navigate to {string}", navigateTo);
66
66
  * Navigate to the current page
67
67
  * @protect
68
68
  */
69
- async function browserNavigateBack() {
69
+ export async function browserNavigateBack() {
70
70
  await context.web.goBack({}, this);
71
71
  }
72
72
  Then("Browser navigate back", browserNavigateBack);
@@ -75,7 +75,7 @@ Then("Browser navigate back", browserNavigateBack);
75
75
  * Navigate forward in browser history
76
76
  * @protect
77
77
  */
78
- async function browserNavigateForward() {
78
+ export async function browserNavigateForward() {
79
79
  await context.web.goForward({}, this);
80
80
  }
81
81
  Then("Browser navigate forward", browserNavigateForward);
@@ -85,7 +85,7 @@ Then("Browser navigate forward", browserNavigateForward);
85
85
  * @param {string} filePath the file path or empty to store in the test data file
86
86
  * @protect
87
87
  */
88
- async function storeBrowserSession(filePath) {
88
+ export async function storeBrowserSession(filePath) {
89
89
  await context.web.saveStoreState(filePath, this);
90
90
  }
91
91
  When("Store browser session {string}", storeBrowserSession);
@@ -95,7 +95,7 @@ When("Store browser session {string}", storeBrowserSession);
95
95
  * @param {string} filePath the file path or empty
96
96
  * @protect
97
97
  */
98
- async function resetBrowserSession(filePath) {
98
+ export async function resetBrowserSession(filePath) {
99
99
  await context.web.restoreSaveState(filePath, this);
100
100
  }
101
101
  When("Reset browser session {string}", resetBrowserSession);
@@ -107,7 +107,7 @@ When("Reset browser session {string}", resetBrowserSession);
107
107
  * @param {string} textToVerify the target text to verify
108
108
  * @protect
109
109
  */
110
- async function verifyTextRelatedToText(textAnchor, climb, textToVerify) {
110
+ export async function verifyTextRelatedToText(textAnchor, climb, textToVerify) {
111
111
  await context.web.verifyTextRelatedToText(textAnchor, climb, textToVerify, null, this);
112
112
  }
113
113
  Then(
@@ -120,7 +120,7 @@ Then(
120
120
  * @requestName the name of the bruno request file
121
121
  * @protect
122
122
  */
123
- async function runBrunoRequest(requestName) {
123
+ export async function runBrunoRequest(requestName) {
124
124
  await executeBrunoRequest(requestName, {}, context, this);
125
125
  }
126
126
  When("Bruno - {string}", runBrunoRequest);
@@ -131,7 +131,7 @@ When("bruno - {string}", runBrunoRequest);
131
131
  * @param {string} fileName the downloaded file to verify
132
132
  * @protect
133
133
  */
134
- async function verify_the_downloaded_file_exists(fileName) {
134
+ export async function verify_the_downloaded_file_exists(fileName) {
135
135
  const downloadFolder = path.join(context.reportFolder, "downloads");
136
136
  const downloadFile = path.join(downloadFolder, fileName);
137
137
  await verifyFileExists(downloadFile, {}, context, this);
@@ -148,7 +148,7 @@ When("Noop", async function () {});
148
148
  * @param {string} url URL to be verified against current URL
149
149
  * @protect
150
150
  */
151
- async function verify_page_url(url) {
151
+ export async function verify_page_url(url) {
152
152
  await context.web.verifyPagePath(url, {}, this);
153
153
  }
154
154
  Then("Verify the page url is {string}", verify_page_url);
@@ -158,7 +158,7 @@ Then("Verify the page url is {string}", verify_page_url);
158
158
  * @param {string} title Title to be verified against current Title
159
159
  * @protect
160
160
  */
161
- async function verify_page_title(title) {
161
+ export async function verify_page_title(title) {
162
162
  await context.web.verifyPageTitle(title, {}, this);
163
163
  }
164
164
  Then("Verify the page title is {string}", verify_page_title);
@@ -170,7 +170,7 @@ Then("Verify the page title is {string}", verify_page_title);
170
170
  * @param {world} - Optional world context
171
171
  * @returns Promise that resolves after the specified duration
172
172
  */
173
- async function sleep(duration) {
174
- await context.web.sleep(duration, {}, null);
173
+ export async function sleep(duration) {
174
+ await context.web.sleep(duration, {}, this);
175
175
  }
176
176
  Then("Sleep for {string} ms", { timeout: -1 }, sleep);
@@ -10,7 +10,7 @@ import * as t from "@babel/types";
10
10
  import { CucumberExpression, ParameterTypeRegistry } from "@cucumber/cucumber-expressions";
11
11
  import { existsSync, readFileSync, writeFileSync } from "fs";
12
12
  import { getAiConfig } from "../code_gen/page_reflection.js";
13
- import socketLogger from "../utils/socket_logger.js";
13
+ import socketLogger, { getErrorMessage } from "../utils/socket_logger.js";
14
14
 
15
15
  const STEP_KEYWORDS = new Set(["Given", "When", "Then"]);
16
16
 
@@ -308,18 +308,27 @@ export function getDefaultPrettierConfig() {
308
308
  const configContent = readFileSync(prettierConfigPath, "utf-8");
309
309
  prettierConfig = JSON.parse(configContent);
310
310
  } catch (error) {
311
- socketLogger.error("Error parsing Prettier config file", error);
312
- console.error(`Error parsing Prettier config file: ${error}`);
311
+ socketLogger.error(
312
+ `Error parsing Prettier config file: ${getErrorMessage(error)}`,
313
+ undefined,
314
+ "getDefaultPrettierConfig"
315
+ );
313
316
  }
314
317
  } else {
315
318
  // save the default config to .prettierrc
316
319
  try {
317
320
  writeFileSync(prettierConfigPath, JSON.stringify(prettierConfig, null, 2), "utf-8");
318
- socketLogger.info(`Created default Prettier config at ${prettierConfigPath}`);
319
- console.log(`Created default Prettier config at ${prettierConfigPath}`);
321
+ socketLogger.info(
322
+ `Created default Prettier config at ${prettierConfigPath}`,
323
+ undefined,
324
+ "getDefaultPrettierConfig"
325
+ );
320
326
  } catch (error) {
321
- socketLogger.error(`Error writing Prettier config file: ${error}`);
322
- console.error(`Error writing Prettier config file: ${error}`);
327
+ socketLogger.error(
328
+ `Error writing Prettier config file: ${getErrorMessage(error)}`,
329
+ undefined,
330
+ "getDefaultPrettierConfig"
331
+ );
323
332
  }
324
333
  }
325
334
  return prettierConfig;
@@ -86,6 +86,30 @@ const parseDataSource = (paramNode, stepParams) => {
86
86
  // }
87
87
  throw new Error("Unknown parameter type");
88
88
  };
89
+
90
+
91
+ const parseOptions = (optionsNode) => {
92
+ const properties = optionsNode.properties;
93
+ const options = {}
94
+ for (const prop of properties) {
95
+ let key = "";
96
+ if (prop.key.type === 'Identifier') {
97
+ key = prop.key.name;
98
+ } else if (prop.key.type === 'StringLiteral') {
99
+ key = prop.key.value;
100
+ }
101
+ if (key === 'context') continue;
102
+ options[key] = prop.value.value;
103
+ }
104
+ return options;
105
+ }
106
+
107
+ const parseOptionsFromCallNode = (call, index) => {
108
+ if (call.arguments[index] && call.arguments[index].type === "ObjectExpression") {
109
+ return parseOptions(call.arguments[index]);
110
+ }
111
+ return null
112
+ }
89
113
  const invertStableCommand = (call, elements, stepParams) => {
90
114
  const methodName = call.callee.property.name;
91
115
  const step = { type: null, parameters: [], element: null, locators: null };
@@ -104,6 +128,7 @@ const invertStableCommand = (call, elements, stepParams) => {
104
128
  step.dataSource = inputParam.dataSource;
105
129
  step.dataKey = inputParam.dataKey;
106
130
  }
131
+ step.options = parseOptionsFromCallNode(call, 3);
107
132
  break;
108
133
  }
109
134
 
@@ -130,6 +155,8 @@ const invertStableCommand = (call, elements, stepParams) => {
130
155
  step.count = clickCountProp.value.value;
131
156
  }
132
157
  }
158
+
159
+ step.options = parseOptionsFromCallNode(call, 2);
133
160
  break;
134
161
 
135
162
  case "setDateTime": {
@@ -147,6 +174,7 @@ const invertStableCommand = (call, elements, stepParams) => {
147
174
  step.dataKey = dateParam.dataKey;
148
175
  step.parameters = [toVariableName(dateParam.dataKey), format, enterParam];
149
176
  }
177
+ step.options = parseOptionsFromCallNode(call, 5);
150
178
  break;
151
179
  }
152
180
 
@@ -163,6 +191,7 @@ const invertStableCommand = (call, elements, stepParams) => {
163
191
  step.dataKey = selectParam.dataKey;
164
192
  step.parameters = [toVariableName(selectParam.dataKey)];
165
193
  }
194
+ step.options = parseOptionsFromCallNode(call, 3);
166
195
  break;
167
196
  }
168
197
 
@@ -176,6 +205,7 @@ const invertStableCommand = (call, elements, stepParams) => {
176
205
  step.dataKey = text.dataKey;
177
206
  step.parameters = [toVariableName(text.dataKey)];
178
207
  }
208
+ step.options = parseOptionsFromCallNode(call, 2);
179
209
  break;
180
210
  }
181
211
  case "waitForTextToDisappear": {
@@ -188,6 +218,7 @@ const invertStableCommand = (call, elements, stepParams) => {
188
218
  step.dataKey = text.dataKey;
189
219
  step.parameters = [toVariableName(text.dataKey)];
190
220
  }
221
+ step.options = parseOptionsFromCallNode(call, 2);
191
222
  break;
192
223
  }
193
224
 
@@ -203,6 +234,7 @@ const invertStableCommand = (call, elements, stepParams) => {
203
234
  step.dataKey = text.dataKey;
204
235
  step.parameters = [toVariableName(text.dataKey), climb ?? null];
205
236
  }
237
+ step.options = parseOptionsFromCallNode(call, 4);
206
238
  break;
207
239
  }
208
240
  case "extractAttribute": {
@@ -233,6 +265,7 @@ const invertStableCommand = (call, elements, stepParams) => {
233
265
  step.regex = "";
234
266
  step.trimSpaces = false;
235
267
  }
268
+ step.options = parseOptionsFromCallNode(call, 4);
236
269
  break;
237
270
  }
238
271
  case "verifyAttribute": {
@@ -249,6 +282,7 @@ const invertStableCommand = (call, elements, stepParams) => {
249
282
  step.dataKey = value.dataKey;
250
283
  step.parameters = [attribute, toVariableName(value.dataKey)];
251
284
  }
285
+ step.options = parseOptionsFromCallNode(call, 4);
252
286
  break;
253
287
  }
254
288
 
@@ -277,6 +311,7 @@ const invertStableCommand = (call, elements, stepParams) => {
277
311
  step.parameters = [toVariableName(fillParam.dataKey), enterValue];
278
312
  }
279
313
  }
314
+ step.options = parseOptionsFromCallNode(call, 4);
280
315
  break;
281
316
 
282
317
  case "loadTestDataAsync": {
@@ -307,23 +342,28 @@ const invertStableCommand = (call, elements, stepParams) => {
307
342
  step.dataKey = url.dataKey;
308
343
  step.parameters = [toVariableName(url.dataKey)];
309
344
  }
345
+ step.options = parseOptionsFromCallNode(call, 2);
310
346
  break;
311
347
  }
312
348
 
313
349
  case "goBack":
314
350
  step.type = Types.GO_BACK;
351
+ step.options = parseOptionsFromCallNode(call, 0);
315
352
  break;
316
353
 
317
354
  case "goForward":
318
355
  step.type = Types.GO_FORWARD;
356
+ step.options = parseOptionsFromCallNode(call, 0);
319
357
  break;
320
358
 
321
359
  case "reloadPage":
322
360
  step.type = Types.RELOAD;
361
+ step.options = parseOptionsFromCallNode(call, 0);
323
362
  break;
324
363
 
325
364
  case "closePage":
326
365
  step.type = Types.CLOSE_PAGE;
366
+ step.options = parseOptionsFromCallNode(call, 0);
327
367
  break;
328
368
 
329
369
  case "simpleClick":
@@ -341,12 +381,15 @@ const invertStableCommand = (call, elements, stepParams) => {
341
381
  case "hover":
342
382
  step.type = Types.HOVER;
343
383
  step.element = extractElement(call.arguments[0]);
384
+ step.options = parseOptionsFromCallNode(call, 2);
344
385
  break;
345
386
 
346
387
  case "setCheck":
347
388
  step.type = Types.CHECK;
348
389
  step.element = extractElement(call.arguments[0]);
349
390
  step.check = call.arguments[1].value;
391
+ ///
392
+ step.options = parseOptionsFromCallNode(call, 3);
350
393
  break;
351
394
 
352
395
  case "setViewportSize": {
@@ -354,12 +397,14 @@ const invertStableCommand = (call, elements, stepParams) => {
354
397
  const width = call.arguments[0].value;
355
398
  const height = call.arguments[1].value;
356
399
  step.parameters = [width, height];
400
+ step.options = parseOptionsFromCallNode(call, 2);
357
401
  break;
358
402
  }
359
403
 
360
404
  case "visualVerification": {
361
405
  step.type = Types.VISUAL_VERIFICATION;
362
406
  step.parameters = [call.arguments[0].value];
407
+ step.options = parseOptionsFromCallNode(call, 1);
363
408
  break;
364
409
  }
365
410
  case "waitForUserInput": {
@@ -370,6 +415,7 @@ const invertStableCommand = (call, elements, stepParams) => {
370
415
  case "extractEmailData": {
371
416
  step.type = Types.MAIL_GET_CODE_OR_URL;
372
417
  step.parameters = [call.arguments[0].value];
418
+ step.options = parseOptionsFromCallNode(call, 1);
373
419
  break;
374
420
  }
375
421
  case "verifyTextRelatedToText": {
@@ -383,6 +429,7 @@ const invertStableCommand = (call, elements, stepParams) => {
383
429
  const textToVerify =
384
430
  textToVerifyParse.type === "literal" ? textToVerifyParse.value : toVariableName(textToVerifyParse.dataKey);
385
431
  step.parameters = [textAnchor, climb, textToVerify];
432
+ step.options = parseOptionsFromCallNode(call, 3);
386
433
  break;
387
434
  }
388
435
  case "setInputFiles": {
@@ -396,6 +443,7 @@ const invertStableCommand = (call, elements, stepParams) => {
396
443
  step.dataKey = fileParam.dataKey;
397
444
  step.parameters = [toVariableName(fileParam.dataKey)];
398
445
  }
446
+ step.options = parseOptionsFromCallNode(call, 3);
399
447
  break;
400
448
  }
401
449
  case "snapshotValidation": {
@@ -405,6 +453,7 @@ const invertStableCommand = (call, elements, stepParams) => {
405
453
  step.parameters = [inputParam.value];
406
454
  }
407
455
  step.selectors = call.arguments[0].value;
456
+ step.options = parseOptionsFromCallNode(call, 3);
408
457
  break;
409
458
  }
410
459
  case "verifyPageTitle": {
@@ -417,6 +466,7 @@ const invertStableCommand = (call, elements, stepParams) => {
417
466
  step.dataKey = text.dataKey;
418
467
  step.parameters = [toVariableName(text.dataKey)];
419
468
  }
469
+ step.options = parseOptionsFromCallNode(call, 1);
420
470
  break;
421
471
  }
422
472
  case "verifyPagePath": {
@@ -429,6 +479,7 @@ const invertStableCommand = (call, elements, stepParams) => {
429
479
  step.dataKey = path.dataKey;
430
480
  step.parameters = [toVariableName(path.dataKey)];
431
481
  }
482
+ step.options = parseOptionsFromCallNode(call, 1);
432
483
  break;
433
484
  }
434
485
  case "verifyProperty": {
@@ -443,6 +494,7 @@ const invertStableCommand = (call, elements, stepParams) => {
443
494
  step.dataKey = value.dataKey;
444
495
  step.parameters = [property, toVariableName(value.dataKey)];
445
496
  }
497
+ step.options = parseOptionsFromCallNode(call, 4);
446
498
  break;
447
499
  }
448
500
  case "extractProperty": {
@@ -473,8 +525,71 @@ const invertStableCommand = (call, elements, stepParams) => {
473
525
  step.regex = "";
474
526
  step.trimSpaces = false;
475
527
  }
528
+ step.options = parseOptionsFromCallNode(call, 4);
529
+ break;
530
+ }
531
+
532
+ case "conditionalWait": {
533
+ step.type = Types.CONDITIONAL_WAIT;
534
+ step.element = extractElement(call.arguments[0]);
535
+ const condition = call.arguments[1].value;
536
+
537
+ const _timeout = parseDataSource(call.arguments[2], stepParams);
538
+ let timeout = 30;
539
+ if (_timeout.type === "literal") {
540
+ if (isNaN(_timeout.value)) {
541
+ throw new Error(`Timeout value must be a number, got ${_timeout.value}`);
542
+ }
543
+ timeout = Number(_timeout.value) * 1000;
544
+ } else {
545
+ step.dataSource = _timeout.dataSource;
546
+ step.dataKey = _timeout.dataKey;
547
+ timeout = toVariableName(_timeout.dataKey);
548
+ }
549
+ // step.timeout = timeout;
550
+ // step.value = "true";
551
+ step.parameters = [timeout, condition, step.value];
552
+ // step.condition = condition;
553
+ step.options = parseOptionsFromCallNode(call, 4);
476
554
  break;
477
555
  }
556
+
557
+ case "sleep": {
558
+ step.type = Types.SLEEP;
559
+ const duration = parseDataSource(call.arguments[0], stepParams);
560
+ if (duration.type === "literal") {
561
+ if (isNaN(duration.value)) {
562
+ throw new Error(`Sleep duration must be a number, got ${duration.value}`);
563
+ }
564
+ step.parameters = [Number(duration.value)];
565
+ } else {
566
+ step.dataSource = duration.dataSource;
567
+ step.dataKey = duration.dataKey;
568
+ step.parameters = [toVariableName(duration.dataKey)];
569
+ }
570
+ step.options = parseOptionsFromCallNode(call, 1);
571
+ break;
572
+ }
573
+ case "verify_file_exists": {
574
+ step.type = Types.VERIFY_FILE_EXISTS;
575
+ const filePath = parseDataSource(call.arguments[0], stepParams);
576
+ if (filePath.type === "literal") {
577
+ step.parameters = [filePath.value];
578
+ } else {
579
+ step.dataSource = filePath.dataSource;
580
+ step.dataKey = filePath.dataKey;
581
+ step.parameters = [toVariableName(filePath.dataKey)];
582
+ }
583
+ step.options = parseOptionsFromCallNode(call, 1);
584
+ break;
585
+ }
586
+
587
+ // case "verifyPagePath":
588
+ // {
589
+ // step.type = Types.VERIFY_PAGE_PATH;
590
+ // step.parameters = [call.arguments[0].value];
591
+ // break;
592
+ // }
478
593
  default:
479
594
  return; // Skip if no matching method
480
595
  }
@@ -4,6 +4,7 @@ import path from "path";
4
4
  import { CodePage } from "./page_reflection.js";
5
5
  import { compareElements, generateSignature } from "./function_signature.js";
6
6
  import { updateStepDefinitions } from "../recorderv3/step_utils.js";
7
+ import socketLoggerInstance from "../utils/socket_logger.js";
7
8
  /**
8
9
  * DuplicationAnalizer class
9
10
  * How to use:
@@ -101,7 +102,7 @@ async function compareWithScenario(projectDir, scenario) {
101
102
  analyzerExisting.load();
102
103
  // generate a temporary folder
103
104
  const folder = fs.mkdtempSync(path.join(os.tmpdir(), "blinq-"));
104
- await updateStepDefinitions({ scenario, featureName: "temp", projectDir: folder });
105
+ await updateStepDefinitions({ scenario, featureName: "temp", projectDir: folder, logger: socketLoggerInstance });
105
106
  const analyzerScenario = new DuplicationAnalizer(folder);
106
107
  analyzerScenario.load();
107
108
  const compareResults = [];
@@ -11,6 +11,8 @@ async function verify_the_opportunity_name_was_created(_name) {
11
11
  }
12
12
  */
13
13
 
14
+ import strip from "strip-comments";
15
+
14
16
  function generateSignature(page, functionName) {
15
17
  let functionCode = null;
16
18
  let method = null;
@@ -28,6 +30,8 @@ function generateSignature(page, functionName) {
28
30
  method.node.body.parent.params.forEach((param) => {
29
31
  parameters.push(param.name);
30
32
  });
33
+ const strippedFunctionCode = strip(functionCode);
34
+ functionCode = strippedFunctionCode !== "" ? strippedFunctionCode : functionCode;
31
35
  let lines = functionCode.split("\n");
32
36
  // trim all lines
33
37
  lines = lines.map((line) => line.trim());
@@ -170,7 +170,7 @@ export class CodePage {
170
170
  this.generateModel(fileContentNew);
171
171
  }
172
172
  catch (e) {
173
- logger.info("failed to format the code");
173
+ logger.error("failed to format the code");
174
174
  logger.debug(e);
175
175
  }
176
176
  if (!existsSync(this.sourceFileName)) {
@@ -288,6 +288,11 @@ export class CodePage {
288
288
  codePart.name = node.id.name;
289
289
  }
290
290
  }
291
+ findKey(funcString, key) {
292
+ const sourceRegex = new RegExp(`${key}:\\s*(.*)`);
293
+ const match = funcString.match(sourceRegex);
294
+ return match ? match[1] : null;
295
+ }
291
296
  collectAllTemplates() {
292
297
  const templates = [];
293
298
  if (this.cucumberCalls.length > 0) {
@@ -300,10 +305,12 @@ export class CodePage {
300
305
  const stepType = cucumberCall["stepType"];
301
306
  let firstFind = true;
302
307
  const stepPaths = [];
308
+ let source = null;
303
309
  for (let j = 0; j < this.methods.length; j++) {
304
310
  const method = this.methods[j];
305
311
  if (method.name === methodName) {
306
312
  if (firstFind) {
313
+ source = this.findKey(method.codePart, "source");
307
314
  foundMethod = true;
308
315
  const paramsObj = (method.node?.params ?? []);
309
316
  if (paramsObj && paramsObj.length > 0) {
@@ -315,7 +322,7 @@ export class CodePage {
315
322
  }
316
323
  }
317
324
  if (foundMethod) {
318
- templates.push({ pattern, methodName, params, stepType, paths: stepPaths });
325
+ templates.push({ pattern, methodName, params, stepType, paths: stepPaths, source });
319
326
  }
320
327
  }
321
328
  }
@@ -363,6 +370,39 @@ export class CodePage {
363
370
  return result;
364
371
  }
365
372
  }
373
+ addInfraCommandUtil(methodName, description, stepVariables, stepCodeLines, renamedText, previousText, parametersMap, protectStep = false, source = null, codePath = "") {
374
+ let code = "\n";
375
+ code += "/**\n";
376
+ code += ` * ${description}\n`;
377
+ const tags = [];
378
+ if (protectStep)
379
+ tags.push("@protect");
380
+ if (source)
381
+ tags.push(`@${source}`);
382
+ if (tags.length > 0)
383
+ code += ` * ${tags.join(" ")}\n`;
384
+ if (codePath !== null)
385
+ code += ` * @path=${escapeForComment(codePath)}\n`;
386
+ const matches = previousText.match(/"[^"]*"/g);
387
+ const countInMethodName = matches ? matches.length : 0;
388
+ code += " */\n";
389
+ code += `async function ${methodName} (${new Array(countInMethodName)
390
+ .fill(0)
391
+ .map((v, index) => `param${index}`)
392
+ .join(", ")}){\n`;
393
+ code += `// source: ${source}\n`;
394
+ code += `// implemented_at: ${new Date().toISOString()}\n`;
395
+ stepCodeLines.forEach((line) => (code += ` ${line}\n`));
396
+ if (renamedText === "verify_page_title" || renamedText === "verify_page_url") {
397
+ const parameters = stepVariables.map((v) => parametersMap[v.text.replace(/[<>]/g, "")]);
398
+ code += `await ${renamedText}(${parameters.map((v) => (isNaN(Number(v)) ? `"${v}"` : Number(v))).join(", ")});\n`;
399
+ }
400
+ else {
401
+ code += `await ${renamedText}(${stepVariables.map((v) => (isNaN(Number(v.text)) ? `"${v.text}"` : Number(v.text))).join(", ")});\n`;
402
+ }
403
+ code += "}\n";
404
+ return this._injectMethod(methodName, code);
405
+ }
366
406
  addInfraCommand(methodName, description, stepVarables, stepCodeLines, protectStep = false, source = null, codePath = "") {
367
407
  let code = "\n";
368
408
  code += "/**\n";
@@ -528,12 +568,13 @@ export class CodePage {
528
568
  for (let j = i - 1; j >= 0; j--) {
529
569
  const nextElement = elements[keys[j]];
530
570
  if (this._isSimilarElement(currentElement, nextElement)) {
531
- mergedElements[currentElement.element_key] = currentElement;
532
- replacedKeys[nextElement.element_key] = currentElement.element_key;
571
+ // mergedElements[currentElement.element_key!] = currentElement;
572
+ // replacedKeys[nextElement.element_key!] = currentElement.element_key!;
533
573
  foundMatch = true;
534
- keys.splice(j, 1);
535
- i--;
536
- j++;
574
+ break;
575
+ // keys.splice(j, 1);
576
+ // i--;
577
+ // j++;
537
578
  }
538
579
  }
539
580
  if (!foundMatch) {
@@ -543,7 +584,7 @@ export class CodePage {
543
584
  if (Object.keys(replacedKeys).length === 0)
544
585
  return;
545
586
  for (const key in replacedKeys) {
546
- const regexp = new RegExp(`elements\$begin:math:display$\\\\s*["']${key}["']\\\\s*\\$end:math:display$`, "g");
587
+ const regexp = new RegExp(`elements\\[\\s*["']${key}["']\\s*\\]`, "g");
547
588
  this.fileContent = this.fileContent.replace(regexp, () => `elements["${replacedKeys[key]}"]`);
548
589
  }
549
590
  this.insertElements(mergedElements);
@@ -620,7 +661,7 @@ export class CodePage {
620
661
  let locatorsMetadataFileName = this.sourceFileName.replace(".mjs", ".json");
621
662
  const config = getAiConfig();
622
663
  if (config && config.locatorsMetadataDir) {
623
- locatorsMetadataFileName = path.join(config.locatorsMetadataDir, path.basename(locatorsMetadataFileName));
664
+ locatorsMetadataFileName = locatorsMetadataFileName.replace(path.join("features", "step_definitions"), path.join(config.locatorsMetadataDir));
624
665
  if (!existsSync(path.dirname(locatorsMetadataFileName))) {
625
666
  mkdirSync(path.dirname(locatorsMetadataFileName), { recursive: true });
626
667
  }
@@ -632,7 +673,7 @@ export class CodePage {
632
673
  }
633
674
  }
634
675
  catch {
635
- logger.info("failed to read locators metadata file", locatorsMetadataFileName);
676
+ logger.error("failed to read locators metadata file", locatorsMetadataFileName);
636
677
  }
637
678
  const keys = Object.keys(locatorsMetadata);
638
679
  keys.forEach((key) => {
@@ -642,7 +683,7 @@ export class CodePage {
642
683
  writeFileSync(locatorsMetadataFileName, JSON.stringify(metadata, null, 2), "utf8");
643
684
  }
644
685
  catch {
645
- logger.info("failed to write locators metadata file", locatorsMetadataFileName);
686
+ logger.error("failed to write locators metadata file", locatorsMetadataFileName);
646
687
  }
647
688
  }
648
689
  _getVariableStartEnd(variableName) {