I spent the greater part of a day getting familiar with Selenium. Selenium is, as per its homepage, a suite of tools for automating web browsers. So, why would I want to bother automating browsers? Excellent question.

Selenium provides developers with an easy way to automate testing common use cases. For example, If I want to make sure that when a user is attempting to login to a page and they hit the enter key after entering there credentials it works, in Firefox, or Chrome, or IE, I can do that. Furthermore combined with tools like Sauce Labs I can automate this one step further. I can write one test and now I’m testing that the my code runs as expected (or discovering it doesn’t) on up to 128 different browser and version combinations! That is powerful.

Continuing on with the aforementioned login example, let’s dig into some actual code.

This is an example of an early version of a test I wrote in Python using Selenium’s WebDriver.

First, unittest and the relevant selenium utils are imported: webdriver and Keys. The former allows Selenium to use a native browser and Keys provides functionality replicating key strokes and mouse clicks.

import unittest from selenium import webdriver from selenium.webdriver.common.keys import Keys class AccountProfileTest(unittest.TestCase):

After subclassing Python’s unittest, setUp and tearDown methods allow us to properly prepare the environment for each test as well as take care of any cleanup necessary after it’s been run. The setUp function is where we will be focusing as it is where the login is performed.

First, test data is declared that will be used during user creation. Next, we launch a Firefox instance and direct it toward the desired url. The implicitly_wait function ensures our driver will allow the page to load before attempting to ‘find’ elements within the code. This prevents the driver from failing to locate specific elements. It should be noted that although Selenium is pointed a a local web server, this test was constructed for openscienceframework.org.

# initialize a Firefox webdriver def setUp(self): # setup browser and test user data self.user_email = 'test@test.com' self.user_password = 'testtest' self.user_name = 'Testy McTester' self.driver = webdriver.Firefox() # ensure driver allows page source to load before continuing self.driver.implicitly_wait() # load OSF homepage self.driver.get('http://localhost:5000')

Here is where it gets interesting. Selenium is a browser automation tool, right? Right. So how do we replicate a mouse click on a specific button? Well, first we need to find it. As an aside, I _highly_ recommend using Firebug or a similar tool if you decide to play with Selenium and manually walk through test development. After first locating the element you want to click in the source we use one of Selenium’s find_element_by_* methods to focus on it. Selenium provides many find functions such as id, css, text, link_text, and xpath to name a few. Futhermore, you have the option of either find_element_by_* to locate the first element found or find_elements_by_* which returns a list of all elements found matching the pattern. In this example I focus on xpath but give examples of grabbing both a single element as well as a list.

The first find method I call is for the a link on the page directed toward the ‘/account’ url. Clicking it with a mouse is emulated by chaining the click method onto the end of the call.

# load login page self.driver.find_element_by_xpath('//a[@href="/account"]').click()

After the page has loaded (as per driver.implicitly_wait() in the setUp method), the driver locates and assigns a list of username and password fields to username_fields and password_fields respectively. You may have noticed that find_elements_by_xpath was used here. find_element_by_* would have worked all the same but I thought it would be nice to include an example.

# grab the username and password fields username_fields = self.driver.find_elements_by_xpath('//form[@name="signin"]//input[@id="username"]') password_fields = self.driver.find_elements_by_xpath('//form[@name="signin"]//input[@id="password"]')

Now that we have a list of username and password fields how do we enter Testy McTester’s login credentials? Selenium makes this easy for us with the send_keys method. As with the simulated mouse click, once you have located the pointer call the appropriate method, send_keys, along with whatever you desire. Here, point at the first username and password field in each list and send the user_email and user_password constants declared in setUp.

# enter the username / password username_fields[0].send_keys(self.user_email) password_fields[0].send_keys(self.user_password)

Finding the ‘sign in’ submit button is a bit trickier. First, we must find all button that have ‘submit’ types. Next, we iterate through the returned list and find the button whose text is ‘sign in.’ Once we have it, we can simulate the clicking it with a mouse with the click method.

# locate and click login button submit_buttons = self.driver.find_elements_by_xpath('//button[@type="submit"]') submit_button = [b for b in submit_buttons if b.text.lower() == 'sign in'][0] submit_button.click()

After setUp has completed, the first unittest below will be executed and any assertions declared will be tested. After it has completed the tearDown function will execute. Here, it simply closes the Firefox instance deleting any cookies and clearing the cache to ensure that if setUp is called again it will have a clean slate.

# close the Firefox webdriver def tearDown(self): self.driver.close()

And that’s it. This is a very simple example of what Selenium can do and by no means shows anywhere near all of its potential. Not only is Selenium an incredibly powerful tool but it is really fun to work with as well. Manually walking through each step in the browser with Firebug provided me with a pleasant escape from the standard code, code, code.

The current version of this test, and many, many more are up on GitHub and are free for use. My co-workers and I would also appreciate any feedback you may have!

-H.