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 👋:
Contents of this Guide
- The
until
method of theWebDriverWait
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:
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.”