Posts tagged with capybara

Component-Based Acceptance Testing

Have you heard of page objects? They're awesome. I'll refer to them as POs. They were conceived as a set of guidelines for organizing the actions a user takes within an application, and they work quite well. There are a few shortcoming with POs, however. Namely, the guidelines (or lack thereof) around how to handle pieces of the app that are shared across pages. That's where components are useful.

A component is a piece of a page; a full page is comprised of zero or more components. Alongside components, a page can also have unique segments that do not fit well into a component.

On the modern web, components are more than a visual abstraction. Web components are increasing in usage as frameworks like AngularEmber and React advocate their adoption to properly encapsulate HTML, CSS and JavaScript. If you're already organizing your front-end code into components, this article will feel like a natural fit. Uncoincidentally, the behavioral encapsulation of components within acceptance tests is often the same behavioral encapsulation of components in the front-end code. But I'm getting a little ahead of myself...

Let's quickly recap POs. POs date back to 2004, when originally called WindowDrivers. Selenium WebDriver popularized the technique under the name Page Objects. Martin Fowler wrote about his latest approach to POs in 2013. There's even some interesting academic research on the impacts of POs. Generally speaking, a single PO represents a single page being tested. It knows the details of interacting with that page, for example, how to find an element to click.

Acceptance tests have two primary categories of events: actions and assertions. Actions are the interactions with the browser. Assertions are checks that the browser is in the correct state. The community prefers that POs perform actions on the page, yet do not make assertions. Assertions should reside in the test itself.

To demonstrate POs and components, let's write some acceptance tests around a couple basic interactions with Twitter's profile page, pictured below.

Twitter Profile Page

When clicking the blue feather icon on the top right, it opens a dialog that allows the user to compose a tweet.

Twitter Compose Dialog

For this demonstration, we'll use Ruby, RSpec and Capybara to mimic these interactions in our acceptance tests, but the rules we'll discuss here can be readily translated to other toolsets.

We might start with a PO that looks like the following. This simple PO knows how to visit a profile page, navigate to a user's followers, and begin composing a tweet.

module Page
  class Profile
    include Capybara::DSL

    def navigate(handle)
      visit "/#{handle}"
    end

    def navigate_to_followers
      click_link 'Followers'
    end

    def open_tweetbox
      click_button 'Tweet'
    end
  end
end

The following test uses each part of the above PO.

describe 'the profile page' do
  let(:profile_page) { Page::Profile.new }
  
  before do
    profile_page.navigate('mikepack_')
  end
  
  it 'allows me to navigate to the followers page' do
    profile_page.navigate_to_followers
  
    expect(current_path).to eq('/mikepack_/followers')
  end
  
  it 'allows me to write a new tweet' do
    profile_page.open_tweetbox
  
    expect(page).to have_content('Compose new Tweet')
  end
end

That's pretty much all a PO does. For me, there are a few outstanding questions at this point, but we've largely showcased the pattern. To highlight where POs start breaking down, let's model the "followers" page using a PO.

module Page
  class Followers
    include Capybara::DSL

    def navigate(handle)
      visit "/#{handle}/followers"
    end
  
    def navigate_to_tweets
      click_link 'Tweets'
    end
  
    # Duplicated from Page::Profile
    def open_tweetbox
      click_button 'Tweet'
    end
  end
end

Uh oh, we've encountered our first problem: a user can create a tweet from both the main profile page and from the followers page. We need to share the #open_tweetbox action between these two pages. The conventional wisdom here is to create another "tweetbox page", like the following. We'll move the #open_tweetbox method into the new PO and out of the other POs, and rename it to #open.

module Page
  class Tweetbox
    include Capybara::DSL
  
    def open
      click_button 'Tweet'
    end
  end
end

Our test for the profile page now incorporates the new Tweetbox PO and our code is a whole lot more DRY.

describe 'the profile page' do
  let(:profile_page) { Page::Profile.new }
  let(:tweetbox_page) { Page::Tweetbox.new } # New code
  
  before do
    # Original setup remains the same
  end
  
  it 'allows me to navigate to the followers page' do
    # Original test remains the same
  end
  
  it 'allows me to write a new tweet' do
    tweetbox.open
  
    expect(page).to have_content('Compose new Tweet')
  end
end

We're now up against another conundrum: if both the tweets page and the followers pages have the ability to compose a new tweet, do we duplicate the test for composing a tweet in both pages? Do we put it in one page and not the other? How do we choose which page?

This is where components enter the scene. In fact, we almost have a component already: Page::Tweetbox. I dislike the conventional wisdom to make any portion of a page another PO, like we did with Page::Tweetbox. In my opinion, POs should represent full pages. I believe that whole pages and portions of pages (ie components) carry significantly different semantics. We should treat POs and components differently, even though their implementations are mostly consistent. Let's talk about the differences.

