v1.9.4 — clicks that reported success without landing
A click on an element below the fold reported success and did nothing. This release fixes that,
and adds the harness that found it.
The defect
prepareElementForInteraction scrolled only when isElementVisible said no — and that check
asks whether an element is rendered: display, visibility, opacity, and a non-zero rect. A
button 1298px down an 800px viewport passes all four. So no scroll happened, getElementCenter
returned its box-model coordinate, and Input.dispatchMouseEvent interpreted that as a viewport
coordinate and dispatched at a point outside the viewport.
Nothing errored. The command returned {action:"click", selector, x, y, snapshot} — a success
shape, complete with coordinates, for a click that never landed.
Measured on the stress fixture before the fix: a raw bridge.send click reported y=1298 and the
page's own click counter stayed at 0, while smart.click reported y=468 and reached 1.
smart.click differed only because pollActionability happened to scroll first — so the same
command worked or silently missed depending on which path reached it.
The fix
A new isElementInViewport check tests the element's centre, because that is the exact point
a click is dispatched at — a half-visible element's centre is what decides whether the click
lands. prepareElementForInteraction now scrolls when a target is either not rendered or not
reachable.
That covers every handler routed through withFreshElement: click, double-click, hover, type and
the rest, in both modes and through raw bridge.send. After the fix all three paths report
y=468 and each call registers exactly one click — verified with a probe confirming a single call
does not double-fire and that clicking a neighbouring element leaves the counter untouched.
Also fixed
Six tools were missing from the code-mode invocation hints. detect_tables and extract_table
are server-composed and need their smart.* helpers; the four robot_training_* tools have
neither a bridge command nor a smart.* binding and are full-mode only. Code-mode callers who
found them through search got no hint and hit "Unknown command".
New: the stress harness
tests/e2e-stress.mjs drives one matrix through both code mode and --full against a real
browser and a deterministic fixture page. Every case asserts an effect rather than an
acknowledgement — a click is verified by the DOM changing, typing by the field holding the value.
That distinction is the whole point, and it is what caught this defect. It passes 29/0 in code
mode and 31/0 in full mode.
It is not part of npm test: it needs Chrome with the extension loaded, and belongs in the
release checklist as a manual pre-submission gate.
Known characteristic, not fixed
A second server spawned into an already-running session takes around 24 seconds before its first
command can succeed. The single-bridge election is deliberate — it refuses to cut over to a rival
while its incumbent is verified, so a rogue local server cannot hijack a live session — but it
does mean a client's first tool call inside that window times out.
Upgrading
npx crawlio-browser@latest init. If you use the Chrome extension, take the matching build from
the release assets; the fix is in the extension, not the server.