Running Firefox in Headless Mode on CI Servers

This article explains how developers can execute the Firefox Web Browser in headless mode on Continuous Integration (CI) servers that lack a graphical display. It covers the essential system dependencies, the required command-line flags, how to configure popular automated testing frameworks like Selenium and Playwright, and how to implement these steps within a CI pipeline configuration.

Understanding Native Headless Mode

Modern versions of Firefox (version 56 and later) ship with a built-in headless mode. Previously, developers had to use tools like Xvfb (X virtual framebuffer) to simulate a display on Linux-based CI servers. Today, passing the --headless flag tells Firefox to run without rendering a Graphical User Interface (GUI), significantly reducing memory overhead and execution time.

System Dependencies for CI Environments

Even when running without a display, Firefox requires certain underlying OS libraries to function on stripped-down Linux CI containers (like Ubuntu or Debian). Before running Firefox, ensure your CI environment installs the browser along with these required packages:

sudo apt-get update
sudo apt-get install -y firefox libgtk-3-0 libdbus-glib-1-2 libxt6

If you are using Selenium, you must also install geckodriver, which acts as the bridge between your tests and the Firefox browser.

Running Headless Firefox via the Command Line

To run Firefox in headless mode directly from the CI server's terminal, use the --headless flag. This is useful for quick smoke tests or taking automated screenshots.

# Launch Firefox in headless mode and capture a screenshot of a URL
firefox --headless --screenshot https://example.com

# Specify a custom window size for the screenshot
firefox --headless --window-size=1920,1080 --screenshot https://example.com

Configuring Automated Testing Frameworks

Most developers run Firefox in CI through automated testing frameworks. You must configure your framework's driver to pass the --headless argument to the browser upon initialization.

Selenium (Python)

from selenium import webdriver
from selenium.webdriver.firefox.options import Options

options = Options()
options.add_argument("--headless")

driver = webdriver.Firefox(options=options)
driver.get("https://example.com")
print(driver.title)
driver.quit()

Selenium (Node.js)

const { Builder } = require('selenium-webdriver');
const firefox = require('selenium-webdriver/firefox');

(async function runTest() {
  let options = new firefox.Options();
  options.addArguments('--headless');

  let driver = await new Builder()
    .forBrowser('firefox')
    .setFirefoxOptions(options)
    .build();

  try {
    await driver.get('https://example.com');
  } finally {
    await driver.quit();
  }
})();

Selenium (Java)

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.firefox.FirefoxOptions;

public class HeadlessFirefox {
    public static void main(String[] args) {
        FirefoxOptions options = new FirefoxOptions();
        options.addArguments("--headless");

        WebDriver driver = new FirefoxDriver(options);
        driver.get("https://example.com");
        driver.quit();
    }
}

Playwright

Playwright runs browsers in headless mode by default. You only need to specify Firefox as the target browser in your script or configuration.

const { firefox } = require('playwright');

(async () => {
  const browser = await firefox.launch({ headless: true });
  const page = await browser.newPage();
  await page.goto('https://example.com');
  await browser.close();
})();

Example CI Pipeline Configuration

Below is an example of a GitHub Actions workflow that sets up a headless environment, installs Firefox, and runs a test suite without a graphical display.

name: Firefox Headless CI

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v3

      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.10'

      - name: Install Firefox and Geckodriver
        run: |
          sudo apt-get update
          sudo apt-get install -y firefox
          wget https://github.com/mozilla/geckodriver/releases/download/v0.33.0/geckodriver-v0.33.0-linux64.tar.gz
          tar -xvzf geckodriver*
          chmod +x geckodriver
          sudo mv geckodriver /usr/local/bin/

      - name: Install Python dependencies
        run: pip install selenium pytest

      - name: Run headless tests
        run: pytest

Troubleshooting Common CI Issues

  • Viewport Size: Headless browsers often default to a smaller window size (e.g., 800x600), which can cause responsive web elements to hide and tests to fail. Always explicitly set the window size using --window-size=1920,1080 in your options.
  • Shared Memory Crashes: Docker containers in CI environments typically have a small /dev/shm (shared memory) partition (64MB), which can cause Firefox to crash on heavy pages. Fix this by setting the MOZ_FORCE_DISABLE_E10S=1 environment variable or mounting a larger shared memory volume (--shm-size=2g) in your Docker configuration.