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://google.com") 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://google.com") 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:
$ ruby watir_tc_google.rb yes, found the text $
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://google.com") @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:
$ ruby watir_tc_google.rb Loaded suite watir_tc_google Started F Finished in 3.487 seconds. 1) Failure: test_Watir_search(TestGoogle) [watir_tc_google.rb:16]: Couldn't find text: 'Ruby rocks!'. <nil> is not true. 1 tests, 2 assertions, 1 failures, 0 errors $
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://google.com") @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:
$ ruby watir_tc_google.rb Loaded suite watir_tc_google Started . Finished in 3.975 seconds. 1 tests, 2 assertions, 0 failures, 0 errors $
That's better. ![]()
Avoiding duplication (DRY)
Reference: See Wikipedia's entry for Don't Repeat Yourself (DRY)
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://google.com") @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://google.com") @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://google.com") @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://google.com") @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://google.com") @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).
Comments (2)
Jun 13, 2008
Alan Baird says:
"require 'rubygems'" is not needed if you are using the One Click Installer sinc..."require 'rubygems'" is not needed if you are using the One Click Installer since it is automatically required.
May 10, 2010
Mark Hampton says:
Hi, I wanted to have an example running on Firefox. The new Browser interface ma...Hi, I wanted to have an example running on Firefox. The new Browser interface makes for an easy way to support both IE and Firefox. Also Google seems to have changed and we need to wait for the search results to be displayed (it seems). Below is the example with those changes ans the tests passing.