Capybara is an extensible Ruby based test automation framework that can utilise multiple driver plugins such as Selenium Webdriver, Webkit or native build Ruby Drivers.
Editor; e.g. VSCode
Create a Gemfile and add:
Browser Framework; e.g. Capybara
Browser Driver; e.g. Selenium Webdriver + Browser Drivers
Test Runner; e.g. Spec and Spec_Helper
First setup your project directory in a location that you have full r/w/x permissions as a normal user
Create a file called Gemfile in this root, and paste in the items below (ensure that you use the latest gem versions for each - check at https://rubygems.org :
source 'https://rubygems.org'
gem 'capybara', '~> 3.39.0', '>= 3.39.1' # Released: May 12, 2023
gem 'rspec', '~> 3.12' # Released: October 26, 2022
gem 'rubocop', '~> 1.52' # Released: June 02, 2023
gem 'selenium-webdriver', '~> 4.9', '>= 4.9.1' # Released: May 08, 2023
gem 'launchy', '~> 2.5', '>= 2.5.2' # Released: December 28, 2022
gem 'webdrivers', '~> 5.2' # Released: September 29, 2022
gem 'ffi', '~> 1.15', '>= 1.15.5' # Released: January 10, 2022
Now from a terminal execute a bundle install (If you just upgraded bundler and/or Ruby run a bundle update first):
bugbare@bugbare-N501VW:~/dev/ruby/rspec-capybara/intro$ bundle install
Fetching gem metadata from https://rubygems.org/.
Fetching gem metadata from https://rubygems.org/.........
Fetching public_suffix 5.0.1
Fetching ast 2.4.2
Using bundler 2.4.13
Fetching mini_mime 1.1.2
Using racc 1.6.2
Fetching rack 3.0.7
Using matrix 0.4.2
Using json 2.6.3
Fetching regexp_parser 2.8.0
Fetching ffi 1.15.5
Using diff-lcs 1.5.0
Fetching parallel 1.23.0
Fetching rainbow 3.1.1
Installing ast 2.4.2
Installing rainbow 3.1.1
Installing parallel 1.23.0
Installing mini_mime 1.1.2
Installing rack 3.0.7
Installing regexp_parser 2.8.0
Installing public_suffix 5.0.1
Using rexml 3.2.5
Using rspec-support 3.12.0
Fetching ruby-progressbar 1.13.0
Fetching unicode-display_width 2.4.2
Using rubyzip 2.3.2
Using websocket 1.2.9
Using nokogiri 1.15.2 (x86_64-linux)
Using rspec-core 3.12.2
Using rspec-expectations 3.12.3
Using rspec-mocks 3.12.5
Fetching parser 3.2.2.1
Installing unicode-display_width 2.4.2
Installing ruby-progressbar 1.13.0
Using selenium-webdriver 4.9.1
Fetching xpath 3.2.0
Installing xpath 3.2.0
Using rspec 3.12.0
Using webdrivers 5.2.0
Fetching rack-test 2.1.0
Fetching addressable 2.8.4
Installing ffi 1.15.5 with native extensions
Installing rack-test 2.1.0
Installing addressable 2.8.4
Installing parser 3.2.2.1
Fetching capybara 3.39.1
Fetching launchy 2.5.2
Installing launchy 2.5.2
Installing capybara 3.39.1
Fetching rubocop-ast 1.29.0
Installing rubocop-ast 1.29.0
Fetching rubocop 1.52.0
Installing rubocop 1.52.0
Bundle complete! 7 Gemfile dependencies, 34 gems now installed.
Use `bundle info [gemname]` to see where a bundled gem is installed
Create a sub-folder spec within the project root folder
Create a file called spec_helper.rb and paste in the following:
# frozen_string_literal: true
require 'bundler'
require 'capybara/dsl'
require 'capybara/rspec'
Bundler.setup(:default)
Bundler.require
# Which default browser do you want Selenium WebDriver to use?
# :selenium_headless # Selenium driving Firefox in a headless configuration
# :selenium_chrome # Selenium driving Chrome
# :selenium_chrome_headless # Selenium driving Chrome in a headless configuration
# https://github.com/teamcapybara/capybara#selenium
Capybara.default_driver = :selenium_headless
Capybara.app_host = 'https://the-internet.herokuapp.com'
Capybara.default_max_wait_time = 10
# Run using:
# bundle exec rspec spec/features/login.rb --format documentation
# ... Or...
RSpec.configure do |config|
config.formatter = :documentation
end
Add a new sub-folder features under the spec folder
Create a file called home_spec.rb within the features folder and paste in the following:
require 'spec_helper'
feature 'Visit The-Internet Home Page' do
background do
visit '/'
end
scenario 'Title: Welcome to the-internet' do
expect(page).to have_content('Welcome to the-internet')
expect(page).to have_css('h1', text: 'Welcome to the-internet')
end
scenario 'Subtitle: Available Examples' do
expect(page).to have_css('h2', text: 'Available Examples')
end
scenario 'Current URL: https://the-internet.herokuapp.com/' do
expect(page).to have_current_path('https://the-internet.herokuapp.com')
end
scenario 'Verify Link Exists: Form Authentication' do
expect(page).to have_link('Form Authentication')
end
scenario 'Click Link: Form Authentication ==> Navigate to /login' do
click_link('Form Authentication')
expect(page).to have_current_path('/login')
end
scenario 'Verify Link Exists: Dropdown' do
expect(page).to have_link('Dropdown')
end
scenario 'Click On: Dropdown ==> Navigate to /dropdown' do
# Click on a Link or Button
click_on('Dropdown')
expect(page).to have_current_path('/dropdown')
end
scenario 'Find Link and Click: Dynamic Loading ==> Navigate to /dynamic_loading' do
# Can find_button('Text').click too!
find_link('Dynamic Loading').click
expect(page).to have_current_path('/dynamic_loading')
end
end
Open up a terminal session and set the cwd to be the location of the project root folder
Run the following command to execute the test created in the previous section
bundle exec rspec spec/features/home_spec.rb
A browser should fire up, go to the site: https://the-internet.herokuapp.com/
and do a few checks as per the test.
The terminal console output should display as follows at the end of the test:
bugbare@bugbare-N501VW:~/dev/ruby/rspec-capybara/intro$ bundle exec rspec spec/features/home_spec.rb
Visit The-Internet Home Page
Title: Welcome to the-internet
Subtitle: Available Examples
Current URL: https://the-internet.herokuapp.com/
Verify Link Exists: Form Authentication
Click Link: Form Authentication ==> Navigate to /login
Verify Link Exists: Dropdown
Click On: Dropdown ==> Navigate to /dropdown
Find Link and Click: Dynamic Loading ==> Navigate to /dynamic_loading
Finished in 9.51 seconds (files took 2.02 seconds to load)
8 examples, 0 failures
See the DSL readme from the capybara development team for details on the most common operations such as:
See Team Capybara Examples for Navigating
feature 'Visit The-Internet Home Page' do
background do
visit '/'
end
See the Actions documentation for detailed information on
By default, Capybara uses the CSS selector type, this can be overridden to xpath, for example, by adding :xpath symbol explicitly as part of a given element selection statement:
within(:xpath, './/ul/li') { ... }
find(:xpath, './/ul/li').text
find(:xpath, './/li[contains(.//a[@href = "#"]/text(), "foo")]').value
To set another selector type, e.g. xpath, add the following statement to the environment config file:
Capybara.default_selector = :xpath
find('.//ul/li').text
To see the full list of selectors supported by Capybara go to Capybara Selectors
NOTE: Xpath expressions using just // will always try to find the defined locator within the whole document!
The .// expression which means "any descendant of the current node" allows for xpath selectors within a defined parent node:
page.find(:xpath, '//body').all(:xpath, './/script')
# The same behaviour applies to within, therefore use .// to search within a section defined by xpath:
within(:xpath, '//body') do
page.find(:xpath, './/script')
within(:xpath, './/table/tbody') do
...
end
end
It is also possible to construct and evaluate the results of an ad-hoc selector:
Capybara.add_selector(:my_attribute) do
xpath { |id| XPath.descendant[XPath.attr(:my_attribute) == id.to_s] }
end
find(:my_attribute, 'post_123') # find element with matching attribute
Capybara.add_selector(:row) do
xpath { |num| ".//tbody/tr[#{num}]" }
end
find(:row, 3) # find 3rd row in table body
Capybara.add_selector(:flash_type) do
css { |type| "#flash.#{type}" }
end
find(:flash_type, :notice) # find element with id of 'flash' and class of 'notice'
The within function below has been used to identify elements within a form with id checkboxes, useful for identifying, updating and/or validating multiple elements and their values, restricted by a section within a page :
require 'spec_helper'
feature 'Within: Scoping' do
background do
visit '/checkboxes'
end
scenario 'Within (Checkboxes): checkbox 1' do
within('form#checkboxes') do
expect(page).to have_content('checkbox 1')
end
end
scenario 'Within (Checkboxes): checkbox 2' do
within('form#checkboxes') do
expect(page).to have_content('checkbox 2')
end
end
end
See the Matchers documentation for more information on how to make assertions on expectations like the ones below:
expect(page).to have_selector('table tr')
expect(page).to have_selector(:xpath, './/table/tr')
expect(page).to have_xpath('.//table/tr')
expect(page).to have_css('table tr.foo')
expect(page).to have_content('foo')
See the Finders documentation for detailed information on how to search for elements on the page, e.g.:
find_field('First Name').value
find_field(id: 'my_field').value
find_link('Hello', :visible => :all).visible?
find_link(class: ['some_class', 'some_other_class'], :visible => :all).visible?
find_button('Send').click
find_button(value: '1234').click
find(:xpath, ".//table/tr").click
find("#overlay").find("h1").click
all('a').each { |a| a[:href] }
Composite find selectors can be used for elements that require more than one parameter to be used in order to be identifiable:
find_field('First Name'){ |el| el['data-xyz'] == '123' }
find("#img_loading"){ |img| img['complete'] == true }
Custom selectors, or the modification of an existing selector are also useful when the existing set of selector parameters are not appropriate.
In addition find can be used to restrict expectations to a specific section and/or parent element:
find('#navigation').click_link('Home')
expect(find('#navigation')).to have_button('Sign out')
Windows can be selected based on a given action; e.g. a click opens up a new window:
facebook_window = window_opened_by do
click_button 'Like'
end
within_window facebook_window do
find('#login_email').set('a@example.com')
find('#login_password').set('qwerty')
click_button 'Submit'
end
Capybara has a simple function to identify and accept, dismiss, cancel, etc. alerts
Alerts:
accept_alert do
click_link('Show Alert')
end
Confirmation:
dismiss_confirm do
click_link('Show Confirm')
end
Prompt:
accept_prompt(with: 'Linus Torvalds') do
click_link('Show Prompt About Linux')
end
Evaluate responses from prompts:
message = accept_prompt(with: 'Linus Torvalds') do
click_link('Show Prompt About Linux')
end
expect(message).to eq('Who is the chief architect of Linux?')
Capybara automatically deals with this by waiting for elements to appear on the page; it will retry looking for that content for a brief time. You can adjust how long this period is (the default is 2 seconds):
Capybara.default_max_wait_time = 5
This behaviour does not impact negated assertions:
# consider a page where the `a` tag is removed through AJAX after 1s
visit(some_path)
!page.has_xpath?('a') # is false, as this is evaluated immediately when page loads
page.has_no_xpath?('a') # is true, as the wait = 2s before the current evaluation is taken as the outcome
The above is not default behaviour for Capybara RSpec matchers, which will wait on both types of assertion:
expect(page).not_to have_xpath('a')
expect(page).to have_no_xpath('a')
Capybara RSpec matchers will also evaluate everything loaded within a page, and if an element is somehow removed, it will reload the page and re-evaluate. To turn this off, set Capybara.automatic_reload to false.
The Capybara DSL also has some useful inbuilt functions that can help with debugging test scripts:
To save and open up a page during runtime insert the following line into a test block:
save_and_open_page
To store the current state of any given page DOM:
print page.html
To save the rendered page as an image file:
page.save_screenshot('screenshot.png')
To save and open any given rendered page as an image:
save_and_open_screenshot
To setup the tests to use a specific drive, e.g. Chrome, use the following code block in your environment configuration file:
Capybara.register_driver :selenium do |app|
Capybara::Selenium::Driver.new(app, :browser => :chrome)
end
To make it an optional setting, you can name it uniquely:
# Note: Capybara registers this by default
Capybara.register_driver :selenium_chrome do |app|
Capybara::Selenium::Driver.new(app, :browser => :chrome)
end
The following statement can be used to change a driver type within a test code block:
Capybara.current_driver = :selenium_chrome