Home / Blog / Selenium with Java: Complete Project Setup from Zero
Selenium with Java: Complete Project Setup from Zero
Selenium with Java is the most widely used automation stack in enterprise QA. If you are targeting SDET roles at banks, telecoms, or large IT service companies, Java + Selenium is what they will ask for.
This guide sets up a complete, production-ready project from scratch — Maven for dependency management, TestNG for test execution, and Page Object Model for maintainable code.
Prerequisites
Install these before starting:
- Java JDK 17+ — Download from
adoptium.net(Eclipse Temurin). During installation, set theJAVA_HOMEenvironment variable. - Maven — Download from
maven.apache.org. Add to PATH. - IntelliJ IDEA Community Edition — Free IDE from JetBrains, excellent Java support.
- Google Chrome — Latest version.
Verify installations:
java -version
# java version "17.x.x"
mvn -version
# Apache Maven 3.x.xStep 1: Create the Maven Project
Open IntelliJ IDEA → New Project → Maven.
Set the project details:
- GroupId:
com.qaknowledgehub - ArtifactId:
selenium-framework - Version:
1.0.0
Or create from the command line:
mvn archetype:generate \
-DgroupId=com.qaknowledgehub \
-DartifactId=selenium-framework \
-DarchetypeArtifactId=maven-archetype-quickstart \
-DinteractiveMode=falseStep 2: Configure pom.xml
Replace the contents of pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.qaknowledgehub</groupId>
<artifactId>selenium-framework</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<selenium.version>4.27.0</selenium.version>
<testng.version>7.9.0</testng.version>
</properties>
<dependencies>
<!-- Selenium WebDriver -->
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>${selenium.version}</version>
</dependency>
<!-- TestNG -->
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>${testng.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.5</version>
<configuration>
<suiteXmlFiles>
<suiteXmlFile>testng.xml</suiteXmlFile>
</suiteXmlFiles>
</configuration>
</plugin>
</plugins>
</build>
</project>Run mvn clean install to download dependencies.
Step 3: Project Folder Structure
Create this structure inside src/:
src/
├── main/java/com/qaknowledgehub/
│ ├── base/
│ │ └── BasePage.java ← Shared WebDriver methods
│ ├── pages/
│ │ ├── LoginPage.java
│ │ └── DashboardPage.java
│ └── utils/
│ ├── DriverFactory.java ← Browser setup
│ └── ConfigReader.java ← Read config properties
└── test/java/com/qaknowledgehub/
├── base/
│ └── BaseTest.java ← Test setup/teardown
└── tests/
└── LoginTest.javaStep 4: DriverFactory — Browser Setup
// src/main/java/com/qaknowledgehub/utils/DriverFactory.java
package com.qaknowledgehub.utils;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.firefox.FirefoxDriver;
public class DriverFactory {
public static WebDriver createDriver(String browser) {
return switch (browser.toLowerCase()) {
case "chrome" -> createChromeDriver();
case "firefox" -> createFirefoxDriver();
default -> throw new IllegalArgumentException("Unsupported browser: " + browser);
};
}
private static WebDriver createChromeDriver() {
ChromeOptions options = new ChromeOptions();
options.addArguments("--window-size=1920,1080");
// Uncomment for headless CI execution:
// options.addArguments("--headless=new");
return new ChromeDriver(options);
}
private static WebDriver createFirefoxDriver() {
return new FirefoxDriver();
}
}Note: Selenium 4.x uses Selenium Manager to auto-download browser drivers. You no longer need to manually download
chromedriver.exe.
Step 5: BasePage — Shared WebDriver Interactions
// src/main/java/com/qaknowledgehub/base/BasePage.java
package com.qaknowledgehub.base;
import org.openqa.selenium.*;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import java.time.Duration;
public class BasePage {
protected WebDriver driver;
protected WebDriverWait wait;
public BasePage(WebDriver driver) {
this.driver = driver;
this.wait = new WebDriverWait(driver, Duration.ofSeconds(10));
}
protected void click(By locator) {
wait.until(ExpectedConditions.elementToBeClickable(locator)).click();
}
protected void type(By locator, String text) {
WebElement element = wait.until(ExpectedConditions.visibilityOfElementLocated(locator));
element.clear();
element.sendKeys(text);
}
protected String getText(By locator) {
return wait.until(ExpectedConditions.visibilityOfElementLocated(locator)).getText();
}
protected boolean isDisplayed(By locator) {
try {
return driver.findElement(locator).isDisplayed();
} catch (NoSuchElementException e) {
return false;
}
}
protected void waitForUrl(String urlFragment) {
wait.until(ExpectedConditions.urlContains(urlFragment));
}
}Step 6: Page Objects — LoginPage and DashboardPage
// src/main/java/com/qaknowledgehub/pages/LoginPage.java
package com.qaknowledgehub.pages;
import com.qaknowledgehub.base.BasePage;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
public class LoginPage extends BasePage {
private static final By EMAIL_FIELD = By.id("user-name");
private static final By PASSWORD_FIELD = By.id("password");
private static final By LOGIN_BUTTON = By.id("login-button");
private static final By ERROR_MESSAGE = By.cssSelector("[data-test='error']");
public LoginPage(WebDriver driver) {
super(driver);
}
public void navigate() {
driver.get("https://www.saucedemo.com");
}
public void login(String username, String password) {
type(EMAIL_FIELD, username);
type(PASSWORD_FIELD, password);
click(LOGIN_BUTTON);
}
public String getErrorMessage() {
return getText(ERROR_MESSAGE);
}
public boolean isErrorDisplayed() {
return isDisplayed(ERROR_MESSAGE);
}
}// src/main/java/com/qaknowledgehub/pages/DashboardPage.java
package com.qaknowledgehub.pages;
import com.qaknowledgehub.base.BasePage;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
public class DashboardPage extends BasePage {
private static final By PAGE_TITLE = By.cssSelector(".title");
private static final By PRODUCT_ITEMS = By.cssSelector(".inventory_item");
public DashboardPage(WebDriver driver) {
super(driver);
}
public boolean isOnDashboard() {
return driver.getCurrentUrl().contains("/inventory");
}
public String getPageTitle() {
return getText(PAGE_TITLE);
}
public int getProductCount() {
return driver.findElements(PRODUCT_ITEMS).size();
}
}Step 7: BaseTest — TestNG Setup and Teardown
// src/test/java/com/qaknowledgehub/base/BaseTest.java
package com.qaknowledgehub.base;
import com.qaknowledgehub.utils.DriverFactory;
import org.openqa.selenium.WebDriver;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
public class BaseTest {
protected WebDriver driver;
@BeforeMethod
public void setUp() {
driver = DriverFactory.createDriver("chrome");
driver.manage().window().maximize();
}
@AfterMethod
public void tearDown() {
if (driver != null) {
driver.quit();
}
}
}Step 8: Write Tests
// src/test/java/com/qaknowledgehub/tests/LoginTest.java
package com.qaknowledgehub.tests;
import com.qaknowledgehub.base.BaseTest;
import com.qaknowledgehub.pages.DashboardPage;
import com.qaknowledgehub.pages.LoginPage;
import org.testng.Assert;
import org.testng.annotations.Test;
public class LoginTest extends BaseTest {
@Test(description = "Valid credentials should redirect to inventory page")
public void testValidLoginRedirectsToDashboard() {
LoginPage loginPage = new LoginPage(driver);
loginPage.navigate();
loginPage.login("standard_user", "secret_sauce");
DashboardPage dashboard = new DashboardPage(driver);
Assert.assertTrue(dashboard.isOnDashboard(), "User should be on dashboard");
Assert.assertEquals(dashboard.getPageTitle(), "Products");
}
@Test(description = "Invalid password should show error message")
public void testInvalidPasswordShowsError() {
LoginPage loginPage = new LoginPage(driver);
loginPage.navigate();
loginPage.login("standard_user", "wrong_password");
Assert.assertTrue(loginPage.isErrorDisplayed(), "Error message should be visible");
Assert.assertTrue(
loginPage.getErrorMessage().contains("Username and password do not match"),
"Error message text incorrect"
);
}
@Test(description = "Empty fields should show validation error")
public void testEmptyCredentialsShowsError() {
LoginPage loginPage = new LoginPage(driver);
loginPage.navigate();
loginPage.login("", "");
Assert.assertTrue(loginPage.isErrorDisplayed());
Assert.assertTrue(loginPage.getErrorMessage().contains("Username is required"));
}
@Test(description = "Locked out user should see appropriate message")
public void testLockedOutUserShowsError() {
LoginPage loginPage = new LoginPage(driver);
loginPage.navigate();
loginPage.login("locked_out_user", "secret_sauce");
Assert.assertTrue(loginPage.isErrorDisplayed());
Assert.assertTrue(loginPage.getErrorMessage().contains("locked out"));
}
}Step 9: testng.xml — Test Suite Configuration
Create testng.xml in the project root:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="QA Knowledge Hub Test Suite" verbose="1">
<test name="Login Tests">
<classes>
<class name="com.qaknowledgehub.tests.LoginTest"/>
</classes>
</test>
</suite>Step 10: Run the Tests
mvn clean testOr run the LoginTest class directly from IntelliJ by right-clicking the class → Run.
Step 11: Add GitHub Actions CI
Create .github/workflows/selenium-tests.yml:
name: Selenium Tests
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
- name: Cache Maven packages
uses: actions/cache@v3
with:
path: ~/.m2
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
- name: Run tests (headless)
run: mvn clean test -Dbrowser=chrome -Dheadless=trueUpdate DriverFactory.createChromeDriver() to read the headless system property:
private static WebDriver createChromeDriver() {
ChromeOptions options = new ChromeOptions();
options.addArguments("--window-size=1920,1080");
if (Boolean.parseBoolean(System.getProperty("headless", "false"))) {
options.addArguments("--headless=new", "--no-sandbox", "--disable-dev-shm-usage");
}
return new ChromeDriver(options);
}Key Selenium Concepts for Interviews
Locator Strategies (in order of preference)
// 1. ID — most reliable, fastest
By.id("login-button")
// 2. CSS Selector — versatile, readable
By.cssSelector("[data-test='error']")
By.cssSelector(".inventory_item .btn_primary")
// 3. XPath — use when CSS cannot reach it
By.xpath("//button[contains(text(), 'Add to cart')]")
By.xpath("//input[@placeholder='Search products']")
// 4. Name — for form inputs
By.name("username")
// Avoid: By.className and By.tagName alone — too genericExplicit Wait vs Implicit Wait
// AVOID: Implicit wait (applies globally, mixes with explicit wait)
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
// PREFER: Explicit wait (targeted, predictable)
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
WebElement element = wait.until(
ExpectedConditions.visibilityOfElementLocated(By.id("result"))
);Never mix implicit and explicit waits. Implicit wait is applied globally and interferes with explicit wait timeouts in unpredictable ways.
Handling Dropdowns
import org.openqa.selenium.support.ui.Select;
Select dropdown = new Select(driver.findElement(By.id("country-select")));
dropdown.selectByVisibleText("India");
dropdown.selectByValue("IN");
dropdown.selectByIndex(2);Handling Alerts
// Accept an alert
driver.switchTo().alert().accept();
// Dismiss an alert
driver.switchTo().alert().dismiss();
// Get alert text
String alertText = driver.switchTo().alert().getText();Switching to IFrames
// Switch into iframe
driver.switchTo().frame("iframe-name");
// or
driver.switchTo().frame(driver.findElement(By.cssSelector("iframe.payment")));
// Switch back to main content
driver.switchTo().defaultContent();Summary
You now have a working Selenium + Java + TestNG project with Page Object Model and CI/CD integration. The test suite runs against saucedemo.com — a free practice automation site purpose-built for learning.
Your next steps:
- Add 5 more test classes (product listing, cart, checkout)
- Add data-driven tests using TestNG
@DataProvider - Add test reporting with ExtentReports
- Push to GitHub and verify the Actions workflow runs green
This project is interview-ready once it has 20+ tests across 3+ pages with working CI.
Recommended Resource
QA Interview Kit
Interview prep kit with real-world QA and API scenarios.
Related Posts
35 Playwright Interview Questions (With Answers)
Playwright interview questions and answers for QA and SDET roles — covering setup, locators, waits, fixtures, API testing, and debugging.
Read article →Page Object Model in Selenium: Design and Best Practices
Learn the Page Object Model design pattern for Selenium — why it exists, how to implement it correctly in Java, and the mistakes to avoid.
Read article →60 Selenium Interview Questions for Java Developers
The most asked Selenium + Java interview questions with complete answers — covering WebDriver setup, locators, waits, POM, TestNG, and framework design.
Read article →