Selenium Expected Conditions - possible to use 'or'?

17,916

Solution 1

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.

Solution 2

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)

Solution 3

Not exactly through EC, but does achieve the same result - with a bonus.
Still using WebDriverWait's until() method, but passing the pure find_elements_*() methods inside a lambda expression:

WebDriverWait(driver, 10).until(lambda driver: driver.find_elements_by_id("id1") or \
                                               driver.find_elements_by_css_selector("#id2"))[0]

The find_elements_*() methods return a list of all matched elements, or an empty one if there aren't such - which is a a boolean false. Thus if the first call doesn't find anything, the second is evaluated; that repeats until either of them finds a match, or the time runs out.

The bonus - as they return values, the index [0] at the end will actually return you the matched element - if you have any use for it, in the follow-up calls.

Share:
17,916

Related videos on Youtube

artfulrobot
Author by

artfulrobot

Stitching together websites, databases and open source cunning to help you change the world.

Updated on September 14, 2022

Comments

  • artfulrobot
    artfulrobot over 1 year

    I'm using Selenium 2 / WebDriver with the Python API, as follows:

    from selenium.webdriver.support import expected_conditions as EC
    
    # code that causes an ajax query to be run
    
    WebDriverWait(driver, 10).until( EC.presence_of_element_located( \
        (By.CSS_SELECTOR, "div.some_result")));
    

    I want to wait for either a result to be returned (div.some_result) or a "Not found" string. Is that possible? Kind of:

    WebDriverWait(driver, 10).until( \
        EC.presence_of_element_located( \
             (By.CSS_SELECTOR, "div.some_result")) \
        or 
        EC.presence_of_element_located( \
             (By.CSS_SELECTOR, "div.no_result")) \
    );
    

    I realise I could do this with a CSS selector (div.no_result, div.some_result), but is there a way to do it using the Selenium expected conditions method?

  • Martin C. Martin
    Martin C. Martin over 5 years
    Great solution @artfulrobot! However, this drops the return value of fn(), which is the element that was found. Changing it to "res = fn(driver); if res: return res" does the trick.
  • Mohammed Shareef C
    Mohammed Shareef C over 3 years
    Really pythonic version of the ExpectedCondition.or in selenium Java
  • Mohamed Benkedadra
    Mohamed Benkedadra over 3 years
    Small addition about the NB part .. the EC will return False when using presence_of_all_elements_located because it will return an empty list .. on the other hand, it will throw an exception on presence_of_element_located because it's not working with lists. at least this is what i noticed when testing. NICE SOLUTION BY THE WAY !