JuliusBrussee/caveman · error
box model has no quad
Error message
box model has no quad
What it means
After fetching the DOM box model, box needs a coordinate quad (8 floats: 4 x/y points). It prefers the content quad and falls back to the border quad; if both are shorter than 8 elements the model carries no usable geometry, so it fails with 'box model has no quad' rather than computing a garbage bounding box from partial coordinates.
Solutions
- Ensure the element is visible and rendered (scroll it into view, remove display:none) before performing actions on it
- Re-query the element after layout settles; wait for the page/element to be actionable instead of acting immediately
- If the node is stale/detached, re-resolve it to a fresh backend node id and retry
- Fall back to an alternative geometry source (e.g. getBoundingClientRect via Runtime.evaluate) if the CDP box model stays empty
Example fix
// before
await page.Click("#hidden-panel") // display:none -> empty quad
// after
await page.Eval("document.querySelector('#hidden-panel').style.display='block'")
await page.Click("#hidden-panel") Defensive patterns
Strategy: retry
Validate before calling
// guard: only act on elements expected to be rendered
func expectVisible(sel string) error {
if !isDisplayed(sel) { return fmt.Errorf("%s not rendered; skip action", sel) }
return nil
} Try / catch
x, y, w, h, err := d.box(ctx, target)
if err != nil {
if err.Error() == "box model has no quad" {
// element hidden or not laid out: make visible / re-resolve, then retry once
return d.retryAfterVisible(ctx, target)
}
return err
} Prevention
- Wait for elements to be visible/actionable before clicking or typing
- Avoid acting on display:none or detached nodes; re-resolve after DOM updates
- Scroll elements into view first so layout geometry exists
- If quad errors persist, fall back to getBoundingClientRect via JS evaluation
When it happens
Trigger: dom.GetBoxModel returns a BoxModel whose Content and Border slices both have fewer than 8 entries — e.g. the node is display:none, detached from layout, or not rendered — when box is invoked via waitActionable.
Common situations: Clicking/typing on a hidden element (display:none or visibility tricks) that produces an empty box model; an element inside a collapsed or not-yet-laid-out subtree; a detached node whose backend id still resolves but has no geometry; viewport/iframes edge cases where layout is skipped.
Related errors
- browser returned no result object
- missing backend DOM node id
- select: missing option
- browser function returned no result object
- browser function threw
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/ce25a04611841729.
Report an issue: GitHub.
Appendix: source
Thrown at browse/cdp.go:318
func (d *CDPDriver) box(ctx context.Context, target Target) (float64, float64, float64, float64, error) {
if target.BackendDOMNodeID <= 0 {
return 0, 0, 0, 0, errors.New("missing backend DOM node id")
}
var model *dom.BoxModel
if err := chromedp.Run(ctx, chromedp.ActionFunc(func(actionCtx context.Context) error {
var err error
model, err = dom.GetBoxModel().WithBackendNodeID(cdp.BackendNodeID(target.BackendDOMNodeID)).Do(actionCtx)
return err
})); err != nil {
return 0, 0, 0, 0, err
}
quad := model.Content
if len(quad) < 8 {
quad = model.Border
}
if len(quad) < 8 {
return 0, 0, 0, 0, errors.New("box model has no quad")
}
minX, maxX := quad[0], quad[0]
minY, maxY := quad[1], quad[1]
for i := 0; i+1 < len(quad); i += 2 {
minX = math.Min(minX, quad[i])
maxX = math.Max(maxX, quad[i])
minY = math.Min(minY, quad[i+1])
maxY = math.Max(maxY, quad[i+1])
}
return (minX + maxX) / 2, (minY + maxY) / 2, maxX - minX, maxY - minY, nil
}
func (d *CDPDriver) visible(ctx context.Context, target Target) (bool, error) {
return d.callBoolOnNode(ctx, target, `function(){ const s = window.getComputedStyle(this); const r = this.getBoundingClientRect(); return !!s && s.display !== "none" && s.visibility !== "hidden" && s.pointerEvents !== "none" && Number(s.opacity || "1") > 0 && r.width > 0 && r.height > 0; }`)
}
func (d *CDPDriver) enabled(ctx context.Context, target Target) (bool, error) {
return d.callBoolOnNode(ctx, target, `function(){ return !(this.disabled === true || this.getAttribute("disabled") !== null || this.getAttribute("aria-disabled") === "true" || this.closest("[inert]")); }`)View on GitHub (pinned to 3ee70a1026)