logo logo

Write Your First Web Test Using Selenium WebDriver, Python and Chrome

main post image

This tutorial will make web UI testing easy. We will build a simple yet robust web UI test solution using Python, pytest, and Selenium WebDriver. We will learn strategies for good test design as well as patterns for good automation code. By the end of the tutorial, you’ll be a web test automation champ! Your Python test project can be the foundation for your own test cases, too.

Tutorial Chapters

  1. Set Your Test Automation Goals (Chapter 1)
  2. Create A Python Test Automation Project Using Pytest (Chapter 2)
  3. Installing Selenium WebDriver Using Python and Chrome (Chapter 3)
  4. You’re here → Write Your First Web Test Using Selenium WebDriver, Python and Chrome (Chapter 4)
  5. Develop Page Object Selenium Tests Using Python (Chapter 5)
  6. How to Read Config Files in Python Selenium Tests (Chapter 6)
  7. Take Your Python Test Automation To The Next Level (Chapter 7)

With WebDriver ready to go, let’s write our first web test! The test will be a simple DuckDuckGo search. DuckDuckGo is a search engine that doesn’t track user data. Users can enter search phrases and get links to matching websites, just like any other search engine.

Before writing automation code, it’s always best to write the test procedure in plain language. Writing the procedure forces us to think first and foremost about the behavior under test. Here’s our test procedure:

  1. Navigate to the DuckDuckGo home page
  2. Enter the search phrase
  3. Verify that:
    1. Results appear on the results page
    2. The search phrase appears in the search bar
    3. At least one search result contains the search phrase

It’s fairly basic, but it covers typical searching behavior end-to-end.

The Code

Add the following test function to tests/test_web.py:

def test_basic_duckduckgo_search(browser):
  URL = 'https://www.duckduckgo.com'
  PHRASE = 'panda'
  
  browser.get(URL)
  
  search_input = browser.find_element_by_id('search_form_input_homepage')
  search_input.send_keys(PHRASE + Keys.RETURN)
  
  link_divs = browser.find_elements_by_css_selector('#links > div')
  assert len(link_divs) > 0
  
  xpath = f"//div[@id='links']//*[contains(text(), '{PHRASE}')]"
  results = browser.find_elements_by_xpath(xpath)
  assert len(results) > 0
  
  search_input = browser.find_element_by_id('search_form_input')
  assert search_input.get_attribute('value') == PHRASE

The test_basic_duckduckgo_search function implements our test procedure following the Arrange-Act-Assert pattern. Notice that the test function declares a parameter named browser, which is the same name as our fixture for ChromeDriver setup and cleanup. pytest will automatically call the fixture and inject the WebDriver reference whenever this test is run. The test function then uses the browser variable to make several WebDriver calls. Let’s see how those calls work.

Arrange

URL = 'https://www.duckduckgo.com'

The test declares the URL for the DuckDuckGo home page as a variable for readability and maintainability.

PHRASE = 'panda'

This is the search phrase that the test will use. Since the test covers a “basic” search, the phrase doesn’t matter too much. Other tests exercising different behaviors should use more complex phrases. Again, the test declares it at the top of the test function for readability and maintainability.

browser.get(URL)

The starting point for the test is the DuckDuckGo home page. This call navigates the browser to the given URL. Be warned, though: this call does not wait for the page to load. It simply initiates the loading interaction.

Act

search_input = browser.find_element_by_id('search_form_input_homepage')

The first step to automating web interactions is to find the target element(s). Elements may or may not appear on the page. Automation must use a locator to find the element if it does exist and then construct an object representing that element. There are many types of locators: IDs, class names, CSS selectors, XPaths, and more. Locators will find all matching elements on the page – there could be more than one. Try to use the simplest locator that will uniquely identify the target element(s).

To write locators, you need to see the HTML structure of the page. Chrome DevTools makes it easy to inspect the markup of any live page. Simply right-click the page and choose “Inspect”. You can see all elements on the “Elements” tab. For our test, we want to find the search input field on the DuckDuckGo home page. That element has an id attribute with the value “search_form_input_homepage”, as shown below:

DuckDuckGo Search Input

We can get that element using WebDriver’s find_element_by_id method. The search_input variable is assigned the object representing the search input element on the page. Remember, since the WebDriver instance has an implicit wait, it will wait up to 10 seconds for the search input element to appear on the page.

search_input.send_keys(PHRASE + Keys.RETURN)

With the element in hand, we can trigger interactions with it. The send_keys method sends a sequence of keystrokes to the search input element, just like a human user would type at a keyboard. The call above sends the search phrase. The RETURN key at the end submits the search.

Assert (1)

link_divs = browser.find_elements_by_css_selector('#links > div')

The result page should display a div element with an ID named “links” that has a child div element for each result link. The CSS selector above finds all such result link divs. Notice that “elements” is plural – this call will return a list.

assert len(link_divs) > 0

The test must verify that results did actually appear for the search phrase. This assert statement makes sure that at least one result link was found on the page.

Assert (2)

xpath = f"//div[@id='links']//*[contains(text(), '{PHRASE}')]"

