From d7f05fbfd49592f71743dc51696c985bf8e49847 Mon Sep 17 00:00:00 2001 From: Luke Melia Date: Thu, 19 Jun 2008 00:14:04 -0400 Subject: [PATCH] Started moving selenium on rails tests over to RSpec stories. See the updated README_DEVELOPERS for info on running the new tests. --- app/views/notes/_notes.rhtml | 5 +- doc/README_DEVELOPERS | 27 +++++- stories/helper.rb | 33 +++++-- stories/notes/view_add_remove.story | 41 ++++++++ stories/steps/login.rb | 19 +--- stories/steps/notes.rb | 94 +++++++++++++++++++ stories/steps/users.rb | 30 ++++++ test/selenium/notes/badge_count.rsel | 27 ------ test/selenium/notes/link_to_note.rsel | 5 - test/selenium/notes/see_all_notes.rsel | 5 - test/selenium/notes/toggle_notes.rsel | 5 - .../lib/webrat/selenium/selenium_session.rb | 40 ++++++-- 12 files changed, 257 insertions(+), 74 deletions(-) create mode 100644 stories/notes/view_add_remove.story create mode 100644 stories/steps/notes.rb create mode 100644 stories/steps/users.rb delete mode 100644 test/selenium/notes/badge_count.rsel delete mode 100644 test/selenium/notes/link_to_note.rsel delete mode 100644 test/selenium/notes/see_all_notes.rsel delete mode 100644 test/selenium/notes/toggle_notes.rsel diff --git a/app/views/notes/_notes.rhtml b/app/views/notes/_notes.rhtml index 3a020010..9391d108 100644 --- a/app/views/notes/_notes.rhtml +++ b/app/views/notes/_notes.rhtml @@ -10,12 +10,13 @@ :title =>"Delete this note", :class=>"delete_item", :id => "delete_note_"+note.id.to_s), - :update => dom_id(note), + {:update => dom_id(note), :loading => visual_effect(:fade, dom_id(note, 'container')), :complete => "Element.remove('#{dom_id(note, 'container')}');", :url => note_path(note), :method => :delete, - :confirm => "Are you sure that you want to delete the note \'#{note.id.to_s}\'?" ) + " " -%> + :confirm => "Are you sure that you want to delete the note \'#{note.id.to_s}\'?" }, + { :class => 'delete_note' }) -%>  <%= link_to_function(image_tag( "blank.png", :title => "Edit item", :class=>"edit_item"), "Element.toggle('#{dom_id(note)}'); Element.toggle('#{dom_id(note, 'edit')}'); Effect.Appear('#{dom_id(note, 'edit')}'); Form.focusFirstElement('#{dom_id(note, 'edit_form')}');" ) + " | " %> <%= link_to("In: " + note.project.name, project_path(note.project), :class=>"footer_link" ) %> |  diff --git a/doc/README_DEVELOPERS b/doc/README_DEVELOPERS index e8f9bde0..5e48875a 100644 --- a/doc/README_DEVELOPERS +++ b/doc/README_DEVELOPERS @@ -12,8 +12,10 @@ To avoid showing the migrations as tests are run, add the following to your data If you want to run tests using another database, that's fine, too. Just change your database.yml accordingly. -3. SELENIUM TESTS +3. SELENIUM TESTS (Selenium on Rails) +This testing style is deprecated and are being moved over to Selenium via RSpec stories by lukemelia (See #4 below for the new style). + To run selenium tests, start Tracks in test mode using script/server -e test @@ -24,4 +26,25 @@ Then open a browser to and interact with the test runner. -For more information about Selenium on Rails, see vendor/plugins/selenium-on-rails/README \ No newline at end of file +For more information about Selenium on Rails, see vendor/plugins/selenium-on-rails/README + +4. RSPEC STORY RUNNER TESTS + +To run the stories, which are browser tests using selenium, start Tracks in test mode using + + rake db:test:prepare + script/server -e test + +Then start Selenium by running + + selenium + +which is a script installed with the Selenium gem (sudo gem install Selenium) + +Once the site and selenium server are running, then run all stories with + + script/story + +or a specific set with + + script/story stories/login/*.story diff --git a/stories/helper.rb b/stories/helper.rb index 8ad77009..db0c50ca 100644 --- a/stories/helper.rb +++ b/stories/helper.rb @@ -7,6 +7,7 @@ require 'spec' require 'spec/rails' require 'spec/story' require 'webrat/selenium' +require 'action_controller/test_process' module Spec module Story @@ -47,10 +48,30 @@ class SeleniumRailsStory < Test::Unit::TestCase end end + def should_not_see(text_or_regexp) + if text_or_regexp.is_a?(Regexp) + response.should_not have_tag("*", text_or_regexp) + else + response.should_not have_tag("*", /#{Regexp.escape(text_or_regexp)}/i) + end + end + def response webrat_session.response_body end + def logged_in_as(user) + visits("/selenium_helper/login?as=#{user.login}") + end + + def selenium + SeleniumDriverManager.instance.running_selenium_driver + end + + def badge_count_should_show(count) + response.should have_tag('#badge_count', count.to_s) + end + def method_missing(name, *args) if webrat_session.respond_to?(name) webrat_session.send(name, *args) @@ -70,6 +91,7 @@ end class DatabaseResetListener include Singleton + def scenario_started(*args) if defined?(ActiveRecord::Base) connection = ActiveRecord::Base.connection @@ -79,24 +101,23 @@ class DatabaseResetListener end end - def scenario_succeeded(*args) + def method_missing sym, *args, &block + # ignore all messages you don't care about end - alias :scenario_pending :scenario_succeeded - alias :scenario_failed :scenario_succeeded end class CookieResetListener include Singleton + def scenario_started(*args) %w[tracks_login auth_token _session_id].each do |cookie_name| SeleniumDriverManager.instance.running_selenium_driver.get_eval("window.document.cookie = '#{cookie_name}=;expires=Thu, 01-Jan-1970 00:00:01 GMT; path=/';") end end - def scenario_succeeded(*args) + def method_missing sym, *args, &block + # ignore all messages you don't care about end - alias :scenario_pending :scenario_succeeded - alias :scenario_failed :scenario_succeeded end class Spec::Story::Runner::ScenarioRunner diff --git a/stories/notes/view_add_remove.story b/stories/notes/view_add_remove.story new file mode 100644 index 00000000..34e519fe --- /dev/null +++ b/stories/notes/view_add_remove.story @@ -0,0 +1,41 @@ +Story: View, add, remove notes + + As a Tracks user + I want to view, add, and remove notes + So that I can keep important information easily accessible + + Scenario: View and toggle notes + + Given a logged in user Luis + And Luis has two projects with one note each + When Luis visits the notes page + Then two notes should be visible + And the badge should show 2 + When Luis clicks Toggle Notes + Then the body of the notes should be shown + + Scenario: Add a new note + + Given a logged in user Luis + And Luis has one project Pass Final Exam with no notes + When Luis adds a note from the Pass Final Exam project page + Then Luis should see the note on the Pass Final Exam project page + And Luis should see the note on the notes page + And the badge on the notes page should show 1 + + Scenario: Delete note from notes page + + Given a logged in user Luis + And Luis has one project Pass Final Exam with 2 notes + When Luis visits the notes page + And Luis deletes the first note + Then the first note should disappear + Then the badge should show 1 + + Scenario: Link to note + Given a logged in user Luis + And Luis has one project Pass Final Exam with 1 note + When Luis visits the Pass Final Exam project page + And clicks the icon next to the note + Then he should see the note text + \ No newline at end of file diff --git a/stories/steps/login.rb b/stories/steps/login.rb index 42599521..8729a5e8 100644 --- a/stories/steps/login.rb +++ b/stories/steps/login.rb @@ -1,19 +1,6 @@ -steps_for :login do - Given "an admin user Reinier with the password abracadabra" do - @reinier = User.create!(:login => 'reinier', :password => 'abracadabra', :password_confirmation => 'abracadabra', :is_admin => true) - @reinier.create_preference - end - - Given "Reinier is not logged in" do - end - - Given "no users exist" do - User.delete_all - end - - Given "a visitor named Reinier" do - end - +steps_for :login do + include_steps_for :users + When "Reinier submits the login form with an incorrect password" do fills_in 'Login', :with => 'reinier' fills_in 'Password', :with => 'incorrectpass' diff --git a/stories/steps/notes.rb b/stories/steps/notes.rb new file mode 100644 index 00000000..aa17722a --- /dev/null +++ b/stories/steps/notes.rb @@ -0,0 +1,94 @@ +steps_for :notes do + include_steps_for :users + + Given "Luis has two projects with one note each" do + project_a = @luis.projects.create!(:name => 'project A') + project_a.notes.create!(:user_id => @luis.id, :body => 'note for project A') + project_b = @luis.projects.create!(:name => 'project B') + project_b.notes.create!(:user_id => @luis.id, :body => 'note for project B') + end + + Given "Luis has one project Pass Final Exam with no notes" do + @exam_project = @luis.projects.create!(:name => 'Pass Final Exam') + end + + Given "Luis has one project Pass Final Exam with 1 note" do + Given "Luis has one project Pass Final Exam with no notes" + @exam_project.notes.create!(:user_id => @luis.id, :body => 'exam note 1') + end + + Given "Luis has one project Pass Final Exam with 2 notes" do + Given "Luis has one project Pass Final Exam with 1 note" + @exam_project.notes.create!(:user_id => @luis.id, :body => 'exam note 2') + end + + When "Luis visits the notes page" do + visits '/notes' + end + + When "Luis adds a note from the Pass Final Exam project page" do + When "Luis visits the Pass Final Exam project page" + clicks_link 'Add a note', :wait => :ajax + fills_in 'new_note_body', :with => 'new exam note' + clicks_button 'Add note', :wait => :ajax + end + + When "Luis visits the Pass Final Exam project page" do + visits "/projects/#{@exam_project.to_param}" + end + + When "Luis deletes the first note" do + selenium.click "css=a.delete_note" + selenium.get_confirmation.should =~ /delete/ + end + + When "clicks the icon next to the note" do + selenium.click "css=a.link_to_notes" + wait_for_page_to_load + end + + When "Luis clicks Toggle Notes" do + clicks_link 'Toggle notes', :wait => :effects + end + + Then "the body of the notes should be shown" do + wait_for_effects + selenium.is_visible("css=body.notes").should be_true + end + + Then "Luis should see the note on the Pass Final Exam project page" do + should_see "new exam note" + end + + Then "Luis should see the note on the notes page" do + visits '/notes' + should_see "new exam note" + end + + Then "the badge on the notes page should show 1" do + badge_count_should_show(1) + end + + Then "the first note should disappear" do + wait_for_ajax_and_effects + should_not_see 'exam note 1' + end + + Then "the badge should show 1" do + wait_for_ajax_and_effects + badge_count_should_show(1) + end + + Then "the badge should show 2" do + badge_count_should_show(2) + end + + Then "two notes should be visible" do + should_see 'note for project A' + should_see 'note for project B' + end + + Then "he should see the note text" do + should_see 'exam note 1' + end +end \ No newline at end of file diff --git a/stories/steps/users.rb b/stories/steps/users.rb new file mode 100644 index 00000000..834c41d0 --- /dev/null +++ b/stories/steps/users.rb @@ -0,0 +1,30 @@ +steps_for :users do + + Given "an admin user Reinier with the password abracadabra" do + @reinier = User.create!(:login => 'reinier', :password => 'abracadabra', :password_confirmation => 'abracadabra', :is_admin => true) + @reinier.create_preference + end + + Given "an admin user Reinier" do + Given "an admin user Reinier with the password abracadabra" + end + + Given "a logged in user Luis" do + @luis = User.create!(:login => 'luis', :password => 'sesame', :password_confirmation => 'sesame', :is_admin => false) + @luis.create_preference + logged_in_as @luis + end + + Given "no users exist" do + User.delete_all + end + + Given "Reinier is not logged in" do + #nothing to do + end + + Given "a visitor named Reinier" do + #nothing to do + end + +end \ No newline at end of file diff --git a/test/selenium/notes/badge_count.rsel b/test/selenium/notes/badge_count.rsel deleted file mode 100644 index ace3d85d..00000000 --- a/test/selenium/notes/badge_count.rsel +++ /dev/null @@ -1,27 +0,0 @@ -setup :fixtures => :all -login :as => 'admin' -open "/notes/" -assert_text 'badge_count', '2' - -# add new note -open "/projects/1" -click "css=#add_note_href" -type "css=#new_note_body", "new note" -click "add-new-note" - -# wait until new note is saved -wait_for_text_not_present "new note" -wait_for_text_present "new note" - -# check badge count is one more -open "/notes/" -assert_text 'badge_count', '3' - -# delete note -click "css=#delete_note_1" -assert_confirmation "Are you sure that you want to delete the note '1'?" - -# check badge decreased -wait_for_visible "flash" -wait_for_element_not_present "container_note_1" -assert_text 'badge_count', '2' diff --git a/test/selenium/notes/link_to_note.rsel b/test/selenium/notes/link_to_note.rsel deleted file mode 100644 index a9a3ae7e..00000000 --- a/test/selenium/notes/link_to_note.rsel +++ /dev/null @@ -1,5 +0,0 @@ -setup :fixtures => :all -login :as => 'admin' -open "/projects/1" -click_and_wait "css=#note_1 .link_to_notes" -assert_element_present "note_1" diff --git a/test/selenium/notes/see_all_notes.rsel b/test/selenium/notes/see_all_notes.rsel deleted file mode 100644 index 83b9f52c..00000000 --- a/test/selenium/notes/see_all_notes.rsel +++ /dev/null @@ -1,5 +0,0 @@ -setup :fixtures => :all -login :as => 'admin' -open "/notes/" -assert_element_present "note_1" -assert_element_present "note_2" diff --git a/test/selenium/notes/toggle_notes.rsel b/test/selenium/notes/toggle_notes.rsel deleted file mode 100644 index c7551b32..00000000 --- a/test/selenium/notes/toggle_notes.rsel +++ /dev/null @@ -1,5 +0,0 @@ -setup :fixtures => :all -login :as => 'admin' -open "/notes" -click "css=#toggle-notes-nav" -wait_for_element_present "css=body.notes" diff --git a/vendor/plugins/webrat/lib/webrat/selenium/selenium_session.rb b/vendor/plugins/webrat/lib/webrat/selenium/selenium_session.rb index cbd0baea..b00735f3 100644 --- a/vendor/plugins/webrat/lib/webrat/selenium/selenium_session.rb +++ b/vendor/plugins/webrat/lib/webrat/selenium/selenium_session.rb @@ -11,24 +11,52 @@ module Webrat end def fills_in(label_text, options) - @selenium.type("webrat=#{Regexp.escape(label_text)}", "#{options[:with]}") + @selenium.type("webrat=#{label_text}", "#{options[:with]}") end def response_body @selenium.get_html_source end - def clicks_button(button_text = nil) + def clicks_button(button_text = nil, options = {}) + button_text, options = nil, button_text if button_text.is_a?(Hash) && options == {} button_text ||= '*' @selenium.click("button=#{button_text}") - @selenium.wait_for_page_to_load() + wait_for_result(options[:wait]) end - def clicks_link(link_text) - @selenium.click("webratlink=#{Regexp.escape(link_text)}") - @selenium.wait_for_page_to_load() + def clicks_link(link_text, options = {}) + @selenium.click("webratlink=#{link_text}") + wait_for_result(options[:wait]) end + def wait_for_result(wait_type) + if wait_type == :ajax + wait_for_ajax + elsif wait_type == :effects + wait_for_effects + else + wait_for_page_to_load + end + end + + def wait_for_page_to_load(timeout = 15000) + @selenium.wait_for_page_to_load(timeout) + end + + def wait_for_ajax(timeout = 15000) + @selenium.wait_for_condition "window.Ajax.activeRequestCount == 0", timeout + end + + def wait_for_effects(timeout = 15000) + @selenium.wait_for_condition "window.Effect.Queue.size() == 0", timeout + end + + def wait_for_ajax_and_effects + wait_for_ajax + wait_for_effects + end + def selects(option_text, options = {}) id_or_name_or_label = options[:from]