diff --git a/lib/capybara/node/finders.rb b/lib/capybara/node/finders.rb index eb7ee606..7b582373 100644 --- a/lib/capybara/node/finders.rb +++ b/lib/capybara/node/finders.rb @@ -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] 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] 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 diff --git a/lib/capybara/node/matchers.rb b/lib/capybara/node/matchers.rb index a5443b5c..e17c53cf 100644 --- a/lib/capybara/node/matchers.rb +++ b/lib/capybara/node/matchers.rb @@ -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 diff --git a/lib/capybara/queries/selector_query.rb b/lib/capybara/queries/selector_query.rb index 9061ef49..3ce97b5d 100644 --- a/lib/capybara/queries/selector_query.rb +++ b/lib/capybara/queries/selector_query.rb @@ -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 diff --git a/lib/capybara/rspec/matchers.rb b/lib/capybara/rspec/matchers.rb index 5071850a..30386103 100644 --- a/lib/capybara/rspec/matchers.rb +++ b/lib/capybara/rspec/matchers.rb @@ -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 diff --git a/lib/capybara/spec/session/find_field_spec.rb b/lib/capybara/spec/session/find_field_spec.rb index 6d79bd2c..50a36e54 100644 --- a/lib/capybara/spec/session/find_field_spec.rb +++ b/lib/capybara/spec/session/find_field_spec.rb @@ -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 diff --git a/lib/capybara/spec/session/find_spec.rb b/lib/capybara/spec/session/find_spec.rb index b118307a..61ff06d2 100644 --- a/lib/capybara/spec/session/find_spec.rb +++ b/lib/capybara/spec/session/find_spec.rb @@ -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')