Skip to content

Bug 2062055 - [firefox-devtools-mcp] resolve geckodriver on all platforms so aarch64 Linux can launch - #170

Merged
juliandescottes merged 2 commits into
mozilla:mainfrom
shoemoney:bug-2062055-arm64-geckodriver
Aug 28, 2026
Merged

juliandescottes merged 2 commits into
mozilla:mainfrom
shoemoney:bug-2062055-arm64-geckodriver

Conversation

@shoemoney

@shoemoney shoemoney commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

connect() built the geckodriver service without a driver path on every non-Windows platform:

let serviceBuilder;
if (process.platform === 'win32') {
  const geckodriverPath = await findGeckodriver();     // Bug 2040849
  serviceBuilder = new firefox.ServiceBuilder(geckodriverPath);
} else {
  // On other platforms, the default ServiceBuilder should locate and
  // start geckodriver successfully.
  serviceBuilder = new firefox.ServiceBuilder();       // <- falls through to selenium-manager
}

With no path, selenium-webdriver invokes its bundled selenium-manager. The Linux binary shipped with selenium-webdriver@4.36.0 is x86-64 only:

$ file node_modules/selenium-webdriver/bin/linux/selenium-manager
node_modules/selenium-webdriver/bin/linux/selenium-manager: ELF 64-bit LSB pie executable,
  x86-64, version 1 (SYSV), static-pie linked, stripped

$ node_modules/selenium-webdriver/bin/linux/selenium-manager --version
bash: cannot execute binary file: Exec format error

So on aarch64 the session dies before Firefox is ever contacted, and a native geckodriver sitting in PATH does not help, because nothing on this branch looks there.

Confirming the report

@masato.kojima's diagnosis is exactly right, and I could reproduce it on real hardware. Raspberry Pi, aarch64 Ubuntu 24.04, official Mozilla linux64-aarch64 Firefox, geckodriver 0.37.1 aarch64 in PATH, unmodified main at e04a955:

host: aarch64   firefox: Mozilla Firefox 154.0   geckodriver: 0.37.1
FAIL pi/BASELINE (12ms) -> Unable to obtain browser driver.

Same error string as the bug, and it fails in 12ms, before any browser work, which is the tell that this is the driver lookup and not Firefox.

The change

Resolve the path on every platform. This was the only one of four ServiceBuilder sites not already doing so: the Android path and the --connect-existing path both call findGeckodriver() unconditionally, and the Windows branch of this very block already did. The else was the outlier.

findGeckodriver() checks PATH, then ~/.cache/selenium/geckodriver, then downloads a platform-correct binary via the geckodriver package (a regular dependency at 6.0.2). None of those tiers can produce a wrong-architecture binary.

The diff on the source is net -12/+11: it deletes the branch rather than adding one.

Verification

Same Pi, same Firefox, same geckodriver, one-file patch:

result
main (e04a955) FAIL in 12ms, Unable to obtain browser driver.
with this patch PASS in 3411ms, Gecko/20100101 Firefox/154.0

The regression I was worried about, and checked. Passing a driver path also stops selenium-webdriver calling getBinaryPaths(), which is how selenium-manager discovers the Firefox binary, and setBinary() is only called here when --firefox-path is given. So the question was whether geckodriver finds Firefox on its own. On macOS, launching with no --firefox-path:

mac/baseline/no-path   PASS (11009ms)  Firefox/154.0
mac/fixed/no-path      PASS ( 7210ms)  Firefox/154.0

It does, and it is ~3.8s faster because the selenium-manager round trip is gone. On the Pi it passes both with and without --firefox-path.

Full suite on macOS, including the integration tests against a real Firefox:

npm run lint          clean
prettier --check      clean
tsc --noEmit          clean
npm run build         clean
vitest run            50 files, 650 tests passed

Test

One unit test in tests/firefox/core.test.ts, using the existing mocked-Selenium harness. It captures the ServiceBuilder constructor argument, which is the thing that was missing. On unmodified main:

x should build the geckodriver service with an explicit binary path
AssertionError: expected 'undefined' to be 'string'

That undefined is the bug: it is the argument selenium-manager gets invoked in place of.

I did not add an integration test for this: reproducing it requires an aarch64 Linux host, and CI runs x86-64, so the test would pass there for the wrong reason and prove nothing. The hardware evidence above is in this PR instead.

Note

The bug is still UNCONFIRMED in Bugzilla. Everything above reproduces it on real aarch64 hardware, so it can be confirmed if that is useful, happy to add the details there.

中文说明

在非 Windows 平台上,connect() 构造 geckodriver 服务时未传入驱动路径,导致 selenium-webdriver 回退到其内置的 selenium-manager。而 selenium-webdriver@4.36.0 附带的 Linux 版 selenium-manager 仅为 x86-64 二进制,在 aarch64 主机上无法执行(Exec format error),因此会话在接触 Firefox 之前就以 Unable to obtain browser driver 失败,即使 PATH 中已存在原生 aarch64 geckodriver 也无济于事。

