automation_model 1.0.788-dev → 1.0.788-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/lib/api.js +40 -12
- package/lib/api.js.map +1 -1
- package/lib/auto_page.d.ts +1 -1
- package/lib/auto_page.js +59 -18
- package/lib/auto_page.js.map +1 -1
- package/lib/browser_manager.d.ts +2 -2
- package/lib/browser_manager.js +115 -52
- package/lib/browser_manager.js.map +1 -1
- package/lib/bruno.js.map +1 -1
- package/lib/check_performance.d.ts +1 -0
- package/lib/check_performance.js +57 -0
- package/lib/check_performance.js.map +1 -0
- package/lib/command_common.d.ts +2 -2
- package/lib/command_common.js +26 -25
- package/lib/command_common.js.map +1 -1
- package/lib/file_checker.js +7 -0
- package/lib/file_checker.js.map +1 -1
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/lib/init_browser.d.ts +1 -2
- package/lib/init_browser.js +124 -128
- package/lib/init_browser.js.map +1 -1
- package/lib/locator_log.js.map +1 -1
- package/lib/network.d.ts +2 -2
- package/lib/network.js +287 -199
- package/lib/network.js.map +1 -1
- package/lib/route.d.ts +64 -2
- package/lib/route.js +493 -192
- package/lib/route.js.map +1 -1
- package/lib/scripts/axe.mini.js +23978 -1
- package/lib/snapshot_validation.js +3 -0
- package/lib/snapshot_validation.js.map +1 -1
- package/lib/stable_browser.d.ts +13 -8
- package/lib/stable_browser.js +427 -82
- package/lib/stable_browser.js.map +1 -1
- package/lib/table_helper.js +14 -0
- package/lib/table_helper.js.map +1 -1
- package/lib/test_context.d.ts +1 -0
- package/lib/test_context.js +1 -0
- package/lib/test_context.js.map +1 -1
- package/lib/utils.d.ts +6 -2
- package/lib/utils.js +121 -14
- package/lib/utils.js.map +1 -1
- 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) {
|
|
@@ -50,7 +104,12 @@ function registerNetworkEvents(world, web, context, page) {
|
|
|
50
104
|
const networkFile = _getNetworkFile(world, web, context);
|
|
51
105
|
function saveNetworkData() {
|
|
52
106
|
if (context && context.networkData) {
|
|
53
|
-
|
|
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
|
+
}
|
|
54
113
|
}
|
|
55
114
|
}
|
|
56
115
|
if (!context) {
|
|
@@ -65,109 +124,99 @@ 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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
});
|
|
88
|
-
// Event listener for when a response is received
|
|
89
|
-
page.on("response", async (response) => {
|
|
90
|
-
const request = response.request();
|
|
91
|
-
const requestId = request.requestId;
|
|
92
|
-
const receivedTime = Date.now();
|
|
93
|
-
// await handleRequestFinishedOrFailed(request, false);
|
|
94
|
-
// Find the corresponding data object
|
|
95
|
-
const data = networkData.find((item) => item.requestId === requestId);
|
|
96
|
-
if (data) {
|
|
97
|
-
data.status = response.status();
|
|
98
|
-
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
|
+
});
|
|
99
146
|
saveNetworkData();
|
|
100
147
|
}
|
|
101
|
-
|
|
102
|
-
// console.error("
|
|
148
|
+
catch (error) {
|
|
149
|
+
// console.error("Error handling request:", error);
|
|
103
150
|
}
|
|
104
151
|
});
|
|
105
|
-
// Event listener for when a
|
|
106
|
-
page.on("
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
// Get response size
|
|
119
|
-
try {
|
|
120
|
-
const body = await response.body();
|
|
121
|
-
data.size = body.length;
|
|
122
|
-
}
|
|
123
|
-
catch (e) {
|
|
124
|
-
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();
|
|
125
165
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
domainLookupStart: 80.655,
|
|
129
|
-
domainLookupEnd: 80.668,
|
|
130
|
-
connectStart: 80.668,
|
|
131
|
-
secureConnectionStart: 106.688,
|
|
132
|
-
connectEnd: 129.69,
|
|
133
|
-
requestStart: 129.81,
|
|
134
|
-
responseStart: 187.006,
|
|
135
|
-
responseEnd: 188.209
|
|
136
|
-
*/
|
|
137
|
-
data.type = type;
|
|
138
|
-
data.domainLookupStart = timing.domainLookupStart;
|
|
139
|
-
data.domainLookupEnd = timing.domainLookupEnd;
|
|
140
|
-
data.connectStart = timing.connectStart;
|
|
141
|
-
data.secureConnectionStart = timing.secureConnectionStart;
|
|
142
|
-
data.connectEnd = timing.connectEnd;
|
|
143
|
-
data.requestStart = timing.requestStart;
|
|
144
|
-
data.responseStart = timing.responseStart;
|
|
145
|
-
data.responseEnd = timing.responseEnd;
|
|
146
|
-
saveNetworkData();
|
|
147
|
-
if (world && world.attach) {
|
|
148
|
-
world.attach(JSON.stringify(data), { mediaType: "application/json+network" });
|
|
166
|
+
else {
|
|
167
|
+
// console.error("No data found for request ID", requestId);
|
|
149
168
|
}
|
|
150
169
|
}
|
|
151
|
-
|
|
152
|
-
// console.error("
|
|
170
|
+
catch (error) {
|
|
171
|
+
// console.error("Error handling response:", error);
|
|
153
172
|
}
|
|
154
173
|
});
|
|
155
|
-
// Event listener for when a request
|
|
156
|
-
page.on("
|
|
157
|
-
const requestId = request.requestId;
|
|
158
|
-
const endTime = Date.now();
|
|
159
|
-
const startTime = requestTimes.get(requestId);
|
|
160
|
-
await handleRequestFinishedOrFailed(request, true);
|
|
174
|
+
// Event listener for when a request is finished
|
|
175
|
+
page.on("requestfinished", async (request) => {
|
|
161
176
|
try {
|
|
162
|
-
const
|
|
163
|
-
const
|
|
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();
|
|
164
183
|
// Find the corresponding data object
|
|
165
184
|
const data = networkData.find((item) => item.requestId === requestId);
|
|
166
185
|
if (data) {
|
|
167
186
|
data.responseEnd = endTime;
|
|
168
187
|
data.responseTime = endTime - startTime;
|
|
169
|
-
|
|
170
|
-
|
|
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;
|
|
171
220
|
saveNetworkData();
|
|
172
221
|
if (world && world.attach) {
|
|
173
222
|
world.attach(JSON.stringify(data), { mediaType: "application/json+network" });
|
|
@@ -178,7 +227,41 @@ function registerNetworkEvents(world, web, context, page) {
|
|
|
178
227
|
}
|
|
179
228
|
}
|
|
180
229
|
catch (error) {
|
|
181
|
-
//
|
|
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
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
catch (error) {
|
|
264
|
+
// console.error("Error handling request failed:", error);
|
|
182
265
|
}
|
|
183
266
|
});
|
|
184
267
|
}
|
|
@@ -188,7 +271,11 @@ function registerNetworkEvents(world, web, context, page) {
|
|
|
188
271
|
}
|
|
189
272
|
}
|
|
190
273
|
async function appendEntryToStepFile(stepHash, entry) {
|
|
274
|
+
if (!stepHash)
|
|
275
|
+
return;
|
|
276
|
+
const debug = createDebug("network:appendEntryToStepFile");
|
|
191
277
|
const file = path.join(detailedNetworkFolder, `${stepHash}.json`);
|
|
278
|
+
debug("appending to step file:", file);
|
|
192
279
|
let data = [];
|
|
193
280
|
try {
|
|
194
281
|
/* read if it already exists */
|
|
@@ -199,16 +286,31 @@ async function appendEntryToStepFile(stepHash, entry) {
|
|
|
199
286
|
/* ignore – file does not exist or cannot be parsed */
|
|
200
287
|
}
|
|
201
288
|
data.push(entry);
|
|
202
|
-
|
|
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
|
+
}
|
|
203
296
|
}
|
|
204
297
|
const detailedNetworkFolder = path.join(tmpdir(), "blinq_network_events");
|
|
298
|
+
let outOfStep = true;
|
|
299
|
+
let timeoutId = null;
|
|
205
300
|
const executionState = {
|
|
206
301
|
currentStepHash: null,
|
|
302
|
+
previousStepHash: null,
|
|
207
303
|
liveRequestsMap: new Map(),
|
|
304
|
+
liveRequestsMapPrevious: new Map(),
|
|
208
305
|
};
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
if (
|
|
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)) {
|
|
212
314
|
return;
|
|
213
315
|
}
|
|
214
316
|
// check if the folder exists, if not create it
|
|
@@ -217,15 +319,10 @@ export function networkBeforeStep(stepName) {
|
|
|
217
319
|
}
|
|
218
320
|
// const stepHash = stepNameToHash(stepName);
|
|
219
321
|
let stepHash = "";
|
|
220
|
-
executionState.liveRequestsMap
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
stepHash = process.env.CURRENT_STEP_ID;
|
|
225
|
-
}
|
|
226
|
-
else {
|
|
227
|
-
stepHash = stepNameToHash(stepName);
|
|
228
|
-
}
|
|
322
|
+
executionState.liveRequestsMapPrevious = executionState.liveRequestsMap;
|
|
323
|
+
executionState.liveRequestsMap = new Map();
|
|
324
|
+
stepHash = stepNameToHash(stepName);
|
|
325
|
+
executionState.previousStepHash = executionState.currentStepHash; // ➊ NEW
|
|
229
326
|
executionState.currentStepHash = stepHash;
|
|
230
327
|
// check if the file exists, if exists delete it
|
|
231
328
|
const networkFile = path.join(detailedNetworkFolder, `${stepHash}.json`);
|
|
@@ -236,30 +333,35 @@ export function networkBeforeStep(stepName) {
|
|
|
236
333
|
// Ignore error if file does not exist
|
|
237
334
|
}
|
|
238
335
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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)) {
|
|
242
342
|
return;
|
|
243
343
|
}
|
|
244
|
-
await new Promise((r) => setTimeout(r, 1000));
|
|
245
|
-
|
|
246
|
-
const file = path.join(detailedNetworkFolder, `${executionState.currentStepHash}.json`);
|
|
247
|
-
await fs.promises.writeFile(file, JSON.stringify(entries, null, 2), "utf8");
|
|
344
|
+
//await new Promise((r) => setTimeout(r, 1000));
|
|
345
|
+
await saveMap(true);
|
|
248
346
|
/* reset for next step */
|
|
249
|
-
executionState.
|
|
250
|
-
executionState.
|
|
251
|
-
|
|
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);
|
|
252
355
|
}
|
|
253
356
|
function stepNameToHash(stepName) {
|
|
254
357
|
const templateName = _stepNameToTemplate(stepName);
|
|
255
358
|
// create hash from the template name
|
|
256
359
|
return crypto.createHash("sha256").update(templateName).digest("hex");
|
|
257
360
|
}
|
|
258
|
-
function handleRequest(request) {
|
|
259
|
-
const
|
|
260
|
-
if (!storeDetailedNetworkData
|
|
361
|
+
function handleRequest(request, context) {
|
|
362
|
+
const debug = createDebug("automation_model:network:handleRequest");
|
|
363
|
+
if (!storeDetailedNetworkData(context))
|
|
261
364
|
return;
|
|
262
|
-
}
|
|
263
365
|
const entry = {
|
|
264
366
|
requestId: request.requestId,
|
|
265
367
|
url: request.url(),
|
|
@@ -270,48 +372,33 @@ function handleRequest(request) {
|
|
|
270
372
|
stepHash: executionState.currentStepHash,
|
|
271
373
|
};
|
|
272
374
|
executionState.liveRequestsMap.set(request, entry);
|
|
273
|
-
|
|
375
|
+
debug("Request to", request.url(), "with", request.requestId, "added to current step map at", Date.now());
|
|
274
376
|
}
|
|
275
|
-
function
|
|
276
|
-
const
|
|
277
|
-
if (!storeDetailedNetworkData)
|
|
377
|
+
async function handleRequestFinishedOrFailed(request, failed, context) {
|
|
378
|
+
const debug = createDebug("automation_model:network:handleRequestFinishedOrFailed");
|
|
379
|
+
if (!storeDetailedNetworkData(context))
|
|
278
380
|
return;
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
// read the existing data if it exists (should be an array)
|
|
282
|
-
let existingData = [];
|
|
283
|
-
if (fs.existsSync(networkFile)) {
|
|
284
|
-
const data = fs.readFileSync(networkFile, "utf8");
|
|
285
|
-
try {
|
|
286
|
-
existingData = JSON.parse(data);
|
|
287
|
-
}
|
|
288
|
-
catch (e) {
|
|
289
|
-
// console.error("Failed to parse existing network data:", e);
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
// Add the live requests to the existing data
|
|
293
|
-
existingData.push(requestData);
|
|
294
|
-
// Save the updated data back to the file
|
|
295
|
-
// console.log("Saving network data to file:", networkFile);
|
|
296
|
-
fs.writeFileSync(networkFile, JSON.stringify(existingData, null, 2), "utf8");
|
|
297
|
-
}
|
|
298
|
-
async function handleRequestFinishedOrFailed(request, failed) {
|
|
299
|
-
const storeDetailedNetworkData = process.env.STORE_DETAILED_NETWORK_DATA === "true";
|
|
300
|
-
if (!storeDetailedNetworkData) {
|
|
301
|
-
return;
|
|
302
|
-
}
|
|
381
|
+
const requestId = request.requestId;
|
|
382
|
+
debug("Request id in handleRequestFinishedOrFailed:", requestId, "at", Date.now());
|
|
303
383
|
// const response = await request.response(); // This may be null if the request failed
|
|
304
384
|
let entry = executionState.liveRequestsMap.get(request);
|
|
385
|
+
debug("Request entry found in current map:", entry?.requestId || false);
|
|
305
386
|
if (!entry) {
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
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
|
+
};
|
|
401
|
+
}
|
|
315
402
|
}
|
|
316
403
|
// Remove the request from the live requests map
|
|
317
404
|
let respData;
|
|
@@ -330,28 +417,35 @@ async function handleRequestFinishedOrFailed(request, failed) {
|
|
|
330
417
|
}
|
|
331
418
|
else {
|
|
332
419
|
const response = await request.response();
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
const contentType = headers["content-type"] || null;
|
|
420
|
+
const headers = response?.headers?.() || {};
|
|
421
|
+
let contentType = headers["content-type"] || null;
|
|
336
422
|
let body = null;
|
|
337
423
|
try {
|
|
338
|
-
if (
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
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
|
+
}
|
|
346
439
|
}
|
|
347
440
|
else {
|
|
348
|
-
//
|
|
349
|
-
//
|
|
350
|
-
// body
|
|
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
|
|
351
444
|
}
|
|
352
445
|
}
|
|
353
446
|
catch (err) {
|
|
354
447
|
console.error("Error reading response body:", err);
|
|
448
|
+
body = await response.text();
|
|
355
449
|
}
|
|
356
450
|
respData = {
|
|
357
451
|
status: response.status(),
|
|
@@ -365,42 +459,36 @@ async function handleRequestFinishedOrFailed(request, failed) {
|
|
|
365
459
|
if (executionState.liveRequestsMap.has(request)) {
|
|
366
460
|
/* “normal” path – keep it in the buffer */
|
|
367
461
|
entry.response = respData;
|
|
462
|
+
if (outOfStep && executionState.currentStepHash) {
|
|
463
|
+
await saveMap(true);
|
|
464
|
+
}
|
|
368
465
|
}
|
|
369
466
|
else {
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
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
|
+
}
|
|
373
476
|
}
|
|
374
477
|
entry.response = respData;
|
|
375
478
|
return;
|
|
376
479
|
}
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
//
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
//
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
// // body = buffer.toString("base64"); // if you want to store binary safely
|
|
391
|
-
// }
|
|
392
|
-
// } catch (err) {
|
|
393
|
-
// console.error("Error reading response body:", err);
|
|
394
|
-
// }
|
|
395
|
-
// requestData.response = {
|
|
396
|
-
// status: response.status(),
|
|
397
|
-
// headers,
|
|
398
|
-
// url: response.url(),
|
|
399
|
-
// timestamp: Date.now(),
|
|
400
|
-
// body,
|
|
401
|
-
// contentType,
|
|
402
|
-
// };
|
|
403
|
-
// saveNetworkDataToFile(requestData);
|
|
404
|
-
// }
|
|
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;
|
|
492
|
+
}
|
|
405
493
|
export { registerNetworkEvents, registerDownloadEvent };
|
|
406
494
|
//# sourceMappingURL=network.js.map
|