Friday, January 10, 2014

Using Capybara and RSpec assertions in Page Objects

On my Rails project, I am using RSpec and Capybara to write functional tests.  I did not want to specify any HTML elements in my Capybara feature files, since that makes the feature files brittle to HTML / CSS changes. It also violates the DRY principle and basic code hygiene.

So, I decided to refactor out my HTML centric Capybara code into separate Page Objects. If you are unfamiliar with Page Objects, then read the following:



The Page Object pattern for encapsulating HTML centric DSL is a common pattern followed while writing UI level functional tests in ThoughtWorks.

The problem I was facing when I refactored my code into Page objects was that I was unable to use the RSpec 'expect' syntax in Page Objects. Turns out all I had to do in my page objects was:

include RSpec::Matchers

Here is the full code from my project on Github.
https://github.com/gsluthra/dakshina/tree/master/spec/features

The appropriate feature files and page objects in a GIST:
https://gist.github.com/gsluthra/8356015
# Page Object
class CapsuleCreateForm
include Capybara::DSL
def submit_form(capsule={})
fill_in 'Title', :with => capsule[:title]
fill_in 'Description', :with => capsule[:description]
fill_in 'capsule_study_text', :with => capsule[:study_text]
fill_in 'capsule_assignment_instructions', :with => capsule[:assignment_instructions]
fill_in 'capsule_guidelines_for_evaluators', :with => capsule[:guidelines_for_evaluators]
click_button 'Save Capsule'
end
end
require 'spec_helper'
#Feature File
feature 'Capsules Feature' do
let(:capsuleHash) { attributes_for(:tdd_capsule) }
let(:capsuleForm) { CapsuleCreateForm.new }
let(:capsuleViewPage) { CapsuleViewPage.new }
scenario 'Add a new capsule and displays the capsule in view mode' do
visit '/capsules/new'
expect {
capsuleForm.submit_form(capsuleHash)
}.to change(Capsule, :count).by(1)
capsuleViewPage.validate_on_page
capsuleViewPage.validate_capsule_data(capsuleHash)
end
end
require 'spec_helper'
# Page Object
class CapsuleViewPage
include Capybara::DSL
include RSpec::Matchers
def validate_on_page
expect(page).to have_selector('#capsule-title-page')
expect(page).to have_link('Edit')
end
def validate_capsule_data (capsule={})
expect(page).to have_content capsule[:title]
expect(page).to have_content capsule[:description]
expect(page).to have_content capsule[:study_text]
expect(page).to have_content capsule[:assignment_instructions]
expect(page).to have_content capsule[:guidelines_for_evaluators]
end
end

2 comments:

Unknown said...

You link your post to Martin Fowler's article where he strongly recommends not to use assertions inside page objects and the hole post is about using them.

Gurpreet said...

hmmm... I like the way my page object is designed.

Plus maybe you missed this point in Martin's post: "One form of assertions is fine even for people like me who generally favor a no-assertion style. These assertions are those that check the invariants of a page or the application at this point, rather than specific things that a test is probing".

Think about this one.. and then again look at my assertions.

The assertion methods like validate_capsule_data is a reusable method whose HTML elements I would like to keep inside the object. I don't want it replicated outside by everyone who wishes to validate the content of this page.