@dev-blinq/cucumber_client 1.0.1176-dev → 1.0.1176-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 +85 -11
- 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 +112 -18
- 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 +152 -48
- 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 +59 -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 +236 -79
- package/bin/client/recorderv3/cli.js +1 -0
- package/bin/client/recorderv3/implemented_steps.js +111 -11
- package/bin/client/recorderv3/index.js +45 -4
- package/bin/client/recorderv3/network.js +299 -0
- package/bin/client/recorderv3/step_runner.js +179 -13
- package/bin/client/recorderv3/step_utils.js +159 -14
- package/bin/client/recorderv3/update_feature.js +54 -29
- 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
|
@@ -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.1176-
|
|
4
|
-
"description": "",
|
|
3
|
+
"version": "1.0.1176-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.688-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
|
-
}
|