capybara 2.15.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. checksums.yaml +5 -5
  2. data/History.md +137 -2
  3. data/README.md +36 -25
  4. data/lib/capybara/config.rb +11 -57
  5. data/lib/capybara/cucumber.rb +2 -3
  6. data/lib/capybara/driver/base.rb +19 -16
  7. data/lib/capybara/driver/node.rb +5 -4
  8. data/lib/capybara/dsl.rb +1 -0
  9. data/lib/capybara/helpers.rb +19 -29
  10. data/lib/capybara/minitest/spec.rb +16 -13
  11. data/lib/capybara/minitest.rb +140 -137
  12. data/lib/capybara/node/actions.rb +68 -89
  13. data/lib/capybara/node/base.rb +11 -18
  14. data/lib/capybara/node/document.rb +2 -2
  15. data/lib/capybara/node/document_matchers.rb +8 -8
  16. data/lib/capybara/node/element.rb +32 -42
  17. data/lib/capybara/node/finders.rb +64 -71
  18. data/lib/capybara/node/matchers.rb +50 -71
  19. data/lib/capybara/node/simple.rb +11 -17
  20. data/lib/capybara/queries/ancestor_query.rb +12 -8
  21. data/lib/capybara/queries/base_query.rb +22 -18
  22. data/lib/capybara/queries/current_path_query.rb +12 -25
  23. data/lib/capybara/queries/match_query.rb +3 -7
  24. data/lib/capybara/queries/selector_query.rb +100 -96
  25. data/lib/capybara/queries/sibling_query.rb +5 -5
  26. data/lib/capybara/queries/text_query.rb +35 -35
  27. data/lib/capybara/queries/title_query.rb +8 -11
  28. data/lib/capybara/rack_test/browser.rb +15 -18
  29. data/lib/capybara/rack_test/css_handlers.rb +6 -4
  30. data/lib/capybara/rack_test/driver.rb +6 -10
  31. data/lib/capybara/rack_test/form.rb +52 -39
  32. data/lib/capybara/rack_test/node.rb +93 -63
  33. data/lib/capybara/rails.rb +2 -6
  34. data/lib/capybara/result.rb +22 -22
  35. data/lib/capybara/rspec/compound.rb +5 -10
  36. data/lib/capybara/rspec/features.rb +17 -48
  37. data/lib/capybara/rspec/matcher_proxies.rb +31 -15
  38. data/lib/capybara/rspec/matchers.rb +116 -58
  39. data/lib/capybara/rspec.rb +5 -10
  40. data/lib/capybara/selector/css.rb +6 -11
  41. data/lib/capybara/selector/filter.rb +1 -17
  42. data/lib/capybara/selector/filter_set.rb +18 -15
  43. data/lib/capybara/selector/filters/base.rb +7 -6
  44. data/lib/capybara/selector/filters/expression_filter.rb +6 -23
  45. data/lib/capybara/selector/filters/node_filter.rb +2 -12
  46. data/lib/capybara/selector/selector.rb +28 -34
  47. data/lib/capybara/selector.rb +129 -117
  48. data/lib/capybara/selenium/driver.rb +172 -163
  49. data/lib/capybara/selenium/node.rb +218 -104
  50. data/lib/capybara/server.rb +3 -2
  51. data/lib/capybara/session/config.rb +47 -59
  52. data/lib/capybara/session/matchers.rb +23 -14
  53. data/lib/capybara/session.rb +175 -229
  54. data/lib/capybara/spec/fixtures/no_extension +1 -0
  55. data/lib/capybara/spec/public/test.js +38 -6
  56. data/lib/capybara/spec/session/accept_alert_spec.rb +1 -0
  57. data/lib/capybara/spec/session/accept_confirm_spec.rb +3 -2
  58. data/lib/capybara/spec/session/accept_prompt_spec.rb +30 -1
  59. data/lib/capybara/spec/session/all_spec.rb +31 -18
  60. data/lib/capybara/spec/session/ancestor_spec.rb +6 -8
  61. data/lib/capybara/spec/session/assert_all_of_selectors_spec.rb +6 -5
  62. data/lib/capybara/spec/session/assert_current_path.rb +12 -11
  63. data/lib/capybara/spec/session/assert_selector.rb +1 -0
  64. data/lib/capybara/spec/session/assert_text.rb +31 -23
  65. data/lib/capybara/spec/session/assert_title.rb +13 -3
  66. data/lib/capybara/spec/session/attach_file_spec.rb +57 -29
  67. data/lib/capybara/spec/session/body_spec.rb +1 -0
  68. data/lib/capybara/spec/session/check_spec.rb +7 -6
  69. data/lib/capybara/spec/session/choose_spec.rb +5 -4
  70. data/lib/capybara/spec/session/click_button_spec.rb +24 -32
  71. data/lib/capybara/spec/session/click_link_or_button_spec.rb +8 -7
  72. data/lib/capybara/spec/session/click_link_spec.rb +8 -7
  73. data/lib/capybara/spec/session/current_scope_spec.rb +4 -3
  74. data/lib/capybara/spec/session/current_url_spec.rb +19 -8
  75. data/lib/capybara/spec/session/dismiss_confirm_spec.rb +1 -1
  76. data/lib/capybara/spec/session/dismiss_prompt_spec.rb +1 -0
  77. data/lib/capybara/spec/session/element/assert_match_selector.rb +1 -1
  78. data/lib/capybara/spec/session/element/match_xpath_spec.rb +1 -1
  79. data/lib/capybara/spec/session/element/matches_selector_spec.rb +5 -5
  80. data/lib/capybara/spec/session/evaluate_async_script_spec.rb +23 -0
  81. data/lib/capybara/spec/session/evaluate_script_spec.rb +5 -4
  82. data/lib/capybara/spec/session/execute_script_spec.rb +4 -3
  83. data/lib/capybara/spec/session/fill_in_spec.rb +30 -5
  84. data/lib/capybara/spec/session/find_button_spec.rb +4 -3
  85. data/lib/capybara/spec/session/find_by_id_spec.rb +2 -1
  86. data/lib/capybara/spec/session/find_field_spec.rb +9 -15
  87. data/lib/capybara/spec/session/find_link_spec.rb +6 -5
  88. data/lib/capybara/spec/session/find_spec.rb +37 -31
  89. data/lib/capybara/spec/session/first_spec.rb +60 -33
  90. data/lib/capybara/spec/session/frame/frame_title_spec.rb +23 -0
  91. data/lib/capybara/spec/session/frame/frame_url_spec.rb +23 -0
  92. data/lib/capybara/spec/session/frame/switch_to_frame_spec.rb +2 -1
  93. data/lib/capybara/spec/session/frame/within_frame_spec.rb +9 -16
  94. data/lib/capybara/spec/session/go_back_spec.rb +1 -0
  95. data/lib/capybara/spec/session/go_forward_spec.rb +1 -0
  96. data/lib/capybara/spec/session/has_all_selectors_spec.rb +69 -0
  97. data/lib/capybara/spec/session/has_button_spec.rb +2 -1
  98. data/lib/capybara/spec/session/has_css_spec.rb +3 -2
  99. data/lib/capybara/spec/session/has_current_path_spec.rb +49 -22
  100. data/lib/capybara/spec/session/has_field_spec.rb +4 -3
  101. data/lib/capybara/spec/session/has_link_spec.rb +5 -4
  102. data/lib/capybara/spec/session/has_none_selectors_spec.rb +76 -0
  103. data/lib/capybara/spec/session/has_select_spec.rb +32 -31
  104. data/lib/capybara/spec/session/has_selector_spec.rb +5 -4
  105. data/lib/capybara/spec/session/has_table_spec.rb +2 -1
  106. data/lib/capybara/spec/session/has_text_spec.rb +9 -13
  107. data/lib/capybara/spec/session/has_title_spec.rb +1 -0
  108. data/lib/capybara/spec/session/has_xpath_spec.rb +1 -0
  109. data/lib/capybara/spec/session/headers.rb +2 -1
  110. data/lib/capybara/spec/session/html_spec.rb +1 -0
  111. data/lib/capybara/spec/session/node_spec.rb +107 -58
  112. data/lib/capybara/spec/session/node_wrapper_spec.rb +36 -0
  113. data/lib/capybara/spec/session/refresh_spec.rb +6 -2
  114. data/lib/capybara/spec/session/reset_session_spec.rb +19 -0
  115. data/lib/capybara/spec/session/response_code.rb +1 -0
  116. data/lib/capybara/spec/session/save_and_open_page_spec.rb +1 -0
  117. data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +6 -11
  118. data/lib/capybara/spec/session/save_page_spec.rb +1 -17
  119. data/lib/capybara/spec/session/save_screenshot_spec.rb +3 -3
  120. data/lib/capybara/spec/session/select_spec.rb +21 -20
  121. data/lib/capybara/spec/session/selectors_spec.rb +2 -2
  122. data/lib/capybara/spec/session/sibling_spec.rb +1 -1
  123. data/lib/capybara/spec/session/text_spec.rb +17 -3
  124. data/lib/capybara/spec/session/title_spec.rb +11 -1
  125. data/lib/capybara/spec/session/uncheck_spec.rb +4 -3
  126. data/lib/capybara/spec/session/unselect_spec.rb +7 -6
  127. data/lib/capybara/spec/session/visit_spec.rb +64 -3
  128. data/lib/capybara/spec/session/window/become_closed_spec.rb +2 -1
  129. data/lib/capybara/spec/session/window/current_window_spec.rb +1 -0
  130. data/lib/capybara/spec/session/window/open_new_window_spec.rb +1 -0
  131. data/lib/capybara/spec/session/window/switch_to_window_spec.rb +2 -1
  132. data/lib/capybara/spec/session/window/window_opened_by_spec.rb +2 -1
  133. data/lib/capybara/spec/session/window/window_spec.rb +12 -12
  134. data/lib/capybara/spec/session/window/windows_spec.rb +2 -3
  135. data/lib/capybara/spec/session/window/within_window_spec.rb +15 -71
  136. data/lib/capybara/spec/session/within_spec.rb +1 -0
  137. data/lib/capybara/spec/spec_helper.rb +36 -18
  138. data/lib/capybara/spec/test_app.rb +17 -9
  139. data/lib/capybara/spec/views/form.erb +7 -0
  140. data/lib/capybara/spec/views/initial_alert.erb +10 -0
  141. data/lib/capybara/spec/views/with_fixed_header_footer.erb +17 -0
  142. data/lib/capybara/spec/views/with_hover.erb +5 -0
  143. data/lib/capybara/spec/views/with_html.erb +27 -1
  144. data/lib/capybara/spec/views/with_js.erb +11 -0
  145. data/lib/capybara/spec/views/within_frames.erb +4 -1
  146. data/lib/capybara/version.rb +2 -1
  147. data/lib/capybara/window.rb +6 -10
  148. data/lib/capybara.rb +29 -26
  149. data/spec/basic_node_spec.rb +1 -0
  150. data/spec/capybara_spec.rb +16 -69
  151. data/spec/dsl_spec.rb +5 -13
  152. data/spec/filter_set_spec.rb +5 -4
  153. data/spec/fixtures/selenium_driver_rspec_failure.rb +2 -1
  154. data/spec/fixtures/selenium_driver_rspec_success.rb +3 -2
  155. data/spec/minitest_spec.rb +13 -4
  156. data/spec/minitest_spec_spec.rb +12 -3
  157. data/spec/per_session_config_spec.rb +9 -8
  158. data/spec/rack_test_spec.rb +21 -20
  159. data/spec/result_spec.rb +17 -16
  160. data/spec/rspec/features_spec.rb +17 -14
  161. data/spec/rspec/scenarios_spec.rb +5 -7
  162. data/spec/rspec/shared_spec_matchers.rb +96 -99
  163. data/spec/rspec/views_spec.rb +2 -1
  164. data/spec/rspec_matchers_spec.rb +18 -2
  165. data/spec/rspec_spec.rb +11 -15
  166. data/spec/selector_spec.rb +5 -6
  167. data/spec/selenium_spec_chrome.rb +20 -11
  168. data/spec/selenium_spec_edge.rb +27 -0
  169. data/spec/selenium_spec_ie.rb +31 -0
  170. data/spec/selenium_spec_marionette.rb +38 -12
  171. data/spec/server_spec.rb +33 -33
  172. data/spec/session_spec.rb +2 -1
  173. data/spec/shared_selenium_session.rb +82 -22
  174. data/spec/spec_helper.rb +3 -6
  175. metadata +76 -81
  176. data/lib/capybara/query.rb +0 -7
  177. data/spec/selenium_spec_firefox.rb +0 -68
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Capybara
3
4
  module Node
