@dev-blinq/cucumber_client 1.0.1271-dev → 1.0.1271-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.
- package/bin/assets/bundled_scripts/recorder.js +159 -14224
- package/bin/assets/preload/recorderv3.js +3 -1
- package/bin/assets/scripts/dom_parent.js +4 -0
- package/bin/assets/scripts/recorder.js +8 -4
- package/bin/assets/scripts/unique_locators.js +847 -693
- package/bin/assets/templates/_hooks_template.txt +37 -0
- package/bin/assets/templates/page_template.txt +2 -16
- package/bin/assets/templates/utils_template.txt +1 -46
- package/bin/client/apiTest/apiTest.js +6 -0
- package/bin/client/cli_helpers.js +11 -13
- package/bin/client/code_cleanup/utils.js +5 -1
- package/bin/client/code_gen/code_inversion.js +53 -4
- package/bin/client/code_gen/page_reflection.js +838 -902
- package/bin/client/code_gen/playwright_codeget.js +52 -13
- package/bin/client/cucumber/feature.js +89 -27
- package/bin/client/cucumber/project_to_document.js +1 -1
- package/bin/client/cucumber/steps_definitions.js +84 -76
- package/bin/client/cucumber_selector.js +17 -1
- package/bin/client/local_agent.js +7 -6
- package/bin/client/project.js +186 -196
- package/bin/client/recorderv3/bvt_recorder.js +171 -61
- package/bin/client/recorderv3/implemented_steps.js +74 -16
- package/bin/client/recorderv3/index.js +50 -25
- package/bin/client/recorderv3/network.js +299 -0
- package/bin/client/recorderv3/services.js +4 -16
- package/bin/client/recorderv3/step_runner.js +331 -67
- package/bin/client/recorderv3/step_utils.js +155 -5
- package/bin/client/recorderv3/update_feature.js +32 -30
- package/bin/client/recording.js +1 -0
- package/bin/client/run_cucumber.js +5 -1
- package/bin/client/scenario_report.js +0 -5
- package/bin/client/test_scenario.js +0 -1
- package/bin/client/utils/socket_logger.js +132 -0
- package/bin/index.js +1 -0
- package/bin/logger.js +3 -2
- package/bin/min/consoleApi.min.cjs +2 -3
- package/bin/min/injectedScript.min.cjs +16 -16
- package/package.json +24 -14
|
@@ -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,75 @@ function makeStepTextUnique(step, stepsDefinitions) {
|
|
|
69
71
|
}
|
|
70
72
|
|
|
71
73
|
export async function saveRecording({ step, cucumberStep, codePage, projectDir, stepsDefinitions }) {
|
|
72
|
-
|
|
74
|
+
let routesPath = path.join(tmpdir(), "blinq_temp_routes");
|
|
75
|
+
|
|
76
|
+
if (process.env.TEMP_RUN === "true") {
|
|
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
|
+
routesPath = path.join(tmpdir(), "blinq_temp_routes");
|
|
115
|
+
if (process.env.TEMP_RUN === "true") {
|
|
116
|
+
console.log("Save routes in temp folder for running:", routesPath);
|
|
117
|
+
if (existsSync(routesPath)) {
|
|
118
|
+
console.log("Removing existing temp_routes_folder:", routesPath);
|
|
119
|
+
rmSync(routesPath, { recursive: true });
|
|
120
|
+
}
|
|
121
|
+
mkdirSync(routesPath, { recursive: true });
|
|
122
|
+
console.log("Created temp_routes_folder:", routesPath);
|
|
123
|
+
saveRoutes({ step, folderPath: routesPath });
|
|
124
|
+
} else {
|
|
125
|
+
console.log("Saving routes in project directory:", projectDir);
|
|
126
|
+
if (existsSync(routesPath)) {
|
|
127
|
+
// remove the folder
|
|
128
|
+
try {
|
|
129
|
+
rmSync(routesPath, { recursive: true });
|
|
130
|
+
console.log("Removed temp_routes_folder:", routesPath);
|
|
131
|
+
} catch (error) {
|
|
132
|
+
console.error("Error removing temp_routes folder", error);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
routesPath = path.join(projectDir, "data", "routes");
|
|
136
|
+
console.log("Saving routes to:", routesPath);
|
|
137
|
+
if (!existsSync(routesPath)) {
|
|
138
|
+
mkdirSync(routesPath, { recursive: true });
|
|
139
|
+
}
|
|
140
|
+
saveRoutes({ step, folderPath: routesPath });
|
|
141
|
+
}
|
|
142
|
+
|
|
85
143
|
cucumberStep.text = step.text;
|
|
86
144
|
const recording = new Recording();
|
|
87
145
|
const steps = step.commands;
|
|
@@ -108,6 +166,7 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
|
|
|
108
166
|
isStaticToken,
|
|
109
167
|
status,
|
|
110
168
|
} = step.commands[0].value;
|
|
169
|
+
|
|
111
170
|
const result = await generateApiCode(
|
|
112
171
|
{
|
|
113
172
|
url,
|
|
@@ -132,6 +191,7 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
|
|
|
132
191
|
step.keyword,
|
|
133
192
|
stepsDefinitions
|
|
134
193
|
);
|
|
194
|
+
|
|
135
195
|
if (!step.isImplemented) {
|
|
136
196
|
stepsDefinitions.addStep({
|
|
137
197
|
name: step.text,
|
|
@@ -139,6 +199,7 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
|
|
|
139
199
|
source: "recorder",
|
|
140
200
|
});
|
|
141
201
|
}
|
|
202
|
+
|
|
142
203
|
cucumberStep.methodName = result.methodName;
|
|
143
204
|
return result.codePage;
|
|
144
205
|
} else {
|
|
@@ -156,17 +217,29 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
|
|
|
156
217
|
if (step.commands && step.commands.length > 0 && step.commands[0]) {
|
|
157
218
|
path = step.commands[0].lastKnownUrlPath;
|
|
158
219
|
}
|
|
220
|
+
let protect = false;
|
|
221
|
+
if (step.commands && step.commands.length > 0 && step.commands[0].type) {
|
|
222
|
+
if (step.commands[0].type === "verify_element_property" || step.commands[0].type === "conditional_wait") {
|
|
223
|
+
protect = true;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
159
226
|
const infraResult = codePage.addInfraCommand(
|
|
160
227
|
methodName,
|
|
161
228
|
description,
|
|
162
229
|
cucumberStep.getVariablesList(),
|
|
163
230
|
generateCodeResult.codeLines,
|
|
164
|
-
|
|
231
|
+
protect,
|
|
165
232
|
"recorder",
|
|
166
233
|
path
|
|
167
234
|
);
|
|
168
235
|
const keyword = (cucumberStep.keywordAlias ?? cucumberStep.keyword).trim();
|
|
169
|
-
const stepResult = codePage.addCucumberStep(
|
|
236
|
+
const stepResult = codePage.addCucumberStep(
|
|
237
|
+
keyword,
|
|
238
|
+
cucumberStep.getTemplate(),
|
|
239
|
+
methodName,
|
|
240
|
+
steps.length,
|
|
241
|
+
step.finalTimeout
|
|
242
|
+
);
|
|
170
243
|
|
|
171
244
|
if (!step.isImplemented) {
|
|
172
245
|
stepsDefinitions.addStep({
|
|
@@ -177,6 +250,7 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
|
|
|
177
250
|
}
|
|
178
251
|
|
|
179
252
|
codePage.removeUnusedElements();
|
|
253
|
+
codePage.mergeSimilarElements();
|
|
180
254
|
cucumberStep.methodName = methodName;
|
|
181
255
|
if (generateCodeResult.locatorsMetadata) {
|
|
182
256
|
codePage.addLocatorsMetadata(generateCodeResult.locatorsMetadata);
|
|
@@ -306,6 +380,12 @@ export async function updateStepDefinitions({ scenario, featureName, projectDir
|
|
|
306
380
|
const utilsTemplateFilePath = path.join(__dirname, "../../assets", "templates", "utils_template.txt");
|
|
307
381
|
const utilsContent = readFileSync(utilsTemplateFilePath, "utf8");
|
|
308
382
|
writeFileSync(utilsFilePath, utilsContent, "utf8");
|
|
383
|
+
const hooksTemplateFilePath = path.join(__dirname, "../../assets", "templates", "_hooks_template.txt");
|
|
384
|
+
if (existsSync(hooksTemplateFilePath)) {
|
|
385
|
+
const hooksFilePath = path.join(stepDefinitionFolderPath, "_hooks.mjs");
|
|
386
|
+
const hooksContent = readFileSync(hooksTemplateFilePath, "utf8");
|
|
387
|
+
writeFileSync(hooksFilePath, hooksContent, "utf8");
|
|
388
|
+
}
|
|
309
389
|
const steps = scenario.steps;
|
|
310
390
|
|
|
311
391
|
const stepsDefinitions = new StepsDefinitions(projectDir);
|
|
@@ -321,6 +401,35 @@ export async function updateStepDefinitions({ scenario, featureName, projectDir
|
|
|
321
401
|
}
|
|
322
402
|
}
|
|
323
403
|
if ((step.isImplemented && !step.shouldOverride) || step.commands.length === 0) {
|
|
404
|
+
let routesPath = path.join(tmpdir(), `blinq_temp_routes`);
|
|
405
|
+
if (process.env.TEMP_RUN === "true") {
|
|
406
|
+
console.log("Save routes in temp folder for running:", routesPath);
|
|
407
|
+
if (existsSync(routesPath)) {
|
|
408
|
+
console.log("Removing existing temp_routes_folder:", routesPath);
|
|
409
|
+
routesPath = path.join(tmpdir(), `blinq_temp_routes`);
|
|
410
|
+
rmSync(routesPath, { recursive: true });
|
|
411
|
+
}
|
|
412
|
+
mkdirSync(routesPath, { recursive: true });
|
|
413
|
+
console.log("Created temp_routes_folder:", routesPath);
|
|
414
|
+
saveRoutes({ step, folderPath: routesPath });
|
|
415
|
+
} else {
|
|
416
|
+
console.log("Saving routes in project directory:", projectDir);
|
|
417
|
+
if (existsSync(routesPath)) {
|
|
418
|
+
// remove the folder
|
|
419
|
+
try {
|
|
420
|
+
rmSync(routesPath, { recursive: true });
|
|
421
|
+
console.log("Removed temp_routes_folder:", routesPath);
|
|
422
|
+
} catch (error) {
|
|
423
|
+
console.error("Error removing temp_routes folder", error);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
routesPath = path.join(projectDir, "data", "routes");
|
|
427
|
+
console.log("Saving routes to:", routesPath);
|
|
428
|
+
if (!existsSync(routesPath)) {
|
|
429
|
+
mkdirSync(routesPath, { recursive: true });
|
|
430
|
+
}
|
|
431
|
+
saveRoutes({ step, folderPath: routesPath });
|
|
432
|
+
}
|
|
324
433
|
continue;
|
|
325
434
|
}
|
|
326
435
|
const cucumberStep = getCucumberStep({ step });
|
|
@@ -328,7 +437,6 @@ export async function updateStepDefinitions({ scenario, featureName, projectDir
|
|
|
328
437
|
const stepDefsFilePath = locateDefinitionPath(featureFolder, pageName);
|
|
329
438
|
// path.join(stepDefinitionFolderPath, pageName + "_page.mjs");
|
|
330
439
|
let codePage = getCodePage(stepDefsFilePath);
|
|
331
|
-
|
|
332
440
|
codePage = await saveRecording({ step, cucumberStep, codePage, projectDir, stepsDefinitions });
|
|
333
441
|
if (!codePage) {
|
|
334
442
|
continue;
|
|
@@ -340,3 +448,45 @@ export async function updateStepDefinitions({ scenario, featureName, projectDir
|
|
|
340
448
|
}
|
|
341
449
|
writeFileSync(utilsFilePath, utilsContent, "utf8");
|
|
342
450
|
}
|
|
451
|
+
|
|
452
|
+
export function saveRoutes({ step, folderPath }) {
|
|
453
|
+
const routeItems = step.routeItems;
|
|
454
|
+
if (!routeItems || routeItems.length === 0) {
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
const cucumberStep = getCucumberStep({ step });
|
|
458
|
+
const template = cucumberStep.getTemplate();
|
|
459
|
+
const stepNameHash = createHash("sha256").update(template).digest("hex");
|
|
460
|
+
console.log("Saving routes for step:", step.text, "with hash:", stepNameHash);
|
|
461
|
+
|
|
462
|
+
const routeItemsWithFilters = routeItems.map((routeItem) => {
|
|
463
|
+
const oldFilters = routeItem.filters;
|
|
464
|
+
const queryParamsObject = {};
|
|
465
|
+
oldFilters.queryParams.forEach((queryParam) => {
|
|
466
|
+
queryParamsObject[queryParam.key] = queryParam.value;
|
|
467
|
+
});
|
|
468
|
+
const newFilters = { path: oldFilters.path, method: oldFilters.method, queryParams: queryParamsObject };
|
|
469
|
+
return {
|
|
470
|
+
...routeItem,
|
|
471
|
+
filters: newFilters,
|
|
472
|
+
};
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
const routesFilePath = path.join(folderPath, stepNameHash + ".json");
|
|
476
|
+
console.log("Routes file path:", routesFilePath);
|
|
477
|
+
const routesData = {
|
|
478
|
+
template,
|
|
479
|
+
routes: routeItemsWithFilters,
|
|
480
|
+
};
|
|
481
|
+
console.log("Routes data to save:", routesData);
|
|
482
|
+
|
|
483
|
+
if (!existsSync(folderPath)) {
|
|
484
|
+
mkdirSync(folderPath, { recursive: true });
|
|
485
|
+
}
|
|
486
|
+
try {
|
|
487
|
+
writeFileSync(routesFilePath, JSON.stringify(routesData, null, 2), "utf8");
|
|
488
|
+
console.log("Saved routes to", routesFilePath);
|
|
489
|
+
} catch (error) {
|
|
490
|
+
console.error("Failed to save routes to", routesFilePath, "Error:", error);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
@@ -64,7 +64,7 @@ const escapeNonPrintables = (text) => {
|
|
|
64
64
|
export function getCommandContent(command) {
|
|
65
65
|
switch (command.type) {
|
|
66
66
|
case "click_element": {
|
|
67
|
-
return
|
|
67
|
+
return `${command.count === 2 ? "Double click" : "Click"} on ${escapeNonPrintables(command.element.name)}`;
|
|
68
68
|
}
|
|
69
69
|
case "fill_element": {
|
|
70
70
|
return `fill ${escapeNonPrintables(command.element.name)} with ${escapeNonPrintables(command.parameters[0])}${command.parameters[1] ? ` and press enter key` : ""}`;
|
|
@@ -82,7 +82,7 @@ export function getCommandContent(command) {
|
|
|
82
82
|
return `verify the element ${escapeNonPrintables(command.element.name)} contains text ${escapeNonPrintables(command.parameters[0])}`;
|
|
83
83
|
}
|
|
84
84
|
case "context_click": {
|
|
85
|
-
return
|
|
85
|
+
return `${command.count === 2 ? "Double click" : "Click"} on ${escapeNonPrintables(command.label)} in the context of ${escapeNonPrintables(command.value)}`;
|
|
86
86
|
}
|
|
87
87
|
case "hover_element": {
|
|
88
88
|
return `hover over ${escapeNonPrintables(command.element.name)}`;
|
|
@@ -99,6 +99,9 @@ export function getCommandContent(command) {
|
|
|
99
99
|
case "verify_page_snapshot": {
|
|
100
100
|
return `verify page snapshot stored in ${command.parameters[0]}`;
|
|
101
101
|
}
|
|
102
|
+
case "parameterized_click": {
|
|
103
|
+
return `${command.count === 2 ? "Parameterized double click" : "Parameterized click"} on ${escapeNonPrintables(command.element.name)}`;
|
|
104
|
+
}
|
|
102
105
|
default: {
|
|
103
106
|
return "";
|
|
104
107
|
}
|
|
@@ -126,10 +129,14 @@ export function getExamplesContent(parametersMap) {
|
|
|
126
129
|
function getTagsContent(scenario, featureFileObject) {
|
|
127
130
|
let oldTags = featureFileObject?.scenarios?.find((s) => s.name === scenario.name)?.tags ?? [];
|
|
128
131
|
for (const tag of scenario.tags) {
|
|
129
|
-
if (
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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");
|
|
133
140
|
}
|
|
134
141
|
}
|
|
135
142
|
|
|
@@ -210,14 +217,12 @@ const GherkinToObject = (gherkin) => {
|
|
|
210
217
|
steps: [],
|
|
211
218
|
};
|
|
212
219
|
while (idx < lines.length && lines[idx].startsWith("@")) {
|
|
213
|
-
skipEmptyLines();
|
|
214
220
|
const tags = [...lines[idx].matchAll(/@([^@]+)/g)].map((match) => match[1].trim());
|
|
215
221
|
scenario.tags.push(...(tags ?? []));
|
|
216
222
|
idx++;
|
|
223
|
+
skipEmptyLines();
|
|
217
224
|
}
|
|
218
225
|
|
|
219
|
-
skipEmptyLines();
|
|
220
|
-
|
|
221
226
|
if (idx < lines.length && (lines[idx].startsWith("Scenario:") || lines[idx].startsWith("Scenario Outline:"))) {
|
|
222
227
|
scenario.name = lines[idx].substring(lines[idx].indexOf(":") + 1).trim();
|
|
223
228
|
idx++;
|
|
@@ -240,32 +245,32 @@ const GherkinToObject = (gherkin) => {
|
|
|
240
245
|
!lines[idx].startsWith("@")
|
|
241
246
|
) {
|
|
242
247
|
const line = lines[idx++];
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
type: "comment",
|
|
248
|
-
text: comment,
|
|
249
|
-
};
|
|
250
|
-
scenario.steps.push(command);
|
|
251
|
-
}
|
|
252
|
-
} else if (line.startsWith("Examples:")) {
|
|
253
|
-
obj.hasParams = true;
|
|
254
|
-
const command = {
|
|
248
|
+
let command;
|
|
249
|
+
if (line.startsWith("Examples:")) {
|
|
250
|
+
scenario.hasParams = true;
|
|
251
|
+
command = {
|
|
255
252
|
type: "examples",
|
|
256
253
|
lines: [],
|
|
257
254
|
};
|
|
258
|
-
|
|
259
255
|
while (idx < lines.length && lines[idx].startsWith("|")) {
|
|
260
256
|
const line = lines[idx++];
|
|
261
257
|
command.lines.push(line);
|
|
262
258
|
}
|
|
263
259
|
} else {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
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
|
+
}
|
|
268
271
|
}
|
|
272
|
+
scenario.steps.push(command);
|
|
273
|
+
skipEmptyLines();
|
|
269
274
|
}
|
|
270
275
|
|
|
271
276
|
return scenario;
|
|
@@ -274,7 +279,6 @@ const GherkinToObject = (gherkin) => {
|
|
|
274
279
|
while (idx < lines.length) {
|
|
275
280
|
const scenario = getScenario();
|
|
276
281
|
if (scenario === -1) break;
|
|
277
|
-
|
|
278
282
|
if (scenario.error) {
|
|
279
283
|
return {
|
|
280
284
|
error: scenario.error,
|
|
@@ -300,8 +304,7 @@ function updateExistingScenario({ featureFileContent, scenarioName, scenarioCont
|
|
|
300
304
|
skipScenarioIndex = i;
|
|
301
305
|
continue;
|
|
302
306
|
}
|
|
303
|
-
let scenarioContent = `${
|
|
304
|
-
|
|
307
|
+
let scenarioContent = `${scenario.hasParams ? "Scenario Outline" : "Scenario"}: ${scenario.name}`;
|
|
305
308
|
let tagsLine;
|
|
306
309
|
if (scenario.tags?.length > 0) {
|
|
307
310
|
tagsLine = `${scenario.tags.map((t) => `@${t}`).join(" ")}`;
|
|
@@ -324,7 +327,6 @@ function updateExistingScenario({ featureFileContent, scenarioName, scenarioCont
|
|
|
324
327
|
if (skipScenarioIndex !== -1) {
|
|
325
328
|
finalContent = results.join("\n") + "\n" + scenarioContent;
|
|
326
329
|
}
|
|
327
|
-
|
|
328
330
|
return finalContent;
|
|
329
331
|
}
|
|
330
332
|
export async function updateFeatureFile({ featureName, scenario, override, projectDir }) {
|
package/bin/client/recording.js
CHANGED
|
@@ -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
|
|
|
@@ -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";
|
package/bin/logger.js
CHANGED
|
@@ -17,8 +17,8 @@ function formatWithInspect(val) {
|
|
|
17
17
|
let alignColorsAndTime = null;
|
|
18
18
|
let fileRotateTransport = null;
|
|
19
19
|
let errorFileRotateTransport = null;
|
|
20
|
-
export let logger = null;
|
|
21
20
|
function initLogger() {
|
|
21
|
+
let logger = null;
|
|
22
22
|
try {
|
|
23
23
|
alignColorsAndTime = format.combine(
|
|
24
24
|
format.timestamp({
|
|
@@ -59,9 +59,10 @@ function initLogger() {
|
|
|
59
59
|
console.log(error);
|
|
60
60
|
logger = console;
|
|
61
61
|
}
|
|
62
|
+
return logger;
|
|
62
63
|
}
|
|
63
64
|
|
|
64
|
-
initLogger();
|
|
65
|
+
export const logger = initLogger();
|
|
65
66
|
const changeToTaskLogger = (logFile) => {
|
|
66
67
|
logger.remove(fileRotateTransport);
|
|
67
68
|
fileRotateTransport.filename = logFile;
|