In the previous post in this series, I showed you how you can execute Capybara via its own session object, which means you did not have to incorporate any of the Capybara DSL into your own logic. Here I’ll do the exact opposite of that by showing you how to incorporate Capybara into your own particular logic. Then I’ll show how that segues nicely into fitting Capybara within different test runners.
To get started, create a script called capy-module.rb. Let’s first create the basis for our script:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
require 'capybara/dsl' require 'rspec/expectations' Capybara.default_driver = :selenium module WeightPage end class TestRunner include WeightPage def mercury_test end end TestRunner.new.mercury_test |
Here I have a TestRunner class that includes the WeightPage module. The TestRunner has its mecury_test() method executed although nothing is actually happening yet. Do note that, unlike the script from the first post, I’m requiring the Capybara DSL file (require 'capybara/dsl'
) rather than just Capybara itself (require 'capybara'
). I’m doing this because the ‘dsl’ file contains all the Capybara methods that need to be “mixed in”.
Mixing in Capybara
So let’s handle that mix in process. Change the script accordingly:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
require 'capybara/dsl' require 'rspec/expectations' Capybara.default_driver = :selenium module WeightPage include Capybara::DSL include RSpec::Matchers end class TestRunner include WeightPage def mercury_test end end TestRunner.new.mercury_test |
You see here that you can include the Capybara DSL modules into your own modules or classes. Thus in my WeightPage, Capybara::DSL is included. This means I’m going to be able to use Capybara’s DSL methods that you saw in the first post — like visit(), click_on(), etc — as if I had defined them in my own logic. Notice that this approach is no different than how you would include anything else, such as RSpec’s matchers. If you follow this pattern in your own code, you can now mix in — and thus use — any of the standard Capybara methods into your own module or class methods.
You’ll notice that this script is using something called the default_driver
. As mentioned in the first post, Capybara defaults to assuming you are testing against a Rack-based application. That means that behind the scenes the Capybara.default_driver that I used above is set to :rack_test. That would mean the driver being used is Rack::Test as opposed to Selenium WebDriver. Thus, upon execution, no browser would open and no HTTP requests would be made. With the session object, I just passed the driver I wanted to the session. However, here I’m not using a session object any more. So here I set the default_driver to be :selenium so that Capybara knows what driver and library to apply.
First, let’s check that the DSL part works. Let’s use the visit() method.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
require 'capybara/dsl' require 'rspec/expectations' Capybara.default_driver = :selenium module WeightPage include Capybara::DSL include RSpec::Matchers def url 'http://localhost:9292/weight' end end class TestRunner include WeightPage def mercury_test visit url end end TestRunner.new.mercury_test |
If you run this, you’ll find that the weight page is visited in the browser. Note that I’m calling the Capybara visit() method directly in mercury_test(). That’s because the TestRunner is including WeightPage and WeightPage is including the Capybara DSL.
Now, let’s fill out the mercury_test() method in the TestRunner class and provide stub methods in the WeightPage module:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
require 'capybara/dsl' require 'rspec/expectations' Capybara.default_driver = :selenium module WeightPage include Capybara::DSL include RSpec::Matchers def url 'http://localhost:9292/weight' end def convert_weight_for_mercury(value) end def expect_exact_weight(merc_weight) end def expect_approximate_weight(merc_weight) end end class TestRunner include WeightPage def mercury_test visit url convert_weight_for_mercury(200) expect_exact_weight(75.6) expect_approximate_weight(75) end end TestRunner.new.mercury_test |
Notice here that I’m calling a series of methods within mercury_test() and those are being called against the WeightPage module, which is included in the TestRunner. What this is doing is allowing me to treat WeightPage as a page object, of sorts. It’s not quite the same thing as a page object since I’m not instantiating WeightPage at all but, conceptually, the same pattern applies.
The Page Object pattern is a simple way of structuring your test framework if you are dealing with browser-based applications. You simply model each page — or even each major UI component — as an object. Aspects such as CSS selectors can be stored as properties on the page object and you can implement methods to manipulate the page.
Now let’s fill in the logic for the methods in WeightPage:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
require 'capybara/dsl' require 'rspec/expectations' Capybara.default_driver = :selenium module WeightPage include Capybara::DSL include RSpec::Matchers def url 'http://localhost:9292/weight' end def convert_weight_for_mercury(value) fill_in 'wt', with: value click_on 'calculate' end def expect_exact_weight(merc_weight) expect(mercury_value.to_f).to eq(merc_weight) end def expect_approximate_weight(merc_weight) expect(mercury_value.to_i).to be_within(0.9).of(merc_weight.to_f) end private def mercury_value page.find_field('outputmrc').value end end class TestRunner include WeightPage def mercury_test visit url convert_weight_for_mercury(200) expect_exact_weight(75.6) expect_approximate_weight(75) end end TestRunner.new.mercury_test |
I’m not saying that the particular coding practices I applied here are necessarily stellar but they should give you an idea of what’s possible when you include the Capybara DSL into your own logic. As just one example, you saw how this allowed you to use the page object pattern fairly effectively.
Using an Actual Test Runner with Capybara
You might want to use Ruby’s basic unit test library (Test::Unit) and have your Capybara-based tests run in the context of that. To try this out, create a file called capy-test-unit.rb. Let’s first get the structure in place:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
require 'capybara' require 'minitest/autorun' class CapybaraTest < Minitest::Test include Capybara::DSL def teardown Capybara.reset_sessions! Capybara.use_default_driver end end class GUITest < CapybaraTest def setup Capybara.current_driver = :selenium end end class MercuryWeightTest < GUITest def test_weight_on_mercury end end |
To be clear, here I’m using Minitest rather than Test::Unit directly. Ruby 1.9 included an updatd version of Test::Unit and called this new version MiniTest. There are many ways to structure your test logic. Above I have my MercuryWeightTest which inherits from GUITest. I do this because perhaps I might also have an APITest that I can run the same test logic against. You can see I have a test_weight_on_mercury() method. The GUITest derives from the CapybaraTest class.
You can see that in the CapybaraTest class I’m mixing in the Capybara DSL. The inheritance chain you just saw is what’s going to allow me to use Capybara’s API in my tests. Apart from setting the default driver in the setup() block, you see that I also use a teardown block. This makes a call to something referred to as reset_sessions!
and you might wonder what that is. This call invokes some code in the underlying driver. I say “some code” because that code will differ depending on the driver. In the case of Selenium, for example, this call deletes all cookies to ensure that you start each scenario with no leakage from the previous one. Different drivers will provide their own implementation for reset_sessions.
All that’s needed here is to use the logic we have been using and populate the test method:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
require 'capybara' require 'minitest/autorun' class CapybaraTest < Minitest::Test include Capybara::DSL def teardown Capybara.reset_sessions! Capybara.use_default_driver end end class GUITest < CapybaraTest def setup Capybara.current_driver = :selenium end end class MercuryWeightTest < GUITest def test_weight_on_mercury visit 'http://localhost:9292/weight' assert page.has_content? 'Your Weight On Astronomical Bodies' fill_in 'wt', with: '200' click_on 'calculate' assert page.find_field('outputmrc').value == '75.6' assert_in_delta 75, page.find_field('outputmrc').value.to_f, 0.9 end end |
The output from running this file should be something like this:
Run options: --seed 46617 # Running: . Finished in 10.128993s, 0.0987 runs/s, 0.2962 assertions/s. 1 runs, 3 assertions, 0 failures, 0 errors, 0 skips
I won’t go into too much detail here since really it’s just another example of fitting in the DSL to a specific test framework rather than a home-grown one like the TestRunner example. What this shows you however is that rather than using your own custom test runner, as we did in capy-module.rb, you can hook Capybara into existing test solutions.
Using RSpec with Capybara
Continuing the theme, let’s look at hooking Capybara into RSpec. RSpec is without doubt one of the most popular Ruby test frameworks. Here I’ll show you the minimum to get the above example working in RSpec. I don’t intend to teach too much about RSpec here since that’s an entire topic unto itself, so it will help if you’ve had some exposure to it. First, in some project directory, create a directory called spec. Within that directory create a file called spec_helper.rb. This is going to be a small bit of logic:
1 2 3 |
require 'capybara/rspec' Capybara.default_driver = :selenium |
Notice here how I’m not using require 'capybara'
as in the first post or even require 'capybara/dsl'
as above. Instead I’m loading some RSpec support that Capybara provides by requiring ‘capybara/rspec’. This essentially lets you mix in the DSL to your specs.
Now, in that /spec directory, create a file called weight_on_mercury_spec.rb and put the following in it:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
require 'spec_helper' describe "weight", :type => :feature do it "will be calculated for Mercury" do visit 'http://localhost:9292/weight' expect(page).to have_content 'Your Weight On Astronomical Bodies' fill_in 'wt', with: '200' click_on 'calculate' expect(page.find_field('outputmrc').value).to eq('75.6') expect(page.find_field('outputmrc').value.to_f).to be_within(0.9).of('75'.to_f) end end |
If from your project directory (the one that contains the /spec directory), you run the command rspec
, your output from that should be something like this:
. Finished in 10.57 seconds (files took 1.25 seconds to load) 1 example, 0 failures
In RSpec the describe() block is an example group. It contains a series of it() blocks that execute tests. Something to note above is that I “tagged” the example group with :type => :feature
. This is necessary so that RSpec incorporates the Capybara DSL into the context for each RSpec block.
All of this runs through an actual browser, just as we’ve been doing, but now let’s consider some special aspects of headless execution. Most testers want a headless browser with good JavaScript support. A headless browser is one that runs without a UI. Headless browsers typically run tests faster than opening a real browser and make it easy to run in continuous integration environments, where it’s often the case that a windowing environment — whether Microsoft Windows or X11 on Linux — is not available. Capybara provides a couple of options here. Capybara-WebKit is a driver that wraps QtWebKit. It provides a WebKit browser implementation that works well for headless implementations. However, one logistical difficulty is that you will need to install the Qt system libraries separately. Further, there is still a reliance on X11 being present on Linux distributions despite the browser being headless.
Poltergeist is another driver, which, in the background, will use QtWebKit. The difference here is that it wraps PhantomJS, which can be a little less difficult to work with than Capybara-WebKit. You will still need to install PhantomJS as a system dependency, but the PhantomJS project has tackled some of the issues around making QtWebKit purely headless. Specifically, you will not need X11 and you will not need to install the entire Qt framework, because PhantomJS bundles all this for you.
So let’s test this out by using Poltergeist. You will need the Poltergeist gem and the PhantomJS driver installed. See the respective links above to handle that. Once you’ve done that, change your spec_helper.rb file to look like this:
1 2 3 4 5 |
require 'capybara/rspec' require 'capybara/poltergeist' Capybara.default_driver = :selenium Capybara.javascript_driver = :poltergeist |
So now in order to get a test to run headlessly using Capybara, you can use :js => true
tag to switch to the Capybara.javascript_driver. This driver will be :selenium by default. However you can provide a specific driver option to switch to one specific driver, which you can see I did above. Now add a new test to weight_on_mercury_spec.rb:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
require 'spec_helper' describe "weight", :type => :feature do it "will be calculated for Mercury" do visit 'http://localhost:9292/weight' expect(page).to have_content 'Your Weight On Astronomical Bodies' fill_in 'wt', with: '200' click_on 'calculate' expect(page.find_field('outputmrc').value).to eq('75.6') expect(page.find_field('outputmrc').value.to_f).to be_within(0.9).of('75'.to_f) end it "will be calculated for Mercury headlessly", :js => true do visit 'http://localhost:9292/weight' expect(page).to have_content 'Your Weight On Astronomical Bodies' fill_in 'wt', with: '200' click_on 'calculate' expect(page.find_field('outputmrc').value).to eq('75.6') expect(page.find_field('outputmrc').value.to_f).to be_within(0.9).of('75'.to_f) end end |
Now when you run this, you will see the first test call up the browser as normal. The second test, however, will run without a browser appearing.
Finally, Capybara does add a DSL for writing descriptive “feature style” acceptance tests using RSpec. You can change the previous script to this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
require 'spec_helper' feature "weight" do scenario "will be calculated for Mercury" do visit 'http://localhost:9292/weight' expect(page).to have_content 'Your Weight On Astronomical Bodies' fill_in 'wt', with: '200' click_on 'calculate' expect(page.find_field('outputmrc').value).to eq('75.6') expect(page.find_field('outputmrc').value.to_f).to be_within(0.9).of('75'.to_f) end scenario "will be calculated for Mercury headlessly", :js => true do visit 'http://localhost:9292/weight' expect(page).to have_content 'Your Weight On Astronomical Bodies' fill_in 'wt', with: '200' click_on 'calculate' expect(page.find_field('outputmrc').value).to eq('75.6') expect(page.find_field('outputmrc').value.to_f).to be_within(0.9).of('75'.to_f) end end |
Here feature
is an alias for describe ..., :type => :feature
and scenario
is an alias for it
. Again, it’s important to understand that this works because of how Capybara is integrating itself into RSpec.
Using Cucumber with Capybara
Finally, let’s make sure you see how to apply Capybara in a simple Cucumber project. As with RSpec, my goal here isn’t to teach Cucumber but rather just to show you how to integrate Capybara into it in the easiest way possible. Create a directory called features. Within that directory, create two more directories: support and steps.
In the features directory, create a file called weight.feature and put the following in it:
1 2 3 4 5 6 7 |
Ability: Calculate Weight on Other Planets Scenario: Mercury Given the weight page When the weight calculated is "200" Then the weight on Mercury will be exactly "75.6" And the weight on Mercury will be roughly "75" |
In features/support create a file called env.rb and put the following in it:
1 2 3 4 5 6 7 8 9 |
require 'capybara' Capybara.default_driver = :selenium Before do Capybara.reset_sessions! end World(Capybara::DSL) |
Here Capybara is required and then the DSL is placed into Cucumber’s context by passing it World(). Note that a Before hook is used to reset the sessions before each scenario is run. This matches conceptually what we did earlier with the Test::Unit example.
Finally, in the features/steps directory create a file called weight_steps.rb and put the following in it:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
Given(/^the weight page$/) do visit 'http://localhost:9292/weight' expect(page).to have_content 'Your Weight On Astronomical Bodies' end When(/^the weight calculated is "(.*?)"$/) do |value| fill_in 'wt', with: value click_on 'calculate' end Then(/^the weight on Mercury will be exactly "(.*?)"$/) do |value| expect(find_field('outputmrc').value).to eq(value) end Then(/^the weight on Mercury will be roughly "(.*?)"$/) do |value| expect(find_field('outputmrc').value.to_f).to be_within(0.9).of(value.to_f) end |
Your output from this should be something like this:
Ability: Calculate Weight on Other Planets Scenario: Mercury # features\weight.feature:3 Given the weight page # features/steps/weight_steps.rb:1 When the weight calculated is "200" # features/steps/weight_steps.rb:6 Then the weight on Mercury will be exactly "75.6" # features/steps/weight_steps.rb:11 And the weight on Mercury will be roughly "75" # features/steps/weight_steps.rb:15 1 scenario (1 passed) 4 steps (4 passed) 0m4.474s
As a note, you can incorporate the Capybara DLS directly into Cucumber just as you did with RSpec in a slightly simpler way. Change your env.rb file to this:
1 2 3 4 5 6 7 |
require 'capybara/cucumber' Capybara.default_driver = :selenium Before do Capybara.reset_sessions! end |
Finally, let’s try to run a headless example like we did with RSpec. As before you’ll have to set up a JavaScript driver. Change env.rb to this:
1 2 3 4 5 6 7 8 9 |
require 'capybara/cucumber' require 'capybara/poltergeist' Capybara.default_driver = :selenium Capybara.javascript_driver = :poltergeist Before do Capybara.reset_sessions! end |
Now add a headless scenario to your feature file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
Ability: Calculate Weight on Other Planets Scenario: Mercury Given the weight page When the weight calculated is "200" Then the weight on Mercury will be exactly "75.6" And the weight on Mercury will be roughly "75" @javascript Scenario: Mercury (Headless) Given the weight page When the weight calculated is "200" Then the weight on Mercury will be exactly "75.6" And the weight on Mercury will be roughly "75" |
What you see here is that Capybara also hooks into any Cucumber scenarios you have tagged with @javascript and automatically switches you to the driver you have set up to handle JavaScript.
As with RSpec, you’ll see the above runs the first scenario through a browser and runs the second scenario headlessly.
So what you’ve seen here is that you can use Capybara in various contexts, including your own custom test runners, as well as leveraging the wider ecosystem of Ruby-based test solutions. The fact that you can incorporate the Capybara DSL allows you to write your own custom frameworks. Many people have done just this, such as with AE Page Objects, Capybara UI, SitePrism and Kookaburra.