Loading...

Handling exceptions in Selenium WebDriver

 During many years of working with Selenium, I have noticed that sometimes even experienced testers do not understand how to handle exceptions thrown by Selenium. Handling them on wrong way can cause with flaky tests. In this article I will focus on most popular exceptions and explain why they are throwing and how to fix them.

The most popular exception is StaleElementReferenceException. It occurs when we want to perform any method (e.g. getText()) on WebElement which was extracted (found) before but the element has been changed in the dom three in the meantime. I have seen many times that testers were adding waits before the row in which this exception was thrown. Unfortunately usually it is not solving the problem. I will explain why, on the timer which is visible on the page https://testcraftsmanship.com/selenium-playground/. Most of us is used to page object pattern and probably would create page with element defined as below with method to extracting the value of the timer.

@FindBy(id = "page-heading-timer")
private WebElement pageHeadingTimer;

public String headingTimerValue() {
    return pageHeadingTimer.getText();
}


When we will use it in our tests, it will be working as expected for most of the time, but from time to time it will throw exception. Thing is that before executing pageHeadingTimer.getText() we have to lazy initialize pageHeadingTimer and time between initialization and executing method is very short. You should imagine the situation in which we initialize the pageHeadingTimer just in last millisecond and create the object for e.g. 10:21:00. When we execute the getText() method then we want to get text from element which just changed in dom to 10:21:01. Previous element is not available anymore, so we are getting StaleElementReferenceException.

The situation will be the same, if we won't be using page object pattern and to extracting timer value we will use the code listed below:

WebElement element = driver.findElement(By.id("page-heading-timer"));
String timerValue = element.getText();


Here we initialize element with use of findElement method. The exception will be thrown when getText is executed on element which is not available anymore but was available while executing method findElement. As in example above here exception will throw very rare because timer has to change between executing findElement and getText methods. I guess that right now it should be clear that adding waits before finding or getting text will not solve the problem.
To solve the problem we just need to handle this rare situation which is causing it. One way to handle this situation is shown below:

public String headingTimerValue() {
    try {
        return element.getText();
    } catch (StaleElementReferenceException e) {
        return element.getText();
    }
}


I know that it does not look nice, but I think that we can accept it for this very element that we know that can be reloaded in dom from time to time. Important thing is that we should know why we are using this structure and not to apply it for every WebElement we have in our framework.

Next exception called ElementNotInteractableException should be less problematic. We can imagine that it can be thrown when element we want to perform any operation on is not interactable. I can explain it based on the example from the page https://testcraftsmanship.com/selenium-playground/. On the dom three of the page we can find element with id close-btn. Unfortunately, the element is not visible and accessible on the web page. When we will click on [License conditions] button then this button (with id close-btn) will become visible and accessible - of course I will not happen just after clicking on [License conditions] button. We need to wait for content to be clickable. This is the listing before the fix:

@FindBy(id = "open-modal-window-btn")
private WebElement openModalWindowButton;

@FindBy(id = "close-btn")
private WebElement closeButton;

public PlaygroundPage(WebDriver driver) {
    super(driver);
}

public PlaygroundPage clickLicenseConditionsButton() {
    openModalWindowButton.click();
    return this;
}

public PlaygroundPage clickCloseButton() {
    closeButton.click();
    return this;
}


In this example solution is super simple, and I guess that should be clear for everyone. Thing we need to add is the wait for the element to be clickable. Updated methods which work correctly:

public PlaygroundPage clickLicenseConditionsButton() {
    openModalWindowButton.click();
    return this;
}

public PlaygroundPage clickCloseButton() {
    new WebDriverWait(driver, Duration.ofSeconds(TIMEOUT_IN_SECONDS))
        .until(ExpectedConditions.elementToBeClickable(closeButton));
    closeButton.click();
    return this;
}


Reason of throwing NoSuchElementException exception is different from previous one but solution is quite similar. In this very case element on which we want to perform any operation is not available in the dom three. Let's navigate once again to the page https://testcraftsmanship.com/selenium-playground/. What we want to do is just click on the 'Actions' menu, then on 'Append banner' submenu and after that check that the banner is visible. Problem in this scenario is that banner is displayed with a small delay and before that it is not available in the doom three (you are not able to find element with id 'success-alert'). Because there is no banner on the page (in the small amount of time before it will be displayed) and we want to get its value we are getting NoSuchElementException. This is the listing before the fix:

@FindBy(id = "navbar-item-actions")
private WebElement actionsMenu;

@FindBy(id = "navbar-item-action-wait-for-alert")
private WebElement showAlertMenuItem;

@FindBy(id = "success-alert")
private WebElement successAlert;

public SeleniumPlaygroundHomePage clickShowAlertButton() {
    actionsMenu.click();
    showAlertMenuItem.click();
    return this;
}

public String getAlertText() {
    return successAlert.getText();
}


Solution in this case is similar to the previous one. We just need to add the wait for the element to be visible. Updated methods which work correctly:

public SeleniumPlaygroundHomePage clickShowAlertButton() {
    actionsMenu.click();
    showAlertMenuItem.click();
    return this;
}

public String getAlertText() {
    return new WebDriverWait(DriverFactory.getDriver(),TIMEOUT_IN_SECONDS)
    .until(ExpectedConditions.visibilityOf(successAlert))
    .getText();
}


Hope, it helps you to fix flaky tests.