Capybara

extracts from https://github.com/jnicklas/capybara.


about

key benefits:

Capybara:

installation

Gemfile:

gem 'capybara'

rails_helper.rb (not required indeed):

require 'capybara/rails'

RSpec integration

rails_helper.rb (not required indeed):

require 'capybara/rspec'

RSpec can be configured to infer spec type from a file location (Rails only):

RSpec.configure do |config|
  config.infer_spec_type_from_file_location!
end

in this case all specs in spec/features/ have type feature automatically (Capybara specs).

or else just tag example groups with type: :feature manually if file location is different.

drivers

using JS driver

use js: true (or just :js when there are no other options) option to switch to JS driver (selenium by default).

switching drivers

switching driver creates a new session - you may not be able to switch in the middle of a test.

driver types

headless drivers don’t require display server (X11, Xvfb, etc.) - all drivers mentioned below except Selenium are headless.

RackTest

for RSpec it’s better to have RackTest as default driver and mark tests that require JS support with js: true to use default JS driver.

Selenium

install chromedriver to use Selenium with Chrome browser (see below on how to change browser for Selenium):

$ brew install chromedriver

Capybara-webkit

Poltergeist

require capybara/poltergeist in rails_helper.rb to use it.

customizing drivers

Capybara allows to override standard driver configuration:

Capybara.register_driver :selenium do |app|
  Capybara::Selenium::Driver.new(app, browser: :chrome)
end

or register customized standard driver as a new driver:

Capybara.register_driver :selenium_chrome do |app|
  Capybara::Selenium::Driver.new(app, browser: :chrome)
end

now it’s possible to use selenium_chrome driver whenever you would use a standard driver.

DSL

RSpec method aliases

Capybara alias RSpec method
feature describe ..., type: :feature
background before
scenario it
given/given! let/let!

finding elements

precedence of locators (attributes or texts) elements are found by:

locator element
name inputs (file, checkbox, radio, text), textarea, select
id all
value inputs (submit, reset, image, button), button
label text inputs (file, checkbox, radio, text), select
text button, link, select option
title inputs (submit, reset, image, button), button, link

only generic methods (find/all matchers) or methods whose name implies that they work with either CSS or XPath selector (has_selector?/has_css?/has_xpath? matchers) use selectors as locators.

all other methods designed to work with specific elements (click_link, has_field?, find_button, etc.) use attribute values and texts to find corresponding elements only - they don’t accept CSS or XPath selectors!

selectors

CSS is default selector.

XPath selector can be specified inline:

find(:xpath, '//ul/li').text

or set as default selector:

Capybara.default_selector = :xpath
find('//ul/li').text

custom selectors

definition:

Capybara.add_selector(:row) do
  xpath { |num| ".//tbody/tr[#{num}]" }
end

Capybara.add_selector(:flash_type) do
  css { |type| "#flash.#{type}" }
end

usage:

find(:row, 3)
find(:flash_type, :notice)

actions (clicking)

http://www.rubydoc.info/github/jnicklas/capybara/master/Capybara/Node/Actions

actions (interacting with forms)

http://www.rubydoc.info/github/jnicklas/capybara/master/Capybara/Node/Actions

common actions:

matchers (querying)

http://www.rubydoc.info/github/jnicklas/capybara/master/Capybara/Node/Matchers

matchers check if page or current node have specified elements or text.

generic Capybara matchers:

specific matchers (not all):

all Capybara matchers have corresponding Capybara RSpec matchers:

finders

http://www.rubydoc.info/github/jnicklas/capybara/master/Capybara/Node/Finders

finders like actions or matchers use selectors to find elements.

generic finders:

specific finders (not all):

scoping

generic method is within - it uses selector to restrict actions within a specific area of the page or node.

specific scoping methods:

scripting

NOTE: only if driver supports executing JS.

page.execute_script("$('body').empty()")
result = page.evaluate_script('4 + 4')

modals

NOTE: only if supported by driver.

wrap code that produces modal in a block:

example:

accept_alert do
  click_link('Show Alert')
end

debugging

if supported by driver and capybara-screenshot gem is installed:

matching

how Capybara finds elements is customized using 2 options:

exactness (Capybara.exact)

can be set:

strategy (Capybara.match)

4 strategies:

transactions

  1. https://relishapp.com/rspec/rspec-rails/docs/transactions

