Merge branch 'brynary/master'

Conflicts:
	Manifest.txt
	README.txt
This commit is contained in:
David Chelimsky 2008-10-22 12:45:31 -05:00
commit bad6923adb
47 changed files with 1311 additions and 215 deletions

1
.gitignore vendored
View File

@ -1,4 +1,5 @@
coverage coverage
.DS_Store
pkg pkg
doc doc
ri ri

View File

@ -2,38 +2,53 @@
* Major enhancements * Major enhancements
* Added Merb support (Gwyn Morfey, Jeremy Burks, Rob Kaufman)
* Added #basic_auth(user, pass) to support HTTP Basic Auth (Aslak Hellesøy)
* Added support for Sinatra and Rack (Aslak Hellesøy)
* Added #within for manipulating the current page within a selector scope * Added #within for manipulating the current page within a selector scope
* Add support for simulating SSL requests (Luke Melia) * Add support for simulating SSL requests (Luke Melia)
* Add support for file fields via #attaches_file method (Patch from Kyle Hargraves) * Add support for file fields via #attaches_file method (Kyle Hargraves)
* Support relative links, including href="?foo=bar" (Patch from Kyle Hargraves)
* Separated Rails-specific code from the Webrat core to make it easier to use Webrat with other environments
* Alias visits as visit, clicks_link as click_link, etc. for better readability
* Raise error when trying to interact with a disabled form element (Luke Melia)
* Minor enhancements * Minor enhancements
* Add support for redirect_to :back by sending HTTP_REFERER headers (Patch from Hendrik Volkmer) * Add Webrat.root method for cross-framework support (Krzysztof Zylawy)
* Add #select_date for quickly filling out Rails-style date fields (Alex Lang)
* Support selecting options by their values (Alex Lang)
* Support for clicking areas of an image map (Alex Lang)
* Add should_see and should_not_see for verifying HTML response bodys
* Support relative links, including href="?foo=bar" (Kyle Hargraves)
* Separated Rails-specific code from the Webrat core to make it easier to use Webrat with other environments
* Alias visits as visit, clicks_link as click_link, etc. for better readability
* Raise error when trying to interact with a disabled form element (Luke Melia)
* Don't send disabled form elements to the server (Nicholas A. Evans)
* Display response body when the page load is not successful (David Leal)
* CGI escape form field values (Miha Filej)
* Add support for redirect_to :back by sending HTTP_REFERER headers (Hendrik Volkmer)
* Expose current DOM (as an Hpricot object) as #current_dom * Expose current DOM (as an Hpricot object) as #current_dom
* Add support for disabling JavaScript when clicking a link to enable testing of both JS * Add support for disabling JavaScript when clicking a link to enable testing of both JS
and non-JS implementations (Luke Melia and Bryan Helmkamp) and non-JS implementations (Luke Melia and Bryan Helmkamp)
* Support &nbsp's as spaces in matching link text (Patch from Luke Melia) * Support &nbsp's as spaces in matching link text (Luke Melia)
* Support button elements (Patch from Nick Sieger) * Support button elements (Nick Sieger)
* Support matching select options by regexp (Patch from Kyle Hargraves) * Support matching select options by regexp (Kyle Hargraves)
* save_and_open_page rewrites css and image references to provide a friendlier debugging experience (Luke Melia) * save_and_open_page rewrites css and image references to provide a friendlier debugging experience (Luke Melia)
* Added support for matching alt attributes in fields (primarly for clicks_button) (Patch from Aaron Quint) * Added support for matching alt attributes in fields (primarly for clicks_button) (Aaron Quint)
* Support '&' in submitted values (Patch from Kyle Hargraves) * Support '&' in submitted values (Kyle Hargraves)
* Support clicking links by title (Patch from Dan Barry) * Support clicking links by title (Dan Barry)
* Added missing spec for clicking image buttons (Patch from Tim Harper) * Added missing spec for clicking image buttons (Tim Harper)
* Switched tests to specs, and from Mocha to RSpec's mocking library * Switched tests to specs, and from Mocha to RSpec's mocking library
* Add support to click_button for IDs (Gwyn Morfey)
* Miscellaneous core refactorings (Jan Suchal)
* Bug fixes * Bug fixes
* Don't open blank pages in the browser (Patch from Kyle Hargraves) * Fix initialization of WWW::Mechanize (Derek Kastner)
* Support radio buttons with multiple labels (Patch from Dan Barry) * Don't open blank pages in the browser (Kyle Hargraves)
* Fix load order bug on some platforms (Patch from Ismael Celis) * Support radio buttons with multiple labels (Dan Barry)
* Fix bug with empty select list option (Patch from Kyle Hargraves) * Fix load order bug on some platforms (Ismael Celis)
* Fix bug with empty select list option (Kyle Hargraves)
* Fix regression of not sending default values in password fields * Fix regression of not sending default values in password fields
* Don't explode if encountering inputs with no type attribute (assume text) * Don't explode if encountering inputs with no type attribute (assume text)
* Fix bug where choosing a radio button in a series with a default submitted the incorrect field value (Luke Melia)
== 0.2.0 / 2008-04-04 == 0.2.0 / 2008-04-04
@ -43,29 +58,29 @@
* Add radio button support via #chooses method * Add radio button support via #chooses method
* Add basic support for Rails-generated JavaScript link tags * Add basic support for Rails-generated JavaScript link tags
* Add support for checkboxes (Patches from Kyle Hargraves and Jarkko Laine) * Add support for checkboxes (Patches from Kyle Hargraves and Jarkko Laine)
* Add support for textarea fields (Patch from Sacha Schlegel) * Add support for textarea fields (Sacha Schlegel)
* 8 Minor enhancements * 8 Minor enhancements
* Added reloads method to reload the page (Patch from Kamal Fariz Mahyuddi) * Added reloads method to reload the page (Kamal Fariz Mahyuddi)
* Prevent making a request if clicking on local anchor link (Patch from Kamal Fariz Mahyuddi) * Prevent making a request if clicking on local anchor link (Kamal Fariz Mahyuddi)
* Added clicks_link_within(selector, link_text), allowing restricting link search * Added clicks_link_within(selector, link_text), allowing restricting link search
to within a given css selector (Patch from Luke Melia) to within a given css selector (Luke Melia)
* Allow specifying the input name/label when doing a select (Patch from David Chelimsky) * Allow specifying the input name/label when doing a select (David Chelimsky)
* Raise a specific exception if the developer tries to manipulate form elements before loading a page (Patch from James Deville) * Raise a specific exception if the developer tries to manipulate form elements before loading a page (James Deville)
* Add support for alternate POST, PUT and DELETE link clicking (Patch from Kyle Hargraves) * Add support for alternate POST, PUT and DELETE link clicking (Kyle Hargraves)
* Change clicks_link to find the shortest matching link (Patch from Luke Melia) * Change clicks_link to find the shortest matching link (Luke Melia)
* Improve matching for labels in potentially ambiguous cases * Improve matching for labels in potentially ambiguous cases
* 7 Bug fixes * 7 Bug fixes
* Fix incorrect serializing of collection inputs, i.e. name contains [] (Patch from Kamal Fariz Mahyuddi) * Fix incorrect serializing of collection inputs, i.e. name contains [] (Kamal Fariz Mahyuddi)
* Serialize empty text field values just like browsers (Patch from Kamal Fariz Mahyuddi) * Serialize empty text field values just like browsers (Kamal Fariz Mahyuddi)
* Quick fix to avoid @dom not initialized warnings (Patch from Kamal Fariz Mahyuddi) * Quick fix to avoid @dom not initialized warnings (Kamal Fariz Mahyuddi)
* Docfix: bad reference to #select method in README (Patch from Luke Melia) * Docfix: bad reference to #select method in README (Luke Melia)
* Ensure Rails-style checkboxes work properly (checkboxes followed by a hidden input with the same name) * Ensure Rails-style checkboxes work properly (checkboxes followed by a hidden input with the same name)
* Fix Edge Rails (a.k.a. 2.0 RC) compatibility (Patch from David Chelimsky) * Fix Edge Rails (a.k.a. 2.0 RC) compatibility (David Chelimsky)
* Support param hashes nested more than one level (Patch from David Chelimsky) * Support param hashes nested more than one level (David Chelimsky)
== 0.1.0 / 2007-11-28 == 0.1.0 / 2007-11-28

View File

