Selenium Expected Conditions - possible to use 'or'?
Ancient question but,
Consider how WedDriverWait
works, in an example independent from selenium:
def is_even(n):
return n % 2 == 0
x = 10
WebDriverWait(x, 5).until(is_even)
This will wait up to 5 seconds for is_even(x)
to return True
now, WebDriverWait(7, 5).until(is_even)
will take 5 seconds and them raise a TimeoutException
Turns out, you can return any non Falsy value and capture it:
def return_if_even(n):
if n % 2 == 0:
return n
else:
return False
x = 10
y = WebDriverWait(x, 5).until(return_if_even)
print(y) # >> 10
Now consider how the methods of EC
works:
print(By.CSS_SELECTOR) # first note this is only a string
>> 'css selector'
cond = EC.presence_of_element_located( ('css selector', 'div.some_result') )
# this is only a function(*ish), and you can call it right away:
cond(driver)
# if element is in page, returns the element, raise an exception otherwise
You probably would want to try something like:
def presence_of_any_element_located(parent, *selectors):
ecs = []
for selector in selectors:
ecs.append(
EC.presence_of_element_located( ('css selector', selector) )
)
# Execute the 'EC' functions agains 'parent'
ecs = [ec(parent) for ec in ecs]
return any(ecs)
this WOULD work if EC.presence_of_element_located
returned False
when selector
not found in parent
, but it raises an exception, an easy-to-understand workaround would be:
def element_in_parent(parent, selector):
matches = parent.find_elements_by_css_selector(selector)
if len(matches) == 0:
return False
else:
return matches
def any_element_in_parent(parent, *selectors):
for selector in selectors:
matches = element_in_parent(parent, selector)
# if there is a match, return right away
if matches:
return matches
# If list was exhausted
return False
# let's try
any_element_in_parent(driver, 'div.some_result', 'div.no_result')
# if found in driver, will return matches, else, return False
# For convenience, let's make a version wich takes a tuple containing the arguments (either one works):
cond = lambda args: any_element_in_parent(*args)
cond( (driver, 'div.some_result', 'div.no_result') )
# exactly same result as above
# At last, wait up until 5 seconds for it
WebDriverWait((driver, 'div.some_result', 'div.no_result'), 5).until(cond)
My goal was to explain, artfulrobot already gave a snippet for general use of actual EC
methods, just note that
class A(object):
def __init__(...): pass
def __call__(...): pass
Is just a more flexible way to define functions (actually, a 'function-like', but that's irrelevant in this context)
I did it like this:
class AnyEc:
""" Use with WebDriverWait to combine expected_conditions
in an OR.
"""
def __init__(self, *args):
self.ecs = args
def __call__(self, driver):
for fn in self.ecs:
try:
res = fn(driver)
if res:
return True
# Or return res if you need the element found
except:
pass
Then call it like...
from selenium.webdriver.support import expected_conditions as EC
# ...
WebDriverWait(driver, 10).until( AnyEc(
EC.presence_of_element_located(
(By.CSS_SELECTOR, "div.some_result")),
EC.presence_of_element_located(
(By.CSS_SELECTOR, "div.no_result")) ))
Obviously it would be trivial to also implement an AllEc
class likewise.
Nb. the try:
block is odd. I was confused because some ECs return true/false while others will throw NoSuchElementException
for False. The Exceptions are caught by WebDriverWait so my AnyEc thing was producing odd results because the first one to throw an exception meant AnyEc didn't proceed to the next test.