automation_model 1.0.785-dev → 1.0.785-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.
Files changed (44) hide show
  1. package/lib/api.js +28 -11
  2. package/lib/api.js.map +1 -1
  3. package/lib/auto_page.d.ts +1 -1
  4. package/lib/auto_page.js +58 -17
  5. package/lib/auto_page.js.map +1 -1
  6. package/lib/browser_manager.d.ts +2 -2
  7. package/lib/browser_manager.js +102 -52
  8. package/lib/browser_manager.js.map +1 -1
  9. package/lib/bruno.js.map +1 -1
  10. package/lib/check_performance.d.ts +1 -0
  11. package/lib/check_performance.js +57 -0
  12. package/lib/check_performance.js.map +1 -0
  13. package/lib/command_common.d.ts +1 -1
  14. package/lib/command_common.js +26 -16
  15. package/lib/command_common.js.map +1 -1
  16. package/lib/file_checker.js +7 -0
  17. package/lib/file_checker.js.map +1 -1
  18. package/lib/index.js +1 -0
  19. package/lib/index.js.map +1 -1
  20. package/lib/init_browser.d.ts +1 -2
  21. package/lib/init_browser.js +124 -128
  22. package/lib/init_browser.js.map +1 -1
  23. package/lib/locator_log.js.map +1 -1
  24. package/lib/network.d.ts +2 -2
  25. package/lib/network.js +341 -178
  26. package/lib/network.js.map +1 -1
  27. package/lib/route.d.ts +64 -2
  28. package/lib/route.js +493 -192
  29. package/lib/route.js.map +1 -1
  30. package/lib/scripts/axe.mini.js +23978 -1
  31. package/lib/snapshot_validation.js +3 -0
  32. package/lib/snapshot_validation.js.map +1 -1
  33. package/lib/stable_browser.d.ts +13 -7
  34. package/lib/stable_browser.js +474 -114
  35. package/lib/stable_browser.js.map +1 -1
  36. package/lib/table_helper.js +14 -0
  37. package/lib/table_helper.js.map +1 -1
  38. package/lib/test_context.d.ts +1 -0
  39. package/lib/test_context.js +1 -0
  40. package/lib/test_context.js.map +1 -1
  41. package/lib/utils.d.ts +6 -2
  42. package/lib/utils.js +121 -14
  43. package/lib/utils.js.map +1 -1
  44. package/package.json +18 -11
package/lib/network.js CHANGED
@@ -3,6 +3,60 @@ import fs from "fs";
3
3
  import { _stepNameToTemplate } from "./route.js";
4
4
  import crypto from "crypto";
5
5
  import { tmpdir } from "os";
