DEV Community

She11 QA
She11 QA

Posted on Edited on

Complete Guide to Building a Scalable Java-Selenium Automation Framework

QA Automation - Best Practices Guide

Framework: Hybrid Test Automation Framework

Stack: Java - Selenium 4 - TestNG - Maven - ExtentReports

Project: Enterprise QA Automation

Table of Contents

  • Project Structure
  • Naming Conventions
  • Page Object Model (UI Pages)
  • Business Components
  • Test Scripts
  • Data Management
  • Waits & Synchronization
  • Assertions & Reporting
  • Parallel Execution
  • Run Manager & Configuration
  • ExtentReports
  • CI/CD & Pipeline
  • Error Handling
  • Code Quality
  • Environment Configuration

1. Project Structure

The framework follows a strict layered architecture. Never mix responsibilities across layers.


src/
└── test/
    ├── java/
    │   ├── allocator/                  + Entry point & parallel execution engine
    │   │   ├── Allocator.java
    │   │   └── ParallelRunner.java
    │   ├── uiPages/                    + Page Object locators ONLY (no logic)
    │   ├── businessComponents/         + Reusable page actions & validations
    │   ├── commonComponents/           + Grouped reusable component flows
    │   └── testscripts/                + Test scripts (keywords/steps only)
    └── resources/
        ├── Run Manager.xlsm            + Test execution control
        └── GlobalSettings.properties
Enter fullscreen mode Exit fullscreen mode

Rules:

  • uiPages: Only By locators (no logic, no assertions).
  • businessComponents: Only reusable actions; never call driver directly in test scripts.
  • testscripts: Only orchestrate business component calls (keyword-driven).
  • allocator: Do NOT modify unless changing threading/reporting strategy.

2. Naming Conventions

Layer Class Naming Method Naming
uiPages page_name_uipages.java static final By FieldName
businessComponents pagePageName.java camelCase() verb-first
testscripts ScriptName.java execute() or step methods

Examples:


// Good - uiPages locator
public static final By btn_Submit = By.xpath("//button[@id='btnSubmit']/span");

// Good - business component method
public void fillFormDetails(String primaryField, String secondaryField) throws InterruptedException { }

// Bad - logic inside uiPages
public void clickSubmit() { driver.findElement(btn_Submit).click(); } // Don't do this in uiPages!
Enter fullscreen mode Exit fullscreen mode

Locator ID Preferences (Priority Order):

  • By.id() — most stable

  • By.name()

  • By.cssSelector()

  • By.xpath() — only when no better option exists; avoid absolute XPaths

Preferred:


By.id("btnSubmit")
By.cssSelector("button[data-id='submit']")
Enter fullscreen mode Exit fullscreen mode

Acceptable:

By.xpath("//button[@id='btnSubmit']/span")
Enter fullscreen mode Exit fullscreen mode

Avoid - fragile absolute XPath:

By.xpath("/html/body/div[2]/form/button[1]")
Enter fullscreen mode Exit fullscreen mode

3. Page Object Model (UI Pages)

Each UI page maps to one class in uiPages. Keep locators static and final.

// Good structure
package uiPages;

import org.openqa.selenium.By;

public class form_details_page {

    // Group locators with comments for readability
    // === Header Section ===
    public static final By txt_referenceNumber = By.xpath("//input[@id='referenceNumber']");
    public static final By txt_submissionDate = By.id("submissionDate");

    // === Dynamic Locators - use methods ===
    public static By get_recordLink(String recordId) {
        return By.xpath("//a[text()='" + recordId + "']");
    }
}
Enter fullscreen mode Exit fullscreen mode

Rules:

  • One class per page/module.

  • Use static final for all fixed locators.

  • Use static methods for dynamic/parameterized locators.

  • Group related locators with inline comments.

  • Do not import Selenium WebDriver or WebElement in UI pages.

4. Business Components

Business components contain the actual Selenium interactions and extend GeneralComponents (which extends ReusableLibrary).


// Good business component method
public void fillFormDetails(String userName, String effectiveDate) {
    sendKeys(form_details_page.txt_userName, userName, "User Name");
    sendKeys(form_details_page.txt_effectiveDate, effectiveDate, "Effective Date");
    report_updateTestLog("Fill Form Details", 
        "User: " + userName + " | Date: " + effectiveDate, Status.PASS);
}

// Bad - raw driver calls inside a business component
driver.findElement(By.xpath("//input[@id='userName']")).sendKeys(userName);
Enter fullscreen mode Exit fullscreen mode

