Enterprise Framework Architecture
Learn to build production-ready automation frameworks following industry best practices.
Project Structure
selenium-framework/
├── src/
│ ├── main/
│ │ └── java/
│ │ └── com/automation/
│ │ ├── config/
│ │ │ ├── ConfigReader.java
│ │ │ └── DriverFactory.java
│ │ ├── pages/
│ │ │ ├── BasePage.java
│ │ │ ├── LoginPage.java
│ │ │ └── HomePage.java
│ │ ├── utils/
│ │ │ ├── WaitHelper.java
│ │ │ ├── ScreenshotUtil.java
│ │ │ └── ExcelReader.java
│ │ └── constants/
│ │ └── AppConstants.java
│ └── test/
│ ├── java/
│ │ └── com/automation/
│ │ ├── tests/
│ │ │ ├── BaseTest.java
│ │ │ └── LoginTest.java
│ │ └── listeners/
│ │ └── TestListener.java
│ └── resources/
│ ├── config.properties
│ ├── testng.xml
│ └── testdata/
│ └── users.xlsx
├── reports/
│ └── extent-reports/
├── screenshots/
├── logs/
├── pom.xml
└── README.md
Configuration Management
config.properties:
# Application
base.url=https://example.com
app.timeout=10
# Browser
browser=chrome
headless=false
maximize=true
# Grid
grid.enabled=false
grid.url=http://localhost:4444
# Database
db.url=jdbc:mysql://localhost:3306/testdb
db.username=root
db.password=secret
# Reporting
screenshot.on.failure=true
extent.report.path=reports/extent-reports
ConfigReader.java:
package com.automation.config;
import java.io.FileInputStream;
import java.util.Properties;
public class ConfigReader {
private static Properties properties;
static {
try {
FileInputStream fis = new FileInputStream("src/test/resources/config.properties");
properties = new Properties();
properties.load(fis);
} catch (Exception e) {
e.printStackTrace();
}
}
public static String getProperty(String key) {
return properties.getProperty(key);
}
public static String getBaseUrl() {
return properties.getProperty("base.url");
}
public static String getBrowser() {
return properties.getProperty("browser", "chrome");
}
public static int getTimeout() {
return Integer.parseInt(properties.getProperty("app.timeout", "10"));
}
public static boolean isHeadless() {
return Boolean.parseBoolean(properties.getProperty("headless", "false"));
}
}
Driver Factory Pattern
DriverFactory.java:
package com.automation.config;
import io.github.bonigarcia.wdm.WebDriverManager;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.firefox.FirefoxOptions;
import org.openqa.selenium.edge.EdgeDriver;
import org.openqa.selenium.remote.RemoteWebDriver;
import java.net.URL;
import java.time.Duration;
public class DriverFactory {
private static ThreadLocal<WebDriver> driver = new ThreadLocal<>();
public static WebDriver getDriver() {
return driver.get();
}
public static void setDriver(WebDriver driverInstance) {
driver.set(driverInstance);
}
public static void initializeDriver(String browser) {
WebDriver webDriver;
if (ConfigReader.getProperty("grid.enabled").equals("true")) {
webDriver = initializeRemoteDriver(browser);
} else {
webDriver = initializeLocalDriver(browser);
}
webDriver.manage().window().maximize();
webDriver.manage().timeouts().implicitlyWait(
Duration.ofSeconds(ConfigReader.getTimeout())
);
setDriver(webDriver);
}
private static WebDriver initializeLocalDriver(String browser) {
WebDriver webDriver;
switch (browser.toLowerCase()) {
case "chrome":
WebDriverManager.chromedriver().setup();
ChromeOptions chromeOptions = new ChromeOptions();
if (ConfigReader.isHeadless()) {
chromeOptions.addArguments("--headless");
}
chromeOptions.addArguments("--disable-notifications");
chromeOptions.addArguments("--disable-popup-blocking");
webDriver = new ChromeDriver(chromeOptions);
break;
case "firefox":
WebDriverManager.firefoxdriver().setup();
FirefoxOptions firefoxOptions = new FirefoxOptions();
if (ConfigReader.isHeadless()) {
firefoxOptions.addArguments("--headless");
}
webDriver = new FirefoxDriver(firefoxOptions);
break;
case "edge":
WebDriverManager.edgedriver().setup();
webDriver = new EdgeDriver();
break;
default:
throw new RuntimeException("Browser not supported: " + browser);
}
return webDriver;
}
private static WebDriver initializeRemoteDriver(String browser) {
try {
ChromeOptions options = new ChromeOptions();
URL gridUrl = new URL(ConfigReader.getProperty("grid.url"));
return new RemoteWebDriver(gridUrl, options);
} catch (Exception e) {
throw new RuntimeException("Failed to initialize remote driver", e);
}
}
public static void quitDriver() {
if (driver.get() != null) {
driver.get().quit();
driver.remove();
}
}
}
Base Test Class
BaseTest.java:
package com.automation.tests;
import com.automation.config.ConfigReader;
import com.automation.config.DriverFactory;
import org.openqa.selenium.WebDriver;
import org.testng.annotations.*;
public class BaseTest {
protected WebDriver driver;
@BeforeMethod
@Parameters("browser")
public void setUp(@Optional("chrome") String browser) {
DriverFactory.initializeDriver(browser);
driver = DriverFactory.getDriver();
driver.get(ConfigReader.getBaseUrl());
}
@AfterMethod
public void tearDown() {
DriverFactory.quitDriver();
}
}
Screenshot Utility
ScreenshotUtil.java:
package com.automation.utils;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.WebDriver;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
public class ScreenshotUtil {
public static String captureScreenshot(WebDriver driver, String testName) {
try {
TakesScreenshot ts = (TakesScreenshot) driver;
File source = ts.getScreenshotAs(OutputType.FILE);
String timestamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
String fileName = testName + "_" + timestamp + ".png";
String destination = "screenshots/" + fileName;
File finalDestination = new File(destination);
FileUtils.copyFile(source, finalDestination);
return destination;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public static String captureFullPageScreenshot(WebDriver driver, String testName) {
try {
// Take full page screenshot (Chrome only)
ChromeDriver chromeDriver = (ChromeDriver) driver;
File source = chromeDriver.getScreenshotAs(OutputType.FILE);
String timestamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
String destination = "screenshots/" + testName + "_full_" + timestamp + ".png";
FileUtils.copyFile(source, new File(destination));
return destination;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
Test Listener with Reporting
TestListener.java:
package com.automation.listeners;
import com.automation.config.DriverFactory;
import com.automation.utils.ScreenshotUtil;
import com.aventstack.extentreports.ExtentReports;
import com.aventstack.extentreports.ExtentTest;
import com.aventstack.extentreports.Status;
import com.aventstack.extentreports.reporter.ExtentSparkReporter;
import org.testng.ITestContext;
import org.testng.ITestListener;
import org.testng.ITestResult;
public class TestListener implements ITestListener {
private static ExtentReports extent;
private static ThreadLocal<ExtentTest> test = new ThreadLocal<>();
@Override
public void onStart(ITestContext context) {
ExtentSparkReporter reporter = new ExtentSparkReporter(
"reports/extent-reports/test-report.html"
);
reporter.config().setDocumentTitle("Test Automation Report");
reporter.config().setReportName("Functional Testing");
extent = new ExtentReports();
extent.attachReporter(reporter);
extent.setSystemInfo("Environment", "QA");
extent.setSystemInfo("Tester", System.getProperty("user.name"));
}
@Override
public void onTestStart(ITestResult result) {
ExtentTest extentTest = extent.createTest(result.getMethod().getMethodName());
test.set(extentTest);
}
@Override
public void onTestSuccess(ITestResult result) {
test.get().log(Status.PASS, "Test passed");
}
@Override
public void onTestFailure(ITestResult result) {
test.get().log(Status.FAIL, "Test failed");
test.get().log(Status.FAIL, result.getThrowable());
String screenshot = ScreenshotUtil.captureScreenshot(
DriverFactory.getDriver(),
result.getMethod().getMethodName()
);
if (screenshot != null) {
test.get().addScreenCaptureFromPath(screenshot);
}
}
@Override
public void onTestSkipped(ITestResult result) {
test.get().log(Status.SKIP, "Test skipped");
test.get().log(Status.SKIP, result.getThrowable());
}
@Override
public void onFinish(ITestContext context) {
extent.flush();
}
}
Logging Framework
pom.xml (add log4j2):
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.20.0</version>
</dependency>
log4j2.xml:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
<File name="File" fileName="logs/automation.log">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} [%t] %-5level %logger{36} - %msg%n"/>
</File>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="Console"/>
<AppenderRef ref="File"/>
</Root>
</Loggers>
</Configuration>
Usage in tests:
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class LoginTest extends BaseTest {
private static final Logger log = LogManager.getLogger(LoginTest.class);
@Test
public void testLogin() {
log.info("Starting login test");
LoginPage loginPage = new LoginPage(driver);
loginPage.login("user", "pass");
log.info("Login successful");
}
}
Performance Optimization
public class PerformanceOptimization {
// ❌ Bad - Multiple database calls
@Test
public void inefficientTest() {
for (int i = 0; i < 10; i++) {
driver.findElement(By.id("item-" + i)).click();
// Each iteration makes a findElement call
}
}
// ✅ Good - Find once, use multiple times
@Test
public void efficientTest() {
List<WebElement> items = driver.findElements(By.className("item"));
for (WebElement item : items) {
item.click();
}
}
// Use CSS Selectors over XPath
// ❌ Slower
driver.findElement(By.xpath("//div[@class='container']/input[@id='username']"));
// ✅ Faster
driver.findElement(By.cssSelector("div.container input#username"));
// Disable images for faster page load
ChromeOptions options = new ChromeOptions();
Map<String, Object> prefs = new HashMap<>();
prefs.put("profile.managed_default_content_settings.images", 2);
options.setExperimentalOption("prefs", prefs);
WebDriver driver = new ChromeDriver(options);
}
Retry Logic
import org.testng.IRetryAnalyzer;
import org.testng.ITestResult;
public class RetryAnalyzer implements IRetryAnalyzer {
private int retryCount = 0;
private static final int maxRetryCount = 2;
@Override
public boolean retry(ITestResult result) {
if (retryCount < maxRetryCount) {
retryCount++;
return true;
}
return false;
}
}
// Usage
@Test(retryAnalyzer = RetryAnalyzer.class)
public void flakyTest() {
// Test that might fail intermittently
}
Constants Management
AppConstants.java:
package com.automation.constants;
public class AppConstants {
// Timeouts
public static final int EXPLICIT_WAIT = 10;
public static final int PAGE_LOAD_TIMEOUT = 30;
public static final int IMPLICIT_WAIT = 5;
// URLs
public static final String LOGIN_URL = "/login";
public static final String DASHBOARD_URL = "/dashboard";
// Test Data
public static final String VALID_USERNAME = "testuser@example.com";
public static final String VALID_PASSWORD = "Test@123";
// Messages
public static final String LOGIN_SUCCESS_MSG = "Welcome back!";
public static final String LOGIN_ERROR_MSG = "Invalid credentials";
}
Best Practices Checklist
- ✅ Use Page Object Model - Separate page logic from tests
- ✅ Implement Driver Factory - Centralized driver management
- ✅ Configuration Management - External properties files
- ✅ Use ThreadLocal for parallel execution - Thread-safe drivers
- ✅ Implement proper waits - Explicit waits over implicit
- ✅ Capture screenshots on failure - Debugging aid
- ✅ Use logging - Track test execution
- ✅ Implement retry logic - Handle flaky tests
- ✅ Follow naming conventions - Clear, descriptive names
- ✅ Keep tests independent - No dependencies between tests
Key Takeaways
✅ Framework structure enables scalability and maintainability
✅ Configuration management simplifies environment changes
✅ Proper reporting and logging aid debugging
✅ ThreadLocal ensures thread safety for parallel execution