@dev-blinq/cucumber_client 1.0.1255-dev → 1.0.1255-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 (35) hide show
  1. package/bin/assets/bundled_scripts/recorder.js +159 -14071
  2. package/bin/assets/preload/recorderv3.js +4 -2
  3. package/bin/assets/scripts/dom_parent.js +26 -0
  4. package/bin/assets/scripts/recorder.js +9 -8
  5. package/bin/assets/scripts/unique_locators.js +847 -547
  6. package/bin/assets/templates/_hooks_template.txt +37 -0
  7. package/bin/assets/templates/page_template.txt +2 -16
  8. package/bin/assets/templates/utils_template.txt +44 -71
  9. package/bin/client/apiTest/apiTest.js +6 -0
  10. package/bin/client/cli_helpers.js +11 -13
  11. package/bin/client/code_cleanup/utils.js +5 -1
  12. package/bin/client/code_gen/code_inversion.js +61 -4
  13. package/bin/client/code_gen/page_reflection.js +10 -3
  14. package/bin/client/code_gen/playwright_codeget.js +55 -16
  15. package/bin/client/cucumber/feature.js +89 -27
  16. package/bin/client/cucumber/project_to_document.js +1 -1
  17. package/bin/client/cucumber/steps_definitions.js +84 -76
  18. package/bin/client/cucumber_selector.js +13 -1
  19. package/bin/client/local_agent.js +3 -3
  20. package/bin/client/project.js +7 -1
  21. package/bin/client/recorderv3/bvt_recorder.js +285 -80
  22. package/bin/client/recorderv3/implemented_steps.js +74 -16
  23. package/bin/client/recorderv3/index.js +47 -25
  24. package/bin/client/recorderv3/network.js +299 -0
  25. package/bin/client/recorderv3/services.js +4 -16
  26. package/bin/client/recorderv3/step_runner.js +326 -67
  27. package/bin/client/recorderv3/step_utils.js +152 -5
  28. package/bin/client/recorderv3/update_feature.js +66 -34
  29. package/bin/client/recording.js +3 -0
  30. package/bin/client/run_cucumber.js +5 -1
  31. package/bin/client/scenario_report.js +0 -5
  32. package/bin/client/test_scenario.js +0 -1
  33. package/bin/client/utils/socket_logger.js +132 -0
  34. package/bin/index.js +1 -0
  35. package/package.json +15 -12
@@ -1,4 +1,4 @@
1
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
1
+ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
2
2
  import path from "path";
3
3
  import url from "url";
4
4
  import logger from "../../logger.js";
@@ -9,6 +9,8 @@ import { Step } from "../cucumber/feature.js";
9
9
  import { locateDefinitionPath, StepsDefinitions } from "../cucumber/steps_definitions.js";
10
10
  import { Recording } from "../recording.js";
11
11
  import { generateApiCode } from "../code_gen/api_codegen.js";
12
+ import { tmpdir } from "os";
13
+ import { createHash } from "crypto";
12
14
 
13
15
  const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
14
16
 
@@ -69,19 +71,74 @@ function makeStepTextUnique(step, stepsDefinitions) {
69
71
  }
70
72
 
