Playing the waiting game

One difficulty testers run into when they are new to driving browsers with cucumber is knowing how to handle sites that contain a lot of ajax calls. They write scripts that assume the elements on the page exist and are shocked when the tests fails because it was trying to access something that wasn’t on the page yet.

In this post I’ll write a simple scenario that demos the async handling capabilities in the page-object gem. I’ll also briefly introduce you to a new gem that I use to generate my new projects. I’ll do all of this by writing a scenario that uses one of the examples google has provided to demo the GWT libraries. For those of you who have taken one of my classes you will already be familiar with this example but perhaps there are still a few things here for you to learn. Let’s get started writing the code!

Creating the project

The first thing I need to do is create my project structure. Let me see. I think I’ll use the page-object gem with Selenium. Let’s use the tesetgen gem to get started. If you don’t have this gem installed already you can just execute gem install testgen. Here’s the command I used to generate my project:


testgen project ajax_example --pageobject-driver=selenium

This creates my entire project structure. Let’s quickly verify that everything is OKay. The first thing we need to do is make sure we have all of the gems installed. testgen created a Gemfile file for us so all we need to do is change to the newly created ajax_example directory and execute bundle install.

Now that we’re sure we have all of the gems installed it’s time to run cucumber. testgen created a Rakefile for us to make that easy. Just type the rake command in the ajax_example directory. You should see the following:


Using the default profile...
0 scenarios
0 steps
0m0.000s

We’re now ready to write a scenario.

Writing the scenario

We’ll be using the DynaTable example from the GWT site. Here is the page. On this page select a day checkbox and then the classes offered on that day are displayed in the row with the professors name. Let’s try to write a scenario.

I start by creating a file in the features directory. Here’s the content:


Feature: Displaying class schedules

I need to be able to display the class schedule for professors.  When I
select a day the page should display all of the class taught by professors
on that day as well as the time for the classes.

  Scenario: Displaying classes offered by professors
    Given I am on the google dynamic table page
    When I view the schedule for "Monday"
    Then I should see that "Inman Mendez" offers a class at "Mon 9:45-10:35"
    When I view the schedule for "Tuesday"
    Then I should see that "Inman Mendez" offers a class at "Tues 2:15-3:05"
    And I should see that "Teddy Gibbs" offers a class at "Tues 10:00-10:50"

Notice that I didn’t say anything about clicking buttons or checkboxes. Also notice that I in no way stated how the class or instructor’s name are found or represented on the page. Why do you think I did this? I’ll have a lot more to say about this in future posts.

My next step is to create the step definitions. To do this I will again execute the ‘rake’ command. Cucumber is nice enough to generate the pending step definitions.

Given /^I am on the google dynamic table page$/ do
  pending # express the regexp above with the code you wish you had
end

When /^I view the schedule for "([^\"]*)"$/ do |arg1|
  pending # express the regexp above with the code you wish you had
end

Then /^I should see that "([^\"]*)" offers a class at "([^\"]*)"$/ do |arg1, arg2|
  pending # express the regexp above with the code you wish you had
end

I know I’m going to create a page object but first of all I want to think about what that object will look like. I think I’ll write the code I wish I had in a couple of the step definitions. Here’s what I came up with:

When /^I view the schedule for "([^\"]*)"$/ do |day|
  page.select_schedule_for day
end

Then /^I should see that "([^\"]*)" offers a class at "([^\"]*)"$/ do |name, expected|
  page.schedule_for(name).include? expected
end

Yeah. I think that’s how I want to interact with the page object. These step definitions are not complete. I have just written enough to understand what I need to include in the page object I am about to write. Now, let’s write it.

Creating the Page Object

In order to implement the functionality I currently need in my page object I will need to provide access to the day checkboxes, the none button (in order to clear all of the checkboxes), and the table that contains the schedules. I’ll also need to provide the url for the page. I create a new file in the pages directory named dynamic_table_page.rb. Here’s the first version of this class:

class DynamicTablePage
  include PageObject

  page_url "http://gwt.google.com/samples/DynaTable/DynaTable.html"
  table(:dyna_table, :class => 'table')
  checkbox(:sunday, :id => 'gwt-uid-1')
  checkbox(:monday, :id => 'gwt-uid-2')
  checkbox(:tuesday, :id => 'gwt-uid-3')
  checkbox(:wednesday, :id => 'gwt-uid-4')
  checkbox(:thursday, :id => 'gwt-uid-5')
  checkbox(:friday, :id => 'gwt-uid-6')
  checkbox(:saturday, :id => 'gwt-uid-7')
  button(:none, :value => 'None')

end

The next thing I need to do is write the two methods I determined I needed earlier. Here they are:

def select_schedule_for(day)
  none
  self.send "check_#{day.downcase}"
end

def schedule_for(name)
  the_row = dyna_table_element.find { |row| row[0].text == name}
  the_row[2].text
end

I think there are a few things here that need explaining. Let’s take it one method at a time.

Sending messages

