From 4acdae77fc41cb070e4765c581efb104c81e3075 Mon Sep 17 00:00:00 2001 From: Jeremy Schoemaker Date: Sun, 23 Aug 2026 14:51:02 -0500 Subject: [PATCH 1/2] Bug 2062055 - [firefox-devtools-mcp] resolve geckodriver on all platforms 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. --- src/firefox/core.ts | 23 +++++++++++------------ tests/firefox/core.test.ts | 21 +++++++++++++++++++++ 2 files changed, 32 insertions(+), 12 deletions(-) diff --git a/src/firefox/core.ts b/src/firefox/core.ts index ceb9d30..1c32039 100644 --- a/src/firefox/core.ts +++ b/src/firefox/core.ts @@ -349,18 +349,17 @@ export class FirefoxCore { } } - let serviceBuilder; - if (process.platform === 'win32') { - // On windows, firefox.ServiceBuilder() invoked from the MCP will hang. - // geckodriver has to be in the PATH. See Bug 2040849. - const geckodriverPath = await findGeckodriver(); - logDebug(`Using geckodriver: ${geckodriverPath}`); - serviceBuilder = new firefox.ServiceBuilder(geckodriverPath); - } else { - // On other platforms, the default ServiceBuilder should locate and - // start geckodriver successfully. - serviceBuilder = new firefox.ServiceBuilder(); - } + // 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. + const geckodriverPath = await findGeckodriver(); + logDebug(`Using geckodriver: ${geckodriverPath}`); + const serviceBuilder = new firefox.ServiceBuilder(geckodriverPath); if (this.logFilePath) { // Open file for appending, create if doesn't exist diff --git a/tests/firefox/core.test.ts b/tests/firefox/core.test.ts index e9311a5..e9e7de8 100644 --- a/tests/firefox/core.test.ts +++ b/tests/firefox/core.test.ts @@ -317,6 +317,7 @@ describe('FirefoxCore connect() profile handling', () => { const mockWindowSize = vi.fn(); const mockSetAcceptInsecureCerts = vi.fn(); const mockSetStdio = vi.fn(); + const mockServiceBuilderCtor = vi.fn(); beforeEach(() => { vi.clearAllMocks(); @@ -333,7 +334,11 @@ describe('FirefoxCore connect() profile handling', () => { setAcceptInsecureCerts = mockSetAcceptInsecureCerts; }, ServiceBuilder: class { + constructor(...args: unknown[]) { + mockServiceBuilderCtor(...args); + } setStdio = mockSetStdio; + addArguments = vi.fn(); }, }, })); @@ -383,4 +388,20 @@ describe('FirefoxCore connect() profile handling', () => { join('/path/to/test/profile', MCP_PROFILE_DIR_NAME) ); }); + + // 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. + it('should build the geckodriver service with an explicit binary path', async () => { + const { FirefoxCore } = await import('@/firefox/core.js'); + + const core = new FirefoxCore({ headless: true }); + await core.connect(); + + expect(mockServiceBuilderCtor).toHaveBeenCalledTimes(1); + const [geckodriverPath] = mockServiceBuilderCtor.mock.calls[0] as [unknown]; + expect(typeof geckodriverPath).toBe('string'); + expect(String(geckodriverPath)).toContain('geckodriver'); + }); }); From 350e325a794bb8409cd363626bd600f949e7cb34 Mon Sep 17 00:00:00 2001 From: Jeremy Schoemaker Date: Thu, 27 Aug 2026 15:47:58 -0500 Subject: [PATCH 2/2] chore: shorten geckodriver resolution comments per review --- src/firefox/core.ts | 10 ++-------- tests/firefox/core.test.ts | 6 ++---- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/src/firefox/core.ts b/src/firefox/core.ts index 1c32039..d65201a 100644 --- a/src/firefox/core.ts +++ b/src/firefox/core.ts @@ -349,14 +349,8 @@ export class FirefoxCore { } } - // 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. const geckodriverPath = await findGeckodriver(); logDebug(`Using geckodriver: ${geckodriverPath}`); const serviceBuilder = new firefox.ServiceBuilder(geckodriverPath); diff --git a/tests/firefox/core.test.ts b/tests/firefox/core.test.ts index e9e7de8..f00e437 100644 --- a/tests/firefox/core.test.ts +++ b/tests/firefox/core.test.ts @@ -389,10 +389,8 @@ describe('FirefoxCore connect() profile handling', () => { ); }); - // 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. it('should build the geckodriver service with an explicit binary path', async () => { const { FirefoxCore } = await import('@/firefox/core.js');