Answer
Designing a Selenium Framework from Scratch
As a Senior Automation Architect, this is the blueprint for an enterprise-grade framework:
Phase 1 — Technology Selection
CODE
Language: Java (most common for Selenium)
Test Framework: TestNG (parallel, groups, listeners)
Build Tool: Maven (dependency management, CI integration)
Browser Driver: WebDriverManager (auto-manages driver versions)
Reporting: ExtentReports + Allure
Logging: Log4j2
Data: Apache POI (Excel) + Properties files
Version Control: Git + GitHub
CI/CD: Jenkins / GitHub Actions
Phase 2 — Project Structure
CODE
project-root/
├── src/
│ ├── main/java/
│ │ ├── base/
│ │ │ ├── BaseTest.java ← driver init, config load, teardown
│ │ │ └── BasePage.java ← common wait methods, WebDriver ref
│ │ ├── pages/
│ │ │ ├── LoginPage.java ← page objects
│ │ │ └── DashboardPage.java
│ │ ├── utils/
│ │ │ ├── DriverManager.java ← ThreadLocal WebDriver
│ │ │ ├── ExcelUtils.java ← test data reading
│ │ │ ├── ScreenshotUtils.java ← screenshot on failure
│ │ │ └── WaitUtils.java ← reusable explicit waits
│ │ ├── listeners/
│ │ │ ├── ExtentReportListener.java
│ │ │ └── RetryListener.java
│ │ └── constants/
│ │ └── FrameworkConstants.java ← paths, timeouts, URLs
│ └── test/
│ ├── java/tests/
│ │ ├── LoginTest.java
│ │ └── CheckoutTest.java
│ └── resources/
│ ├── config.properties ← env-specific settings
│ ├── log4j2.xml ← logging config
│ └── testdata/
│ └── TestData.xlsx
├── testng.xml ← suite definition
├── testng-regression.xml
├── testng-smoke.xml
├── pom.xml
└── .github/workflows/ci.yml ← GitHub Actions
Phase 3 — Core Components
▸FrameworkConstants.java
Java
public class FrameworkConstants {
public static final int EXPLICIT_WAIT = 15;
public static final int PAGE_LOAD_TIMEOUT = 30;
public static final String SCREENSHOT_PATH = "test-output/screenshots/";
public static final String EXTENT_REPORT_PATH = "test-output/ExtentReport.html";
public static final String EXCEL_DATA_PATH = "src/test/resources/testdata/TestData.xlsx";
}
▸BasePage.java
Java
public class BasePage {
protected WebDriver driver;
protected WebDriverWait wait;
public BasePage() {
this.driver = DriverManager.getDriver();
this.wait = new WebDriverWait(driver, Duration.ofSeconds(EXPLICIT_WAIT));
}
protected WebElement waitForElement(By locator) {
return wait.until(ExpectedConditions.visibilityOfElementLocated(locator));
}
protected void clickElement(By locator) {
waitForElement(locator).click();
}
protected void typeText(By locator, String text) {
WebElement el = waitForElement(locator);
el.clear();
el.sendKeys(text);
}
}
Phase 4 — CI/CD Integration
GitHub Actions workflow:
YAML
name: Selenium Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with: { java-version: '17', distribution: 'temurin' }
- name: Run Smoke Tests
run: mvn test -Dgroups="smoke" -Dheadless=true
- name: Upload Reports
uses: actions/upload-artifact@v4
with:
name: test-reports
path: test-output/
Phase 5 — Execution Commands
Bash
# Run smoke tests headless
mvn test -Dgroups="smoke" -Dheadless=true
# Cross-browser regression
mvn test -DsuiteXmlFile=testng-regression.xml
# Rerun failures
mvn test -DsuiteXmlFile=test-output/testng-failed.xml
Framework Design Principles (Senior Level)
- ✓No hardcoded values — everything in
config.propertiesorConstants - ✓ThreadLocal driver — mandatory for parallel execution
- ✓Page Object Model — never write locators directly in test classes
- ✓Single Responsibility — each class/page does one thing
- ✓Explicit over implicit waits — predictable, debuggable
- ✓Self-documenting tests — test method names read like user stories
- ✓CI/CD first — framework must run headless in Docker from day 1