Rules:

  • Always use GeneralComponents helper methods (clickElement, sendKeys, selectDropDownByValue, etc.) — never call driver.findElement() directly.

  • Every significant action should update the report via report_updateTestLog.

  • Use wait (FluentWait and explicitWait) defined in GeneralComponents — never use Thread.sleep().

  • Use PauseScript() only as a last resort; prefer explicit waits.

  • Keep methods atomic — one action per method when possible.

FluentWait Usage:

// Correct - use inherited waits
wait.until(ExpectedConditions.visibilityOfElementLocated(signin_home_page.welcomeText));
Enter fullscreen mode Exit fullscreen mode

5. Test Scripts

Test scripts are the entry point for each test case. They call business component methods in sequence.


// Good test script structure
public class TC_001_CreateNewRecord extends ScriptHelper {

    public void execute() {
        // Step 1: Login
        pdf_login_new = new pdf_login_helper();
        login.login();

        // Step 2: Navigate to New Record
        pdf_home_new = new pdf_home_helper();
        home.clickNewRecord();

        // Step 3: Fill form details
        pdf_formDetails = new pdf_formDetails_helper();
        formDetails.fillFormDetails(
            dataTable.getData("TestData", "UserName"),
            dataTable.getData("TestData", "EffectiveDate")
        );
    }
}
Enter fullscreen mode Exit fullscreen mode

Rules:

  • Test scripts must not contain XPaths, locators, or driver calls.

  • Each step should be a business component call.

  • Use dataTable.getData() for test data — never hardcode values.

  • Keep test scripts short and readable (under 100 lines where possible).

  • One test script = one test scenario.

6. Data Management

Excel Run Manager:

  • Control test execution in Run Manager.xlsm — set Execute column to Yes/No.

  • Use TestConfig_optionsID to reference browser/environment config from the TestConfig_options sheet.

  • Set IterationMode to RUN_ALL_ITERATIONS, RUN_ONE_ITERATION_ONLY, or RUN_RANGE_OF_ITERATIONS.

Test Data:


// Read Data from Excel data sheet
String email = dataTable.getData("TestData", "Email");
String country = dataTable.getCommonData("country", "DataValue");

// Never hardcode test data
String email = "user@example.com"; // Don't do this
Enter fullscreen mode Exit fullscreen mode

Data Best Practices:

Rule Reason
Use CommonData sheet for shared values (email, URL, etc.) Single source of truth
Use scenario-specific sheets for test-specific data Separation of concerns
Do not store passwords in plain text in Excel Security
Use JavaFaker for generating random test data where applicable Data independence
// Using JavaFaker for random data
Faker faker = new Faker();
String productName = faker.company().name();
String productRef = faker.number().digits(8);

Enter fullscreen mode Exit fullscreen mode

7. Waits & Synchronization

Wait Hierarchy (use in this order):

Wait Type When to Use Config Key
wait (FluentWait) Standard UI elements wait in properties
longwait (FluentWait) Slow-loading pages/modals longwait in properties
ExpectedConditions.visibilityOf Checking visibility before interaction
ExpectedConditions.elementToBeClickable Before clicking dynamic elements
PauseScript() Last resort only — unavoidable timing gaps

Best practice wait pattern:


try {
    wait.until(ExpectedConditions.visibilityOfElementLocated(locator));
    clickElement(locator, "Button Name");
} catch (TimeoutException e) {
    report_updateTestLog("Step", "Element not visible after timeout", Status.FAIL);
}

// For elements that may or may not appear
try {
    wait.until(ExpectedConditions.visibilityOfElementLocated(optionalElement));
    clickElement(optionalElement, "Optional Button");
} catch (TimeoutException | NoSuchElementException e) {
    report_updateTestLog("Step", "Optional element not present - skipping", Status.PASS);
}

// Never use
Thread.sleep(5000);
Enter fullscreen mode Exit fullscreen mode

Configuring Wait Timeouts:

Set in GlobalSettings.properties:


wait=30
longwait=60
Enter fullscreen mode Exit fullscreen mode

8. Assertions & Reporting

Status Levels:

Status When to Use
Status.PASS Assertion passed / action successful
Status.FAIL Critical assertion failed — stops test
Status.WARNING Non-critical mismatch (e.g., dynamic external values)
Status.DONE Informational step (no assertion)

Good assertion pattern:


if (driver.findElement(welcomeText).isDisplayed()) {
    report_updateTestLog("Login Validation", "Homepage loaded successfully", Status.PASS);
} else {
    report_updateTestLog("Login Validation", "Homepage did NOT load", Status.FAIL);
}

// Use framework helper for value comparison
validateActualExpectedValue(actualValue, expectedValue, "Field Name");

// Use WARNING for values dependent on external systems (APIs, DBs)
validateActualExpectedValueForUnpredictableStringValue(actualValue, expectedValue, "Calculated Field");
Enter fullscreen mode Exit fullscreen mode

Log Message Best Practices:


// Good - descriptive, includes actual value
report_updateTestLog("Field Validation", 
    "Actual: " + actualValue + " | Expected: " + expectedValue, Status.PASS);

// Bad - too vague
report_updateTestLog("Check", "OK", Status.PASS);
Enter fullscreen mode Exit fullscreen mode

9. Parallel Execution

The framework supports three threading models via Allocator.java:

Method Description When to Use
executeTestBatch() Fixed ThreadPoolExecutor Production stable runs
executeTestBatch_Virtual() Fixed ThreadPool (active) Default - best for most scenarios
executeTestBatch_AutoThreadPool() WorkStealingPool Experimental - perf testing

Thread Safety Rules:

  • Never use static mutable state in business components or test scripts.

  • referenceNumber in GeneralComponents is static — use with caution in parallel runs; prefer passing values as parameters.

  • Each ParallelRunner instance is independent — ensure no shared file/resource access without synchronization.


// Thread-safe - instance variable
private String referenceNumber;

// Risk in parallel - static shared variable
public static String referenceNumber = null; // Avoid in parallel scenarios
Enter fullscreen mode Exit fullscreen mode

Configuring Threads:

GlobalSettings.properties:


NumberOfThreads=3
Enter fullscreen mode Exit fullscreen mode
  • Recommended: Set threads based on available machine CPUs (e.g., threads = CPUs * 2).
  • Caution: Too many threads on a single machine causes driver conflicts and flaky tests.

10. Run Manager & Configuration

Run Manager Sheet Columns:

Column Value Notes
Execute Yes / No Controls if test runs
TestScenario Package/folder name Maps to test script folder
TestCase Class name Exact Java class name
IterationMode RUN_ALL_ITERATIONS Controls data iteration
StepsToExecute/EndIteration Number For ranged iterations
TestConfigurationID Config name Maps to TestConfigurations sheet

TestConfigurations Sheet:

Column Example Values
ExecutionMode LOCAL, REMOTE, GRID
Browser EDGE, CHROME, FIREFOX
Platform WINDOWS, LINUX
ExecutionTimeout 120, leave blank for auto

GlobalSettings.properties Key Properties:


# Execution
RunConfiguration=Regression          # Sheet name in Run Manager
NumberOfThreads=2
DefaultBrowser=EDGE
DefaultExecutionMode=LOCAL
DefaultPlatform=WINDOWS
Environment=SIT                       # SIT or UAT

# URLs
uialuat_SIT=https://sit.app.example.com
uialuat_UAT=https://uat.app.example.com

# Waits (seconds)
wait=30
longwait=60

# Reporting
ProjectName=AutomationProject
GenerateHTMLReport=false
Enter fullscreen mode Exit fullscreen mode

11. ExtentReports

Reports are auto-generated in target/Reports/Extent Result/ExtentReport.html.

Best Practices:

  • Always call extentReportFlush() at the end — already handled in Allocator.driveBatchExecution().

  • Ensure report_updateTestLog() is used — it auto-attaches screenshots on failure.

  • Never create a new ExtentReports instance inside test scripts.

Viewing Reports:


target/
└── Reports/
    ├── Extent Result/
    │   └── ExtentReport.html  • Open in browser
    └── HTML Reports/
        └── index.html
Enter fullscreen mode Exit fullscreen mode

12. CI/CD & Pipeline

Maven Profiles:


# Run via Allocator (Hybrid Framework) - DEFAULT
mvn clean test -PRunAllocator

# Run via TestNG suite
mvn clean test -PRunTestNGTests

# Pass run configuration at runtime
mvn clean test -PRunAllocator -DRunConfiguration=SIT_Smoke

# Pass environment at runtime
mvn clean test -PRunAllocator -DRunConfiguration=SIT_Smoke -DEnvironment=SIT
Enter fullscreen mode Exit fullscreen mode

