Dump format #
The contract between the oracle service, the comparator, and eventually the engine under test. Frozen: changing it requires changing every consumer at once.
Oracle protocol #
The oracle is a long-lived process speaking newline-delimited JSON on stdio. One JSON object per line in each direction, no pretty-printing, no interleaved text on stdout. Diagnostics go to stderr.
Browsers stay open for the process lifetime. Nothing spawns a browser per case.
Request #
{"id": "case-0001", "html": "<!doctype html>...", "engines": ["chromium", "firefox", "webkit"]}
engines is optional; omitted means every engine the service launched.
Response #
{"id": "case-0001", "dumps": {"chromium": {...}, "firefox": {...}}, "errors": {"webkit": "message"}}
dumps holds one dump per engine that succeeded. errors holds a message per engine
that failed, and is omitted when empty. A response always carries the id it answers.
Control #
{"id": "...", "op": "ping"} responds {"id": "...", "ok": true, "engines": [...]}.
{"id": "...", "op": "shutdown"} closes browsers and exits.
Dump #
{
"engine": "chromium",
"viewport": {"width": 800, "height": 600},
"overflowed": false,
"nodes": [
{
"index": 0,
"parent": -1,
"tag": "HTML",
"depth": 0,
"bounding": {"x": 0.0, "y": 0.0, "w": 800.0, "h": 100.0},
"rects": [{"x": 0.0, "y": 0.0, "w": 800.0, "h": 100.0}],
"style": {"display": "block", "...": "..."}
}
]
}
nodesis every element in document order, starting at<html>at index 0. Document order meansindexincreases monotonically and a parent always precedes its children.parentis an index intonodes, or-1for the root.tagis the uppercase tag name as the DOM reports it.rectsisgetClientRects(), one entry per box fragment, in content order. An empty array means the element generates no box — that is the signal, not a nullbounding.boundingisgetBoundingClientRect(), always present, zeroed whenrectsis empty.- Coordinates are viewport-relative CSS pixels as doubles, serialized at full precision.
overflowedis true when the document scrolls in either axis. Overflow means a scrollbar may have stolen viewport width by a platform-dependent amount, so comparisons on an overflowed case are not trustworthy and the case is discarded.
Style subset #
A fixed, ordered list. Present on every node, values exactly as getComputedStyle
returns them.
display position box-sizing direction writing-mode float clear
width height min-width max-width min-height max-height
margin-top margin-right margin-bottom margin-left
padding-top padding-right padding-bottom padding-left
border-top-width border-right-width border-bottom-width border-left-width
overflow-x overflow-y
A mismatch here indicts the style pipeline rather than layout, which is the second attribution axis.
Comparison #
Comparison never uses absolute coordinates.
- Shape. The sequence of
(tag, depth, parent)must match. A mismatch is a parse divergence and the case reports that and stops — no geometry is compared. - Size. Each node's
boundingwidth and height, and each fragment's width and height pairwise. - Relative offset. Each node's
boundingorigin minus its parent'sboundingorigin. The root compares against the viewport origin. A single wrong offset then appears once rather than corrupting every descendant.
Tolerance is 1/60 px (0.01667), the coarsest engine quantization — Gecko app units. Blink and WebKit quantize at 1/64. Differences below tolerance are quantization noise.
Both an exact verdict (within tolerance or not) and the raw signed delta are reported. The verdict gates pass/fail; the delta is the dense reward signal and needs no threshold.