From cd574cb4186ce169cfa01b574eeef98be5006adf Mon Sep 17 00:00:00 2001 From: Julian Descottes Date: Fri, 12 Jun 2026 13:38:50 +0200 Subject: [PATCH] Bug 2018268 - feature: expose firefox version number in get_firefox_info --- src/firefox/core.ts | 19 ++++++++++++--- src/firefox/index.ts | 8 +++++++ src/tools/firefox-management.ts | 2 ++ src/utils/version.ts | 28 ++++++++++++++++++++++ tests/firefox/core-prefs.test.ts | 1 + tests/firefox/core.test.ts | 1 + tests/tools/firefox-management.test.ts | 1 + tests/utils/version.test.ts | 33 ++++++++++++++++++++++++++ 8 files changed, 90 insertions(+), 3 deletions(-) create mode 100644 src/utils/version.ts create mode 100644 tests/utils/version.test.ts diff --git a/src/firefox/core.ts b/src/firefox/core.ts index f8ee64e..a19992c 100644 --- a/src/firefox/core.ts +++ b/src/firefox/core.ts @@ -85,11 +85,12 @@ async function findGeckodriver(): Promise { } export class FirefoxCore { - private driver: WebDriver | null = null; private currentContextId: string | null = null; - private originalEnv: Record = {}; - private logFilePath: string | undefined; + private driver: WebDriver | null = null; + private firefoxVersion: string | null = null; private logFileFd: number | undefined; + private logFilePath: string | undefined; + private originalEnv: Record = {}; private profileWarning: string | null = null; constructor(private options: FirefoxLaunchOptions) {} @@ -254,6 +255,11 @@ export class FirefoxCore { this.options.connectExisting ? 'Connected to existing Firefox' : 'Firefox launched with BiDi' ); + // Retrieve the Firefox version from the returned capabilities. + const driverCapabilities = await this.driver.getCapabilities(); + this.firefoxVersion = (driverCapabilities.get('browserVersion') as string) ?? null; + logDebug(`Browser version: ${this.firefoxVersion}`); + // Remember current window handle (browsing context) this.currentContextId = await this.driver.getWindowHandle(); logDebug(`Browsing context ID: ${this.currentContextId}`); @@ -328,6 +334,13 @@ export class FirefoxCore { this.currentContextId = contextId; } + /** + * Get the current firefox version, as a string (eg "153.0a1") + */ + getFirefoxVersion(): string | null { + return this.firefoxVersion; + } + /** * Get log file path */ diff --git a/src/firefox/index.ts b/src/firefox/index.ts index c729bb8..d3a547d 100644 --- a/src/firefox/index.ts +++ b/src/firefox/index.ts @@ -444,6 +444,14 @@ export class FirefoxClient { return await this.core.isConnected(); } + /** + * Get current browser version (eg "153.0a1"). + * @internal + */ + getFirefoxVersion(): string | null { + return this.core.getFirefoxVersion(); + } + /** * Get log file path (if logging is enabled) */ diff --git a/src/tools/firefox-management.ts b/src/tools/firefox-management.ts index b6b22c6..9299db6 100644 --- a/src/tools/firefox-management.ts +++ b/src/tools/firefox-management.ts @@ -127,12 +127,14 @@ export async function handleGetFirefoxInfo(_input: unknown) { const firefox = await getFirefox(); const options = firefox.getOptions(); const logFilePath = firefox.getLogFilePath(); + const version = firefox.getFirefoxVersion(); const info = []; info.push('Firefox Instance Configuration'); info.push(''); info.push(`Binary: ${options.firefoxPath ?? 'System Firefox (default)'}`); + info.push(`Firefox version: ${version ?? '(unknown)'}`); info.push(`Headless: ${options.headless ? 'Yes' : 'No'}`); if (options.viewport) { diff --git a/src/utils/version.ts b/src/utils/version.ts new file mode 100644 index 0000000..b54e66c --- /dev/null +++ b/src/utils/version.ts @@ -0,0 +1,28 @@ +/** + * Firefox version number helpers. + */ + +function getMajorVersion(version: string): number { + const [major, _rhs] = version.split('.'); + if (!major) { + throw new Error(`Unable to parse Firefox version ${version}`); + } + return Number.parseInt(major, 10); +} + +/** + * Simplified Firefox version comparison helper, only concerned with major + * version checks. + */ +export function compareVersions(versionA: string, versionB: string): number { + const majorA = getMajorVersion(versionA); + const majorB = getMajorVersion(versionB); + + if (majorA < majorB) { + return -1; + } + if (majorA > majorB) { + return 1; + } + return 0; +} diff --git a/tests/firefox/core-prefs.test.ts b/tests/firefox/core-prefs.test.ts index 030217f..da799a9 100644 --- a/tests/firefox/core-prefs.test.ts +++ b/tests/firefox/core-prefs.test.ts @@ -44,6 +44,7 @@ describe('FirefoxCore prefs via firefoxOptions', () => { build: vi.fn().mockResolvedValue({ getWindowHandle: mockGetWindowHandle, get: vi.fn().mockResolvedValue(undefined), + getCapabilities: vi.fn(() => ({ get: vi.fn(() => '123.4') })), }), })), Browser: { FIREFOX: 'firefox' }, diff --git a/tests/firefox/core.test.ts b/tests/firefox/core.test.ts index 03f8185..bfdf93f 100644 --- a/tests/firefox/core.test.ts +++ b/tests/firefox/core.test.ts @@ -112,6 +112,7 @@ describe('FirefoxCore connect() profile handling', () => { setFirefoxOptions: vi.fn().mockReturnThis(), setFirefoxService: vi.fn().mockReturnThis(), build: vi.fn().mockResolvedValue({ + getCapabilities: vi.fn(() => ({ get: vi.fn(() => '123.4') })), getWindowHandle: vi.fn().mockResolvedValue('mock-context-id'), get: vi.fn().mockResolvedValue(undefined), }), diff --git a/tests/tools/firefox-management.test.ts b/tests/tools/firefox-management.test.ts index f8467ca..fd8949e 100644 --- a/tests/tools/firefox-management.test.ts +++ b/tests/tools/firefox-management.test.ts @@ -222,6 +222,7 @@ describe('Firefox Management Tools', () => { }, }), getLogFilePath: vi.fn().mockReturnValue(undefined), + getFirefoxVersion: vi.fn().mockReturnValue('123.4'), }; mockGetFirefox.mockResolvedValue(mockFirefoxWithPrefs); diff --git a/tests/utils/version.test.ts b/tests/utils/version.test.ts new file mode 100644 index 0000000..488efe3 --- /dev/null +++ b/tests/utils/version.test.ts @@ -0,0 +1,33 @@ +/** + * Unit tests for logger utilities + */ + +import { describe, it, expect } from 'vitest'; +import { compareVersions } from '../../src/utils/version.js'; + +describe('Version helpers', () => { + describe('compareVersions', () => { + it('should return -1 for lower version', () => { + expect(compareVersions('153.0a1', '154.0')).toBe(-1); + expect(compareVersions('16.0a1', '154.0')).toBe(-1); + expect(compareVersions('153.0a1', '154')).toBe(-1); + expect(compareVersions('153', '154.0')).toBe(-1); + }); + it('should return 1 for greater version', () => { + expect(compareVersions('153.0a1', '152.0')).toBe(1); + expect(compareVersions('153.0a1', '99.0')).toBe(1); + expect(compareVersions('153', '99.0')).toBe(1); + expect(compareVersions('153.0a1', '99')).toBe(1); + }); + it('should return 0 for identical major', () => { + expect(compareVersions('153.0a1', '153.0')).toBe(0); + expect(compareVersions('153.0a1', '153.99')).toBe(0); + expect(compareVersions('153.0a1', '153')).toBe(0); + expect(compareVersions('153.0a1', '153.xyz')).toBe(0); + }); + it('should throw on unparseable version string', () => { + expect(() => compareVersions('', '153.0')).toThrow('Unable to parse Firefox version'); + expect(() => compareVersions('153.0', '')).toThrow('Unable to parse Firefox version'); + }); + }); +});