capybara 3.8.1 → 3.33.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (242) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +1 -0
  3. data/History.md +465 -0
  4. data/License.txt +1 -1
  5. data/README.md +58 -57
  6. data/lib/capybara/config.rb +10 -4
  7. data/lib/capybara/cucumber.rb +1 -1
  8. data/lib/capybara/driver/base.rb +2 -2
  9. data/lib/capybara/driver/node.rb +26 -5
  10. data/lib/capybara/dsl.rb +12 -4
  11. data/lib/capybara/helpers.rb +8 -4
  12. data/lib/capybara/minitest/spec.rb +162 -85
  13. data/lib/capybara/minitest.rb +248 -148
  14. data/lib/capybara/node/actions.rb +149 -96
  15. data/lib/capybara/node/base.rb +27 -10
  16. data/lib/capybara/node/document.rb +12 -0
  17. data/lib/capybara/node/document_matchers.rb +9 -5
  18. data/lib/capybara/node/element.rb +254 -109
  19. data/lib/capybara/node/finders.rb +83 -76
  20. data/lib/capybara/node/matchers.rb +279 -141
  21. data/lib/capybara/node/simple.rb +25 -6
  22. data/lib/capybara/queries/ancestor_query.rb +5 -7
  23. data/lib/capybara/queries/base_query.rb +11 -5
  24. data/lib/capybara/queries/current_path_query.rb +3 -3
  25. data/lib/capybara/queries/match_query.rb +1 -0
  26. data/lib/capybara/queries/selector_query.rb +467 -103
  27. data/lib/capybara/queries/sibling_query.rb +5 -4
  28. data/lib/capybara/queries/style_query.rb +6 -2
  29. data/lib/capybara/queries/text_query.rb +17 -3
  30. data/lib/capybara/queries/title_query.rb +2 -2
  31. data/lib/capybara/rack_test/browser.rb +22 -15
  32. data/lib/capybara/rack_test/driver.rb +10 -1
  33. data/lib/capybara/rack_test/errors.rb +6 -0
  34. data/lib/capybara/rack_test/form.rb +33 -28
  35. data/lib/capybara/rack_test/node.rb +74 -6
  36. data/lib/capybara/registration_container.rb +44 -0
  37. data/lib/capybara/registrations/drivers.rb +36 -0
  38. data/lib/capybara/registrations/patches/puma_ssl.rb +27 -0
  39. data/lib/capybara/registrations/servers.rb +44 -0
  40. data/lib/capybara/result.rb +55 -23
  41. data/lib/capybara/rspec/features.rb +4 -4
  42. data/lib/capybara/rspec/matcher_proxies.rb +36 -15
  43. data/lib/capybara/rspec/matchers/base.rb +111 -0
  44. data/lib/capybara/rspec/matchers/become_closed.rb +33 -0
  45. data/lib/capybara/rspec/matchers/compound.rb +88 -0
  46. data/lib/capybara/rspec/matchers/count_sugar.rb +37 -0
  47. data/lib/capybara/rspec/matchers/have_ancestor.rb +28 -0
  48. data/lib/capybara/rspec/matchers/have_current_path.rb +29 -0
  49. data/lib/capybara/rspec/matchers/have_selector.rb +77 -0
  50. data/lib/capybara/rspec/matchers/have_sibling.rb +27 -0
  51. data/lib/capybara/rspec/matchers/have_text.rb +33 -0
  52. data/lib/capybara/rspec/matchers/have_title.rb +29 -0
  53. data/lib/capybara/rspec/matchers/match_selector.rb +27 -0
  54. data/lib/capybara/rspec/matchers/match_style.rb +38 -0
  55. data/lib/capybara/rspec/matchers/spatial_sugar.rb +39 -0
  56. data/lib/capybara/rspec/matchers.rb +117 -311
  57. data/lib/capybara/selector/builders/css_builder.rb +84 -0
  58. data/lib/capybara/selector/builders/xpath_builder.rb +69 -0
  59. data/lib/capybara/selector/css.rb +17 -15
  60. data/lib/capybara/selector/definition/button.rb +52 -0
  61. data/lib/capybara/selector/definition/checkbox.rb +26 -0
  62. data/lib/capybara/selector/definition/css.rb +10 -0
  63. data/lib/capybara/selector/definition/datalist_input.rb +35 -0
  64. data/lib/capybara/selector/definition/datalist_option.rb +25 -0
  65. data/lib/capybara/selector/definition/element.rb +27 -0
  66. data/lib/capybara/selector/definition/field.rb +40 -0
  67. data/lib/capybara/selector/definition/fieldset.rb +14 -0
  68. data/lib/capybara/selector/definition/file_field.rb +13 -0
  69. data/lib/capybara/selector/definition/fillable_field.rb +33 -0
  70. data/lib/capybara/selector/definition/frame.rb +17 -0
  71. data/lib/capybara/selector/definition/id.rb +6 -0
  72. data/lib/capybara/selector/definition/label.rb +62 -0
  73. data/lib/capybara/selector/definition/link.rb +54 -0
  74. data/lib/capybara/selector/definition/link_or_button.rb +16 -0
  75. data/lib/capybara/selector/definition/option.rb +27 -0
  76. data/lib/capybara/selector/definition/radio_button.rb +27 -0
  77. data/lib/capybara/selector/definition/select.rb +81 -0
  78. data/lib/capybara/selector/definition/table.rb +109 -0
  79. data/lib/capybara/selector/definition/table_row.rb +21 -0
  80. data/lib/capybara/selector/definition/xpath.rb +5 -0
  81. data/lib/capybara/selector/definition.rb +277 -0
  82. data/lib/capybara/selector/filter.rb +1 -0
  83. data/lib/capybara/selector/filter_set.rb +26 -19
  84. data/lib/capybara/selector/filters/base.rb +24 -5
  85. data/lib/capybara/selector/filters/expression_filter.rb +3 -3
  86. data/lib/capybara/selector/filters/locator_filter.rb +29 -0
  87. data/lib/capybara/selector/filters/node_filter.rb +16 -2
  88. data/lib/capybara/selector/regexp_disassembler.rb +214 -0
  89. data/lib/capybara/selector/selector.rb +73 -367
  90. data/lib/capybara/selector/xpath_extensions.rb +17 -0
  91. data/lib/capybara/selector.rb +221 -480
  92. data/lib/capybara/selenium/atoms/getAttribute.min.js +1 -0
  93. data/lib/capybara/selenium/atoms/isDisplayed.min.js +1 -0
  94. data/lib/capybara/selenium/atoms/src/getAttribute.js +161 -0
  95. data/lib/capybara/selenium/atoms/src/isDisplayed.js +454 -0
  96. data/lib/capybara/selenium/driver.rb +203 -86
  97. data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +88 -14
  98. data/lib/capybara/selenium/driver_specializations/edge_driver.rb +124 -0
  99. data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +89 -0
  100. data/lib/capybara/selenium/driver_specializations/internet_explorer_driver.rb +26 -0
  101. data/lib/capybara/selenium/driver_specializations/safari_driver.rb +24 -0
  102. data/lib/capybara/selenium/extensions/file_input_click_emulation.rb +34 -0
  103. data/lib/capybara/selenium/extensions/find.rb +110 -0
  104. data/lib/capybara/selenium/extensions/html5_drag.rb +191 -22
  105. data/lib/capybara/selenium/extensions/modifier_keys_stack.rb +28 -0
  106. data/lib/capybara/selenium/extensions/scroll.rb +78 -0
  107. data/lib/capybara/selenium/logger_suppressor.rb +34 -0
  108. data/lib/capybara/selenium/node.rb +298 -93
  109. data/lib/capybara/selenium/nodes/chrome_node.rb +100 -8
  110. data/lib/capybara/selenium/nodes/edge_node.rb +104 -0
  111. data/lib/capybara/selenium/nodes/firefox_node.rb +131 -0
  112. data/lib/capybara/selenium/nodes/ie_node.rb +22 -0
  113. data/lib/capybara/selenium/nodes/safari_node.rb +118 -0
  114. data/lib/capybara/selenium/patches/action_pauser.rb +26 -0
  115. data/lib/capybara/selenium/patches/atoms.rb +18 -0
  116. data/lib/capybara/selenium/patches/is_displayed.rb +16 -0
  117. data/lib/capybara/selenium/patches/logs.rb +45 -0
  118. data/lib/capybara/selenium/patches/pause_duration_fix.rb +1 -3
  119. data/lib/capybara/selenium/patches/persistent_client.rb +20 -0
  120. data/lib/capybara/server/animation_disabler.rb +4 -3
  121. data/lib/capybara/server/checker.rb +6 -2
  122. data/lib/capybara/server/middleware.rb +23 -13
  123. data/lib/capybara/server.rb +30 -7
  124. data/lib/capybara/session/config.rb +14 -10
  125. data/lib/capybara/session/matchers.rb +11 -7
  126. data/lib/capybara/session.rb +152 -111
  127. data/lib/capybara/spec/public/offset.js +6 -0
  128. data/lib/capybara/spec/public/test.js +101 -10
  129. data/lib/capybara/spec/session/all_spec.rb +96 -6
  130. data/lib/capybara/spec/session/ancestor_spec.rb +5 -0
  131. data/lib/capybara/spec/session/assert_all_of_selectors_spec.rb +29 -0
  132. data/lib/capybara/spec/session/assert_current_path_spec.rb +5 -2
  133. data/lib/capybara/spec/session/assert_selector_spec.rb +0 -10
  134. data/lib/capybara/spec/session/assert_style_spec.rb +4 -4
  135. data/lib/capybara/spec/session/assert_text_spec.rb +9 -5
  136. data/lib/capybara/spec/session/attach_file_spec.rb +63 -36
  137. data/lib/capybara/spec/session/check_spec.rb +10 -4
  138. data/lib/capybara/spec/session/choose_spec.rb +8 -2
  139. data/lib/capybara/spec/session/click_button_spec.rb +117 -61
  140. data/lib/capybara/spec/session/click_link_or_button_spec.rb +16 -0
  141. data/lib/capybara/spec/session/click_link_spec.rb +17 -6
  142. data/lib/capybara/spec/session/element/matches_selector_spec.rb +40 -39
  143. data/lib/capybara/spec/session/evaluate_script_spec.rb +13 -0
  144. data/lib/capybara/spec/session/execute_script_spec.rb +1 -0
  145. data/lib/capybara/spec/session/fill_in_spec.rb +47 -6
  146. data/lib/capybara/spec/session/find_field_spec.rb +1 -1
  147. data/lib/capybara/spec/session/find_spec.rb +74 -4
  148. data/lib/capybara/spec/session/first_spec.rb +1 -1
  149. data/lib/capybara/spec/session/frame/switch_to_frame_spec.rb +13 -1
  150. data/lib/capybara/spec/session/frame/within_frame_spec.rb +12 -1
  151. data/lib/capybara/spec/session/has_all_selectors_spec.rb +1 -1
  152. data/lib/capybara/spec/session/has_ancestor_spec.rb +46 -0
  153. data/lib/capybara/spec/session/has_any_selectors_spec.rb +25 -0
  154. data/lib/capybara/spec/session/has_button_spec.rb +16 -0
  155. data/lib/capybara/spec/session/has_css_spec.rb +122 -12
  156. data/lib/capybara/spec/session/has_current_path_spec.rb +6 -4
  157. data/lib/capybara/spec/session/has_field_spec.rb +55 -0
  158. data/lib/capybara/spec/session/has_select_spec.rb +34 -6
  159. data/lib/capybara/spec/session/has_selector_spec.rb +11 -4
  160. data/lib/capybara/spec/session/has_sibling_spec.rb +50 -0
  161. data/lib/capybara/spec/session/has_table_spec.rb +166 -0
  162. data/lib/capybara/spec/session/has_text_spec.rb +48 -1
  163. data/lib/capybara/spec/session/has_xpath_spec.rb +17 -0
  164. data/lib/capybara/spec/session/html_spec.rb +7 -0
  165. data/lib/capybara/spec/session/matches_style_spec.rb +35 -0
  166. data/lib/capybara/spec/session/node_spec.rb +643 -18
  167. data/lib/capybara/spec/session/node_wrapper_spec.rb +1 -1
  168. data/lib/capybara/spec/session/refresh_spec.rb +4 -0
  169. data/lib/capybara/spec/session/reset_session_spec.rb +23 -8
  170. data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +2 -2
  171. data/lib/capybara/spec/session/save_screenshot_spec.rb +4 -4
  172. data/lib/capybara/spec/session/scroll_spec.rb +117 -0
  173. data/lib/capybara/spec/session/select_spec.rb +10 -10
  174. data/lib/capybara/spec/session/selectors_spec.rb +36 -5
  175. data/lib/capybara/spec/session/uncheck_spec.rb +2 -2
  176. data/lib/capybara/spec/session/unselect_spec.rb +1 -1
  177. data/lib/capybara/spec/session/window/become_closed_spec.rb +20 -17
  178. data/lib/capybara/spec/session/window/switch_to_window_spec.rb +4 -0
  179. data/lib/capybara/spec/session/window/window_opened_by_spec.rb +4 -0
  180. data/lib/capybara/spec/session/window/window_spec.rb +59 -58
  181. data/lib/capybara/spec/session/window/windows_spec.rb +4 -0
  182. data/lib/capybara/spec/session/within_spec.rb +23 -0
  183. data/lib/capybara/spec/spec_helper.rb +16 -6
  184. data/lib/capybara/spec/test_app.rb +28 -23
  185. data/lib/capybara/spec/views/animated.erb +49 -0
  186. data/lib/capybara/spec/views/form.erb +48 -7
  187. data/lib/capybara/spec/views/frame_child.erb +3 -2
  188. data/lib/capybara/spec/views/frame_one.erb +1 -0
  189. data/lib/capybara/spec/views/obscured.erb +47 -0
  190. data/lib/capybara/spec/views/offset.erb +32 -0
  191. data/lib/capybara/spec/views/react.erb +45 -0
  192. data/lib/capybara/spec/views/scroll.erb +20 -0
  193. data/lib/capybara/spec/views/spatial.erb +31 -0
  194. data/lib/capybara/spec/views/tables.erb +67 -0
  195. data/lib/capybara/spec/views/with_animation.erb +29 -1
  196. data/lib/capybara/spec/views/with_dragula.erb +24 -0
  197. data/lib/capybara/spec/views/with_hover.erb +1 -0
  198. data/lib/capybara/spec/views/with_hover1.erb +10 -0
  199. data/lib/capybara/spec/views/with_html.erb +32 -6
  200. data/lib/capybara/spec/views/with_js.erb +3 -1
  201. data/lib/capybara/spec/views/with_jstree.erb +26 -0
  202. data/lib/capybara/spec/views/with_scope_other.erb +6 -0
  203. data/lib/capybara/spec/views/with_sortable_js.erb +21 -0
  204. data/lib/capybara/version.rb +1 -1
  205. data/lib/capybara/window.rb +11 -11
  206. data/lib/capybara.rb +118 -111
  207. data/spec/basic_node_spec.rb +14 -3
  208. data/spec/capybara_spec.rb +29 -29
  209. data/spec/css_builder_spec.rb +101 -0
  210. data/spec/dsl_spec.rb +46 -21
  211. data/spec/filter_set_spec.rb +5 -5
  212. data/spec/fixtures/selenium_driver_rspec_failure.rb +1 -1
  213. data/spec/fixtures/selenium_driver_rspec_success.rb +1 -1
  214. data/spec/minitest_spec.rb +18 -4
  215. data/spec/minitest_spec_spec.rb +59 -44
  216. data/spec/rack_test_spec.rb +117 -89
  217. data/spec/regexp_dissassembler_spec.rb +250 -0
  218. data/spec/result_spec.rb +51 -49
  219. data/spec/rspec/features_spec.rb +3 -0
  220. data/spec/rspec/shared_spec_matchers.rb +112 -97
  221. data/spec/rspec_spec.rb +35 -17
  222. data/spec/sauce_spec_chrome.rb +43 -0
  223. data/spec/selector_spec.rb +244 -28
  224. data/spec/selenium_spec_chrome.rb +125 -54
  225. data/spec/selenium_spec_chrome_remote.rb +26 -12
  226. data/spec/selenium_spec_edge.rb +23 -8
  227. data/spec/selenium_spec_firefox.rb +208 -0
  228. data/spec/selenium_spec_firefox_remote.rb +15 -18
  229. data/spec/selenium_spec_ie.rb +82 -13
  230. data/spec/selenium_spec_safari.rb +148 -0
  231. data/spec/server_spec.rb +118 -77
  232. data/spec/session_spec.rb +19 -3
  233. data/spec/shared_selenium_node.rb +83 -0
  234. data/spec/shared_selenium_session.rb +110 -65
  235. data/spec/spec_helper.rb +57 -9
  236. data/spec/xpath_builder_spec.rb +93 -0
  237. metadata +257 -17
  238. data/lib/capybara/rspec/compound.rb +0 -94
  239. data/lib/capybara/selenium/driver_specializations/marionette_driver.rb +0 -49
  240. data/lib/capybara/selenium/nodes/marionette_node.rb +0 -121
  241. data/lib/capybara/spec/session/has_style_spec.rb +0 -25
  242. data/spec/selenium_spec_marionette.rb +0 -172
