Time to put it all together. We will build a complete end-to-end data-driven test for the Banking Portal that uses ExcelUtils for test data, ConfigReader for configuration, a centralized DataProvider, and proper POM structure. This is what a real framework looks like.
src/
├── test/
│ ├── java/com/practice/
│ │ ├── base/
│ │ │ └── BaseTest.java ← Browser setup + config
│ │ ├── pages/
│ │ │ ├── banking/
│ │ │ │ ├── LoginPage.java ← Login page object
│ │ │ │ ├── DashboardPage.java ← Dashboard page object
│ │ │ │ └── TransferPage.java ← Transfer page object
│ │ │ └── shopping/
│ │ │ ├── ShopHomePage.java ← Shopping home page
│ │ │ └── CatalogPage.java ← Search results page
│ │ ├── data/
│ │ │ └── DataProviders.java ← All DataProviders
│ │ ├── utils/
│ │ │ ├── ExcelUtils.java ← Excel reader
│ │ │ ├── CsvUtils.java ← CSV reader
│ │ │ └── ConfigReader.java ← Config reader
│ │ └── tests/
│ │ ├── banking/
│ │ │ ├── LoginTests.java ← Login data-driven tests
│ │ │ └── TransferTests.java ← Transfer data-driven tests
│ │ └── shopping/
│ │ └── SearchTests.java ← Search data-driven tests
│ └── resources/
│ ├── config.properties ← Configuration
│ └── test-data/
│ ├── login-data.xlsx ← Login test data
│ ├── transfer-data.xlsx ← Transfer test data
│ └── search-terms.csv ← Search test data# src/test/resources/config.properties
base.url=https://www.testerrank.com
browser=chrome
headless=false
implicit.wait=5
explicit.wait=10
screenshot.on.failure=true
default.username=testuser
default.password=password123package com.practice.base;
import com.practice.utils.ConfigReader;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import java.time.Duration;
public class BaseTest {
protected WebDriver driver;
@BeforeMethod
public void setUp() {
String browser = ConfigReader.get(
"browser", "chrome");
boolean headless = ConfigReader.getBoolean(
"headless", false);
driver = createDriver(browser, headless);
driver.manage().window().maximize();
driver.manage().timeouts().implicitlyWait(
Duration.ofSeconds(
ConfigReader.getInt("implicit.wait", 5)
));
}
private WebDriver createDriver(String browser,
boolean headless) {
return switch (browser.toLowerCase()) {
case "firefox" -> new FirefoxDriver();
default -> {
ChromeOptions options = new ChromeOptions();
if (headless) {
options.addArguments("--headless=new");
}
yield new ChromeDriver(options);
}
};
}
protected void navigateTo(String path) {
String baseUrl = ConfigReader.get(
"base.url", "https://www.testerrank.com");
driver.get(baseUrl + path);
}
@AfterMethod
public void tearDown() {
if (driver != null) driver.quit();
}
}package com.practice.pages.banking;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import java.time.Duration;
public class LoginPage {
private final WebDriver driver;
private final WebDriverWait wait;
// Locators
private final By usernameField = By.id("username");
private final By passwordField = By.id("password");
private final By loginButton =
By.cssSelector("button[type='submit']");
private final By errorMessage =
By.cssSelector("[data-testid='error-message']");
public LoginPage(WebDriver driver) {
this.driver = driver;
this.wait = new WebDriverWait(
driver, Duration.ofSeconds(10));
}
public void enterUsername(String username) {
WebElement field = wait.until(
ExpectedConditions.visibilityOfElementLocated(
usernameField));
field.clear();
field.sendKeys(username);
}
public void enterPassword(String password) {
driver.findElement(passwordField).clear();
driver.findElement(passwordField)
.sendKeys(password);
}
public void clickLogin() {
driver.findElement(loginButton).click();
}
public DashboardPage loginAs(String username,
String password) {
enterUsername(username);
enterPassword(password);
clickLogin();
return new DashboardPage(driver);
}
public boolean isErrorDisplayed() {
try {
return wait.until(
ExpectedConditions
.visibilityOfElementLocated(
errorMessage))
.isDisplayed();
} catch (Exception e) {
return false;
}
}
public String getErrorMessage() {
return wait.until(
ExpectedConditions
.visibilityOfElementLocated(errorMessage))
.getText();
}
}package com.practice.data;
import com.practice.utils.ExcelUtils;
import org.testng.annotations.DataProvider;
import java.io.IOException;
public class DataProviders {
private static final String DATA_DIR =
"src/test/resources/test-data/";
@DataProvider(name = "bankingLoginData")
public Object[][] bankingLoginData() throws IOException {
return ExcelUtils.readSheet(
DATA_DIR + "login-data.xlsx",
"BankingLogin");
}
@DataProvider(name = "bankingTransferData")
public Object[][] bankingTransferData()
throws IOException {
return ExcelUtils.readSheet(
DATA_DIR + "transfer-data.xlsx",
"Transfers");
}
}package com.practice.tests.banking;
import com.practice.base.BaseTest;
import com.practice.data.DataProviders;
import com.practice.pages.banking.DashboardPage;
import com.practice.pages.banking.LoginPage;
import org.testng.Assert;
import org.testng.annotations.Test;
public class LoginTests extends BaseTest {
@Test(dataProvider = "bankingLoginData",
dataProviderClass = DataProviders.class,
description = "Verify login with multiple "
+ "credential combinations")
public void testBankingLogin(
String username, String password,
String expectedResult,
String expectedMessage) {
navigateTo("/banking");
LoginPage loginPage = new LoginPage(driver);
loginPage.enterUsername(username);
loginPage.enterPassword(password);
loginPage.clickLogin();
if ("success".equals(expectedResult)) {
DashboardPage dashboard =
new DashboardPage(driver);
Assert.assertTrue(dashboard.isLoaded(),
"Dashboard should load for user: "
+ username);
} else {
Assert.assertTrue(
loginPage.isErrorDisplayed(),
"Error should appear for user: "
+ username);
if (expectedMessage != null
&& !expectedMessage.isEmpty()) {
Assert.assertEquals(
loginPage.getErrorMessage(),
expectedMessage,
"Error message mismatch for: "
+ username);
}
}
}
}<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="Banking Regression Suite">
<test name="Banking Login Tests">
<classes>
<class name=
"com.practice.tests.banking.LoginTests" />
</classes>
</test>
</suite>
<!-- Run: mvn test -DsuiteXmlFile=testng.xml -->
<!-- CI/CD: mvn test -DsuiteXmlFile=testng.xml
-Dbase.url=https://staging.example.com
-Dheadless=true -->This is the exact architecture interviewers expect you to describe. When they ask "Walk me through your framework," follow this flow: config.properties -> ConfigReader -> BaseTest -> Page Objects -> DataProviders -> ExcelUtils -> Test Class -> testng.xml. Cover each layer in 1-2 sentences.
Key Point: A complete data-driven framework has 6 layers: config, BaseTest, page objects, DataProviders, utils, and test classes.