Skip to content

fix: connect-existing with remote/containerized Firefox#50

Merged
freema merged 1 commit intomozilla:mainfrom
MayCXC:connect-existing-bidi-v2
Mar 29, 2026
Merged

fix: connect-existing with remote/containerized Firefox#50
freema merged 1 commit intomozilla:mainfrom
MayCXC:connect-existing-bidi-v2

Conversation

@MayCXC
Copy link
Copy Markdown
Contributor

@MayCXC MayCXC commented Mar 28, 2026

Summary

Fixes several issues that prevented --connect-existing from working with Firefox running on a remote host or in a separate container (e.g., via SSH tunnel).

Bug fixes

  1. --marionette-host CLI arg was silently ignored. The option was parsed and the type had the field, but it was never passed to the options object. Geckodriver always connected to 127.0.0.1.

  2. selenium-manager downloaded a 307MB Firefox binary. In connect-existing mode Firefox is already running. Changed --browser firefox to --driver geckodriver so selenium-manager only downloads the driver (~5MB).

  3. No cleanup on disconnect. No lifecycle handling existed. The Marionette session was never released when the MCP client disconnected, so Firefox refused new connections until restarted. Added SIGTERM/SIGINT/stdin handlers that clean up Firefox and close the server before exiting, following the pattern from the MCP SDK examples. StdioServerTransport does not currently fire onclose on stdin EOF (PR #1814), so raw stdin listeners are included as a workaround.

  4. kill() skipped session cleanup. Was synchronous and did not send DELETE /session. Made async with session cleanup so Marionette accepts new connections.

  5. Seamless reconnect after Firefox restart. getFirefox() now detects a lost connection and reconnects transparently instead of throwing an error on the first call.

New features

  1. --marionette-host parameter. Controls which host geckodriver connects to for Marionette and where the BiDi WebSocket URL is rewritten to point. Supports MARIONETTE_HOST env var.

  2. BiDi support for connect-existing. Opens a WebSocket to Firefox's Remote Agent using the webSocketUrl session capability, enabling console and network event monitoring.

Test plan

  • list_pages works with --connect-existing --marionette-host host.internal
  • list_console_messages returns BiDi data
  • Quit and restart Firefox without MCP reconnect: seamless reconnect on next tool call, pages and BiDi both work
  • MCP reconnect without quitting Firefox: old container exits cleanly (no zombie), new container connects, pages and BiDi both work
  • No stale Marionette sessions after disconnect

Supersedes #51.

@MayCXC MayCXC force-pushed the connect-existing-bidi-v2 branch from 1b0f461 to fcd5855 Compare March 28, 2026 08:16
@MayCXC MayCXC force-pushed the connect-existing-bidi-v2 branch 2 times, most recently from ae0f07a to 5f6b39f Compare March 29, 2026 11:54
@MayCXC MayCXC changed the title Enable BiDi support for connect-existing mode fix: connect-existing with remote/containerized Firefox Mar 29, 2026
@MayCXC MayCXC force-pushed the connect-existing-bidi-v2 branch 6 times, most recently from 478f62d to 4740414 Compare March 29, 2026 13:41
When using --connect-existing to connect to Firefox running on a
different host (e.g., via SSH tunnel or in a separate container),
several issues prevented it from working:

1. The --marionette-host CLI arg was parsed but never passed to
   the options object, so geckodriver always connected to 127.0.0.1.

2. selenium-manager was invoked with --browser firefox, causing it
   to download a 307MB Firefox binary that connect-existing never
   uses. Changed to --driver geckodriver to fetch only the driver.

3. No cleanup on disconnect: the Marionette session was never
   released when the MCP client disconnected, so Firefox refused
   new connections until restarted. Added SIGTERM/SIGINT/stdin
   handlers that call firefox.close() before exit. Raw stdin
   listeners are needed because StdioServerTransport does not fire
   onclose on stdin EOF.

4. kill() was synchronous and skipped DELETE /session, leaving
   stale sessions. Made it async and added session cleanup.

5. BiDi was unavailable in connect-existing mode. Added WebSocket
   support via the webSocketUrl session capability, with URL
   rewriting for remote hosts. BiDi subscription failure is
   non-fatal so Classic WebDriver continues working.
@MayCXC MayCXC force-pushed the connect-existing-bidi-v2 branch from 4740414 to c02fc02 Compare March 29, 2026 13:48
@MayCXC
Copy link
Copy Markdown
Contributor Author

MayCXC commented Mar 29, 2026

@juliandescottes @freema iterated on this a bit, it is ready now.

@freema
Copy link
Copy Markdown
Collaborator

freema commented Mar 29, 2026

Hey @MayCXC, this looks great, thanks! I'll merge this into main. I'll add tests and do some manual testing, then cut a new release.

Small note: kill() is now async but reset() calls it without await — I'll fix that when adding tests.

@freema freema merged commit 6e17b50 into mozilla:main Mar 29, 2026
freema added a commit that referenced this pull request Mar 29, 2026
- Fix prettier formatting (long lines, missing line breaks)
- Fix curly brace requirement for if statements
- Fix no-base-to-string: use String() instead of .toString()
- Fix no-floating-promises: void async kill() in reset()
- Fix no-misused-promises: wrap async cleanup in void handler
- Remove unused FirefoxDisconnectedError import
- Fix IBiDi type to include subscribe method
- Fix type narrowing for bidiConnection return

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
freema added a commit that referenced this pull request Mar 29, 2026
Tests cover:
- GeckodriverHttpDriver BiDi: getBidi(), subscribe, caching, error handling
- Session cleanup: kill() and quit() send DELETE /session
- kill() resilience when DELETE fails
- marionetteHost option passthrough
- Reconnect: reset() clears driver state

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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