You already set up Maven with Selenium and TestNG in the Selenium course — this lesson revisits that setup with TestNG as the centrepiece rather than an afterthought. When Selenium is the main event, you throw in TestNG as a test runner and move on. When TestNG is the focus, you configure it deliberately: the right Surefire version, a testng.xml that actually drives the run, and a project layout that scales from one test class to five hundred. This lesson builds that foundation and explains why each piece exists rather than just telling you what to type.
The pom.xml — what you actually need
For a pure TestNG project (no Selenium yet):
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>com.mycompany.tests</groupId>
<artifactId>testng-suite</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>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>7.10.2</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>src/test/resources/testng.xml</suiteXmlFile>
</suiteXmlFiles>
</configuration>
</plugin>
</plugins>
</build>
</project>Three things worth understanding here, not just copying:
<scope>test</scope> on TestNG. This scopes the dependency to the test compile and test runtime classpaths only. TestNG never ends up in your production JAR. This isn't just cleanliness — it's a correctness boundary. If you accidentally run application code from a test method, the test scope prevents production from depending on test infrastructure.
maven-surefire-plugin version. Maven ships with a bundled Surefire version that is often outdated. By declaring the plugin explicitly with version 3.2.5 you get correct TestNG 7 support, better parallel test handling, and accurate JUnit/TestNG detection. Always pin it.
<suiteXmlFile>. This tells Surefire to hand control to testng.xml entirely. Without this, Surefire falls back to its own test-discovery rules (scanning for classes named *Test.java), which ignores all your TestNG group and parameter configuration. Once you have a testng.xml, always point Surefire at it.
Project layout
testng-suite/
├── pom.xml
└── src/
└── test/
├── java/
│ └── com/mycompany/tests/
│ ├── base/ ← BaseTest.java
│ └── tests/ ← @Test classes
└── resources/
├── testng.xml ← suite descriptor
└── testdata/ ← Excel, CSV, JSON files
No src/main/java needed — this is a pure test project. The base/ package holds your BaseTest class with shared setup/teardown that all test classes extend. The tests/ package holds the actual @Test classes. testdata/ holds external data files your @DataProvider methods will read in chapter 3.
A minimal testng.xml to start with
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="TestNG Suite" verbose="1">
<test name="All Tests">
<packages>
<package name="com.mycompany.tests.tests"/>
</packages>
</test>
</suite>verbose="1" prints each test name as it runs — helpful during development to see what's executing. Use verbose="0" in CI to keep logs clean. <packages> auto-discovers every @Test class in that package — you never have to register individual class names. Add a new test class and it runs on the next mvn test automatically.
A verification test class
package com.mycompany.tests.tests;
import org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
public class SetupVerificationTest {
@BeforeMethod
public void setup() {
System.out.println("Setup: ready to run test");
}
@Test
public void testngIsConfiguredCorrectly() {
Assert.assertTrue(true, "If you see this pass, TestNG is wired up correctly");
}
@Test
public void assertionsWork() {
Assert.assertEquals("testng", "testng", "String equality works");
Assert.assertNotNull("some value", "Not-null check works");
}
@AfterMethod
public void teardown() {
System.out.println("Teardown: test complete");
}
}Run mvn clean test. You should see:
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
BUILD SUCCESS
If you see Tests run: 0, the most common cause is that Surefire can't find your testng.xml — double-check the path in <suiteXmlFile>.
Running tests — three ways
# Run everything via testng.xml (standard)
mvn clean test
# Run a single class directly (bypasses testng.xml)
mvn test -Dtest=SetupVerificationTest
# Run tests in a specific group (only works if testng.xml has group config)
mvn test -Dgroups=smoke
# Pass a different suite file without editing pom.xml
mvn test -DsuiteXmlFile=src/test/resources/smoke.xmlIn IntelliJ: right-click any @Test method → Run. Right-click the class → Run. Right-click testng.xml → Run. The IntelliJ TestNG plugin is built in — no installation required.
The full setup flow
Step 1 of 6
Create Maven project
IntelliJ → File → New Project → Maven. GroupId: com.mycompany.tests, ArtifactId: testng-suite. Alternatively: mvn archetype:generate. Both produce the same pom.xml skeleton.
Surefire output and reports
After mvn test, Surefire writes two sets of output:
- Console: test names and pass/fail status
target/surefire-reports/: XML and TXT results for each class — the input for CI tools like Jenkinstest-output/: TestNG's own HTML report — opentest-output/index.htmlin a browser for the full report with method-level details
The test-output/ directory is generated by TestNG, not Maven. If you're running tests via IntelliJ directly (not mvn test), you'll find it in the project root rather than target/. Add test-output/ and target/ to your .gitignore.
⚠️ Common mistakes
- Missing
<scope>test</scope>. Without it, TestNG lands on your production classpath. It compiles, everything appears to work, but you've shipped a test framework into your production artefact. Surefire also behaves differently when TestNG is on the compile scope vs test scope — always scope it correctly. - Outdated Surefire. Maven's bundled Surefire is often 2.22.x, which has known issues with TestNG 7 parallel execution and data providers. Always declare Surefire 3.2.5 or higher explicitly — one line in
<plugins>prevents hours of mysterious failures. - Surefire pointing at the wrong XML path.
<suiteXmlFile>testng.xml</suiteXmlFile>is evaluated relative to the project root, notsrc/test/resources/. If you put the file insrc/test/resources/you must writesrc/test/resources/testng.xml. Check this first when you seeTests run: 0— it's the most common root cause.
🎯 Practice task
Build the TestNG project skeleton you'll use for this entire course. 25–30 minutes.
- Create a new Maven project
testng-suitewith GroupIdcom.mycompany.tests. Do not reuse your Selenium project — this keeps the courses cleanly separated. - Add the TestNG dependency and Surefire plugin as shown. Run
mvn dependency:treeand confirmtestng-7.10.2appears in the output. - Create the package tree and
testng.xml. Runmvn clean test— expectTests run: 0, BUILD SUCCESS(no test classes yet). - Add
SetupVerificationTest.java. Runmvn clean test. ConfirmTests run: 2. - Force a failure. Change one assertion to
Assert.assertEquals(1, 2). Runmvn test. Read the failure output — note how TestNG reports the expected vs actual values and which method failed. Revert. - Open the HTML report. After a passing run, open
test-output/index.htmlin a browser. Explore the suite summary and the per-method detail. You'll be reading this report regularly throughout the course. - Stretch. Run
mvn test -Dtest=SetupVerificationTest#testngIsConfiguredCorrectly— the#methodNamesyntax runs a single method. Useful for focused debugging.
Next lesson: the complete TestNG annotation model. All ten lifecycle annotations, their execution order, and the @Test attributes that control individual test behaviour.