These Selenium interview questions for 3 years of experience are for mid-level automation engineers (about 1–3 years). The focus shifts to framework design: Page Object Model and Page Factory, advanced locators and Shadow DOM, complex widgets, browser options and headless runs, singleton drivers, soft assertions, configuration per environment, TestNG and Extent integration, Maven, logging and data-driven tests.
Planning your learning? Follow the Selenium automation testing roadmap.
Page Object Model & Framework Design
1. Explain Page Object Model with a complete code example.
POM is a design pattern that creates an abstraction layer between tests and UI. Each page = one Java class.
// LoginPage.java — Page Object class
public class LoginPage {
private WebDriver driver;
@FindBy(id="username") private WebElement usernameField;
@FindBy(id="password") private WebElement passwordField;
@FindBy(id="loginBtn") private WebElement loginButton;
@FindBy(css=".error-msg") private WebElement errorMessage;
public LoginPage(WebDriver driver) {
this.driver = driver;
PageFactory.initElements(driver, this); // Initialize @FindBy elements
}
public void enterUsername(String user) { usernameField.clear(); usernameField.sendKeys(user); }
public void enterPassword(String pass) { passwordField.clear(); passwordField.sendKeys(pass); }
public DashboardPage clickLogin() { loginButton.click(); return new DashboardPage(driver); }
public String getErrorMessage() { return errorMessage.getText(); }
public DashboardPage login(String user, String pass) { enterUsername(user); enterPassword(pass); return clickLogin(); }
}
// LoginTest.java — Test class
LoginPage lp = new LoginPage(driver);DashboardPage dp = lp.login("admin", "pass123");Assert.assertTrue(dp.isLoaded());
2. What are @FindBy, @FindBys, @FindAll annotations in Page Factory?
Page Factory annotations provide different strategies for locating elements.
- @FindBy: Locates element using a SINGLE locator strategy. Most commonly used.
@FindBy(id="username") WebElement usernameField;
@FindBy(xpath="//input[@type=\"text\"]") WebElement searchBox;
- @FindBys: Uses AND logic — element must match ALL specified conditions.
@FindBys({@FindBy(tagName="input"), @FindBy(className="required")})
WebElement requiredInput; // Must be both an input AND have class 'required'
- @FindAll: Uses OR logic — element matches ANY of the conditions.
@FindAll({@FindBy(id="btn1"), @FindBy(id="btn2")})
List<WebElement> buttons; // Elements matching EITHER id
- @CacheLookup: Caches element after first lookup — do NOT use for dynamic/stale elements.
@FindBy(id="header") @CacheLookup WebElement header; // Found once, cached
- Page Factory initializes all @FindBy elements lazily — actual findElement() called only when element is used
3. What is a BasePage class? What should it contain?
BasePage is the parent class all Page Object classes inherit from. Contains reusable helper methods.
protected WebDriver driver;protected WebDriverWait wait;
Common methods in BasePage
- clickElement(By locator): wait for clickability then click
- typeText(By locator, String text): clear then sendKeys
- getText(By locator):
wait for visibility then getText() - isElementVisible(By locator): return boolean without throwing exception
- waitForElement(By locator): explicit wait for visibility
- selectDropdown(By locator, String text): Select class wrapper
- scrollToElement(WebElement el): JS scrollIntoView
- takeScreenshot(): TakesScreenshot wrapper
- acceptAlert(), dismissAlert(): Alert handling wrappers
- Benefits: DRY (Don't Repeat Yourself), consistent waits, centralized element interaction, easier debugging
Advanced Locator Strategies
4. What are Relative Locators in Selenium 4? Give examples.
Relative Locators (also called Friendly Locators) allow finding elements relative to other elements' positions on the page.
- Import: import static org.openqa.selenium.support.locators.RelativeLocator.*;
- above(element): Find element directly above another
WebElement label = driver.findElement(with(By.tagName("label")).above(passwordField));
- below(element): Find element directly below another
WebElement hint = driver.findElement(with(By.tagName("span")).below(usernameField));
- toLeftOf(element): Find element to the left of another
WebElement prevBtn = driver.findElement(with(By.tagName("button")).toLeftOf(nextButton));
- toRightOf(element): Find element to the right of another
WebElement price = driver.findElement(with(By.tagName("span")).toRightOf(productLabel));
- near(element):
Find element within 50px proximity (default)
WebElement input = driver.findElement(with(By.tagName("input")).near(label));
Chaining: driver.findElement(with(By.tagName("input")).below(label).toRightOf(sidebar));
- Use case: When elements don't have IDs/stable attributes but their position relative to labelled elements is stable
5. How do you create dynamic XPath for table rows and cells?
Dynamic locators are needed when rows and cells change based on data.
- Find row by cell text: //tr[td[text()="John Doe']] — find <tr> that contains a <td> with 'John Doe"
- Get specific cell in a row: //tr[td[text()='John Doe']]/td[3] — get 3rd cell of that row
- Dynamic row number:
String xpath = String.format("//tr[%d]/td[2]", rowNumber);
Find cell by column header value
int colIndex = // find index of header 'Price'
String xpath = "//tbody/tr["+rowIndex+"]/td["+colIndex+']';
Parameterized XPath helper method
public WebElement getCell(int row, int col) {
return driver.findElement(By.xpath("//table//tr["+row+"]/td["+col+']'));
}
- Find all rows in table body: //tbody/tr
Total row count: driver.findElements(By.xpath("//tbody/tr")).size()
6. How do you locate elements inside Shadow DOM?
Shadow DOM is encapsulated DOM that standard Selenium locators cannot penetrate.
- What is Shadow DOM: Web Components use Shadow DOM to encapsulate HTML/CSS from the main document
- Standard findElement() cannot find elements inside Shadow DOM — returns NoSuchElementException
Solution 1 — JavaScript (most reliable):
WebElement shadowHost = driver.findElement(By.cssSelector("my-component"));
SearchContext shadowRoot = shadowHost.getShadowRoot(); // Selenium 4
WebElement innerEl = shadowRoot.findElement(By.cssSelector("input"));
- Solution 2 — JavaScript for older Selenium:
WebElement shadowRoot = (WebElement) js.executeScript("return arguments[0].shadowRoot", shadowHost);
WebElement inner = (WebElement) js.executeScript("return arguments[0].querySelector(\"input\")", shadowRoot);
- Nested Shadow DOM: Must expand one shadow root at a time
- Common in: Chrome settings pages, Polymer/Lit web components, Salesforce Lightning
Handling Complex UI Elements
7. How do you handle date pickers in Selenium?
Date pickers vary widely in implementation — each requires a different approach.
- Simple text input:
element.clear(); element.sendKeys("12/25/2024"); // Direct input HTML date input (type=date): element.sendKeys("2024-12-25"); // ISO format- Calendar widget approach: Click on calendar, navigate months, click on date
clickNextMonth() until desired month is shown, then click date cell
- JS approach:
js.executeScript("arguments[0].value=\"2024-12-25\"", dateInput); jQuery date picker: js.executeScript("$(\"#datepicker\").datepicker(\"setDate\", \"12/25/2024\");")- Key challenge: Calendar navigation — store expected month/year, compare with displayed, click next/prev
- Tip: Always verify the set date is correct by reading back the value attribute
8. How do you handle auto-suggest (typeahead) dropdowns?
Typeahead dropdowns show suggestions as you type. They are NOT standard <select> elements.
1. Clear the field and type the search text: searchBox.clear(); searchBox.sendKeys("Hyd");
2. Wait for suggestions to appear
wait.until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector(".suggestions li")));
3. Get all suggestions: List<WebElement> suggestions = driver.findElements(By.cssSelector(".suggestions li"));
4. Click the matching suggestion
for(WebElement s : suggestions) { if(s.getText().equals("Hyderabad")) { s.click(); break; } }
- 5. Verify selection: Assert.assertEquals(searchBox.getAttribute("value"), "Hyderabad");
- Common issues: Suggestions load slowly — add explicit wait before accessing list
- Some typeaheads use keyboard navigation:
sendKeys(Keys.ARROW_DOWN) then Keys.ENTER
9. How do you handle dynamic tables — reading all rows, getting specific cell values?
Dynamic tables require looping and indexing strategies.
Get all rows: List<WebElement> rows = driver.findElements(By.xpath("//table/tbody/tr"));
Get headers: List<WebElement> headers = driver.findElements(By.xpath("//table/thead/tr/th"));
Read entire table into 2D list
List<List<String>> tableData = new ArrayList<>();
for(WebElement row : rows) {
List<String> rowData = new ArrayList<>();
for(WebElement cell : row.findElements(By.tagName("td"))) {
rowData.add(cell.getText());
}
tableData.add(rowData);
}
- Find row by column value:
//tr[td[text()='John']] - Get column index by header text: loop through headers, find index where getText() equals header name
- Sort verification: Extract column values, copy to List, sort with Collections.sort(), compare with original
10. How do you handle infinite scroll and pagination?
Infinite scroll and pagination both require scrolling/clicking strategies to load more content.
INFINITE SCROLL
int previousHeight = 0;
while(true) {
js.executeScript("window.scrollTo(0, document.body.scrollHeight);");
Thread.sleep(2000); // Wait for content to load
int newHeight = ((Long) js.executeScript("return document.body.scrollHeight")).intValue();
if(newHeight == previousHeight) break; // No more content
previousHeight = newHeight;
}
PAGINATION
while(true) {
// Process current page data
WebElement nextBtn = driver.findElement(By.cssSelector(".next-page"));
if(!nextBtn.isEnabled()) break; // Last page reached
nextBtn.click();
wait.until(ExpectedConditions.stalenessOf(firstRowElement)); // Wait for new page
}
- Collect data from all pages: accumulate into a master List across each iteration
Browser Options & Capabilities
11. How do you configure ChromeOptions in Selenium? List all important options.
ChromeOptions allows customizing Chrome browser behavior before launching.
ChromeOptions options = new ChromeOptions();
COMMON OPTIONS
options.addArguments("--headless=new"); // Headless mode (new flag for Chrome 112+)
options.addArguments("--incognito"); // Private/incognito mode
options.addArguments("--disable-gpu"); // Required for headless on some systems
options.addArguments("--no-sandbox"); // Required in Linux/Docker environments
options.addArguments("--window-size=1920,1080"); // Set window size in headless
options.addArguments("--disable-notifications"); // Block browser notifications
options.addArguments("--disable-popup-blocking"); // Allow popups
options.addArguments("--start-maximized"); // Start maximized
options.addArguments("--disable-extensions"); // Disable extensions
options.addArguments("--lang=en-US"); // Set browser language
DOWNLOAD SETTINGS
- Map<String,Object> prefs = new HashMap<>();
prefs.put("download.default_directory", "/path/to/downloads");prefs.put("download.prompt_for_download", false);
options.setExperimentalOption("prefs", prefs);
ACCEPT SSL CERTIFICATES
options.setAcceptInsecureCerts(true);
PROXY
Proxy proxy = new Proxy(); proxy.setHttpProxy("proxy-host:8080");
options.setProxy(proxy);
12. How do you run Selenium tests in headless mode? What are its limitations?
Headless mode runs the browser without a visible UI — essential for CI/CD environments.
Chrome Headless: options.addArguments("--headless=new"); // Use 'new' for Chrome 112+
Old flag (still works): options.addArguments("--headless");
- Must also set: --disable-gpu, --no-sandbox, --window-size=1920,1080
- Firefox Headless:
FirefoxOptions ffOptions = new FirefoxOptions(); ffOptions.addArguments("-headless");
LIMITATIONS OF HEADLESS
- Some websites detect headless and block automation (use stealth plugins to bypass)
- Screenshots may differ from headed mode due to GPU rendering differences
- Certain CSS animations, canvas, and WebGL features may behave differently
- Pop-up/modal handling can be inconsistent
- Extensions cannot be loaded in headless mode
- Performance metrics may differ slightly from real browser
ADVANTAGE: 30-40% faster execution, no display server needed (CI), lower resource usage
File Download & @CacheLookup
13. How do you handle file download in Selenium? How do you verify a file was downloaded?
File download cannot be directly triggered by Selenium — the browser handles it. We configure browser settings to auto-download to a known folder, then verify.
- CHROME — auto-download without dialog:
HashMap<String, Object> prefs = new HashMap<>();
prefs.put("download.default_directory", System.getProperty("user.dir") + "/downloads");
prefs.put("download.prompt_for_download", false); // no save dialog
prefs.put("download.directory_upgrade", true);
prefs.put("plugins.always_open_pdf_externally", true); // download PDF instead of preview
prefs.put("safebrowsing.enabled", true);
ChromeOptions options = new ChromeOptions();
options.setExperimentalOption("prefs", prefs);
WebDriver driver = new ChromeDriver(options);
- FIREFOX — set download folder via profile:
FirefoxProfile profile = new FirefoxProfile();
profile.setPreference("browser.download.folderList", 2);
profile.setPreference("browser.download.dir", "/path/to/downloads");
profile.setPreference("browser.helperApps.neverAsk.saveToDisk",
"application/pdf,application/zip,text/csv,application/octet-stream");
profile.setPreference("browser.download.manager.showWhenStarting", false);
FirefoxOptions options = new FirefoxOptions();
options.setProfile(profile);
WebDriver driver = new FirefoxDriver(options);
CLICK DOWNLOAD LINK / BUTTON
driver.findElement(By.id("downloadBtn")).click();
// Browser saves file automatically to configured folder
VERIFY FILE DOWNLOADED — wait + check file exists
public boolean waitForFileDownload(String downloadDir, String fileName, int timeoutSecs) {
File file = new File(downloadDir + '/' + fileName);
FluentWait<File> wait = new FluentWait<>(file)
.withTimeout(Duration.ofSeconds(timeoutSecs))
.pollingEvery(Duration.ofSeconds(1));
return wait.until(f -> f.exists() && f.length() > 0
&& !f.getName().endsWith(".crdownload")); // .crdownload = Chrome in-progress
}
boolean downloaded = waitForFileDownload(downloadDir, "report.pdf", 30);
Assert.assertTrue(downloaded, "File was not downloaded within timeout");
VERIFY FILE CONTENT (CSV example)
List<String> lines = Files.readAllLines(Path.of(downloadDir + "/data.csv"));
Assert.assertEquals(lines.get(0), "Name,Age,Email"); // verify header row
Assert.assertTrue(lines.size() > 1, "CSV should have data rows");
HEADLESS DOWNLOAD (Chrome headless)
// Headless Chrome needs extra CDP command to allow downloads:
((ChromeDriver) driver).executeCdpCommand("Page.setDownloadBehavior",
Map.of("behavior", "allow", "downloadPath", downloadDir));
14. What is @CacheLookup in Page Factory? When should you use it and when should you avoid it?
@CacheLookup tells Page Factory to find the element ONCE and cache the WebElement reference — instead of re-finding it on every access.
WITHOUT @CacheLookup (default behaviour)
@FindBy(id = "username")
private WebElement usernameField;
// Every time usernameField is accessed → driver.findElement() called fresh
// Slower but always gets fresh DOM reference
WITH @CacheLookup
@CacheLookup
@FindBy(id = "submitBtn")
private WebElement submitButton;
// Element found ONCE on first access → reference cached in memory
// Faster — no repeated DOM lookups
HOW IT WORKS INTERNALLY
Page Factory uses Java Proxy pattern — each field access is intercepted
Without @CacheLookup: intercept → findElement() → return element (every time)
With @CacheLookup: intercept → check cache → if cached return it → else findElement + cache
WHEN TO USE @CacheLookup
Static elements that NEVER change after page load
Header navigation links, footer elements, logo
Elements on pages that don't reload/re-render after interactions
Performance-critical scenarios with many repeated element accesses
WHEN NOT TO USE @CacheLookup (❌ will cause StaleElementReferenceException)
Dynamic elements updated by AJAX / JavaScript after interactions
Elements inside tables or lists that refresh
Elements on Single Page Applications (React, Angular, Vue) that re-render
Any element that might be removed and recreated in the DOM
After any action that causes page reload or DOM refresh
BEST PRACTICE: Use @CacheLookup sparingly — only for truly static elements.
- When in doubt, leave it off — a slightly slower test is better than a flaky one.
EXAMPLE — safe usage
@CacheLookup @FindBy(css = "header .logo") private WebElement logo; // safe — never changes
@CacheLookup @FindBy(id = "pageTitle") private WebElement pageTitle; // safe — static
@FindBy(id = "searchResults") private WebElement results; // no cache — dynamic!
Singleton WebDriver & Soft Assertions
15. What is the Singleton WebDriver pattern? Implement it properly for a test framework.
The Singleton pattern ensures only ONE WebDriver instance exists per thread — avoiding duplicate browsers and resource waste.
PROBLEM WITHOUT SINGLETON
If BaseTest creates driver in @BeforeMethod and multiple page objects also create drivers
→ Multiple browser windows open → tests interfere with each other
BASIC SINGLETON (single-threaded):
public class DriverManager {
private static WebDriver driver;
private DriverManager() {} // prevent instantiation
public static WebDriver getDriver() {
if (driver == null) {
driver = new ChromeDriver();
driver.manage().window().maximize();
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
}
return driver;
}
public static void quitDriver() {
if (driver != null) { driver.quit(); driver = null; }
}
}
THREAD-SAFE SINGLETON with ThreadLocal (for parallel execution)
public class DriverManager {
private static final ThreadLocal<WebDriver> driverThread = new ThreadLocal<>();
public static WebDriver getDriver() { return driverThread.get(); }
public static void initDriver(String browser) {
WebDriver driver = DriverFactory.createDriver(browser);
driver.manage().window().maximize();
driverThread.set(driver);
}
public static void quitDriver() {
if (driverThread.get() != null) {
driverThread.get().quit();
driverThread.remove(); // prevent memory leak!
}
}
}
BASE TEST using DriverManager
public class BaseTest {
@BeforeMethod
@Parameters("browser")
public void setUp(@Optional("chrome") String browser) {
DriverManager.initDriver(browser);
}
@AfterMethod
public void tearDown() { DriverManager.quitDriver(); }
}
PAGE OBJECTS using DriverManager
public class LoginPage {
public void login(String user, String pass) {
DriverManager.getDriver().findElement(By.id("user")).sendKeys(user);
DriverManager.getDriver().findElement(By.id("pass")).sendKeys(pass);
DriverManager.getDriver().findElement(By.id("login")).click();
}
}
16. What are Soft Assertions? How do you use SoftAssert in TestNG and AssertJ with Selenium?
Hard assertions stop test execution on first failure. Soft assertions collect ALL failures and report them together — ideal for form validation tests.
- HARD ASSERT (default) — stops on first failure:
Assert.assertEquals(actual, expected); // if this fails → test stops immediately
Assert.assertTrue(condition); // never reached if above failed
- SOFT ASSERT (TestNG) — continues collecting failures:
SoftAssert softAssert = new SoftAssert();
softAssert.assertEquals(driver.getTitle(), "Dashboard", "Page title mismatch");
softAssert.assertTrue(welcomeMsg.isDisplayed(), "Welcome message not visible");
softAssert.assertEquals(userName.getText(), "Alice", "Username mismatch");
softAssert.assertFalse(errorBanner.isDisplayed(), "Error banner should be hidden");
softAssert.assertAll(); // MANDATORY — reports all failures together
// If any assertion failed → test marked as FAILED with all failure details
IMPORTANT: softAssert.assertAll() MUST be the last line — else failures are swallowed!
- USE SOFT ASSERTS FOR form validation testing:
// Submit form and verify ALL field errors appear
driver.findElement(By.id("submitBtn")).click();
SoftAssert sa = new SoftAssert();
sa.assertEquals(nameError.getText(), "Name is required");
sa.assertEquals(emailError.getText(), "Email is required");
sa.assertEquals(phoneError.getText(), "Phone is required");
sa.assertAll(); // verify ALL 3 errors, not just the first
- ASSERTJ (fluent alternative — works without TestNG):
// Add dependency: org.assertj:assertj-core:3.x
import static org.assertj.core.api.Assertions.*;
assertThat(driver.getTitle()).isEqualTo("Dashboard");
assertThat(element.getText()).contains("Welcome").startsWith("Hello");
assertThat(element.isDisplayed()).as("Button should be visible").isTrue();
ASSERTJ SOFT ASSERTIONS
SoftAssertions softly = new SoftAssertions();
softly.assertThat(titleEl.getText()).isEqualTo("Dashboard");
softly.assertThat(userEl.getText()).isEqualTo("Alice");
softly.assertAll(); // report all failures
17. How do you scroll web pages in Selenium? Explain all approaches.
Selenium provides multiple ways to scroll — each suited for different scenarios.
METHOD 1 — JavaScript window.scrollBy (scroll by pixels)
JavascriptExecutor js = (JavascriptExecutor) driver;
js.executeScript("window.scrollBy(0, 500)"); // scroll down 500px
js.executeScript("window.scrollBy(0, -500)"); // scroll up 500px
js.executeScript("window.scrollBy(500, 0)"); // scroll right 500px
METHOD 2 — JavaScript scrollTo (scroll to absolute position)
js.executeScript("window.scrollTo(0, 0)"); // scroll to top
js.executeScript("window.scrollTo(0, document.body.scrollHeight)"); // scroll to bottom
METHOD 3 — JavaScript scrollIntoView (scroll to element)
WebElement element = driver.findElement(By.id("footer"));
js.executeScript("arguments[0].scrollIntoView(true);", element); // scroll to top of element
js.executeScript("arguments[0].scrollIntoView({behavior:\"smooth\", block:\"center\"});", element);
METHOD 4 — Actions scrollToElement (Selenium 4, W3C standard)
WebElement target = driver.findElement(By.id("targetSection"));
new Actions(driver).scrollToElement(target).perform();
METHOD 5 — Actions scrollByAmount (Selenium 4)
new Actions(driver).scrollByAmount(0, 500).perform(); // scroll down 500px
new Actions(driver).scrollByAmount(0, -300).perform(); // scroll up 300px
METHOD 6 — Keys.PAGE_DOWN / Keys.END (keyboard scrolling)
driver.findElement(By.tagName("body")).sendKeys(Keys.PAGE_DOWN);
driver.findElement(By.tagName("body")).sendKeys(Keys.END); // jump to bottom
driver.findElement(By.tagName("body")).sendKeys(Keys.HOME); // jump to top
METHOD 7 — Scroll inside a specific container/div
WebElement scrollableDiv = driver.findElement(By.id("resultsContainer"));
js.executeScript("arguments[0].scrollTop = arguments[0].scrollHeight", scrollableDiv);
WHEN TO USE WHICH
scrollIntoView — best for clicking elements not in viewport (most common use case)
scrollTo bottom — infinite scroll testing, load more content
Actions scrollToElement — Selenium 4 preferred, W3C compliant
scrollByAmount — controlled incremental scrolling (Actions, Selenium 4)
18. How do you manage test configuration across environments (dev/qa/staging/prod)?
A professional framework reads all environment-specific config from external files — never hardcodes URLs or credentials.
APPROACH 1 — Properties file per environment
// src/test/resources/config/qa.properties
base.url=https://qa.example.com
db.url=jdbc:mysql://qa-db:3306/testdb
implicit.wait=10
browser=chrome
headless=false
// src/test/resources/config/staging.properties
base.url=https://staging.example.com
db.url=jdbc:mysql://staging-db:3306/testdb
implicit.wait=15
browser=chrome
headless=true
- CONFIG READER class:
public class ConfigReader {
private static Properties props = new Properties();
static {
String env = System.getProperty("env", "qa"); // default to qa
String configFile = "config/" + env + ".properties";
try (InputStream is = ConfigReader.class.getClassLoader().getResourceAsStream(configFile)) {
props.load(is);
} catch (IOException e) { throw new RuntimeException("Cannot load config: " + configFile); }
}
public static String getBaseUrl() { return props.getProperty("base.url"); }
public static String getBrowser() { return props.getProperty("browser", "chrome"); }
public static boolean isHeadless() { return Boolean.parseBoolean(props.getProperty("headless", "false")); }
public static int getImplicitWait() { return Integer.parseInt(props.getProperty("implicit.wait", "10")); }
}
RUN WITH SPECIFIC ENV
mvn test -Denv=staging // use staging config
mvn test -Denv=prod -Dbrowser=firefox
APPROACH 2 — Environment variables (safer for credentials)
String baseUrl = System.getenv("BASE_URL"); // set in CI/CD secrets
String dbPass = System.getenv("DB_PASSWORD"); // never in properties file!
APPROACH 3 — Combined (properties for non-sensitive, env vars for secrets)
base.url and browser → properties file (safe to commit)
passwords, API keys, tokens → environment variables (never commit!)
.gitignore must include: *.properties files containing credentials
TestNG + Selenium Integration
19. How do you integrate TestNG with Selenium? Show a complete BaseTest with listeners.
TestNG is the most popular test framework for Selenium automation. Proper integration ensures clean setup, teardown, and reporting.
MAVEN DEPENDENCY (pom.xml):
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>7.9.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.github.bonigarcia</groupId>
<artifactId>webdrivermanager</artifactId>
<version>5.7.0</version>
</dependency>
BASE TEST CLASS
public class BaseTest {
protected WebDriver driver;
@Parameters({"browser", "env"})
@BeforeMethod
public void setUp(@Optional("chrome") String browser, @Optional("qa") String env) {
System.setProperty("env", env);
driver = DriverFactory.createDriver(browser, ConfigReader.isHeadless());
driver.manage().window().maximize();
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
driver.get(ConfigReader.getBaseUrl());
}
@AfterMethod(alwaysRun = true)
public void tearDown(ITestResult result) {
if (result.getStatus() == ITestResult.FAILURE) {
takeScreenshot(result.getName()); // screenshot on failure
}
if (driver != null) driver.quit();
}
public void takeScreenshot(String testName) {
File src = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
String path = "screenshots/" + testName + '_' + System.currentTimeMillis() + ".png";
try { FileUtils.copyFile(src, new File(path)); }
catch (IOException e) { e.printStackTrace(); }
}
}
- TESTNG LISTENER — for ExtentReports integration:
public class TestListener implements ITestListener {
@Override public void onTestFailure(ITestResult result) {
System.out.println("FAILED: " + result.getName());
// Capture screenshot and add to report
}
@Override public void onTestSuccess(ITestResult result) {
System.out.println("PASSED: " + result.getName());
}
@Override public void onTestStart(ITestResult result) {
System.out.println("STARTED: " + result.getName());
}
}
REGISTER LISTENER in testng.xml
<suite name='Selenium Suite'>
<listeners>
<listener class-name='listeners.TestListener'/>
</listeners>
<test name='Regression'>
<parameter name='browser' value='chrome'/>
<parameter name='env' value='qa'/>
<classes><class name='tests.LoginTest'/></classes>
</test>
</suite>
20. How do you set up ExtentReports with Selenium and TestNG? Show complete integration.
ExtentReports generates beautiful HTML test reports with screenshots, logs, and test status — essential for professional frameworks.
MAVEN DEPENDENCY
<dependency>
<groupId>com.aventstack</groupId>
<artifactId>extentreports</artifactId>
<version>5.1.1</version>
</dependency>
- EXTENT MANAGER — singleton report setup:
public class ExtentManager {
private static ExtentReports extent;
private static final ThreadLocal<ExtentTest> test = new ThreadLocal<>();
public static ExtentReports getInstance() {
if (extent == null) {
ExtentSparkReporter reporter = new ExtentSparkReporter("reports/ExtentReport.html");
reporter.config().setDocumentTitle("Selenium Test Report");
reporter.config().setReportName("Automation Results");
reporter.config().setTheme(Theme.DARK);
extent = new ExtentReports();
extent.attachReporter(reporter);
extent.setSystemInfo("OS", System.getProperty("os.name"));
extent.setSystemInfo("Browser", "Chrome");
extent.setSystemInfo("Environment", ConfigReader.getEnv());
}
return extent;
}
public static ExtentTest getTest() { return test.get(); }
public static void setTest(ExtentTest t) { test.set(t); }
}
- LISTENER — create test node and handle pass/fail/skip:
public class ExtentReportListener implements ITestListener {
@Override
public void onStart(ITestContext context) {
ExtentManager.getInstance(); // initialise report
}
@Override
public void onTestStart(ITestResult result) {
ExtentTest test = ExtentManager.getInstance()
.createTest(result.getMethod().getDescription(), result.getName());
ExtentManager.setTest(test);
}
@Override
public void onTestSuccess(ITestResult result) {
ExtentManager.getTest().pass("Test Passed");
}
@Override
public void onTestFailure(ITestResult result) {
ExtentManager.getTest().fail(result.getThrowable());
// Attach screenshot
String screenPath = captureScreenshot(result.getName());
try { ExtentManager.getTest().addScreenCaptureFromPath(screenPath); }
catch (Exception e) { e.printStackTrace(); }
}
@Override
public void onTestSkipped(ITestResult result) {
ExtentManager.getTest().skip(result.getThrowable());
}
@Override
public void onFinish(ITestContext context) {
ExtentManager.getInstance().flush(); // write HTML report to disk
}
}
LOG TO REPORT from test/page methods
ExtentManager.getTest().info("Clicked login button");
ExtentManager.getTest().pass("Login successful");
ExtentManager.getTest().fail('Button not found: ' + e.getMessage());
ExtentManager.getTest().log(Status.INFO, "Navigating to " + url);
21. How do you implement retry for failed Selenium tests using IRetryAnalyzer?
Flaky tests that fail intermittently due to network delays or timing issues can be automatically retried using TestNG's IRetryAnalyzer.
STEP 1 — Create RetryAnalyzer
public class RetryAnalyzer implements IRetryAnalyzer {
private int retryCount = 0;
private static final int MAX_RETRY_COUNT = 2;
@Override
public boolean retry(ITestResult result) {
if (!result.isSuccess() && retryCount < MAX_RETRY_COUNT) {
retryCount++;
System.out.println("[RETRY] " + result.getName() + " attempt " + retryCount);
return true; // true = retry this test
}
return false; // false = give up, mark as failed
}
}
STEP 2A — Apply to specific test only
@Test(retryAnalyzer = RetryAnalyzer.class)
public void testFlakyFeature() { ... }
STEP 2B — Apply to ALL tests via IAnnotationTransformer (recommended)
public class RetryTransformer implements IAnnotationTransformer {
@Override
public void transform(ITestAnnotation annotation, Class testClass,
Constructor testConstructor, Method testMethod) {
annotation.setRetryAnalyzer(RetryAnalyzer.class);
}
}
STEP 3 — Register in testng.xml
<listeners>
<listener class-name='listeners.RetryTransformer'/>
<listener class-name='listeners.ExtentReportListener'/>
</listeners>
- RETRY WITH DRIVER REINIT — restart browser on retry:
@Override
public boolean retry(ITestResult result) {
if (retryCount < MAX_RETRY_COUNT) {
retryCount++;
DriverManager.quitDriver(); // close failed browser
DriverManager.initDriver("chrome"); // fresh browser for retry
return true;
}
return false;
}
IMPORTANT NOTES
Max retry = 2 means up to 3 total attempts (1 original + 2 retries)
If test passes on retry → TestNG marks PASSED
Use retry only for genuinely flaky tests — not to hide real bugs
Log all retries to ExtentReports for visibility
Maven for Selenium & Log4j2 Logging
22. Show a complete pom.xml for a Selenium + TestNG framework with all essential dependencies and plugins.
A well-structured pom.xml is the backbone of every professional Selenium Maven project.
- <project>
<groupId>com.example</groupId>
<artifactId>selenium-framework</artifactId>
<version>1.0.0</version>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<selenium.version>4.18.1</selenium.version>
<testng.version>7.9.0</testng.version>
<browser>chrome</browser> <!-- default, override with -Dbrowser=firefox -->
<env>qa</env> <!-- default, override with -Denv=staging -->
</properties>
<dependencies>
<!-- Selenium -->
<dependency><groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId><version>${selenium.version}</version></dependency>
<!-- WebDriverManager -->
<dependency><groupId>io.github.bonigarcia</groupId>
<artifactId>webdrivermanager</artifactId><version>5.7.0</version></dependency>
<!-- TestNG -->
<dependency><groupId>org.testng</groupId>
<artifactId>testng</artifactId><version>${testng.version}</version><scope>test</scope></dependency>
<!-- ExtentReports -->
<dependency><groupId>com.aventstack</groupId>
<artifactId>extentreports</artifactId><version>5.1.1</version></dependency>
<!-- Apache POI (Excel) -->
<dependency><groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId><version>5.2.5</version></dependency>
<!-- Log4j2 -->
<dependency><groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId><version>2.23.1</version></dependency>
<!-- REST Assured (for API+UI hybrid) -->
<dependency><groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId><version>5.4.0</version><scope>test</scope></dependency>
</dependencies>
<build><plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.5</version>
<configuration>
<suiteXmlFiles><suiteXmlFile>testng.xml</suiteXmlFile></suiteXmlFiles>
<systemPropertyVariables>
<browser>${browser}</browser>
<env>${env}</env>
</systemPropertyVariables>
</configuration>
</plugin>
</plugins></build>
<!-- Maven Profiles for different test suites -->
<profiles>
<profile><id>smoke</id>
<properties><suiteFile>testng-smoke.xml</suiteFile></properties>
</profile>
<profile><id>regression</id>
<properties><suiteFile>testng-regression.xml</suiteFile></properties>
</profile>
</profiles>
</project>
RUN COMMANDS
mvn clean test // run all tests (default suite)
mvn clean test -Dbrowser=firefox -Denv=staging // override properties
mvn clean test -P smoke // run smoke profile
mvn clean test -DsuiteXmlFile=testng-regression.xml
23. How do you add Log4j2 logging to a Selenium framework? Show configuration and usage.
Logging is essential in automation frameworks to trace test execution, debug failures, and keep audit trails.
MAVEN DEPENDENCY
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.23.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.23.1</version>
</dependency>
- LOG4J2 CONFIG FILE — src/main/resources/log4j2.xml:
<?xml version='1.0' encoding='UTF-8'?>
<Configuration status='WARN'>
<Appenders>
<!-- Console output -->
<Console name='Console' target='SYSTEM_OUT'>
<PatternLayout pattern='%d{HH:mm:ss} [%t] %-5level %logger{36} - %msg%n'/>
</Console>
<!-- Rolling file — new file each day -->
<RollingFile name='FileLogger' fileName='logs/automation.log'
filePattern='logs/automation-%d{yyyy-MM-dd}.log'>
<PatternLayout pattern='%d{yyyy-MM-dd HH:mm:ss} %-5level [%t] %logger - %msg%n'/>
<Policies><TimeBasedTriggeringPolicy/></Policies>
<DefaultRolloverStrategy max='7'/> <!-- keep 7 days of logs -->
</RollingFile>
</Appenders>
<Loggers>
<Root level='INFO'>
<AppenderRef ref='Console'/>
<AppenderRef ref='FileLogger'/>
</Root>
</Loggers>
</Configuration>
USAGE IN CLASSES
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class LoginPage {
private static final Logger log = LogManager.getLogger(LoginPage.class);
public void login(String username, String password) {
log.info("Attempting login with user: {}", username);
driver.findElement(By.id("username")).sendKeys(username);
driver.findElement(By.id("password")).sendKeys(password);
driver.findElement(By.id("loginBtn")).click();
log.debug("Login button clicked");
}
public boolean isLoginSuccessful() {
boolean result = driver.getCurrentUrl().contains("/dashboard");
log.info("Login successful: {}", result);
return result;
}
}
LOG LEVELS (lowest to highest severity)
TRACE — finest detail, method entry/exit
DEBUG — diagnostic info, variable values
INFO — normal operation milestones (test steps)
WARN — something unexpected but test can continue
ERROR — test step failed, exception caught
FATAL — critical failure, framework can't continue
Data-Driven Testing in Selenium
24. How do you implement Data-Driven Testing in Selenium using @DataProvider and Apache POI?
Data-Driven Testing (DDT) separates test logic from test data — run the same test with multiple data sets from Excel, JSON, or CSV.
APPROACH 1 — @DataProvider with hardcoded data
@DataProvider(name = "loginData")
public Object[][] loginData() {
return new Object[][] {
{"admin@test.com", "Admin@123", true, "Dashboard"},
{"user@test.com", "User@123", true, "Home"},
{"wrong@test.com", "wrongPass", false, "Invalid credentials"},
{'', "Admin@123", false, "Email is required"},
};
}
@Test(dataProvider = "loginData")
public void testLogin(String email, String pass, boolean shouldPass, String expected) {
loginPage.login(email, pass);
if (shouldPass) {
Assert.assertTrue(driver.getTitle().contains(expected));
} else {
Assert.assertTrue(errorMsg.getText().contains(expected));
}
}
APPROACH 2 — @DataProvider reading from Excel (Apache POI)
public class ExcelUtils {
public static Object[][] readExcel(String filePath, String sheetName) throws IOException {
FileInputStream fis = new FileInputStream(filePath);
Workbook wb = WorkbookFactory.create(fis);
Sheet sheet = wb.getSheet(sheetName);
int rows = sheet.getLastRowNum();
int cols = sheet.getRow(0).getLastCellNum();
Object[][] data = new Object[rows][cols]; // skip header row 0
for (int r = 1; r <= rows; r++) {
Row row = sheet.getRow(r);
for (int c = 0; c < cols; c++) {
Cell cell = row.getCell(c);
data[r-1][c] = cell == null ? '' : cell.toString();
}
}
wb.close(); fis.close();
return data;
}
}
@DataProvider(name = "excelLoginData")
public Object[][] getExcelData() throws IOException {
return ExcelUtils.readExcel("src/test/resources/testdata/LoginData.xlsx", "LoginSheet");
}
@Test(dataProvider = "excelLoginData")
public void testLoginFromExcel(String email, String pass, String expected) {
loginPage.login(email, pass);
Assert.assertTrue(driver.getTitle().contains(expected));
}
APPROACH 3 — @DataProvider reading from JSON
// Use Jackson ObjectMapper to read JSON test data
ObjectMapper mapper = new ObjectMapper();
List<Map<String, String>> testData = mapper.readValue(
new File("src/test/resources/testdata/loginData.json"),
new TypeReference<List<Map<String, String>>>() {});
Object[][] data = new Object[testData.size()][1];
for (int i = 0; i < testData.size(); i++) { data[i][0] = testData.get(i); }
return data;
PARALLEL DATA PROVIDER
@DataProvider(name = "loginData", parallel = true) // run data sets in parallel!
public Object[][] loginData() { ... }
Quick-Fire Questions
Short answers to revise the night before.
25. What is Page Object Model (POM)?
Design pattern where each web page is represented by a Java class with its locators and methods. Tests use page methods, not locators directly.
26. Why do we use POM?
Makes tests maintainable — if a locator changes, update only the page class. Makes tests readable. Promotes code reuse across multiple tests.
27. What is the advantage of PageFactory?
Elements are initialized lazily (only when first used), cleaner syntax with annotations, and integrates well with POM structure.
28. What is a DriverFactory class?
A utility class that manages WebDriver creation and destruction. Centralizes browser setup so tests don't each create their own driver.
29. What is ThreadLocal in DriverFactory?
ThreadLocal<WebDriver> ensures each thread (parallel test) gets its own separate driver instance, preventing conflicts in parallel execution.
30. What is Data-Driven Testing?
Running the same test case with multiple sets of test data. Eliminates code duplication and increases test coverage.
31. What is @DataProvider in TestNG?
An annotation that supplies multiple sets of data to a @Test method. The test runs once for each row of data returned.
32. What is Apache POI?
A Java library used to read and write Microsoft Excel files (.xlsx). Used in Selenium for data-driven testing from Excel sheets.
33. What is testng.xml?
Configuration file for TestNG that defines test suites, groups, parallel execution settings, and which test classes/methods to run.
34. What is ExtentReports?
A reporting library that generates HTML test execution reports with charts, test steps, screenshots, and pass/fail statistics.
35. What is the difference between extent.flush() and not calling it?
flush() writes buffered report data to the HTML file. If not called, the report file is empty or incomplete.
36. What is a Hybrid Framework?
Combines POM + Data-Driven + Keyword-Driven approaches. Uses page classes for structure, Excel for data, and config files for settings.
37. What is config.properties and why is it used?
A properties file storing environment-specific settings (URL, browser, credentials). Avoids hardcoding values in tests; easy to change environments.
38. How do you run a specific test group using testng.xml?
Use <groups> tag inside testng.xml: <include name="smoke'/> runs only tests annotated with @Test(groups='smoke").
39. What is Selenium Grid?
Distributed test execution system. Hub receives test commands and distributes them to Nodes (machines/browsers) for parallel execution.
40. What is the difference between Hub and Node in Selenium Grid?
Hub is the central controller that receives test requests. Nodes are machines registered to the Hub where tests actually run.
41. What is RemoteWebDriver?
A WebDriver implementation that communicates with a remote machine (Grid Node) instead of a local browser.
42. What is parallel execution in TestNG?
Running multiple tests simultaneously using thread-count in testng.xml. Can be parallel on methods, classes, or tests level.
43. Why do we use ThreadLocal for WebDriver?
To ensure each parallel thread has its own separate WebDriver instance. Without it, multiple threads share one driver and tests interfere with each other.
44. What is BDD?
Behavior Driven Development — writing tests in plain English using Given/When/Then format so business and technical teams share understanding.
45. What is Cucumber?
A BDD framework that connects plain English feature files (Gherkin) to Java step definition methods.
46. What is a Feature file in Cucumber?
A .feature file written in Gherkin language that describes test scenarios in plain English: Feature, Scenario, Given, When, Then.
47. What is a Step Definition?
A Java method annotated with @Given, @When, @Then that implements the plain-English step from the feature file.
48. What is Scenario Outline in Cucumber?
A parameterized scenario that runs multiple times with different data from an Examples table — data-driven BDD testing.
49. What is the @CucumberOptions annotation?
Configuration for the Cucumber test runner: specifies features folder, glue (step definitions package), plugins for reporting.
50. What is headless browser testing?
Running browser tests without a visible browser UI. Faster execution and required in server/CI environments with no display.
51. What is hard assertion vs soft assertion?
Hard assertion (Assert) stops test immediately on failure. Soft assertion (SoftAssert) collects all failures and reports them at assertAll().
52. How do you handle dynamic elements in Selenium?
Use contains(), starts-with() in XPath, use relative locators based on parent/sibling elements, or use explicit waits for dynamic content.
53. What is the difference between parallel='methods' and parallel='tests' in TestNG?
parallel='methods' runs test methods simultaneously. parallel='tests' runs <test> tags in testng.xml simultaneously.
54. What is Gherkin?
Plain-English syntax used to write Cucumber feature files. Keywords: Feature, Scenario, Given, When, Then, And, But, Background.
55. How do you pass browser name as a parameter in TestNG?
Use <parameter name="browser' value='chrome'/> in testng.xml and @Parameters({'browser"}) annotation in the test class.
How to Prepare with 3 Years of Experience
- Prepare a two-minute walkthrough of your framework: structure, driver management, data, reporting and CI.
- Expect live coding: a page object, a DataProvider or a reusable wait helper.
- Read the questions for 5 years of experience: Grid, CI/CD and design patterns usually come up at this level too.