You have built the framework. Now comes the reality — tests will fail. Not because your code is wrong, but because web applications are dynamic. Elements load late, modals pop up unexpectedly, and what worked yesterday breaks today. Knowing how to debug is as important as knowing how to write tests.
The most common exception. It means Selenium could not find the element. Three possible causes: wrong locator, element not loaded yet, or element is inside an iframe.
// WRONG — element might not be loaded yet
WebElement button = driver.findElement(By.id("submit"));
// CORRECT — wait for it
WebElement button = wait.until(
ExpectedConditions.elementToBeClickable(By.id("submit")));
// If element is in an iframe:
driver.switchTo().frame("payment-frame");
WebElement button = driver.findElement(By.id("pay-btn"));
driver.switchTo().defaultContent(); // Always switch backYou found an element, but by the time you interact with it, the DOM has changed. The element reference is "stale" — it points to an element that no longer exists in the current DOM. Common after page navigations, AJAX updates, or JavaScript rerenders.
// PROBLEM — element becomes stale after page update
WebElement price = driver.findElement(By.id("total"));
catalog.sortBy("Price: Low to High"); // Page rerenders
String text = price.getText(); // STALE! DOM changed
// FIX — re-find the element after DOM changes
catalog.sortBy("Price: Low to High");
WebElement price = wait.until(
ExpectedConditions.visibilityOfElementLocated(
By.id("total")));
String text = price.getText(); // Fresh referenceSelenium tries to click an element but something else is covering it — a modal, a loading spinner, a cookie banner, or a fixed header. The element is there, but it is hidden behind another element.
// FIX 1 — Wait for the overlay to disappear
wait.until(ExpectedConditions.invisibilityOfElementLocated(
By.cssSelector(".loading-spinner")));
driver.findElement(By.id("submit")).click();
// FIX 2 — Scroll to element first
WebElement element = driver.findElement(By.id("submit"));
((JavascriptExecutor) driver).executeScript(
"arguments[0].scrollIntoView({block: 'center'});",
element);
element.click();
// FIX 3 — JavaScript click (last resort)
WebElement element = driver.findElement(By.id("submit"));
((JavascriptExecutor) driver).executeScript(
"arguments[0].click();", element);The explicit wait ran out before the condition was met. Either the element truly did not appear, or your timeout is too short. Check if the page actually loaded, the network request completed, and the element exists in the DOM.
Browser and driver version mismatch. ChromeDriver 120 cannot work with Chrome 125. Solution: use WebDriverManager or Selenium Manager (built into Selenium 4.6+) to auto-download the correct driver version.
Q: How do you handle StaleElementReferenceException in your framework?
A: StaleElementReferenceException happens when the DOM updates after I have already found an element. The element reference becomes invalid because it points to an old DOM node. I handle it in two ways. First, in BasePage, I have a clickWithRetry method that catches StaleElementReferenceException and re-finds the element before retrying. Second, I follow the practice of finding elements as late as possible — instead of storing WebElement references at the top of a method, I find elements right before I interact with them. If a page action causes a DOM rerender, I re-find all elements after the action completes.
Never use Thread.sleep() to "fix" a flaky test. It hides the real problem. Today it needs 3 seconds, tomorrow it needs 5, next week it needs 10. Use explicit waits that check for the actual condition — element visible, text present, URL changed.
Q: A test passes locally but fails in CI. How do you debug?
A: First, I check if CI runs in headless mode — some elements behave differently in headless (tooltips, hover effects). Second, I check the screen resolution — CI containers often have 1024x768 which can hide responsive elements. I add --window-size=1920,1080 to ChromeOptions for headless. Third, I check the log file and screenshot from CI. Fourth, I check network speed — CI environments can be slower, so I might need longer explicit waits. Fifth, I check if any test ran before this one and left the browser in an unexpected state. I always try to reproduce the exact CI conditions locally: same browser, headless mode, same resolution.
Add a "debug mode" to your framework. When enabled, it adds extra logging, takes screenshots at every step, and slows down execution. Use it to investigate failures without modifying test code: mvn test -Ddebug.mode=true
Key Point: Debugging is a skill. Read the error, check the screenshot, check the log, run in headed mode, verify the locator. Most failures come from timing, stale elements, or locator changes.