Back to Resources

Blog

Posted March 8, 2023

Using Selenium Wait Commands to Improve Page Load Tutorial

Learn how to use Selenium's implicit, explicit, and fluent wait commands to improve automated test scripts.

quote

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.

How to Implement Wait for Page to Load with Selenium

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.

Selenium Tests without Wait Commands

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.

Test 1: Add Item to Cart and Change Quantity

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:

1
from selenium import webdriverfrom selenium.webdriver import Keysfrom selenium.webdriver.common.by import By
2
3
print("test case started")
4
driver = webdriver.Chrome()
5
6
# Maximize the window size
7
driver.maximize_window()
8
9
# Navigate to the URL
10
driver.get("https://magento.softwaretestingboard.com/")
11
print(driver.title)
12
13
searchBtn = driver.find_element(By.ID, 'search')searchBtn.send_keys("backpack")
14
searchBtn.send_keys(Keys.ENTER)
15
16
backpack = driver.find_element(By.XPATH, "//*[contains(text(), 'Driven Backpack')]")backpack.click()
17
18
addToCart = driver.find_element(By.ID, 'product-addtocart-button')
19
addToCart.click()
20
21
cart = driver.find_element(By.XPATH, "//a[normalize-space()='shopping cart']")
22
cart.click()
23
24
quantity = 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]")
25
quantity.clear()
26
quantity.send_keys("2")
27
28
print("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:

1
NoSuchElementException: Message: no such element: Unable to locate element: {"method":"xpath","selector":"//a[normalize-space()='shopping cart']"}

Test 2: Get an Item in Size Large and Color Black

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:

1
from selenium import webdriver
2
from selenium.webdriver.common.by import By
3
4
print("test case started")
5
driver = webdriver.Chrome()
6
7
# Maximize the window size
8
driver.maximize_window()
9
10
# Navigate to the URL
11
driver.get("https://magento.softwaretestingboard.com/")
12
print(driver.title)
13
14
largeSize = driver.find_element(By.XPATH,
15
"//div[@class='swatch-opt-158']//div[@id='option-label-size-143-item-169']")
16
largeSize.click()
17
18
blackColor = driver.find_element(By.XPATH, "//div[@id='option-label-color-93-item-49']")
19
blackColor.click()
20
21
addToCart = 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]")
22
addToCart.click()
23
24
cart = driver.find_element(By.XPATH, "//a[@class='action showcart']")
25
cart.click()
26
27
topCartButton = driver.find_element((By.XPATH, "//a[@class='action showcart']"))
28
topCartButton.click()
29
30
product = driver.find_element(By.XPATH, "//a[@data-bind='attr: {href: product_url}, html: product_name']")
31
32
print(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:

1
NoSuchElementException: 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.

Test 3: Use Navigation Bar to Search Men's Pants

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:

1
from selenium import webdriver
2
from selenium.webdriver import ActionChains
3
from selenium.webdriver.common.by import By
4
5
print("test case started")
6
driver = webdriver.Chrome()
7
8
# Maximize the window size
9
driver.maximize_window()
10
11
# Navigate to the URL
12
driver.get("https://magento.softwaretestingboard.com/")
13
print(driver.title)
14
15
# 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" buttons
16
menTab = 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]")
17
action = ActionChains(driver)
18
action.move_to_element(menTab).perform()
19
20
bottomsButton = driver.find_element(By.ID, "ui-id-18")
21
action = ActionChains(driver)
22
action.move_to_element(bottomsButton).perform()
23
24
# Conclude the test run by implementing a click command on the "Pants" button that leads you to the Men's Pants page
25
pantsButton = driver.find_element(By.ID, "ui-id-23")
26
pantsButton.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:

1
NoSuchElementException: 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.

Adding Selenium Wait Commands

Now, let's see how these three tests can be improved by adding some of Selenium's wait commands.

Improving Test 1 with the Implicit Wait Command

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:

