saush

Using RSpec and Watir for functional testing in web applications

Posted in Ruby by sausheong on May 18, 2008

I started playing around with RSpec the past week while also fiddling with Watir at around the same time, so it wasn’t a big leap of creativity to combine both together.

RSpec is the popular behavior-driven development test framework written in Ruby while Watir (Web Application Testing In Ruby) is a set of Ruby APIs used to automate Internet Explorer, making use of Ruby’s built-in Win32OLE capabilities.

By itself RSpec can only test Ruby applications or scripts. The immense popularity of Ruby on Rails produced RSpec on Rails but personally I didn’t quite like testing each model, controller and view separately and it is more unit testing than functional testing of an entire system. Watir on its own is an excellent tool to automate navigating through web sites and web applications through the Internet Explorer. However it doesn’t provide the kind of structure and driven directions that a BDD test framework like RSpec does.

When combined together, RSpec + Watir makes real magic in doing functional testing for web applications. However you need to realize that Watir only runs on IE (though there exists FireWatir and SafariWatir, both which runs on other platforms for FireFox and Safari respectively). While this seems terribly restrictive to some, I come to realize that the strength of RSpec + Watir is not in testing browser compatibility but in being able to run through the web application as seen and used by the actual user, through an actual browser. RSpec + Watir produces a synergistic capability where each cannot. The combined tool allows us to test any web application (whether Ruby on Rails or not) and to do it in a BDD way.

Installing RSpec and Watir is very simple. You just need to install the RSpec and Watir gems respectively:

c:\> gem install watir
c:\> gem install rspec

And you’re off! There’re dozens of articles and blogs that describe how each tool can be used on its own so in this post I will just focus on describing how they can be used together (you should try to read up a bit on RSpec and Watir first). For this I will try to ‘test’ Facebook, with the following 2 very simple examples:

Facebook functional testing
- Edit my profile
- It should add new work information
- It should upload a new picture
- Navigate the online friends tab
- It should flash every online friend
- It should send the first online friend found a message

Now, we translate this into a properly nested example group in RSpec:

require 'spec'

describe 'Facebook' do
describe '(edit my profile)' do
it 'should add new work information into my profile'
it 'should upload a new picture into my profile'
end

describe '(navigate the online friends tab)' do
it 'should flash every online friend'
it 'should send the first online friend a message'
end

end

To someone who has never used RSpec (or this is the first time you’re encountering RSpec) this looks facetious but trust me in this, we’ll expand it in a while and you’ll see how powerful it can be. For now suspend your disbelief, save the code into a ruby script named fb1.rb and run the following:

c:\> spec fb1.rb --format specdoc

You will an output like this:

Facebook (edit my profile)
- should add new work information (PENDING: Not Yet Implemented)
- should upload a new picture (PENDING: Not Yet Implemented)

Facebook (navigate the online friends tab)
- should flash every online friend (PENDING: Not Yet Implemented)
- should send the first online friend a message (PENDING: Not Yet Implemented)

Pending:
Facebook (edit my profile) should add new work information (Not Yet Implemented)
Facebook (edit my profile) should upload a new picture (Not Yet Implemented)
Facebook (navigate the online friends tab) should flash every online friend (Not Yet Implemented)
Facebook (navigate the online friends tab) should send the first online friend a message (Not Yet Implemented)

Finished in 0.02 seconds

4 examples, 0 failures, 4 pending

It should be clear now (or at least, less fuzzy) why this is called ‘Behavior Driven Development’. In a real development scenario, we’ll be writing such examples before writing any code and this will tell us how the application should behave. After this is done, we’ll switch back to the code and implement the behavior. I use the term ‘we’ loosely because these examples can be written by the end-user. By doing this, we can ‘describe’ how the application should function.

Now that we have the examples described, let’s fill in the bits. In a development scenario, we would have gone back to developing the application but here we’re just going to continue writing the example.

First, let’s require Watir then add in a before and an after block. Before and after blocks are part of RSpec and allows us to set up the necessary objects for testing. In our case we use the before block to set up IE and log into Facebook, while the after block is used to log out of Facebook and close IE.

