Setting Up a Maven Project with Selenium and TestNG

9 min read

In Java, you don't run a script — you build a project. The project has a build tool, a dependency manager, a fixed folder layout, and a test runner. For Selenium that combination is almost always Maven + TestNG, and that's what this lesson scaffolds. By the end you'll have a Maven project that compiles cleanly, has Selenium 4 and TestNG on its classpath, and runs an empty test suite via mvn clean test. Every later lesson builds on this skeleton.

Maven in one paragraph

Maven is Java's build tool and dependency manager — Java's answer to npm or pip. You declare what your project depends on in a pom.xml file, and Maven downloads those libraries (plus their transitive dependencies) from Maven Central into a local cache (~/.m2/repository). It also defines the lifecycle: mvn compile, mvn test, mvn package, mvn install — each phase having a fixed set of plugins that run.

You don't have to love XML to use Maven. You just have to read it.

Creating the project in IntelliJ IDEA

IntelliJ IDEA (Community Edition is free) is the IDE every Java QA team in this course assumes:

  1. File → New → Project, pick Maven in the left rail.
  2. Set GroupId to com.mycompany.tests and ArtifactId to selenium-tests. The combo identifies your project uniquely on Maven Central if you ever publish.
  3. Click Create. IntelliJ generates pom.xml, the src/ folder tree, and an empty .idea/ directory with project metadata.

You can also create the project from the command line with mvn archetype:generate, but the IntelliJ wizard is faster and produces the same result.

The folder layout — Maven's strict convention

Maven enforces a fixed project structure. Don't fight it:

selenium-tests/
├── pom.xml                       ← dependencies and build config
├── src/
│   ├── main/java/                ← (empty for a pure test project)
│   └── test/
│       ├── java/
│       │   └── com/mycompany/tests/
│       │       ├── base/         ← BaseTest.java (driver setup/teardown)
│       │       ├── pages/        ← Page Object classes
│       │       └── tests/        ← @Test classes go here
│       └── resources/
│           ├── testng.xml        ← TestNG suite configuration
│           └── testdata/         ← test data files (JSON, CSV, .xlsx)
└── target/                       ← compiled output (add to .gitignore)

Two rules to internalise:

  • Test code lives under src/test/java, not src/main/java. Maven treats them differently: src/test/java is on the classpath only during test phases. If you put tests in src/main/java, they ship with your production artefact — which for a test-only project is fine but is a code smell.
  • The Java package path must match the folder path. package com.mycompany.tests.tests; lives in com/mycompany/tests/tests/. Mismatched paths produce confusing compiler errors.

The pom.xml — three dependencies that matter

Open pom.xml and replace the auto-generated <dependencies> block:

<project xmlns="http://maven.apache.org/POM/4.0.0">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.mycompany.tests</groupId>
    <artifactId>selenium-tests</artifactId>
    <version>1.0-SNAPSHOT</version>
 
    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
 
    <dependencies>
        <!-- Selenium WebDriver -->
        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-java</artifactId>
            <version>4.21.0</version>
        </dependency>
 
        <!-- TestNG: the test runner and assertions -->
        <dependency>
            <groupId>org.testng</groupId>
            <artifactId>testng</artifactId>
            <version>7.10.2</version>
            <scope>test</scope>
        </dependency>
 
        <!-- WebDriverManager: auto-resolves the matching driver binary -->
        <dependency>
            <groupId>io.github.bonigarcia</groupId>
            <artifactId>webdrivermanager</artifactId>
            <version>5.8.0</version>
        </dependency>
    </dependencies>
 
    <build>
        <plugins>
            <!-- Surefire is the plugin that actually runs tests during mvn test -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>3.2.5</version>
                <configuration>
                    <suiteXmlFiles>
                        <suiteXmlFile>src/test/resources/testng.xml</suiteXmlFile>
                    </suiteXmlFiles>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

What each piece does:

  • <maven.compiler.source>17</maven.compiler.source> — compile against Java 17. Selenium 4 requires at least Java 11; 17 is the modern long-term release.
  • selenium-java is the umbrella artifact that pulls in all browser-specific drivers and the W3C protocol implementation. One line gives you Chrome, Firefox, Edge, Safari support.
  • testng provides the @Test, @BeforeMethod, @AfterMethod annotations and the Assert class.
  • webdrivermanager removes the manual driver-binary dance — WebDriverManager.chromedriver().setup() resolves the correct ChromeDriver version for your installed Chrome, on first run, automatically.
  • maven-surefire-plugin is what runs your tests when you type mvn test. We point it at a testng.xml so suite configuration lives in one place.

After saving, IntelliJ shows a small "Load Maven changes" notification — click it. Maven downloads everything to ~/.m2/repository. The first run takes a minute; subsequent runs are instant because the cache is local.

