@dev-blinq/cucumber_client 1.0.1475-dev → 1.0.1475-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 +49 -49
- package/bin/assets/scripts/recorder.js +87 -34
- package/bin/assets/scripts/snapshot_capturer.js +10 -17
- package/bin/assets/scripts/unique_locators.js +78 -28
- package/bin/assets/templates/_hooks_template.txt +6 -2
- package/bin/assets/templates/utils_template.txt +16 -16
- package/bin/client/code_cleanup/codemod/find_harcoded_locators.js +173 -0
- package/bin/client/code_cleanup/codemod/fix_hardcoded_locators.js +247 -0
- package/bin/client/code_cleanup/utils.js +16 -7
- package/bin/client/code_gen/code_inversion.js +125 -1
- package/bin/client/code_gen/duplication_analysis.js +2 -1
- package/bin/client/code_gen/function_signature.js +8 -0
- package/bin/client/code_gen/index.js +4 -0
- package/bin/client/code_gen/page_reflection.js +90 -9
- package/bin/client/code_gen/playwright_codeget.js +173 -77
- package/bin/client/codemod/find_harcoded_locators.js +173 -0
- package/bin/client/codemod/fix_hardcoded_locators.js +247 -0
- package/bin/client/codemod/index.js +8 -0
- package/bin/client/codemod/locators_array/find_misstructured_elements.js +148 -0
- package/bin/client/codemod/locators_array/fix_misstructured_elements.js +144 -0
- package/bin/client/codemod/locators_array/index.js +114 -0
- package/bin/client/codemod/types.js +1 -0
- package/bin/client/cucumber/feature.js +4 -17
- package/bin/client/cucumber/steps_definitions.js +17 -12
- package/bin/client/recorderv3/bvt_init.js +310 -0
- package/bin/client/recorderv3/bvt_recorder.js +1560 -1183
- package/bin/client/recorderv3/constants.js +45 -0
- package/bin/client/recorderv3/implemented_steps.js +2 -0
- package/bin/client/recorderv3/index.js +3 -293
- package/bin/client/recorderv3/mixpanel.js +39 -0
- package/bin/client/recorderv3/services.js +839 -142
- package/bin/client/recorderv3/step_runner.js +36 -7
- package/bin/client/recorderv3/step_utils.js +316 -98
- package/bin/client/recorderv3/update_feature.js +85 -37
- package/bin/client/recorderv3/utils.js +80 -0
- package/bin/client/recorderv3/wbr_entry.js +61 -0
- package/bin/client/recording.js +1 -0
- package/bin/client/types/locators.js +2 -0
- package/bin/client/upload-service.js +2 -0
- package/bin/client/utils/app_dir.js +21 -0
- package/bin/client/utils/socket_logger.js +100 -125
- package/bin/index.js +5 -0
- package/package.json +21 -6
- package/bin/client/recorderv3/app_dir.js +0 -23
- package/bin/client/recorderv3/network.js +0 -299
- package/bin/client/recorderv3/scriptTest.js +0 -5
- package/bin/client/recorderv3/ws_server.js +0 -72
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
2
|
-
import path from "path";
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
3
|
import { getDefaultPrettierConfig } from "../code_cleanup/utils.js";
|
|
4
4
|
import prettier from "prettier";
|
|
5
|
-
|
|
5
|
+
|
|
6
|
+
function containsScenario({ featureFileContent, scenarioName }) {
|
|
6
7
|
const lines = featureFileContent.split("\n");
|
|
7
8
|
for (const line of lines) {
|
|
8
9
|
const trimmedLine = line.trim();
|
|
@@ -14,7 +15,7 @@ export function containsScenario({ featureFileContent, scenarioName }) {
|
|
|
14
15
|
}
|
|
15
16
|
}
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
function testStringForRegex(text) {
|
|
18
19
|
const regexEndPattern = /\/([gimuy]*)$/;
|
|
19
20
|
if (text.startsWith("/")) {
|
|
20
21
|
const match = regexEndPattern.test(text);
|
|
@@ -32,6 +33,7 @@ export function testStringForRegex(text) {
|
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
const escapeNonPrintables = (text) => {
|
|
36
|
+
if (typeof text !== "string") return text.toString();
|
|
35
37
|
const t = text.replace(/\n/g, "\\n").replace(/\t/g, "\\t"); // .replace(/\|/g, "\\|");
|
|
36
38
|
let result = "";
|
|
37
39
|
// replace \ with \\ and | with \|
|
|
@@ -61,7 +63,8 @@ const escapeNonPrintables = (text) => {
|
|
|
61
63
|
}
|
|
62
64
|
return result;
|
|
63
65
|
};
|
|
64
|
-
|
|
66
|
+
|
|
67
|
+
function getCommandContent(command) {
|
|
65
68
|
switch (command.type) {
|
|
66
69
|
case "click_element": {
|
|
67
70
|
return `${command.count === 2 ? "Double click" : "Click"} on ${escapeNonPrintables(command.element.name)}`;
|
|
@@ -108,7 +111,18 @@ export function getCommandContent(command) {
|
|
|
108
111
|
}
|
|
109
112
|
}
|
|
110
113
|
|
|
111
|
-
|
|
114
|
+
function getExamplesContent(parametersMap, datasets) {
|
|
115
|
+
if (datasets && datasets.length > 0) {
|
|
116
|
+
let result = "";
|
|
117
|
+
const keys = Object.keys(parametersMap);
|
|
118
|
+
result += "\t\tExamples:\n";
|
|
119
|
+
|
|
120
|
+
result += `\t\t| ${keys.join(" | ")} |\n`;
|
|
121
|
+
for (const dataset of datasets) {
|
|
122
|
+
result += `\t\t| ${dataset.data.map((d) => escapeNonPrintables(d.value)).join(" | ")} |\n`;
|
|
123
|
+
}
|
|
124
|
+
return result;
|
|
125
|
+
}
|
|
112
126
|
const keys = Object.keys(parametersMap);
|
|
113
127
|
const l = keys.length;
|
|
114
128
|
let result = "\t\tExamples:\n";
|
|
@@ -144,19 +158,19 @@ function getTagsContent(scenario, featureFileObject) {
|
|
|
144
158
|
return tagsContent;
|
|
145
159
|
}
|
|
146
160
|
|
|
147
|
-
|
|
161
|
+
function getScenarioContent({ scenario }, featureFileObject) {
|
|
148
162
|
const prametersMap = scenario.parametersMap ?? {};
|
|
149
163
|
const isParmatersMapEmpty = Object.keys(prametersMap).length === 0;
|
|
150
164
|
let scenarioContent = isParmatersMapEmpty ? `Scenario: ${scenario.name}\n` : `Scenario Outline: ${scenario.name}\n`;
|
|
151
165
|
|
|
152
|
-
|
|
166
|
+
const tagsContent = getTagsContent(scenario, featureFileObject);
|
|
153
167
|
|
|
154
|
-
scenarioContent =
|
|
168
|
+
scenarioContent = `\t${tagsContent}\n${scenarioContent}`;
|
|
155
169
|
|
|
156
170
|
for (const step of scenario.steps) {
|
|
157
171
|
const commands = step.commands ?? [];
|
|
158
|
-
|
|
159
|
-
|
|
172
|
+
const commentContents = commands.map(getCommandContent).map(escapeNonPrintables);
|
|
173
|
+
const commentContent = commentContents.filter((content) => content.length > 0).join(", ");
|
|
160
174
|
|
|
161
175
|
if (commentContent) {
|
|
162
176
|
scenarioContent += `\t# ${commentContent}\n`;
|
|
@@ -164,18 +178,19 @@ export function getScenarioContent({ scenario }, featureFileObject) {
|
|
|
164
178
|
scenarioContent += `\t${step.keyword} ${escapeString(step.text)}\n`;
|
|
165
179
|
}
|
|
166
180
|
if (!isParmatersMapEmpty) {
|
|
167
|
-
scenarioContent += getExamplesContent(prametersMap);
|
|
181
|
+
scenarioContent += getExamplesContent(prametersMap, scenario.datasets);
|
|
168
182
|
}
|
|
169
183
|
return scenarioContent;
|
|
170
184
|
}
|
|
171
|
-
|
|
185
|
+
|
|
186
|
+
function escapeString(str) {
|
|
172
187
|
// Step 1: Replace all literal newline characters with a temporary placeholder
|
|
173
188
|
const placeholder = "\\n";
|
|
174
189
|
str = str.replace(/\n/g, placeholder);
|
|
175
190
|
return str;
|
|
176
|
-
}
|
|
191
|
+
}
|
|
177
192
|
|
|
178
|
-
|
|
193
|
+
function GherkinToObject(gherkin) {
|
|
179
194
|
const obj = {
|
|
180
195
|
featureName: "",
|
|
181
196
|
scenarios: [],
|
|
@@ -211,7 +226,7 @@ const GherkinToObject = (gherkin) => {
|
|
|
211
226
|
}
|
|
212
227
|
|
|
213
228
|
const getScenario = () => {
|
|
214
|
-
|
|
229
|
+
const scenario = {
|
|
215
230
|
name: "",
|
|
216
231
|
tags: [],
|
|
217
232
|
steps: [],
|
|
@@ -235,7 +250,7 @@ const GherkinToObject = (gherkin) => {
|
|
|
235
250
|
skipEmptyLines();
|
|
236
251
|
|
|
237
252
|
if (idx >= lines.length) {
|
|
238
|
-
return
|
|
253
|
+
return scenario;
|
|
239
254
|
}
|
|
240
255
|
|
|
241
256
|
while (
|
|
@@ -288,29 +303,50 @@ const GherkinToObject = (gherkin) => {
|
|
|
288
303
|
skipEmptyLines();
|
|
289
304
|
}
|
|
290
305
|
return obj;
|
|
291
|
-
}
|
|
306
|
+
}
|
|
292
307
|
|
|
293
308
|
// remove lines starting with "Scenario:" or "Scenario Outline:" that contain the scenario name until the next scenario and then append the new scenario content
|
|
294
309
|
// assumes that there are no multiple scenarios with the same name and having comments and tags in each scenario
|
|
295
|
-
function updateExistingScenario({
|
|
310
|
+
function updateExistingScenario({
|
|
311
|
+
featureFileContent,
|
|
312
|
+
scenarioName: newScenarioName,
|
|
313
|
+
scenarioContent: newScenarioContent,
|
|
314
|
+
}) {
|
|
296
315
|
const featureFileObject = GherkinToObject(featureFileContent);
|
|
297
316
|
if (featureFileObject.error) return "error";
|
|
317
|
+
|
|
298
318
|
const results = [];
|
|
299
|
-
let skipScenarioIndex = -1;
|
|
300
319
|
results.push(`Feature: ${featureFileObject.featureName}`);
|
|
320
|
+
|
|
321
|
+
let indexOfScenarioToUpdate = -1;
|
|
322
|
+
|
|
323
|
+
for (let i = 0; i < featureFileObject.scenarios.length; i++) {
|
|
324
|
+
if (featureFileObject.scenarios[i].name === newScenarioName) {
|
|
325
|
+
indexOfScenarioToUpdate = i;
|
|
326
|
+
break;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
301
330
|
for (let i = 0; i < featureFileObject.scenarios.length; i++) {
|
|
302
331
|
const scenario = featureFileObject.scenarios[i];
|
|
303
|
-
|
|
304
|
-
|
|
332
|
+
|
|
333
|
+
if (i === indexOfScenarioToUpdate) {
|
|
334
|
+
results.push("");
|
|
335
|
+
results.push(newScenarioContent.trim());
|
|
336
|
+
results.push("");
|
|
305
337
|
continue;
|
|
306
338
|
}
|
|
307
|
-
|
|
339
|
+
|
|
340
|
+
const scenarioHeader = `${scenario.hasParams ? "Scenario Outline" : "Scenario"}: ${scenario.name}`;
|
|
308
341
|
let tagsLine;
|
|
342
|
+
|
|
309
343
|
if (scenario.tags?.length > 0) {
|
|
310
|
-
tagsLine =
|
|
344
|
+
tagsLine = scenario.tags.map((t) => `@${t}`).join(" ");
|
|
311
345
|
}
|
|
312
|
-
|
|
313
|
-
results.push(`\t${
|
|
346
|
+
|
|
347
|
+
if (tagsLine) results.push(`\t${tagsLine}`);
|
|
348
|
+
results.push(`\t${scenarioHeader}`);
|
|
349
|
+
|
|
314
350
|
for (const step of scenario.steps) {
|
|
315
351
|
if (step.type === "examples") {
|
|
316
352
|
results.push(`\t\tExamples:`);
|
|
@@ -323,14 +359,12 @@ function updateExistingScenario({ featureFileContent, scenarioName, scenarioCont
|
|
|
323
359
|
}
|
|
324
360
|
results.push("");
|
|
325
361
|
}
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
finalContent = results.join("\n") + "\n" + scenarioContent;
|
|
329
|
-
}
|
|
330
|
-
return finalContent;
|
|
362
|
+
|
|
363
|
+
return results.join("\n");
|
|
331
364
|
}
|
|
332
|
-
|
|
333
|
-
|
|
365
|
+
|
|
366
|
+
async function updateFeatureFile({ featureName, scenario, override, projectDir, logger }) {
|
|
367
|
+
const featureFilePath = path.join(projectDir, "features", `${featureName}.feature`);
|
|
334
368
|
const isFeatureFileExists = existsSync(featureFilePath);
|
|
335
369
|
const scenarioContent = getScenarioContent(
|
|
336
370
|
{ scenario },
|
|
@@ -361,13 +395,13 @@ export async function updateFeatureFile({ featureName, scenario, override, proje
|
|
|
361
395
|
plugins: ["prettier-plugin-gherkin"],
|
|
362
396
|
});
|
|
363
397
|
} catch (error) {
|
|
364
|
-
|
|
398
|
+
logger.error("Error formatting feature file content with Prettier:", error);
|
|
365
399
|
}
|
|
366
400
|
writeFileSync(featureFilePath, updatedFeatureFileContent);
|
|
367
401
|
return;
|
|
368
402
|
}
|
|
369
403
|
}
|
|
370
|
-
let updatedFeatureFileContent = featureFileContent
|
|
404
|
+
let updatedFeatureFileContent = `${featureFileContent}\n${scenarioContent}`;
|
|
371
405
|
try {
|
|
372
406
|
updatedFeatureFileContent = await prettier.format(updatedFeatureFileContent, {
|
|
373
407
|
...prettierConfig,
|
|
@@ -375,7 +409,7 @@ export async function updateFeatureFile({ featureName, scenario, override, proje
|
|
|
375
409
|
plugins: ["prettier-plugin-gherkin"],
|
|
376
410
|
});
|
|
377
411
|
} catch (error) {
|
|
378
|
-
|
|
412
|
+
logger.error("Error formatting feature file content with Prettier:", error);
|
|
379
413
|
}
|
|
380
414
|
writeFileSync(featureFilePath, updatedFeatureFileContent);
|
|
381
415
|
} else {
|
|
@@ -387,8 +421,22 @@ export async function updateFeatureFile({ featureName, scenario, override, proje
|
|
|
387
421
|
plugins: ["prettier-plugin-gherkin"],
|
|
388
422
|
});
|
|
389
423
|
} catch (error) {
|
|
390
|
-
|
|
424
|
+
logger.error("Error formatting feature file content with Prettier:", error);
|
|
391
425
|
}
|
|
392
426
|
writeFileSync(featureFilePath, featureFileContent);
|
|
393
427
|
}
|
|
394
428
|
}
|
|
429
|
+
|
|
430
|
+
export {
|
|
431
|
+
containsScenario,
|
|
432
|
+
testStringForRegex,
|
|
433
|
+
escapeNonPrintables,
|
|
434
|
+
getCommandContent,
|
|
435
|
+
getExamplesContent,
|
|
436
|
+
getTagsContent,
|
|
437
|
+
getScenarioContent,
|
|
438
|
+
escapeString,
|
|
439
|
+
GherkinToObject,
|
|
440
|
+
updateExistingScenario,
|
|
441
|
+
updateFeatureFile,
|
|
442
|
+
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { getErrorMessage } from "../utils/socket_logger.js";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import {
|
|
5
|
+
FIXED_FILE_NAMES,
|
|
6
|
+
FIXED_FOLDER_NAMES,
|
|
7
|
+
SaveJobErrorType,
|
|
8
|
+
UpdateStepDefinitionsError,
|
|
9
|
+
UTF8_ENCODING,
|
|
10
|
+
} from "./constants.js";
|
|
11
|
+
import url from "node:url";
|
|
12
|
+
const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
|
|
13
|
+
|
|
14
|
+
export function handleFileInitOps(projectDir, logger) {
|
|
15
|
+
try {
|
|
16
|
+
const stepDefinitionFolderPath = path.join(
|
|
17
|
+
projectDir,
|
|
18
|
+
FIXED_FOLDER_NAMES.FEATURES,
|
|
19
|
+
FIXED_FOLDER_NAMES.STEP_DEFINITIONS
|
|
20
|
+
);
|
|
21
|
+
if (!existsSync(stepDefinitionFolderPath)) {
|
|
22
|
+
mkdirSync(stepDefinitionFolderPath, { recursive: true });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const utilsFilePath = path.join(
|
|
26
|
+
projectDir,
|
|
27
|
+
FIXED_FOLDER_NAMES.FEATURES,
|
|
28
|
+
FIXED_FOLDER_NAMES.STEP_DEFINITIONS,
|
|
29
|
+
FIXED_FILE_NAMES.UTILS
|
|
30
|
+
);
|
|
31
|
+
const utilsTemplateFilePath = path.join(
|
|
32
|
+
__dirname,
|
|
33
|
+
`../../${FIXED_FOLDER_NAMES.ASSETS}`,
|
|
34
|
+
FIXED_FOLDER_NAMES.TEMPLATES,
|
|
35
|
+
FIXED_FILE_NAMES.UTILS_TEMPLATE
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
if (!existsSync(utilsTemplateFilePath)) {
|
|
39
|
+
throw new Error(`Utils template not found at: ${utilsTemplateFilePath}`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const utilsContent = readFileSync(utilsTemplateFilePath, UTF8_ENCODING);
|
|
43
|
+
writeFileSync(utilsFilePath, utilsContent, UTF8_ENCODING);
|
|
44
|
+
|
|
45
|
+
const hooksTemplateFilePath = path.join(
|
|
46
|
+
__dirname,
|
|
47
|
+
`../../${FIXED_FOLDER_NAMES.ASSETS}`,
|
|
48
|
+
FIXED_FOLDER_NAMES.TEMPLATES,
|
|
49
|
+
FIXED_FILE_NAMES.HOOKS_TEMPLATE
|
|
50
|
+
);
|
|
51
|
+
if (existsSync(hooksTemplateFilePath)) {
|
|
52
|
+
const hooksFilePath = path.join(stepDefinitionFolderPath, FIXED_FILE_NAMES.HOOKS);
|
|
53
|
+
const hooksContent = readFileSync(hooksTemplateFilePath, UTF8_ENCODING);
|
|
54
|
+
writeFileSync(hooksFilePath, hooksContent, UTF8_ENCODING);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const featureFolder = path.join(projectDir, FIXED_FOLDER_NAMES.FEATURES);
|
|
58
|
+
|
|
59
|
+
return { featureFolder, utilsFilePath, utilsContent };
|
|
60
|
+
} catch (error) {
|
|
61
|
+
throw new UpdateStepDefinitionsError({
|
|
62
|
+
type: SaveJobErrorType.FILE_SYSTEM_ERROR,
|
|
63
|
+
message: "❌ Failed to initialize step definition folder structure",
|
|
64
|
+
meta: { reason: getErrorMessage(error) },
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export async function potentialErrorWrapper(fn, errorMessage, type, logger) {
|
|
70
|
+
try {
|
|
71
|
+
return await fn();
|
|
72
|
+
} catch (error) {
|
|
73
|
+
logger.error(`❌ ${errorMessage}: ${getErrorMessage(error)}`);
|
|
74
|
+
throw new UpdateStepDefinitionsError({
|
|
75
|
+
type: type,
|
|
76
|
+
message: errorMessage,
|
|
77
|
+
meta: { reason: getErrorMessage(error) },
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { io } from "socket.io-client";
|
|
2
|
+
import { BVTRecorderInit } from "./bvt_init.js";
|
|
3
|
+
import { loadArgs, showUsage, validateCLIArg } from "../cli_helpers.js";
|
|
4
|
+
const requiredEnvVars = ["PROJECT_ID", "BLINQ_TOKEN", "SESSION_ID", "WORKER_WS_SERVER_URL", "REMOTE_ORIGINS_URL"];
|
|
5
|
+
function validateEnvVariables() {
|
|
6
|
+
const missingVars = requiredEnvVars.filter((varName) => !process.env[varName]);
|
|
7
|
+
if (missingVars.length > 0) {
|
|
8
|
+
throw new Error(`Missing required environment variables: ${missingVars.join(", ")}`);
|
|
9
|
+
}
|
|
10
|
+
else {
|
|
11
|
+
console.log("All required environment variables are set.");
|
|
12
|
+
requiredEnvVars.forEach((varName) => {
|
|
13
|
+
console.log(`${varName}: ${process.env[varName]}`);
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function getEnvironmentConfig() {
|
|
18
|
+
const args = loadArgs();
|
|
19
|
+
const projectDir = args[0] || process.env.PROJECT_ID;
|
|
20
|
+
const envName = args[1]?.split("=")[1] || process.env.ENV_NAME;
|
|
21
|
+
const roomId = args[2] || process.env.SESSION_ID;
|
|
22
|
+
const shouldTakeScreenshot = args[3] || process.env.SHOULD_TAKE_SCREENSHOT;
|
|
23
|
+
const TOKEN = process.env.BLINQ_TOKEN;
|
|
24
|
+
try {
|
|
25
|
+
validateCLIArg(projectDir, "projectDir");
|
|
26
|
+
validateCLIArg(envName, "envName");
|
|
27
|
+
validateCLIArg(roomId, "roomId");
|
|
28
|
+
validateCLIArg(shouldTakeScreenshot, "shouldTakeScreenshot");
|
|
29
|
+
if (!TOKEN) {
|
|
30
|
+
throw new Error("BLINQ_TOKEN env variable not set");
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
const usage = `Usage: node bvt_recorder.js <projectDir> <envName> <roomId>`;
|
|
35
|
+
if (error instanceof Error) {
|
|
36
|
+
showUsage(error, usage);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
const unknownError = new Error("An unknown error occurred");
|
|
40
|
+
showUsage(unknownError, usage);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return { envName, projectDir, roomId, TOKEN };
|
|
44
|
+
}
|
|
45
|
+
function initWebBVTRecorder() {
|
|
46
|
+
const socket = io(process.env.WORKER_WS_SERVER_URL, {
|
|
47
|
+
path: "/ws",
|
|
48
|
+
transports: ["websocket", "polling"],
|
|
49
|
+
reconnection: true,
|
|
50
|
+
});
|
|
51
|
+
validateEnvVariables();
|
|
52
|
+
const { envName, projectDir, roomId, TOKEN } = getEnvironmentConfig();
|
|
53
|
+
BVTRecorderInit({
|
|
54
|
+
envName: envName,
|
|
55
|
+
projectDir,
|
|
56
|
+
roomId,
|
|
57
|
+
TOKEN,
|
|
58
|
+
socket,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
initWebBVTRecorder();
|
package/bin/client/recording.js
CHANGED
|
@@ -12,6 +12,7 @@ class ScenarioUploadService {
|
|
|
12
12
|
this.runsApiBaseURL + "/scenarios/create",
|
|
13
13
|
{
|
|
14
14
|
name,
|
|
15
|
+
branch: process.env.GIT_BRANCH ? process.env.GIT_BRANCH : "main",
|
|
15
16
|
},
|
|
16
17
|
{
|
|
17
18
|
headers: {
|
|
@@ -107,6 +108,7 @@ class ScenarioUploadService {
|
|
|
107
108
|
{
|
|
108
109
|
scenarioId,
|
|
109
110
|
projectId,
|
|
111
|
+
testcase_id: process.env.TESTCASE_ID,
|
|
110
112
|
},
|
|
111
113
|
{
|
|
112
114
|
headers: {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
function getAppDataDir(project_id) {
|
|
4
|
+
if (process.env.BLINQ_APPDATA_DIR) {
|
|
5
|
+
return path.join(process.env.BLINQ_APPDATA_DIR, "blinq.io", project_id);
|
|
6
|
+
}
|
|
7
|
+
let appDataDir;
|
|
8
|
+
switch (process.platform) {
|
|
9
|
+
case "win32":
|
|
10
|
+
appDataDir = path.join(process.env.APPDATA, "blinq.io", project_id);
|
|
11
|
+
break;
|
|
12
|
+
case "darwin":
|
|
13
|
+
appDataDir = path.join(os.homedir(), "Library", "Application Support", "blinq.io", project_id);
|
|
14
|
+
break;
|
|
15
|
+
default:
|
|
16
|
+
appDataDir = path.join(os.homedir(), ".config", "blinq.io", project_id);
|
|
17
|
+
break;
|
|
18
|
+
}
|
|
19
|
+
return appDataDir;
|
|
20
|
+
}
|
|
21
|
+
export { getAppDataDir };
|
|
@@ -1,132 +1,107 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
1
|
+
function getErrorMessage(err) {
|
|
2
|
+
if (typeof err === "string") {
|
|
3
|
+
return err;
|
|
4
|
+
}
|
|
5
|
+
else if (err instanceof Error) {
|
|
6
|
+
return err.message;
|
|
7
|
+
}
|
|
8
|
+
else {
|
|
9
|
+
try {
|
|
10
|
+
return JSON.stringify(err);
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return String(err);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function responseSize(response) {
|
|
18
|
+
try {
|
|
19
|
+
if (typeof response !== "string") {
|
|
20
|
+
return new Blob([JSON.stringify(response)]).size;
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
return new Blob([response]).size;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return -1;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
16
30
|
/**
|
|
17
31
|
* SocketLogger - Singleton for structured socket-based logging.
|
|
18
32
|
*
|
|
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
33
|
* @example
|
|
28
|
-
* import
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
34
|
+
* import SocketLogger from "./socket_logger";
|
|
35
|
+
* SocketLogger.getInstance().init(socket, { context: "BVTRecorder" });
|
|
36
|
+
* SocketLogger.getInstance().info("Step started", { step: 2 });
|
|
37
|
+
* SocketLogger.getInstance().error("Failed!", { error: "bad stuff" });
|
|
32
38
|
*/
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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;
|
|
39
|
+
export class SocketLogger {
|
|
40
|
+
static instance;
|
|
41
|
+
socket = null;
|
|
42
|
+
defaultContext = "BVTRecorder";
|
|
43
|
+
defaultEventName = "BVTRecorder.log";
|
|
44
|
+
constructor() { }
|
|
45
|
+
static getInstance() {
|
|
46
|
+
if (!SocketLogger.instance) {
|
|
47
|
+
SocketLogger.instance = new SocketLogger();
|
|
48
|
+
}
|
|
49
|
+
return SocketLogger.instance;
|
|
70
50
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
51
|
+
init(sock, opts) {
|
|
52
|
+
this.socket = sock;
|
|
53
|
+
this.defaultContext = opts?.context || "";
|
|
54
|
+
this.defaultEventName = opts?.eventName || "recorder.log";
|
|
55
|
+
}
|
|
56
|
+
log(level, message, extra, context) {
|
|
57
|
+
if (!this.socket || typeof this.socket.emit !== "function")
|
|
58
|
+
return;
|
|
59
|
+
const data = typeof message === "object" ? message : { message };
|
|
60
|
+
let dataSize = 0;
|
|
61
|
+
try {
|
|
62
|
+
dataSize = Buffer.byteLength(JSON.stringify(data || ""), "utf8");
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
dataSize = -1;
|
|
66
|
+
}
|
|
67
|
+
const eventPayload = {
|
|
68
|
+
level,
|
|
69
|
+
context: context || this.defaultContext,
|
|
70
|
+
data: JSON.stringify({
|
|
71
|
+
...data,
|
|
72
|
+
errorMessage: level === "error" && data ? getErrorMessage(data) : undefined,
|
|
73
|
+
}),
|
|
74
|
+
timestamp: new Date().toISOString(),
|
|
75
|
+
dataSize,
|
|
76
|
+
...extra,
|
|
77
|
+
};
|
|
78
|
+
try {
|
|
79
|
+
if (this.socket) {
|
|
80
|
+
this.socket.emit(this.defaultEventName, eventPayload);
|
|
81
|
+
}
|
|
82
|
+
console.log(`${context ?? this.defaultContext} [${level.toUpperCase()}]:`, {
|
|
83
|
+
...data,
|
|
84
|
+
...extra,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
catch (e) {
|
|
88
|
+
console.error("Socket logging error:", e);
|
|
89
|
+
console.log("Socket event payload:", eventPayload);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
info(msg, ext, ctx) {
|
|
93
|
+
this.log("info", msg, ext, ctx);
|
|
94
|
+
}
|
|
95
|
+
warn(msg, ext, ctx) {
|
|
96
|
+
this.log("warn", msg, ext, ctx);
|
|
97
|
+
}
|
|
98
|
+
debug(msg, ext, ctx) {
|
|
99
|
+
this.log("debug", msg, ext, ctx);
|
|
100
|
+
}
|
|
101
|
+
error(msg, ext, ctx) {
|
|
102
|
+
this.log("error", msg, ext, ctx);
|
|
90
103
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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;
|
|
104
|
+
}
|
|
105
|
+
const socketLoggerInstance = SocketLogger.getInstance();
|
|
106
|
+
export default socketLoggerInstance;
|
|
107
|
+
export { getErrorMessage, responseSize };
|