@@ -7,7 +7,7 @@ module Capybara
7
7
  # A {Capybara::Result} represents a collection of {Capybara::Node::Element} on the page. It is possible to interact with this
8
8
  # collection similar to an Array because it implements Enumerable and offers the following Array methods through delegation:
9
9
  #
10
- # * []
10
+ # * \[\]
11
11
  # * each()
12
12
  # * at()
13
13
  # * size()
@@ -16,6 +16,8 @@ module Capybara
16
16
  # * first()
17
17
  # * last()
18
18
  # * empty?()
19
+ # * values_at()
20
+ # * sample()
19
21
  #
20
22
  # @see Capybara::Node::Element
21
23
  #
@@ -26,13 +28,15 @@ module Capybara
26
28
  def initialize(elements, query)
27
29
  @elements = elements
28
30
  @result_cache = []
29
- @results_enum = lazy_select_elements { |node| query.matches_filters?(node) }
31
+ @filter_errors = []
32
+ @results_enum = lazy_select_elements { |node| query.matches_filters?(node, @filter_errors) }
30
33
  @query = query
34
+ @allow_reload = false
31
35
  end
32
36
 
33
37
  def_delegators :full_results, :size, :length, :last, :values_at, :inspect, :sample
34
38
 
35
- alias :index :find_index
39
+ alias index find_index
36
40
 
