UI Tests – Introducing a simple DSL

In the first and second posts of this series we introduced Page Objects and evolved them to include page partials and return the next page object. In this post we will introduce a simple domain specific language that will eliminate a lot of the annoying repetitive work we have done so far. After we look at the DSL we will refactor our tests to take advantage of its’ capabilities. When we are finished our scripts will be cleaner than when we left off at the end of the last post.

WatirHelper

WatirHelper is a very simple DSL that adds methods to your page objects that perform routine tasks such as setting data in a text_field and clicking a button. I created this DSL over the course of two years while coaching teams on agile practices. I usually recommend teams implement ATDD and when they do I help the testers automate the tests with Cucumber. It has had extensive usage with Watir and FireWatir. It does not currently work with SafariWatir but I am working on it. I have not tested it with Celerity but intend to do so soon. It is fairly well commented so you should have no problem getting up to speed quickly. I am introducing a fairly stripped down version here but the full version will be available in a book that I hope to release after the first of the year.

WatirHelper is implemented as a simple module that you include in your page objects. It then adds methods that you can use to define your page and access its’ elements. For example, it will add a text_field class method to your page object. Let’s look at how we would call this in the CheckoutPage class for the name field from the last post.

  
class CheckoutPage
  include WatirHelper

  text_field(:name, :id => 'order_name')
  ...

  def initialize(browser)
    @browser = browser
  end

  ...
end

It looks nice but what does it do for us? Let’s take a look at the text_field method to start to understand the benefit of using this domain specific language. Here is the method from WatirHelper:

  
def text_field(name, identifier)
  identifier = make_safe_identifier(identifier)
  define_method(name) do
    @browser.text_field(identifier).value
  end
  define_method("#{name}=") do |value|
    @browser.text_field(identifier).set(value)
  end
  define_method("#{name}_text_field") do
    @browser.text_field(identifier)
  end
end

On line 3 it is adding a method with the name passed in – in our case name. This method returns the value contained in the text_field. On line 6 it adds another method that sets the value in the text_field. And finally on line 9 it adds another method that returns the Watir TextField object. So adding

  
  text_field(:name, :id => 'order_name')

causes WatirHelper to add the following three methods to our page object:

  
  def name
    @browser.text_field(identifier).value
  end
  
  def name=(value)
    @browser.text_field(identifier).set(value)
  end
  
  def name_text_field
    @browser.text_field(identifier)
  end

Let’s look at one more example to give you another view of its’ power. Adding

  
  button(:place_order, :value => 'Place Order')

causes WatirHelper to add the following three methods to our page object:

  
  def place_order
    @browser.button(identifier).click
  end

  def place_order_no_wait
    @browser.button(identifier).click_no_wait
  end

  def place_order_button
    @browser.button(identifier)
  end

Let’s put it to use

The best way to understand WatirHelper‘s power is to see it in action. I will begin by re-writing the CheckoutPage using WatirHelper. The original class had a method for each element on the page. Here it is:

  
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 now it is time to look at the new and improved version of CheckoutPage.

  
class CheckoutPage
  include WatirHelper

  text_field(:name, :id => 'order_name')
  text_field(:address, :id => 'order_address')
  text_field(:email, :id => 'order_email')
  select_list(:pay_type, :id => 'order_pay_type')
  button(:place_order, :value => 'Place Order')

  def initialize(browser)
    @browser = browser
  end
end

As you can see we have eliminated a lot of boilerplate code. The new version is significantly shorter than the previous. But what does this do to our ShoppingCartPage? Let’s take a look:

  
class ShoppingCartPage
  include WatirHelper

  QUANTITY_COLUMN = 1
  DESCRIPTION_COLUMN = 2
  EACH_COLUMN = 3
  TOTAL_COLUMN = 4
  HEADER_OFFSET = 2

  link(:checkout, :text => 'Checkout')
  link(:continue_shopping, :text => 'Continue shopping')
  cell(:cart_total, :class => 'total-cell')
  table(:shopping_cart, :index => 1)

  def initialize(browser)
    @browser = browser
  end

  def goto_checkout_page
    checkout
    CheckoutPage.new(@browser)
  end

  def quantity_for_line(line_number)
    cart_data_for_line(line_number)[QUANTITY_COLUMN].text
  end

  def description_for_line(line_number)
    cart_data_for_line(line_number)[DESCRIPTION_COLUMN].text
  end

  def each_for_line(line_number)
    cart_data_for_line(line_number)[EACH_COLUMN].text
  end

  def total_for_line(line_number)
    cart_data_for_line(line_number)[TOTAL_COLUMN].text
  end

  private

  def cart_data_for_line(line_number)
    shopping_cart[HEADER_OFFSET+line_number]
  end
end

Again, we have a much smaller and cleaner page object.

How does this change the way I work?

In the final post in this series I will talk extensively about the ATDD workflow and how that effects when and how we write the code. I will mention a couple of things here that I think are important and directly relate to WatirHelper.

First of all we can completely decouple the creation of the page objects from the creation of the step definitions that use them. As soon as the screen mock-ups are complete you can quickly create a page object that contains the visible elements on the page. If the mock-up goes through changes it is very easy to make the corresponding change in the page object.

