Skip to main content

Frontend Diagnostics API

The proxy injects window.__devtool into all HTML pages, providing 50+ primitives for comprehensive DOM inspection, layout debugging, and accessibility auditing.

Overview

When browsing through the proxy, every page has access to:

window.__devtool.inspect('#my-element')     // Comprehensive element analysis
window.__devtool.screenshot('bug-report') // Capture screenshots
window.__devtool.auditAccessibility() // Full a11y audit
window.__devtool.ask('Which looks better?', ['A', 'B']) // User interaction

Execute these via the proxy:

proxy {action: "exec", id: "app", code: "window.__devtool.inspect('#header')"}

Design Principles

  1. Primitives over Monoliths - Small, focused functions (~20-30 lines)
  2. Composability - Functions return rich data for chaining
  3. Error Resilient - Return {error: ...} instead of throwing
  4. ES5 Compatible - Works in all modern browsers
  5. Zero Dependencies - Pure browser APIs

Quick Reference

CategoryFunctions
Element InspectiongetElementInfo, getPosition, getComputed, getBox, getLayout, getContainer, getStacking, getTransform, getOverflow
Tree WalkingwalkChildren, walkParents, findAncestor
Visual StateisVisible, isInViewport, checkOverlap
Layout DiagnosticsfindOverflows, findStackingContexts, findOffscreen
Visual Overlayshighlight, removeHighlight, clearAllOverlays
InteractiveselectElement, measureBetween, waitForElement, ask
State CapturecaptureDOM, captureStyles, captureState, captureNetwork
AccessibilitygetA11yInfo, getContrast, getTabOrder, getScreenReaderText, auditAccessibility
Compositeinspect, diagnoseLayout, showLayout

Element Inspection

getElementInfo

Basic element information:

window.__devtool.getElementInfo('#nav-button')
{
tag: "button",
id: "nav-button",
classes: ["btn", "btn-primary"],
attributes: {type: "submit", disabled: ""},
textContent: "Submit"
}

getPosition

Element position and dimensions:

window.__devtool.getPosition('#sidebar')
{
rect: {top: 60, left: 0, width: 250, height: 800},
viewport: {x: 0, y: 60},
scroll: {top: 120, left: 0},
offsetParent: "body"
}

getComputed

Computed CSS properties:

window.__devtool.getComputed('#header', ['display', 'position', 'z-index'])
{
display: "flex",
position: "fixed",
"z-index": "1000"
}

getBox

Box model breakdown:

window.__devtool.getBox('.card')
{
margin: {top: 16, right: 16, bottom: 16, left: 16},
border: {top: 1, right: 1, bottom: 1, left: 1},
padding: {top: 24, right: 24, bottom: 24, left: 24},
content: {width: 300, height: 200}
}

getLayout

Layout properties:

window.__devtool.getLayout('.container')
{
display: "grid",
position: "relative",
flexbox: null,
grid: {
columns: "repeat(3, 1fr)",
rows: "auto",
gap: "16px"
},
float: "none"
}

getStacking

Stacking context information:

window.__devtool.getStacking('.modal')
{
zIndex: 1000,
createsContext: true,
reason: "z-index with position: fixed",
parentContext: "body"
}

getTransform

Transform matrix decomposition:

window.__devtool.getTransform('.rotated')
{
matrix: [0.866, 0.5, -0.5, 0.866, 0, 0],
decomposed: {
translate: {x: 0, y: 0},
rotate: 30,
scale: {x: 1, y: 1},
skew: {x: 0, y: 0}
}
}

getOverflow

Overflow detection:

window.__devtool.getOverflow('.scrollable')
{
overflowX: "auto",
overflowY: "scroll",
scrollWidth: 1200,
scrollHeight: 800,
clientWidth: 400,
clientHeight: 600,
hasHorizontalOverflow: true,
hasVerticalOverflow: true
}

Tree Walking

walkChildren

Traverse descendants:

window.__devtool.walkChildren('.nav', 2)
{
element: {tag: "nav", classes: ["nav"]},
children: [
{
element: {tag: "ul", classes: ["nav-list"]},
children: [
{element: {tag: "li", classes: ["nav-item"]}},
{element: {tag: "li", classes: ["nav-item"]}}
]
}
]
}

With filter:

window.__devtool.walkChildren('body', 3, 'button')
→ Only button elements up to depth 3

walkParents

Traverse ancestors:

window.__devtool.walkParents('.nested-item')
[
{tag: "li", classes: ["item"]},
{tag: "ul", classes: ["list"]},
{tag: "div", classes: ["container"]},
{tag: "main", id: "content"},
{tag: "body"},
{tag: "html"}
]

findAncestor

Find first matching ancestor:

window.__devtool.findAncestor('.button', '[data-modal]')
{tag: "div", classes: ["modal"], attributes: {"data-modal": "confirm"}}

Visual State

isVisible

Check visibility with reason:

window.__devtool.isVisible('.hidden-element')
{
visible: false,
reason: "display: none",
details: {
display: "none",
visibility: "visible",
opacity: 1,
clipPath: "none"
}
}

isInViewport

Check viewport intersection:

window.__devtool.isInViewport('.footer')
{
inViewport: false,
intersection: 0,
position: "below",
distanceToViewport: 450
}

checkOverlap

Check if elements overlap:

window.__devtool.checkOverlap('.tooltip', '.button')
{
overlaps: true,
intersection: {width: 50, height: 20, area: 1000},
relative: "above-right"
}

Layout Diagnostics

findOverflows

Find all elements with overflow:

window.__devtool.findOverflows()
[
{
selector: ".sidebar",
scrollWidth: 1024,
clientWidth: 250,
overflow: "horizontal"
},
{
selector: ".content",
scrollHeight: 2000,
clientHeight: 800,
overflow: "vertical"
}
]

findStackingContexts

Locate all stacking contexts:

window.__devtool.findStackingContexts()
[
{selector: ".modal", zIndex: 1000, reason: "position: fixed + z-index"},
{selector: ".header", zIndex: 100, reason: "position: sticky + z-index"},
{selector: ".tooltip", zIndex: 50, reason: "transform"}
]

findOffscreen

Find elements outside viewport:

window.__devtool.findOffscreen()
[
{selector: ".hidden-menu", position: "left", distance: -300},
{selector: ".footer-extra", position: "below", distance: 500}
]

Visual Overlays

highlight

Highlight an element:

window.__devtool.highlight('.problem-element', {
color: 'rgba(255, 0, 0, 0.3)',
border: '2px solid red',
duration: 5000
})
{highlightId: "hl-1", selector: ".problem-element"}

Persistent highlight (no auto-remove):

window.__devtool.highlight('#debug-target', {duration: 0})
→ Stays until manually removed

removeHighlight

Remove specific highlight:

window.__devtool.removeHighlight('hl-1')
{removed: true}

clearAllOverlays

Remove all visual overlays:

window.__devtool.clearAllOverlays()
{cleared: 5}

Interactive

selectElement

Interactive element picker:

window.__devtool.selectElement()
→ Promise that resolves when user clicks:
{
selector: "#user-avatar",
element: {tag: "img", classes: ["avatar"]},
position: {x: 150, y: 200}
}

User sees a crosshair cursor and highlight on hover. Click to select, Escape to cancel.

measureBetween

Measure distance between elements:

window.__devtool.measureBetween('#header', '#footer')
{
distance: {x: 0, y: 850, diagonal: 850},
direction: "below",
gap: {horizontal: 0, vertical: 800}
}

waitForElement

Wait for dynamic element:

window.__devtool.waitForElement('.loading-complete', 5000)
→ Promise resolves when element appears or times out
{
found: true,
element: {tag: "div", classes: ["loading-complete"]},
waited: 1234
}

ask

Show modal for user input:

window.__devtool.ask('Which layout do you prefer?', ['Grid', 'Flexbox', 'Float'])
→ Promise resolves with user selection:
{answer: "Grid", index: 0}

Timeout example:

window.__devtool.ask('Is this correct?', ['Yes', 'No'], {timeout: 30000})

State Capture

captureDOM

Capture full page HTML:

window.__devtool.captureDOM()
{
html: "<!DOCTYPE html><html>...",
hash: "a1b2c3d4",
timestamp: "2024-01-15T10:30:00Z",
size: 45678
}

captureStyles

Capture all styles for element:

window.__devtool.captureStyles('.button')
{
inline: {color: "red"},
computed: {
display: "inline-flex",
padding: "8px 16px",
// ... all computed styles
}
}

captureState

Capture browser state:

window.__devtool.captureState(['localStorage', 'sessionStorage', 'cookies'])
{
localStorage: {theme: "dark", user: "..."},
sessionStorage: {cart: "..."},
cookies: {session_id: "..."}
}