37
41
  def each(&block)
38
42
  return enum_for(:each) unless block_given?
@@ -40,7 +44,7 @@ module Capybara
40
44
  @result_cache.each(&block)
41
45
  loop do
42
46
  next_result = @results_enum.next
43
- @result_cache << next_result
47
+ add_to_cache(next_result)
44
48
  yield next_result
45
49
  end
46
50
  self
@@ -56,7 +60,12 @@ module Capybara
56
60
  nil
57
61
  end
58
62
  when Range
59
- idx.max
63
+ # idx.max is broken with beginless ranges
64
+ # idx.end && idx.max # endless range will have end == nil
65
+ max = idx.end
66
+ max = nil if max&.negative?
67
+ max -= 1 if max && idx.exclude_end?
68
+ max
60
69
  end
61
70
 
62
71
  if max_idx.nil?
@@ -73,27 +82,29 @@ module Capybara
73
82
  end
74
83
 
75
84
  def compare_count
85
+ return 0 unless @query
86
+
87
+ count, min, max, between = @query.options.values_at(:count, :minimum, :maximum, :between)
88
+
76
89
  # Only check filters for as many elements as necessary to determine result
77
- if (count = @query.options[:count])
78
- count = Integer(count)
90
+ if count && (count = Integer(count))
79
91
  return load_up_to(count + 1) <=> count
