capybara 2.15.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
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