Hands‑On Java Automation Testing From Beginner To Advanced
Build Your First Working Solution
Goal: By the end of this chapter you will be able to
- set up a Java Selenium project from scratch
- write, run and debug a simple test
- extend it into a mini‑project (login test)
- troubleshoot common problems
- add advanced features (parallel runs, data‑driven, reporting)
Audience: Developers, QA engineers, and anyone who wants a solid, production‑ready foundation in Java‑based automation testing.
Table of Contents
| Section | What you’ll learn |
|---|---|
| 1. Fundamentals | Core concepts, environment set‑up, Maven/Gradle, Selenium basics |
| 2. Implementation | Hands‑on coding – first test, mini‑project, troubleshooting |
| 3. Advanced Topics | Parallel execution, data‑driven, custom listeners, reporting |
| 4. Real‑World Applications | E‑commerce, mobile web, API integration |
| 5. Exercises | Practice projects to cement knowledge |
1. Fundamentals
1.1 Quick Setup
| Tool | Why | How to install |
|---|---|---|
| JDK 17 | Java 17 is the current LTS version. | brew install openjdk@17 (mac) or download from Oracle |
| IDE | VSCode + Java Extension, IntelliJ IDEA Community | |
| Maven | Dependency management | brew install maven |
| Selenium 4.x | Web automation | Maven dependency: <dependency>...</dependency> |
| WebDriverManager | Auto‑download drivers | <dependency>io.github.bonigarcia:webdrivermanager</dependency> |
| TestNG | Test framework | <dependency>org.testng:testng</dependency> |
| Allure | Reporting | <dependency>io.qameta.allure:allure-testng</dependency> |
Project Skeleton
# Create a Maven project
mvn archetype:generate -DgroupId=com.example -DartifactId=automation-demo -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false
cd automation-demo
Add the following to pom.xml (excerpt):
<dependencies>
<!-- Selenium -->
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>4.21.0</version>
</dependency>
<!-- WebDriverManager -->
<dependency>
<groupId>io.github.bonigarcia</groupId>
<artifactId>webdrivermanager</artifactId>
<version>5.9.1</version>
</dependency>
<!-- TestNG -->
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>7.9.0</version>
<scope>test</scope>
</dependency>
<!-- Allure -->
<dependency>
<groupId>io.qameta.allure</groupId>
<artifactId>allure-testng</artifactId>
<version>2.20.1</version>
<scope>test</scope>
</dependency>
</dependencies>
Pro Tip: Keep your
pom.xmltidy. Use<dependencyManagement>to pin versions once, then reference them in modules.
1.2 Essential Commands & Immediate Practice
| Command | What it does | When to use |
|---|---|---|
mvn clean compile | Clean target folder, compile sources | Before first run |
mvn test | Execute TestNG tests | After writing tests |
mvn test -Dtest=LoginTest | Run a specific test class | Debugging |
mvn surefire:test | Run tests with Surefire plugin | Same as mvn test |
mvn clean test -DfailIfNoTests=false | Run tests, ignore if none | CI pipelines |
Practice Exercise
- Create a
src/test/java/com/example/HelloTest.java. - Add a trivial test that prints “Hello, Selenium!”.
- Run
mvn test.
package com.example;
import org.testng.Assert;
import org.testng.annotations.Test;
public class HelloTest {
@Test
public void hello() {
System.out.println("Hello, Selenium!");
Assert.assertTrue(true);
}
}
Why – This confirms the environment works, Maven resolves dependencies, and TestNG runs tests.
1.3 Core Concepts
| Concept | What you’ll see | Practical Example |
|---|---|---|
| Page Object Model (POM) | Encapsulate page elements & actions | LoginPage.java |
| Implicit vs Explicit Waits | Reduce flakiness | WebDriverWait |
| TestNG Annotations | @BeforeSuite, @AfterSuite, @DataProvider | Setup/teardown |
| Allure Reporting | Visual test results | @Attachment |
Tip: Keep page objects in
src/main/java/com/example/pages. Tests insrc/test/java/com/example/tests.
2. Implementation
2.1 First Running Example – “Open Browser & Verify Title”
package com.example.tests;
import com.example.pages.HomePage;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.testng.Assert;
import org.testng.annotations.Test;
import io.github.bonigarcia.webdrivermanager.WebDriverManager;
public class OpenBrowserTest {
@Test
public void openHomePage() {
WebDriverManager.setUpChromeDriver();
ChromeOptions options = new ChromeOptions();
options.addArguments("--headless"); // run without UI
WebDriver driver = new org.openqa.selenium.chrome.ChromeDriver(options);
HomePage home = new HomePage(driver);
home.navigate();
String title = driver.getTitle();
System.out.println("Page title: " + title);
Assert.assertEquals(title, "Example Domain");
driver.quit();
}
}
Explanation
WebDriverManagerautomatically downloads the correct ChromeDriver.HomePageis a simple page object (see next section).- Headless mode speeds up CI runs.
2.2 Build Your First Mini‑Project – Login Test
Step 1: Page Objects
package com.example.pages;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
public class LoginPage {
private final WebDriver driver;
private final By username = By.id("user");
private final By password = By.id("pass");
private final By loginBtn = By.id("login");
private final By errorMsg = By.id("error");
public LoginPage(WebDriver driver) { this.driver = driver; }
public void navigate() { driver.get("https://example.com/login"); }
public void setUsername(String user) { driver.findElement(username).sendKeys(user); }
public void setPassword(String pass) { driver.findElement(password).sendKeys(pass); }
public void clickLogin() { driver.findElement(loginBtn).click(); }
public String getError() { return driver.findElement(errorMsg).getText(); }
}
Step 2: Test Class
package com.example.tests;
import com.example.pages.LoginPage;
import io.github.bonigarcia.webdrivermanager.WebDriverManager;
import org.openqa.selenium.WebDriver;
import org.testng.Assert;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
public class LoginTest {
@Test(dataProvider = "credentials")
public void testLogin(String user, String pass, boolean expected) {
WebDriverManager.setUpChromeDriver();
WebDriver driver = new org.openqa.selenium.chrome.ChromeDriver();
LoginPage login = new LoginPage(driver);
login.navigate();
login.setUsername(user);
login.setPassword(pass);
login.clickLogin();
if (expected) {
Assert.assertTrue(driver.getCurrentUrl().contains("/dashboard"));
} else {
Assert.assertEquals(login.getError(), "Invalid credentials");
}
driver.quit();
}
@DataProvider
public Object[][] credentials() {
return new Object[][] {
{"validUser", "validPass", true},
{"invalid", "wrong", false}
};
}
}
Key Take‑aways
- DataProvider lets you run the same test with different inputs.
- Page Objects isolate locators – easier to maintain.
- Assertions verify expected behavior.
2.3 Common Issues & Hands‑On Troubleshooting
| Symptom | Likely Cause | Fix |
|---|---|---|
SessionNotCreatedException | Wrong ChromeDriver version | Use WebDriverManager or set -Dwebdriver.chrome.driver |
NoSuchElementException | Element not found (timing) | Add explicit wait: new WebDriverWait(driver, 10).until(ExpectedConditions.visibilityOfElementLocated(By.id("login"))); |
StaleElementReferenceException | Page re‑rendered | Refresh reference or use ExpectedConditions.stalenessOf(element) |
ElementNotInteractableException | Element hidden or disabled | Use Actions to click or scroll into view |
TestNG failures due to @Test not executed | Wrong package name or missing @Test annotation | Verify classpath, mvn test |
Pro Tip: Use
driver.manage().timeouts().implicitlyWait(5, TimeUnit.SECONDS);sparingly. Prefer explicit waits for determinism.
2.4 Extend and Customize Your Implementation
| Feature | How to add | Example |
|---|---|---|
| Parallel Test Execution | @Test(threadPoolSize = 5) or parallel="tests" in testng.xml | Run 5 login tests concurrently |
| Custom Listeners | Implement ITestListener | Log start/end times |
| Reporting | Allure annotations (@Step, @Attachment) | Attach screenshots on failure |
| Parameterization | @Parameters({"env"}) | Run tests against staging/production |
| Dockerized Selenium Grid | docker-compose.yml with Selenium Hub & Nodes | Scale out tests in CI |
Example: Custom Listener
public class MyListener implements ITestListener {
@Override
public void onTestStart(ITestResult result) {
System.out.println("Starting: " + result.getName());
}
@Override
public void onTestSuccess(ITestResult result) {
System.out.println("Success: " + result.getName());
}
@Override
public void onTestFailure(ITestResult result) {
System.out.println("Failure: " + result.getName());
}
}
Add to testng.xml:
<listeners>
<listener class-name="com.example.listeners.MyListener"/>
</listeners>
3. Advanced Topics
3.1 Parallel Execution & Thread‑Safety
- Use TestNG
parallel="methods"or JUnit 5@Execution(ExecutionMode.CONCURRENT). - Ensure WebDriver is thread‑local:
ThreadLocal<WebDriver> driver = ThreadLocal.withInitial(() -> new ChromeDriver());. - Use Selenium Grid or Selenoid for distributed execution.
3.2 Data‑Driven Testing
- CSV, JSON, or Excel as data sources.
- Use
@DataProviderwith aStream<Object[]>for large data sets. - Example:
public Object[][] loadCsv(String path).
3.3 Custom Listeners & Reporting
- Allure: Add
@Attachmentfor screenshots. - ExtentReports: Add logs, screenshots, and HTML reports.
- Selenium‑Grid‑Reports: Visual grid execution.
3.4 Performance & Optimizations
- Page Object Cache: Cache elements with
@CacheLookup. - Explicit Waits: Use
FluentWaitfor flexible polling. - Headless + GPU:
--disable-gpufor faster runs. - Parallel Grid: Up to 10 nodes with
docker-compose.
3.5 Security & Data Privacy
- Avoid hardcoding credentials. Use environment variables or Vault.
- Mask sensitive data in reports (
@Attachmentwithmask=true).
4. Real‑World Applications
| Domain | Typical Test Cases | Tools/Tech |
|---|---|---|
| E‑Commerce | Cart flow, payment gateway, promo codes | Selenium, TestNG, Allure |
| Mobile Web | Responsiveness, touch gestures | Appium, Java, TestNG |
| API Integration | REST calls, JSON schema validation | RestAssured, Java, TestNG |
| CI/CD | Jenkins, GitHub Actions, Docker | Maven, Allure, Docker |
Case Study:
- Project: Automate login, checkout, and order confirmation for a demo e‑commerce site.
- Approach: Page Object Model, Data‑Driven, Parallel runs on Docker Compose.
- Outcome: 95% test coverage, 10x faster regression suite.
5. Exercises
| # | Skill Focus | Description | Expected Deliverable |
|---|---|---|---|
| 1 | Fundamentals | Write a test that opens a browser, navigates to https://example.com, and verifies the page title. | OpenBrowserTest.java |
| 2 | Page Objects | Create a SearchPage with methods: enterQuery(String), clickSearch(), getResults(). | SearchPage.java |
| 3 | DataProvider | Use a CSV file to test login with 20 credential sets. | LoginTest.java with @DataProvider |
| 4 | Parallel Execution | Configure TestNG to run all tests in parallel with 5 threads. | testng.xml |
| 5 | Reporting | Integrate Allure, attach a screenshot on failure. | Allure report |
| 6 | Advanced | Implement a custom ITestListener that logs test start/end times to a file. | MyListener.java |
| 7 | Real‑World | Automate a checkout flow for a demo e‑commerce site. Use Page Objects, Data‑Driven, and Parallel execution. | Full test suite |
Pro Tip: Commit your exercises to GitHub; run them on GitHub Actions to simulate CI.
Quick Reference Cheat Sheet
| Topic | Command / Code | Notes |
|---|---|---|
| Maven compile | mvn clean compile | |
| Run all tests | mvn test | |
| Run specific test | mvn test -Dtest=MyTest | |
| Add Page Object | public class MyPage { ... } | |
| Explicit wait | new WebDriverWait(driver, 10).until(ExpectedConditions.elementToBeClickable(By.id("login"))); | |
| DataProvider | @DataProvider(name="dp") public Object[][] data() { ... } | |
| Parallel config | testng.xml <suite parallel="tests" thread-count="5"> | |
| Allure attachment | @Attachment(value = "Screenshot", type = "image/png") public byte[] screenshot() { return ((TakesScreenshot) driver).getScreenshotAs(OutputType.BYTES); } |
Final Thoughts
- Start small, iterate fast.
- Keep tests readable. Use descriptive names, avoid magic strings.
- Maintain page objects. Refactor when locators change.
- Automate only what you need. Avoid flaky tests.
- Integrate early. Add tests to CI to catch regressions.
Happy coding! 🚀