80
92
  end
81
93
 
82
- if (min = @query.options[:minimum])
83
- min = Integer(min)
94
+ if min && (min = Integer(min))
84
95
  return -1 if load_up_to(min) < min
85
96
  end
86
97
 
87
- if (max = @query.options[:maximum])
88
- max = Integer(max)
98
+ if max && (max = Integer(max))
89
99
  return 1 if load_up_to(max + 1) > max
90
100
  end
91
101
 
92
- if (between = @query.options[:between])
93
- min, max = between.minmax
94
- size = load_up_to(max + 1)
95
- return 0 if between.include? size
96
- return size <=> min
102
+ if between
103
+ min, max = (between.begin && between.min) || 1, between.end
104
+ max -= 1 if max && between.exclude_end?
105
+
106
+ size = load_up_to(max ? max + 1 : min)
107
+ return size <=> min unless between.include?(size)
97
108
  end
98
109
 
99
110
  0
@@ -108,11 +119,13 @@ module Capybara
108
119
  if count.zero?
109
120
  message << ' but there were no matches'
110
121
  else
111
- message << ", found #{count} #{Capybara::Helpers.declension('match', 'matches', count)}: " << full_results.map(&:text).map(&:inspect).join(', ')
122
+ message << ", found #{count} #{Capybara::Helpers.declension('match', 'matches', count)}: " \
123
+ << full_results.map(&:text).map(&:inspect).join(', ')
112
124
  end