4
-
5
5
  ##
6
6
  #
7
7
  # A {Capybara::Node::Simple} is a simpler version of {Capybara::Node::Base} which
@@ -28,7 +28,7 @@ module Capybara
28
28
  #
29
29
  # @return [String] The text of the element
30
30
  #
31
- def text(type=nil)
31
+ def text(_type = nil)
32
32
  native.text
33
33
  end
34
34
 
@@ -45,7 +45,7 @@ module Capybara
45
45
  attr_name = name.to_s
46
46
  if attr_name == 'value'
47
47
  value
48
- elsif 'input' == tag_name and 'checkbox' == native[:type] and 'checked' == attr_name
48
+ elsif tag_name == 'input' and native[:type] == 'checkbox' and attr_name == 'checked'
49
49
  native['checked'] == 'checked'
50
50
  else
51
51
  native[attr_name]
@@ -79,12 +79,12 @@ module Capybara
79
79
  native['_capybara_raw_value']
80
80
  elsif tag_name == 'select'
81
81
  if native['multiple'] == 'multiple'
82
- native.xpath(".//option[@selected='selected']").map { |option| option[:value] || option.content }
82
+ native.xpath(".//option[@selected='selected']").map { |option| option[:value] || option.content }
83
83
  else
84
84
  option = native.xpath(".//option[@selected='selected']").first || native.xpath(".//option").first
