automation_model 1.0.786-dev → 1.0.786-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.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 +102 -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 +341 -178
- 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 -7
- package/lib/stable_browser.js +427 -81
- 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 -1
- package/lib/test_context.js +1 -1
- 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) {
|
|
@@ -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
|
-
|
|
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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
-
|
|
100
|
-
console.error("
|
|
148
|
+
catch (error) {
|
|
149
|
+
// console.error("Error handling request:", error);
|
|
101
150
|
}
|
|
102
151
|
});
|
|
103
|
-
// Event listener for when a
|
|
104
|
-
page.on("
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
-
|
|
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
|
-
|
|
150
|
-
console.error("
|
|
170
|
+
catch (error) {
|
|
171
|
+
// console.error("Error handling response:", error);
|
|
151
172
|
}
|
|
152
173
|
});
|
|
153
|
-
// Event listener for when a request
|
|
154
|
-
page.on("
|
|
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
|
|
161
|
-
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();
|
|
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
|
-
|
|
168
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
194
|
-
|
|
195
|
-
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)) {
|
|
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
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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
|
-
|
|
215
|
-
fs.
|
|
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
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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
|
-
|
|
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
|
|
233
|
-
if (!storeDetailedNetworkData
|
|
361
|
+
function handleRequest(request, context) {
|
|
362
|
+
const debug = createDebug("automation_model:network:handleRequest");
|
|
363
|
+
if (!storeDetailedNetworkData(context))
|
|
234
364
|
return;
|
|
235
|
-
|
|
236
|
-
|
|
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
|
-
|
|
371
|
+
requestTimestamp: Date.now(),
|
|
244
372
|
stepHash: executionState.currentStepHash,
|
|
245
373
|
};
|
|
246
|
-
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());
|
|
247
376
|
}
|
|
248
|
-
function
|
|
249
|
-
const
|
|
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
|
-
|
|
254
|
-
//
|
|
255
|
-
let
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
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
|
-
|
|
284
|
-
|
|
404
|
+
let respData;
|
|
405
|
+
// executionState.liveRequestsMap.delete(request);
|
|
406
|
+
if (failed) {
|
|
285
407
|
// Handle failed request
|
|
286
|
-
|
|
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
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
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
|
-
|
|
446
|
+
catch (err) {
|
|
447
|
+
console.error("Error reading response body:", err);
|
|
309
448
|
body = await response.text();
|
|
310
449
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
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
|
-
|
|
318
|
-
|
|
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
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
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
|