On this page14 sections

Selenium + Java Automation Framework

A Maven-based Selenium 4 + Java framework with Page Object Model, TestNG suite management, extent reports, and a GitHub Actions CI pipeline.

Intermediate-to-advancedE2EUI automationCross-browser testingRegression testingui automation
Setup: ~30 minLanguage: Java 17Framework: Selenium 4Package manager: MavenBest for: Java automation engineers preparing for enterprise QA roles

Overview

This project provides a production-ready Selenium 4 + Java 17 automation framework built with Maven, TestNG, and the Page Object Model. It targets a demo e-commerce application and covers the patterns that matter in enterprise Java test teams: WebDriverManager for browser binary management, a thread-local WebDriver strategy for parallel execution without shared state, data-driven testing with TestNG DataProvider, and ExtentReports for rich HTML reporting. The GitHub Actions pipeline runs the regression suite on every push.

Project goals

  • Implement thread-local WebDriver management so tests can run in parallel without browser context collisions
  • Apply the Page Object Model with a BaseTest class handling driver lifecycle — no WebDriver references inside test classes
  • Use TestNG DataProvider to drive data-intensive tests (login with multiple credential sets, form validation) from external sources
  • Integrate WebDriverManager to remove the need to manually download and configure ChromeDriver or GeckoDriver
  • Generate an ExtentReports HTML report with step-level logging and embedded screenshots on failure
  • Run the regression suite in parallel across two threads in GitHub Actions

Architecture

Page Object Model with Thread-Local Driver and TestNG Listeners

A BaseTest class manages the WebDriver lifecycle using a ThreadLocal<WebDriver> to support parallel test execution. Page Objects encapsulate locators and actions. A TestNG ITestListener captures failures and attaches screenshots to the ExtentReport. Test data is supplied via TestNG DataProvider and external CSV/JSON files.

src/test/java/tests/TestNG test classes; contain @Test methods and DataProvider references
src/main/java/pages/Page Object classes using FindBy annotations and PageFactory
src/main/java/base/BaseTest: ThreadLocal driver setup/teardown, browser factory
src/main/java/utils/ScreenshotUtil, ConfigReader, WaitUtils, TestDataReader
src/main/java/listeners/ExtentReportListener implementing ITestListener
testng.xmlSuite definition: parallel='methods', thread-count='2'
pom.xmlMaven dependencies, Surefire plugin config, Java 17 compiler

Prerequisites

  • Java Development Kit (JDK) 17 or later — JAVA_HOME must be set
  • Maven 3.8 or later — mvn must be on PATH
  • Git
  • Chrome or Firefox browser installed locally
  • An IDE (IntelliJ IDEA recommended) with the TestNG plugin

Folder structure

Project structure
Bash
pom.xml                                   # Maven project descriptor: dependencies, Surefire plugin, Java version
testng.xml                                # TestNG suite: parallel='methods', thread-count='2', all test classes
src/main/resources/config.properties      # Runtime config: BASE_URL, BROWSER, implicit wait timeout
src/main/java/base/BaseTest.java          # ThreadLocal<WebDriver> setup and @AfterMethod teardown; all tests extend this
src/main/java/base/BrowserFactory.java    # Creates WebDriver instances via WebDriverManager for Chrome, Firefox, Edge
src/main/java/pages/LoginPage.java        # @FindBy locators and login(), getErrorMessage() actions
src/main/java/pages/ProductPage.java      # Product listing: sortBy(), filterByCategory(), addToCart(productName)
src/main/java/pages/CheckoutPage.java     # Multi-step checkout: fillShipping(), fillPayment(), submitOrder(), getConfirmation()
src/main/java/utils/ConfigReader.java     # Reads config.properties; singleton with lazy init
src/main/java/utils/WaitUtils.java        # Explicit wait helpers: waitForVisible, waitForClickable, waitForText
src/main/java/utils/ScreenshotUtil.java   # Takes and saves a screenshot; called by the TestNG listener on failure
src/main/java/listeners/ExtentReportListener.java  # ITestListener: starts test, logs pass/fail, attaches screenshot on failure
src/test/java/tests/LoginTest.java        # Login happy path, wrong credentials, empty fields, account lockout
src/test/java/tests/CheckoutTest.java     # End-to-end checkout with DataProvider-driven payment scenarios
src/test/java/data/TestData.java          # DataProvider methods returning Object[][] for login and checkout data sets
test-output/ExtentReport.html             # Generated report (gitignored); created after each test run
.github/workflows/selenium.yml            # CI: checkout, setup-java, mvn test, upload test-output artifact

Setup & run

Installation

  1. 1.Clone the repository: git clone <repo-url> && cd selenium-java
  2. 2.Verify Java and Maven: java -version && mvn -version
  3. 3.Install dependencies and compile: mvn clean compile
  4. 4.Copy config: cp src/main/resources/config.properties.example src/main/resources/config.properties
  5. 5.Set BASE_URL, BROWSER, and test account credentials in config.properties
  6. 6.Run the smoke suite to verify setup: mvn test -Dgroups=smoke

Commands

Run the full regression suite

mvn clean test

Executes all tests defined in testng.xml; ExtentReports is generated in test-output/

