Tuesday, February 1, 2011

How to Create watir Frame work : Test Driver tc_main.rb

Web Application Testing in Ruby: How to Create watir Frame work ?


$:.unshift File.join(File.dirname(__FILE__), ".", "lib")

002 require 'watir'

003 require 'roo'

004 require 'test/unit'

005 require 'customer'

006 require 'admin'

007 $stdout = File.new('log.txt',File::WRONLY|File::APPEND|File::CREAT)

008 $stderr = File.new('log.txt',File::WRONLY|File::APPEND|File::CREAT)

009

010 class TC_WatirMelon < Test::Unit::TestCase

011 @@colmap = {:module_name=>0, :method_name=>1, :comments=>2, :exp_outcome=>3, :exp_error=>4, :first_param=>5}

012 @@ss_format = ARGV[0]

013 @@specified_browser = ARGV[1]

014

015 def setup

016 puts "[Starting at #{Time.now}]\n"

017 case @@ss_format

018 when "excel"

019 @ss = Excel.new("watirmelon.xls")

020 when "wiki"

021 @ss = Excel.new("http://localhost:8080/download/attachments/2097153/watirmelon.xls")

022 when "gdocs"

023 @ss = Google.new("0AtL3mPY2rEqmdEY3XzRqUlZKSmM5Z3EtM21UdFdqb1E")

024 else

025 @ss = Openoffice.new("watirmelon.ods")

026 end

027 @ss.default_sheet = @ss.sheets.first

028 case @@specified_browser

029 when "firefox"

030 Watir::Browser.default = 'firefox'

031 @browser = Watir::Browser.new

032 else

033 Watir::Browser.default = 'ie'

034 @browser = Watir::Browser.new

035 @browser.speed = :zippy

036 @browser.visible = true

037 end

038 end

039

040 def test_run_sheet()

041 @ss.first_row.upto(@ss.last_row) do |row|

042 #Read row into array

043 line = Array.new

044 @ss.first_column.upto(@ss.last_column) do |column|

045 line << @ss.cell(row, column).to_s.strip

046 end

047

048 module_name = line[@@colmap[:module_name]]

049 if module_name != "Function" then #if not a header

050 method_name = line[@@colmap[:method_name]].downcase.gsub(' ','_') #automatically determine ruby method name based upon data sheet

051 exp_outcome = line[@@colmap[:exp_outcome]]

052 exp_error = line[@@colmap[:exp_error]]

053 first_param = @@colmap[:first_param]

054 required_module = Kernel.const_get(module_name)

055 required_method = required_module.method(method_name)

056 arity = required_method.arity() # this is how many arguments the method requires, it is negative if a 'catch all' is supplied.

057 arity = ((arity * -1) - 1) if arity < 0 # arity is negative when there is a 'catch all'

058 arity = arity-1 # Ignore the first browser parameter

059 unless arity == 0

060 parameters = line[first_param..first_param+(arity-1)]

061 else

062 parameters = []

063 end

064 begin

065 act_outcome, act_output = required_method.call(@browser, *parameters)

066 rescue Test::Unit::AssertionFailedError => e

067 self.send(:add_failure, e.message, e.backtrace)

068 act_outcome = false

069 act_output = e.message

070 end

071 if (exp_outcome == 'Success') and act_outcome then

072 assert(true, "Expected outcome and actual outcome are the same")

073 result = 'PASS'

074 elsif (exp_outcome == 'Error') and (not act_outcome) and (exp_error.strip! == act_output.strip!)

075 assert(true, "Expected outcome and actual outcome are the same, and error messages match")

076 result = 'PASS'

077 else

078 result = 'FAIL'

079 begin

080 assert(false,"Row: #{row}: Expected outcome and actual outcome for #{method_name} for #{module_name} do not match, or error messages do not match.")

081 rescue Test::Unit::AssertionFailedError => e

082 self.send(:add_failure, e.message, e.backtrace)

083 end

084 end

085 puts "###########################################"

086 puts "[Running: #{module_name}.#{method_name}]"

087 puts "[Expected Outcome: #{exp_outcome}]"

088 puts "[Expected Error: #{exp_error}]"

089 puts "[Actual Outcome: Success]" if act_outcome

090 puts "[Actual Outcome: Error]" if not act_outcome

091 puts "[Actual Output: #{act_output}]"

092 puts "[RESULT: #{result}]"

093 puts "###########################################"

094 end

095 end

096 end

097

098 def teardown

099 @browser.close

100 puts "[Finishing at #{Time.now}]\n\n"

101 end

102

103 end

How to Create watir Frame work ?

One common challenge I see over and over again is people figuring out how to design a logical and maintainable automated testing framework. I have designed quite a few frameworks for various projects, but one thing that has consistently been a win for me is purposely separating test case and test execution design.

It’s therefore logical that the design of my Watir framework deliberately separates test case design and test execution design so that:

■test case design is done visually in spreadsheets; and
■test execution design is done in ruby methods, because code is the most efficient and maintainable way.
Since I last published details about my framework on this blog, I have started doing assertions using the Test::Unit ruby library. The reasons I chose Test::Unit are:

■it is easy to ‘mix-in’ Test::Unit assertions into modules of ruby code using include Test::Unit::Assertions;
■it is included with ruby;
■ruby scripts with Test::Unit::TestCase are instantly executable, in my case, from SciTE;
■its assertions are easy to understand and use.
I have also made some other improvements to my framework code, including:

■the ability to specify browser types, and spreadsheet sources, as command line arguments (with defaults);
■logging test output to a file;
■no longer attaching to an open browser, the same browser instance is used completely for all tests (and elegantly closed at the end).
The main design has been kept the same, in that a spreadsheet (either excel, openoffice or Google Docs) contains tests grouped by functional area, which call a method in a particular module.

The great thing about my framework is that adding a new test is a matter of designing the test case, and then writing the ruby method: as the methods are called dynamically from the spreadsheet, no extra glue is needed!

Enough talk, here’s the code. The Google spreadsheet is here. You can find a .zip file of all the required files to run it here. It runs on the depot app, which you get here. You will need two gems: Watir (oh duh), and Roo.