Pick the Browser, Headless Mode and Grid From Environment Variables
Run the same suite on Chrome, Firefox or Edge, locally or on a Grid, headed or headless, by changing environment variables instead of code.
Stable Verified against Selenium 4.48.0
Problem
Switching from Chrome to Firefox, or from local to Grid, means editing code and remembering to change it back. CI needs headless; developers debugging need headed.
Solution
A compact factory that reads BROWSER, HEADLESS and GRID_URL. The full version with thread safety is in the Driver Factory lesson; this is the minimal copy-paste form.
Environment-driven driver creation
Selenium 4 Stable
public static WebDriver createDriver() throws MalformedURLException { String browser = System.getenv().getOrDefault("BROWSER", "chrome"); boolean headless = !"false".equalsIgnoreCase(System.getenv().getOrDefault("HEADLESS", "true")); String grid = System.getenv("GRID_URL");
AbstractDriverOptions<?> options = switch (browser) { case "firefox" -> { FirefoxOptions o = new FirefoxOptions(); if (headless) o.addArguments("-headless"); yield o; } case "edge" -> { EdgeOptions o = new EdgeOptions(); if (headless) o.addArguments("--headless=new"); yield o; } default -> { ChromeOptions o = new ChromeOptions(); if (headless) o.addArguments("--headless=new"); o.addArguments("--window-size=1366,768"); yield o; } };
if (grid != null && !grid.isBlank()) return new RemoteWebDriver(new URL(grid), options); return switch (browser) { case "firefox" -> new FirefoxDriver((FirefoxOptions) options); case "edge" -> new EdgeDriver((EdgeOptions) options); default -> new ChromeDriver((ChromeOptions) options); };}
// BROWSER=firefox HEADLESS=false mvn test// GRID_URL=http://grid:4444 mvn testimport osfrom selenium import webdriver
def create_driver(): browser = os.getenv("BROWSER", "chrome") headless = os.getenv("HEADLESS", "true").lower() != "false" grid = os.getenv("GRID_URL")
if browser == "firefox": options = webdriver.FirefoxOptions() if headless: options.add_argument("-headless") elif browser == "edge": options = webdriver.EdgeOptions() if headless: options.add_argument("--headless=new") else: options = webdriver.ChromeOptions() if headless: options.add_argument("--headless=new") options.add_argument("--window-size=1366,768")
if grid: return webdriver.Remote(command_executor=grid, options=options) return {"firefox": webdriver.Firefox, "edge": webdriver.Edge}.get(browser, webdriver.Chrome)(options=options)
# BROWSER=firefox HEADLESS=false pytest# GRID_URL=http://grid:4444 pytestconst { Builder } = require('selenium-webdriver');const chrome = require('selenium-webdriver/chrome');const firefox = require('selenium-webdriver/firefox');const edge = require('selenium-webdriver/edge');
async function createDriver() {const browser = process.env.BROWSER ?? 'chrome';const headless = (process.env.HEADLESS ?? 'true') !== 'false';const grid = process.env.GRID_URL;
const chromeOpts = new chrome.Options().addArguments('--window-size=1366,768');const firefoxOpts = new firefox.Options();const edgeOpts = new edge.Options();if (headless) { chromeOpts.addArguments('--headless=new'); firefoxOpts.addArguments('-headless'); edgeOpts.addArguments('--headless=new');}
const builder = new Builder().forBrowser(browser) .setChromeOptions(chromeOpts).setFirefoxOptions(firefoxOpts).setEdgeOptions(edgeOpts);if (grid) builder.usingServer(grid);return builder.build();}
// BROWSER=firefox HEADLESS=false npm test// GRID_URL=http://grid:4444 npm testpublic static IWebDriver CreateDriver(){ var browser = Environment.GetEnvironmentVariable("BROWSER") ?? "chrome"; var headless = !string.Equals(Environment.GetEnvironmentVariable("HEADLESS") ?? "true", "false", StringComparison.OrdinalIgnoreCase); var grid = Environment.GetEnvironmentVariable("GRID_URL");
DriverOptions options; switch (browser) { case "firefox": { var o = new FirefoxOptions(); if (headless) o.AddArgument("-headless"); options = o; break; } case "edge": { var o = new EdgeOptions(); if (headless) o.AddArgument("--headless=new"); options = o; break; } default: { var o = new ChromeOptions(); if (headless) o.AddArgument("--headless=new"); o.AddArgument("--window-size=1366,768"); options = o; break; } }
if (!string.IsNullOrWhiteSpace(grid)) return new RemoteWebDriver(new Uri(grid), options); return browser switch { "firefox" => new FirefoxDriver((FirefoxOptions)options), "edge" => new EdgeDriver((EdgeOptions)options), _ => new ChromeDriver((ChromeOptions)options), };}
// $env:BROWSER="firefox"; $env:HEADLESS="false"; dotnet testWhy It Works
Environment variables are the one configuration channel every CI system, container and shell supports. Defaults favour the CI case (Chrome, headless); developers override for debugging. Selenium Manager resolves whichever driver is needed.
Gotchas
- Firefox’s headless flag is
-headless(one dash); Chromium’s is--headless=new. - When
GRID_URLis set, the browser runs remotely: uploads needLocalFileDetector, downloads need managed downloads, andlocalhostin URLs refers to the Grid node. - Validate the
BROWSERvalue and fail early with a clear message; a typo silently falling back to Chrome hides misconfiguration.
Learn the theory
- Driver Factory and Thread Safety Build a WebDriver factory that reads configuration, supports local, Grid and cloud targets, and hands each parallel test its own driver via ThreadLocal or fixtures without leaks.
- CI/CD Pipelines: GitHub Actions, GitLab CI and Jenkins Run Selenium suites in CI reliably: headless browsers, Selenium Manager caching, Grid services in pipelines, parallel jobs, artifacts on failure, and the settings that stop CI-only flakiness.