Q26 of 37 · Selenium

How would you architect a Selenium framework from scratch for a Java team?

SeleniumSeniorseleniumframework-designarchitecturejavasenior

Short answer

Short answer: Layered: driver factory + config → page objects + components → test base classes → tests + data providers. Use Maven, TestNG, AssertJ, Allure, Selenium Manager, Docker Grid for CI. Optimise for readability of tests, isolation of locators, and parallel-safety from day one.

Detail

A clean Selenium framework has five layers, top to bottom:

1. Tests — the only layer the rest of QA reads regularly. Each test is one user journey, with assertions in the test (not in page objects), parameterised via @DataProvider where useful. Tests should read like the user's intent: new LoginPage(driver).loginAs(...).orderItem(...).expectConfirmation().

2. Page Objects + Component Objects — wrap pages and reusable fragments (navbar, modal, table row). Locators are private. Methods return either void, the same page (for chained actions), or the next page object. Never assert in page objects.

3. Test Base Classes@BeforeMethod creates the driver, navigates to base URL, optionally logs in. @AfterMethod captures screenshot on failure and quits the driver. Listeners (retry, reporting) are wired here.

4. Driver Factory + Config — one source of truth for browser, headless, base URL, parallel mode. Reads from -D system properties, env vars, or a profile file. Must be thread-safe (ThreadLocal<WebDriver>) for parallel runs.

5. Utilities — explicit-wait helpers, JSON Schema validation, file fixtures, API helpers for state setup.

Stack picks I'd defend:

  • Maven + Java 17 + Selenium 4.x — current LTS-aligned baseline.
  • TestNG over JUnit 5 for parallel-test execution, data providers, and groups.
  • AssertJ over TestNG asserts — much better failure messages.
  • Allure for reports — historical trends, attachments, narratives.
  • Selenium Manager (built into 4.10+) — no webdriver-manager dep.
  • Docker compose Grid for CI — Chrome + Firefox nodes, hub on the build runner.
  • Lombok sparingly — @Slf4j, @Builder for test data builders.

Folder layout:

src/main/java/.../
   pages/        # page objects + components
   factory/      # DriverFactory, BrowserConfig
   listeners/    # RetryListener, ScreenshotListener
   utils/        # waits, schemas, builders
src/test/java/.../
   base/         # BaseTest with @BeforeMethod / @AfterMethod
   tests/        # actual test classes
   data/         # data providers
src/test/resources/
   testng.xml    # suite definition
   fixtures/     # JSON / CSV test data

Things I'd push back on:

  • Custom logging frameworks. Use SLF4J + Logback.
  • Generated UI test cases from BDD that aren't actually used by non-devs.
  • Page objects with hundreds of methods. Split into components when a class crosses ~100 lines.

The interview signal: layered separation, naming the parallel-safety boundary (ThreadLocal<WebDriver>), and being opinionated about tooling with reasons.

// EXAMPLE

DriverFactory.java

public class DriverFactory {
    private static final ThreadLocal<WebDriver> driver = new ThreadLocal<>();

    public static WebDriver get() {
        if (driver.get() == null) {
            driver.set(create());
        }
        return driver.get();
    }

    public static void quit() {
        if (driver.get() != null) {
            driver.get().quit();
            driver.remove();
        }
    }

    private static WebDriver create() {
        String browser = System.getProperty("browser", "chrome");
        boolean headless = Boolean.parseBoolean(System.getProperty("headless", "true"));

        return switch (browser) {
            case "chrome" -> {
                ChromeOptions o = new ChromeOptions();
                if (headless) o.addArguments("--headless=new");
                yield new ChromeDriver(o);
            }
            case "firefox" -> new FirefoxDriver();
            default -> throw new IllegalArgumentException("Unknown browser: " + browser);
        };
    }
}

// WHAT INTERVIEWERS LOOK FOR

A clear five-layer mental model, opinionated tooling picks, ThreadLocal<WebDriver> for parallel safety, and a folder layout that scales beyond a toy project.

// COMMON PITFALL

Putting locators or assertions in tests, or creating one mega 'BasePage' that everything inherits from. Both make refactors painful and hide responsibilities.