diff --git a/Rakefile b/Rakefile index 2a6bfbe..d1ce857 100644 --- a/Rakefile +++ b/Rakefile @@ -9,8 +9,6 @@ Hoe.new('webrat', Webrat::VERSION) do |p| p.developer "Bryan Helmkamp", "bryan@brynary.com" p.developer "Seth Fitzsimmons", "seth@mojodna.net" - # require "rubygems"; require "ruby-debug"; Debugger.start; debugger - p.description = p.paragraphs_of('README.txt', 4..6).join("\n\n") p.url = p.paragraphs_of('README.txt', 1).first.split("\n").first.strip p.changes = p.paragraphs_of('History.txt', 0..3).join("\n\n") diff --git a/lib/webrat.rb b/lib/webrat.rb index 8f7c3bf..8bad61b 100644 --- a/lib/webrat.rb +++ b/lib/webrat.rb @@ -1,6 +1,41 @@ -require File.join(File.dirname(__FILE__), "webrat", "rails_extensions") -require File.join(File.dirname(__FILE__), "webrat", "session") +Dir[File.join(File.dirname(__FILE__), "webrat", "*.rb")].each do |file| + require File.expand_path(file) +end module Webrat VERSION = '0.2.1' -end \ No newline at end of file +end + +module ActionController + module Integration + class Session + + unless instance_methods.include?("put_via_redirect") + include Webrat::RedirectActions + end + + def current_page + @current_page ||= Webrat::Page.new(self) + end + + # Issues a GET request for a page, follows any redirects, and verifies the final page + # load was successful. + # + # Example: + # visits "/" + def visits(*args) + @current_page = Webrat::Page.new(self, *args) + end + + [:fills_in, :clicks_button, :selects, :chooses, :checks, :unchecks].each do |method_name| + define_method(method_name) do |*args| + current_page.send(method_name, *args) + end + end + + end + end +end + + + diff --git a/lib/webrat/button_finder.rb b/lib/webrat/button_finder.rb new file mode 100644 index 0000000..d0f1da0 --- /dev/null +++ b/lib/webrat/button_finder.rb @@ -0,0 +1,30 @@ +require File.expand_path(File.join(File.dirname(__FILE__), "field_finder")) + +module Webrat + class ButtonFinder < FieldFinder + def initialize(root, name = nil) + @root = root + @id_or_name_or_label = name + @element_types = %w[input] + @input_types = %w[submit image] + + @candidates = nil + end + + def find + if @id_or_name_or_label.nil? + candidates.first + else + find_by_id(@id_or_name_or_label) || + find_by_name || + find_by_value + end + end + + protected + + def find_by_value + candidates.detect { |el| el.attributes["value"] == @id_or_name_or_label } + end + end +end \ No newline at end of file diff --git a/lib/webrat/field_finder.rb b/lib/webrat/field_finder.rb new file mode 100644 index 0000000..d2c01c3 --- /dev/null +++ b/lib/webrat/field_finder.rb @@ -0,0 +1,82 @@ +module Webrat + class FieldFinder + def initialize(root, id_or_name_or_label, element_types, input_types = nil) + @root = root + @id_or_name_or_label = id_or_name_or_label.to_s + @element_types = Array(element_types) + @input_types = Array(input_types) + + @candidates = nil + end + + def find + find_by_id(@id_or_name_or_label) || + find_by_name || + find_by_label + end + + protected + + # def find_select_list_by_name_or_label(name_or_label) # :nodoc: + # select = find_element_by_name("select", name_or_label) + # return select if select + # + # label = find_form_label(name_or_label) + # label ? field_for_label(label) : nil + # end + # + # def find_option_by_value(option_value, select=nil) # :nodoc: + # options = select.nil? ? (dom / "option") : (select / "option") + # options.detect { |el| el.innerHTML == option_value } + # end + + def field_for_label(label) + inputs_within_label = canidates_within(label) + + if inputs_within_label.any? + inputs_within_label.first + else + find_by_id(label.attributes["for"]) + end + end + + def find_by_id(id) + candidates.detect { |el| el.attributes["id"] == id } + end + + def find_by_name + candidates.detect { |el| el.attributes["name"] == @id_or_name_or_label } + end + + def find_by_label + label = canididate_labels.sort_by { |el| el.innerText.strip.size }.first + label ? field_for_label(label) : nil + end + + def canididate_labels + (@root / "label").select { |el| el.innerText =~ /^\W*#{Regexp.escape(@id_or_name_or_label)}\b/i } + end + + def candidates + return @candidates if @candidates + @candidates = canidates_within(@root) + end + + def canidates_within(root) + candidates = [] + + @element_types.each do |element_type| + if "input" == element_type && @input_types.any? + @input_types.each do |input_type| + candidates += (root / "input[@type=#{input_type}]") + end + else + candidates += (root / element_type) + end + end + + candidates + end + + end +end \ No newline at end of file diff --git a/lib/webrat/form.rb b/lib/webrat/form.rb new file mode 100644 index 0000000..2873f9f --- /dev/null +++ b/lib/webrat/form.rb @@ -0,0 +1,129 @@ +module Webrat + class Form + def initialize(page, form) + @page = page + @form = form + @params = default_params + end + + def set(input_element, value) + new_param = param_parser.parse_query_parameters("#{input_element.attributes["name"]}=#{value}") + merge(new_param) + end + + def unset(input_element) + @params.delete(input_element.attributes['name']) + end + + def find_field(*args) + FieldFinder.new(@form, *args).find + end + + def find_button(name = nil) + ButtonFinder.new(@form, name).find + end + + def submit + Page.new(@page.session, form_action, form_method, @params) + end + + protected + + # def find_field_by_name(name) # :nodoc: + # find_element_by_name("input", name) || find_element_by_name("textarea", name) + # 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_radio_buttons_for(form) + # add_default_params_from_textareas_for(form) + # add_default_params_from_selects_for(form) + # end + + def default_params + {} + end + + def add_default_params_from_radio_buttons_for(form) # :nodoc: + (form / "input[@type='radio][@checked='checked']").each do |input| + add_form_data(input, input.attributes["value"]) + end + end + + def add_default_params_from_checkboxes_for(form) # :nodoc: + (form / "input[@type='checkbox][@checked='checked']").each do |input| + add_form_data(input, input.attributes["value"] || "on") + end + end + + def add_default_params_from_selects_for(form) # :nodoc: + (form / "select").each do |select| + selected_options = select / "option[@selected='selected']" + selected_options = select / "option:first" if selected_options.empty? + selected_options.each do |option| + add_form_data(select, option.attributes["value"] || option.innerHTML) + end + end + end + + def add_default_params_from_inputs_for(form) # :nodoc: + (form / "input").each do |input| + next unless %w[text password hidden].include?(input.attributes["type"]) + add_form_data(input, input.attributes["value"]) + end + end + + def add_default_params_from_textareas_for(form) # :nodoc: + (form / "textarea").each do |input| + add_form_data(input, input.inner_html) + end + end + + def form_method + @form.attributes["method"].blank? ? :get : @form.attributes["method"].downcase + end + + def form_action + @form.attributes["action"].blank? ? current_url : @form.attributes["action"] + end + + def param_parser + if defined?(CGIMethods) + CGIMethods + else + ActionController::AbstractRequest + end + end + + def merge(new_param) + new_param.each do |key, value| + case @params[key] + when Hash, HashWithIndifferentAccess + merge_hash_values(@params[key], value) + when Array + @params[key] += value + else + @params[key] = value + end + end + end + + def merge_hash_values(a, b) # :nodoc: + a.keys.each do |k| + if b.has_key?(k) + case [a[k], b[k]].map(&:class) + when [Hash, Hash] + a[k] = merge_hash_values(a[k], b[k]) + b.delete(k) + when [Array, Array] + a[k] += b[k] + b.delete(k) + end + end + end + a.merge!(b) + end + + end +end \ No newline at end of file diff --git a/lib/webrat/link.rb b/lib/webrat/link.rb new file mode 100644 index 0000000..14bb552 --- /dev/null +++ b/lib/webrat/link.rb @@ -0,0 +1,51 @@ +module Webrat + class Link + + def initialize(link_element) + end + + def click + end + + protected + + def authenticity_token + 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 onclick + end + + def http_method + 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 + + end +end \ No newline at end of file diff --git a/lib/webrat/link_finder.rb b/lib/webrat/link_finder.rb new file mode 100644 index 0000000..14d7ea7 --- /dev/null +++ b/lib/webrat/link_finder.rb @@ -0,0 +1,32 @@ +# def clicks_link_with_method(link_text, http_method) # :nodoc: +# link = all_links.detect { |el| el.innerHTML =~ /#{link_text}/i } +# flunk("No link with text #{link_text.inspect} was found") if link.nil? +# request_page(http_method, link.attributes["href"]) +# end +# +# def find_shortest_matching_link(links, link_text) +# candidates = links.select { |el| el.innerHTML =~ /#{link_text}/i } +# candidates.sort_by { |el| el.innerText.strip.size }.first +# end +# +# def clicks_one_link_of(links, link_text) +# link = find_shortest_matching_link(links, link_text) +# +# 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}) unless href =~ /^#/ && http_method == :get +# end +# +# def all_links # :nodoc: +# (dom / "a[@href]") +# end +# +# def links_within(selector) # :nodoc: +# (dom / selector / "a[@href]") +# end \ No newline at end of file diff --git a/lib/webrat/logging.rb b/lib/webrat/logging.rb new file mode 100644 index 0000000..08e9870 --- /dev/null +++ b/lib/webrat/logging.rb @@ -0,0 +1,18 @@ +module Webrat + module Logging + + 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 \ No newline at end of file diff --git a/lib/webrat/page.rb b/lib/webrat/page.rb new file mode 100644 index 0000000..c4aea1b --- /dev/null +++ b/lib/webrat/page.rb @@ -0,0 +1,280 @@ +require "hpricot" +require "English" + +module Webrat + class Page + include Logging + + attr_reader :session + + def initialize(session, url = nil, method = :get, data = {}) + @session = session + @url = url + @method = method + @data = data + + reset_dom + reloads if @url + 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] + flunk("No value was provided") if value.nil? + + form_with_input = nil + found_input = nil + + forms.each do |form| + found_input = form.find_field(field, %w[input textarea], %w[text password]) + + if found_input + form_with_input = form + break + end + end + + flunk("Could not find input #{field.inspect}") if found_input.nil? + + form_with_input.set(found_input, value) + 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) + flunk("Could not find checkbox #{field.inspect}") if checkbox.nil? + 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) + flunk("Could not find checkbox #{field.inspect}") if checkbox.nil? + 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 an input radio button exists on the current page and marks it + # as checked, so that the value will be submitted with the form. + # + # Example: + # chooses 'First Option' + def chooses(field) + radio = find_field_by_name_or_label(field) + flunk("Could not find radio button #{field.inspect}") if radio.nil? + flunk("Input #{radio.inspect} is not a radio button") unless radio.attributes['type'] == 'radio' + add_form_data(radio, radio.attributes["value"] || "on") + 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]) + flunk("Could not find select list #{options[:from].inspect}") if select.nil? + option_node = find_option_by_value(option_text, select) + flunk("Could not find option #{option_text.inspect}") if option_node.nil? + else + option_node = find_option_by_value(option_text) + 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 + + # Saves the currently loaded page out to RAILS_ROOT/tmp/ and opens it in the default + # web browser if on OS X. Useful for debugging. + # + # Example: + # save_and_open + def save_and_open + return unless File.exist?(RAILS_ROOT + "/tmp") + + filename = "webrat-#{Time.now.to_i}.html" + File.open(RAILS_ROOT + "/tmp/#{filename}", "w") do |f| + f.write response.body + end + `open tmp/#{filename}` + 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) + clicks_one_link_of(all_links, link_text) + end + + # Works like clicks_link, but only looks for the link text within a given selector + # + # Example: + # clicks_link_within "#user_12", "Vote" + def clicks_link_within(selector, link_text) + clicks_one_link_of(links_within(selector), link_text) + 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 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) + form_with_button = nil + found_button = nil + + forms.each do |form| + found_button = form.find_button(value) + + if found_button + form_with_button = form + break + end + end + + flunk("Could not find button #{value.inspect}") if found_button.nil? + form_with_button.set(found_button, found_button.attributes["value"]) unless found_button.attributes["name"].blank? + form_with_button.submit + end + + # Reloads the last page requested. Note that this will resubmit forms + # and their data. + # + # Example: + # reloads + def reloads + request_page(@url, @method, @data) + end + + def submits_form(form_id = nil) # :nodoc: + end + + protected + + def request_page(url, method, data) + debug_log "REQUESTING PAGE: #{method.to_s.upcase} #{url} with #{data.inspect}" + + session.send "#{method}_via_redirect", url, data || {} + + if response.body =~ /Exception caught/ || response.body.blank? + save_and_open + end + + session.assert_response :success + reset_dom + end + + def response + session.response + end + + def reset_dom + @dom = nil + @forms = nil + end + + def find_button(value = nil) # :nodoc: + return nil unless value + + (dom / "input[@type='submit']").detect do |el| + el.attributes["value"] =~ /^\W*#{value}\b/i + end + end + + def form_for_node(node) # :nodoc: + return node if node.name == "form" + node = node.parent until node.name == "form" + node + end + + def forms + return @forms if @forms + + @forms = (dom / "form").map do |form_element| + Form.new(self, form_element) + end + end + + def dom # :nodoc: + return @dom if defined?(@dom) && @dom + flunk("You must visit a path before working with the page.") unless @session.response + @dom = Hpricot(@session.response.body) + end + + def flunk(message) + raise message + end + + end +end \ No newline at end of file diff --git a/lib/webrat/rails_extensions.rb b/lib/webrat/redirect_actions.rb similarity index 60% rename from lib/webrat/rails_extensions.rb rename to lib/webrat/redirect_actions.rb index 8b3d8d3..fd3cce8 100644 --- a/lib/webrat/rails_extensions.rb +++ b/lib/webrat/redirect_actions.rb @@ -1,5 +1,7 @@ +# For Rails before http://dev.rubyonrails.org/ticket/10497 was committed module Webrat module RedirectActions + def put_via_redirect(path, parameters = {}, headers = {}) put path, parameters, headers follow_redirect! while redirect? @@ -11,15 +13,6 @@ module Webrat follow_redirect! while redirect? status end - end -end - -# Waiting for http://dev.rubyonrails.org/ticket/10497 to be committed. - -module ActionController - module Integration - class Session - include Webrat::RedirectActions unless instance_methods.include?("put_via_redirect") - end + end end diff --git a/lib/webrat/session.rb b/lib/webrat/session.rb deleted file mode 100644 index c4ada10..0000000 --- a/lib/webrat/session.rb +++ /dev/null @@ -1,526 +0,0 @@ -require "hpricot" -require "English" - -module Webrat - module 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) - clicks_one_link_of(all_links, link_text) - end - - # Works like clicks_link, but only looks for the link text within a given selector - # - # Example: - # clicks_link_within "#user_12", "Vote" - def clicks_link_within(selector, link_text) - clicks_one_link_of(links_within(selector), link_text) - 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 an input radio button exists on the current page and marks it - # as checked, so that the value will be submitted with the form. - # - # Example: - # chooses 'First Option' - def chooses(field) - radio = find_field_by_name_or_label(field) - return flunk("Could not find radio button #{field.inspect}") if radio.nil? - return flunk("Input #{radio.inspect} is not a radio button") unless radio.attributes['type'] == 'radio' - add_form_data(radio, radio.attributes["value"] || "on") - 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 - - # Saves the currently loaded page out to RAILS_ROOT/tmp/ and opens it in the default - # web browser if on OS X. Useful for debugging. - # - # Example: - # save_and_open_page - def save_and_open_page - return unless File.exist?(RAILS_ROOT + "/tmp") - - filename = "webrat-#{Time.now.to_i}.html" - File.open(RAILS_ROOT + "/tmp/#{filename}", "w") do |f| - f.write response.body - end - `open tmp/#{filename}` - end - - # Reloads the last page requested. Note that this will resubmit forms - # and their data. - # - # Example: - # reloads - def reloads - request_page(*@last_request_args) if defined?(@last_request_args) && @last_request_args - 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 = all_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_shortest_matching_link(links, link_text) - candidates = links.select { |el| el.innerHTML =~ /#{link_text}/i } - candidates.sort_by { |el| el.innerText.strip.size }.first - end - - def clicks_one_link_of(links, link_text) - link = find_shortest_matching_link(links, link_text) - - 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}) unless href =~ /^#/ && http_method == :get - 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"] =~ /^\W*#{value}\b/i } - 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| - case form_data[form_number][key] - when Hash, HashWithIndifferentAccess; then merge(form_data[form_number][key], value) - when Array; then 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) - case [a[k], b[k]].map(&:class) - when [Hash, Hash] - a[k] = merge(a[k], b[k]) - b.delete(k) - when [Array, Array] - a[k] += b[k] - b.delete(k) - end - 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 - @last_request_args = [method, url, data] - self.send "#{method}_via_redirect", @current_url, data || {} - - if response.body =~ /Exception caught/ || response.body.blank? - save_and_open_page - end - - 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_radio_buttons_for(form) - add_default_params_from_textareas_for(form) - add_default_params_from_selects_for(form) - end - - def add_default_params_from_inputs_for(form) # :nodoc: - (form / "input").each do |input| - next unless %w[text password 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[@type='checkbox][@checked='checked']").each do |input| - add_form_data(input, input.attributes["value"] || "on") - end - end - - def add_default_params_from_radio_buttons_for(form) # :nodoc: - (form / "input[@type='radio][@checked='checked']").each do |input| - add_form_data(input, input.attributes["value"]) - end - end - - def add_default_params_from_textareas_for(form) # :nodoc: - (form / "textarea").each do |input| - add_form_data(input, input.inner_html) - end - end - - def add_default_params_from_selects_for(form) # :nodoc: - (form / "select").each do |select| - selected_options = select / "option[@selected='selected']" - selected_options = select / "option:first" if selected_options.empty? - selected_options.each do |option| - add_form_data(select, option.attributes["value"] || option.innerHTML) - end - 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 all_links # :nodoc: - (dom / "a[@href]") - end - - def links_within(selector) # :nodoc: - (dom / selector / "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 defined?(@dom) && @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 - -module ActionController - module Integration - class Session - include Webrat::Session - end - end -end diff --git a/test/fills_in_test.rb b/test/fills_in_test.rb index 9074f24..3b4c01a 100644 --- a/test/fills_in_test.rb +++ b/test/fills_in_test.rb @@ -20,120 +20,122 @@ class FillsInTest < Test::Unit::TestCase @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 + def test_should_fail_if_input_not_found + @response.stubs(:body).returns(<<-EOS) +
+
+ EOS + + assert_raises RuntimeError do + @session.fills_in "Email", :with => "foo@example.com" + end + end - @session.expects(:post_via_redirect).with("/login", "user" => {"mail1" => "", "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", "mail2" => ""}) - @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 + + + 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" => {"mail1" => "", "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", "mail2" => ""}) + @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 - - 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 index 50351d5..9c0418e 100644 --- a/test/helper.rb +++ b/test/helper.rb @@ -4,7 +4,6 @@ require "test/unit" begin require "redgreen" unless ENV['TM_CURRENT_LINE']; rescue LoadError; end require "mocha" - require "active_support" silence_warnings do