Building off of the answer to How to wait until the page is loaded with Selenium for Python? I am attempting to create a method that allows multiple elements to be polled for presence using Expected Conditions.
I receive an error 'bool' object is not callable on the line containing: wait.until(any(expectations)).
The thought process was to allow an number of Xpaths to be passed as expected conditions, then using any(), in a similar manner to this java based answer, Trying to wait for one of two elements in the page using selenium xpath, check if any of the conditions are present.
What would be the proper way use any() in this case? Or better yet, what needs to be done for this method to work?
Assume that the Selenium .get('url') has already been executed immediately prior to calling this method.
def wait_with_xpath_expectation(self, search_elements, timeout=6, max_attempts=3):
"""
Selenium wait for an element(s) designated by Xpath to become available in the DOM. Useful for javascript AJAXy
loading pages where content may be be rendered dynamically on the client side after page load appears complete.
search_elements may be one Xpath as a string or many as a list. This allows for any of multiple elements on a
page or pages to be determined to have loaded based on expectations.
:param search_elements: string or list (strings converted to lists), Xpath(s)
:param timeout: int, seconds
:param max_attempts: int, time to wait before giving up on page loading
:return: Boolean, True if page has loaded, False if all attempts have failed
"""
# Page is not loaded yet
loaded = False
# Initialize attempt count
attempt = 0
# If only one element has been passed, ensure it is encapsulated by a list
if type(search_elements) is str:
search_elements = [search_elements]
# Begin the polling loop
while attempt < max_attempts:
try:
while loaded is False:
# Create a list of expected elements using list comprehension
expectations = [EC.presence_of_element_located((By.XPATH, element)) for element in search_elements]
# Define wait
wait = WebDriverWait(self.browser, timeout)
# Execute
wait.until(any(expectations))
# Acknowledge load
loaded = True
print("Success: Page loaded based on expectations")
# Exit returning True for load
return loaded
except TimeoutException as e:
# Increment attempts
attempt += 1
# Check again if max attempts has not been exceeded
while attempt < max_attempts:
# Increase timeout by 20%
timeout *= .2
# Try again
continue
# Print an error if max attempts is exceeded
print("Error: Max load with expectations attempts exceeded,", e)
# Exit returning False for load
return loaded
You can have an expected condition class to wait for a combination of expected conditions. Here is an example of one.
class wait_for_all(object):
def __init__(self, methods):
self.methods = methods
def __call__(self, driver):
try:
for method in self.methods:
if not method(driver):
return False
return True
except StaleElementReferenceException:
return False
This would then be used by building an array of the expected conditions and checking for all of them in the same wait. (Example lines split for clarity.)
methods = []
methods.append(EC.visibility_of_element_located(BY.ID, "idElem1"))
methods.append(EC.visibility_of_element_located(BY.ID, "idElem2"))
method = wait_for_all(methods)
WebDriverWait(driver, 5).until(method)
This will perform one five second wait while checking for visibility of two different elements.
I have documented this further in a blog post here.