To understand explicit waits better, I found I needed to understand what's happening in the following:
- The expected condition function.
- The
WebdriverWait.until
method
The simplest expected condition is presence_of_element_located
. It is just a wrapper around driver.find_element()
def presence_of_element_located(locator):
def _predicate(driver):
return driver.find_element(*locator)
return _predicate
Other expected conditions will check the element for certain conditions but I'll keep this example simple.
The result of presence_of_element_located
is passed into WebDriverWait.until
, typically examples will look like this:
wait = WebDriverWait(driver, timeout=30)
element = wait.until(ec.presence_of_element_located((By.ID, "my_id")))
When I break down what's happening in the above it starts to become a little more clear.
a_callable_method = ec.presence_of_element_located((By.ID, "my_id"))
wait = WebDriverWait(driver, timeout=30)
element = wait.until(a_callable_method)
WebDriverWait.until
is simply a while loop calling a_callable_method
you passed in. The method/function we are passing in always takes driver as an argument.
def until(self, method, message: str = ""):
"""Calls the method provided with the driver as an argument until the \
return value does not evaluate to ``False``.
:param method: callable(WebDriver)
:param message: optional message for :exc:`TimeoutException`
:returns: the result of the last call to `method`
:raises: :exc:`selenium.common.exceptions.TimeoutException` if timeout occurs
"""
screen = None
stacktrace = None
end_time = time.monotonic() + self._timeout
while True:
try:
value = method(self._driver)
if value:
return value
except self._ignored_exceptions as exc:
screen = getattr(exc, 'screen', None)
stacktrace = getattr(exc, 'stacktrace', None)
time.sleep(self._poll)
if time.monotonic() > end_time:
break
raise TimeoutException(message, screen, stacktrace)
In other words, explicit waiting is just a retry loop until a certain condition is met, or the timer runs out. In our example it's trying to find the element with id="my_id". If/when the element is found, it will be returned, otherwise a TimeoutException
will be raised.
The real power of explicit wait starts to shine when looking at some of the other expected conditions:
def visibility_of(element):
def _predicate(_):
return _element_if_visible(element)
return _predicate
def _element_if_visible(element, visibility=True):
return element if element.is_displayed() == visibility else False
Using visibility_of
will look for the element but then also check to see if the element has the condition consistent with being visible.