for some drivers (Selenium) Capybara starts an actual HTTP server in the same process but in a separate thread. RackTest is not one of these drivers - it uses Rack interface to interact with your application.

this might pose a problem when accessing DB since transactions are not shared among threads - Capybara will not see changes made to DB.

solution

spec/rails_helper.rb (DatabaseCleaner configuration section is from DatabaseCleaner’s README):

RSpec.configure do |config|
  # true by default
  config.use_transactional_fixtures = false
end

#------------------------------------------------------------------------------
# DatabaseCleaner
#------------------------------------------------------------------------------

RSpec.configure do |config|
  config.before(:suite) do
    if config.use_transactional_fixtures?
      raise(<<-MSG)
        Don't let RSpec run examples in transactions -
        let DatabaseCleaner decide whether to do it or not.
      MSG
    end

    DatabaseCleaner.clean_with :truncation
  end

  config.before(:each) do
    DatabaseCleaner.strategy = :transaction
  end

  config.before(:each, type: :feature) do
    if Capybara.current_driver != :rack_test
      DatabaseCleaner.strategy = :truncation
    end
  end

  config.before(:each) do
    DatabaseCleaner.start
  end

  config.append_after(:each) do
    DatabaseCleaner.clean
  end
end

Ajax

Capybara waits for elements to appear on page - it retries finding the element for a brief period of time (2 seconds by default) before giving up and throwing error. this period can be adjusted:

Capybara.default_max_wait_time = 5

Capybara retries finding the element for 2 seconds only if it’s not found. if it’s necessary to check that the element is not present after Ajax request use has_no_XXX? matchers instead since they wait for the asynchronous process to complete while has_XXX? matchers check presence of the element immediately and falsely return true:

!page.has_xpath?('a')   # finds <a> element (but shouldn't)
page.has_no_xpath?('a') # doesn't find <a> element (okay)

this doesn’t concern Capybara RSpec matchers - these statements are equivalent:

expect(page).not_to have_xpath('a')
expect(page).to have_no_xpath('a')

both of them wait for the asynchronous process to complete before checking the element for the presence.

calling remote servers

RackTest driver doesn’t support calling remote servers - in-process Rack applications only.

to work with remote server:

Capybara.current_driver = :selenium
Capybara.app_host = 'http://www.google.com'
...
visit('/')

or else visit URL directly:

visit('http://www.google.com')

if working with remote server it’s not necessary to boot rack application - you can configure Capybara not to do it automatically:

Capybara.run_server = false

structure of feature specs

steps to implement feature spec:

don’t test internal application logic that doesn’t depend on user actions - e.g. redirects that depend on system state managed by application itself.

if something is easier to test with unit tests then it shouldn’t be tested with feature specs.

troubleshooting

domain is set to example.com in tests

all in all don’t test against domain - use _path helpers only.

login_as method doesn’t log in user

TODO: still not resolved.

UPDATE

try this.

assets (both CSS and JS) are not served when using any JS driver

in my case the problem was that both CSS and JS application manifests were not linked using asset tag helpers (stylesheet_link_tag and javascript_include_tag) in application layout file (application.html.slim) for test Rails environment:

- unless Rails.env.test?
  = stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track' => true
  = javascript_include_tag 'application', 'data-turbolinks-track' => true

also rendering jivosite partial resulted in JS error about $ being undefined:

= render 'application/jivosite'

but this is not critical and doesn’t influence loading application manifests.

debugging commands hang when using binding.pry with webkit driver

use selenium driver or first insert debugging commands before binding.pry and only then run your specs (hang occurs only when issuing debugging commands after code execution has been paused by binding.pry).

selenium driver sometimes fails to locate elements

when using selenium driver Capybara sometimes fails to locate elements (links in my case) on page. increasing Capybara.default_max_wait_time doesn’t help.

for some reason it doesn’t happen when using webkit driver.

most likely find_field doesn’t work with submit inputs

just find submit inputs with find: find("input[type='submit']").

persisted data is not available in tests

sure there might be different reasons for this including wrapping tests in transactions (see transactions) but it my case the problem was as following:

but the payment is nil.

the point is that when reading database in spec you don’t wait till controller action is completed and page fully rendered - unlike Capybara matchers or finders.

that is why it’s necessary to wait till new page is ready and the simplest way to do it is to use some Capybara matcher (e.g. checking current_page before reading database).