@ -8,6 +8,8 @@ init.rb
install.rb install.rb
lib/webrat.rb lib/webrat.rb
lib/webrat/core.rb lib/webrat/core.rb
lib/webrat/core/area.rb
lib/webrat/core/assertions.rb
lib/webrat/core/field.rb lib/webrat/core/field.rb
lib/webrat/core/flunk.rb lib/webrat/core/flunk.rb
lib/webrat/core/form.rb lib/webrat/core/form.rb
@ -19,21 +21,42 @@ lib/webrat/core/select_option.rb
lib/webrat/core/session.rb lib/webrat/core/session.rb
lib/webrat/mechanize.rb lib/webrat/mechanize.rb
lib/webrat/mechanize/mechanize_session.rb lib/webrat/mechanize/mechanize_session.rb
lib/webrat/merb.rb
lib/webrat/merb/indifferent_access.rb
lib/webrat/merb/param_parser.rb
lib/webrat/merb/support.rb
lib/webrat/merb/url_encoded_pair_parser.rb
lib/webrat/rack/rack_session.rb
lib/webrat/rails.rb lib/webrat/rails.rb
lib/webrat/rails/rails_session.rb lib/webrat/rails/rails_session.rb
lib/webrat/rails/redirect_actions.rb lib/webrat/rails/redirect_actions.rb
lib/webrat/rails/session.rb lib/webrat/rails/session.rb
lib/webrat/selenium.rb lib/webrat/selenium.rb
lib/webrat/selenium/location_strategy_javascript/button.js
lib/webrat/selenium/location_strategy_javascript/label.js
lib/webrat/selenium/location_strategy_javascript/webrat.js
lib/webrat/selenium/location_strategy_javascript/webratlink.js
lib/webrat/selenium/location_strategy_javascript/webratlinkwithin.js
lib/webrat/selenium/location_strategy_javascript/webratselectwithoption.js
lib/webrat/selenium/selenium_session.rb lib/webrat/selenium/selenium_session.rb
lib/webrat/sinatra/sinatra_session.rb
manifest.diff
mechanize_spike.rb
selenium_spike.rb
spec/api/attaches_file_spec.rb spec/api/attaches_file_spec.rb
spec/api/basic_auth_spec.rb
spec/api/checks_spec.rb spec/api/checks_spec.rb
spec/api/chooses_spec.rb spec/api/chooses_spec.rb
spec/api/clicks_area_spec.rb
spec/api/clicks_button_spec.rb spec/api/clicks_button_spec.rb
spec/api/clicks_link_spec.rb spec/api/clicks_link_spec.rb
spec/api/fills_in_spec.rb spec/api/fills_in_spec.rb
spec/api/reloads_spec.rb spec/api/reloads_spec.rb
spec/api/save_and_open_spec.rb spec/api/save_and_open_spec.rb
spec/api/selects_date_spec.rb
spec/api/selects_spec.rb spec/api/selects_spec.rb
spec/api/should_not_see_spec.rb
spec/api/should_see_spec.rb
spec/api/visits_spec.rb spec/api/visits_spec.rb
spec/api/within_spec.rb spec/api/within_spec.rb
spec/fakes/test_session.rb spec/fakes/test_session.rb
@ -42,4 +65,10 @@ spec/rcov.opts
spec/spec_helper.rb spec/spec_helper.rb
spec/webrat/core/logging_spec.rb spec/webrat/core/logging_spec.rb
spec/webrat/core/session_spec.rb spec/webrat/core/session_spec.rb
spec/webrat/mechanize/mechanize_session_spec.rb
spec/webrat/merb/helper.rb
spec/webrat/merb/indifferent_access_spec.rb
spec/webrat/merb/session_spec.rb
spec/webrat/rails/helper.rb
spec/webrat/rails/rails_session_spec.rb spec/webrat/rails/rails_session_spec.rb
webrat.gemspec

View File

@ -59,6 +59,13 @@ tests to break unnecessarily as your application evolves:
A test written with Webrat can handle these changes to these without any modifications. A test written with Webrat can handle these changes to these without any modifications.
=== Merb
To avoid losing sessions, you need this in environments/test.rb:
Merb::Config.use do |c|
c[:session_store] = 'memory'
end
=== Install === Install
To install the latest release: To install the latest release:

View File

@ -35,28 +35,39 @@ def remove_task(task_name)
Rake.application.remove_task(task_name) Rake.application.remove_task(task_name)
end end
def set_file_list
if ENV['TEST_MODE'] == "merb"
list = FileList['spec/**/*_spec.rb']
list = list.find_all do |file| !file.match("rails") end
return list
else
return FileList['spec/**/*_spec.rb']
end
end
remove_task "test" remove_task "test"
remove_task "test_deps" remove_task "test_deps"
desc "Run all specs in spec directory" desc "Run all specs in spec directory"
Spec::Rake::SpecTask.new do |t| Spec::Rake::SpecTask.new do |t|
t.spec_opts = ['--options', "\"#{File.dirname(__FILE__)}/spec/spec.opts\""] t.spec_opts = ['--options', "\"#{File.dirname(__FILE__)}/spec/spec.opts\""]
t.spec_files = FileList['spec/**/*_spec.rb'] t.spec_files = set_file_list
end end
desc "Run all specs in spec directory with RCov" desc "Run all specs in spec directory with RCov"
Spec::Rake::SpecTask.new(:rcov) do |t| Spec::Rake::SpecTask.new(:rcov) do |t|
t.spec_opts = ['--options', "\"#{File.dirname(__FILE__)}/spec/spec.opts\""] t.spec_opts = ['--options', "\"#{File.dirname(__FILE__)}/spec/spec.opts\""]
t.spec_files = FileList['spec/**/*_spec.rb'] t.spec_files = set_file_list
t.rcov = true t.rcov = true
t.rcov_opts = lambda do t.rcov_opts = lambda do
IO.readlines(File.dirname(__FILE__) + "/spec/rcov.opts").map {|l| l.chomp.split " "}.flatten IO.readlines(File.dirname(__FILE__) + "/spec/rcov.opts").map {|l| l.chomp.split " "}.flatten
end end
end end
require 'spec/rake/verify_rcov' require 'spec/rake/verify_rcov'
RCov::VerifyTask.new(:verify_rcov => :rcov) do |t| RCov::VerifyTask.new(:verify_rcov => :rcov) do |t|
t.threshold = 97.1 # Make sure you have rcov 0.7 or higher! t.threshold = 96.2 # Make sure you have rcov 0.7 or higher!
end end
remove_task "default" remove_task "default"

View File

@ -1,3 +1,3 @@
if RAILS_ENV == "test" if RAILS_ENV == "test" || RAILS_ENV == "selenium"
require File.join(File.dirname(__FILE__), "lib", "webrat") require File.join(File.dirname(__FILE__), "lib", "webrat")
end end

View File

@ -1,9 +1,12 @@
module Webrat module Webrat
VERSION = '0.2.1' VERSION = '0.2.2'
def self.root
defined?(RAILS_ROOT) ? RAILS_ROOT : Merb.root
end
end end
require "rubygems" require "rubygems"
require "active_support"
require File.dirname(__FILE__) + "/webrat/core" require File.dirname(__FILE__) + "/webrat/core"
require File.dirname(__FILE__) + "/webrat/rails" if defined?(RAILS_ENV) require File.dirname(__FILE__) + "/webrat/rails" if defined?(RAILS_ENV)
require File.dirname(__FILE__) + "/webrat/merb" if defined?(Merb)

44
lib/webrat/core/area.rb Normal file
View File

