Rspec, page objects and user flows

I love it when people challenge my views.

Recently, Chris McMahon from WMF challenged my view: is Cucumber best for end-to-end testing when RSpec will do? Especially in an environment where non-technical people aren’t involved in writing the specifications.

Admittedly, Cucumber does create some overhead (those pesky step definitions with pesky regular expressions (that I love)), but it does give you plain English (or LOLCAT) written, human readable, executable specifications. But the overhead gives you something more. It gives you a way to create reusable chunks of code that you can call wherever you want when want to test something (or set something up). For example: Given I am logged into Wikipedia. But what do you do if you’re using RSpec?

What is needed is something to sit in between RSpec code and page objects that provide common functionality to reduce repetition of code, such as logging in, in each RSpec specification.

I’ve been trying to come up with a good term to describe these and the best I can come up with is ‘user flows‘, as essentially they are a flow that a user follows throughout the system, spanning multiple pages.

Adding user flows to the Wikimedia custom page object project

I thought I would experiment with user flows in the Wikimedia custom page object project that I recently created. First I replicated my Cucumber features as RSpec specs, which was easy enough, but started to notice a lot of duplication of code.

For example: this was repeated at the beginning of most specs:

visit Wikipedia::LoginPage do |page|
  page.login_with USERNAME, PASSWORD
  page.should be_logged_in
end

Whilst it isn’t a huge amount of repetition (thanks to the useful login_with method on the LoginPage), it’s still not ideal. Enter ruby modules.

Using modules to store pages and flows

As far as I know, modules perform two main functions in ruby: first as a namespace for classes (such as the Wikipedia::LogonPage class), and secondly as a way to group methods that don’t belong in a class, which are often ‘mixed into’ other classes. Perfect! A spot to store our flows.

So, since I already had both a Wikipedia and Commons module, I could simply add module methods to these modules to represent our user flows.

module Wikipedia

  extend PageHelper
  extend RSpec::Expectations
  extend RSpec::Matchers

  def self.ensure_logged_in
    visit Wikipedia::LoginPage do |page|
      page.login_with USERNAME, PASSWORD
      page.should be_logged_in
    end
  end
end

Wiring these up to RSpec

I needed to do a little wiring to ensure these user flows can be easily used in RSpec. In my spec_helper.rb file (which is the equivalent to cucumber.rb in Cucumberland), I added the following to ensure that my browser object I created in RSpec is available to use in the flows:

RSpec.configure do |config|
  config.include PageHelper
  config.before(:each) do
    @browser = browser
    Commons.browser = @browser
    Wikipedia.browser = @browser
  end
  config.after(:suite) { browser.close }
end

and that was all that was needed to start using my user flows in my RSpec specifications.

A completed RSpec specification in my WMF suite looks something like this:

describe 'Editing a Wikipedia Page' do

  context 'Logged on user' do

    it 'should be able to edit own user page' do
      Wikipedia::ensure_logged_in
      content, edit_message = Wikipedia::edit_user_page_by_adding_new_content
      visit Wikipedia::UserPage do |page|
        page.page_content.should include content
        page.view_history
        page.history_content.should include edit_message
        page.history_content.should include Wikipedia::USERNAME
      end

    end

  end

end

where Wikipedia::ensure_logged_in and Wikipedia::edit_user_page_by_adding_new_content are two user flows that I defined in the Wikipedia module.

Summary

I found using page objects directly in RSpec lacking, so I created a concept of user flows in modules that can be easily used from RSpec to reduce repetition and increase readability of specs. If I were to use RSpec for end to end tests, I would find this incredibly useful as a replacement for what Cucumber steps provide.

One thought on “Rspec, page objects and user flows

Comments are closed.