JuliusBrussee/caveman · error
element not actionable: last box=%+v
Error message
element not actionable: last box=%+v
What it means
waitActionable polls an element's bounding box until it is visible, stable, and positioned where a real interaction can land. If the element still fails actionability checks when the deadline expires, it fails closed with 'element not actionable: last box=<last observed geometry>' so agents never click invisible or moving targets.
Solutions
- Inspect the last box in the error: width/height 0 means hidden or not rendered — fix the selector to target the visible interactive element, or trigger rendering (scroll/click) first
- Increase the actionability deadline for slow pages, or add explicit waits for the loading overlay to disappear before acting
- If the element moves continuously (animations), disable the animation in the page or wait for it to finish before dispatching the action
Example fix
// before
res, err := driver.Act(ctx, browse.ActionRequest{Target: "#submit"}) // hidden during skeleton load
// after
if err := driver.WaitForHidden(ctx, ".skeleton-loader"); err != nil { return err } // or extend deadline
res, err := driver.Act(ctx, browse.ActionRequest{Target: "#submit"}) Defensive patterns
Strategy: retry
Validate before calling
// check visibility before acting
visible, err := driver.IsVisible(ctx, target)
if err != nil || !visible { return fmt.Errorf("target %s not visible yet", target) } Try / catch
res, err := driver.Act(ctx, req)
if err != nil && strings.Contains(err.Error(), "element not actionable") {
// inspect last box: zero-size => hidden; moving => wait for animation;
// then retry with a longer deadline or corrected selector
} Prevention
- Wait for loading overlays/skeletons to disappear before dispatching actions
- Target visible interactive elements, not hidden wrappers
- Extend deadlines on slow or animated pages
- Re-resolve the target after DOM replacement (virtualized lists)
When it happens
Trigger: Calling Act on an element whose box never stabilizes or is zero/off-screen within the wait deadline: element hidden by CSS (display:none, zero size), covered by overlays, constantly re-rendering, or detached and replaced so its box keeps resetting.
Common situations: Animating or skeleton-loading UIs where the box never settles; modals/spinners covering the target; virtualized lists replacing DOM nodes; lazy-rendered content that never appears because a prerequisite step (scroll, login) was skipped; selectors matching an invisible wrapper element.
Related errors
- unknown action
- start browser
- browser function returned no result object
- browser function threw
- option not found
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/95e77335e14ec568.
Report an issue: GitHub.
Appendix: source
Thrown at browse/cdp.go:284
}
x2, y2, w2, h2, err2 := d.box(ctx, target)
stable := err2 == nil && math.Abs(x-x2) < 0.5 && math.Abs(y-y2) < 0.5 && math.Abs(w-w2) < 0.5 && math.Abs(h-h2) < 0.5
if stable {
visible, err := d.visible(ctx, target)
if err == nil && visible {
enabled, err := d.enabled(ctx, target)
if err == nil && enabled {
ok, err := d.receivesEvents(ctx, target, x2, y2)
if err == nil && ok {
return x2, y2, nil
}
}
}
}
last = boxPoint{x: x2, y: y2, w: w2, h: h2}
}
if time.Now().After(deadline) {
return 0, 0, fmt.Errorf("element not actionable: last box=%+v", last)
}
select {
case <-ctx.Done():
return 0, 0, ctx.Err()
case <-time.After(100 * time.Millisecond):
}
}
}
type boxPoint struct {
x float64
y float64
w float64
h float64
}
func (d *CDPDriver) box(ctx context.Context, target Target) (float64, float64, float64, float64, error) {
if target.BackendDOMNodeID <= 0 {View on GitHub (pinned to 3ee70a1026)