automation_model 1.0.773-dev → 1.0.773-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 +28 -11
- package/lib/api.js.map +1 -1
- package/lib/auto_page.js +60 -24
- package/lib/auto_page.js.map +1 -1
- package/lib/browser_manager.js +56 -34
- 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 +1 -1
- package/lib/command_common.js +30 -16
- package/lib/command_common.js.map +1 -1
- package/lib/file_checker.js +136 -25
- 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 +122 -126
- 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 +346 -168
- package/lib/network.js.map +1 -1
- package/lib/route.d.ts +64 -15
- package/lib/route.js +529 -188
- package/lib/route.js.map +1 -1
- package/lib/scripts/axe.mini.js +23994 -1
- package/lib/snapshot_validation.js.map +1 -1
- package/lib/stable_browser.d.ts +13 -7
- package/lib/stable_browser.js +421 -95
- 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 +17 -10
package/lib/network.js
CHANGED
|
@@ -2,6 +2,61 @@ import path from "path";
|
|
|
2
2
|
import fs from "fs";
|
|
3
3
|
import { _stepNameToTemplate } from "./route.js";
|
|
4
4
|
import crypto from "crypto";
|
|
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
|
+
}
|
|
5
60
|
function _getNetworkFile(world = null, web = null, context = null) {
|
|
6
61
|
let networkFile = null;
|
|
7
62
|
if (world && world.reportFolder) {
|
|
@@ -45,17 +100,22 @@ function registerDownloadEvent(page, world, context) {
|
|
|
45
100
|
}
|
|
46
101
|
}
|
|
47
102
|
function registerNetworkEvents(world, web, context, page) {
|
|
103
|
+
// Map to hold request start times and IDs
|
|
48
104
|
const networkFile = _getNetworkFile(world, web, context);
|
|
49
105
|
function saveNetworkData() {
|
|
50
106
|
if (context && context.networkData) {
|
|
51
|
-
|
|
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
|
+
}
|
|
52
113
|
}
|
|
53
114
|
}
|
|
54
115
|
if (!context) {
|
|
55
116
|
console.error("No context found to register network events");
|
|
56
117
|
return;
|
|
57
118
|
}
|
|
58
|
-
// Map to hold request start times and IDs
|
|
59
119
|
const requestTimes = new Map();
|
|
60
120
|
let requestIdCounter = 0;
|
|
61
121
|
if (page) {
|
|
@@ -64,118 +124,144 @@ function registerNetworkEvents(world, web, context, page) {
|
|
|
64
124
|
const networkData = context.networkData;
|
|
65
125
|
// Event listener for when a request is made
|
|
66
126
|
page.on("request", (request) => {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
// Event listener for when a response is received
|
|
87
|
-
page.on("response", async (response) => {
|
|
88
|
-
const request = response.request();
|
|
89
|
-
const requestId = request.requestId;
|
|
90
|
-
const receivedTime = Date.now();
|
|
91
|
-
// Find the corresponding data object
|
|
92
|
-
const data = networkData.find((item) => item.requestId === requestId);
|
|
93
|
-
if (data) {
|
|
94
|
-
data.status = response.status();
|
|
95
|
-
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
|
+
});
|
|
96
146
|
saveNetworkData();
|
|
97
147
|
}
|
|
98
|
-
|
|
99
|
-
console.error("
|
|
148
|
+
catch (error) {
|
|
149
|
+
// console.error("Error handling request:", error);
|
|
100
150
|
}
|
|
101
151
|
});
|
|
102
|
-
// Event listener for when a
|
|
103
|
-
page.on("
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
// Get response size
|
|
116
|
-
try {
|
|
117
|
-
const body = await response.body();
|
|
118
|
-
data.size = body.length;
|
|
119
|
-
}
|
|
120
|
-
catch (e) {
|
|
121
|
-
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();
|
|
122
165
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
domainLookupStart: 80.655,
|
|
126
|
-
domainLookupEnd: 80.668,
|
|
127
|
-
connectStart: 80.668,
|
|
128
|
-
secureConnectionStart: 106.688,
|
|
129
|
-
connectEnd: 129.69,
|
|
130
|
-
requestStart: 129.81,
|
|
131
|
-
responseStart: 187.006,
|
|
132
|
-
responseEnd: 188.209
|
|
133
|
-
*/
|
|
134
|
-
data.type = type;
|
|
135
|
-
data.domainLookupStart = timing.domainLookupStart;
|
|
136
|
-
data.domainLookupEnd = timing.domainLookupEnd;
|
|
137
|
-
data.connectStart = timing.connectStart;
|
|
138
|
-
data.secureConnectionStart = timing.secureConnectionStart;
|
|
139
|
-
data.connectEnd = timing.connectEnd;
|
|
140
|
-
data.requestStart = timing.requestStart;
|
|
141
|
-
data.responseStart = timing.responseStart;
|
|
142
|
-
data.responseEnd = timing.responseEnd;
|
|
143
|
-
saveNetworkData();
|
|
144
|
-
if (world && world.attach) {
|
|
145
|
-
world.attach(JSON.stringify(data), { mediaType: "application/json+network" });
|
|
166
|
+
else {
|
|
167
|
+
// console.error("No data found for request ID", requestId);
|
|
146
168
|
}
|
|
147
169
|
}
|
|
148
|
-
|
|
149
|
-
console.error("
|
|
170
|
+
catch (error) {
|
|
171
|
+
// console.error("Error handling response:", error);
|
|
150
172
|
}
|
|
151
173
|
});
|
|
152
|
-
// Event listener for when a request
|
|
153
|
-
page.on("
|
|
154
|
-
const requestId = request.requestId;
|
|
155
|
-
const endTime = Date.now();
|
|
156
|
-
const startTime = requestTimes.get(requestId);
|
|
157
|
-
await handleRequestFinishedOrFailed(request, true);
|
|
174
|
+
// Event listener for when a request is finished
|
|
175
|
+
page.on("requestfinished", async (request) => {
|
|
158
176
|
try {
|
|
159
|
-
const
|
|
160
|
-
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();
|
|
161
183
|
// Find the corresponding data object
|
|
162
184
|
const data = networkData.find((item) => item.requestId === requestId);
|
|
163
185
|
if (data) {
|
|
164
186
|
data.responseEnd = endTime;
|
|
165
187
|
data.responseTime = endTime - startTime;
|
|
166
|
-
|
|
167
|
-
|
|
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;
|
|
168
220
|
saveNetworkData();
|
|
169
221
|
if (world && world.attach) {
|
|
170
222
|
world.attach(JSON.stringify(data), { mediaType: "application/json+network" });
|
|
171
223
|
}
|
|
172
224
|
}
|
|
173
225
|
else {
|
|
174
|
-
console.error("No data found for request ID", requestId);
|
|
226
|
+
// console.error("No data found for request ID", requestId);
|
|
175
227
|
}
|
|
176
228
|
}
|
|
177
229
|
catch (error) {
|
|
178
|
-
//
|
|
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);
|
|
179
265
|
}
|
|
180
266
|
});
|
|
181
267
|
}
|
|
@@ -184,133 +270,225 @@ function registerNetworkEvents(world, web, context, page) {
|
|
|
184
270
|
console.error("No page found to register network events");
|
|
185
271
|
}
|
|
186
272
|
}
|
|
187
|
-
|
|
188
|
-
|
|
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
|
+
}
|
|
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
|
-
|
|
194
|
-
|
|
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)) {
|
|
195
314
|
return;
|
|
196
315
|
}
|
|
197
316
|
// check if the folder exists, if not create it
|
|
198
317
|
if (!fs.existsSync(detailedNetworkFolder)) {
|
|
199
318
|
fs.mkdirSync(detailedNetworkFolder, { recursive: true });
|
|
200
319
|
}
|
|
201
|
-
const stepHash = stepNameToHash(stepName);
|
|
320
|
+
// const stepHash = stepNameToHash(stepName);
|
|
321
|
+
let stepHash = "";
|
|
322
|
+
executionState.liveRequestsMapPrevious = executionState.liveRequestsMap;
|
|
323
|
+
executionState.liveRequestsMap = new Map();
|
|
324
|
+
stepHash = stepNameToHash(stepName);
|
|
325
|
+
executionState.previousStepHash = executionState.currentStepHash; // ➊ NEW
|
|
326
|
+
executionState.currentStepHash = stepHash;
|
|
202
327
|
// check if the file exists, if exists delete it
|
|
203
328
|
const networkFile = path.join(detailedNetworkFolder, `${stepHash}.json`);
|
|
204
|
-
|
|
205
|
-
fs.
|
|
329
|
+
try {
|
|
330
|
+
fs.rmSync(path.join(networkFile), { force: true });
|
|
206
331
|
}
|
|
207
|
-
|
|
332
|
+
catch (err) {
|
|
333
|
+
// Ignore error if file does not exist
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
const saveQueue = new SaveQueue();
|
|
337
|
+
async function saveMap(current) {
|
|
338
|
+
await saveQueue.enqueueSave(current);
|
|
208
339
|
}
|
|
209
|
-
export function networkAfterStep(stepName) {
|
|
210
|
-
if (!storeDetailedNetworkData) {
|
|
340
|
+
export async function networkAfterStep(stepName, context) {
|
|
341
|
+
if (!storeDetailedNetworkData(context)) {
|
|
211
342
|
return;
|
|
212
343
|
}
|
|
213
|
-
|
|
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);
|
|
214
355
|
}
|
|
215
356
|
function stepNameToHash(stepName) {
|
|
216
357
|
const templateName = _stepNameToTemplate(stepName);
|
|
217
358
|
// create hash from the template name
|
|
218
|
-
return crypto.createHash("sha256").update(
|
|
359
|
+
return crypto.createHash("sha256").update(templateName).digest("hex");
|
|
219
360
|
}
|
|
220
|
-
function handleRequest(request) {
|
|
221
|
-
|
|
361
|
+
function handleRequest(request, context) {
|
|
362
|
+
const debug = createDebug("automation_model:network:handleRequest");
|
|
363
|
+
if (!storeDetailedNetworkData(context))
|
|
222
364
|
return;
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
const requestData = {
|
|
226
|
-
requestId,
|
|
365
|
+
const entry = {
|
|
366
|
+
requestId: request.requestId,
|
|
227
367
|
url: request.url(),
|
|
228
368
|
method: request.method(),
|
|
229
369
|
headers: request.headers(),
|
|
230
370
|
postData: request.postData(),
|
|
231
|
-
|
|
371
|
+
requestTimestamp: Date.now(),
|
|
232
372
|
stepHash: executionState.currentStepHash,
|
|
233
373
|
};
|
|
234
|
-
executionState.liveRequestsMap.set(request,
|
|
374
|
+
executionState.liveRequestsMap.set(request, entry);
|
|
375
|
+
debug("Request to", request.url(), "with", request.requestId, "added to current step map at", Date.now());
|
|
235
376
|
}
|
|
236
|
-
function
|
|
237
|
-
|
|
377
|
+
async function handleRequestFinishedOrFailed(request, failed, context) {
|
|
378
|
+
const debug = createDebug("automation_model:network:handleRequestFinishedOrFailed");
|
|
379
|
+
if (!storeDetailedNetworkData(context))
|
|
238
380
|
return;
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
//
|
|
242
|
-
let
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
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
|
+
};
|
|
250
401
|
}
|
|
251
402
|
}
|
|
252
|
-
// Add the live requests to the existing data
|
|
253
|
-
existingData.push(requestData);
|
|
254
|
-
// Save the updated data back to the file
|
|
255
|
-
fs.writeFileSync(networkFile, JSON.stringify(existingData, null, 2), "utf8");
|
|
256
|
-
}
|
|
257
|
-
async function handleRequestFinishedOrFailed(request, failed) {
|
|
258
|
-
if (!storeDetailedNetworkData) {
|
|
259
|
-
return;
|
|
260
|
-
}
|
|
261
|
-
const response = await request.response(); // This may be null if the request failed
|
|
262
|
-
const requestData = executionState.liveRequestsMap.get(request);
|
|
263
|
-
if (!requestData) {
|
|
264
|
-
//console.warn("No request data found for request", request);
|
|
265
|
-
return;
|
|
266
|
-
}
|
|
267
403
|
// Remove the request from the live requests map
|
|
268
|
-
|
|
269
|
-
|
|
404
|
+
let respData;
|
|
405
|
+
// executionState.liveRequestsMap.delete(request);
|
|
406
|
+
if (failed) {
|
|
270
407
|
// Handle failed request
|
|
271
|
-
|
|
408
|
+
respData = {
|
|
272
409
|
status: null,
|
|
273
410
|
headers: {},
|
|
274
|
-
timing: null,
|
|
275
411
|
url: request.url(),
|
|
276
412
|
timestamp: Date.now(),
|
|
277
413
|
body: null,
|
|
278
414
|
contentType: null,
|
|
279
415
|
error: "Request failed",
|
|
280
416
|
};
|
|
281
|
-
saveNetworkDataToFile(requestData);
|
|
282
|
-
return;
|
|
283
417
|
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
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
|
+
}
|
|
292
445
|
}
|
|
293
|
-
|
|
446
|
+
catch (err) {
|
|
447
|
+
console.error("Error reading response body:", err);
|
|
294
448
|
body = await response.text();
|
|
295
449
|
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
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);
|
|
300
464
|
}
|
|
301
465
|
}
|
|
302
|
-
|
|
303
|
-
|
|
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
|
+
}
|
|
304
476
|
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
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;
|
|
314
492
|
}
|
|
315
493
|
export { registerNetworkEvents, registerDownloadEvent };
|
|
316
494
|
//# sourceMappingURL=network.js.map
|