Skip to main content
Glama
measure_element.tsβ€’8.63 kB
import { ToolHandler, ToolMetadata, SessionConfig } from '../../common/types.js'; import { BrowserToolBase } from '../base.js'; import type { ToolContext, ToolResponse } from '../../common/types.js'; export class MeasureElementTool extends BrowserToolBase implements ToolHandler { static getMetadata(sessionConfig?: SessionConfig): ToolMetadata { return { name: "measure_element", description: "πŸ“ MEASUREMENT TOOL - DEBUG SPACING ISSUES: See padding, margin, border, and dimension measurements in visual box model format. Use when elements have unexpected spacing or size. Returns compact visual representation showing content β†’ padding β†’ border β†’ margin with directional arrows (↑24px for top margin, etc.). Also provides raw dimensions useful for scroll detection (clientHeight vs content height). For parent-child centering issues, use inspect_dom() first (shows if child is centered in parent). For comparing alignment between two elements, use compare_element_alignment(). For quick scroll detection, use inspect_dom() instead (shows 'scrollable ↕️'). More readable than get_computed_styles() or evaluate() for box model debugging.", priority: 7, outputs: [ "Header: Element: <tag id/class/testid>", "Position/size line: @ (x,y) widthxheight px", "Box Model section: Content size, Padding (with directional arrows), Border (with arrows or shorthand), Margin (with arrows)", "Total Space line: totalWidthxtotalHeight px (with margin)", "Optional suggestion to run inspect_ancestors when unusual spacing detected", ], examples: [ "measure_element({ selector: 'testid:card' })", "measure_element({ selector: '#hero' })", ], exampleOutputs: [ { call: "measure_element({ selector: 'testid:card' })", output: `Element: <div data-testid=\"card\">\n@ (240,320) 360x240px\n\nBox Model:\n Content: 328x208px\n Padding: ↑16px ↓16px ←8px β†’8px\n Border: none\n Margin: ↑0px ↓24px ←0px β†’0px\n\nTotal Space: 360x264px (with margin)` } ], inputSchema: { type: "object", properties: { selector: { type: "string", description: "CSS selector or testid shorthand (e.g., 'testid:submit', '#login-button')" } }, required: ["selector"], }, }; } async execute(args: { selector: string }, context: ToolContext): Promise<ToolResponse> { return this.safeExecute(context, async (page) => { const normalizedSelector = this.normalizeSelector(args.selector); // Use standard element selection with visibility preference const locator = page.locator(normalizedSelector); const { element, elementIndex, totalCount } = await this.selectPreferredLocator(locator, { originalSelector: args.selector, }); // Format selection warning if multiple elements matched const warning = this.formatElementSelectionInfo( args.selector, elementIndex, totalCount ); // Get element descriptor const elementInfo = await element.evaluate((el) => { const tag = el.tagName.toLowerCase(); const testId = el.getAttribute('data-testid') || el.getAttribute('data-test') || el.getAttribute('data-cy'); const id = el.id ? `#${el.id}` : ''; const classes = el.className && typeof el.className === 'string' ? `.${el.className.split(' ').filter(c => c).slice(0, 2).join('.')}` : ''; let descriptor = `<${tag}`; if (testId) descriptor += ` data-testid="${testId}"`; else if (id) descriptor += id; else if (classes) descriptor += classes; descriptor += '>'; return { descriptor }; }); // Get box model measurements const measurements = await element.evaluate((el) => { const computed = window.getComputedStyle(el); const rect = el.getBoundingClientRect(); const parseValue = (val: string): number => parseFloat(val) || 0; return { x: Math.round(rect.x), y: Math.round(rect.y), width: parseValue(computed.width), height: parseValue(computed.height), marginTop: parseValue(computed.marginTop), marginRight: parseValue(computed.marginRight), marginBottom: parseValue(computed.marginBottom), marginLeft: parseValue(computed.marginLeft), paddingTop: parseValue(computed.paddingTop), paddingRight: parseValue(computed.paddingRight), paddingBottom: parseValue(computed.paddingBottom), paddingLeft: parseValue(computed.paddingLeft), borderTopWidth: parseValue(computed.borderTopWidth), borderRightWidth: parseValue(computed.borderRightWidth), borderBottomWidth: parseValue(computed.borderBottomWidth), borderLeftWidth: parseValue(computed.borderLeftWidth), borderStyle: computed.borderStyle, borderColor: computed.borderColor }; }); // Calculate dimensions const contentWidth = Math.round(measurements.width - measurements.paddingLeft - measurements.paddingRight); const contentHeight = Math.round(measurements.height - measurements.paddingTop - measurements.paddingBottom); const boxWidth = Math.round(measurements.width); const boxHeight = Math.round(measurements.height); const totalWidth = Math.round(boxWidth + measurements.marginLeft + measurements.marginRight); const totalHeight = Math.round(boxHeight + measurements.marginTop + measurements.marginBottom); // Format border info const formatBorder = (): string => { const { borderTopWidth: top, borderRightWidth: right, borderBottomWidth: bottom, borderLeftWidth: left, borderStyle: style } = measurements; // Check if all sides are the same if (top === right && right === bottom && bottom === left) { if (top === 0) return ' Border: none'; return ` Border: ${top}px ${style}`; } // Different sides const lines: string[] = []; if (top > 0) lines.push(`↑${top}px`); if (right > 0) lines.push(`β†’${right}px`); if (bottom > 0) lines.push(`↓${bottom}px`); if (left > 0) lines.push(`←${left}px`); return lines.length > 0 ? ` Border: ${lines.join(' ')} ${style}` : ' Border: none'; }; // Format spacing (margin/padding) with directional arrows const formatSpacing = (top: number, right: number, bottom: number, left: number): string => { const parts: string[] = []; if (top > 0) parts.push(`↑${Math.round(top)}px`); if (bottom > 0) parts.push(`↓${Math.round(bottom)}px`); if (left > 0) parts.push(`←${Math.round(left)}px`); if (right > 0) parts.push(`β†’${Math.round(right)}px`); return parts.length > 0 ? parts.join(' ') : '0px'; }; // Build output in compact text format const sections: string[] = []; if (warning) { sections.push(warning.trim()); } sections.push(`Element: ${elementInfo.descriptor}`); sections.push(`@ (${measurements.x},${measurements.y}) ${boxWidth}x${boxHeight}px`); sections.push(''); sections.push('Box Model:'); sections.push(` Content: ${contentWidth}x${contentHeight}px`); sections.push(` Padding: ${formatSpacing(measurements.paddingTop, measurements.paddingRight, measurements.paddingBottom, measurements.paddingLeft)}`); sections.push(formatBorder()); sections.push(` Margin: ${formatSpacing(measurements.marginTop, measurements.marginRight, measurements.marginBottom, measurements.marginLeft)}`); sections.push(''); sections.push(`Total Space: ${totalWidth}x${totalHeight}px (with margin)`); // Detect unusual spacing and suggest inspect_ancestors const hasUnusualMargins = measurements.marginLeft > 100 || measurements.marginRight > 100; const isWidthConstrained = boxWidth < 800 && (measurements.marginLeft + measurements.marginRight) > 200; if (hasUnusualMargins || isWidthConstrained) { sections.push(''); sections.push('πŸ’‘ Unexpected spacing/width detected. Check parent constraints:'); sections.push(` inspect_ancestors({ selector: "${args.selector}" })`); } return { content: [ { type: 'text', text: sections.join('\n') } ], isError: false }; }); } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/antonzherdev/mcp-web-inspector'

If you have feedback or need assistance with the MCP directory API, please join our Discord server