2016-03-08 00:52:19 +00:00
# frozen_string_literal: true
2018-01-08 20:23:54 +00:00
2010-07-10 00:20:32 +00:00
module Capybara
2010-11-21 13:37:36 +00:00
module Node
2010-07-10 00:20:32 +00:00
module Finders
2010-07-17 17:05:00 +00:00
##
#
2015-02-26 04:16:38 +00:00
# Find an {Capybara::Node::Element} based on the given arguments. +find+ will raise an error if the element
2012-11-20 05:53:13 +00:00
# is not found.
2010-07-17 17:05:00 +00:00
#
2015-08-25 20:35:43 +00:00
# @!macro waiting_behavior
# If the driver is capable of executing JavaScript, +$0+ will wait for a set amount of time
# and continuously retry finding the element until either the element is found or the time
# expires. The length of time +find+ will wait is controlled through {Capybara.default_max_wait_time}
# and defaults to 2 seconds.
# @option options [false, Numeric] wait (Capybara.default_max_wait_time) Maximum time to wait for matching element to appear.
2010-07-17 17:05:00 +00:00
#
2010-07-19 18:18:16 +00:00
# +find+ takes the same options as +all+.
2010-07-17 17:05:00 +00:00
#
2010-07-19 18:18:16 +00:00
# page.find('#foo').find('.bar')
2016-10-26 19:24:51 +00:00
# page.find(:xpath, './/div[contains(., "bar")]')
2016-10-04 18:10:29 +00:00
# page.find('li', text: 'Quox').click_link('Delete')
2010-07-17 17:05:00 +00:00
#
# @param (see Capybara::Node::Finders#all)
2015-08-25 20:35:43 +00:00
#
2013-02-25 19:34:58 +00:00
# @option options [Boolean] match The matching strategy to use.
2013-02-24 16:47:53 +00:00
#
2015-02-26 04:16:38 +00:00
# @return [Capybara::Node::Element] The found element
2013-02-25 19:34:58 +00:00
# @raise [Capybara::ElementNotFound] If the element can't be found before time expires
2010-07-17 17:05:00 +00:00
#
2018-01-13 00:57:41 +00:00
def find ( * args , ** options , & optional_filter_block )
options [ :session_options ] = session_options
synced_resolve Capybara :: Queries :: SelectorQuery . new ( * args , options , & optional_filter_block )
2017-07-10 21:42:15 +00:00
end
##
#
# Find an {Capybara::Node::Element} based on the given arguments that is also an ancestor of the element called on. +ancestor+ will raise an error if the element
# is not found.
#
# +ancestor+ takes the same options as +find+.
#
# element.ancestor('#foo').find('.bar')
# element.ancestor(:xpath, './/div[contains(., "bar")]')
# element.ancestor('ul', text: 'Quox').click_link('Delete')
#
# @param (see Capybara::Node::Finders#find)
#
# @!macro waiting_behavior
#
# @option options [Boolean] match The matching strategy to use.
#
# @return [Capybara::Node::Element] The found element
# @raise [Capybara::ElementNotFound] If the element can't be found before time expires
#
2018-01-13 00:57:41 +00:00
def ancestor ( * args , ** options , & optional_filter_block )
options [ :session_options ] = session_options
synced_resolve Capybara :: Queries :: AncestorQuery . new ( * args , ** options , & optional_filter_block )
2017-07-10 21:42:15 +00:00
end
##
#
# Find an {Capybara::Node::Element} based on the given arguments that is also a sibling of the element called on. +sibling+ will raise an error if the element
# is not found.
#
#
# +sibling+ takes the same options as +find+.
#
# element.sibling('#foo').find('.bar')
# element.sibling(:xpath, './/div[contains(., "bar")]')
# element.sibling('ul', text: 'Quox').click_link('Delete')
#
# @param (see Capybara::Node::Finders#find)
#
# @macro waiting_behavior
#
# @option options [Boolean] match The matching strategy to use.
#
# @return [Capybara::Node::Element] The found element
# @raise [Capybara::ElementNotFound] If the element can't be found before time expires
#
2018-01-13 00:57:41 +00:00
def sibling ( * args , ** options , & optional_filter_block )
options [ :session_options ] = session_options
synced_resolve Capybara :: Queries :: SiblingQuery . new ( * args , ** options , & optional_filter_block )
2010-07-10 00:20:32 +00:00
end
2010-07-19 18:40:28 +00:00
2010-07-17 17:05:00 +00:00
##
#
# Find a form field on the page. The field can be found by its name, id or label text.
#
2016-04-13 20:50:37 +00:00
# @overload find_field([locator], options={})
# @param [String] locator name, id, placeholder or text of associated label element
#
# @macro waiting_behavior
#
2015-08-25 20:35:43 +00:00
#
2016-07-28 08:25:24 +00:00
# @option options [Boolean] checked Match checked field?
# @option options [Boolean] unchecked Match unchecked field?
2016-04-13 20:50:37 +00:00
# @option options [Boolean, Symbol] disabled (false) Match disabled field?
# * true - only finds a disabled field
# * false - only finds an enabled field
# * :all - finds either an enabled or disabled field
2016-07-28 08:25:24 +00:00
# @option options [Boolean] readonly Match readonly field?
# @option options [String, Regexp] with Value of field to match on
# @option options [String] type Type of field to match on
2016-04-14 18:22:51 +00:00
# @option options [Boolean] multiple Match fields that can have multiple values?
2016-04-27 05:44:48 +00:00
# @option options [String] id Match fields that match the id attribute
# @option options [String] name Match fields that match the name attribute
# @option options [String] placeholder Match fields that match the placeholder attribute
2016-09-07 07:34:15 +00:00
# @option options [String, Array<String>] Match fields that match the class(es) passed
2015-02-26 04:16:38 +00:00
# @return [Capybara::Node::Element] The found element
2010-07-17 17:05:00 +00:00
#
2016-04-13 20:50:37 +00:00
2018-01-09 22:05:50 +00:00
def find_field ( locator = nil , ** options , & optional_filter_block )
2016-09-23 22:03:36 +00:00
find ( :field , locator , options , & optional_filter_block )
2010-07-10 00:20:32 +00:00
end
2010-07-17 17:05:00 +00:00
##
#
# Find a link on the page. The link can be found by its id or text.
#
2016-04-13 20:50:37 +00:00
# @overload find_link([locator], options={})
# @param [String] locator id, title, text, or alt of enclosed img element
#
# @macro waiting_behavior
2015-08-25 20:35:43 +00:00
#
2016-12-15 18:59:01 +00:00
# @option options [String,Regexp,nil] href Value to match against the links href, if nil finds link placeholders (<a> elements with no href attribute)
2016-09-07 07:34:15 +00:00
# @option options [String] id Match links with the id provided
# @option options [String] title Match links with the title provided
# @option options [String] alt Match links with a contained img element whose alt matches
# @option options [String, Array<String>] class Match links that match the class(es) provided
2015-02-26 04:16:38 +00:00
# @return [Capybara::Node::Element] The found element
2010-07-17 17:05:00 +00:00
#
2018-01-09 22:05:50 +00:00
def find_link ( locator = nil , ** options , & optional_filter_block )
2016-09-23 22:03:36 +00:00
find ( :link , locator , options , & optional_filter_block )
2010-07-10 00:20:32 +00:00
end
2010-07-17 17:05:00 +00:00
##
#
2015-08-25 21:50:44 +00:00
# Find a button on the page.
# This can be any \<input> element of type submit, reset, image, button or it can be a
# \<button> element. All buttons can be found by their id, value, or title. \<button> elements can also be found
# by their text content, and image \<input> elements by their alt attribute
2015-08-25 20:35:43 +00:00
#
2016-04-13 20:50:37 +00:00
# @overload find_button([locator], options={})
# @param [String] locator id, value, title, text content, alt of image
#
# @overload find_button(options={})
#
# @macro waiting_behavior
#
# @option options [Boolean, Symbol] disabled (false) Match disabled button?
# * true - only finds a disabled button
# * false - only finds an enabled button
# * :all - finds either an enabled or disabled button
2016-09-07 07:34:15 +00:00
# @option options [String] id Match buttons with the id provided
# @option options [String] title Match buttons with the title provided
# @option options [String] value Match buttons with the value provided
2017-10-12 16:56:10 +00:00
# @option options [String, Array<String>] class Match buttons that match the class(es) provided
2015-02-26 04:16:38 +00:00
# @return [Capybara::Node::Element] The found element
2010-07-17 17:05:00 +00:00
#
2018-01-09 22:05:50 +00:00
def find_button ( locator = nil , ** options , & optional_filter_block )
2016-09-23 22:03:36 +00:00
find ( :button , locator , options , & optional_filter_block )
2010-07-10 00:20:32 +00:00
end
2010-07-17 17:05:00 +00:00
##
#
# Find a element on the page, given its id.
#
2015-08-25 20:35:43 +00:00
# @macro waiting_behavior
#
2016-04-13 20:50:37 +00:00
# @param [String] id id of element
2015-08-25 20:35:43 +00:00
#
2015-02-26 04:16:38 +00:00
# @return [Capybara::Node::Element] The found element
2010-07-17 17:05:00 +00:00
#
2016-08-17 23:14:39 +00:00
def find_by_id ( id , ** options , & optional_filter_block )
2016-09-23 22:03:36 +00:00
find ( :id , id , options , & optional_filter_block )
2010-07-10 00:20:32 +00:00
end
2010-07-17 17:05:00 +00:00
##
2016-09-23 22:03:36 +00:00
# @!method all([kind = Capybara.default_selector], locator = nil, options = {})
2010-07-17 17:05:00 +00:00
#
# Find all elements on the page matching the given selector
# and options.
#
# Both XPath and CSS expressions are supported, but Capybara
# does not try to automatically distinguish between them. The
# following statements are equivalent:
#
# page.all(:css, 'a#person_123')
2016-10-26 19:24:51 +00:00
# page.all(:xpath, './/a[@id="person_123"]')
2010-07-17 17:05:00 +00:00
#
#
# If the type of selector is left out, Capybara uses
2010-08-27 18:52:06 +00:00
# {Capybara.default_selector}. It's set to :css by default.
2010-07-17 17:05:00 +00:00
#
# page.all("a#person_123")
#
# Capybara.default_selector = :xpath
2016-10-26 19:24:51 +00:00
# page.all('.//a[@id="person_123"]')
2010-07-17 17:05:00 +00:00
#
# The set of found elements can further be restricted by specifying
# options. It's possible to select elements by their text or visibility:
#
2016-10-04 18:10:29 +00:00
# page.all('a', text: 'Home')
# page.all('#menu li', visible: true)
2010-07-17 17:05:00 +00:00
#
2017-11-14 20:14:24 +00:00
# By default Capybara's waiting behavior will wait up to `Capybara.default_max_wait_time`
# seconds for matching elements to be available and then return an empty result if none
# are available. It is possible to set expectations on the number of results located and
# Capybara will raise an exception if the number of elements located don't satisfy the
# specified conditions. The expectations can be set using
2014-01-17 19:00:45 +00:00
#
2016-10-04 18:10:29 +00:00
# page.assert_selector('p#foo', count: 4)
# page.assert_selector('p#foo', maximum: 10)
# page.assert_selector('p#foo', minimum: 1)
# page.assert_selector('p#foo', between: 1..10)
2014-01-17 19:00:45 +00:00
#
# See {Capybara::Helpers#matches_count?} for additional information about
# count matching.
#
2016-09-23 22:03:36 +00:00
# @param [Symbol] kind Optional selector type (:css, :xpath, :field, etc.) - Defaults to Capybara.default_selector
2017-11-14 20:14:24 +00:00
# @param [String] locator The locator for the specified selector
2016-09-23 22:03:36 +00:00
# @option options [String, Regexp] text Only find elements which contain this text or match this regexp
2017-11-14 20:14:24 +00:00
# @option options [String, Boolean] exact_text (Capybara.exact_text) When String the elements contained text must match exactly, when Boolean controls whether the :text option must match exactly
2016-09-23 22:03:36 +00:00
# @option options [Boolean, Symbol] visible Only find elements with the specified visibility:
2014-10-22 04:57:30 +00:00
# * 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.
2016-09-23 22:03:36 +00:00
# @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
2017-11-14 20:14:24 +00:00
# @option options [Integer, false] wait (Capybara.default_max_wait_time) The time to wait for matching elements to become available
2016-09-23 22:03:36 +00:00
# @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?
2013-01-28 23:58:15 +00:00
# @return [Capybara::Result] A collection of found elements
2017-11-14 20:14:24 +00:00
# @raise [Capybara::ExpectationNotMet] The number of elements found doesn't match the specified conditions
2017-05-02 01:39:08 +00:00
def all ( * args , ** options , & optional_filter_block )
2018-02-16 23:02:01 +00:00
minimum_specified = options_include_minimum? ( options )
2018-01-09 22:05:50 +00:00
options = { minimum : 1 } . merge ( options ) unless minimum_specified
2017-05-02 01:39:08 +00:00
options [ :session_options ] = session_options
2018-01-12 00:45:50 +00:00
query = Capybara :: Queries :: SelectorQuery . new ( * args . push ( options ) , & optional_filter_block )
result = nil
2017-11-14 20:14:24 +00:00
begin
synchronize ( query . wait ) do
result = query . resolve_for ( self )
raise Capybara :: ExpectationNotMet , result . failure_message unless result . matches_count?
result
end
rescue Capybara :: ExpectationNotMet
raise if minimum_specified || ( result . compare_count == 1 )
Result . new ( [ ] , nil )
2014-01-17 19:00:45 +00:00
end
2010-12-23 03:24:20 +00:00
end
2015-04-23 18:11:57 +00:00
alias_method :find_all , :all
2010-07-10 00:20:32 +00:00
2010-12-23 03:24:20 +00:00
##
#
# Find the first element on the page matching the given selector
2017-11-15 00:43:31 +00:00
# and options. By default `first` will wait up to `Capybara.default_max_wait_time`
# seconds for matching elements to appear and then raise an error if no matching
2018-02-16 23:02:01 +00:00
# element is found, or `nil` if the provided count options allow for empty results.
2010-12-23 03:24:20 +00:00
#
2011-12-28 21:31:49 +00:00
# @overload first([kind], locator, options)
# @param [:css, :xpath] kind The type of selector
# @param [String] locator The selector
2013-03-29 02:15:07 +00:00
# @param [Hash] options Additional options; see {#all}
2015-02-26 04:16:38 +00:00
# @return [Capybara::Node::Element] The found element or nil
2018-02-16 23:02:01 +00:00
# @raise [Capybara::ElementNotFound] If element(s) matching the provided options can't be found before time expires
2010-12-23 03:24:20 +00:00
#
2017-11-15 00:43:31 +00:00
def first ( * args , ** options , & optional_filter_block )
2018-02-16 23:02:01 +00:00
options = { minimum : 1 } . merge ( options ) unless options_include_minimum? ( options )
2016-08-17 23:14:39 +00:00
all ( * args , ** options , & optional_filter_block ) . first
2010-12-23 03:24:20 +00:00
end
2016-09-23 22:03:36 +00:00
2018-01-09 22:05:50 +00:00
private
2017-07-10 21:42:15 +00:00
def synced_resolve ( query )
synchronize ( query . wait ) do
2018-01-12 00:45:50 +00:00
if prefer_exact? ( query )
2017-07-10 21:42:15 +00:00
result = query . resolve_for ( self , true )
result = query . resolve_for ( self , false ) if result . empty? && query . supports_exact? && ! query . exact?
else
result = query . resolve_for ( self )
end
2017-09-21 00:08:02 +00:00
2018-01-12 00:45:50 +00:00
raise Capybara :: Ambiguous , " Ambiguous match, found #{ result . size } elements matching #{ query . description } " if ambiguous? ( query , result )
2018-01-09 22:05:50 +00:00
raise Capybara :: ElementNotFound , " Unable to find #{ query . description } " if result . empty?
2017-07-10 21:42:15 +00:00
result . first
end . tap ( & :allow_reload! )
end
2018-01-12 00:45:50 +00:00
def ambiguous? ( query , result )
query . match == :one or query . match == :smart and result . size > 1
end
def prefer_exact? ( query )
query . match == :smart or query . match == :prefer_exact
end
2018-02-16 23:02:01 +00:00
def options_include_minimum? ( opts )
% i [ count minimum between ] . any? { | k | opts . key? ( k ) }
end
2010-07-10 00:20:32 +00:00
end
end
end