113
125
  unless rest.empty?
114
126
  elements = rest.map { |el| el.text rescue '<<ERROR>>' }.map(&:inspect).join(', ') # rubocop:disable Style/RescueModifier
115
- message << '. Also found ' << elements << ', which matched the selector but not all filters.'
127
+ message << '. Also found ' << elements << ', which matched the selector but not all filters. '
128
+ message << @filter_errors.join('. ') if (rest.size == 1) && count.zero?
116
129
  end
117
130
  message
118
131
  end
@@ -125,12 +138,26 @@ module Capybara
125
138
  @elements.length
126
139
  end
127
140
 
141
+ ##
142
+ # @api private
143
+ #
144
+ def allow_reload!
145
+ @allow_reload = true
146
+ self
147
+ end
148
+
128
149
  private
129
150
 
151
+ def add_to_cache(elem)
152
+ elem.allow_reload!(@result_cache.size) if @allow_reload
153
+ @result_cache << elem
154
+ end
155
+
130
156
  def load_up_to(num)
131
157
  loop do
132
158
  break if @result_cache.size >= num
133
- @result_cache << @results_enum.next
159
+
160
+ add_to_cache(@results_enum.next)
134
161
  end
135
162
  @result_cache.size
136
163
  end
@@ -144,13 +171,18 @@ module Capybara
144
171
  @rest ||= @elements - full_results
145
172
  end
146
173
 
147
- def lazy_select_elements(&block)
148
- # JRuby has an issue with lazy enumerators which
174
+ if RUBY_PLATFORM == 'java'
175
+ # JRuby < 9.2.8.0 has an issue with lazy enumerators which
149
176
  # causes a concurrency issue with network requests here
150
177
  # https://github.com/jruby/jruby/issues/4212
151
- if RUBY_PLATFORM == 'java'
178
+ # while JRuby >= 9.2.8.0 leaks threads when using lazy enumerators
179
+ # https://github.com/teamcapybara/capybara/issues/2349
180
+ # so disable the use and JRuby users will need to pay a performance penalty
181
+ def lazy_select_elements(&block)
152
182
  @elements.select(&block).to_enum # non-lazy evaluation
153
- else
183
+ end
184
+ else
185
+ def lazy_select_elements(&block)
154
186
  @elements.lazy.select(&block)
155
187
  end
156
188
  end
@@ -14,10 +14,10 @@ RSpec.configure do |config|
14
14
  end
15
15
 
16
16
  RSpec.configure do |config|
17
- config.alias_example_group_to :feature, capybara_feature: true, type: :feature
18
- config.alias_example_group_to :xfeature, capybara_feature: true, type: :feature, skip: 'Temporarily disabled with xfeature'
19
- config.alias_example_group_to :ffeature, capybara_feature: true, type: :feature, focus: true
17
+ config.alias_example_group_to :feature, :capybara_feature, type: :feature
18
+ config.alias_example_group_to :xfeature, :capybara_feature, type: :feature, skip: 'Temporarily disabled with xfeature'
19
+ config.alias_example_group_to :ffeature, :capybara_feature, :focus, type: :feature
20
20
  config.alias_example_to :scenario
