diff --git a/History.txt b/History.txt index 65ebd15..c3c6f97 100644 --- a/History.txt +++ b/History.txt @@ -1,40 +1,100 @@ -== Trunk +== Trunk (Git) +* _IMPORTANT_ Breaking change: + + * Removed init.rb auto-require of webrat/rails + * Removed auto-require of webrat/rails when requiring webrat when RAILS_ENV is + defined + + In your env.rb file, ensure you have: + + require "webrat/rails" + * Major enhancements - * Added selects_time, selects_date, and selects_datetime to API. (Ben Mabey, Ticket #36) + * Added select_time, select_date, and select_datetime to API. [#36] (Ben Mabey) + * Use Hpricot and REXML when not parsing with Nokogiri (on JRuby, for example) * Minor enhancements - * Now sets the mode with configuration.mode in the block (gaffo, Ticket #85) - * Allow clicking links by id and id regexp (gaffo) - * Raise Webrat::PageLoadError when a failure occurs so that application exceptions can be more accurately tested (Ryan Briones) - * Helpful error message for missing option in select box. (Ben Mabey, Ticket #40) + * Now sets the mode with configuration.mode in the block (Mike Gaffney, Ticket #85) + * Detect if the document is XML or HTML using the Content-Type when in Rails mode + * Expose #selenium method for direct access to Selenium client + * Check nokogiri gem version before requiring nokogiri + * Include the Selenium server jar file in the gem (Bryan Helmkamp, Ben Schwarz) + * Added key_down, key_up and fire_event to Selenium session (Fernando Garcia) + * Fix outputing README during Rails plugin install (Fernando Garcia) + * Strip newlines when matching label text (Miha Filej) + * Add simple support for accessing Webrat's matchers from RSpec by requiring + "webrat/rspec-rails" (David Chelimsky) + * Support save_and_open_page in Windows and Cygwin (Mike Gaffney) + * Added RadioField#checked? to indicated whether or not a radio button is checked + (Luke Melia) + * Add spec:jruby rake task for running Webrat's spec suite with JRuby + * Added field_by_xpath to locate a Webrat::Field using arbitrary XPath expressions + * Helpful error message for missing option values [#40] (Ben Mabey) + * Add set_hidden_field method (Noah Davis, Bryan Helmkamp) + * Add submit_form method for submitting a form by ID (Noah Davis, Bryan Helmkamp) + * Switch to using Nokogiri.parse for simple XML/XHTML autodetection [#66] + * Removed Webrat.root method, which seemed to be unused + * Added Webrat.configure method for global Webrat configuration [#33] + (Mike Gaffney) + * Added automatic starting and stopping of the Selenium server and a Mongrel Rails + app server when using webrat/selenium + * Switch to using selenium-client gem and vendor selenium-server.jar (Luke Melia) + * Added gemspec so the gem builds on GitHub now + * Deprecate old style methods (fills_in is deprecated in favor of fill_in, etc.) + * Improcements to the README and RDoc (Bryan Helmkamp, Mike Gaffney) + * Allow clicking links by id and id regexp (Mike Gaffney) + * Raise Webrat::DisabledFieldError when attempting to manipulate a disabled field + * Raise Webrat::NotFoundErrors when an element is not found + * Raise Webrat::PageLoadError when a failure occurs so that application exceptions + can be more accurately tested (Ryan Briones) + * Helpful error message for missing option in select box. [#40] (Ben Mabey) +* Bug fixes + + * Match against link _text_ which decodes character references. + Useful for multibyte languages like Japanese (moronatural@gmail.com) + * Fix params hash generation for Mechanize when Merb is not defined [#62] + * Expose some Webrat methods that were missing from the Webrat::Methods module + (Low Chin Chau) + * Allow mechanize session to pass through basic auth (Ryan Briones) + * Improvements to the Mechanize support (Jeremy Burks) + * Fix following fully qualified local links (Lawrence Pit) + * Fixed bug where Webrat would lose complex parameters (like foo[bar[baz]][]) + in Merb due to not correctly merging Mashes. (Drew Colthorp) + * Extend Rails' ActionController::IntegrationTest instead of + ActionController::Integration::Session (Fixes using Webrat's #select method and + avoids usage of method_missing) + == 0.3.2 / 2008-11-08 -* Minor enhancements +* 1 Minor enhancement - * Fixes behavior or have_tag when a block is passed. It passes the matched node(s) to the block for further specs again. (Carl Lerche) + * Fixes behavior or have_tag when a block is passed. It passes the matched node(s) + to the block for further specs again. (Carl Lerche) == 0.3.1 / 2008-11-07 - * Minor enhancements +* 1 Minor enhancement - * Use @_webrat_session instance variable instead of @session for Merb integration to avoid collisions + * Use @_webrat_session instance variable instead of @session for Merb integration + to avoid collisions == 0.3.0 / 2008-11-07 -* Major enhancements +* 4 Major enhancements * Added Merb support (Gwyn Morfey, Jeremy Burks, Rob Kaufman, Yehuda Katz) * Added experimental Selenium support (Luke Melia) * Add have_selector, have_xpath, have_tag and contain matchers from Merb * Switch from Hpricot to Nokogiri for XML parsing (thanks, Aaron Patterson) -* Minor enhancements +* 37 Minor enhancements * Added #within for manipulating the current page within a selector scope - * Add support for file fields via #attaches_file method (Rails only at the moment) (Kyle Hargraves) + * Add support for file fields via #attaches_file method (Rails only at the moment) + (Kyle Hargraves) * Add support for simulating SSL requests (Luke Melia) * Added #basic_auth(user, pass) to support HTTP Basic Auth (Aslak Hellesøy) * Added support for Sinatra and Rack (Aslak Hellesøy) @@ -47,24 +107,29 @@ * Allow clicking links by a regular expression * Add #http_accept for including MIME type HTTP "Accept" headers (Ryan Briones) * Add #header to support inclusion of custom HTTP headers (Ryan Briones) - * Consider response codes 200-499 as successful enough to not raise a Webrat error (David Leal) + * Consider response codes 200-499 as successful enough to not raise a Webrat error + (David Leal) * Add support for clicking areas of an image map (Alex Lang) * 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 + * 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) + * Add support for redirect_to :back by sending HTTP_REFERER headers + (Hendrik Volkmer) * 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 - and non-JS implementations (Luke Melia and Bryan Helmkamp) + * Add support for disabling JavaScript when clicking a link to enable testing of + both JS and non-JS implementations (Luke Melia and Bryan Helmkamp) * Support  's as spaces in matching link text (Luke Melia) * Support button elements (Nick Sieger) * 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) - * Added support for matching alt attributes in fields (primarly for clicks_button) (Aaron Quint) + * 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) + (Aaron Quint) * Support '&' in submitted values (Kyle Hargraves) * Support clicking links by title (Dan Barry) * Added missing spec for clicking image buttons (Tim Harper) @@ -72,7 +137,7 @@ * Add support to click_button for IDs (Gwyn Morfey) * Miscellaneous core refactorings (Jan Suchal) -* Bug fixes +* 8 Bug fixes * Fix initialization of WWW::Mechanize (Derek Kastner) * Don't open blank pages in the browser (Kyle Hargraves) @@ -81,7 +146,8 @@ * Fix bug with empty select list option (Kyle Hargraves) * Fix regression of not sending default values in password fields * 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) + * 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 @@ -98,25 +164,29 @@ * Added reloads method to reload the page (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 - to within a given css selector (Luke Melia) + to within a given css selector (Luke Melia) * 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 (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 (Kyle Hargraves) * Change clicks_link to find the shortest matching link (Luke Melia) * Improve matching for labels in potentially ambiguous cases * 7 Bug fixes - * Fix incorrect serializing of collection inputs, i.e. name contains [] (Kamal Fariz Mahyuddi) + * Fix incorrect serializing of collection inputs, i.e. name contains [] + (Kamal Fariz Mahyuddi) * Serialize empty text field values just like browsers (Kamal Fariz Mahyuddi) * Quick fix to avoid @dom not initialized warnings (Kamal Fariz Mahyuddi) * 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 (David Chelimsky) * Support param hashes nested more than one level (David Chelimsky) == 0.1.0 / 2007-11-28 * 1 major enhancement + * Birthday! diff --git a/README.rdoc b/README.rdoc index 367244d..35b5c0c 100644 --- a/README.rdoc +++ b/README.rdoc @@ -4,6 +4,7 @@ - http://groups.google.com/group/webrat - http://webrat.lighthouseapp.com/ - http://github.com/brynary/webrat +- #webrat on Freenode == Description @@ -15,7 +16,7 @@ web application. * Browser Simulator for expressive, high level acceptance testing without the performance hit and browser dependency of Selenium or Watir (See Webrat::Session) * Use the same API for Browser Simulator and real Selenium tests using - Webrat::SeleniumSession when necessary (eg. for testing AJAX interactions) + Webrat::Selenium when necessary (eg. for testing AJAX interactions) * Supports multiple Ruby web frameworks: Rails, Merb and Sinatra * Supports popular test frameworks: RSpec, Cucumber, Test::Unit and Shoulda * Webrat::Matchers API for verifying rendered HTML using CSS, XPath, etc. @@ -40,6 +41,14 @@ Behind the scenes, Webrat will ensure: * If a URL is invalid, the test will fail. * If a page load or form submission is unsuccessful, the test will fail. +== Installing Nokogiri + +Users of Debian Linux (e.g. Ubuntu) need to run: + + sudo apt-get install libxslt1-dev libxml2-dev. + +Otherwise the Nokogiri gem, which Webrat depends on, won't install properly. + == Install for Rails To install the latest release as a gem: diff --git a/Rakefile b/Rakefile index 08ff95e..93581c2 100644 --- a/Rakefile +++ b/Rakefile @@ -21,14 +21,14 @@ spec = Gem::Specification.new do |s| s.bindir = "bin" s.description = s.summary s.require_path = "lib" - s.files = %w(History.txt init.rb install.rb MIT-LICENSE.txt README.rdoc Rakefile) + Dir["lib/**/*"] + s.files = %w(History.txt install.rb MIT-LICENSE.txt README.rdoc Rakefile) + Dir["lib/**/*"] + Dir["vendor/**/*"] # rdoc s.has_rdoc = true s.extra_rdoc_files = %w(README.rdoc MIT-LICENSE.txt) # Dependencies - s.add_dependency "nokogiri", ">= 1.0.3" + s.add_dependency "nokogiri", ">= 1.0.6" s.rubyforge_project = "webrat" end @@ -75,8 +75,13 @@ task :install_gem => [:clean, :package] do sh "sudo gem install --local #{gem}" end +desc "Delete generated RDoc" +task :clobber_docs do + FileUtils.rm_rf("doc") +end + desc "Generate RDoc" -task :docs do +task :docs => :clobber_docs do system "hanna --title 'Webrat #{Webrat::VERSION} API Documentation'" end @@ -85,4 +90,15 @@ task "spec:jruby" do system "jruby -S rake spec" end -task :default => :spec \ No newline at end of file +desc "Run each spec in isolation to test for dependency issues" +task :spec_deps do + Dir["spec/**/*_spec.rb"].each do |test| + if !system("spec #{test} &> /dev/null") + puts "Dependency Issues: #{test}" + end + end +end + +task :default => :spec + +task :precommit => ["spec", "spec:jruby"] \ No newline at end of file diff --git a/init.rb b/init.rb deleted file mode 100644 index 9e01cd9..0000000 --- a/init.rb +++ /dev/null @@ -1,3 +0,0 @@ -if (RAILS_ENV =~ /^test/) || RAILS_ENV == "selenium" - require File.join(File.dirname(__FILE__), "lib", "webrat") -end diff --git a/lib/webrat.rb b/lib/webrat.rb index 5cb4769..c80d952 100644 --- a/lib/webrat.rb +++ b/lib/webrat.rb @@ -10,6 +10,8 @@ module Webrat VERSION = '0.3.2.1' def self.require_xml + gem "nokogiri", ">= 1.0.6" + if on_java? # We need Nokogiri's CSS to XPath support, even if using REXML and Hpricot for parsing and searching require "nokogiri/css" @@ -17,7 +19,7 @@ module Webrat require "rexml/document" else require "nokogiri" - require "webrat/core/nokogiri" + require "webrat/core/xml/nokogiri" end end diff --git a/lib/webrat/core.rb b/lib/webrat/core.rb index 68650ef..26d9962 100644 --- a/lib/webrat/core.rb +++ b/lib/webrat/core.rb @@ -1,13 +1,13 @@ require "webrat/core/configuration" require "webrat/core/xml" -require "webrat/core/nokogiri" +require "webrat/core/xml/nokogiri" require "webrat/core/logging" -require "webrat/core/form" +require "webrat/core/elements/form" require "webrat/core/scope" -require "webrat/core/link" -require "webrat/core/area" -require "webrat/core/label" -require "webrat/core/select_option" +require "webrat/core/elements/link" +require "webrat/core/elements/area" +require "webrat/core/elements/label" +require "webrat/core/elements/select_option" require "webrat/core/session" require "webrat/core/methods" require "webrat/core/matchers" diff --git a/lib/webrat/core/configuration.rb b/lib/webrat/core/configuration.rb index f94f28e..d6ae615 100755 --- a/lib/webrat/core/configuration.rb +++ b/lib/webrat/core/configuration.rb @@ -7,10 +7,15 @@ module Webrat @@configuration = configuration end - def self.configuration + def self.configuration # :nodoc: @@configuration ||= Webrat::Configuration.new end - + + # Webrat can be configured using the Webrat.configure method. For example: + # + # Webrat.configure do |config| + # config.parse_with_nokogiri = false + # end class Configuration RAILS_MODE = :rails @@ -24,7 +29,7 @@ module Webrat attr_writer :parse_with_nokogiri # Webrat's mode, set automatically when requiring webrat/rails, webrat/merb, etc. - attr_accessor :mode + attr_accessor :mode # :nodoc: # Save and open pages with error status codes (500-599) in a browser? Defualts to true. attr_writer :open_error_files diff --git a/lib/webrat/core/area.rb b/lib/webrat/core/elements/area.rb similarity index 51% rename from lib/webrat/core/area.rb rename to lib/webrat/core/elements/area.rb index b24283e..5c08bb2 100644 --- a/lib/webrat/core/area.rb +++ b/lib/webrat/core/elements/area.rb @@ -1,34 +1,21 @@ +require "webrat/core/elements/element" + module Webrat - class Area #:nodoc: + class Area < Element #:nodoc: - def initialize(session, element) - @session = session - @element = element + def self.xpath_search + ".//area" 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 + protected def href - @element["href"] + Webrat::XML.attribute(@element, "href") end - - def title - @element["title"] - end - - def id - @element["id"] - end - def absolute_href if href =~ /^\?/ diff --git a/lib/webrat/core/elements/element.rb b/lib/webrat/core/elements/element.rb new file mode 100644 index 0000000..6261d9f --- /dev/null +++ b/lib/webrat/core/elements/element.rb @@ -0,0 +1,29 @@ +module Webrat + + class Element # :nodoc: + + def self.load_all(session, dom) + Webrat::XML.xpath_search(dom, xpath_search).map do |element| + load(session, element) + end + end + + def self.load(session, element) + return nil if element.nil? + session.elements[Webrat::XML.xpath_to(element)] ||= self.new(session, element) + end + + attr_reader :element + + def initialize(session, element) + @session = session + @element = element + end + + def path + Webrat::XML.xpath_to(@element) + end + + end + +end \ No newline at end of file diff --git a/lib/webrat/core/field.rb b/lib/webrat/core/elements/field.rb similarity index 54% rename from lib/webrat/core/field.rb rename to lib/webrat/core/elements/field.rb index 5ab0ddf..fd359c0 100644 --- a/lib/webrat/core/field.rb +++ b/lib/webrat/core/elements/field.rb @@ -2,35 +2,58 @@ require "cgi" require "webrat/core_extensions/blank" require "webrat/core_extensions/nil_to_param" +require "webrat/core/elements/element" + module Webrat # Raised when Webrat is asked to manipulate a disabled form field class DisabledFieldError < WebratError end - class Field #:nodoc: - - def self.class_for_element(element) - if element.name == "input" - if %w[submit image].include?(element["type"]) - field_class = "button" - else - field_class = element["type"] || "text" #default type; 'type' attribute is not mandatory - end - else - field_class = element.name - end - Webrat.const_get("#{field_class.capitalize}Field") - rescue NameError - raise "Invalid field element: #{element.inspect}" - end - + class Field < Element #:nodoc: attr_reader :value - def initialize(form, element) - @form = form - @element = element - - @value = default_value + def self.xpath_search + [".//button", ".//input", ".//textarea", ".//select"] + end + + def self.field_classes + @field_classes || [] + end + + def self.inherited(klass) + @field_classes ||= [] + @field_classes << klass + # raise args.inspect + end + + def self.load(session, element) + return nil if element.nil? + session.elements[Webrat::XML.xpath_to(element)] ||= field_class(element).new(session, element) + end + + def self.field_class(element) + case element.name + when "button" then ButtonField + when "select" then SelectField + when "textarea" then TextareaField + else + case Webrat::XML.attribute(element, "type") + when "checkbox" then CheckboxField + when "hidden" then HiddenField + when "radio" then RadioField + when "password" then PasswordField + when "file" then FileField + when "reset" then ResetField + when "submit" then ButtonField + when "image" then ButtonField + else TextField + end + end + end + + def initialize(*args) + super + @value = default_value end def label_text @@ -39,36 +62,11 @@ module Webrat end def id - @element["id"] - end - - def path - @element.path - end - - def matches_id?(id) - if id.is_a?(Regexp) - @element["id"] =~ id - else - @element["id"] == id.to_s - end - end - - def matches_name?(name) - @element["name"] == name.to_s - end - - def matches_label?(label_text) - return false if labels.empty? - labels.any? { |label| label.matches_text?(label_text) } - end - - def matches_alt?(alt) - @element["alt"] =~ /^\W*#{Regexp.escape(alt.to_s)}/i + Webrat::XML.attribute(@element, "id") end def disabled? - @element.attributes.has_key?("disabled") && @element["disabled"] != 'false' + @element.attributes.has_key?("disabled") && Webrat::XML.attribute(@element, "disabled") != 'false' end def raise_error_if_disabled @@ -99,8 +97,21 @@ module Webrat protected + def form + Form.load(@session, form_element) + end + + def form_element + parent = @element.parent + + while parent.respond_to?(:parent) + return parent if parent.name == 'form' + parent = parent.parent + end + end + def name - @element["name"] + Webrat::XML.attribute(@element, "name") end def escaped_value @@ -108,13 +119,15 @@ module Webrat end def labels - @labels ||= label_elements.map { |element| Label.new(self, element) } + @labels ||= label_elements.map do |element| + Label.load(@session, element) + end end def label_elements return @label_elements unless @label_elements.nil? @label_elements = [] - + parent = @element.parent while parent.respond_to?(:parent) if parent.name == 'label' @@ -123,16 +136,16 @@ module Webrat end parent = parent.parent end - + unless id.blank? - @label_elements += Webrat::XML.css_search(@form.element, "label[@for='#{id}']") + @label_elements += Webrat::XML.xpath_search(form.element, ".//label[@for = '#{id}']") end - + @label_elements end def default_value - @element["value"] + Webrat::XML.attribute(@element, "value") end def replace_param_value(params, oval, nval) @@ -154,14 +167,10 @@ module Webrat class ButtonField < Field #:nodoc: - def matches_text?(text) - @element.inner_html =~ /#{Regexp.escape(text.to_s)}/i + def self.xpath_search + [".//button", ".//input[@type = 'submit']", ".//input[@type = 'image']"] end - def matches_value?(value) - @element["value"] =~ /^\W*#{Regexp.escape(value.to_s)}/i || matches_text?(value) || matches_alt?(value) - end - def to_param return nil if @value.nil? super @@ -173,19 +182,23 @@ module Webrat def click raise_error_if_disabled - set(@element["value"]) unless @element["name"].blank? - @form.submit + set(Webrat::XML.attribute(@element, "value")) unless Webrat::XML.attribute(@element, "name").blank? + form.submit end end class HiddenField < Field #:nodoc: + def self.xpath_search + ".//input[@type = 'hidden']" + end + def to_param if collection_name? super else - checkbox_with_same_name = @form.field(name, CheckboxField) + checkbox_with_same_name = form.field_named(name, CheckboxField) if checkbox_with_same_name.to_param.blank? super @@ -205,6 +218,10 @@ module Webrat class CheckboxField < Field #:nodoc: + def self.xpath_search + ".//input[@type = 'checkbox']" + end + def to_param return nil if @value.nil? super @@ -212,11 +229,11 @@ module Webrat def check raise_error_if_disabled - set(@element["value"] || "on") + set(Webrat::XML.attribute(@element, "value") || "on") end def checked? - @element["checked"] == "checked" + Webrat::XML.attribute(@element, "checked") == "checked" end def uncheck @@ -227,8 +244,8 @@ module Webrat protected def default_value - if @element["checked"] == "checked" - @element["value"] || "on" + if Webrat::XML.attribute(@element, "checked") == "checked" + Webrat::XML.attribute(@element, "value") || "on" else nil end @@ -237,10 +254,19 @@ module Webrat end class PasswordField < Field #:nodoc: + + def self.xpath_search + ".//input[@type = 'password']" + end + end class RadioField < Field #:nodoc: + def self.xpath_search + ".//input[@type = 'radio']" + end + def to_param return nil if @value.nil? super @@ -252,22 +278,22 @@ module Webrat option.set(nil) end - set(@element["value"] || "on") + set(Webrat::XML.attribute(@element, "value") || "on") end def checked? - @element["checked"] == "checked" + Webrat::XML.attribute(@element, "checked") == "checked" end protected def other_options - @form.fields.select { |f| f.name == name } + form.fields.select { |f| f.name == name } end def default_value - if @element["checked"] == "checked" - @element["value"] || "on" + if Webrat::XML.attribute(@element, "checked") == "checked" + Webrat::XML.attribute(@element, "value") || "on" else nil end @@ -277,16 +303,24 @@ module Webrat class TextareaField < Field #:nodoc: + def self.xpath_search + ".//textarea" + end + protected def default_value - @element.inner_html + Webrat::XML.inner_html(@element) end end class FileField < Field #:nodoc: - + + def self.xpath_search + ".//input[@type = 'file']" + end + attr_accessor :content_type def set(value, content_type = nil) @@ -315,35 +349,37 @@ module Webrat end class TextField < Field #:nodoc: + def self.xpath_search + [".//input[@type = 'text']", ".//input[not(@type)]"] + end end class ResetField < Field #:nodoc: + def self.xpath_search + ".//input[@type = 'reset']" + end end class SelectField < Field #:nodoc: - def find_option(text) - options.detect { |o| o.matches_text?(text) } - end - - protected - - def default_value - selected_options = Webrat::XML.css_search(@element, "option[@selected='selected']") - selected_options = Webrat::XML.css_search(@element, "option:first") if selected_options.empty? - - selected_options.map do |option| - return "" if option.nil? - option["value"] || option.inner_html - end + def self.xpath_search + ".//select" end def options - option_elements.map { |oe| SelectOption.new(self, oe) } + @options ||= SelectOption.load_all(@session, @element) end + + protected - def option_elements - Webrat::XML.css_search(@element, "option") + def default_value + selected_options = Webrat::XML.xpath_search(@element, ".//option[@selected = 'selected']") + selected_options = Webrat::XML.xpath_search(@element, ".//option[position() = 1]") if selected_options.empty? + + selected_options.map do |option| + return "" if option.nil? + Webrat::XML.attribute(option, "value") || Webrat::XML.inner_html(option) + end end end diff --git a/lib/webrat/core/elements/form.rb b/lib/webrat/core/elements/form.rb new file mode 100644 index 0000000..81859da --- /dev/null +++ b/lib/webrat/core/elements/form.rb @@ -0,0 +1,103 @@ +require "webrat/core/elements/field" +require "webrat/core_extensions/blank" + +require "webrat/core/elements/element" +require "webrat/core/locators/field_named_locator" + +module Webrat + class Form < Element #:nodoc: + attr_reader :element + + def self.xpath_search + ".//form" + end + + def fields + @fields ||= Field.load_all(@session, @element) + end + + def submit + @session.request_page(form_action, form_method, params) + end + + def field_named(name, *field_types) + Webrat::Locators::FieldNamedLocator.new(@session, dom, name, *field_types).locate + end + + protected + + def dom + Webrat::XML.xpath_at(@session.dom, path) + end + + def fields_by_type(field_types) + if field_types.any? + fields.select { |f| field_types.include?(f.class) } + else + fields + end + end + + def params + all_params = {} + + fields.each do |field| + next if field.to_param.nil? + merge(all_params, field.to_param) + end + + all_params + end + + def form_method + Webrat::XML.attribute(@element, "method").blank? ? :get : Webrat::XML.attribute(@element, "method").downcase + end + + def form_action + Webrat::XML.attribute(@element, "action").blank? ? @session.current_url : Webrat::XML.attribute(@element, "action") + end + + def merge(all_params, new_param) + new_param.each do |key, value| + case all_params[key] + when *hash_classes + merge_hash_values(all_params[key], value) + when Array + all_params[key] += value + else + all_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{|value| value.class} + when *hash_classes.zip(hash_classes) + 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 + + def hash_classes + klasses = [Hash] + + case Webrat.configuration.mode + when :rails + klasses << HashWithIndifferentAccess + when :merb + klasses << Mash + end + + klasses + end + + end +end diff --git a/lib/webrat/core/elements/label.rb b/lib/webrat/core/elements/label.rb new file mode 100644 index 0000000..9e7f29b --- /dev/null +++ b/lib/webrat/core/elements/label.rb @@ -0,0 +1,31 @@ +require "webrat/core/elements/element" + +module Webrat + class Label < Element #:nodoc: + + attr_reader :element + + def self.xpath_search + ".//label" + end + + def for_id + Webrat::XML.attribute(@element, "for") + end + + def field + Field.load(@session, field_element) + end + + protected + + def field_element + if for_id.blank? + Webrat::XML.xpath_at(@element, *Field.xpath_search) + else + Webrat::XML.css_search(@session.dom, "#" + for_id).first + end + end + + end +end diff --git a/lib/webrat/core/link.rb b/lib/webrat/core/elements/link.rb similarity index 65% rename from lib/webrat/core/link.rb rename to lib/webrat/core/elements/link.rb index 0382213..3204db5 100644 --- a/lib/webrat/core/link.rb +++ b/lib/webrat/core/elements/link.rb @@ -1,11 +1,12 @@ require "webrat/core_extensions/blank" +require "webrat/core/elements/element" + module Webrat - class Link #:nodoc: + class Link < Element #:nodoc: - def initialize(session, element) - @session = session - @element = element + def self.xpath_search + ".//a[@href]" end def click(options = {}) @@ -21,35 +22,10 @@ module Webrat end end - def matches_text?(link_text) - if link_text.is_a?(Regexp) - matcher = link_text - else - matcher = /#{Regexp.escape(link_text.to_s)}/i - end - - replace_nbsp(text) =~ matcher || replace_nbsp_ref(inner_html) =~ matcher || title =~ matcher - end - - def matches_id?(id_or_regexp) - if id_or_regexp.is_a?(Regexp) - (id =~ id_or_regexp) ? true : false - else - (id == id_or_regexp) ? true : false - end - end - - def inner_html - @element.inner_html - end - - def text - @element.inner_text - end - protected + def id - @element['id'] + Webrat::XML.attribute(@element, "id") end def data @@ -57,11 +33,11 @@ module Webrat end def title - @element['title'] + Webrat::XML.attribute(@element, "title") end def href - @element["href"] + Webrat::XML.attribute(@element, "href") end def absolute_href @@ -81,7 +57,7 @@ module Webrat end def onclick - @element["onclick"] + Webrat::XML.attribute(@element, "onclick") end def http_method @@ -109,14 +85,6 @@ module Webrat raise Webrat::WebratError.new("No HTTP method for _method param in #{onclick.inspect}") end end - - private - def replace_nbsp(str) - str.gsub([0xA0].pack('U'), ' ') - end - - def replace_nbsp_ref(str) - str.gsub(' ',' ').gsub(' ', ' ') - end + end end diff --git a/lib/webrat/core/elements/select_option.rb b/lib/webrat/core/elements/select_option.rb new file mode 100644 index 0000000..ce5a16d --- /dev/null +++ b/lib/webrat/core/elements/select_option.rb @@ -0,0 +1,35 @@ +require "webrat/core/elements/element" + +module Webrat + class SelectOption < Element #:nodoc: + + def self.xpath_search + ".//option" + end + + def choose + select.raise_error_if_disabled + select.set(value) + end + + protected + + def select + SelectField.load(@session, select_element) + end + + def select_element + parent = @element.parent + + while parent.respond_to?(:parent) + return parent if parent.name == 'select' + parent = parent.parent + end + end + + def value + Webrat::XML.attribute(@element, "value") || Webrat::XML.inner_html(@element) + end + + end +end \ No newline at end of file diff --git a/lib/webrat/core/form.rb b/lib/webrat/core/form.rb deleted file mode 100644 index 682e1d1..0000000 --- a/lib/webrat/core/form.rb +++ /dev/null @@ -1,157 +0,0 @@ -require "webrat/core/field" -require "webrat/core_extensions/blank" - -module Webrat - class Form #:nodoc: - attr_reader :element - - def initialize(session, element) - @session = session - @element = element - @fields = nil - end - - def field(locator, *field_types) - field_with_id(locator, *field_types) || - field_named(locator, *field_types) || - field_labeled(locator, *field_types) || - nil - end - - def field_by_element(element, *field_types) - fields_by_type(field_types).detect { |possible_field| possible_field.path == element.path } - end - - def find_select_option(option_text) - select_fields = fields_by_type([SelectField]) - - select_fields.each do |select_field| - result = select_field.find_option(option_text) - return result if result - end - - nil - end - - def find_button(value = nil) - return fields_by_type([ButtonField]).first if value.nil? - possible_buttons = fields_by_type([ButtonField]) - possible_buttons.detect { |possible_button| possible_button.matches_id?(value) } || - possible_buttons.detect { |possible_button| possible_button.matches_value?(value) } - end - - def fields - return @fields if @fields - - @fields = Webrat::XML.css_search(@element, "button", "input", "textarea", "select").collect do |field_element| - Field.class_for_element(field_element).new(self, field_element) - end - end - - def labels - @labels ||= element.search("label").map { |element| Label.new(nil, element) } - end - - def submit - @session.request_page(form_action, form_method, params) - end - - def field_with_id(id, *field_types) - possible_fields = fields_by_type(field_types) - possible_fields.detect { |possible_field| possible_field.matches_id?(id) } - end - - def field_named(name, *field_types) - possible_fields = fields_by_type(field_types) - possible_fields.detect { |possible_field| possible_field.matches_name?(name) } - end - - def field_labeled(label, *field_types) - possible_fields = fields_by_type(field_types) - matching_fields = possible_fields.select do |possible_field| - possible_field.matches_label?(label) - end - matching_fields.min { |a, b| a.label_text.length <=> b.label_text.length } - end - - def label_matching(label_text) - labels.detect { |label| label.matches_text?(label_text) } - end - - def matches_id?(id) - @element["id"] == id.to_s - end - - protected - - def fields_by_type(field_types) - if field_types.any? - fields.select { |f| field_types.include?(f.class) } - else - fields - end - end - - def params - all_params = {} - - fields.each do |field| - next if field.to_param.nil? - merge(all_params, field.to_param) - end - - all_params - end - - def form_method - @element["method"].blank? ? :get : @element["method"].downcase - end - - def form_action - @element["action"].blank? ? @session.current_url : @element["action"] - end - - def merge(all_params, new_param) - new_param.each do |key, value| - case all_params[key] - when *hash_classes - merge_hash_values(all_params[key], value) - when Array - all_params[key] += value - else - all_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{|value| value.class} - when *hash_classes.zip(hash_classes) - 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 - - def hash_classes - klasses = [Hash] - - case Webrat.configuration.mode - when Webrat::Configuration::RAILS_MODE - klasses << HashWithIndifferentAccess - when Webrat::Configuration::MERB_MODE - klasses << Mash - end - - klasses - end - - end -end diff --git a/lib/webrat/core/hpricot.rb b/lib/webrat/core/hpricot.rb deleted file mode 100644 index e69de29..0000000 diff --git a/lib/webrat/core/label.rb b/lib/webrat/core/label.rb deleted file mode 100644 index 5557108..0000000 --- a/lib/webrat/core/label.rb +++ /dev/null @@ -1,26 +0,0 @@ -module Webrat - class Label #:nodoc: - - def initialize(field, element) - @field = field - @element = element - end - - def matches_text?(label_text) - text =~ /^\W*#{Regexp.escape(label_text.to_s)}\b/i - end - - def text - str = @element.inner_text - str.gsub!("\n","") - str.strip! - str.squeeze!(" ") - str - end - - def for_id - @element['for'] - end - - end -end diff --git a/lib/webrat/core/locators.rb b/lib/webrat/core/locators.rb index 73602a0..c3f4cfe 100644 --- a/lib/webrat/core/locators.rb +++ b/lib/webrat/core/locators.rb @@ -1,98 +1,19 @@ -require "webrat/core_extensions/detect_mapped" +require "webrat/core/locators/area_locator" +require "webrat/core/locators/button_locator" +require "webrat/core/locators/field_labeled_locator" +require "webrat/core/locators/label_locator" +require "webrat/core/locators/field_named_locator" +require "webrat/core/locators/field_by_id_locator" +require "webrat/core/locators/select_option_locator" +require "webrat/core/locators/link_locator" +require "webrat/core/locators/field_locator" +require "webrat/core/locators/form_locator" module Webrat module Locators - - def field_by_xpath(xpath, *field_types) - element = dom.at(xpath) - - return nil unless element - - forms.detect_mapped do |form| - form.field_by_element(element, *field_types) - end - end - def field_labeled(label, *field_types) - find_field_labeled(label, *field_types) || - raise(NotFoundError.new("Could not find field labeled #{label.inspect}")) - end - - def field_named(name, *field_types) - field_by_xpath("//*[@name='#{id}']", *field_types) || - raise(NotFoundError.new("Could not find field named #{name.inspect}")) - end - - def field_with_id(id, *field_types) - field_by_xpath("//*[@id='#{id}']", *field_types) || - raise(NotFoundError.new("Could not find field with id #{id.inspect}")) - end - - def field(id, *field_types) # :nodoc: - # This is the default locator strategy - field_by_xpath("//*[@id='#{id}']", *field_types) || - field_by_xpath("//*[@name='#{id}']", *field_types) || - field_labeled(id, *field_types) || - raise(NotFoundError.new("Could not find field: #{args.inspect}")) - end - - def find_field_labeled(label, *field_types) #:nodoc: - forms.detect_mapped do |form| - form.field_labeled(label, *field_types) - end - end - - def find_select_option(option_text, id_or_name_or_label) #:nodoc: - if id_or_name_or_label - field = field(id_or_name_or_label, SelectField) - return field.find_option(option_text) - else - select_option = forms.detect_mapped do |form| - form.find_select_option(option_text) - end - - return select_option if select_option - end - - raise NotFoundError.new("Could not find option #{option_text.inspect}") - end - - def find_button(value) #:nodoc: - button = forms.detect_mapped do |form| - form.find_button(value) - end - - if button - return button - else - raise NotFoundError.new("Could not find button #{value.inspect}") - end - end - - def find_area(area_name) #:nodoc: - areas.detect { |area| area.matches_text?(area_name) } || - raise(NotFoundError.new("Could not find area with name #{area_name}")) - end - - def find_link(text_or_title_or_id) #:nodoc: - matching_links = links.select do |possible_link| - possible_link.matches_text?(text_or_title_or_id) || possible_link.matches_id?(text_or_title_or_id) - end - - if matching_links.any? - matching_links.min { |a, b| a.text.length <=> b.text.length } - else - raise NotFoundError.new("Could not find link with text or title or id #{text_or_title_or_id.inspect}") - end - end - - def find_field_id_for_label(label_text) #:nodoc: - label = forms.detect_mapped { |form| form.label_matching(label_text) } - if label - label.for_id - else - raise NotFoundError.new("Could not find the label with text #{label_text}") - end + def field_by_xpath(xpath) + Field.load(@session, Webrat::XML.xpath_at(dom, xpath)) end end diff --git a/lib/webrat/core/locators/area_locator.rb b/lib/webrat/core/locators/area_locator.rb new file mode 100644 index 0000000..dae3fa2 --- /dev/null +++ b/lib/webrat/core/locators/area_locator.rb @@ -0,0 +1,38 @@ +require "webrat/core/locators/locator" + +module Webrat + module Locators + + class AreaLocator < Locator # :nodoc: + + def locate + Area.load(@session, area_element) + end + + def area_element + area_elements.detect do |area_element| + Webrat::XML.attribute(area_element, "title") =~ matcher || + Webrat::XML.attribute(area_element, "id") =~ matcher + end + end + + def matcher + /#{Regexp.escape(@value.to_s)}/i + end + + def area_elements + Webrat::XML.xpath_search(@dom, Area.xpath_search) + end + + def error_message + "Could not find area with name #{@value}" + end + + end + + def find_area(id_or_title) #:nodoc: + AreaLocator.new(@session, dom, id_or_title).locate! + end + + end +end \ No newline at end of file diff --git a/lib/webrat/core/locators/button_locator.rb b/lib/webrat/core/locators/button_locator.rb new file mode 100644 index 0000000..9e6adee --- /dev/null +++ b/lib/webrat/core/locators/button_locator.rb @@ -0,0 +1,54 @@ +require "webrat/core/locators/locator" + +module Webrat + module Locators + + class ButtonLocator < Locator # :nodoc: + + def locate + ButtonField.load(@session, button_element) + end + + def button_element + button_elements.detect do |element| + @value.nil? || + matches_id?(element) || + matches_value?(element) || + matches_html?(element) || + matches_alt?(element) + end + end + + def matches_id?(element) + (@value.is_a?(Regexp) && Webrat::XML.attribute(element, "id") =~ @value) || + (!@value.is_a?(Regexp) && Webrat::XML.attribute(element, "id") == @value.to_s) + end + + def matches_value?(element) + Webrat::XML.attribute(element, "value") =~ /^\W*#{Regexp.escape(@value.to_s)}/i + end + + def matches_html?(element) + Webrat::XML.inner_html(element) =~ /#{Regexp.escape(@value.to_s)}/i + end + + def matches_alt?(element) + Webrat::XML.attribute(element, "alt") =~ /^\W*#{Regexp.escape(@value.to_s)}/i + end + + def button_elements + Webrat::XML.xpath_search(@dom, *ButtonField.xpath_search) + end + + def error_message + "Could not find button #{@value.inspect}" + end + + end + + def find_button(value) #:nodoc: + ButtonLocator.new(@session, dom, value).locate! + end + + end +end \ No newline at end of file diff --git a/lib/webrat/core/locators/field_by_id_locator.rb b/lib/webrat/core/locators/field_by_id_locator.rb new file mode 100644 index 0000000..a34084d --- /dev/null +++ b/lib/webrat/core/locators/field_by_id_locator.rb @@ -0,0 +1,37 @@ +require "webrat/core/locators/locator" + +module Webrat + module Locators + + class FieldByIdLocator < Locator # :nodoc: + + def locate + Field.load(@session, field_element) + end + + def field_element + field_elements.detect do |field_element| + if @value.is_a?(Regexp) + Webrat::XML.attribute(field_element, "id") =~ @value + else + Webrat::XML.attribute(field_element, "id") == @value.to_s + end + end + end + + def field_elements + Webrat::XML.xpath_search(@dom, *Field.xpath_search) + end + + def error_message + "Could not find field with id #{@value.inspect}" + end + + end + + def field_with_id(id, *field_types) + FieldByIdLocator.new(@session, dom, id, *field_types).locate! + end + + end +end \ No newline at end of file diff --git a/lib/webrat/core/locators/field_labeled_locator.rb b/lib/webrat/core/locators/field_labeled_locator.rb new file mode 100644 index 0000000..6c485d2 --- /dev/null +++ b/lib/webrat/core/locators/field_labeled_locator.rb @@ -0,0 +1,50 @@ +require "webrat/core_extensions/detect_mapped" +require "webrat/core/locators/locator" + +module Webrat + module Locators + + class FieldLabeledLocator < Locator # :nodoc: + + def locate + matching_labels.any? && matching_labels.first.field + end + + def matching_labels + matching_label_elements.sort_by do |label_element| + text(label_element).length + end.map do |label_element| + Label.load(@session, label_element) + end + end + + def matching_label_elements + label_elements.select do |label_element| + text(label_element) =~ /^\W*#{Regexp.escape(@value.to_s)}\b/i + end + end + + def label_elements + Webrat::XML.xpath_search(@dom, Label.xpath_search) + end + + def error_message + "Could not find field labeled #{@value.inspect}" + end + + def text(element) + str = Webrat::XML.all_inner_text(element) + str.gsub!("\n","") + str.strip! + str.squeeze!(" ") + str + end + + end + + def field_labeled(label, *field_types) + FieldLabeledLocator.new(@session, dom, label, *field_types).locate! + end + + end +end \ No newline at end of file diff --git a/lib/webrat/core/locators/field_locator.rb b/lib/webrat/core/locators/field_locator.rb new file mode 100644 index 0000000..83ea96b --- /dev/null +++ b/lib/webrat/core/locators/field_locator.rb @@ -0,0 +1,25 @@ +require "webrat/core/locators/locator" + +module Webrat + module Locators + + class FieldLocator < Locator # :nodoc: + + def locate + FieldByIdLocator.new(@session, @dom, @value).locate || + FieldNamedLocator.new(@session, @dom, @value, *@field_types).locate || + FieldLabeledLocator.new(@session, @dom, @value, *@field_types).locate + end + + def error_message + "Could not find field: #{@value.inspect}" + end + + end + + def field(*args) # :nodoc: + FieldLocator.new(@session, dom, *args).locate! + end + + end +end \ No newline at end of file diff --git a/lib/webrat/core/locators/field_named_locator.rb b/lib/webrat/core/locators/field_named_locator.rb new file mode 100644 index 0000000..2f2d592 --- /dev/null +++ b/lib/webrat/core/locators/field_named_locator.rb @@ -0,0 +1,41 @@ +require "webrat/core/locators/locator" + +module Webrat + module Locators + + class FieldNamedLocator < Locator # :nodoc: + + def locate + Field.load(@session, field_element) + end + + def field_element + field_elements.detect do |field_element| + Webrat::XML.attribute(field_element, "name") == @value.to_s + end + end + + def field_elements + Webrat::XML.xpath_search(@dom, *xpath_searches) + end + + def xpath_searches + if @field_types.any? + @field_types.map { |field_type| field_type.xpath_search }.flatten + else + Array(Field.xpath_search) + end + end + + def error_message + "Could not find field named #{@value.inspect}" + end + + end + + def field_named(name, *field_types) + FieldNamedLocator.new(@session, dom, name, *field_types).locate! + end + + end +end \ No newline at end of file diff --git a/lib/webrat/core/locators/form_locator.rb b/lib/webrat/core/locators/form_locator.rb new file mode 100644 index 0000000..571e631 --- /dev/null +++ b/lib/webrat/core/locators/form_locator.rb @@ -0,0 +1,19 @@ +require "webrat/core/locators/locator" + +module Webrat + module Locators + + class FormLocator < Locator # :nodoc: + + def locate + Form.load(@session, form_element) + end + + def form_element + Webrat::XML.css_at(@dom, "#" + @value) + end + + end + + end +end \ No newline at end of file diff --git a/lib/webrat/core/locators/label_locator.rb b/lib/webrat/core/locators/label_locator.rb new file mode 100644 index 0000000..633fa05 --- /dev/null +++ b/lib/webrat/core/locators/label_locator.rb @@ -0,0 +1,34 @@ +require "webrat/core_extensions/detect_mapped" +require "webrat/core/locators/locator" + +module Webrat + module Locators + + class LabelLocator < Locator # :nodoc: + + def locate + Label.load(@session, label_element) + end + + def label_element + label_elements.detect do |label_element| + text(label_element) =~ /^\W*#{Regexp.escape(@value.to_s)}\b/i + end + end + + def label_elements + Webrat::XML.xpath_search(@dom, Label.xpath_search) + end + + def text(label_element) + str = Webrat::XML.all_inner_text(label_element) + str.gsub!("\n","") + str.strip! + str.squeeze!(" ") + str + end + + end + + end +end \ No newline at end of file diff --git a/lib/webrat/core/locators/link_locator.rb b/lib/webrat/core/locators/link_locator.rb new file mode 100644 index 0000000..4523170 --- /dev/null +++ b/lib/webrat/core/locators/link_locator.rb @@ -0,0 +1,66 @@ +require "webrat/core/locators/locator" + +module Webrat + module Locators + + class LinkLocator < Locator # :nodoc: + + def locate + Link.load(@session, link_element) + end + + def link_element + matching_links.min { |a, b| Webrat::XML.all_inner_text(a).length <=> Webrat::XML.all_inner_text(b).length } + end + + def matching_links + @matching_links ||= link_elements.select do |link_element| + matches_text?(link_element) || + matches_id?(link_element) + end + end + + def matches_text?(link) + if @value.is_a?(Regexp) + matcher = @value + else + matcher = /#{Regexp.escape(@value.to_s)}/i + end + + replace_nbsp(Webrat::XML.all_inner_text(link)) =~ matcher || + replace_nbsp_ref(Webrat::XML.inner_html(link)) =~ matcher || + Webrat::XML.attribute(link, "title")=~ matcher + end + + def matches_id?(link) + if @value.is_a?(Regexp) + (Webrat::XML.attribute(link, "id") =~ @value) ? true : false + else + (Webrat::XML.attribute(link, "id") == @value) ? true : false + end + end + + def link_elements + Webrat::XML.xpath_search(@dom, *Link.xpath_search) + end + + def replace_nbsp(str) + str.gsub([0xA0].pack('U'), ' ') + end + + def replace_nbsp_ref(str) + str.gsub(' ',' ').gsub(' ', ' ') + end + + def error_message + "Could not find link with text or title or id #{@value.inspect}" + end + + end + + def find_link(text_or_title_or_id) #:nodoc: + LinkLocator.new(@session, dom, text_or_title_or_id).locate! + end + + end +end \ No newline at end of file diff --git a/lib/webrat/core/locators/locator.rb b/lib/webrat/core/locators/locator.rb new file mode 100644 index 0000000..66fe6ec --- /dev/null +++ b/lib/webrat/core/locators/locator.rb @@ -0,0 +1,20 @@ +module Webrat + module Locators + + class Locator # :nodoc: + + def initialize(session, dom, value, *field_types) + @session = session + @dom = dom + @value = value + @field_types = field_types + end + + def locate! + locate || raise(NotFoundError.new(error_message)) + end + + end + + end +end \ No newline at end of file diff --git a/lib/webrat/core/locators/select_option_locator.rb b/lib/webrat/core/locators/select_option_locator.rb new file mode 100644 index 0000000..a471690 --- /dev/null +++ b/lib/webrat/core/locators/select_option_locator.rb @@ -0,0 +1,59 @@ +require "webrat/core_extensions/detect_mapped" +require "webrat/core/locators/locator" + +module Webrat + module Locators + + class SelectOptionLocator < Locator # :nodoc: + + def initialize(session, dom, option_text, id_or_name_or_label) + @session = session + @dom = dom + @option_text = option_text + @id_or_name_or_label = id_or_name_or_label + end + + def locate + if @id_or_name_or_label + field = FieldLocator.new(@session, @dom, @id_or_name_or_label, SelectField).locate! + + field.options.detect do |o| + if @option_text.is_a?(Regexp) + Webrat::XML.inner_html(o.element) =~ @option_text + else + Webrat::XML.inner_html(o.element) == @option_text.to_s + end + end + else + option_element = option_elements.detect do |o| + if @option_text.is_a?(Regexp) + Webrat::XML.inner_html(o) =~ @option_text + else + Webrat::XML.inner_html(o) == @option_text.to_s + end + end + + SelectOption.load(@session, option_element) + end + end + + def option_elements + Webrat::XML.xpath_search(@dom, *SelectOption.xpath_search) + end + + def error_message + if @id_or_name_or_label + "The '#{@option_text}' option was not found in the #{@id_or_name_or_label.inspect} select box" + else + "Could not find option #{@option_text.inspect}" + end + end + + end + + def select_option(option_text, id_or_name_or_label = nil) #:nodoc: + SelectOptionLocator.new(@session, dom, option_text, id_or_name_or_label).locate! + end + + end +end \ No newline at end of file diff --git a/lib/webrat/core/matchers/have_content.rb b/lib/webrat/core/matchers/have_content.rb index 8344ed2..9a2c6a0 100644 --- a/lib/webrat/core/matchers/have_content.rb +++ b/lib/webrat/core/matchers/have_content.rb @@ -7,8 +7,13 @@ module Webrat end def matches?(stringlike) - @document = Webrat::XML.document(stringlike) - @element = @document.inner_text + if Webrat.configuration.parse_with_nokogiri? + @document = Webrat.nokogiri_document(stringlike) + else + @document = Webrat.hpricot_document(stringlike) + end + + @element = Webrat::XML.inner_text(@document) case @content when String diff --git a/lib/webrat/core/matchers/have_xpath.rb b/lib/webrat/core/matchers/have_xpath.rb index 8d1825a..8c61b0d 100644 --- a/lib/webrat/core/matchers/have_xpath.rb +++ b/lib/webrat/core/matchers/have_xpath.rb @@ -1,5 +1,5 @@ -require "webrat/core/nokogiri" -require "webrat/core/rexml" +require "webrat/core/xml/nokogiri" +require "webrat/core/xml/rexml" module Webrat module Matchers diff --git a/lib/webrat/core/methods.rb b/lib/webrat/core/methods.rb index 0583665..0d993e8 100644 --- a/lib/webrat/core/methods.rb +++ b/lib/webrat/core/methods.rb @@ -7,14 +7,18 @@ module Webrat def #{meth}(*args, &blk) webrat_session.#{meth}(*args, &blk) end - - def webrat_session - @_webrat_session ||= ::Webrat.session_class.new(self) - end RUBY end end + def webrat + webrat_session + end + + def webrat_session + @_webrat_session ||= ::Webrat.session_class.new(self) + end + # all of these methods delegate to the @session, which should # be created transparently. # @@ -32,8 +36,6 @@ module Webrat :chooses, :choose, :selects, :select, :attaches_file, :attach_file, - :cookies, - :response, :current_page, :current_url, :clicks_link, :click_link, @@ -42,12 +44,16 @@ module Webrat :reload, :reloads, :clicks_link_within, :click_link_within, :field_labeled, + :select_option, :set_hidden_field, :submit_form, :request_page, :current_dom, :selects_date, :selects_time, :selects_datetime, :select_date, :select_time, :select_datetime, - :wait_for_page_to_load, - :field_by_xpath + :field_by_xpath, + :field_with_id, + :selenium, + :simulate, :automate + end diff --git a/lib/webrat/core/scope.rb b/lib/webrat/core/scope.rb index 1f47702..df92c5a 100644 --- a/lib/webrat/core/scope.rb +++ b/lib/webrat/core/scope.rb @@ -1,4 +1,4 @@ -require "webrat/core/form" +require "webrat/core/elements/form" require "webrat/core/locators" require "webrat/core_extensions/deprecate" @@ -25,6 +25,8 @@ module Webrat end end + attr_reader :session + def initialize(session, &block) #:nodoc: @session = session instance_eval(&block) if block_given? @@ -96,12 +98,7 @@ module Webrat # select "February", :from => "event_month" # select "February", :from => "Event Month" def select(option_text, options = {}) - if option = find_select_option(option_text, options[:from]) - option.choose - else - select_box_text = options[:from] ? " in the '#{options[:from]}' select box" : '' - raise NotFoundError.new("The '#{option_text}' option was not found#{select_box_text}") - end + select_option(option_text, options[:from]).choose end webrat_deprecate :selects, :select @@ -134,7 +131,7 @@ module Webrat date_to_select : Date.parse(date_to_select) id_prefix = locate_id_prefix(options) do - year_field = field_by_xpath("//*[contains(@id, '_#{DATE_TIME_SUFFIXES[:year]}')]") + year_field = FieldByIdLocator.new(@session, dom, /(.*?)_#{DATE_TIME_SUFFIXES[:year]}$/).locate raise NotFoundError.new("No date fields were found") unless year_field && year_field.id =~ /(.*?)_1i/ $1 end @@ -168,7 +165,7 @@ module Webrat time = time_to_select.is_a?(Time) ? time_to_select : Time.parse(time_to_select) id_prefix = locate_id_prefix(options) do - hour_field = field_by_xpath("//*[contains(@id, '_#{DATE_TIME_SUFFIXES[:hour]}')]") + hour_field = FieldByIdLocator.new(@session, dom, /(.*?)_#{DATE_TIME_SUFFIXES[:hour]}$/).locate raise NotFoundError.new("No time fields were found") unless hour_field && hour_field.id =~ /(.*?)_4i/ $1 end @@ -190,7 +187,7 @@ module Webrat def select_datetime(time_to_select, options ={}) time = time_to_select.is_a?(Time) ? time_to_select : Time.parse(time_to_select) - options[:id_prefix] ||= (options[:from] ? field_by_xpath("//*[@id='#{options[:from]}']") : nil) + options[:id_prefix] ||= (options[:from] ? FieldByIdLocator.new(@session, dom, options[:from]).locate : nil) select_date time, options select_time time, options @@ -263,8 +260,7 @@ module Webrat webrat_deprecate :clicks_button, :click_button def submit_form(id) - form = forms.detect { |f| f.matches_id?(id) } - form.submit + FormLocator.new(@session, dom, id).locate.submit end def dom # :nodoc: @@ -280,16 +276,28 @@ module Webrat end protected - + def page_dom #:nodoc: return @response.dom if @response.respond_to?(:dom) - dom = Webrat::XML.document(@response_body) + + if @session.xml_content_type? + dom = Webrat::XML.xml_document(@response_body) + else + dom = Webrat::XML.html_document(@response_body) + end + Webrat.define_dom_method(@response, dom) return dom end def scoped_dom #:nodoc: - Webrat::XML.document(@scope.dom.search(@selector).first.to_html) + source = Webrat::XML.to_html(Webrat::XML.css_search(@scope.dom, @selector).first) + + if @session.xml_content_type? + Webrat::XML.xml_document(source) + else + Webrat::XML.html_document(source) + end end def locate_field(field_locator, *field_types) #:nodoc: @@ -302,27 +310,20 @@ module Webrat def locate_id_prefix(options, &location_strategy) #:nodoc: return options[:id_prefix] if options[:id_prefix] - id_prefix = options[:from] ? find_field_id_for_label(options[:from]) : yield - end - - def areas #:nodoc: - Webrat::XML.css_search(dom, "area").map do |element| - Area.new(@session, element) - end - end - - def links #:nodoc: - Webrat::XML.css_search(dom, "a[@href]").map do |link_element| - Link.new(@session, link_element) + + if options[:from] + if (label = LabelLocator.new(@session, dom, options[:from]).locate) + label.for_id + else + raise NotFoundError.new("Could not find the label with text #{options[:from]}") + end + else + yield end end def forms #:nodoc: - return @forms if @forms - - @forms = Webrat::XML.css_search(dom, "form").map do |form_element| - Form.new(@session, form_element) - end + @forms ||= Form.load_all(@session, dom) end end diff --git a/lib/webrat/core/select_option.rb b/lib/webrat/core/select_option.rb deleted file mode 100644 index 461c8ae..0000000 --- a/lib/webrat/core/select_option.rb +++ /dev/null @@ -1,29 +0,0 @@ -module Webrat - class SelectOption #:nodoc: - - def initialize(select, element) - @select = select - @element = element - end - - def matches_text?(text) - if text.is_a?(Regexp) - @element.inner_html =~ text - else - @element.inner_html == text.to_s - end - end - - def choose - @select.raise_error_if_disabled - @select.set(value) - end - - protected - - def value - @element["value"] || @element.inner_html - end - - end -end \ No newline at end of file diff --git a/lib/webrat/core/session.rb b/lib/webrat/core/session.rb index be6a55c..ae038a8 100644 --- a/lib/webrat/core/session.rb +++ b/lib/webrat/core/session.rb @@ -32,6 +32,7 @@ module Webrat include Logging attr_reader :current_url + attr_reader :elements def initialize(context = nil) #:nodoc: @http_method = :get @@ -39,6 +40,8 @@ module Webrat @default_headers = {} @custom_headers = {} @context = context + + reset end # Saves the page out to RAILS_ROOT/tmp/ and opens it in the default @@ -110,8 +113,8 @@ module Webrat save_and_open_page if exception_caught? && Webrat.configuration.open_error_files? raise PageLoadError.new("Page load was not successful (Code: #{response_code.inspect}):\n#{formatted_error}") unless success_code? - @_scopes = nil - @_page_scope = nil + reset + @current_url = url @http_method = http_method @data = data @@ -179,7 +182,7 @@ module Webrat end end - def rewrite_css_and_image_references(response_html) #:nodoc + def rewrite_css_and_image_references(response_html) # :nodoc: return response_html unless doc_root response_html.gsub(/"\/(stylesheets|images)/, doc_root + '/\1') end @@ -197,6 +200,24 @@ module Webrat @_page_scope ||= Scope.from_page(self, response, response_body) end + def dom + page_scope.dom + end + + def xml_content_type? + false + end + + def simulate + return if Webrat.configuration.mode == :selenium + yield + end + + def automate + return unless Webrat.configuration.mode == :selenium + yield + end + def_delegators :current_scope, :fill_in, :fills_in def_delegators :current_scope, :set_hidden_field def_delegators :current_scope, :submit_form @@ -215,8 +236,17 @@ module Webrat def_delegators :current_scope, :should_not_see def_delegators :current_scope, :field_labeled def_delegators :current_scope, :field_by_xpath + def_delegators :current_scope, :field_with_id + def_delegators :current_scope, :select_option + + private + + def reset + @elements = {} + @_scopes = nil + @_page_scope = nil + end - private # accessor for testing def ruby_platform RUBY_PLATFORM diff --git a/lib/webrat/core/xml.rb b/lib/webrat/core/xml.rb index 626dc6c..86dd2c8 100644 --- a/lib/webrat/core/xml.rb +++ b/lib/webrat/core/xml.rb @@ -1,3 +1,7 @@ +require "webrat/core/xml/nokogiri" +require "webrat/core/xml/hpricot" +require "webrat/core/xml/rexml" + module Webrat #:nodoc: module XML #:nodoc: @@ -5,31 +9,107 @@ module Webrat #:nodoc: if Webrat.configuration.parse_with_nokogiri? Webrat.nokogiri_document(stringlike) else - return stringlike.dom if stringlike.respond_to?(:dom) + Webrat.rexml_document(Webrat.hpricot_document(stringlike).to_html) + end + end + + def self.html_document(stringlike) #:nodoc: + if Webrat.configuration.parse_with_nokogiri? + Webrat.html_nokogiri_document(stringlike) + else + Webrat.rexml_document(Webrat.hpricot_document(stringlike).to_html) + end + end + + def self.xml_document(stringlike) #:nodoc: + if Webrat.configuration.parse_with_nokogiri? + Webrat.xml_nokogiri_document(stringlike) + else + Webrat.rexml_document(Webrat.hpricot_document(stringlike).to_html) + end + end - if Hpricot::Doc === stringlike - stringlike - elsif Hpricot::Elements === stringlike - stringlike - elsif StringIO === stringlike - Hpricot(stringlike.string) - elsif stringlike.respond_to?(:body) - Hpricot(stringlike.body.to_s) + def self.to_html(element) + if Webrat.configuration.parse_with_nokogiri? + element.to_html + else + element.to_s + end + end + + def self.inner_html(element) + if Webrat.configuration.parse_with_nokogiri? + element.inner_html + else + element.text + end + end + + def self.all_inner_text(element) + if Webrat.configuration.parse_with_nokogiri? + element.inner_text + else + Hpricot(element.to_s).children.first.inner_text + end + end + + def self.inner_text(element) + if Webrat.configuration.parse_with_nokogiri? + element.inner_text + else + if defined?(Hpricot::Doc) && element.is_a?(Hpricot::Doc) + element.inner_text else - Hpricot(stringlike.to_s) + element.text end end end - def self.css_search(element, *searches) #:nodoc: + def self.xpath_to(element) if Webrat.configuration.parse_with_nokogiri? - element.css(*searches) + element.path else - searches.map do |search| - element.search(search) - end.flatten.compact + element.xpath end end + def self.attribute(element, attribute_name) + return element[attribute_name] if element.is_a?(Hash) + + if Webrat.configuration.parse_with_nokogiri? + element[attribute_name] + else + element.attributes[attribute_name] + end + end + + def self.xpath_at(*args) + xpath_search(*args).first + end + + def self.css_at(*args) + css_search(*args).first + end + + def self.xpath_search(element, *searches) + searches.flatten.map do |search| + if Webrat.configuration.parse_with_nokogiri? + element.xpath(search) + else + REXML::XPath.match(element, search) + end + end.flatten.compact + end + + def self.css_search(element, *searches) #:nodoc: + xpath_search(element, css_to_xpath(*searches)) + end + + def self.css_to_xpath(*selectors) + selectors.map do |rule| + Nokogiri::CSS.xpath_for(rule, :prefix => ".//") + end.flatten.uniq + end + end end \ No newline at end of file diff --git a/lib/webrat/core/xml/hpricot.rb b/lib/webrat/core/xml/hpricot.rb new file mode 100644 index 0000000..1face18 --- /dev/null +++ b/lib/webrat/core/xml/hpricot.rb @@ -0,0 +1,19 @@ +module Webrat + + def self.hpricot_document(stringlike) + return stringlike.dom if stringlike.respond_to?(:dom) + + if Hpricot::Doc === stringlike + stringlike + elsif Hpricot::Elements === stringlike + stringlike + elsif StringIO === stringlike + Hpricot(stringlike.string) + elsif stringlike.respond_to?(:body) + Hpricot(stringlike.body.to_s) + else + Hpricot(stringlike.to_s) + end + end + +end \ No newline at end of file diff --git a/lib/webrat/core/nokogiri.rb b/lib/webrat/core/xml/nokogiri.rb similarity index 50% rename from lib/webrat/core/nokogiri.rb rename to lib/webrat/core/xml/nokogiri.rb index b40ba56..e9dc6e0 100644 --- a/lib/webrat/core/nokogiri.rb +++ b/lib/webrat/core/xml/nokogiri.rb @@ -18,6 +18,38 @@ module Webrat end end + def self.html_nokogiri_document(stringlike) #:nodoc: + return stringlike.dom if stringlike.respond_to?(:dom) + + if Nokogiri::HTML::Document === stringlike + stringlike + elsif Nokogiri::XML::NodeSet === stringlike + stringlike + elsif StringIO === stringlike + Nokogiri::HTML(stringlike.string) + elsif stringlike.respond_to?(:body) + Nokogiri::HTML(stringlike.body.to_s) + else + Nokogiri::HTML(stringlike.to_s) + end + end + + def self.xml_nokogiri_document(stringlike) #:nodoc: + return stringlike.dom if stringlike.respond_to?(:dom) + + if Nokogiri::HTML::Document === stringlike + stringlike + elsif Nokogiri::XML::NodeSet === stringlike + stringlike + elsif StringIO === stringlike + Nokogiri::XML(stringlike.string) + elsif stringlike.respond_to?(:body) + Nokogiri::XML(stringlike.body.to_s) + else + Nokogiri::XML(stringlike.to_s) + end + end + def self.define_dom_method(object, dom) #:nodoc: object.meta_class.send(:define_method, :dom) do dom diff --git a/lib/webrat/core/rexml.rb b/lib/webrat/core/xml/rexml.rb similarity index 100% rename from lib/webrat/core/rexml.rb rename to lib/webrat/core/xml/rexml.rb diff --git a/lib/webrat/rails.rb b/lib/webrat/rails.rb index 2a89508..216a966 100644 --- a/lib/webrat/rails.rb +++ b/lib/webrat/rails.rb @@ -1,4 +1,5 @@ require "webrat" +require "action_controller/integration" module Webrat class RailsSession < Session #:nodoc: @@ -35,6 +36,10 @@ module Webrat response.code.to_i end + def xml_content_type? + response.headers["Content-Type"].to_s =~ /xml/ + end + protected def integration_session @@ -71,13 +76,15 @@ end module ActionController #:nodoc: module Integration #:nodoc: - class Session #:nodoc: + Session.class_eval do unless instance_methods.include?("put_via_redirect") require "webrat/rails/redirect_actions" include Webrat::RedirectActions end - - include Webrat::Methods end end -end \ No newline at end of file + IntegrationTest.class_eval do + include Webrat::Methods + include Webrat::Matchers + end +end diff --git a/lib/webrat/rspec-rails.rb b/lib/webrat/rspec-rails.rb index ad81883..df266f4 100644 --- a/lib/webrat/rspec-rails.rb +++ b/lib/webrat/rspec-rails.rb @@ -1,5 +1,5 @@ # Supports using the matchers in controller, helper, and view specs if you're -# using rspec-rails. Just add a require statement to spec/spec_helper.rb: +# using rspec-rails. Just add a require statement to spec/spec_helper.rb or env.rb: # # require 'webrat/rspec-rails' # diff --git a/lib/webrat/selenium.rb b/lib/webrat/selenium.rb index 7472f7f..21e333a 100644 --- a/lib/webrat/selenium.rb +++ b/lib/webrat/selenium.rb @@ -2,6 +2,7 @@ require "webrat" gem "selenium-client", ">=1.2.9" require "selenium/client" require "webrat/selenium/selenium_session" +require "webrat/selenium/matchers" module Webrat @@ -34,24 +35,68 @@ module Webrat system "mongrel_rails stop -c #{RAILS_ROOT} --pid #{pid_file}" end - module Selenium #:nodoc: + # To use Webrat's Selenium support, you'll need the selenium-client gem installed. + # Activate it with (for example, in your env.rb): + # + # require "webrat/selenium" + # + # Then, if you're using Cucumber, configure it to use a + # Webrat::Selenium::Rails::World as the scenario context by adding + # the following to env.rb: + # + # World do + # Webrat::Selenium::Rails::World.new + # end + # + # == Dropping down to the selenium-client API + # + # If you ever need to do something with Selenium not provided in the Webrat API, + # you can always drop down to the selenium-client API using the selenium method. + # For example: + # + # When "I drag the photo to the left" do + # selenium.dragdrop("id=photo_123", "+350, 0") + # end + # + # == Auto-starting of the mongrel and java server + # + # Webrat will automatically start the Selenium Java server process and an instance + # of Mongrel when a test is run. The Mongrel will run in the "selenium" environment + # instead of "test", so ensure you've got that defined, and will run on port 3001. + # + # == Waiting + # + # In order to make writing Selenium tests as easy as possible, Webrat will automatically + # wait for the correct elements to exist on the page when trying to manipulate them + # with methods like fill_in, etc. In general, this means you should be able to write + # your Webrat::Selenium tests ignoring the concurrency issues that can plague in-browser + # testing, so long as you're using the Webrat API. + module Selenium + module Rails #:nodoc: class World < ::ActionController::IntegrationTest + include Webrat::Selenium::Matchers def initialize #:nodoc: @_result = Test::Unit::TestResult.new end + def response + webrat_session.response + end + + def wait_for(*args, &block) + webrat_session.wait_for(*args, &block) + end + end end end end -module ::ActionController #:nodoc: - module Integration #:nodoc: - class Session #:nodoc: - include Webrat::Methods - end +module ActionController #:nodoc: + IntegrationTest.class_eval do + include Webrat::Methods end -end \ No newline at end of file +end diff --git a/lib/webrat/selenium/matchers.rb b/lib/webrat/selenium/matchers.rb new file mode 100644 index 0000000..a3d38f8 --- /dev/null +++ b/lib/webrat/selenium/matchers.rb @@ -0,0 +1,108 @@ +module Webrat + module Selenium + module Matchers + + class HaveXpath + def initialize(expected) + @expected = expected + end + + def matches?(response) + response.session.wait_for do + response.selenium.is_element_present("xpath=#{@expected}") + end + end + + # ==== Returns + # String:: The failure message. + def failure_message + "expected following text to match xpath #{@expected}:\n#{@document}" + end + + # ==== Returns + # String:: The failure message to be displayed in negative matches. + def negative_failure_message + "expected following text to not match xpath #{@expected}:\n#{@document}" + end + end + + def have_xpath(xpath) + HaveXpath.new(xpath) + end + + class HaveSelector + def initialize(expected) + @expected = expected + end + + def matches?(response) + response.session.wait_for do + response.selenium.is_element_present("css=#{@expected}") + end + end + + # ==== Returns + # String:: The failure message. + def failure_message + "expected following text to match selector #{@expected}:\n#{@document}" + end + + # ==== Returns + # String:: The failure message to be displayed in negative matches. + def negative_failure_message + "expected following text to not match selector #{@expected}:\n#{@document}" + end + end + + def have_selector(content) + HaveSelector.new(content) + end + + class HasContent #:nodoc: + def initialize(content) + @content = content + end + + def matches?(response) + if @content.is_a?(Regexp) + text_finder = "regexp:#{@content.source}" + else + text_finder = @content + end + + response.session.wait_for do + response.selenium.is_text_present(text_finder) + end + end + + # ==== Returns + # String:: The failure message. + def failure_message + "expected the following element's content to #{content_message}:\n#{@element}" + end + + # ==== Returns + # String:: The failure message to be displayed in negative matches. + def negative_failure_message + "expected the following element's content to not #{content_message}:\n#{@element}" + end + + def content_message + case @content + when String + "include \"#{@content}\"" + when Regexp + "match #{@content.inspect}" + end + end + end + + # Matches the contents of an HTML document with + # whatever string is supplied + def contain(content) + HasContent.new(content) + end + + end + end +end \ No newline at end of file diff --git a/lib/webrat/selenium/selenium_session.rb b/lib/webrat/selenium/selenium_session.rb index 2efe829..0b61e95 100644 --- a/lib/webrat/selenium/selenium_session.rb +++ b/lib/webrat/selenium/selenium_session.rb @@ -1,9 +1,31 @@ module Webrat + class TimeoutError < WebratError + end + + class SeleniumResponse + attr_reader :body + attr_reader :session + + def initialize(session, body) + @session = session + @body = body + end + + def selenium + session.selenium + end + end + class SeleniumSession def initialize(*args) # :nodoc: - extend_selenium - define_location_strategies + end + + def simulate + end + + def automate + yield end def visit(url) @@ -14,11 +36,16 @@ module Webrat def fill_in(field_identifier, options) locator = "webrat=#{Regexp.escape(field_identifier)}" + selenium.wait_for_element locator, 5 selenium.type(locator, "#{options[:with]}") end webrat_deprecate :fills_in, :fill_in + def response + SeleniumResponse.new(self, response_body) + end + def response_body #:nodoc: selenium.get_html_source end @@ -30,53 +57,30 @@ module Webrat pattern = adjust_if_regexp(button_text_or_regexp) end pattern ||= '*' - selenium.click("button=#{pattern}") - wait_for_result(options[:wait]) + locator = "button=#{pattern}" + + selenium.wait_for_element locator, 5 + selenium.click locator end webrat_deprecate :clicks_button, :click_button def click_link(link_text_or_regexp, options = {}) pattern = adjust_if_regexp(link_text_or_regexp) - selenium.click("webratlink=#{pattern}") - wait_for_result(options[:wait]) + locator = "webratlink=#{pattern}" + selenium.wait_for_element locator, 5 + selenium.click locator end webrat_deprecate :clicks_link, :click_link def click_link_within(selector, link_text, options = {}) - selenium.click("webratlinkwithin=#{selector}|#{link_text}") - wait_for_result(options[:wait]) + locator = "webratlinkwithin=#{selector}|#{link_text}" + selenium.wait_for_element locator, 5 + selenium.click locator end webrat_deprecate :clicks_link_within, :click_link_within - - def wait_for_result(wait_type) - if wait_type == :ajax - wait_for_ajax - elsif wait_type == :effects - wait_for_effects - else - wait_for_page_to_load - end - end - - def wait_for_page_to_load(timeout = 15000) - selenium.wait_for_page_to_load(timeout) - end - - def wait_for_ajax(timeout = 15000) - selenium.wait_for_condition "Ajax.activeRequestCount == 0", timeout - end - - def wait_for_effects(timeout = 15000) - selenium.wait_for_condition "window.Effect.Queue.size() == 0", timeout - end - - def wait_for_ajax_and_effects - wait_for_ajax - wait_for_effects - end def select(option_text, options = {}) id_or_name_or_label = options[:from] @@ -86,30 +90,28 @@ module Webrat else select_locator = "webratselectwithoption=#{option_text}" end + + selenium.wait_for_element select_locator, 5 selenium.select(select_locator, option_text) end webrat_deprecate :selects, :select def choose(label_text) - selenium.click("webrat=#{label_text}") + locator = "webrat=#{label_text}" + selenium.wait_for_element locator, 5 + selenium.click locator end webrat_deprecate :chooses, :choose def check(label_text) - selenium.check("webrat=#{label_text}") + locator = "webrat=#{label_text}" + selenium.wait_for_element locator, 5 + selenium.check locator end webrat_deprecate :checks, :check - - def is_ordered(*args) #:nodoc: - selenium.is_ordered(*args) - end - - def dragdrop(*args) #:nodoc: - selenium.dragdrop(*args) - end def fire_event(field_identifier, event) locator = "webrat=#{Regexp.escape(field_identifier)}" @@ -126,12 +128,38 @@ module Webrat selenium.key_up(locator, key_code) end - def browser + def wait_for(params={}) + timeout = params[:timeout] || 5 + message = params[:message] || "Timeout exceeded" + + begin_time = Time.now + + while (Time.now - begin_time) < timeout + value = nil + + begin + value = yield + rescue ::Spec::Expectations::ExpectationNotMetError, ::Selenium::CommandError, Webrat::WebratError + value = nil + end + + return value if value + + sleep 0.25 + end + + raise Webrat::TimeoutError.new(message + " (after #{timeout} sec)") + true + end + + def selenium return $browser if $browser setup $browser end + webrat_deprecate :browser, :selenium + protected def setup #:nodoc: @@ -140,15 +168,13 @@ module Webrat Webrat.start_app_server end - $browser = ::Selenium::Client::Driver.new("localhost", 4444, "*firefox", "http://0.0.0.0:3001") $browser.set_speed(0) $browser.start teardown_at_exit - end - - def selenium #:nodoc: - browser + + extend_selenium + define_location_strategies end def teardown_at_exit #:nodoc: diff --git a/spec/api/basic_auth_spec.rb b/spec/api/basic_auth_spec.rb index 16f908d..02156cf 100644 --- a/spec/api/basic_auth_spec.rb +++ b/spec/api/basic_auth_spec.rb @@ -12,9 +12,11 @@ describe "Basic Auth HTTP headers" do it "should be present in form submits" do with_html <<-HTML +
+ HTML webrat_session.should_receive(:post).with("/form1", {}, {'HTTP_AUTHORIZATION' => "Basic dXNlcjpzZWNyZXQ=\n"}) click_button diff --git a/spec/api/check_spec.rb b/spec/api/check_spec.rb index 8e9bcd4..9254960 100644 --- a/spec/api/check_spec.rb +++ b/spec/api/check_spec.rb @@ -26,12 +26,14 @@ describe "check" do it "should check rails style checkboxes" do with_html <<-HTML + + HTML webrat_session.should_receive(:get).with("/login", "user" => {"tos" => "1"}) @@ -86,8 +88,10 @@ end describe "uncheck" do it "should fail if no checkbox found" do with_html <<-HTML + + HTML lambda { uncheck "remember_me" }.should raise_error(Webrat::NotFoundError) @@ -95,9 +99,11 @@ describe "uncheck" do it "should fail if input is not a checkbox" do with_html <<-HTML + + HTML lambda { uncheck "remember_me" }.should raise_error(Webrat::NotFoundError) @@ -105,22 +111,26 @@ describe "uncheck" do it "should fail if the checkbox is disabled" do with_html <<-HTML + + HTML lambda { uncheck "remember_me" }.should raise_error(Webrat::DisabledFieldError) end it "should uncheck rails style checkboxes" do with_html <<-HTML + + HTML webrat_session.should_receive(:get).with("/login", "user" => {"tos" => "0"}) check "TOS" @@ -130,10 +140,12 @@ describe "uncheck" do it "should result in value not being posted" do with_html <<-HTML + + HTML webrat_session.should_receive(:post).with("/login", {}) uncheck "remember_me" diff --git a/spec/api/choose_spec.rb b/spec/api/choose_spec.rb index 9137388..31df4ae 100644 --- a/spec/api/choose_spec.rb +++ b/spec/api/choose_spec.rb @@ -3,8 +3,10 @@ require File.expand_path(File.dirname(__FILE__) + "/../spec_helper") describe "choose" do it "should fail if no radio buttons found" do with_html <<-HTML + + HTML lambda { choose "first option" }.should raise_error(Webrat::NotFoundError) @@ -12,9 +14,11 @@ describe "choose" do it "should fail if input is not a radio button" do with_html <<-HTML + + HTML lambda { choose "first_option" }.should raise_error(Webrat::NotFoundError) @@ -22,6 +26,7 @@ describe "choose" do it "should check rails style radio buttons" do with_html <<-HTML + + HTML webrat_session.should_receive(:get).with("/login", "user" => {"gender" => "M"}) choose "Male" @@ -37,6 +43,7 @@ describe "choose" do it "should only submit last chosen value" do with_html <<-HTML + + HTML webrat_session.should_receive(:get).with("/login", "user" => {"gender" => "M"}) choose "Female" @@ -53,10 +61,12 @@ describe "choose" do it "should fail if the radio button is disabled" do with_html <<-HTML + + HTML lambda { choose "first_option" }.should raise_error(Webrat::DisabledFieldError) @@ -64,10 +74,12 @@ describe "choose" do it "should result in the value on being posted if not specified" do with_html <<-HTML + + HTML webrat_session.should_receive(:post).with("/login", "first_option" => "on") choose "first_option" @@ -76,10 +88,12 @@ describe "choose" do it "should result in the value on being posted if not specified and checked by default" do with_html <<-HTML + + HTML webrat_session.should_receive(:post).with("/login", "first_option" => "on") click_button @@ -87,6 +101,7 @@ describe "choose" do it "should result in the value of the selected radio button being posted when a subsequent one is checked by default" do with_html <<-HTML + + HTML webrat_session.should_receive(:post).with("/login", "user" => {"gender" => "M"}) choose "Male" diff --git a/spec/api/click_area_spec.rb b/spec/api/click_area_spec.rb index 791817f..65bfbbe 100644 --- a/spec/api/click_area_spec.rb +++ b/spec/api/click_area_spec.rb @@ -3,9 +3,11 @@ require File.expand_path(File.dirname(__FILE__) + "/../spec_helper") describe "click_area" do it "should use get by default" do with_html <<-HTML + + HTML webrat_session.should_receive(:get).with("/page", {}) click_area "Berlin" @@ -13,9 +15,11 @@ describe "click_area" do it "should assert valid response" do with_html <<-HTML + + HTML webrat_session.response_code = 501 lambda { click_area "Berlin" }.should raise_error(Webrat::PageLoadError) @@ -24,9 +28,11 @@ describe "click_area" do [200, 300, 400, 499].each do |status| it "should consider the #{status} status code as success" do with_html <<-HTML + + HTML webrat_session.response_code = status lambda { click_area "Berlin" }.should_not raise_error @@ -35,9 +41,11 @@ describe "click_area" do it "should fail if the area doesn't exist" do with_html <<-HTML + + HTML lambda { @@ -47,9 +55,11 @@ describe "click_area" do it "should not be case sensitive" do with_html <<-HTML + + HTML webrat_session.should_receive(:get).with("/page", {}) click_area "berlin" @@ -59,9 +69,11 @@ describe "click_area" do it "should follow relative links" do webrat_session.stub!(:current_url => "/page") with_html <<-HTML + + HTML webrat_session.should_receive(:get).with("/page/sub", {}) click_area "Berlin" @@ -69,9 +81,11 @@ describe "click_area" do it "should follow fully qualified local links" do with_html <<-HTML + + HTML webrat_session.should_receive(:get).with("http://www.example.com/page", {}) click_area "Berlin" @@ -79,9 +93,11 @@ describe "click_area" do it "should follow query parameters" do with_html <<-HTML + + HTML webrat_session.should_receive(:get).with("/page?foo=bar", {}) click_area "Berlin" diff --git a/spec/api/click_button_spec.rb b/spec/api/click_button_spec.rb index 5e55ad1..6e19c80 100644 --- a/spec/api/click_button_spec.rb +++ b/spec/api/click_button_spec.rb @@ -3,7 +3,9 @@ require File.expand_path(File.dirname(__FILE__) + "/../spec_helper") describe "click_button" do it "should fail if no buttons" do with_html <<-HTML + + HTML lambda { click_button }.should raise_error(Webrat::NotFoundError) @@ -11,9 +13,11 @@ describe "click_button" do it "should fail if input is not a submit button" do with_html <<-HTML + + HTML lambda { click_button }.should raise_error(Webrat::NotFoundError) @@ -22,9 +26,11 @@ describe "click_button" do it "should fail if button is disabled" do with_html <<-HTML + + HTML lambda { click_button }.should raise_error(Webrat::DisabledFieldError) @@ -32,9 +38,11 @@ describe "click_button" do it "should default to get method" do with_html <<-HTML + + HTML webrat_session.should_receive(:get) click_button @@ -42,9 +50,11 @@ describe "click_button" do it "should assert valid response" do with_html <<-HTML + + HTML webrat_session.response_code = 501 lambda { click_button }.should raise_error(Webrat::PageLoadError) @@ -53,9 +63,11 @@ describe "click_button" do [200, 300, 400, 499].each do |status| it "should consider the #{status} status code as success" do with_html <<-HTML + + HTML webrat_session.response_code = status lambda { click_button }.should_not raise_error @@ -64,12 +76,14 @@ describe "click_button" do it "should submit the first form by default" do with_html <<-HTML + + HTML webrat_session.should_receive(:get).with("/form1", {}) click_button @@ -77,10 +91,12 @@ describe "click_button" do it "should not explode on file fields" do with_html <<-HTML + + HTML click_button end @@ -102,9 +118,11 @@ describe "click_button" do it "should use action from form" do with_html <<-HTML + + HTML webrat_session.should_receive(:get).with("/login", {}) click_button @@ -112,9 +130,11 @@ describe "click_button" do it "should use method from form" do with_html <<-HTML + + HTML webrat_session.should_receive(:post) click_button @@ -122,10 +142,12 @@ describe "click_button" do it "should send button as param if it has a name" do with_html <<-HTML + + HTML webrat_session.should_receive(:post).with("/login", "login" => "Login") click_button("Login") @@ -133,10 +155,12 @@ describe "click_button" do it "should not send button as param if it has no name" do with_html <<-HTML + + HTML webrat_session.should_receive(:post).with("/login", {}) click_button("Login") @@ -144,10 +168,12 @@ describe "click_button" do it "should send default password field values" do with_html <<-HTML + + HTML webrat_session.should_receive(:get).with("/login", "user" => {"password" => "mypass"}) click_button @@ -155,10 +181,12 @@ describe "click_button" do it "should send default hidden field values" do with_html <<-HTML + + HTML webrat_session.should_receive(:get).with("/login", "user" => {"email" => "test@example.com"}) click_button @@ -166,10 +194,12 @@ describe "click_button" do it "should send default text field values" do with_html <<-HTML + + HTML webrat_session.should_receive(:get).with("/login", "user" => {"email" => "test@example.com"}) click_button @@ -177,14 +207,16 @@ describe "click_button" do it "should not send disabled field values" do with_html <<-HTML + + HTML webrat_session.should_receive(:get).with("/login", {}) click_button @@ -192,10 +224,12 @@ describe "click_button" do it "should send default checked fields" do with_html <<-HTML + + HTML webrat_session.should_receive(:get).with("/login", "user" => {"tos" => "1"}) click_button @@ -203,6 +237,7 @@ describe "click_button" do it "should send default radio options" do with_html <<-HTML + + HTML webrat_session.should_receive(:get).with("/login", "user" => {"gender" => "F"}) click_button @@ -217,11 +253,13 @@ describe "click_button" do it "should send correct data for rails style unchecked fields" do with_html <<-HTML + + HTML webrat_session.should_receive(:get).with("/login", "user" => {"tos" => "0"}) click_button @@ -229,11 +267,13 @@ describe "click_button" do it "should send correct data for rails style checked fields" do with_html <<-HTML + + HTML webrat_session.should_receive(:get).with("/login", "user" => {"tos" => "1"}) click_button @@ -241,6 +281,7 @@ describe "click_button" do it "should send default collection fields" do with_html <<-HTML + + HTML webrat_session.should_receive(:post).with("/login", "options" => ["burger", "fries", "soda", "soda", "dessert"], @@ -263,10 +305,12 @@ describe "click_button" do it "should not send default unchecked fields" do with_html <<-HTML + + HTML webrat_session.should_receive(:get).with("/login", {}) click_button @@ -274,17 +318,35 @@ describe "click_button" do it "should send default textarea values" do with_html <<-HTML + + HTML webrat_session.should_receive(:post).with("/posts", "post" => {"body" => "Post body here!"}) click_button end + it "should properly handle HTML entities in textarea default values" do + pending "needs bug fix" do + with_html <<-HTML + + + + HTML + webrat_session.should_receive(:post).with("/posts", "post" => {"body" => "Peanut butter & jelly"}) + click_button + end + end + it "should send default selected option value from select" do with_html <<-HTML + + HTML webrat_session.should_receive(:get).with("/login", "month" => "2") click_button @@ -299,6 +362,7 @@ describe "click_button" do it "should send default selected option inner html from select when no value attribute" do with_html <<-HTML + + HTML webrat_session.should_receive(:get).with("/login", "month" => "February") click_button @@ -313,6 +378,7 @@ describe "click_button" do it "should send first select option value when no option selected" do with_html <<-HTML + + HTML webrat_session.should_receive(:get).with("/login", "month" => "1") click_button @@ -327,11 +394,13 @@ describe "click_button" do it "should handle nested properties" do with_html <<-HTML + + HTML webrat_session.should_receive(:post).with("/login", "contestant" => {"scores" => {'1' => '2', '3' => '4'}}) click_button @@ -339,10 +408,12 @@ describe "click_button" do it "should send default empty text field values" do with_html <<-HTML + + HTML webrat_session.should_receive(:get).with("/login", "user" => {"email" => ""}) click_button @@ -350,10 +421,12 @@ describe "click_button" do it "should recognize button tags" do with_html <<-HTML + + HTML webrat_session.should_receive(:get).with("/login", "user" => {"email" => ""}) click_button @@ -361,9 +434,11 @@ describe "click_button" do it "should recognize image button tags" do with_html <<-HTML + + HTML webrat_session.should_receive(:get) click_button @@ -371,9 +446,11 @@ describe "click_button" do it "should find buttons by their IDs" do with_html <<-HTML + + HTML webrat_session.should_receive(:get) click_button "my_button" @@ -381,9 +458,11 @@ describe "click_button" do it "should find image buttons by their alt text" do with_html <<-HTML + + HTML webrat_session.should_receive(:get) click_button "Go" @@ -391,10 +470,12 @@ describe "click_button" do it "should recognize button tags by content" do with_html <<-HTML + + HTML webrat_session.should_receive(:get).with("/login", "user" => {"email" => ""}) click_button "Login" diff --git a/spec/api/click_link_spec.rb b/spec/api/click_link_spec.rb index a7aace4..ac6f1dc 100644 --- a/spec/api/click_link_spec.rb +++ b/spec/api/click_link_spec.rb @@ -3,7 +3,9 @@ require File.expand_path(File.dirname(__FILE__) + "/../spec_helper") describe "click_link" do it "should click links with ampertands" do with_html <<-HTML + Save & go back + HTML webrat_session.should_receive(:get).with("/page", {}) click_link "Save & go back" @@ -11,7 +13,9 @@ describe "click_link" do it "should use get by default" do with_html <<-HTML + Link text + HTML webrat_session.should_receive(:get).with("/page", {}) click_link "Link text" @@ -19,7 +23,9 @@ describe "click_link" do it "should click get links" do with_html <<-HTML + Link text + HTML webrat_session.should_receive(:get).with("/page", {}) click_link "Link text", :method => :get @@ -27,7 +33,9 @@ describe "click_link" do it "should click link on substring" do with_html <<-HTML + Link text + HTML webrat_session.should_receive(:get).with("/page", {}) click_link "ink tex", :method => :get @@ -35,7 +43,9 @@ describe "click_link" do it "should click delete links" do with_html <<-HTML + Link text + HTML webrat_session.should_receive(:delete).with("/page", {}) click_link "Link text", :method => :delete @@ -44,7 +54,9 @@ describe "click_link" do it "should click post links" do with_html <<-HTML + Link text + HTML webrat_session.should_receive(:post).with("/page", {}) click_link "Link text", :method => :post @@ -52,7 +64,9 @@ describe "click_link" do it "should click put links" do with_html <<-HTML + Link text + HTML webrat_session.should_receive(:put).with("/page", {}) click_link "Link text", :method => :put @@ -60,7 +74,9 @@ describe "click_link" do it "should click links by regexp" do with_html <<-HTML + Link text + HTML webrat_session.should_receive(:get).with("/page", {}) click_link /link [a-z]/i @@ -68,7 +84,9 @@ describe "click_link" do it "should click links by id" do with_html <<-HTML + Link text + HTML webrat_session.should_receive(:get).with("/page", {}) click_link "link_text_link" @@ -76,7 +94,9 @@ describe "click_link" do it "should click links by id regexp" do with_html <<-HTML + Link text + HTML webrat_session.should_receive(:get).with("/page", {}) click_link /_text_/ @@ -84,6 +104,7 @@ describe "click_link" do it "should click rails javascript links with authenticity tokens" do with_html <<-HTML + Posts + HTML webrat_session.should_receive(:post).with("/posts", "authenticity_token" => "aa79cb354597a60a3786e7e291ed4f74d77d3a62") click_link "Posts" @@ -103,6 +125,7 @@ describe "click_link" do it "should click rails javascript delete links" do with_html <<-HTML + Delete + HTML webrat_session.should_receive(:delete).with("/posts/1", {}) click_link "Delete" @@ -122,6 +146,7 @@ describe "click_link" do it "should click rails javascript post links" do with_html <<-HTML + Posts + HTML webrat_session.should_receive(:post).with("/posts", {}) click_link "Posts" @@ -136,6 +162,7 @@ describe "click_link" do it "should click rails javascript post links without javascript" do with_html <<-HTML + Posts + HTML webrat_session.should_receive(:get).with("/posts", {}) click_link "Posts", :javascript => false @@ -150,6 +178,7 @@ describe "click_link" do it "should click rails javascript put links" do with_html <<-HTML + Put + HTML webrat_session.should_receive(:put).with("/posts", {}) click_link "Put" @@ -169,6 +199,7 @@ describe "click_link" do it "should fail if the javascript link doesn't have a value for the _method input" do with_html <<-HTML + Link + HTML lambda { @@ -189,7 +221,9 @@ describe "click_link" do it "should assert valid response" do with_html <<-HTML + Link text + HTML webrat_session.response_code = 501 lambda { click_link "Link text" }.should raise_error(Webrat::PageLoadError) @@ -198,7 +232,9 @@ describe "click_link" do [200, 300, 400, 499].each do |status| it "should consider the #{status} status code as success" do with_html <<-HTML + Link text + HTML webrat_session.response_code = status lambda { click_link "Link text" }.should_not raise_error @@ -207,7 +243,9 @@ describe "click_link" do it "should fail is the link doesn't exist" do with_html <<-HTML + Link text + HTML lambda { @@ -217,7 +255,9 @@ describe "click_link" do it "should not be case sensitive" do with_html <<-HTML + Link text + HTML webrat_session.should_receive(:get).with("/page", {}) click_link "LINK TEXT" @@ -225,7 +265,9 @@ describe "click_link" do it "should match link substrings" do with_html <<-HTML + This is some cool link text, isn't it? + HTML webrat_session.should_receive(:get).with("/page", {}) click_link "Link text" @@ -233,7 +275,9 @@ describe "click_link" do it "should work with elements in the link" do with_html <<-HTML + Link text + HTML webrat_session.should_receive(:get).with("/page", {}) click_link "Link text" @@ -241,8 +285,10 @@ describe "click_link" do it "should match the first matching link" do with_html <<-HTML + Link text Link text + HTML webrat_session.should_receive(:get).with("/page1", {}) click_link "Link text" @@ -250,10 +296,10 @@ describe "click_link" do it "should choose the shortest link text match" do with_html <<-HTML - - Linkerama - Link - + + Linkerama + Link + HTML webrat_session.should_receive(:get).with("/page2", {}) @@ -262,9 +308,9 @@ describe "click_link" do it "should treat non-breaking spaces as spaces" do with_html <<-HTML - - This is a link - + + This is a link + HTML webrat_session.should_receive(:get).with("/page1", {}) @@ -274,8 +320,10 @@ describe "click_link" do it "should not match on non-text contents" do pending "needs fix" do with_html <<-HTML - My house - Location + + My house + Location + HTML webrat_session.should_receive(:get).with("/page2", {}) @@ -286,10 +334,10 @@ describe "click_link" do it "should click link within a selector" do with_html <<-HTML - Link -