@ricardodeazambuja/browser-mcp-server 1.0.3 → 1.4.0
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-v1.3.0.md +42 -0
- package/CHANGELOG-v1.4.0.md +8 -0
- package/README.md +271 -45
- package/package.json +11 -10
- package/plugins/.gitkeep +0 -0
- package/src/.gitkeep +0 -0
- package/src/browser.js +152 -0
- package/src/cdp.js +58 -0
- package/src/index.js +126 -0
- package/src/tools/.gitkeep +0 -0
- package/src/tools/console.js +139 -0
- package/src/tools/docs.js +1611 -0
- package/src/tools/index.js +60 -0
- package/src/tools/info.js +139 -0
- package/src/tools/interaction.js +126 -0
- package/src/tools/keyboard.js +27 -0
- package/src/tools/media.js +264 -0
- package/src/tools/mouse.js +104 -0
- package/src/tools/navigation.js +72 -0
- package/src/tools/network.js +552 -0
- package/src/tools/pages.js +149 -0
- package/src/tools/performance.js +517 -0
- package/src/tools/security.js +470 -0
- package/src/tools/storage.js +467 -0
- package/src/tools/system.js +196 -0
- package/src/utils.js +131 -0
- package/tests/.gitkeep +0 -0
- package/tests/fixtures/.gitkeep +0 -0
- package/tests/fixtures/test-media.html +35 -0
- package/tests/fixtures/test-network.html +48 -0
- package/tests/fixtures/test-performance.html +61 -0
- package/tests/fixtures/test-security.html +33 -0
- package/tests/fixtures/test-storage.html +76 -0
- package/tests/run-all.js +50 -0
- package/{test-browser-automation.js → tests/test-browser-automation.js} +44 -5
- package/{test-mcp.js → tests/test-mcp.js} +9 -4
- package/tests/test-media-tools.js +168 -0
- package/tests/test-network.js +212 -0
- package/tests/test-performance.js +254 -0
- package/tests/test-security.js +203 -0
- package/tests/test-storage.js +192 -0
- package/CHANGELOG-v1.0.2.md +0 -126
- package/browser-mcp-server-playwright.js +0 -792
|
@@ -0,0 +1,517 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Performance Profiling Tools (CDP-based)
|
|
3
|
+
* CPU profiling, heap snapshots, memory metrics, web vitals, code coverage
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { connectToBrowser } = require('../browser');
|
|
7
|
+
const { getCDPSession } = require('../cdp');
|
|
8
|
+
const { debugLog } = require('../utils');
|
|
9
|
+
|
|
10
|
+
// Local state for performance tools
|
|
11
|
+
let profilingActive = false;
|
|
12
|
+
let coverageActive = false;
|
|
13
|
+
|
|
14
|
+
const definitions = [
|
|
15
|
+
{
|
|
16
|
+
name: 'browser_perf_start_profile',
|
|
17
|
+
description: 'Start CPU profiling to track JavaScript execution (see browser_docs)',
|
|
18
|
+
inputSchema: {
|
|
19
|
+
type: 'object',
|
|
20
|
+
properties: {
|
|
21
|
+
sampleInterval: {
|
|
22
|
+
type: 'number',
|
|
23
|
+
description: 'Microseconds between samples (default: 100)'
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
additionalProperties: false,
|
|
27
|
+
$schema: 'http://json-schema.org/draft-07/schema#'
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
name: 'browser_perf_stop_profile',
|
|
32
|
+
description: 'Stop CPU profiling and get profile data (see browser_docs)',
|
|
33
|
+
inputSchema: {
|
|
34
|
+
type: 'object',
|
|
35
|
+
properties: {},
|
|
36
|
+
additionalProperties: false,
|
|
37
|
+
$schema: 'http://json-schema.org/draft-07/schema#'
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: 'browser_perf_take_heap_snapshot',
|
|
42
|
+
description: 'Capture heap snapshot for memory analysis (see browser_docs)',
|
|
43
|
+
inputSchema: {
|
|
44
|
+
type: 'object',
|
|
45
|
+
properties: {
|
|
46
|
+
reportProgress: {
|
|
47
|
+
type: 'boolean',
|
|
48
|
+
description: 'Report progress events (default: false)'
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
additionalProperties: false,
|
|
52
|
+
$schema: 'http://json-schema.org/draft-07/schema#'
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
name: 'browser_perf_get_heap_usage',
|
|
57
|
+
description: 'Get current JavaScript heap usage statistics (see browser_docs)',
|
|
58
|
+
inputSchema: {
|
|
59
|
+
type: 'object',
|
|
60
|
+
properties: {},
|
|
61
|
+
additionalProperties: false,
|
|
62
|
+
$schema: 'http://json-schema.org/draft-07/schema#'
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
name: 'browser_perf_get_metrics',
|
|
67
|
+
description: 'Get runtime performance metrics (DOM nodes, event listeners, JS heap) (see browser_docs)',
|
|
68
|
+
inputSchema: {
|
|
69
|
+
type: 'object',
|
|
70
|
+
properties: {},
|
|
71
|
+
additionalProperties: false,
|
|
72
|
+
$schema: 'http://json-schema.org/draft-07/schema#'
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
name: 'browser_perf_get_performance_metrics',
|
|
77
|
+
description: 'Get web vitals and navigation timing (FCP, LCP, CLS, TTFB) (see browser_docs)',
|
|
78
|
+
inputSchema: {
|
|
79
|
+
type: 'object',
|
|
80
|
+
properties: {},
|
|
81
|
+
additionalProperties: false,
|
|
82
|
+
$schema: 'http://json-schema.org/draft-07/schema#'
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
name: 'browser_perf_start_coverage',
|
|
87
|
+
description: 'Start tracking CSS and JavaScript code coverage (see browser_docs)',
|
|
88
|
+
inputSchema: {
|
|
89
|
+
type: 'object',
|
|
90
|
+
properties: {
|
|
91
|
+
resetOnNavigation: {
|
|
92
|
+
type: 'boolean',
|
|
93
|
+
description: 'Reset coverage on navigation (default: true)'
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
additionalProperties: false,
|
|
97
|
+
$schema: 'http://json-schema.org/draft-07/schema#'
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
name: 'browser_perf_stop_coverage',
|
|
102
|
+
description: 'Stop coverage and get results showing used vs unused code (see browser_docs)',
|
|
103
|
+
inputSchema: {
|
|
104
|
+
type: 'object',
|
|
105
|
+
properties: {},
|
|
106
|
+
additionalProperties: false,
|
|
107
|
+
$schema: 'http://json-schema.org/draft-07/schema#'
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
];
|
|
111
|
+
|
|
112
|
+
const handlers = {
|
|
113
|
+
browser_perf_start_profile: async (args) => {
|
|
114
|
+
try {
|
|
115
|
+
if (profilingActive) {
|
|
116
|
+
return {
|
|
117
|
+
content: [{
|
|
118
|
+
type: 'text',
|
|
119
|
+
text: '⚠️ CPU profiling is already active.\n\nUse browser_perf_stop_profile to get results first.'
|
|
120
|
+
}]
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const cdp = await getCDPSession();
|
|
125
|
+
const interval = args.sampleInterval || 100;
|
|
126
|
+
|
|
127
|
+
await cdp.send('Profiler.enable');
|
|
128
|
+
await cdp.send('Profiler.start', { samplingInterval: interval });
|
|
129
|
+
profilingActive = true;
|
|
130
|
+
|
|
131
|
+
debugLog(`Started CPU profiling with ${interval}μs sample interval`);
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
content: [{
|
|
135
|
+
type: 'text',
|
|
136
|
+
text: `✅ CPU profiling started with sample interval: ${interval}μs\n\nProfiling JavaScript execution...\nUse browser_perf_stop_profile to get results.`
|
|
137
|
+
}]
|
|
138
|
+
};
|
|
139
|
+
} catch (error) {
|
|
140
|
+
debugLog(`CDP error in browser_perf_start_profile: ${error.message}`);
|
|
141
|
+
return {
|
|
142
|
+
content: [{
|
|
143
|
+
type: 'text',
|
|
144
|
+
text: `❌ CDP Error: ${error.message}\n\nPossible causes:\n- Browser doesn't support CPU profiling\n- CDP session disconnected\n- Page not fully loaded`
|
|
145
|
+
}],
|
|
146
|
+
isError: true
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
|
|
151
|
+
browser_perf_stop_profile: async (args) => {
|
|
152
|
+
try {
|
|
153
|
+
if (!profilingActive) {
|
|
154
|
+
return {
|
|
155
|
+
content: [{
|
|
156
|
+
type: 'text',
|
|
157
|
+
text: '⚠️ CPU profiling is not active.\n\nUse browser_perf_start_profile to start profiling first.'
|
|
158
|
+
}]
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const cdp = await getCDPSession();
|
|
163
|
+
const { profile } = await cdp.send('Profiler.stop');
|
|
164
|
+
await cdp.send('Profiler.disable');
|
|
165
|
+
profilingActive = false;
|
|
166
|
+
|
|
167
|
+
debugLog('Stopped CPU profiling');
|
|
168
|
+
|
|
169
|
+
// Profile can be huge - provide summary instead of full data
|
|
170
|
+
const totalTime = profile.timeDeltas ? profile.timeDeltas.reduce((a, b) => a + b, 0) : 0;
|
|
171
|
+
const topFunctions = profile.nodes
|
|
172
|
+
.filter(n => n.callFrame && n.callFrame.functionName)
|
|
173
|
+
.slice(0, 15)
|
|
174
|
+
.map(n => ({
|
|
175
|
+
function: n.callFrame.functionName || '(anonymous)',
|
|
176
|
+
url: n.callFrame.url || '(internal)',
|
|
177
|
+
line: n.callFrame.lineNumber
|
|
178
|
+
}));
|
|
179
|
+
|
|
180
|
+
const summary = {
|
|
181
|
+
totalNodes: profile.nodes.length,
|
|
182
|
+
totalSamples: profile.samples ? profile.samples.length : 0,
|
|
183
|
+
durationMicroseconds: totalTime,
|
|
184
|
+
durationMs: (totalTime / 1000).toFixed(2),
|
|
185
|
+
topFunctions: topFunctions
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
content: [{
|
|
190
|
+
type: 'text',
|
|
191
|
+
text: `✅ CPU Profile Results:\n\n${JSON.stringify(summary, null, 2)}\n\nNote: Full profile data (${profile.nodes.length} nodes) is too large to display.`
|
|
192
|
+
}]
|
|
193
|
+
};
|
|
194
|
+
} catch (error) {
|
|
195
|
+
profilingActive = false;
|
|
196
|
+
debugLog(`CDP error in browser_perf_stop_profile: ${error.message}`);
|
|
197
|
+
return {
|
|
198
|
+
content: [{
|
|
199
|
+
type: 'text',
|
|
200
|
+
text: `❌ CDP Error: ${error.message}\n\nProfiling has been stopped.`
|
|
201
|
+
}],
|
|
202
|
+
isError: true
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
|
|
207
|
+
browser_perf_take_heap_snapshot: async (args) => {
|
|
208
|
+
try {
|
|
209
|
+
const cdp = await getCDPSession();
|
|
210
|
+
const reportProgress = args.reportProgress || false;
|
|
211
|
+
|
|
212
|
+
debugLog('Taking heap snapshot...');
|
|
213
|
+
|
|
214
|
+
let chunks = [];
|
|
215
|
+
let chunkCount = 0;
|
|
216
|
+
|
|
217
|
+
// Listen for heap snapshot chunks
|
|
218
|
+
cdp.on('HeapProfiler.addHeapSnapshotChunk', (params) => {
|
|
219
|
+
chunks.push(params.chunk);
|
|
220
|
+
chunkCount++;
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
if (reportProgress) {
|
|
224
|
+
cdp.on('HeapProfiler.reportHeapSnapshotProgress', (params) => {
|
|
225
|
+
debugLog(`Heap snapshot progress: ${params.done}/${params.total}`);
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
await cdp.send('HeapProfiler.takeHeapSnapshot', { reportProgress });
|
|
230
|
+
|
|
231
|
+
// Remove listeners
|
|
232
|
+
cdp.removeAllListeners('HeapProfiler.addHeapSnapshotChunk');
|
|
233
|
+
if (reportProgress) {
|
|
234
|
+
cdp.removeAllListeners('HeapProfiler.reportHeapSnapshotProgress');
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const fullSnapshot = chunks.join('');
|
|
238
|
+
const snapshotSize = fullSnapshot.length;
|
|
239
|
+
|
|
240
|
+
debugLog(`Heap snapshot complete: ${snapshotSize} bytes in ${chunkCount} chunks`);
|
|
241
|
+
|
|
242
|
+
return {
|
|
243
|
+
content: [{
|
|
244
|
+
type: 'text',
|
|
245
|
+
text: `✅ Heap Snapshot Captured\n\nSize: ${(snapshotSize / 1024).toFixed(2)} KB\nChunks: ${chunkCount}\n\nNote: Snapshot data is too large to display in full. Use Chrome DevTools to analyze heap snapshots in detail.`
|
|
246
|
+
}]
|
|
247
|
+
};
|
|
248
|
+
} catch (error) {
|
|
249
|
+
debugLog(`CDP error in browser_perf_take_heap_snapshot: ${error.message}`);
|
|
250
|
+
return {
|
|
251
|
+
content: [{
|
|
252
|
+
type: 'text',
|
|
253
|
+
text: `❌ CDP Error: ${error.message}\n\nPossible causes:\n- Browser doesn't support heap profiling\n- Not enough memory to capture snapshot`
|
|
254
|
+
}],
|
|
255
|
+
isError: true
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
},
|
|
259
|
+
|
|
260
|
+
browser_perf_get_heap_usage: async (args) => {
|
|
261
|
+
try {
|
|
262
|
+
const cdp = await getCDPSession();
|
|
263
|
+
const result = await cdp.send('Runtime.getHeapUsage');
|
|
264
|
+
|
|
265
|
+
const heapInfo = {
|
|
266
|
+
usedSize: result.usedSize,
|
|
267
|
+
usedSizeMB: (result.usedSize / 1024 / 1024).toFixed(2),
|
|
268
|
+
totalSize: result.totalSize,
|
|
269
|
+
totalSizeMB: (result.totalSize / 1024 / 1024).toFixed(2),
|
|
270
|
+
limit: result.limit,
|
|
271
|
+
limitMB: (result.limit / 1024 / 1024).toFixed(2),
|
|
272
|
+
usagePercent: ((result.usedSize / result.totalSize) * 100).toFixed(2)
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
return {
|
|
276
|
+
content: [{
|
|
277
|
+
type: 'text',
|
|
278
|
+
text: `📊 JavaScript Heap Usage:\n\n${JSON.stringify(heapInfo, null, 2)}`
|
|
279
|
+
}]
|
|
280
|
+
};
|
|
281
|
+
} catch (error) {
|
|
282
|
+
debugLog(`CDP error in browser_perf_get_heap_usage: ${error.message}`);
|
|
283
|
+
return {
|
|
284
|
+
content: [{
|
|
285
|
+
type: 'text',
|
|
286
|
+
text: `❌ CDP Error: ${error.message}`
|
|
287
|
+
}],
|
|
288
|
+
isError: true
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
},
|
|
292
|
+
|
|
293
|
+
browser_perf_get_metrics: async (args) => {
|
|
294
|
+
try {
|
|
295
|
+
const cdp = await getCDPSession();
|
|
296
|
+
await cdp.send('Performance.enable');
|
|
297
|
+
const { metrics } = await cdp.send('Performance.getMetrics');
|
|
298
|
+
await cdp.send('Performance.disable');
|
|
299
|
+
|
|
300
|
+
const formattedMetrics = metrics.map(m => ({
|
|
301
|
+
name: m.name,
|
|
302
|
+
value: m.value
|
|
303
|
+
}));
|
|
304
|
+
|
|
305
|
+
return {
|
|
306
|
+
content: [{
|
|
307
|
+
type: 'text',
|
|
308
|
+
text: `📊 Runtime Performance Metrics:\n\n${JSON.stringify(formattedMetrics, null, 2)}`
|
|
309
|
+
}]
|
|
310
|
+
};
|
|
311
|
+
} catch (error) {
|
|
312
|
+
debugLog(`CDP error in browser_perf_get_metrics: ${error.message}`);
|
|
313
|
+
return {
|
|
314
|
+
content: [{
|
|
315
|
+
type: 'text',
|
|
316
|
+
text: `❌ CDP Error: ${error.message}`
|
|
317
|
+
}],
|
|
318
|
+
isError: true
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
},
|
|
322
|
+
|
|
323
|
+
browser_perf_get_performance_metrics: async (args) => {
|
|
324
|
+
try {
|
|
325
|
+
const { page } = await connectToBrowser();
|
|
326
|
+
|
|
327
|
+
// Use Performance API to get web vitals and timing
|
|
328
|
+
const metrics = await page.evaluate(() => {
|
|
329
|
+
const result = {
|
|
330
|
+
navigation: {},
|
|
331
|
+
paint: {},
|
|
332
|
+
webVitals: {}
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
// Navigation Timing
|
|
336
|
+
if (performance.timing) {
|
|
337
|
+
const t = performance.timing;
|
|
338
|
+
result.navigation = {
|
|
339
|
+
domContentLoaded: t.domContentLoadedEventEnd - t.navigationStart,
|
|
340
|
+
loadComplete: t.loadEventEnd - t.navigationStart,
|
|
341
|
+
domInteractive: t.domInteractive - t.navigationStart,
|
|
342
|
+
ttfb: t.responseStart - t.navigationStart
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Paint Timing
|
|
347
|
+
if (performance.getEntriesByType) {
|
|
348
|
+
const paintEntries = performance.getEntriesByType('paint');
|
|
349
|
+
paintEntries.forEach(entry => {
|
|
350
|
+
result.paint[entry.name] = entry.startTime;
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Try to get LCP using PerformanceObserver (if available)
|
|
355
|
+
const lcpEntries = performance.getEntriesByType('largest-contentful-paint');
|
|
356
|
+
if (lcpEntries && lcpEntries.length > 0) {
|
|
357
|
+
result.webVitals.lcp = lcpEntries[lcpEntries.length - 1].startTime;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Get CLS (Cumulative Layout Shift)
|
|
361
|
+
const clsEntries = performance.getEntriesByType('layout-shift');
|
|
362
|
+
if (clsEntries) {
|
|
363
|
+
result.webVitals.cls = clsEntries
|
|
364
|
+
.filter(entry => !entry.hadRecentInput)
|
|
365
|
+
.reduce((sum, entry) => sum + entry.value, 0);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
return result;
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
return {
|
|
372
|
+
content: [{
|
|
373
|
+
type: 'text',
|
|
374
|
+
text: `📊 Web Performance Metrics:\n\n${JSON.stringify(metrics, null, 2)}\n\nNote: Some metrics may not be available depending on page state and browser support.`
|
|
375
|
+
}]
|
|
376
|
+
};
|
|
377
|
+
} catch (error) {
|
|
378
|
+
debugLog(`Error in browser_perf_get_performance_metrics: ${error.message}`);
|
|
379
|
+
return {
|
|
380
|
+
content: [{
|
|
381
|
+
type: 'text',
|
|
382
|
+
text: `❌ Error: ${error.message}`
|
|
383
|
+
}],
|
|
384
|
+
isError: true
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
},
|
|
388
|
+
|
|
389
|
+
browser_perf_start_coverage: async (args) => {
|
|
390
|
+
try {
|
|
391
|
+
if (coverageActive) {
|
|
392
|
+
return {
|
|
393
|
+
content: [{
|
|
394
|
+
type: 'text',
|
|
395
|
+
text: '⚠️ Code coverage is already active.\n\nUse browser_perf_stop_coverage to get results first.'
|
|
396
|
+
}]
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const cdp = await getCDPSession();
|
|
401
|
+
const resetOnNavigation = args.resetOnNavigation !== false;
|
|
402
|
+
|
|
403
|
+
await cdp.send('Profiler.enable');
|
|
404
|
+
await cdp.send('Profiler.startPreciseCoverage', {
|
|
405
|
+
callCount: false,
|
|
406
|
+
detailed: true
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
await cdp.send('DOM.enable');
|
|
410
|
+
await cdp.send('CSS.enable');
|
|
411
|
+
await cdp.send('CSS.startRuleUsageTracking');
|
|
412
|
+
|
|
413
|
+
coverageActive = true;
|
|
414
|
+
|
|
415
|
+
debugLog('Started code coverage tracking');
|
|
416
|
+
|
|
417
|
+
return {
|
|
418
|
+
content: [{
|
|
419
|
+
type: 'text',
|
|
420
|
+
text: `✅ Code coverage started for CSS and JavaScript\n\nResetOnNavigation: ${resetOnNavigation}\n\nUse browser_perf_stop_coverage to get results.`
|
|
421
|
+
}]
|
|
422
|
+
};
|
|
423
|
+
} catch (error) {
|
|
424
|
+
debugLog(`CDP error in browser_perf_start_coverage: ${error.message}`);
|
|
425
|
+
return {
|
|
426
|
+
content: [{
|
|
427
|
+
type: 'text',
|
|
428
|
+
text: `❌ CDP Error: ${error.message}\n\nPossible causes:\n- Browser doesn't support code coverage\n- CDP session disconnected`
|
|
429
|
+
}],
|
|
430
|
+
isError: true
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
},
|
|
434
|
+
|
|
435
|
+
browser_perf_stop_coverage: async (args) => {
|
|
436
|
+
try {
|
|
437
|
+
if (!coverageActive) {
|
|
438
|
+
return {
|
|
439
|
+
content: [{
|
|
440
|
+
type: 'text',
|
|
441
|
+
text: '⚠️ Code coverage is not active.\n\nUse browser_perf_start_coverage to start tracking first.'
|
|
442
|
+
}]
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
const cdp = await getCDPSession();
|
|
447
|
+
|
|
448
|
+
// Get JavaScript coverage
|
|
449
|
+
const { result: jsCoverage } = await cdp.send('Profiler.takePreciseCoverage');
|
|
450
|
+
await cdp.send('Profiler.stopPreciseCoverage');
|
|
451
|
+
await cdp.send('Profiler.disable');
|
|
452
|
+
|
|
453
|
+
// Get CSS coverage
|
|
454
|
+
const { ruleUsage: cssCoverage } = await cdp.send('CSS.stopRuleUsageTracking');
|
|
455
|
+
await cdp.send('CSS.disable');
|
|
456
|
+
await cdp.send('DOM.disable');
|
|
457
|
+
|
|
458
|
+
coverageActive = false;
|
|
459
|
+
|
|
460
|
+
debugLog('Stopped code coverage tracking');
|
|
461
|
+
|
|
462
|
+
// Summarize coverage data
|
|
463
|
+
const jsSummary = jsCoverage.slice(0, 10).map(entry => {
|
|
464
|
+
const totalBytes = entry.functions.reduce((sum, fn) => {
|
|
465
|
+
return sum + fn.ranges.reduce((s, r) => s + (r.endOffset - r.startOffset), 0);
|
|
466
|
+
}, 0);
|
|
467
|
+
const usedBytes = entry.functions.reduce((sum, fn) => {
|
|
468
|
+
return sum + fn.ranges.filter(r => r.count > 0).reduce((s, r) => s + (r.endOffset - r.startOffset), 0);
|
|
469
|
+
}, 0);
|
|
470
|
+
|
|
471
|
+
return {
|
|
472
|
+
url: entry.url,
|
|
473
|
+
usedBytes,
|
|
474
|
+
totalBytes,
|
|
475
|
+
coverage: totalBytes > 0 ? ((usedBytes / totalBytes) * 100).toFixed(2) + '%' : 'N/A'
|
|
476
|
+
};
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
const cssSummary = cssCoverage.slice(0, 10).map(rule => ({
|
|
480
|
+
used: rule.used,
|
|
481
|
+
styleSheetId: rule.styleSheetId,
|
|
482
|
+
startOffset: rule.startOffset,
|
|
483
|
+
endOffset: rule.endOffset
|
|
484
|
+
}));
|
|
485
|
+
|
|
486
|
+
const result = {
|
|
487
|
+
javascript: {
|
|
488
|
+
filesAnalyzed: jsCoverage.length,
|
|
489
|
+
topFiles: jsSummary
|
|
490
|
+
},
|
|
491
|
+
css: {
|
|
492
|
+
rulesAnalyzed: cssCoverage.length,
|
|
493
|
+
topRules: cssSummary.slice(0, 5)
|
|
494
|
+
}
|
|
495
|
+
};
|
|
496
|
+
|
|
497
|
+
return {
|
|
498
|
+
content: [{
|
|
499
|
+
type: 'text',
|
|
500
|
+
text: `✅ Code Coverage Results:\n\n${JSON.stringify(result, null, 2)}\n\nNote: Showing top 10 files. Full coverage data available via CDP.`
|
|
501
|
+
}]
|
|
502
|
+
};
|
|
503
|
+
} catch (error) {
|
|
504
|
+
coverageActive = false;
|
|
505
|
+
debugLog(`CDP error in browser_perf_stop_coverage: ${error.message}`);
|
|
506
|
+
return {
|
|
507
|
+
content: [{
|
|
508
|
+
type: 'text',
|
|
509
|
+
text: `❌ CDP Error: ${error.message}\n\nCoverage tracking has been stopped.`
|
|
510
|
+
}],
|
|
511
|
+
isError: true
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
};
|
|
516
|
+
|
|
517
|
+
module.exports = { definitions, handlers };
|