chrometools-mcp 3.2.6 → 3.2.10
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/CHANGELOG.md +90 -0
- package/browser/page-manager.js +31 -0
- package/index.js +54 -5
- package/package.json +1 -1
- package/utils/post-click-diagnostics.js +277 -0
- package/publish_output.txt +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,96 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [3.2.10] - 2026-01-29
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- **Network request deduplication** — Fixed duplicate pending requests in diagnostics
|
|
9
|
+
- Prevented same requestId from being added multiple times during redirects/retries
|
|
10
|
+
- Added deduplication check in Network.requestWillBeSent event handler
|
|
11
|
+
- Updates existing request instead of creating duplicate entry
|
|
12
|
+
- Example: example.com showed 4 pending (2 URLs × 2 duplicates) → now shows 2 pending (correct count)
|
|
13
|
+
- **Memory leak prevention** — Limited networkRequests array growth
|
|
14
|
+
- Keeps maximum 500 most recent network requests in memory
|
|
15
|
+
- Automatically removes oldest requests when limit exceeded
|
|
16
|
+
- Prevents unbounded memory growth during long browser sessions
|
|
17
|
+
- Example: After 100 page navigations, memory stays bounded
|
|
18
|
+
|
|
19
|
+
## [3.2.9] - 2026-01-29
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
- **navigateTo diagnostics** — Post-navigation diagnostics for navigateTo tool
|
|
23
|
+
- Detects chrome-error:// pages (unreachable servers, DNS failures)
|
|
24
|
+
- Waits 20s for slow page loads and network requests
|
|
25
|
+
- Reports JS console errors and network errors after navigation
|
|
26
|
+
- Shows pending requests if page loads slowly
|
|
27
|
+
- Same comprehensive diagnostics as click tool
|
|
28
|
+
- Example: Navigate to offline backend → instant error report instead of silent failure
|
|
29
|
+
- **openBrowser diagnostics** — Post-navigation diagnostics for openBrowser tool
|
|
30
|
+
- Same comprehensive diagnostics as navigateTo and click
|
|
31
|
+
- Critical for first action in session - shows errors immediately
|
|
32
|
+
- Detects chrome-error:// pages on initial load
|
|
33
|
+
- Reports network errors, console errors, pending requests
|
|
34
|
+
- Example: Open unreachable backend → instant error report with details
|
|
35
|
+
|
|
36
|
+
### Changed
|
|
37
|
+
- **Diagnostics naming** — Renamed for clarity and universal use
|
|
38
|
+
- "POST-CLICK DIAGNOSTICS" → "POST-ACTION DIAGNOSTICS"
|
|
39
|
+
- Function parameters: beforeClickTimestamp → beforeActionTimestamp
|
|
40
|
+
- Comments updated to reflect use in both click and navigate actions
|
|
41
|
+
- File remains post-click-diagnostics.js for backward compatibility
|
|
42
|
+
|
|
43
|
+
## [3.2.8] - 2026-01-29
|
|
44
|
+
|
|
45
|
+
### Changed
|
|
46
|
+
- **Network wait timeout** — Increased from 5s to 20s for slow APIs
|
|
47
|
+
- Gives slow backend APIs time to complete before timeout
|
|
48
|
+
- AI gets complete success/error status instead of "pending unknown"
|
|
49
|
+
- Pending requests after 20s are reported with details (URL, method, runtime)
|
|
50
|
+
- Clear warning: "Status unknown - may complete successfully or fail"
|
|
51
|
+
|
|
52
|
+
### Fixed
|
|
53
|
+
- **Click timeout on network errors** — No more 30s timeout when backend unreachable
|
|
54
|
+
- Detects chrome-error:// pages (ERR_CONNECTION_REFUSED, DNS_PROBE_FINISHED_NXDOMAIN, etc.)
|
|
55
|
+
- Returns error details immediately after 500ms diagnostic wait
|
|
56
|
+
- Shows error code and suggestion: "Backend likely not running or unreachable"
|
|
57
|
+
- Reduces diagnosis from 3 API calls to 1
|
|
58
|
+
- Example: Form submits to localhost:8001 (not running) → instant error report instead of 30s timeout
|
|
59
|
+
- **Network request tracking** — Now tracks ALL requests triggered by click, not just pending at 500ms
|
|
60
|
+
- Filters requests by timestamp (only those started AFTER click)
|
|
61
|
+
- Catches slow-starting requests that begin after initial 500ms wait
|
|
62
|
+
- Shows accurate count: completed/pending/total requests
|
|
63
|
+
- Prevents false "No network requests triggered" when requests start late
|
|
64
|
+
- **Delayed error collection** — Errors from requests that complete during maxWait are now captured
|
|
65
|
+
- Added 100ms delay after network wait before collecting errors
|
|
66
|
+
- Catches errors from requests that finish right as timeout expires
|
|
67
|
+
- Network summary shows: "⚠️ Network: 2 OK, 1 failed" when errors present
|
|
68
|
+
- Ensures AI sees errors even if request completes at edge of timeout window
|
|
69
|
+
- **Pending request reporting** — AI now sees details about slow/hanging requests
|
|
70
|
+
- Lists pending requests with URL, method, and elapsed time
|
|
71
|
+
- Suggests backend performance check or network connectivity issues
|
|
72
|
+
- Example: "POST /api/slow - Running for: 20145ms"
|
|
73
|
+
|
|
74
|
+
## [3.2.7] - 2026-01-29
|
|
75
|
+
|
|
76
|
+
### Added
|
|
77
|
+
- **Post-click diagnostics** — Click tool now automatically detects and reports errors
|
|
78
|
+
- Waits 500ms after click to capture async events
|
|
79
|
+
- Detects pending network requests and waits for completion (up to 5s)
|
|
80
|
+
- Collects JavaScript console errors and network errors
|
|
81
|
+
- **Error limit**: Max 15 console errors + 15 network errors to prevent spam
|
|
82
|
+
- Shows omitted error count if limit exceeded
|
|
83
|
+
- Returns diagnostics in click response for immediate AI feedback
|
|
84
|
+
- Prevents AI from making blind follow-up requests when errors occur
|
|
85
|
+
- New module: `utils/post-click-diagnostics.js`
|
|
86
|
+
|
|
87
|
+
### Changed
|
|
88
|
+
- **Click behavior** — Enhanced UX for AI agents
|
|
89
|
+
- Click now includes network activity summary (requests completed, timing)
|
|
90
|
+
- Errors displayed immediately in click response
|
|
91
|
+
- AI can see what broke without additional tool calls
|
|
92
|
+
- Better error context: timestamp, location, status codes
|
|
93
|
+
- Smart error limiting prevents overwhelming AI with hundreds of errors
|
|
94
|
+
|
|
5
95
|
## [3.2.6] - 2026-01-28
|
|
6
96
|
|
|
7
97
|
### Removed
|
package/browser/page-manager.js
CHANGED
|
@@ -30,6 +30,21 @@ export const consoleLogs = [];
|
|
|
30
30
|
// Network requests storage
|
|
31
31
|
export const networkRequests = [];
|
|
32
32
|
|
|
33
|
+
// Maximum number of network requests to keep in memory (prevent unbounded growth)
|
|
34
|
+
const MAX_NETWORK_REQUESTS = 500;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Clean old network requests to prevent memory leak
|
|
38
|
+
* Keeps only the most recent MAX_NETWORK_REQUESTS requests
|
|
39
|
+
*/
|
|
40
|
+
function cleanOldNetworkRequests() {
|
|
41
|
+
if (networkRequests.length > MAX_NETWORK_REQUESTS) {
|
|
42
|
+
// Remove oldest requests (keep most recent)
|
|
43
|
+
const removeCount = networkRequests.length - MAX_NETWORK_REQUESTS;
|
|
44
|
+
networkRequests.splice(0, removeCount);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
33
48
|
// Page analysis cache
|
|
34
49
|
export const pageAnalysisCache = new Map();
|
|
35
50
|
|
|
@@ -52,6 +67,19 @@ export async function setupNetworkMonitoring(page) {
|
|
|
52
67
|
|
|
53
68
|
client.on('Network.requestWillBeSent', (event) => {
|
|
54
69
|
const timestamp = new Date().toISOString();
|
|
70
|
+
|
|
71
|
+
// Check if request already exists (prevent duplicates from redirects/retries)
|
|
72
|
+
const existingReq = networkRequests.find(r => r.requestId === event.requestId);
|
|
73
|
+
if (existingReq) {
|
|
74
|
+
// Update existing request instead of creating duplicate
|
|
75
|
+
existingReq.url = event.request.url;
|
|
76
|
+
existingReq.method = event.request.method;
|
|
77
|
+
existingReq.headers = event.request.headers;
|
|
78
|
+
existingReq.postData = event.request.postData;
|
|
79
|
+
existingReq.timestamp = timestamp;
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
55
83
|
networkRequests.push({
|
|
56
84
|
requestId: event.requestId,
|
|
57
85
|
url: event.request.url,
|
|
@@ -64,6 +92,9 @@ export async function setupNetworkMonitoring(page) {
|
|
|
64
92
|
status: 'pending',
|
|
65
93
|
documentURL: event.documentURL
|
|
66
94
|
});
|
|
95
|
+
|
|
96
|
+
// Clean old requests to prevent unbounded memory growth
|
|
97
|
+
cleanOldNetworkRequests();
|
|
67
98
|
});
|
|
68
99
|
|
|
69
100
|
client.on('Network.responseReceived', (event) => {
|
package/index.js
CHANGED
|
@@ -54,6 +54,8 @@ import {getToolsFromGroups, getAllGroupNames} from './server/tool-groups.js';
|
|
|
54
54
|
import {executeElementAction} from './utils/element-actions.js';
|
|
55
55
|
// Import hints generator
|
|
56
56
|
import {generateClickHints, generateNavigationHints} from './utils/hints-generator.js';
|
|
57
|
+
// Import post-click diagnostics
|
|
58
|
+
import {runPostClickDiagnostics, formatDiagnosticsForAI} from './utils/post-click-diagnostics.js';
|
|
57
59
|
|
|
58
60
|
// Import Recorder modules
|
|
59
61
|
// Note: injectRecorder removed - now using Chrome Extension
|
|
@@ -313,12 +315,22 @@ async function executeToolInternal(name, args) {
|
|
|
313
315
|
|
|
314
316
|
if (name === "openBrowser") {
|
|
315
317
|
const validatedArgs = schemas.OpenBrowserSchema.parse(args);
|
|
318
|
+
|
|
319
|
+
// Capture timestamp BEFORE opening for diagnostics
|
|
320
|
+
const beforeOpenTimestamp = Date.now();
|
|
321
|
+
|
|
316
322
|
const page = await getOrCreatePage(validatedArgs.url);
|
|
317
323
|
const title = await page.title();
|
|
318
324
|
|
|
325
|
+
// Run post-navigation diagnostics (same as navigateTo)
|
|
326
|
+
const diagnostics = await runPostClickDiagnostics(page, beforeOpenTimestamp);
|
|
327
|
+
|
|
319
328
|
// Generate AI hints
|
|
320
329
|
const hints = await generateNavigationHints(page, validatedArgs.url);
|
|
321
330
|
|
|
331
|
+
// Format diagnostics for output
|
|
332
|
+
const diagnosticsText = formatDiagnosticsForAI(diagnostics);
|
|
333
|
+
|
|
322
334
|
// Check if extension is connected
|
|
323
335
|
const extensionConnected = isExtensionConnected();
|
|
324
336
|
const usedExistingChrome = isConnectedToExistingChrome();
|
|
@@ -329,11 +341,20 @@ async function executeToolInternal(name, args) {
|
|
|
329
341
|
extensionNote = `\n\n⚠️ EXTENSION NOT CONNECTED\nConnected to existing Chrome - extension needs manual installation.\n${instructions.installSteps.join('\n')}\n\nAlternative: ${instructions.alternativeFix}`;
|
|
330
342
|
}
|
|
331
343
|
|
|
344
|
+
let hintsText = '\n\n** AI HINTS **';
|
|
345
|
+
hintsText += `\nPage type: ${hints.pageType}`;
|
|
346
|
+
if (hints.availableActions.length > 0) {
|
|
347
|
+
hintsText += `\nAvailable actions: ${hints.availableActions.join(', ')}`;
|
|
348
|
+
}
|
|
349
|
+
if (hints.suggestedNext.length > 0) {
|
|
350
|
+
hintsText += `\nSuggested next: ${hints.suggestedNext.join('; ')}`;
|
|
351
|
+
}
|
|
352
|
+
|
|
332
353
|
return {
|
|
333
354
|
content: [
|
|
334
355
|
{
|
|
335
356
|
type: "text",
|
|
336
|
-
text: `Browser opened successfully!\nURL: ${validatedArgs.url}\nPage title: ${title}\n\nBrowser remains open for interaction
|
|
357
|
+
text: `Browser opened successfully!\nURL: ${validatedArgs.url}\nPage title: ${title}\n\nBrowser remains open for interaction.${hintsText}${diagnosticsText}${extensionNote}`,
|
|
337
358
|
},
|
|
338
359
|
],
|
|
339
360
|
};
|
|
@@ -380,6 +401,9 @@ async function executeToolInternal(name, args) {
|
|
|
380
401
|
throw new Error(`Element not found: ${identifier}`);
|
|
381
402
|
}
|
|
382
403
|
|
|
404
|
+
// Capture timestamp BEFORE click for error filtering
|
|
405
|
+
const beforeClickTimestamp = Date.now();
|
|
406
|
+
|
|
383
407
|
// Try multiple click methods for better reliability
|
|
384
408
|
try {
|
|
385
409
|
// Method 1: Puppeteer click (most reliable for most cases)
|
|
@@ -395,11 +419,15 @@ async function executeToolInternal(name, args) {
|
|
|
395
419
|
await element.evaluate(el => el.click());
|
|
396
420
|
}
|
|
397
421
|
}
|
|
398
|
-
await new Promise(resolve => setTimeout(resolve, validatedArgs.waitAfter || 1500));
|
|
399
422
|
|
|
400
|
-
//
|
|
423
|
+
// NEW POST-CLICK PATTERN:
|
|
424
|
+
// 1. Run post-click diagnostics (waits 500ms, checks pending requests, collects errors)
|
|
425
|
+
const diagnostics = await runPostClickDiagnostics(page, beforeClickTimestamp);
|
|
426
|
+
|
|
427
|
+
// 2. Generate AI hints after click
|
|
401
428
|
const hints = await generateClickHints(page, identifier);
|
|
402
429
|
|
|
430
|
+
// 3. Format output with hints and diagnostics
|
|
403
431
|
let hintsText = '\n\n** AI HINTS **';
|
|
404
432
|
if (hints.modalOpened) hintsText += '\nModal opened - interact with it or close';
|
|
405
433
|
if (hints.newElements.length > 0) {
|
|
@@ -409,8 +437,11 @@ async function executeToolInternal(name, args) {
|
|
|
409
437
|
hintsText += `\nSuggested next: ${hints.suggestedNext.join('; ')}`;
|
|
410
438
|
}
|
|
411
439
|
|
|
440
|
+
// 4. Add diagnostics to output
|
|
441
|
+
const diagnosticsText = formatDiagnosticsForAI(diagnostics);
|
|
442
|
+
|
|
412
443
|
const content = [
|
|
413
|
-
{ type: "text", text: `Clicked: ${identifier}${hintsText}` }
|
|
444
|
+
{ type: "text", text: `Clicked: ${identifier}${hintsText}${diagnosticsText}` }
|
|
414
445
|
];
|
|
415
446
|
|
|
416
447
|
// Only add screenshot if requested
|
|
@@ -1343,6 +1374,9 @@ async function executeToolInternal(name, args) {
|
|
|
1343
1374
|
browserOpened = true;
|
|
1344
1375
|
}
|
|
1345
1376
|
|
|
1377
|
+
// Capture timestamp BEFORE navigation for diagnostics
|
|
1378
|
+
const beforeNavTimestamp = Date.now();
|
|
1379
|
+
|
|
1346
1380
|
// Navigate to the new URL (skip if we just created page with this URL)
|
|
1347
1381
|
if (!browserOpened) {
|
|
1348
1382
|
await page.goto(validatedArgs.url, { waitUntil: validatedArgs.waitUntil || 'networkidle2' });
|
|
@@ -1350,17 +1384,32 @@ async function executeToolInternal(name, args) {
|
|
|
1350
1384
|
|
|
1351
1385
|
const title = await page.title();
|
|
1352
1386
|
|
|
1387
|
+
// Run post-navigation diagnostics (same as post-click)
|
|
1388
|
+
const diagnostics = await runPostClickDiagnostics(page, beforeNavTimestamp);
|
|
1389
|
+
|
|
1353
1390
|
// Generate AI hints
|
|
1354
1391
|
const hints = await generateNavigationHints(page, validatedArgs.url);
|
|
1355
1392
|
|
|
1393
|
+
// Format diagnostics for output
|
|
1394
|
+
const diagnosticsText = formatDiagnosticsForAI(diagnostics);
|
|
1395
|
+
|
|
1356
1396
|
const message = browserOpened
|
|
1357
1397
|
? `Browser opened and navigated to: ${validatedArgs.url}`
|
|
1358
1398
|
: `Navigated to: ${validatedArgs.url}`;
|
|
1359
1399
|
|
|
1400
|
+
let hintsText = '\n\n** AI HINTS **';
|
|
1401
|
+
hintsText += `\nPage type: ${hints.pageType}`;
|
|
1402
|
+
if (hints.availableActions.length > 0) {
|
|
1403
|
+
hintsText += `\nAvailable actions: ${hints.availableActions.join(', ')}`;
|
|
1404
|
+
}
|
|
1405
|
+
if (hints.suggestedNext.length > 0) {
|
|
1406
|
+
hintsText += `\nSuggested next: ${hints.suggestedNext.join('; ')}`;
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1360
1409
|
return {
|
|
1361
1410
|
content: [{
|
|
1362
1411
|
type: "text",
|
|
1363
|
-
text: `${message}\nPage title: ${title}
|
|
1412
|
+
text: `${message}\nPage title: ${title}${hintsText}${diagnosticsText}`
|
|
1364
1413
|
}],
|
|
1365
1414
|
};
|
|
1366
1415
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chrometools-mcp",
|
|
3
|
-
"version": "3.2.
|
|
3
|
+
"version": "3.2.10",
|
|
4
4
|
"description": "MCP (Model Context Protocol) server for Chrome automation using Puppeteer. Persistent browser sessions, UI framework detection (MUI, Ant Design, etc.), Page Object support, visual testing, Figma comparison. Works seamlessly in WSL, Linux, macOS, and Windows.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Post-Action Diagnostics
|
|
3
|
+
* Collects errors and waits for network requests after user actions (click, navigation, etc.)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { consoleLogs, networkRequests } from '../browser/page-manager.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Wait for pending network requests to complete
|
|
10
|
+
* @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
|
+
* @returns {Promise<{pendingFound: boolean, waitedMs: number, completedRequests: number, totalRequests: number}>}
|
|
14
|
+
*/
|
|
15
|
+
export async function waitForPendingRequests(beforeActionTimestamp, initialWaitMs = 500, maxWaitMs = 5000) {
|
|
16
|
+
const startTime = Date.now();
|
|
17
|
+
|
|
18
|
+
// Step 1: Wait initial period to let requests start
|
|
19
|
+
await new Promise(resolve => setTimeout(resolve, initialWaitMs));
|
|
20
|
+
|
|
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
|
+
};
|
|
26
|
+
|
|
27
|
+
// Step 3: Check for pending requests (from post-action requests)
|
|
28
|
+
const checkPending = () => {
|
|
29
|
+
return getPostActionRequests().filter(req => req.status === 'pending');
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
let pending = checkPending();
|
|
33
|
+
let allPostActionRequests = getPostActionRequests();
|
|
34
|
+
const initialPendingCount = pending.length;
|
|
35
|
+
|
|
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
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const finalRequests = getPostActionRequests();
|
|
47
|
+
const completedRequests = finalRequests.filter(req => req.status === 'completed' || (typeof req.status === 'number'));
|
|
48
|
+
const pendingRequests = pending.map(req => ({
|
|
49
|
+
url: req.url,
|
|
50
|
+
method: req.method,
|
|
51
|
+
timestamp: req.timestamp
|
|
52
|
+
}));
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
pendingFound: initialPendingCount > 0,
|
|
56
|
+
waitedMs: Date.now() - startTime,
|
|
57
|
+
completedRequests: completedRequests.length,
|
|
58
|
+
stillPending: pending.length,
|
|
59
|
+
pendingRequests: pendingRequests,
|
|
60
|
+
totalRequests: finalRequests.length
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Collect errors from console logs and network requests
|
|
66
|
+
* @param {number} sinceTimestamp - Only collect errors after this timestamp (default: collect recent errors)
|
|
67
|
+
* @param {number} maxConsoleErrors - Maximum console errors to return (default: 15)
|
|
68
|
+
* @param {number} maxNetworkErrors - Maximum network errors to return (default: 15)
|
|
69
|
+
* @returns {Object} Object with consoleErrors and networkErrors arrays
|
|
70
|
+
*/
|
|
71
|
+
export function collectErrors(sinceTimestamp = null, maxConsoleErrors = 15, maxNetworkErrors = 15) {
|
|
72
|
+
const errors = {
|
|
73
|
+
consoleErrors: [],
|
|
74
|
+
networkErrors: [],
|
|
75
|
+
jsExceptions: [],
|
|
76
|
+
consoleErrorsOmitted: 0,
|
|
77
|
+
networkErrorsOmitted: 0
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// If no timestamp provided, look back 10 seconds
|
|
81
|
+
const cutoffTime = sinceTimestamp || (Date.now() - 10000);
|
|
82
|
+
const cutoffDate = new Date(cutoffTime).toISOString();
|
|
83
|
+
|
|
84
|
+
// Collect console errors (with limit)
|
|
85
|
+
let consoleErrorCount = 0;
|
|
86
|
+
consoleLogs.forEach(log => {
|
|
87
|
+
if (log.type === 'error') {
|
|
88
|
+
// Check if error is recent
|
|
89
|
+
const logTime = new Date(log.timestamp || 0).toISOString();
|
|
90
|
+
if (!sinceTimestamp || logTime >= cutoffDate) {
|
|
91
|
+
if (consoleErrorCount < maxConsoleErrors) {
|
|
92
|
+
errors.consoleErrors.push({
|
|
93
|
+
message: log.text,
|
|
94
|
+
timestamp: log.timestamp,
|
|
95
|
+
location: log.location || 'unknown'
|
|
96
|
+
});
|
|
97
|
+
} else {
|
|
98
|
+
errors.consoleErrorsOmitted++;
|
|
99
|
+
}
|
|
100
|
+
consoleErrorCount++;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// Collect network errors (failed requests, with limit)
|
|
106
|
+
let networkErrorCount = 0;
|
|
107
|
+
networkRequests.forEach(req => {
|
|
108
|
+
if (req.status === 'failed' || (typeof req.status === 'number' && req.status >= 400)) {
|
|
109
|
+
// Check if error is recent
|
|
110
|
+
const reqTime = req.timestamp;
|
|
111
|
+
if (!sinceTimestamp || reqTime >= cutoffDate) {
|
|
112
|
+
if (networkErrorCount < maxNetworkErrors) {
|
|
113
|
+
errors.networkErrors.push({
|
|
114
|
+
url: req.url,
|
|
115
|
+
method: req.method,
|
|
116
|
+
status: req.status,
|
|
117
|
+
statusText: req.statusText,
|
|
118
|
+
errorText: req.errorText,
|
|
119
|
+
timestamp: req.timestamp
|
|
120
|
+
});
|
|
121
|
+
} else {
|
|
122
|
+
errors.networkErrorsOmitted++;
|
|
123
|
+
}
|
|
124
|
+
networkErrorCount++;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
return errors;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Full post-action diagnostics: wait for requests and collect errors
|
|
134
|
+
* @param {Page} page - Puppeteer page instance
|
|
135
|
+
* @param {number} beforeActionTimestamp - Timestamp before action (to filter errors)
|
|
136
|
+
* @returns {Promise<Object>} Diagnostics result with errors and network info
|
|
137
|
+
*/
|
|
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);
|
|
142
|
+
|
|
143
|
+
// Small delay to let pending requests update their error status
|
|
144
|
+
// (handles case where request completes with error right after maxWait expires)
|
|
145
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
146
|
+
|
|
147
|
+
// Check for chrome error page (ERR_CONNECTION_REFUSED, etc.)
|
|
148
|
+
const url = page.url();
|
|
149
|
+
let chromeErrorInfo = null;
|
|
150
|
+
if (url.startsWith('chrome-error://')) {
|
|
151
|
+
chromeErrorInfo = await page.evaluate(() => {
|
|
152
|
+
const errorCode = document.querySelector('#error-code');
|
|
153
|
+
const suggestionText = document.querySelector('.suggestions');
|
|
154
|
+
return {
|
|
155
|
+
errorCode: errorCode?.textContent || 'UNKNOWN_ERROR',
|
|
156
|
+
suggestion: suggestionText?.textContent?.trim() || 'Connection failed'
|
|
157
|
+
};
|
|
158
|
+
}).catch(() => ({ errorCode: 'PAGE_LOAD_ERROR', suggestion: 'Navigation failed' }));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Collect errors that occurred after the action (including errors from just-completed requests)
|
|
162
|
+
const errors = collectErrors(beforeActionTimestamp);
|
|
163
|
+
|
|
164
|
+
// Combine into diagnostics report
|
|
165
|
+
const diagnostics = {
|
|
166
|
+
networkActivity: {
|
|
167
|
+
hadPendingRequests: networkInfo.pendingFound,
|
|
168
|
+
completedRequests: networkInfo.completedRequests,
|
|
169
|
+
stillPending: networkInfo.stillPending,
|
|
170
|
+
pendingRequests: networkInfo.pendingRequests,
|
|
171
|
+
totalRequests: networkInfo.totalRequests,
|
|
172
|
+
waitedMs: networkInfo.waitedMs
|
|
173
|
+
},
|
|
174
|
+
chromeError: chromeErrorInfo,
|
|
175
|
+
errors: {
|
|
176
|
+
consoleErrors: errors.consoleErrors,
|
|
177
|
+
networkErrors: errors.networkErrors,
|
|
178
|
+
consoleErrorsOmitted: errors.consoleErrorsOmitted,
|
|
179
|
+
networkErrorsOmitted: errors.networkErrorsOmitted,
|
|
180
|
+
totalErrors: errors.consoleErrors.length + errors.networkErrors.length
|
|
181
|
+
},
|
|
182
|
+
hasErrors: (errors.consoleErrors.length + errors.networkErrors.length) > 0 || chromeErrorInfo !== null
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
return diagnostics;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Format diagnostics for AI-friendly output
|
|
190
|
+
* @param {Object} diagnostics - Diagnostics object from runPostClickDiagnostics
|
|
191
|
+
* @returns {string} Formatted text for AI
|
|
192
|
+
*/
|
|
193
|
+
export function formatDiagnosticsForAI(diagnostics) {
|
|
194
|
+
let output = '\n\n** POST-ACTION DIAGNOSTICS **';
|
|
195
|
+
|
|
196
|
+
// Chrome error page (connection refused, DNS failed, etc.)
|
|
197
|
+
if (diagnostics.chromeError) {
|
|
198
|
+
output += `\n\n🔴 CRITICAL: Navigation Failed`;
|
|
199
|
+
output += `\n Error: ${diagnostics.chromeError.errorCode}`;
|
|
200
|
+
output += `\n Suggestion: ${diagnostics.chromeError.suggestion}`;
|
|
201
|
+
output += `\n → Backend likely not running or unreachable`;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Network activity
|
|
205
|
+
const netActivity = diagnostics.networkActivity;
|
|
206
|
+
if (netActivity.totalRequests > 0) {
|
|
207
|
+
const errorCount = diagnostics.errors.networkErrors.length;
|
|
208
|
+
const successCount = netActivity.completedRequests - errorCount;
|
|
209
|
+
|
|
210
|
+
// Show warning if there are pending requests after timeout
|
|
211
|
+
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)`;
|
|
220
|
+
}
|
|
221
|
+
} else {
|
|
222
|
+
output += '\n✓ No network requests triggered';
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Errors
|
|
226
|
+
if (diagnostics.errors.totalErrors > 0) {
|
|
227
|
+
output += `\n\n⚠️ ERRORS DETECTED (${diagnostics.errors.totalErrors} total):`;
|
|
228
|
+
|
|
229
|
+
// Console errors
|
|
230
|
+
if (diagnostics.errors.consoleErrors.length > 0) {
|
|
231
|
+
output += `\n\nJavaScript Console Errors (${diagnostics.errors.consoleErrors.length}):`;
|
|
232
|
+
diagnostics.errors.consoleErrors.forEach((err, idx) => {
|
|
233
|
+
output += `\n ${idx + 1}. ${err.message}`;
|
|
234
|
+
if (err.location && err.location !== 'unknown') {
|
|
235
|
+
output += ` [${err.location}]`;
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
// Show if some errors were omitted
|
|
239
|
+
if (diagnostics.errors.consoleErrorsOmitted > 0) {
|
|
240
|
+
output += `\n ... and ${diagnostics.errors.consoleErrorsOmitted} more console error(s) (omitted to prevent spam)`;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Network errors
|
|
245
|
+
if (diagnostics.errors.networkErrors.length > 0) {
|
|
246
|
+
output += `\n\nNetwork Errors (${diagnostics.errors.networkErrors.length}):`;
|
|
247
|
+
diagnostics.errors.networkErrors.forEach((err, idx) => {
|
|
248
|
+
output += `\n ${idx + 1}. ${err.method} ${err.url}`;
|
|
249
|
+
output += `\n Status: ${err.status}${err.statusText ? ' ' + err.statusText : ''}`;
|
|
250
|
+
if (err.errorText) {
|
|
251
|
+
output += `\n Error: ${err.errorText}`;
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
// Show if some errors were omitted
|
|
255
|
+
if (diagnostics.errors.networkErrorsOmitted > 0) {
|
|
256
|
+
output += `\n ... and ${diagnostics.errors.networkErrorsOmitted} more network error(s) (omitted to prevent spam)`;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
} else {
|
|
260
|
+
output += '\n✓ No errors detected';
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Pending requests (if any still running after timeout)
|
|
264
|
+
if (netActivity.stillPending > 0 && netActivity.pendingRequests.length > 0) {
|
|
265
|
+
output += `\n\n⏳ PENDING REQUESTS (${netActivity.stillPending} still running):`;
|
|
266
|
+
netActivity.pendingRequests.forEach((req, idx) => {
|
|
267
|
+
output += `\n ${idx + 1}. ${req.method} ${req.url}`;
|
|
268
|
+
const elapsed = Date.now() - new Date(req.timestamp).getTime();
|
|
269
|
+
output += `\n Running for: ${elapsed}ms`;
|
|
270
|
+
});
|
|
271
|
+
output += `\n\n💡 Suggestion: These requests may be slow or hanging`;
|
|
272
|
+
output += `\n → Check backend performance or network connectivity`;
|
|
273
|
+
output += `\n → Consider using getNetworkRequest() to monitor progress`;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return output;
|
|
277
|
+
}
|
package/publish_output.txt
DELETED
|
File without changes
|