From e746335d47f1e22b36303c5b6977195ec8f3f39e Mon Sep 17 00:00:00 2001 From: Bryan Helmkamp Date: Sat, 26 Jul 2008 12:17:00 -0400 Subject: [PATCH] Add #within method for working within a selector scope --- Rakefile | 2 +- lib/webrat/core/page.rb | 219 ++++-------------------------------- lib/webrat/core/scope.rb | 195 +++++++++++++++++++++++++++++++- lib/webrat/core/session.rb | 4 + lib/webrat/rails/session.rb | 4 + spec/api/within_spec.rb | 21 ++++ 6 files changed, 242 insertions(+), 203 deletions(-) create mode 100644 spec/api/within_spec.rb diff --git a/Rakefile b/Rakefile index 19035e3..e3d475d 100644 --- a/Rakefile +++ b/Rakefile @@ -56,7 +56,7 @@ end require 'spec/rake/verify_rcov' RCov::VerifyTask.new(:verify_rcov => :rcov) do |t| - t.threshold = 97.4 # Make sure you have rcov 0.7 or higher! + t.threshold = 97.5 # Make sure you have rcov 0.7 or higher! end remove_task "default" diff --git a/lib/webrat/core/page.rb b/lib/webrat/core/page.rb index a61b40d..9b4262a 100644 --- a/lib/webrat/core/page.rb +++ b/lib/webrat/core/page.rb @@ -1,9 +1,11 @@ require "rubygems" require "hpricot" +require "forwardable" require "English" module Webrat class Page + extend Forwardable include Logging attr_reader :session @@ -21,97 +23,9 @@ module Webrat session.current_page = self end - # Verifies an input field or textarea exists on the current page, and stores a value for - # it which will be sent when the form is submitted. - # - # Examples: - # fills_in "Email", :with => "user@example.com" - # fills_in "user[email]", :with => "user@example.com" - # - # The field value is required, and must be specified in options[:with]. - # field can be either the value of a name attribute (i.e. user[email]) - # or the text inside a element that points at the field. - def fills_in(id_or_name_or_label, options = {}) - field = scope.find_field(id_or_name_or_label, TextField, TextareaField, PasswordField) - field.set(options[:with]) + def within(selector) + yield Scope.new(self, session.response_body, selector) end - - alias_method :fill_in, :fills_in - - # Verifies that an input checkbox exists on the current page and marks it - # as checked, so that the value will be submitted with the form. - # - # Example: - # checks 'Remember Me' - def checks(id_or_name_or_label) - field = scope.find_field(id_or_name_or_label, CheckboxField) - field.check - end - - alias_method :check, :checks - - # Verifies that an input checkbox exists on the current page and marks it - # as unchecked, so that the value will not be submitted with the form. - # - # Example: - # unchecks 'Remember Me' - def unchecks(id_or_name_or_label) - field = scope.find_field(id_or_name_or_label, CheckboxField) - field.uncheck - end - - alias_method :uncheck, :unchecks - - # Verifies that an input radio button exists on the current page and marks it - # as checked, so that the value will be submitted with the form. - # - # Example: - # chooses 'First Option' - def chooses(label) - field = scope.find_field(label, RadioField) - field.choose - end - - alias_method :choose, :chooses - - # Verifies that a an option element exists on the current page with the specified - # text. You can optionally restrict the search to a specific select list by - # assigning options[:from] the value of the select list's name or - # a label. Stores the option's value to be sent when the form is submitted. - # - # Examples: - # selects "January" - # selects "February", :from => "event_month" - # selects "February", :from => "Event Month" - def selects(option_text, options = {}) - id_or_name_or_label = options[:from] - - if id_or_name_or_label - field = scope.find_field(id_or_name_or_label, SelectField) - option = field.find_option(option_text) - else - option = scope.find_select_option(option_text) - end - - flunk("Could not find option #{option_text.inspect}") if option.nil? - option.choose - end - - alias_method :select, :selects - - # Verifies that an input file field exists on the current page and sets - # its value to the given +file+, so that the file will be uploaded - # along with the form. An optional content_type may be given. - # - # Example: - # attaches_file "Resume", "/path/to/the/resume.txt" - # attaches_file "Photo", "/path/to/the/image.png", "image/png" - def attaches_file(id_or_name_or_label, path, content_type = nil) - field = scope.find_field(id_or_name_or_label, FileField) - field.set(path, content_type) - end - - alias_method :attach_file, :attaches_file # Saves the page out to RAILS_ROOT/tmp/ and opens it in the default # web browser if on OS X. Useful for debugging. @@ -130,108 +44,6 @@ module Webrat open_in_browser(filename) end - def open_in_browser(path) # :nodoc - `open #{path}` - end - - # Issues a request for the URL pointed to by a link on the current page, - # follows any redirects, and verifies the final page load was successful. - # - # clicks_link has very basic support for detecting Rails-generated - # JavaScript onclick handlers for PUT, POST and DELETE links, as well as - # CSRF authenticity tokens if they are present. - # - # Javascript imitation can be disabled by passing the option :javascript => false - # - # Example: - # clicks_link "Sign up" - # - # clicks_link "Sign up", :javascript => false - def clicks_link(link_text, options = {}) - link = scope.find_link(link_text) - link.click(nil, options) - end - - alias_method :click_link, :clicks_link - - # Works like clicks_link, but only looks for the link text within a given selector - # - # Example: - # clicks_link_within "#user_12", "Vote" - def clicks_link_within(selector, link_text) - link = scope.find_link(link_text, selector) - link.click - end - - alias_method :click_link_within, :clicks_link_within - - # Works like clicks_link, but forces a GET request - # - # Example: - # clicks_get_link "Log out" - def clicks_get_link(link_text) - link = scope.find_link(link_text) - link.click(:get) - end - - alias_method :click_get_link, :clicks_get_link - - # Works like clicks_link, but issues a DELETE request instead of a GET - # - # Example: - # clicks_delete_link "Log out" - def clicks_delete_link(link_text) - link = scope.find_link(link_text) - link.click(:delete) - end - - alias_method :click_delete_link, :clicks_delete_link - - # Works like clicks_link, but issues a POST request instead of a GET - # - # Example: - # clicks_post_link "Vote" - def clicks_post_link(link_text) - link = scope.find_link(link_text) - link.click(:post) - end - - alias_method :click_post_link, :clicks_post_link - - # Works like clicks_link, but issues a PUT request instead of a GET - # - # Example: - # clicks_put_link "Update profile" - def clicks_put_link(link_text) - link = scope.find_link(link_text) - link.click(:put) - end - - alias_method :click_put_link, :clicks_put_link - - # Verifies that a submit button exists for the form, then submits the form, follows - # any redirects, and verifies the final page was successful. - # - # Example: - # clicks_button "Login" - # clicks_button - # - # The URL and HTTP method for the form submission are automatically read from the - # action and method attributes of the
element. - def clicks_button(value = nil) - button = nil - - scope.forms.each do |form| - button = form.find_button(value) - break if button - end - - flunk("Could not find button #{value.inspect}") if button.nil? - button.click - end - - alias_method :click_button, :clicks_button - # Reloads the last page requested. Note that this will resubmit forms # and their data. # @@ -243,13 +55,26 @@ module Webrat alias_method :reload, :reloads - def submits_form(form_id = nil) # :nodoc: - end - - alias_method :submit_form, :submits_form + def_delegators :scope, :fill_in, :fills_in + def_delegators :scope, :check, :checks + def_delegators :scope, :uncheck, :unchecks + def_delegators :scope, :choose, :chooses + def_delegators :scope, :select, :selects + def_delegators :scope, :attach_file, :attaches_file + def_delegators :scope, :click_link, :clicks_link + def_delegators :scope, :click_link_within, :clicks_link_within + def_delegators :scope, :click_get_link, :clicks_get_link + def_delegators :scope, :click_delete_link, :clicks_delete_link + def_delegators :scope, :click_post_link, :clicks_post_link + def_delegators :scope, :click_put_link, :clicks_put_link + def_delegators :scope, :click_button, :clicks_button protected - + + def open_in_browser(path) # :nodoc + `open #{path}` + end + def load_page session.request_page(@url, @method, @data) diff --git a/lib/webrat/core/scope.rb b/lib/webrat/core/scope.rb index f6ae23a..c6e3d35 100644 --- a/lib/webrat/core/scope.rb +++ b/lib/webrat/core/scope.rb @@ -1,18 +1,196 @@ module Webrat class Scope - def initialize(page, html) + def initialize(page, html, selector = nil) @page = page @html = html + @selector = selector end - def find_select_option(option_text) + # Verifies an input field or textarea exists on the current page, and stores a value for + # it which will be sent when the form is submitted. + # + # Examples: + # fills_in "Email", :with => "user@example.com" + # fills_in "user[email]", :with => "user@example.com" + # + # The field value is required, and must be specified in options[:with]. + # field can be either the value of a name attribute (i.e. user[email]) + # or the text inside a element that points at the field. + def fills_in(id_or_name_or_label, options = {}) + find_field(id_or_name_or_label, TextField, TextareaField, PasswordField).set(options[:with]) + end + + alias_method :fill_in, :fills_in + + # Verifies that an input checkbox exists on the current page and marks it + # as checked, so that the value will be submitted with the form. + # + # Example: + # checks 'Remember Me' + def checks(id_or_name_or_label) + find_field(id_or_name_or_label, CheckboxField).check + end + + alias_method :check, :checks + + # Verifies that an input checkbox exists on the current page and marks it + # as unchecked, so that the value will not be submitted with the form. + # + # Example: + # unchecks 'Remember Me' + def unchecks(id_or_name_or_label) + find_field(id_or_name_or_label, CheckboxField).uncheck + end + + alias_method :uncheck, :unchecks + + # Verifies that an input radio button exists on the current page and marks it + # as checked, so that the value will be submitted with the form. + # + # Example: + # chooses 'First Option' + def chooses(label) + find_field(label, RadioField).choose + end + + alias_method :choose, :chooses + + # Verifies that a an option element exists on the current page with the specified + # text. You can optionally restrict the search to a specific select list by + # assigning options[:from] the value of the select list's name or + # a label. Stores the option's value to be sent when the form is submitted. + # + # Examples: + # selects "January" + # selects "February", :from => "event_month" + # selects "February", :from => "Event Month" + def selects(option_text, options = {}) + find_select_option(option_text, options[:from]).choose + end + + alias_method :select, :selects + + # Verifies that an input file field exists on the current page and sets + # its value to the given +file+, so that the file will be uploaded + # along with the form. An optional content_type may be given. + # + # Example: + # attaches_file "Resume", "/path/to/the/resume.txt" + # attaches_file "Photo", "/path/to/the/image.png", "image/png" + def attaches_file(id_or_name_or_label, path, content_type = nil) + find_field(id_or_name_or_label, FileField).set(path, content_type) + end + + alias_method :attach_file, :attaches_file + + # Issues a request for the URL pointed to by a link on the current page, + # follows any redirects, and verifies the final page load was successful. + # + # clicks_link has very basic support for detecting Rails-generated + # JavaScript onclick handlers for PUT, POST and DELETE links, as well as + # CSRF authenticity tokens if they are present. + # + # Javascript imitation can be disabled by passing the option :javascript => false + # + # Example: + # clicks_link "Sign up" + # + # clicks_link "Sign up", :javascript => false + def clicks_link(link_text, options = {}) + find_link(link_text).click(nil, options) + end + + alias_method :click_link, :clicks_link + + # Works like clicks_link, but only looks for the link text within a given selector + # + # Example: + # clicks_link_within "#user_12", "Vote" + def clicks_link_within(selector, link_text) + find_link(link_text, selector).click + end + + alias_method :click_link_within, :clicks_link_within + + # Works like clicks_link, but forces a GET request + # + # Example: + # clicks_get_link "Log out" + def clicks_get_link(link_text) + find_link(link_text).click(:get) + end + + alias_method :click_get_link, :clicks_get_link + + # Works like clicks_link, but issues a DELETE request instead of a GET + # + # Example: + # clicks_delete_link "Log out" + def clicks_delete_link(link_text) + find_link(link_text).click(:delete) + end + + alias_method :click_delete_link, :clicks_delete_link + + # Works like clicks_link, but issues a POST request instead of a GET + # + # Example: + # clicks_post_link "Vote" + def clicks_post_link(link_text) + find_link(link_text).click(:post) + end + + alias_method :click_post_link, :clicks_post_link + + # Works like clicks_link, but issues a PUT request instead of a GET + # + # Example: + # clicks_put_link "Update profile" + def clicks_put_link(link_text) + find_link(link_text).click(:put) + end + + alias_method :click_put_link, :clicks_put_link + + # Verifies that a submit button exists for the form, then submits the form, follows + # any redirects, and verifies the final page was successful. + # + # Example: + # clicks_button "Login" + # clicks_button + # + # The URL and HTTP method for the form submission are automatically read from the + # action and method attributes of the element. + def clicks_button(value = nil) + find_button(value).click + end + + alias_method :click_button, :clicks_button + + protected + + def find_select_option(option_text, id_or_name_or_label) + if id_or_name_or_label + field = find_field(id_or_name_or_label, SelectField) + return field.find_option(option_text) + else + forms.each do |form| + result = form.find_select_option(option_text) + return result if result + end + end + + flunk("Could not find option #{option_text.inspect}") + end + + def find_button(value) forms.each do |form| - result = form.find_select_option(option_text) - return result if result + button = form.find_button(value) + return button if button end - nil + flunk("Could not find button #{value.inspect}") end def find_link(text, selector = nil) @@ -55,6 +233,13 @@ module Webrat def dom # :nodoc: return @dom if defined?(@dom) && @dom @dom = Hpricot(@html) + + if @selector + html = (@dom / @selector).first.to_html + @dom = Hpricot(html) + end + + return @dom end end diff --git a/lib/webrat/core/session.rb b/lib/webrat/core/session.rb index f52c7fe..49d0ee0 100644 --- a/lib/webrat/core/session.rb +++ b/lib/webrat/core/session.rb @@ -44,6 +44,10 @@ module Webrat current_page.save_and_open end + def within(selector, &block) + current_page.within(selector, &block) + end + def method_missing(name, *args) if current_page.respond_to?(name) current_page.send(name, *args) diff --git a/lib/webrat/rails/session.rb b/lib/webrat/rails/session.rb index 1b73721..0105d7e 100644 --- a/lib/webrat/rails/session.rb +++ b/lib/webrat/rails/session.rb @@ -19,6 +19,10 @@ module ActionController super || webrat_session.respond_to?(name) end + def within(selector, &block) + webrat_session.within(selector, &block) + end + def method_missing(name, *args) if webrat_session.respond_to?(name) webrat_session.send(name, *args) diff --git a/spec/api/within_spec.rb b/spec/api/within_spec.rb new file mode 100644 index 0000000..e59eb3b --- /dev/null +++ b/spec/api/within_spec.rb @@ -0,0 +1,21 @@ +require File.expand_path(File.dirname(__FILE__) + "/../spec_helper") + +describe "within" do + before do + @session = Webrat::TestSession.new + end + + it "should click links within a scope" do + @session.response_body = <<-EOS + Link +
+ Link +
+ EOS + + @session.should_receive(:get).with("/page2", {}) + @session.within "#container" do |scope| + scope.clicks_link "Link" + end + end +end