@ -0,0 +1,44 @@
module Webrat
class Area
def initialize(session, element)
@session = session
@element = element
end
def click(method = nil, options = {})
@session.request_page(absolute_href, :get, {})
end
def matches_text?(id_or_title)
matcher = /#{Regexp.escape(id_or_title.to_s)}/i
title =~ matcher || id =~ matcher
end
protected
def href
@element["href"]
end
def title
@element["title"]
end
def id
@element["id"]
end
def absolute_href
if href =~ /^\?/
"#{@session.current_url}#{href}"
elsif href !~ %r{^https?://[\w|.]+(/.*)} && (href !~ /^\//)
"#{@session.current_url}/#{href}"
else
href
end
end
end
end

View File

@ -0,0 +1,27 @@
module Webrat
module Assertions
def should_see(text_or_regexp)
case text_or_regexp
when Regexp
return if scoped_html.match(text_or_regexp)
else
return if scoped_html.include?(text_or_regexp)
end
flunk("Should see #{text_or_regexp.inspect} but didn't")
end
def should_not_see(text_or_regexp)
case text_or_regexp
when Regexp
return unless scoped_html.match(text_or_regexp)
else
return unless scoped_html.include?(text_or_regexp)
end
flunk("Should not see #{text_or_regexp.inspect} but did")
end
end
end

View File

@ -1,3 +1,5 @@
require "cgi"
module Webrat module Webrat
class Field class Field
@ -6,12 +8,11 @@ module Webrat
if %w[submit image].include?(element["type"]) if %w[submit image].include?(element["type"])
field_class = "button" field_class = "button"
else else
field_class = element["type"] || "text" field_class = element["type"] || "text" #default type; 'type' attribute is not mandatory
end end
else else
field_class = element.name field_class = element.name
end end
Webrat.const_get("#{field_class.capitalize}Field") Webrat.const_get("#{field_class.capitalize}Field")
rescue NameError rescue NameError
raise "Invalid field element: #{element.inspect}" raise "Invalid field element: #{element.inspect}"
@ -30,11 +31,11 @@ module Webrat
end end
def matches_id?(id) def matches_id?(id)
@element["id"] == id.to_s matches?(self.id, id)
end end
def matches_name?(name) def matches_name?(name)
@element["name"] == name.to_s matches?(self.name, name)
end end
def matches_label?(label_text) def matches_label?(label_text)
@ -47,7 +48,7 @@ module Webrat
end end
def disabled? def disabled?
!@element["disabled"].nil? && @element["disabled"] != 'false' @element.attributes.has_key?("disabled") && @element["disabled"] != 'false'
end end
def raise_error_if_disabled def raise_error_if_disabled
@ -55,8 +56,8 @@ module Webrat
end end
def to_param def to_param
value = @value.to_s.gsub('&', '%26') return nil if disabled?
param_parser.parse_query_parameters("#{name}=#{value}") param_parser.parse_query_parameters("#{name}=#{escaped_value}")
end end
def set(value) def set(value)
@ -69,6 +70,14 @@ module Webrat
protected protected
def matches?(string, string_or_regex)
if string_or_regex.is_a?(Regexp)
string_or_regex.match string
else
string == string_or_regex
end
end
def id def id
@element["id"] @element["id"]
end end
@ -77,6 +86,10 @@ module Webrat
@element["name"] @element["name"]
end end
def escaped_value
CGI.escape(@value.to_s)
end
def labels def labels
@labels ||= label_elements.map { |element| Label.new(self, element) } @labels ||= label_elements.map { |element| Label.new(self, element) }
end end
@ -108,10 +121,10 @@ module Webrat
def param_parser def param_parser
if defined?(CGIMethods) if defined?(CGIMethods)
CGIMethods CGIMethods
else elsif defined?(ActionController::AbstractRequest)
require "action_controller"
require "action_controller/integration"
ActionController::AbstractRequest ActionController::AbstractRequest
else
Webrat::ParamParser #used for Merb
end end
end end
@ -167,7 +180,7 @@ module Webrat
else else
checkbox_with_same_name = @form.find_field(name, CheckboxField) checkbox_with_same_name = @form.find_field(name, CheckboxField)
if checkbox_with_same_name.to_param.nil? if checkbox_with_same_name.to_param.blank?
super super
else else
nil nil
@ -295,7 +308,7 @@ module Webrat
class SelectField < Field class SelectField < Field
def find_option(text) def find_option(text)
options.detect { |o| o.matches_text?(text) } options.detect { |o| o.matches_text?(text) || o.matches_value?(text)}
end end
protected protected

View File

@ -17,39 +17,28 @@ module Webrat
nil nil
end end
def find_select_option(option_text) def find_select_option(option_text, field_name_pattern = nil)
select_fields = fields_by_type([SelectField]) select_fields = fields_by_type([SelectField])
select_fields.select{|field| field_name_pattern.nil? || field.matches_name?(field_name_pattern) || field.matches_id?(field_name_pattern)}.each do |select_field|
select_fields.each do |select_field|
result = select_field.find_option(option_text) result = select_field.find_option(option_text)
return result if result return result if result
end end
nil nil
end end
def find_button(value = nil) def find_button(value = nil)
return fields_by_type([ButtonField]).first if value.nil? return fields_by_type([ButtonField]).first if value.nil?
possible_buttons = fields_by_type([ButtonField]) possible_buttons = fields_by_type([ButtonField])
possible_buttons.detect { |possible_button| possible_button.matches_id?(value) } ||
possible_buttons.each do |possible_button| possible_buttons.detect { |possible_button| possible_button.matches_value?(value) }
return possible_button if possible_button.matches_value?(value)
end
nil
end end
def fields def fields
return @fields if @fields return @fields if @fields
@fields = [] @fields = (@element / "button, input, textarea, select").collect do |field_element|
Field.class_for_element(field_element).new(self, field_element)
(@element / "button, input, textarea, select").each do |field_element|
@fields << Field.class_for_element(field_element).new(self, field_element)
end end
@fields
end end
def submit def submit
@ -59,29 +48,18 @@ module Webrat
protected protected
def find_field_by_id(possible_fields, id) def find_field_by_id(possible_fields, id)
possible_fields.each do |possible_field| possible_fields.detect { |possible_field| possible_field.matches_id?(id) }
return possible_field if possible_field.matches_id?(id)
end
nil
end end
def find_field_by_name(possible_fields, name) def find_field_by_name(possible_fields, name)
possible_fields.each do |possible_field| possible_fields.detect { |possible_field| possible_field.matches_name?(name) }
return possible_field if possible_field.matches_name?(name)
end
nil
end end
def find_field_by_label(possible_fields, label) def find_field_by_label(possible_fields, label)
matching_fields = [] matching_fields = possible_fields.select do |possible_field|
possible_field.matches_label?(label)
possible_fields.each do |possible_field|
matching_fields << possible_field if possible_field.matches_label?(label)
end end
matching_fields.min { |a, b| a.label_text.length <=> b.label_text.length }
matching_fields.sort_by { |f| f.label_text.length }.first
end end
def fields_by_type(field_types) def fields_by_type(field_types)
@ -107,10 +85,12 @@ module Webrat
@element["action"].blank? ? @session.current_url : @element["action"] @element["action"].blank? ? @session.current_url : @element["action"]
end end
HASH = [Hash, HashWithIndifferentAccess] rescue [Hash]
def merge(all_params, new_param) def merge(all_params, new_param)
new_param.each do |key, value| new_param.each do |key, value|
case all_params[key] case all_params[key]
when Hash, HashWithIndifferentAccess when *HASH
merge_hash_values(all_params[key], value) merge_hash_values(all_params[key], value)
when Array when Array
all_params[key] += value all_params[key] += value
@ -123,7 +103,7 @@ module Webrat
def merge_hash_values(a, b) # :nodoc: def merge_hash_values(a, b) # :nodoc:
a.keys.each do |k| a.keys.each do |k|
if b.has_key?(k) if b.has_key?(k)
case [a[k], b[k]].map(&:class) case [a[k], b[k]].map{|value| value.class}
when [Hash, Hash] when [Hash, Hash]
a[k] = merge_hash_values(a[k], b[k]) a[k] = merge_hash_values(a[k], b[k])
b.delete(k) b.delete(k)

View File

@ -3,12 +3,14 @@ module Webrat
def debug_log(message) # :nodoc: def debug_log(message) # :nodoc:
return unless logger return unless logger
logger.debug(message) logger.debug message
end end
def logger # :nodoc: def logger # :nodoc:
if defined? RAILS_DEFAULT_LOGGER if defined? RAILS_DEFAULT_LOGGER
RAILS_DEFAULT_LOGGER RAILS_DEFAULT_LOGGER
elsif defined? Merb
Merb.logger
else else
nil nil
end end

View File

@ -4,6 +4,7 @@ module Webrat
class Scope class Scope
include Logging include Logging
include Flunk include Flunk
include Assertions
def initialize(session, html, selector = nil) def initialize(session, html, selector = nil)
@session = session @session = session
@ -11,6 +12,22 @@ module Webrat
@selector = selector @selector = selector
end end
def selects_date(date_string, options = {})
id_or_name = options[:from]
date = Date.parse date_string
year_option = find_select_option(date.year.to_s, /#{id_or_name.to_s}.*1i/)
month_option = find_select_option(date.month.to_s, /#{id_or_name.to_s}.*2i/)
day_option = find_select_option(date.day.to_s, /#{id_or_name.to_s}.*3i/)
flunk("Could not find date picker for #{date_string}") if year_option.nil? || month_option.nil? || day_option.nil?
year_option.choose
month_option.choose
day_option.choose
end
alias_method :select_date, :selects_date
# Verifies an input field or textarea exists on the current page, and stores a value for # 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. # it which will be sent when the form is submitted.
# #
@ -90,6 +107,12 @@ module Webrat
alias_method :attach_file, :attaches_file alias_method :attach_file, :attaches_file
def clicks_area(area_name)
find_area(area_name).click
end
alias_method :click_area, :clicks_area
# Issues a request for the URL pointed to by a link on the current page, # 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. # follows any redirects, and verifies the final page load was successful.
# #
@ -165,19 +188,21 @@ module Webrat
alias_method :click_button, :clicks_button alias_method :click_button, :clicks_button
def dom # :nodoc: def dom # :nodoc:
return @dom if defined?(@dom) && @dom @dom ||= Hpricot(scoped_html)
@dom = Hpricot(@html)
if @selector
html = (@dom / @selector).first.to_html
@dom = Hpricot(html)
end
return @dom
end end
protected protected
def scoped_html
@scoped_html ||= begin
if @selector
(Hpricot(@html) / @selector).first.to_html
else
@html
end
end
end
def find_select_option(option_text, id_or_name_or_label) def find_select_option(option_text, id_or_name_or_label)
if id_or_name_or_label if id_or_name_or_label
field = find_field(id_or_name_or_label, SelectField) field = find_field(id_or_name_or_label, SelectField)
@ -197,19 +222,26 @@ module Webrat
button = form.find_button(value) button = form.find_button(value)
return button if button return button if button
end end
flunk("Could not find button #{value.inspect}") flunk("Could not find button #{value.inspect}")
end end
def find_link(text, selector = nil) def find_area(area_name)
matching_links = [] areas.select{|area| area.matches_text?(area_name)}.first || flunk("Could not find area with name #{area_name}")
end
links_within(selector).each do |possible_link| def areas
matching_links << possible_link if possible_link.matches_text?(text) (dom / "area").map do |element|
Area.new(@session, element)
end
end
def find_link(text, selector = nil)
matching_links = links_within(selector).select do |possible_link|
possible_link.matches_text?(text)
end end
if matching_links.any? if matching_links.any?
matching_links.sort_by { |l| l.text.length }.first matching_links.min { |a, b| a.text.length <=> b.text.length }
else else
flunk("Could not find link with text #{text.inspect}") flunk("Could not find link with text #{text.inspect}")
end end

View File

@ -14,6 +14,10 @@ module Webrat
end end
end end
def matches_value?(value)
@element.attributes['value'] == value.to_s
end
def choose def choose
@select.raise_error_if_disabled @select.raise_error_if_disabled
@select.set(value) @select.set(value)

View File

@ -12,6 +12,7 @@ module Webrat
def initialize def initialize
@http_method = :get @http_method = :get
@data = {} @data = {}
@default_headers = {}
end end
# Saves the page out to RAILS_ROOT/tmp/ and opens it in the default # Saves the page out to RAILS_ROOT/tmp/ and opens it in the default
@ -52,16 +53,27 @@ module Webrat
File.expand_path(".") File.expand_path(".")
end end
def basic_auth(user, pass)
@default_headers['HTTP_AUTHORIZATION'] = "Basic " + ["#{user}:#{pass}"].pack("m*")
end
def headers
@default_headers.dup
end
def request_page(url, http_method, data) def request_page(url, http_method, data)
debug_log "REQUESTING PAGE: #{http_method.to_s.upcase} #{url} with #{data.inspect}" h = headers
if @current_url h['HTTP_REFERER'] = @current_url if @current_url
send "#{http_method}", url, data || {}, {"HTTP_REFERER" => @current_url}
else debug_log "REQUESTING PAGE: #{http_method.to_s.upcase} #{url} with #{data.inspect} and HTTP headers #{h.inspect}"
if h.empty?
send "#{http_method}", url, data || {} send "#{http_method}", url, data || {}
else
send "#{http_method}", url, data || {}, h
end end
save_and_open_page if exception_caught? save_and_open_page if exception_caught?
flunk("Page load was not successful (Code: #{response_code.inspect})") unless success_code? flunk("Page load was not successful (Code: #{response_code.inspect}):\n#{formatted_error}") unless success_code?
@scope = nil @scope = nil
@current_url = url @current_url = url
@ -109,6 +121,11 @@ module Webrat
yield Scope.new(self, response_body, selector) yield Scope.new(self, response_body, selector)
end end
# Issues a GET request for a page, follows any redirects, and verifies the final page
# load was successful.
#
# Example:
# visits "/"
def visits(url = nil, http_method = :get, data = {}) def visits(url = nil, http_method = :get, data = {})
request_page(url, http_method, data) request_page(url, http_method, data)
end end
@ -124,18 +141,26 @@ module Webrat
response_html.gsub(/"\/(stylesheets|images)/, doc_root + '/\1') response_html.gsub(/"\/(stylesheets|images)/, doc_root + '/\1')
end end
# Subclasses can override this to show error messages without html
def formatted_error
response_body
end
def_delegators :current_scope, :fill_in, :fills_in def_delegators :current_scope, :fill_in, :fills_in
def_delegators :current_scope, :check, :checks def_delegators :current_scope, :check, :checks
def_delegators :current_scope, :uncheck, :unchecks def_delegators :current_scope, :uncheck, :unchecks
def_delegators :current_scope, :choose, :chooses def_delegators :current_scope, :choose, :chooses
def_delegators :current_scope, :select, :selects def_delegators :current_scope, :select, :selects
def_delegators :current_scope, :select_date, :selects_date
def_delegators :current_scope, :attach_file, :attaches_file def_delegators :current_scope, :attach_file, :attaches_file
def_delegators :current_scope, :click_area, :clicks_area
def_delegators :current_scope, :click_link, :clicks_link def_delegators :current_scope, :click_link, :clicks_link
def_delegators :current_scope, :click_get_link, :clicks_get_link def_delegators :current_scope, :click_get_link, :clicks_get_link
def_delegators :current_scope, :click_delete_link, :clicks_delete_link def_delegators :current_scope, :click_delete_link, :clicks_delete_link
def_delegators :current_scope, :click_post_link, :clicks_post_link def_delegators :current_scope, :click_post_link, :clicks_post_link
def_delegators :current_scope, :click_put_link, :clicks_put_link def_delegators :current_scope, :click_put_link, :clicks_put_link
def_delegators :current_scope, :click_button, :clicks_button def_delegators :current_scope, :click_button, :clicks_button
def_delegators :current_scope, :should_see
def_delegators :current_scope, :should_not_see
end end
end end

View File

@ -2,6 +2,7 @@ module Webrat
class MechanizeSession < Session class MechanizeSession < Session
def initialize(mechanize = WWW::Mechanize.new) def initialize(mechanize = WWW::Mechanize.new)
super()
@mechanize = mechanize @mechanize = mechanize
end end

49
lib/webrat/merb.rb Normal file
View File

@ -0,0 +1,49 @@
Dir[File.join(File.dirname(__FILE__), "merb", "*.rb")].sort.each do |file|
require File.expand_path(file)
end
module Webrat
class Session
include Merb::Test::RequestHelper
attr_reader :response
def get(url, data, headers = nil)
do_request(url, data, headers, "GET")
end
def post(url, data, headers = nil)
do_request(url, data, headers, "POST")
end
def put(url, data, headers = nil)
do_request(url, data, headers, "PUT")
end
def delete(url, data, headers = nil)
do_request(url, data, headers, "DELETE")
end
def response_body
@response.body.to_s
end
def response_code
@response.status
end
protected
def do_request(url, data, headers, method)
@response = request(url, :params => (data && data.any?) ? data : nil, :headers => headers, :method => method)
self.get(@response.headers['Location'], nil, @response.headers) if @response.status == 302
end
end
end
class Merb::Test::RspecStory
def browser
@browser ||= Webrat::Session.new
end
end

View File

@ -0,0 +1,125 @@
# This class has dubious semantics and we only have it so that
# people can write params[:key] instead of params['key']
# and they get the same value for both keys.
class HashWithIndifferentAccess < Hash
def initialize(constructor = {})
if constructor.is_a?(Hash)
super()
update(constructor)
else
super(constructor)
end
end
def default(key = nil)
if key.is_a?(Symbol) && include?(key = key.to_s)
self[key]
else
super
end
end
alias_method :regular_writer, :[]= unless method_defined?(:regular_writer)
alias_method :regular_update, :update unless method_defined?(:regular_update)
#
# Assigns a new value to the hash.
#
# Example:
#
# hash = HashWithIndifferentAccess.new
# hash[:key] = "value"
#
def []=(key, value)
regular_writer(convert_key(key), convert_value(value))
end
#
# Updates the instantized hash with values from the second.
#
# Example:
#
# >> hash_1 = HashWithIndifferentAccess.new
# => {}
#
# >> hash_1[:key] = "value"
# => "value"
#
# >> hash_2 = HashWithIndifferentAccess.new
# => {}
#
# >> hash_2[:key] = "New Value!"
# => "New Value!"
#
# >> hash_1.update(hash_2)
# => {"key"=>"New Value!"}
#
def update(other_hash)
other_hash.each_pair { |key, value| regular_writer(convert_key(key), convert_value(value)) }
self
end
alias_method :merge!, :update
# Checks the hash for a key matching the argument passed in
def key?(key)
super(convert_key(key))
end
alias_method :include?, :key?
alias_method :has_key?, :key?
alias_method :member?, :key?
# Fetches the value for the specified key, same as doing hash[key]
def fetch(key, *extras)
super(convert_key(key), *extras)
end
# Returns an array of the values at the specified indicies.
def values_at(*indices)
indices.collect {|key| self[convert_key(key)]}
end
# Returns an exact copy of the hash.
def dup
HashWithIndifferentAccess.new(self)
end
# Merges the instantized and the specified hashes together, giving precedence to the values from the second hash
# Does not overwrite the existing hash.
def merge(hash)
self.dup.update(hash)
end
# Removes a specified key from the hash.
def delete(key)
super(convert_key(key))
end
def stringify_keys!; self end
def symbolize_keys!; self end
def to_options!; self end
# Convert to a Hash with String keys.
def to_hash
Hash.new(default).merge(self)
end
protected
def convert_key(key)
key.kind_of?(Symbol) ? key.to_s : key
end
def convert_value(value)
case value
when Hash
value.with_indifferent_access
when Array
value.collect { |e| e.is_a?(Hash) ? e.with_indifferent_access : e }
else
value
end
end
end

View File

@ -0,0 +1,19 @@
require "cgi"
module Webrat
class ParamParser
def self.parse_query_parameters(query_string)
return {} if query_string.blank?
pairs = query_string.split('&').collect do |chunk|
next if chunk.empty?
key, value = chunk.split('=', 2)
next if key.empty?
value = value.nil? ? nil : CGI.unescape(value)
[ CGI.unescape(key), value ]
end.compact
UrlEncodedPairParser.new(pairs).result
end
end
end

View File

@ -0,0 +1,12 @@
class Hash
def with_indifferent_access
hash = HashWithIndifferentAccess.new(self)
hash.default = self.default
hash
end
end
class NilClass
def to_param
nil
end
end

View File

@ -0,0 +1,96 @@
require "cgi"
require "strscan"
class UrlEncodedPairParser < StringScanner #:nodoc:
attr_reader :top, :parent, :result
def initialize(pairs = [])
super('')
@result = {}
pairs.each { |key, value| parse(key, value) }
end
KEY_REGEXP = %r{([^\[\]=&]+)}
BRACKETED_KEY_REGEXP = %r{\[([^\[\]=&]+)\]}
# Parse the query string
def parse(key, value)
self.string = key
@top, @parent = result, nil
# First scan the bare key
key = scan(KEY_REGEXP) or return
key = post_key_check(key)
# Then scan as many nestings as present
until eos?
r = scan(BRACKETED_KEY_REGEXP) or return
key = self[1]
key = post_key_check(key)
end
bind(key, value)
end
private
# After we see a key, we must look ahead to determine our next action. Cases:
#
# [] follows the key. Then the value must be an array.
# = follows the key. (A value comes next)
# & or the end of string follows the key. Then the key is a flag.
# otherwise, a hash follows the key.
def post_key_check(key)
if scan(/\[\]/) # a[b][] indicates that b is an array
container(key, Array)
nil
elsif check(/\[[^\]]/) # a[b] indicates that a is a hash
container(key, Hash)
nil
else # End of key? We do nothing.
key
end
end
# Add a container to the stack.
def container(key, klass)
type_conflict! klass, top[key] if top.is_a?(Hash) && top.key?(key) && ! top[key].is_a?(klass)
value = bind(key, klass.new)
type_conflict! klass, value unless value.is_a?(klass)
push(value)
end
# Push a value onto the 'stack', which is actually only the top 2 items.
def push(value)
@parent, @top = @top, value
end
# Bind a key (which may be nil for items in an array) to the provided value.
def bind(key, value)
if top.is_a? Array
if key
if top[-1].is_a?(Hash) && ! top[-1].key?(key)
top[-1][key] = value
else
top << {key => value}.with_indifferent_access
push top.last
value = top[key]
end
else
top << value
end
elsif top.is_a? Hash
key = CGI.unescape(key)
parent << (@top = {}) if top.key?(key) && parent.is_a?(Array)
top[key] ||= value
return top[key]
else
raise ArgumentError, "Don't know what to do: top is #{top.inspect}"
end
return value
end
def type_conflict!(klass, value)
raise TypeError, "Conflicting types for parameter containers. Expected an instance of #{klass} but found an instance of #{value.class}. This can be caused by colliding Array and Hash parameters like qs[]=value&qs[key]=value. (The parameters received were #{value.inspect}.)"
end
end

View File

@ -0,0 +1,24 @@
require 'webrat'
class CGIMethods
def self.parse_query_parameters(params)
hash = {}
params.split('&').each do |p|
pair = p.split('=')
hash[pair[0]] = pair[1]
end
hash
end
end
module Webrat
class RackSession < Session
def response_body
@response.body
end
def response_code
@response.status
end
end
end

View File

@ -6,15 +6,6 @@ module ActionController
include Webrat::RedirectActions include Webrat::RedirectActions
end end
# Issues a GET request for a page, follows any redirects, and verifies the final page
# load was successful.
#
# Example:
# visits "/"
def visits(*args)
webrat_session.visits(*args)
end
def respond_to?(name) def respond_to?(name)
super || webrat_session.respond_to?(name) super || webrat_session.respond_to?(name)
end end
@ -32,6 +23,7 @@ module ActionController
def webrat_session def webrat_session
@webrat_session ||= Webrat::RailsSession.new(self) @webrat_session ||= Webrat::RailsSession.new(self)
end end
end end
end end
end end

View File

@ -0,0 +1,12 @@
if (locator == '*') {
return selenium.browserbot.locationStrategies['xpath'].call(this, "//input[@type='submit']", inDocument, inWindow)
}
var inputs = inDocument.getElementsByTagName('input');
return $A(inputs).find(function(candidate){
inputType = candidate.getAttribute('type');
if (inputType == 'submit' || inputType == 'image') {
var buttonText = $F(candidate);
return (PatternMatcher.matches(locator + '*', buttonText));
}
return false;
});

View File

@ -0,0 +1,16 @@
var allLabels = inDocument.getElementsByTagName("label");
var candidateLabels = $A(allLabels).select(function(candidateLabel){
var regExp = new RegExp('^' + locator + '\\b', 'i');
var labelText = getText(candidateLabel).strip();
return (labelText.search(regExp) >= 0);
});
if (candidateLabels.length == 0) {
return null;
}
candidateLabels = candidateLabels.sortBy(function(s) { return s.length * -1; }); //reverse length sort
var locatedLabel = candidateLabels.first();
var labelFor = locatedLabel.getAttribute('for');
if ((labelFor == null) && (locatedLabel.hasChildNodes())) {
return locatedLabel.firstChild; //TODO: should find the first form field, not just any node
}
return selenium.browserbot.locationStrategies['id'].call(this, labelFor, inDocument, inWindow);

View File

@ -0,0 +1,5 @@
var locationStrategies = selenium.browserbot.locationStrategies;
return locationStrategies['id'].call(this, locator, inDocument, inWindow)
|| locationStrategies['name'].call(this, locator, inDocument, inWindow)
|| locationStrategies['label'].call(this, locator, inDocument, inWindow)
|| null;

View File

@ -0,0 +1,9 @@
var links = inDocument.getElementsByTagName('a');
var candidateLinks = $A(links).select(function(candidateLink) {
return PatternMatcher.matches(locator, getText(candidateLink));
});
if (candidateLinks.length == 0) {
return null;
}
candidateLinks = candidateLinks.sortBy(function(s) { return s.length * -1; }); //reverse length sort
return candidateLinks.first();

View File

@ -0,0 +1,15 @@
var locatorParts = locator.split('|');
var cssAncestor = locatorParts[0];
var linkText = locatorParts[1];
var matchingElements = cssQuery(cssAncestor, inDocument);
var candidateLinks = matchingElements.collect(function(ancestor){
var links = ancestor.getElementsByTagName('a');
return $A(links).select(function(candidateLink) {
return PatternMatcher.matches(linkText, getText(candidateLink));
});
}).flatten().compact();
if (candidateLinks.length == 0) {
return null;
}
candidateLinks = candidateLinks.sortBy(function(s) { return s.length * -1; }); //reverse length sort
return candidateLinks.first();

View File

@ -0,0 +1,5 @@
var optionElements = inDocument.getElementsByTagName('option');
var locatedOption = $A(optionElements).find(function(candidate){
return (PatternMatcher.matches(locator, getText(candidate)));
});
return locatedOption ? locatedOption.parentNode : null;

View File

@ -2,7 +2,7 @@ module Webrat
class SeleniumSession < Session class SeleniumSession < Session
def initialize(selenium_driver) def initialize(selenium_driver)
super super()
@selenium = selenium_driver @selenium = selenium_driver
define_location_strategies define_location_strategies
end end
@ -11,6 +11,8 @@ module Webrat
@selenium.open(url) @selenium.open(url)
end end
alias_method :visit, :visits
def fills_in(field_identifier, options) def fills_in(field_identifier, options)
locator = "webrat=#{Regexp.escape(field_identifier)}" locator = "webrat=#{Regexp.escape(field_identifier)}"
@selenium.type(locator, "#{options[:with]}") @selenium.type(locator, "#{options[:with]}")
@ -26,11 +28,13 @@ module Webrat
@selenium.click("button=#{button_text}") @selenium.click("button=#{button_text}")
wait_for_result(options[:wait]) wait_for_result(options[:wait])
end end
alias_method :click_button, :clicks_button
def clicks_link(link_text, options = {}) def clicks_link(link_text, options = {})
@selenium.click("webratlink=#{link_text}") @selenium.click("webratlink=#{link_text}")
wait_for_result(options[:wait]) wait_for_result(options[:wait])
end end
alias_method :click_link, :clicks_link
def clicks_link_within(selector, link_text, options = {}) def clicks_link_within(selector, link_text, options = {})
@selenium.click("webratlinkwithin=#{selector}|#{link_text}") @selenium.click("webratlinkwithin=#{selector}|#{link_text}")
@ -52,7 +56,7 @@ module Webrat
end end
def wait_for_ajax(timeout = 15000) def wait_for_ajax(timeout = 15000)
@selenium.wait_for_condition "window.Ajax.activeRequestCount == 0", timeout @selenium.wait_for_condition "Ajax.activeRequestCount == 0", timeout
end end
def wait_for_effects(timeout = 15000) def wait_for_effects(timeout = 15000)
@ -86,83 +90,11 @@ module Webrat
protected protected
def define_location_strategies def define_location_strategies
@selenium.add_location_strategy('label', <<-JS) Dir[File.join(File.dirname(__FILE__), "location_strategy_javascript", "*.js")].sort.each do |file|
var allLabels = inDocument.getElementsByTagName("label"); strategy_js = File.read(file)
var candidateLabels = $A(allLabels).select(function(candidateLabel){ strategy_name = File.basename(file, '.js')
var regExp = new RegExp('^' + locator + '\\\\b', 'i'); @selenium.add_location_strategy(strategy_name, strategy_js)
var labelText = getText(candidateLabel).strip(); end
return (labelText.search(regExp) >= 0); end
});
if (candidateLabels.length == 0) {
return null;
}
candidateLabels = candidateLabels.sortBy(function(s) { return s.length * -1; }); //reverse length sort
var locatedLabel = candidateLabels.first();
var labelFor = locatedLabel.getAttribute('for');
return selenium.browserbot.locationStrategies['id'].call(this, labelFor, inDocument, inWindow);
JS
@selenium.add_location_strategy('webrat', <<-JS)
var locationStrategies = selenium.browserbot.locationStrategies;
return locationStrategies['id'].call(this, locator, inDocument, inWindow)
|| locationStrategies['name'].call(this, locator, inDocument, inWindow)
|| locationStrategies['label'].call(this, locator, inDocument, inWindow)
|| null;
JS
@selenium.add_location_strategy('button', <<-JS)
if (locator == '*') {
return selenium.browserbot.locationStrategies['xpath'].call(this, "//input[@type='submit']", inDocument, inWindow)
}
var inputs = inDocument.getElementsByTagName('input');
return $A(inputs).find(function(candidate){
inputType = candidate.getAttribute('type');
if (inputType == 'submit' || inputType == 'image') {
var buttonText = $F(candidate);
return (PatternMatcher.matches(locator + '*', buttonText));
}
return false;
});
JS
@selenium.add_location_strategy('webratlink', <<-JS)
var links = inDocument.getElementsByTagName('a');
var candidateLinks = $A(links).select(function(candidateLink) {
return PatternMatcher.matches(locator, getText(candidateLink));
});
if (candidateLinks.length == 0) {
return null;
}
candidateLinks = candidateLinks.sortBy(function(s) { return s.length * -1; }); //reverse length sort
return candidateLinks.first();
JS
@selenium.add_location_strategy('webratlinkwithin', <<-JS)
var locatorParts = locator.split('|');
var cssAncestor = locatorParts[0];
var linkText = locatorParts[1];
var matchingElements = cssQuery(cssAncestor, inDocument);
var candidateLinks = matchingElements.collect(function(ancestor){
var links = ancestor.getElementsByTagName('a');
return $A(links).select(function(candidateLink) {
return PatternMatcher.matches(linkText, getText(candidateLink));
});
}).flatten().compact();
if (candidateLinks.length == 0) {
return null;
}
candidateLinks = candidateLinks.sortBy(function(s) { return s.length * -1; }); //reverse length sort
return candidateLinks.first();
JS
@selenium.add_location_strategy('webratselectwithoption', <<-JS)
var optionElements = inDocument.getElementsByTagName('option');
var locatedOption = $A(optionElements).find(function(candidate){
return (PatternMatcher.matches(locator, getText(candidate)));
});
return locatedOption ? locatedOption.parentNode : null;
JS
end
end end
end end

View File

@ -0,0 +1,19 @@
require 'webrat/rack/rack_session'
require 'sinatra'
require 'sinatra/test/methods'
module Webrat
class SinatraSession < RackSession
include Sinatra::Test::Methods
%w(get head post put delete).each do |verb|
define_method(verb) do |*args| # (path, data, headers = nil)
path, data, headers = *args
params = data.merge({:env => headers || {}})
self.__send__("#{verb}_it", path, params)
follow! while @response.redirect?
end
end
end
end

View File

@ -1,5 +1,5 @@
require File.expand_path(File.dirname(__FILE__) + "/../spec_helper") require File.expand_path(File.dirname(__FILE__) + "/../spec_helper")
unless ENV["TEST_MODE"] == "merb" #TODO - Rob
describe "attaches_file" do describe "attaches_file" do
before do before do
@session = Webrat::TestSession.new @session = Webrat::TestSession.new
@ -70,3 +70,4 @@ describe "attaches_file" do
@session.clicks_button @session.clicks_button
end end
end end
end

View File

@ -0,0 +1,23 @@
require File.expand_path(File.dirname(__FILE__) + "/../spec_helper")
describe "Basic Auth HTTP headers" do
before do
@session = Webrat::TestSession.new
@session.basic_auth('user', 'secret')
end
it "should be present in visits" do
@session.should_receive(:get).with("/", {}, {'HTTP_AUTHORIZATION' => "Basic dXNlcjpzZWNyZXQ=\n"})
@session.visits("/")
end
it "should be present in form submits" do
@session.response_body = <<-EOS
<form method="post" action="/form1">
<input type="submit" />
</form>
EOS
@session.should_receive(:post).with("/form1", {}, {'HTTP_AUTHORIZATION' => "Basic dXNlcjpzZWNyZXQ=\n"})
@session.clicks_button
end
end

View File

@ -0,0 +1,81 @@
require File.expand_path(File.dirname(__FILE__) + "/../spec_helper")
describe "clicks_area" do
before do
@session = Webrat::TestSession.new
end
it "should use get by default" do
@session.response_body = <<-EOS
<map name="map_de" id="map_de">
<area href="/page" title="Berlin" id="berlin" shape="poly" alt="Berlin" coords="180,89,180" />
</map>
EOS
@session.should_receive(:get).with("/page", {})
@session.clicks_area "Berlin"
end
it "should assert valid response" do
@session.response_body = <<-EOS
<map name="map_de" id="map_de">
<area href="/page" title="Berlin" id="berlin" shape="poly" alt="Berlin" coords="180,89,180" />
</map>
EOS
@session.response_code = 404
lambda { @session.clicks_area "Berlin" }.should raise_error
end
it "should fail if the area doesn't exist" do
@session.response_body = <<-EOS
<map name="map_de" id="map_de">
<area href="/page" title="Berlin" id="berlin" shape="poly" alt="Berlin" coords="180,89,180" />
</map>
EOS
lambda {
@session.clicks_area "Missing area"
}.should raise_error
end
it "should not be case sensitive" do
@session.response_body = <<-EOS
<map name="map_de" id="map_de">
<area href="/page" title="Berlin" id="berlin" shape="poly" alt="Berlin" coords="180,89,180" />
</map>
EOS
@session.should_receive(:get).with("/page", {})
@session.clicks_area "berlin"
end
it "should follow relative links" do
@session.stub!(:current_url).and_return("/page")
@session.response_body = <<-EOS
<map name="map_de" id="map_de">
<area href="sub" title="Berlin" id="berlin" shape="poly" alt="Berlin" coords="180,89,180" />
</map>
EOS
@session.should_receive(:get).with("/page/sub", {})
@session.clicks_area "Berlin"
end
it "should follow fully qualified local links" do
@session.response_body = <<-EOS
<map name="map_de" id="map_de">
<area href="http://www.example.com/page" title="Berlin" id="berlin" shape="poly" alt="Berlin" coords="180,89,180" />
</map>
EOS
@session.should_receive(:get).with("http://www.example.com/page", {})
@session.clicks_area "Berlin"
end
it "should follow query parameters" do
@session.response_body = <<-EOS
<map name="map_de" id="map_de">
<area href="/page?foo=bar" title="Berlin" id="berlin" shape="poly" alt="Berlin" coords="180,89,180" />
</map>
EOS
@session.should_receive(:get).with("/page?foo=bar", {})
@session.clicks_area "Berlin"
end
end

View File

@ -165,6 +165,21 @@ describe "clicks_button" do
@session.clicks_button @session.clicks_button
end end
it "should not send disabled field values" do
@session.response_body = <<-EOS
<form method="get" action="/login">
<input disabled id="user_email" name="user[email]" value="test@example.com" type="text" />
<input disabled id="user_gender_male" name="user[gender]" type="radio" value="M" />
<label for="user_gender_male">Male</label>
<input disabled id="user_gender_female" name="user[gender]" type="radio" value="F" checked="checked" />
<label for="user_gender_female">Female</label>
<input type="submit" />
</form>
EOS
@session.should_receive(:get).with("/login", {})
@session.clicks_button
end
it "should send default checked fields" do it "should send default checked fields" do
@session.response_body = <<-EOS @session.response_body = <<-EOS
<form method="get" action="/login"> <form method="get" action="/login">
@ -344,6 +359,16 @@ describe "clicks_button" do
@session.clicks_button @session.clicks_button
end end
it "should find buttons by their IDs" do
@session.response_body = <<-EOS
<form action="/">
<input type="submit" id="my_button" />
</form>
EOS
@session.should_receive(:get)
@session.clicks_button "my_button"
end
it "should find image buttons by their alt text" do it "should find image buttons by their alt text" do
@session.response_body = <<-EOS @session.response_body = <<-EOS
<form action="/"> <form action="/">

View File

@ -145,6 +145,18 @@ describe "fills_in" do
@session.clicks_button @session.clicks_button
end end
it "should work if the input type is not set" do
@session.response_body = <<-EOS
<form method="post" action="/login">
<input id="user_email" name="user[email]" />
<input type="submit" />
</form>
EOS
@session.should_receive(:post).with("/login", "user" => {"email" => "foo@example.com"})
@session.fills_in "user[email]", :with => "foo@example.com"
@session.clicks_button
end
it "should work with symbols" do it "should work with symbols" do
@session.response_body = <<-EOS @session.response_body = <<-EOS
<form method="post" action="/login"> <form method="post" action="/login">
@ -157,4 +169,17 @@ describe "fills_in" do
@session.fills_in :email, :with => "foo@example.com" @session.fills_in :email, :with => "foo@example.com"
@session.clicks_button @session.clicks_button
end end
it "should escape field values" do
@session.response_body = <<-EOS
<form method="post" action="/users">
<label for="user_phone">Phone</label>
<input id="user_phone" name="user[phone]" type="text" />
<input type="submit" />
</form>
EOS
@session.should_receive(:post).with("/users", "user" => {"phone" => "+1 22 33"})
@session.fills_in 'Phone', :with => "+1 22 33"
@session.clicks_button
end
end end

View File

@ -0,0 +1,111 @@
require File.expand_path(File.dirname(__FILE__) + "/../spec_helper")
describe "date_selects" do
before do
@session = Webrat::TestSession.new
@example_date_select = <<-EOS
<form method="post" action="/login">
<label for="created_at">Created at</label><br />
<select id="created_at_1i" name="created_at(1i)">
<option value="2002">2002</option>
<option value="2003">2003</option>
</select>
<select id="created_at_2i" name="created_at(2i)">
<option value="11">November</option>
<option value="12">December</option>
</select>
<select id="created_at_3i" name="created_at(3i)">
<option value="1">1</option>
<option value="2">2</option>
</select>
<input type="button" value="submit"/>
</form>
EOS
end
it "should fail if option not found" do
@session.response_body = @example_date_select
lambda { @session.selects_date "2008-07-13"}.should raise_error
end
it "should fail if option not found in list specified by element name" do
@session.response_body = @example_date_select
lambda { @session.selects_date "2008-07-13", :from => "created_at" }.should raise_error
end
it "should fail if specified list not found" do
@session.response_body = <<-EOS
<form method="get" action="/login">
<select name="month"><option value="1">January</option></select>
</form>
EOS
lambda { @session.selects_date "2003-12-01", :from => "created_at" }.should raise_error
end
it "should send value from option" do
@session.response_body = <<-EOS
<form method="post" action="/login">
<label for="updated_at">Created at</label><br />
<select id="updated_at_1i" name="updated_at(1i)">
<option value=""></option>
<option value="2003">2003</option>
</select>
<select id="updated_at_2i" name="updated_at(2i)">
<option value=""></option>
<option value="12">December</option>
</select>
<select id="updated_at_3i" name="updated_at(3i)">
<option value=""></option>
<option value="1">1</option>
</select>
<label for="created_at">Created at</label><br />
<select id="created_at_1i" name="created_at(1i)">
<option value="2003">2003</option>
</select>
<select id="created_at_2i" name="created_at(2i)">
<option value="12">December</option>
</select>
<select id="created_at_3i" name="created_at(3i)">
<option value="1">1</option>
</select>
<input type="button" value="submit"/>
</form>
EOS
@session.should_receive(:post).with("/login", "created_at(1i)" => "2003", 'created_at(2i)' => '12', 'created_at(3i)' => '1', "updated_at(1i)" => "", 'updated_at(2i)' => '', 'updated_at(3i)' => '')
@session.selects_date '2003-12-01', :from => "created_at"
@session.clicks_button
end
it "should work without specifying the field name or label" do
@session.response_body = @example_date_select
@session.should_receive(:post).with("/login", "created_at(1i)" => "2003", 'created_at(2i)' => '12', 'created_at(3i)' => '1')
@session.selects_date '2003-12-01'
@session.clicks_button
end
it "should correctly set day and month when there are the same options available" do
@session.response_body = <<-EOS
<form method="post" action="/login">
<label for="created_at">Created at</label><br />
<select id="created_at_1i" name="created_at(1i)">
<option value="2003">2003</option>
</select>
<select id="created_at_2i" name="created_at(2i)">
<option value="1">January</option>
<option value="12">December</option>
</select>
<select id="created_at_3i" name="created_at(3i)">
<option value="1">1</option>
<option value="12">12</option>
</select>
<input type="button" value="submit"/>
</form>
EOS
@session.should_receive(:post).with("/login", "created_at(1i)" => "2003", 'created_at(2i)' => '12', 'created_at(3i)' => '1')
@session.selects_date '2003-12-01'
@session.clicks_button
end
end

View File

@ -108,6 +108,18 @@ describe "selects" do
@session.clicks_button @session.clicks_button
end end
it "should send value from option in list specified by value" do
@session.response_body = <<-EOS
<form method="post" action="/login">
<select name="start_month"><option value="s1">January</option></select>
<input type="submit" />
</form>
EOS
@session.should_receive(:post).with("/login", "start_month" => "s1")
@session.selects "s1", :from => "start_month"
@session.clicks_button
end
it "should send value from option in list specified by label" do it "should send value from option in list specified by label" do
@session.response_body = <<-EOS @session.response_body = <<-EOS
<form method="post" action="/login"> <form method="post" action="/login">

View File

@ -0,0 +1,73 @@
require File.expand_path(File.dirname(__FILE__) + "/../spec_helper")
describe "should_not_see" do
before do
@session = Webrat::TestSession.new
end
it "should fail if the string is in the HTML" do
@session.response_body = <<-EOS
<a href="/page2">Link</a>
EOS
lambda {
@session.should_not_see "Link"
}.should raise_error
end
it "should fail if the regexp is in the HTML" do
@session.response_body = <<-EOS
<a href="/page2">Link</a>
EOS
lambda {
@session.should_not_see /Li(n)[ck]/
}.should raise_error
end
it "should fail if the string is in the HTML scope" do
@session.response_body = <<-EOS
<div id="first">
<a href="/page2">Link</a>
</div>
<div id="second">
</div>
EOS
lambda {
@session.within "#first" do |scope|
scope.should_not_see "Link"
end
}.should raise_error
end
it "should pass if the string is not in the HTML scope" do
@session.response_body = <<-EOS
<div id="first">
<a href="/page2">Link</a>
</div>
<div id="second">
</div>
EOS
@session.within "#second" do |scope|
scope.should_not_see "Link"
end
end
it "should pass if the string is not in the HTML" do
@session.response_body = <<-EOS
<a href="/page2">Link</a>
EOS
@session.should_not_see "Missing"
end
it "should pass if the regexp is not in the HTML" do
@session.response_body = <<-EOS
<a href="/page2">Different</a>
EOS
@session.should_not_see /Li(n)[ck]/
end
end

View File

@ -0,0 +1,73 @@
require File.expand_path(File.dirname(__FILE__) + "/../spec_helper")
describe "should_see" do
before do
@session = Webrat::TestSession.new
end
it "should pass if the string is in the HTML" do
@session.response_body = <<-EOS
<a href="/page2">Link</a>
EOS
@session.should_see "Link"
end
it "should pass if the regexp is in the HTML" do
@session.response_body = <<-EOS
<a href="/page2">Link</a>
EOS
@session.should_see /Li(n)[ck]/
end
it "should pass if the string is in the HTML scope" do
@session.response_body = <<-EOS
<div id="first">
<a href="/page2">Link</a>
</div>
<div id="second">
</div>
EOS
@session.within "#first" do |scope|
scope.should_see "Link"
end
end
it "should fail if the string is not in the HTML scope" do
@session.response_body = <<-EOS
<div id="first">
<a href="/page2">Link</a>
</div>
<div id="second">
</div>
EOS
lambda {
@session.within "#second" do |scope|
scope.should_see "Link"
end
}.should raise_error
end
it "should fail if the string is not in the HTML" do
@session.response_body = <<-EOS
<a href="/page2">Link</a>
EOS
lambda {
@session.should_see "Missing"
}.should raise_error
end
it "should fail if the regexp is not in the HTML" do
@session.response_body = <<-EOS
<a href="/page2">Different</a>
EOS
lambda {
@session.should_see /Li(n)[ck]/
}.should raise_error
end
end

View File

@ -5,17 +5,17 @@ require "spec/interop/test"
# gem install redgreen for colored test output # gem install redgreen for colored test output
begin require "redgreen" unless ENV['TM_CURRENT_LINE']; rescue LoadError; end begin require "redgreen" unless ENV['TM_CURRENT_LINE']; rescue LoadError; end
require "active_support"
silence_warnings do
require "action_controller"
require "action_controller/integration"
end
require File.expand_path(File.dirname(__FILE__) + "/../lib/webrat") require File.expand_path(File.dirname(__FILE__) + "/../lib/webrat")
require File.expand_path(File.dirname(__FILE__) + "/../lib/webrat/rails")
require File.dirname(__FILE__) + "/fakes/test_session" require File.dirname(__FILE__) + "/fakes/test_session"
if ["rails","merb"].include?(ENV["TEST_MODE"])
require File.join(File.dirname(__FILE__), "webrat", "#{ENV["TEST_MODE"]}", "helper.rb")
else
puts "Assuming test mode is Rails... for Merb set TEST_MODE=merb and rerun."
ENV["TEST_MODE"] = 'rails'
require File.join(File.dirname(__FILE__), "webrat", "#{ENV["TEST_MODE"]}", "helper.rb")
end
Spec::Runner.configure do |config| Spec::Runner.configure do |config|
# Nothing to configure yet # Nothing to configure yet
end end

View File

@ -0,0 +1,13 @@
require File.expand_path(File.dirname(__FILE__) + "/../../../lib/webrat/mechanize")
describe Webrat::MechanizeSession do
before(:each) do
@mech = Webrat::MechanizeSession.new
end
describe "headers method" do
it "should return empty headers for a newly initialized session" do
@mech.headers.should == {}
end
end
end

View File

@ -0,0 +1,2 @@
require 'merb-core'
require File.expand_path(File.dirname(__FILE__) + "/../../../lib/webrat/merb")

View File

@ -0,0 +1,48 @@
require File.expand_path(File.dirname(__FILE__) + "/../../spec_helper")
require File.expand_path(File.dirname(__FILE__) + "/helper")
describe HashWithIndifferentAccess do
it "should not update constructor when not a hash" do
HashWithIndifferentAccess.should_receive(:update).never
HashWithIndifferentAccess.new('test')
end
it "should get the default for key" do
h = HashWithIndifferentAccess.new(:test => 'me')
h.should_receive(:super).never
h.default(:test).should == 'me'
end
context "a hash with a test value applied" do
setup do
@h = HashWithIndifferentAccess.new
@h[:test] = 'me'
end
it "should assign a new value" do
@h[:test].should == 'me'
end
it "should return true if asked for existing key" do
@h.key?(:test).should be_true
end
it "should return array of values for keys" do
@h.values_at(:test).should == ['me']
end
it "should merge with another hash" do
another = HashWithIndifferentAccess.new(:value => 'test')
@h.merge(another).values_at(:test, :value).should == ['me','test']
end
it "should delete the key" do
@h.delete(:test)
@h.any?.should be_false
@h[:test].should be_nil
end
end
end

View File

@ -0,0 +1,43 @@
require File.expand_path(File.dirname(__FILE__) + "/../../spec_helper")
require File.expand_path(File.dirname(__FILE__) + "/helper")
describe Webrat::Session do
it "should not pass empty params if data is and empty hash" do
session = Webrat::Session.new
response = OpenStruct.new
response.status = 200
session.should_receive(:request).with('url', {:params=> nil, :method=>"GET", :headers=>nil}).and_return(response)
session.get('url', {}, nil)
end
%w{post put delete}.each do |request_method|
it "should call do request with method #{request_method.upcase} for a #{request_method} call" do
session = Webrat::Session.new
response = OpenStruct.new
response.status = 200
session.should_receive(:request).with('url', {:params=>nil, :method=>request_method.upcase, :headers=>nil}).and_return(response)
session.send(request_method, 'url', {}, nil)
end
end
context "a session with a response" do
setup do
@session = Webrat::Session.new
@response = OpenStruct.new
@response.status = 200
@response.body = 'test response'
@session.instance_variable_set(:@response, @response)
end
it "should return body of a request as a response_body" do
@session.response_body.should == @response.body
end
it "should return status of a request as a response_code" do
@session.response_code.should == @response.status
end
end
end

View File

@ -0,0 +1,7 @@
require "active_support"
silence_warnings do
require "action_controller"
require "action_controller/integration"
end
require File.expand_path(File.dirname(__FILE__) + "/../../../lib/webrat/rails")

35
webrat.gemspec Normal file
View File

@ -0,0 +1,35 @@
Gem::Specification.new do |s|
s.name = %q{webrat}
s.version = "0.2.1"
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
s.authors = ["Bryan Helmkamp", "Seth Fitzsimmons"]
s.date = %q{2008-10-13}
s.description = %q{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. Initial development was sponsored by [EastMedia](http://www.eastmedia.com). Synopsis --------}
s.email = ["bryan@brynary.com", "seth@mojodna.net"]
s.extra_rdoc_files = ["History.txt", "MIT-LICENSE.txt", "Manifest.txt", "README.txt", "TODO.txt"]
s.files = ["History.txt", "MIT-LICENSE.txt", "Manifest.txt", "README.txt", "Rakefile", "TODO.txt", "init.rb", "install.rb", "lib/webrat.rb", "lib/webrat/core.rb", "lib/webrat/core/assertions.rb", "lib/webrat/core/field.rb", "lib/webrat/core/flunk.rb", "lib/webrat/core/form.rb", "lib/webrat/core/label.rb", "lib/webrat/core/link.rb", "lib/webrat/core/logging.rb", "lib/webrat/core/scope.rb", "lib/webrat/core/select_option.rb", "lib/webrat/core/session.rb", "lib/webrat/mechanize.rb", "lib/webrat/mechanize/mechanize_session.rb", "lib/webrat/rails.rb", "lib/webrat/rails/rails_session.rb", "lib/webrat/rails/redirect_actions.rb", "lib/webrat/rails/session.rb", "lib/webrat/selenium.rb", "lib/webrat/selenium/selenium_session.rb", "mechanize_spike.rb", "selenium_spike.rb", "spec/api/attaches_file_spec.rb", "spec/api/checks_spec.rb", "spec/api/chooses_spec.rb", "spec/api/clicks_button_spec.rb", "spec/api/clicks_link_spec.rb", "spec/api/fills_in_spec.rb", "spec/api/reloads_spec.rb", "spec/api/save_and_open_spec.rb", "spec/api/selects_spec.rb", "spec/api/should_not_see_spec.rb", "spec/api/should_see_spec.rb", "spec/api/visits_spec.rb", "spec/api/within_spec.rb", "spec/fakes/test_session.rb", "spec/integration/rails_spec.rb", "spec/rcov.opts", "spec/spec.opts", "spec/spec_helper.rb", "spec/webrat/core/logging_spec.rb", "spec/webrat/core/session_spec.rb", "spec/webrat/rails/rails_session_spec.rb", "webrat.gemspec"]
s.has_rdoc = true
s.homepage = %q{- [Code on GitHub](http://github.com/brynary/webrat)}
s.rdoc_options = ["--main", "README.txt"]
s.require_paths = ["lib"]
s.rubyforge_project = %q{webrat}
s.rubygems_version = %q{1.2.0}
s.summary = %q{Ruby Acceptance Testing for Web applications}
if s.respond_to? :specification_version then
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
s.specification_version = 2
if current_version >= 3 then
s.add_runtime_dependency(%q<hpricot>, [">= 0.6"])
s.add_development_dependency(%q<hoe>, [">= 1.7.0"])
else
s.add_dependency(%q<hpricot>, [">= 0.6"])
s.add_dependency(%q<hoe>, [">= 1.7.0"])
end
else
s.add_dependency(%q<hpricot>, [">= 0.6"])
s.add_dependency(%q<hoe>, [">= 1.7.0"])
end
end