Testing Web Apps Developed via Vagrant Using Capybara

20130427 Aigi Tunnel Group 1

As a software consultancy, we often perform additional or maintenance development on a previous project. It’s great to be able to maintain positive customer relationships and provide good value to them, but I admit I often dread returning to an old project. The main reason for my fear and trembling is trying to get the development environment up and running again. In the interim since the last time I (or someone else) touched the project, any number of things can go wrong. Just recently I encountered the following nightmare dependency change:

  • My version of Chrome had updated
  • which required me to update my Chrome selenium driver
  • which required me to update Selenium
  • which required me to update my selenium gem
  • which required me to update my ruby version…

I don’t know why I updated my development environment; perhaps I’ll die.

I’ve encountered similar problems with other development libraries and applications, whether it’s MySQL or build-essentials or nginx.

So when I started work on a new web app recently, I swore to protect myself from this madness as best I could. I turned to Vagrant and Chef in a bid to automate the setup of my development environment. In short, developing in this method means you write all your code inside a Linux virtual machine, either on your own machine or in the cloud. That’s all well and good for most things — bash or zsh is still there, and vim still works — but periodically, some aspect of development you take for granted will provide a challenge.

One of the first snags I hit was testing with Capybara. Unless you use phantomjs for your engine (which caused some timing issues for us), you will need to have some way to fake rendering in X. This includes headless webkit testing with capybara-webkit (built on Qt), which is our preferred Capybara driver. So I had to update my Vagrant to include the “libqt4” cookbook, and make sure xvfb was running when I ran my specs.

A more subtle problem was how to handle investigating test failures. One of the most powerful tools in your arsenal with Capybara is the save_and_open_page method, which saves the current page’s HTML and opens it in your browser. Well, wouldn’t you know it, this doesn’t work over SSH on your virtual machine. But it is so useful, I had to find a way to replicate it. Here’s what I came up with:

  1. In your spec_helper.rb, set Capybara.save_and_open_page_path = "/vagrant/capybara_output". Anything in the /vagrant directory is automatically copied back to the host machine in the same directory where your Vagrantfile lives.
  2. Instead of calling save_and_open_page>, just call save_page so that it merely saves the HTML and doesn’t try to open it. (Or, if you are feeling naughty, monkey patch save_and_open_page to prevent Capybara from trying to open the page.)
  3. Now you need to make your host machine open your browser to the newly saved HTML. I set up the following Rake task to run on my host. It opens any recently saved page in capybara_output (code lightly modified from what Capybara does):
    desc "open a page saved by capybara"
    task :open_saved_page do
      file_name = Dir.glob(File.join('./capybara_output', '*.*')).max { |a,b| File.ctime(a) <=> File.ctime(b) }
      if file_name && (Time.now - File.ctime(file_name) < 300) # last 5 minutes
          require "launchy"
        rescue LoadError
          warn "Page saved to #{file_name} with save_and_open_page."
          warn "Please install the launchy gem to open page automatically."
  4. I also added the following Guardfile to make watch for file changes:
    guard :rake, task: 'open_saved_page' do
      watch (%r{^capybara_output/.+\.html$})
  5. Now, once my guard task is running, any call to save_page in my tests on my virtual machine will result in opening the HTML on my host machine.

Another little tip: if you want to use the asset_host for Capybara, just port forward 3000 to your virtual machine port. Then as long as you have rails running in development, you can just use http://localhost:3000 as Capybara.asset_host, and it will work the same as if your web app were running on your own machine.

I'm enjoying the process of creating a well-defined development environment, but what I'm really looking forward to is when this work pays off down the road. I'm confident from past experiences that it will be.