18

I tried to create a function with custom wait condition in Python. However, I get an error:

TypeError: 'bool' object is not callable

def waittest(driver, locator, attr, value):
    element = driver.find_element_by_xpath(locator)
    if element.get_attribute(attr) == value:
        return element
    else:
        return False
wait = WebDriverWait(driver, 10)
element = wait.until(waittest(driver, '//div[@id="text"]', "myCSSClass", "false"))    
aaron
  • 39,695
  • 6
  • 46
  • 102
jacobcan118
  • 7,797
  • 12
  • 50
  • 95
  • Please provide the full error. Where is error happening? – Elis Byberi Nov 21 '17 at 19:40
  • 1
    Why call another method? see --http://selenium-python.readthedocs.io/waits.html: element = WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.ID, "myDynamicElement")) – Ywapom Nov 21 '17 at 19:40

4 Answers4

20

WebDriverWait(driver, 10).until() accepts a callable object which will accept an instance of a webdriver (driver is our example) as an argument. The simplest custom wait, which expects to see 2 elements, will look like

WebDriverWait(driver, 10).until(
    lambda wd: len(wd.find_elements(By.XPATH, 'an xpath')) == 2
)

The waittest function has to be rewritten as:

class waittest:
    def __init__(self, locator, attr, value):
        self._locator = locator
        self._attribute = attr
        self._attribute_value = value

    def __call__(self, driver):
        element = driver.find_element_by_xpath(self._locator)
        if element.get_attribute(self._attribute) == self._attribute_value:
            return element
        else:
            return False

And then it can be used as

element = WebDriverWait(driver, 10).until(
    waittest('//div[@id="text"]', "myCSSClass", "false")
)
Dmytro Serdiuk
  • 859
  • 10
  • 20
12

what I really end up to do is using lambda

self.wait.until(lambda x: waittest(driver, "//div[@id="text"]", "myCSSClass", "false"))
Jay Joshi
  • 1,402
  • 1
  • 13
  • 32
jacobcan118
  • 7,797
  • 12
  • 50
  • 95
2

The wait.until(..) function from selenium expects a function that it can call. However, you are, effectively, giving it a boolean.

element = wait.until(waittest(driver, '//div[@id="text"]', "myCSSClass", "false"))

Can be rewritten as:

value = waittest(driver, '//div[@id="text"]', "myCSSClass", "false")
element = wait.until(value)

Which makes this more clear - waittest returns a boolean, and wait.until(..) tries to call it as a function - hence 'bool' object is not callable.

You need to have your custom condition return a function that selenium can call to check if it's true yet. You can do this by defining a function inside your function:

def waittest(locator, attr, value):
    def check_condition(driver):
        element = driver.find_element_by_xpath(locator)
        if element.get_attribute(attr) == value:
            return element
        else:
            return False
    return check_condition
Jacob Davis-Hansson
  • 2,603
  • 20
  • 26
1

It may not really answer question, I just want to save my custom wait function here :D

I don't like WebDriverWait() it need another import that not really easy to remember or type and it also used another function to select element.

Example:

from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait 
from selenium.webdriver.support import expected_conditions as EC

WebDriverWait(driver, 5).until(EC.presence_of_element_located((By.CLASS_NAME , 'myClass')))

here custom wait to use with default function

import time

def waitFor(maxSecond, runFunction, param):
    while maxSecond:
        try:
            return runFunction(param)
        except:
            time.sleep(0.5)
            maxSecond -= 0.5

Usage:

# wait 5 second
waitFor(5, driver.find_element_by_class_name, 'myClass')
# Or
link = waitFor(5, driver.find_element_by_class_name, 'myClass')
link.click()
# Or
link = waitFor(5,....)
if not link:
    print('Timeout, link not found')

and for reference

To find single elements

  • find_element_by_id
  • find_element_by_name
  • find_element_by_xpath
  • find_element_by_link_text
  • find_element_by_partial_link_text
  • find_element_by_tag_name
  • find_element_by_class_name
  • find_element_by_css_selector

To find multiple elements (these methods will return a list):

  • find_elements_by_name
  • find_elements_by_xpath
  • find_elements_by_link_text
  • find_elements_by_partial_link_text
  • find_elements_by_tag_name
  • find_elements_by_class_name
  • find_elements_by_css_selector
ewwink
  • 18,382
  • 2
  • 44
  • 54
  • you have a very good point. Perl Selenium actually keeps the same set of find method. Not sure why Python went to a different way, pretty bad. – oldpride Apr 08 '22 at 02:52