Verifying that some results appeared is good, but we should also verify that the results match our search phrase. We can use an XPath to pinpoint result links that contain our search phrase in the text. XPaths are more complex than names and CSS selectors, but they are also more powerful. The XPath above searches for any div with the ID of “links”, and then it looks for descendants that contain the search phrase text.

(Does the “f” before the string look new to you? That’s an f-string, and it makes string formatting easy!)

phrase_results = browser.find_elements_by_xpath(xpath)

This call finds all elements using the previously concatenated XPath. We could have combined these two lines into one, but splitting the lines is more readable and more Pythonic.

assert len(phrase_results) > 0

Like the previous assertion, this one makes sure that at least one element was found. It’s a simple sanity check. It could be made to be more robust – like verifying that every result on the page contains the search phrase text – but that would be hard. Not every result may contain the exact text of the search phrase. For example, some might have uppercase characters. Locators and logic would need to be much more complicated for an advanced verification. Since this is a basic search test, the simpler assertion should be sufficient.

Assert (3)

search_input = browser.find_element_by_id('search_form_input')

The final assertion verifies that the search phrase still appears in the search input. The line above is identical to the find element call from the Arrange phase. It finds the search input element again. Why can’t we just use the search_input object again? Unfortunately, the previous element has gone stale. The page changed from the search page to the results page. Even though the element looks the same, it is different, and it also needs a new locator. Therefore, we need to fetch it freshly.

assert search_input.get_attribute('value') == PHRASE

The text typed into an input element is accessible as its “value” attribute. This line asserts that the “value” attribute is equal to the search phrase. It verifies that the phrase hasn’t disappeared.

Review and Run the Web Test

The full code for tests/test_web.py should now look like this (with additional comments for clarity):

"""
This module contains web test cases for the tutorial.
Tests use Selenium WebDriver with Chrome and ChromeDriver.
The fixtures set up and clean up the ChromeDriver instance.
"""

import pytest

from selenium.webdriver import Chrome
from selenium.webdriver.common.keys import Keys


@pytest.fixture
def browser():
  # Initialize ChromeDriver
  driver = Chrome()

  # Wait implicitly for elements to be ready before attempting interactions
  driver.implicitly_wait(10)
  
  # Return the driver object at the end of setup
  yield driver
  
  # For cleanup, quit the driver
  driver.quit()


def test_basic_duckduckgo_search(browser):
  # Set up some test case data
  URL = 'https://www.duckduckgo.com'
  PHRASE = 'panda'

  # Navigate to the DuckDuckGo home page
  browser.get(URL)

  # Find the search input element
  # In the DOM, it has an 'id' attribute of 'search_form_input_homepage'
  search_input = browser.find_element_by_id('search_form_input_homepage')

  # Send a search phrase to the input and hit the RETURN key
  search_input.send_keys(PHRASE + Keys.RETURN)

  # Verify that results appear on the results page
  link_divs = browser.find_elements_by_css_selector('#links > div')
  assert len(link_divs) > 0

  # Verify that at least one search result contains the search phrase
  xpath = f"//div[@id='links']//*[contains(text(), '{PHRASE}')]"
  phrase_results = browser.find_elements_by_xpath(xpath)
  assert len(phrase_results) > 0

  # Verify that the search phrase is the same
  search_input = browser.find_element_by_id('search_form_input')
  assert search_input.get_attribute('value') == PHRASE

Now, run the test to make sure it works:

$ pipenv run python -m pytest
============================= test session starts ==============================
platform darwin -- Python 3.7.3, pytest-4.5.0, py-1.8.0, pluggy-0.12.0
rootdir: /Users/andylpk247/Programming/automation-panda/python-webui-testing
collected 9 items                                                              

tests/test_math.py ........                                              [ 88%]
tests/test_web.py .                                                      [100%]

=========================== 9 passed in 6.10 seconds ===========================

When the web test runs, it will open Google Chrome. You can watch it automatically enter the search phrase, wait for the results page, and then quit the browser. Neat!

DuckDuckGo in Chrome

If the test fails to run, check the following:

  • Does the test machine have Chrome installed?
  • Is ChromeDriver on the system path?
  • Does the ChromeDriver version match the Chrome version?
  • Are there any filesystem permission problems?
  • Are firewalls blocking any ports?
  • Is the test code correct?

Improvements?

Congrats on writing your first web UI test! It certainly takes more work than writing unit tests.

Even though the test runs successfully, there are a few things we could do to improve its code. In the next chapter, we’ll refactor this test using the Page Object Pattern.

 

[Update on August 25, 2019: Changes to the DuckDuckGo search page required locator updates for the search input elements.]

 

TestProject Test Automation Tool

Avatar

About the author

AutomationPanda

Andy Knight is the "Automation Panda" - an engineer, consultant, and international speaker who loves all things software. He specializes in building robust test automation systems from the ground up. Read his tech blog at AutomationPanda.com, and follow him on Twitter at @AutomationPanda.

Join TestProject Community

Get full access to the world's first cloud-based, open source friendly testing community. Enjoy TestProject's end-to-end test automation Platform, Forum, Blog and Docs - All for FREE.

Join Us Now

Leave a Reply

Join TestProject Newsletter

Join a 20K community of readers! Always stay up-to-date with all the latest test automation trends, best practice and tips shared by leading software testing community experts across the globe!

FacebookLinkedInTwitterEmail