85
85
  option[:value] || option.content if option
86
86
  end
87
- elsif tag_name == 'input' && %w(radio checkbox).include?(native[:type])
87
+ elsif tag_name == 'input' && %w[radio checkbox].include?(native[:type])
88
88
  native[:value] || 'on'
89
89
  else
90
90
  native[:value]
@@ -100,14 +100,13 @@ module Capybara
100
100
  # @return [Boolean] Whether the element is visible
101
101
  #
102
102
  def visible?(check_ancestors = true)
103
- return false if (tag_name == 'input') && (native[:type]=="hidden")
103
+ return false if (tag_name == 'input') && (native[:type] == "hidden")
104
104
 
105
105
  if check_ancestors
106
- #check size because oldest supported nokogiri doesnt support xpath boolean() function
107
- native.xpath("./ancestor-or-self::*[contains(@style, 'display:none') or contains(@style, 'display: none') or @hidden or name()='script' or name()='head']").size() == 0
106
+ !native.xpath("boolean(./ancestor-or-self::*[contains(@style, 'display:none') or contains(@style, 'display: none') or @hidden or name()='script' or name()='head'])")
108
107
  else
109
- #no need for an xpath if only checking the current element
110
- !(native.has_attribute?('hidden') || (native[:style] =~ /display:\s?none/) || %w(script head).include?(tag_name))
108
+ # No need for an xpath if only checking the current element
109
+ !(native.has_attribute?('hidden') || (native[:style] =~ /display:\s?none/) || %w[script head].include?(tag_name))
111
110
  end
