From f8de784356986b4a21c346da0bef54960fa763bc Mon Sep 17 00:00:00 2001 From: Rikesh Dhokia Date: Sun, 26 Jun 2011 16:22:44 +1000 Subject: [PATCH 1/2] rikesh: adding capybara support so we can increase out acceptance level testing coverage --- Gemfile | 1 + Gemfile.lock | 19 ++ capybara_features/advanced_search.feature | 24 +++ capybara_features/capybara.feature | 9 + .../step_definitions/database_steps.rb | 52 +++++ .../step_definitions/web_steps.rb | 201 ++++++++++++++++++ capybara_features/support/env.rb | 54 +++++ capybara_features/support/hooks.rb | 10 + capybara_features/support/paths.rb | 137 ++++++++++++ capybara_features/support/reset_couchdb.rb | 11 + config/cucumber.yml | 1 + lib/tasks/cucumber.rake | 16 +- 12 files changed, 530 insertions(+), 5 deletions(-) create mode 100644 capybara_features/advanced_search.feature create mode 100644 capybara_features/capybara.feature create mode 100644 capybara_features/step_definitions/database_steps.rb create mode 100644 capybara_features/step_definitions/web_steps.rb create mode 100644 capybara_features/support/env.rb create mode 100644 capybara_features/support/hooks.rb create mode 100644 capybara_features/support/paths.rb create mode 100644 capybara_features/support/reset_couchdb.rb diff --git a/Gemfile b/Gemfile index 138d4137e..ef8296216 100644 --- a/Gemfile +++ b/Gemfile @@ -28,5 +28,6 @@ group :test do gem 'rspec', '1.3.2' gem 'rspec-rails', '1.3.4' gem 'webrat', '0.7.1' + gem 'capybara', '1.0.0' end diff --git a/Gemfile.lock b/Gemfile.lock index 7499dc0fd..41b588411 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -13,6 +13,15 @@ GEM activesupport (= 2.3.11) activesupport (2.3.11) builder (2.1.2) + capybara (1.0.0) + mime-types (>= 1.16) + nokogiri (>= 1.3.3) + rack (>= 1.0.0) + rack-test (>= 0.5.4) + selenium-webdriver (~> 0.2.0) + xpath (~> 0.1.4) + childprocess (0.1.9) + ffi (~> 1.0.6) configuration (1.2.0) couchrest (0.34) mime-types (>= 1.15) @@ -29,6 +38,7 @@ GEM diff-lcs (1.1.2) escape (0.0.4) fastercsv (1.5.3) + ffi (1.0.9) gherkin (2.2.9) json (~> 1.4.6) term-ansicolor (~> 1.0.5) @@ -73,6 +83,12 @@ GEM rspec-rails (1.3.4) rack (>= 1.0.0) rspec (~> 1.3.1) + rubyzip (0.9.4) + selenium-webdriver (0.2.2) + childprocess (>= 0.1.9) + ffi (>= 1.0.7) + json_pure + rubyzip subexec (0.0.4) sunspot (1.1.0) escape (= 0.0.4) @@ -84,11 +100,14 @@ GEM nokogiri (>= 1.2.0) rack (>= 1.0) rack-test (>= 0.5.3) + xpath (0.1.4) + nokogiri (~> 1.3) PLATFORMS ruby DEPENDENCIES + capybara (= 1.0.0) couchrest (= 0.34) cucumber (= 0.9.4) cucumber-rails (= 0.3.2) diff --git a/capybara_features/advanced_search.feature b/capybara_features/advanced_search.feature new file mode 100644 index 000000000..bc48044e3 --- /dev/null +++ b/capybara_features/advanced_search.feature @@ -0,0 +1,24 @@ +@wip +Feature: So that I can find a child that has been entered in to RapidFTR + As a user of the website + I want to use the advanced search criteria to find all relevant results + + Background: + Given I am logged in + And I am on child advanced search page + + @javascript + Scenario: Searching for children by the user who entered the details + + Given the following children exist in the system: + | name | created_by | + | Willis | aid_worker_1 | + | Will | aid_worker_2 | + + When I check "created_by" + And I fill in "aid_worker_1" for "created_by_value" + And I press "Search" + + Then I should be on the child advanced search results page + +# And I should see "Willis" in the search results diff --git a/capybara_features/capybara.feature b/capybara_features/capybara.feature new file mode 100644 index 000000000..3a8a1aeb4 --- /dev/null +++ b/capybara_features/capybara.feature @@ -0,0 +1,9 @@ +@javascript +Feature: Test capybara is configured correctly + + Scenario: Log into RapidFTR + Given a user "rapidftr" with a password "rapidftr" + Given I am on the login page + When I fill in "rapidftr" for "user_name" + And I fill in "rapidftr" for "password" + And I press "Log in" diff --git a/capybara_features/step_definitions/database_steps.rb b/capybara_features/step_definitions/database_steps.rb new file mode 100644 index 000000000..d5e3e23a5 --- /dev/null +++ b/capybara_features/step_definitions/database_steps.rb @@ -0,0 +1,52 @@ +Given /^an? (user|admin) "([^\"]*)" with(?: a)? password "([^\"]*)"$/ do |user_type, username, password| + user_type = user_type == 'user' ? 'User' : 'Administrator' + @user = User.new( + :user_name=>username, + :password=>password, + :password_confirmation=>password, + :user_type=> user_type, + :full_name=>username, + :email=>"#{username}@test.com") + @user.save! +end + +Given /^an? (user|admin) "([^"]+)"$/ do |user_type, user_name| + Given %(a #{user_type} "#{user_name}" with password "123") +end + +Given /^I am logged in as "(.+)"/ do |user_name| + @session = Session.for_user(User.find_by_user_name(user_name), nil) + @session.save! + @session.put_in_cookie cookies +end + +Given /^I have an expired session/ do + @session.destroy +end + +Given /^user "(.+)" is disabled$/ do |username| + user = User.find_by_user_name(username) + user.disabled = true + user.save! +end + +Then /^user "(.+)" should be disabled$/ do |username| + User.find_by_user_name(username).should be_disabled +end + +Then /^user "(.+)" should not be disabled$/ do |username| + User.find_by_user_name(username).should_not be_disabled +end + +Given /^the following admin contact info:$/ do |table| + contact_info = table.hashes.inject({}) do |result, current| + result[current["key"]] = current["value"] + result + end + contact_info[:id] = "administrator" + ContactInformation.create contact_info +end + + + + diff --git a/capybara_features/step_definitions/web_steps.rb b/capybara_features/step_definitions/web_steps.rb new file mode 100644 index 000000000..060bef87e --- /dev/null +++ b/capybara_features/step_definitions/web_steps.rb @@ -0,0 +1,201 @@ +# IMPORTANT: This file is generated by cucumber-rails - edit at your own peril. +# It is recommended to regenerate this file in the future when you upgrade to a +# newer version of cucumber-rails. Consider adding your own code to a new file +# instead of editing this one. Cucumber will automatically load all features/**/*.rb +# files. + +require 'uri' +require File.expand_path(File.join(File.dirname(__FILE__), "..", "support", "paths")) + +module WithinHelpers + def with_scope(locator) + locator ? within(locator) { yield } : yield + end +end +World(WithinHelpers) + +Given /^(?:|I )am on (.+)$/ do |page_name| + visit path_to(page_name) +end + +When /^(?:|I )go to (.+)$/ do |page_name| + visit path_to(page_name) +end + +When /^(?:|I )press "([^\"]*)"(?: within "([^\"]*)")?$/ do |button, selector| + with_scope(selector) do + click_button(button) + end +end + +When /^(?:|I )follow "([^\"]*)"(?: within "([^\"]*)")?$/ do |link, selector| + with_scope(selector) do + click_link(link) + end +end + +When /^(?:|I )fill in "([^\"]*)" with "([^\"]*)"(?: within "([^\"]*)")?$/ do |field, value, selector| + with_scope(selector) do + fill_in(field, :with => value) + end +end + +When /^(?:|I )fill in "([^\"]*)" for "([^\"]*)"(?: within "([^\"]*)")?$/ do |value, field, selector| + with_scope(selector) do + fill_in(field, :with => value) + end +end + +# Use this to fill in an entire form with data from a table. Example: +# +# When I fill in the following: +# | Account Number | 5002 | +# | Expiry date | 2009-11-01 | +# | Note | Nice guy | +# | Wants Email? | | +# +# TODO: Add support for checkbox, select og option +# based on naming conventions. +# +When /^(?:|I )fill in the following(?: within "([^\"]*)")?:$/ do |selector, fields| + with_scope(selector) do + fields.rows_hash.each do |name, value| + When %{I fill in "#{name}" with "#{value}"} + end + end +end + +When /^(?:|I )select "([^\"]*)" from "([^\"]*)"(?: within "([^\"]*)")?$/ do |value, field, selector| + with_scope(selector) do + select(value, :from => field) + end +end + +When /^(?:|I )check "([^\"]*)"(?: within "([^\"]*)")?$/ do |field, selector| + with_scope(selector) do + check(field) + end +end + +When /^(?:|I )uncheck "([^\"]*)"(?: within "([^\"]*)")?$/ do |field, selector| + with_scope(selector) do + uncheck(field) + end +end + +When /^(?:|I )choose "([^\"]*)"(?: within "([^\"]*)")?$/ do |field, selector| + with_scope(selector) do + choose(field) + end +end + +When /^(?:|I )attach the file "([^\"]*)" to "([^\"]*)"(?: within "([^\"]*)")?$/ do |path, field, selector| + with_scope(selector) do + attach_file(field, path) + end +end + +Then /^(?:|I )should see "([^\"]*)"(?: within "([^\"]*)")?$/ do |text, selector| + with_scope(selector) do + if defined?(Spec::Rails::Matchers) + page.should have_content(text) + else + assert page.has_content?(text) + end + end +end + +Then /^(?:|I )should see \/([^\/]*)\/(?: within "([^\"]*)")?$/ do |regexp, selector| + regexp = Regexp.new(regexp) + with_scope(selector) do + if defined?(Spec::Rails::Matchers) + page.should have_xpath('//*', :text => regexp) + else + assert page.has_xpath?('//*', :text => regexp) + end + end +end + +Then /^(?:|I )should not see "([^\"]*)"(?: within "([^\"]*)")?$/ do |text, selector| + with_scope(selector) do + if defined?(Spec::Rails::Matchers) + page.should have_no_content(text) + else + assert page.has_no_content?(text) + end + end +end + +Then /^(?:|I )should not see \/([^\/]*)\/(?: within "([^\"]*)")?$/ do |regexp, selector| + regexp = Regexp.new(regexp) + with_scope(selector) do + if defined?(Spec::Rails::Matchers) + page.should have_no_xpath('//*', :text => regexp) + else + assert page.has_no_xpath?('//*', :text => regexp) + end + end +end + +Then /^the "([^\"]*)" field(?: within "([^\"]*)")? should contain "([^\"]*)"$/ do |field, selector, value| + with_scope(selector) do + if defined?(Spec::Rails::Matchers) + find_field(field).value.should =~ /#{value}/ + else + assert_match(/#{value}/, field_labeled(field).value) + end + end +end + +Then /^the "([^\"]*)" field(?: within "([^\"]*)")? should not contain "([^\"]*)"$/ do |field, selector, value| + with_scope(selector) do + if defined?(Spec::Rails::Matchers) + find_field(field).value.should_not =~ /#{value}/ + else + assert_no_match(/#{value}/, find_field(field).value) + end + end +end + +Then /^the "([^\"]*)" checkbox(?: within "([^\"]*)")? should be checked$/ do |label, selector| + with_scope(selector) do + if defined?(Spec::Rails::Matchers) + find_field(label)['checked'].should == 'checked' + else + assert_equal 'checked', field_labeled(label)['checked'] + end + end +end + +Then /^the "([^\"]*)" checkbox(?: within "([^\"]*)")? should not be checked$/ do |label, selector| + with_scope(selector) do + if defined?(Spec::Rails::Matchers) + find_field(label)['checked'].should_not == 'checked' + else + assert_not_equal 'checked', field_labeled(label)['checked'] + end + end +end + +Then /^(?:|I )should be on (.+)$/ do |page_name| + if defined?(Spec::Rails::Matchers) + URI.parse(current_url).path.should == path_to(page_name) + else + assert_equal path_to(page_name), URI.parse(current_url).path + end +end + +Then /^(?:|I )should have the following query string:$/ do |expected_pairs| + actual_params = CGI.parse(URI.parse(current_url).query) + expected_params = Hash[expected_pairs.rows_hash.map{|k,v| [k,[v]]}] + + if defined?(Spec::Rails::Matchers) + actual_params.should == expected_params + else + assert_equal expected_params, actual_params + end +end + +Then /^show me the page$/ do + save_and_open_page +end diff --git a/capybara_features/support/env.rb b/capybara_features/support/env.rb new file mode 100644 index 000000000..3060122fd --- /dev/null +++ b/capybara_features/support/env.rb @@ -0,0 +1,54 @@ +# IMPORTANT: This file is generated by cucumber-rails - edit at your own peril. +# It is recommended to regenerate this file in the future when you upgrade to a +# newer version of cucumber-rails. Consider adding your own code to a new file +# instead of editing this one. Cucumber will automatically load all features/**/*.rb +# files. + +ENV["RAILS_ENV"] ||= "cucumber" +require File.expand_path(File.dirname(__FILE__) + '/../../config/environment') + +require 'cucumber/formatter/unicode' # Remove this line if you don't want Cucumber Unicode support +require 'cucumber/rails/rspec' +require 'cucumber/rails/world' +require 'cucumber/rails/active_record' +require 'cucumber/web/tableish' + +require 'capybara/rails' +require 'capybara/cucumber' + +require 'spec/stubs/cucumber' + +Capybara.run_server = true #Whether start server when testing +Capybara.default_selector = :xpath #default selector , you can change to :css +Capybara.default_wait_time = 2 #When we testing AJAX, we can set a default wait time +Capybara.ignore_hidden_elements = false #Ignore hidden elements when testing, make helpful when you hide or show elements using javascript +Capybara.javascript_driver = :selenium #default driver when you using @javascript tag + +# If you set this to false, any error raised from within your app will bubble +# up to your step definition and out to cucumber unless you catch it somewhere +# on the way. You can make Rails rescue errors and render error pages on a +# per-scenario basis by tagging a scenario or feature with the @allow-rescue tag. +# +# If you set this to true, Rails will rescue all errors and render error +# pages, more or less in the same way your application would behave in the +# default production environment. It's not recommended to do this for all +# of your scenarios, as this makes it hard to discover errors in your application. +ActionController::Base.allow_rescue = false + +# If you set this to true, each scenario will run in a database transaction. +# You can still turn off transactions on a per-scenario basis, simply tagging +# a feature or scenario with the @no-txn tag. If you are using Capybara, +# tagging with @culerity or @javascript will also turn transactions off. +# +# If you set this to false, transactions will be off for all scenarios, +# regardless of whether you use @no-txn or not. +# +# Beware that turning transactions off will leave data in your database +# after each scenario, which can lead to hard-to-debug failures in +# subsequent scenarios. If you do this, we recommend you create a Before +# block that will explicitly put your database in a known state. +Cucumber::Rails::World.use_transactional_fixtures = true + +# How to clean your database when transactions are turned off. See +# http://github.com/bmabey/database_cleaner for more info. + diff --git a/capybara_features/support/hooks.rb b/capybara_features/support/hooks.rb new file mode 100644 index 000000000..fff8c2528 --- /dev/null +++ b/capybara_features/support/hooks.rb @@ -0,0 +1,10 @@ + +# Before each scenario... +Before do +# CouchRestRails::Tests.setup +end + +# After each scenario... +After do +# CouchRestRails::Tests.teardown +end \ No newline at end of file diff --git a/capybara_features/support/paths.rb b/capybara_features/support/paths.rb new file mode 100644 index 000000000..4d01352b3 --- /dev/null +++ b/capybara_features/support/paths.rb @@ -0,0 +1,137 @@ +module NavigationHelpers + # Maps a name to a path. Used by the + # + # When /^I go to (.+)$/ do |page_name| + # + # step definition in web_steps.rb + # + def path_to(page_name, options = {}) + + format = page_name[/^(?:|the )(\w+) formatted/,1] + options.reverse_merge!( :format => format ) + + case page_name + + when /the home\s?page/ + '/' + when /the new create_custom_field page/ + new_create_custom_field_path + + when /the new create_custom_fields.feature page/ + new_create_custom_fields.feature_path + + when /the new add_suggested_field_to_form_section page/ + new_add_suggested_field_to_form_section_path + + when /the new assign_unique_id_to_a_child page/ + new_assign_unique_id_to_a_child_path(options) + + when /add child page/ + new_child_path(options) + + when /new child page/ + new_child_path(options) + + when /children listing page/ + children_path(options) + + when /saved record page for child with name "(.+)"/ + child_name = $1 + child = Summary.by_name(:key => child_name) + raise "no child named '#{child_name}'" if child.nil? + child_path( child, options ) + + when /child record page for "(.+)"/ + child_name = $1 + child = Summary.by_name(:key => child_name) + raise "no child named '#{child_name}'" if child.nil? + child_path( child, options ) + + when /change log page for "(.+)"/ + child_name = $1 + child = Summary.by_name(:key => child_name) + raise "no child named '#{child_name}'" if child.nil? + child_history_path( child, options ) + + when /new user page/ + new_user_path(options) + + when /manage users page/ + users_path(options) + + when /edit user page for "(.+)"/ + user = User.find_by_user_name($1) + edit_user_path(user, options) + + when /child search page/ + search_children_path(options) + + when /child advanced search page/ + advanced_search_index_path(options) + + when /login page/ + login_path(options) + + when /logout page/ + logout_path(options) + + when /child search results page/ + search_children_path(options) + + when /child advanced search results page/ + advanced_search_index_path(options) + + when /create form section page/ + new_formsection_path(options) + + when /edit form section page for "(.+)"$/ + edit_form_section_path(:id => $1) + + when /edit field page for "(.+)" on "(.+)" form$/ + edit_formsection_field_path(:formsection_id => $2, :id => $1) + + when /form section page/ + formsections_path(options) + + when /choose field type page/ + arbitrary_form_section = FormSection.new + new_formsection_field_path( arbitrary_form_section, options ) + + when /the edit user page for "(.+)"$/ + user = User.by_user_name(:key => $1) + raise "no user named #{$1}" if user.nil? + edit_user_path(user) + + when /new field page for "(.+)"/ + field_type = $1 + new_formsection_field_path(:type => field_type) + + when /the edit form section page for "(.+)"/ + form_section = $1 + formsection_fields_path(form_section) + + when /the admin page/ + admin_path(options) + + when /the edit administrator contact information page/ + edit_contact_information_path(:administrator) + when /(the )?administrator contact page/ + contact_information_path(:administrator, options) + + when /all child Ids/ + child_ids_path + + # Add more mappings here. + # Here is an example that pulls values out of the Regexp: + # + # when /^(.*)'s profile page$/i + # user_profile_path(User.find_by_login($1)) + + else + raise "Can't find mapping from \"#{page_name}\" to a path.\n" + + "Now, go and add a mapping in #{__FILE__}" + end + end +end + +World(NavigationHelpers) diff --git a/capybara_features/support/reset_couchdb.rb b/capybara_features/support/reset_couchdb.rb new file mode 100644 index 000000000..6d8ad5116 --- /dev/null +++ b/capybara_features/support/reset_couchdb.rb @@ -0,0 +1,11 @@ + +Before do + Session.all.each {|s| s.destroy } + Child.all.each {|c| c.destroy } + User.all.each {|u| u.destroy } + SuggestedField.all.each {|u| u.destroy } + ContactInformation.all.each {|c| c.destroy } + RapidFTR::FormSectionSetup.reset_definitions + Sunspot.remove_all!(Child) + Sunspot.commit +end diff --git a/config/cucumber.yml b/config/cucumber.yml index b5b82b09c..03a34f214 100644 --- a/config/cucumber.yml +++ b/config/cucumber.yml @@ -1,2 +1,3 @@ default: --format progress features --strict --tags ~@wip +browser: --format progress capybara_features --strict --tags @javascript --tags ~@wip wip: --tags @wip:3 --wip features diff --git a/lib/tasks/cucumber.rake b/lib/tasks/cucumber.rake index ab04dc887..af7dbf972 100644 --- a/lib/tasks/cucumber.rake +++ b/lib/tasks/cucumber.rake @@ -14,7 +14,7 @@ begin require 'cucumber/rake/task' namespace :cucumber do - Cucumber::Rake::Task.new({:ok => 'db:test:prepare'}, 'Run features that should pass') do |t| + Cucumber::Rake::Task.new({:headless => 'db:test:prepare'}, 'Run features that should pass in headless mode') do |t| t.binary = vendored_cucumber_bin # If nil, the gem's binary is used. t.fork = true # You may get faster startup if you set this to false t.profile = 'default' @@ -26,11 +26,17 @@ begin t.profile = 'wip' end - desc 'Run all features' - task :all => [:ok, :wip] + Cucumber::Rake::Task.new({:browser => 'db:test:prepare'}, 'Run all features that should pass in a browser') do |t| + t.binary = vendored_cucumber_bin + t.fork = true # You may get faster startup if you set this to false + t.profile = 'browser' + end + + desc 'Run all features in headless and browser modes' + task :all => [:headless, :browser] end - desc 'Alias for cucumber:ok' - task :cucumber => 'cucumber:ok' + desc 'Alias for cucumber:headless and cucumber:browser' + task :cucumber => 'cucumber:all' task :default => :cucumber From 8cb3e542dc1975cda158448ac4a3158b40ae710b Mon Sep 17 00:00:00 2001 From: Rikesh Dhokia Date: Sun, 26 Jun 2011 16:48:44 +1000 Subject: [PATCH 2/2] rikesh: rename wip cucumber task to headless_wip --- lib/tasks/cucumber.rake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tasks/cucumber.rake b/lib/tasks/cucumber.rake index af7dbf972..932a35016 100644 --- a/lib/tasks/cucumber.rake +++ b/lib/tasks/cucumber.rake @@ -20,7 +20,7 @@ begin t.profile = 'default' end - Cucumber::Rake::Task.new({:wip => 'db:test:prepare'}, 'Run features that are being worked on') do |t| + Cucumber::Rake::Task.new({:headless_wip => 'db:test:prepare'}, 'Run features that are being worked on in headless mode') do |t| t.binary = vendored_cucumber_bin t.fork = true # You may get faster startup if you set this to false t.profile = 'wip'