We are a software consultancy based in Berlin, Germany. We deliver
high quality web apps in short timespans.

Upstream Agile GmbH Logo

Testing Couchapps with Cucumber and Culerity

October 25, 2009 by alex

On last week’s RailsCamp UK I started hacking on a new CouchApp called HejHej. Its purpose is to help me learn Swedish, but what’s more important here: I wrote this app BDD style using Cucumber, the famous BDD tool and Culerity, my humble addition that allows me to test any webapp (including client side JavaScript) with it.

The whole thing is available on Github so you can check out the features and steps there. In the following post I will show the necessary steps to test your own CouchApps with Cucumber.

First of all you will need to install some software:

  1. Java - most computers have it installed already (OSX does). If not go to java.sun.com
  2. JRuby - Culerity is written in Ruby but wraps a Java library called HTMLUnit, hence we need both. You can get JRuby at jruby.org. Just download the tar and extract it to any directory.
  3. Culerity - comes as a RubyGem that you will have to install into JRuby. To install run JRUBY_HOME/bin/jruby -S gem install culerity --source=http://gemcutter.org
  4. Cucumber - jruby -S gem install cucumber
  5. RestClient - helps to clean up the test database: jruby -S gem install rest-client jruby-openssl

Next change to your CouchApp’s directory and create the following directory structure:


  CouchApp root
  |- features
     |- support
        |- env.rb
        |- paths.rb
     |- step_definitions
        |- common_culerity_steps.rb

Copy and paste the following into your env.rb:


  require 'rubygems'
  require 'culerity'

  require 'cucumber/formatter/unicode'

  require 'restclient'

  Before do
    RestClient.delete "#{host}/#{database}" rescue nil
    RestClient.put "#{host}/#{database}", ""
    system "couchapp push"
  end

Warning: the before hook you see there will delete and recreate your CouchDB database before every run, so make sure you are using a separate database for testing than for your actual “production” database.