本改动在所有平台上都自行解析 geckodriver 路径。这是本仓库四处 ServiceBuilder 调用中唯一尚未这样做的一处:Android 路径与 --connect-existing 路径均已无条件调用 findGeckodriver(),同一代码块的 Windows 分支也是如此。

在树莓派(aarch64 Ubuntu 24.04、官方 aarch64 Firefox 154.0、geckodriver 0.37.1)上验证:main 失败(12ms),打上本补丁后通过(3411ms)。同时在 macOS 上确认,由于传入驱动路径也会跳过 selenium-manager 对 Firefox 二进制的查找,在不指定 --firefox-path 时 Firefox 仍能被正确发现,且启动快约 3.8 秒。

macOS 全量测试(含针对真实 Firefox 的集成测试):50 个文件、650 个测试全部通过。

…orms so aarch64 Linux can launch

The normal launch path built `new firefox.ServiceBuilder()` with no driver
path on every non-Windows platform, so selenium-webdriver fell back to its
bundled selenium-manager. The Linux selenium-manager shipped with
selenium-webdriver 4.36.0 is an x86-64 binary:

  node_modules/selenium-webdriver/bin/linux/selenium-manager:
    ELF 64-bit LSB pie executable, x86-64, static-pie linked, stripped

On an aarch64 host it cannot execute, and the session fails immediately with
"Unable to obtain browser driver" even when a native aarch64 geckodriver is
in PATH.

This was the only one of four ServiceBuilder sites not already resolving the
path itself. The Android and --connect-existing paths call findGeckodriver()
unconditionally, and the Windows branch of this same block already did too
(Bug 2040849). This makes the remaining branch consistent with them.

findGeckodriver() checks PATH, then the selenium cache, then downloads a
platform-correct binary via the geckodriver package, which is a regular
dependency. Verified on macOS that Firefox is still discovered without
--firefox-path, since passing a driver path also disables selenium-manager's
browser lookup.

@juliandescottes juliandescottes left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks again for the patches!
I think I kept the non-windows branch as-is to avoid regressions, but it sounds better to use a single strategy to locate geckodriver here.

Comments are a bit too long and might get stale soon-ish, let's simplify them.

Comment thread src/firefox/core.ts Outdated
Comment on lines +352 to +359
// Always resolve geckodriver ourselves rather than letting selenium-webdriver
// fall back to its bundled selenium-manager. The Linux selenium-manager
// shipped with selenium-webdriver 4.36.0 is an x86-64 binary, so on an
// aarch64 host it fails with "Exec format error" and the session dies with
// "Unable to obtain browser driver" even when a native geckodriver is in
// PATH. See Bug 2062055. On Windows the same call would hang instead
// (Bug 2040849). findGeckodriver() checks PATH, then the selenium cache,
// then downloads a platform-correct binary via the geckodriver package.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use a shorter comment

Suggested change
// Always resolve geckodriver ourselves rather than letting selenium-webdriver
// fall back to its bundled selenium-manager. The Linux selenium-manager
// shipped with selenium-webdriver 4.36.0 is an x86-64 binary, so on an
// aarch64 host it fails with "Exec format error" and the session dies with
// "Unable to obtain browser driver" even when a native geckodriver is in
// PATH. See Bug 2062055. On Windows the same call would hang instead
// (Bug 2040849). findGeckodriver() checks PATH, then the selenium cache,
// then downloads a platform-correct binary via the geckodriver package.
// Always resolve geckodriver ourselves rather than relying on selenium
// entirely. See Bug 2062055, 2040849.

Comment thread tests/firefox/core.test.ts Outdated
Comment on lines +392 to +395
// Bug 2062055: with no geckodriver path, selenium-webdriver falls back to its
// bundled selenium-manager, whose Linux binary is x86-64 only — so on aarch64
// the session dies with "Unable to obtain browser driver" even when a native
// geckodriver is in PATH. Resolving the path ourselves avoids that entirely.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Bug 2062055: with no geckodriver path, selenium-webdriver falls back to its
// bundled selenium-manager, whose Linux binary is x86-64 only — so on aarch64
// the session dies with "Unable to obtain browser driver" even when a native
// geckodriver is in PATH. Resolving the path ourselves avoids that entirely.
// Bug 2062055: geckodriver path should always be resolved before
// calling the ServiceBuilder.

@shoemoney

Copy link
Copy Markdown
Contributor Author

Shortened both comments per your suggestions, in 350e325.

@juliandescottes

Copy link
Copy Markdown
Collaborator

Thanks a lot @shoemoney ! Merging.

@juliandescottes
juliandescottes merged commit 3c031f5 into mozilla:main Aug 28, 2026
1 check passed
@mightykatun

mightykatun commented Sep 8, 2026

Copy link
Copy Markdown

This introduced a browser-resolution regression in 0.10.2 on x86-64 Linux.

