lib/capybara/node/finders.rb in capybara-0.4.0 vs lib/capybara/node/finders.rb in capybara-0.4.1.rc

- old
+ new

@@ -1,7 +1,7 @@ module Capybara - class Node + module Node module Finders ## # # Find an {Capybara::Element} based on the given arguments. +find+ will raise an error if the element @@ -22,28 +22,23 @@ # @option options [String] :message An error message in case the element can't be found # @return [Capybara::Element] The found element # @raise [Capybara::ElementNotFound] If the element can't be found before time expires # def find(*args) - node = wait_conditionally_until { all(*args).first } - ensure - options = if args.last.is_a?(Hash) then args.last else {} end - raise Capybara::ElementNotFound, options[:message] || "Unable to find '#{args[1] || args[0]}'" unless node + begin + node = wait_conditionally_until { first(*args) } + rescue TimeoutError + end + unless node + options = if args.last.is_a?(Hash) then args.last else {} end + raise Capybara::ElementNotFound, options[:message] || "Unable to find '#{args[1] || args[0]}'" + end return node end ## # - # @deprecated {#find} now behaves like locate used to. Use {#find} instead. - # - def locate(*args) - Capybara.deprecate("locate", "find") - find(*args) - end - - ## - # # Find a form field on the page. The field can be found by its name, id or label text. # # @param [String] locator Which field to find # @return [Capybara::Element] The found element # @@ -118,33 +113,89 @@ # @option options [String, Regexp] text Only find elements which contain this text or match this regexp # @option options [Boolean] visible Only find elements that are visible on the page # @return [Capybara::Element] The found elements # def all(*args) - options = if args.last.is_a?(Hash) then args.pop else {} end + options = extract_normalized_options(args) - results = Capybara::Selector.normalize(*args).map do |path| - base.find(path) - end.flatten + Capybara::Selector.normalize(*args). + map { |path| find_in_base(path) }.flatten. + select { |node| matches_options(node, options) }. + map { |node| convert_element(node) } + end - if text = options[:text] - text = Regexp.escape(text) unless text.kind_of?(Regexp) + ## + # + # Find the first element on the page matching the given selector + # and options, or nil if no element matches. + # + # When only the first matching element is needed, this method can + # be faster than all(*args).first. + # + # @param [:css, :xpath, String] kind_or_locator Either the kind of selector or the selector itself + # @param [String] locator The selector + # @param [Hash{Symbol => Object}] options Additional options; see {all} + # @return Capybara::Element The found element + # + def first(*args) + options = extract_normalized_options(args) - results = results.select { |node| node.text.match(text) } + Capybara::Selector.normalize(*args).each do |path| + find_in_base(path).each do |node| + if matches_options(node, options) + return convert_element(node) + end + end end - if options[:visible] or Capybara.ignore_hidden_elements - results = results.select { |node| node.visible? } - end - - results.map { |n| Capybara::Element.new(session, n) } + nil end protected + def find_in_base(xpath) + base.find(xpath) + end + + def convert_element(element) + Capybara::Node::Element.new(session, element) + end + def wait_conditionally_until - if driver.wait? then session.wait_until { yield } else yield end + if wait? then session.wait_until { yield } else yield end end + def extract_normalized_options(args) + options = if args.last.is_a?(Hash) then args.pop.dup else {} end + + if text = options[:text] + options[:text] = Regexp.escape(text) unless text.kind_of?(Regexp) + end + + if !options.has_key?(:visible) + options[:visible] = Capybara.ignore_hidden_elements + end + + if selected = options[:selected] + options[:selected] = [selected].flatten + end + + options + end + + def matches_options(node, options) + return false if options[:text] and not node.text.match(options[:text]) + return false if options[:visible] and not node.visible? + return false if options[:with] and not node.value == options[:with] + return false if options[:checked] and not node.checked? + return false if options[:unchecked] and node.checked? + return false if options[:selected] and not has_selected_options?(node, options[:selected]) + true + end + + def has_selected_options?(node, expected) + actual = node.find('.//option').select { |option| option.selected? }.map { |option| option.text } + (expected - actual).empty? + end end end end