71
73
  export async function saveRecording({ step, cucumberStep, codePage, projectDir, stepsDefinitions }) {
72
- // console.log("saveRecording", step.text);
74
+ let routesPath = path.join(tmpdir(), "blinq_temp_routes");
75
+
76
+ if (process.env.TEMP_RUN) {
77
+ if (existsSync(routesPath)) {
78
+ rmSync(routesPath, { recursive: true });
79
+ }
80
+ mkdirSync(routesPath, { recursive: true });
81
+ saveRoutes({ step, folderPath: routesPath });
82
+ } else {
83
+ if (existsSync(routesPath)) {
84
+ // remove the folder
85
+ try {
86
+ rmSync(routesPath, { recursive: true });
87
+ console.log("Removed temp_routes_folder:", routesPath);
88
+ } catch (error) {
89
+ console.error("Error removing temp_routes folder", error);
90
+ }
91
+ routesPath = path.join(projectDir, "data", "routes");
92
+ if (!existsSync(routesPath)) {
93
+ mkdirSync(routesPath, { recursive: true });
94
+ }
95
+ saveRoutes({ step, folderPath: routesPath });
96
+ }
97
+ }
98
+
73
99
  if (step.isImplementedWhileRecording && !process.env.TEMP_RUN) {
74
100
  return;
75
101
  }
102
+
76
103
  if (step.isImplemented && step.shouldOverride) {
77
104
  let stepDef = stepsDefinitions.findMatchingStep(step.text);
78
105
  codePage = getCodePage(stepDef.file);
79
106
  } else {
80
107
  const isUtilStep = makeStepTextUnique(step, stepsDefinitions);
108
+
81
109
  if (isUtilStep) {
82
110
  return;
83
111
  }
84
112
  }
113
+
114
+ if (process.env.TEMP_RUN === "true") {
115
+ console.log("Save routes in temp folder for running:", routesPath);
116
+ if (existsSync(routesPath)) {
117
+ console.log("Removing existing temp_routes_folder:", routesPath);
118
+ rmSync(routesPath, { recursive: true });
119
+ }
120
+ mkdirSync(routesPath, { recursive: true });
121
+ console.log("Created temp_routes_folder:", routesPath);
122
+ saveRoutes({ step, folderPath: routesPath });
123
+ } else {
124
+ console.log("Saving routes in project directory:", projectDir);
125
+ if (existsSync(routesPath)) {
126
+ // remove the folder
127
+ try {
128
+ rmSync(routesPath, { recursive: true });
129
+ console.log("Removed temp_routes_folder:", routesPath);
130
+ } catch (error) {
131
+ console.error("Error removing temp_routes folder", error);
132
+ }
133
+ }
134
+ routesPath = path.join(projectDir, "data", "routes");
135
+ console.log("Saving routes to:", routesPath);
136
+ if (!existsSync(routesPath)) {
137
+ mkdirSync(routesPath, { recursive: true });
138
+ }
139
+ saveRoutes({ step, folderPath: routesPath });
140
+ }
141
+
85
142
  cucumberStep.text = step.text;
86
143
  const recording = new Recording();
87
144
  const steps = step.commands;
@@ -108,6 +165,7 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
108
165
  isStaticToken,
109
166
  status,
110
167
  } = step.commands[0].value;