Azure Pipeline (azure-pipelines.yml):

  • Ensure NumberOfThreads is set to match pipeline agent specs.

  • Use pipeline variables for environment switching instead of hardcoding.

  • Archive target/Reports/ as a pipeline artifact for post-run analysis.


# Good pipeline variable usage
- task: Maven@3
  inputs:
    goals: 'clean test'
    options: '-PRunAllocator -DRunConfiguration=$(RunConfig) -DEnvironment=$(Env)'
Enter fullscreen mode Exit fullscreen mode

13. Error Handling

Exception Handling Pattern:


// Catch specific exceptions and report
try {
    wait.until(ExpectedConditions.visibilityOfElementLocated(locator));
    clickElement(locator, "Submit Button");
} catch (TimeoutException e) {
    report_updateTestLog("Submit", "Element not visible - TimeoutException", Status.FAIL);
} catch (NoSuchElementException e) {
    report_updateTestLog("Submit", "Element not found in DOM", Status.FAIL);
}

// For optional elements (may or may not appear)
try {
    clickElement(optionalPopup, "Optional Popup OK");
} catch (NoSuchElementException | TimeoutException e) {
    // Silently skip - optional element
}

// Never swallow exceptions silently without a log
try {
    // ...
} catch (Exception e) {
    // Empty catch - bad practice
}
Enter fullscreen mode Exit fullscreen mode

Stop Execution on Critical Failure:

The ParallelRunner checks frameworkParameters.getStopExecution() — use this mechanism when a prerequisite test fails and subsequent tests cannot proceed.

14. Code Quality

General Rules:

Rule Detail
DRY Extract repeated actions into GeneralComponents or CommonMethods.
Single Responsibility Each method does one thing.
Descriptive Names fillFormDetails() not step1().
No Magic Numbers Use named constants or data table values.
Comment Why, Not What Code is self-explanatory; comments explain business logic.
Remove Dead Code Don't leave commented-out blocks; use version control instead.

Preferred Patterns:


// Extract repeated logic into CommonMethods
public String getProcessedReferenceNumber(String refNumber) {
    return refNumber.substring(0, refNumber.length() - 1) + "B";
}

// Use constants instead of magic strings
private static final String PASS_STATUS = "PASS";
private static final String ENVIRONMENT_SIT = "SIT";

// Null/empty guard
if (testData != null && !testData.isEmpty()) {
    getTestControlObject().getReport().reportEvent("TestControlData", testData, Status.PASS);
}
Enter fullscreen mode Exit fullscreen mode

Code Review Checklist:

  • No hardcoded URLs, credentials, or test data.

  • All driver.findElement() calls go through GeneralComponents helpers.

  • All XPaths are relative (not absolute).

  • All exceptions are caught and logged to report.

  • Report steps have meaningful descriptions.

  • No Thread.sleep() in code.

  • Run Manager updated for new test cases.

  • UI page locators added to correct uiPages class.

15. Environment Configuration

Switching Environments:

Environment is controlled via GlobalSettings.properties or pipeline parameter:
Environment=SIT or UAT

Business components should always read URLs from properties:


// Good - environment driven
if (properties.getProperty("Environment").equals("SIT")) {
    driver.get(properties.getProperty("uialuat_SIT"));
} else if (properties.getProperty("Environment").equals("UAT")) {
    driver.get(properties.getProperty("uialuat_UAT"));
}

// Bad - hardcoded URL
driver.get("https://sit.app.example.com");
Enter fullscreen mode Exit fullscreen mode

Supported Environments:

Key Description
SIT System Integration Testing
UAT User Acceptance Testing

Quick Reference - Do's and Don'ts

DO DON'T
Use GeneralComponents helper methods Call driver.findElement() directly in test scripts
Use FluentWait with ExpectedConditions Use Thread.sleep()
Read all test data from Excel/properties Hardcode values in test scripts
Report every step with report_updateTestLog() Leave silent catch blocks
Keep locators in uiPages only Put locators in business components
Use relative XPaths Use absolute XPaths
Use Status.WARNING for dynamic external values Fail tests for known external dependencies
Flush ExtentReport at end of run Create multiple ExtentReport instances
Set Execute=No to skip tests Delete rows from Run Manager
Use parameterized locator methods for dynamic elements Concatenate XPath strings inline

Support

Automation Team: QE Architect

automation.team@example.com

Generated for Hybrid Test Automation Framework — Enterprise QA Automation Project

Top comments (0)