UI Tests – Part Two

In the first post in this series I introduced the Page Object. If you haven’t done so I would suggest you read the first post as we will be building on it in this post. If you want to follow along with the code you can download this zip file that has the code where we left off last time.

In this second post I will introduce two simple concepts that will make our scripts more robust. We will apply one of these enhancements to our scripts taking us further down the path of creating tests that are not brittle. But first we will add one more scenario to our cukes.

Our new Scenario

In order to demonstrate some future techniques I am adding a scenario that performs some data entry. This scenario adds two books to the shopping cart and then checks out with some information.


  Scenario: Purchase two books
    When I purchase "Pragmatic Unit Testing (C#)"
    And I continue shopping
    And I purchase "Pragmatic Version Control"
    And I checkout
    And I enter "Cheezy" in the name field
    And I enter "123 Main Street" in the address field
    And I enter "cheezy@example.com" in the email field
    And I select "Credit card" from the pay type dropdown
    And I place my order
    Then I should see "Thank you for your order"

And of course we have to write a new Page Object to encapsulate the Checkout page.

  
class CheckoutPage

  def initialize(browser)
    @browser = browser
  end

  def name=(name)
    @browser.text_field(:id => 'order_name').set(name)
  end

  def address=(address)
    @browser.text_field(:id => 'order_address').set(address)
  end

  def email=(email)
    @browser.text_field(:id => 'order_email').set(email)
  end

  def pay_type=(pay_type)
    @browser.select_list(:id => 'order_pay_type').set(pay_type)
  end

  def place_order
    @browser.button(:value => 'Place Order').click
  end
end

And finally we will need a few new step definitions.

  
When /^I checkout$/ do
  @shopping_cart.checkout
  @checkout = CheckoutPage.new(@browser)
end

When /^I enter "([^\"]*)" in the name field$/ do |name|
  @checkout.name = name
end

When /^I enter "([^\"]*)" in the address field$/ do |address|
  @checkout.address = address
end

When /^I enter "([^\"]*)" in the email field$/ do |email|
  @checkout.email = email
end

When /^I select "([^\"]*)" from the pay type dropdown$/ do |pay_type|
  @checkout.pay_type = pay_type
end

When /^I place my order$/ do
  @checkout.place_order
end

Then /^I should see "([^\"]*)"$/ do |expected_text|
  @browser.text.should include expected_text
end

Very good. Now I think we are ready to move on.

Eliminating Page Duplication

Many websites have a block of a page duplicated on other pages. For example, it is quite common to have a header and footer section on a site show up on most of the pages. It is also somewhat common to have a side menu show up on all top-level pages.

We do not want to duplicate the code to interact with these reusable page fragments. What can we do to write the code only once and use it in multiple places? Let’s use this page as an example and write some code. This page has a header that appears on all pages as well as some items on the right of each page. Let’s handle the header first.

The header on this page has two menu items: Home and Contact. Let’s create a reusable module that provides access to these menus.

  
module CheezyWorldHeader
  def home
    @browser.link(:text => 'Home').click
  end

  def contact
    @browser.link(:text => 'Contact').click
  end
end

You will notice that this module assumes it has access to an instance variable named @browser. This shouldn’t be a problem if we only us it in our page objects. But what about the items on the right of the page?

  
module CheezyWorldWidgets
  def search_for(value)
    @browser.text_field(:name => 's').set(value)
    @browser.button(:value => 'Submit').click
  end

  def archives_for(month_label)
    select_link(month_label)
  end

  def category(category_label)
    select_link(category_label)
  end

  private
  
  def select_link(label)
    @browser.link(:text => label).click
  end
end

These modules can be used in any page by just requiring them and including them.

  
require 'cheezy_world_header'
require 'cheezy_world_widgets'

class CheezyWorldHomepage
  include CheezyWorldHeader
  include CheezyWorldWidgets

  ...
end

Although this is a simple example I hope you can see how this will help you eliminate a lot of duplication throughout your pages.

Pages returning Pages

There are two step definitions I would like to bring to your attention. They are the steps in which we have a page transition.

  
When /^I purchase "([^\"]*)"$/ do |book|
  @catalog.purchase_book(book)
  @shopping_cart = ShoppingCartPage.new(@browser)
end

When /^I checkout$/ do
  @shopping_cart.checkout
  @checkout = CheckoutPage.new(@browser)
end

In our small example these page transitions seem easy enough. In a larger test suite where we are trying to achieve as much reuse as possible it can often get confusing trying to determine which step causes a page transition. One technique I use that greatly simplifies this situation is having one page object return the next when the transition takes place. This works even better if the method on the page object makes it more obvious that this is taking place. Let’s give it a try here and see how it feels.

Our checkout method on the ShoppingCartPage is the first method we will look at. We could change this method from

  
  def checkout
    @browser.link(:text => 'Checkout').click
  end

to

  
  def goto_checkout_page
    @browser.link(:text => 'Checkout').click
    CheckoutPage.new(@browser)
  end

and the step definition would change from

  
When /^I checkout$/ do
  @shopping_cart.checkout
  @checkout = CheckoutPage.new(@browser)
end

to

  
When /^I checkout$/ do
  @checkout = @shopping_cart.goto_checkout_page
end

