The Complete Selenium Automation Framework Guide
An automation framework is the structure around your tests: how browsers are started, where locators live, where test data comes from, how tests run in parallel, how results are reported and how everything runs in CI. A good framework makes adding the 500th test as easy as the 5th; a poor one turns every UI change into a day of fixing scripts. This guide covers how to design a Selenium framework end to end — with the key code — and links every framework tutorial on the site.
Why You Need a Framework
- Maintainability — a UI change is fixed in one page class, not fifty tests.
- Reusability — login, waits and navigation are written once.
- Scalability — parallel and cross-browser runs, many environments.
- Reliability — consistent synchronisation instead of ad-hoc sleeps.
- Visibility — reports, screenshots and CI results the whole team can use.
Framework Types
| Type | Idea | Trade-off |
|---|---|---|
| Linear (record/playback) | Straight scripts, no structure | Quick to start, difficult to maintain |
| Modular | Split the app into reusable modules/pages | The basis of the Page Object Model |
| Data-driven | Same test, many data sets from Excel/JSON/DB | Great coverage; needs good data management |
| Keyword-driven | Actions defined as keywords in a sheet, mapped to code | Non-coders can write tests; heavy to build and maintain |
| BDD | Gherkin scenarios mapped to step definitions | Business-readable; needs real collaboration |
| Hybrid | A combination — usually POM + data-driven, sometimes BDD | Common enterprise approach |
When you describe your framework as "hybrid", say what it combines. Most Selenium frameworks are POM + data-driven; only mention keyword-driven if you genuinely have a keyword layer. More: Frameworks & Page Object Model.
The 7 Layers
- Base and utilities — driver factory, wait helpers, screenshots, config and data readers, logging.
- Page objects — one class per page (or component) with locators and actions.
- Tests — TestNG classes with test logic and assertions only.
- Test data and configuration — properties per environment, JSON/Excel data.
- Execution —
testng.xmlsuites, groups, parallel settings, listeners. - Build — Maven dependencies and the Surefire plugin.
- Reporting and CI/CD — Extent/Allure reports, failure screenshots, Jenkins pipelines.
Flow:
Jenkins → Maven → TestNG → tests → page objects → driver factory → browser (local or Grid) → reports
Folder Structure
src/main/java/com/company/
├── base/ → DriverFactory, BasePage
├── pages/ → LoginPage, ProductPage, CartPage, CheckoutPage
└── utils/ → ConfigReader, JsonDataReader, ScreenshotUtil
src/test/java/com/company/
├── tests/ → BaseTest, LoginTest, CartTest, CheckoutTest
└── listeners/ → TestListener (screenshots, report logging), RetryAnalyzer
src/test/resources/
├── config/ → qa.properties, staging.properties
├── testdata/ → users.json, products.xlsx
└── testng.xml, smoke.xml
pom.xml · Jenkinsfile · README.md
The Key Code
1. Driver Factory — One Browser Per Thread
public final class DriverFactory {
private static final ThreadLocal<WebDriver> DRIVER =
new ThreadLocal<>();
public static void init(String browser, boolean headless) {
WebDriver driver = switch (browser) {
case "firefox" -> new FirefoxDriver();
default -> {
ChromeOptions options = new ChromeOptions();
if (headless) {
options.addArguments(
"--headless=new",
"--window-size=1920,1080"
);
}
yield new ChromeDriver(options);
}
};
DRIVER.set(driver);
}
public static WebDriver get() {
return DRIVER.get();
}
public static void quit() {
if (DRIVER.get() != null) {
DRIVER.get().quit();
DRIVER.remove();
}
}
}
2. Configuration Per Environment
public final class ConfigReader {
private static final Properties PROPS = new Properties();
static {
String env = System.getProperty("env", "qa");
// Example: mvn test -Denv=staging
try (InputStream in =
ConfigReader.class.getResourceAsStream(
"/config/" + env + ".properties")) {
PROPS.load(in);
} catch (IOException | NullPointerException e) {
throw new IllegalStateException(
"Missing config for env: " + env, e);
}
}
public static String get(String key) {
return System.getProperty(
key,
PROPS.getProperty(key)
);
}
}
System properties override the file, so CI can pass -Dbrowser=firefox without editing anything. Secrets come from environment variables or Jenkins credentials, never from the repository.
3. BasePage — Waits in One Place
public abstract class BasePage {
protected final WebDriver driver = DriverFactory.get();
private final WebDriverWait wait =
new WebDriverWait(driver, Duration.ofSeconds(10));
protected void click(By locator) {
wait.until(
ExpectedConditions.elementToBeClickable(locator)
).click();
}
protected void type(By locator, String text) {
WebElement el = wait.until(
ExpectedConditions.visibilityOfElementLocated(locator)
);
el.clear();
el.sendKeys(text);
}
protected String text(By locator) {
return wait.until(
ExpectedConditions.visibilityOfElementLocated(locator)
).getText();
}
}
4. A Page Object and a Test
public class LoginPage extends BasePage {
private final By username = By.id("user-name");
private final By password = By.id("password");
private final By loginBtn = By.id("login-button");
private final By error =
By.cssSelector("[data-test='error']");
public ProductsPage loginAs(String user, String pass) {
type(username, user);
type(password, pass);
click(loginBtn);
return new ProductsPage();
}
public String errorMessage() {
return text(error);
}
}
public class BaseTest {
@BeforeMethod
public void setUp() {
DriverFactory.init(
ConfigReader.get("browser"),
Boolean.parseBoolean(
ConfigReader.get("headless")
)
);
DriverFactory.get()
.get(ConfigReader.get("baseUrl"));
}
@AfterMethod(alwaysRun = true)
public void tearDown() {
DriverFactory.quit();
}
}
public class LoginTest extends BaseTest {
@Test(groups = "smoke")
public void validUserSeesProducts() {
Assert.assertTrue(
new LoginPage()
.loginAs(
"standard_user",
"secret_sauce"
)
.isLoaded()
);
}
@Test(
dataProvider = "invalidLogins",
groups = "regression"
)
public void invalidLoginShowsError(
String user,
String pass,
String expected) {
LoginPage page = new LoginPage();
page.loginAs(user, pass);
Assert.assertTrue(
page.errorMessage().contains(expected)
);
}
@DataProvider
public Object[][] invalidLogins() {
return new Object[][] {
{
"locked_out_user",
"secret_sauce",
"locked out"
},
{
"standard_user",
"wrong",
"do not match"
}
};
}
}
Tests read like specifications; locators and waits never appear in them. Store By locators rather than WebElement references to reduce stale-element problems.
5. Listener — Evidence on Failure
public class TestListener implements ITestListener {
@Override
public void onTestFailure(ITestResult result) {
byte[] png =
((TakesScreenshot) DriverFactory.get())
.getScreenshotAs(OutputType.BYTES);
// Attach png to your Extent/Allure report
// under result.getName()
}
}
Test Data
Keep data out of code: TestNG data providers for small sets, JSON or Excel files for larger ones, and API calls to create data at runtime so tests don't collide. Generate unique values, such as timestamped emails, for anything that must be new.
More: Data-Driven Testing and Collections in Your Framework.
Execution, Parallel Runs and Reports
- Suites:
smoke.xmlandregression.xml, or groups selected with-Dgroups=smoke. - Parallel:
parallel="classes"or"methods"with a sensiblethread-count— safe because of the ThreadLocal driver. See Parallel, Grid & Cross-Browser. - Reports: Extent or Allure with steps, screenshots and environment details; TestNG XML for CI trends.
- Retries: an
IRetryAnalyzerfor genuinely unstable environments only — log every retry and fix the cause.
CI/CD and Scale
A Jenkinsfile checks out the code, runs:
mvn clean test -Denv=qa -Dheadless=true
It then publishes results and archives screenshots — smoke on every merge, regression nightly.
For consistent browsers and more parallelism, run against a Selenium Grid in Docker:
Design Principles and Anti-Patterns
| Do | Avoid |
|---|---|
| Explicit waits in one helper layer | Thread.sleep() anywhere |
| Independent tests with their own data | Tests that depend on execution order |
Stable locators (IDs, data-testid) |
Absolute XPath and index-based locators |
| Assertions in tests | Assertions buried in page objects |
| ThreadLocal driver | A static shared WebDriver |
| Config and secrets outside the code | Hard-coded URLs and passwords |
| API calls for setup, UI for what users do | Creating all test data through the UI |
| Small, reusable components | 2,000-line "god" page classes |
Framework Readiness Checklist
- Runs with one command (
mvn test) on any machine, and headless in CI. - Switches browser and environment through parameters.
- Runs in parallel without random failures.
- Produces a report with screenshots for every failure.
- Has a README explaining how to run it and how to add a test.
- A new team member can add a test in under an hour.
The Learning Path
Part 1 — Framework Concepts
- How to Explain Your Automation Framework — the 7 layers and a spoken answer.
- OOP Concepts in Your Framework — encapsulation, inheritance, polymorphism, abstraction.
- Java Collections in Your Framework — List, Set and Map with real code.
Part 2 — Design and Data
- Frameworks & Page Object Model
- Data-Driven Testing — Excel, properties files and JSON.
- Maven & CI/CD
Part 3 — A Complete Build (E-Commerce)
- Building an E-Commerce Framework Step by Step
- Reporting, Parallel Execution, Screenshots & CI/CD
- Build a Framework From Scratch Roadmap
Part 4 — BDD and API Frameworks
- Cucumber Hybrid Framework With POM — see also The Complete Cucumber BDD Tutorial.
- REST Assured Framework Design — see also The Complete REST Assured Tutorial.
How to Use This Guide
- Building from scratch: the code on this page → Part 2 → Part 3.
- Interview "explain your framework": the 7 layers above, then Part 1.
- BDD or API frameworks: Part 4.
Pair it with The Complete Selenium Guide and The Complete SDET Interview Guide, and practise explaining it in the Framework Interview Simulator.
From Real Projects
Across Apkope, Canolog and Testsigma I've done both manual testing and Selenium automation in Java with TestNG, tracked execution in TestRail and defects in Jira, and worked in Agile teams. The topics on this page connect directly to that day-to-day work. Keep the layers separate: tests describe the flow, page classes hold the details.
Frequently Asked Questions
What is a hybrid framework?
A framework that combines several approaches — most commonly the Page Object Model with data-driven testing, sometimes with BDD or keyword-driven elements.
What are the layers of an automation framework?
Base and utilities, page objects, tests, test data and configuration, execution (TestNG), build (Maven), and reporting and CI/CD.
What is the Page Object Model?
A design pattern where each page or component is a class holding its locators and actions, so tests call page methods and locator changes happen in one place.
How is test data separated from test logic?
Data lives in properties, JSON or Excel files, data providers or the database, and is read at runtime — tests contain only logic and assertions.
How do you make a framework run in parallel safely?
Use one WebDriver per thread with ThreadLocal, independent test data, no order dependencies and thread-safe reporting.
How does the framework run in CI/CD?
Jenkins runs Maven, which runs the TestNG suite with parameters for environment and browser, then publishes reports and screenshots — on every merge and nightly.
Should I use PageFactory?
It works, but many teams prefer plain By locators with explicit waits: simpler and less prone to stale-element issues.