All Study MaterialArchitecture

Selenium WebDriver Framework Architecture: From Scratch to Production

June 9, 202610 min read views
SeleniumJavaArchitecturePage Object Model

Start with driver lifecycle management

The single most common source of Selenium flakiness isn't a bad locator — it's driver lifecycle mismanagement: a driver that isn't quit properly, a shared driver instance leaking state between tests, or a driver created fresh per test method when it should be scoped per class for performance. A DriverFactory (or a ThreadLocal-backed driver manager for parallel runs) that centralizes creation and teardown fixes most of this in one place.

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

  public static WebDriver getDriver() {
    if (driver.get() == null) {
      driver.set(new ChromeDriver(getOptions()));
    }
    return driver.get();
  }

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

Waits: the difference between flaky and reliable

Never mix implicit and explicit waits in the same project — they interact unpredictably and are one of the most common causes of intermittent timeouts. Standardize on explicit WebDriverWait with condition-based waits (ExpectedConditions.elementToBeClickable), and centralize the wait configuration so every page object uses the same timeout policy rather than each author picking their own magic number.

Page Object Model, done properly

  • Locators as private fields, never exposed outside the page object.
  • Public methods describe user intent (submitOrder()), not raw Selenium calls.
  • A page object returns the next page object when an action causes navigation, so tests read as a natural chain: loginPage.login(user).goToOrders().placeOrder(item).
  • Avoid assertions inside page objects — assertions belong in the test, page objects only describe and perform actions.

Config and environments

Externalize base URLs, credentials, and browser choice into a properties/YAML file per environment (dev/staging/prod), loaded at runtime — never hardcode an environment's URL directly into a test. Combine with a TestNG/JUnit parameterization layer so the same suite runs against any environment by changing one config value, not editing test code.

Parallel execution

TestNG's parallel="methods" or parallel="classes" in testng.xml, paired with the ThreadLocal driver pattern above, is the standard way to run Selenium suites in parallel without cross-test driver contamination. Selenium Grid (or a cloud grid provider) then lets that parallelism span multiple machines/browsers, not just multiple threads on one box.

Reporting and CI

Allure or ExtentReports for readable, screenshot-attached HTML reports; a Maven/Gradle CI step that fails the build on any test failure and archives the report as a build artifact. Capture a screenshot automatically on failure (via a TestNG @AfterMethod listener checking the test result) rather than relying on someone remembering to add one manually per test.

The trade-off Selenium still wins on

Selenium's WebDriver protocol support across a wider range of real browsers and versions (including older ones some enterprise clients still require) is genuinely broader than Playwright's — that's usually the actual reason a team chooses Selenium today, not unfamiliarity with newer tools.

Related

REST API Test Automation Architecture with RestAssuredPlaywright Test Framework Architecture: A Practical BlueprintPlaywright vs Selenium vs Cypress: An Honest Comparison for 2026