Selenium Browser Creation Factory Pattern Java

Selenium WebDriver Factory Pattern in Java

A SOLID, Scalable Approach

If your test automation framework has a giant if-else block deciding whether to launch Chrome, Firefox, or Edge, you already know the pain. Every new browser means editing the same fragile method, and every edit risks breaking the browsers that already work. This is exactly the problem the Selenium WebDriver Factory Pattern in Java solves.

In this post, you’ll learn how to build a clean, extensible browser factory using the Factory design pattern combined with SOLID principles — so adding a new browser never means touching existing, working code again.

Why Use the Factory Pattern With Selenium WebDriver?

Most Selenium projects start small: one test, one browser, one ChromeDriver() call. But as a test suite grows, you typically need to:

  • Run the same tests across Chrome, Firefox, and Edge
  • Support headless execution in CI/CD pipelines
  • Swap browsers based on configuration, not hardcoded values

Without a factory, this logic gets duplicated across test classes. With a Selenium WebDriver Factory Pattern in Java, driver creation lives in exactly one place, and every test simply asks for a browser instance without knowing (or caring) how it’s built.

Setting Up the Project Dependencies

Before writing any factory code, add Selenium to your Maven project:

<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>4.23.0</version>
</dependency>

Selenium 4.6 and above includes Selenium Manager, which resolves browser drivers automatically — no manual chromedriver.exe downloads needed.

Step 1: Define a BrowserType Enum

Start with a simple enum to represent supported browsers:

public enum BrowserType {
CHROME,
FIREFOX,
EDGE
}

This keeps browser selection type-safe instead of relying on error-prone strings like "chrome" scattered through your test code.

Step 2: Create the DriverCreator Interface

This interface is the core of the Factory Pattern. Instead of one factory method doing everything, each browser gets its own implementation:

import org.openqa.selenium.WebDriver;
public interface DriverCreator {
WebDriver createDriver();
}

A single method with a single purpose keeps this interface aligned with the Single Responsibility Principle — it does one job, and does it clearly.

Step 3: Implement One Creator Per Browser

Each browser gets its own class, isolating its configuration from every other browser:

public class ChromeDriverCreator implements DriverCreator {
@Override
public WebDriver createDriver() {
ChromeOptions options = new ChromeOptions();
return new ChromeDriver(options);
}
}
public class FirefoxDriverCreator implements DriverCreator {
@Override
public WebDriver createDriver() {
FirefoxOptions options = new FirefoxOptions();
return new FirefoxDriver(options);
}
}
public class EdgeDriverCreator implements DriverCreator {
@Override
public WebDriver createDriver() {
EdgeOptions options = new EdgeOptions();
return new EdgeDriver(options);
}
}

Because every class implements the same interface, they’re fully interchangeable — a direct application of the Liskov Substitution Principle.

Step 4: Build the BrowserFactory

Rather than a switch statement, use a registry (a map) that connects each BrowserType to its DriverCreator:

import java.util.HashMap;
import java.util.Map;
public class BrowserFactory {
private static final Map<BrowserType, DriverCreator> CREATORS = new HashMap<>();
static {
CREATORS.put(BrowserType.CHROME, new ChromeDriverCreator());
CREATORS.put(BrowserType.FIREFOX, new FirefoxDriverCreator());
CREATORS.put(BrowserType.EDGE, new EdgeDriverCreator());
}
private BrowserFactory() {}
public static WebDriver createDriver(BrowserType browserType) {
DriverCreator creator = CREATORS.get(browserType);
if (creator == null) {
throw new IllegalArgumentException("Unsupported browser: " + browserType);
}
return creator.createDriver();
}
public static void registerCreator(BrowserType browserType, DriverCreator creator) {
CREATORS.put(browserType, creator);
}
}

Notice that BrowserFactory depends only on the DriverCreator abstraction, never on ChromeDriver or FirefoxDriver directly. That’s the Dependency Inversion Principle in action, and it’s what makes the whole design open for extension.

Step 5: Add a New Browser Without Breaking Anything

This is where the pattern pays off. To support Safari tomorrow, you write one new class:

public class SafariDriverCreator implements DriverCreator {
@Override
public WebDriver createDriver() {
return new SafariDriver();
}
}

Then register it — either in the static block or dynamically at runtime:

BrowserFactory.registerCreator(BrowserType.SAFARI, new SafariDriverCreator());

No existing class changes. No risk to Chrome, Firefox, or Edge tests. That’s the Open/Closed Principle: open for extension, closed for modification.

Step 6: Write a Test Using the Factory

With the factory in place, tests become clean and readable:

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.WebDriver;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class BrowserFactoryTest {
private WebDriver driver;
@BeforeEach
void setUp() {
driver = BrowserFactory.createDriver(BrowserType.CHROME);
}
@Test
void shouldOpenChromeAndLoadGoogle() {
driver.get("https://www.google.com");
assertTrue(driver.getTitle().contains("Google"));
}
@AfterEach
void tearDown() {
if (driver != null) {
driver.quit();
}
}
}

The test doesn’t know or care how the Chrome driver is built. If that logic changes — say, adding headless mode — it changes in one place: ChromeDriverCreator.

Keeping the Interface Segregated as Requirements Grow

As your framework matures, you’ll likely want per-browser options like headless mode or custom download directories. Resist the temptation to bloat the DriverCreator interface with extra methods for every option. Instead:

  • Pass configuration through each creator’s constructor, or
  • Introduce a small BrowserOptions object passed into createDriver()

This keeps the interface narrow and stable, satisfying the Interface Segregation Principle even as individual browser setups become more complex.

Final Thoughts

A well-structured Selenium WebDriver Factory Pattern in Java does more than tidy up your code — it makes your entire test framework easier to scale, maintain, and hand off to other engineers. By combining the Factory Pattern with SOLID principles, you get a browser-creation system where adding new browsers, options, or configurations never risks breaking what already works.

If you’re building out a full framework, the next logical step is making browser selection configuration-driven — for example, reading -Dbrowser=chrome from the command line so your CI/CD pipeline can switch browsers without recompiling any code.

Related Links

✓ Selenium Locators Tutorial
✓ Selenium Waits Explained
✓ Page Object Model in Selenium
✓ Selenium Framework Design
✓ Selenium Grid Explained
✓ Jenkins Pipeline for Selenium Tests
✓ Complete Selenium Tutorial


Discover more from Rotebit

Subscribe to get the latest posts sent to your email.

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply