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,447 @@
|
|
|
1
|
+
import { BrowserToolBase } from './base.js';
|
|
2
|
+
import { createSuccessResponse, createErrorResponse } 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 class InspectDomTool extends BrowserToolBase {
|
|
8
|
+
/**
|
|
9
|
+
* Execute the DOM inspection tool
|
|
10
|
+
*/
|
|
11
|
+
async execute(args, context) {
|
|
12
|
+
return this.safeExecute(context, async (page) => {
|
|
13
|
+
const selector = args.selector ? this.normalizeSelector(args.selector) : 'body';
|
|
14
|
+
const includeHidden = args.includeHidden ?? false;
|
|
15
|
+
const maxChildren = args.maxChildren ?? 20;
|
|
16
|
+
const maxDepth = args.maxDepth ?? 5;
|
|
17
|
+
try {
|
|
18
|
+
// Get the target element and its semantic children
|
|
19
|
+
const inspectionData = await page.evaluate(({ sel, hidden, max, maxDepth }) => {
|
|
20
|
+
const target = document.querySelector(sel);
|
|
21
|
+
if (!target) {
|
|
22
|
+
return { error: `Element not found: ${sel}` };
|
|
23
|
+
}
|
|
24
|
+
// Get element info
|
|
25
|
+
const getElementInfo = (el) => {
|
|
26
|
+
const rect = el.getBoundingClientRect();
|
|
27
|
+
const styles = window.getComputedStyle(el);
|
|
28
|
+
const isVisible = styles.display !== 'none' &&
|
|
29
|
+
styles.visibility !== 'hidden' &&
|
|
30
|
+
parseFloat(styles.opacity) > 0 &&
|
|
31
|
+
rect.width > 0 &&
|
|
32
|
+
rect.height > 0;
|
|
33
|
+
return {
|
|
34
|
+
rect: {
|
|
35
|
+
x: Math.round(rect.x),
|
|
36
|
+
y: Math.round(rect.y),
|
|
37
|
+
width: Math.round(rect.width),
|
|
38
|
+
height: Math.round(rect.height),
|
|
39
|
+
},
|
|
40
|
+
isVisible,
|
|
41
|
+
styles: {
|
|
42
|
+
display: styles.display,
|
|
43
|
+
visibility: styles.visibility,
|
|
44
|
+
opacity: parseFloat(styles.opacity),
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
// Check if element is semantic (worth showing)
|
|
49
|
+
const isSemanticElement = (el) => {
|
|
50
|
+
const tag = el.tagName.toLowerCase();
|
|
51
|
+
// Semantic HTML tags
|
|
52
|
+
const semanticTags = new Set([
|
|
53
|
+
'header',
|
|
54
|
+
'nav',
|
|
55
|
+
'main',
|
|
56
|
+
'article',
|
|
57
|
+
'section',
|
|
58
|
+
'aside',
|
|
59
|
+
'footer',
|
|
60
|
+
'form',
|
|
61
|
+
'button',
|
|
62
|
+
'input',
|
|
63
|
+
'select',
|
|
64
|
+
'textarea',
|
|
65
|
+
'a',
|
|
66
|
+
'h1',
|
|
67
|
+
'h2',
|
|
68
|
+
'h3',
|
|
69
|
+
'h4',
|
|
70
|
+
'h5',
|
|
71
|
+
'h6',
|
|
72
|
+
'p',
|
|
73
|
+
'ul',
|
|
74
|
+
'ol',
|
|
75
|
+
'li',
|
|
76
|
+
'table',
|
|
77
|
+
'img',
|
|
78
|
+
'video',
|
|
79
|
+
'audio',
|
|
80
|
+
'svg',
|
|
81
|
+
'canvas',
|
|
82
|
+
'iframe',
|
|
83
|
+
'dialog',
|
|
84
|
+
'details',
|
|
85
|
+
'summary',
|
|
86
|
+
]);
|
|
87
|
+
if (semanticTags.has(tag))
|
|
88
|
+
return true;
|
|
89
|
+
// Elements with test IDs
|
|
90
|
+
if (el.hasAttribute('data-testid') ||
|
|
91
|
+
el.hasAttribute('data-test') ||
|
|
92
|
+
el.hasAttribute('data-cy')) {
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
// Elements with ARIA roles
|
|
96
|
+
if (el.hasAttribute('role'))
|
|
97
|
+
return true;
|
|
98
|
+
// Interactive elements
|
|
99
|
+
if (el.hasAttribute('onclick') || el.hasAttribute('contenteditable'))
|
|
100
|
+
return true;
|
|
101
|
+
// Containers with significant direct text (>10 chars)
|
|
102
|
+
const directText = Array.from(el.childNodes)
|
|
103
|
+
.filter((node) => node.nodeType === Node.TEXT_NODE)
|
|
104
|
+
.map((node) => node.textContent?.trim() || '')
|
|
105
|
+
.join(' ')
|
|
106
|
+
.trim();
|
|
107
|
+
if (directText.length > 10)
|
|
108
|
+
return true;
|
|
109
|
+
return false;
|
|
110
|
+
};
|
|
111
|
+
// Get selector for element
|
|
112
|
+
const getSelector = (el) => {
|
|
113
|
+
// Prefer test IDs
|
|
114
|
+
if (el.hasAttribute('data-testid')) {
|
|
115
|
+
return `[data-testid="${el.getAttribute('data-testid')}"]`;
|
|
116
|
+
}
|
|
117
|
+
if (el.hasAttribute('data-test')) {
|
|
118
|
+
return `[data-test="${el.getAttribute('data-test')}"]`;
|
|
119
|
+
}
|
|
120
|
+
if (el.hasAttribute('data-cy')) {
|
|
121
|
+
return `[data-cy="${el.getAttribute('data-cy')}"]`;
|
|
122
|
+
}
|
|
123
|
+
// Use ID if available
|
|
124
|
+
if (el.id) {
|
|
125
|
+
return `#${el.id}`;
|
|
126
|
+
}
|
|
127
|
+
// Use class + tag for common patterns
|
|
128
|
+
const tag = el.tagName.toLowerCase();
|
|
129
|
+
const classes = Array.from(el.classList)
|
|
130
|
+
.slice(0, 2)
|
|
131
|
+
.join('.');
|
|
132
|
+
if (classes) {
|
|
133
|
+
return `${tag}.${classes}`;
|
|
134
|
+
}
|
|
135
|
+
return tag;
|
|
136
|
+
};
|
|
137
|
+
// Check if element is interactive
|
|
138
|
+
const isInteractive = (el) => {
|
|
139
|
+
const tag = el.tagName.toLowerCase();
|
|
140
|
+
const interactiveTags = new Set(['button', 'a', 'input', 'select', 'textarea']);
|
|
141
|
+
return (interactiveTags.has(tag) ||
|
|
142
|
+
el.hasAttribute('onclick') ||
|
|
143
|
+
el.hasAttribute('contenteditable') ||
|
|
144
|
+
el.getAttribute('role') === 'button');
|
|
145
|
+
};
|
|
146
|
+
// Get target element info
|
|
147
|
+
const targetInfo = getElementInfo(target);
|
|
148
|
+
// Get all immediate children
|
|
149
|
+
const allChildren = Array.from(target.children);
|
|
150
|
+
const semanticChildren = [];
|
|
151
|
+
let skippedWrappers = 0;
|
|
152
|
+
// Count elements for summary
|
|
153
|
+
const elementCounts = {};
|
|
154
|
+
const interactiveCounts = {};
|
|
155
|
+
// Helper to count elements in entire subtree (for overview)
|
|
156
|
+
const countElementsInTree = (root) => {
|
|
157
|
+
const counts = {};
|
|
158
|
+
const interactiveCounts = {};
|
|
159
|
+
const traverse = (el) => {
|
|
160
|
+
const tag = el.tagName.toLowerCase();
|
|
161
|
+
counts[tag] = (counts[tag] || 0) + 1;
|
|
162
|
+
if (isInteractive(el)) {
|
|
163
|
+
interactiveCounts[tag] = (interactiveCounts[tag] || 0) + 1;
|
|
164
|
+
}
|
|
165
|
+
Array.from(el.children).forEach(traverse);
|
|
166
|
+
};
|
|
167
|
+
traverse(root);
|
|
168
|
+
return { counts, interactiveCounts };
|
|
169
|
+
};
|
|
170
|
+
// Recursive helper to collect semantic children, drilling through non-semantic wrappers
|
|
171
|
+
const collectSemanticChildren = (elements, depth = 0) => {
|
|
172
|
+
for (const child of elements) {
|
|
173
|
+
const childInfo = getElementInfo(child);
|
|
174
|
+
const tag = child.tagName.toLowerCase();
|
|
175
|
+
// Count all immediate children for summary (depth 0 only)
|
|
176
|
+
if (depth === 0) {
|
|
177
|
+
elementCounts[tag] = (elementCounts[tag] || 0) + 1;
|
|
178
|
+
}
|
|
179
|
+
// Skip hidden elements unless includeHidden is true
|
|
180
|
+
if (!hidden && !childInfo.isVisible) {
|
|
181
|
+
if (depth === 0)
|
|
182
|
+
skippedWrappers++;
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
// Check if this element is semantic
|
|
186
|
+
const isSemantic = isSemanticElement(child);
|
|
187
|
+
if (isSemantic) {
|
|
188
|
+
// This is a semantic element - add it to the list and stop drilling
|
|
189
|
+
const text = child.textContent?.trim().slice(0, 100) || '';
|
|
190
|
+
const testId = child.getAttribute('data-testid') ||
|
|
191
|
+
child.getAttribute('data-test') ||
|
|
192
|
+
child.getAttribute('data-cy') ||
|
|
193
|
+
undefined;
|
|
194
|
+
const semanticChild = {
|
|
195
|
+
tag: child.tagName.toLowerCase(),
|
|
196
|
+
selector: getSelector(child),
|
|
197
|
+
testId,
|
|
198
|
+
role: child.getAttribute('role') || undefined,
|
|
199
|
+
text,
|
|
200
|
+
position: childInfo.rect,
|
|
201
|
+
isVisible: childInfo.isVisible,
|
|
202
|
+
isInteractive: isInteractive(child),
|
|
203
|
+
childCount: child.children.length,
|
|
204
|
+
};
|
|
205
|
+
semanticChildren.push(semanticChild);
|
|
206
|
+
// Count this semantic element in interactiveCounts if it's interactive
|
|
207
|
+
if (isInteractive(child)) {
|
|
208
|
+
interactiveCounts[tag] = (interactiveCounts[tag] || 0) + 1;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
else if (depth < maxDepth) {
|
|
212
|
+
// This is a non-semantic wrapper - drill through it to find semantic children
|
|
213
|
+
if (depth === 0)
|
|
214
|
+
skippedWrappers++;
|
|
215
|
+
// Recursively look for semantic children inside this wrapper
|
|
216
|
+
collectSemanticChildren(Array.from(child.children), depth + 1);
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
// Hit max depth - count as skipped wrapper
|
|
220
|
+
if (depth === 0)
|
|
221
|
+
skippedWrappers++;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
// Start collecting semantic children from immediate children
|
|
226
|
+
collectSemanticChildren(allChildren);
|
|
227
|
+
// For body/main containers, also count elements in entire tree
|
|
228
|
+
const targetTag = target.tagName.toLowerCase();
|
|
229
|
+
const isTopLevelContainer = targetTag === 'body' || sel.includes('main-layout') || sel.includes('main');
|
|
230
|
+
let treeCounts = null;
|
|
231
|
+
if (isTopLevelContainer) {
|
|
232
|
+
const treeData = countElementsInTree(target);
|
|
233
|
+
const testIdCount = target.querySelectorAll('[data-testid], [data-test], [data-cy]').length;
|
|
234
|
+
treeCounts = {
|
|
235
|
+
...treeData,
|
|
236
|
+
testIdCount,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
// Limit children shown
|
|
240
|
+
const totalSemantic = semanticChildren.length;
|
|
241
|
+
const shownChildren = semanticChildren.slice(0, max);
|
|
242
|
+
const omittedCount = Math.max(0, totalSemantic - max);
|
|
243
|
+
// Detect layout pattern
|
|
244
|
+
let layoutPattern = 'unknown';
|
|
245
|
+
if (shownChildren.length >= 2) {
|
|
246
|
+
const first = shownChildren[0].position;
|
|
247
|
+
const second = shownChildren[1].position;
|
|
248
|
+
const horizontalGap = Math.abs(second.x - (first.x + first.width));
|
|
249
|
+
const verticalGap = Math.abs(second.y - (first.y + first.height));
|
|
250
|
+
if (horizontalGap < 50 && verticalGap > 20) {
|
|
251
|
+
layoutPattern = 'vertical';
|
|
252
|
+
}
|
|
253
|
+
else if (verticalGap < 50 && horizontalGap > 20) {
|
|
254
|
+
layoutPattern = 'horizontal';
|
|
255
|
+
}
|
|
256
|
+
else if (horizontalGap < 50 && verticalGap < 50) {
|
|
257
|
+
layoutPattern = 'grid';
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return {
|
|
261
|
+
target: {
|
|
262
|
+
tag: target.tagName.toLowerCase(),
|
|
263
|
+
selector: getSelector(target),
|
|
264
|
+
position: targetInfo.rect,
|
|
265
|
+
isVisible: targetInfo.isVisible,
|
|
266
|
+
},
|
|
267
|
+
children: shownChildren,
|
|
268
|
+
stats: {
|
|
269
|
+
totalChildren: allChildren.length,
|
|
270
|
+
semanticCount: totalSemantic,
|
|
271
|
+
shownCount: shownChildren.length,
|
|
272
|
+
omittedCount,
|
|
273
|
+
skippedWrappers,
|
|
274
|
+
},
|
|
275
|
+
elementCounts,
|
|
276
|
+
interactiveCounts,
|
|
277
|
+
treeCounts,
|
|
278
|
+
layoutPattern,
|
|
279
|
+
};
|
|
280
|
+
}, { sel: selector, hidden: includeHidden, max: maxChildren, maxDepth });
|
|
281
|
+
// Check for errors from evaluate
|
|
282
|
+
if ('error' in inspectionData) {
|
|
283
|
+
return createErrorResponse(inspectionData.error);
|
|
284
|
+
}
|
|
285
|
+
// Format compact text output
|
|
286
|
+
const lines = [];
|
|
287
|
+
const { target, children, stats, layoutPattern, elementCounts, interactiveCounts, treeCounts } = inspectionData;
|
|
288
|
+
// Header
|
|
289
|
+
lines.push(`DOM Inspection: <${target.tag}${target.selector ? ' ' + target.selector : ''}>`);
|
|
290
|
+
lines.push(`@ (${target.position.x},${target.position.y}) ${target.position.width}x${target.position.height}px`);
|
|
291
|
+
lines.push('');
|
|
292
|
+
// Add page/section overview for top-level containers
|
|
293
|
+
if (treeCounts) {
|
|
294
|
+
lines.push('Page Overview:');
|
|
295
|
+
// Show semantic structure counts
|
|
296
|
+
const semanticStructure = ['header', 'nav', 'main', 'article', 'section', 'aside', 'footer'];
|
|
297
|
+
const structureCounts = semanticStructure
|
|
298
|
+
.filter(tag => (treeCounts.counts[tag] || 0) > 0)
|
|
299
|
+
.map(tag => `${treeCounts.counts[tag]} ${tag}${treeCounts.counts[tag] > 1 ? 's' : ''}`)
|
|
300
|
+
.join(', ');
|
|
301
|
+
if (structureCounts) {
|
|
302
|
+
lines.push(` Structure: ${structureCounts}`);
|
|
303
|
+
}
|
|
304
|
+
// Show interactive element counts
|
|
305
|
+
const interactiveTypes = ['button', 'a', 'input', 'select', 'textarea'];
|
|
306
|
+
const interactiveSummary = interactiveTypes
|
|
307
|
+
.filter(tag => (treeCounts.interactiveCounts[tag] || 0) > 0)
|
|
308
|
+
.map(tag => {
|
|
309
|
+
const count = treeCounts.interactiveCounts[tag];
|
|
310
|
+
const label = tag === 'a' ? 'link' : tag;
|
|
311
|
+
return `${count} ${label}${count > 1 ? 's' : ''}`;
|
|
312
|
+
})
|
|
313
|
+
.join(', ');
|
|
314
|
+
if (interactiveSummary) {
|
|
315
|
+
lines.push(` Interactive: ${interactiveSummary}`);
|
|
316
|
+
}
|
|
317
|
+
// Show form counts
|
|
318
|
+
const formCount = treeCounts.counts.form || 0;
|
|
319
|
+
const inputCount = (treeCounts.counts.input || 0) + (treeCounts.counts.select || 0) + (treeCounts.counts.textarea || 0);
|
|
320
|
+
if (formCount > 0) {
|
|
321
|
+
lines.push(` Forms: ${formCount} form${formCount > 1 ? 's' : ''} with ${inputCount} input${inputCount !== 1 ? 's' : ''}`);
|
|
322
|
+
}
|
|
323
|
+
// Show test coverage
|
|
324
|
+
if (treeCounts.testIdCount && treeCounts.testIdCount > 0) {
|
|
325
|
+
lines.push(` Test Coverage: ${treeCounts.testIdCount} element${treeCounts.testIdCount > 1 ? 's' : ''} with test IDs`);
|
|
326
|
+
}
|
|
327
|
+
lines.push('');
|
|
328
|
+
}
|
|
329
|
+
// Children summary
|
|
330
|
+
if (stats.semanticCount === 0) {
|
|
331
|
+
lines.push(`Children (0 semantic, skipped ${stats.skippedWrappers} wrapper divs):`);
|
|
332
|
+
lines.push('');
|
|
333
|
+
// Show interactive element summary if available
|
|
334
|
+
const hasInteractive = Object.keys(interactiveCounts).length > 0;
|
|
335
|
+
if (hasInteractive) {
|
|
336
|
+
lines.push('Interactive Elements Found:');
|
|
337
|
+
const interactiveTypes = ['button', 'a', 'input', 'select', 'textarea'];
|
|
338
|
+
interactiveTypes.forEach(tag => {
|
|
339
|
+
const count = interactiveCounts[tag] || 0;
|
|
340
|
+
if (count > 0) {
|
|
341
|
+
const label = tag === 'a' ? 'link' : tag;
|
|
342
|
+
lines.push(` • ${count} ${label}${count > 1 ? 's' : ''}`);
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
lines.push('');
|
|
346
|
+
lines.push(`💡 Tip: Use maxChildren parameter or drill down with specific selectors (e.g., "button", "a")`);
|
|
347
|
+
lines.push(` to inspect these elements. They were skipped because they lack test IDs or semantic containers.`);
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
lines.push('⚠ No semantic or interactive elements found at this level.');
|
|
351
|
+
lines.push('');
|
|
352
|
+
lines.push('The page uses generic <div> wrappers without semantic HTML, test IDs, or ARIA roles.');
|
|
353
|
+
}
|
|
354
|
+
lines.push('');
|
|
355
|
+
lines.push('Suggestions:');
|
|
356
|
+
lines.push(`1. Use get_visible_html({ selector: "${args.selector || 'body'}" }) to see raw HTML`);
|
|
357
|
+
lines.push('2. Look for interactive elements by class/id (e.g., .button, #submit-btn)');
|
|
358
|
+
lines.push('3. Recommend adding data-testid attributes for better testability');
|
|
359
|
+
lines.push('');
|
|
360
|
+
lines.push('To improve this page\'s structure, consider:');
|
|
361
|
+
lines.push(' - Adding semantic HTML: <header>, <main>, <nav>, <button>');
|
|
362
|
+
lines.push(' - Adding test IDs: data-testid="submit-button"');
|
|
363
|
+
lines.push(' - Adding ARIA roles: role="button", role="navigation"');
|
|
364
|
+
// Add drill-down suggestions when Page Overview shows interactive but Children shows none
|
|
365
|
+
if (treeCounts && Object.keys(interactiveCounts).length === 0 && Object.keys(treeCounts.interactiveCounts).length > 0) {
|
|
366
|
+
lines.push('');
|
|
367
|
+
lines.push('💡 Try drilling down to find interactive elements:');
|
|
368
|
+
const currentSelector = args.selector || 'body';
|
|
369
|
+
// Suggest specific selectors based on what's in the tree
|
|
370
|
+
if (treeCounts.interactiveCounts.button && treeCounts.interactiveCounts.button > 0) {
|
|
371
|
+
lines.push(` inspect_dom({ selector: "${currentSelector} button" })`);
|
|
372
|
+
}
|
|
373
|
+
if (treeCounts.interactiveCounts.input && treeCounts.interactiveCounts.input > 0) {
|
|
374
|
+
lines.push(` inspect_dom({ selector: "${currentSelector} input" })`);
|
|
375
|
+
}
|
|
376
|
+
if (treeCounts.interactiveCounts.a && treeCounts.interactiveCounts.a > 0) {
|
|
377
|
+
lines.push(` inspect_dom({ selector: "${currentSelector} a" })`);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
else {
|
|
382
|
+
lines.push(`Children (${stats.shownCount} of ${stats.semanticCount}${stats.skippedWrappers > 0 ? `, skipped ${stats.skippedWrappers} wrappers` : ''}):`);
|
|
383
|
+
lines.push('');
|
|
384
|
+
// List children
|
|
385
|
+
children.forEach((child, index) => {
|
|
386
|
+
const prefix = `[${index}]`;
|
|
387
|
+
const tag = child.testId
|
|
388
|
+
? `<${child.tag} data-testid="${child.testId}">`
|
|
389
|
+
: `<${child.tag}${child.selector ? ' ' + child.selector : ''}>`;
|
|
390
|
+
const roleInfo = child.role ? ` | ${child.role}` : '';
|
|
391
|
+
lines.push(`${prefix} ${tag}${roleInfo}`);
|
|
392
|
+
// Position
|
|
393
|
+
lines.push(` @ (${child.position.x},${child.position.y}) ${child.position.width}x${child.position.height}px`);
|
|
394
|
+
// Calculate offset from previous sibling
|
|
395
|
+
if (index > 0) {
|
|
396
|
+
const prev = children[index - 1];
|
|
397
|
+
const horizontalGap = child.position.x - (prev.position.x + prev.position.width);
|
|
398
|
+
const verticalGap = child.position.y - (prev.position.y + prev.position.height);
|
|
399
|
+
if (Math.abs(horizontalGap) < 50 && verticalGap > 10) {
|
|
400
|
+
// Vertical layout
|
|
401
|
+
lines.push(` gap from [${index - 1}]: ↓${Math.round(verticalGap)}px (vertical layout)`);
|
|
402
|
+
}
|
|
403
|
+
else if (Math.abs(verticalGap) < 50 && horizontalGap > 10) {
|
|
404
|
+
// Horizontal layout
|
|
405
|
+
lines.push(` gap from [${index - 1}]: →${Math.round(horizontalGap)}px (horizontal layout)`);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
// Text content
|
|
409
|
+
if (child.text) {
|
|
410
|
+
lines.push(` "${child.text}"`);
|
|
411
|
+
}
|
|
412
|
+
// Status symbols
|
|
413
|
+
const statusParts = [];
|
|
414
|
+
statusParts.push(child.isVisible ? '✓ visible' : '✗ hidden');
|
|
415
|
+
if (child.isInteractive)
|
|
416
|
+
statusParts.push('⚡ interactive');
|
|
417
|
+
if (child.childCount > 0)
|
|
418
|
+
statusParts.push(`${child.childCount} children`);
|
|
419
|
+
if (child.testId)
|
|
420
|
+
statusParts.push('has test ID');
|
|
421
|
+
lines.push(` ${statusParts.join(', ')}`);
|
|
422
|
+
lines.push('');
|
|
423
|
+
});
|
|
424
|
+
// Omitted elements notice
|
|
425
|
+
if (stats.omittedCount > 0) {
|
|
426
|
+
lines.push(`... ${stats.omittedCount} more semantic children omitted (use maxChildren to show more)`);
|
|
427
|
+
lines.push('');
|
|
428
|
+
}
|
|
429
|
+
// Layout pattern
|
|
430
|
+
if (layoutPattern !== 'unknown') {
|
|
431
|
+
lines.push(`Layout: ${layoutPattern}`);
|
|
432
|
+
}
|
|
433
|
+
// Mixed structure tip
|
|
434
|
+
if (stats.skippedWrappers > 0 && stats.semanticCount > 0) {
|
|
435
|
+
lines.push('');
|
|
436
|
+
lines.push(`💡 Tip: Some elements found, but ${stats.skippedWrappers} wrapper divs were skipped.`);
|
|
437
|
+
lines.push(' Consider adding test IDs to key elements for easier selection.');
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
return createSuccessResponse(lines.join('\n'));
|
|
441
|
+
}
|
|
442
|
+
catch (error) {
|
|
443
|
+
return createErrorResponse(`Failed to inspect DOM: ${error.message}`);
|
|
444
|
+
}
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { BrowserToolBase } from './base.js';
|
|
2
|
+
import { ToolContext, ToolResponse } from '../common/types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Tool for clicking elements on the page
|
|
5
|
+
*/
|
|
6
|
+
export declare class ClickTool extends BrowserToolBase {
|
|
7
|
+
/**
|
|
8
|
+
* Execute the click tool
|
|
9
|
+
*/
|
|
10
|
+
execute(args: any, context: ToolContext): Promise<ToolResponse>;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Tool for clicking a link and switching to the new tab
|
|
14
|
+
*/
|
|
15
|
+
export declare class ClickAndSwitchTabTool extends BrowserToolBase {
|
|
16
|
+
/**
|
|
17
|
+
* Execute the click and switch tab tool
|
|
18
|
+
*/
|
|
19
|
+
execute(args: any, context: ToolContext): Promise<ToolResponse>;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Tool for clicking elements inside iframes
|
|
23
|
+
*/
|
|
24
|
+
export declare class IframeClickTool extends BrowserToolBase {
|
|
25
|
+
/**
|
|
26
|
+
* Execute the iframe click tool
|
|
27
|
+
*/
|
|
28
|
+
execute(args: any, context: ToolContext): Promise<ToolResponse>;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Tool for filling elements inside iframes
|
|
32
|
+
*/
|
|
33
|
+
export declare class IframeFillTool extends BrowserToolBase {
|
|
34
|
+
/**
|
|
35
|
+
* Execute the iframe fill tool
|
|
36
|
+
*/
|
|
37
|
+
execute(args: any, context: ToolContext): Promise<ToolResponse>;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Tool for filling form fields
|
|
41
|
+
*/
|
|
42
|
+
export declare class FillTool extends BrowserToolBase {
|
|
43
|
+
/**
|
|
44
|
+
* Execute the fill tool
|
|
45
|
+
*/
|
|
46
|
+
execute(args: any, context: ToolContext): Promise<ToolResponse>;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Tool for selecting options from dropdown menus
|
|
50
|
+
*/
|
|
51
|
+
export declare class SelectTool extends BrowserToolBase {
|
|
52
|
+
/**
|
|
53
|
+
* Execute the select tool
|
|
54
|
+
*/
|
|
55
|
+
execute(args: any, context: ToolContext): Promise<ToolResponse>;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Tool for hovering over elements
|
|
59
|
+
*/
|
|
60
|
+
export declare class HoverTool extends BrowserToolBase {
|
|
61
|
+
/**
|
|
62
|
+
* Execute the hover tool
|
|
63
|
+
*/
|
|
64
|
+
execute(args: any, context: ToolContext): Promise<ToolResponse>;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Tool for uploading files
|
|
68
|
+
*/
|
|
69
|
+
export declare class UploadFileTool extends BrowserToolBase {
|
|
70
|
+
/**
|
|
71
|
+
* Execute the upload file tool
|
|
72
|
+
*/
|
|
73
|
+
execute(args: any, context: ToolContext): Promise<ToolResponse>;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Tool for executing JavaScript in the browser
|
|
77
|
+
*/
|
|
78
|
+
export declare class EvaluateTool extends BrowserToolBase {
|
|
79
|
+
/**
|
|
80
|
+
* Execute the evaluate tool
|
|
81
|
+
*/
|
|
82
|
+
execute(args: any, context: ToolContext): Promise<ToolResponse>;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Tool for dragging elements on the page
|
|
86
|
+
*/
|
|
87
|
+
export declare class DragTool extends BrowserToolBase {
|
|
88
|
+
/**
|
|
89
|
+
* Execute the drag tool
|
|
90
|
+
*/
|
|
91
|
+
execute(args: any, context: ToolContext): Promise<ToolResponse>;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Tool for pressing keyboard keys
|
|
95
|
+
*/
|
|
96
|
+
export declare class PressKeyTool extends BrowserToolBase {
|
|
97
|
+
/**
|
|
98
|
+
* Execute the key press tool
|
|
99
|
+
*/
|
|
100
|
+
execute(args: any, context: ToolContext): Promise<ToolResponse>;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Tool for switching browser tabs
|
|
104
|
+
*/
|