@dev-blinq/cucumber_client 1.0.1404-dev → 1.0.1405-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.
@@ -184,34 +184,6 @@ export class BVTRecorder {
184
184
  projectDir: this.projectDir,
185
185
  logger: this.logger,
186
186
  });
187
- this.stepRunner = new BVTStepRunner({
188
- projectDir: this.projectDir,
189
- sendExecutionStatus: (data) => {
190
- if (data && data.type) {
191
- switch (data.type) {
192
- case "cmdExecutionStart":
193
- console.log("Sending cmdExecutionStart event for cmdId:", data);
194
- this.sendEvent(this.events.cmdExecutionStart, data);
195
- break;
196
- case "cmdExecutionSuccess":
197
- console.log("Sending cmdExecutionSuccess event for cmdId:", data);
198
- this.sendEvent(this.events.cmdExecutionSuccess, data);
199
- break;
200
- case "cmdExecutionError":
201
- console.log("Sending cmdExecutionError event for cmdId:", data);
202
- this.sendEvent(this.events.cmdExecutionError, data);
203
- break;
204
- case "interceptResults":
205
- console.log("Sending interceptResults event");
206
- this.sendEvent(this.events.interceptResults, data);
207
- break;
208
- default:
209
- console.warn("Unknown command execution status type:", data.type);
210
- break;
211
- }
212
- }
213
- },
214
- });
215
187
  this.pageSet = new Set();
216
188
  this.lastKnownUrlPath = "";
217
189
  // TODO: what is world?