require 'spec'
require 'watir'

email = ''
password = ''

describe 'Facebook' do
before :all do
$ie = IE.new
$ie.goto('http://www.facebook.com')
$ie.text_field(:id, 'email').set(email)
$ie.text_field(:id, 'pass').set(password)
$ie.button(:id, 'doquicklogin').click
end

describe '(edit my profile)' do
it 'should add new work information'
it 'should upload a new picture'
end

describe '(navigate the online friends tab)' do
it 'should flash every online friend'
it 'should send the first online friend a message'
end

after :all do
$ie.link(:text, 'logout').click
$ie.close
end
end

We start off in the before block by starting up a new IE browser, then directing it to go to Facebook’s main page. After that we look for two text fields with the id ‘email’ and ‘pass’ respectively and fill it up with email and password accordingly. Once that is done, we click on the login button.

The after block is obvious so I won’t elaborate.

Let’s go to the first example, the ‘edit my profile’ block.


describe '(Editing my profile)' do

it 'should add new work information' do
$ie.link(:after? => $ie.link(:text, 'Profile'), :text => 'edit').click
$ie.link(:text, 'Work').click
$ie.text_field(:name, 'work_history_1_company').set('Way Cool Company')
$ie.text_field(:name, 'work_history_1_position').set('Master of the Universe')
$ie.text_field(:name, 'work_history_1_description').set('Lord and Sovereign of All I See')
$ie.text_field(:name, 'work_history_1_location_sq').set('Singapore')
$ie.checkbox(:name, 'work_history_1_workspan_current').set
$ie.select_list(:name, 'work_history_1_workspan_start_month').set('June')
$ie.select_list(:name, 'work_history_1_workspan_start_year').set('2008')
$ie.button(:id, 'save').click
$ie.text.should include('Changes saved.')
end

it 'should upload a new picture' do
$ie.link(:after? => $ie.link(:text, 'Profile'), :text => 'edit').click
$ie.link(:text, 'Picture').click
file = (File.dirname(File.expand_path(__FILE__)) + '\\sausheong.jpg').gsub! '/', '\\'
$ie.file_field(:id, 'pic').set(file)
$ie.checkbox(:id, 'agree').set
$ie.button(:id, 'uploadbutton').click
$ie.text.should include('Your picture has been uploaded successfully.')
end

end

Notice that we added in a do .. end block at the end of the expectation. The code is quite obvious in most parts. The first line in the first expectation block is:


$ie.link(:after? => $ie.link(:text, 'Profile'), :text => 'edit').click

It shouldn’t surprise you that it works exactly how it looks like. This tells IE to look for a link that is after the link that contains the text ‘Profile’, as well as has the text ‘edit’, then click that link.

The last line in the first block uses RSpec syntax again and is very ‘English’-like:


$ie.text.should include('Changes saved.')

This line tells us that the text in the page should have the words ‘Changes saved.’. If it doesn’t then this example fails.

The second expectation block uploads a file to Facebook. The code is again quite self-evident, we just need to set the full path to the file to upload to the upload file field and click on the upload button, then check if it is successful.

The second example is slightly more tricky, because we will be dealing with Javascript quite a bit.

describe '(Navigate online friends)' do

it 'should flash all online friends ' do
$ie.div(:id, 'buddy_list_tab').click
sleep 5
friend_items = $ie.lis.find_all {|li| li.id =~ /buddy_list_item_*/}
friend_items.each {|item| $ie.link(:after?, item).flash 5 }
$first_friend = friend_items.first
end

it 'should send the first friend a message' do
$ie.link(:after?, $first_friend).click
friend_id = $first_friend.id.delete('buddy_list_item_')
$ie.text_field(:id, "chat_input_#{friend_id}").set('Hello Facebook World!')
$ie.fire_keydown_on("chat_input_#{friend_id}", 13)
$ie.div(:id, "chat_conv_content_#{friend_id}").text.should include('Hello Facebook World!')
end

end

The first expectation block looks for the online friends tab at the bottom of Facebook, which is a