6
+ import createDebug from "debug";
7
+ const debug = createDebug("automation_model:network");
8
+ class SaveQueue {
9
+ queue = [];
10
+ isProcessing = false;
11
+ maxRetries = 3;
12
+ async enqueueSave(current) {
13
+ this.queue.push({ current, retryCount: 0 });
14
+ if (!this.isProcessing) {
15
+ await this.processQueue();
16
+ }
17
+ }
18
+ async processQueue() {
19
+ this.isProcessing = true;
20
+ while (this.queue.length > 0) {
21
+ const item = this.queue.shift();
22
+ try {
23
+ await this.saveSafely(item.current);
24
+ }
25
+ catch (error) {
26
+ console.error(`Save failed for ${item.current ? "current" : "previous"} step:`, error);
27
+ // Retry logic
28
+ if (item.retryCount < this.maxRetries) {
29
+ item.retryCount++;
30
+ this.queue.unshift(item); // Put it back at the front
31
+ await new Promise((resolve) => setTimeout(resolve, 100 * item.retryCount)); // Exponential backoff
32
+ }
33
+ }
34
+ }
35
+ this.isProcessing = false;
36
+ }
37
+ async saveSafely(current) {
38
+ const stepHash = current ? executionState.currentStepHash : executionState.previousStepHash;
39
+ if (!stepHash)
40
+ return;
41
+ const file = path.join(detailedNetworkFolder, `${stepHash}.json`);
42
+ const entries = current
43
+ ? Array.from(executionState.liveRequestsMap.values())
44
+ : Array.from(executionState.liveRequestsMapPrevious.values());
45
+ // Ensure all entries are JSON-serializable
46
+ const validEntries = entries.filter((entry) => {
47
+ try {
48
+ JSON.stringify(entry);
49
+ return true;
50
+ }
51
+ catch {
52
+ console.warn(`Skipping non-serializable entry: ${entry.requestId}`);
53
+ return false;
54
+ }
55
+ });
56
+ const jsonString = JSON.stringify(validEntries, null, 2);
57
+ await fs.promises.writeFile(file, jsonString, "utf8");
58
+ }
59
+ }
6
60
  function _getNetworkFile(world = null, web = null, context = null) {
7
61
  let networkFile = null;
8
62
  if (world && world.reportFolder) {
@@ -46,17 +100,22 @@ function registerDownloadEvent(page, world, context) {
46
100
  }
47
101
  }
48
102
  function registerNetworkEvents(world, web, context, page) {
103
+ // Map to hold request start times and IDs
49
104
  const networkFile = _getNetworkFile(world, web, context);
50
105
  function saveNetworkData() {
51
106
  if (context && context.networkData) {
52
- fs.writeFileSync(networkFile, JSON.stringify(context.networkData, null, 2), "utf8");
107
+ try {
108
+ fs.writeFileSync(networkFile, JSON.stringify(context.networkData, null, 2), "utf8");
109
+ }
110
+ catch (error) {
111
+ console.error("Error saving network data:", error);
112
+ }
53
113
  }
54
114
  }
55
115
  if (!context) {
56
116
  console.error("No context found to register network events");
57
117
  return;
58
118
  }
59
- // Map to hold request start times and IDs
60
119
  const requestTimes = new Map();
61
120
  let requestIdCounter = 0;
62
121
  if (page) {
@@ -65,118 +124,144 @@ function registerNetworkEvents(world, web, context, page) {
65
124
  const networkData = context.networkData;
66
125
  // Event listener for when a request is made
67
126
  page.on("request", (request) => {
68
- const requestId = requestIdCounter++;
69
- request.requestId = requestId; // Assign a unique ID to the request
70
- handleRequest(request);
71
- const startTime = Date.now();
72
- requestTimes.set(requestId, startTime);
73
- // Initialize data for this request
74
- networkData.push({
75
- requestId,
76
- requestStart: startTime,
77
- requestUrl: request.url(),
78
- method: request.method(),
79
- status: "Pending",
80
- responseTime: null,
81
- responseReceived: null,
82
- responseEnd: null,
83
- size: null,
84
- });
85
- saveNetworkData();
86
- });
87
- // Event listener for when a response is received
88
- page.on("response", async (response) => {
89
- const request = response.request();
90
- const requestId = request.requestId;
91
- const receivedTime = Date.now();
92
- // Find the corresponding data object
93
- const data = networkData.find((item) => item.requestId === requestId);
94
- if (data) {
95
- data.status = response.status();
96
- data.responseReceived = receivedTime;
127
+ try {
128
+ // console.log("Request started:", request.url());
129
+ const requestId = requestIdCounter++;
130
+ request.requestId = requestId; // Assign a unique ID to the request
131
+ handleRequest(request, context);
132
+ const startTime = Date.now();
133
+ requestTimes.set(requestId, startTime);
134
+ // Initialize data for this request
135
+ networkData.push({
136
+ requestId,
137
+ requestStart: startTime,
138
+ requestUrl: request.url(),
139
+ method: request.method(),
140
+ status: "Pending",
141
+ responseTime: null,
142
+ responseReceived: null,
143
+ responseEnd: null,
144
+ size: null,
145
+ });
97
146
  saveNetworkData();
98
147
  }
99
- else {
100
- console.error("No data found for request ID", requestId);
148
+ catch (error) {
149
+ // console.error("Error handling request:", error);
101
150
  }
102
151
  });
103
- // Event listener for when a request is finished
104
- page.on("requestfinished", async (request) => {
105
- const requestId = request.requestId;
106
- const endTime = Date.now();
107
- const startTime = requestTimes.get(requestId);
108
- await handleRequestFinishedOrFailed(request, false);
109
- const response = await request.response();
110
- const timing = request.timing();
111
- // Find the corresponding data object
112
- const data = networkData.find((item) => item.requestId === requestId);
113
- if (data) {
114
- data.responseEnd = endTime;
115
- data.responseTime = endTime - startTime;
116
- // Get response size
117
- try {
118
- const body = await response.body();
119
- data.size = body.length;
120
- }
121
- catch (e) {
122
- data.size = 0;
152
+ // Event listener for when a response is received
153
+ page.on("response", async (response) => {
154
+ try {
155
+ const request = response.request();
156
+ const requestId = request.requestId;
157
+ const receivedTime = Date.now();
158
+ // await handleRequestFinishedOrFailed(request, false);
159
+ // Find the corresponding data object
160
+ const data = networkData.find((item) => item.requestId === requestId);
161
+ if (data) {
162
+ data.status = response.status();
163
+ data.responseReceived = receivedTime;
164
+ saveNetworkData();
123
165
  }
124
- const type = request.resourceType();
125
- /*
126
- domainLookupStart: 80.655,
127
- domainLookupEnd: 80.668,
128
- connectStart: 80.668,
129
- secureConnectionStart: 106.688,
130
- connectEnd: 129.69,
131
- requestStart: 129.81,
132
- responseStart: 187.006,
133
- responseEnd: 188.209
134
- */
135
- data.type = type;
136
- data.domainLookupStart = timing.domainLookupStart;
137
- data.domainLookupEnd = timing.domainLookupEnd;
138
- data.connectStart = timing.connectStart;
139
- data.secureConnectionStart = timing.secureConnectionStart;
140
- data.connectEnd = timing.connectEnd;
141
- data.requestStart = timing.requestStart;
142
- data.responseStart = timing.responseStart;
143
- data.responseEnd = timing.responseEnd;
144
- saveNetworkData();
145
- if (world && world.attach) {
146
- world.attach(JSON.stringify(data), { mediaType: "application/json+network" });
166
+ else {
167
+ // console.error("No data found for request ID", requestId);
147
168
  }
148
169
  }
149
- else {
150
- console.error("No data found for request ID", requestId);
170
+ catch (error) {
171
+ // console.error("Error handling response:", error);
151
172
  }
152
173
  });
153
- // Event listener for when a request fails
154
- page.on("requestfailed", async (request) => {
155
- const requestId = request.requestId;
156
- const endTime = Date.now();
157
- const startTime = requestTimes.get(requestId);
158
- await handleRequestFinishedOrFailed(request, true);
174
+ // Event listener for when a request is finished
175
+ page.on("requestfinished", async (request) => {
159
176
  try {
160
- const res = await request.response();
161
- const statusCode = res ? res.status() : request.failure().errorText;
177
+ const requestId = request.requestId;
178
+ const endTime = Date.now();
179
+ const startTime = requestTimes.get(requestId);
180
+ await handleRequestFinishedOrFailed(request, false, context);
181
+ const response = await request.response();
182
+ const timing = request.timing();
162
183
  // Find the corresponding data object
163
184
  const data = networkData.find((item) => item.requestId === requestId);
164
185
  if (data) {
165
186
  data.responseEnd = endTime;
166
187
  data.responseTime = endTime - startTime;
167
- data.status = statusCode;
168
- data.size = 0;
188
+ // Get response size
189
+ try {
190
+ let size = 0;
191
+ if (responseHasBody(response)) {
192
+ const buf = await response.body();
193
+ size = buf?.length ?? 0;
194
+ }
195
+ data.size = size;
196
+ }
197
+ catch {
198
+ data.size = 0;
199
+ }
200
+ const type = request.resourceType();
201
+ /*
202
+ domainLookupStart: 80.655,
203
+ domainLookupEnd: 80.668,
204
+ connectStart: 80.668,
205
+ secureConnectionStart: 106.688,
206
+ connectEnd: 129.69,
207
+ requestStart: 129.81,
208
+ responseStart: 187.006,
209
+ responseEnd: 188.209
210
+ */
211
+ data.type = type;
212
+ data.domainLookupStart = timing.domainLookupStart;
213
+ data.domainLookupEnd = timing.domainLookupEnd;
214
+ data.connectStart = timing.connectStart;
215
+ data.secureConnectionStart = timing.secureConnectionStart;
216
+ data.connectEnd = timing.connectEnd;
217
+ data.requestStart = timing.requestStart;
218
+ data.responseStart = timing.responseStart;
219
+ data.responseEnd = timing.responseEnd;
169
220
  saveNetworkData();
170
221
  if (world && world.attach) {
171
222
  world.attach(JSON.stringify(data), { mediaType: "application/json+network" });
172
223
  }
173
224
  }
174
225
  else {
175
- console.error("No data found for request ID", requestId);
226
+ // console.error("No data found for request ID", requestId);
227
+ }
228
+ }
229
+ catch (error) {
230
+ // console.error("Error handling request finished:", error);
231
+ }
232
+ });
233
+ // Event listener for when a request fails
234
+ page.on("requestfailed", async (request) => {
235
+ try {
236
+ const requestId = request.requestId;
237
+ const endTime = Date.now();
238
+ const startTime = requestTimes.get(requestId);
239
+ await handleRequestFinishedOrFailed(request, true, context);
240
+ try {
241
+ const res = await request.response();
242
+ const statusCode = res ? res.status() : request.failure().errorText;
243
+ // Find the corresponding data object
244
+ const data = networkData.find((item) => item.requestId === requestId);
245
+ if (data) {
246
+ data.responseEnd = endTime;
247
+ data.responseTime = endTime - startTime;
248
+ data.status = statusCode;
249
+ data.size = 0;
250
+ saveNetworkData();
251
+ if (world && world.attach) {
252
+ world.attach(JSON.stringify(data), { mediaType: "application/json+network" });
253
+ }
254
+ }
255
+ else {
256
+ // console.error("No data found for request ID", requestId);
257
+ }
258
+ }
259
+ catch (error) {
260
+ // ignore
176
261
  }
177
262
  }
178
263
  catch (error) {
179
- // ignore
264
+ // console.error("Error handling request failed:", error);
180
265
  }
181
266
  });
182
267
  }
@@ -185,14 +270,47 @@ function registerNetworkEvents(world, web, context, page) {
185
270
  console.error("No page found to register network events");
186
271
  }
187
272
  }
273
+ async function appendEntryToStepFile(stepHash, entry) {
274
+ if (!stepHash)
275
+ return;
276
+ const debug = createDebug("network:appendEntryToStepFile");
277
+ const file = path.join(detailedNetworkFolder, `${stepHash}.json`);
278
+ debug("appending to step file:", file);
279
+ let data = [];
280
+ try {
281
+ /* read if it already exists */
282
+ const txt = await fs.promises.readFile(file, "utf8");
283
+ data = JSON.parse(txt);
284
+ }
285
+ catch {
286
+ /* ignore – file does not exist or cannot be parsed */
287
+ }
288
+ data.push(entry);
289
+ try {
290
+ debug("writing to step file:", file);
291
+ await fs.promises.writeFile(file, JSON.stringify(data, null, 2), "utf8");
292
+ }
293
+ catch (error) {
294
+ debug("Error writing to step file:", error);
295
+ }
296
+ }
188
297
  const detailedNetworkFolder = path.join(tmpdir(), "blinq_network_events");
298
+ let outOfStep = true;
299
+ let timeoutId = null;
189
300
  const executionState = {
190
301
  currentStepHash: null,
302
+ previousStepHash: null,
191
303
  liveRequestsMap: new Map(),
304
+ liveRequestsMapPrevious: new Map(),
192
305
  };
193
- export function networkBeforeStep(stepName) {
194
- const storeDetailedNetworkData = process.env.STORE_DETAILED_NETWORK_DATA === "true";
195
- if (!storeDetailedNetworkData) {
306
+ const storeDetailedNetworkData = (context) => context && context.STORE_DETAILED_NETWORK_DATA === true;
307
+ export function networkBeforeStep(stepName, context) {
308
+ if (timeoutId) {
309
+ clearTimeout(timeoutId);
310
+ timeoutId = null;
311
+ }
312
+ outOfStep = false;
313
+ if (!storeDetailedNetworkData(context)) {
196
314
  return;
197
315
  }
198
316
  // check if the folder exists, if not create it
@@ -201,131 +319,176 @@ export function networkBeforeStep(stepName) {
201
319
  }
202
320
  // const stepHash = stepNameToHash(stepName);
203
321
  let stepHash = "";
204
- if (process.env.CURRENT_STEP_ID) {
205
- console.log("Using CURRENT_STEP_ID from environment variables:", process.env.CURRENT_STEP_ID);
206
- // If CURRENT_STEP_ID is set, use it as the step hash
207
- stepHash = process.env.CURRENT_STEP_ID;
208
- }
209
- else {
210
- stepHash = stepNameToHash(stepName);
211
- }
322
+ executionState.liveRequestsMapPrevious = executionState.liveRequestsMap;
323
+ executionState.liveRequestsMap = new Map();
324
+ stepHash = stepNameToHash(stepName);
325
+ executionState.previousStepHash = executionState.currentStepHash; // ➊ NEW
326
+ executionState.currentStepHash = stepHash;
212
327
  // check if the file exists, if exists delete it
213
328
  const networkFile = path.join(detailedNetworkFolder, `${stepHash}.json`);
214
- if (fs.existsSync(networkFile)) {
215
- fs.unlinkSync(networkFile);
329
+ try {
330
+ fs.rmSync(path.join(networkFile), { force: true });
331
+ }
332
+ catch (err) {
333
+ // Ignore error if file does not exist
216
334
  }
217
- executionState.currentStepHash = stepHash;
218
335
  }
219
- export function networkAfterStep(stepName) {
220
- const storeDetailedNetworkData = process.env.STORE_DETAILED_NETWORK_DATA === "true";
221
- if (!storeDetailedNetworkData) {
336
+ const saveQueue = new SaveQueue();
337
+ async function saveMap(current) {
338
+ await saveQueue.enqueueSave(current);
339
+ }
340
+ export async function networkAfterStep(stepName, context) {
341
+ if (!storeDetailedNetworkData(context)) {
222
342
  return;
223
343
  }
224
- executionState.currentStepHash = null;
344
+ //await new Promise((r) => setTimeout(r, 1000));
345
+ await saveMap(true);
346
+ /* reset for next step */
347
+ //executionState.previousStepHash = executionState.currentStepHash; // ➋ NEW
348
+ //executionState.liveRequestsMap.clear();
349
+ outOfStep = true;
350
+ // set a timer of 60 seconds to the outOfStep, after that it will be set to false so no network collection will happen
351
+ timeoutId = setTimeout(() => {
352
+ outOfStep = false;
353
+ context.STORE_DETAILED_NETWORK_DATA = false;
354
+ }, 60000);
225
355
  }
226
356
  function stepNameToHash(stepName) {
227
357
  const templateName = _stepNameToTemplate(stepName);
228
358
  // create hash from the template name
229
359
  return crypto.createHash("sha256").update(templateName).digest("hex");
230
360
  }
231
- function handleRequest(request) {
232
- const storeDetailedNetworkData = process.env.STORE_DETAILED_NETWORK_DATA === "true";
233
- if (!storeDetailedNetworkData || !executionState.currentStepHash) {
361
+ function handleRequest(request, context) {
362
+ const debug = createDebug("automation_model:network:handleRequest");
363
+ if (!storeDetailedNetworkData(context))
234
364
  return;
235
- }
236
- const requestId = request.requestId;
237
- const requestData = {
238
- requestId,
365
+ const entry = {
366
+ requestId: request.requestId,
239
367
  url: request.url(),
240
368
  method: request.method(),
241
369
  headers: request.headers(),
242
370
  postData: request.postData(),
243
- timestamp: Date.now(),
371
+ requestTimestamp: Date.now(),
244
372
  stepHash: executionState.currentStepHash,
245
373
  };
246
- executionState.liveRequestsMap.set(request, requestData);
374
+ executionState.liveRequestsMap.set(request, entry);
375
+ debug("Request to", request.url(), "with", request.requestId, "added to current step map at", Date.now());
247
376
  }
248
- function saveNetworkDataToFile(requestData) {
249
- const storeDetailedNetworkData = process.env.STORE_DETAILED_NETWORK_DATA === "true";
250
- if (!storeDetailedNetworkData) {
377
+ async function handleRequestFinishedOrFailed(request, failed, context) {
378
+ const debug = createDebug("automation_model:network:handleRequestFinishedOrFailed");
379
+ if (!storeDetailedNetworkData(context))
251
380
  return;
252
- }
253
- const networkFile = path.join(detailedNetworkFolder, `${requestData.stepHash}.json`);
254
- // read the existing data if it exists (should be an array)
255
- let existingData = [];
256
- if (fs.existsSync(networkFile)) {
257
- const data = fs.readFileSync(networkFile, "utf8");
258
- try {
259
- existingData = JSON.parse(data);
260
- }
261
- catch (e) {
262
- console.error("Failed to parse existing network data:", e);
381
+ const requestId = request.requestId;
382
+ debug("Request id in handleRequestFinishedOrFailed:", requestId, "at", Date.now());
383
+ // const response = await request.response(); // This may be null if the request failed
384
+ let entry = executionState.liveRequestsMap.get(request);
385
+ debug("Request entry found in current map:", entry?.requestId || false);
386
+ if (!entry) {
387
+ // check if the request is in the previous step's map
388
+ entry = executionState.liveRequestsMapPrevious.get(request);
389
+ debug("Request entry found in previous map:", entry?.requestId || false);
390
+ if (!entry) {
391
+ debug("No entry, creating fallback! for url:", request.url());
392
+ entry = {
393
+ requestId: request.requestId,
394
+ url: request.url(),
395
+ method: request.method?.() ?? "GET",
396
+ headers: request.headers?.() ?? {},
397
+ postData: request.postData?.() ?? undefined,
398
+ stepHash: executionState.previousStepHash ?? "unknown",
399
+ requestTimestamp: Date.now(),
400
+ };
263
401
  }
264
402
  }
265
- // Add the live requests to the existing data
266
- existingData.push(requestData);
267
- // Save the updated data back to the file
268
- console.log("Saving network data to file:", networkFile);
269
- fs.writeFileSync(networkFile, JSON.stringify(existingData, null, 2), "utf8");
270
- }
271
- async function handleRequestFinishedOrFailed(request, failed) {
272
- const storeDetailedNetworkData = process.env.STORE_DETAILED_NETWORK_DATA === "true";
273
- if (!storeDetailedNetworkData) {
274
- return;
275
- }
276
- const response = await request.response(); // This may be null if the request failed
277
- const requestData = executionState.liveRequestsMap.get(request);
278
- if (!requestData) {
279
- //console.warn("No request data found for request", request);
280
- return;
281
- }
282
403
  // Remove the request from the live requests map
283
- executionState.liveRequestsMap.delete(request);
284
- if (failed || !response) {
404
+ let respData;
405
+ // executionState.liveRequestsMap.delete(request);
406
+ if (failed) {
285
407
  // Handle failed request
286
- requestData.response = {
408
+ respData = {
287
409
  status: null,
288
410
  headers: {},
289
- timing: null,
290
411
  url: request.url(),
291
412
  timestamp: Date.now(),
292
413
  body: null,
293
414
  contentType: null,
294
415
  error: "Request failed",
295
416
  };
296
- saveNetworkDataToFile(requestData);
297
- return;
298
417
  }
299
- // Handle successful request with a response
300
- const headers = response.headers();
301
- const contentType = headers["content-type"] || "";
302
- let body = null;
303
- try {
304
- if (contentType.includes("application/json")) {
305
- const text = await response.text();
306
- body = JSON.parse(text);
418
+ else {
419
+ const response = await request.response();
420
+ const headers = response?.headers?.() || {};
421
+ let contentType = headers["content-type"] || null;
422
+ let body = null;
423
+ try {
424
+ if (responseHasBody(response)) {
425
+ if (contentType && contentType.includes("application/json")) {
426
+ body = JSON.parse(await response.text());
427
+ }
428
+ else if ((contentType && contentType.includes("text")) ||
429
+ (contentType && contentType.includes("application/csv"))) {
430
+ body = await response.text();
431
+ if (contentType.includes("application/csv"))
432
+ contentType = "text/csv";
433
+ }
434
+ else {
435
+ // If you want binary, you could read it here—but only when responseHasBody(response) is true
436
+ // const buffer = await response.body();
437
+ // body = buffer.toString("base64");
438
+ }
439
+ }
440
+ else {
441
+ // For redirects / no-body statuses, it's useful to keep redirect info
442
+ // e.g., include Location header if present
443
+ // body stays null
444
+ }
307
445
  }
308
- else if (contentType.includes("text")) {
446
+ catch (err) {
447
+ console.error("Error reading response body:", err);
309
448
  body = await response.text();
310
449
  }
311
- else {
312
- // Optionally handle binary here
313
- // const buffer = await response.body();
314
- // body = buffer.toString("base64"); // if you want to store binary safely
450
+ respData = {
451
+ status: response.status(),
452
+ headers,
453
+ url: response.url(),
454
+ timestamp: Date.now(),
455
+ body,
456
+ contentType,
457
+ };
458
+ }
459
+ if (executionState.liveRequestsMap.has(request)) {
460
+ /* “normal” path – keep it in the buffer */
461
+ entry.response = respData;
462
+ if (outOfStep && executionState.currentStepHash) {
463
+ await saveMap(true);
315
464
  }
316
465
  }
317
- catch (err) {
318
- console.error("Error reading response body:", err);
466
+ else {
467
+ if (executionState.liveRequestsMapPrevious.has(request)) {
468
+ entry.response = respData;
469
+ await saveMap(false);
470
+ }
471
+ else {
472
+ /* orphan response – append directly to the previous step file */
473
+ entry.response = respData;
474
+ await appendEntryToStepFile(entry.stepHash, entry); // ➍ NEW
475
+ }
319
476
  }
320
- requestData.response = {
321
- status: response.status(),
322
- headers,
323
- url: response.url(),
324
- timestamp: Date.now(),
325
- body,
326
- contentType,
327
- };
328
- saveNetworkDataToFile(requestData);
477
+ entry.response = respData;
478
+ return;
479
+ }
480
+ function responseHasBody(response) {
481
+ if (!response)
482
+ return false;
483
+ const s = response.status?.() ?? 0;
484
+ // RFC 7231: 1xx, 204, 205, 304 have no body. Playwright: 3xx (redirect) body is unavailable.
485
+ if ((s >= 100 && s < 200) || s === 204 || s === 205 || s === 304 || (s >= 300 && s < 400))
486
+ return false;
487
+ // HEAD responses have no body by definition
488
+ const method = response.request?.().method?.() ?? "GET";
489
+ if (method === "HEAD")
490
+ return false;
491
+ return true;
329
492
  }
330
493
  export { registerNetworkEvents, registerDownloadEvent };
331
494
  //# sourceMappingURL=network.js.map