112
111
  end
113
112
 
@@ -140,7 +139,7 @@ module Capybara
140
139
  native.has_attribute?('selected')
141
140
  end
142
141
 
143
- def synchronize(seconds=nil)
142
+ def synchronize(_seconds = nil)
144
143
  yield # simple nodes don't need to wait
145
144
  end
146
145
 
@@ -152,12 +151,7 @@ module Capybara
152
151
  #
153
152
  # @return [String] The title of the document
154
153
  def title
155
- if native.respond_to? :title
156
- native.title
157
- else
158
- #old versions of nokogiri don't have #title - remove in 3.0
159
- native.xpath('/html/head/title | /html/title').first.text
160
- end
154
+ native.title
161
155
  end
162
156
 
163
157
  def inspect
@@ -1,25 +1,29 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Capybara
3
4
  module Queries
4
- class AncestorQuery < MatchQuery
5
+ class AncestorQuery < Capybara::Queries::SelectorQuery
5
6
  # @api private
6
7
  def resolve_for(node, exact = nil)
7
- @resolved_node = node
8
+ @child_node = node
8
9
  node.synchronize do
9
10
  match_results = super(node.session.current_scope, exact)
10
- node.all(:xpath, XPath.ancestor) do |el|
11
- match_results.include?(el)
12
- end
11
+ node.all(:xpath, XPath.ancestor) { |el| match_results.include?(el) }
13
12
  end
14
13
  end
15
14
 
16
15
  def description
16
+ child_query = @child_node && @child_node.instance_variable_get(:@query)
17
17
  desc = super
18
- if @resolved_node && (child_query = @resolved_node.instance_variable_get(:@query))
19
- desc += " that is an ancestor of #{child_query.description}"
20
- end
18
+ desc += " that is an ancestor of #{child_query.description}" if child_query
21
19
  desc
22
20
  end
21
+
22
+ private
23
+
24
+ def valid_keys
25
+ super - COUNT_KEYS
26
+ end
23
27
  end
24
28
  end
25
29
  end
@@ -1,9 +1,10 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Capybara
3
4
  # @api private
4
5
  module Queries
5
6
  class BaseQuery
6
- COUNT_KEYS = [:count, :minimum, :maximum, :between]
7
+ COUNT_KEYS = %i[count minimum maximum between].freeze
7
8
 
8
9
  attr_reader :options
9
10
  attr_writer :session_options
@@ -20,8 +21,11 @@ module Capybara
20
21
  self.class.wait(options, session_options.default_max_wait_time)
21
22
  end
22
23
 
23
- def self.wait(options, default=Capybara.default_max_wait_time)
24
- options.fetch(:wait, default) || 0
24
+ def self.wait(options, default = Capybara.default_max_wait_time)
25
+ # if no value or nil for the :wait option is passed it should default to the default
26
+ w = options.fetch(:wait, nil)
27
+ w = default if w.nil?
28
+ w || 0
25
29
  end
26
30
 
27
31
  ##
@@ -30,11 +34,7 @@ module Capybara
30
34
  # Returns false if query does not have any count options specified.
31
35
  #
32
36
  def expects_none?
33
- if COUNT_KEYS.any? { |k| options.has_key? k }
34
- matches_count?(0)
35
- else
36
- false
37
- end
37
+ count_specified? ? matches_count?(0) : false
38
38
  end
39
39
 
40
40
  ##
@@ -47,11 +47,11 @@ module Capybara
47
47
  # @param [Integer] count The actual number. Should be coercible via Integer()
48
48
  #
49
49
  def matches_count?(count)
50
- return (Integer(options[:count]) == count) if options[:count]
50
+ return (Integer(options[:count]) == count) if options[:count]
51
51
  return false if options[:maximum] && (Integer(options[:maximum]) < count)