tag and click on it. It’s not a button but Watir is intelligent enough to trigger the onclick event that is attached to this tag. This shows the list of friends who are online. Note that we ask the script to wait for 5 seconds using the sleep method, this is because sometimes it takes some time for Facebook to look for friends who are online. Of course, we could have waited until there are some online friends but to make the code simpler I opted to just wait till there are some friends online. The next few lines simply gets the list of friends and flash each of them. Flashing (this is part of Watir) is the act of highlighting an element with a particularly bright color (defaults to yellow) a few times in succession.

The second expectation block takes the first friend it found in the previous block and clicks on the link. This should open up a chat window, and we will use that chat window and write some message in the input. The next bit is a bit tricky so I’ll go in depth a bit.

Watir has a method on any HTML element that fires events on it, like ‘onclick’, ‘onchange’ and so on. However this is a generic event firing method, which accepts only the event name as the input and nothing else. This is not useful in the case where we need to fire events and send in extra data, like when we type in data into an input field and press ‘enter’. Unfortunately this is precisely what is needed. If we use Watir’s fire_event method on the ‘onkeydown’ event, we won’t be able to send the chat message as it expects the ‘enter’ key to be pressed. This is because fire_event just tells IE that a keydown event happens but not what key was pressed.

So what can we do?

In comes some Ruby magic (well, object-oriented magic really), which allows us to extend the default Watir::IE class. Just add this class definition in the same file:


class Watir::IE
def fire_keydown_on(element_id, key)
ie.Document.parentWindow.execScript("key = document.createEventObject(); key.keyCode = #{key}")
ie.Document.parentWindow.execScript("document.getElementById('#{element_id}').fireEvent('onkeydown', key)")
end
end

We have just added a new method ‘fire_keydown_on’ for the IE class, which takes in the element id, and the key. This method calls Javascript to create an event object (this only works in IE, by the way) and sets the key code to be ’13’, which is the carriage-return key (the enter key). The it calls Javascript to get the HTML element (using the element id) and fire the ‘onkeydown’ event on it, while passing on the event object it just created.

Now armed with this new method, after typing in the chat message, we call keydown on the input field:


$ie.fire_keydown_on("chat_input_#{friend_id}", 13)

This sends an onkeydown event to that element and passes the parameter value that represents a carriage-return. This simulates the user actually pressing the ‘enter’ key and viola! the message is sent.

Let’s put everything in a single code listing:


require 'rubygems'
require 'spec'
require 'watir'
include Watir

email = 'YOUR FACEBOOK LOGIN EMAIL'
password = 'YOUR FACEBOOK PASSWORD'

describe 'Facebook' do
before :all do
$ie = IE.new
$ie.goto('http://www.facebook.com')
$ie.text_field(:id, 'email').set(email)
$ie.text_field(:id, 'pass').set(password)
$ie.button(:id, 'doquicklogin').click
end

describe '(Editing my profile)' do

it 'should add new work information' do
# add new work history information into the profile
$ie.link(:after? => $ie.link(:text, 'Profile'), :text => 'edit').click
$ie.link(:text, 'Work').click
$ie.text_field(:name, 'work_history_1_company').set('Way Cool Company')
$ie.text_field(:name, 'work_history_1_position').set('Master of the Universe')
$ie.text_field(:name, 'work_history_1_description').set('Lord and Sovereign of All I See')
$ie.text_field(:name, 'work_history_1_location_sq').set('Singapore')
$ie.checkbox(:name, 'work_history_1_workspan_current').set
$ie.select_list(:name, 'work_history_1_workspan_start_month').set('June')
$ie.select_list(:name, 'work_history_1_workspan_start_year').set('2008')
$ie.button(:id, 'save').click
# test to see if the changes are saved
$ie.text.should include('Changes saved.')
end

it 'should upload a new picture' do
# upload a picture from the local machine
$ie.link(:after? => $ie.link(:text, 'Profile'), :text => 'edit').click
$ie.link(:text, 'Picture').click
file = (File.dirname(File.expand_path(__FILE__)) + '\\sausheong.jpg').gsub! '/', '\\'
$ie.file_field(:id, 'pic').set(file)
$ie.checkbox(:id, 'agree').set
$ie.button(:id, 'uploadbutton').click
# test to see if the picture is uploaded
$ie.text.should include('Your picture has been uploaded successfully.')
end