21
21
  config.alias_example_to :xscenario, skip: 'Temporarily disabled with xscenario'
22
- config.alias_example_to :fscenario, focus: true
22
+ config.alias_example_to :fscenario, :focus
23
23
  end
@@ -2,32 +2,57 @@
2
2
 
3
3
  module Capybara
4
4
  module RSpecMatcherProxies
5
- def all(*args, &block)
5
+ def all(*args, **kwargs, &block)
6
6
  if defined?(::RSpec::Matchers::BuiltIn::All) && args.first.respond_to?(:matches?)
7
7
  ::RSpec::Matchers::BuiltIn::All.new(*args)
8
8
  else
9
- find_all(*args, &block)
9
+ find_all(*args, **kwargs, &block)
10
10
  end
11
11
  end
12
12
 
13
- def within(*args)
13
+ def within(*args, **kwargs, &block)
14
14
  if block_given?
15
- within_element(*args, &Proc.new)
15
+ within_element(*args, **kwargs, &block)
16
16
  else
17
17
  be_within(*args)
18
18
  end
19
19
  end
20
20
  end
21
+ end
22
+
23
+ if RUBY_ENGINE == 'jruby'
24
+ # :nocov:
25
+ module Capybara::DSL
26
+ class <<self
27
+ remove_method :included
21
28
 
22
- module DSLRSpecProxyInstaller
23
- module ClassMethods
24
29
  def included(base)
25
- if defined?(::RSpec::Matchers)
26
- base.include(::Capybara::RSpecMatcherProxies) if base.include?(::RSpec::Matchers)
30
+ warn 'including Capybara::DSL in the global scope is not recommended!' if base == Object
31
+ if defined?(::RSpec::Matchers) && base.include?(::RSpec::Matchers)
32
+ base.send(:include, ::Capybara::RSpecMatcherProxies)
27
33
  end
28
34
  super
29
35
  end
30
36
  end
37
+ end
38
+
39
+ if defined?(::RSpec::Matchers)
40
+ module ::RSpec::Matchers
41
+ def self.included(base)
42
+ base.send(:include, ::Capybara::RSpecMatcherProxies) if base.include?(::Capybara::DSL)
43
+ super
44
+ end
45
+ end
46
+ end
47
+ # :nocov:
48
+ else
49
+ module Capybara::DSLRSpecProxyInstaller
50
+ module ClassMethods
51
+ def included(base)
52
+ base.include(::Capybara::RSpecMatcherProxies) if defined?(::RSpec::Matchers) && base.include?(::RSpec::Matchers)
53
+ super
54
+ end
55
+ end
31
56
 
32
57
  def self.prepended(base)
33
58
  class <<base
@@ -36,7 +61,7 @@ module Capybara
36
61
  end
37
62
  end
38
63
 
39
- module RSpecMatcherProxyInstaller
64
+ module Capybara::RSpecMatcherProxyInstaller
40
65
  module ClassMethods
41
66
  def included(base)
42
67
  base.include(::Capybara::RSpecMatcherProxies) if base.include?(::Capybara::DSL)
@@ -51,11 +76,7 @@ module Capybara
51
76
  end
52
77
  end
53
78
 
54
- DSL.prepend ::Capybara::DSLRSpecProxyInstaller
55
- end
79
+ Capybara::DSL.prepend ::Capybara::DSLRSpecProxyInstaller
56
80
 
