TestNG Basics
Everything so far has been "write a script and run the class." That works for a handful of scripts, but real automation suites need a test framework: something that discovers test methods, runs setup/teardown around them, reports pass/fail per method, and lets you group and run subsets of tests. TestNG is the most widely used test framework for Selenium+Java projects (JUnit is the other common choice). This lesson covers the annotations and structure you'll use constantly.
Adding TestNG to Your Maven Project
Add the dependency to your pom.xml, alongside your Selenium dependency:
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>7.9.0</version>
<scope>test</scope>
</dependency>
Core Annotations
| Annotation | Runs |
|---|---|
| @BeforeClass | Once, before any test method in the class |
| @BeforeMethod | Before each @Test method |
| @Test | Marks the method itself as a test case |
| @AfterMethod | After each @Test method (runs even if the test failed) |
| @AfterClass | Once, after all test methods in the class have run |
Assertions
TestNG's org.testng.Assert class provides the checks that actually decide pass/fail. If an assertion fails, TestNG throws an AssertionError, marks the test failed, and moves on to the next test.
Assert.assertEquals(actualTitle, "RecipeBox - My Recipes", "Page title mismatch after login");
Assert.assertTrue(driver.findElements(By.className("recipe-card")).size() > 0);
Assert.assertFalse(driver.findElement(By.id("loginBtn")).isEnabled(), "Login button should be disabled until fields are filled");
Worked Example: RecipeBoxLoginTest
A full TestNG class for testing RecipeBox's login page — with setup, teardown, and two independent test methods: one for a successful login, one for a failed login with a bad password.
public class RecipeBoxLoginTest {
private WebDriver driver;
@BeforeMethod
public void setUp() {
driver = new ChromeDriver();
driver.manage().window().maximize();
driver.get("https://recipebox.example.com/login");
}
@Test
public void loginWithValidCredentialsSucceeds() {
driver.findElement(By.id("email")).sendKeys("tester@example.com");
driver.findElement(By.id("password")).sendKeys("Test@1234");
driver.findElement(By.id("loginBtn")).click();
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
wait.until(ExpectedConditions.urlContains("/recipes"));
Assert.assertTrue(driver.getCurrentUrl().contains("/recipes"),
"Should redirect to the recipe list after a valid login");
}
@Test
public void loginWithWrongPasswordShowsError() {
driver.findElement(By.id("email")).sendKeys("tester@example.com");
driver.findElement(By.id("password")).sendKeys("WrongPassword");
driver.findElement(By.id("loginBtn")).click();
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
WebElement error = wait.until(
ExpectedConditions.visibilityOfElementLocated(By.className("login-error"))
);
Assert.assertEquals(error.getText(), "Invalid email or password.");
}
@AfterMethod
public void tearDown() {
if (driver != null) {
driver.quit();
}
}
}
Running Tests via testng.xml
A testng.xml suite file lets you group test classes into a run without touching your IDE's "run" button — this is exactly what a CI pipeline (Jenkins, GitHub Actions) will invoke.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="RecipeBoxSuite">
<test name="LoginTests">
<classes>
<class name="com.recipebox.tests.RecipeBoxLoginTest"/>
</classes>
</test>
</suite>
Run it from the command line with Maven's Surefire plugin configured to pick up the file, or directly:
mvn test -DsuiteXmlFile=testng.xml