Add #within method for working within a selector scope

This commit is contained in:
Bryan Helmkamp 2008-07-26 12:17:00 -04:00
parent 4f843d3d01
commit e746335d47
6 changed files with 242 additions and 203 deletions

View File

@ -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"

View File

@ -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 <tt>options[:with]</tt>.
# <tt>field</tt> can be either the value of a name attribute (i.e. <tt>user[email]</tt>)
# or the text inside a <tt><label></tt> element that points at the <tt><input></tt> 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 <tt>options[:from]</tt> 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 <tt>content_type</tt> 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
# <tt>action</tt> and <tt>method</tt> attributes of the <tt><form></tt> 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)

View File

@ -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 <tt>options[:with]</tt>.
# <tt>field</tt> can be either the value of a name attribute (i.e. <tt>user[email]</tt>)
# or the text inside a <tt><label></tt> element that points at the <tt><input></tt> 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 <tt>options[:from]</tt> 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 <tt>content_type</tt> 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
# <tt>action</tt> and <tt>method</tt> attributes of the <tt><form></tt> 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

View File

@ -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)

View File

@ -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)

21
spec/api/within_spec.rb Normal file
View File

@ -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
<a href="/page1">Link</a>
<div id="container">
<a href="/page2">Link</a>
</div>
EOS
@session.should_receive(:get).with("/page2", {})
@session.within "#container" do |scope|
scope.clicks_link "Link"
end
end
end