chrometools-mcp 3.2.10 → 3.3.8

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.
@@ -4,51 +4,74 @@
4
4
  */
5
5
 
6
6
  import { consoleLogs, networkRequests } from '../browser/page-manager.js';
7
+ import { getNetworkRequestsFromBridge, isBridgeConnected } from '../bridge/bridge-client.js';
7
8
 
8
9
  /**
9
- * Wait for pending network requests to complete
10
+ * Wait for network requests to complete
11
+ * Tracks all requests (GET, POST, PUT, PATCH, DELETE) that started within detection window
10
12
  * @param {number} beforeActionTimestamp - Timestamp before action to track new requests
11
- * @param {number} initialWaitMs - Initial wait time before checking (default: 500ms)
12
- * @param {number} maxWaitMs - Maximum time to wait for requests (default: 5000ms)
13
+ * @param {number} detectionWindowMs - Time window to detect requests (default: 200ms)
14
+ * @param {number} maxWaitMs - Maximum time to wait for requests (default: 10000ms)
13
15
  * @returns {Promise<{pendingFound: boolean, waitedMs: number, completedRequests: number, totalRequests: number}>}
14
16
  */
15
- export async function waitForPendingRequests(beforeActionTimestamp, initialWaitMs = 500, maxWaitMs = 5000) {
17
+ export async function waitForPendingRequests(beforeActionTimestamp, detectionWindowMs = 200, maxWaitMs = 10000) {
16
18
  const startTime = Date.now();
17
19
 
18
- // Step 1: Wait initial period to let requests start
19
- await new Promise(resolve => setTimeout(resolve, initialWaitMs));
20
+ // Step 1: Wait for detection window to let requests start
21
+ await new Promise(resolve => setTimeout(resolve, detectionWindowMs));
20
22
 
21
- // Step 2: Get requests that started AFTER action
22
- const getPostActionRequests = () => {
23
- const cutoffDate = new Date(beforeActionTimestamp).toISOString();
24
- return networkRequests.filter(req => req.timestamp >= cutoffDate);
25
- };
23
+ // Step 2: Find all requests (GET, POST, PUT, PATCH, DELETE) that started within detection window
24
+ const cutoffStart = new Date(beforeActionTimestamp).toISOString();
25
+ const cutoffEnd = new Date(beforeActionTimestamp + detectionWindowMs).toISOString();
26
+
27
+ const trackedRequests = networkRequests.filter(req => {
28
+ // Track all HTTP methods
29
+ if (!['GET', 'POST', 'PUT', 'PATCH', 'DELETE'].includes(req.method)) {
30
+ return false;
31
+ }
32
+ // Only requests in detection window [T0, T0+200ms]
33
+ return req.timestamp >= cutoffStart && req.timestamp <= cutoffEnd;
34
+ });
35
+
36
+ // If no requests found, return immediately
37
+ if (trackedRequests.length === 0) {
38
+ return {
39
+ pendingFound: false,
40
+ waitedMs: Date.now() - startTime,
41
+ completedRequests: 0,
42
+ stillPending: 0,
43
+ pendingRequests: [],
44
+ totalRequests: 0,
45
+ trackedRequests: []
46
+ };
47
+ }
48
+
49
+ // Step 3: Wait for these specific requests to complete
50
+ const trackedRequestIds = new Set(trackedRequests.map(req => req.requestId));
26
51
 
27
- // Step 3: Check for pending requests (from post-action requests)
28
52
  const checkPending = () => {
29
- return getPostActionRequests().filter(req => req.status === 'pending');
53
+ return networkRequests.filter(req =>
54
+ trackedRequestIds.has(req.requestId) && req.status === 'pending'
55
+ );
30
56
  };
31
57
 
32
58
  let pending = checkPending();
33
- let allPostActionRequests = getPostActionRequests();
34
59
  const initialPendingCount = pending.length;
35
60
 
36
- // Step 4: If there are pending requests OR new requests appeared, wait for completion
37
- if (pending.length > 0 || allPostActionRequests.length > 0) {
38
- // Wait for pending requests to complete (with timeout)
39
- while (pending.length > 0 && (Date.now() - startTime) < maxWaitMs) {
40
- await new Promise(resolve => setTimeout(resolve, 100)); // Check every 100ms
41
- pending = checkPending();
42
- allPostActionRequests = getPostActionRequests(); // Update total count
43
- }
61
+ // Wait for requests to complete (with configurable timeout)
62
+ while (pending.length > 0 && (Date.now() - startTime) < maxWaitMs) {
63
+ await new Promise(resolve => setTimeout(resolve, 100)); // Check every 100ms
64
+ pending = checkPending();
44
65
  }
45
66
 
46
- const finalRequests = getPostActionRequests();
67
+ // Collect final results for tracked requests
68
+ const finalRequests = networkRequests.filter(req => trackedRequestIds.has(req.requestId));
47
69
  const completedRequests = finalRequests.filter(req => req.status === 'completed' || (typeof req.status === 'number'));
48
- const pendingRequests = pending.map(req => ({
70
+ const stillPendingRequests = pending.map(req => ({
49
71
  url: req.url,
50
72
  method: req.method,
51
- timestamp: req.timestamp
73
+ timestamp: req.timestamp,
74
+ status: 'pending' // Still waiting after timeout
52
75
  }));
53
76
 
54
77
  return {
@@ -56,8 +79,14 @@ export async function waitForPendingRequests(beforeActionTimestamp, initialWaitM
56
79
  waitedMs: Date.now() - startTime,
57
80
  completedRequests: completedRequests.length,
58
81
  stillPending: pending.length,
59
- pendingRequests: pendingRequests,
60
- totalRequests: finalRequests.length
82
+ pendingRequests: stillPendingRequests,
83
+ totalRequests: finalRequests.length,
84
+ trackedRequests: finalRequests.map(req => ({
85
+ method: req.method,
86
+ url: req.url,
87
+ status: req.status,
88
+ statusText: req.statusText
89
+ }))
61
90
  };
62
91
  }
63
92
 
@@ -133,19 +162,54 @@ export function collectErrors(sinceTimestamp = null, maxConsoleErrors = 15, maxN
133
162
  * Full post-action diagnostics: wait for requests and collect errors
134
163
  * @param {Page} page - Puppeteer page instance
135
164
  * @param {number} beforeActionTimestamp - Timestamp before action (to filter errors)
165
+ * @param {Object} options - Options for diagnostics
166
+ * @param {boolean} options.skipNetworkWait - Skip waiting for network requests (default: false)
167
+ * @param {number} options.networkWaitTimeout - Custom timeout for network wait in ms (default: 10000)
168
+ * @param {string} options.urlBeforeAction - URL before action (to detect navigation/form submit)
136
169
  * @returns {Promise<Object>} Diagnostics result with errors and network info
137
170
  */
138
- export async function runPostClickDiagnostics(page, beforeActionTimestamp) {
139
- // Wait for network requests (passing timestamp to track post-action requests)
140
- // maxWait = 20s to give slow APIs time to complete
141
- const networkInfo = await waitForPendingRequests(beforeActionTimestamp, 500, 20000);
171
+ export async function runPostClickDiagnostics(page, beforeActionTimestamp, options = {}) {
172
+ const { skipNetworkWait = false, networkWaitTimeout = 10000, urlBeforeAction = null } = options;
173
+
174
+ // Wait for network requests (all methods within 200ms detection window)
175
+ // Default maxWait = 10s for click, configurable via networkWaitTimeout parameter
176
+ const networkInfo = skipNetworkWait
177
+ ? { pendingFound: false, waitedMs: 0, completedRequests: 0, stillPending: 0, pendingRequests: [], totalRequests: 0, trackedRequests: [], allRecentRequests: [] }
178
+ : await waitForPendingRequests(beforeActionTimestamp, 200, networkWaitTimeout);
142
179
 
143
180
  // Small delay to let pending requests update their error status
144
181
  // (handles case where request completes with error right after maxWait expires)
145
182
  await new Promise(resolve => setTimeout(resolve, 100));
146
183
 
184
+ // Check for page navigation (indicates form submit in non-SPA apps)
185
+ const currentUrl = page.url();
186
+ let navigationDetected = null;
187
+ if (urlBeforeAction && currentUrl !== urlBeforeAction) {
188
+ navigationDetected = {
189
+ from: urlBeforeAction,
190
+ to: currentUrl,
191
+ likelyFormSubmit: true // Page URL changed after click - likely form POST with redirect
192
+ };
193
+ }
194
+
195
+ // Fetch network requests from Bridge (Extension webRequest API)
196
+ // These persist across page navigations, unlike CDP requests
197
+ let bridgeRequests = [];
198
+ if (isBridgeConnected()) {
199
+ try {
200
+ const allBridgeRequests = await getNetworkRequestsFromBridge({ timeout: 2000 });
201
+ // Filter to requests after beforeActionTimestamp
202
+ const cutoffTime = beforeActionTimestamp - 1000; // 1s buffer
203
+ bridgeRequests = allBridgeRequests.filter(req =>
204
+ req.timestamp >= cutoffTime
205
+ );
206
+ } catch (e) {
207
+ // Bridge not available, continue without
208
+ }
209
+ }
210
+
147
211
  // Check for chrome error page (ERR_CONNECTION_REFUSED, etc.)
148
- const url = page.url();
212
+ const url = currentUrl;
149
213
  let chromeErrorInfo = null;
150
214
  if (url.startsWith('chrome-error://')) {
151
215
  chromeErrorInfo = await page.evaluate(() => {
@@ -169,8 +233,19 @@ export async function runPostClickDiagnostics(page, beforeActionTimestamp) {
169
233
  stillPending: networkInfo.stillPending,
170
234
  pendingRequests: networkInfo.pendingRequests,
171
235
  totalRequests: networkInfo.totalRequests,
172
- waitedMs: networkInfo.waitedMs
236
+ waitedMs: networkInfo.waitedMs,
237
+ trackedRequests: networkInfo.trackedRequests || [],
238
+ allRecentRequests: networkInfo.allRecentRequests || [],
239
+ // Bridge requests (from Extension webRequest API) - persist across page navigations
240
+ bridgeRequests: bridgeRequests.map(req => ({
241
+ method: req.method,
242
+ url: req.url,
243
+ type: req.type,
244
+ status: req.status,
245
+ timestamp: req.timestamp
246
+ }))
173
247
  },
248
+ navigation: navigationDetected,
174
249
  chromeError: chromeErrorInfo,
175
250
  errors: {
176
251
  consoleErrors: errors.consoleErrors,
@@ -201,25 +276,49 @@ export function formatDiagnosticsForAI(diagnostics) {
201
276
  output += `\n → Backend likely not running or unreachable`;
202
277
  }
203
278
 
204
- // Network activity
279
+ // Page navigation detection (form submit in non-SPA apps)
280
+ if (diagnostics.navigation) {
281
+ output += `\n\nšŸ”„ Page navigation detected (form submit):`;
282
+ output += `\n From: ${diagnostics.navigation.from}`;
283
+ output += `\n To: ${diagnostics.navigation.to}`;
284
+ output += `\n → This indicates a successful form POST with page reload`;
285
+ }
286
+
287
+ // Network activity - show all tracked requests (GET/POST/PUT/PATCH/DELETE)
205
288
  const netActivity = diagnostics.networkActivity;
206
- if (netActivity.totalRequests > 0) {
207
- const errorCount = diagnostics.errors.networkErrors.length;
208
- const successCount = netActivity.completedRequests - errorCount;
289
+ const trackedRequests = netActivity.trackedRequests || [];
209
290
 
210
- // Show warning if there are pending requests after timeout
291
+ // Show requests detected within 200ms after action
292
+ if (trackedRequests.length > 0) {
293
+ output += `\n\nšŸ“” Network requests (${trackedRequests.length}):`;
294
+ trackedRequests.forEach((req, idx) => {
295
+ const statusIcon = req.status === 'pending' ? 'ā³' :
296
+ (req.status === 'completed' || (typeof req.status === 'number' && req.status < 400) ? 'āœ“' : 'āœ—');
297
+ const statusText = req.statusText || req.status || 'pending';
298
+ output += `\n ${idx + 1}. ${statusIcon} ${req.method} ${req.url}`;
299
+ output += `\n → Status: ${statusText}`;
300
+ });
301
+
302
+ // Show if some requests are still pending after timeout
211
303
  if (netActivity.stillPending > 0) {
212
- output += `\nāš ļø Network: ${successCount} OK, ${errorCount} failed, ${netActivity.stillPending} PENDING`;
213
- output += `\n ā±ļø Timeout: Stopped waiting after ${netActivity.waitedMs}ms`;
214
- output += `\n → ${netActivity.stillPending} request(s) still running - status unknown`;
215
- output += `\n → May complete successfully or fail - cannot determine outcome`;
216
- } else if (errorCount > 0) {
217
- output += `\nāš ļø Network: ${successCount} OK, ${errorCount} failed (${netActivity.waitedMs}ms)`;
218
- } else {
219
- output += `\nāœ“ Network: ${netActivity.completedRequests} completed (${netActivity.waitedMs}ms)`;
304
+ output += `\n\nā³ ${netActivity.stillPending} request(s) still pending after ${Math.round(netActivity.waitedMs)}ms timeout`;
220
305
  }
221
306
  } else {
222
- output += '\nāœ“ No network requests triggered';
307
+ output += '\n\nšŸ“” No network requests detected within 200ms';
308
+ }
309
+
310
+ // Bridge requests (from Extension - persist across page reloads)
311
+ const bridgeRequests = netActivity.bridgeRequests || [];
312
+ if (bridgeRequests.length > 0) {
313
+ output += `\n\nšŸ“” Browser-level requests (via Extension):`;
314
+ bridgeRequests.forEach((req, idx) => {
315
+ const statusIcon = req.status === 'pending' ? 'ā³' :
316
+ (req.status === 'completed' || (typeof req.status === 'number' && req.status < 400) ? 'āœ“' : 'āœ—');
317
+ output += `\n ${idx + 1}. ${statusIcon} ${req.method} ${req.url}`;
318
+ if (req.status !== 'pending') {
319
+ output += ` → ${req.status}`;
320
+ }
321
+ });
223
322
  }
224
323
 
225
324
  // Errors