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
- Primitives over Monoliths - Small, focused functions (~20-30 lines)
- Composability - Functions return rich data for chaining
- Error Resilient - Return
{error: ...}instead of throwing - ES5 Compatible - Works in all modern browsers
- Zero Dependencies - Pure browser APIs
Quick Reference
| Category | Functions |
|---|---|
| Element Inspection | getElementInfo, getPosition, getComputed, getBox, getLayout, getContainer, getStacking, getTransform, getOverflow |
| Tree Walking | walkChildren, walkParents, findAncestor |
| Visual State | isVisible, isInViewport, checkOverlap |
| Layout Diagnostics | findOverflows, findStackingContexts, findOffscreen |
| Visual Overlays | highlight, removeHighlight, clearAllOverlays |
| Interactive | selectElement, measureBetween, waitForElement, ask |
| State Capture | captureDOM, captureStyles, captureState, captureNetwork |
| Accessibility | getA11yInfo, getContrast, getTabOrder, getScreenReaderText, auditAccessibility |
| Composite | inspect, 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
- See Element Inspection API
- Try Accessibility Auditing
- Explore Debugging Web Apps