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.

Selenium interview questions by experience: Freshers · 1 year · 3 years · 5 years
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
Advertisement

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"));
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.