@dev-blinq/cucumber_client 1.0.1318-dev → 1.0.1320-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.
@@ -731,14 +731,21 @@ const generateCode = (recording, codePage, userData, projectDir, methodName) =>
731
731
  return;
732
732
  }
733
733
 
734
- if (process.env.TEMP_RUN === "true") {
735
- codeLines.push(...generateReportCommand("start", recordingStep.cmdId));
736
- }
734
+ // if (process.env.TEMP_RUN === "true") {
735
+ // codeLines.push(...generateReportCommand("start", recordingStep.cmdId));
736
+ // }
737
737
  const result = _generateCodeFromCommand(recordingStep, elements, userData);
738
- codeLines.push(...result.codeLines);
739
- if (process.env.TEMP_RUN === "true") {
740
- codeLines.push(...generateReportCommand("end", recordingStep.cmdId));
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
+ );
741
744
  }
745
+ codeLines.push(...result.codeLines);
746
+ // if (process.env.TEMP_RUN === "true") {
747
+ // codeLines.push(...generateReportCommand("end", recordingStep.cmdId));
748
+ // }
742
749
  if (result.elements) {
743
750
  elements = result.elements;
744
751
  }
@@ -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 NetworkMonitor from "./network.js";
18
+ import { Step } from "../cucumber/feature.js";
19
19
 
20
20
  const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
21
21
 
