I tried to create a function with custom wait condition in Python. However, I get an error:
TypeError: \'bool\' object is not callable
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
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
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")
)
what I really end up to do is using lambda
self.wait.until(lambda x: waittest(driver, "//div[@id="text"]", "myCSSClass", "false"))