end

describe '(Navigate online friends)' do

it 'should flash all online friends ' do
# click on the online friends tab to open the list
$ie.div(:id, 'buddy_list_tab').click
sleep 5
# flash every friend
friend_items = $ie.lis.find_all {|li| li.id =~ /buddy_list_item_*/}
friend_items.each {|item| $ie.link(:after?, item).flash 2}
$first_friend = friend_items.first
end

it 'should send the first friend a message' do
# send a message to the first friend
$ie.link(:after?, $first_friend).click

# -- first find the friend's Facebook ID
friend_id = $first_friend.id.delete('buddy_list_item_')
# -- then enter something into the chat input
$ie.text_field(:id, "chat_input_#{friend_id}").set('Hello Facebook World!')
# -- run some javascript to send an 'enter' keystroke to the chat input (this works only in IE)
$ie.fire_keydown_on("chat_input_#{friend_id}", 13)

# test to make sure the message is sent
$ie.div(:id, "chat_conv_content_#{friend_id}").text.should include('Hello Facebook World!')
end

end

after :all do
$ie.link(:text, 'logout').click
$ie.close
end
end

class Watir::IE
def fire_keydown_on(element_id, key)
ie.Document.parentWindow.execScript("key = document.createEventObject(); key.keyCode = #{key}")
ie.Document.parentWindow.execScript("document.getElementById('#{element_id}').fireEvent('onkeydown', key)")
end
end

Remember if you’re copying the code above directly, replace the image file name with something in the same directory as your ruby script.

When it comes down to actual development you’ll probably not get away with writing unit tests to make sure your code is good to go and RSpec is a very good framework to use for this. However for functional testing that really tests the features of your application from the viewpoint of the user of your web application, you should try out RSpec + Watir. It will give you new appreciation of how someone will navigate through your application and who knows, maybe to give you new ideas on improving the UI.

Tagged with: , ,

Leaving Welcome

Posted in general by sausheong on May 7, 2008

Then there’s the matter of leaving Welcome. Well, it’s more or less public now, Francois announced it in the corporate meeting 2 days ago, together with the new organization change and the news spread out fast. People who left Welcome got to know about it and started to ping me through instant messaging to both ask why and congratulate me on my career move. I even have recruiters coming to me to ask me to keep in touch in case I need to recruit in my new job. More than one person has expressed total shock that I will be leaving so much so that I suspect they think I’m part of the furniture in Welcome!

It’s a strange feeling to be in transition, in a limbo between a current job and a new job. What makes it even stranger is that my current team of 50+ people has been sliced and diced and scattered to the 4 winds, forming new departments with new heads or pushed to existing teams but with new structures. People are jostling over the new organization, wanting to take charge of this and that team, having meetings, forming ‘alliances’ or simply adjusting to life without me around.  It’s the feeling of being operated on while supposedly under anesthetics but you’re wide awake. Nominally I’m still ‘in charge’ but the feeling has been that I’ve been put on a pedestal now. It is not only sad to leave the team that I’ve built up over the years, but also pretty devastating to see that the team itself is dissected and served up to different people.

Still, life goes on, projects to be delivered, customers to be serviced, products to be launched. Welcome is Welcome. Work still to be done while I’m here, whatever I can contribute before my final farewell.

Book published!

Posted in Mashups, Rails, Ruby by sausheong on May 7, 2008

I haven’t written up anything recently because things has been moving at a dizzying pace both at work and with my other activities. The good news is finally published! After so many months of hard work nailing down those chapters and correcting and fine-tuning them, it’s finally done! I’m the proud author of a new book and it has been the fulfillment of a long-time dream.

You can get a copy of it from my publisher or from Amazon or from the other online bookstores mentioned here.

Follow

Get every new post delivered to your Inbox.

Join 448 other followers