52
52
  return false if options[:minimum] && (Integer(options[:minimum]) > count)
53
- return false if options[:between] && !(options[:between] === count)
54
- return true
53
+ return false if options[:between] && !options[:between].include?(count)
54
+ true
55
55
  end
56
56
 
57
57
  ##
@@ -66,10 +66,14 @@ module Capybara
66
66
  String.new("expected not to find #{description}") << count_message
67
67
  end
68
68
 
69
- private
69
+ private
70
+
71
+ def count_specified?
72
+ COUNT_KEYS.any? { |k| options.key? k }
73
+ end
70
74
 
71
75
  def count_message
72
- message = String.new()
76
+ message = "".dup
73
77
  if options[:count]
74
78
  message << " #{options[:count]} #{Capybara::Helpers.declension('time', 'times', options[:count])}"
75
79
  elsif options[:between]
@@ -84,11 +88,11 @@ module Capybara
84
88
 
85
89
  def assert_valid_keys
86
90
  invalid_keys = @options.keys - valid_keys
87
- unless invalid_keys.empty?
88
- invalid_names = invalid_keys.map(&:inspect).join(", ")
89
- valid_names = valid_keys.map(&:inspect).join(", ")
90
- raise ArgumentError, "invalid keys #{invalid_names}, should be one of #{valid_names}"
91
- end
91
+ return if invalid_keys.empty?
92
+
93
+ invalid_names = invalid_keys.map(&:inspect).join(", ")
94
+ valid_names = valid_keys.map(&:inspect).join(", ")
95
+ raise ArgumentError, "invalid keys #{invalid_names}, should be one of #{valid_names}"
92
96
  end
93
97
  end
94
98
  end
@@ -1,34 +1,28 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'addressable/uri'
3
4
 
4
5
  module Capybara
5
6
  # @api private
6
7
  module Queries
7
8
  class CurrentPathQuery < BaseQuery
8
- def initialize(expected_path, options = {})
9
+ def initialize(expected_path, **options)
9
10
  super(options)
10
11
  @expected_path = expected_path
11
12
  @options = {
12
- url: false,
13
- only_path: false }.merge(options)
13
+ url: !@expected_path.is_a?(Regexp) && !::Addressable::URI.parse(@expected_path || "").hostname.nil?,
14
+ ignore_query: false
15
+ }.merge(options)
14
16
  assert_valid_keys
15
17
  end
16
18
 
17
19
  def resolves_for?(session)
18
- @actual_path = if options[:url]
19
- session.current_url
20
- else
21
- uri = ::Addressable::URI.parse(session.current_url)
22
-
23
- if options[:only_path]
24
- uri.path unless uri.nil? # Ensure the parsed url isn't nil.
25
- else
26
- uri.request_uri unless uri.nil? # Ensure the parsed url isn't nil.
27
- end
28
- end
20
+ uri = ::Addressable::URI.parse(session.current_url)
21
+ uri.query = nil if uri && options[:ignore_query]
22
+ @actual_path = options[:url] ? uri.to_s : uri && uri.request_uri
29
23
 
30
24
  if @expected_path.is_a? Regexp
31
- @actual_path.match(@expected_path)
25
+ @actual_path.to_s.match(@expected_path)
32
26
  else
33
27
  ::Addressable::URI.parse(@expected_path) == ::Addressable::URI.parse(@actual_path)
34
28
  end
@@ -42,22 +36,15 @@ module Capybara
42
36
  failure_message_helper(' not')
43
37
  end
44
38
 
45
- private
39
+ private
46
40
 
47
41
  def failure_message_helper(negated = '')
48
- verb = (@expected_path.is_a?(Regexp))? 'match' : 'equal'
42
+ verb = @expected_path.is_a?(Regexp) ? 'match' : 'equal'
49
43
  "expected #{@actual_path.inspect}#{negated} to #{verb} #{@expected_path.inspect}"
50
44
  end
51
45
 
52
46
  def valid_keys
53
- [:wait, :url, :only_path]
54
- end
55
-
56
- def assert_valid_keys
57
- super
58
- if options[:url] && options[:only_path]
59
- raise ArgumentError, "the :url and :only_path options cannot both be true"
60
- end
47
+ %i[wait url ignore_query]
61
48
  end
62
49
  end
63
50
  end
@@ -2,18 +2,14 @@ module Capybara
2
2
  module Queries
3
3
  class MatchQuery < Capybara::Queries::SelectorQuery
4
4
  def visible
5
- if options.has_key?(:visible)
6
- super
7
- else
8
- :all
9
- end
5
+ options.key?(:visible) ? super : :all
10
6
  end
11
7
 
12
- private
8
+ private
13
9
 
14
10
  def valid_keys
15
11
  super - COUNT_KEYS
16
12
  end
17
13
  end
18
14
  end
