Hardcoding URLs, credentials, and timeouts in your test code is the fastest way to create a maintenance nightmare. What happens when you need to run the same tests against staging instead of dev? Edit 50 test files? No. You use a config file and change one value.
# src/test/resources/config.properties
# Application
base.url=https://testerrank.com
app.name=TesterRank Practice Portals
# Browser
browser=chrome
headless=false
# Timeouts (in seconds)
implicit.wait=5
explicit.wait=10
page.load.timeout=30
# Test Credentials (for practice portals only)
default.username=testuser
default.password=password123
# Screenshots
screenshot.on.failure=true
screenshot.dir=screenshots
# Retry
retry.count=1
# Logging
log.level=INFOConfigReader loads the properties file once and provides methods to read values. The key feature: it checks system properties first, then falls back to the file. This means CI/CD pipelines can override any value without editing files.
package com.testerrank.utils;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;
public class ConfigReader {
private static Properties properties;
private static final String DEFAULT_CONFIG =
"src/test/resources/config.properties";
// Load once during suite initialization
public static void init() {
if (properties != null) return; // Already loaded
properties = new Properties();
String configFile = System.getProperty(
"config.file", DEFAULT_CONFIG);
try (FileInputStream fis =
new FileInputStream(configFile)) {
properties.load(fis);
} catch (IOException e) {
throw new RuntimeException(
"Cannot load config file: " + configFile, e);
}
}
// Get string value: system property > config file > default
public static String get(String key, String defaultValue) {
// System property always wins (for CI/CD overrides)
String sysValue = System.getProperty(key);
if (sysValue != null) return sysValue;
// Then check config file
init(); // Ensure loaded
return properties.getProperty(key, defaultValue);
}
public static String get(String key) {
return get(key, null);
}
public static int getInt(String key, int defaultValue) {
String value = get(key);
if (value == null) return defaultValue;
return Integer.parseInt(value.trim());
}
public static boolean getBoolean(String key,
boolean defaultValue) {
String value = get(key);
if (value == null) return defaultValue;
return Boolean.parseBoolean(value.trim());
}
}This is where ConfigReader becomes essential. In your Jenkins or GitHub Actions pipeline, you override values via system properties. Zero file changes required.
# Local development — uses config.properties defaults
mvn test
# CI staging environment — overrides via -D flags
mvn test \
-Dbase.url=https://staging.testerrank.com \
-Dbrowser=chrome \
-Dheadless=true \
-Dscreenshot.on.failure=true
# CI production smoke test
mvn test \
-Dbase.url=https://testerrank.com \
-Dgroups=smoke \
-Dheadless=true
# Run with a completely different config file
mvn test -Dconfig.file=src/test/resources/config-staging.propertiesQ: How do you handle different environments (dev, staging, production) in your framework?
A: We use a ConfigReader utility that follows a priority chain: system properties first, then config.properties file, then default values. This means the test code never changes between environments. For local development, config.properties has dev settings. In CI, the pipeline passes -Dbase.url=https://staging.example.com as a system property, which ConfigReader picks up automatically. We also support environment-specific config files that can be loaded via -Dconfig.file=config-staging.properties. The key principle is that test code is environment-agnostic.
Never put real production credentials in config.properties — that file gets committed to Git. Use environment variables or CI/CD secret stores for real credentials. The config file should only contain defaults for local development against practice portals.
The priority chain is: system property (-D flag) > config file > hardcoded default. This is the exact same pattern used by Spring Boot. Mentioning this in interviews shows you understand enterprise Java patterns.
Key Point: ConfigReader reads from system properties first, config file second, defaults last. This makes your tests environment-agnostic — same code runs on dev, staging, and prod.