168
+
111
169
  const result = await generateApiCode(
112
170
  {
113
171
  url,
@@ -132,6 +190,7 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
132
190
  step.keyword,
133
191
  stepsDefinitions
134
192
  );
193
+
135
194
  if (!step.isImplemented) {
136
195
  stepsDefinitions.addStep({
137
196
  name: step.text,
@@ -139,6 +198,7 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
139
198
  source: "recorder",
140
199
  });
141
200
  }
201
+
142
202
  cucumberStep.methodName = result.methodName;
143
203
  return result.codePage;
144
204
  } else {
@@ -156,17 +216,29 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
156
216
  if (step.commands && step.commands.length > 0 && step.commands[0]) {
157
217
  path = step.commands[0].lastKnownUrlPath;
158
218
  }
219
+ let protect = false;
220
+ if (step.commands && step.commands.length > 0 && step.commands[0].type) {
221
+ if (step.commands[0].type === "verify_element_property" || step.commands[0].type === "conditional_wait") {
222
+ protect = true;
223
+ }
224
+ }
159
225
  const infraResult = codePage.addInfraCommand(
160
226
  methodName,
161
227
  description,
162
228
  cucumberStep.getVariablesList(),
163
229
  generateCodeResult.codeLines,
164
- false,
230
+ protect,
165
231
  "recorder",
166
232
  path
167
233
  );
168
234
  const keyword = (cucumberStep.keywordAlias ?? cucumberStep.keyword).trim();
169
- const stepResult = codePage.addCucumberStep(keyword, cucumberStep.getTemplate(), methodName, steps.length);
235
+ const stepResult = codePage.addCucumberStep(
236
+ keyword,
237
+ cucumberStep.getTemplate(),
238
+ methodName,
239
+ steps.length,
240
+ step.finalTimeout
241
+ );
170
242
 
171
243
  if (!step.isImplemented) {
172
244
  stepsDefinitions.addStep({
@@ -306,6 +378,12 @@ export async function updateStepDefinitions({ scenario, featureName, projectDir
306
378
  const utilsTemplateFilePath = path.join(__dirname, "../../assets", "templates", "utils_template.txt");
307
379
  const utilsContent = readFileSync(utilsTemplateFilePath, "utf8");
308
380
  writeFileSync(utilsFilePath, utilsContent, "utf8");
381
+ const hooksTemplateFilePath = path.join(__dirname, "../../assets", "templates", "_hooks_template.txt");
382
+ if (existsSync(hooksTemplateFilePath)) {
383
+ const hooksFilePath = path.join(stepDefinitionFolderPath, "_hooks.mjs");
384
+ const hooksContent = readFileSync(hooksTemplateFilePath, "utf8");
385
+ writeFileSync(hooksFilePath, hooksContent, "utf8");
386
+ }
309
387
  const steps = scenario.steps;
310
388
 
311
389
  const stepsDefinitions = new StepsDefinitions(projectDir);
@@ -321,6 +399,34 @@ export async function updateStepDefinitions({ scenario, featureName, projectDir
321
399
  }
322
400
  }
323
401
  if ((step.isImplemented && !step.shouldOverride) || step.commands.length === 0) {
402
+ let routesPath = path.join(tmpdir(), `blinq_temp_routes`);
403
+ if (process.env.TEMP_RUN === "true") {
404
+ console.log("Save routes in temp folder for running:", routesPath);
405
+ if (existsSync(routesPath)) {
406
+ console.log("Removing existing temp_routes_folder:", routesPath);
407
+ rmSync(routesPath, { recursive: true });
408
+ }
409
+ mkdirSync(routesPath, { recursive: true });
410
+ console.log("Created temp_routes_folder:", routesPath);
411
+ saveRoutes({ step, folderPath: routesPath });
412
+ } else {
413
+ console.log("Saving routes in project directory:", projectDir);
414
+ if (existsSync(routesPath)) {
415
+ // remove the folder
416
+ try {
417
+ rmSync(routesPath, { recursive: true });
418
+ console.log("Removed temp_routes_folder:", routesPath);
419
+ } catch (error) {
420
+ console.error("Error removing temp_routes folder", error);
421
+ }
422
+ }
423
+ routesPath = path.join(projectDir, "data", "routes");
424
+ console.log("Saving routes to:", routesPath);
425
+ if (!existsSync(routesPath)) {
426
+ mkdirSync(routesPath, { recursive: true });
427
+ }
428
+ saveRoutes({ step, folderPath: routesPath });
429
+ }
324
430
  continue;
325
431
  }
326
432
  const cucumberStep = getCucumberStep({ step });
@@ -328,7 +434,6 @@ export async function updateStepDefinitions({ scenario, featureName, projectDir
328
434
  const stepDefsFilePath = locateDefinitionPath(featureFolder, pageName);
329
435
  // path.join(stepDefinitionFolderPath, pageName + "_page.mjs");
330
436
  let codePage = getCodePage(stepDefsFilePath);
331
-
332
437
  codePage = await saveRecording({ step, cucumberStep, codePage, projectDir, stepsDefinitions });
333
438
  if (!codePage) {
334
439
  continue;
@@ -340,3 +445,45 @@ export async function updateStepDefinitions({ scenario, featureName, projectDir
340
445
  }
341
446
  writeFileSync(utilsFilePath, utilsContent, "utf8");
342
447
  }
448
+
449
+ export function saveRoutes({ step, folderPath }) {
450
+ const routeItems = step.routeItems;
451
+ if (!routeItems || routeItems.length === 0) {
452
+ return;
453
+ }
454
+ const cucumberStep = getCucumberStep({ step });
455
+ const template = cucumberStep.getTemplate();
456
+ const stepNameHash = createHash("sha256").update(template).digest("hex");
457
+ console.log("Saving routes for step:", step.text, "with hash:", stepNameHash);
458
+
459
+ const routeItemsWithFilters = routeItems.map((routeItem) => {
460
+ const oldFilters = routeItem.filters;
461
+ const queryParamsObject = {};
462
+ oldFilters.queryParams.forEach((queryParam) => {
463
+ queryParamsObject[queryParam.key] = queryParam.value;
464
+ });
465
+ const newFilters = { path: oldFilters.path, method: oldFilters.method, queryParams: queryParamsObject };
466
+ return {
467
+ ...routeItem,
468
+ filters: newFilters,
469
+ };
470
+ });
471
+
472
+ const routesFilePath = path.join(folderPath, stepNameHash + ".json");
473
+ console.log("Routes file path:", routesFilePath);
474
+ const routesData = {
475
+ template,
476
+ routes: routeItemsWithFilters,
477
+ };
478
+ console.log("Routes data to save:", routesData);
479
+
480
+ if (!existsSync(folderPath)) {
481
+ mkdirSync(folderPath, { recursive: true });
482
+ }
483
+ try {
484
+ writeFileSync(routesFilePath, JSON.stringify(routesData, null, 2), "utf8");
485
+ console.log("Saved routes to", routesFilePath);
486
+ } catch (error) {
487
+ console.error("Failed to save routes to", routesFilePath, "Error:", error);
488
+ }
489
+ }
@@ -1,6 +1,7 @@
1
1
  import { existsSync, readFileSync, writeFileSync } from "fs";
2
2
  import path from "path";
3
-
3
+ import { getDefaultPrettierConfig } from "../code_cleanup/utils.js";
4
+ import prettier from "prettier";
4
5
  export function containsScenario({ featureFileContent, scenarioName }) {
5
6
  const lines = featureFileContent.split("\n");
6
7
  for (const line of lines) {
@@ -63,7 +64,7 @@ const escapeNonPrintables = (text) => {
63
64
  export function getCommandContent(command) {
64
65
  switch (command.type) {
65
66
  case "click_element": {
66
- return `click on ${escapeNonPrintables(command.element.name)}`;
67
+ return `${command.count === 2 ? "Double click" : "Click"} on ${escapeNonPrintables(command.element.name)}`;
67
68
  }
68
69
  case "fill_element": {
69
70
  return `fill ${escapeNonPrintables(command.element.name)} with ${escapeNonPrintables(command.parameters[0])}${command.parameters[1] ? ` and press enter key` : ""}`;
@@ -81,7 +82,7 @@ export function getCommandContent(command) {
81
82
  return `verify the element ${escapeNonPrintables(command.element.name)} contains text ${escapeNonPrintables(command.parameters[0])}`;
82
83
  }
83
84
  case "context_click": {
84
- return `click on ${escapeNonPrintables(command.label)} in the context of ${escapeNonPrintables(command.value)}`;
85
+ return `${command.count === 2 ? "Double click" : "Click"} on ${escapeNonPrintables(command.label)} in the context of ${escapeNonPrintables(command.value)}`;
85
86
  }
86
87
  case "hover_element": {
87
88
  return `hover over ${escapeNonPrintables(command.element.name)}`;
@@ -98,6 +99,9 @@ export function getCommandContent(command) {
98
99
  case "verify_page_snapshot": {
99
100
  return `verify page snapshot stored in ${command.parameters[0]}`;
100
101
  }
102
+ case "parameterized_click": {
103
+ return `${command.count === 2 ? "Parameterized double click" : "Parameterized click"} on ${escapeNonPrintables(command.element.name)}`;
104
+ }
101
105
  default: {
102
106
  return "";
103
107
  }
@@ -125,10 +129,14 @@ export function getExamplesContent(parametersMap) {
125
129
  function getTagsContent(scenario, featureFileObject) {
126
130
  let oldTags = featureFileObject?.scenarios?.find((s) => s.name === scenario.name)?.tags ?? [];
127
131
  for (const tag of scenario.tags) {
128
- if (oldTags.includes(tag)) {
129
- oldTags = oldTags.filter((t) => t !== tag);
130
- } else {
131
- oldTags.push(tag);
132
+ if (tag === "global_test_data") {
133
+ if (!oldTags.includes("global_test_data")) {
134
+ oldTags.push("global_test_data");
135
+ }
136
+ }
137
+
138
+ if (tag === "remove_global_test_data") {
139
+ oldTags = oldTags.filter((t) => t !== "global_test_data");
132
140
  }
133
141
  }
134
142
 
@@ -209,14 +217,12 @@ const GherkinToObject = (gherkin) => {
209
217
  steps: [],
210
218
  };
211
219
  while (idx < lines.length && lines[idx].startsWith("@")) {
212
- skipEmptyLines();
213
220
  const tags = [...lines[idx].matchAll(/@([^@]+)/g)].map((match) => match[1].trim());
214
221
  scenario.tags.push(...(tags ?? []));
215
222
  idx++;
223
+ skipEmptyLines();
216
224
  }
217
225
 
218
- skipEmptyLines();
219
-
220
226
  if (idx < lines.length && (lines[idx].startsWith("Scenario:") || lines[idx].startsWith("Scenario Outline:"))) {
221
227
  scenario.name = lines[idx].substring(lines[idx].indexOf(":") + 1).trim();
222
228
  idx++;
@@ -239,32 +245,32 @@ const GherkinToObject = (gherkin) => {
239
245
  !lines[idx].startsWith("@")
240
246
  ) {
241
247
  const line = lines[idx++];
242
- if (line.startsWith("#")) {
243
- const comment = line;
244
- if (comment) {
245
- const command = {
246
- type: "comment",
247
- text: comment,
248
- };
249
- scenario.steps.push(command);
250
- }
251
- } else if (line.startsWith("Examples:")) {
252
- obj.hasParams = true;
253
- const command = {
248
+ let command;
249
+ if (line.startsWith("Examples:")) {
250
+ scenario.hasParams = true;
251
+ command = {
254
252
  type: "examples",
255
253
  lines: [],
256
254
  };
257
-
258
255
  while (idx < lines.length && lines[idx].startsWith("|")) {
259
256
  const line = lines[idx++];
260
257
  command.lines.push(line);
261
258
  }
262
259
  } else {
263
- scenario.steps.push({
264
- type: "step",
265
- text: line,
266
- });
260
+ if (line.startsWith("#")) {
261
+ command = {
262
+ type: "comment",
263
+ text: line,
264
+ };
265
+ } else {
266
+ command = {
267
+ type: "step",
268
+ text: line,
269
+ };
270
+ }
267
271
  }
272
+ scenario.steps.push(command);
273
+ skipEmptyLines();
268
274
  }
269
275
 
270
276
  return scenario;
@@ -273,7 +279,6 @@ const GherkinToObject = (gherkin) => {
273
279
  while (idx < lines.length) {
274
280
  const scenario = getScenario();
275
281
  if (scenario === -1) break;
276
-
277
282
  if (scenario.error) {
278
283
  return {
279
284
  error: scenario.error,
@@ -299,8 +304,7 @@ function updateExistingScenario({ featureFileContent, scenarioName, scenarioCont
299
304
  skipScenarioIndex = i;
300
305
  continue;
301
306
  }
302
- let scenarioContent = `${featureFileObject.hasParams ? "Scenario Outline" : "Scenario"}: ${scenario.name}`;
303
-
307
+ let scenarioContent = `${scenario.hasParams ? "Scenario Outline" : "Scenario"}: ${scenario.name}`;
304
308
  let tagsLine;
305
309
  if (scenario.tags?.length > 0) {
306
310
  tagsLine = `${scenario.tags.map((t) => `@${t}`).join(" ")}`;
@@ -323,7 +327,6 @@ function updateExistingScenario({ featureFileContent, scenarioName, scenarioCont
323
327
  if (skipScenarioIndex !== -1) {
324
328
  finalContent = results.join("\n") + "\n" + scenarioContent;
325
329
  }
326
-
327
330
  return finalContent;
328
331
  }
329
332
  export async function updateFeatureFile({ featureName, scenario, override, projectDir }) {
@@ -333,6 +336,8 @@ export async function updateFeatureFile({ featureName, scenario, override, proje
333
336
  { scenario },
334
337
  isFeatureFileExists ? GherkinToObject(readFileSync(featureFilePath, "utf8")) : undefined
335
338
  );
339
+ const prettierConfig = getDefaultPrettierConfig();
340
+ // Format the code using Prettier
336
341
 
337
342
  if (isFeatureFileExists) {
338
343
  const featureFileContent = readFileSync(featureFilePath, "utf8");
@@ -341,7 +346,7 @@ export async function updateFeatureFile({ featureName, scenario, override, proje
341
346
  if (!override) {
342
347
  throw new Error(`Scenario "${scenario.name}" already exists in feature "${featureName}"`);
343
348
  } else {
344
- const updatedFeatureFileContent = updateExistingScenario({
349
+ let updatedFeatureFileContent = updateExistingScenario({
345
350
  featureFileContent,
346
351
  scenarioName: scenario.name,
347
352
  scenarioContent,
@@ -349,14 +354,41 @@ export async function updateFeatureFile({ featureName, scenario, override, proje
349
354
  if (updatedFeatureFileContent === "error") {
350
355
  throw new Error(`Error while parsing feature file: Invalid gherkin`);
351
356
  }
357
+ try {
358
+ updatedFeatureFileContent = await prettier.format(updatedFeatureFileContent, {
359
+ ...prettierConfig,
360
+ parser: "gherkin",
361
+ plugins: ["prettier-plugin-gherkin"],
362
+ });
363
+ } catch (error) {
364
+ console.error("Error formatting feature file content with Prettier:", error);
365
+ }
352
366
  writeFileSync(featureFilePath, updatedFeatureFileContent);
353
367
  return;
354
368
  }
355
369
  }
356
- const updatedFeatureFileContent = featureFileContent + "\n" + scenarioContent;
370
+ let updatedFeatureFileContent = featureFileContent + "\n" + scenarioContent;
371
+ try {
372
+ updatedFeatureFileContent = await prettier.format(updatedFeatureFileContent, {
373
+ ...prettierConfig,
374
+ parser: "gherkin",
375
+ plugins: ["prettier-plugin-gherkin"],
376
+ });
377
+ } catch (error) {
378
+ console.error("Error formatting feature file content with Prettier:", error);
379
+ }
357
380
  writeFileSync(featureFilePath, updatedFeatureFileContent);
358
381
  } else {
359
- const featureFileContent = `Feature: ${featureName}\n${scenarioContent}`;
382
+ let featureFileContent = `Feature: ${featureName}\n${scenarioContent}`;
383
+ try {
384
+ featureFileContent = await prettier.format(featureFileContent, {
385
+ ...prettierConfig,
386
+ parser: "gherkin",
387
+ plugins: ["prettier-plugin-gherkin"],
388
+ });
389
+ } catch (error) {
390
+ console.error("Error formatting feature file content with Prettier:", error);
391
+ }
360
392
  writeFileSync(featureFilePath, featureFileContent);
361
393
  }
362
394
  }
@@ -11,6 +11,8 @@ const Types = {
11
11
  PARAMETERIZED_CLICK: "parameterized_click",
12
12
  CONTEXT_CLICK: "context_click",
13
13
  NAVIGATE: "navigate",
14
+ GO_BACK: "browser_go_back",
15
+ GO_FORWARD: "browser_go_forward",
14
16
  FILL: "fill_element",
15
17
  FILL_SIMPLE: "fill_simple",
16
18
  EXECUTE: "execute_page_method",
@@ -50,6 +52,7 @@ const Types = {
50
52
  VERIFY_FILE_EXISTS: "verify_file_exists",
51
53
  SET_INPUT_FILES: "set_input_files",
52
54
  VERIFY_PAGE_SNAPSHOT: "verify_page_snapshot",
55
+ CONDITIONAL_WAIT: "conditional_wait",
53
56
  };
54
57
  class Recording {
55
58
  steps = [];
@@ -131,6 +131,10 @@ const runCucumber = async (
131
131
  } else if (!process.env.NODE_ENV_BLINQ) {
132
132
  serviceUrl = "https://logic.blinq.io";
133
133
  logger.info("Running in prod mode");
134
+ } else if (process.env.WHITELABEL === "true") {
135
+ // if it's a whitelabel it will use "api" instead of "logic"
136
+ serviceUrl = process.env.NODE_ENV_BLINQ;
137
+ logger.info("Running in custom mode: " + serviceUrl);
134
138
  } else {
135
139
  serviceUrl = process.env.NODE_ENV_BLINQ.replace("api", "logic");
136
140
  logger.info("Running in custom mode: " + serviceUrl);
@@ -355,7 +359,7 @@ const runCucumber = async (
355
359
  await aiAgent.createNewStepLocal(
356
360
  featureName,
357
361
  cucumberStep,
358
- feature,
362
+ feature.comments,
359
363
  userData,
360
364
  first,
361
365
  previousTasks,
@@ -33,17 +33,12 @@ const findNextIdInFolder = (folder) => {
33
33
  // get temp file path from --temp-file arg
34
34
  const getTempFilePath = () => {
35
35
  let tempFilePath = null;
36
-
37
36
  for (const arg of process.argv) {
38
37
  const [key, path] = arg.split("=");
39
38
  if (key === "--temp-file" && !!path) {
40
39
  tempFilePath = path;
41
40
  }
42
41
  }
43
-
44
- if (tempFilePath === null) {
45
- tempFilePath = process.env.TEMP_FILE_PATH;
46
- }
47
42
  return tempFilePath;
48
43
  };
49
44
 
@@ -267,7 +267,6 @@ try {
267
267
  }
268
268
  if (validatePath !== "null") {
269
269
  await prevrunResult.context.stable.verifyPagePath(validatePath);
270
-
271
270
  }
272
271
  //finalCompare = true;
273
272
  } catch (e) {
@@ -0,0 +1,132 @@
1
+ /**
2
+ * @typedef {Object} SocketLoggerEventPayload
3
+ * @property {string} level Log level (e.g. "info", "warn", "error", "debug")
4
+ * @property {string} context Log context/subsystem (e.g. "BVTRecorder")
5
+ * @property {*} data The log message payload (string, object, etc)
6
+ * @property {string} timestamp ISO string when the log was emitted
7
+ * @property {number} dataSize Size of data in bytes (-1 if unknown)
8
+ */
9
+
10
+ /**
11
+ * @typedef {Object} SocketLoggerInitOptions
12
+ * @property {string=} context Default context for all logs (optional)
13
+ * @property {string=} eventName Default event name (default: "recorder.log")
14
+ */
15
+
16
+ /**
17
+ * SocketLogger - Singleton for structured socket-based logging.
18
+ *
19
+ * @namespace SocketLogger
20
+ * @property {function(import('socket.io-client').Socket|import('socket.io').Socket, SocketLoggerInitOptions=):void} init
21
+ * @property {function(string, (string|*), Object=, string=, string=):void} log
22
+ * @property {function((string|*), Object=):void} info
23
+ * @property {function((string|*), Object=):void} warn
24
+ * @property {function((string|*), Object=):void} debug
25
+ * @property {function((string|*), Object=):void} error
26
+ *
27
+ * @example
28
+ * import logger from "./socket_logger.js";
29
+ * logger.init(socket, { context: "BVTRecorder" });
30
+ * logger.info("Step started", { step: 2 });
31
+ * logger.error("Failed!", { error: "bad stuff" });
32
+ */
33
+ const SocketLogger = (function () {
34
+ /** @type {import('socket.io-client').Socket|import('socket.io').Socket|null} */
35
+ let socket = null;
36
+ /** @type {string} */
37
+ let defaultContext = "";
38
+ /** @type {string} */
39
+ let defaultEventName = "recorder.log";
40
+
41
+ /**
42
+ * Initialize the logger (call once).
43
+ * @param {import('socket.io-client').Socket|import('socket.io').Socket} sock
44
+ * @param {SocketLoggerInitOptions=} opts
45
+ */
46
+ function init(sock, opts) {
47
+ socket = sock;
48
+ defaultContext = (opts && opts.context) || "";
49
+ defaultEventName = (opts && opts.eventName) || "recorder.log";
50
+ }
51
+
52
+ /**
53
+ * Low-level log method (most users use info/warn/debug/error).
54
+ * @param {string} level Log level ("info", "warn", "debug", "error")
55
+ * @param {string|*} message The log message or object
56
+ * @param {Object=} extra Extra fields (will be merged into log payload)
57
+ * @param {string=} eventName Override event name for this log (default: "recorder.log")
58
+ * @param {string=} context Override log context for this log (default: set in init)
59
+ */
60
+ function log(level, message, extra, eventName, context) {
61
+ if (!socket || typeof socket.emit !== "function") return;
62
+ /** @type {*} */
63
+ var data = typeof message === "object" ? message : { message: message };
64
+ /** @type {number} */
65
+ var dataSize = 0;
66
+ try {
67
+ dataSize = Buffer.byteLength(JSON.stringify(data || ""), "utf8");
68
+ } catch (e) {
69
+ dataSize = -1;
70
+ }
71
+ /** @type {SocketLoggerEventPayload} */
72
+ var eventPayload = Object.assign(
73
+ {
74
+ level: level,
75
+ context: context || defaultContext,
76
+ data: data,
77
+ timestamp: new Date().toISOString(),
78
+ dataSize: dataSize,
79
+ },
80
+ extra || {}
81
+ );
82
+ // @ts-ignore
83
+ try {
84
+ if (socket) {
85
+ socket.emit(eventName || defaultEventName, eventPayload);
86
+ }
87
+ } catch (e) {
88
+ console.error("Socket logging error:", e);
89
+ console.log("Socket event payload:", eventPayload);
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Write an info-level log event.
95
+ * @param {string|*} msg The message or object
96
+ * @param {Object=} ext Any extra fields/metadata
97
+ */
98
+ function info(msg, ext) {
99
+ log("info", msg, ext);
100
+ }
101
+
102
+ /**
103
+ * Write a warn-level log event.
104
+ * @param {string|*} msg The message or object
105
+ * @param {Object=} ext Any extra fields/metadata
106
+ */
107
+ function warn(msg, ext) {
108
+ log("warn", msg, ext);
109
+ }
110
+
111
+ /**
112
+ * Write a debug-level log event.
113
+ * @param {string|*} msg The message or object
114
+ * @param {Object=} ext Any extra fields/metadata
115
+ */
116
+ function debug(msg, ext) {
117
+ log("debug", msg, ext);
118
+ }
119
+
120
+ /**
121
+ * Write an error-level log event.
122
+ * @param {string|*} msg The message or object
123
+ * @param {Object=} ext Any extra fields/metadata
124
+ */
125
+ function error(msg, ext) {
126
+ log("error", msg, ext);
127
+ }
128
+
129
+ return { init, log, info, warn, debug, error };
130
+ })();
131
+
132
+ export default SocketLogger;
package/bin/index.js CHANGED
@@ -13,4 +13,5 @@ export * from "./client/cucumber/feature_data.js";
13
13
  export * from "./client/cucumber/steps_definitions.js";
14
14
  export * from "./client/profiler.js";
15
15
  export * from "./client/code_cleanup/utils.js";
16
+
16
17
  export * from "./client/code_cleanup/find_step_definition_references.js";