Answer
Hybrid Framework Architecture
Folder Structure
CODE
selenium-hybrid-framework/
├── src/
│ ├── main/java/com/automateqa/
│ │ ├── config/
│ │ │ └── ConfigReader.java ← reads config.properties
│ │ ├── driver/
│ │ │ └── DriverManager.java ← WebDriver init/quit
│ │ ├── pages/
│ │ │ ├── BasePage.java ← common utilities
│ │ │ ├── LoginPage.java
│ │ │ └── DashboardPage.java
│ │ └── utils/
│ │ ├── ExcelUtil.java ← read test data from Excel
│ │ ├── WaitUtil.java ← explicit waits
│ │ └── ScreenshotUtil.java ← capture screenshots
│ └── test/java/com/automateqa/
│ ├── base/
│ │ └── BaseTest.java ← @BeforeMethod / @AfterMethod
│ ├── tests/
│ │ ├── LoginTest.java
│ │ └── CheckoutTest.java
│ └── listeners/
│ └── TestListener.java ← TestNG listener for reports
├── src/test/resources/
│ ├── testdata/
│ │ └── LoginData.xlsx
│ ├── config/
│ │ ├── config.properties ← browser, baseUrl, timeout
│ │ └── staging.properties
│ └── testng.xml
└── pom.xml
Layer 1 — Configuration
Java
// config/ConfigReader.java
public class ConfigReader {
private static Properties props = new Properties();
static {
try {
String env = System.getProperty("env", "config");
FileInputStream fis = new FileInputStream(
"src/test/resources/config/" + env + ".properties");
props.load(fis);
} catch (IOException e) {
throw new RuntimeException("config.properties not found", e);
}
}
public static String get(String key) {
return System.getProperty(key, props.getProperty(key));
}
}
PROPERTIES
# config/config.properties
browser=chrome
baseUrl=https://staging.automateqa.online
implicitWait=10
explicitWait=15
headless=false
Layer 2 — Driver Management
Java
// driver/DriverManager.java
public class DriverManager {
private static ThreadLocal<WebDriver> driver = new ThreadLocal<>();
public static WebDriver getDriver() {
return driver.get();
}
public static void init() {
String browser = ConfigReader.get("browser");
boolean headless = Boolean.parseBoolean(ConfigReader.get("headless"));
WebDriver d;
switch (browser.toLowerCase()) {
case "firefox":
FirefoxOptions ffOpts = new FirefoxOptions();
if (headless) ffOpts.addArguments("--headless");
d = new FirefoxDriver(ffOpts);
break;
case "edge":
d = new EdgeDriver();
break;
default: // chrome
ChromeOptions opts = new ChromeOptions();
if (headless) opts.addArguments("--headless=new", "--no-sandbox",
"--disable-dev-shm-usage");
d = new ChromeDriver(opts);
}
d.manage().window().maximize();
d.manage().timeouts().implicitlyWait(
Duration.ofSeconds(Long.parseLong(ConfigReader.get("implicitWait"))));
driver.set(d);
}
public static void quit() {
if (driver.get() != null) {
driver.get().quit();
driver.remove();
}
}
}
Layer 3 — BaseTest
Java
// base/BaseTest.java
public class BaseTest {
@BeforeMethod(alwaysRun = true)
public void setUp(Method method) {
DriverManager.init();
DriverManager.getDriver().get(ConfigReader.get("baseUrl"));
LogUtil.info("=== Starting: " + method.getName() + " ===");
}
@AfterMethod(alwaysRun = true)
public void tearDown(ITestResult result) {
if (result.getStatus() == ITestResult.FAILURE) {
ScreenshotUtil.capture(result.getName());
LogUtil.error("TEST FAILED: " + result.getName());
}
DriverManager.quit();
}
}
Layer 4 — Test with Data Provider
Java
// tests/LoginTest.java
public class LoginTest extends BaseTest {
@DataProvider(name = "loginData", parallel = false)
public Object[][] loginData() throws Exception {
return ExcelUtil.readData("src/test/resources/testdata/LoginData.xlsx", "Sheet1");
// Returns: [["valid","alice@t.com","Pass@123","Dashboard"],
// ["invalid","wrong@t.com","bad","Invalid credentials"]]
}
@Test(dataProvider = "loginData", groups = {"smoke", "regression"})
public void testLogin(String scenario, String email, String pass, String expected) {
LogUtil.info("Running: " + scenario);
LoginPage loginPage = new LoginPage(DriverManager.getDriver());
loginPage.login(email, pass);
if (scenario.equals("valid")) {
DashboardPage dash = new DashboardPage(DriverManager.getDriver());
Assert.assertTrue(dash.isWelcomeDisplayed(), "Dashboard not shown");
} else {
Assert.assertEquals(loginPage.getError(), expected);
}
}
}
Layer 5 — Extent Report Listener
Java
// listeners/TestListener.java
public class TestListener implements ITestListener {
private static ExtentReports extent = ExtentManager.getInstance();
private static ThreadLocal<ExtentTest> test = new ThreadLocal<>();
@Override
public void onTestStart(ITestResult result) {
ExtentTest extentTest = extent.createTest(result.getMethod().getMethodName());
test.set(extentTest);
}
@Override
public void onTestSuccess(ITestResult result) {
test.get().pass("Test passed");
}
@Override
public void onTestFailure(ITestResult result) {
test.get().fail(result.getThrowable());
String screenshotPath = ScreenshotUtil.capture(result.getName());
test.get().addScreenCaptureFromPath(screenshotPath);
}
@Override
public void onFinish(ITestContext context) {
extent.flush();
}
}
