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']}")
--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.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.