mcp-web-inspector 0.1.1 → 0.1.2

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.
@@ -11,7 +11,7 @@ import { FindByTextTool } from './tools/browser/findByText.js';
11
11
  import { GetComputedStylesTool } from './tools/browser/computedStyles.js';
12
12
  import { MeasureElementTool } from './tools/browser/measureElement.js';
13
13
  import { ElementExistsTool } from './tools/browser/elementExists.js';
14
- import { ComparePositionsTool } from './tools/browser/comparePositions.js';
14
+ import { CompareElementAlignmentTool } from './tools/browser/compareElementAlignment.js';
15
15
  import { GoBackTool, GoForwardTool } from './tools/browser/navigation.js';
16
16
  import { DragTool, PressKeyTool } from './tools/browser/interaction.js';
17
17
  import { WaitForElementTool } from './tools/browser/waitForElement.js';
@@ -96,7 +96,7 @@ let findByTextTool;
96
96
  let getComputedStylesTool;
97
97
  let measureElementTool;
98
98
  let elementExistsTool;
99
- let comparePositionsTool;
99
+ let compareElementAlignmentTool;
100
100
  let waitForElementTool;
101
101
  let waitForNetworkIdleTool;
102
102
  let listNetworkRequestsTool;
@@ -510,8 +510,8 @@ function initializeTools(server) {
510
510
  measureElementTool = new MeasureElementTool(server);
511
511
  if (!elementExistsTool)
512
512
  elementExistsTool = new ElementExistsTool(server);
513
- if (!comparePositionsTool)
514
- comparePositionsTool = new ComparePositionsTool(server);
513
+ if (!compareElementAlignmentTool)
514
+ compareElementAlignmentTool = new CompareElementAlignmentTool(server);
515
515
  if (!waitForElementTool)
516
516
  waitForElementTool = new WaitForElementTool(server);
517
517
  if (!waitForNetworkIdleTool)
@@ -651,8 +651,8 @@ export async function handleToolCall(name, args, server) {
651
651
  return await measureElementTool.execute(args, context);
652
652
  case "element_exists":
653
653
  return await elementExistsTool.execute(args, context);
654
- case "compare_positions":
655
- return await comparePositionsTool.execute(args, context);
654
+ case "compare_element_alignment":
655
+ return await compareElementAlignmentTool.execute(args, context);
656
656
  case "wait_for_element":
657
657
  return await waitForElementTool.execute(args, context);
658
658
  case "wait_for_network_idle":
@@ -0,0 +1,11 @@
1
+ import { BrowserToolBase } from './base.js';
2
+ import { ToolContext, ToolResponse } from '../common/types.js';
3
+ /**
4
+ * Tool for comparing alignment of two elements
5
+ */
6
+ export declare class CompareElementAlignmentTool extends BrowserToolBase {
7
+ /**
8
+ * Execute the compare element alignment tool
9
+ */
10
+ execute(args: any, context: ToolContext): Promise<ToolResponse>;
11
+ }
@@ -0,0 +1,171 @@
1
+ import { BrowserToolBase } from './base.js';
2
+ import { createSuccessResponse, createErrorResponse } from '../common/types.js';
3
+ /**
4
+ * Tool for comparing alignment of two elements
5
+ */
6
+ export class CompareElementAlignmentTool extends BrowserToolBase {
7
+ /**
8
+ * Execute the compare element alignment tool
9
+ */
10
+ async execute(args, context) {
11
+ return this.safeExecute(context, async (page) => {
12
+ const selector1 = this.normalizeSelector(args.selector1);
13
+ const selector2 = this.normalizeSelector(args.selector2);
14
+ try {
15
+ // Get locators for both elements
16
+ const locator1 = page.locator(selector1);
17
+ const locator2 = page.locator(selector2);
18
+ // Check if both elements exist
19
+ const count1 = await locator1.count();
20
+ const count2 = await locator2.count();
21
+ if (count1 === 0) {
22
+ return createErrorResponse(`First element not found: ${args.selector1}`);
23
+ }
24
+ if (count2 === 0) {
25
+ return createErrorResponse(`Second element not found: ${args.selector2}`);
26
+ }
27
+ // Handle multiple matches by using first() - show warning
28
+ const targetLocator1 = count1 > 1 ? locator1.first() : locator1;
29
+ const targetLocator2 = count2 > 1 ? locator2.first() : locator2;
30
+ let warnings = '';
31
+ if (count1 > 1) {
32
+ warnings += `⚠ Warning: First selector matched ${count1} elements, using first\n`;
33
+ }
34
+ if (count2 > 1) {
35
+ warnings += `⚠ Warning: Second selector matched ${count2} elements, using first\n`;
36
+ }
37
+ if (warnings) {
38
+ warnings += '\n';
39
+ }
40
+ // Get bounding boxes
41
+ const box1 = await targetLocator1.boundingBox();
42
+ const box2 = await targetLocator2.boundingBox();
43
+ // Get element descriptors
44
+ const getDescriptor = async (locator) => {
45
+ return await locator.evaluate((element) => {
46
+ const tagName = element.tagName.toLowerCase();
47
+ const testId = element.getAttribute('data-testid') || element.getAttribute('data-test') || element.getAttribute('data-cy');
48
+ const id = element.id ? `#${element.id}` : '';
49
+ const classes = element.className && typeof element.className === 'string'
50
+ ? `.${element.className.split(' ').filter(c => c).join('.')}`
51
+ : '';
52
+ let descriptor = `<${tagName}`;
53
+ if (testId)
54
+ descriptor += ` data-testid="${testId}"`;
55
+ else if (id)
56
+ descriptor += id;
57
+ else if (classes)
58
+ descriptor += classes;
59
+ descriptor += '>';
60
+ return descriptor;
61
+ });
62
+ };
63
+ const descriptor1 = await getDescriptor(targetLocator1);
64
+ const descriptor2 = await getDescriptor(targetLocator2);
65
+ // Handle hidden elements
66
+ if (!box1) {
67
+ return createErrorResponse(`First element is hidden or has no dimensions: ${descriptor1}`);
68
+ }
69
+ if (!box2) {
70
+ return createErrorResponse(`Second element is hidden or has no dimensions: ${descriptor2}`);
71
+ }
72
+ // Extract short name from descriptor for compact output
73
+ const getShortName = (descriptor, selector) => {
74
+ const testIdMatch = descriptor.match(/data-testid="([^"]+)"/);
75
+ if (testIdMatch)
76
+ return testIdMatch[1];
77
+ const idMatch = descriptor.match(/#([^>]+)/);
78
+ if (idMatch)
79
+ return idMatch[1];
80
+ // Use original selector if available
81
+ return selector;
82
+ };
83
+ const name1 = getShortName(descriptor1, args.selector1);
84
+ const name2 = getShortName(descriptor2, args.selector2);
85
+ // Calculate all alignment values
86
+ const tolerance = 2; // Allow 2px tolerance for rounding
87
+ // Edge positions
88
+ const top1 = Math.round(box1.y);
89
+ const top2 = Math.round(box2.y);
90
+ const topDiff = Math.abs(top1 - top2);
91
+ const topAligned = topDiff <= tolerance;
92
+ const left1 = Math.round(box1.x);
93
+ const left2 = Math.round(box2.x);
94
+ const leftDiff = Math.abs(left1 - left2);
95
+ const leftAligned = leftDiff <= tolerance;
96
+ const right1 = Math.round(box1.x + box1.width);
97
+ const right2 = Math.round(box2.x + box2.width);
98
+ const rightDiff = Math.abs(right1 - right2);
99
+ const rightAligned = rightDiff <= tolerance;
100
+ const bottom1 = Math.round(box1.y + box1.height);
101
+ const bottom2 = Math.round(box2.y + box2.height);
102
+ const bottomDiff = Math.abs(bottom1 - bottom2);
103
+ const bottomAligned = bottomDiff <= tolerance;
104
+ // Center positions
105
+ const centerH1 = Math.round(box1.x + box1.width / 2);
106
+ const centerH2 = Math.round(box2.x + box2.width / 2);
107
+ const centerHDiff = Math.abs(centerH1 - centerH2);
108
+ const centerHAligned = centerHDiff <= tolerance;
109
+ const centerV1 = Math.round(box1.y + box1.height / 2);
110
+ const centerV2 = Math.round(box2.y + box2.height / 2);
111
+ const centerVDiff = Math.abs(centerV1 - centerV2);
112
+ const centerVAligned = centerVDiff <= tolerance;
113
+ // Dimensions
114
+ const width1 = Math.round(box1.width);
115
+ const width2 = Math.round(box2.width);
116
+ const widthDiff = Math.abs(width1 - width2);
117
+ const widthSame = widthDiff <= tolerance;
118
+ const height1 = Math.round(box1.height);
119
+ const height2 = Math.round(box2.height);
120
+ const heightDiff = Math.abs(height1 - height2);
121
+ const heightSame = heightDiff <= tolerance;
122
+ // Format compact output
123
+ const formatAlignment = (aligned, val1, val2, diff, unit = 'px') => {
124
+ const symbol = aligned ? '✓' : '✗';
125
+ const status = aligned ? 'aligned' : 'not aligned';
126
+ if (aligned) {
127
+ return `${symbol} ${status} (both @ ${val1}${unit})`;
128
+ }
129
+ else {
130
+ return `${symbol} ${status} (${val1}${unit} vs ${val2}${unit}, diff: ${diff}${unit})`;
131
+ }
132
+ };
133
+ const formatDimension = (same, val1, val2, diff, unit = 'px') => {
134
+ const symbol = same ? '✓' : '✗';
135
+ const status = same ? 'same' : 'different';
136
+ if (same) {
137
+ return `${symbol} ${status} (${val1}${unit})`;
138
+ }
139
+ else {
140
+ return `${symbol} ${status} (${val1}${unit} vs ${val2}${unit}, diff: ${diff}${unit})`;
141
+ }
142
+ };
143
+ // Build output
144
+ const lines = [
145
+ warnings,
146
+ `Alignment: ${descriptor1} vs ${descriptor2}`,
147
+ ` ${name1}: @ (${left1},${top1}) ${width1}×${height1}px`,
148
+ ` ${name2}: @ (${left2},${top2}) ${width2}×${height2}px`,
149
+ '',
150
+ 'Edges:',
151
+ ` Top: ${formatAlignment(topAligned, top1, top2, topDiff)}`,
152
+ ` Left: ${formatAlignment(leftAligned, left1, left2, leftDiff)}`,
153
+ ` Right: ${formatAlignment(rightAligned, right1, right2, rightDiff)}`,
154
+ ` Bottom: ${formatAlignment(bottomAligned, bottom1, bottom2, bottomDiff)}`,
155
+ '',
156
+ 'Centers:',
157
+ ` Horizontal: ${formatAlignment(centerHAligned, centerH1, centerH2, centerHDiff)}`,
158
+ ` Vertical: ${formatAlignment(centerVAligned, centerV1, centerV2, centerVDiff)}`,
159
+ '',
160
+ 'Dimensions:',
161
+ ` Width: ${formatDimension(widthSame, width1, width2, widthDiff)}`,
162
+ ` Height: ${formatDimension(heightSame, height1, height2, heightDiff)}`
163
+ ];
164
+ return createSuccessResponse(lines.filter(l => l !== undefined).join('\n'));
165
+ }
166
+ catch (error) {
167
+ return createErrorResponse(`Failed to compare element alignment: ${error.message}`);
168
+ }
169
+ });
170
+ }
171
+ }
@@ -391,6 +391,13 @@ export class InspectDomTool extends BrowserToolBase {
391
391
  lines.push(`${prefix} ${tag}${roleInfo}`);
392
392
  // Position
393
393
  lines.push(` @ (${child.position.x},${child.position.y}) ${child.position.width}x${child.position.height}px`);
394
+ // Calculate distances from all parent edges
395
+ const fromLeft = child.position.x - target.position.x;
396
+ const fromRight = (target.position.x + target.position.width) - (child.position.x + child.position.width);
397
+ const fromTop = child.position.y - target.position.y;
398
+ const fromBottom = (target.position.y + target.position.height) - (child.position.y + child.position.height);
399
+ // Format edge distances (centering is obvious: equal left/right = horizontal center, equal top/bottom = vertical center)
400
+ lines.push(` from edges: ←${fromLeft}px →${fromRight}px ↑${fromTop}px ↓${fromBottom}px`);
394
401
  // Calculate offset from previous sibling
395
402
  if (index > 0) {
396
403
  const prev = children[index - 1];
package/dist/tools.d.ts CHANGED
@@ -314,7 +314,7 @@ export declare function createToolDefinitions(sessionConfig?: SessionConfig): [{
314
314
  };
315
315
  }, {
316
316
  readonly name: "inspect_dom";
317
- readonly description: "START HERE FOR LAYOUT DEBUGGING: Get page structure overview and identify elements. Progressive DOM inspection that skips wrapper divs and shows only semantic elements (header, nav, main, form, button, elements with test IDs, ARIA roles, etc.). Use without selector for full page overview, then drill down by calling with a child's selector. Returns compact text format with position, visibility, and layout patterns. More efficient than get_html() or evaluate() for understanding structure. Supports testid shortcuts.";
317
+ readonly description: "START HERE FOR LAYOUT DEBUGGING: Progressive DOM inspection that shows parent-child relationships, centering issues, and spacing gaps. Skips wrapper divs and shows only semantic elements (header, nav, main, form, button, elements with test IDs, ARIA roles, etc.).\n\nWORKFLOW: Call without selector for page overview, then drill down by calling with child's selector.\n\nDETECTS: Parent-relative positioning, vertical/horizontal centering, sibling spacing gaps, layout patterns.\n\nOUTPUT FORMAT:\n[0] <button data-testid=\"menu\">\n @ (16,8) 40×40px ← Absolute viewport position (x,y) and size\n from edges: ←16px →1144px ↑8px ↓8px ← Distance from parent edges (↑8px = ↓8px means vertically centered)\n \"Menu\"\n ✓ visible, ⚡ interactive\n\n[1] <div data-testid=\"title\">\n @ (260,2) 131×28px\n from edges: ←244px →244px ↑2px ↓42px ← Equal left/right (244px) = horizontally centered, unequal top/bottom = NOT vertically centered\n gap from [0]: →16px ← Spacing between siblings\n \"Title\"\n ✓ visible, 2 children\n\nSYMBOLS: ✓=visible, ✗=hidden, ⚡=interactive, ←→=horizontal edges, ↑↓=vertical edges\nCENTERING: Equal left/right distances = horizontally centered, equal top/bottom = vertically centered\n\nRELATED TOOLS: For comparing TWO elements' alignment (not parent-child), use compare_element_alignment(). For box model (padding/margin), use measure_element().\n\nMore efficient than get_html() or evaluate(). Supports testid shortcuts.";
318
318
  readonly inputSchema: {
319
319
  readonly type: "object";
320
320
  readonly properties: {
@@ -427,7 +427,7 @@ export declare function createToolDefinitions(sessionConfig?: SessionConfig): [{
427
427
  };
428
428
  }, {
429
429
  readonly name: "measure_element";
430
- readonly description: "DEBUG SPACING ISSUES: See padding, margin, and border 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.). More readable than get_computed_styles() for box model debugging.";
430
+ readonly description: "DEBUG SPACING ISSUES: See padding, margin, and border 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.). 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(). More readable than get_computed_styles() for box model debugging.";
431
431
  readonly inputSchema: {
432
432
  readonly type: "object";
433
433
  readonly properties: {
@@ -452,8 +452,8 @@ export declare function createToolDefinitions(sessionConfig?: SessionConfig): [{
452
452
  readonly required: readonly ["selector"];
453
453
  };
454
454
  }, {
455
- readonly name: "compare_positions";
456
- readonly description: "FIND ALIGNMENT GAPS: Check if two elements are properly aligned or have spacing issues. Perfect for debugging layout problems like 'element not aligned with header' or 'gap between panels'. Checks edge alignment (top, left, right, bottom) or dimension matching (width, height). Returns alignment status and gap size in pixels. More efficient than evaluate() with manual getBoundingClientRect() calculations.";
455
+ readonly name: "compare_element_alignment";
456
+ readonly description: "COMPARE TWO ELEMENTS: Get comprehensive alignment and dimension comparison in one call. Shows edge alignment (top, left, right, bottom), center alignment (horizontal, vertical), and dimensions (width, height). Perfect for debugging 'are these headers aligned?' or 'do these panels match?'. Returns all alignment info with ✓/✗ symbols and pixel differences. For parent-child centering, use inspect_dom() instead (automatically shows if children are centered in parent). More efficient than evaluate() with manual getBoundingClientRect() calculations.";
457
457
  readonly inputSchema: {
458
458
  readonly type: "object";
459
459
  readonly properties: {
@@ -465,13 +465,8 @@ export declare function createToolDefinitions(sessionConfig?: SessionConfig): [{
465
465
  readonly type: "string";
466
466
  readonly description: "CSS selector, text selector, or testid shorthand for the second element (e.g., 'testid:chat-header', '#secondary-header')";
467
467
  };
468
- readonly checkAlignment: {
469
- readonly type: "string";
470
- readonly description: "What to check: 'top', 'left', 'right', 'bottom' (edge alignment), or 'width', 'height' (dimension matching)";
471
- readonly enum: readonly ["top", "left", "right", "bottom", "width", "height"];
472
- };
473
468
  };
474
- readonly required: readonly ["selector1", "selector2", "checkAlignment"];
469
+ readonly required: readonly ["selector1", "selector2"];
475
470
  };
476
471
  }, {
477
472
  readonly name: "wait_for_element";
package/dist/tools.js CHANGED
@@ -250,7 +250,32 @@ export function createToolDefinitions(sessionConfig) {
250
250
  },
251
251
  {
252
252
  name: "inspect_dom",
253
- description: "START HERE FOR LAYOUT DEBUGGING: Get page structure overview and identify elements. Progressive DOM inspection that skips wrapper divs and shows only semantic elements (header, nav, main, form, button, elements with test IDs, ARIA roles, etc.). Use without selector for full page overview, then drill down by calling with a child's selector. Returns compact text format with position, visibility, and layout patterns. More efficient than get_html() or evaluate() for understanding structure. Supports testid shortcuts.",
253
+ description: `START HERE FOR LAYOUT DEBUGGING: Progressive DOM inspection that shows parent-child relationships, centering issues, and spacing gaps. Skips wrapper divs and shows only semantic elements (header, nav, main, form, button, elements with test IDs, ARIA roles, etc.).
254
+
255
+ WORKFLOW: Call without selector for page overview, then drill down by calling with child's selector.
256
+
257
+ DETECTS: Parent-relative positioning, vertical/horizontal centering, sibling spacing gaps, layout patterns.
258
+
259
+ OUTPUT FORMAT:
260
+ [0] <button data-testid="menu">
261
+ @ (16,8) 40×40px ← Absolute viewport position (x,y) and size
262
+ from edges: ←16px →1144px ↑8px ↓8px ← Distance from parent edges (↑8px = ↓8px means vertically centered)
263
+ "Menu"
264
+ ✓ visible, ⚡ interactive
265
+
266
+ [1] <div data-testid="title">
267
+ @ (260,2) 131×28px
268
+ from edges: ←244px →244px ↑2px ↓42px ← Equal left/right (244px) = horizontally centered, unequal top/bottom = NOT vertically centered
269
+ gap from [0]: →16px ← Spacing between siblings
270
+ "Title"
271
+ ✓ visible, 2 children
272
+
273
+ SYMBOLS: ✓=visible, ✗=hidden, ⚡=interactive, ←→=horizontal edges, ↑↓=vertical edges
274
+ CENTERING: Equal left/right distances = horizontally centered, equal top/bottom = vertically centered
275
+
276
+ RELATED TOOLS: For comparing TWO elements' alignment (not parent-child), use compare_element_alignment(). For box model (padding/margin), use measure_element().
277
+
278
+ More efficient than get_html() or evaluate(). Supports testid shortcuts.`,
254
279
  inputSchema: {
255
280
  type: "object",
256
281
  properties: {
@@ -368,7 +393,7 @@ export function createToolDefinitions(sessionConfig) {
368
393
  },
369
394
  {
370
395
  name: "measure_element",
371
- description: "DEBUG SPACING ISSUES: See padding, margin, and border 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.). More readable than get_computed_styles() for box model debugging.",
396
+ description: "DEBUG SPACING ISSUES: See padding, margin, and border 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.). 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(). More readable than get_computed_styles() for box model debugging.",
372
397
  inputSchema: {
373
398
  type: "object",
374
399
  properties: {
@@ -395,8 +420,8 @@ export function createToolDefinitions(sessionConfig) {
395
420
  },
396
421
  },
397
422
  {
398
- name: "compare_positions",
399
- description: "FIND ALIGNMENT GAPS: Check if two elements are properly aligned or have spacing issues. Perfect for debugging layout problems like 'element not aligned with header' or 'gap between panels'. Checks edge alignment (top, left, right, bottom) or dimension matching (width, height). Returns alignment status and gap size in pixels. More efficient than evaluate() with manual getBoundingClientRect() calculations.",
423
+ name: "compare_element_alignment",
424
+ description: "COMPARE TWO ELEMENTS: Get comprehensive alignment and dimension comparison in one call. Shows edge alignment (top, left, right, bottom), center alignment (horizontal, vertical), and dimensions (width, height). Perfect for debugging 'are these headers aligned?' or 'do these panels match?'. Returns all alignment info with ✓/✗ symbols and pixel differences. For parent-child centering, use inspect_dom() instead (automatically shows if children are centered in parent). More efficient than evaluate() with manual getBoundingClientRect() calculations.",
400
425
  inputSchema: {
401
426
  type: "object",
402
427
  properties: {
@@ -407,14 +432,9 @@ export function createToolDefinitions(sessionConfig) {
407
432
  selector2: {
408
433
  type: "string",
409
434
  description: "CSS selector, text selector, or testid shorthand for the second element (e.g., 'testid:chat-header', '#secondary-header')"
410
- },
411
- checkAlignment: {
412
- type: "string",
413
- description: "What to check: 'top', 'left', 'right', 'bottom' (edge alignment), or 'width', 'height' (dimension matching)",
414
- enum: ["top", "left", "right", "bottom", "width", "height"]
415
435
  }
416
436
  },
417
- required: ["selector1", "selector2", "checkAlignment"],
437
+ required: ["selector1", "selector2"],
418
438
  },
419
439
  },
420
440
  {
@@ -502,7 +522,7 @@ export const BROWSER_TOOLS = [
502
522
  "find_by_text",
503
523
  // Visibility & Position
504
524
  "check_visibility",
505
- "compare_positions",
525
+ "compare_element_alignment",
506
526
  "element_exists",
507
527
  "wait_for_element",
508
528
  "wait_for_network_idle",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-web-inspector",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Web Inspector MCP: Give LLMs visual superpowers to see, debug, and test any web page.",
5
5
  "license": "MIT",
6
6
  "author": "Anton",