@@ -322,7 +294,7 @@ export class BVTRecorder {
322
294
  this.#remoteDebuggerPort = await findAvailablePort();
323
295
  process.env.CDP_LISTEN_PORT = this.#remoteDebuggerPort;
324
296
 
325
- this.stepRunner.setRemoteDebugPort(this.#remoteDebuggerPort);
297
+ // this.stepRunner.setRemoteDebugPort(this.#remoteDebuggerPort);
326
298
  this.world = { attach: () => {} };
327
299
 
328
300
  const ai_config_file = path.join(this.projectDir, "ai_config.json");
@@ -348,12 +320,40 @@ export class BVTRecorder {
348
320
  let stopTime = Date.now();
349
321
  this.logger.info(`Browser launched in ${(stopTime - startTime) / 1000} s`);
350
322
  this.bvtContext = bvtContext;
323
+ this.stepRunner = new BVTStepRunner({
324
+ projectDir: this.projectDir,
325
+ sendExecutionStatus: (data) => {
326
+ if (data && data.type) {
327
+ switch (data.type) {
328
+ case "cmdExecutionStart":
329
+ console.log("Sending cmdExecutionStart event for cmdId:", data);
330
+ this.sendEvent(this.events.cmdExecutionStart, data);
331
+ break;
332
+ case "cmdExecutionSuccess":
333
+ console.log("Sending cmdExecutionSuccess event for cmdId:", data);
334
+ this.sendEvent(this.events.cmdExecutionSuccess, data);
335
+ break;
336
+ case "cmdExecutionError":
337
+ console.log("Sending cmdExecutionError event for cmdId:", data);
338
+ this.sendEvent(this.events.cmdExecutionError, data);
339
+ break;
340
+ case "interceptResults":
341
+ console.log("Sending interceptResults event");
342
+ this.sendEvent(this.events.interceptResults, data);
343
+ break;
344
+ default:
345
+ console.warn("Unknown command execution status type:", data.type);
346
+ break;
347
+ }
348
+ }
349
+ },
350
+ bvtContext: this.bvtContext,
351
+ });
351
352
  const context = bvtContext.playContext;
352
353
  this.context = context;
353
354
  this.web = bvtContext.stable || bvtContext.web;
354
355
  this.web.tryAllStrategies = true;
355
356
  this.page = bvtContext.page;
356
-
357
357
  this.pageSet.add(this.page);
358
358
  this.lastKnownUrlPath = this._updateUrlPath();
359
359
  const browser = await this.context.browser();
@@ -789,7 +789,6 @@ export class BVTRecorder {
789
789
  }
790
790
 
791
791
  async startRecordingInput() {
792
- console.log("startRecordingInput");
793
792
  await this.setMode("recordingInput");
794
793
  }
795
794
  async stopRecordingInput() {
@@ -813,9 +812,17 @@ export class BVTRecorder {
813
812
  }
814
813
 
815
814
  async abortExecution() {
816
- this.bvtContext.web.abortedExecution = true;
817
815
  await this.stepRunner.abortExecution();
818
816
  }
817
+
818
+ async pauseExecution({ cmdId }) {
819
+ await this.stepRunner.pauseExecution(cmdId);
820
+ }
821
+
822
+ async resumeExecution({ cmdId }) {
823
+ await this.stepRunner.resumeExecution(cmdId);
824
+ }
825
+
819
826
  async dealyedRevertMode() {
820
827
  const timerId = setTimeout(async () => {
821
828
  await this.revertMode();
@@ -835,7 +842,6 @@ export class BVTRecorder {
835
842
 
836
843
  this.bvtContext.navigate = true;
837
844
  this.bvtContext.loadedRoutes = null;
838
- this.bvtContext.web.abortedExecution = false;
839
845
  for (const [key, value] of Object.entries(_env)) {
840
846
  process.env[key] = value;
841
847
  }
@@ -871,7 +877,6 @@ export class BVTRecorder {
871
877
  delete process.env[key];
872
878
  }
873
879
  this.bvtContext.navigate = false;
874
- this.bvtContext.web.abortedExecution = false;
875
880
  }
876
881
  }
877
882
  async saveScenario({ scenario, featureName, override, isSingleStep }) {
@@ -110,6 +110,7 @@ const init = ({ envName, projectDir, roomId, TOKEN }) => {
110
110
  socket.emit("BVTRecorder.browserOpened", null, roomId);
111
111
  })
112
112
  .catch((e) => {
113
+ console.error("BVTRecorder.browserLaunchFailed", e);
113
114
  socket.emit("BVTRecorder.browserLaunchFailed", e, roomId);
114
115
  });
115
116
  const timeOutForFunction = async (promise, timeout = 5000) => {
@@ -146,7 +147,8 @@ const init = ({ envName, projectDir, roomId, TOKEN }) => {
146
147
  // console.log("BVTRecorder.browserOpened");
147
148
  socket.emit("BVTRecorder.browserOpened", null, roomId);
148
149
  })
149
- .catch(() => {
150
+ .catch((e) => {
151
+ console.error("BVTRecorder.browserLaunchFailed", e);
150
152
  socket.emit("BVTRecorder.browserLaunchFailed", null, roomId);
151
153
  });
152
154
  },
@@ -227,6 +229,12 @@ const init = ({ envName, projectDir, roomId, TOKEN }) => {
227
229
  "recorderWindow.abortExecution": async (input) => {
228
230
  return recorder.abortExecution(input);
229
231
  },
232
+ "recorderWindow.pauseExecution": async (input) => {
233
+ return recorder.pauseExecution(input);
234
+ },
235
+ "recorderWindow.resumeExecution": async (input) => {
236
+ return recorder.resumeExecution(input);
237
+ },
230
238
  "recorderWindow.loadExistingScenario": async (input) => {
231
239
  return recorder.loadExistingScenario(input);
232
240
  },
@@ -15,45 +15,62 @@ import fs from "fs";
15
15
  import { locateDefinitionPath } from "../cucumber/steps_definitions.js";
16
16
  import { tmpdir } from "os";
17
17
 
18
- // let copiedCodeToTemp = false;
19
- async function withAbort(fn, signal) {
20
- if (!signal) {
21
- return await fn();
22
- }
23
- return new Promise((resolve, reject) => {
24
- const abortHandler = () => reject(new Error("Aborted"));
25
- signal.addEventListener("abort", abortHandler, { once: true });
26
-
27
- fn()
28
- .then(resolve)
29
- .catch(reject)
30
- .finally(() => {
31
- signal.removeEventListener("abort", abortHandler);
32
- });
33
- });
34
- }
35
18
  export class BVTStepRunner {
36
- #currentStepController;
19
+ #currentStepController = null;
37
20
  #port;
38
- constructor({ projectDir, sendExecutionStatus }) {
21
+ #lastAttemptedCmdId = null;
22
+
23
+ constructor({ projectDir, sendExecutionStatus, bvtContext }) {
39
24
  this.projectDir = projectDir;
40
25
  this.sendExecutionStatus = sendExecutionStatus;
26
+ this.bvtContext = bvtContext;
27
+ this.liveExecutionMap = new Map();
41
28
  }
29
+
42
30
  setRemoteDebugPort(port) {
43
31
  this.#port = port;
44
32
  }
33
+
34
+ // Abort the current cucumber step execution by signaling the wrapper
45
35
  async abortExecution() {
36
+ if (this.bvtContext.web.pausedCmd) {
37
+ this.bvtContext.web.pausedCmd = null;
38
+ }
39
+ this.liveExecutionMap.clear();
46
40
  if (this.#currentStepController) {
47
41
  this.#currentStepController.abort();
48
42
  }
49
43
  }
50
44
 
45
+ async pauseExecution(cmdId) {
46
+ if (this.bvtContext.web) {
47
+ this.bvtContext.web.pausedCmd = this.liveExecutionMap.get(cmdId);
48
+ }
49
+ }
50
+
51
+ async resumeExecution(cmdId) {
52
+ if (this.bvtContext.web.pausedCmd) {
53
+ const { resolve } = this.bvtContext.web.pausedCmd;
54
+ if (resolve) {
55
+ resolve();
56
+ }
57
+ this.bvtContext.web.pausedCmd = null;
58
+ } else {
59
+ if (this.liveExecutionMap.has(cmdId)) {
60
+ const { resolve } = this.liveExecutionMap.get(cmdId);
61
+ if (resolve) {
62
+ resolve();
63
+ }
64
+ } else {
65
+ console.warn(`No paused command found for cmdId: ${cmdId}`);
66
+ }
67
+ }
68
+ }
69
+
51
70
  async copyCodetoTempFolder({ step, parametersMap, tempFolderPath }) {
52
- // const tempFolderPath = path.join(this.projectDir, "__temp_features");
53
71
  if (!fs.existsSync(tempFolderPath)) {
54
72
  fs.mkdirSync(tempFolderPath);
55
73
  }
56
- //copy all files from "./features" "./temp" folder
57
74
  if (fs.existsSync(tempFolderPath)) {
58
75
  fs.rmSync(tempFolderPath, { recursive: true });
59
76
  }
@@ -61,12 +78,10 @@ export class BVTStepRunner {
61
78
  overwrite: true,
62
79
  recursive: true,
63
80
  });
64
- // copiedCodeToTemp = true;
65
81
  }
66
82
 
67
83
  async writeTempFeatureFile({ step, parametersMap, tempFolderPath, tags }) {
68
84
  const tFilePath = path.join(tempFolderPath, "__temp.feature");
69
- // console.log(tFilePath);
70
85
  let tFileContent = `# temp feature file
71
86
  Feature: Temp feature
72
87
  ${tags ? tags.join(" ") : ""}
@@ -78,211 +93,266 @@ export class BVTStepRunner {
78
93
  return tFilePath;
79
94
  }
80
95
 
81
- executeStepRemote = async ({ feature_file_path, scenario, tempFolderPath, stepText, config }, options) => {
96
+ // Generate wrapper code that integrates AbortSignal into cucumber step definitions
97
+ generateWrapperCode() {
98
+ return `
99
+ import {setDefinitionFunctionWrapper} from "@dev-blinq/cucumber-js";
100
+
101
+ setDefinitionFunctionWrapper((fn) => {
102
+ return async function (...args) {
103
+ const signal = global.__BVT_STEP_ABORT_SIGNAL;
104
+ if (signal) {
105
+ signal.throwIfAborted?.();
106
+ const abortHandler = () => {
107
+ throw new Error("Aborted");
108
+ };
109
+ signal.addEventListener("abort", abortHandler, { once: true });
110
+ try {
111
+ return await fn.apply(this, args);
112
+ } finally {
113
+ signal.removeEventListener("abort", abortHandler);
114
+ }
115
+ }
116
+ return await fn.apply(this, args);
117
+ };
118
+ });
119
+
120
+ `;
121
+ }
122
+
123
+ // Write the wrapper code to temp folder
124
+ async writeWrapperCode(tempFolderPath, abortSignal) {
125
+ // tempFolderPath/step_definitions/utils.mjs -> Make a file name that follows this file but always before the next file
126
+ let fileName = "utils" + Math.random().toString(36).substring(2, 7) + ".mjs";
127
+ while (existsSync(path.join(tempFolderPath, "step_definitions", fileName))) {
128
+ fileName = "utils" + Math.random().toString(36).substring(2, 7) + ".mjs";
129
+ }
130
+ const wrapperCode = this.generateWrapperCode();
131
+
132
+ // Ensure directory exists
133
+ const stepDefinitionFolderPath = path.join(tempFolderPath, "step_definitions");
134
+ if (!existsSync(stepDefinitionFolderPath)) {
135
+ mkdirSync(stepDefinitionFolderPath, { recursive: true });
136
+ }
137
+
138
+ writeFileSync(path.join(stepDefinitionFolderPath, fileName), wrapperCode);
139
+
140
+ // Set the abort signal globally so the wrapper can access it
141
+ global.__BVT_STEP_ABORT_SIGNAL = abortSignal;
142
+
143
+ return path.join(stepDefinitionFolderPath, fileName);
144
+ }
145
+
146
+ // Execute cucumber step - simplified without abort signal handling at this level
147
+ async executeStepWithAbort({ feature_file_path, scenario, tempFolderPath, stepText, config }, options) {
82
148
  const { skipAfter = true, skipBefore = true } = options || {};
83
- const environment = {
84
- ...process.env,
85
- };
86
149
 
87
- try {
88
- const { loadConfiguration, loadSupport, runCucumber } = await import("@dev-blinq/cucumber-js/api");
89
- const { runConfiguration } = await loadConfiguration(
90
- {
91
- provided: {
92
- name: [scenario],
93
- paths: [feature_file_path],
94
- import: [path.join(tempFolderPath, "step_definitions", "**", "*.mjs")],
95
- // format: ["bvt"],
96
- },
150
+ const environment = { ...process.env };
151
+ const { loadConfiguration, loadSupport, runCucumber } = await import("@dev-blinq/cucumber-js/api");
152
+
153
+ const { runConfiguration } = await loadConfiguration(
154
+ {
155
+ provided: {
156
+ name: [scenario],
157
+ paths: [feature_file_path],
158
+ import: [path.join(tempFolderPath, "step_definitions", "**", "*.mjs")],
97
159
  },
98
- { cwd: process.cwd(), env: environment }
99
- );
100
- // const files = glob.sync(path.join(tempFolderPath, "step_definitions", "**", "*.mjs"));
101
- // console.log("Files found:", files);
102
- const support = await loadSupport(runConfiguration, { cwd: process.cwd(), env: environment });
103
- // console.log("found ", support.stepDefinitions.length, "step definitions");
104
- // support.stepDefinitions.map((step) => {
105
- // console.log("step", step.pattern);
106
- // });
107
-
108
- support.afterTestRunHookDefinitions = [];
109
- if (skipAfter) {
110
- // ignore afterAll/after hooks
111
- support.afterTestCaseHookDefinitions = [];
112
- }
113
- if (skipBefore && !config.legacySyntax) {
114
- // ignore beforeAll/before hooks
115
- support.beforeTestCaseHookDefinitions = support.beforeTestCaseHookDefinitions.filter((hook) => {
116
- return hook.uri.endsWith("utils.mjs");
117
- });
118
- }
119
- support.beforeTestRunHookDefinitions = [];
120
-
121
- let errorMesssage = null;
122
- let info = null;
123
- let errInfo = null;
124
- const result = await runCucumber({ ...runConfiguration, support }, environment, (message) => {
125
- if (message.testStepFinished) {
126
- const { testStepFinished } = message;
127
- const { testStepResult } = testStepFinished;
128
- if (testStepResult.status === "FAILED" || testStepResult.status === "AMBIGUOUS") {
129
- if (!errorMesssage) {
130
- errorMesssage = testStepResult.message;
131
- if (info) {
132
- errInfo = info;
133
- }
160
+ },
161
+ { cwd: process.cwd(), env: environment }
162
+ );
163
+
164
+ const support = await loadSupport(runConfiguration, { cwd: process.cwd(), env: environment });
165
+
166
+ support.afterTestRunHookDefinitions = [];
167
+ if (skipAfter) {
168
+ support.afterTestCaseHookDefinitions = [];
169
+ }
170
+ if (skipBefore && !config.legacySyntax) {
171
+ support.beforeTestCaseHookDefinitions = support.beforeTestCaseHookDefinitions.filter((hook) => {
172
+ return hook.uri.endsWith("utils.mjs");
173
+ });
174
+ }
175
+ support.beforeTestRunHookDefinitions = [];
176
+
177
+ let errorMessage = null;
178
+ let info = null;
179
+ let errInfo = null;
180
+
181
+ const result = await runCucumber({ ...runConfiguration, support }, environment, (message) => {
182
+ if (message.testStepFinished) {
183
+ const { testStepFinished } = message;
184
+ const { testStepResult } = testStepFinished;
185
+ if (testStepResult.status === "FAILED" || testStepResult.status === "AMBIGUOUS") {
186
+ if (!errorMessage) {
187
+ errorMessage = testStepResult.message;
188
+ if (info) {
189
+ errInfo = info;
134
190
  }
135
191
  }
136
- if (testStepResult.status === "UNDEFINED") {
137
- if (!errorMesssage) {
138
- errorMesssage = `step ${JSON.stringify(stepText)} is ${testStepResult.status}`;
139
- if (info) {
140
- errInfo = info;
141
- }
192
+ }
193
+ if (testStepResult.status === "UNDEFINED") {
194
+ if (!errorMessage) {
195
+ errorMessage = `step ${JSON.stringify(stepText)} is ${testStepResult.status}`;
196
+ if (info) {
197
+ errInfo = info;
142
198
  }
143
199
  }
144
200
  }
145
- if (message.attachment) {
146
- const attachment = message.attachment;
147
- if (attachment.mediaType === "application/json" && attachment.body) {
148
- const body = JSON.parse(attachment.body);
149
- info = body.info;
150
- const result = body.result;
151
-
152
- if (result.status === "PASSED") {
153
- this.sendExecutionStatus({
154
- type: "cmdExecutionSuccess",
155
- cmdId: body.cmdId,
156
- selectedStrategy: info?.selectedStrategy,
157
- });
158
- } else {
159
- this.sendExecutionStatus({
160
- type: "cmdExecutionError",
161
- cmdId: body.cmdId,
162
- error: {
163
- message: result.message,
164
- info,
165
- },
166
- });
167
- }
168
- } else if (attachment.mediaType === "application/json+intercept-results" && attachment.body) {
169
- const body = JSON.parse(attachment.body);
170
- if (body) {
171
- this.sendExecutionStatus({
172
- type: "interceptResults",
173
- interceptResults: body,
174
- });
175
- }
201
+ }
202
+ if (message.attachment) {
203
+ const attachment = message.attachment;
204
+ if (attachment.mediaType === "application/json" && attachment.body) {
205
+ const body = JSON.parse(attachment.body);
206
+ info = body.info;
207
+ const result = body.result;
208
+
209
+ if (result.status === "PASSED") {
210
+ this.sendExecutionStatus({
211
+ type: "cmdExecutionSuccess",
212
+ cmdId: body.cmdId,
213
+ selectedStrategy: info?.selectedStrategy,
214
+ });
215
+ } else {
216
+ this.sendExecutionStatus({
217
+ type: "cmdExecutionError",
218
+ cmdId: body.cmdId,
219
+ error: {
220
+ message: result.message,
221
+ info,
222
+ },
223
+ });
224
+ }
225
+ } else if (attachment.mediaType === "application/json+intercept-results" && attachment.body) {
226
+ const body = JSON.parse(attachment.body);
227
+ if (body) {
228
+ this.sendExecutionStatus({
229
+ type: "interceptResults",
230
+ interceptResults: body,
231
+ });
176
232
  }
177
233
  }
178
- });
179
- if (errorMesssage) {
180
- const bvtError = new Error(errorMesssage);
181
- Object.assign(bvtError, { info: errInfo });
182
- throw bvtError;
183
234
  }
235
+ });
184
236
 
185
- return {
186
- result,
187
- info,
188
- };
189
- } catch (error) {
190
- console.error("Error running cucumber-js", error);
191
- throw error;
237
+ if (errorMessage) {
238
+ const bvtError = new Error(errorMessage);
239
+ Object.assign(bvtError, { info: errInfo });
240
+ throw bvtError;
192
241
  }
193
- };
242
+
243
+ return { result, info };
244
+ }
194
245
 
195
246
  async runStep({ step, parametersMap, envPath, tags, config }, bvtContext, options) {
196
- let cmdIDs = (step.commands || []).map((cmd) => cmd.cmdId);
197
- if (bvtContext.web) {
198
- bvtContext.web.getCmdId = () => {
199
- if (cmdIDs.length === 0) {
200
- cmdIDs = (step.commands || []).map((cmd) => cmd.cmdId);
201
- }
202
- const cId = cmdIDs.shift();
203
- this.sendExecutionStatus({
204
- type: "cmdExecutionStart",
205
- cmdId: cId,
206
- });
207
- return cId;
208
- };
209
- }
210
- let codePage; // = getCodePage();
211
- // const tempFolderPath = process.env.tempFeaturesFolderPath;
212
- const __temp_features_FolderName = "__temp_features" + Math.random().toString(36).substring(2, 7);
213
- const tempFolderPath = path.join(this.projectDir, __temp_features_FolderName);
214
- process.env.tempFeaturesFolderPath = __temp_features_FolderName;
215
- process.env.TESTCASE_REPORT_FOLDER_PATH = tempFolderPath;
216
- // if (!copiedCodeToTemp) {
217
- // await this.copyCodetoTempFolder({ step, parametersMap, tempFolderPath });
218
- // }
219
- await this.copyCodetoTempFolder({ step, parametersMap, tempFolderPath });
220
- // console.log({ feature_file_path });
221
- let stepsDefinitions = loadStepDefinitions(this.projectDir, false, true);
222
- // console.log({ stepsDefinitions });
223
- const cucumberStep = getCucumberStep({ step });
224
- if (cucumberStep.parameters && Array.isArray(cucumberStep.parameters)) {
225
- cucumberStep.parameters.forEach((param) => {
226
- if (param.variableName) {
227
- param.callValue = parametersMap[param.variableName];
228
- }
229
- });
230
- }
247
+ // Create a new AbortController for this specific step execution
248
+ this.#currentStepController = new AbortController();
249
+ const { signal } = this.#currentStepController;
231
250
 
232
- if (!step.isImplemented && step.commands.length > 0) {
233
- const pageName = generatePageName(step.startFrame?.url ?? "default");
234
- const stepDefinitionFolderPath = path.join(tempFolderPath, "step_definitions");
235
- if (!existsSync(stepDefinitionFolderPath)) {
236
- mkdirSync(stepDefinitionFolderPath, { recursive: true });
251
+ try {
252
+ this.#lastAttemptedCmdId = null;
253
+ let cmdIDs = (step.commands || []).map((cmd) => cmd.cmdId);
254
+ bvtContext.web.pausedCmd = null;
255
+
256
+ // Clear the liveExecutionMap and set up new entries for this step
257
+ this.liveExecutionMap.clear();
258
+
259
+ for (const cmdId of cmdIDs) {
260
+ this.liveExecutionMap.set(cmdId, {
261
+ resolve: () => {},
262
+ reject: () => {},
263
+ });
237
264
  }
238
- const stepDefsFilePath = locateDefinitionPath(tempFolderPath, pageName);
239
- //path.join(stepDefinitionFolderPath, pageName + "_page.mjs");
240
- codePage = getCodePage(stepDefsFilePath);
241
- codePage = await saveRecording({ step, cucumberStep, codePage, projectDir: this.projectDir, stepsDefinitions });
242
- if (codePage) {
243
- await codePage.save(stepDefsFilePath);
244
- // console.log("saved code page: ", stepDefsFilePath);
265
+
266
+ if (bvtContext.web) {
267
+ bvtContext.web.getCmdId = () => {
268
+ if (cmdIDs.length === 0) {
269
+ cmdIDs = (step.commands || []).map((cmd) => cmd.cmdId);
270
+ }
271
+ const cId = cmdIDs.shift();
272
+ this.sendExecutionStatus({
273
+ type: "cmdExecutionStart",
274
+ cmdId: cId,
275
+ });
276
+ this.#lastAttemptedCmdId = cId;
277
+ return cId;
278
+ };
245
279
  }
246
- if (!codePage) {
247
- codePage = getUtilsCodePage(this.projectDir);
280
+
281
+ const __temp_features_FolderName = "__temp_features" + Math.random().toString(36).substring(2, 7);
282
+ const tempFolderPath = path.join(this.projectDir, __temp_features_FolderName);
283
+ process.env.tempFeaturesFolderPath = __temp_features_FolderName;
284
+ process.env.TESTCASE_REPORT_FOLDER_PATH = tempFolderPath;
285
+
286
+ await this.copyCodetoTempFolder({ step, parametersMap, tempFolderPath });
287
+
288
+ // Write abort wrapper code with this step's signal
289
+ await this.writeWrapperCode(tempFolderPath, signal);
290
+
291
+ let stepsDefinitions = loadStepDefinitions(this.projectDir, false, true);
292
+ const cucumberStep = getCucumberStep({ step });
293
+
294
+ if (cucumberStep.parameters && Array.isArray(cucumberStep.parameters)) {
295
+ cucumberStep.parameters.forEach((param) => {
296
+ if (param.variableName) {
297
+ param.callValue = parametersMap[param.variableName];
298
+ }
299
+ });
248
300
  }
249
- } else {
250
- let routesPath = path.join(tmpdir(), `blinq_temp_routes`);
251
- if (process.env.TEMP_RUN === "true") {
252
- // console.log("Save routes in temp folder for running:", routesPath);
253
- if (existsSync(routesPath)) {
254
- console.log("Removing existing temp_routes_folder:", routesPath);
255
- rmSync(routesPath, { recursive: true });
301
+
302
+ if (!step.isImplemented && step.commands.length > 0) {
303
+ const pageName = generatePageName(step.startFrame?.url ?? "default");
304
+ const stepDefinitionFolderPath = path.join(tempFolderPath, "step_definitions");
305
+ if (!existsSync(stepDefinitionFolderPath)) {
306
+ mkdirSync(stepDefinitionFolderPath, { recursive: true });
307
+ }
308
+ const stepDefsFilePath = locateDefinitionPath(tempFolderPath, pageName);
309
+ let codePage = getCodePage(stepDefsFilePath);
310
+ codePage = await saveRecording({
311
+ step,
312
+ cucumberStep,
313
+ codePage,
314
+ projectDir: this.projectDir,
315
+ stepsDefinitions,
316
+ });
317
+ if (codePage) {
318
+ await codePage.save(stepDefsFilePath);
319
+ }
320
+ if (!codePage) {
321
+ codePage = getUtilsCodePage(this.projectDir);
256
322
  }
257
- mkdirSync(routesPath, { recursive: true });
258
- console.log("Created temp_routes_folder:", routesPath);
259
- saveRoutes({ step, folderPath: routesPath });
260
323
  } else {
261
- console.log("Saving routes in project directory:", this.projectDir);
262
- if (existsSync(routesPath)) {
263
- // remove the folder
264
- try {
324
+ let routesPath = path.join(tmpdir(), `blinq_temp_routes`);
325
+ if (process.env.TEMP_RUN === "true") {
326
+ if (existsSync(routesPath)) {
265
327
  rmSync(routesPath, { recursive: true });
266
- console.log("Removed temp_routes_folder:", routesPath);
267
- } catch (error) {
268
- console.error("Error removing temp_routes folder", error);
269
328
  }
270
- }
271
- routesPath = path.join(this.projectDir, "data", "routes");
272
- console.log("Saving routes to:", routesPath);
273
- if (!existsSync(routesPath)) {
274
329
  mkdirSync(routesPath, { recursive: true });
330
+ saveRoutes({ step, folderPath: routesPath });
331
+ } else {
332
+ if (existsSync(routesPath)) {
333
+ try {
334
+ rmSync(routesPath, { recursive: true });
335
+ } catch (error) {
336
+ console.error("Error removing temp_routes folder", error);
337
+ }
338
+ }
339
+ routesPath = path.join(this.projectDir, "data", "routes");
340
+ if (!existsSync(routesPath)) {
341
+ mkdirSync(routesPath, { recursive: true });
342
+ }
343
+ saveRoutes({ step, folderPath: routesPath });
275
344
  }
276
- saveRoutes({ step, folderPath: routesPath });
277
345
  }
278
- }
279
- const feature_file_path = await this.writeTempFeatureFile({ step, parametersMap, tempFolderPath, tags });
280
- // console.log({ feature_file_path, step_text: step.text });
281
346
 
282
- const stepExecController = new AbortController();
283
- this.#currentStepController = stepExecController;
284
- const { result, info } = await withAbort(async () => {
285
- return await this.executeStepRemote(
347
+ const feature_file_path = await this.writeTempFeatureFile({
348
+ step,
349
+ parametersMap,
350
+ tempFolderPath,
351
+ tags,
352
+ });
353
+
354
+ // Execute the cucumber step - if wrapper throws "Aborted", it will propagate up
355
+ const { result, info } = await this.executeStepWithAbort(
286
356
  {
287
357
  feature_file_path,
288
358
  tempFolderPath,
@@ -292,12 +362,25 @@ export class BVTStepRunner {
292
362
  },
293
363
  options
294
364
  );
295
- }, stepExecController.signal).finally(() => {
296
- // rm temp folder
297
- if (fs.existsSync(tempFolderPath)) {
298
- fs.rmSync(tempFolderPath, { recursive: true });
365
+
366
+ return { result, info };
367
+ } catch (error) {
368
+ if (error.message && error.message.includes("Aborted")) {
369
+ throw new Error("Aborted");
370
+ } else throw error;
371
+ } finally {
372
+ // Clean up this step's controller and global reference
373
+ this.#currentStepController = null;
374
+ global.__BVT_STEP_ABORT_SIGNAL = null;
375
+
376
+ // Clean up temp folder
377
+ const __temp_features_FolderName = process.env.tempFeaturesFolderPath;
378
+ if (__temp_features_FolderName) {
379
+ const tempFolderPath = path.join(this.projectDir, __temp_features_FolderName);
380
+ if (fs.existsSync(tempFolderPath)) {
381
+ fs.rmSync(tempFolderPath, { recursive: true });
382
+ }
299
383
  }
300
- });
301
- return { result, info };
384
+ }
302
385
  }
303
386
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dev-blinq/cucumber_client",
3
- "version": "1.0.1404-dev",
3
+ "version": "1.0.1405-dev",
4
4
  "description": " ",
5
5
  "main": "bin/index.js",
6
6
  "types": "bin/index.d.ts",