SitePrism.Vcr

Code Climate Build Status Coverage Status Dependency Status

The purpose of this gem is to give an easy way for integrating SitePrism (it is Page Object Model DSL for Capybara) and VCR (it is a powerful tool for recording and stubbing HTTP interactions).

Such integration allows you to write acceptance tests more easily since you receive handy tool for managing VCR cassettes. Those cassettes can be easily linked with SitePrism elements (in fact, Capybara elements since SitePrism doesn't have own elements). Afterwards those linked cassettes can be used for stubbing external API responses while doing actions (click, change etc) over an element that cassettes are defined for.

Features

  • Links VCR cassettes with SitePrism elements.
  • Links VCR cassettes with SitePrism pages.
  • Applies VCR cassettes on any event (click, change, blur etc).
  • Applies VCR cassettes on page loading.
  • Defines a waiter to wait on a result of an action.
  • Allows to redefine default VCR cassettes (cassettes which were specified while describing a SitePrism element or a SitePrism page).
  • Allows to redefine a default waiter (a waiter which was specified while describing a SitePrism element or a SitePrism page).

Installation

Add this line to your application's Gemfile:

gem 'site_prism.vcr'

And then execute:

$ bundle install

Or install it yourself as:

$ gem install site_prism.vcr

Usage

Linking VCR cassettes with SitePrism elements

To link VCR cassettes with SitePrism elements, you have to use element_with_vcr instead of element method of SitePrism for specifying elements:

class ProductsPage < SitePrism::Page
  element_with_vcr \
    :car_details_link,
    '#car_details' do
      fixtures ['ford', 'cars/ford_features']
    end
end

fixtures helper method is used for defining VCR cassettes. All cassettes are taken from a path which you have defined in cassette_library_dir configuration option of VCR. Please, refer to documentation of VCR to get more info about configuration options.

You can specify cassettes for already defined elements, if your page inherits another one:

class TransportPage < SitePrism::Page
  element :transport_details_link, '#car_details'
end

class CarsPage < TransportPage
  link_vcr_with_element :transport_details_link do
    fixtures ['cars/ford']
  end
end

Path helper method

In case you have a lot of cassettes which are stored in some subdirectory, you can use path helper method to specify a list of such cassettes:

class ProductsPage < SitePrism::Page
  element_with_vcr \
    :car_details_link,
    '#car_details' do
      path 'cars/small', ['ford', 'ford_features', 'prices']
      path 'offerings',  ['used_cars', 'new_cars']
    end
end

The path helper method can be used a few times in a block to define cassettes. The code above is identical to:

class ProductsPage < SitePrism::Page
  element_with_vcr \
    :car_details_link,
    '#car_details' do
      fixtures [
        'cars/small/ford',
        'cars/small/ford_features',
        'cars/small/prices',
        'offerings/used_cars',
        'offerings/new_cars'
      ]
    end
end

Home path helper method

There is a possibility to define a home path to cassettes which are applied for a particular element:

class ProductsPage < SitePrism::Page
  element_with_vcr \
    :car_details_link,
    '#car_details' do
      home_path 'cars/small'

      fixtures ['~/ford', '~/ford_features', '~/prices']
    end
end

Here ~ points to cars/small directory, all cassettes will be taken from this directory. The previous example is identical to this one:

class ProductsPage < SitePrism::Page
  element_with_vcr \
    :car_details_link,
    '#car_details' do
      fixtures [
        'cars/small/ford',
        'cars/small/ford_features',
        'cars/small/prices'
      ]
    end
end

Home path is a very useful while redefining default cassettes (It is described below).

Also, you can use a defined home path with the path helper method:

class ProductsPage < SitePrism::Page
  element_with_vcr \
    :car_details_link,
    '#car_details' do
      home_path 'cars/small'

      path '~/', ['ford', 'ford_features', 'prices']
    end
end

Sometimes you need to specify a cassette which lies in a parent directory of a home path, in this case you can use a relative path with a home path:

class ProductsPage < SitePrism::Page
  element_with_vcr \
    :car_details_link,
    '#car_details' do
      home_path 'cars/small'

      fixture ['~/../ford']
    end
end

Shortcut path helper method

The home path helper method helps you to avoid duplication while specifing cassettes. There is a shortcut_path method which does the same, but it can be used to define shorcuts for any path:

class ProductsPage < SitePrism::Page
  element_with_vcr \
    :car_details_link,
    '#car_details' do
      shortcut_path 'cars', 'cars/small'

      fixture [':cars/ford']
    end
end

Here :cars points to the cars/small directory.

Everything described for the home_path helper method works for the shortcut_path method as well.

Applying VCR cassettes on click

Cassettes can be applied on a click event:

@products_page.car_details_link.click_and_apply_vcr

This code applies VCR cassettes which were specified while defining a SitePrism element. But, there is also possibility to override them:

@products_page.car_details_link.click_and_apply_vcr do
  fixtures ['cars/volvo']
end

This code completely overrides default cassettes, but only for this one particular click action. If you want to apply default cassettes again after this code, just use code without specifying custom cassettes:

@products_page.car_details_link.click_and_apply_vcr do # overrides all default cassettes
  fixtures ['cars/volvo']
end

@products_page.car_details_link.click_and_apply_vcr # uses default cassettes again

Also, there is possibility to add new cassettes instead of overriding default one:

@products_page.car_details_link.click_and_apply_vcr do
  fixtures ['cars/volvo']
  union # makes this library add new cassettes to a list with default cassettes
end

Similar to defining SitePrism elements with VCR cassettes, you can use path helper method while applying fixtures:

@products_page.car_details_link.click_and_apply_vcr do
  path 'cars/small', ['volvo', 'volvo_features', 'prices']
end

Also, if you have specified a home path while defining a SitePrism element, you can use it here:

@products_page.car_details_link.click_and_apply_vcr do
  fixtures ['~/volvo', '~/volvo_features', '~/prices']
end

or

@products_page.car_details_link.click_and_apply_vcr do
  path '~/', ['volvo', 'volvo_features', 'prices']
end

Home path can be defined while applying Vcr:

@products_page.car_details_link.click_and_apply_vcr do
  home_path 'cars/volvo'

  path '~/', ['volvo', 'volvo_features', 'prices']
end

Exchange default cassettes

There may be a situation when you need to exchange some default cassette for one specific test. It is a very easy to do:

@products_page.car_details_link.click_and_apply_vcr do
  exchange 'volvo', 'ford'
end

When you use a home path:

@products_page.car_details_link.click_and_apply_vcr do
  exchange '~/volvo', '~/ford'
end

Also, multiple cassettes can be exchanged:

@products_page.car_details_link.click_and_apply_vcr do
  exchange ['~/volvo', '~/ford'], ['~/mazda', '~/toyota']
end

Waiters

Waiters are very important part of this gem (actually, waiters are part of SitePrism gem, but they are used widely here). When we do some action and that action causes a few HTTP interactions we have to wait for a result of them before expecting something on a page. The good approach is to wait for some visibility or invisibility of an element. For example, you have a list of products when you click on a button to show details of some product, you may wait until loading indicator which may be shown on a details page of a product disappears. Capybara already waits for an element to appear, but it hasn't any possibility to wait for invisibility of an element, SitePrism has this capability and it is very useful.

There is reason why you should use them when you use SitePrism.Vcr. If you specify a waiter while describing SitePrism elements or applying VCR cassettes, SitePrism.Vcr will know when the inserted cassettes should be ejected from Vcr to avoid a situation when some unexpected cassette is applied.

There are 2 ways for defining a waiter. When you define SitePrism elements:

class ProductsPage < SitePrism::Page
  element_with_vcr \
    :car_details_link,
    '#car_details' do
      fixtures ['ford', 'cars/ford_features']
      waiter &:wait_until_loading_indicator_invisible # our code will wait until the loading indicator has disappeared from a page
    end
end

The second way is to set it while applying Vcr cassettes:

@products_page.car_details_link.click_and_apply_vcr do
  fixtures ['cars/volvo']
  waiter &:wait_until_loading_indicator_invisible
end

Note: Using the second way, you can override a default waiter which was specified while defining SitePrism element.

In this case once we meet an expectation defined in a waiter, Vcr cassettes will be ejected and you will avoid issues with mixing unexpected cassettes. If you don't specify a waiter, you have to eject them manually:

after do
  SPV::Helpers.eject_all_cassettes
end

or directly in the test:

it 'displays details of a product' do
  products_page.products.first.show_details_btn.click_and_apply_vcr
  products_page.details.should have_content('Volvo')

  SPV::Helpers.eject_all_cassettes

  products_page.products.second.show_details_btn.click_and_apply_vcr
  products_page.details.should have_content('Ford')
end

Note: Waiters must be defined in a block. In a block you have access to an instance of class where you define elements.

There may be situation when you don't need a waiter to eject all cassettes. In this case you can pass an additional option to a waiter to disable ejecting all cassettes:

@products_page.car_details_link.click_and_apply_vcr do
  fixtures ['cars/volvo']
  waiter(eject_cassettes: false) { self.wait_until_loading_indicator_invisible }
end

The same thing can be defined for a default waiter.

In case you need to change only options defined for a default waiter, but you don't need to change a waiter, you can use waiter_options helper method:

@products_page.car_details_link.click_and_apply_vcr do
  waiter_options(eject_cassettes: false)
end

Applying VCR cassettes on any event

There may be a situation when you need to apply cassettes for some custom event rather than for a click event. It may be a change event for a select box or a drag-and-drop event for a list or a blur event for an input element. SitePrism.Vcr gem provides a way to archive such goal:

@products_page.cars_dropdown.shift_event{
  set 'Ford'
}.apply_vcr # uses default fixtures defined for this element

or if you need to use another cassettes:

@products_page.cars_dropdown.shift_event{
  set 'Ford'
}.apply_vcr do
  fixtures ['cars/ford/prices']
end

The block which is passed to shift_event method is executed in a context of an element, it means any method of Capybara::Node::Element object can be used there. Similar to click_and_apply_vcr method, you can override cassettes, add new cassettes, use path helper method while applying cassettes and home_path helper method specified while defining a SitePrism element.

Linking and applying VCR cassettes with SitePrism pages

External HTTP interactions may be done on page loading as well. This gem supports capability to apply Vcr cassettes on page loading. To define default cassettes you have to use the vcr_options_for_load class method:

class ProductsPage < SitePrism::Page
  vcr_options_for_load do
    fixtures ['max']
  end
end

Everything described above about defining cassettes for SitePrism elements is true for defining cassettes for pages.

Applying cassettes is almost the same as it is shown for SitePrism elements:

page.load_and_apply_vcr do
  fixtures ['max', 'felix']

  waiter &:wait_for_max_and_felix
end

All arguments passed to the load_and_apply_vcr method will be passed to the load method of SitePrism. It allows to change an url of the being loaded page:

page.load_and_apply_vcr(cat: 'tom') do
  fixtures ['max', 'felix']

  waiter &:wait_for_max_and_felix
end

In this case, SitePrism will alter an url and it will look like:

http://localhost/cats/tom

Altering default cassettes in sub-classes of pages

There is a possibility to alter default cassettes defined for a parent page class. It can be done with the adjust_parent_vcr_options method:

  class BasePage < SitePrism::Page
    vcr_options_for_load do
      fixtures ['cars', 'products']

      waiter &:wait_for_cars_list
    end
  end

  class CarsPage < BasePage
    adjust_parent_vcr_options do
      fixtures ['features']

      waiter &:wait_for_cars_and_features_list

      union # if it is omitted, the cassettes defined in this block will
      # replace the cassettes defined in the parent page class
    end
  end

In this case cars, products, features cassettes will be applied while loading the cars page.

Any helper methods can be used in a block passed to the adjust_parent_vcr_options method.

Applying VCR cassettes in sections

There isn't any method which you can use to apply VCR cassettes with sections. Actually, it is needless, because usually we do actions over elements. But, if you need such functionality for sections, you already have such possibility:

  class ListSection < SitePrism::Section
    # elements here

    def initialize(parent, element)
      super

      @applier = SPV::Applier.new(self)
    end

    def show_more
      @applier.shift_event{
        self.scroll_down
      }.apply_vcr do
        fixtures ["storages/more_storages"]

        waiter { self.wait_until_loading_indicator_invisible }
      end
    end
  end

In this example we apply VCR cassettes after scrolling down the content in a section.

It may be useful to stub the API request produced by an event which isn't directly related to any element rather it related to overall elements in a section.

Using Vcr options for cassettes

Vcr provides number of options which can be used for cassettes. For example, you may pass ERB into cassettes. This gem doesn't bother you use any options for Vcr cassettes. If you want to do so, you have to use a hash instead of a cassette name:

class ProductsPage < SitePrism::Page
  element_with_vcr \
    :car_details_link,
    '#car_details' do
      home_path 'cars/small'

      path '~/', [{fixture: 'ford', options: {erb: {amount: 109} } }, 'ford_features', 'prices']
    end
end

It works with any kind of helper methods where you list names of cassettes, even with the exchange helper method:

@products_page.car_details_link.click_and_apply_vcr do
  exchange '~/volvo', {fixture: '~/toyota', options: {erb: {amount: 1000} } }
end

Examples

The simple example of using this gem you can find here

Contributing

  1. Fork it
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create new Pull Request

Bitdeli Badge