Have you ever run an automated test only to have it crash on you because of a timeout or element exception such as NoSuchElementException
or ElementNotVisibleException
? Don't worry: it's not uncommon for tests to be flaky because of external factors, with the major ones being page load times.
The good news is that you can keep slow-loading pages or elements from crashing your tests by using one of Selenium's three wait commands. Using these wait commands gives the DOM time to load HTML elements or pages, preventing your tests from crashing as easily.
In this guide you will learn how to use Selenium's implicit, explicit, and fluent wait commands to improve automated test scripts.
This tutorial starts by examining a few simple Selenium tests written in Python that don't use wait commands to determine why they're failing. Then, it considers each of the three wait commands—implicit wait, explicit wait, and fluent wait—and how they can improve these tests.
Let's look at three Selenium tests that don't use wait commands and consider why they're crashing. These tests are written for Luma, a dummy e-commerce platform.
The overarching objective of the first test is to add an item (in this case, a backpack) to the shopping cart, go to the checkout page, and finally conclude the test by changing the quantity of the item.
The entire script looks like this:
1from selenium import webdriverfrom selenium.webdriver import Keysfrom selenium.webdriver.common.by import By23print("test case started")4driver = webdriver.Chrome()56# Maximize the window size7driver.maximize_window()89# Navigate to the URL10driver.get("https://magento.softwaretestingboard.com/")11print(driver.title)1213searchBtn = driver.find_element(By.ID, 'search')searchBtn.send_keys("backpack")14searchBtn.send_keys(Keys.ENTER)1516backpack = driver.find_element(By.XPATH, "//*[contains(text(), 'Driven Backpack')]")backpack.click()1718addToCart = driver.find_element(By.ID, 'product-addtocart-button')19addToCart.click()2021cart = driver.find_element(By.XPATH, "//a[normalize-space()='shopping cart']")22cart.click()2324quantity = driver.find_element(By.XPATH, "/html[1]/body[1]/div[2]/main[1]/div[3]/div[1]/div[2]/form[1]/div[1]/table[1]/tbody[1]/tr[1]/td[3]/div[1]/div[1]/label[1]/input[1]")25quantity.clear()26quantity.send_keys("2")2728print("item quantity changed successfully")
During the test run, your code will successfully run through the entire script until it's time to navigate to the checkout page.
This is because you'll hit a synchronized loading screen between the product page and adding the item to checkout. Since page loads aren't immediate, you'll inevitably run into issues if you're not using a wait command.
In the next step, the quantity table on the checkout page should be cleared out. But due to synchronization issues, you'll instead get a NoSuchElementException
error:
1NoSuchElementException: Message: no such element: Unable to locate element: {"method":"xpath","selector":"//a[normalize-space()='shopping cart']"}
The objective of the second test is to navigate to Luma and choose a large, black variant of the Hero Hoodie.
The entire script looks like this:
1from selenium import webdriver2from selenium.webdriver.common.by import By34print("test case started")5driver = webdriver.Chrome()67# Maximize the window size8driver.maximize_window()910# Navigate to the URL11driver.get("https://magento.softwaretestingboard.com/")12print(driver.title)1314largeSize = driver.find_element(By.XPATH,15"//div[@class='swatch-opt-158']//div[@id='option-label-size-143-item-169']")16largeSize.click()1718blackColor = driver.find_element(By.XPATH, "//div[@id='option-label-color-93-item-49']")19blackColor.click()2021addToCart = driver.find_element(By.XPATH, "/html[1]/body[1]/div[2]/main[1]/div[3]/div[1]/div[2]/div[3]/div[1]/div[1]/ol[1]/li[4]/div[1]/div[1]/div[3]/div[1]/div[1]/form[1]/button[1]")22addToCart.click()2324cart = driver.find_element(By.XPATH, "//a[@class='action showcart']")25cart.click()2627topCartButton = driver.find_element((By.XPATH, "//a[@class='action showcart']"))28topCartButton.click()2930product = driver.find_element(By.XPATH, "//a[@data-bind='attr: {href: product_url}, html: product_name']")3132print(product.text)
After navigating to the dummy store, the Python script will scan the DOM to find and click the "L" size option for the Hero Hoodie. When it reaches the size options, you'll get a NoSuchElementException
:
1NoSuchElementException: Message: no such element: Unable to locate element: {"method":"xpath","selector":"//div[@class='swatch-opt-158']//div[@id='option-label-size-143-item-169']"}
The reason the test will fail is because the DOM won't be able to detect the "L" size. When validating the item name from the cart, it will also fail due to synchronization issues.
The third test uses the navigation bar to hover over the "Men" menu item, hover over the "Bottoms" option, and click the "Pants" button.
The entire script looks like this:
1from selenium import webdriver2from selenium.webdriver import ActionChains3from selenium.webdriver.common.by import By45print("test case started")6driver = webdriver.Chrome()78# Maximize the window size9driver.maximize_window()1011# Navigate to the URL12driver.get("https://magento.softwaretestingboard.com/")13print(driver.title)1415# The Action class, a built-in feature in Selenium that automates actions accomplished by your keyboard and mouse, hovers over the "Bottoms" option to reveal both the "Pants" and "Shorts" buttons16menTab = driver.find_element(By.XPATH, "/html[1]/body[1]/div[2]/div[1]/div[1]/div[2]/nav[1]/ul[1]/li[3]/a[1]/span[2]")17action = ActionChains(driver)18action.move_to_element(menTab).perform()1920bottomsButton = driver.find_element(By.ID, "ui-id-18")21action = ActionChains(driver)22action.move_to_element(bottomsButton).perform()2324# Conclude the test run by implementing a click command on the "Pants" button that leads you to the Men's Pants page25pantsButton = driver.find_element(By.ID, "ui-id-23")26pantsButton.click()
When you run the test in an integrated development environment (IDE) such as PyCharm, you'll almost immediately get a `NoSuchElementException` since Selenium won't be able to pick up the HTML element from the DOM:
1NoSuchElementException: Message: no such element: Unable to locate element: {"method":"xpath","selector":"/html[1]/body[1]/div[2]/div[1]/div[1]/div[2]/nav[1]/ul[1]/li[3]/a[1]/span[2]"}
Because elements in navigation bars are hidden in nested HTML elements, they are prone to crashing without a wait command.
Now, let's see how these three tests can be improved by adding some of Selenium's wait commands.
The implicit wait command tells the driver to pause for an allotted amount of time to allow the HTML element to load into the DOM. You should therefore use an implicit wait if you're familiar with the UI page and element load times.
Using an implicit wait is incredibly useful for the first test scenario above. During the test run, a synchronized loading screen between the product page and checkout page causes the test to crash.
An implicit wait helps to handle the intermission between the two screens. By simply adding driver.implicitly_wait(5)
, the IDE waits five seconds for the synchronization to complete before moving to the next step.
The entire script with the implicit wait command looks like this:
1from selenium import webdriver2from selenium.webdriver import Keys3from selenium.webdriver.common.by import By45print("test case started")6driver = webdriver.Chrome()78# Maximize the window size9driver.maximize_window()1011# Navigate to the URL12driver.get("https://magento.softwaretestingboard.com/")13print(driver.title)1415searchBtn = driver.find_element(By.ID, 'search')16searchBtn.send_keys("backpack")17searchBtn.send_keys(Keys.ENTER)1819backpack = driver.find_element(By.XPATH, "//*[contains(text(), 'Driven Backpack')]")20backpack.click()2122addToCart = driver.find_element(By.ID, 'product-addtocart-button')23addToCart.click()2425# Implicit wait26driver.implicitly_wait(5)2728cart = driver.find_element(By.XPATH, "//a[normalize-space()='shopping cart']")29cart.click()3031quantity = driver.find_element(By.XPATH, "/html[1]/body[1]/div[2]/main[1]/div[3]/div[1]/div[2]/form[1]/div[1]/table[1]/tbody[1]/tr[1]/td[3]/div[1]/div[1]/label[1]/input[1]")32quantity.clear()33quantity.send_keys("2")34print("item quantity changed successfully")
The explicit wait command tells the driver to wait until certain conditions are present in the DOM—for example, until an HTML element is clickable or visible—before throwing an exception. It's considered a more "intelligent" version of an implicit wait, since it allows the code to pause until certain conditions are met.
However, the syntax of an explicit wait is more complex than an implicit wait. You need to call the explicit wait with the following code:
1wait = WebDriverWait(driver, <number of seconds>)
You then parameterize your explicit wait based on expected conditions (EC), which can involve waiting until the element is visible or clickable, among many other possibilities. Some of the most commonly used ECs during testing are element_to_be_clickable, visibility_of_element_located, element_to_be_selected, and alert_is_present.
When writing your explicit wait, the syntax is:
1wait.until(EC.<expected condition type>((By.XPATH, "<element xpath>")))
Using an explicit wait to improve the second test scenario is useful in two ways. Since the IDE has trouble detecting the size option in the DOM, you can implement an explicit wait to wait until the element is clickable.
Another failure occurs due to synchronization issues after adding your item to the cart itself. You can add an explicit wait until the cart element is visible.
The entire script with explicit waits looks like this:
1from selenium import webdriver2from selenium.webdriver.common.by import By3from selenium.webdriver.support.wait import WebDriverWait4from selenium.webdriver.support import expected_conditions as EC56print("test case started")7driver = webdriver.Chrome()89# Maximize the window size10driver.maximize_window()1112# Navigate to the URL13driver.get("https://magento.softwaretestingboard.com/")14print(driver.title)1516# Explicit wait17wait = WebDriverWait(driver, 10)18largeSize = wait.until(EC.element_to_be_clickable((By.XPATH, "//div[@class='swatch-opt-158']//div[@id='option-label-size-143-item-169']")))19largeSize.click()2021blackColor = driver.find_element(By.XPATH, "//div[@id='option-label-color-93-item-49']")22blackColor.click()2324addToCart = driver.find_element(By.XPATH, "/html[1]/body[1]/div[2]/main[1]/div[3]/div[1]/div[2]/div[3]/div[1]/div[1]/ol[1]/li[4]/div[1]/div[1]/div[3]/div[1]/div[1]/form[1]/button[1]")25addToCart.click()2627cart = driver.find_element(By.XPATH, "//a[@class='action showcart']")28cart.click()2930# Explicit wait31topCartButton = wait.until(EC.visibility_of_element_located((By.XPATH, "//a[@class='action showcart']")))32topCartButton.click()3334product = wait.until(EC.visibility_of_element_located((By.XPATH, "//a[@data-bind='attr: {href: product_url}, html: product_name']")))3536print(product.text)
Similar to an explicit wait, a fluent wait tells the driver to wait until certain ECs are met in the DOM before throwing an exception. Fluent waits are also called "smart waits" because they don't wait the maximum time specified in your code; they pause until the element is discoverable in the DOM.
So, if your elements load at different times—for example, if page load takes fifteen seconds vs. a dynamic button that takes three seconds—or if you're unaware of how long elements take to load, you should opt for fluent waits instead of explicit waits.
The syntax of fluent waits is more customizable but also more complex than either implicit or explicit waits. It comes with two features: poll_frequency and ignored_exceptions. With poll_frequency, you can tell your script to keep rechecking elements every specified number of seconds. If you opt not to use poll_frequency, the default is 500 milliseconds.
You can use ignored_exceptions to tell your script to halt exception errors (such as NoSuchElementException and ElementNotVisibleException). Use it in case element load times are longer than usual, and you wish to recheck the element according to poll intervals.
When writing your fluent wait, the syntax is:
1WebDriverWait(driver, <number of seconds>, poll_frequency=<number of seconds>, ignored_exceptions=[<exception type>, <exception type>])2wait.until(EC.<expected condition type>((By.XPATH, "<element xpath>")))
In the third test scenario, fluent waits can be used to hover over the "Men" navigation bar option and the "Bottoms" option. You can use a fluent wait to make the IDE wait ten seconds before using poll_frequency to recheck the DOM every one second for the specified HTML element.
The entire code with fluent waits looks like this:
1from selenium import webdriver2from selenium.webdriver import ActionChains3from selenium.webdriver.common.by import By4from selenium.webdriver.support.wait import WebDriverWait5from selenium.webdriver.support import expected_conditions as EC6from selenium.common import ElementNotVisibleException, ElementNotSelectableException78print("test case started")9driver = webdriver.Chrome()1011# Maximize the window size12driver.maximize_window()1314# Navigate to the URL15driver.get("https://magento.softwaretestingboard.com/")16print(driver.title)1718# Fluent wait19wait = WebDriverWait(driver, 10, poll_frequency=1, ignored_exceptions=[ElementNotVisibleException, ElementNotSelectableException])2021menTab = wait.until(EC.visibility_of_element_located((By.XPATH, "/html[1]/body[1]/div[2]/div[1]/div[1]/div[2]/nav[1]/ul[1]/li[3]/a[1]/span[2]")))22action = ActionChains(driver)23action.move_to_element(menTab).perform()2425bottomsButton = wait.until(EC.visibility_of_element_located((By.ID, "ui-id-18")))26action = ActionChains(driver)27action.move_to_element(bottomsButton).perform()2829pantsButton = driver.find_element(By.ID, "ui-id-23")30pantsButton.click()3132print(driver.title)
Whether you're loading a new page or searching for nested HTML elements, Selenium's wait commands can help keep your test runs from continuously crashing.
When comparing the three commands, keep in mind that even though using the implicit wait command is the simplest, it slows down test performance. Use it if you're just getting started, but then challenge yourself to progress to explicit waits and fluent waits.
If you want the most comprehensive automation tool to accelerate your releases, check out Sauce Labs. Sauce Labs lets you run Selenium tests securely on the cloud and speed up development with parallel cross-browser testing. Developers can leave the hassle of setting up and maintaining infrastructure to Sauce Labs, so they can do things that matter.