Run a specific TestNG group

mvn test -Dgroups=smoke

Useful for a quick sanity check — only @Test(groups='smoke') methods run

Run a single test class

mvn test -Dtest=LoginTest

Run with a specific browser

mvn test -Dbrowser=firefox

Overrides the browser property from config.properties

Run with a specific TestNG XML suite

mvn test -DsuiteXmlFile=regression.xml

Skip tests and just compile

mvn clean compile -DskipTests

Environment

VariableDescriptionExampleRequired
BASE_URLRoot URL of the application under testhttps://www.saucedemo.comYes
BROWSERBrowser to use for local runs (chrome, firefox, edge)chromeYes
TEST_USERNAMEUsername for the standard test accountstandard_userYes
TEST_PASSWORDPassword for the standard test accountsecret_sauceYes
IMPLICIT_WAIT_SECONDSImplicit wait timeout in seconds — prefer explicit waits; set to 0 to disable0No

Test data strategy

  • TestNG DataProvider supplies multiple credential sets (valid, invalid, locked) to login tests — no copy-pasting test methods
  • Checkout data (shipping addresses, payment details) is stored in a JSON file and read by TestDataReader at test initialisation
  • Credentials and sensitive data are injected via config.properties (gitignored) or CI environment variables — never hardcoded
  • Each test is responsible for its own setup via @BeforeMethod; tests do not depend on the side effects of other tests
  • Random product names use UUID suffixes to avoid conflicts when the application retains state between runs

Reporting

  • ExtentReports 5 generates a self-contained HTML report at test-output/ExtentReport.html after each run
  • The TestNG ITestListener attaches a base64-encoded screenshot to the Extent test node on every failure
  • TestNG's own HTML report (test-output/index.html) is also available as a secondary view
  • Step-level logging via ExtentTest.log() provides a trace of each action inside the report without needing to open traces
  • CI uploads the entire test-output/ directory as a GitHub Actions artifact, including both report formats and screenshots

CI/CD

  • .github/workflows/selenium.yml triggers on push to main and pull_request events
  • actions/setup-java@v4 with java-version: '17' and distribution: 'temurin' installs the JDK
  • WebDriverManager downloads the correct ChromeDriver binary at runtime — no separate setup-chrome step required
  • The CI run uses `mvn clean test -Dbrowser=chrome` for headless Chrome execution
  • Headless mode is enabled by passing '--headless=new' as a ChromeOption via BrowserFactory when the CI environment variable is set
  • test-output/ is uploaded with actions/upload-artifact@v4 for post-run investigation
  • Credentials are passed as GitHub Actions secrets: TEST_USERNAME and TEST_PASSWORD

Common issues

SessionNotCreatedException: ChromeDriver version mismatch

Cause: Manually downloaded ChromeDriver does not match the installed Chrome version

Fix: Remove any manually downloaded driver and let WebDriverManager handle it — it fetches the correct version automatically

StaleElementReferenceException on a previously located element

Cause: The DOM refreshed or re-rendered between locating the element and interacting with it

Fix: Re-locate the element inside a retry loop using WaitUtils.waitForVisible(By locator) rather than caching a WebElement reference

Tests fail in parallel with NullPointerException on WebDriver

Cause: WebDriver is stored in a static field instead of a ThreadLocal, causing threads to overwrite each other's instance

Fix: Use ThreadLocal<WebDriver> in BaseTest; access via getDriver() which calls threadLocal.get()

Implicit wait conflicts with explicit wait causing unexpected delays

Cause: Selenium's implicit wait and WebDriverWait interact — when both are set, the effective timeout can be the sum

Fix: Set implicit wait to 0 and use only explicit WebDriverWait with ExpectedConditions throughout

mvn test fails with 'Unable to access jarfile' in CI

Cause: Maven Surefire cannot find compiled classes — build step was skipped

Fix: Run `mvn clean test` (not just `mvn test`) to ensure compilation happens before the Surefire plugin invokes TestNG

Best practices

  • Use ThreadLocal<WebDriver> in BaseTest to guarantee thread safety when running tests in parallel via TestNG
  • Prefer explicit waits (WebDriverWait + ExpectedConditions) over implicit waits; never mix both
  • Keep Page Object methods at the action level (loginAs, addItemToCart) rather than the locator level (clickLoginButton) — this makes tests read like user stories
  • Annotate smoke tests with @Test(groups='smoke') so fast CI builds can run a targeted subset
  • Use @DataProvider(parallel=true) for data-driven tests to run multiple data sets concurrently
  • Log each significant action with ExtentTest.log(Status.INFO, '...') inside the Page Object methods so the report shows execution context without needing a debugger
  • Gitignore test-output/, target/, and config.properties to keep the repository clean

Next steps

  • Add a REST Assured API test layer to set up and clean up test data without UI interaction
  • Integrate with TestNG's retry analyser to re-run genuinely flaky tests (e.g. network timeouts) without masking real failures
  • Add cross-browser support for Edge and Firefox by parameterising the browser in testng.xml with a `<parameter>` element
  • Set up Allure Reports as an alternative to ExtentReports for richer trend history and CI integration
  • Migrate to a Docker Selenium Grid to enable true parallel cross-browser execution in CI