Configuring Chrome, Firefox, Edge and Safari in Selenium with a Driver Factory

๐Ÿ“‹ Table of Contents โ–พ
  1. Building a Multi-Browser DriverFactory
  2. Common Mistakes

Running your tests on Chrome is one line of code: driver = webdriver.Chrome(). Running them on Firefox, Edge, and Safari requires browser-specific options, driver configurations, and platform considerations. A DriverFactory centralises all of this logic so your tests and page objects never know or care which browser they are running on. Change one configuration parameter, and the entire suite switches browsers.

Building a Multi-Browser DriverFactory

The factory pattern reads a browser name from configuration and returns the appropriate WebDriver instance with browser-specific options pre-configured.

from selenium import webdriver
from selenium.webdriver.chrome.options import Options as ChromeOptions
from selenium.webdriver.firefox.options import Options as FirefoxOptions
from selenium.webdriver.edge.options import Options as EdgeOptions
import os


class DriverFactory:
    @staticmethod
    def create(browser_name=None, headless=None, grid_url=None):
        browser = (browser_name or os.getenv("BROWSER", "chrome")).lower()
        is_headless = headless if headless is not None else os.getenv("HEADLESS", "true") == "true"
        grid = grid_url or os.getenv("GRID_URL")

        if browser == "chrome":
            options = ChromeOptions()
            if is_headless:
                options.add_argument("--headless=new")
            options.add_argument("--no-sandbox")
            options.add_argument("--disable-dev-shm-usage")
            options.add_argument("--window-size=1920,1080")
            options.add_argument("--disable-gpu")

        elif browser == "firefox":
            options = FirefoxOptions()
            if is_headless:
                options.add_argument("--headless")
            options.set_preference("browser.download.folderList", 2)
            options.set_preference("browser.helperApps.neverAsk.saveToDisk",
                                   "application/pdf,text/csv")

        elif browser == "edge":
            options = EdgeOptions()
            if is_headless:
                options.add_argument("--headless=new")
            options.add_argument("--no-sandbox")
            options.add_argument("--window-size=1920,1080")

        elif browser == "safari":
            # Safari does not support headless mode
            # safaridriver is built into macOS โ€” no download needed
            # Must enable: Safari > Develop > Allow Remote Automation
            options = webdriver.SafariOptions()
            if is_headless:
                print("WARNING: Safari does not support headless mode. Running headed.")

        else:
            raise ValueError(f"Unsupported browser: {browser}. "
                             f"Choose: chrome, firefox, edge, safari")

        # Create Remote (Grid/Cloud) or Local driver
        if grid:
            driver = webdriver.Remote(
                command_executor=grid,
                options=options
            )
        else:
            driver_map = {
                "chrome":  webdriver.Chrome,
                "firefox": webdriver.Firefox,
                "edge":    webdriver.Edge,
                "safari":  webdriver.Safari,
            }
            driver = driver_map[browser](options=options)

        driver.maximize_window()
        return driver


# โ”€โ”€ conftest.py integration โ”€โ”€
CONFTEST_EXAMPLE = '''
import pytest
from utils.driver_factory import DriverFactory

@pytest.fixture(scope="function")
def browser(request):
    browser_name = request.config.getoption("--browser", default="chrome")
    grid_url = request.config.getoption("--grid-url", default=None)

    driver = DriverFactory.create(
        browser_name=browser_name,
        grid_url=grid_url
    )

    yield driver
    driver.quit()

def pytest_addoption(parser):
    parser.addoption("--browser", default="chrome",
                     help="Browser: chrome, firefox, edge, safari")
    parser.addoption("--grid-url", default=None,
                     help="Selenium Grid URL")
'''

# Browser-specific quirks to handle in the factory
BROWSER_QUIRKS = [
    {"browser": "Chrome",  "quirk": "Needs --disable-dev-shm-usage in Docker to prevent crashes"},
    {"browser": "Firefox", "quirk": "Download preferences must be set via set_preference(), not args"},
    {"browser": "Edge",    "quirk": "Shares Chrome's option syntax (both use Blink engine)"},
    {"browser": "Safari",  "quirk": "No headless mode; requires macOS; must enable remote automation manually"},
]

print("DriverFactory โ€” Multi-Browser Support")
print("=" * 55)
print("\nUsage:")
print("  pytest tests/ --browser chrome")
print("  pytest tests/ --browser firefox")
print("  pytest tests/ --browser edge --grid-url http://grid:4444")
print("  BROWSER=firefox HEADLESS=true pytest tests/")

print("\n\nBrowser-Specific Quirks:")
for q in BROWSER_QUIRKS:
    print(f"  {q['browser']}: {q['quirk']}")
Note: The DriverFactory completely isolates browser-specific logic from your tests and page objects. When you add Safari support or switch from Chrome to Edge, you modify the factory only โ€” zero changes to tests, page objects, or utilities. This isolation is the practical payoff of the five-layer framework architecture from Chapter 42. The factory lives in Layer 2 (Driver Management) and is the only place in the entire codebase that knows about browser-specific options.
Tip: Add a --browser command-line option to pytest via pytest_addoption in conftest.py. This lets you run the same suite on different browsers without editing any configuration file: pytest --browser firefox for local debugging on Firefox, pytest --browser chrome for CI. In GitHub Actions, parameterise the job matrix to run the suite on multiple browsers in parallel โ€” each browser gets its own CI job.
Warning: Safari has significant limitations for Selenium automation. It does not support headless mode, it runs only on macOS, it requires manually enabling “Allow Remote Automation” in Safari’s Develop menu, and safaridriver only supports one session at a time (no parallel Safari tests on a single machine). For CI/CD, Safari testing requires either a macOS runner (GitHub Actions supports this) or a cloud provider like BrowserStack that offers Safari on their infrastructure.

Common Mistakes

Mistake 1 โ€” Using browser-specific options directly in tests

โŒ Wrong: options = ChromeOptions(); options.add_argument("--headless=new") inside a test file โ€” ties the test to Chrome.

โœ… Correct: All browser options are configured in DriverFactory.create(). Tests receive a generic driver from the fixture and never import browser-specific option classes.

Mistake 2 โ€” Not handling Safari’s unique limitations

โŒ Wrong: Adding Safari to the browser matrix and expecting it to work identically to Chrome โ€” then being surprised by headless failures, single-session limits, and macOS-only constraints.

โœ… Correct: Documenting Safari’s limitations in the factory (skip headless, warn about single session), running Safari tests on a dedicated macOS agent or cloud provider, and accepting a smaller parallel capacity for Safari runs.

🧠 Test Yourself

What is the primary benefit of a DriverFactory pattern in a cross-browser Selenium framework?