Skip to main content

Iframes, Implicit and Explicit waits in Selenium using Python with example

Modern web-technologies uses AJAX for loading data instead of refreshing the whole page. With this approach, if an element is not present in DOM, an exception will be raised.

To understand this better, I have created a small example on JSFiddle, which uses JQuery to fetch some data from a REST API and then update the page with the results using AJAX.

If we do not use any delay, our test case will fail. One solution could be to use time.sleep() after clicking the button to wait for the response.

from selenium import webdriver
import unittest
import time

CHROMEDRIVER = "/Users/sarbjit/webdrivers/chromedriver"
WEBSITE = "https://jsfiddle.net/sarbjit87/cf3ud4qp/"

class TestJSFiddle(unittest.TestCase):
  def setUp(self):
      self.driver = webdriver.Chrome(executable_path=CHROMEDRIVER)

  def test_page(self):
      self.driver.get(WEBSITE)
      time.sleep(5) # Wait for website to load
      iframe = self.driver.find_element_by_xpath("//iframe[@name='result']")
      self.driver.switch_to_frame(iframe)

      elem = self.driver.find_element_by_css_selector('button.btn')
      elem.click()
      # time.sleep(5)
      # Look for the AJAX response
      elem = self.driver.find_element_by_css_selector('.ajax-output-example-class')
      self.assertRegexpMatches(elem.text, '"userId"')
      self.driver.switch_to_default_content()

  def tearDown(self):
      self.driver.close()

if __name__ == '__main__':
  unittest.main(verbosity=2)

Selenium offers two kind of waits to handle such scenarios - Implicit and Explicit waits. I would like to take a moment here to explain about iFrame selection. If you look at the JSFiddle website, the results are shown in a seperate iFrame. In order to work with our example code, we need to switch the context to that particular frame, this is done using driver.switch_to_frame() and revert back to default frame using driver.switch_to_default_content().

Both Implicit and Explicit wait(s) are dynamic wait(s). By dynamic, we mean if the web driver will find the element before the timeout, it will ignore rest of time and will continue with execution. e.g. if the wait value is specified to 10 and the element is loaded in 4 seconds, remaining 6 seconds wait will be ignored.

Implicit Wait :-

With Implicit wait, webdriver polls the DOM for a certain period of time when trying to find an element before throwing "Not Found Exception". Default value for the implicit wait is set to 0. Implicit wait can only be applied to web driver elements and can not work for other objects like driver etc. 

In our example, we can update the setUp method to include implicit wait statement. Note, this wait will be available throughout the webdriver object life and available for every statement looking for an element.

def setUp(self):
   self.driver = webdriver.Chrome(executable_path=CHROMEDRIVER)
   self.driver.implicitly_wait(10)

Explicit Wait :-

With Explicit wait, webdriver waits for a certain condition to occur. Certain convenience methods are used to determine the occurence of condition. In the code example below, webdriver waits for 10 seconds to timeout before the condition (in this case, presence of an element occurs).  CSS Selectors are again used to locate the element.

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

CHROMEDRIVER = "/Users/sarbjit/webdrivers/chromedriver"
WEBSITE = "https://jsfiddle.net/sarbjit87/cf3ud4qp/"

class TestJSFiddle(unittest.TestCase):
  def setUp(self):
      self.driver = webdriver.Chrome(executable_path=CHROMEDRIVER)

  def test_page(self):
      self.driver.get(WEBSITE)
      time.sleep(5) # Wait for website to load
      iframe = self.driver.find_element_by_xpath("//iframe[@name='result']")
      self.driver.switch_to_frame(iframe)

      elem = self.driver.find_element_by_css_selector('button.btn')
      elem.click()
      # Look for the AJAX response
      try:
          elem = WebDriverWait(self.driver, 10).until(
              EC.presence_of_element_located((By.CSS_SELECTOR, ".ajax-output-example-class"))
          )
      finally:
          self.assertRegexpMatches(elem.text, '"userId"')
      self.driver.switch_to_default_content()

  def tearDown(self):
      self.driver.close()

if __name__ == '__main__':
  unittest.main(verbosity=2)

There are other methods available in selenium.webdriver.common.by for locating the element, use the link below for details. 

https://seleniumhq.github.io/selenium/docs/api/py/webdriver/selenium.webdriver.common.by.html

Simiarly, for the detail list of expected conditions, follow the link below

https://seleniumhq.github.io/selenium/docs/api/py/webdriver_support/selenium.webdriver.support.expected_conditions.html



Comments