Firefox is an excellent choice for automated testing and web scraping — it has official WebDriver support (geckodriver), a stable API, and unique features like network request interception that make it valuable in CI pipelines.

geckodriver: Firefox's WebDriver

Firefox uses geckodriver as its WebDriver implementation. Unlike Chrome's chromedriver which is bundled with Chrome, geckodriver is a separate download.

Setup:

# Download geckodriver from GitHub releases # https://github.com/mozilla/geckodriver/releases wget https://github.com/mozilla/geckodriver/releases/download/v0.34.0/geckodriver-v0.34.0-linux64.tar.gz tar -xzf geckodriver*.tar.gz sudo mv geckodriver /usr/local/bin/ # Verify geckodriver --version 

Using with Selenium (Python):

from selenium import webdriver from selenium.webdriver.firefox.options import Options options = Options() options.add_argument('--headless') # Headless mode  driver = webdriver.Firefox(options=options) driver.get('https://example.com') print(driver.title) driver.quit() 

Using with Selenium (Node.js):

const { Builder, By, until } = require('selenium-webdriver'); const firefox = require('selenium-webdriver/firefox'); const options = new firefox.Options().headless(); const driver = await new Builder() .forBrowser('firefox') .setFirefoxOptions(options) .build(); await driver.get('https://example.com'); console.log(await driver.getTitle()); await driver.quit(); 

Firefox with Playwright

Playwright has first-class Firefox support using a patched Firefox build:

const { firefox } = require('@playwright/test'); // Launch Firefox const browser = await firefox.launch({ headless: true }); const context = await browser.newContext(); const page = await context.newPage(); await page.goto('https://example.com'); console.log(await page.title()); await browser.close(); 

Install Playwright Firefox:

npm install @playwright/test npx playwright install firefox 

Firefox-specific options:

const browser = await firefox.launch({ headless: true, firefoxUserPrefs: { 'media.navigator.enabled': false, // Disable media APIs 'privacy.trackingprotection.enabled': false, // Disable ETP for testing 'network.http.speculative-parallel-limit': 0 // Disable speculative connections } }); 

Firefox Profile Management in Tests

Using a specific profile with geckodriver:

profile = webdriver.FirefoxProfile('/path/to/profile') # Or create a temp profile with custom prefs: profile.set_preference('network.proxy.type', 1) profile.set_preference('network.proxy.http', 'proxy.example.com') profile.set_preference('network.proxy.http_port', 8080) driver = webdriver.Firefox(firefox_profile=profile) 

Pre-loaded cookies/session: If you need to test authenticated pages:

# Load an existing profile with saved session profile = webdriver.FirefoxProfile('/path/to/profile-with-session') driver = webdriver.Firefox(firefox_profile=profile) driver.get('https://your-app.com/dashboard') # Should be logged in from the profile's cookies 

Network Interception

With Playwright (more powerful than Selenium):

// Block external resources await page.route('**/*.{png,jpg,gif,svg}', route => route.abort()); // Mock API responses await page.route('**/api/users', async route => { await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify([{ id: 1, name: 'Test User' }]) }); }); // Intercept and log requests page.on('request', request => { console.log(`${request.method()} ${request.url()}`); }); // Intercept responses page.on('response', response => { if (response.url().includes('/api/')) { console.log(`Response: ${response.status()} ${response.url()}`); } }); 

Capturing Screenshots and Videos

Playwright:

// Screenshot await page.screenshot({ path: 'screenshot.png', fullPage: true }); // Video recording (configured at context level) const context = await browser.newContext({ recordVideo: { dir: 'videos/' } }); const page = await context.newPage(); // ... test actions ... await context.close(); // Saves the video 

Selenium:

driver.save_screenshot('screenshot.png') # Full page screenshot requires scrolling manually or browser extension 

Geckodriver vs ChromeDriver Behavior Differences

User-Agent: Firefox's default UA differs from Chrome. If your site serves different content based on UA, tests may differ.

Network timing: Firefox's network stack has slightly different timing characteristics. Don't rely on exact timing in tests.

JavaScript timing: SpiderMonkey (Firefox's JS engine) can have different performance characteristics from V8 for CPU-intensive operations. Avoid time-sensitive tests.

Default behavior on unhandled alerts: Firefox and Chrome handle window.confirm() dialogs slightly differently in WebDriver mode. Explicitly handle or dismiss dialogs.

geolocation: Firefox prompts for permission; Chrome grants automatically in WebDriver mode. Handle geolocation permission in test setup:

// Playwright const context = await browser.newContext({ geolocation: { latitude: 40.7128, longitude: -74.0060 }, permissions: ['geolocation'] }); 

Testing Firefox Extensions

// Load a Firefox extension in Playwright const context = await firefox.launchPersistentContext('/tmp/profile', { headless: false, firefoxUserPrefs: { 'extensions.webextensions.temporary.install': true } }); // Note: Loading unpacked extensions requires Playwright Firefox's specific API 

With web-ext + geckodriver:

# Run Firefox with extension and geckodriver for testing web-ext run --pre-start-cmd='geckodriver &' --source-dir=./extension 

For CI testing of extensions, the typical approach is to use Playwright and load the extension's .xpi file.

Headless Firefox in Docker

FROM ubuntu:22.04 RUN apt-get update && apt-get install -y \  firefox-esr \  wget \  && rm -rf /var/lib/apt/lists/* # Install geckodriver RUN wget https://github.com/mozilla/geckodriver/releases/download/v0.34.0/geckodriver-v0.34.0-linux64.tar.gz \  && tar -xzf geckodriver*.tar.gz \  && mv geckodriver /usr/local/bin/ \  && rm geckodriver*.tar.gz # Or use Playwright which manages its own Firefox RUN npm install @playwright/test && npx playwright install firefox --with-deps WORKDIR /tests COPY . . CMD ["npx", "playwright", "test", "--project=firefox"] 

Common Docker issues:

  • Missing fonts: Install fonts-freefont-ttf or fonts-noto
  • Missing WebGL: Add --disable-gpu equivalent: firefoxUserPrefs: { 'webgl.disabled': true }
  • X11 requirement for some versions: Use --headless mode explicitly

Web Scraping Considerations

Firefox for scraping: Firefox is a valid choice when:

  • The site detects Chrome-specific APIs and blocks scraping
  • You need extension-based automation (Firefox supports more flexible userScripts)
  • Your scraping requires matching Firefox's user-agent/fingerprint

Avoiding detection:

// Playwright Firefox with custom user-agent const context = await browser.newContext({ userAgent: 'Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0', viewport: { width: 1280, height: 720 } }); 

Ethical scraping note: Respect robots.txt, rate limit your requests, and review the site's terms of service before scraping.

Firefox in CI: GitHub Actions

jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 - run: npm install - run: npx playwright install --with-deps firefox - run: npx playwright test --project=firefox 

GitHub Actions provides ubuntu-latest which includes Firefox ESR. Playwright's --with-deps installs any system dependencies Firefox needs (fonts, libraries).