Repository navigation
fix(web): survive a dropped DevTools reply and stop re-finding the focused element by xpath - #13
Merged
Merged
Conversation
aubrey-wodonga
force-pushed
the
aubrey/cdp-lost-reply-retry
branch
from
October 30, 2026 05:23
a662010 to
cae3aec
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.waitForMessagethrewNo message with id N received.executeJSlogged that and returned null, anddeviceInfothen failed every hierarchy read withnull cannot be cast to non-null type kotlin.Int. The flow failed at whatever assertion orrepeat whilewas 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,
incomingends 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.CdpReplyLostExceptionnow carries the close reason and the method (and Chrome'sreasonfield) of any other events on the socket, such asInspector.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.executeJSretries a lost reply within its existing 5 × 200 ms budget and resolves the target again. JS with side effects (scrollBy,window.scroll,tapOnSyntheticElement,smoothScrollFlutter) passesidempotent = 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.deviceInforeads 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 fordocument.activeElementand then looked it up again withfindElement. When the app re-rendered in between, the lookup failed withno such element: Unable to locate element: {"method":"xpath", ...}. It now usesdriver.switchTo().activeElement(), in the cross-origin iframe branch too, which removes the race and two round trips.WebDriver.ktstill has the xpath version; this PR leaves it alone.Testing
CdpClientTestuses 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.CdpWebDriverLostReplyTestuses a MockWebServer stand-in in attach mode. It checks thatdeviceInforetries 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.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.