57
- if defined?(::RSpec::Matchers)
58
- module ::RSpec::Matchers
59
- prepend ::Capybara::RSpecMatcherProxyInstaller
60
- end
81
+ ::RSpec::Matchers.prepend ::Capybara::RSpecMatcherProxyInstaller if defined?(::RSpec::Matchers)
61
82
  end
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'capybara/rspec/matchers/compound'
4
+ require 'capybara/rspec/matchers/count_sugar'
5
+ require 'capybara/rspec/matchers/spatial_sugar'
6
+
7
+ module Capybara
8
+ module RSpecMatchers
9
+ module Matchers
10
+ class Base
11
+ include ::Capybara::RSpecMatchers::Matchers::Compound if defined?(::Capybara::RSpecMatchers::Matchers::Compound)
12
+
13
+ attr_reader :failure_message, :failure_message_when_negated
14
+
15
+ def initialize(*args, **kw_args, &filter_block)
16
+ @args = args.dup
17
+ @kw_args = kw_args || {}
18
+ @filter_block = filter_block
19
+ end
20
+
21
+ private
22
+
23
+ def session_query_args
24
+ # if @args.last.is_a? Hash
25
+ # @args.last[:session_options] = session_options
26
+ # else
27
+ # @args.push(session_options: session_options)
28
+ # end
29
+ @args
30
+ end
31
+
32
+ def session_query_options
33
+ @kw_args[:session_options] = session_options
34
+ @kw_args
35
+ end
36
+
37
+ def session_options
38
+ @context_el ||= nil
39
+ if @context_el.respond_to? :session_options
40
+ @context_el.session_options
41
+ elsif @context_el.respond_to? :current_scope
42
+ @context_el.current_scope.session_options
43
+ else
44
+ Capybara.session_options
45
+ end
46
+ end
47
+ end
48
+
49
+ class WrappedElementMatcher < Base
50
+ def matches?(actual)
51
+ element_matches?(wrap(actual))
52
+ rescue Capybara::ExpectationNotMet => e
53
+ @failure_message = e.message
54
+ false
55
+ end
56
+
57
+ def does_not_match?(actual)
58
+ element_does_not_match?(wrap(actual))
59
+ rescue Capybara::ExpectationNotMet => e
60
+ @failure_message_when_negated = e.message
61
+ false
62
+ end
63
+
64
+ private
65
+
66
+ def wrap(actual)
67
+ actual = actual.to_capybara_node if actual.respond_to?(:to_capybara_node)
68
+ @context_el = if actual.respond_to?(:has_selector?)
69
+ actual
70
+ else
71
+ Capybara.string(actual.to_s)
72
+ end
73
+ end
74
+ end
75
+
76
+ class CountableWrappedElementMatcher < WrappedElementMatcher
77
+ include ::Capybara::RSpecMatchers::CountSugar
78
+ include ::Capybara::RSpecMatchers::SpatialSugar
79
+ end
80
+
81
+ class NegatedMatcher
82
+ include ::Capybara::RSpecMatchers::Matchers::Compound if defined?(::Capybara::RSpecMatchers::Matchers::Compound)
83
+
84
+ def initialize(matcher)
85
+ super()
86
+ @matcher = matcher
87
+ end
88
+
89
+ def matches?(actual)
90
+ @matcher.does_not_match?(actual)
91
+ end
92
+
93
+ def does_not_match?(actual)
94
+ @matcher.matches?(actual)
95
+ end
96
+
97
+ def description
98
+ "not #{@matcher.description}"
99
+ end
100
+
101
+ def failure_message
102
+ @matcher.failure_message_when_negated
103
+ end
104
+
105
+ def failure_message_when_negated
106
+ @matcher.failure_message
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Capybara
4
+ module RSpecMatchers
5
+ module Matchers
6
+ class BecomeClosed
7
+ def initialize(options)
8
+ @options = options
9
+ end
10
+
11
+ def matches?(window)
12
+ @window = window
13
+ @wait_time = Capybara::Queries::BaseQuery.wait(@options, window.session.config.default_max_wait_time)
14
+ timer = Capybara::Helpers.timer(expire_in: @wait_time)
15
+ while window.exists?
16
+ return false if timer.expired?
17
+
18
+ sleep 0.01
19
+ end
20
+ true
21
+ end
22
+
23
+ def failure_message
24
+ "expected #{@window.inspect} to become closed after #{@wait_time} seconds"
25
+ end
26
+
27
+ def failure_message_when_negated
28
+ "expected #{@window.inspect} not to become closed after #{@wait_time} seconds"
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ if defined?(::RSpec::Expectations::Version)
4
+ module Capybara
5
+ module RSpecMatchers
6
+ module Matchers
7
+ module Compound
8
+ include ::RSpec::Matchers::Composable
9
+
10
+ def and(matcher)
11
+ And.new(self, matcher)
12
+ end
13
+
14
+ def and_then(matcher)
15
+ ::RSpec::Matchers::BuiltIn::Compound::And.new(self, matcher)
16
+ end
17
+
18
+ def or(matcher)
19
+ Or.new(self, matcher)
20
+ end
21
+
22
+ class CapybaraEvaluator
23
+ def initialize(actual)
24
+ @actual = actual
25
+ @match_results = Hash.new { |hsh, matcher| hsh[matcher] = matcher.matches?(@actual) }
26
+ end
27
+
28
+ def matcher_matches?(matcher)
29
+ @match_results[matcher]
30
+ end
31
+
32
+ def reset
33
+ @match_results.clear
34
+ end
35
+ end
36
+
37
+ # @api private
38
+ module Synchronizer
39
+ def match(_expected, actual)
40
+ @evaluator = CapybaraEvaluator.new(actual)
41
+ syncer = sync_element(actual)
42
+ begin
43
+ syncer.synchronize do
44
+ @evaluator.reset
45
+ raise ::Capybara::ElementNotFound unless synchronized_match?
46
+
47
+ true
48
+ end
49
+ rescue StandardError
50
+ false
51
+ end
52
+ end
53
+
54
+ def sync_element(el)
55
+ if el.respond_to? :synchronize
56
+ el
57
+ elsif el.respond_to? :current_scope
58
+ el.current_scope
59
+ else
60
+ Capybara.string(el)
61
+ end
62
+ end
63
+ end
64
+
65
+ class And < ::RSpec::Matchers::BuiltIn::Compound::And
66
+ include Synchronizer
67
+
68
+ private
69
+
70
+ def synchronized_match?
71
+ [matcher_1_matches?, matcher_2_matches?].all?
72
+ end
73
+ end
74
+
75
+ class Or < ::RSpec::Matchers::BuiltIn::Compound::Or
76
+ include Synchronizer
77
+
78
+ private
79
+
80
+ def synchronized_match?
81
+ [matcher_1_matches?, matcher_2_matches?].any?
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Capybara
4
+ module RSpecMatchers
5
+ module CountSugar
6
+ def once; exactly(1); end
7
+ def twice; exactly(2); end
8
+ def thrice; exactly(3); end
9
+
10
+ def exactly(number)
11
+ options[:count] = number
12
+ self
13
+ end
14
+
15
+ def at_most(number)
16
+ options[:maximum] = number
17
+ self
18
+ end
19
+
20
+ def at_least(number)
21
+ options[:minimum] = number
22
+ self
23
+ end
24
+
25
+ def times
26
+ self
27
+ end
28
+
29
+ private
30
+
31
+ def options
32
+ # (@args.last.is_a?(Hash) ? @args : @args.push({})).last
33
+ @kw_args
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'capybara/rspec/matchers/base'
4
+
5
+ module Capybara
6
+ module RSpecMatchers
7
+ module Matchers
8
+ class HaveAncestor < CountableWrappedElementMatcher
9
+ def element_matches?(el)
10
+ el.assert_ancestor(*@args, **session_query_options, &@filter_block)
11
+ end
12
+
13
+ def element_does_not_match?(el)
14
+ el.assert_no_ancestor(*@args, **session_query_options, &@filter_block)
15
+ end
16
+
17
+ def description
18
+ "have ancestor #{query.description}"
19
+ end
20
+
21
+ def query
22
+ # @query ||= Capybara::Queries::AncestorQuery.new(*session_query_args, &@filter_block)
23
+ @query ||= Capybara::Queries::AncestorQuery.new(*session_query_args, **session_query_options, &@filter_block)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'capybara/rspec/matchers/base'
4
+
5
+ module Capybara
6
+ module RSpecMatchers
7
+ module Matchers
8
+ class HaveCurrentPath < WrappedElementMatcher
9
+ def element_matches?(el)
10
+ el.assert_current_path(current_path, **@kw_args)
11
+ end
12
+
13
+ def element_does_not_match?(el)
14
+ el.assert_no_current_path(current_path, **@kw_args)
15
+ end
16
+
17
+ def description
18
+ "have current path #{current_path.inspect}"
19
+ end
20
+
21
+ private
22
+
23
+ def current_path
24
+ @args.first
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'capybara/rspec/matchers/base'
4
+
5
+ module Capybara
6
+ module RSpecMatchers
7
+ module Matchers
8
+ class HaveSelector < CountableWrappedElementMatcher
9
+ def initialize(*args, **kw_args, &filter_block)
10
+ super
11
+ if (RUBY_VERSION >= '2.7') && (@args.size < 2) && @kw_args.keys.any?(String) # rubocop:disable Style/GuardClause
12
+ @args.push(@kw_args)
13
+ @kw_args = {}
14
+ end
15
+ end
16
+
17
+ def element_matches?(el)
18
+ el.assert_selector(*@args, **session_query_options, &@filter_block)
19
+ end
20
+
21
+ def element_does_not_match?(el)
22
+ el.assert_no_selector(*@args, **session_query_options, &@filter_block)
23
+ end
24
+
25
+ def description
26
+ "have #{query.description}"
27
+ end
28
+
29
+ def query
30
+ @query ||= Capybara::Queries::SelectorQuery.new(*session_query_args, **session_query_options, &@filter_block)
31
+ end
32
+ end
33
+
34
+ class HaveAllSelectors < WrappedElementMatcher
35
+ def element_matches?(el)
36
+ el.assert_all_of_selectors(*@args, **session_query_options, &@filter_block)
37
+ end
38
+
39
+ def does_not_match?(_actual)
40
+ raise ArgumentError, 'The have_all_selectors matcher does not support use with not_to/should_not'
41
+ end
42
+
43
+ def description
44
+ 'have all selectors'
45
+ end
46
+ end
47
+
48
+ class HaveNoSelectors < WrappedElementMatcher
49
+ def element_matches?(el)
50
+ el.assert_none_of_selectors(*@args, **session_query_options, &@filter_block)
51
+ end
52
+
53
+ def does_not_match?(_actual)
54
+ raise ArgumentError, 'The have_none_of_selectors matcher does not support use with not_to/should_not'
55
+ end
56
+
57
+ def description
58
+ 'have no selectors'
59
+ end
60
+ end
61
+
62
+ class HaveAnySelectors < WrappedElementMatcher
63
+ def element_matches?(el)
64
+ el.assert_any_of_selectors(*@args, **session_query_options, &@filter_block)
65
+ end
66
+
67
+ def does_not_match?(_actual)
68
+ el.assert_none_of_selectors(*@args, **session_query_options, &@filter_block)
69
+ end
70
+
71
+ def description
72
+ 'have any selectors'
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end