Retrying on Exceptions Python Selenium

Dec. 18, 2018


There are many times when running selenium test where you think "if only the method just ran again when that happened". Trying to make selenium test to handle every use case might not be worth your time, or even practical. For example, I kept getting an error for an element not being clickable simply because notifications popped up in rare cases.

My favorite solution I have found for such cases is to simply run the code again. A very clean way I have found to do just that is to decorate my methods in page objects which I know are likely to fail. For most applications, I'd say this means the code should be revised instead of being allowed to fail; however, selenium test are a fickle mistress, and rerunning the code in rare cases is the correct thing to do.

For example, say for the following page object, my search_field method often failed when I attempted to access the field to type a value into due to NoSuchElementException and StaleElementReferenceException.

class GenericPromptPage(object):

    def __init__(self, driver):
        self.driver = driver

    @property
    def search_field(self):
        return self.driver.find_element_by_css_selector('.my-class .another-class input')

My decorated page object looks like this.

class GenericPromptPage(object):

    def __init__(self, driver):
        self.driver = driver

    @property
    @retry_on_no_element
    @retry_on_stale_element
    def search_field(self):
        return self.driver.find_element_by_css_selector('.my-class .another-class input')

With this new page object, if search_field fails, then the method will rerun itself! It is very clean, readable, and scalable throughout the code base. To actually create the decorators, I created a decorator factory since they all held the same form. It ended up looking like this.

from time import sleep
from functools import wraps
from selenium.common.exceptions import NoSuchElementException, StaleElementReferenceException

def retry_on_exception_factory(exc):
    def retry_on_exc(f):
        @wraps(f)
        def wrapped_f(*args, **kwargs):
            try:
                return f(*args, **kwargs)
            except exc:
                sleep(5)
                return f(*args, **kwargs)

        return wrapped_f

    return retry_on_exc


retry_on_no_element = retry_on_exception_factory(NoSuchElementException)

retry_on_stale_element = retry_on_exception_factory(StaleElementReferenceException)

Happy Coding!

Comment Enter a new comment: