Skip to content

fix(web): survive a dropped DevTools reply and stop re-finding the focused element by xpath - #13

Merged
aubrey-wodonga merged 2 commits into
mainfrom
aubrey/cdp-lost-reply-retry
Oct 30, 2026
Merged

aubrey-wodonga merged 2 commits into
mainfrom
aubrey/cdp-lost-reply-retry

Conversation

@aubrey-wodonga

Copy link
Copy Markdown
Member

Two web-driver fixes for intermittent failures in headless Chrome (--platform web). Each one failed flows at random points.

Lost DevTools replies are retried

CdpClient.waitForMessage threw No message with id N received. executeJS logged that and returned null, and deviceInfo then failed every hierarchy read with null cannot be cast to non-null type kotlin.Int. The flow failed at whatever assertion or repeat while was polling at the time.

It isn't an id race. Each command opens its own WebSocket, so a socket only ever carries one id. In ktor 2.3.13, incoming ends without throwing only when the peer sends a Close frame or the TCP stream hits EOF. So Chrome closed the page socket after the upgrade and before it replied, in one case 2 ms after the command started. Chrome's reason is still unknown, because the old code threw it away. CdpReplyLostException now carries the close reason and the method (and Chrome's reason field) of any other events on the socket, such as Inspector.detached (target_closed). It deliberately keeps no other event content, since page URLs and console output can carry credentials. I couldn't reproduce it locally: 9,000 evaluates against headless Chrome, including 2 MB replies and screenshots on parallel sockets, lost none.

  • executeJS retries a lost reply within its existing 5 × 200 ms budget and resolves the target again. JS with side effects (scrollBy, window.scroll, tapOnSyntheticElement, smoothScrollFlutter) passes idempotent = false, and a lost reply to that call isn't retried, because Chrome may already have run it. The script injection before it is always retried.
  • deviceInfo reads width and height in one call instead of two. Every hierarchy read calls it, so that's one fewer injection and 100 ms less sleep per read. It casts safely, falls back to the last good size, and fails with a clear message if it has never read one.

Key presses go to Selenium's active element

withActiveElement (pressKey, inputText, eraseText) built an xpath for document.activeElement and then looked it up again with findElement. When the app re-rendered in between, the lookup failed with no such element: Unable to locate element: {"method":"xpath", ...}. It now uses driver.switchTo().activeElement(), in the cross-origin iframe branch too, which removes the race and two round trips. WebDriver.kt still has the xpath version; this PR leaves it alone.

Testing

  • CdpClientTest uses a ktor stand-in DevTools socket. It checks that the close reason and event methods end up on the exception and page data doesn't, and that a reply is still matched past unrelated events.
  • CdpWebDriverLostReplyTest uses a MockWebServer stand-in in attach mode. It checks that deviceInfo retries a dropped reply, keeps the last size when every reply is dropped, and fails clearly when it has none. Removing the retry makes the first test fail.
  • Ran a scratch headless-Chrome test (not committed) that drives CdpWebDriver.pressKey(REMOTE_UP). The key reaches the focused element.
  • ./gradlew :maestro-web:test :maestro-client:test -x buildIosDriver --tests 'CdpClientTest' --tests 'CdpScreencastTest' --tests 'maestro.drivers.*' passes.

Issues fixed

Intermittent E2E web failures from both errors above. Our CI installs releases/latest, so this needs a release before it sees the fix.

@aubrey-wodonga aubrey-wodonga self-assigned this Oct 30, 2026
@aubrey-wodonga
aubrey-wodonga force-pushed the aubrey/cdp-lost-reply-retry branch from a662010 to cae3aec Compare October 30, 2026 05:23
@aubrey-wodonga
aubrey-wodonga merged commit 350f7d9 into main Oct 30, 2026
@aubrey-wodonga
aubrey-wodonga deleted the aubrey/cdp-lost-reply-retry branch October 30, 2026 05:24
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.

1 participant