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

AnnotationRuns
@BeforeClassOnce, before any test method in the class
@BeforeMethodBefore each @Test method
@TestMarks the method itself as a test case
@AfterMethodAfter each @Test method (runs even if the test failed)
@AfterClassOnce, after all test methods in the class have run
Note: For Selenium specifically, the common pattern is: open the browser in @BeforeMethod and quit it in @AfterMethod, so every test method gets a completely fresh browser session and one test's leftover state (cookies, an open modal, a stale page) can never leak into the next test.

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
Next up: Writing loginWithWrongPasswordShowsError and three more near-duplicate methods for every bad input combination gets repetitive fast. Head to Data-Driven Testing to run the same test method against many different inputs using @DataProvider.