19
- end
15
+ end
@@ -1,36 +1,24 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Capybara
3
4
  module Queries
4
5
  class SelectorQuery < Queries::BaseQuery
5
6
  attr_accessor :selector, :locator, :options, :expression, :find, :negative
6
7
 
7
- VALID_KEYS = COUNT_KEYS + [:text, :id, :class, :visible, :exact, :exact_text, :match, :wait, :filter_set]
8
- VALID_MATCH = [:first, :smart, :prefer_exact, :one]
8
+ VALID_KEYS = COUNT_KEYS + %i[text id class visible exact exact_text match wait filter_set]
9
+ VALID_MATCH = %i[first smart prefer_exact one].freeze
9
10
 
10
- def initialize(*args, &filter_block)
11
- @options = if args.last.is_a?(Hash) then args.pop.dup else {} end
11
+ def initialize(*args, session_options:, **options, &filter_block)
12
+ @resolved_node = nil
13
+ @options = options.dup
12
14
  super(@options)
15
+ self.session_options = session_options
13
16
 
17
+ @selector = find_selector(args[0].is_a?(Symbol) ? args.shift : args[0])
18
+ @locator = args.shift
14
19
  @filter_block = filter_block
15
20
 
16
- if args[0].is_a?(Symbol)
17
- @selector = Selector.all.fetch(args.shift) do |selector_type|
18
- raise ArgumentError, "Unknown selector type (:#{selector_type})"
19
- nil
20
- end
21
- @locator = args.shift
22
- else
23
- @selector = Selector.all.values.find { |s| s.match?(args[0]) }
24
- @locator = args.shift
25
- end
26
- @selector ||= Selector.all[session_options.default_selector]
27
-
28
- warn "Unused parameters passed to #{self.class.name} : #{args.to_s}" unless args.empty?
29
-
30
- # for compatibility with Capybara 2.0
31
- if session_options.exact_options and @selector == Selector.all[:option]
32
- @options[:exact] = true
33
- end
21
+ raise ArgumentError, "Unused parameters passed to #{self.class.name} : #{args}" unless args.empty?
34
22
 
35
23
  @expression = @selector.call(@locator, @options.merge(enable_aria_label: session_options.enable_aria_label))
36
24
 
@@ -40,10 +28,10 @@ module Capybara
40
28
  end
41
29
 
42
30
  def name; selector.name; end
43
- def label; selector.label or selector.name; end
31
+ def label; selector.label || selector.name; end
44
32
 
45
33
  def description
46
- @description = String.new()
34
+ @description = "".dup
47
35
  @description << "visible " if visible == :visible
48
36
  @description << "non-visible " if visible == :hidden
49
37
  @description << "#{label} #{locator.inspect}"
@@ -53,84 +41,44 @@ module Capybara
53
41
  @description << " with classes [#{Array(options[:class]).join(',')}]" if options[:class]
54
42
  @description << selector.description(options)
55
43
  @description << " that also matches the custom filter block" if @filter_block
44
+ @description << " within #{@resolved_node.inspect}" if describe_within?
56
45
  @description
57
46
  end
58
47
 
59
48
  def matches_filters?(node)
60
- if options[:text]
61
- regexp = if options[:text].is_a?(Regexp)
62
- options[:text]
63
- else
64
- if exact_text == true
65
- /\A#{Regexp.escape(options[:text].to_s)}\z/
66
- else
67
- Regexp.escape(options[:text].to_s)
68
- end
69
- end
70
- text_visible = visible
71
- text_visible = :all if text_visible == :hidden
72
- return false if not node.text(text_visible).match(regexp)
73
- end
74
-
75
- if exact_text.is_a?(String)
76
- regexp = /\A#{Regexp.escape(options[:exact_text])}\z/
77
- text_visible = visible
78
- text_visible = :all if text_visible == :hidden
79
- return false if not node.text(text_visible).match(regexp)
80
- end
49
+ return false if options[:text] && !matches_text_filter(node, options[:text])
50
+ return false if exact_text.is_a?(String) && !matches_exact_text_filter(node, exact_text)
81
51
 
82
52
  case visible
83
- when :visible then return false unless node.visible?
84
- when :hidden then return false if node.visible?
53
+ when :visible then return false unless node.visible?
54
+ when :hidden then return false if node.visible?
85
55
  end
86
56
 
87
- res = node_filters.all? do |name, filter|
88
- if options.has_key?(name)
89
- filter.matches?(node, options[name])
90
- elsif filter.default?
91
- filter.matches?(node, filter.default)
92
- else
93
- true
94
- end
95
- end
96
-
97
- res &&= if node.respond_to?(:session)
98
- node.session.using_wait_time(0){ @filter_block.call(node) }
99
- else
100
- @filter_block.call(node)
101
- end unless @filter_block.nil?
102
-
103
- res
104
-
57
+ matches_node_filters?(node) && matches_filter_block?(node)
105
58
  rescue *(node.respond_to?(:session) ? node.session.driver.invalid_element_errors : [])
