support custom filter block in selector finders/matchers/assertions

This commit is contained in:
Thomas Walpole 2016-09-23 15:03:36 -07:00
parent 9e581b0802
commit b5655e9b9b
6 changed files with 496 additions and 474 deletions

View File

@ -28,8 +28,8 @@ module Capybara
# @return [Capybara::Node::Element] The found element
# @raise [Capybara::ElementNotFound] If the element can't be found before time expires
#
def find(*args)
query = Capybara::Queries::SelectorQuery.new(*args)
def find(*args, &optional_filter_block)
query = Capybara::Queries::SelectorQuery.new(*args, &optional_filter_block)
synchronize(query.wait) do
if (query.match == :smart or query.match == :prefer_exact) and query.supports_exact?
result = query.resolve_for(self, true)
@ -74,9 +74,9 @@ module Capybara
# @return [Capybara::Node::Element] The found element
#
def find_field(locator=nil, options={})
def find_field(locator=nil, options={}, &optional_filter_block)
locator, options = nil, locator if locator.is_a? Hash
find(:field, locator, options)
find(:field, locator, options, &optional_filter_block)
end
alias_method :field_labeled, :find_field
@ -96,9 +96,9 @@ module Capybara
# @option options [String, Array<String>] class Match links that match the class(es) provided
# @return [Capybara::Node::Element] The found element
#
def find_link(locator=nil, options={})
def find_link(locator=nil, options={}, &optional_filter_block)
locator, options = nil, locator if locator.is_a? Hash
find(:link, locator, options)
find(:link, locator, options, &optional_filter_block)
end
##
@ -125,9 +125,9 @@ module Capybara
# @option options [String, Array<String>] class Match links that match the class(es) provided
# @return [Capybara::Node::Element] The found element
#
def find_button(locator=nil, options={})
def find_button(locator=nil, options={}, &optional_filter_block)
locator, options = nil, locator if locator.is_a? Hash
find(:button, locator, options)
find(:button, locator, options, &optional_filter_block)
end
##
@ -140,11 +140,12 @@ module Capybara
#
# @return [Capybara::Node::Element] The found element
#
def find_by_id(id, options={})
find(:id, id, options)
def find_by_id(id, options={}, &optional_filter_block)
find(:id, id, options, &optional_filter_block)
end
##
# @!method all([kind = Capybara.default_selector], locator = nil, options = {})
#
# Find all elements on the page matching the given selector
# and options.
@ -184,26 +185,29 @@ module Capybara
# See {Capybara::Helpers#matches_count?} for additional information about
# count matching.
#
# @overload all([kind], locator, options)
# @param [:css, :xpath] kind The type of selector
# @param [String] locator The selector
# @option options [String, Regexp] text Only find elements which contain this text or match this regexp
# @option options [Boolean, Symbol] visible Only find elements with the specified visibility:
# @param [Symbol] kind Optional selector type (:css, :xpath, :field, etc.) - Defaults to Capybara.default_selector
# @param [String] locator The selector
# @option options [String, Regexp] text Only find elements which contain this text or match this regexp
# @option options [Boolean, Symbol] visible Only find elements with the specified visibility:
# * true - only finds visible elements.
# * false - finds invisible _and_ visible elements.
# * :all - same as false; finds visible and invisible elements.
# * :hidden - only finds invisible elements.
# * :visible - same as true; only finds visible elements.
# @option options [Integer] count Exact number of matches that are expected to be found
# @option options [Integer] maximum Maximum number of matches that are expected to be found
# @option options [Integer] minimum Minimum number of matches that are expected to be found
# @option options [Range] between Number of matches found must be within the given range
# @option options [Boolean] exact Control whether `is` expressions in the given XPath match exactly or partially
# @option options [Integer] wait (Capybara.default_max_wait_time) The time to wait for element count expectations to become true
# @option options [Integer] count Exact number of matches that are expected to be found
# @option options [Integer] maximum Maximum number of matches that are expected to be found
# @option options [Integer] minimum Minimum number of matches that are expected to be found
# @option options [Range] between Number of matches found must be within the given range
# @option options [Boolean] exact Control whether `is` expressions in the given XPath match exactly or partially
# @option options [Integer] wait (Capybara.default_max_wait_time) The time to wait for element count expectations to become true
# @overload all([kind = Capybara.default_selector], locator = nil, options = {})
# @overload all([kind = Capybara.default_selector], locator = nil, options = {}, &filter_block)
# @yieldparam element [Capybara::Node::Element] The element being considered for inclusion in the results
# @yieldreturn [Boolean] Should the element be considered in the results?
# @return [Capybara::Result] A collection of found elements
#
def all(*args)
query = Capybara::Queries::SelectorQuery.new(*args)
def all(*args, &optional_filter_block)
query = Capybara::Queries::SelectorQuery.new(*args, &optional_filter_block)
synchronize(query.wait) do
result = query.resolve_for(self)
raise Capybara::ExpectationNotMet, result.failure_message unless result.matches_count?
@ -227,15 +231,16 @@ module Capybara
# @param [Hash] options Additional options; see {#all}
# @return [Capybara::Node::Element] The found element or nil
#
def first(*args)
def first(*args, &optional_filter_block)
if Capybara.wait_on_first_by_default
options = if args.last.is_a?(Hash) then args.pop.dup else {} end
args.push({minimum: 1}.merge(options))
end
all(*args).first
all(*args, &optional_filter_block).first
rescue Capybara::ExpectationNotMet
nil
end
end
end
end

