@dev-blinq/cucumber_client 1.0.1317-dev → 1.0.1319-dev
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/client/code_gen/playwright_codeget.js +7 -0
- package/bin/client/recorderv3/bvt_recorder.js +51 -3
- package/bin/client/recorderv3/index.js +3 -3
- package/bin/client/recorderv3/network.js +5 -22
- package/bin/client/recorderv3/step_runner.js +54 -23
- package/bin/client/recorderv3/step_utils.js +36 -1
- package/package.json +2 -2
|
@@ -735,6 +735,13 @@ const generateCode = (recording, codePage, userData, projectDir, methodName) =>
|
|
|
735
735
|
codeLines.push(...generateReportCommand("start", recordingStep.cmdId));
|
|
736
736
|
}
|
|
737
737
|
const result = _generateCodeFromCommand(recordingStep, elements, userData);
|
|
738
|
+
if (process.env.TEMP_RUN) {
|
|
739
|
+
// Add try catch block to the generated code, should attach commandId to world object
|
|
740
|
+
result.codeLines.unshift(`try {`);
|
|
741
|
+
result.codeLines.push(
|
|
742
|
+
`} catch (err) { throw new Error("Error in command ${recordingStep.cmdId}: " + err.message); }`
|
|
743
|
+
);
|
|
744
|
+
}
|
|
738
745
|
codeLines.push(...result.codeLines);
|
|
739
746
|
if (process.env.TEMP_RUN === "true") {
|
|
740
747
|
codeLines.push(...generateReportCommand("end", recordingStep.cmdId));
|
|
@@ -15,7 +15,7 @@ import chokidar from "chokidar";
|
|
|
15
15
|
import logger from "../../logger.js";
|
|
16
16
|
import { unEscapeNonPrintables } from "../cucumber/utils.js";
|
|
17
17
|
import { findAvailablePort } from "../utils/index.js";
|
|
18
|
-
import
|
|
18
|
+
import { Step } from "../cucumber/feature.js";
|
|
19
19
|
|
|
20
20
|
const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
|
|
21
21
|
|
|
@@ -209,7 +209,6 @@ export class BVTRecorder {
|
|
|
209
209
|
},
|
|
210
210
|
});
|
|
211
211
|
this.pageSet = new Set();
|
|
212
|
-
this.networkMonitor = new NetworkMonitor();
|
|
213
212
|
this.lastKnownUrlPath = "";
|
|
214
213
|
// TODO: what is world?
|
|
215
214
|
this.world = { attach: () => {} };
|
|
@@ -619,7 +618,6 @@ export class BVTRecorder {
|
|
|
619
618
|
this.pageSet.add(page);
|
|
620
619
|
|
|
621
620
|
await page.waitForLoadState("domcontentloaded");
|
|
622
|
-
|
|
623
621
|
// add listener for frame navigation on new tab
|
|
624
622
|
this._addFrameNavigateListener(page);
|
|
625
623
|
} catch (error) {
|
|
@@ -709,6 +707,7 @@ export class BVTRecorder {
|
|
|
709
707
|
if (this.shouldTakeScreenshot) {
|
|
710
708
|
await this.storeScreenshot(event);
|
|
711
709
|
}
|
|
710
|
+
|
|
712
711
|
this.sendEvent(this.events.onNewCommand, cmdEvent);
|
|
713
712
|
this._updateUrlPath();
|
|
714
713
|
}
|
|
@@ -784,6 +783,7 @@ export class BVTRecorder {
|
|
|
784
783
|
}
|
|
785
784
|
|
|
786
785
|
async startRecordingInput() {
|
|
786
|
+
console.log("startRecordingInput");
|
|
787
787
|
await this.setMode("recordingInput");
|
|
788
788
|
}
|
|
789
789
|
async stopRecordingInput() {
|
|
@@ -1016,6 +1016,53 @@ export class BVTRecorder {
|
|
|
1016
1016
|
const stepParams = parseStepTextParameters(stepName);
|
|
1017
1017
|
return getCommandsForImplementedStep(stepName, step_definitions, stepParams).commands;
|
|
1018
1018
|
}
|
|
1019
|
+
|
|
1020
|
+
parseRouteFiles(step) {
|
|
1021
|
+
const routeFolder = path.join(this.projectDir, "data", "routes");
|
|
1022
|
+
const templateRouteMap = new Map();
|
|
1023
|
+
|
|
1024
|
+
// Go over all the files in the route folder and parse them
|
|
1025
|
+
const routeFiles = readdirSync(routeFolder).filter((file) => file.endsWith(".json"));
|
|
1026
|
+
for (const file of routeFiles) {
|
|
1027
|
+
const filePath = path.join(routeFolder, file);
|
|
1028
|
+
const routeData = JSON.parse(readFileSync(filePath, "utf8"));
|
|
1029
|
+
if (routeData && routeData.template) {
|
|
1030
|
+
const template = routeData.template;
|
|
1031
|
+
const routes = routeData.routes;
|
|
1032
|
+
|
|
1033
|
+
templateRouteMap.set(template, routes);
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
if (!existsSync(routeFolder)) {
|
|
1038
|
+
return null;
|
|
1039
|
+
} else if (step && step.text) {
|
|
1040
|
+
// Convert the step text to cucumber template
|
|
1041
|
+
const cucumberStep = new Step();
|
|
1042
|
+
cucumberStep.text = step.text;
|
|
1043
|
+
const template = cucumberStep.getTemplate();
|
|
1044
|
+
if (templateRouteMap.has(template)) {
|
|
1045
|
+
const routeItems = templateRouteMap.get(template);
|
|
1046
|
+
console.log("Route Items from template:", routeItems);
|
|
1047
|
+
routeItems.forEach((item) => {
|
|
1048
|
+
const filters = item.filters || {};
|
|
1049
|
+
const queryParams = filters?.queryParams || {};
|
|
1050
|
+
console.log("Query Params:", queryParams);
|
|
1051
|
+
const queryParamsArray = Object.keys(queryParams).map((key) => ({
|
|
1052
|
+
paramKey: key,
|
|
1053
|
+
paramValue: queryParams[key],
|
|
1054
|
+
}));
|
|
1055
|
+
console.log("Query Params Array:", queryParamsArray);
|
|
1056
|
+
filters.queryParams = queryParamsArray || [];
|
|
1057
|
+
});
|
|
1058
|
+
step.routeItems = routeItems;
|
|
1059
|
+
console.log("Route Items:", step.routeItems);
|
|
1060
|
+
} else {
|
|
1061
|
+
step.routeItems = null;
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1019
1066
|
loadExistingScenario({ featureName, scenarioName }) {
|
|
1020
1067
|
const step_definitions = loadStepDefinitions(this.projectDir);
|
|
1021
1068
|
const featureFilePath = path.join(this.projectDir, "features", featureName);
|
|
@@ -1044,6 +1091,7 @@ export class BVTRecorder {
|
|
|
1044
1091
|
..._s,
|
|
1045
1092
|
keyword: step.keyword.trim(),
|
|
1046
1093
|
};
|
|
1094
|
+
this.parseRouteFiles(_step);
|
|
1047
1095
|
steps.push(_step);
|
|
1048
1096
|
}
|
|
1049
1097
|
return {
|
|
@@ -16,7 +16,7 @@ class PromisifiedSocketServer {
|
|
|
16
16
|
init() {
|
|
17
17
|
this.socket.on("request", async (data) => {
|
|
18
18
|
const { event, input, id, roomId, socketId } = data;
|
|
19
|
-
|
|
19
|
+
console.log("request", { event, input, id, roomId, socketId });
|
|
20
20
|
try {
|
|
21
21
|
const handler = this.routes[event];
|
|
22
22
|
if (!handler) {
|
|
@@ -86,10 +86,10 @@ const init = ({ envName, projectDir, roomId, TOKEN }) => {
|
|
|
86
86
|
console.log("connecting to " + WS_URL);
|
|
87
87
|
const socket = io(WS_URL);
|
|
88
88
|
socket.on("connect", () => {
|
|
89
|
-
|
|
89
|
+
console.log("connected to server");
|
|
90
90
|
});
|
|
91
91
|
socket.on("disconnect", () => {
|
|
92
|
-
|
|
92
|
+
console.log("disconnected from server");
|
|
93
93
|
});
|
|
94
94
|
socket.emit("joinRoom", { id: roomId, window: "cucumber_client/bvt_recorder" });
|
|
95
95
|
const recorder = new BVTRecorder({
|
|
@@ -1,18 +1,8 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @typedef {Object} NetworkEvent
|
|
3
|
-
* @property {import('playwright').Request} request
|
|
4
|
-
* @property {import('playwright').Response|null} response
|
|
5
|
-
* @property {string} id
|
|
6
|
-
* @property {number} timestamp
|
|
7
|
-
* @property {string} status - 'completed', 'failed'
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
1
|
class NetworkMonitor {
|
|
11
2
|
constructor() {
|
|
12
3
|
this.networkId = 0;
|
|
13
|
-
/** @type {Map<string, NetworkEvent>} */
|
|
14
|
-
this.requestIdMap = new Map();
|
|
15
4
|
this.networkEvents = new Map();
|
|
5
|
+
this.requestIdMap = new Map();
|
|
16
6
|
/** @type {Map<import('playwright').Page, {responseListener: Function, requestFailedListener: Function}>} */
|
|
17
7
|
this.pageListeners = new Map();
|
|
18
8
|
}
|
|
@@ -22,6 +12,7 @@ class NetworkMonitor {
|
|
|
22
12
|
* @param {import('playwright').Page} page
|
|
23
13
|
*/
|
|
24
14
|
addNetworkEventListener(page) {
|
|
15
|
+
|
|
25
16
|
// Create the listener functions
|
|
26
17
|
const responseListener = async (response) => {
|
|
27
18
|
const request = response.request();
|
|
@@ -210,6 +201,7 @@ class NetworkMonitor {
|
|
|
210
201
|
// Try to get response body safely (only for successful responses)
|
|
211
202
|
if (response && networkEvent.status === "completed") {
|
|
212
203
|
try {
|
|
204
|
+
|
|
213
205
|
const isBinary =
|
|
214
206
|
!response.headers()["content-type"]?.includes("application/json") &&
|
|
215
207
|
!response.headers()["content-type"]?.includes("text");
|
|
@@ -237,6 +229,7 @@ class NetworkMonitor {
|
|
|
237
229
|
marshalledEvent.failureReason = request.failure()?.errorText || "Unknown error";
|
|
238
230
|
}
|
|
239
231
|
|
|
232
|
+
|
|
240
233
|
console.log("Marshalled network event:", marshalledEvent);
|
|
241
234
|
|
|
242
235
|
return marshalledEvent;
|
|
@@ -253,18 +246,7 @@ class NetworkMonitor {
|
|
|
253
246
|
}
|
|
254
247
|
}
|
|
255
248
|
|
|
256
|
-
/**
|
|
257
|
-
*@returns {Promise<Object[]>}
|
|
258
|
-
* Get all marshalled network events
|
|
259
|
-
* This is useful for sending to the server or saving to a file.
|
|
260
|
-
* */
|
|
261
|
-
async getAllMarshalledNetworkEvents() {
|
|
262
|
-
const events = this.getAllNetworkEvents();
|
|
263
|
-
const marshalledEvents = await Promise.all(events.map((event) => this.marshallNetworkEvent(event)));
|
|
264
|
-
return marshalledEvents;
|
|
265
|
-
}
|
|
266
249
|
|
|
267
|
-
/**
|
|
268
250
|
* Get marshalled network events since this ID
|
|
269
251
|
* @returns {Promise<Object[]>}
|
|
270
252
|
* @param {number} sinceId
|
|
@@ -281,6 +263,7 @@ class NetworkMonitor {
|
|
|
281
263
|
* @param {number} endId
|
|
282
264
|
* @returns {Promise<Object[]>}
|
|
283
265
|
*/
|
|
266
|
+
|
|
284
267
|
async getMarshalledNetworkEventsInRange(startId, endId) {
|
|
285
268
|
const events = this.getNetworkEventsInRange(startId, endId);
|
|
286
269
|
const marshalledEvents = await Promise.all(events.map((event) => this.marshallNetworkEvent(event)));
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, writeFileSync } from "fs";
|
|
1
|
+
import { existsSync, mkdirSync, rmSync, writeFileSync } from "fs";
|
|
2
2
|
import path from "path";
|
|
3
3
|
import fs from "fs";
|
|
4
4
|
import { generatePageName } from "../code_gen/playwright_codeget.js";
|
|
@@ -9,9 +9,11 @@ import {
|
|
|
9
9
|
getUtilsCodePage,
|
|
10
10
|
loadStepDefinitions,
|
|
11
11
|
saveRecording,
|
|
12
|
+
saveRoutes,
|
|
12
13
|
} from "./step_utils.js";
|
|
13
14
|
import { escapeString, getExamplesContent } from "./update_feature.js";
|
|
14
15
|
import { locateDefinitionPath } from "../cucumber/steps_definitions.js";
|
|
16
|
+
import { tmpdir } from "os";
|
|
15
17
|
|
|
16
18
|
// let copiedCodeToTemp = false;
|
|
17
19
|
async function withAbort(fn, signal) {
|
|
@@ -228,28 +230,57 @@ export class BVTStepRunner {
|
|
|
228
230
|
if (!codePage) {
|
|
229
231
|
codePage = getUtilsCodePage(this.projectDir);
|
|
230
232
|
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
{
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
233
|
+
} else {
|
|
234
|
+
let routesPath = path.join(tmpdir(), `blinq_temp_routes`);
|
|
235
|
+
if (process.env.TEMP_RUN === "true") {
|
|
236
|
+
console.log("Save routes in temp folder for running:", routesPath);
|
|
237
|
+
if (existsSync(routesPath)) {
|
|
238
|
+
console.log("Removing existing temp_routes_folder:", routesPath);
|
|
239
|
+
rmSync(routesPath, { recursive: true });
|
|
240
|
+
}
|
|
241
|
+
mkdirSync(routesPath, { recursive: true });
|
|
242
|
+
console.log("Created temp_routes_folder:", routesPath);
|
|
243
|
+
saveRoutes({ step, folderPath: routesPath });
|
|
244
|
+
} else {
|
|
245
|
+
console.log("Saving routes in project directory:", this.projectDir);
|
|
246
|
+
if (existsSync(routesPath)) {
|
|
247
|
+
// remove the folder
|
|
248
|
+
try {
|
|
249
|
+
rmSync(routesPath, { recursive: true });
|
|
250
|
+
console.log("Removed temp_routes_folder:", routesPath);
|
|
251
|
+
} catch (error) {
|
|
252
|
+
console.error("Error removing temp_routes folder", error);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
routesPath = path.join(this.projectDir, "data", "routes");
|
|
256
|
+
console.log("Saving routes to:", routesPath);
|
|
257
|
+
if (!existsSync(routesPath)) {
|
|
258
|
+
mkdirSync(routesPath, { recursive: true });
|
|
259
|
+
}
|
|
260
|
+
saveRoutes({ step, folderPath: routesPath });
|
|
251
261
|
}
|
|
252
|
-
|
|
253
|
-
|
|
262
|
+
const feature_file_path = await this.writeTempFeatureFile({ step, parametersMap, tempFolderPath, tags });
|
|
263
|
+
// console.log({ feature_file_path, step_text: step.text });
|
|
264
|
+
|
|
265
|
+
const stepExecController = new AbortController();
|
|
266
|
+
this.#currentStepController = stepExecController;
|
|
267
|
+
const { result, info } = await withAbort(async () => {
|
|
268
|
+
return await stepsDefinitions.executeStepRemote(
|
|
269
|
+
{
|
|
270
|
+
feature_file_path,
|
|
271
|
+
tempFolderPath,
|
|
272
|
+
stepText: step.text,
|
|
273
|
+
scenario: "Temp Scenario",
|
|
274
|
+
},
|
|
275
|
+
options
|
|
276
|
+
);
|
|
277
|
+
}, stepExecController.signal).finally(() => {
|
|
278
|
+
// rm temp folder
|
|
279
|
+
if (fs.existsSync(tempFolderPath)) {
|
|
280
|
+
fs.rmSync(tempFolderPath, { recursive: true });
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
return { result, info };
|
|
284
|
+
}
|
|
254
285
|
}
|
|
255
286
|
}
|
|
@@ -71,6 +71,7 @@ function makeStepTextUnique(step, stepsDefinitions) {
|
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
export async function saveRecording({ step, cucumberStep, codePage, projectDir, stepsDefinitions }) {
|
|
74
|
+
|
|
74
75
|
let routesPath = path.join(tmpdir(), "blinq_temp_routes");
|
|
75
76
|
|
|
76
77
|
if (process.env.TEMP_RUN) {
|
|
@@ -105,10 +106,39 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
|
|
|
105
106
|
codePage = getCodePage(stepDef.file);
|
|
106
107
|
} else {
|
|
107
108
|
const isUtilStep = makeStepTextUnique(step, stepsDefinitions);
|
|
109
|
+
|
|
108
110
|
if (isUtilStep) {
|
|
109
111
|
return;
|
|
110
112
|
}
|
|
111
113
|
}
|
|
114
|
+
|
|
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
|
+
}
|
|
112
142
|
|
|
113
143
|
cucumberStep.text = step.text;
|
|
114
144
|
const recording = new Recording();
|
|
@@ -385,11 +415,13 @@ export function saveRoutes({ step, folderPath }) {
|
|
|
385
415
|
const cucumberStep = getCucumberStep({ step });
|
|
386
416
|
const template = cucumberStep.getTemplate();
|
|
387
417
|
const stepNameHash = createHash("sha256").update(template).digest("hex");
|
|
418
|
+
console.log("Saving routes for step:", step.text, "with hash:", stepNameHash);
|
|
419
|
+
|
|
388
420
|
const routeItemsWithFilters = routeItems.map((routeItem) => {
|
|
389
421
|
const oldFilters = routeItem.filters;
|
|
390
422
|
const queryParamsObject = {};
|
|
391
423
|
oldFilters.queryParams.forEach((queryParam) => {
|
|
392
|
-
queryParamsObject[queryParam.
|
|
424
|
+
queryParamsObject[queryParam.paramKey] = queryParam.paramValue;
|
|
393
425
|
});
|
|
394
426
|
const newFilters = { path: oldFilters.path, method: oldFilters.method, queryParams: queryParamsObject };
|
|
395
427
|
return {
|
|
@@ -399,10 +431,13 @@ export function saveRoutes({ step, folderPath }) {
|
|
|
399
431
|
});
|
|
400
432
|
|
|
401
433
|
const routesFilePath = path.join(folderPath, stepNameHash + ".json");
|
|
434
|
+
console.log("Routes file path:", routesFilePath);
|
|
402
435
|
const routesData = {
|
|
403
436
|
template,
|
|
404
437
|
routes: routeItemsWithFilters,
|
|
405
438
|
};
|
|
439
|
+
console.log("Routes data to save:", routesData);
|
|
440
|
+
|
|
406
441
|
if (!existsSync(folderPath)) {
|
|
407
442
|
mkdirSync(folderPath, { recursive: true });
|
|
408
443
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dev-blinq/cucumber_client",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1319-dev",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "bin/index.js",
|
|
6
6
|
"types": "bin/index.d.ts",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"@cucumber/tag-expressions": "^6.1.1",
|
|
33
33
|
"@dev-blinq/cucumber-js": "1.0.178-dev",
|
|
34
34
|
"@faker-js/faker": "^8.1.0",
|
|
35
|
-
"automation_model": "1.0.
|
|
35
|
+
"automation_model": "1.0.777-dev",
|
|
36
36
|
"axios": "^1.7.4",
|
|
37
37
|
"chokidar": "^3.6.0",
|
|
38
38
|
"create-require": "^1.1.1",
|