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-ttforfonts-noto - Missing WebGL: Add
--disable-gpuequivalent:firefoxUserPrefs: { 'webgl.disabled': true } - X11 requirement for some versions: Use
--headlessmode 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).