Here are my guidelines for page and component objects:

  1. If it's shared across pages, it's a component.
  2. Pages have URLs, components don't.
  3. Pages have assertions, components don't.

Let's address these individually.

If it's shared across pages, it's a component.

Let's refactor the Page::Tweetbox object into a component. The following snippet simply changes the name from Page::Tweetbox to Component::Tweetbox. It doesn't answer a majority of our questions, but it's a necessary starting place.

module Component
  class Tweetbox
    include Capybara::DSL
 
    def open
      click_button 'Tweet'
    end
  end
end

In the tests, instead of using the sub-page object, Page::Tweetbox, we would now instantiate the Component::Tweetbox component.

Pages have URLs, components don't.

This is an important distinction as it allows us to build better tools around pages. If we have a base Page class, we can begin to support the notion of a URL. Below we'll add a simple DSL for declaring a page's URL, a reusable #navigate method, and the ability to assert that a page is the current page.

class Page
  # Our mini DSL for declaring a URL
  def self.url(url)
    @url = url
  end
 
  # We're supporting both static and dynamic URLs, so assume
  # it's a dynamic URL if the PO is instantiated with an arg
  def initialize(*args)
    if args.count > 0
      # We're initializing the page for a specific object
      @url = self.class.instance_variable_get(:@url).(*args)
    end
  end
 
  # Our reusable navigate method for all pages
  def navigate(*args)
    page.visit url(*args)
  end
 
  # An assertion we can use to check if a PO is the current page
  def the_current_page?
    expect(current_path).to eq(url)
  end
 
  private
 
  # Helper method for calculating the URL
  def url(*args)
    return @url if @url
 
    url = self.class.instance_variable_get(:@url)
    url.respond_to?(:call) ? url.(*args) : url
  end
 
  include Capybara::DSL
end

Our profile and followers POs can now use the base class we just defined. Let's update them. Below, we use the mini DSL for declaring a URL at the top. This DSL supports passing lambdas to accommodate a PO that has a dynamic URL. We can remove the #navigate method from both POs, and use the one in the Page base class.

The profile page, refactored to use the Page base class.

class Page::Profile < Page
  url lambda { |handle| "/#{handle}" }
 
  def navigate_to_followers
    click_link 'Followers'
  end
end

The followers page, refactored to use the Page base class.

class Page::Followers < Page
  url lambda { |handle| "/#{handle}/followers"}
 
  def navigate_to_tweets
    click_link 'Tweets'
  end
end

Below, the test now uses the updated PO APIs. I'm excluding the component test for creating a new tweet, but I'll begin addressing it shortly.

describe 'the profile page' do
  let(:profile_page) { Page::Profile.new }
 
  before do
    profile_page.navigate('mikepack_')
  end
 
  it 'allows me to navigate to the followers page' do
    profile_page.navigate_to_followers
 
    expect(Page::Followers.new('mikepack_')).to be_the_current_page
  end
end

There are a few things happening in the above test. First, we are not hardcoding URLs in the tests themselves. In the initial example, the URL of the profile page and the URL of the followers page were hardcoded and therefore not reusable across tests. By putting the URL in the PO, we can encapsulate the URL.

Second, we're using the URL within a profile_page PO to navigate to the user's profile page. In our test setup, we tell the browser to navigate to a URL, but we only specify a handle. Since our Page base class supports lambdas to generate URLs, we can dynamically create a URL based off the handle.

Third, we assert that the followers page is the current page, using a little RSpec magic. When making the assertion #be_the_current_page, RSpec will call the method #the_current_page? on whatever object the assertion is being made on. In this case, it's a new instance of Page::Followers. #the_current_page? is expected to return true or false, and our version of it uses the URL specified in the PO to check against the current browser's URL. Below, I've copied the relevent code from the Page base class that fulfills this assertion.

def the_current_page?
  expect(current_path).to eq(url)
end

