Waiting for a custom condition in Selenium or Appium

Inspired by a discussion on our Discord server, I’d like to walkthrough how you can create custom wait conditions in your Selenium or Appium tests. This is a helpful technique to know, as you may encounter a situation in your test where having a single expected condition is not logically exhaustive enough to proceed. For example, if you need to wait for multiple elements to be present. I’ll explain and demonstrate this technique using the Selenium Python client.

Discussing interesting topics in automation testing is precisely why we created our Discord server! If you find this blog post helpful, or learned something new, consider joining our community today 👋:

Join the Tauk Discord Server!
Enabling developers to efficiently diagnose, resolve, and optimize their automation tests. | 9 members

Contents of this Guide

  • The until method of the WebDriverWait class
  • Creating callable Python classes
  • Example: Waiting for at least one of many elements

The until method of the WebDriverWait class

Let’s first start with taking a look at how the until method of the WebDriverWait class is implemented:

selenium/wait.py at trunk · SeleniumHQ/selenium
A browser automation framework and ecosystem. Contribute to SeleniumHQ/selenium development by creating an account on GitHub.
def until(self, method, message=''):
        """Calls the method provided with the driver as an argument until the 
        return value does not evaluate to False.
        """
        screen = None
        stacktrace = None

        end_time = time.time() + self._timeout
        while True:
            try:
                value = method(self._driver)
                if value:
                    return value
            except InvalidSelectorException as e:
                raise e
            except self._ignored_exceptions as exc:
                screen = getattr(exc, 'screen', None)
                stacktrace = getattr(exc, 'stacktrace', None)
            time.sleep(self._poll)
            if time.time() > end_time:
                break
        raise TimeoutException(message, screen, stacktrace)

The until method accepts a method and will invoke that method by passing in the driver object as an argument. If the method returns True then until can return, otherwise an exception will be raised. We can use this knowledge to create our own custom method that will check for our particular desired condition. That said, there’s an interesting nuance to how we can represent this method. In Python, classes can implement the __call__ magic method to give them the ability to behave like a function. This is a common practice with creating custom wait conditions and I’ll further explain this concept in the next section.

Creating callable Python classes

__call__ is a special magic method in Python that, when implemented in a class, gives instances of the class to ability to behave like a function. When implemented, the __call__ method will be invoked when the class is instantiated. For example:

import uuid

class Car:
    def __init__(self, model_name):
        self.name = model_name
        self.manufacturer_id = uuid.uuid4().hex
    
    def __call__(self):
        print(f"Car Name: {self.name} and Manufacturer ID: {self.manufacturer_id}")

# Creating an instance of the Car class
tesla = Car("Model 3")

# Using the instance like a function
tesla()

In this example, when the instance of the Car class is called its name and uniquely generated manufacturer ID will be printed to the output. For instance:

Car Name: Model 3 and Manufacturer ID: 2b62da0285894603a02b2a464b703d9d

Many Python packages, like the Python Selenium client, use the callable class paradigm as an interface for using the APIs provided. It provides the flexibility of organizing logic in a class, with the ease of invoking the logic like a function call.

Now that we understand how to create a callable class, let’s create one to express a custom wait condition in a Selenium test.

Example: Waiting for at least one of many elements

There may be a situation in your automation test where you would want to wait for at least one of many possible elements to be present before proceeding. This is a great use case for creating a custom wait condition. We can create a callable class that is initialized with a list of expected conditions:

class one_expected_condition_from_list:
	def __init__(self, expected_conditions):
		self.expected_conditions = expected_conditions
	
	def __call__(self, driver):
		for expected_condition in self.expected_conditions:
			if expected_condition(driver):
				return True
		
		return False

If at least one of the expected conditions from the list occurs we can return True or otherwise False.

To utilize, we can create a list of conditions in our test case, pass it to our callable class, which we can provide as input to the until method of the WebDriverWait class. For example:

conditions = [
	expected_conditions.presence_of_element_located((By.XPATH, '/html/body/nav[1]/div/div[1]/button')),
	expected_conditions.presence_of_element_located((By.CSS_SELECTOR, '.appium-logo')),
	expected_conditions.presence_of_element_located((By.ID, '#introducing-appium'))
]

self.driver.get("https://appium.io/")

self.wait.until(one_expected_condition_from_list(conditions))

The End

That’s it we’re done! 🎉 You now have the prerequisite knowledge to create custom wait conditions in your Selenium and Appium tests.

Separately, I’m currently working on a platform that enables developers to efficiently diagnose, resolve, and optimize their automation tests. We currently have an ongoing beta program for users to try it and if you want an invitation you can join the waitlist here. Just click on “Request an Invite.”