@dev-blinq/cucumber_client 1.0.1311-dev → 1.0.1313-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/assets/bundled_scripts/recorder.js +57 -57
- package/bin/assets/scripts/unique_locators.js +195 -169
- package/bin/client/code_gen/playwright_codeget.js +23 -4
- package/bin/client/recorderv3/bvt_recorder.js +36 -7
- package/bin/client/recorderv3/index.js +37 -1
- package/bin/client/recorderv3/network.js +299 -0
- package/bin/client/recorderv3/step_runner.js +116 -2
- package/bin/client/recorderv3/step_utils.js +70 -2
- package/package.json +2 -2
|
@@ -0,0 +1,299 @@
|
|
|
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
|
+
class NetworkMonitor {
|
|
11
|
+
constructor() {
|
|
12
|
+
this.networkId = 0;
|
|
13
|
+
/** @type {Map<string, NetworkEvent>} */
|
|
14
|
+
this.requestIdMap = new Map();
|
|
15
|
+
this.networkEvents = new Map();
|
|
16
|
+
/** @type {Map<import('playwright').Page, {responseListener: Function, requestFailedListener: Function}>} */
|
|
17
|
+
this.pageListeners = new Map();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Add network event listeners to a page
|
|
22
|
+
* @param {import('playwright').Page} page
|
|
23
|
+
*/
|
|
24
|
+
addNetworkEventListener(page) {
|
|
25
|
+
// Create the listener functions
|
|
26
|
+
const responseListener = async (response) => {
|
|
27
|
+
const request = response.request();
|
|
28
|
+
let eventId = this.requestIdMap.get(request);
|
|
29
|
+
|
|
30
|
+
if (!eventId) {
|
|
31
|
+
this.networkId++;
|
|
32
|
+
eventId = this.networkId.toString();
|
|
33
|
+
this.requestIdMap.set(request, eventId);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const networkEvent = {
|
|
37
|
+
id: eventId,
|
|
38
|
+
request,
|
|
39
|
+
response,
|
|
40
|
+
timestamp: Date.now(),
|
|
41
|
+
status: "completed",
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
this.networkEvents.set(eventId, networkEvent);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const requestFailedListener = (request) => {
|
|
48
|
+
let eventId = this.requestIdMap.get(request);
|
|
49
|
+
|
|
50
|
+
if (!eventId) {
|
|
51
|
+
this.networkId++;
|
|
52
|
+
eventId = this.networkId.toString();
|
|
53
|
+
this.requestIdMap.set(request, eventId);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const networkEvent = {
|
|
57
|
+
id: eventId,
|
|
58
|
+
request,
|
|
59
|
+
response: null,
|
|
60
|
+
timestamp: Date.now(),
|
|
61
|
+
status: "failed",
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
this.networkEvents.set(eventId, networkEvent);
|
|
65
|
+
};
|
|
66
|
+
// Store the listeners for later removal
|
|
67
|
+
this.pageListeners.set(page, {
|
|
68
|
+
responseListener,
|
|
69
|
+
requestFailedListener,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Add the listeners to the page
|
|
73
|
+
page.on("response", responseListener);
|
|
74
|
+
page.on("requestfailed", requestFailedListener);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Remove network event listeners from a specific page
|
|
79
|
+
* @param {import('playwright').Page} page
|
|
80
|
+
*/
|
|
81
|
+
removeNetworkEventListener(page) {
|
|
82
|
+
const listeners = this.pageListeners.get(page);
|
|
83
|
+
if (listeners) {
|
|
84
|
+
page.off("response", listeners.responseListener);
|
|
85
|
+
page.off("requestfailed", listeners.requestFailedListener);
|
|
86
|
+
this.pageListeners.delete(page);
|
|
87
|
+
console.log("Network event listeners removed from page");
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Remove network event listeners from all pages
|
|
93
|
+
*/
|
|
94
|
+
removeAllNetworkEventListeners() {
|
|
95
|
+
for (const [page, listeners] of this.pageListeners) {
|
|
96
|
+
page.off("response", listeners.responseListener);
|
|
97
|
+
page.off("requestfailed", listeners.requestFailedListener);
|
|
98
|
+
}
|
|
99
|
+
this.pageListeners.clear();
|
|
100
|
+
console.log("All network event listeners removed");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Check if a page has active listeners
|
|
105
|
+
* @param {import('playwright').Page} page
|
|
106
|
+
* @returns {boolean}
|
|
107
|
+
*/
|
|
108
|
+
hasListeners(page) {
|
|
109
|
+
return this.pageListeners.has(page);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Get the number of pages with active listeners
|
|
114
|
+
* @returns {number}
|
|
115
|
+
*/
|
|
116
|
+
getActiveListenersCount() {
|
|
117
|
+
return this.pageListeners.size;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Get all network events until now
|
|
122
|
+
*/
|
|
123
|
+
getAllNetworkEvents() {
|
|
124
|
+
return Array.from(this.networkEvents.values());
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Get network events within a range
|
|
129
|
+
* @param {number} startId
|
|
130
|
+
* @param {number} endId
|
|
131
|
+
* @returns {NetworkEvent[]}
|
|
132
|
+
*/
|
|
133
|
+
getNetworkEventsInRange(startId, endId) {
|
|
134
|
+
const events = [];
|
|
135
|
+
for (let i = startId; i <= endId; i++) {
|
|
136
|
+
const event = this.networkEvents.get(i.toString());
|
|
137
|
+
if (event) {
|
|
138
|
+
events.push(event);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return events;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Get network events since a specific ID
|
|
146
|
+
* @param {number} sinceId
|
|
147
|
+
* @returns {NetworkEvent[]}
|
|
148
|
+
*/
|
|
149
|
+
getNetworkEventsSince(sinceId) {
|
|
150
|
+
return this.getNetworkEventsInRange(sinceId + 1, this.networkId);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Get events by status
|
|
155
|
+
* @param {string} status - 'completed', 'failed'
|
|
156
|
+
* @returns {NetworkEvent[]}
|
|
157
|
+
*/
|
|
158
|
+
getEventsByStatus(status) {
|
|
159
|
+
return Array.from(this.networkEvents.values()).filter((event) => event.status === status);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Get current network ID (latest)
|
|
164
|
+
* @returns {number}
|
|
165
|
+
*/
|
|
166
|
+
getCurrentNetworkId() {
|
|
167
|
+
return this.networkId;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
getCurrentNetworkEventsLength() {
|
|
171
|
+
return this.networkEvents.size;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Get statistics about stored events
|
|
176
|
+
* @returns {Object}
|
|
177
|
+
*/
|
|
178
|
+
getStats() {
|
|
179
|
+
const events = Array.from(this.networkEvents.values());
|
|
180
|
+
return {
|
|
181
|
+
total: events.length,
|
|
182
|
+
completed: events.filter((e) => e.status === "completed").length,
|
|
183
|
+
failed: events.filter((e) => e.status === "failed").length,
|
|
184
|
+
oldestTimestamp: events.length > 0 ? Math.min(...events.map((e) => e.timestamp)) : null,
|
|
185
|
+
newestTimestamp: events.length > 0 ? Math.max(...events.map((e) => e.timestamp)) : null,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Marshall network event for serialization
|
|
191
|
+
* @param {NetworkEvent} networkEvent
|
|
192
|
+
* @returns {Promise<Object>}
|
|
193
|
+
*/
|
|
194
|
+
async marshallNetworkEvent(networkEvent) {
|
|
195
|
+
const { request, response } = networkEvent;
|
|
196
|
+
|
|
197
|
+
try {
|
|
198
|
+
const url = new URL(request.url());
|
|
199
|
+
const marshalledEvent = {
|
|
200
|
+
id: networkEvent.id,
|
|
201
|
+
timestamp: networkEvent.timestamp,
|
|
202
|
+
status: networkEvent.status,
|
|
203
|
+
url: url.href,
|
|
204
|
+
method: request.method(),
|
|
205
|
+
statusCode: response?.status() ?? null,
|
|
206
|
+
statusText: response?.statusText() ?? null,
|
|
207
|
+
queryParams: Object.fromEntries(url.searchParams.entries()),
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
// Try to get response body safely (only for successful responses)
|
|
211
|
+
if (response && networkEvent.status === "completed") {
|
|
212
|
+
try {
|
|
213
|
+
const isBinary =
|
|
214
|
+
!response.headers()["content-type"]?.includes("application/json") &&
|
|
215
|
+
!response.headers()["content-type"]?.includes("text");
|
|
216
|
+
let body;
|
|
217
|
+
if (isBinary) {
|
|
218
|
+
body = await response.body();
|
|
219
|
+
} else {
|
|
220
|
+
body = await response.text();
|
|
221
|
+
}
|
|
222
|
+
let json;
|
|
223
|
+
try {
|
|
224
|
+
if (typeof body === "string") {
|
|
225
|
+
json = JSON.parse(body); // Check if body is valid JSON
|
|
226
|
+
}
|
|
227
|
+
} catch (_) {
|
|
228
|
+
//Ignore
|
|
229
|
+
}
|
|
230
|
+
const responseBody = isBinary ? body : json ? JSON.stringify(json) : body;
|
|
231
|
+
marshalledEvent.contentType = isBinary ? "binary" : json ? "json" : "text";
|
|
232
|
+
marshalledEvent.responseBody = responseBody;
|
|
233
|
+
} catch (error) {
|
|
234
|
+
marshalledEvent.responseBody = `[Error reading response: ${error.message}]`;
|
|
235
|
+
}
|
|
236
|
+
} else if (networkEvent.status === "failed") {
|
|
237
|
+
marshalledEvent.failureReason = request.failure()?.errorText || "Unknown error";
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
console.log("Marshalled network event:", marshalledEvent);
|
|
241
|
+
|
|
242
|
+
return marshalledEvent;
|
|
243
|
+
} catch (error) {
|
|
244
|
+
console.error("Error marshalling network event:", error);
|
|
245
|
+
return {
|
|
246
|
+
id: networkEvent.id,
|
|
247
|
+
timestamp: networkEvent.timestamp,
|
|
248
|
+
status: networkEvent.status,
|
|
249
|
+
url: request.url(),
|
|
250
|
+
method: request.method(),
|
|
251
|
+
error: error.message,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
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
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Get marshalled network events since this ID
|
|
269
|
+
* @returns {Promise<Object[]>}
|
|
270
|
+
* @param {number} sinceId
|
|
271
|
+
*/
|
|
272
|
+
async getMarshalledNetworkEvents(sinceId) {
|
|
273
|
+
const events = this.getNetworkEventsSince(sinceId);
|
|
274
|
+
const marshalledEvents = await Promise.all(events.map((event) => this.marshallNetworkEvent(event)));
|
|
275
|
+
return marshalledEvents;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Get marshalled network events in a range
|
|
280
|
+
* @param {number} startId
|
|
281
|
+
* @param {number} endId
|
|
282
|
+
* @returns {Promise<Object[]>}
|
|
283
|
+
*/
|
|
284
|
+
async getMarshalledNetworkEventsInRange(startId, endId) {
|
|
285
|
+
const events = this.getNetworkEventsInRange(startId, endId);
|
|
286
|
+
const marshalledEvents = await Promise.all(events.map((event) => this.marshallNetworkEvent(event)));
|
|
287
|
+
return marshalledEvents;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Clear all network events
|
|
292
|
+
*/
|
|
293
|
+
clearNetworkEvents() {
|
|
294
|
+
this.networkEvents.clear();
|
|
295
|
+
this.networkId = 0;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
export default NetworkMonitor;
|
|
@@ -33,8 +33,9 @@ async function withAbort(fn, signal) {
|
|
|
33
33
|
export class BVTStepRunner {
|
|
34
34
|
#currentStepController;
|
|
35
35
|
#port;
|
|
36
|
-
constructor({ projectDir }) {
|
|
36
|
+
constructor({ projectDir, sendExecutionStatus }) {
|
|
37
37
|
this.projectDir = projectDir;
|
|
38
|
+
this.sendExecutionStatus = sendExecutionStatus;
|
|
38
39
|
}
|
|
39
40
|
setRemoteDebugPort(port) {
|
|
40
41
|
this.#port = port;
|
|
@@ -74,6 +75,119 @@ export class BVTStepRunner {
|
|
|
74
75
|
writeFileSync(tFilePath, tFileContent);
|
|
75
76
|
return tFilePath;
|
|
76
77
|
}
|
|
78
|
+
|
|
79
|
+
executeStepRemote = async ({ feature_file_path, scenario, tempFolderPath, stepText }, options) => {
|
|
80
|
+
if (!options) {
|
|
81
|
+
options = {
|
|
82
|
+
skipAfter: true,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
const environment = {
|
|
86
|
+
...process.env,
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
const { loadConfiguration, loadSupport, runCucumber } = await import("@dev-blinq/cucumber-js/api");
|
|
91
|
+
const { runConfiguration } = await loadConfiguration(
|
|
92
|
+
{
|
|
93
|
+
provided: {
|
|
94
|
+
name: [scenario],
|
|
95
|
+
paths: [feature_file_path],
|
|
96
|
+
import: [path.join(tempFolderPath, "step_definitions", "**", "*.mjs")],
|
|
97
|
+
// format: ["bvt"],
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
{ cwd: process.cwd(), env: environment }
|
|
101
|
+
);
|
|
102
|
+
// const files = glob.sync(path.join(tempFolderPath, "step_definitions", "**", "*.mjs"));
|
|
103
|
+
// console.log("Files found:", files);
|
|
104
|
+
const support = await loadSupport(runConfiguration, { cwd: process.cwd(), env: environment });
|
|
105
|
+
// console.log("found ", support.stepDefinitions.length, "step definitions");
|
|
106
|
+
// support.stepDefinitions.map((step) => {
|
|
107
|
+
// console.log("step", step.pattern);
|
|
108
|
+
// });
|
|
109
|
+
|
|
110
|
+
if (options.skipAfter) {
|
|
111
|
+
// ignore afterAll/after hooks
|
|
112
|
+
support.afterTestCaseHookDefinitions = [];
|
|
113
|
+
support.afterTestRunHookDefinitions = [];
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
let errorMesssage = null;
|
|
117
|
+
let info = null;
|
|
118
|
+
let errInfo = null;
|
|
119
|
+
const result = await runCucumber({ ...runConfiguration, support }, environment, (message) => {
|
|
120
|
+
if (message.testStepFinished) {
|
|
121
|
+
const { testStepFinished } = message;
|
|
122
|
+
const { testStepResult } = testStepFinished;
|
|
123
|
+
if (testStepResult.status === "FAILED" || testStepResult.status === "AMBIGUOUS") {
|
|
124
|
+
if (!errorMesssage) {
|
|
125
|
+
errorMesssage = testStepResult.message;
|
|
126
|
+
if (info) {
|
|
127
|
+
errInfo = info;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (testStepResult.status === "UNDEFINED") {
|
|
132
|
+
if (!errorMesssage) {
|
|
133
|
+
errorMesssage = `step ${JSON.stringify(stepText)} is ${testStepResult.status}`;
|
|
134
|
+
if (info) {
|
|
135
|
+
errInfo = info;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (message.attachment) {
|
|
141
|
+
const attachment = message.attachment;
|
|
142
|
+
if (attachment.mediaType === "application/json" && attachment.body) {
|
|
143
|
+
const body = JSON.parse(attachment.body);
|
|
144
|
+
info = body.info;
|
|
145
|
+
|
|
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
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
if (errorMesssage) {
|
|
176
|
+
const bvtError = new Error(errorMesssage);
|
|
177
|
+
Object.assign(bvtError, { info: errInfo });
|
|
178
|
+
throw bvtError;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
result,
|
|
183
|
+
info,
|
|
184
|
+
};
|
|
185
|
+
} catch (error) {
|
|
186
|
+
console.error("Error running cucumber-js", error);
|
|
187
|
+
throw error;
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
|
|
77
191
|
async runStep({ step, parametersMap, envPath, tags }, bvtContext, options) {
|
|
78
192
|
let codePage; // = getCodePage();
|
|
79
193
|
// const tempFolderPath = process.env.tempFeaturesFolderPath;
|
|
@@ -121,7 +235,7 @@ export class BVTStepRunner {
|
|
|
121
235
|
const stepExecController = new AbortController();
|
|
122
236
|
this.#currentStepController = stepExecController;
|
|
123
237
|
const { result, info } = await withAbort(async () => {
|
|
124
|
-
return await
|
|
238
|
+
return await this.executeStepRemote(
|
|
125
239
|
{
|
|
126
240
|
feature_file_path,
|
|
127
241
|
tempFolderPath,
|
|
@@ -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,10 +71,35 @@ 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);
|
|
@@ -82,6 +109,7 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
|
|
|
82
109
|
return;
|
|
83
110
|
}
|
|
84
111
|
}
|
|
112
|
+
|
|
85
113
|
cucumberStep.text = step.text;
|
|
86
114
|
const recording = new Recording();
|
|
87
115
|
const steps = step.commands;
|
|
@@ -108,6 +136,7 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
|
|
|
108
136
|
isStaticToken,
|
|
109
137
|
status,
|
|
110
138
|
} = step.commands[0].value;
|
|
139
|
+
|
|
111
140
|
const result = await generateApiCode(
|
|
112
141
|
{
|
|
113
142
|
url,
|
|
@@ -132,6 +161,7 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
|
|
|
132
161
|
step.keyword,
|
|
133
162
|
stepsDefinitions
|
|
134
163
|
);
|
|
164
|
+
|
|
135
165
|
if (!step.isImplemented) {
|
|
136
166
|
stepsDefinitions.addStep({
|
|
137
167
|
name: step.text,
|
|
@@ -139,6 +169,7 @@ export async function saveRecording({ step, cucumberStep, codePage, projectDir,
|
|
|
139
169
|
source: "recorder",
|
|
140
170
|
});
|
|
141
171
|
}
|
|
172
|
+
|
|
142
173
|
cucumberStep.methodName = result.methodName;
|
|
143
174
|
return result.codePage;
|
|
144
175
|
} else {
|
|
@@ -345,3 +376,40 @@ export async function updateStepDefinitions({ scenario, featureName, projectDir
|
|
|
345
376
|
}
|
|
346
377
|
writeFileSync(utilsFilePath, utilsContent, "utf8");
|
|
347
378
|
}
|
|
379
|
+
|
|
380
|
+
export function saveRoutes({ step, folderPath }) {
|
|
381
|
+
const routeItems = step.routeItems;
|
|
382
|
+
if (!routeItems || routeItems.length === 0) {
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
const cucumberStep = getCucumberStep({ step });
|
|
386
|
+
const template = cucumberStep.getTemplate();
|
|
387
|
+
const stepNameHash = createHash("sha256").update(template).digest("hex");
|
|
388
|
+
const routeItemsWithFilters = routeItems.map((routeItem) => {
|
|
389
|
+
const oldFilters = routeItem.filters;
|
|
390
|
+
const queryParamsObject = {};
|
|
391
|
+
oldFilters.queryParams.forEach((queryParam) => {
|
|
392
|
+
queryParamsObject[queryParam.key] = queryParam.value;
|
|
393
|
+
});
|
|
394
|
+
const newFilters = { path: oldFilters.path, method: oldFilters.method, queryParams: queryParamsObject };
|
|
395
|
+
return {
|
|
396
|
+
...routeItem,
|
|
397
|
+
filters: newFilters,
|
|
398
|
+
};
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
const routesFilePath = path.join(folderPath, stepNameHash + ".json");
|
|
402
|
+
const routesData = {
|
|
403
|
+
template,
|
|
404
|
+
routes: routeItemsWithFilters,
|
|
405
|
+
};
|
|
406
|
+
if (!existsSync(folderPath)) {
|
|
407
|
+
mkdirSync(folderPath, { recursive: true });
|
|
408
|
+
}
|
|
409
|
+
try {
|
|
410
|
+
writeFileSync(routesFilePath, JSON.stringify(routesData, null, 2), "utf8");
|
|
411
|
+
console.log("Saved routes to", routesFilePath);
|
|
412
|
+
} catch (error) {
|
|
413
|
+
console.error("Failed to save routes to", routesFilePath, "Error:", error);
|
|
414
|
+
}
|
|
415
|
+
}
|
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.1313-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.177-dev",
|
|
34
34
|
"@faker-js/faker": "^8.1.0",
|
|
35
|
-
"automation_model": "1.0.
|
|
35
|
+
"automation_model": "1.0.774-dev",
|
|
36
36
|
"axios": "^1.7.4",
|
|
37
37
|
"chokidar": "^3.6.0",
|
|
38
38
|
"create-require": "^1.1.1",
|