captureNetwork

Get resource timing data:

window.__devtool.captureNetwork()
{
entries: [
{
name: "https://example.com/api/users",
type: "fetch",
duration: 145,
transferSize: 2340
},
{
name: "https://example.com/main.js",
type: "script",
duration: 89,
transferSize: 45678
}
],
count: 42
}

Accessibility

getA11yInfo

Get accessibility attributes:

window.__devtool.getA11yInfo('#nav-button')
{
role: "button",
ariaLabel: "Open navigation",
ariaExpanded: "false",
tabIndex: 0,
focusable: true
}

getContrast

Check color contrast:

window.__devtool.getContrast('.button')
{
foreground: "#ffffff",
background: "#2196f3",
ratio: 4.52,
passes: {
AA_normal: true,
AA_large: true,
AAA_normal: false,
AAA_large: true
}
}

getTabOrder

Get document tab order:

window.__devtool.getTabOrder()
[
{selector: "a.skip-link", tabIndex: 0, order: 1},
{selector: "#search-input", tabIndex: 0, order: 2},
{selector: "button.menu", tabIndex: 0, order: 3},
// ...
]

getScreenReaderText

Get screen reader announcement:

window.__devtool.getScreenReaderText('.icon-button')
{
text: "Close dialog",
sources: ["aria-label"],
role: "button"
}

auditAccessibility

Full page accessibility audit:

window.__devtool.auditAccessibility()
{
score: 85,
errors: [
{
type: "missing-alt",
selector: "img.hero-image",
message: "Image missing alt text"
}
],
warnings: [
{
type: "low-contrast",
selector: ".muted-text",
message: "Contrast ratio 3.2:1 below AA threshold"
}
],
passes: 42,
total: 50
}

Composite Functions

inspect

Comprehensive element analysis (combines 8+ primitives):

window.__devtool.inspect('#my-button')
{
element: {tag: "button", id: "my-button", classes: ["btn"]},
position: {rect: {...}, viewport: {...}},
box: {margin: {...}, padding: {...}},
layout: {display: "inline-flex", position: "relative"},
stacking: {zIndex: "auto", createsContext: false},
visibility: {visible: true},
accessibility: {role: "button", ariaLabel: "Submit form"},
contrast: {ratio: 4.52, passes: {AA: true}}
}

diagnoseLayout

Find all layout issues:

window.__devtool.diagnoseLayout()
{
overflows: [...],
stackingIssues: [...],
offscreenElements: [...],
summary: {
overflowCount: 2,
stackingContextCount: 5,
offscreenCount: 3
}
}

showLayout

Visual debugging overlay:

window.__devtool.showLayout({
showMargins: true,
showPadding: true,
showGrid: true
})
{overlayId: "layout-1"}

Displays colored overlays showing margins (orange), padding (green), and grid lines.

Screenshots

screenshot

Capture the current viewport:

window.__devtool.screenshot('bug-report')
{
name: "bug-report",
path: "/tmp/devtool-screenshots/bug-report-1705312200.png",
size: {width: 1920, height: 1080}
}

Access via proxylog:

proxylog {proxy_id: "app", types: ["screenshot"]}

Logging

log, debug, info, warn, error

Send custom logs to the proxy:

window.__devtool.log('Custom message', 'info', {extra: 'data'})
window.__devtool.debug('Debug info')
window.__devtool.warn('Warning message')
window.__devtool.error('Error occurred', {stack: '...'})

Query via:

proxylog {proxy_id: "app", types: ["custom"]}

Connection Status

isConnected

Check WebSocket connection:

window.__devtool.isConnected()
true

getStatus

Detailed connection status:

window.__devtool.getStatus()
{
connected: true,
reconnectAttempts: 0,
lastMessage: "2024-01-15T10:30:00Z"
}

Error Handling

All primitives return error objects instead of throwing:

window.__devtool.getPosition('.nonexistent')
{error: "Element not found", selector: ".nonexistent"}

window.__devtool.getContrast('.element-with-gradient')
{error: "Cannot compute contrast for gradient background"}

Performance

  • All primitives complete in under 10ms on typical pages
  • Synchronous operations (getPosition, getBox) are instant
  • Async operations (selectElement, ask) wait for user input
  • No external dependencies - pure browser APIs

Next Steps