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.
Repository
View repository ↗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 referencessrc/main/java/pages/— Page Object classes using FindBy annotations and PageFactorysrc/main/java/base/— BaseTest: ThreadLocal driver setup/teardown, browser factorysrc/main/java/utils/— ScreenshotUtil, ConfigReader, WaitUtils, TestDataReadersrc/main/java/listeners/— ExtentReportListener implementing ITestListenertestng.xml— Suite definition: parallel='methods', thread-count='2'pom.xml— Maven dependencies, Surefire plugin config, Java 17 compilerPrerequisites
- ✓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
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 artifactSetup & run
Installation
- 1.
Clone the repository: git clone <repo-url> && cd selenium-java - 2.
Verify Java and Maven: java -version && mvn -version - 3.
Install dependencies and compile: mvn clean compile - 4.
Copy config: cp src/main/resources/config.properties.example src/main/resources/config.properties - 5.
Set BASE_URL, BROWSER, and test account credentials in config.properties - 6.
Run the smoke suite to verify setup: mvn test -Dgroups=smoke
Commands
Run the full regression suite
mvn clean testExecutes all tests defined in testng.xml; ExtentReports is generated in test-output/
Run a specific TestNG group
mvn test -Dgroups=smokeUseful for a quick sanity check — only @Test(groups='smoke') methods run
Run a single test class
mvn test -Dtest=LoginTestRun with a specific browser
mvn test -Dbrowser=firefoxOverrides the browser property from config.properties
Run with a specific TestNG XML suite
mvn test -DsuiteXmlFile=regression.xmlSkip tests and just compile
mvn clean compile -DskipTestsEnvironment
| Variable | Description | Example | Required |
|---|---|---|---|
BASE_URL | Root URL of the application under test | https://www.saucedemo.com | Yes |
BROWSER | Browser to use for local runs (chrome, firefox, edge) | chrome | Yes |
TEST_USERNAME | Username for the standard test account | standard_user | Yes |
TEST_PASSWORD | Password for the standard test account | secret_sauce | Yes |
IMPLICIT_WAIT_SECONDS | Implicit wait timeout in seconds — prefer explicit waits; set to 0 to disable | 0 | No |
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