A minimal testng.xml

Create src/test/resources/testng.xml:

<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="SeleniumSuite" verbose="1">
    <test name="SmokeTests">
        <packages>
            <package name="com.mycompany.tests.tests"/>
        </packages>
    </test>
</suite>

The <packages> element tells TestNG to find every @Test annotation under com.mycompany.tests.tests. We'll add specific classes, groups, and parallel config in chapter 5; this is the minimum that lets mvn test pick something up.

Maven commands you'll use daily

Open a terminal at the project root:

mvn clean test                       # delete target/, recompile, run all tests
mvn test -Dtest=LoginTest             # run just one test class
mvn test -Dgroups=smoke               # run all tests in the "smoke" group
mvn clean install                    # full build: compile + test + package
mvn dependency:tree                  # see every transitive dependency (debug version conflicts)

The two you'll type most often are mvn clean test (during development) and mvn test (in CI, to skip the clean step on a fresh checkout). Run mvn clean test now — with no test classes yet, you'll see a build success and "Tests run: 0." That's exactly what we want; the project compiles.

The end-to-end setup

Step 1 of 5

New project

IntelliJ → File → New → Project → Maven. GroupId com.mycompany.tests, ArtifactId selenium-tests. IntelliJ scaffolds pom.xml and src/.

Comparison with Cypress and Playwright

If you're coming from JavaScript-land, the setup feels heavier:

# Cypress
npm install cypress --save-dev
npx cypress open
 
# Playwright
npm init playwright@latest
 
# Selenium (Java)
# 1. Install Maven, install JDK 17
# 2. Create Maven project in IntelliJ
# 3. Edit pom.xml manually
# 4. Add testng.xml manually
# 5. Run `mvn clean test`

That's the trade-off. JavaScript test runners come with batteries included; Java's ecosystem expects you to wire it yourself. The upside is that every Java project at every company looks the same — pom.xml, src/test/java, Maven lifecycle. Once you've set up one, you've set up them all. New developers join your team and the layout is instantly familiar.

The Core Java cheat sheet on qa.codes covers the build-tool fundamentals every Java QA engineer leans on.

⚠️ Common mistakes

  • Putting test classes under src/main/java. They compile, but Surefire never finds them — Maven's lifecycle treats main/ and test/ differently and Surefire only scans test/. The symptom is mvn test reporting "Tests run: 0" while you swear there's a test class in the project. Move the file.
  • Forgetting <scope>test</scope> on TestNG. Without it, TestNG ends up on the production classpath. It compiles fine but pulls a test framework into a production-shaped artefact, which is a smell at minimum and an audit finding at worst. Always scope test-only libraries to test.
  • Mixing Selenium versions. Don't add selenium-java 4.21.0 and selenium-chrome-driver 3.141.59 in the same pom — they're incompatible. selenium-java already pulls in every per-browser dependency at the matching version. Run mvn dependency:tree if you suspect a conflict; the output makes the duplicate stand out instantly.

🎯 Practice task

Build the skeleton you'll use for the rest of this course. 25–35 minutes.

  1. Confirm java -version reports 17 or higher, and mvn -version reports any 3.6+. Install JDK 17 and Maven if either is missing.
  2. In IntelliJ, create a Maven project named selenium-tests with GroupId com.mycompany.tests. Confirm pom.xml exists at the project root and src/main/java, src/test/java were generated.
  3. Replace the auto-generated <dependencies> block with the one in this lesson — three dependencies plus the Surefire plugin. Click "Load Maven changes" in IntelliJ and watch the External Libraries panel fill up with Selenium, TestNG, and WebDriverManager.
  4. Create the package tree under src/test/java/com/mycompany/tests/: base/, pages/, and tests/. Then create src/test/resources/testng.xml with the minimal suite from the lesson.
  5. Run mvn clean test from the project root. The build prints BUILD SUCCESS and Tests run: 0. That zero is correct — there are no tests yet.
  6. Force a failure to see error messages. Add an obviously malformed dependency like <version>BOGUS</version> to one of the entries, save, and re-run mvn clean test. Read the failure message — it'll point at the bad version. Revert and confirm the build is green again. Knowing what Maven errors look like saves debugging time later.
  7. Stretch: in IntelliJ, right-click pom.xml → Maven → Show Effective POM. The expanded view shows every plugin and property Maven actually uses, including inherited defaults. Skim it once — most of the configuration you'll ever touch is on this single screen.

Next lesson: write your first real Selenium test class. We'll create HomePageTest.java, drive a real browser, and watch it open, navigate, assert, and quit.

// tip to track lessons you complete and pick up where you left off across devices.