106
59
  return false
107
60
  end
108
61
 
109
62
  def visible
110
- case (vis = options.fetch(:visible){ @selector.default_visibility(session_options.ignore_hidden_elements) })
111
- when true then :visible
112
- when false then :all
113
- else vis
63
+ case (vis = options.fetch(:visible) { @selector.default_visibility(session_options.ignore_hidden_elements) })
64
+ when true then :visible
65
+ when false then :all
66
+ else vis
114
67
  end
115
68
  end
116
69
 
117
70
  def exact?
118
- return false if !supports_exact?
119
- options.fetch(:exact, session_options.exact)
71
+ supports_exact? ? options.fetch(:exact, session_options.exact) : false
120
72
  end
121
73
 
122
74
  def match
123
75
  options.fetch(:match, session_options.match)
124
76
  end
125
77
 
126
- def xpath(exact=nil)
127
- exact = self.exact? if exact.nil?
78
+ def xpath(exact = nil)
79
+ exact = exact? if exact.nil?
128
80
  expr = apply_expression_filters(@expression)
129
- expr = if expr.respond_to?(:to_xpath) and exact
130
- expr.to_xpath(:exact)
131
- else
132
- expr.to_s
133
- end
81
+ expr = exact ? expr.to_xpath(:exact) : expr.to_s if expr.respond_to?(:to_xpath)
134
82
  filtered_xpath(expr)
135
83
  end
136
84
 
@@ -140,11 +88,12 @@ module Capybara
140
88
 
141
89
  # @api private
142
90
  def resolve_for(node, exact = nil)
91
+ @resolved_node = node
143
92
  node.synchronize do
144
93
  children = if selector.format == :css
145
- node.find_css(self.css)
94
+ node.find_css(css)
146
95
  else
147
- node.find_xpath(self.xpath(exact))
96
+ node.find_xpath(xpath(exact))
148
97
  end.map do |child|
149
98
  if node.is_a?(Capybara::Node::Base)
150
99
  Capybara::Node::Element.new(node.session, child, node, self)
@@ -161,14 +110,45 @@ module Capybara
161
110
  @expression.respond_to? :to_xpath
162
111
  end
163
112
 
164
- private
113
+ private
114
+
115
+ def find_selector(locator)
116
+ selector = if locator.is_a?(Symbol)
117
+ Selector.all.fetch(locator) { |sel_type| raise ArgumentError, "Unknown selector type (:#{sel_type})" }
118
+ else
119
+ Selector.all.values.find { |s| s.match?(locator) }
120
+ end
121
+ selector || Selector.all[session_options.default_selector]
122
+ end
165
123
 
166
124
  def valid_keys
167
125
  VALID_KEYS + custom_keys
168
126
  end
169
127
 
128
+ def matches_node_filters?(node)
129
+ node_filters.all? do |name, filter|
130
+ if options.key?(name)
131
+ filter.matches?(node, options[name])
132
+ elsif filter.default?
133
+ filter.matches?(node, filter.default)
134
+ else
135
+ true
136
+ end
137
+ end
138
+ end
139
+
140
+ def matches_filter_block?(node)
141
+ return true unless @filter_block
142
+
143
+ if node.respond_to?(:session)
144
+ node.session.using_wait_time(0) { @filter_block.call(node) }
145
+ else
146
+ @filter_block.call(node)
147
+ end
148
+ end
149
+
170
150
  def node_filters
171
- if options.has_key?(:filter_set)
151
+ if options.key?(:filter_set)
172
152
  ::Capybara::Selector::FilterSet.all[options[:filter_set]].node_filters
173
153
  else
174
154
  @selector.node_filters
@@ -177,7 +157,7 @@ module Capybara
177
157
 
178
158
  def expression_filters
179
159
  filters = @selector.expression_filters
180
- filters.merge ::Capybara::Selector::FilterSet.all[options[:filter_set]].expression_filters if options.has_key?(:filter_set)
160
+ filters.merge ::Capybara::Selector::FilterSet.all[options[:filter_set]].expression_filters if options.key?(:filter_set)
181
161
  filters
182
162
  end
183
163
 
@@ -193,13 +173,13 @@ module Capybara
193
173
  end
194
174
 
195
175
  def filtered_xpath(expr)
196
- if options.has_key?(:id) || options.has_key?(:class)
176
+ if options.key?(:id) || options.key?(:class)
197
177
  expr = "(#{expr})"
198
- expr = "#{expr}[#{XPath.attr(:id) == options[:id]}]" if options.has_key?(:id) && !custom_keys.include?(:id)
199
- if options.has_key?(:class) && !custom_keys.include?(:class)
178
+ expr = "#{expr}[#{XPath.attr(:id) == options[:id]}]" if options.key?(:id) && !custom_keys.include?(:id)
179
+ if options.key?(:class) && !custom_keys.include?(:class)
200
180
  class_xpath = Array(options[:class]).map do |klass|
