Python Selenium WebDriver. Writing my own expected condition

I'm trying to write my own expected condition. What I need... I have an iframe. and I also have an image in it. I want to continue processing when image's scr will change. What I did:

class url_changed_condition(object):
    '''
    Checks whether url in iframe has changed or not
    '''
    def __init__(self, urls):
        self._current_url, self._new_url = urls

    def __call__(self, ignored):
        return self._current_url != self._new_url  

and later in the code I have:

def process_image(self, locator, current_url):
    try:
        WebDriverWait(self.driver, 10).until(ec.presence_of_element_located((By.TAG_NAME, u"iframe")))
        iframe = self.driver.find_element(*locator)
        if iframe:
            print "Iframe found!"
        self.driver.switch_to_frame(iframe)
        WebDriverWait(self.driver, 10).until(ec.presence_of_element_located((By.XPATH, u"//div")))

        # WebDriverWait(self.driver, 10).until(
            # url_changed_condition(
                # (current_url, self.driver.find_element(By.XPATH, u"//a/img").get_attribute(u"src"))))

        img_url = self.driver.find_element(By.XPATH, u"//a/img").get_attribute(u"src")
        print img_url
        self.search_dict[self._search_item].append(img_url)
        self.driver.switch_to_default_content()
    except NoSuchElementException as NSE:
        print "iframe not found! {0}".format(NSE.msg)
    except:
        print "something went wrong"
        import traceback
        import sys
        type_, value_, trace_ = sys.exc_info()
        print type_, value_
        print traceback.format_tb(trace_)
    finally:
        return current_url  

This code works, but returns the same url multiple times. The problem is when I uncomment url_changed_condition it falls with TimeoutException in
(current_url, self.driver.find_element(By.XPATH, u"//a/img").get_attribute(u"src"))
The line below it works fine... I don't get it.


Solution 1:

Looks like this topic misses an example of a Custom Expected Condition.

It is actually pretty easy. First of all, what is an Expected Condition in Python selenium bindings:

  • it is a new-style class (based on object)
  • it has __call__() magic method defined which returns a boolean

There is a big set of built-in expected condition classes.

Let's work through example. Let's say we want to wait until an element's text will start with a desired text:

from selenium.webdriver.support import expected_conditions as EC

class wait_for_text_to_start_with(object):
    def __init__(self, locator, text_):
        self.locator = locator
        self.text = text_

    def __call__(self, driver):
        try:
            element_text = EC._find_element(driver, self.locator).text
            return element_text.startswith(self.text)
        except StaleElementReferenceException:
            return False

Usage:

WebDriverWait(driver, 10).until(wait_for_text_to_start_with((By.ID, 'myid'), "Hello, World!"))

Solution 2:

Using the technique described by @alecxe , but modified slightly to cope with the expected_conditions which accept an element instead of a locator: (In this case, since there is no selenium.webdriver.support.expected_conditions.invisibility_of(element), I'm waiting for the is_displayed() method to signal False)

class wait_for_element_to_be_invisible(object):
    def __init__(self, element):
        self.element = element

    def __call__(self, driver):
        return not(self.element.is_displayed())

def test_collapsible_blocks_expand_or_collapse(self):
    self.browser.get(self.server_url+'/courses/1/')
    shadables = self.browser.find_elements_by_class_name('shade')
    for shadable in shadables:
        ## parent, then sibling element (*)
        shady_bit = shadable.find_element_by_xpath('../following-sibling::*')
        element = WebDriverWait(self.browser, 10).until(
            EC.visibility_of(shady_bit))
        shadable.click()
        element = WebDriverWait(self.browser, 10).until(
            self.wait_for_element_to_be_invisible(shady_bit))

The HTML fragment with the relevant bits of the DOM is:

<h4 class="shadable"><span class="shade" title="Show/Hide">↓</span>
  <a href="/link/to/uri">Title of Section</a>
</h4>
<article class="abstract">
  <p>Descriptive part which is shadable
  </p>
</article>`

Solution 3:

According to the documentation:

WebDriverWait by default calls the ExpectedCondition every 500 milliseconds until it returns successfully. A successful return is for ExpectedCondition type is Boolean return true or not null return value for all other ExpectedCondition types.

The fact that you get the same URL multiple times if you comment out the custom wait should give you a hint.

In __call__() you're always returning False since the URLs never change. Since you're returning False, the ExpectedCondition is never met and you get the TimeoutException.

So either redefine the ExpectedCondition logic or test for a different case.