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).
Top comments (0)