Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 16 additions & 5 deletions src/firefox/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -364,11 +364,22 @@ export class FirefoxCore {
}
}

// 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);
// Giving the service an executable skips getBinaryPaths(), which resolves
// geckodriver *and* the Firefox binary injected into moz:firefoxOptions.binary.
// Only do that where Selenium Manager cannot run: win32 hangs when invoked from
// the MCP (Bug 2040849), non-x64 Linux ships an x86-64 binary (Bug 2062055).
const mustResolveGeckodriver =
process.platform === 'win32' || (process.platform === 'linux' && process.arch !== 'x64');

let serviceBuilder;
if (mustResolveGeckodriver) {
const geckodriverPath = await findGeckodriver();
logDebug(`Using geckodriver: ${geckodriverPath}`);
serviceBuilder = new firefox.ServiceBuilder(geckodriverPath);
} else {
logDebug('Letting Selenium Manager resolve geckodriver and Firefox');
serviceBuilder = new firefox.ServiceBuilder();
}

if (this.logFilePath) {
// Create the parent directory, as the generated-path branch above does.
Expand Down
59 changes: 48 additions & 11 deletions tests/firefox/core.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import { join } from 'node:path';
import { MCP_PROFILE_DIR_NAME } from '@/firefox/profile.js';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { FirefoxCore } from '@/firefox/core.js';
import type { FirefoxLaunchOptions } from '@/firefox/types.js';

Expand Down Expand Up @@ -389,18 +389,55 @@ describe('FirefoxCore connect() profile handling', () => {
);
});

// 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');
// Bug 2062055 and its follow-up. Giving the DriverService an executable also
// opts out of Selenium Manager resolving the Firefox binary, so the explicit
// path is only correct where Selenium Manager cannot run.
describe('geckodriver resolution', () => {
const platform = process.platform;
const arch = process.arch;

const core = new FirefoxCore({ headless: true });
await core.connect();
const setPlatform = (value: NodeJS.Platform, archValue: string) => {
Object.defineProperty(process, 'platform', { value, configurable: true });
Object.defineProperty(process, 'arch', { value: archValue, configurable: true });
};

afterEach(() => {
setPlatform(platform, arch);
});

it.each([
['win32', 'x64'],
['linux', 'arm64'],
] as const)(
'resolves geckodriver explicitly on %s/%s, where Selenium Manager cannot',
async (osName, archName) => {
setPlatform(osName, archName);
const { FirefoxCore } = await import('@/firefox/core.js');

await new FirefoxCore({ headless: true }).connect();

expect(mockServiceBuilderCtor).toHaveBeenCalledTimes(1);
const [geckodriverPath] = mockServiceBuilderCtor.mock.calls[0] as [unknown];
expect(typeof geckodriverPath).toBe('string');
expect(String(geckodriverPath)).toContain('geckodriver');
}
);

it.each([
['linux', 'x64'],
['darwin', 'arm64'],
] as const)(
'leaves the service executable unset on %s/%s, so Selenium Manager still finds Firefox',
async (osName, archName) => {
setPlatform(osName, archName);
const { FirefoxCore } = await import('@/firefox/core.js');

expect(mockServiceBuilderCtor).toHaveBeenCalledTimes(1);
const [geckodriverPath] = mockServiceBuilderCtor.mock.calls[0] as [unknown];
expect(typeof geckodriverPath).toBe('string');
expect(String(geckodriverPath)).toContain('geckodriver');
await new FirefoxCore({ headless: true }).connect();

expect(mockServiceBuilderCtor).toHaveBeenCalledTimes(1);
expect(mockServiceBuilderCtor.mock.calls[0]).toHaveLength(0);
}
);
});
});

Expand Down