View File

@ -36,8 +36,8 @@ module Capybara
# @option args [Range] :between (nil) Range of times that should contain number of times text occurs
# @return [Boolean] If the expression exists
#
def has_selector?(*args)
assert_selector(*args)
def has_selector?(*args, &optional_filter_block)
assert_selector(*args, &optional_filter_block)
rescue Capybara::ExpectationNotMet
return false
end
@ -50,12 +50,433 @@ module Capybara
# @param (see Capybara::Node::Finders#has_selector?)
# @return [Boolean]
#
def has_no_selector?(*args)
assert_no_selector(*args)
def has_no_selector?(*args, &optional_filter_block)
assert_no_selector(*args, &optional_filter_block)
rescue Capybara::ExpectationNotMet
return false
end
##
#
# Asserts that a given selector is on the page or a descendant of the current node.
#
# page.assert_selector('p#foo')
# page.assert_selector(:xpath, './/p[@id="foo"]')
# page.assert_selector(:foo)
#
# By default it will check if the expression occurs at least once,
# but a different number can be specified.
#
# page.assert_selector('p#foo', :count => 4)
#
# This will check if the expression occurs exactly 4 times. See
# {Capybara::Node::Finders#all} for other available result size options.
#
# If a :count of 0 is specified, it will behave like {#assert_no_selector};
# however, use of that method is preferred over this one.
#
# It also accepts all options that {Capybara::Node::Finders#all} accepts,
# such as :text and :visible.
#
# page.assert_selector('li', :text => 'Horse', :visible => true)
#
# `assert_selector` can also accept XPath expressions generated by the
# XPath gem:
#
# page.assert_selector(:xpath, XPath.descendant(:p))
#
# @param (see Capybara::Node::Finders#all)
# @option options [Integer] :count (nil) Number of times the expression should occur
# @raise [Capybara::ExpectationNotMet] If the selector does not exist
#
def assert_selector(*args, &optional_filter_block)
query = Capybara::Queries::SelectorQuery.new(*args, &optional_filter_block)
synchronize(query.wait) do
result = query.resolve_for(self)
unless result.matches_count? && ((!result.empty?) || query.expects_none?)
raise Capybara::ExpectationNotMet, result.failure_message
end
end
return true
end
##
#
# Asserts that a given selector is not on the page or a descendant of the current node.
# Usage is identical to Capybara::Node::Matchers#assert_selector
#
# Query options such as :count, :minimum, :maximum, and :between are
# considered to be an integral part of the selector. This will return
# true, for example, if a page contains 4 anchors but the query expects 5:
#
# page.assert_no_selector('a', :minimum => 1) # Found, raises Capybara::ExpectationNotMet
# page.assert_no_selector('a', :count => 4) # Found, raises Capybara::ExpectationNotMet
# page.assert_no_selector('a', :count => 5) # Not Found, returns true
#
# @param (see Capybara::Node::Finders#assert_selector)
# @raise [Capybara::ExpectationNotMet] If the selector exists
#
def assert_no_selector(*args, &optional_filter_block)
query = Capybara::Queries::SelectorQuery.new(*args, &optional_filter_block)
synchronize(query.wait) do
result = query.resolve_for(self)
if result.matches_count? && ((!result.empty?) || query.expects_none?)
raise Capybara::ExpectationNotMet, result.negative_failure_message
end
end
return true
end
alias_method :refute_selector, :assert_no_selector
##
#
# Checks if a given XPath expression is on the page or a descendant of the current node.
#
# page.has_xpath?('.//p[@id="foo"]')
#
# By default it will check if the expression occurs at least once,
# but a different number can be specified.
#
# page.has_xpath?('.//p[@id="foo"]', :count => 4)
#
# This will check if the expression occurs exactly 4 times.
#
# It also accepts all options that {Capybara::Node::Finders#all} accepts,
# such as :text and :visible.
#
# page.has_xpath?('.//li', :text => 'Horse', :visible => true)
#
# has_xpath? can also accept XPath expressions generate by the
# XPath gem:
#
# xpath = XPath.generate { |x| x.descendant(:p) }
# page.has_xpath?(xpath)
#
# @param [String] path An XPath expression
# @param options (see Capybara::Node::Finders#all)
# @option options [Integer] :count (nil) Number of times the expression should occur
# @return [Boolean] If the expression exists
#
def has_xpath?(path, options={}, &optional_filter_block)
has_selector?(:xpath, path, options, &optional_filter_block)
end
##
#
# Checks if a given XPath expression is not on the page or a descendant of the current node.
# Usage is identical to Capybara::Node::Matchers#has_xpath?
#
# @param (see Capybara::Node::Finders#has_xpath?)
# @return [Boolean]
#
def has_no_xpath?(path, options={}, &optional_filter_block)
has_no_selector?(:xpath, path, options, &optional_filter_block)
end
##
#
# Checks if a given CSS selector is on the page or a descendant of the current node.
#
# page.has_css?('p#foo')
#
# By default it will check if the selector occurs at least once,
# but a different number can be specified.
#
# page.has_css?('p#foo', :count => 4)
#
# This will check if the selector occurs exactly 4 times.
#
# It also accepts all options that {Capybara::Node::Finders#all} accepts,
# such as :text and :visible.
#
# page.has_css?('li', :text => 'Horse', :visible => true)
#
# @param [String] path A CSS selector
# @param options (see Capybara::Node::Finders#all)
# @option options [Integer] :count (nil) Number of times the selector should occur
# @return [Boolean] If the selector exists
#
def has_css?(path, options={}, &optional_filter_block)
has_selector?(:css, path, options, &optional_filter_block)
end
##
#
# Checks if a given CSS selector is not on the page or a descendant of the current node.
# Usage is identical to Capybara::Node::Matchers#has_css?
#
# @param (see Capybara::Node::Finders#has_css?)
# @return [Boolean]
#
def has_no_css?(path, options={}, &optional_filter_block)
has_no_selector?(:css, path, options, &optional_filter_block)
end
##
#
# Checks if the page or current node has a link with the given
# text or id.
#
# @param [String] locator The text or id of a link to check for
# @param options
# @option options [String, Regexp] :href The value the href attribute must be
# @return [Boolean] Whether it exists
#
def has_link?(locator=nil, options={}, &optional_filter_block)
locator, options = nil, locator if locator.is_a? Hash
has_selector?(:link, locator, options, &optional_filter_block)
end
##
#
# Checks if the page or current node has no link with the given
# text or id.
#
# @param (see Capybara::Node::Finders#has_link?)
# @return [Boolean] Whether it doesn't exist
#
def has_no_link?(locator=nil, options={}, &optional_filter_block)
locator, options = nil, locator if locator.is_a? Hash
has_no_selector?(:link, locator, options, &optional_filter_block)
end
##
#
# Checks if the page or current node has a button with the given
# text, value or id.
#
# @param [String] locator The text, value or id of a button to check for
# @return [Boolean] Whether it exists
#
def has_button?(locator=nil, options={}, &optional_filter_block)
locator, options = nil, locator if locator.is_a? Hash
has_selector?(:button, locator, options, &optional_filter_block)
end
##
#
# Checks if the page or current node has no button with the given
# text, value or id.
#
# @param [String] locator The text, value or id of a button to check for
# @return [Boolean] Whether it doesn't exist
#
def has_no_button?(locator=nil, options={}, &optional_filter_block)
locator, options = nil, locator if locator.is_a? Hash
has_no_selector?(:button, locator, options, &optional_filter_block)
end
##
#
# Checks if the page or current node has a form field with the given
# label, name or id.
#
# For text fields and other textual fields, such as textareas and
# HTML5 email/url/etc. fields, it's possible to specify a :with
# option to specify the text the field should contain:
#
# page.has_field?('Name', :with => 'Jonas')
#
# It is also possible to filter by the field type attribute:
#
# page.has_field?('Email', :type => 'email')
#
# Note: 'textarea' and 'select' are valid type values, matching the associated tag names.
#
# @param [String] locator The label, name or id of a field to check for
# @option options [String, Regexp] :with The text content of the field or a Regexp to match
# @option options [String] :type The type attribute of the field
# @return [Boolean] Whether it exists
#
def has_field?(locator=nil, options={}, &optional_filter_block)
locator, options = nil, locator if locator.is_a? Hash
has_selector?(:field, locator, options, &optional_filter_block)
end
##
#
# Checks if the page or current node has no form field with the given
# label, name or id. See {Capybara::Node::Matchers#has_field?}.
#
# @param [String] locator The label, name or id of a field to check for
# @option options [String, Regexp] :with The text content of the field or a Regexp to match
# @option options [String] :type The type attribute of the field
# @return [Boolean] Whether it doesn't exist
#
def has_no_field?(locator=nil, options={}, &optional_filter_block)
locator, options = nil, locator if locator.is_a? Hash
has_no_selector?(:field, locator, options, &optional_filter_block)
end
##
#
# Checks if the page or current node has a radio button or
# checkbox with the given label, value or id, that is currently
# checked.
#
# @param [String] locator The label, name or id of a checked field
# @return [Boolean] Whether it exists
#
def has_checked_field?(locator=nil, options={}, &optional_filter_block)
locator, options = nil, locator if locator.is_a? Hash
has_selector?(:field, locator, options.merge(:checked => true), &optional_filter_block)
end
##
#
# Checks if the page or current node has no radio button or
# checkbox with the given label, value or id, that is currently
# checked.
#
# @param [String] locator The label, name or id of a checked field
# @return [Boolean] Whether it doesn't exist
#
def has_no_checked_field?(locator=nil, options={}, &optional_filter_block)
locator, options = nil, locator if locator.is_a? Hash
has_no_selector?(:field, locator, options.merge(:checked => true))
end
##
#
# Checks if the page or current node has a radio button or
# checkbox with the given label, value or id, that is currently
# unchecked.
#
# @param [String] locator The label, name or id of an unchecked field
# @return [Boolean] Whether it exists
#
def has_unchecked_field?(locator=nil, options={}, &optional_filter_block)
locator, options = nil, locator if locator.is_a? Hash
has_selector?(:field, locator, options.merge(:unchecked => true), &optional_filter_block)
end
##
#
# Checks if the page or current node has no radio button or
# checkbox with the given label, value or id, that is currently
# unchecked.
#
# @param [String] locator The label, name or id of an unchecked field
# @return [Boolean] Whether it doesn't exist
#
def has_no_unchecked_field?(locator=nil, options={}, &optional_filter_block)
locator, options = nil, locator if locator.is_a? Hash
has_no_selector?(:field, locator, options.merge(:unchecked => true), &optional_filter_block)
end
##
#
# Checks if the page or current node has a select field with the
# given label, name or id.
#
# It can be specified which option should currently be selected:
#
# page.has_select?('Language', :selected => 'German')
#
# For multiple select boxes, several options may be specified:
#
# page.has_select?('Language', :selected => ['English', 'German'])
#
# It's also possible to check if the exact set of options exists for
# this select box:
#
# page.has_select?('Language', :options => ['English', 'German', 'Spanish'])
#
# You can also check for a partial set of options:
#
# page.has_select?('Language', :with_options => ['English', 'German'])
#
# @param [String] locator The label, name or id of a select box
# @option options [Array] :options Options which should be contained in this select box
# @option options [Array] :with_options Partial set of options which should be contained in this select box
# @option options [String, Array] :selected Options which should be selected
# @return [Boolean] Whether it exists
#
def has_select?(locator=nil, options={}, &optional_filter_block)
locator, options = nil, locator if locator.is_a? Hash
has_selector?(:select, locator, options, &optional_filter_block)
end
##
#
# Checks if the page or current node has no select field with the
# given label, name or id. See {Capybara::Node::Matchers#has_select?}.
#
# @param (see Capybara::Node::Matchers#has_select?)
# @return [Boolean] Whether it doesn't exist
#
def has_no_select?(locator=nil, options={}, &optional_filter_block)
locator, options = nil, locator if locator.is_a? Hash
has_no_selector?(:select, locator, options, &optional_filter_block)
end
##
#
# Checks if the page or current node has a table with the given id
# or caption:
#
# page.has_table?('People')
#
# @param [String] locator The id or caption of a table
# @return [Boolean] Whether it exist
#
def has_table?(locator=nil, options={}, &optional_filter_block)
locator, options = nil, locator if locator.is_a? Hash
has_selector?(:table, locator, options, &optional_filter_block)
end
##
#
# Checks if the page or current node has no table with the given id
# or caption. See {Capybara::Node::Matchers#has_table?}.
#
# @param (see Capybara::Node::Matchers#has_table?)
# @return [Boolean] Whether it doesn't exist
#
def has_no_table?(locator=nil, options={}, &optional_filter_block)
locator, options = nil, locator if locator.is_a? Hash
has_no_selector?(:table, locator, options, &optional_filter_block)
end
##
#
# Asserts that the current_node matches a given selector
#
# node.assert_matches_selector('p#foo')
# node.assert_matches_selector(:xpath, '//p[@id="foo"]')
# node.assert_matches_selector(:foo)
#
# It also accepts all options that {Capybara::Node::Finders#all} accepts,
# such as :text and :visible.
#
# node.assert_matches_selector('li', :text => 'Horse', :visible => true)
#
# @param (see Capybara::Node::Finders#all)
# @raise [Capybara::ExpectationNotMet] If the selector does not match
#
def assert_matches_selector(*args)
query = Capybara::Queries::MatchQuery.new(*args)
synchronize(query.wait) do
result = query.resolve_for(self.query_scope)
unless result.include? self
raise Capybara::ExpectationNotMet, "Item does not match the provided selector"
end
end
return true
end
def assert_not_matches_selector(*args)
query = Capybara::Queries::MatchQuery.new(*args)
synchronize(query.wait) do
result = query.resolve_for(self.query_scope)
if result.include? self
raise Capybara::ExpectationNotMet, 'Item matched the provided selector'
end
end
return true
end
alias_method :refute_matches_selector, :assert_not_matches_selector
##
#
# Checks if the current node matches given selector
@ -127,426 +548,6 @@ module Capybara
not_matches_selector?(:css, css, options)
end
##
#
# Asserts that a given selector is on the page or a descendant of the current node.
#
# page.assert_selector('p#foo')
# page.assert_selector(:xpath, './/p[@id="foo"]')
# page.assert_selector(:foo)
#
# By default it will check if the expression occurs at least once,
# but a different number can be specified.
#
# page.assert_selector('p#foo', :count => 4)
#
# This will check if the expression occurs exactly 4 times. See
# {Capybara::Node::Finders#all} for other available result size options.
#
# If a :count of 0 is specified, it will behave like {#assert_no_selector};
# however, use of that method is preferred over this one.
#
# It also accepts all options that {Capybara::Node::Finders#all} accepts,
# such as :text and :visible.
#
# page.assert_selector('li', :text => 'Horse', :visible => true)
#
# `assert_selector` can also accept XPath expressions generated by the
# XPath gem:
#
# page.assert_selector(:xpath, XPath.descendant(:p))
#
# @param (see Capybara::Node::Finders#all)
# @option options [Integer] :count (nil) Number of times the expression should occur
# @raise [Capybara::ExpectationNotMet] If the selector does not exist
#
def assert_selector(*args)
query = Capybara::Queries::SelectorQuery.new(*args)
synchronize(query.wait) do
result = query.resolve_for(self)
unless result.matches_count? && ((!result.empty?) || query.expects_none?)
raise Capybara::ExpectationNotMet, result.failure_message
end
end
return true
end
##
#
# Asserts that a given selector is not on the page or a descendant of the current node.
# Usage is identical to Capybara::Node::Matchers#assert_selector
#
# Query options such as :count, :minimum, :maximum, and :between are
# considered to be an integral part of the selector. This will return
# true, for example, if a page contains 4 anchors but the query expects 5:
#
# page.assert_no_selector('a', :minimum => 1) # Found, raises Capybara::ExpectationNotMet
# page.assert_no_selector('a', :count => 4) # Found, raises Capybara::ExpectationNotMet
# page.assert_no_selector('a', :count => 5) # Not Found, returns true
#
# @param (see Capybara::Node::Finders#assert_selector)
# @raise [Capybara::ExpectationNotMet] If the selector exists
#
def assert_no_selector(*args)
query = Capybara::Queries::SelectorQuery.new(*args)
synchronize(query.wait) do
result = query.resolve_for(self)
if result.matches_count? && ((!result.empty?) || query.expects_none?)
raise Capybara::ExpectationNotMet, result.negative_failure_message
end
end
return true
end
alias_method :refute_selector, :assert_no_selector
##
#
# Asserts that the current_node matches a given selector
#
# node.assert_matches_selector('p#foo')
# node.assert_matches_selector(:xpath, '//p[@id="foo"]')
# node.assert_matches_selector(:foo)
#
# It also accepts all options that {Capybara::Node::Finders#all} accepts,
# such as :text and :visible.
#
# node.assert_matches_selector('li', :text => 'Horse', :visible => true)
#
# @param (see Capybara::Node::Finders#all)
# @raise [Capybara::ExpectationNotMet] If the selector does not match
#
def assert_matches_selector(*args)
query = Capybara::Queries::MatchQuery.new(*args)
synchronize(query.wait) do
result = query.resolve_for(self.query_scope)
unless result.include? self
raise Capybara::ExpectationNotMet, "Item does not match the provided selector"
end
end
return true
end
def assert_not_matches_selector(*args)
query = Capybara::Queries::MatchQuery.new(*args)
synchronize(query.wait) do
result = query.resolve_for(self.query_scope)
if result.include? self
raise Capybara::ExpectationNotMet, 'Item matched the provided selector'
end
end
return true
end
alias_method :refute_matches_selector, :assert_not_matches_selector
##
#
# Checks if a given XPath expression is on the page or a descendant of the current node.
#
# page.has_xpath?('.//p[@id="foo"]')
#
# By default it will check if the expression occurs at least once,
# but a different number can be specified.
#
# page.has_xpath?('.//p[@id="foo"]', :count => 4)
#
# This will check if the expression occurs exactly 4 times.
#
# It also accepts all options that {Capybara::Node::Finders#all} accepts,
# such as :text and :visible.
#
# page.has_xpath?('.//li', :text => 'Horse', :visible => true)
#
# has_xpath? can also accept XPath expressions generate by the
# XPath gem:
#
# xpath = XPath.generate { |x| x.descendant(:p) }
# page.has_xpath?(xpath)
#
# @param [String] path An XPath expression
# @param options (see Capybara::Node::Finders#all)
# @option options [Integer] :count (nil) Number of times the expression should occur
# @return [Boolean] If the expression exists
#
def has_xpath?(path, options={})
has_selector?(:xpath, path, options)
end
##
#
# Checks if a given XPath expression is not on the page or a descendant of the current node.
# Usage is identical to Capybara::Node::Matchers#has_xpath?
#
# @param (see Capybara::Node::Finders#has_xpath?)
# @return [Boolean]
#
def has_no_xpath?(path, options={})
has_no_selector?(:xpath, path, options)
end
##
#
# Checks if a given CSS selector is on the page or a descendant of the current node.
#
# page.has_css?('p#foo')
#
# By default it will check if the selector occurs at least once,
# but a different number can be specified.
#
# page.has_css?('p#foo', :count => 4)
#
# This will check if the selector occurs exactly 4 times.
#
# It also accepts all options that {Capybara::Node::Finders#all} accepts,
# such as :text and :visible.
#
# page.has_css?('li', :text => 'Horse', :visible => true)
#
# @param [String] path A CSS selector
# @param options (see Capybara::Node::Finders#all)
# @option options [Integer] :count (nil) Number of times the selector should occur
# @return [Boolean] If the selector exists
#
def has_css?(path, options={})
has_selector?(:css, path, options)
end
##
#
# Checks if a given CSS selector is not on the page or a descendant of the current node.
# Usage is identical to Capybara::Node::Matchers#has_css?
#
# @param (see Capybara::Node::Finders#has_css?)
# @return [Boolean]
#
def has_no_css?(path, options={})
has_no_selector?(:css, path, options)
end
##
#
# Checks if the page or current node has a link with the given
# text or id.
#
# @param [String] locator The text or id of a link to check for
# @param options
# @option options [String, Regexp] :href The value the href attribute must be
# @return [Boolean] Whether it exists
#
def has_link?(locator=nil, options={})
locator, options = nil, locator if locator.is_a? Hash
has_selector?(:link, locator, options)
end
##
#
# Checks if the page or current node has no link with the given
# text or id.
#
# @param (see Capybara::Node::Finders#has_link?)
# @return [Boolean] Whether it doesn't exist
#
def has_no_link?(locator=nil, options={})
locator, options = nil, locator if locator.is_a? Hash
has_no_selector?(:link, locator, options)
end
##
#
# Checks if the page or current node has a button with the given
# text, value or id.
#
# @param [String] locator The text, value or id of a button to check for
# @return [Boolean] Whether it exists
#
def has_button?(locator=nil, options={})
locator, options = nil, locator if locator.is_a? Hash
has_selector?(:button, locator, options)
end
##
#
# Checks if the page or current node has no button with the given
# text, value or id.
#
# @param [String] locator The text, value or id of a button to check for
# @return [Boolean] Whether it doesn't exist
#
def has_no_button?(locator=nil, options={})
locator, options = nil, locator if locator.is_a? Hash
has_no_selector?(:button, locator, options)
end
##
#
# Checks if the page or current node has a form field with the given
# label, name or id.
#
# For text fields and other textual fields, such as textareas and
# HTML5 email/url/etc. fields, it's possible to specify a :with
# option to specify the text the field should contain:
#
# page.has_field?('Name', :with => 'Jonas')
#
# It is also possible to filter by the field type attribute:
#
# page.has_field?('Email', :type => 'email')
#
# Note: 'textarea' and 'select' are valid type values, matching the associated tag names.
#
# @param [String] locator The label, name or id of a field to check for
# @option options [String, Regexp] :with The text content of the field or a Regexp to match
# @option options [String] :type The type attribute of the field
# @return [Boolean] Whether it exists
#
def has_field?(locator=nil, options={})
locator, options = nil, locator if locator.is_a? Hash
has_selector?(:field, locator, options)
end
##
#
# Checks if the page or current node has no form field with the given
# label, name or id. See {Capybara::Node::Matchers#has_field?}.
#
# @param [String] locator The label, name or id of a field to check for
# @option options [String, Regexp] :with The text content of the field or a Regexp to match
# @option options [String] :type The type attribute of the field
# @return [Boolean] Whether it doesn't exist
#
def has_no_field?(locator=nil, options={})
locator, options = nil, locator if locator.is_a? Hash
has_no_selector?(:field, locator, options)
end
##
#
# Checks if the page or current node has a radio button or
# checkbox with the given label, value or id, that is currently
# checked.
#
# @param [String] locator The label, name or id of a checked field
# @return [Boolean] Whether it exists
#
def has_checked_field?(locator=nil, options={})
locator, options = nil, locator if locator.is_a? Hash
has_selector?(:field, locator, options.merge(:checked => true))
end
##
#
# Checks if the page or current node has no radio button or
# checkbox with the given label, value or id, that is currently
# checked.
#
# @param [String] locator The label, name or id of a checked field
# @return [Boolean] Whether it doesn't exist
#
def has_no_checked_field?(locator=nil, options={})
locator, options = nil, locator if locator.is_a? Hash
has_no_selector?(:field, locator, options.merge(:checked => true))
end
##
#
# Checks if the page or current node has a radio button or
# checkbox with the given label, value or id, that is currently
# unchecked.
#
# @param [String] locator The label, name or id of an unchecked field
# @return [Boolean] Whether it exists
#
def has_unchecked_field?(locator=nil, options={})
locator, options = nil, locator if locator.is_a? Hash
has_selector?(:field, locator, options.merge(:unchecked => true))
end
##
#
# Checks if the page or current node has no radio button or
# checkbox with the given label, value or id, that is currently
# unchecked.
#
# @param [String] locator The label, name or id of an unchecked field
# @return [Boolean] Whether it doesn't exist
#
def has_no_unchecked_field?(locator=nil, options={})
locator, options = nil, locator if locator.is_a? Hash
has_no_selector?(:field, locator, options.merge(:unchecked => true))
end
##
#
# Checks if the page or current node has a select field with the
# given label, name or id.
#
# It can be specified which option should currently be selected:
#
# page.has_select?('Language', :selected => 'German')
#
# For multiple select boxes, several options may be specified:
#
# page.has_select?('Language', :selected => ['English', 'German'])
#
# It's also possible to check if the exact set of options exists for
# this select box:
#
# page.has_select?('Language', :options => ['English', 'German', 'Spanish'])
#
# You can also check for a partial set of options:
#
# page.has_select?('Language', :with_options => ['English', 'German'])
#
# @param [String] locator The label, name or id of a select box
# @option options [Array] :options Options which should be contained in this select box
# @option options [Array] :with_options Partial set of options which should be contained in this select box
# @option options [String, Array] :selected Options which should be selected
# @return [Boolean] Whether it exists
#
def has_select?(locator=nil, options={})
locator, options = nil, locator if locator.is_a? Hash
has_selector?(:select, locator, options)
end
##
#
# Checks if the page or current node has no select field with the
# given label, name or id. See {Capybara::Node::Matchers#has_select?}.
#
# @param (see Capybara::Node::Matchers#has_select?)
# @return [Boolean] Whether it doesn't exist
#
def has_no_select?(locator=nil, options={})
locator, options = nil, locator if locator.is_a? Hash
has_no_selector?(:select, locator, options)
end
##
#
# Checks if the page or current node has a table with the given id
# or caption:
#
# page.has_table?('People')
#
# @param [String] locator The id or caption of a table
# @return [Boolean] Whether it exist
#
def has_table?(locator=nil, options={})
locator, options = nil, locator if locator.is_a? Hash
has_selector?(:table, locator, options)
end
##
#
# Checks if the page or current node has no table with the given id
# or caption. See {Capybara::Node::Matchers#has_table?}.
#
# @param (see Capybara::Node::Matchers#has_table?)
# @return [Boolean] Whether it doesn't exist
#
def has_no_table?(locator=nil, options={})
locator, options = nil, locator if locator.is_a? Hash
has_no_selector?(:table, locator, options)
end
##
# Asserts that the page or current node has the given text content,
@ -643,6 +644,7 @@ module Capybara
def ==(other)
self.eql?(other) || (other.respond_to?(:base) && base == other.base)
end
end
end
end