@@ -190,16 +190,16 @@ export class BVTRecorder {
190
190
  if (data && data.type) {
191
191
  switch (data.type) {
192
192
  case "cmdExecutionStart":
193
- console.log("Sending cmdExecutionStart event for cmdId:", data.cmdId);
194
- this.sendEvent(this.events.cmdExecutionStart, data.cmdId);
193
+ console.log("Sending cmdExecutionStart event for cmdId:", data);
194
+ this.sendEvent(this.events.cmdExecutionStart, data);
195
195
  break;
196
196
  case "cmdExecutionSuccess":
197
- console.log("Sending cmdExecutionSuccess event for cmdId:", data.cmdId);
198
- this.sendEvent(this.events.cmdExecutionSuccess, data.cmdId);
197
+ console.log("Sending cmdExecutionSuccess event for cmdId:", data);
198
+ this.sendEvent(this.events.cmdExecutionSuccess, data);
199
199
  break;
200
200
  case "cmdExecutionFailure":
201
- console.log("Sending cmdExecutionFailure event for cmdId:", data.cmdId);
202
- this.sendEvent(this.events.cmdExecutionFailure, data.cmdId);
201
+ console.log("Sending cmdExecutionFailure event for cmdId:", data);
202
+ this.sendEvent(this.events.cmdExecutionFailure, data);
203
203
  break;
204
204
  default:
205
205
  console.warn("Unknown command execution status type:", data.type);
@@ -209,10 +209,9 @@ 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
- this.world = { attach: () => {} };
214
+ this.world = { attach: () => { } };
216
215
  this.shouldTakeScreenshot = true;
217
216
  this.watcher = null;
218
217
  }
@@ -320,7 +319,7 @@ export class BVTRecorder {
320
319
  process.env.CDP_LISTEN_PORT = this.#remoteDebuggerPort;
321
320
 
322
321
  this.stepRunner.setRemoteDebugPort(this.#remoteDebuggerPort);
323
- this.world = { attach: () => {} };
322
+ this.world = { attach: () => { } };
324
323
 
325
324
  const ai_config_file = path.join(this.projectDir, "ai_config.json");
326
325
  let ai_config = {};
@@ -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
  }
@@ -726,7 +725,7 @@ export class BVTRecorder {
726
725
  }
727
726
  async closeBrowser() {
728
727
  delete process.env.TEMP_RUN;
729
- await this.watcher.close().then(() => {});
728
+ await this.watcher.close().then(() => { });
730
729
  this.watcher = null;
731
730
  this.previousIndex = null;
732
731
  this.previousHistoryLength = null;
@@ -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
- // console.log("request", { event, input, id, roomId, socketId });
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
- // console.log('connected to server')
89
+ console.log("connected to server");
90
90
  });
91
91
  socket.on("disconnect", () => {
92
- // console.log('disconnected from server')
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) {
@@ -130,7 +132,7 @@ export class BVTStepRunner {
130
132
  }
131
133
  if (testStepResult.status === "UNDEFINED") {
132
134
  if (!errorMesssage) {
133
- errorMesssage = `step ${JSON.stringify(stepText)} is ${testStepResult.status}`;
135
+ errorMesssage = stepText ? `step ${JSON.stringify(stepText)} is ${testStepResult.status}` : testStepResult.message;
134
136
  if (info) {
135
137
  errInfo = info;
136
138
  }
@@ -142,33 +144,46 @@ export class BVTStepRunner {
142
144
  if (attachment.mediaType === "application/json" && attachment.body) {
143
145
  const body = JSON.parse(attachment.body);
144
146
  info = body.info;
147
+ const result = body.result;
145
148
 
146
- if (body && body.payload) {
147
- const payload = body.payload;
148
- const content = payload.content;
149
- const type = payload.type;
150
- if (type === "cmdReport") {
151
- if (content) {
152
- const report = JSON.parse(content);
153
- switch (report.status) {
154
- case "start":
155
- this.sendExecutionStatus({
156
- type: "cmdExecutionStart",
157
- cmdId: report.cmdId,
158
- });
159
- break;
160
- case "end":
161
- this.sendExecutionStatus({
162
- type: "cmdExecutionSuccess",
163
- cmdId: report.cmdId,
164
- });
165
- break;
166
- default:
167
- console.warn("Unknown command report status:", report.status);
168
- }
169
- }
170
- }
149
+ if (result.status === "PASSED") {
150
+ this.sendExecutionStatus({
151
+ type: "cmdExecutionSuccess",
152
+ cmdId: body.cmdId,
153
+ });
154
+ } else {
155
+ this.sendExecutionStatus({
156
+ type: "cmdExecutionError",
157
+ cmdId: body.cmdId,
158
+ error: result.error,
159
+ });
171
160
  }
161
+ // if (body && body.payload) {
162
+ // const payload = body.payload;
163
+ // const content = payload.content;
164
+ // const type = payload.type;
165
+ // if (type === "cmdReport") {
166
+ // if (content) {
167
+ // const report = JSON.parse(content);
168
+ // switch (report.status) {
169
+ // case "start":
170
+ // this.sendExecutionStatus({
171
+ // type: "cmdExecutionStart",
172
+ // cmdId: report.cmdId,
173
+ // });
174
+ // break;
175
+ // case "end":
176
+ // this.sendExecutionStatus({
177
+ // type: "cmdExecutionSuccess",
178
+ // cmdId: report.cmdId,
179
+ // });
180
+ // break;
181
+ // default:
182
+ // console.warn("Unknown command report status:", report.status);
183
+ // }
184
+ // }
185
+ // }
186
+ // }
172
187
  }
173
188
  }
174
189
  });
@@ -189,6 +204,20 @@ export class BVTStepRunner {
189
204
  };
190
205
 
191
206
  async runStep({ step, parametersMap, envPath, tags }, bvtContext, options) {
207
+ let cmdIDs = (step.commands || []).map((cmd) => cmd.cmdId);
208
+ if (bvtContext.web) {
209
+ bvtContext.web.getCmdId = () => {
210
+ if (cmdIDs.length === 0) {
211
+ cmdIDs = (step.commands || []).map((cmd) => cmd.cmdId);
212
+ }
213
+ const cId = cmdIDs.shift();
214
+ this.sendExecutionStatus({
215
+ type: "cmdExecutionStart",
216
+ cmdId: cId,
217
+ })
218
+ return cId;
219
+ }
220
+ }
192
221
  let codePage; // = getCodePage();
193
222
  // const tempFolderPath = process.env.tempFeaturesFolderPath;
194
223
  const __temp_features_FolderName = "__temp_features" + Math.random().toString(36).substring(2, 7);
@@ -228,6 +257,35 @@ export class BVTStepRunner {
228
257
  if (!codePage) {
229
258
  codePage = getUtilsCodePage(this.projectDir);
230
259
  }
260
+ } else {
261
+ let routesPath = path.join(tmpdir(), `blinq_temp_routes`);
262
+ if (process.env.TEMP_RUN === "true") {
263
+ console.log("Save routes in temp folder for running:", routesPath);
264
+ if (existsSync(routesPath)) {
265
+ console.log("Removing existing temp_routes_folder:", routesPath);
266
+ rmSync(routesPath, { recursive: true });
267
+ }
268
+ mkdirSync(routesPath, { recursive: true });
269
+ console.log("Created temp_routes_folder:", routesPath);
270
+ saveRoutes({ step, folderPath: routesPath });
271
+ } else {
272
+ console.log("Saving routes in project directory:", this.projectDir);
273
+ if (existsSync(routesPath)) {
274
+ // remove the folder
275
+ try {
276
+ rmSync(routesPath, { recursive: true });
277
+ console.log("Removed temp_routes_folder:", routesPath);
278
+ } catch (error) {
279
+ console.error("Error removing temp_routes folder", error);
280
+ }
281
+ }
282
+ routesPath = path.join(this.projectDir, "data", "routes");
283
+ console.log("Saving routes to:", routesPath);
284
+ if (!existsSync(routesPath)) {
285
+ mkdirSync(routesPath, { recursive: true });
286
+ }
287
+ saveRoutes({ step, folderPath: routesPath });
288
+ }
231
289
  }
232
290
  const feature_file_path = await this.writeTempFeatureFile({ step, parametersMap, tempFolderPath, tags });
233
291
  // console.log({ feature_file_path, step_text: step.text });
@@ -235,7 +293,7 @@ export class BVTStepRunner {
235
293
  const stepExecController = new AbortController();
236
294
  this.#currentStepController = stepExecController;
237
295
  const { result, info } = await withAbort(async () => {
238
- return await this.executeStepRemote(
296
+ return await stepsDefinitions.executeStepRemote(
239
297
  {
240
298
  feature_file_path,
241
299
  tempFolderPath,
@@ -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.key] = queryParam.value;
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.1318-dev",
3
+ "version": "1.0.1320-dev",
4
4
  "description": "",
5
5
  "main": "bin/index.js",
6
6
  "types": "bin/index.d.ts",