Environment: Linux x86-64, Node.js 24.20.0, Firefox not installed (therefore no geckodriver-discoverable system Firefox and no firefox/firefox-esr on PATH).

With 0.10.2 and no --firefox-path, the first tool call fails:

Unable to detect Firefox binary automatically, please provide the full path via --firefox-path

Original error: Expected browser binary location, but unable to find binary in default location, no 'moz:firefoxOptions.binary' capability provided, and no binary flag set on the command line

With 0.10.1, an end-to-end list_pages call succeeds without --firefox-path. Selenium Manager resolves/downloads both components and selects its managed Firefox under ~/.cache/selenium/firefox/. Supplying that same cached Firefox explicitly to 0.10.2 also works.

The root cause is the interaction noted in this PR description: selenium-webdriver's Firefox Driver.createSession() calls getBinaryPaths(caps) only when the supplied DriverService has no executable. That call obtains both driverPath and browserPath, then inserts the latter into moz:firefoxOptions.binary. Always constructing new firefox.ServiceBuilder(geckodriverPath) skips that entire path. It fixes the incompatible x86-64 Selenium Manager binary on aarch64 Linux, but also disables automatic Firefox discovery/provisioning on x86-64 Linux where Selenium Manager works.

I think the least-regressive fix is to bypass Selenium Manager only where required:

const mustResolveGeckodriver =
  process.platform === 'win32' ||
  (process.platform === 'linux' && process.arch !== 'x64');

const serviceBuilder = mustResolveGeckodriver
  ? new firefox.ServiceBuilder(await findGeckodriver())
  : new firefox.ServiceBuilder();

This preserves the prior Windows workaround and the non-x64 Linux fix while restoring Selenium Manager's joint driver/browser resolution on x64 Linux and macOS. Longer term, an architecture-compatible Selenium Manager binary or a separate browser resolver/downloader would permit one uniform path.

The unit test added here currently asserts an explicit service executable on every platform. It should instead cover explicit resolution on Windows/non-x64 Linux and retain a supported-Selenium-Manager case where the executable remains unset.

@juliandescottes

Copy link
Copy Markdown
Collaborator

I will file a follow up bug on Bugzilla, thanks for the heads up.

@shoemoney

Copy link
Copy Markdown
Contributor Author

You're right, and thank you for the report. This is my regression, from #170.

Your diagnosis is exactly the mechanism: getBinaryPaths(caps) runs only when the DriverService has no executable, and it resolves both geckodriver and the Firefox binary it injects into moz:firefoxOptions.binary. Passing a path opts out of both. What makes it worse is that the Android branch in the same function already relies on that behaviour on purpose, with a comment saying so, and I still changed the desktop path without connecting the two.

I've opened #188 with the fix you proposed, gating explicit resolution on win32 and non-x64 Linux and leaving the executable unset everywhere else.

You were also right about the test. The one I added asserted an explicit path on every platform, so it encoded the regression as the expected behaviour and could never have failed on this. I replaced it with per-platform cases and checked they actually fail against the 0.10.2 code rather than only passing against the fix: reverting core.ts fails the two "executable unset" cases and passes the other 26.

Your longer-term point stands too, that an architecture-compatible Selenium Manager binary or our own browser resolver would let every platform share one path. Happy to take that on as a follow-up if the maintainers want it.

Apologies for the broken week, and for the delayed reply.

juliandescottes pushed a commit that referenced this pull request Sep 10, 2026
PR #170 made every platform pass an explicit geckodriver path to
firefox.ServiceBuilder. That fixed aarch64 Linux, where Selenium Manager ships
an x86-64 binary that cannot execute, but it also regressed browser resolution
in 0.10.2 for everyone else.

selenium-webdriver's Firefox Driver.createSession() calls getBinaryPaths() only
when the supplied DriverService has no executable, and that one call resolves
geckodriver *and* the Firefox binary it injects into moz:firefoxOptions.binary.
Handing the service a path therefore also opts out of finding Firefox. On a
machine with no system Firefox, 0.10.1 launched fine because Selenium Manager
downloaded and selected one under ~/.cache/selenium/firefox/; 0.10.2 fails with
"Unable to detect Firefox binary automatically" unless --firefox-path is given.

This is the same mechanism the Android branch relies on deliberately, where
skipping getBinaryPaths() is what stops a desktop binary being injected over
androidPackage.

Resolve geckodriver ourselves only where Selenium Manager genuinely cannot do
the job: win32, where ServiceBuilder() invoked from the MCP hangs (Bug 2040849),
and non-x64 Linux (Bug 2062055). Elsewhere leave the executable unset so
Selenium Manager resolves both halves as it did in 0.10.1.

The test added in #170 asserted an explicit path on every platform, so it
encoded the regression as expected behaviour and could not have caught this. It
is replaced with per-platform cases covering both sides, verified to fail
against the 0.10.2 code and pass against this change.

Reported and diagnosed by @mightykatun on #170.
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.

3 participants