20

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"))    
2
  • Please provide the full error. Where is error happening? Commented Nov 21, 2017 at 19:40
  • 1
    Why call another method? see --selenium-python.readthedocs.io/waits.html: element = WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.ID, "myDynamicElement")) Commented Nov 21, 2017 at 19:40

4 Answers 4

23

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")
)
Sign up to request clarification or add additional context in comments.

Comments

12

what I really end up to do is using lambda

self.wait.until(lambda x: waittest(driver, "//div[@id="text"]", "myCSSClass", "false"))

Comments

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

Comments

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

1 Comment

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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.