201
- "contains(concat(' ',normalize-space(@class),' '),' #{klass} ')"
202
- end.join(" and ")
181
+ XPath.attr(:class).contains_word(klass)
182
+ end.reduce(:&)
203
183
  expr = "#{expr}[#{class_xpath}]"
204
184
  end
205
185
  end
@@ -207,12 +187,12 @@ module Capybara
207
187
  end
208
188
 
209
189
  def filtered_css(expr)
210
- if options.has_key?(:id) || options.has_key?(:class)
190
+ if options.key?(:id) || options.key?(:class)
211
191
  css_selectors = expr.split(',').map(&:rstrip)
212
192
  expr = css_selectors.map do |sel|
213
- sel += "##{Capybara::Selector::CSS.escape(options[:id])}" if options.has_key?(:id) && !custom_keys.include?(:id)
214
- sel += Array(options[:class]).map { |k| ".#{Capybara::Selector::CSS.escape(k)}"}.join if options.has_key?(:class) && !custom_keys.include?(:class)
215
- sel
193
+ sel += "##{Capybara::Selector::CSS.escape(options[:id])}" if options.key?(:id) && !custom_keys.include?(:id)
194
+ sel += Array(options[:class]).map { |k| ".#{Capybara::Selector::CSS.escape(k)}" }.join if options.key?(:class) && !custom_keys.include?(:class)
195
+ sel
216
196
  end.join(", ")
217
197
  end
218
198
  expr
@@ -220,7 +200,7 @@ module Capybara
220
200
 
221
201
  def apply_expression_filters(expr)
222
202
  expression_filters.inject(expr) do |memo, (name, ef)|
223
- if options.has_key?(name)
203
+ if options.key?(name)
224
204
  ef.apply_filter(memo, options[name])
225
205
  elsif ef.default?
226
206
  ef.apply_filter(memo, ef.default)
@@ -231,14 +211,38 @@ module Capybara
231
211
  end
232
212
 
233
213
  def warn_exact_usage
234
- if options.has_key?(:exact) && !supports_exact?
235
- warn "The :exact option only has an effect on queries using the XPath#is method. Using it with the query \"#{expression.to_s}\" has no effect."
236
- end
214
+ return unless options.key?(:exact) && !supports_exact?
215
+ warn "The :exact option only has an effect on queries using the XPath#is method. Using it with the query \"#{expression}\" has no effect."
237
216
  end
238
217
 
239
218
  def exact_text
240
219
  options.fetch(:exact_text, session_options.exact_text)
241
220
  end
221
+
222
+ def describe_within?
223
+ @resolved_node && !(@resolved_node.is_a?(::Capybara::Node::Document) ||
224
+ (@resolved_node.is_a?(::Capybara::Node::Simple) && @resolved_node.path == '/'))
225
+ end
226
+
227
+ def matches_text_filter(node, text_option)
228
+ regexp = if text_option.is_a?(Regexp)
229
+ text_option
230
+ elsif exact_text == true
231
+ /\A#{Regexp.escape(text_option.to_s)}\z/
232
+ else
233
+ Regexp.escape(text_option.to_s)
234
+ end
235
+ text_visible = visible
236
+ text_visible = :all if text_visible == :hidden
237
+ node.text(text_visible).match(regexp)
238
+ end
239
+
240
+ def matches_exact_text_filter(node, exact_text_option)
241
+ regexp = /\A#{Regexp.escape(exact_text_option)}\z/
242
+ text_visible = visible
243
+ text_visible = :all if text_visible == :hidden
244
+ node.text(text_visible).match(regexp)
245
+ end
242
246
  end
243
247
  end
244
248
  end
@@ -1,13 +1,14 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Capybara
3
4
  module Queries
4
5
  class SiblingQuery < MatchQuery
5
6
  # @api private
6
7
  def resolve_for(node, exact = nil)
7
- @resolved_node = node
8
+ @sibling_node = node
8
9
  node.synchronize do
9
10
  match_results = super(node.session.current_scope, exact)
10
- node.all(:xpath, XPath.preceding_sibling.union(XPath.following_sibling)) do |el|
11
+ node.all(:xpath, XPath.preceding_sibling + XPath.following_sibling) do |el|
11
12
  match_results.include?(el)
12
13
  end
13
14
  end
@@ -15,9 +16,8 @@ module Capybara
15
16
 
16
17
  def description
17
18
  desc = super
18
- if @resolved_node && (child_query = @resolved_node.instance_variable_get(:@query))
19
- desc += " that is a sibling of #{child_query.description}"
20
- end
19
+ sibling_query = @sibling_node && @sibling_node.instance_variable_get(:@query)
20
+ desc += " that is a sibling of #{sibling_query.description}" if sibling_query
21
21
  desc
22
22
  end
23
23
  end