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.
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!
Pingback: Tweets that mention UI Tests – Part Two | CheezyWorld -- Topsy.com
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.
Pingback: UI Tests – Introducing a simple DSL | CheezyWorld
Pingback: UI Tests – putting it all together | CheezyWorld
Pingback: UI Tests – Default Data | CheezyWorld
Pingback: UI Tests – Part Two
Pingback: My simple Cucumber + Watir page object pattern framework | WatirMelon
Pingback: Introducing page-object gem | CheezyWorld