Also, the post development sync is much easier when using WatirHelper. This is the time when the developer is completing the code and we need to synch the test with the code. Items we might find here are mis-identified element types (we thought it was a button but it ended up being a link with an image) and incorrect locators (we supplied the wrong :id). As you can imagine, both of these situations are incredibly easy to resolve with WatirHelper

What’s next?

We are now half way through this series on writing UI tests. The remaining posts in this series will continue to build upon what we have written so far. Each post will introduce one or more concepts that will help us make our tests more robust and maintainable. The next post will talk about handling large amounts of data in your scripts and building higher-level tests. I hope you stay aboard for the journey.

By the way, if you want to look at the entire WatirHelper module from this posting you can find it in the features/support directory of this posts finished codebase.

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.

28 thoughts on “UI Tests – Introducing a simple DSL

  1. Pingback: Tweets that mention UI Tests – Introducing a simple DSL | CheezyWorld -- Topsy.com

  2. Pingback: A Smattering of Selenium #33 « Official Selenium Blog

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

  4. Pingback: Improving my WatirMelonCucumber page object framework | WatirMelon

  5. Pingback: Introducing page-object gem | CheezyWorld

  6. This is great stuff. I do have one question, though: within this framework, how do you identify things that are nested inside of iframes, for example? Is that possible?

    If there’s a way, it’s not obvious to me.

    Thanks for your help!

  7. Cheezy, I’m getting this error:

    /Users/abrahamheward/.rvm/gems/ruby-1.9.2-p290/gems/page-object-0.3.1/lib/page-object/accessors.rb:218:in `block in link': undefined method `click_link_for’ for nil:NilClass (NoMethodError)

    With this code:

    class Home
    def initialize(browser)
    @browser = browser
    end
    include PageObject
    link(:site_editor, :text=>”Site Editor”)
    end
    home = Home.new(browser)
    home.site_editor

    What’s going wrong?

    • Abe,

      Please do not create the initialize method. The PageObject gem already has one and it performs many additional duties. Just remove the method and it should work appropriately.

      • If you need a creation hook then you can create an initialize_page method. It is a callback that is called after the page is initialized and prior to traversing to the page if you are using the page factory with the visit_page method.

  8. Is there a way to work with a long list of links without having to list all of them as page individual page objects. I would like to be able to pass the link text to the page object call as a variable.

    • It is very possible to do this but I think you are missing the point for even having page objects. The purpose for page objects it to abstract the things on the page from the tests. If you pass the link text you are not building the abstraction and you are creating a brittle test.

      Anyway, here is some code that will return a list of links on the page that all have the same class:

      links(:product, :class => ‘product’)

      With this declaration you now have a method named `product_elements` that will return all of those links as an `Array`.

  9. pardon, may be this is not the right spot to raise my question.

    the question goes like this:

    I want to check existence of div element on the fly by using some attributes passed from the feature file, but I tied to use div_element(:id=>’xxx’)? , I’m getting error.

    any advise is helpful.
    thanks

  10. hey Cheezy,

    I’m back again, is there any google discussion going on for Page Object Model gem you’ve developed so far? it would be so much helpful to toss question back and forth over there i think.

    Thanks

  11. Hi Cheezy

    Have all the Accessor methods that check for the existence (methods ending in ?) of the element been implemented?

    e.g. table(:Table, :css => “table”)

    when I try to run the Table? method it says (note I am using jRuby and selenium):

    …RuntimeError: exists? not available on table element
    exists? at /Users/zsethna/.rvm/gems/jruby-1.7.8/gems/page-object-0.9.5/lib/page-object/platforms/selenium_webdriver/table.rb:32
    Table? at /Users/zsethna/.rvm/gems/jruby-1.7.8/gems/page-object-0.9.5/lib/page-object/accessors.rb:1226
    call at org/jruby/RubyMethod.java:124

    Any ideas on whats happening here?
    Thanks

  12. I might be being a bit slow, but I really can’t see what the watir helper gets you in his case. Say we have defined a POM of a checkout page with an element called ‘order email’. We define a method in the checkout page POM to access that element.

    Def order_email()
    @browser.text_field(:id => ‘order_email’)
    end

    That makes total sense, because the order_email element could change on the page, so we have encapsulated the method by which we access it. Now, if it changes on the page, we only need to change our method. To access it, we can just write

    checkout_page.order_email

    And to set it we can write

    checkout_page.order_email.set(value)

    But in your example, when you encapsulate the order_email element, you also write a new way of doing the .set method:

    def order_email=(value)
    @browser.text_field(:id => ‘order_email’).set(value)
    end

    That is, you encapsulate the .set method too. Why? The .set method in watir isn’t likely to change. And writing it with new syntax makes the code harder to read. (I’m writing this coming on to a project where someone has used your watir helper, and it took me quite a while to figure out what was going on, and then even longer to figure out why they’d written custom set methods, etc.).

    It seems to me that this part of watir helper only really helps with the writing of custom versions of ways to do things with the element we are encapsulating, like, in your example, writing a custom set method. But if there is no gain to writing such custom methods (as I can’t see there is, given that it’s the page that’s likley to change, and not the methods in watir), then I can’t see why you’d use watir helper.

    Again, I am new to this so sorry if I missed something obvious.

    Thanks,
    Adam

  13. Bought a “Cucumber_and _Cheese” book. It’s fantstic. After reading this you will be able to set up a working framework within minutes. Good work.

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>