Home / Blog / Selenium with Java: Complete Project Setup from Zero

Selenium

Selenium with Java: Complete Project Setup from Zero

QA Knowledge Hub·2026-04-05·7 min read

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:

  1. Java JDK 17+ — Download from adoptium.net (Eclipse Temurin). During installation, set the JAVA_HOME environment variable.
  2. Maven — Download from maven.apache.org. Add to PATH.
  3. IntelliJ IDEA Community Edition — Free IDE from JetBrains, excellent Java support.
  4. Google Chrome — Latest version.

Verify installations:

java -version
# java version "17.x.x"

mvn -version
# Apache Maven 3.x.x

Step 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=false

Step 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.java

Step 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 test

Or 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=true

Update 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 generic

Explicit 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:

  1. Add 5 more test classes (product listing, cart, checkout)
  2. Add data-driven tests using TestNG @DataProvider
  3. Add test reporting with ExtentReports
  4. 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.

999Get This Guide →

Related Posts

📝
Interview Prep
Apr 2026·12 min read

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 →
📝
Selenium
Apr 2026·8 min read

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 →
📝
Interview Prep
Apr 2026·11 min read

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 →