View File

@ -7,8 +7,9 @@ module Capybara
VALID_KEYS = COUNT_KEYS + [:text, :id, :class, :visible, :exact, :match, :wait, :filter_set]
VALID_MATCH = [:first, :smart, :prefer_exact, :one]
def initialize(*args)
def initialize(*args, &filter_block)
@options = if args.last.is_a?(Hash) then args.pop.dup else {} end
@filter_block = filter_block
if args[0].is_a?(Symbol)
@selector = Selector.all.fetch(args.shift) do |selector_type|
@ -45,6 +46,7 @@ module Capybara
@description << " with id #{options[:id]}" if options[:id]
@description << " with classes #{Array(options[:class]).join(',')}]" if options[:class]
@description << selector.description(options)
@description << " that also matches the custom filter block" if @filter_block
@description
end
@ -59,7 +61,7 @@ module Capybara
when :hidden then return false if node.visible?
end
query_filters.all? do |name, filter|
res = query_filters.all? do |name, filter|
if options.has_key?(name)
filter.matches?(node, options[name])
elsif filter.default?
@ -68,6 +70,9 @@ module Capybara
true
end
end
res &&= @filter_block.call(node) unless @filter_block.nil?
res
end
def visible

View File

@ -16,8 +16,9 @@ module Capybara
class HaveSelector < Matcher
attr_reader :failure_message, :failure_message_when_negated
def initialize(*args)
def initialize(*args, &filter_block)
@args = args
@filter_block = filter_block
end
def matches?(actual)
@ -39,7 +40,7 @@ module Capybara
end
def query
@query ||= Capybara::Queries::SelectorQuery.new(*@args)
@query ||= Capybara::Queries::SelectorQuery.new(*@args, &@filter_block)
end
# RSpec 2 compatibility:
@ -221,8 +222,8 @@ module Capybara
alias_method :failure_message_for_should_not, :failure_message_when_negated
end
def have_selector(*args)
HaveSelector.new(*args)
def have_selector(*args, &optional_filter_block)
HaveSelector.new(*args, &optional_filter_block)
end
def match_selector(*args)
@ -233,16 +234,16 @@ module Capybara
::RSpec::Matchers.define_negated_matcher :not_match_selector, :match_selector if defined?(::RSpec::Expectations::Version) && (Gem::Version.new(RSpec::Expectations::Version::STRING) >= Gem::Version.new('3.1'))
def have_xpath(xpath, options={})
HaveSelector.new(:xpath, xpath, options)
def have_xpath(xpath, options={}, &optional_filter_block)
HaveSelector.new(:xpath, xpath, options, &optional_filter_block)
end
def match_xpath(xpath, options={})
MatchSelector.new(:xpath, xpath, options)
end
def have_css(css, options={})
HaveSelector.new(:css, css, options)
def have_css(css, options={}, &optional_filter_block)
HaveSelector.new(:css, css, options, &optional_filter_block)
end
def match_css(css, options={})
@ -262,39 +263,39 @@ module Capybara
HaveCurrentPath.new(path, options)
end
def have_link(locator=nil, options={})
def have_link(locator=nil, options={}, &optional_filter_block)
locator, options = nil, locator if locator.is_a? Hash
HaveSelector.new(:link, locator, options)
HaveSelector.new(:link, locator, options, &optional_filter_block)
end
def have_button(locator=nil, options={})
def have_button(locator=nil, options={}, &optional_filter_block)
locator, options = nil, locator if locator.is_a? Hash
HaveSelector.new(:button, locator, options)
HaveSelector.new(:button, locator, options, &optional_filter_block)
end
def have_field(locator=nil, options={})
def have_field(locator=nil, options={}, &optional_filter_block)
locator, options = nil, locator if locator.is_a? Hash
HaveSelector.new(:field, locator, options)
HaveSelector.new(:field, locator, options, &optional_filter_block)
end
def have_checked_field(locator=nil, options={})
def have_checked_field(locator=nil, options={}, &optional_filter_block)
locator, options = nil, locator if locator.is_a? Hash
HaveSelector.new(:field, locator, options.merge(:checked => true))
HaveSelector.new(:field, locator, options.merge(:checked => true), &optional_filter_block)
end
def have_unchecked_field(locator=nil, options={})
def have_unchecked_field(locator=nil, options={}, &optional_filter_block)
locator, options = nil, locator if locator.is_a? Hash
HaveSelector.new(:field, locator, options.merge(:unchecked => true))
HaveSelector.new(:field, locator, options.merge(:unchecked => true), &optional_filter_block)
end
def have_select(locator=nil, options={})
def have_select(locator=nil, options={}, &optional_filter_block)
locator, options = nil, locator if locator.is_a? Hash
HaveSelector.new(:select, locator, options)
HaveSelector.new(:select, locator, options, &optional_filter_block)
end
def have_table(locator=nil, options={})
def have_table(locator=nil, options={}, &optional_filter_block)
locator, options = nil, locator if locator.is_a? Hash
HaveSelector.new(:table, locator, options)
HaveSelector.new(:table, locator, options, &optional_filter_block)
end
##
@ -306,5 +307,6 @@ module Capybara
def become_closed(options = {})
BecomeClosed.new(options)
end
end
end

View File

@ -8,7 +8,6 @@ Capybara::SpecHelper.spec '#find_field' do
expect(@session.find_field('Dog').value).to eq('dog')
expect(@session.find_field('form_description').text).to eq('Descriptive text goes here')
expect(@session.find_field('Region')[:name]).to eq('form[region]')
end
context "aria_label attribute with Capybara.enable_aria_label" do
@ -107,4 +106,9 @@ Capybara::SpecHelper.spec '#find_field' do
expect(@session.find_field(with: 'dog')['id']).to eq "form_pets_dog"
end
end
it "should accept an optional filter block" do
# this would be better done with the :with option but this is just a test
expect(@session.find_field('form[pets][]'){ |node| node.value == 'dog' }[:id]).to eq "form_pets_dog"
end
end

View File

@ -399,6 +399,10 @@ Capybara::SpecHelper.spec '#find' do
end
end
it "supports a custom filter block" do
expect(@session.find(:css, 'input'){|node| node.disabled? }[:name]).to eq('disabled_text')
end
context "within a scope" do
before do
@session.visit('/with_scope')