1
from selenium import webdriver
2
from selenium.webdriver import Keys
3
from selenium.webdriver.common.by import By
4
5
print("test case started")
6
driver = webdriver.Chrome()
7
8
# Maximize the window size
9
driver.maximize_window()
10
11
# Navigate to the URL
12
driver.get("https://magento.softwaretestingboard.com/")
13
print(driver.title)
14
15
searchBtn = driver.find_element(By.ID, 'search')
16
searchBtn.send_keys("backpack")
17
searchBtn.send_keys(Keys.ENTER)
18
19
backpack = driver.find_element(By.XPATH, "//*[contains(text(), 'Driven Backpack')]")
20
backpack.click()
21
22
addToCart = driver.find_element(By.ID, 'product-addtocart-button')
23
addToCart.click()
24
25
# Implicit wait
26
driver.implicitly_wait(5)
27
28
cart = driver.find_element(By.XPATH, "//a[normalize-space()='shopping cart']")
29
cart.click()
30
31
quantity = 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]")
32
quantity.clear()
33
quantity.send_keys("2")
34
print("item quantity changed successfully")

Improving Test 2 with the Explicit Wait Command

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:

1
wait = 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:

1
wait.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:

1
from selenium import webdriver
2
from selenium.webdriver.common.by import By
3
from selenium.webdriver.support.wait import WebDriverWait
4
from selenium.webdriver.support import expected_conditions as EC
5
6
print("test case started")
7
driver = webdriver.Chrome()
8
9
# Maximize the window size
10
driver.maximize_window()
11
12
# Navigate to the URL
13
driver.get("https://magento.softwaretestingboard.com/")
14
print(driver.title)
15
16
# Explicit wait
17
wait = WebDriverWait(driver, 10)
18
largeSize = wait.until(EC.element_to_be_clickable((By.XPATH, "//div[@class='swatch-opt-158']//div[@id='option-label-size-143-item-169']")))
19
largeSize.click()
20
21
blackColor = driver.find_element(By.XPATH, "//div[@id='option-label-color-93-item-49']")
22
blackColor.click()
23
24
addToCart = 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]")
25
addToCart.click()
26
27
cart = driver.find_element(By.XPATH, "//a[@class='action showcart']")
28
cart.click()
29
30
# Explicit wait
31
topCartButton = wait.until(EC.visibility_of_element_located((By.XPATH, "//a[@class='action showcart']")))
32
topCartButton.click()
33
34
product = wait.until(EC.visibility_of_element_located((By.XPATH, "//a[@data-bind='attr: {href: product_url}, html: product_name']")))
35
36
print(product.text)

Improving Test 3 with the Fluent Wait Command

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:

1
WebDriverWait(driver, <number of seconds>, poll_frequency=<number of seconds>, ignored_exceptions=[<exception type>, <exception type>])
2
wait.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:

1
from selenium import webdriver
2
from selenium.webdriver import ActionChains
3
from selenium.webdriver.common.by import By
4
from selenium.webdriver.support.wait import WebDriverWait
5
from selenium.webdriver.support import expected_conditions as EC
6
from selenium.common import ElementNotVisibleException, ElementNotSelectableException
7
8
print("test case started")
9
driver = webdriver.Chrome()
10
11
# Maximize the window size
12
driver.maximize_window()
13
14
# Navigate to the URL
15
driver.get("https://magento.softwaretestingboard.com/")
16
print(driver.title)
17
18
# Fluent wait
19
wait = WebDriverWait(driver, 10, poll_frequency=1, ignored_exceptions=[ElementNotVisibleException, ElementNotSelectableException])
20
21
menTab = 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]")))
22
action = ActionChains(driver)
23
action.move_to_element(menTab).perform()
24
25
bottomsButton = wait.until(EC.visibility_of_element_located((By.ID, "ui-id-18")))
26
action = ActionChains(driver)
27
action.move_to_element(bottomsButton).perform()
28
29
pantsButton = driver.find_element(By.ID, "ui-id-23")
30
pantsButton.click()
31
32
print(driver.title)

Are You Using Selenium Wait Commands?

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.

Need to test right now? Get started free.

Ship code that behaves exactly as it should, faster.

© 2023 Sauce Labs Inc., all rights reserved. SAUCE and SAUCE LABS are registered trademarks owned by Sauce Labs Inc. in the United States, EU, and may be registered in other jurisdictions.