Skip to content

fix(click): bound boundingBox() in mouse-sequence fallback to prevent 30s timeout 500s#5663

Merged
skyfallsin merged 1 commit into
jo-inc:masterfrom
leoneparise:fix/bounded-mouse-sequence-boundingbox
Jul 19, 2026
Merged

fix(click): bound boundingBox() in mouse-sequence fallback to prevent 30s timeout 500s#5663
skyfallsin merged 1 commit into
jo-inc:masterfrom
leoneparise:fix/bounded-mouse-sequence-boundingbox

Conversation

@leoneparise

Copy link
Copy Markdown
Contributor

Problem

When a /tabs/:tabId/click request hits the mouse-sequence fallback and the target element has detached (page changed between snapshot and click — common on SPAs like LinkedIn search results), locator.boundingBox() is called with no timeout. Playwright waits its default 30s for the element to resolve, which blows the entire handler budget:

{"level":"warn","msg":"click timeout, trying mouse sequence"}
... 27s of silence ...
{"level":"error","msg":"click failed","error":"action timed out after 30000ms"}
{"level":"error","msg":"internal error","error":"action timed out after 30000ms","stack":"Error: action timed out after 30000ms\n    at Timeout._onTimeout (file:///app/server.js:485:31)"}
{"level":"info","msg":"res","status":500,"ms":30938}

Two compounding issues:

  1. The client waits the full 30s and gets an opaque 500.
  2. The generic action timed out after 30000ms message is matched by isTimeoutError(), and since click is in NAVIGATION_TIMEOUT_ACTIONS, handleRouteError() destroys the whole user session (fresh BrowserContext + proxy) — losing every other live tab — over what is really just a stale ref.

Observed in production logs (v1.11.2) during a LinkedIn people-search batch: two such 500s in one run, each costing ~31s and killing the session of a concurrently-used tab.

Fix

In dispatchMouseSequence:

  • boundingBox() is now bounded to min(3000ms, remaining handler budget), floored at 500ms.
  • On timeout it throws a 422 Element not actionable error that (a) returns in ~3s instead of 30s, (b) deliberately avoids the timed out after phrase so isTimeoutError() doesn't nuke the session, and (c) tells the caller to snapshot + retry — which is exactly what the existing post-timeout refresh path recommends.

Tests

New tests/unit/clickBoundingBoxTimeout.test.js (7 tests):

  • source contract: no bare boundingBox() calls remain in server.js; the call site uses bboxTimeout; the error carries statusCode = 422
  • budget math: cap at 3s, pass-through under cap, 500ms floor
  • error classification: new error is NOT a session-destroying timeout; documents that the old behavior WAS

node --check server.js passes; full unit suite: 661 passed, with the only failures (security.test.js, cookies.test.js) failing identically on clean master in my environment (real-browser spawn tests), i.e. pre-existing/environment-dependent.

… 30s handler timeout 500s

When a normal click attempt fails because the element detached after a page
change (SPA re-render between snapshot and click), the mouse-sequence
fallback called locator.boundingBox() with no timeout. Playwright waited its
default 30s for the element to resolve, blowing the entire HANDLER_TIMEOUT_MS
budget and surfacing as:

  click failed: action timed out after 30000ms -> 500 internal error

Worse, that generic 'timed out after' error is classified by isTimeoutError()
as a navigation timeout, so handleRouteError() destroyed the whole user
session (fresh proxy/context) over a stale ref.

Fix:
- boundingBox() is now bounded to min(3s, remaining handler budget),
  floored at 500ms.
- On timeout it throws a 422 'Element not actionable' error whose message
  deliberately avoids the 'timed out after' phrase, so the session survives
  and the client gets an actionable hint (snapshot + retry) in ~3s
  instead of a 500 after 30s.

Adds tests/unit/clickBoundingBoxTimeout.test.js covering the source
contract (no bare boundingBox() calls), the budget math, and the error
classification.
@skyfallsin
skyfallsin merged commit e018394 into jo-inc:master Jul 19, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants