This post is the third in a series of “Getting Started with Selenium Testing” posts from Dave Haeffner, a noted expert on Selenium and automated testing, and a frequent contributor to the Sauce blog and Selenium community. This series is for those who are brand new to test automation with Selenium and a new chapter will be posted every Tuesday (eight chapters in all).
Fundamentally, Selenium works with two pieces of information -- the element on a page you want to use and what you want to do with it. This one-two punch will be repeated over and over until you achieve the outcome you want in your application -- at which point you will perform an assertion to confirm that the result is what you intended. Let's take logging in to a website as an example. With Selenium you would: 1. Visit the main page of a site 2. Find the login button and click it 3. Find the login form's username field and input text 4. Find the login form's password field and input text 5. Find the login form and click it Selenium is able to find and interact with elements on a page by way of various locator strategies. The list includes Class, CSS, ID, Link Text, Name, Partial Link Text, Tag Name, and XPath. While each serves a purpose, you only need to know a few to start writing effective tests.
The simplest way to find locators is to inspect the elements on a page. The best way to do this is from within your web browser. Fortunately, popular browsers come pre-loaded with development tools that make this simple to accomplish. When viewing the page, right-click on the element you want to interact with and click Inspect Element. This will bring up a small window with all of the HTML for the page but zoomed into your highlighted selection. From here you can see if there are unique or descriptive attributes you can work with.
Your focus with picking an effective element should be on finding something that is unique, descriptive, and unlikely to change. Ripe candidates for this are id
and class
attributes. Whereas copy (e.g., text, or the text of a link) is less ideal since it is more apt to change. This may not hold true for when you make assertions, but it's a good goal to strive for. If the elements you are attempting to work with don't have unique id
and class
attributes directly on them, look at the element that houses them (a.k.a. the parent element). Oftentimes the parent element has a unique locator that you can use to start with and drill down into the element you want to use (with CSS selectors). And if you can't find any unique elements, have a conversation with your development team letting them know what you are trying to accomplish. It's not hard for them to add helpful, semantic markup to make test automation easier, especially when they know the use case you are trying to automate. The alternative can be a lengthy, painful process which will probably yield working test code -- but it will be brittle and hard to maintain.
There are five parts to writing a Selenium test: 1. Find the elements you want to use 2. Write a test with Selenium actions that use these elements 3. Figure out what to assert 4. Write the assertion and verify it 5. Double-check the assertion by forcing it to fail As part of writing your Selenium test, you will also need to create and destroy a browser instance. This is something that we will pull out of our tests in a future lesson, but it's worth knowing about up front. Let's take our login example from above and step through the test writing process.
The best way to find the Selenium actions for your specific language is to look at the available language binding wiki pages (Ruby, Python,JavaScript) or the Selenium HQ getting started documentation for WebDriver. For this example, I'll be using the Ruby programming language, RSpec (an open-source testing framework written in Ruby), and the-internet (an open-source example application I built).
Let's use the login example on the-internet). Here's the markup from the page.
html
Username
Password
Login
Note the unique elements on the form. The username input field has a unique id
, as does the password input field. The submit button doesn't, but the parent element (form
) does. So instead of clicking the submit button, we will have to submit the form instead. Let's put these elements to use in our first test (or 'spec' as it's called in RSpec).
# filename: login_spec.rb
require 'selenium-webdriver'
describe 'Login' do
before(:each) do @driver = Selenium::WebDriver.for :firefox end
after(:each) do @driver.quit end
it 'succeeded' do @driver.get 'http://the-internet.herokuapp.com/login' @driver.find_element(id: 'username').send_keys('tomsmith') @driver.find_element(id: 'password').send_keys('SuperSecretPassword!') @driver.find_element(id: 'login').submit end
end
If we run this (e.g., rspec login_spec.rb
from the command-line), it will run and pass. But there's one thing missing - an assertion. In order to find an element to make an assertion against, we need to see what the markup is after submitting the login form.
Here is the markup that renders on the page after submitting the login form.
You logged into a secure area! x
After logging in, there looks to be a couple of things we can key off of for our assertion. There's the flash message class (most appealing), the logout button (appealing), or the copy from the h2 or the flash message (least appealing). Since the flash message class name is descriptive, denotes success, and is less likely to change than the copy, let's go with that.
# filename: login_spec.rb
require 'selenium-webdriver'
describe 'Login' do
before(:each) do @driver = Selenium::WebDriver.for :firefox end
after(:each) do @driver.quit end
it 'successful' do @driver.get 'http://the-internet.herokuapp.com/login' @driver.find_element(id: 'username').send_keys('username') @driver.find_element(id: 'password').send_keys('password') @driver.find_element(id: 'login').submit @driver.find_element(css: '.flash.success').displayed?.should be_true end
end
Now when we run this test (rspec login_spec.rb
from the command-line) it will pass just like before, but now there is an assertion which should catch a failure if something is amiss. Just to make certain that this test is doing what we think it should, let's change the assertion to force a failure and run it again. A simple fudging of the locator will suffice.
@driver.find_element(css: '.flash.successasdf').displayed?.should be_true
Dave is the author of Elemental Selenium (a free, once weekly Selenium tip newsletter that is read by hundreds of testing professionals) as well as a new book, The Selenium Guidebook. He is also the creator and maintainer of ChemistryKit (an open-source Selenium framework). He has helped numerous companies successfully implement automated acceptance testing; including The Motley Fool, ManTech International, Sittercity, and Animoto. He is a founder and co-organizer of the Selenium Hangout and has spoken at numerous conferences and meetups about acceptance testing.