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

  1. 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
  2. Increase the actionability deadline for slow pages, or add explicit waits for the loading overlay to disappear before acting
  3. 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

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


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)