mcp-web-inspector 0.1.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/LICENSE +21 -0
- package/README.md +1017 -0
- package/dist/evals/evals.d.ts +5 -0
- package/dist/evals/evals.js +41 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +62 -0
- package/dist/requestHandler.d.ts +3 -0
- package/dist/requestHandler.js +53 -0
- package/dist/toolHandler.d.ts +91 -0
- package/dist/toolHandler.js +725 -0
- package/dist/tools/api/base.d.ts +33 -0
- package/dist/tools/api/base.js +49 -0
- package/dist/tools/api/index.d.ts +2 -0
- package/dist/tools/api/index.js +3 -0
- package/dist/tools/api/requests.d.ts +47 -0
- package/dist/tools/api/requests.js +168 -0
- package/dist/tools/browser/base.d.ts +51 -0
- package/dist/tools/browser/base.js +111 -0
- package/dist/tools/browser/cleanSession.d.ts +10 -0
- package/dist/tools/browser/cleanSession.js +42 -0
- package/dist/tools/browser/comparePositions.d.ts +11 -0
- package/dist/tools/browser/comparePositions.js +149 -0
- package/dist/tools/browser/computedStyles.d.ts +11 -0
- package/dist/tools/browser/computedStyles.js +128 -0
- package/dist/tools/browser/console.d.ts +37 -0
- package/dist/tools/browser/console.js +106 -0
- package/dist/tools/browser/elementExists.d.ts +9 -0
- package/dist/tools/browser/elementExists.js +57 -0
- package/dist/tools/browser/elementInspection.d.ts +21 -0
- package/dist/tools/browser/elementInspection.js +151 -0
- package/dist/tools/browser/elementPosition.d.ts +11 -0
- package/dist/tools/browser/elementPosition.js +107 -0
- package/dist/tools/browser/elementVisibility.d.ts +12 -0
- package/dist/tools/browser/elementVisibility.js +224 -0
- package/dist/tools/browser/findByText.d.ts +13 -0
- package/dist/tools/browser/findByText.js +207 -0
- package/dist/tools/browser/getRequestDetails.d.ts +9 -0
- package/dist/tools/browser/getRequestDetails.js +137 -0
- package/dist/tools/browser/getTestIds.d.ts +12 -0
- package/dist/tools/browser/getTestIds.js +148 -0
- package/dist/tools/browser/index.d.ts +7 -0
- package/dist/tools/browser/index.js +7 -0
- package/dist/tools/browser/inspectDom.d.ts +12 -0
- package/dist/tools/browser/inspectDom.js +447 -0
- package/dist/tools/browser/interaction.d.ts +104 -0
- package/dist/tools/browser/interaction.js +259 -0
- package/dist/tools/browser/listNetworkRequests.d.ts +10 -0
- package/dist/tools/browser/listNetworkRequests.js +74 -0
- package/dist/tools/browser/measureElement.d.ts +9 -0
- package/dist/tools/browser/measureElement.js +139 -0
- package/dist/tools/browser/navigation.d.ts +38 -0
- package/dist/tools/browser/navigation.js +109 -0
- package/dist/tools/browser/output.d.ts +11 -0
- package/dist/tools/browser/output.js +29 -0
- package/dist/tools/browser/querySelectorAll.d.ts +12 -0
- package/dist/tools/browser/querySelectorAll.js +201 -0
- package/dist/tools/browser/response.d.ts +29 -0
- package/dist/tools/browser/response.js +67 -0
- package/dist/tools/browser/screenshot.d.ts +16 -0
- package/dist/tools/browser/screenshot.js +70 -0
- package/dist/tools/browser/useragent.d.ts +15 -0
- package/dist/tools/browser/useragent.js +32 -0
- package/dist/tools/browser/visiblePage.d.ts +20 -0
- package/dist/tools/browser/visiblePage.js +170 -0
- package/dist/tools/browser/waitForElement.d.ts +10 -0
- package/dist/tools/browser/waitForElement.js +38 -0
- package/dist/tools/browser/waitForNetworkIdle.d.ts +8 -0
- package/dist/tools/browser/waitForNetworkIdle.js +32 -0
- package/dist/tools/codegen/generator.d.ts +21 -0
- package/dist/tools/codegen/generator.js +158 -0
- package/dist/tools/codegen/index.d.ts +11 -0
- package/dist/tools/codegen/index.js +187 -0
- package/dist/tools/codegen/recorder.d.ts +14 -0
- package/dist/tools/codegen/recorder.js +62 -0
- package/dist/tools/codegen/types.d.ts +28 -0
- package/dist/tools/codegen/types.js +1 -0
- package/dist/tools/common/types.d.ts +17 -0
- package/dist/tools/common/types.js +20 -0
- package/dist/tools/index.d.ts +2 -0
- package/dist/tools/index.js +2 -0
- package/dist/tools.d.ts +557 -0
- package/dist/tools.js +554 -0
- package/dist/types.d.ts +16 -0
- package/dist/types.js +1 -0
- package/package.json +60 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { BrowserToolBase } from './base.js';
|
|
2
|
+
import { getNetworkLog } from '../../toolHandler.js';
|
|
3
|
+
export class GetRequestDetailsTool extends BrowserToolBase {
|
|
4
|
+
async execute(args, context) {
|
|
5
|
+
return this.safeExecute(context, async () => {
|
|
6
|
+
const { index } = args;
|
|
7
|
+
const networkLog = getNetworkLog();
|
|
8
|
+
if (index < 0 || index >= networkLog.length) {
|
|
9
|
+
return {
|
|
10
|
+
content: [{
|
|
11
|
+
type: "text",
|
|
12
|
+
text: `Error: Invalid index ${index}. Valid range: 0-${networkLog.length - 1}`
|
|
13
|
+
}],
|
|
14
|
+
isError: true
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
const req = networkLog[index];
|
|
18
|
+
// Build compact text response
|
|
19
|
+
const lines = [];
|
|
20
|
+
lines.push(`Request Details [${index}]:\n`);
|
|
21
|
+
lines.push(`${req.method} ${req.url}`);
|
|
22
|
+
if (req.status) {
|
|
23
|
+
lines.push(`Status: ${req.status} ${req.statusText || 'OK'} (took ${req.timing}ms)`);
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
lines.push(`Status: Pending (no response yet)`);
|
|
27
|
+
}
|
|
28
|
+
// Calculate sizes
|
|
29
|
+
const requestSize = req.requestData.postData
|
|
30
|
+
? req.requestData.postData.length
|
|
31
|
+
: 0;
|
|
32
|
+
const responseSize = req.responseData?.body
|
|
33
|
+
? req.responseData.body.length
|
|
34
|
+
: 0;
|
|
35
|
+
const formatBytes = (bytes) => {
|
|
36
|
+
if (bytes === 0)
|
|
37
|
+
return '0 bytes';
|
|
38
|
+
if (bytes < 1024)
|
|
39
|
+
return `${bytes} bytes`;
|
|
40
|
+
return `${(bytes / 1024).toFixed(1)}KB`;
|
|
41
|
+
};
|
|
42
|
+
if (responseSize > 0) {
|
|
43
|
+
lines.push(`Size: ${formatBytes(requestSize)} → ${formatBytes(responseSize)}`);
|
|
44
|
+
}
|
|
45
|
+
else if (requestSize > 0) {
|
|
46
|
+
lines.push(`Size: ${formatBytes(requestSize)} →`);
|
|
47
|
+
}
|
|
48
|
+
// Request headers (show important ones)
|
|
49
|
+
const importantRequestHeaders = ['content-type', 'authorization', 'cookie', 'user-agent', 'accept'];
|
|
50
|
+
const reqHeaders = Object.entries(req.requestData.headers)
|
|
51
|
+
.filter(([key]) => importantRequestHeaders.includes(key.toLowerCase()));
|
|
52
|
+
if (reqHeaders.length > 0) {
|
|
53
|
+
lines.push('\nRequest Headers:');
|
|
54
|
+
reqHeaders.forEach(([key, value]) => {
|
|
55
|
+
// Truncate sensitive values
|
|
56
|
+
if (key.toLowerCase() === 'authorization' || key.toLowerCase() === 'cookie') {
|
|
57
|
+
const truncated = value.length > 20
|
|
58
|
+
? value.substring(0, 17) + '...'
|
|
59
|
+
: value;
|
|
60
|
+
lines.push(` ${key}: ${truncated}`);
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
lines.push(` ${key}: ${value}`);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
// Request body
|
|
68
|
+
if (req.requestData.postData) {
|
|
69
|
+
lines.push('\nRequest Body:');
|
|
70
|
+
// Mask passwords in JSON
|
|
71
|
+
let displayData = req.requestData.postData;
|
|
72
|
+
try {
|
|
73
|
+
const parsed = JSON.parse(displayData);
|
|
74
|
+
if (parsed.password)
|
|
75
|
+
parsed.password = '***';
|
|
76
|
+
if (parsed.pass)
|
|
77
|
+
parsed.pass = '***';
|
|
78
|
+
displayData = JSON.stringify(parsed);
|
|
79
|
+
}
|
|
80
|
+
catch (e) {
|
|
81
|
+
// Not JSON, use as is
|
|
82
|
+
}
|
|
83
|
+
// Truncate at 500 chars
|
|
84
|
+
if (displayData.length > 500) {
|
|
85
|
+
lines.push(` ${displayData.substring(0, 500)}`);
|
|
86
|
+
lines.push(` ... [${displayData.length - 500} more chars]`);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
lines.push(` ${displayData}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// Response headers (show important ones)
|
|
93
|
+
const importantResponseHeaders = ['content-type', 'set-cookie', 'cache-control', 'location', 'x-cache'];
|
|
94
|
+
const respHeaders = req.responseData?.headers
|
|
95
|
+
? Object.entries(req.responseData.headers)
|
|
96
|
+
.filter(([key]) => importantResponseHeaders.includes(key.toLowerCase()))
|
|
97
|
+
: [];
|
|
98
|
+
if (respHeaders.length > 0) {
|
|
99
|
+
lines.push('\nResponse Headers:');
|
|
100
|
+
respHeaders.forEach(([key, value]) => {
|
|
101
|
+
// Truncate cookies
|
|
102
|
+
if (key.toLowerCase() === 'set-cookie') {
|
|
103
|
+
const truncated = value.length > 60
|
|
104
|
+
? value.substring(0, 57) + '...'
|
|
105
|
+
: value;
|
|
106
|
+
lines.push(` ${key}: ${truncated}`);
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
lines.push(` ${key}: ${value}`);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
// Response body
|
|
114
|
+
if (req.responseData?.body) {
|
|
115
|
+
lines.push('\nResponse Body (truncated at 500 chars):');
|
|
116
|
+
const body = req.responseData.body;
|
|
117
|
+
if (body.length > 500) {
|
|
118
|
+
lines.push(` ${body.substring(0, 500)}`);
|
|
119
|
+
lines.push(` ... [${body.length - 500} more chars]`);
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
lines.push(` ${body}`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
else if (req.status) {
|
|
126
|
+
lines.push('\nResponse Body: (none or binary data)');
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
content: [{
|
|
130
|
+
type: "text",
|
|
131
|
+
text: lines.join('\n')
|
|
132
|
+
}],
|
|
133
|
+
isError: false
|
|
134
|
+
};
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { BrowserToolBase } from './base.js';
|
|
2
|
+
import { ToolContext, ToolResponse } from '../common/types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Tool for discovering all test identifiers on the page
|
|
5
|
+
* Returns compact text format showing all data-testid, data-test, data-cy attributes
|
|
6
|
+
*/
|
|
7
|
+
export declare class GetTestIdsTool extends BrowserToolBase {
|
|
8
|
+
/**
|
|
9
|
+
* Execute the test ID discovery tool
|
|
10
|
+
*/
|
|
11
|
+
execute(args: any, context: ToolContext): Promise<ToolResponse>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { BrowserToolBase } from './base.js';
|
|
2
|
+
import { createSuccessResponse, createErrorResponse } from '../common/types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Tool for discovering all test identifiers on the page
|
|
5
|
+
* Returns compact text format showing all data-testid, data-test, data-cy attributes
|
|
6
|
+
*/
|
|
7
|
+
export class GetTestIdsTool extends BrowserToolBase {
|
|
8
|
+
/**
|
|
9
|
+
* Execute the test ID discovery tool
|
|
10
|
+
*/
|
|
11
|
+
async execute(args, context) {
|
|
12
|
+
return this.safeExecute(context, async (page) => {
|
|
13
|
+
const attributes = args.attributes
|
|
14
|
+
? args.attributes.split(',').map((a) => a.trim())
|
|
15
|
+
: ['data-testid', 'data-test', 'data-cy'];
|
|
16
|
+
const showAll = args.showAll === true;
|
|
17
|
+
try {
|
|
18
|
+
// Discover all test IDs on the page
|
|
19
|
+
const discoveryData = await page.evaluate((attrs) => {
|
|
20
|
+
const byAttribute = {};
|
|
21
|
+
const duplicates = {};
|
|
22
|
+
let totalCount = 0;
|
|
23
|
+
attrs.forEach((attr) => {
|
|
24
|
+
const elements = document.querySelectorAll(`[${attr}]`);
|
|
25
|
+
const values = [];
|
|
26
|
+
const counts = {};
|
|
27
|
+
elements.forEach((el) => {
|
|
28
|
+
const value = el.getAttribute(attr);
|
|
29
|
+
if (value) {
|
|
30
|
+
values.push(value);
|
|
31
|
+
totalCount++;
|
|
32
|
+
// Track duplicates
|
|
33
|
+
counts[value] = (counts[value] || 0) + 1;
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
if (values.length > 0) {
|
|
37
|
+
byAttribute[attr] = values;
|
|
38
|
+
// Store duplicates (values that appear more than once)
|
|
39
|
+
const attrDuplicates = {};
|
|
40
|
+
Object.entries(counts).forEach(([value, count]) => {
|
|
41
|
+
if (count > 1) {
|
|
42
|
+
attrDuplicates[value] = count;
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
if (Object.keys(attrDuplicates).length > 0) {
|
|
46
|
+
duplicates[attr] = attrDuplicates;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
return {
|
|
51
|
+
totalCount,
|
|
52
|
+
byAttribute,
|
|
53
|
+
duplicates,
|
|
54
|
+
};
|
|
55
|
+
}, attributes);
|
|
56
|
+
// Format compact text output
|
|
57
|
+
const lines = [];
|
|
58
|
+
if (discoveryData.totalCount === 0) {
|
|
59
|
+
lines.push('Found 0 test IDs');
|
|
60
|
+
lines.push('');
|
|
61
|
+
lines.push('⚠ No test ID attributes found on this page.');
|
|
62
|
+
lines.push('');
|
|
63
|
+
lines.push('Searched for:');
|
|
64
|
+
attributes.forEach((attr) => {
|
|
65
|
+
lines.push(` • ${attr}`);
|
|
66
|
+
});
|
|
67
|
+
lines.push('');
|
|
68
|
+
lines.push('Suggestions:');
|
|
69
|
+
lines.push(' - Use inspect_dom to see page structure');
|
|
70
|
+
lines.push(' - Consider adding test IDs to interactive elements');
|
|
71
|
+
lines.push(' - Example: <button data-testid="submit-button">Submit</button>');
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
lines.push(`Found ${discoveryData.totalCount} test ID${discoveryData.totalCount > 1 ? 's' : ''}:`);
|
|
75
|
+
lines.push('');
|
|
76
|
+
// Group by attribute type
|
|
77
|
+
Object.entries(discoveryData.byAttribute).forEach(([attr, values]) => {
|
|
78
|
+
lines.push(`${attr} (${values.length}):`);
|
|
79
|
+
// Format values in a compact way
|
|
80
|
+
if (showAll || values.length <= 10) {
|
|
81
|
+
// Show all if requested or if 10 or fewer
|
|
82
|
+
lines.push(` ${values.join(', ')}`);
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
// Show first 8, then indicate more
|
|
86
|
+
const shown = values.slice(0, 8);
|
|
87
|
+
const remaining = values.length - 8;
|
|
88
|
+
lines.push(` ${shown.join(', ')},`);
|
|
89
|
+
lines.push(` ... and ${remaining} more`);
|
|
90
|
+
lines.push(` 💡 Use showAll: true to see all ${values.length} test IDs`);
|
|
91
|
+
}
|
|
92
|
+
lines.push('');
|
|
93
|
+
});
|
|
94
|
+
// Add duplicate warnings
|
|
95
|
+
const hasDuplicates = Object.keys(discoveryData.duplicates).length > 0;
|
|
96
|
+
if (hasDuplicates) {
|
|
97
|
+
lines.push('⚠ Warning: Duplicate test IDs found (test IDs should be unique):');
|
|
98
|
+
lines.push('');
|
|
99
|
+
Object.entries(discoveryData.duplicates).forEach(([attr, dups]) => {
|
|
100
|
+
Object.entries(dups).forEach(([value, count]) => {
|
|
101
|
+
lines.push(` ${attr}: "${value}" appears ${count} times`);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
lines.push('');
|
|
105
|
+
lines.push('⚠ Impact of Duplicate Test IDs:');
|
|
106
|
+
lines.push(' - Flaky tests (selectors match multiple elements)');
|
|
107
|
+
lines.push(' - Ambiguous interactions (which element to click?)');
|
|
108
|
+
lines.push(' - Test automation will fail or behave unpredictably');
|
|
109
|
+
lines.push('');
|
|
110
|
+
lines.push('🔧 How to Fix:');
|
|
111
|
+
lines.push(' 1. Use query_selector_all to locate all duplicates');
|
|
112
|
+
// Add example for the first duplicate
|
|
113
|
+
const firstDupAttr = Object.keys(discoveryData.duplicates)[0];
|
|
114
|
+
const firstDupValue = Object.keys(discoveryData.duplicates[firstDupAttr])[0];
|
|
115
|
+
const firstDupCount = discoveryData.duplicates[firstDupAttr][firstDupValue];
|
|
116
|
+
if (firstDupAttr === 'data-testid') {
|
|
117
|
+
lines.push(` query_selector_all({ selector: "testid:${firstDupValue}" })`);
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
lines.push(` query_selector_all({ selector: "[${firstDupAttr}='${firstDupValue}']" })`);
|
|
121
|
+
}
|
|
122
|
+
lines.push(' 2. Identify which elements should keep the test ID');
|
|
123
|
+
lines.push(' 3. Rename duplicates to be unique and descriptive');
|
|
124
|
+
lines.push(` Example: "${firstDupValue}" → "${firstDupValue}-primary", "${firstDupValue}-mobile"`);
|
|
125
|
+
lines.push(' 4. If one is hidden/unused, consider removing it entirely');
|
|
126
|
+
lines.push('');
|
|
127
|
+
lines.push('💡 Best Practice: Test IDs must be unique across the entire page');
|
|
128
|
+
lines.push('');
|
|
129
|
+
}
|
|
130
|
+
// Add usage tip
|
|
131
|
+
lines.push('💡 Tip: Use these test IDs with selector shortcuts:');
|
|
132
|
+
const firstAttr = Object.keys(discoveryData.byAttribute)[0];
|
|
133
|
+
const firstValue = discoveryData.byAttribute[firstAttr][0];
|
|
134
|
+
if (firstAttr === 'data-testid') {
|
|
135
|
+
lines.push(` testid:${firstValue} → [data-testid="${firstValue}"]`);
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
lines.push(` ${firstAttr}:${firstValue} → [${firstAttr}="${firstValue}"]`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return createSuccessResponse(lines.join('\n'));
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
return createErrorResponse(`Failed to discover test IDs: ${error.message}`);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { BrowserToolBase } from './base.js';
|
|
2
|
+
import { ToolContext, ToolResponse } from '../common/types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Tool for progressive DOM inspection with semantic filtering and spatial layout
|
|
5
|
+
* This is the PRIMARY tool for understanding page structure
|
|
6
|
+
*/
|
|
7
|
+
export declare class InspectDomTool extends BrowserToolBase {
|
|
8
|
+
/**
|
|
9
|
+
* Execute the DOM inspection tool
|
|
10
|
+
*/
|
|
11
|
+
execute(args: any, context: ToolContext): Promise<ToolResponse>;
|
|
12
|
+
}
|