@dev-blinq/cucumber_client 1.0.1226-dev → 1.0.1226-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/recorderv3.js +68 -5
- package/bin/assets/preload/unique_locators.js +1 -1
- 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 +841 -0
- package/bin/assets/scripts/yaml.js +4770 -0
- package/bin/assets/templates/_hooks_template.txt +37 -0
- package/bin/assets/templates/page_template.txt +2 -16
- package/bin/assets/templates/utils_template.txt +48 -63
- package/bin/client/apiTest/apiTest.js +6 -0
- package/bin/client/cli_helpers.js +11 -13
- package/bin/client/code_cleanup/utils.js +37 -14
- package/bin/client/code_gen/code_inversion.js +68 -10
- package/bin/client/code_gen/page_reflection.js +12 -15
- package/bin/client/code_gen/playwright_codeget.js +163 -33
- package/bin/client/cucumber/feature.js +85 -27
- package/bin/client/cucumber/steps_definitions.js +84 -76
- package/bin/client/local_agent.js +3 -3
- package/bin/client/project.js +7 -1
- package/bin/client/recorderv3/bvt_recorder.js +267 -80
- package/bin/client/recorderv3/implemented_steps.js +74 -12
- package/bin/client/recorderv3/index.js +58 -8
- package/bin/client/recorderv3/network.js +299 -0
- package/bin/client/recorderv3/step_runner.js +319 -67
- package/bin/client/recorderv3/step_utils.js +145 -4
- package/bin/client/recorderv3/update_feature.js +58 -30
- package/bin/client/recording.js +7 -0
- package/bin/client/run_cucumber.js +5 -1
- package/bin/client/scenario_report.js +4 -8
- package/bin/client/test_scenario.js +0 -1
- package/bin/index.js +1 -0
- package/package.json +15 -8
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, writeFileSync } from "fs";
|
|
1
|
+
import { existsSync, mkdirSync, rmSync, writeFileSync } from "fs";
|
|
2
2
|
import path from "path";
|
|
3
|
-
import fs from "fs";
|
|
4
3
|
import { generatePageName } from "../code_gen/playwright_codeget.js";
|
|
5
4
|
import {
|
|
6
5
|
executeStep,
|
|
@@ -9,43 +8,69 @@ import {
|
|
|
9
8
|
getUtilsCodePage,
|
|
10
9
|
loadStepDefinitions,
|
|
11
10
|
saveRecording,
|
|
11
|
+
saveRoutes,
|
|
12
12
|
} from "./step_utils.js";
|
|
13
13
|
import { escapeString, getExamplesContent } from "./update_feature.js";
|
|
14
|
+
import fs from "fs";
|
|
14
15
|
import { locateDefinitionPath } from "../cucumber/steps_definitions.js";
|
|
16
|
+
import { tmpdir } from "os";
|
|
15
17
|
|
|
16
|
-
// let copiedCodeToTemp = false;
|
|
17
|
-
async function withAbort(fn, signal) {
|
|
18
|
-
if (!signal) {
|
|
19
|
-
return await fn();
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const abortPromise = new Promise((_, reject) => {
|
|
23
|
-
signal.addEventListener("abort", () => reject(new Error("Aborted")), { once: true });
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
return await Promise.race([fn(), abortPromise]);
|
|
27
|
-
}
|
|
28
18
|
export class BVTStepRunner {
|
|
29
|
-
#currentStepController;
|
|
19
|
+
#currentStepController = null;
|
|
30
20
|
#port;
|
|
31
|
-
|
|
21
|
+
#lastAttemptedCmdId = null;
|
|
22
|
+
|
|
23
|
+
constructor({ projectDir, sendExecutionStatus, bvtContext }) {
|
|
32
24
|
this.projectDir = projectDir;
|
|
25
|
+
this.sendExecutionStatus = sendExecutionStatus;
|
|
26
|
+
this.bvtContext = bvtContext;
|
|
27
|
+
this.liveExecutionMap = new Map();
|
|
33
28
|
}
|
|
29
|
+
|
|
34
30
|
setRemoteDebugPort(port) {
|
|
35
31
|
this.#port = port;
|
|
36
32
|
}
|
|
33
|
+
|
|
34
|
+
// Abort the current cucumber step execution by signaling the wrapper
|
|
37
35
|
async abortExecution() {
|
|
36
|
+
if (this.bvtContext.web.pausedCmd) {
|
|
37
|
+
this.bvtContext.web.pausedCmd = null;
|
|
38
|
+
}
|
|
39
|
+
this.liveExecutionMap.clear();
|
|
38
40
|
if (this.#currentStepController) {
|
|
39
41
|
this.#currentStepController.abort();
|
|
40
42
|
}
|
|
41
43
|
}
|
|
42
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
|
+
|
|
43
70
|
async copyCodetoTempFolder({ step, parametersMap, tempFolderPath }) {
|
|
44
|
-
// const tempFolderPath = path.join(this.projectDir, "__temp_features");
|
|
45
71
|
if (!fs.existsSync(tempFolderPath)) {
|
|
46
72
|
fs.mkdirSync(tempFolderPath);
|
|
47
73
|
}
|
|
48
|
-
//copy all files from "./features" "./temp" folder
|
|
49
74
|
if (fs.existsSync(tempFolderPath)) {
|
|
50
75
|
fs.rmSync(tempFolderPath, { recursive: true });
|
|
51
76
|
}
|
|
@@ -53,14 +78,13 @@ export class BVTStepRunner {
|
|
|
53
78
|
overwrite: true,
|
|
54
79
|
recursive: true,
|
|
55
80
|
});
|
|
56
|
-
// copiedCodeToTemp = true;
|
|
57
81
|
}
|
|
58
82
|
|
|
59
|
-
async writeTempFeatureFile({ step, parametersMap, tempFolderPath }) {
|
|
83
|
+
async writeTempFeatureFile({ step, parametersMap, tempFolderPath, tags }) {
|
|
60
84
|
const tFilePath = path.join(tempFolderPath, "__temp.feature");
|
|
61
|
-
// console.log(tFilePath);
|
|
62
85
|
let tFileContent = `# temp feature file
|
|
63
86
|
Feature: Temp feature
|
|
87
|
+
${tags ? tags.join(" ") : ""}
|
|
64
88
|
Scenario Outline: Temp Scenario
|
|
65
89
|
Given ${escapeString(step.text)}
|
|
66
90
|
`;
|
|
@@ -68,67 +92,295 @@ export class BVTStepRunner {
|
|
|
68
92
|
writeFileSync(tFilePath, tFileContent);
|
|
69
93
|
return tFilePath;
|
|
70
94
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
95
|
+
|
|
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) {
|
|
148
|
+
const { skipAfter = true, skipBefore = true } = options || {};
|
|
149
|
+
|
|
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")],
|
|
159
|
+
},
|
|
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");
|
|
91
173
|
});
|
|
92
174
|
}
|
|
175
|
+
support.beforeTestRunHookDefinitions = [];
|
|
93
176
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (testStepResult.status === "UNDEFINED") {
|
|
194
|
+
if (!errorMessage) {
|
|
195
|
+
errorMessage = `step ${JSON.stringify(stepText)} is ${testStepResult.status}`;
|
|
196
|
+
if (info) {
|
|
197
|
+
errInfo = info;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
107
201
|
}
|
|
108
|
-
if (
|
|
109
|
-
|
|
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
|
+
});
|
|
232
|
+
}
|
|
233
|
+
}
|
|
110
234
|
}
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
if (errorMessage) {
|
|
238
|
+
const bvtError = new Error(errorMessage);
|
|
239
|
+
Object.assign(bvtError, { info: errInfo });
|
|
240
|
+
throw bvtError;
|
|
111
241
|
}
|
|
112
|
-
const feature_file_path = await this.writeTempFeatureFile({ step, parametersMap, tempFolderPath });
|
|
113
|
-
// console.log({ feature_file_path, step_text: step.text });
|
|
114
242
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
243
|
+
return { result, info };
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async runStep({ step, parametersMap, envPath, tags, config }, bvtContext, options) {
|
|
247
|
+
// Create a new AbortController for this specific step execution
|
|
248
|
+
this.#currentStepController = new AbortController();
|
|
249
|
+
const { signal } = this.#currentStepController;
|
|
250
|
+
|
|
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
|
+
});
|
|
264
|
+
}
|
|
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
|
+
};
|
|
279
|
+
}
|
|
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
|
+
});
|
|
300
|
+
}
|
|
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);
|
|
322
|
+
}
|
|
323
|
+
} else {
|
|
324
|
+
let routesPath = path.join(tmpdir(), `blinq_temp_routes`);
|
|
325
|
+
if (process.env.TEMP_RUN === "true") {
|
|
326
|
+
if (existsSync(routesPath)) {
|
|
327
|
+
rmSync(routesPath, { recursive: true });
|
|
328
|
+
}
|
|
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 });
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
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(
|
|
119
356
|
{
|
|
120
357
|
feature_file_path,
|
|
121
358
|
tempFolderPath,
|
|
122
359
|
stepText: step.text,
|
|
123
360
|
scenario: "Temp Scenario",
|
|
361
|
+
config,
|
|
124
362
|
},
|
|
125
363
|
options
|
|
126
364
|
);
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
+
}
|
|
131
383
|
}
|
|
132
|
-
}
|
|
384
|
+
}
|
|
133
385
|
}
|
|
134
386
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
2
2
|
import path from "path";
|
|
3
3
|
import url from "url";
|
|
4
4
|
import logger from "../../logger.js";
|
|
@@ -9,6 +9,8 @@ import { Step } from "../cucumber/feature.js";
|
|
|
9
9
|
import { locateDefinitionPath, StepsDefinitions } from "../cucumber/steps_definitions.js";
|
|
10
10
|
import { Recording } from "../recording.js";
|
|
11
11
|
import { generateApiCode } from "../code_gen/api_codegen.js";
|
|
12
|
+
import { tmpdir } from "os";
|
|
13
|
+
import { createHash } from "crypto";
|
|
12
14
|
|
|
13
15
|
const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
|
|
14
16
|
|
|
@@ -69,19 +71,74 @@ function makeStepTextUnique(step, stepsDefinitions) {
|
|
|
69
71
|
}
|
|
70
72
|
|
|
71
73
|
export async function saveRecording({ step, cucumberStep, codePage, projectDir, stepsDefinitions }) {
|
|
72
|
-
|
|
74
|
+
let routesPath = path.join(tmpdir(), "blinq_temp_routes");
|
|
75
|
+
|
|
76
|
+
if (process.env.TEMP_RUN) {
|
|
77
|
+
if (existsSync(routesPath)) {
|
|
78
|
+
rmSync(routesPath, { recursive: true });
|
|
79
|
+
}
|
|
80
|
+
mkdirSync(routesPath, { recursive: true });
|
|
81
|
+
saveRoutes({ step, folderPath: routesPath });
|
|
82
|
+
} else {
|
|
83
|
+
if (existsSync(routesPath)) {
|
|
84
|
+
// remove the folder
|
|
85
|
+
try {
|
|
86
|
+
rmSync(routesPath, { recursive: true });
|
|
87
|
+
console.log("Removed temp_routes_folder:", routesPath);
|
|
88
|
+
} catch (error) {
|
|
89
|
+
console.error("Error removing temp_routes folder", error);
|
|
90
|
+
}
|
|
91
|
+
routesPath = path.join(projectDir, "data", "routes");
|
|
92
|
+
if (!existsSync(routesPath)) {
|
|
93
|
+
mkdirSync(routesPath, { recursive: true });
|
|
94
|
+
}
|
|
95
|
+
saveRoutes({ step, folderPath: routesPath });
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
73
99
|
if (step.isImplementedWhileRecording && !process.env.TEMP_RUN) {
|
|
74
100
|
return;
|
|
75
101
|
}
|
|
102
|
+
|
|
76
103
|
if (step.isImplemented && step.shouldOverride) {
|
|
77
104
|
let stepDef = stepsDefinitions.findMatchingStep(step.text);
|
|
78
105
|
codePage = getCodePage(stepDef.file);
|
|
79
106
|
} else {
|
|
80
107
|
const isUtilStep = makeStepTextUnique(step, stepsDefinitions);
|
|
108
|
+
|
|
81
109
|
if (isUtilStep) {
|
|
82
110
|
return;
|
|
83
111
|
}
|
|
84
112
|
}
|
|
113
|
+
|
|
114
|
+
if (process.env.TEMP_RUN === "true") {
|
|
115
|
+
console.log("Save routes in temp folder for running:", routesPath);
|
|
116
|
+
if (existsSync(routesPath)) {
|
|
117
|
+
console.log("Removing existing temp_routes_folder:", routesPath);
|
|
118
|
+
rmSync(routesPath, { recursive: true });
|
|
119
|
+
}
|
|
120
|
+
mkdirSync(routesPath, { recursive: true });
|
|
121
|
+
console.log("Created temp_routes_folder:", routesPath);
|
|
122
|
+
saveRoutes({ step, folderPath: routesPath });
|
|
123
|
+
} else {
|
|
124
|
+
console.log("Saving routes in project directory:", projectDir);
|
|
125
|
+
if (existsSync(routesPath)) {
|
|
126
|
+
// remove the folder
|
|
127
|
+
try {
|
|
128
|
+
rmSync(routesPath, { recursive: true });
|
|
129
|
+
console.log("Removed temp_routes_folder:", routesPath);
|
|
130
|
+
} catch (error) {
|
|
131
|
+
console.error("Error removing temp_routes folder", error);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
routesPath = path.join(projectDir, "data", "routes");
|
|
135
|
+
console.log("Saving routes to:", routesPath);
|
|
136
|
+
if (!existsSync(routesPath)) {
|
|
137
|
+
mkdirSync(routesPath, { recursive: true });
|
|
138
|
+
}
|
|
139
|
+
saveRoutes({ step, folderPath: routesPath });
|
|
140
|
+
}
|
|
141
|
+
|
|
85
142
|
cucumberStep.text = step.text;
|
|
86
143
|
const recording = new Recording();
|
|
87
144
|
const steps = step.commands;
|
|
@@ -108,6 +165,7 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
|
|
|
108
165
|
isStaticToken,
|
|
109
166
|
status,
|
|
110
167
|
} = step.commands[0].value;
|
|
168
|
+
|
|
111
169
|
const result = await generateApiCode(
|
|
112
170
|
{
|
|
113
171
|
url,
|
|
@@ -132,6 +190,7 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
|
|
|
132
190
|
step.keyword,
|
|
133
191
|
stepsDefinitions
|
|
134
192
|
);
|
|
193
|
+
|
|
135
194
|
if (!step.isImplemented) {
|
|
136
195
|
stepsDefinitions.addStep({
|
|
137
196
|
name: step.text,
|
|
@@ -139,6 +198,7 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
|
|
|
139
198
|
source: "recorder",
|
|
140
199
|
});
|
|
141
200
|
}
|
|
201
|
+
|
|
142
202
|
cucumberStep.methodName = result.methodName;
|
|
143
203
|
return result.codePage;
|
|
144
204
|
} else {
|
|
@@ -166,7 +226,13 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
|
|
|
166
226
|
path
|
|
167
227
|
);
|
|
168
228
|
const keyword = (cucumberStep.keywordAlias ?? cucumberStep.keyword).trim();
|
|
169
|
-
const stepResult = codePage.addCucumberStep(
|
|
229
|
+
const stepResult = codePage.addCucumberStep(
|
|
230
|
+
keyword,
|
|
231
|
+
cucumberStep.getTemplate(),
|
|
232
|
+
methodName,
|
|
233
|
+
steps.length,
|
|
234
|
+
step.finalTimeout
|
|
235
|
+
);
|
|
170
236
|
|
|
171
237
|
if (!step.isImplemented) {
|
|
172
238
|
stepsDefinitions.addStep({
|
|
@@ -306,6 +372,12 @@ export async function updateStepDefinitions({ scenario, featureName, projectDir
|
|
|
306
372
|
const utilsTemplateFilePath = path.join(__dirname, "../../assets", "templates", "utils_template.txt");
|
|
307
373
|
const utilsContent = readFileSync(utilsTemplateFilePath, "utf8");
|
|
308
374
|
writeFileSync(utilsFilePath, utilsContent, "utf8");
|
|
375
|
+
const hooksTemplateFilePath = path.join(__dirname, "../../assets", "templates", "_hooks_template.txt");
|
|
376
|
+
if (existsSync(hooksTemplateFilePath)) {
|
|
377
|
+
const hooksFilePath = path.join(stepDefinitionFolderPath, "_hooks.mjs");
|
|
378
|
+
const hooksContent = readFileSync(hooksTemplateFilePath, "utf8");
|
|
379
|
+
writeFileSync(hooksFilePath, hooksContent, "utf8");
|
|
380
|
+
}
|
|
309
381
|
const steps = scenario.steps;
|
|
310
382
|
|
|
311
383
|
const stepsDefinitions = new StepsDefinitions(projectDir);
|
|
@@ -321,6 +393,34 @@ export async function updateStepDefinitions({ scenario, featureName, projectDir
|
|
|
321
393
|
}
|
|
322
394
|
}
|
|
323
395
|
if ((step.isImplemented && !step.shouldOverride) || step.commands.length === 0) {
|
|
396
|
+
let routesPath = path.join(tmpdir(), `blinq_temp_routes`);
|
|
397
|
+
if (process.env.TEMP_RUN === "true") {
|
|
398
|
+
console.log("Save routes in temp folder for running:", routesPath);
|
|
399
|
+
if (existsSync(routesPath)) {
|
|
400
|
+
console.log("Removing existing temp_routes_folder:", routesPath);
|
|
401
|
+
rmSync(routesPath, { recursive: true });
|
|
402
|
+
}
|
|
403
|
+
mkdirSync(routesPath, { recursive: true });
|
|
404
|
+
console.log("Created temp_routes_folder:", routesPath);
|
|
405
|
+
saveRoutes({ step, folderPath: routesPath });
|
|
406
|
+
} else {
|
|
407
|
+
console.log("Saving routes in project directory:", projectDir);
|
|
408
|
+
if (existsSync(routesPath)) {
|
|
409
|
+
// remove the folder
|
|
410
|
+
try {
|
|
411
|
+
rmSync(routesPath, { recursive: true });
|
|
412
|
+
console.log("Removed temp_routes_folder:", routesPath);
|
|
413
|
+
} catch (error) {
|
|
414
|
+
console.error("Error removing temp_routes folder", error);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
routesPath = path.join(projectDir, "data", "routes");
|
|
418
|
+
console.log("Saving routes to:", routesPath);
|
|
419
|
+
if (!existsSync(routesPath)) {
|
|
420
|
+
mkdirSync(routesPath, { recursive: true });
|
|
421
|
+
}
|
|
422
|
+
saveRoutes({ step, folderPath: routesPath });
|
|
423
|
+
}
|
|
324
424
|
continue;
|
|
325
425
|
}
|
|
326
426
|
const cucumberStep = getCucumberStep({ step });
|
|
@@ -328,7 +428,6 @@ export async function updateStepDefinitions({ scenario, featureName, projectDir
|
|
|
328
428
|
const stepDefsFilePath = locateDefinitionPath(featureFolder, pageName);
|
|
329
429
|
// path.join(stepDefinitionFolderPath, pageName + "_page.mjs");
|
|
330
430
|
let codePage = getCodePage(stepDefsFilePath);
|
|
331
|
-
|
|
332
431
|
codePage = await saveRecording({ step, cucumberStep, codePage, projectDir, stepsDefinitions });
|
|
333
432
|
if (!codePage) {
|
|
334
433
|
continue;
|
|
@@ -340,3 +439,45 @@ export async function updateStepDefinitions({ scenario, featureName, projectDir
|
|
|
340
439
|
}
|
|
341
440
|
writeFileSync(utilsFilePath, utilsContent, "utf8");
|
|
342
441
|
}
|
|
442
|
+
|
|
443
|
+
export function saveRoutes({ step, folderPath }) {
|
|
444
|
+
const routeItems = step.routeItems;
|
|
445
|
+
if (!routeItems || routeItems.length === 0) {
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
const cucumberStep = getCucumberStep({ step });
|
|
449
|
+
const template = cucumberStep.getTemplate();
|
|
450
|
+
const stepNameHash = createHash("sha256").update(template).digest("hex");
|
|
451
|
+
console.log("Saving routes for step:", step.text, "with hash:", stepNameHash);
|
|
452
|
+
|
|
453
|
+
const routeItemsWithFilters = routeItems.map((routeItem) => {
|
|
454
|
+
const oldFilters = routeItem.filters;
|
|
455
|
+
const queryParamsObject = {};
|
|
456
|
+
oldFilters.queryParams.forEach((queryParam) => {
|
|
457
|
+
queryParamsObject[queryParam.key] = queryParam.value;
|
|
458
|
+
});
|
|
459
|
+
const newFilters = { path: oldFilters.path, method: oldFilters.method, queryParams: queryParamsObject };
|
|
460
|
+
return {
|
|
461
|
+
...routeItem,
|
|
462
|
+
filters: newFilters,
|
|
463
|
+
};
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
const routesFilePath = path.join(folderPath, stepNameHash + ".json");
|
|
467
|
+
console.log("Routes file path:", routesFilePath);
|
|
468
|
+
const routesData = {
|
|
469
|
+
template,
|
|
470
|
+
routes: routeItemsWithFilters,
|
|
471
|
+
};
|
|
472
|
+
console.log("Routes data to save:", routesData);
|
|
473
|
+
|
|
474
|
+
if (!existsSync(folderPath)) {
|
|
475
|
+
mkdirSync(folderPath, { recursive: true });
|
|
476
|
+
}
|
|
477
|
+
try {
|
|
478
|
+
writeFileSync(routesFilePath, JSON.stringify(routesData, null, 2), "utf8");
|
|
479
|
+
console.log("Saved routes to", routesFilePath);
|
|
480
|
+
} catch (error) {
|
|
481
|
+
console.error("Failed to save routes to", routesFilePath, "Error:", error);
|
|
482
|
+
}
|
|
483
|
+
}
|