This is what your common_culerity_steps.rb should look like:


  require 'culerity'

  Symbol.class_eval do
    def to_proc
      Proc.new{|object| object.send(self)}
    end
  end unless :symbol.respond_to?(:to_proc)

  Before do
    $server ||= Culerity::run_server
    $browser = Culerity::RemoteBrowserProxy.new $server, {:browser => :firefox, :javascript_exceptions => true, :resynchronize => true, :status_code_exceptions => true}
    $browser.log_level = :warning
  end

  def host
    'http://localhost:5984'
  end

  def database
    'hejhej'
  end

  at_exit do
    $browser.exit if $browser
    $server.close if $server
  end

  When /I press "(.*)"/ do |button|
    button = [$browser.button(:text, button), $browser.button(:id, button)].find(&:exist?)
    button.click
    When 'I wait for the AJAX call to finish'
  end

  When /I click "(.*)"/ do |link|
    When "I follow \"#{link}\""
  end

  When /I follow "(.*)"/ do |link|
    _link = [[:text, /^#{Regexp.escape(link)}$/], [:id, link], [:title, link]].map{|args| $browser.link(*args)}.find{|__link| __link.exist?}
    raise "link \"#{link}\" not found" unless _link
    _link.click
    When 'I wait for the AJAX call to finish'
  end

  When /I follow \/(.*)\// do |link|
    $browser.link(:text, /#{link}/).click
    When 'I wait for the AJAX call to finish'
  end

  When /I fill in "(.*)" with "(.*)"/ do |field, value|
    find_by_label_or_id(:text_field, field).set value
  end

  When /I attach "(.*)" to "(.*)"/ do |value, field|
    $browser.file_field(:id, find_label(field).for).set(value)
  end

  When /I check "(.*)"/ do |field|
    find_by_label_or_id(:check_box, field).set true
  end

  def find_by_label_or_id(element, field)
    begin
      $browser.send(element, :id, find_label(/#{field}/).for)
    rescue #Celerity::Exception::UnknownObjectException
      $browser.send element, :id, field
    end
  end

  When /^I uncheck "(.*)"$/ do |field|
    $browser.check_box(:id, find_label(field).for).set(false)
  end

  When /^I select "([^"]+)" from "([^"]+)"$/ do |value, field|
    find_by_label_or_id(:select_list, field).select value
  end

  When /^I select "([^"]+)"$/ do |value|
    $browser.option(:text => value).select
  end

  When /I choose "(.*)"/ do |field|
    $browser.radio(:id, find_label(field).for).set(true)
  end

  When /I go to the (.+)/ do |path|
    $browser.goto host + path_to(path)
  end

  When /I wait for the AJAX call to finish/ do
    sleep 0.4
  end

  When /^I visit "([^"]+)"$/ do |url|
    $browser.goto host + url
  end

  Then /^I should see "(.*)"$/ do |text|
    Then "I should see /#{Regexp.escape(text)}/"
  end

  Then /^I should see \/(.*)\/$/ do |text|
    # if we simply check for the browser.html content
    # we don't find content that has been added dynamically,
    # e.g. after an ajax call
    div = $browser.div(:text, /#{text}/)
    begin
      div.html
    rescue
      #puts $browser.html
      raise("div with '#{text}' not found")
    end
  end


  Then /I should see the text "(.*)"/ do |text|
    $browser.html.should include(text)
  end

  Then /I should not see the text "(.*)"/ do |text|
    $browser.html.should_not include(text)
  end

  Then /I should not see "(.*)"/ do |text|
    div = $browser.div(:text, /#{text}/).html rescue nil
    div.should be_nil
  end

  Then /I should see no link "([^"]+)"/ do |text|
    $browser.link(:text => text).should_not exist
  end

  Then /I should not find the page "([^"]+)"/ do |url|
    no_exception = false
    begin
      $browser.goto host + url
      no_exception = true
    rescue => e
      e.message.should =~ /404/
    end
    no_exception.should be_false
  end

  Then /^"([^\"]*)" should be chosen$/ do |field|
    find_by_label_or_id(:radio, field).should be_checked
  end

  Then /^"([^\"]*)" should be checked$/ do |field|
    find_by_label_or_id(:check_box, field).should be_checked
  end

  Then /^"([^\"]*)" should not be checked$/ do |field|
    find_by_label_or_id(:check_box, field).should_not be_checked
  end

  Then /^"([^\"]*)" should be selected$/ do |selection|
    $browser.option(:text => selection).should be_selected
  end

  When 'show response' do
    p $browser.url
    open_response_in_browser
  end


  def find_label(text)
    $browser.label :text, text
  end

  def open_response_in_browser
    tmp_file = '/tmp/culerity_results.html'
    FileUtils.rm_f tmp_file
    File.open(tmp_file, 'w') do |f|
      f < < $browser.div(:id, 'content').html
    end
    `open #{tmp_file}`
  end
</code>

Make sure to change the database name (and host if necessary).

Finally the paths.rb


  module NavigationHelpers
    def path_to(page_name)
      case page_name
      when /start page/
        "/#{database}/_design/#{database}/index.html"
      else
        raise "Can't find mapping from \"#{page_name}\" to a path."
      end
    end
  end

  World(NavigationHelpers)

Now that you are done with the prerequisites you can write your first feature. Create a file start_page.feature in the features directory and paste the following:


  Feature: start page
    In order to feel welcome
    As a user
    I want to be welcomed on the start page

    Scenario: go to the start page
      When I go to the start page
      Then I should see "Welcome"

Now you can run your first feature:

jruby -S cucumber features/start_page.feature

Congratulations, you can now test drive your application. Explore the step definitions in the common_culerity_steps.rb in order to learn how to traverse links (When I follow “link”), fill out forms etc.

In order to make the feature pass you should add the text “Welcome” to the index.html of your CouchApp.

Hello Explorer!

You’ve found our very old Website Archive (2007-2012). It’s a bit out of date, but we love looking back at how it all started - and how far we’ve come since then! We’re now fully focused on building our coworking management product, Cobot.

Take a look →