Introduction
This is a very basic introduction to the design of a Watir test suite. Thanks to FinnTechAS for contributing this example.
Driving IE with Watir
The Watir library (a Ruby gem) lets you control Internet Explorer.
require "rubygems"
require "watir"
ie = Watir::IE.new
ie.goto("http:)
ie.text_field(:name, 'q').value = "Watir"
ie.button(:name, 'btnG').click
This script will go to Google, type "Watir" in the search box and click the Search button.
As you see from the example above, Watir lets you identify objects in the IE DOM using the syntax .element_type(how, what).
In this example we tell it to look for a text field where the name attribute is set to q. When we call the Watir::IE#text_field method, it will return an instance of Watir::TextField. Let's add a couple of lines to our script:
if ie.contains_text("Watir is a simple open-source library for automating web browsers")
puts "yes, found the text"
else
puts "no, couldn't find the text"
end
ie.close
This version will check if the result list contains the text we expect, print out a relevant message depending on the result (puts means 'put string'), then close the IE window. The complete script now looks like this:
require "rubygems"
require "watir"
ie = Watir::IE.new
ie.goto("http:)
ie.text_field(:name, 'q').value = "Watir"
ie.button(:name, 'btnG').click
if ie.contains_text("Watir is a simple open-source library for automating web browsers")
puts "yes, found the text"
else
puts "no, couldn't find the text"
end
ie.close
Dowload this example: google-extended1.rb
Let's save this to a file called watir_tc_google.rb and run it from the command line:
Test::Unit
This is (almost) the same test using Ruby's built-in Test::Unit framework:
require "rubygems"
require "watir"
require "test/unit"
class TestGoogle < Test::Unit::TestCase
def setup
@ie = Watir::IE.new
end
def test_watir_search
@ie.goto("http:)
@ie.text_field(:name, 'q').value = "Watir"
@ie.button(:name, 'btnG').click
assert(@ie.contains_text("Watir is a simple open-source library for automating web browsers"), "Couldn't find text: Watir is a simple...")
assert(@ie.contains_text("Ruby rocks!"), "Couldn't find text: 'Ruby rocks!'")
end
def teardown
@ie.close
end
end
Dowload this example: google-extended2.rb
We're creating a test case called TestGoogle, which is a subclass of Test::Unit::TestCase. The Test::Unit framework will look for and run all the methods that start with test (in this case, it's only one; test_watir_search). The setup and teardown methods are automatically called by the framework before and after every test method. It also provides some useful methods like assert, assert_equal, among others
, which can be given a failure message argument that will be added to the test output if the assertion fails. Notice that the ie variable now is called @ie. The @ sign makes it an instance variable - the scope is thus not local to the setup method, but lets our test_watir_search method access the variable as well.
The framework will also automatically run the test case if we execute the file. Here's the output when we run it from the command line:
Oops, our second assertion failed. Perhaps someone should fix that. For now, we'll just make our test pass by replacing assert with assert_nil, and change the failure message accordingly:
def test_watir_search
@ie.goto("http:)
@ie.text_field(:name, 'q').value = "Watir"
@ie.button(:name, 'btnG').click
assert(@ie.contains_text("Watir is a simple open-source library for automating web browsers"), "Couldn't find text: Watir is a simple...")
assert_nil(@ie.contains_text("Ruby rocks!"), "Couldn't find text: 'Ruby rocks!'")
end
Dowload this example: google-extended2-fixed.rb
nil is Ruby's idea of 'no value', and incidentally it's exactly what the contains_text method will return if it doesn't find the text we give it. Now let's see:
That's better. 
Avoiding duplication
We now have a test that searches Google and looks for a specific text in the result list. But what if we want to search for another text? Perhaps we want to learn about some popular programming paradigms. We could do it like this:
require "rubygems"
require "watir"
require "test/unit"
class TestGoogle < Test::Unit::TestCase
def setup
@ie = Watir::IE.new
end
def test_watir_search
@ie.goto("http:)
@ie.text_field(:name, 'q').value = "Watir"
@ie.button(:name, 'btnG').click
assert(@ie.contains_text("Watir is a simple open-source library for automating web browsers"), "Couldn't find text: Watir is a simple...")
assert_nil(@ie.contains_text("Ruby rocks!"), "Found unexpected text: 'Ruby rocks!'")
end
def test_dry_search
@ie.goto("http:)
@ie.text_field(:name, 'q').value = "don't repeat yourself"
@ie.button(:name, 'btnG').click
assert(@ie.contains_text("(DRY) is a statement exhorting developers to avoid"))
end
def teardown
@ie.close
end
end
Dowload this example: google-extended3-new-test.rb
This works fine - but if for some reason the name attribute of Google's search button (or anything else on the page) changes we'll have to edit the script twice, or maybe more if we create more tests. Let's extract the relevant code to a method, that takes the search query as an argument:
def simple_search(query)
@ie.goto("http:)
@ie.text_field(:name, 'q').value = query
@ie.button(:name, 'btnG').click
end
Now our script looks like this:
require "rubygems"
require "watir"
require "test/unit"
class TestGoogle < Test::Unit::TestCase
def setup
@ie = Watir::IE.new
end
def test_watir_search
simple_search("Watir")
assert(@ie.contains_text("Watir is a simple open-source library for automating web browsers"), "Couldn't find text: Watir is a simple...")
assert_nil(@ie.contains_text("Ruby rocks!"), "Found unexpected text: 'Ruby rocks!'")
end
def test_dry_search
simple_search("don't repeat yourself")
assert(@ie.contains_text("(DRY) is a statement exhorting developers to avoid"))
end
def teardown
@ie.close
end
#========= HELPER METHODS =========
private
def simple_search(query)
@ie.goto("http:)
@ie.text_field(:name, 'q').value = query
@ie.button(:name, 'btnG').click
end
end
Dowload this example: google-extended4.rb
Creating a class
Adding helper methods to the test case is a step forward, but we'll run into problems if we want to use our simple_search method in another file (that is, another test case). Eventually we'll also want to store and reuse more information (like error messages, database connections), specific to some domain (like the app under test). The solution is to create a class. Here's what our Google test looks like, refactored:
require "rubygems"
require "watir"
require "test/unit"
class Google
attr_accessor :ie
def initialize
@ie = Watir::IE.new
end
def simple_search(query)
@ie.goto("http:)
@ie.text_field(:name, 'q').value = query
@ie.button(:name, 'btnG').click
end
def login_gmail
# code
end
def check_calendar(date)
# code
end
def close
@ie.close
end
end
class TestGoogle < Test::Unit::TestCase
def setup
@site = Google.new
end
def test_watir_search
@site.simple_search("Watir")
assert(@ie.contains_text("Watir is a simple open-source library for automating web browsers",
"Couldn't find text: Watir is a simple..."))
assert_nil(@site.ie.contains_text("Ruby rocks!", "Found unexpected text: 'Ruby rocks!'"))
end
def test_dry_search
@site.simple_search("don't repeat yourself")
assert(@site.ie.contains_text("a statement exhorting developers to avoid duplication in code"))
end
def teardown
@site.close
end
end
Dowload this example: google-extended5.rb
Our class is called Google, and the idea is to encapsulate everything we might want to do on that site - like log in to Gmail or check our appointments for a specific date. The initialize method is what gets called when we later instantiate the class (Google.new in the test case). We are wrapping the Watir::IE object inside our Google class, so when we want to call it from our test scripts, we need to use @site.ie (assuming the variable holding our Google instance is called @site). By typing "attr_accessor :ie" at the top of our class, Ruby will automatically create methods for accessing or changing this instance variable ("getter" and "setter" methods).
"require 'rubygems'" is not needed if you are using the One Click Installer since it is automatically required.