Using this approach our purchase book step will result in

  
When /^I purchase "([^\"]*)"$/ do |book|
  @shopping_cart = @catalog.add_book_to_shopping_cart(book)
end

I’ll leave the exercise of changing the page object for you.

What’s ahead

There is still much to come in this series on making robust UI tests using Cucumber. In the next post I will introduce a very simple domain specific language that eliminates much of the code we have written to date. Future posts talk about topics like “how to write tests that have large data requirements” and other fun topics.

If you find these posts helpful let me know by posting a comment below. Here is the finished code from this post if you wish to take a look.

Update: The code for this series is now available in github. You can access it here. There is a branch for each checkpoint in this series with the latest on master.

18 thoughts on “UI Tests – Part Two

  1. I’m really digging these concepts. We tried to do UI testing at my job previously and it fell apart because it was too time consuming/difficult to maintain them. I’m practicing the techniques you’re using here with SpecFlow in hopes that I can generate some interest at work. Our projects are all .NET based, so I think I would have an easier time pitching SpecFlow than Cuke4Nuke. Thanks for sharing!

    • Hi Kyle,
      We are also a .NET shop but I think you will have better results and find it really easy to use ruby and cucmber instead of SpecFlow. I had played with SpecFlow a bit as I was thinking of going this way (since everyone at my company knows .NET).

      After some thought, some PoC and a couple discussions with another dev, I felt it’s best to use Cucumber and Ruby. I always have a soft spot in my heart for dynamic languages…I really hate typing everything but more so in the fact you can do so much with less code at times and who needs extension methods when you can just change the behavior or inject methods with ease.

      Why use Cucumber instead of SpecFlow…
      1. Ruby is easy – any one can learn it
      2. The Ruby Community is a great resource
      3. Try using watin? (Sticking with the original is way easier…watir)
      4. FireWatir, Celerity, Selenium
      5. Did I mention Ruby is easy to learn
      6. SpecFlow doesn’t have nearly the community support that Cucumber has
      7. No Proprietary IDE – who really likes visual studio anyways?
      8. Dynamic languages are fun and have fun terms like “Duck Punching” and “Factory Girl” (careful googling Cucumber and Factory Girl together…)

      Anyways – from experience, I’ve found that sometimes it’s best to stick with the originals and leave the knockoffs/ports to the side because you will lack support and community. Use cucumber for a bit and I think you’ll find it’s way easier – the only thing in Ruby that was a little stretch was the fact the “operators” in .NET are actually functions in Ruby.

      • Hi Dan,
        Thanks for your insight. The more I’ve been using SpecFlow over the last few days, the more I’ve been thinking it may not be the right way to go.

        The biggest reason (which you nailed) is the lack of community support. After you get away from basics there isn’t much help out there. I’m spending a lot of time under the hood of SF, and I’m also writing significantly more code.

        I think I’m going to abandon .NET for a while, start over using Cucumber, and see how it goes. Thanks again!

        • Good luck Kyle – I think you will really find Cucumber and Ruby easy to use. I love the flexibility of dynamic languages and especially ruby. Injecting methods, creating entire classes on the fly and changing functionality on a dime is huge power…but this is also a double edged sword which can make debugging really hard to do (especially in a run time language like ruby) but in the right hands and with thought this is super powerful – hence the ability to do alot with very little!

  2. Pingback: Tweets that mention UI Tests – Part Two | CheezyWorld -- Topsy.com

  3. Cheezy,

    How do you handle the situation of step definition collisions? While this is obviously a very simple example, in a larger scope, I’m sure you’d have multiple situations where you might have name fields, and thus: “And I enter “Cheezy” in the name field” wouldn’t uniquely map to a single page (here the checkout page).

    One could obviously overcome the issue by using more specific language in the step: “And I enter “Cheezy” in the name field on the checkout page”, but in the scope of reading the scenario, that seems overly redundant. One goal is to keep the feature files easily readable and understandable by completely non-technical folk, and becoming excessively verbose would likely make the scenarios laborious to read for the non-technical.

    • Dave,

      I rarely condone creating tests that specify behavior at such a low level. This is again part of what makes tests brittle. In the fourth post in this series I will go into a lot of detail about this and introduce a few solutions. In the mean time, I would suggest using the more specific language. I know it is not clean but it will work. I am hoping to have the third post out by weeks end and the fourth post out early next week so I hope you can wait that long for a better solution.

      • Looking forward to parts 3 and 4! Since my comment yesterday, I found that I could be specific without being overly redundant by employing tables and the page data defaults. “When I create a user with:” etc.

        The codebase I’m working in is still quite young so refactoring to better testing patterns isn’t a momentous task.

  4. Pingback: UI Tests – Introducing a simple DSL | CheezyWorld

  5. Pingback: UI Tests – putting it all together | CheezyWorld

  6. Pingback: UI Tests – Default Data | CheezyWorld

  7. Pingback: UI Tests – Part Two

  8. Pingback: My simple Cucumber + Watir page object pattern framework | WatirMelon

  9. Pingback: Introducing page-object gem | CheezyWorld

  10. Isn’t that a bit of mixing of responsibilities? I’m a page and a controller of navigation? Could that be handled in a navigation module of some kind that would have named methods for the context related to the navigation between pages?

Leave a Reply

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

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>