In the first method we are pressing the none button which clears all of the checkboxes. Next we check the appropriate checkbox. The page-object gem generates four methods when you define a checkbox. Let’s look at the four methods generated for the :monday checkbox declaration above.

def check_monday
  # check the monday checkbox
end

def uncheck_monday
  # uncheck the monday checkbox
end

def monday_checked?
  # return whether the monday checkbox is checked
end

def monday_element
  # return the monday checkbox element
end

Understanding the generated methods it makes it clear that what we want to call is the check_? method. When you call a method in Ruby it actually sends a message to the object on which the call is to be made. In fact, Ruby provides a send method that performs the actual call. In other words:

page.check_monday

# is the same as

page.send "check_monday"

This is what allows us to call the appropriate checkbox method based on the name passed into the method. Notice that we force the day name to lowercase so “Monday” as an argument yields check_monday.

def select_schedule_for(day)
  none
  self.send "check_#{day.downcase}"
end

Allow me to Enumerate

Ruby has a module mixin named Enumerable. The numerous methods provided by this module add the ability to traverse, search, and sort items in a collection. In the page-object gem the Table, TableRow, OrderedList, and UnorderedList elements all include Enumerable. This provides the ability to perform activities on their children. For example, you can loop through all of the TableRow elements by using the methods on Table and then you can loop through the TableCell elements by calling the methods on TableRow.

We use one of the methods from Enumerable to help us find the correct row in the table. The find method calls a block one time for each item in the list. The method will return the first item in which the block returns true. We can look for the name of the professor by looking at the first column of the table.

def schedule_for(name)
  the_row = dyna_table_element.find { |row| row[0].text == name}
  the_row[2].text
end

The final thing we need to do is return the text from the third row column which contains the courses offered.

Getting rid of the magic

The final change I wish to make to the page object is to eliminate the magic numbers. Magic numbers are numbers that appear in code that have a significance but their meaning is not obvious. For example, we are using 0 and 2 in the schedule_for method. If I come back to this code several months after writing it I am sure I will not remember exactly what those numbers mean. To resolve this I will add constants that provide some meaning. After making that change you can see the entire class.

class DynamicTablePage
  include PageObject

  NAME_COLUMN = 0
  SCHEDULE_COLUMN = 2

  page_url "http://gwt.google.com/samples/DynaTable/DynaTable.html"
  table(:dyna_table, :class => 'table')
  checkbox(:sunday, :id => 'gwt-uid-1')
  checkbox(:monday, :id => 'gwt-uid-2')
  checkbox(:tuesday, :id => 'gwt-uid-3')
  checkbox(:wednesday, :id => 'gwt-uid-4')
  checkbox(:thursday, :id => 'gwt-uid-5')
  checkbox(:friday, :id => 'gwt-uid-6')
  checkbox(:saturday, :id => 'gwt-uid-7')
  button(:none, :value => 'None')

  def select_schedule_for(day)
    none
    self.send "check_#{day.downcase}"
  end

  def schedule_for(name)
    the_row = dyna_table_element.find { |row| row[NAME_COLUMN].text == name}
    the_row[SCHEDULE_COLUMN].text
  end
end

Finishing the Step Definitions

I can now finish the step definitions. I’ll be using the PageObject::PageFactory to create my objects. Let’s look at the steps and then we can discuss the details.

*)"$/ do |day|
  on_page(DynamicTablePage).select_schedule_for day
end

Then /^I should see that "([^\"]*)" offers a class at "([^\"]*)"$/ do |name, expected|
  on_page(DynamicTablePage) do |page|
    page.wait_until(2) do
      page.schedule_for(name).include? expected
    end
  end
end

The third step beginning on line nine is where the ajax heavy lifting occurs. I am using the wait_until method available on PageObject. There are numerous methods to handle ajax calls baked into the gem. This particular method will wait until the block returns true. By default it will wait for 30 seconds but I thought that was too long so I am passing in 2 to set the wait time to 2 seconds. If the expected text for the schedule does not show up within 2 seconds an error will be thrown and the step will fail.

Wrapping up

Please take the time to look at the other methods that page-object provides that make testing ajax sites simple. As always, please let me know what you think of this post.

3 thoughts on “Playing the waiting game

  1. Hey Cheezy,

    Any chance you could add a generic “wait_for_ajax” method to your code, a la what’s discussed here:

    http://agilesoftwaretesting.com/?p=111
    and here:
    http://tanin.nanakorn.com/watir/wait_for_ajax

    Currently I’ve added a kludge to the Watir::Browser class that looks like this:


    def wait_for_ajax(timeout=30)
    while !(self.execute_script("return jQuery.active == 0"));end
    self.wait(timeout)
    end

    But I have no doubt that you’ll be able to write something much better.

    I find that writing custom wait statements all over the place is tedious and sometimes very complicated. Also, I generally find I can’t use the stuff that you’ve written because your framework model often doesn’t fit into how and when I need to wait for stuff.

    • Abe,

      I’ll look into this. It should be that hard. In the mean while, would you mind going to the github site and opening up an issue?

      Thanks
      -Cheezy

Leave a Reply

Your email address will not be published. Required fields are marked *