commit a86f8ca56fc044656ac93a9fabbf88a8df5916bd Author: Bryan Helmkamp Date: Sun Mar 2 15:14:52 2008 -0500 Import from subversion diff --git a/CHANGELOG b/CHANGELOG new file mode 100644 index 0000000..3f93184 --- /dev/null +++ b/CHANGELOG @@ -0,0 +1,18 @@ +== SVN + +* Allow specifying the input name/label when doing a select (Patch from David Chelimsky) +* Raise a specific exception if the developer tries to manipulate form elements before loading a page (Patch from James Deville) +* Add basic support for Rails-generated JavaScript link tags +* Ensure Rails-style checkboxes work properly (checkboxes followed by a hidden input with the same name) +* Add support for alternate POST, PUT and DELETE link clicking (Patch from Kyle Hargraves) +* Add support for checkboxes (Patches from Kyle Hargraves and Jarkko Laine) +* Improve matching for labels in potentially ambiguous cases +* Add support for textarea fields (Patch from Sacha Schlegel) +* Fix Edge Rails (a.k.a. 2.0 RC) compatibility (Patch from David Chelimsky) +* Support param hashes nested more than one level (Patch from David Chelimsky) + +== 0.1.0 / 2007-11-28 + +* 1 major enhancement + * Birthday! + diff --git a/MIT-LICENSE b/MIT-LICENSE new file mode 100644 index 0000000..33f7cda --- /dev/null +++ b/MIT-LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2007 Bryan Helmkamp, Seth Fitzsimmons + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README b/README new file mode 100644 index 0000000..ab096ac --- /dev/null +++ b/README @@ -0,0 +1,78 @@ += Webrat - Ruby Acceptance Testing for Web applications + +by Bryan Helmkamp and Seth Fitzsimmons . + +Initial development sponsored by EastMedia (http://www.eastmedia.com). + +== DESCRIPTION: + +Webrat lets you quickly write robust and thorough acceptance tests for a Ruby +web application. By leveraging the DOM, it can run tests similarly to an +in-browser testing solution without the associated performance hit (and +browser dependency). The result is tests that are less fragile and more +effective at verifying that the app will respond properly to users. + +When comparing Webrat with an in-browser testing solution like Watir or +Selenium, the primary consideration should be how much JavaScript the +application uses. In-browser testing is currently the only way to test JS, and +that may make it a requirement for your project. If JavaScript is not central +to your application, Webrat is a simpler, effective solution that will let you +run your tests much faster and more frequently. (Benchmarks forthcoming.) + +== SYNOPSIS: + + def test_sign_up + visits "/" + clicks_link "Sign up" + fills_in "Email", :with => "good@example.com" + select "Free account" + clicks_button "Register" + ... + end + +Behind the scenes, this will perform the following work: + +1. Verify that loading the home page is successful +2. Verify that a "Sign up" link exists on the home page +3. Verify that loading the URL pointed to by the "Sign up" link leads to a + successful page +4. Verify that there is an "Email" input field on the Sign Up page +5. Verify that there is an select field on the Sign Up page with an option for + "Free account" +6. Verify that there is a "Register" submit button on the page +7. Verify that submitting the Sign Up form with the values "good@example.com" + and "Free account" leads to a successful page + +Take special note of the things _not_ specified in that test, that might cause +tests to break unnecessarily as your application evolves: + +* The input field IDs or names (e.g. "user_email" or "user[email]"), which + could change if you rename a model +* The ID of the form element (Webrat can do a good job of guessing, even if + there are multiple forms on the page.) +* The URLs of links followed +* The URL the form submission should be sent to, which could change if you + adjust your routes or controllers +* The HTTP method for the login request + +A test written with Webrat can handle these changes smoothly. + +== REQUIREMENTS: + +* Rails >= 1.2.6 +* Hpricot >= 0.6 +* Rails integration tests in Test::Unit _or_ +* RSpec stories (using an RSpec version >= revision 2997) + +== INSTALL: + + $ ruby script/plugin install http://svn.eastmedia.net/public/plugins/webrat/ + +== HISTORY: + +See CHANGELOG in this directory. + +== LICENSE: + +Copyright (c) 2007 Bryan Helmkamp, Seth Fitzsimmons. +See MIT-LICENSE in this directory. diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..a559268 --- /dev/null +++ b/Rakefile @@ -0,0 +1,26 @@ +require 'rake' +require 'rake/testtask' +require 'rake/rdoctask' + +desc 'Default: run unit tests.' +task :default => :test + +desc 'Test the webrat plugin.' +Rake::TestTask.new(:test) do |t| + t.pattern = 'test/**/*_test.rb' + t.verbose = true +end + +desc 'Generate RDoc documentation for the Webrat plugin.' +Rake::RDocTask.new(:rdoc) do |rdoc| + rdoc.rdoc_dir = 'rdoc' + rdoc.title = 'Webrat' + rdoc.options << '--line-numbers' << '--inline-source' + rdoc.rdoc_files.include('README') + rdoc.rdoc_files.include('lib/**/*.rb') +end + +desc "Upload rdoc to brynary.com" +task :publish_rdoc => :rdoc do + sh "scp -r rdoc/ brynary.com:/apps/uploads/webrat" +end \ No newline at end of file diff --git a/TODO b/TODO new file mode 100644 index 0000000..4600c74 --- /dev/null +++ b/TODO @@ -0,0 +1,5 @@ +Option button support +Full support for multiple forms on a page +Track the current form based on the location of the last manipulated input, use this as a default for clicks_button +Make current_url work with redirections +Support for a hash mapping page names to page URLs \ No newline at end of file diff --git a/init.rb b/init.rb new file mode 100644 index 0000000..0e19c46 --- /dev/null +++ b/init.rb @@ -0,0 +1,3 @@ +if RAILS_ENV == "test" + require File.join(File.dirname(__FILE__), "lib", "webrat") +end diff --git a/install.rb b/install.rb new file mode 100644 index 0000000..bd23f1b --- /dev/null +++ b/install.rb @@ -0,0 +1 @@ +puts IO.read(File.join(File.dirname(__FILE__), 'README')) diff --git a/lib/webrat.rb b/lib/webrat.rb new file mode 100644 index 0000000..6c695dc --- /dev/null +++ b/lib/webrat.rb @@ -0,0 +1,2 @@ +require File.join(File.dirname(__FILE__), "webrat", "rails_extensions") +require File.join(File.dirname(__FILE__), "webrat", "session") diff --git a/lib/webrat/rails_extensions.rb b/lib/webrat/rails_extensions.rb new file mode 100644 index 0000000..2f89cf1 --- /dev/null +++ b/lib/webrat/rails_extensions.rb @@ -0,0 +1,22 @@ +module ActionController + module Integration + + class Session + # Waiting for http://dev.rubyonrails.org/ticket/10497 to be committed. + def put_via_redirect(path, parameters = {}, headers = {}) + put path, parameters, headers + follow_redirect! while redirect? + status + end + + # Waiting for http://dev.rubyonrails.org/ticket/10497 to be committed. + def delete_via_redirect(path, parameters = {}, headers = {}) + delete path, parameters, headers + follow_redirect! while redirect? + status + end + + end + + end +end diff --git a/lib/webrat/session.rb b/lib/webrat/session.rb new file mode 100644 index 0000000..a709362 --- /dev/null +++ b/lib/webrat/session.rb @@ -0,0 +1,438 @@ +require "hpricot" +require "English" + +module ActionController + module Integration + + class Session + # Issues a GET request for a page, follows any redirects, and verifies the final page + # load was successful. + # + # Example: + # visits "/" + def visits(path) + request_page(:get, path) + end + + # Issues a request for the URL pointed to by a link on the current page, + # follows any redirects, and verifies the final page load was successful. + # + # clicks_link has very basic support for detecting Rails-generated + # JavaScript onclick handlers for PUT, POST and DELETE links, as well as + # CSRF authenticity tokens if they are present. + # + # Example: + # clicks_link "Sign up" + def clicks_link(link_text) + link = links.detect { |el| el.innerHTML =~ /#{link_text}/i } + return flunk("No link with text #{link_text.inspect} was found") if link.nil? + + onclick = link.attributes["onclick"] + href = link.attributes["href"] + + http_method = http_method_from_js(onclick) + authenticity_token = authenticity_token_value(onclick) + + request_page(http_method, href, authenticity_token.blank? ? {} : {"authenticity_token" => authenticity_token}) + end + + # Works like clicks_link, but forces a GET request + # + # Example: + # clicks_get_link "Log out" + def clicks_get_link(link_text) + clicks_link_with_method(link_text, :get) + end + + # Works like clicks_link, but issues a DELETE request instead of a GET + # + # Example: + # clicks_delete_link "Log out" + def clicks_delete_link(link_text) + clicks_link_with_method(link_text, :delete) + end + + # Works like clicks_link, but issues a POST request instead of a GET + # + # Example: + # clicks_post_link "Vote" + def clicks_post_link(link_text) + clicks_link_with_method(link_text, :post) + end + + # Works like clicks_link, but issues a PUT request instead of a GET + # + # Example: + # clicks_put_link "Update profile" + def clicks_put_link(link_text) + clicks_link_with_method(link_text, :put) + end + + # Verifies an input field or textarea exists on the current page, and stores a value for + # it which will be sent when the form is submitted. + # + # Examples: + # fills_in "Email", :with => "user@example.com" + # fills_in "user[email]", :with => "user@example.com" + # + # The field value is required, and must be specified in options[:with]. + # field can be either the value of a name attribute (i.e. user[email]) + # or the text inside a element that points at the field. + def fills_in(field, options = {}) + value = options[:with] + return flunk("No value was provided") if value.nil? + input = find_field_by_name_or_label(field) + return flunk("Could not find input #{field.inspect}") if input.nil? + add_form_data(input, value) + # TODO - Set current form + end + + # Verifies that a an option element exists on the current page with the specified + # text. You can optionally restrict the search to a specific select list by + # assigning options[:from] the value of the select list's name or + # a label. Stores the option's value to be sent when the form is submitted. + # + # Examples: + # selects "January" + # selects "February", :from => "event_month" + # selects "February", :from => "Event Month" + def selects(option_text, options = {}) + if options[:from] + select = find_select_list_by_name_or_label(options[:from]) + return flunk("Could not find select list #{options[:from].inspect}") if select.nil? + option_node = find_option_by_value(option_text, select) + return flunk("Could not find option #{option_text.inspect}") if option_node.nil? + else + option_node = find_option_by_value(option_text) + return flunk("Could not find option #{option_text.inspect}") if option_node.nil? + select = option_node.parent + end + add_form_data(select, option_node.attributes["value"] || option_node.innerHTML) + # TODO - Set current form + end + + # Verifies that an input checkbox exists on the current page and marks it + # as checked, so that the value will be submitted with the form. + # + # Example: + # checks 'Remember Me' + def checks(field) + checkbox = find_field_by_name_or_label(field) + return flunk("Could not find checkbox #{field.inspect}") if checkbox.nil? + return flunk("Input #{checkbox.inspect} is not a checkbox") unless checkbox.attributes['type'] == 'checkbox' + add_form_data(checkbox, checkbox.attributes["value"] || "on") + end + + # Verifies that an input checkbox exists on the current page and marks it + # as unchecked, so that the value will not be submitted with the form. + # + # Example: + # unchecks 'Remember Me' + def unchecks(field) + checkbox = find_field_by_name_or_label(field) + return flunk("Could not find checkbox #{field.inspect}") if checkbox.nil? + return flunk("Input #{checkbox.inspect} is not a checkbox") unless checkbox.attributes['type'] == 'checkbox' + remove_form_data(checkbox) + + (form_for_node(checkbox) / "input").each do |input| + next unless input.attributes["type"] == "hidden" && input.attributes["name"] == checkbox.attributes["name"] + add_form_data(input, input.attributes["value"]) + end + end + + # Verifies that a submit button exists for the form, then submits the form, follows + # any redirects, and verifies the final page was successful. + # + # Example: + # clicks_button "Login" + # clicks_button + # + # The URL and HTTP method for the form submission are automatically read from the + # action and method attributes of the
element. + def clicks_button(value = nil) + button = value ? find_button(value) : submit_buttons.first + return flunk("Could not find button #{value.inspect}") if button.nil? + add_form_data(button, button.attributes["value"]) unless button.attributes["name"].blank? + submit_form(form_for_node(button)) + end + + def submits_form(form_id = nil) # :nodoc: + end + + protected # Methods you could call, but probably shouldn't + + def authenticity_token_value(onclick) + return unless onclick && onclick.include?("s.setAttribute('name', 'authenticity_token');") && + onclick =~ /s\.setAttribute\('value', '([a-f0-9]{40})'\);/ + $LAST_MATCH_INFO.captures.first + end + + def http_method_from_js(onclick) + if !onclick.blank? && onclick.include?("f.submit()") + http_method_from_js_form(onclick) + else + :get + end + end + + def http_method_from_js_form(onclick) + if onclick.include?("m.setAttribute('name', '_method')") + http_method_from_fake_method_param(onclick) + else + :post + end + end + + def http_method_from_fake_method_param(onclick) + if onclick.include?("m.setAttribute('value', 'delete')") + :delete + elsif onclick.include?("m.setAttribute('value', 'put')") + :put + else + raise "No HTTP method for _method param in #{onclick.inspect}" + end + end + + def clicks_link_with_method(link_text, http_method) # :nodoc: + link = links.detect { |el| el.innerHTML =~ /#{link_text}/i } + return flunk("No link with text #{link_text.inspect} was found") if link.nil? + request_page(http_method, link.attributes["href"]) + end + + def find_field_by_name_or_label(name_or_label) # :nodoc: + input = find_field_by_name(name_or_label) + return input if input + + label = find_form_label(name_or_label) + label ? input_for_label(label) : nil + end + + def find_select_list_by_name_or_label(name_or_label) # :nodoc: + select = find_select_list_by_name(name_or_label) + return select if select + + label = find_form_label(name_or_label) + label ? select_list_for_label(label) : nil + end + + def find_option_by_value(option_value, select=nil) # :nodoc: + options = select.nil? ? option_nodes : (select / "option") + options.detect { |el| el.innerHTML == option_value } + end + + def find_button(value = nil) # :nodoc: + return nil unless value + submit_buttons.detect { |el| el.attributes["value"] == value } + end + + def add_form_data(input_element, value) # :nodoc: + form = form_for_node(input_element) + data = param_parser.parse_query_parameters("#{input_element.attributes["name"]}=#{value}") + merge_form_data(form_number(form), data) + end + + def remove_form_data(input_element) # :nodoc: + form = form_for_node(input_element) + form_number = form_number(form) + form_data[form_number] ||= {} + form_data[form_number].delete(input_element.attributes['name']) + end + + def submit_form(form) # :nodoc: + form_number = form_number(form) + request_page(form_method(form), form_action(form), form_data[form_number]) + end + + def merge_form_data(form_number, data) # :nodoc: + form_data[form_number] ||= {} + + data.each do |key, value| + if form_data[form_number][key].is_a?(Hash) + merge(form_data[form_number][key], value) + else + form_data[form_number][key] = value + end + end + end + + def merge(a, b) # :nodoc: + a.keys.each do |k| + if b.has_key?(k) and Hash === a[k] and Hash === b[k] + a[k] = merge(a[k], b[k]) + b.delete(k) + end + end + a.merge!(b) + end + + def request_page(method, url, data = {}) # :nodoc: + debug_log "REQUESTING PAGE: #{method.to_s.upcase} #{url} with #{data.inspect}" + @current_url = url + self.send "#{method}_via_redirect", @current_url, data || {} + assert_response :success + reset_dom + end + + def input_for_label(label) # :nodoc: + if input = (label / "input").first + input # nested inputs within labels + else + # input somewhere else, referenced by id + input_id = label.attributes["for"] + (dom / "##{input_id}").first + end + end + + def select_list_for_label(label) # :nodoc: + if select_list = (label / "select").first + select_list # nested inputs within labels + else + # input somewhere else, referenced by id + select_list_id = label.attributes["for"] + (dom / "##{select_list_id}").first + end + end + + def param_parser # :nodoc: + if defined?(CGIMethods) + CGIMethods + else + ActionController::AbstractRequest + end + end + + def submit_buttons # :nodoc: + input_fields.select { |el| el.attributes["type"] == "submit" } + end + + def find_field_by_name(name) # :nodoc: + find_input_by_name(name) || find_textarea_by_name(name) + end + + def find_input_by_name(name) # :nodoc: + input_fields.detect { |el| el.attributes["name"] == name } + end + + def find_select_list_by_name(name) # :nodoc: + select_lists.detect { |el| el.attributes["name"] == name } + end + + def find_textarea_by_name(name) # :nodoc: + textarea_fields.detect{ |el| el.attributes['name'] == name } + end + + def find_form_label(text) # :nodoc: + candidates = form_labels.select { |el| el.innerText =~ /^\W*#{text}\b/i } + candidates.sort_by { |el| el.innerText.strip.size }.first + end + + def form_action(form) # :nodoc: + form.attributes["action"].blank? ? current_url : form.attributes["action"] + end + + def form_method(form) # :nodoc: + form.attributes["method"].blank? ? :get : form.attributes["method"].downcase + end + + def add_default_params # :nodoc: + (dom / "form").each do |form| + add_default_params_for(form) + end + end + + def add_default_params_for(form) # :nodoc: + add_default_params_from_inputs_for(form) + add_default_params_from_checkboxes_for(form) + add_default_params_from_textateas_for(form) + end + + def add_default_params_from_inputs_for(form) # :nodoc: + (form / "input").each do |input| + next if input.attributes["value"].blank? || !%w[text hidden].include?(input.attributes["type"]) + add_form_data(input, input.attributes["value"]) + end + end + + def add_default_params_from_checkboxes_for(form) # :nodoc: + (form / "input").each do |input| + next if input.attributes["type"] != "checkbox" + if input.attributes["checked"] == "checked" + add_form_data(input, input.attributes["value"] || "on") + end + end + end + + def add_default_params_from_textateas_for(form) # :nodoc: + (form / "textarea").each do |input| + add_form_data(input, input.inner_html) + end + end + + def form_for_node(node) # :nodoc: + return node if node.name == "form" + node = node.parent until node.name == "form" + node + end + + def reset_dom # :nodoc: + @form_data = [] + @dom = nil + end + + def form_data # :nodoc: + @form_data ||= [] + end + + def links # :nodoc: + (dom / "a[@href]") + end + + def form_number(form) # :nodoc: + (dom / "form").index(form) + end + + def input_fields # :nodoc: + (dom / "input") + end + + def textarea_fields # :nodoc + (dom / "textarea") + end + + def form_labels # :nodoc: + (dom / "label") + end + + def select_lists # :nodoc: + (dom / "select") + end + + def option_nodes # :nodoc: + (dom / "option") + end + + def dom # :nodoc: + return @dom if @dom + raise "You must visit a path before working with the page." unless response + @dom = Hpricot(response.body) + add_default_params + @dom + end + + def debug_log(message) # :nodoc: + return unless logger + logger.debug + end + + def logger # :nodoc: + if defined? RAILS_DEFAULT_LOGGER + RAILS_DEFAULT_LOGGER + else + nil + end + end + + end + + end +end diff --git a/test/checks_test.rb b/test/checks_test.rb new file mode 100644 index 0000000..7fcd70f --- /dev/null +++ b/test/checks_test.rb @@ -0,0 +1,121 @@ +require File.dirname(__FILE__) + "/helper" + +class ChecksTest < Test::Unit::TestCase + def setup + @session = ActionController::Integration::Session.new + @session.stubs(:assert_response) + @session.stubs(:get_via_redirect) + @session.stubs(:response).returns(@response=mock) + end + + def test_should_fail_if_no_checkbox_found + @response.stubs(:body).returns(<<-EOS) + +
+ EOS + @session.expects(:flunk) + @session.checks "remember_me" + end + + def test_should_fail_if_input_is_not_a_checkbox + @response.stubs(:body).returns(<<-EOS) +
+ +
+ EOS + @session.expects(:flunk) + @session.checks "remember_me" + end + + def test_should_check_rails_style_checkboxes + @response.stubs(:body).returns(<<-EOS) +
+ + + + +
+ EOS + @session.expects(:get_via_redirect).with("/login", "user" => {"tos" => "1"}) + @session.checks "TOS" + @session.clicks_button + end + + def test_should_result_in_the_value_on_being_posted_if_not_specified + @response.stubs(:body).returns(<<-EOS) +
+ + +
+ EOS + @session.expects(:post_via_redirect).with("/login", "remember_me" => "on") + @session.checks "remember_me" + @session.clicks_button + end + + def test_should_result_in_a_custom_value_being_posted + @response.stubs(:body).returns(<<-EOS) +
+ + +
+ EOS + @session.expects(:post_via_redirect).with("/login", "remember_me" => "yes") + @session.checks "remember_me" + @session.clicks_button + end +end + +class UnchecksTest < Test::Unit::TestCase + def setup + @session = ActionController::Integration::Session.new + @session.stubs(:assert_response) + @session.stubs(:get_via_redirect) + @session.stubs(:response).returns(@response=mock) + end + + def test_should_fail_if_no_checkbox_found + @response.stubs(:body).returns(<<-EOS) +
+
+ EOS + @session.expects(:flunk) + @session.unchecks "remember_me" + end + + def test_should_fail_if_input_is_not_a_checkbox + @response.stubs(:body).returns(<<-EOS) +
+ +
+ EOS + @session.expects(:flunk) + @session.unchecks "remember_me" + end + + def test_should_uncheck_rails_style_checkboxes + @response.stubs(:body).returns(<<-EOS) +
+ + + + +
+ EOS + @session.expects(:get_via_redirect).with("/login", "user" => {"tos" => "0"}) + @session.unchecks "TOS" + @session.clicks_button + end + + def test_should_result_in_value_not_being_posted + @response.stubs(:body).returns(<<-EOS) +
+ + +
+ EOS + @session.expects(:post_via_redirect).with("/login", {}) + @session.unchecks "remember_me" + @session.clicks_button + end +end diff --git a/test/clicks_button_test.rb b/test/clicks_button_test.rb new file mode 100644 index 0000000..d4ba7e3 --- /dev/null +++ b/test/clicks_button_test.rb @@ -0,0 +1,218 @@ +require File.dirname(__FILE__) + "/helper" + +class ClicksButtonTest < Test::Unit::TestCase + def setup + @session = ActionController::Integration::Session.new + @session.stubs(:assert_response) + @session.stubs(:get_via_redirect) + @session.stubs(:response).returns(@response=mock) + end + + def test_should_fail_if_no_buttons + @response.stubs(:body).returns(<<-EOS) +
+ EOS + @session.expects(:flunk) + @session.clicks_button + end + + def test_should_fail_if_input_is_not_a_submit_button + @response.stubs(:body).returns(<<-EOS) +
+ +
+ EOS + @session.expects(:flunk) + @session.clicks_button + end + + def test_should_default_to_get_method + @response.stubs(:body).returns(<<-EOS) +
+ +
+ EOS + @session.expects(:get_via_redirect) + @session.clicks_button + end + + def test_should_assert_valid_response + @response.stubs(:body).returns(<<-EOS) +
+ +
+ EOS + @session.expects(:assert_response).with(:success) + @session.clicks_button + end + + def test_should_default_to_current_url + @response.stubs(:body).returns(<<-EOS) +
+ +
+ EOS + @session.expects(:current_url).returns("/current") + @session.expects(:get_via_redirect).with("/current", {}) + @session.clicks_button + end + + def test_should_submit_the_first_form_by_default + @response.stubs(:body).returns(<<-EOS) +
+ +
+
+ +
+ EOS + @session.expects(:get_via_redirect).with("/form1", {}) + @session.clicks_button + end + + def test_should_submit_the_form_with_the_specified_button + @response.stubs(:body).returns(<<-EOS) +
+ +
+
+ +
+ EOS + @session.expects(:get_via_redirect).with("/form2", {}) + @session.clicks_button "Form2" + end + + def test_should_use_action_from_form + @response.stubs(:body).returns(<<-EOS) +
+ +
+ EOS + @session.expects(:get_via_redirect).with("/login", {}) + @session.clicks_button + end + + def test_should_use_method_from_form + @response.stubs(:body).returns(<<-EOS) +
+ +
+ EOS + @session.expects(:post_via_redirect) + @session.clicks_button + end + + def test_should_send_button_as_param_if_it_has_a_name + @response.stubs(:body).returns(<<-EOS) +
+ + +
+ EOS + @session.expects(:post_via_redirect).with("/login", "login" => "Login") + @session.clicks_button("Login") + end + + def test_should_not_send_button_as_param_if_it_has_no_name + @response.stubs(:body).returns(<<-EOS) +
+ + +
+ EOS + @session.expects(:post_via_redirect).with("/login", {}) + @session.clicks_button("Login") + end + + def test_should_send_default_hidden_field_values + @response.stubs(:body).returns(<<-EOS) +
+ + +
+ EOS + @session.expects(:get_via_redirect).with("/login", "user" => {"email" => "test@example.com"}) + @session.clicks_button + end + + def test_should_send_default_text_field_values + @response.stubs(:body).returns(<<-EOS) +
+ + +
+ EOS + @session.expects(:get_via_redirect).with("/login", "user" => {"email" => "test@example.com"}) + @session.clicks_button + end + + def test_should_send_default_checked_fields + @response.stubs(:body).returns(<<-EOS) +
+ + +
+ EOS + @session.expects(:get_via_redirect).with("/login", "user" => {"tos" => "1"}) + @session.clicks_button + end + + def test_should_send_correct_data_for_rails_style_unchecked_fields + @response.stubs(:body).returns(<<-EOS) +
+ + TOS + +
+ EOS + @session.expects(:get_via_redirect).with("/login", "user" => {"tos" => "0"}) + @session.clicks_button + end + + def test_should_send_correct_data_for_rails_style_checked_fields + @response.stubs(:body).returns(<<-EOS) +
+ + TOS + +
+ EOS + @session.expects(:get_via_redirect).with("/login", "user" => {"tos" => "1"}) + @session.clicks_button + end + + def test_should_not_send_default_unchecked_fields + @response.stubs(:body).returns(<<-EOS) +
+ + +
+ EOS + @session.expects(:get_via_redirect).with("/login", {}) + @session.clicks_button + end + + def test_should_send_default_textarea_values + @response.stubs(:body).returns(<<-EOS) +
+ + +
+ EOS + @session.expects(:post_via_redirect).with("/posts", "post" => {"body" => "Post body here!"}) + @session.clicks_button + end + + def test_should_handle_nested_properties + @response.stubs(:body).returns(<<-EOS) +
+ + + +
+ EOS + @session.expects(:post_via_redirect).with("/login", "contestant" => {"scores" => {'1' => '2', '3' => '4'}}) + @session.clicks_button + end +end diff --git a/test/clicks_link_test.rb b/test/clicks_link_test.rb new file mode 100644 index 0000000..5f63ada --- /dev/null +++ b/test/clicks_link_test.rb @@ -0,0 +1,162 @@ +require File.dirname(__FILE__) + "/helper" + +class ClicksLinkTest < Test::Unit::TestCase + def setup + @session = ActionController::Integration::Session.new + @session.stubs(:assert_response) + @session.stubs(:get_via_redirect) + @session.stubs(:response).returns(@response=mock) + end + + def test_should_use_get_by_default + @response.stubs(:body).returns(<<-EOS) + Link text + EOS + @session.expects(:get_via_redirect).with("/page", {}) + @session.clicks_link "Link text" + end + + def test_should_click_get_links + @response.stubs(:body).returns(<<-EOS) + Link text + EOS + @session.expects(:get_via_redirect).with("/page", {}) + @session.clicks_get_link "Link text" + end + + def test_should_click_delete_links + @response.stubs(:body).returns(<<-EOS) + Link text + EOS + @session.expects(:delete_via_redirect).with("/page", {}) + @session.clicks_delete_link "Link text" + end + + def test_should_click_post_links + @response.stubs(:body).returns(<<-EOS) + Link text + EOS + @session.expects(:post_via_redirect).with("/page", {}) + @session.clicks_post_link "Link text" + end + + def test_should_click_put_links + @response.stubs(:body).returns(<<-EOS) + Link text + EOS + @session.expects(:put_via_redirect).with("/page", {}) + @session.clicks_put_link "Link text" + end + + def test_should_click_rails_javascript_links_with_authenticity_tokens + @response.stubs(:body).returns(<<-EOS) + Posts + EOS + @session.expects(:post_via_redirect).with("/posts", "authenticity_token" => "aa79cb354597a60a3786e7e291ed4f74d77d3a62") + @session.clicks_link "Posts" + end + + def test_should_click_rails_javascript_delete_links + @response.stubs(:body).returns(<<-EOS) + Delete + EOS + @session.expects(:delete_via_redirect).with("/posts/1", {}) + @session.clicks_link "Delete" + end + + def test_should_click_rails_javascript_post_links + @response.stubs(:body).returns(<<-EOS) + Posts + EOS + @session.expects(:post_via_redirect).with("/posts", {}) + @session.clicks_link "Posts" + end + + def test_should_click_rails_javascript_put_links + @response.stubs(:body).returns(<<-EOS) + Put + EOS + @session.expects(:put_via_redirect).with("/posts", {}) + @session.clicks_link "Put" + end + + def test_should_assert_valid_response + @response.stubs(:body).returns(<<-EOS) + Link text + EOS + @session.expects(:assert_response).with(:success) + @session.clicks_link "Link text" + end + + def test_should_not_be_case_sensitive + @response.stubs(:body).returns(<<-EOS) + Link text + EOS + @session.expects(:get_via_redirect).with("/page", {}) + @session.clicks_link "LINK TEXT" + end + + def test_should_match_link_substrings + @response.stubs(:body).returns(<<-EOS) + This is some cool link text, isn't it? + EOS + @session.expects(:get_via_redirect).with("/page", {}) + @session.clicks_link "Link text" + end + + def test_should_work_with_elements_in_the_link + @response.stubs(:body).returns(<<-EOS) + Link text + EOS + @session.expects(:get_via_redirect).with("/page", {}) + @session.clicks_link "Link text" + end + + def test_should_match_the_first_matching_link + @response.stubs(:body).returns(<<-EOS) + Link text + Link text + EOS + @session.expects(:get_via_redirect).with("/page1", {}) + @session.clicks_link "Link text" + end +end diff --git a/test/fills_in_test.rb b/test/fills_in_test.rb new file mode 100644 index 0000000..2edd320 --- /dev/null +++ b/test/fills_in_test.rb @@ -0,0 +1,139 @@ +require File.dirname(__FILE__) + "/helper" + +class FillsInTest < Test::Unit::TestCase + def setup + @session = ActionController::Integration::Session.new + @session.stubs(:assert_response) + @session.stubs(:get_via_redirect) + @session.stubs(:response).returns(@response=mock) + end + + def test_should_work_with_textareas + @response.stubs(:body).returns(<<-EOS) +
+ + + +
+ EOS + @session.expects(:post_via_redirect).with("/login", "user" => {"text" => "filling text area"}) + @session.fills_in "User Text", :with => "filling text area" + @session.clicks_button + end + + def test_should_fail_if_input_not_found + @response.stubs(:body).returns(<<-EOS) +
+
+ EOS + @session.expects(:flunk) + @session.fills_in "Email", :with => "foo@example.com" + end + + def test_should_allow_overriding_default_form_values + @response.stubs(:body).returns(<<-EOS) +
+ + + +
+ EOS + @session.expects(:post_via_redirect).with("/login", "user" => {"email" => "foo@example.com"}) + @session.fills_in "user[email]", :with => "foo@example.com" + @session.clicks_button + end + + def test_should_choose_the_closest_label_match + @response.stubs(:body).returns(<<-EOS) +
+ + + + + +
+ EOS + + @session.expects(:post_via_redirect).with("/login", "user" => {"mail2" => "value"}) + @session.fills_in "Some", :with => "value" + @session.clicks_button + end + + def test_should_choose_the_first_label_match_if_closest_is_a_tie + @response.stubs(:body).returns(<<-EOS) +
+ + + + + +
+ EOS + + @session.expects(:post_via_redirect).with("/login", "user" => {"mail1" => "value"}) + @session.fills_in "Some mail", :with => "value" + @session.clicks_button + end + + def test_should_anchor_label_matches_to_start_of_label + @response.stubs(:body).returns(<<-EOS) +
+ + +
+ EOS + + assert_raises(RuntimeError) { @session.fills_in "mail", :with => "value" } + end + + def test_should_anchor_label_matches_to_word_boundaries + @response.stubs(:body).returns(<<-EOS) +
+ + +
+ EOS + + assert_raises(RuntimeError) { @session.fills_in "Email", :with => "value" } + end + + def test_should_work_with_inputs_nested_in_labels + @response.stubs(:body).returns(<<-EOS) +
+ + +
+ EOS + @session.expects(:post_via_redirect).with("/login", "user" => {"email" => "foo@example.com"}) + @session.fills_in "Email", :with => "foo@example.com" + @session.clicks_button + end + + def test_should_work_with_full_input_names + @response.stubs(:body).returns(<<-EOS) +
+ + +
+ EOS + @session.expects(:post_via_redirect).with("/login", "user" => {"email" => "foo@example.com"}) + @session.fills_in "user[email]", :with => "foo@example.com" + @session.clicks_button + end + + def test_should_work_with_symbols + @response.stubs(:body).returns(<<-EOS) +
+ + + +
+ EOS + @session.expects(:post_via_redirect).with("/login", "user" => {"email" => "foo@example.com"}) + @session.fills_in :email, :with => "foo@example.com" + @session.clicks_button + end +end diff --git a/test/helper.rb b/test/helper.rb new file mode 100644 index 0000000..34ddb3b --- /dev/null +++ b/test/helper.rb @@ -0,0 +1,17 @@ +require "rubygems" +require "test/unit" +# gem install redgreen for colored test output +begin require "redgreen" unless ENV['TM_CURRENT_LINE']; rescue LoadError; end +require "mocha" + +require "active_support" +require "action_controller" +require "action_controller/integration" + +require File.expand_path(File.dirname(__FILE__) + "/../lib/webrat") + +class ActionController::Integration::Session + def flunk(message) + raise message + end +end \ No newline at end of file diff --git a/test/selects_test.rb b/test/selects_test.rb new file mode 100644 index 0000000..a602def --- /dev/null +++ b/test/selects_test.rb @@ -0,0 +1,93 @@ +require File.dirname(__FILE__) + "/helper" + +class SelectsTest < Test::Unit::TestCase + def setup + @session = ActionController::Integration::Session.new + @session.stubs(:assert_response) + @session.stubs(:get_via_redirect) + @session.stubs(:response).returns(@response=mock) + end + + def test_should_fail_if_option_not_found + @response.stubs(:body).returns(<<-EOS) +
+ +
+ EOS + @session.expects(:flunk) + @session.selects "February" + end + + def test_should_fail_if_option_not_found_in_list_specified_by_element_name + @response.stubs(:body).returns(<<-EOS) +
+ + +
+ EOS + @session.expects(:flunk) + @session.selects "February", :from => "year" + end + + def test_should_fail_if_specified_list_not_found + @response.stubs(:body).returns(<<-EOS) +
+ +
+ EOS + @session.expects(:flunk) + @session.selects "February", :from => "year" + end + + def test_should_send_value_from_option + @response.stubs(:body).returns(<<-EOS) +
+ + +
+ EOS + @session.expects(:post_via_redirect).with("/login", "month" => "1") + @session.selects "January" + @session.clicks_button + end + + def test_should_send_value_from_option_in_list_specified_by_name + @response.stubs(:body).returns(<<-EOS) +
+ + + +
+ EOS + @session.expects(:post_via_redirect).with("/login", "end_month" => "e1") + @session.selects "January", :from => "end_month" + @session.clicks_button + end + + def test_should_send_value_from_option_in_list_specified_by_label + @response.stubs(:body).returns(<<-EOS) +
+ + + + + +
+ EOS + @session.expects(:post_via_redirect).with("/login", "end_month" => "e1") + @session.selects "January", :from => "End Month" + @session.clicks_button + end + + def test_should_use_option_text_if_no_value + @response.stubs(:body).returns(<<-EOS) +
+ + +
+ EOS + @session.expects(:post_via_redirect).with("/login", "month" => "January") + @session.selects "January" + @session.clicks_button + end +end diff --git a/test/visits_test.rb b/test/visits_test.rb new file mode 100644 index 0000000..a1146cc --- /dev/null +++ b/test/visits_test.rb @@ -0,0 +1,25 @@ +require File.dirname(__FILE__) + "/helper" + +class VisitsTest < Test::Unit::TestCase + def setup + @session = ActionController::Integration::Session.new + @session.stubs(:assert_response) + @session.stubs(:get_via_redirect) + end + + def test_should_use_get + @session.expects(:get_via_redirect).with("/", {}) + @session.visits("/") + end + + def test_should_assert_valid_response + @session.expects(:assert_response).with(:success) + @session.visits("/") + end + + def test_should_require_a_visit_before_manipulating_page + assert_raise(RuntimeError) do + @session.fills_in "foo", :with => "blah" + end + end +end