@dev-blinq/cucumber_client 1.0.1180-dev → 1.0.1180-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 +220 -0
- package/bin/assets/preload/accessibility.js +1 -1
- package/bin/assets/preload/find_context.js +1 -1
- package/bin/assets/preload/generateSelector.js +24 -0
- package/bin/assets/preload/locators.js +18 -0
- package/bin/assets/preload/recorderv3.js +80 -9
- package/bin/assets/preload/unique_locators.js +24 -3
- package/bin/assets/scripts/aria_snapshot.js +235 -0
- package/bin/assets/scripts/dom_attr.js +372 -0
- package/bin/assets/scripts/dom_element.js +0 -0
- package/bin/assets/scripts/dom_parent.js +185 -0
- package/bin/assets/scripts/event_utils.js +105 -0
- package/bin/assets/scripts/pw.js +7886 -0
- package/bin/assets/scripts/recorder.js +1147 -0
- package/bin/assets/scripts/snapshot_capturer.js +155 -0
- package/bin/assets/scripts/unique_locators.js +844 -0
- package/bin/assets/scripts/yaml.js +4770 -0
- package/bin/assets/templates/page_template.txt +2 -16
- package/bin/assets/templates/utils_template.txt +65 -12
- package/bin/client/cli_helpers.js +0 -1
- package/bin/client/code_cleanup/utils.js +43 -14
- package/bin/client/code_gen/code_inversion.js +45 -13
- package/bin/client/code_gen/index.js +3 -0
- package/bin/client/code_gen/page_reflection.js +37 -20
- package/bin/client/code_gen/playwright_codeget.js +151 -45
- package/bin/client/cucumber/feature.js +96 -42
- package/bin/client/cucumber/project_to_document.js +8 -7
- package/bin/client/cucumber/steps_definitions.js +49 -16
- package/bin/client/local_agent.js +9 -7
- package/bin/client/operations/dump_tree.js +159 -5
- package/bin/client/playground/playground.js +1 -1
- package/bin/client/project.js +6 -2
- package/bin/client/recorderv3/bvt_recorder.js +279 -81
- package/bin/client/recorderv3/cli.js +1 -0
- package/bin/client/recorderv3/implemented_steps.js +111 -11
- package/bin/client/recorderv3/index.js +48 -4
- package/bin/client/recorderv3/network.js +299 -0
- package/bin/client/recorderv3/step_runner.js +183 -13
- package/bin/client/recorderv3/step_utils.js +159 -14
- package/bin/client/recorderv3/update_feature.js +53 -28
- package/bin/client/recording.js +8 -0
- package/bin/client/run_cucumber.js +116 -4
- package/bin/client/scenario_report.js +112 -50
- package/bin/client/test_scenario.js +0 -1
- package/bin/index.js +1 -0
- package/package.json +15 -8
- package/bin/client/code_gen/get_implemented_steps.js +0 -27
package/bin/client/recording.js
CHANGED
|
@@ -5,9 +5,14 @@ import { Step } from "../client/cucumber/feature.js";
|
|
|
5
5
|
This list need to be in sync with the list exist in the commands.js file
|
|
6
6
|
*/
|
|
7
7
|
const Types = {
|
|
8
|
+
API: "api",
|
|
8
9
|
CLICK: "click_element",
|
|
9
10
|
CLICK_SIMPLE: "click_simple",
|
|
11
|
+
PARAMETERIZED_CLICK: "parameterized_click",
|
|
12
|
+
CONTEXT_CLICK: "context_click",
|
|
10
13
|
NAVIGATE: "navigate",
|
|
14
|
+
GO_BACK: "browser_go_back",
|
|
15
|
+
GO_FORWARD: "browser_go_forward",
|
|
11
16
|
FILL: "fill_element",
|
|
12
17
|
FILL_SIMPLE: "fill_simple",
|
|
13
18
|
EXECUTE: "execute_page_method",
|
|
@@ -29,6 +34,7 @@ const Types = {
|
|
|
29
34
|
SET_COMBO: "set_combo",
|
|
30
35
|
HOVER: "hover_element",
|
|
31
36
|
EXTRACT_ATTRIBUTE: "extract_attribute",
|
|
37
|
+
EXTRACT_PROPERTY: "extract_property",
|
|
32
38
|
CLOSE_PAGE: "close_page",
|
|
33
39
|
SET_DATE_TIME: "set_date_time",
|
|
34
40
|
SET_VIEWPORT: "set_viewport",
|
|
@@ -40,11 +46,13 @@ const Types = {
|
|
|
40
46
|
SET_INPUT: "set_input",
|
|
41
47
|
WAIT_FOR_USER_INPUT: "wait_for_user_input",
|
|
42
48
|
VERIFY_ATTRIBUTE: "verify_element_attribute",
|
|
49
|
+
VERIFY_PROPERTY: "verify_element_property",
|
|
43
50
|
FILL_UNKNOWN: "fill_unknown",
|
|
44
51
|
VERIFY_TEXT_RELATED_TO_TEXT: "verify_text_in_relation",
|
|
45
52
|
VERIFY_FILE_EXISTS: "verify_file_exists",
|
|
46
53
|
SET_INPUT_FILES: "set_input_files",
|
|
47
54
|
VERIFY_PAGE_SNAPSHOT: "verify_page_snapshot",
|
|
55
|
+
CONDITIONAL_WAIT: "conditional_wait",
|
|
48
56
|
};
|
|
49
57
|
class Recording {
|
|
50
58
|
steps = [];
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, mkdirSync } from "fs";
|
|
1
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync } from "fs";
|
|
2
2
|
import logger from "../logger.js";
|
|
3
3
|
import path from "path";
|
|
4
4
|
import { scenarioResolution } from "./cucumber/feature.js";
|
|
@@ -13,6 +13,77 @@ import crypto from "crypto";
|
|
|
13
13
|
//import debug from "debug";
|
|
14
14
|
process.env.NODE_TLS_REJECT_UNAUTHORIZED = 0;
|
|
15
15
|
|
|
16
|
+
// Initialize the editorLogs array to collect all logs
|
|
17
|
+
const editorLogs = [];
|
|
18
|
+
|
|
19
|
+
// Store original console methods
|
|
20
|
+
const originalConsoleLog = console.log;
|
|
21
|
+
const originalConsoleError = console.error;
|
|
22
|
+
|
|
23
|
+
// Store original process stdout and stderr write methods
|
|
24
|
+
const originalStdoutWrite = process.stdout.write;
|
|
25
|
+
const originalStderrWrite = process.stderr.write;
|
|
26
|
+
|
|
27
|
+
// Override console.log
|
|
28
|
+
console.log = function (...args) {
|
|
29
|
+
const logEntry = {
|
|
30
|
+
message: args.map((arg) => (typeof arg === "object" ? JSON.stringify(arg) : String(arg))).join(" "),
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
editorLogs.push(logEntry);
|
|
34
|
+
originalConsoleLog.apply(console, args);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// Override console.error
|
|
38
|
+
console.error = function (...args) {
|
|
39
|
+
const logEntry = {
|
|
40
|
+
message: args.map((arg) => (typeof arg === "object" ? JSON.stringify(arg) : String(arg))).join(" "),
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
editorLogs.push(logEntry);
|
|
44
|
+
originalConsoleError.apply(console, args);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// Override process.stdout.write
|
|
48
|
+
process.stdout.write = function (chunk, encoding, callback) {
|
|
49
|
+
const logEntry = {
|
|
50
|
+
message: chunk.toString(),
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
editorLogs.push(logEntry);
|
|
54
|
+
return originalStdoutWrite.apply(process.stdout, arguments);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// Override process.stderr.write
|
|
58
|
+
process.stderr.write = function (chunk, encoding, callback) {
|
|
59
|
+
const logEntry = {
|
|
60
|
+
message: chunk.toString(),
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
editorLogs.push(logEntry);
|
|
64
|
+
return originalStderrWrite.apply(process.stderr, arguments);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// Function to write logs to file
|
|
68
|
+
const writeLogsToFile = (filePath) => {
|
|
69
|
+
try {
|
|
70
|
+
const dirPath = path.dirname(filePath);
|
|
71
|
+
|
|
72
|
+
// Create directory if it doesn't exist
|
|
73
|
+
if (!existsSync(dirPath)) {
|
|
74
|
+
mkdirSync(dirPath, { recursive: true });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Format logs as plain message on each line
|
|
78
|
+
const logText = editorLogs.map((log) => `${log.message}`).join("\n");
|
|
79
|
+
|
|
80
|
+
// Write logs to plain text file
|
|
81
|
+
writeFileSync(filePath, logText, { encoding: "utf8" });
|
|
82
|
+
} catch (error) {
|
|
83
|
+
logger.error(`Failed to write logs to file: ${error.message}`);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
16
87
|
//do something when app is closing
|
|
17
88
|
|
|
18
89
|
const runCucumber = async (
|
|
@@ -60,6 +131,10 @@ const runCucumber = async (
|
|
|
60
131
|
} else if (!process.env.NODE_ENV_BLINQ) {
|
|
61
132
|
serviceUrl = "https://logic.blinq.io";
|
|
62
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);
|
|
63
138
|
} else {
|
|
64
139
|
serviceUrl = process.env.NODE_ENV_BLINQ.replace("api", "logic");
|
|
65
140
|
logger.info("Running in custom mode: " + serviceUrl);
|
|
@@ -93,7 +168,14 @@ const runCucumber = async (
|
|
|
93
168
|
}
|
|
94
169
|
return true;
|
|
95
170
|
});
|
|
96
|
-
process.on("exit", async () =>
|
|
171
|
+
process.on("exit", async () => {
|
|
172
|
+
// Write logs to file before exiting
|
|
173
|
+
if (result.scenarioPath) {
|
|
174
|
+
const logsFilePath = path.join(result.scenarioPath, "editorLogs.log");
|
|
175
|
+
writeLogsToFile(logsFilePath);
|
|
176
|
+
}
|
|
177
|
+
await aiAgent.endScenario();
|
|
178
|
+
});
|
|
97
179
|
const context = {};
|
|
98
180
|
result.context = context;
|
|
99
181
|
if (process.env.E2E === "true") {
|
|
@@ -105,6 +187,9 @@ const runCucumber = async (
|
|
|
105
187
|
envFile = await aiAgent.initProjectAndEnvironment(context);
|
|
106
188
|
} catch (e) {
|
|
107
189
|
logger.error(e.message);
|
|
190
|
+
// Write logs to file before exiting due to error
|
|
191
|
+
const logsFilePath = path.join(result.scenarioPath, "editorLogs.log");
|
|
192
|
+
writeLogsToFile(logsFilePath);
|
|
108
193
|
if (exit) {
|
|
109
194
|
process.exit(1);
|
|
110
195
|
}
|
|
@@ -118,6 +203,9 @@ const runCucumber = async (
|
|
|
118
203
|
}
|
|
119
204
|
if (!existsSync(fullFeatureFilePath)) {
|
|
120
205
|
logger.error("Feature file not found: " + fullFeatureFilePath);
|
|
206
|
+
// Write logs to file before exiting due to error
|
|
207
|
+
const logsFilePath = path.join(result.scenarioPath, "editorLogs.log");
|
|
208
|
+
writeLogsToFile(logsFilePath);
|
|
121
209
|
if (exit) {
|
|
122
210
|
process.exit(1);
|
|
123
211
|
}
|
|
@@ -128,6 +216,9 @@ const runCucumber = async (
|
|
|
128
216
|
feature = await scenarioResolution(fullFeatureFilePath);
|
|
129
217
|
} catch (e) {
|
|
130
218
|
logger.error("Error parsing feature file: " + fullFeatureFilePath);
|
|
219
|
+
// Write logs to file before exiting due to error
|
|
220
|
+
const logsFilePath = path.join(result.scenarioPath, "editorLogs.log");
|
|
221
|
+
writeLogsToFile(logsFilePath);
|
|
131
222
|
if (exit) {
|
|
132
223
|
process.exit(1);
|
|
133
224
|
}
|
|
@@ -136,6 +227,9 @@ const runCucumber = async (
|
|
|
136
227
|
const scenario = feature.getScenario(scenarioName);
|
|
137
228
|
if (!scenario) {
|
|
138
229
|
logger.error("Scenario not found: " + scenarioName);
|
|
230
|
+
// Write logs to file before exiting due to error
|
|
231
|
+
const logsFilePath = path.join(result.scenarioPath, "editorLogs.log");
|
|
232
|
+
writeLogsToFile(logsFilePath);
|
|
139
233
|
if (exit) {
|
|
140
234
|
process.exit(1);
|
|
141
235
|
}
|
|
@@ -146,6 +240,15 @@ const runCucumber = async (
|
|
|
146
240
|
|
|
147
241
|
let scenarioId = findNextIdInFolder("./reports");
|
|
148
242
|
let scenarioPath = "./reports" + "/" + scenarioId;
|
|
243
|
+
const dataFilePath = path.join(scenarioPath, "data.json");
|
|
244
|
+
mkdirSync(path.dirname(dataFilePath), { recursive: true });
|
|
245
|
+
if (process.env.NODE_ENV_BLINQ === "local") {
|
|
246
|
+
let dataPath = aiAgent.project.rootFolder + "/data/data.json";
|
|
247
|
+
if (existsSync(dataPath)) {
|
|
248
|
+
const poolData = JSON.parse(readFileSync(dataPath, "utf-8"));
|
|
249
|
+
writeFileSync(dataFilePath, JSON.stringify(poolData, null, 2), "utf-8");
|
|
250
|
+
}
|
|
251
|
+
}
|
|
149
252
|
result.scenarioPath = scenarioPath;
|
|
150
253
|
aiAgent.initTestData(envFile, path.join(scenarioPath, "data.json"));
|
|
151
254
|
let featureFileRelative = path.relative(projectDir, fullFeatureFilePath);
|
|
@@ -256,7 +359,7 @@ const runCucumber = async (
|
|
|
256
359
|
await aiAgent.createNewStepLocal(
|
|
257
360
|
featureName,
|
|
258
361
|
cucumberStep,
|
|
259
|
-
feature,
|
|
362
|
+
feature.comments,
|
|
260
363
|
userData,
|
|
261
364
|
first,
|
|
262
365
|
previousTasks,
|
|
@@ -283,6 +386,9 @@ const runCucumber = async (
|
|
|
283
386
|
) {
|
|
284
387
|
aiAgent.evaluateScenario();
|
|
285
388
|
}
|
|
389
|
+
// Write logs to file on successful completion
|
|
390
|
+
const logsFilePath = path.join(scenarioPath, "editorLogs.log");
|
|
391
|
+
writeLogsToFile(logsFilePath);
|
|
286
392
|
await aiAgent.endScenario();
|
|
287
393
|
} catch (e) {
|
|
288
394
|
if (reconnect) {
|
|
@@ -303,7 +409,12 @@ const runCucumber = async (
|
|
|
303
409
|
logger.error(e.stack);
|
|
304
410
|
message = e.message + "\n" + e.stack;
|
|
305
411
|
}
|
|
412
|
+
aiAgent.scenarioReport.updateLastCommand({ status: false, error: message });
|
|
413
|
+
// Write logs to file on error
|
|
414
|
+
const logsFilePath = path.join(result.scenarioPath, "editorLogs.log");
|
|
415
|
+
writeLogsToFile(logsFilePath);
|
|
306
416
|
await aiAgent.endScenario();
|
|
417
|
+
|
|
307
418
|
if (exit) {
|
|
308
419
|
process.exit(1);
|
|
309
420
|
}
|
|
@@ -316,4 +427,5 @@ const runCucumber = async (
|
|
|
316
427
|
return result;
|
|
317
428
|
}
|
|
318
429
|
};
|
|
319
|
-
|
|
430
|
+
|
|
431
|
+
export { runCucumber, editorLogs, writeLogsToFile };
|
|
@@ -7,6 +7,8 @@ import { getJsonReport } from "./bvt_json_report.js";
|
|
|
7
7
|
import axios from "axios";
|
|
8
8
|
import { getRunsServiceBaseURL } from "./utils/index.js";
|
|
9
9
|
import { axiosClient } from "./utils/axiosClient.js";
|
|
10
|
+
import { promisify } from "util";
|
|
11
|
+
import { exec } from "child_process";
|
|
10
12
|
|
|
11
13
|
const BATCH_SIZE = 10;
|
|
12
14
|
const MAX_RETRIES = 3;
|
|
@@ -31,17 +33,12 @@ const findNextIdInFolder = (folder) => {
|
|
|
31
33
|
// get temp file path from --temp-file arg
|
|
32
34
|
const getTempFilePath = () => {
|
|
33
35
|
let tempFilePath = null;
|
|
34
|
-
|
|
35
36
|
for (const arg of process.argv) {
|
|
36
37
|
const [key, path] = arg.split("=");
|
|
37
38
|
if (key === "--temp-file" && !!path) {
|
|
38
39
|
tempFilePath = path;
|
|
39
40
|
}
|
|
40
41
|
}
|
|
41
|
-
|
|
42
|
-
if (tempFilePath === null) {
|
|
43
|
-
tempFilePath = process.env.TEMP_FILE_PATH;
|
|
44
|
-
}
|
|
45
42
|
return tempFilePath;
|
|
46
43
|
};
|
|
47
44
|
|
|
@@ -193,64 +190,114 @@ class ScenarioReport {
|
|
|
193
190
|
fs.writeFileSync(path, zipBlob);
|
|
194
191
|
return path;
|
|
195
192
|
}
|
|
196
|
-
|
|
193
|
+
// Note to future self:
|
|
194
|
+
// Increased the max retries to 3 and added exponential backoff to increase consistency but traded off availability :)
|
|
197
195
|
async _uploadScenario() {
|
|
198
196
|
try {
|
|
199
|
-
|
|
197
|
+
let scenarioDoc;
|
|
198
|
+
let attempts = 1;
|
|
199
|
+
while (attempts < MAX_RETRIES) {
|
|
200
|
+
try {
|
|
201
|
+
scenarioDoc = await this.scenarioUploadService.createScenarioDocument(this.name);
|
|
202
|
+
|
|
203
|
+
break; // exit loop if successful
|
|
204
|
+
} catch (err) {
|
|
205
|
+
attempts++;
|
|
206
|
+
await this._delay(2 ** attempts * 1000);
|
|
207
|
+
if (attempts >= MAX_RETRIES) {
|
|
208
|
+
logger.error(`Failed to upload scenario document (Attempt ${attempts})`);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
200
213
|
const scenarioDocId = scenarioDoc._id;
|
|
201
214
|
const projectId = scenarioDoc.project_id;
|
|
202
215
|
this.scenario_id = scenarioDocId;
|
|
203
216
|
this._saveToFile();
|
|
204
|
-
|
|
205
|
-
// console.log("scenarioDocId", scenarioDocId);
|
|
206
217
|
await this.sendRetrainStats(scenarioDocId);
|
|
207
|
-
// create zip file
|
|
208
218
|
if (process.env.NODE_ENV_BLINQ === "local") {
|
|
209
|
-
|
|
210
|
-
// upload to server
|
|
211
|
-
const formData = new FormData();
|
|
212
|
-
// const file = File(zipFileBlob, "report.zip");
|
|
213
|
-
formData.append(scenarioDocId, fs.readFileSync(zipFilePath), "report.zip");
|
|
214
|
-
await this.scenarioUploadService.upload(formData);
|
|
219
|
+
await this._uploadLocalBundle(scenarioDocId);
|
|
215
220
|
} else {
|
|
216
|
-
|
|
217
|
-
fs.writeFileSync(path.join(this.scenarioPath, "scenario.json"), JSON.stringify(report, null, 2));
|
|
218
|
-
const fileUris = [...this.getFileUrisScreenShotDir(this.scenarioPath), "scenario.json", "network.json"];
|
|
219
|
-
const networkJsonPath = path.join(this.scenarioPath, "network.json");
|
|
220
|
-
if (!fs.existsSync(networkJsonPath)) {
|
|
221
|
-
fs.writeFileSync(networkJsonPath, JSON.stringify({}), "utf-8");
|
|
222
|
-
}
|
|
223
|
-
const presignedUrls = await this.scenarioUploadService.getPresignedUrls(fileUris, scenarioDocId);
|
|
224
|
-
for (let i = 0; i < fileUris.length; i += BATCH_SIZE) {
|
|
225
|
-
const batch = fileUris.slice(i, Math.min(i + BATCH_SIZE, fileUris.length));
|
|
226
|
-
await Promise.all(
|
|
227
|
-
batch
|
|
228
|
-
.filter((fileUrl) => presignedUrls[fileUrl] !== undefined)
|
|
229
|
-
.map(async (fileUrl) => {
|
|
230
|
-
const filePath = path.join(this.scenarioPath, fileUrl);
|
|
231
|
-
for (let j = 0; j < MAX_RETRIES; j++) {
|
|
232
|
-
const success = await this.scenarioUploadService.uploadFile(filePath, presignedUrls[fileUrl]);
|
|
233
|
-
if (success) {
|
|
234
|
-
return;
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
console.error("Failed to upload file", fileUrl);
|
|
238
|
-
})
|
|
239
|
-
);
|
|
240
|
-
}
|
|
241
|
-
await this.scenarioUploadService.uploadComplete(scenarioDocId, projectId);
|
|
221
|
+
await this._uploadRemoteBundle(scenarioDocId, projectId);
|
|
242
222
|
}
|
|
243
223
|
logger.info("Scenario uploaded successfully");
|
|
244
|
-
|
|
245
224
|
this.logReportLink(projectId, scenarioDocId);
|
|
246
|
-
|
|
247
|
-
// delete zip file
|
|
248
|
-
// fs.unlinkSync(this.scenarioPath + '/example.zip');
|
|
249
225
|
} catch (err) {
|
|
250
|
-
logger.
|
|
251
|
-
logger.debug(err);
|
|
252
|
-
|
|
226
|
+
logger.error("Scenario upload failed");
|
|
227
|
+
logger.debug("Error while uploading : ", err);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
// Local environment uploader decomposed the function for better readability
|
|
231
|
+
async _uploadLocalBundle(scenarioDocId) {
|
|
232
|
+
const zipFilePath = await this.createScenarioZip();
|
|
233
|
+
const formData = new FormData();
|
|
234
|
+
formData.append("scenarioId", scenarioDocId);
|
|
235
|
+
formData.append("file", fs.createReadStream(zipFilePath));
|
|
236
|
+
|
|
237
|
+
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
|
238
|
+
try {
|
|
239
|
+
await this.scenarioUploadService.upload(formData);
|
|
240
|
+
fs.unlinkSync(zipFilePath);
|
|
241
|
+
return;
|
|
242
|
+
} catch (err) {
|
|
243
|
+
logger.debug("Local upload attempt failed", { attempt, error: err });
|
|
244
|
+
// added delays between retries for better consistency over availability
|
|
245
|
+
await this._delay(2 ** attempt * 1000);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
throw new Error(`Local upload failed after ${MAX_RETRIES} attempts`);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// decomposed the function for better readability this function is used to upload
|
|
252
|
+
async _uploadRemoteBundle(scenarioDocId, projectId) {
|
|
253
|
+
const report = getJsonReport(this);
|
|
254
|
+
fs.writeFileSync(path.join(this.scenarioPath, "scenario.json"), JSON.stringify(report, null, 2));
|
|
255
|
+
|
|
256
|
+
const fileUris = [
|
|
257
|
+
...this.getFileUrisScreenShotDir(this.scenarioPath),
|
|
258
|
+
"scenario.json",
|
|
259
|
+
"network.json",
|
|
260
|
+
"editorLogs.log",
|
|
261
|
+
];
|
|
262
|
+
const networkJsonPath = path.join(this.scenarioPath, "network.json");
|
|
263
|
+
if (!fs.existsSync(networkJsonPath)) {
|
|
264
|
+
fs.writeFileSync(networkJsonPath, JSON.stringify({}), "utf-8");
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const presignedUrls = await this.scenarioUploadService.getPresignedUrls(fileUris, scenarioDocId);
|
|
268
|
+
const missing = fileUris.filter((uri) => !presignedUrls[uri]);
|
|
269
|
+
if (missing.length) {
|
|
270
|
+
throw new Error(`Missing presigned URLs for: ${missing.join(", ")}`);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Upload files in batches with retries
|
|
274
|
+
for (let i = 0; i < fileUris.length; i += BATCH_SIZE) {
|
|
275
|
+
const batch = fileUris.slice(i, i + BATCH_SIZE);
|
|
276
|
+
await Promise.all(batch.map((uri) => this._retryUploadFile(uri, presignedUrls[uri])));
|
|
253
277
|
}
|
|
278
|
+
|
|
279
|
+
await this.scenarioUploadService.uploadComplete(scenarioDocId, projectId);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Retry helper it will retry the upload of a file if it fails given file uri and url here Max_retires is and each retiry wait time : 2secs ,4secs, 8 secs
|
|
283
|
+
// Note to self : if the still upload fails a lot we have to investigate the file formats too
|
|
284
|
+
async _retryUploadFile(fileUri, url) {
|
|
285
|
+
const filePath = path.join(this.scenarioPath, fileUri);
|
|
286
|
+
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
|
287
|
+
try {
|
|
288
|
+
await this.scenarioUploadService.uploadFile(filePath, url);
|
|
289
|
+
return;
|
|
290
|
+
} catch (err) {
|
|
291
|
+
logger.debug("Upload file attempt failed ", { fileUri, attempt, error: err });
|
|
292
|
+
await this._delay(2 ** attempt * 1000);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
throw new Error(`Failed to upload file ${fileUri} after ${MAX_RETRIES} attempts`);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Utility delay can be used to delay anything (Note to self : Replace it with the built in delay function later)
|
|
299
|
+
_delay(ms) {
|
|
300
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
254
301
|
}
|
|
255
302
|
|
|
256
303
|
logReportLink(projectId, scenarioId) {
|
|
@@ -269,6 +316,12 @@ class ScenarioReport {
|
|
|
269
316
|
baseUrl = process.env.NODE_ENV_BLINQ.replace("api", "app");
|
|
270
317
|
}
|
|
271
318
|
const reportLink = baseUrl + "/" + projectId + "/scenario-report/" + scenarioId;
|
|
319
|
+
try {
|
|
320
|
+
const status = getJsonReport(this).result.status;
|
|
321
|
+
publishReportLinkToGuacServer(reportLink, status === "PASSED");
|
|
322
|
+
} catch (err) {
|
|
323
|
+
logger.error(`Failed to publish report link to guac server: ${err}`);
|
|
324
|
+
}
|
|
272
325
|
logger.info(`Report link :- ${reportLink}`);
|
|
273
326
|
}
|
|
274
327
|
|
|
@@ -424,5 +477,14 @@ class ScenarioReport {
|
|
|
424
477
|
}
|
|
425
478
|
}
|
|
426
479
|
}
|
|
427
|
-
|
|
480
|
+
function publishReportLinkToGuacServer(reportLink, status) {
|
|
481
|
+
try {
|
|
482
|
+
if (existsSync('/tmp/report_publish.sh')) {
|
|
483
|
+
const execAsync = promisify(exec);
|
|
484
|
+
execAsync("sh /tmp/report_publish.sh " + reportLink + " " + status);
|
|
485
|
+
}
|
|
486
|
+
} catch (error) {
|
|
487
|
+
logger.error("Error while publishing report link to Guac server: " + error);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
428
490
|
export { ScenarioReport, findNextIdInFolder };
|
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/package.json
CHANGED
|
@@ -1,22 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dev-blinq/cucumber_client",
|
|
3
|
-
"version": "1.0.1180-
|
|
4
|
-
"description": "",
|
|
3
|
+
"version": "1.0.1180-stage",
|
|
4
|
+
"description": " ",
|
|
5
5
|
"main": "bin/index.js",
|
|
6
6
|
"types": "bin/index.d.ts",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"scripts": {
|
|
9
|
-
"pack": "mkdir build && mkdir build/bin && cp -R ./src/* ./build/bin && cp ./package.json ./build",
|
|
9
|
+
"pack": "npm run bundle && mkdir build && mkdir build/bin && cp -R ./src/* ./build/bin && cp ./package.json ./build",
|
|
10
10
|
"test": "node ./test/test.js",
|
|
11
11
|
"tests_prod": "rm -rf results.json && cross-env NODE_ENV_BLINQ=prod TOKEN=xxx npx mocha --parallel --jobs=10 ./tests",
|
|
12
12
|
"tests": "node ./multi_test_runner.js",
|
|
13
13
|
"lint": "eslint ./src/**/*.js",
|
|
14
14
|
"clean": "rm -rf ./build",
|
|
15
15
|
"build": "npm run clean && npm run pack",
|
|
16
|
+
"build:watch": "npx nodemon --watch src/client --exec 'npm run build'",
|
|
16
17
|
"update_logic": "rm -rf ../logic/node_modules/@dev-blinq && mkdir ../logic/node_modules/@dev-blinq && mkdir ../logic/node_modules/@dev-blinq/cucumber_client && mkdir ../logic/node_modules/@dev-blinq/cucumber_client/bin && mkdir ../logic/node_modules/@dev-blinq/cucumber_client/node_modules && cp -R ./build/* ../logic/node_modules/@dev-blinq/cucumber_client/bin && cp -R ./node_modules/* ../logic/node_modules/@dev-blinq/cucumber_client/node_modules && cp ./package.json ../logic/node_modules/@dev-blinq/cucumber_client/",
|
|
17
18
|
"version-bump": "npm version prepatch --preid=dev",
|
|
18
|
-
"scripts_regression": "cross-env HEADLESS=true npx mocha --bail --parallel --jobs=12 ./scripts_regression/ --timeout 120000",
|
|
19
|
-
"runtime_reg": "cross-env HEADLESS=true npx mocha --bail --parallel --jobs=12 ./runtime_regression/ --timeout 4800000"
|
|
19
|
+
"scripts_regression": "npm run bundle && cross-env HEADLESS=true npx mocha --bail --parallel --jobs=12 ./scripts_regression/ --timeout 120000",
|
|
20
|
+
"runtime_reg": "npm run bundle && cross-env HEADLESS=true npx mocha --bail --parallel --jobs=12 ./runtime_regression/ --timeout 4800000",
|
|
21
|
+
"bundle": "npx tsup",
|
|
22
|
+
"bundle:watch": "npx tsup --watch",
|
|
23
|
+
"regenerate_baselines": "rm -rf ./scripts_regression/locators_baseline/* ./scripts_regression/commands_baseline/* && npm run scripts_regression && npm run scripts_regression"
|
|
20
24
|
},
|
|
21
25
|
"author": "",
|
|
22
26
|
"license": "ISC",
|
|
@@ -26,9 +30,9 @@
|
|
|
26
30
|
"@babel/traverse": "^7.27.1",
|
|
27
31
|
"@babel/types": "^7.27.1",
|
|
28
32
|
"@cucumber/tag-expressions": "^6.1.1",
|
|
29
|
-
"@dev-blinq/cucumber-js": "1.0.
|
|
33
|
+
"@dev-blinq/cucumber-js": "1.0.91-stage",
|
|
30
34
|
"@faker-js/faker": "^8.1.0",
|
|
31
|
-
"automation_model": "1.0.
|
|
35
|
+
"automation_model": "1.0.691-stage",
|
|
32
36
|
"axios": "^1.7.4",
|
|
33
37
|
"chokidar": "^3.6.0",
|
|
34
38
|
"create-require": "^1.1.1",
|
|
@@ -43,6 +47,7 @@
|
|
|
43
47
|
"patch-package": "^8.0.0",
|
|
44
48
|
"playwright-core": "1.52.0",
|
|
45
49
|
"prettier": "^3.2.5",
|
|
50
|
+
"prettier-plugin-gherkin": "^3.1.2",
|
|
46
51
|
"pureimage": "0.4.9",
|
|
47
52
|
"socket.io": "^4.7.5",
|
|
48
53
|
"socket.io-client": "^4.7.5",
|
|
@@ -59,6 +64,8 @@
|
|
|
59
64
|
"eslint": "^8.50.0",
|
|
60
65
|
"http-server": "^14.1.1",
|
|
61
66
|
"mocha": "^10.2.0",
|
|
62
|
-
"readline-sync": "^1.4.10"
|
|
67
|
+
"readline-sync": "^1.4.10",
|
|
68
|
+
"tsup": "^8.5.0",
|
|
69
|
+
"typescript": "^5.8.3"
|
|
63
70
|
}
|
|
64
71
|
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { StepsDefinitions } from "../cucumber/steps_definitions.js";
|
|
2
|
-
import fs from "fs";
|
|
3
|
-
const getImplementedSteps = (projectDir) => {
|
|
4
|
-
try {
|
|
5
|
-
const stepsDefinitions = new StepsDefinitions(projectDir);
|
|
6
|
-
stepsDefinitions.load(false);
|
|
7
|
-
const keys = Object.keys(stepsDefinitions.steps).filter((key) => key !== "Before" && key !== "After");
|
|
8
|
-
return keys;
|
|
9
|
-
} catch (error) {
|
|
10
|
-
console.log(error);
|
|
11
|
-
process.exit(1);
|
|
12
|
-
}
|
|
13
|
-
};
|
|
14
|
-
const args = process.argv.slice(2);
|
|
15
|
-
if (args.length < 1 || args.length > 2) {
|
|
16
|
-
console.log("Usage: node get_implemented_steps.js <projectDir> [<outputFilePath>]");
|
|
17
|
-
process.exit(1);
|
|
18
|
-
}
|
|
19
|
-
const projectDir = args[0];
|
|
20
|
-
const implementedSteps = getImplementedSteps(projectDir);
|
|
21
|
-
const output = JSON.stringify(implementedSteps, null, 2);
|
|
22
|
-
if (args.length === 2) {
|
|
23
|
-
const outputFilePath = args[1];
|
|
24
|
-
fs.writeFileSync(outputFilePath, output);
|
|
25
|
-
} else {
|
|
26
|
-
console.log(output);
|
|
27
|
-
}
|