This is how we can provide better URL support for POs. Naturally, portions of a page do not have URLs, so components do not have URLs. (If you're being pedantic, a portion of a page can be linked with a fragment identifier, but these almost always link to copy within the page, not specific functionality.)

Pages have assertions, components don't.

The conventional wisdom suggests that POs should not make assertions on the page. They should be used exclusively for performing actions. Having built large systems around POs, I have found no evidence that this is a worthwhile rule. Subjectively, I've noticed an increase in the expressivity of tests which make assertions on POs. Objectively, and more importantly, is the ability to reuse aspects of a PO between actions and assertions, like DOM selectors. Reusing code between actions and assertions is essential to keeping the test suite DRY and loosely coupled. Without making assertions, knowledge about a page is not well-encapsulated within a PO and is strewn throughout the test suite.

But there is one aspect of assertion-free objects that I do embrace, and this brings us back around to addressing how we manage components.

Components should not make assertions. Component objects must exist so that we can fully test our application, but the desire to make assertions on them should lead us down a different path. The following is an acceptable use of components, as we use it to perform actions exclusively. Here, we assume three methods exist on the tweetbox component that allow us to publish a tweet.

describe 'the profile page' do
  let(:profile_page) { Page::Profile.new }
  let(:tweetbox) { Component::Tweetbox.new }
 
  before do
    profile_page.navigate('mikepack_')
  end
 
  it 'shows a tweet immediately after publishing' do
    # These three actions could be wrapped up into one helper action
    # eg #publish_tweet(content)
    tweetbox.open
    tweetbox.write('What a nice day!')
    tweetbox.submit
 
    expect(profile_page).to have_tweet('What a nice day!')
  end
end

In the above example, we use the tweetbox component to perform actions on the page and the profile PO to make assertions about the page. We've introduced a #have_tweet assertion that should know in which part of the page to find tweets and scope the assertion to that DOM selector.

Now, to showcase how not to use components, we just need to revisit our very first test. This test makes assertions about the contents of the tweetbox component. I've copied it below for ease of reference.

describe 'the profile page' do
  let(:profile_page) { Page::Profile.new }
 
  before do
    profile_page.navigate('mikepack_')
  end
 
  it 'allows me to write a new tweet' do
    profile_page.open_tweetbox
 
    expect(page).to have_content('Compose new Tweet')
  end
end

After converting this test to use the tweetbox component, it would look like the following.

describe 'the profile page' do
  let(:profile_page) { Page::Profile.new }
  let(:tweetbox) { Component::Tweetbox.new }
 
  before do
    profile_page.navigate('mikepack_')
  end
 
  it 'allows me to write a new tweet' do
    tweetbox.open
 
    expect(tweetbox).to have_content('Compose new Tweet')
  end
end

Not good. We're making an assertion on the tweetbox component.

Why not make assertions on components? Practically, there's nothing stopping you, but you'll still have to answer the question: "of all the pages that use this component, which page should I make the assertions on?" If you choose one page over another, gaps in test coverage will subsist. If you choose all pages that contain that component, the suite will be unnecessarily slow.

The inclination to make assertions on components stems from the dynamic nature of those components. In the case of the tweetbox component, pressing the "new tweet" button enacts the dynamic behavior of the component. Pressing this button shows a modal and a form for composing a tweet. The dynamic behavior of a component is realized with JavaScript, and should therefore be tested with JavaScript. By testing with JavaScript, there is a single testing entryway with the component and we'll more rigidly cover the component's edge cases.

Below is an equivalent JavaScript test for asserting the same behavior as the test above. You could use Teaspoon as an easy way to integrate JavaScript tests into your Rails environment. I'm also using the Mocha test framework, with the Chai assertion library.

describe('Twitter.Tweetbox', function() {
  fixture.load('tweetbox.html');

  beforeEach(function() {
    new Twitter.Tweetbox();
  });

  it('allows me to write a new tweet when opening the tweetbox', function() {
    $('button:contains("Tweet")').click();

    expect($('.modal-title').text()).to.equal('Compose new Tweet');
  });
});

By testing within JavaScript, we now have a clear point for making assertions. There is no more confusion about where a component should be tested. We continue to use components alongside POs to perform actions in our acceptance suite, but we do not make assertions on them. These tests will run significantly faster than anything we attempt in Capybara, and we're moving the testing logic closer to the code under test.

Wrapping up

Unsurprisingly, if you're using web components or following a component-based structure within your HTML and CSS, component-based acceptance testing is a natural fit. You'll find that components in your tests map closely to components in your markup. This creates more consistency and predictability when maintaining the test suite and forges a shared lexicon between engineering teams.

Your mileage may vary, but I've found this page and component structure to ease the organizational decisions necessary in every acceptance suite. Using the three simple guidelines discussed in this article, your team can make significant strides towards a higher quality suite. Happy testing! 

Posted by Mike Pack on 04/27/2015 at 08:53AM

Tags: acceptance, testing, rspec, capybara, page objects, components


Testing subdomains with RSpec and Capybara

There's a lot of ugly solutions out there regarding this problem and rightfully so, it's a pain in the ass. I've gone through a good number of options and found the following to be the most simple. Warning: It only works on Mac OS X.

Configure Capybara

Capybara allows you to set the server port to run the test server on. This may or may not be necessary depending on your environment. It was necessary for me because Pow hijacks my default port.

spec_helper.rb

Capybara.server_port = 6543

Use the Subdomain

Mac OS X comes with a handy *.127localhost.com domain configured for you. So you can do contact.127localhost.com, test.127localhost.com, etc and it will all point to 127.0.0.1.

When you need to use your subdomain, simply use a url helper:

login_spec.rb

describe 'as a guest user on the mobile login page' do
  before do
    visit login_url(:subdomain => 'contact', :host => '127localhost.com', :port => 6543)
  end

  it 'shows me the login form' do
    page.should have_content('Login')
  end
end

Note: The :subdomain option isn't available by default. I stole it from Ryan Bate's Railscast and gisted it.

Other Options

Fenangling the /etc/hosts feels dirty and pollutes domain resolution.

Ultimately, it would be awesome to boot a Pow server for testing and use something like http://contact.myapp.test where the development app lives at http://contact.myapp.dev. It's possible to get Pow to boot at http://myapp.test by using the POW_DOMAINS environment variable. The issue here is the Pow server still runs in development. You can start the Pow server in an environment other than development by using .powenv. The issue here is managing the .powenv file so that it only exists during testing. Also, there would be additional boot time by restarting the Pow server.

I hope that in future versions of Pow, it's possible to configure the environment based on the top-level domain such as dev or test. That way, you can modify /etc/resolver/test to point to the port for which your test app lives during runtime. The resolver files are pretty simple and could be modified to look like the following:

/etc/resolver/test

# Lovingly generated by Pow
nameserver 127.0.0.1
port 6543

Until Pow provides the ability to run different environments, I'll stick to *.127localhost.com.

Happy testing!

Posted by Mike Pack on 09/16/2011 at 11:01AM

Tags: subdomains, capybara, testing, rspec, pow


Testing Mobile Rails Apps with Capybara

Every web app should have a mobile version and every mobile version should be tested. Testing mobile web apps shouldn't be any more painful than testing desktop apps with the assumption that you're still serving up HTML, CSS, and JavaScript.

My Setup

For mobile detection, I use ActiveDevice, a User Agent sniffing library and some helper methods. While User Agent sniffing isn't the best approach for client-side (use feature detection with something like Modernizr), it's a reliable way to detect mobile devices in Rails.

For acceptance testing that doesn't need to be readily demonstrated to stakeholders, I use straight up Capybara with RSpec. Sometimes I use Steak.

The Pain of Testing Mobile

It's difficult to test mobile web apps because Capybara's default drivers are all desktop User Agents. You could acquire Capybara-iphone, but this solution didn't produce the expected results for me. I was given my mobile views but not my mobile layout. Plus, all this driver does is reset the User Agent for the Rack-Test driver. Further, what if you use Selenium for your default driver?

Platformatec wrote a nice blog post about mobile testing with user agents. The problem is it relies on Selenium. I was seeking a more concrete, driver agnostic way to serve up mobile views to my tests.

How I Test Mobile

This is a simple, straightforward way to invoke your mobile app from within your tests. I'm not convinced it's the most elegant way, but it's clean and simple.

Setup Your Application

In your ApplicationController, give some support for changing over to  your mobile app. This will also come in handy when you want to work on and test your mobile app on your desktop browser.

app/controllers/application_controller.rb

@@format = :html
cattr_accessor :format

Here we simply add a class attribute accessor with a default of :html. This will allow us to say ApplicationController.format

Next we need to add a before filter which will set the desired format upon each request.

app/controllers/application_controller.rb

before_filter :establish_format

private
def establish_format
  # If the request is from a true mobile device, don't set the format
request.format = self.format unless request.format == :mobile
end

Here's what a sample Rails 3 ApplicationController would look like:

app/controllers/application_controller.rb

class ApplicationController < ActionController::Base
  protect_from_forgery
  include ActiveDevice

  # force the mobile version for development:
  #@@format = :mobile
  @@format = :html
  cattr_accessor :format
  before_filter :establish_format

private
  def establish_format
# If the request is from a true mobile device, don't set the format
request.format = self.format unless request.format == :mobile
end
end

Setup Your Tests

Now, in your tests, you can set the desired format for your application.

spec/integration/mobile/some_spec.rb

require 'spec_helper'

describe 'on a mobile device' do
  before do
    ApplicationController.format = :mobile
  end

  after do
    ApplicationController.format = :html
  end

  describe 'as a guest on the home page' do
    before do
      visit root_path
    end

    it 'does what I want' do
      page.should do_what_i_want
    end
  end
end

By setting ApplicationController.format = :mobile, we force the application to render the mobile version of files, for instance: index.mobile.erb. Your application will be invoked, your before filter will be run, and your are serving the mobile app to your tests.

Note: You need to reset your format to :html after your mobile tests are run so that tests which follow this in your suite are run under the default format, :html.

Happy testing!

Posted by Mike Pack on 06/16/2011 at 11:05AM

Tags: rails, testing, rspec, capybara, mobile