Test data (login credentials, search terms) changes per test case. Configuration data (base URL, browser, timeout) stays the same across ALL tests. You do not put configuration in CSV or Excel. You put it in a .properties file — Java's built-in key-value format.
# src/test/resources/config.properties
# Application settings
base.url=https://www.testerrank.com
application.name=TesterRank Practice
# Browser settings
browser=chrome
headless=false
window.maximize=true
# Timeouts (in seconds)
implicit.wait=5
explicit.wait=10
page.load.timeout=30
# Test defaults
default.username=testuser
default.password=password123
# Reporting
screenshot.on.failure=true
retry.count=2
# Environments
staging.url=https://staging.testerrank.com
prod.url=https://testerrank.comYou do not want to load the properties file every time you need a value. Load it once, store it in a static field, and read from memory after that. This is the Singleton pattern — one of the most commonly asked design patterns in QA automation interviews.
package com.practice.utils;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;
/**
* Reads configuration from config.properties.
* Loaded once (singleton). System properties override
* file values for CI/CD flexibility.
*/
public class ConfigReader {
private static final Properties properties;
private static final String CONFIG_PATH =
"src/test/resources/config.properties";
// Static initializer — runs once when class is loaded
static {
properties = new Properties();
try (FileInputStream fis =
new FileInputStream(CONFIG_PATH)) {
properties.load(fis);
} catch (IOException e) {
throw new RuntimeException(
"Failed to load " + CONFIG_PATH
+ ": " + e.getMessage());
}
}
/**
* Gets a config value. Checks System property first
* (for CI/CD override), then falls back to file.
*/
public static String get(String key) {
String systemValue = System.getProperty(key);
if (systemValue != null) {
return systemValue;
}
return properties.getProperty(key);
}
/**
* Gets a config value with a default fallback.
*/
public static String get(String key,
String defaultValue) {
String value = get(key);
return (value != null) ? value : defaultValue;
}
/**
* Gets an integer config value.
*/
public static int getInt(String key, int defaultValue) {
String value = get(key);
if (value == null) return defaultValue;
try {
return Integer.parseInt(value);
} catch (NumberFormatException e) {
return defaultValue;
}
}
/**
* Gets a boolean config value.
*/
public static boolean getBoolean(String key,
boolean defaultValue) {
String value = get(key);
if (value == null) return defaultValue;
return Boolean.parseBoolean(value);
}
}public class BaseTest {
protected WebDriver driver;
@BeforeMethod
public void setUp() {
String browser = ConfigReader.get("browser", "chrome");
boolean headless =
ConfigReader.getBoolean("headless", false);
switch (browser.toLowerCase()) {
case "firefox":
FirefoxOptions ffOptions =
new FirefoxOptions();
if (headless) ffOptions.addArguments(
"--headless");
driver = new FirefoxDriver(ffOptions);
break;
case "chrome":
default:
ChromeOptions chromeOptions =
new ChromeOptions();
if (headless) chromeOptions.addArguments(
"--headless=new");
driver = new ChromeDriver(chromeOptions);
break;
}
if (ConfigReader.getBoolean(
"window.maximize", true)) {
driver.manage().window().maximize();
}
int implicitWait =
ConfigReader.getInt("implicit.wait", 5);
driver.manage().timeouts().implicitlyWait(
Duration.ofSeconds(implicitWait));
String baseUrl = ConfigReader.get("base.url",
"https://www.testerrank.com");
driver.get(baseUrl);
}
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();
}
}
}The magic is in the System.getProperty() check. In CI/CD, you pass values via the Maven command line. These override whatever is in the properties file without changing any code.
# Local development — uses config.properties defaults
mvn test
# CI/CD staging — override base URL and run headless
mvn test -Dbase.url=https://staging.testerrank.com -Dheadless=true -Dbrowser=chrome
# CI/CD production — different URL, Firefox
mvn test -Dbase.url=https://testerrank.com -Dheadless=true -Dbrowser=firefoxQ: How do you manage configuration in your automation framework?
A: We use a config.properties file for default settings like base URL, browser, timeouts, and screenshot preferences. A ConfigReader singleton class loads this file once and provides typed getters (getString, getInt, getBoolean). The key feature is that System properties override file values. In CI/CD, we pass -Dbase.url=... -Dheadless=true on the Maven command line, and the framework automatically picks those up without changing any files.
Never put real passwords in config.properties if the file is committed to Git. Use environment variables for sensitive data: System.getenv("TEST_PASSWORD"). The config file should only have non-sensitive defaults.
In interviews, always mention the System property override mechanism. It shows you understand CI/CD integration. Say: "Our ConfigReader checks System.getProperty() first, then falls back to the config file. This way Jenkins can pass different values per environment without code changes."
Key Point: Properties files hold configuration, not test data. System properties override file values for CI/CD.