TestNG and JUnit Integration
Integrate Selenium with TestNG and JUnit test frameworks for structured, maintainable Java tests.
Selenium 3 & 4 Stable Verified against Selenium 4.48.0
TestNG and JUnit are the two most popular testing frameworks for Java. Both integrate seamlessly with Selenium to provide test organization, assertions, and reporting.
TestNG Setup
Maven Dependencies
<dependencies> <dependency> <groupId>org.seleniumhq.selenium</groupId> <artifactId>selenium-java</artifactId> <version>4.18.1</version> </dependency> <dependency> <groupId>org.testng</groupId> <artifactId>testng</artifactId> <version>7.9.0</version> <scope>test</scope> </dependency></dependencies>Basic TestNG Test
import org.openqa.selenium.WebDriver;import org.openqa.selenium.chrome.ChromeDriver;import org.openqa.selenium.By;import org.testng.annotations.*;import org.testng.Assert;
public class LoginTest { private WebDriver driver;
@BeforeClass public void setupClass() { // Runs once before all tests in this class System.out.println("Setting up test class"); }
@BeforeMethod public void setup() { // Runs before each test method driver = new ChromeDriver(); driver.manage().window().maximize(); }
@Test(priority = 1) public void testValidLogin() { driver.get("https://example.com/login"); driver.findElement(By.id("username")).sendKeys("validuser"); driver.findElement(By.id("password")).sendKeys("validpass"); driver.findElement(By.id("login")).click();
Assert.assertTrue(driver.getCurrentUrl().contains("/dashboard"), "Should redirect to dashboard"); }
@Test(priority = 2) public void testInvalidLogin() { driver.get("https://example.com/login"); driver.findElement(By.id("username")).sendKeys("invalid"); driver.findElement(By.id("password")).sendKeys("wrong"); driver.findElement(By.id("login")).click();
String error = driver.findElement(By.className("error")).getText(); Assert.assertEquals(error, "Invalid credentials"); }
@AfterMethod public void teardown() { // Runs after each test method if (driver != null) { driver.quit(); } }
@AfterClass public void teardownClass() { // Runs once after all tests in this class System.out.println("Tests completed"); }}TestNG Annotations Reference
| Annotation | Description |
|---|---|
@BeforeSuite | Before all tests in suite |
@BeforeClass | Before first test in class |
@BeforeMethod | Before each test method |
@Test | Marks test method |
@AfterMethod | After each test method |
@AfterClass | After last test in class |
@AfterSuite | After all tests in suite |
TestNG Data Providers
import org.testng.annotations.DataProvider;
public class DataDrivenTest { private WebDriver driver;
@BeforeMethod public void setup() { driver = new ChromeDriver(); }
@DataProvider(name = "loginData") public Object[][] loginDataProvider() { return new Object[][] { {"user1", "pass1", true}, {"user2", "pass2", true}, {"invalid", "wrong", false} }; }
@Test(dataProvider = "loginData") public void testLogin(String username, String password, boolean shouldPass) { driver.get("https://example.com/login"); driver.findElement(By.id("username")).sendKeys(username); driver.findElement(By.id("password")).sendKeys(password); driver.findElement(By.id("login")).click();
if (shouldPass) { Assert.assertTrue(driver.getCurrentUrl().contains("/dashboard")); } else { Assert.assertTrue(driver.findElement(By.className("error")).isDisplayed()); } }
@AfterMethod public void teardown() { driver.quit(); }}TestNG XML Suite
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd"><suite name="Regression Suite" parallel="methods" thread-count="3"> <test name="Login Tests"> <classes> <class name="tests.LoginTest"/> <class name="tests.RegistrationTest"/> </classes> </test>
<test name="Search Tests"> <classes> <class name="tests.SearchTest"/> </classes> </test></suite>JUnit 5 Setup
Maven Dependencies
<dependencies> <dependency> <groupId>org.seleniumhq.selenium</groupId> <artifactId>selenium-java</artifactId> <version>4.18.1</version> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>5.10.2</version> <scope>test</scope> </dependency></dependencies>Basic JUnit 5 Test
import org.openqa.selenium.WebDriver;import org.openqa.selenium.chrome.ChromeDriver;import org.openqa.selenium.By;import org.junit.jupiter.api.*;import static org.junit.jupiter.api.Assertions.*;
class LoginTest { private WebDriver driver;
@BeforeAll static void setupAll() { // Runs once before all tests System.out.println("Setting up test class"); }
@BeforeEach void setup() { // Runs before each test driver = new ChromeDriver(); driver.manage().window().maximize(); }
@Test @DisplayName("Valid user can login successfully") void testValidLogin() { driver.get("https://example.com/login"); driver.findElement(By.id("username")).sendKeys("validuser"); driver.findElement(By.id("password")).sendKeys("validpass"); driver.findElement(By.id("login")).click();
assertTrue(driver.getCurrentUrl().contains("/dashboard"), "Should redirect to dashboard"); }
@Test @DisplayName("Invalid credentials show error") void testInvalidLogin() { driver.get("https://example.com/login"); driver.findElement(By.id("username")).sendKeys("invalid"); driver.findElement(By.id("password")).sendKeys("wrong"); driver.findElement(By.id("login")).click();
String error = driver.findElement(By.className("error")).getText(); assertEquals("Invalid credentials", error); }
@AfterEach void teardown() { if (driver != null) { driver.quit(); } }
@AfterAll static void teardownAll() { System.out.println("Tests completed"); }}JUnit 5 Annotations Reference
| Annotation | Description |
|---|---|
@BeforeAll | Before all tests (static) |
@BeforeEach | Before each test |
@Test | Marks test method |
@AfterEach | After each test |
@AfterAll | After all tests (static) |
@DisplayName | Custom test name |
@Disabled | Skip test |
JUnit 5 Parameterized Tests
import org.junit.jupiter.params.ParameterizedTest;import org.junit.jupiter.params.provider.*;
class ParameterizedLoginTest { private WebDriver driver;
@BeforeEach void setup() { driver = new ChromeDriver(); }
@ParameterizedTest @CsvSource({ "user1, pass1, true", "user2, pass2, true", "invalid, wrong, false" }) void testLogin(String username, String password, boolean shouldPass) { driver.get("https://example.com/login"); driver.findElement(By.id("username")).sendKeys(username); driver.findElement(By.id("password")).sendKeys(password); driver.findElement(By.id("login")).click();
if (shouldPass) { assertTrue(driver.getCurrentUrl().contains("/dashboard")); } else { assertTrue(driver.findElement(By.className("error")).isDisplayed()); } }
@ParameterizedTest @ValueSource(strings = {"chrome", "firefox", "edge"}) void testBrowsers(String browser) { // Test across browsers }
@AfterEach void teardown() { driver.quit(); }}Base Test Class Pattern
// For TestNGpublic class BaseTest { protected WebDriver driver; protected WebDriverWait wait;
@BeforeMethod public void setup() { driver = DriverFactory.getDriver(); wait = new WebDriverWait(driver, Duration.ofSeconds(10)); driver.manage().window().maximize(); }
@AfterMethod public void teardown(ITestResult result) { if (result.getStatus() == ITestResult.FAILURE) { takeScreenshot(result.getName()); } DriverFactory.quitDriver(); }
protected void takeScreenshot(String name) { // Screenshot logic }}
// Tests extend BaseTestpublic class LoginTest extends BaseTest { @Test public void testLogin() { driver.get("https://example.com"); // Test logic using inherited driver }}TestNG vs JUnit 5
| Feature | TestNG | JUnit 5 |
|---|---|---|
| Parallel execution | Built-in XML config | Jupiter parallel config |
| Data providers | @DataProvider | @ParameterizedTest |
| Test groups | @Test(groups="") | @Tag |
| Dependencies | dependsOnMethods | @Order |
| Suite files | XML-based | Programmatic |
| Reporting | Built-in HTML | Requires plugins |
Next Steps
- Page Object Model - Structure your tests
- Parallel Execution - Run tests concurrently
- Data-Driven Testing - External test data
- pytest Integration - Python framework comparison
Copy-paste recipes for this topic
- Soft Assertions: Check Several Things, Report All Failures
Verify multiple fields on one page and report every mismatch at once instead of stopping at the first.