dnesteryuk/site_prism.vcr

View on GitHub
README.md

Summary

Maintainability
Test Coverage
# SitePrism.Vcr

[![Code Climate](https://codeclimate.com/github/dnesteryuk/site_prism.vcr.png)](https://codeclimate.com/github/dnesteryuk/site_prism.vcr)
[![Build Status](https://secure.travis-ci.org/dnesteryuk/site_prism.vcr.png?branch=master)](https://travis-ci.org/dnesteryuk/site_prism.vcr)
[![Coverage Status](https://coveralls.io/repos/dnesteryuk/site_prism.vcr/badge.png)](https://coveralls.io/r/dnesteryuk/site_prism.vcr)
[![Dependency Status](https://gemnasium.com/dnesteryuk/site_prism.vcr.png)](https://gemnasium.com/dnesteryuk/site_prism.vcr)

The purpose of this gem is to give an easy way for integrating [SitePrism](https://github.com/natritmeyer/site_prism) (it is Page Object Model DSL for Capybara) and [VCR](https://github.com/vcr/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:

```ruby
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](https://relishapp.com/vcr/vcr/v/2-5-0/docs/configuration/cassette-library-dir) of VCR to get more info about configuration options.

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

```ruby
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:

```ruby
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:

```ruby
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:

```ruby
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:

```ruby
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:

```ruby
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:

```ruby
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:

```ruby
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:

```ruby
@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:

```ruby
@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:

```ruby
@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:

```ruby
@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:

```ruby
@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:

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

or

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

Home path can be defined while applying Vcr:

```ruby
@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:

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

When you use a home path:

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

Also, multiple cassettes can be exchanged:

```ruby
@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:

```ruby
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:

```ruby
@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:

```ruby
after do
  SPV::Helpers.eject_all_cassettes
end
```

or directly in the test:

```ruby
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:

```ruby
@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:

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

If you don't want to eject some cassettes, you can use `eject` option for them:

```ruby
  page.car_details_link.click_and_apply_vcr do
    fixtures [
      'ford', # this cassette will be ejected from VCR queue
      {fixture: 'ferrari', options: {eject: false}} # it won't be ejected from VCR queue
    ]
  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:

```ruby
@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:

```ruby
@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](http://rubydoc.info/github/jnicklas/capybara/master/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:

```ruby
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:

```ruby
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:

```ruby
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:

```ruby
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:

```ruby
  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:

```ruby
  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](https://relishapp.com/vcr/vcr/v/2-5-0/docs/cassettes/dynamic-erb-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:

```ruby
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:

```ruby
@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](https://github.com/dnesteryuk/site_prism.vcr_example)

## 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](https://d2weczhvl823v0.cloudfront.net/nestd/site_prism.vcr/trend.png)](https://bitdeli.com/free "Bitdeli Badge")