Let us put everything together. Here is a complete, production-style test suite for the Banking Portal that uses all the TestNG features we covered — annotations, assertions, groups, DataProviders, and proper structure.
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.testng.annotations.*;
import java.time.Duration;
public class BaseTest {
private static ThreadLocal<WebDriver> driverThread =
new ThreadLocal<>();
private static ThreadLocal<WebDriverWait> waitThread =
new ThreadLocal<>();
@BeforeMethod
@Parameters({"baseUrl"})
public void setUp(@Optional("https://www.testerrank.com") String baseUrl) {
ChromeOptions options = new ChromeOptions();
options.addArguments("--start-maximized");
WebDriver driver = new ChromeDriver(options);
driverThread.set(driver);
waitThread.set(new WebDriverWait(driver, Duration.ofSeconds(10)));
driver.get(baseUrl + "/banking");
}
public WebDriver getDriver() {
return driverThread.get();
}
public WebDriverWait getWait() {
return waitThread.get();
}
@AfterMethod
public void tearDown() {
WebDriver driver = driverThread.get();
if (driver != null) {
driver.quit();
driverThread.remove();
waitThread.remove();
}
}
}import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.testng.Assert;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import org.testng.asserts.SoftAssert;
public class BankingLoginTest extends BaseTest {
@Test(priority = 1, groups = {"smoke"},
description = "Verify login page loads correctly")
public void testLoginPageLoads() {
SoftAssert soft = new SoftAssert();
soft.assertTrue(
getDriver().getTitle().contains("Banking"),
"Title should contain Banking");
WebElement usernameField = getDriver()
.findElement(By.id("username"));
soft.assertTrue(usernameField.isDisplayed(),
"Username field should be visible");
WebElement passwordField = getDriver()
.findElement(By.id("password"));
soft.assertTrue(passwordField.isDisplayed(),
"Password field should be visible");
WebElement loginBtn = getDriver()
.findElement(By.cssSelector("button[type='submit']"));
soft.assertTrue(loginBtn.isEnabled(),
"Login button should be enabled");
soft.assertAll();
}
@Test(priority = 2, groups = {"smoke", "regression"},
description = "Verify successful login with valid credentials")
public void testSuccessfulLogin() {
getDriver().findElement(By.id("username")).sendKeys("testuser");
getDriver().findElement(By.id("password")).sendKeys("password123");
getDriver().findElement(
By.cssSelector("button[type='submit']")).click();
getWait().until(
ExpectedConditions.urlContains("/dashboard"));
Assert.assertTrue(
getDriver().getCurrentUrl().contains("/dashboard"),
"Should redirect to dashboard after login");
}
@DataProvider(name = "invalidLogins")
public Object[][] invalidLoginData() {
return new Object[][] {
{ "invalid", "wrong", "Invalid credentials" },
{ "", "password123", "Username is required" },
{ "testuser", "", "Password is required" },
};
}
@Test(priority = 3, groups = {"regression"},
dataProvider = "invalidLogins",
description = "Verify error messages for invalid login attempts")
public void testInvalidLogin(String username, String password,
String expectedError) {
if (!username.isEmpty()) {
getDriver().findElement(By.id("username"))
.sendKeys(username);
}
if (!password.isEmpty()) {
getDriver().findElement(By.id("password"))
.sendKeys(password);
}
getDriver().findElement(
By.cssSelector("button[type='submit']")).click();
WebElement error = getWait().until(
ExpectedConditions.visibilityOfElementLocated(
By.cssSelector(".error-message")
)
);
Assert.assertTrue(error.getText().contains(expectedError),
"Error should contain: " + expectedError);
}
@Test(priority = 4, groups = {"regression"},
dependsOnMethods = {"testSuccessfulLogin"},
description = "Verify dashboard loads after login")
public void testDashboardAfterLogin() {
// Login first
getDriver().findElement(By.id("username")).sendKeys("testuser");
getDriver().findElement(By.id("password")).sendKeys("password123");
getDriver().findElement(
By.cssSelector("button[type='submit']")).click();
getWait().until(
ExpectedConditions.urlContains("/dashboard"));
// Verify dashboard elements
SoftAssert soft = new SoftAssert();
soft.assertTrue(
getDriver().findElement(By.id("balance")).isDisplayed(),
"Balance should be visible");
soft.assertTrue(
getDriver().findElement(By.id("welcome-message"))
.isDisplayed(),
"Welcome message should be visible");
soft.assertAll();
}
}<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="Banking Suite" parallel="classes" thread-count="2">
<parameter name="baseUrl" value="https://www.testerrank.com" />
<listeners>
<listener class-name="com.autopractice.listeners.TestListener" />
<listener class-name="com.autopractice.listeners.RetryTransformer" />
</listeners>
<test name="Banking Tests">
<groups>
<run>
<include name="smoke" />
<include name="regression" />
</run>
</groups>
<classes>
<class name="com.autopractice.tests.BankingLoginTest" />
</classes>
</test>
</suite>This is a real, production-style test suite. It has a base class with ThreadLocal for thread safety, a test class with groups and DataProviders, and a testng.xml with listeners and parallel execution. This is the kind of code interviewers want to see.
Notice how BaseTest uses @Optional on the baseUrl parameter. This means if testng.xml does not define a "baseUrl" parameter, it defaults to "https://www.testerrank.com". Without @Optional, TestNG throws an error when the parameter is missing.
Key Point: A production TestNG suite has: BaseTest with ThreadLocal, test classes with annotations and DataProviders, and testng.xml with listeners and parallel config.