capybara 2.5.0 → 2.18.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (205) hide show
  1. checksums.yaml +5 -5
  2. data/.yard/templates_custom/default/class/html/selectors.erb +38 -0
  3. data/.yard/templates_custom/default/class/html/setup.rb +17 -0
  4. data/.yard/yard_extensions.rb +78 -0
  5. data/.yardopts +1 -0
  6. data/History.md +413 -10
  7. data/License.txt +1 -1
  8. data/README.md +237 -130
  9. data/lib/capybara/config.rb +132 -0
  10. data/lib/capybara/cucumber.rb +3 -1
  11. data/lib/capybara/driver/base.rb +27 -6
  12. data/lib/capybara/driver/node.rb +14 -5
  13. data/lib/capybara/dsl.rb +2 -3
  14. data/lib/capybara/helpers.rb +13 -65
  15. data/lib/capybara/minitest/spec.rb +177 -0
  16. data/lib/capybara/minitest.rb +278 -0
  17. data/lib/capybara/node/actions.rb +180 -24
  18. data/lib/capybara/node/base.rb +17 -5
  19. data/lib/capybara/node/document.rb +5 -0
  20. data/lib/capybara/node/document_matchers.rb +15 -14
  21. data/lib/capybara/node/element.rb +55 -7
  22. data/lib/capybara/node/finders.rb +179 -67
  23. data/lib/capybara/node/matchers.rb +301 -105
  24. data/lib/capybara/node/simple.rb +15 -4
  25. data/lib/capybara/queries/ancestor_query.rb +25 -0
  26. data/lib/capybara/queries/base_query.rb +69 -3
  27. data/lib/capybara/queries/current_path_query.rb +17 -8
  28. data/lib/capybara/queries/match_query.rb +19 -0
  29. data/lib/capybara/queries/selector_query.rb +251 -0
  30. data/lib/capybara/queries/sibling_query.rb +25 -0
  31. data/lib/capybara/queries/text_query.rb +67 -16
  32. data/lib/capybara/queries/title_query.rb +4 -2
  33. data/lib/capybara/query.rb +3 -131
  34. data/lib/capybara/rack_test/browser.rb +14 -5
  35. data/lib/capybara/rack_test/css_handlers.rb +1 -0
  36. data/lib/capybara/rack_test/driver.rb +15 -8
  37. data/lib/capybara/rack_test/form.rb +34 -12
  38. data/lib/capybara/rack_test/node.rb +29 -12
  39. data/lib/capybara/rails.rb +3 -3
  40. data/lib/capybara/result.rb +104 -9
  41. data/lib/capybara/rspec/compound.rb +95 -0
  42. data/lib/capybara/rspec/features.rb +17 -6
  43. data/lib/capybara/rspec/matcher_proxies.rb +45 -0
  44. data/lib/capybara/rspec/matchers.rb +199 -80
  45. data/lib/capybara/rspec.rb +4 -2
  46. data/lib/capybara/selector/css.rb +30 -0
  47. data/lib/capybara/selector/filter.rb +20 -0
  48. data/lib/capybara/selector/filter_set.rb +74 -0
  49. data/lib/capybara/selector/filters/base.rb +33 -0
  50. data/lib/capybara/selector/filters/expression_filter.rb +40 -0
  51. data/lib/capybara/selector/filters/node_filter.rb +27 -0
  52. data/lib/capybara/selector/selector.rb +276 -0
  53. data/lib/capybara/selector.rb +452 -157
  54. data/lib/capybara/selenium/driver.rb +282 -81
  55. data/lib/capybara/selenium/node.rb +144 -46
  56. data/lib/capybara/server.rb +59 -16
  57. data/lib/capybara/session/config.rb +114 -0
  58. data/lib/capybara/session/matchers.rb +29 -19
  59. data/lib/capybara/session.rb +378 -143
  60. data/lib/capybara/spec/fixtures/no_extension +1 -0
  61. data/lib/capybara/spec/public/jquery-ui.js +13 -791
  62. data/lib/capybara/spec/public/jquery.js +4 -9045
  63. data/lib/capybara/spec/public/test.js +45 -11
  64. data/lib/capybara/spec/session/accept_alert_spec.rb +30 -7
  65. data/lib/capybara/spec/session/accept_confirm_spec.rb +14 -2
  66. data/lib/capybara/spec/session/accept_prompt_spec.rb +35 -6
  67. data/lib/capybara/spec/session/all_spec.rb +45 -32
  68. data/lib/capybara/spec/session/ancestor_spec.rb +85 -0
  69. data/lib/capybara/spec/session/assert_all_of_selectors_spec.rb +110 -0
  70. data/lib/capybara/spec/session/assert_current_path.rb +15 -2
  71. data/lib/capybara/spec/session/assert_selector.rb +29 -28
  72. data/lib/capybara/spec/session/assert_text.rb +59 -20
  73. data/lib/capybara/spec/session/assert_title.rb +25 -11
  74. data/lib/capybara/spec/session/attach_file_spec.rb +42 -4
  75. data/lib/capybara/spec/session/body_spec.rb +1 -0
  76. data/lib/capybara/spec/session/check_spec.rb +90 -14
  77. data/lib/capybara/spec/session/choose_spec.rb +31 -5
  78. data/lib/capybara/spec/session/click_button_spec.rb +20 -9
  79. data/lib/capybara/spec/session/click_link_or_button_spec.rb +15 -9
  80. data/lib/capybara/spec/session/click_link_spec.rb +39 -15
  81. data/lib/capybara/spec/session/current_scope_spec.rb +2 -1
  82. data/lib/capybara/spec/session/current_url_spec.rb +12 -3
  83. data/lib/capybara/spec/session/dismiss_confirm_spec.rb +6 -5
  84. data/lib/capybara/spec/session/dismiss_prompt_spec.rb +4 -3
  85. data/lib/capybara/spec/session/element/assert_match_selector.rb +36 -0
  86. data/lib/capybara/spec/session/element/match_css_spec.rb +23 -0
  87. data/lib/capybara/spec/session/element/match_xpath_spec.rb +23 -0
  88. data/lib/capybara/spec/session/element/matches_selector_spec.rb +106 -0
  89. data/lib/capybara/spec/session/evaluate_async_script_spec.rb +22 -0
  90. data/lib/capybara/spec/session/evaluate_script_spec.rb +23 -1
  91. data/lib/capybara/spec/session/execute_script_spec.rb +22 -3
  92. data/lib/capybara/spec/session/fill_in_spec.rb +50 -32
  93. data/lib/capybara/spec/session/find_button_spec.rb +43 -2
  94. data/lib/capybara/spec/session/find_by_id_spec.rb +3 -2
  95. data/lib/capybara/spec/session/find_field_spec.rb +42 -6
  96. data/lib/capybara/spec/session/find_link_spec.rb +22 -3
  97. data/lib/capybara/spec/session/find_spec.rb +103 -57
  98. data/lib/capybara/spec/session/first_spec.rb +34 -18
  99. data/lib/capybara/spec/session/frame/switch_to_frame_spec.rb +103 -0
  100. data/lib/capybara/spec/session/{within_frame_spec.rb → frame/within_frame_spec.rb} +44 -2
  101. data/lib/capybara/spec/session/go_back_spec.rb +2 -1
  102. data/lib/capybara/spec/session/go_forward_spec.rb +2 -1
  103. data/lib/capybara/spec/session/has_all_selectors_spec.rb +69 -0
  104. data/lib/capybara/spec/session/has_button_spec.rb +17 -8
  105. data/lib/capybara/spec/session/has_css_spec.rb +85 -73
  106. data/lib/capybara/spec/session/has_current_path_spec.rb +91 -7
  107. data/lib/capybara/spec/session/has_field_spec.rb +93 -58
  108. data/lib/capybara/spec/session/has_link_spec.rb +9 -8
  109. data/lib/capybara/spec/session/has_none_selectors_spec.rb +76 -0
  110. data/lib/capybara/spec/session/has_select_spec.rb +159 -59
  111. data/lib/capybara/spec/session/has_selector_spec.rb +64 -28
  112. data/lib/capybara/spec/session/has_table_spec.rb +1 -0
  113. data/lib/capybara/spec/session/has_text_spec.rb +27 -12
  114. data/lib/capybara/spec/session/has_title_spec.rb +22 -4
  115. data/lib/capybara/spec/session/has_xpath_spec.rb +32 -29
  116. data/lib/capybara/spec/session/headers.rb +2 -1
  117. data/lib/capybara/spec/session/html_spec.rb +4 -3
  118. data/lib/capybara/spec/session/node_spec.rb +198 -38
  119. data/lib/capybara/spec/session/refresh_spec.rb +28 -0
  120. data/lib/capybara/spec/session/reset_session_spec.rb +46 -5
  121. data/lib/capybara/spec/session/response_code.rb +2 -1
  122. data/lib/capybara/spec/session/save_and_open_page_spec.rb +1 -0
  123. data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +6 -5
  124. data/lib/capybara/spec/session/save_page_spec.rb +34 -2
  125. data/lib/capybara/spec/session/save_screenshot_spec.rb +31 -1
  126. data/lib/capybara/spec/session/screenshot_spec.rb +4 -2
  127. data/lib/capybara/spec/session/select_spec.rb +34 -32
  128. data/lib/capybara/spec/session/selectors_spec.rb +65 -0
  129. data/lib/capybara/spec/session/sibling_spec.rb +52 -0
  130. data/lib/capybara/spec/session/text_spec.rb +4 -4
  131. data/lib/capybara/spec/session/title_spec.rb +2 -1
  132. data/lib/capybara/spec/session/uncheck_spec.rb +42 -2
  133. data/lib/capybara/spec/session/unselect_spec.rb +17 -16
  134. data/lib/capybara/spec/session/visit_spec.rb +77 -2
  135. data/lib/capybara/spec/session/window/become_closed_spec.rb +12 -11
  136. data/lib/capybara/spec/session/window/current_window_spec.rb +1 -0
  137. data/lib/capybara/spec/session/window/open_new_window_spec.rb +1 -0
  138. data/lib/capybara/spec/session/window/switch_to_window_spec.rb +16 -11
  139. data/lib/capybara/spec/session/window/window_opened_by_spec.rb +7 -4
  140. data/lib/capybara/spec/session/window/window_spec.rb +36 -29
  141. data/lib/capybara/spec/session/window/windows_spec.rb +1 -0
  142. data/lib/capybara/spec/session/window/within_window_spec.rb +31 -7
  143. data/lib/capybara/spec/session/within_spec.rb +14 -6
  144. data/lib/capybara/spec/spec_helper.rb +37 -4
  145. data/lib/capybara/spec/test_app.rb +15 -3
  146. data/lib/capybara/spec/views/buttons.erb +1 -0
  147. data/lib/capybara/spec/views/fieldsets.erb +2 -1
  148. data/lib/capybara/spec/views/form.erb +169 -9
  149. data/lib/capybara/spec/views/frame_child.erb +10 -2
  150. data/lib/capybara/spec/views/frame_one.erb +2 -1
  151. data/lib/capybara/spec/views/frame_parent.erb +3 -2
  152. data/lib/capybara/spec/views/frame_two.erb +2 -1
  153. data/lib/capybara/spec/views/header_links.erb +1 -0
  154. data/lib/capybara/spec/views/host_links.erb +1 -0
  155. data/lib/capybara/spec/views/initial_alert.erb +10 -0
  156. data/lib/capybara/spec/views/path.erb +1 -0
  157. data/lib/capybara/spec/views/popup_one.erb +1 -0
  158. data/lib/capybara/spec/views/popup_two.erb +1 -0
  159. data/lib/capybara/spec/views/postback.erb +2 -1
  160. data/lib/capybara/spec/views/tables.erb +1 -0
  161. data/lib/capybara/spec/views/with_base_tag.erb +1 -0
  162. data/lib/capybara/spec/views/with_count.erb +2 -1
  163. data/lib/capybara/spec/views/with_fixed_header_footer.erb +17 -0
  164. data/lib/capybara/spec/views/with_hover.erb +7 -1
  165. data/lib/capybara/spec/views/with_html.erb +40 -2
  166. data/lib/capybara/spec/views/with_html_entities.erb +1 -0
  167. data/lib/capybara/spec/views/with_js.erb +32 -1
  168. data/lib/capybara/spec/views/with_scope.erb +1 -0
  169. data/lib/capybara/spec/views/with_simple_html.erb +2 -1
  170. data/lib/capybara/spec/views/with_slow_unload.erb +17 -0
  171. data/lib/capybara/spec/views/with_title.erb +2 -1
  172. data/lib/capybara/spec/views/with_unload_alert.erb +14 -0
  173. data/lib/capybara/spec/views/with_windows.erb +7 -0
  174. data/lib/capybara/spec/views/within_frames.erb +3 -2
  175. data/lib/capybara/version.rb +2 -1
  176. data/lib/capybara/window.rb +20 -3
  177. data/lib/capybara.rb +189 -93
  178. data/spec/basic_node_spec.rb +7 -6
  179. data/spec/capybara_spec.rb +90 -4
  180. data/spec/dsl_spec.rb +3 -1
  181. data/spec/filter_set_spec.rb +28 -0
  182. data/spec/fixtures/capybara.csv +1 -0
  183. data/spec/fixtures/selenium_driver_rspec_failure.rb +5 -1
  184. data/spec/fixtures/selenium_driver_rspec_success.rb +5 -1
  185. data/spec/minitest_spec.rb +130 -0
  186. data/spec/minitest_spec_spec.rb +135 -0
  187. data/spec/per_session_config_spec.rb +67 -0
  188. data/spec/rack_test_spec.rb +50 -7
  189. data/spec/result_spec.rb +76 -0
  190. data/spec/rspec/features_spec.rb +21 -8
  191. data/spec/rspec/scenarios_spec.rb +21 -0
  192. data/spec/rspec/{matchers_spec.rb → shared_spec_matchers.rb} +160 -54
  193. data/spec/rspec/views_spec.rb +5 -0
  194. data/spec/rspec_matchers_spec.rb +46 -0
  195. data/spec/rspec_spec.rb +79 -1
  196. data/spec/selector_spec.rb +199 -0
  197. data/spec/selenium_spec_chrome.rb +54 -9
  198. data/spec/selenium_spec_firefox.rb +68 -0
  199. data/spec/selenium_spec_marionette.rb +127 -0
  200. data/spec/server_spec.rb +102 -14
  201. data/spec/session_spec.rb +54 -0
  202. data/spec/shared_selenium_session.rb +215 -0
  203. data/spec/spec_helper.rb +7 -0
  204. metadata +140 -15
  205. data/spec/selenium_spec.rb +0 -128
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Capybara
2
3
  module Node
3
4
  module DocumentMatchers
@@ -10,17 +11,12 @@ module Capybara
10
11
  # @overload $0(regexp, options = {})
11
12
  # @param regexp [Regexp] The regexp that title should match to
12
13
  # @option options [Numeric] :wait (Capybara.default_max_wait_time) Maximum time that Capybara will wait for title to eq/match given string/regexp argument
14
+ # @option options [Boolean] :exact (false) When passed a string should the match be exact or just substring
13
15
  # @raise [Capybara::ExpectationNotMet] if the assertion hasn't succeeded during wait time
14
16
  # @return [true]
15
17
  #
16
18
  def assert_title(title, options = {})
17
- query = Capybara::Queries::TitleQuery.new(title, options)
18
- synchronize(query.wait) do
19
- unless query.resolves_for?(self)
20
- raise Capybara::ExpectationNotMet, query.failure_message
21
- end
22
- end
23
- return true
19
+ _verify_title(title,options) { |query| raise Capybara::ExpectationNotMet, query.failure_message unless query.resolves_for?(self) }
24
20
  end
25
21
 
26
22
  ##
@@ -31,13 +27,7 @@ module Capybara
31
27
  # @return [true]
32
28
  #
33
29
  def assert_no_title(title, options = {})
34
- query = Capybara::Queries::TitleQuery.new(title, options)
35
- synchronize(query.wait) do
36
- if query.resolves_for?(self)
37
- raise Capybara::ExpectationNotMet, query.negative_failure_message
38
- end
39
- end
40
- return true
30
+ _verify_title(title,options) { |query| raise Capybara::ExpectationNotMet, query.negative_failure_message if query.resolves_for?(self) }
41
31
  end
42
32
 
43
33
  ##
@@ -63,6 +53,17 @@ module Capybara
63
53
  rescue Capybara::ExpectationNotMet
64
54
  return false
65
55
  end
56
+
57
+ private
58
+
59
+ def _verify_title(title, options)
60
+ query = Capybara::Queries::TitleQuery.new(title, options)
61
+ synchronize(query.wait) do
62
+ yield(query)
63
+ end
64
+ return true
65
+ end
66
+
66
67
  end
67
68
  end
68
69
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Capybara
2
3
  module Node
3
4
 
@@ -9,7 +10,7 @@ module Capybara
9
10
  # session = Capybara::Session.new(:rack_test, my_app)
10
11
  #
11
12
  # bar = session.find('#bar') # from Capybara::Node::Finders
12
- # bar.select('Baz', :from => 'Quox') # from Capybara::Node::Actions
13
+ # bar.select('Baz', from: 'Quox') # from Capybara::Node::Actions
13
14
  #
14
15
  # {Capybara::Node::Element} also has access to HTML attributes and other properties of the
15
16
  # element:
@@ -22,10 +23,11 @@ module Capybara
22
23
  #
23
24
  class Element < Base
24
25
 
25
- def initialize(session, base, parent, query)
26
+ def initialize(session, base, query_scope, query)
26
27
  super(session, base)
27
- @parent = parent
28
+ @query_scope = query_scope
28
29
  @query = query
30
+ @allow_reload = false
29
31
  end
30
32
 
31
33
  def allow_reload!
@@ -53,7 +55,7 @@ module Capybara
53
55
  # @return [String] The text of the element
54
56
  #
55
57
  def text(type=nil)
56
- type ||= :all unless Capybara.ignore_hidden_elements or Capybara.visible_text_only
58
+ type ||= :all unless session_options.ignore_hidden_elements or session_options.visible_text_only
57
59
  synchronize do
58
60
  if type == :all
59
61
  base.all_text
@@ -91,6 +93,7 @@ module Capybara
91
93
  # @param [String] value The new value
92
94
  # @param [Hash{}] options Driver specific options for how to set the value
93
95
  #
96
+ # @return [Capybara::Node::Element] The element
94
97
  def set(value, options={})
95
98
  options ||= {}
96
99
 
@@ -107,47 +110,58 @@ module Capybara
107
110
  base.set(value)
108
111
  end
109
112
  end
113
+ return self
110
114
  end
111
115
 
112
116
  ##
113
117
  #
114
118
  # Select this node if is an option element inside a select tag
115
119
  #
120
+ # @return [Capybara::Node::Element] The element
116
121
  def select_option
117
122
  warn "Attempt to select disabled option: #{value || text}" if disabled?
118
123
  synchronize { base.select_option }
124
+ return self
119
125
  end
120
126
 
121
127
  ##
122
128
  #
123
129
  # Unselect this node if is an option element inside a multiple select tag
124
130
  #
131
+ # @return [Capybara::Node::Element] The element
125
132
  def unselect_option
126
133
  synchronize { base.unselect_option }
134
+ return self
127
135
  end
128
136
 
129
137
  ##
130
138
  #
131
139
  # Click the Element
132
140
  #
141
+ # @return [Capybara::Node::Element] The element
133
142
  def click
134
143
  synchronize { base.click }
144
+ return self
135
145
  end
136
146
 
137
147
  ##
138
148
  #
139
149
  # Right Click the Element
140
150
  #
151
+ # @return [Capybara::Node::Element] The element
141
152
  def right_click
142
153
  synchronize { base.right_click }
154
+ return self
143
155
  end
144
156
 
145
157
  ##
146
158
  #
147
159
  # Double Click the Element
148
160
  #
161
+ # @return [Capybara::Node::Element] The element
149
162
  def double_click
150
163
  synchronize { base.double_click }
164
+ return self
151
165
  end
152
166
 
153
167
  ##
@@ -220,16 +234,20 @@ module Capybara
220
234
  # :meta
221
235
  # :command - alias of :meta
222
236
  #
237
+ # @return [Capybara::Node::Element] The element
223
238
  def send_keys(*args)
224
239
  synchronize { base.send_keys(*args) }
240
+ return self
225
241
  end
226
242
 
227
243
  ##
228
244
  #
229
245
  # Hover on the Element
230
246
  #
247
+ # @return [Capybara::Node::Element] The element
231
248
  def hover
232
249
  synchronize { base.hover }
250
+ return self
233
251
  end
234
252
 
235
253
  ##
@@ -281,6 +299,26 @@ module Capybara
281
299
  synchronize { base.disabled? }
282
300
  end
283
301
 
302
+ ##
303
+ #
304
+ # Whether or not the element is readonly.
305
+ #
306
+ # @return [Boolean] Whether the element is readonly
307
+ #
308
+ def readonly?
309
+ synchronize { base.readonly? }
310
+ end
311
+
312
+ ##
313
+ #
314
+ # Whether or not the element supports multiple results.
315
+ #
316
+ # @return [Boolean] Whether the element supports multiple results.
317
+ #
318
+ def multiple?
319
+ synchronize { base.multiple? }
320
+ end
321
+
284
322
  ##
285
323
  #
286
324
  # An XPath expression describing where on the page the element can be found
@@ -298,8 +336,10 @@ module Capybara
298
336
  #
299
337
  # @param [String] event The name of the event to trigger
300
338
  #
339
+ # @return [Capybara::Node::Element] The element
301
340
  def trigger(event)
302
341
  synchronize { base.trigger(event) }
342
+ return self
303
343
  end
304
344
 
305
345
  ##
@@ -312,14 +352,16 @@ module Capybara
312
352
  #
313
353
  # @param [Capybara::Node::Element] node The element to drag to
314
354
  #
355
+ # @return [Capybara::Node::Element] The element
315
356
  def drag_to(node)
316
357
  synchronize { base.drag_to(node.base) }
358
+ return self
317
359
  end
318
360
 
319
361
  def reload
320
362
  if @allow_reload
321
363
  begin
322
- reloaded = parent.reload.first(@query.name, @query.locator, @query.options)
364
+ reloaded = query_scope.reload.first(@query.name, @query.locator, @query.options)
323
365
  @base = reloaded.base if reloaded
324
366
  rescue => e
325
367
  raise e unless catch_error?(e)
@@ -329,9 +371,15 @@ module Capybara
329
371
  end
330
372
 
331
373
  def inspect
332
- %(#<Capybara::Node::Element tag="#{tag_name}" path="#{path}">)
374
+ %(#<Capybara::Node::Element tag="#{base.tag_name}" path="#{base.path}">)
333
375
  rescue NotSupportedByDriverError
334
- %(#<Capybara::Node::Element tag="#{tag_name}">)
376
+ %(#<Capybara::Node::Element tag="#{base.tag_name}">)
377
+ rescue => e
378
+ if session.driver.invalid_element_errors.any? { |et| e.is_a?(et)}
379
+ %(Obsolete #<Capybara::Node::Element>)
380
+ else
381
+ raise
382
+ end
335
383
  end
336
384
  end
337
385
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Capybara
2
3
  module Node
3
4
  module Finders
@@ -17,8 +18,8 @@ module Capybara
17
18
  # +find+ takes the same options as +all+.
18
19
  #
19
20
  # page.find('#foo').find('.bar')
20
- # page.find(:xpath, '//div[contains(., "bar")]')
21
- # page.find('li', :text => 'Quox').click_link('Delete')
21
+ # page.find(:xpath, './/div[contains(., "bar")]')
22
+ # page.find('li', text: 'Quox').click_link('Delete')
22
23
  #
23
24
  # @param (see Capybara::Node::Finders#all)
24
25
  #
@@ -27,43 +28,104 @@ module Capybara
27
28
  # @return [Capybara::Node::Element] The found element
28
29
  # @raise [Capybara::ElementNotFound] If the element can't be found before time expires
29
30
  #
30
- def find(*args)
31
- query = Capybara::Query.new(*args)
32
- synchronize(query.wait) do
33
- if query.match == :smart or query.match == :prefer_exact
34
- result = query.resolve_for(self, true)
35
- result = query.resolve_for(self, false) if result.size == 0 && !query.exact?
36
- else
37
- result = query.resolve_for(self)
38
- end
39
- if query.match == :one or query.match == :smart and result.size > 1
40
- raise Capybara::Ambiguous.new("Ambiguous match, found #{result.size} elements matching #{query.description}")
41
- end
42
- if result.size == 0
43
- raise Capybara::ElementNotFound.new("Unable to find #{query.description}")
44
- end
45
- result.first
46
- end.tap(&:allow_reload!)
31
+ def find(*args, &optional_filter_block)
32
+ if args.last.is_a? Hash
33
+ args.last[:session_options] = session_options
34
+ else
35
+ args.push(session_options: session_options)
36
+ end
37
+ synced_resolve Capybara::Queries::SelectorQuery.new(*args, &optional_filter_block)
47
38
  end
48
39
 
49
40
  ##
50
41
  #
51
- # Find a form field on the page. The field can be found by its name, id or label text.
42
+ # Find an {Capybara::Node::Element} based on the given arguments that is also an ancestor of the element called on. +ancestor+ will raise an error if the element
43
+ # is not found.
44
+ #
45
+ # +ancestor+ takes the same options as +find+.
46
+ #
47
+ # element.ancestor('#foo').find('.bar')
48
+ # element.ancestor(:xpath, './/div[contains(., "bar")]')
49
+ # element.ancestor('ul', text: 'Quox').click_link('Delete')
50
+ #
51
+ # @param (see Capybara::Node::Finders#find)
52
+ #
53
+ # @!macro waiting_behavior
54
+ #
55
+ # @option options [Boolean] match The matching strategy to use.
56
+ #
57
+ # @return [Capybara::Node::Element] The found element
58
+ # @raise [Capybara::ElementNotFound] If the element can't be found before time expires
59
+ #
60
+ def ancestor(*args, &optional_filter_block)
61
+ if args.last.is_a? Hash
62
+ args.last[:session_options] = session_options
63
+ else
64
+ args.push(session_options: session_options)
65
+ end
66
+ synced_resolve Capybara::Queries::AncestorQuery.new(*args, &optional_filter_block)
67
+ end
68
+
69
+ ##
70
+ #
71
+ # Find an {Capybara::Node::Element} based on the given arguments that is also a sibling of the element called on. +sibling+ will raise an error if the element
72
+ # is not found.
73
+ #
74
+ #
75
+ # +sibling+ takes the same options as +find+.
76
+ #
77
+ # element.sibling('#foo').find('.bar')
78
+ # element.sibling(:xpath, './/div[contains(., "bar")]')
79
+ # element.sibling('ul', text: 'Quox').click_link('Delete')
80
+ #
81
+ # @param (see Capybara::Node::Finders#find)
52
82
  #
53
83
  # @macro waiting_behavior
54
84
  #
55
- # @param [String] locator Which field to find
85
+ # @option options [Boolean] match The matching strategy to use.
86
+ #
87
+ # @return [Capybara::Node::Element] The found element
88
+ # @raise [Capybara::ElementNotFound] If the element can't be found before time expires
89
+ #
90
+ def sibling(*args, &optional_filter_block)
91
+ if args.last.is_a? Hash
92
+ args.last[:session_options] = session_options
93
+ else
94
+ args.push(session_options: session_options)
95
+ end
96
+ synced_resolve Capybara::Queries::SiblingQuery.new(*args, &optional_filter_block)
97
+ end
98
+
99
+ ##
100
+ #
101
+ # Find a form field on the page. The field can be found by its name, id or label text.
56
102
  #
57
- # @option options [Boolean] checked Match checked field?
58
- # @option options [Boolean] unchecked Match unchecked field?
59
- # @option options [Boolean] disabled (false) Match disabled field?
60
- # @option options [Boolean] readonly Match readonly field?
61
- # @option options [String] with Value of field to match on
62
- # @option options [String] type Type of field to match on
103
+ # @overload find_field([locator], options={})
104
+ # @param [String] locator name, id, placeholder or text of associated label element
105
+ #
106
+ # @macro waiting_behavior
107
+ #
108
+ #
109
+ # @option options [Boolean] checked Match checked field?
110
+ # @option options [Boolean] unchecked Match unchecked field?
111
+ # @option options [Boolean, Symbol] disabled (false) Match disabled field?
112
+ # * true - only finds a disabled field
113
+ # * false - only finds an enabled field
114
+ # * :all - finds either an enabled or disabled field
115
+ # @option options [Boolean] readonly Match readonly field?
116
+ # @option options [String, Regexp] with Value of field to match on
117
+ # @option options [String] type Type of field to match on
118
+ # @option options [Boolean] multiple Match fields that can have multiple values?
119
+ # @option options [String] id Match fields that match the id attribute
120
+ # @option options [String] name Match fields that match the name attribute
121
+ # @option options [String] placeholder Match fields that match the placeholder attribute
122
+ # @option options [String, Array<String>] Match fields that match the class(es) passed
63
123
  # @return [Capybara::Node::Element] The found element
64
124
  #
65
- def find_field(locator, options={})
66
- find(:field, locator, options)
125
+
126
+ def find_field(locator=nil, options={}, &optional_filter_block)
127
+ locator, options = nil, locator if locator.is_a? Hash
128
+ find(:field, locator, options, &optional_filter_block)
67
129
  end
68
130
  alias_method :field_labeled, :find_field
69
131
 
@@ -71,14 +133,21 @@ module Capybara
71
133
  #
72
134
  # Find a link on the page. The link can be found by its id or text.
73
135
  #
74
- # @macro waiting_behavior
136
+ # @overload find_link([locator], options={})
137
+ # @param [String] locator id, title, text, or alt of enclosed img element
138
+ #
139
+ # @macro waiting_behavior
75
140
  #
76
- # @param [String] locator Which link to find
77
- # @option options [String,Regexp] href Value to match against the links href
141
+ # @option options [String,Regexp,nil] href Value to match against the links href, if nil finds link placeholders (<a> elements with no href attribute)
142
+ # @option options [String] id Match links with the id provided
143
+ # @option options [String] title Match links with the title provided
144
+ # @option options [String] alt Match links with a contained img element whose alt matches
145
+ # @option options [String, Array<String>] class Match links that match the class(es) provided
78
146
  # @return [Capybara::Node::Element] The found element
79
147
  #
80
- def find_link(locator, options={})
81
- find(:link, locator, options)
148
+ def find_link(locator=nil, options={}, &optional_filter_block)
149
+ locator, options = nil, locator if locator.is_a? Hash
150
+ find(:link, locator, options, &optional_filter_block)
82
151
  end
83
152
 
84
153
  ##
@@ -87,15 +156,27 @@ module Capybara
87
156
  # This can be any \<input> element of type submit, reset, image, button or it can be a
88
157
  # \<button> element. All buttons can be found by their id, value, or title. \<button> elements can also be found
89
158
  # by their text content, and image \<input> elements by their alt attribute
90
-
91
- # @macro waiting_behavior
92
159
  #
93
- # @param [String] locator Which button to find
94
- # @option options [Boolean] disabled (false) Match disabled button?
160
+ # @overload find_button([locator], options={})
161
+ # @param [String] locator id, value, title, text content, alt of image
162
+ #
163
+ # @overload find_button(options={})
164
+ #
165
+ # @macro waiting_behavior
166
+ #
167
+ # @option options [Boolean, Symbol] disabled (false) Match disabled button?
168
+ # * true - only finds a disabled button
169
+ # * false - only finds an enabled button
170
+ # * :all - finds either an enabled or disabled button
171
+ # @option options [String] id Match buttons with the id provided
172
+ # @option options [String] title Match buttons with the title provided
173
+ # @option options [String] value Match buttons with the value provided
174
+ # @option options [String, Array<String>] class Match buttons that match the class(es) provided
95
175
  # @return [Capybara::Node::Element] The found element
96
176
  #
97
- def find_button(locator, options={})
98
- find(:button, locator, options)
177
+ def find_button(locator=nil, options={}, &optional_filter_block)
178
+ locator, options = nil, locator if locator.is_a? Hash
179
+ find(:button, locator, options, &optional_filter_block)
99
180
  end
100
181
 
101
182
  ##
@@ -104,15 +185,16 @@ module Capybara
104
185
  #
105
186
  # @macro waiting_behavior
106
187
  #
107
- # @param [String] id Which element to find
188
+ # @param [String] id id of element
108
189
  #
109
190
  # @return [Capybara::Node::Element] The found element
110
191
  #
111
- def find_by_id(id, options={})
112
- find(:id, id, options)
192
+ def find_by_id(id, options={}, &optional_filter_block)
193
+ find(:id, id, options, &optional_filter_block)
113
194
  end
114
195
 
115
196
  ##
197
+ # @!method all([kind = Capybara.default_selector], locator = nil, options = {})
116
198
  #
117
199
  # Find all elements on the page matching the given selector
118
200
  # and options.
@@ -122,7 +204,7 @@ module Capybara
122
204
  # following statements are equivalent:
123
205
  #
124
206
  # page.all(:css, 'a#person_123')
125
- # page.all(:xpath, '//a[@id="person_123"]')
207
+ # page.all(:xpath, './/a[@id="person_123"]')
126
208
  #
127
209
  #
128
210
  # If the type of selector is left out, Capybara uses
@@ -131,47 +213,56 @@ module Capybara
131
213
  # page.all("a#person_123")
132
214
  #
133
215
  # Capybara.default_selector = :xpath
134
- # page.all('//a[@id="person_123"]')
216
+ # page.all('.//a[@id="person_123"]')
135
217
  #
136
218
  # The set of found elements can further be restricted by specifying
137
219
  # options. It's possible to select elements by their text or visibility:
138
220
  #
139
- # page.all('a', :text => 'Home')
140
- # page.all('#menu li', :visible => true)
221
+ # page.all('a', text: 'Home')
222
+ # page.all('#menu li', visible: true)
141
223
  #
142
224
  # By default if no elements are found, an empty array is returned;
143
225
  # however, expectations can be set on the number of elements to be found which
144
226
  # will trigger Capybara's waiting behavior for the expectations to match.The
145
227
  # expectations can be set using
146
228
  #
147
- # page.assert_selector('p#foo', :count => 4)
148
- # page.assert_selector('p#foo', :maximum => 10)
149
- # page.assert_selector('p#foo', :minimum => 1)
150
- # page.assert_selector('p#foo', :between => 1..10)
229
+ # page.assert_selector('p#foo', count: 4)
230
+ # page.assert_selector('p#foo', maximum: 10)
231
+ # page.assert_selector('p#foo', minimum: 1)
232
+ # page.assert_selector('p#foo', between: 1..10)
151
233
  #
152
234
  # See {Capybara::Helpers#matches_count?} for additional information about
153
235
  # count matching.
154
236
  #
155
- # @overload all([kind], locator, options)
156
- # @param [:css, :xpath] kind The type of selector
157
- # @param [String] locator The selector
158
- # @option options [String, Regexp] text Only find elements which contain this text or match this regexp
159
- # @option options [Boolean, Symbol] visible Only find elements with the specified visibility:
237
+ # @param [Symbol] kind Optional selector type (:css, :xpath, :field, etc.) - Defaults to Capybara.default_selector
238
+ # @param [String] locator The selector
239
+ # @option options [String, Regexp] text Only find elements which contain this text or match this regexp
240
+ # @option options [String, Boolean] exact_text (Capybara.exact_text) When String the string the elements contained text must match exactly, when Boolean controls whether the :text option must match exactly
241
+ # @option options [Boolean, Symbol] visible Only find elements with the specified visibility:
160
242
  # * true - only finds visible elements.
161
243
  # * false - finds invisible _and_ visible elements.
162
244
  # * :all - same as false; finds visible and invisible elements.
163
245
  # * :hidden - only finds invisible elements.
164
246
  # * :visible - same as true; only finds visible elements.
165
- # @option options [Integer] count Exact number of matches that are expected to be found
166
- # @option options [Integer] maximum Maximum number of matches that are expected to be found
167
- # @option options [Integer] minimum Minimum number of matches that are expected to be found
168
- # @option options [Range] between Number of matches found must be within the given range
169
- # @option options [Boolean] exact Control whether `is` expressions in the given XPath match exactly or partially
170
- # @option options [Integer] wait (Capybara.default_max_wait_time) The time to wait for element count expectations to become true
247
+ # @option options [Integer] count Exact number of matches that are expected to be found
248
+ # @option options [Integer] maximum Maximum number of matches that are expected to be found
249
+ # @option options [Integer] minimum Minimum number of matches that are expected to be found
250
+ # @option options [Range] between Number of matches found must be within the given range
251
+ # @option options [Boolean] exact Control whether `is` expressions in the given XPath match exactly or partially
252
+ # @option options [Integer] wait (Capybara.default_max_wait_time) The time to wait for element count expectations to become true
253
+ # @overload all([kind = Capybara.default_selector], locator = nil, options = {})
254
+ # @overload all([kind = Capybara.default_selector], locator = nil, options = {}, &filter_block)
255
+ # @yieldparam element [Capybara::Node::Element] The element being considered for inclusion in the results
256
+ # @yieldreturn [Boolean] Should the element be considered in the results?
171
257
  # @return [Capybara::Result] A collection of found elements
172
258
  #
173
- def all(*args)
174
- query = Capybara::Query.new(*args)
259
+ def all(*args, &optional_filter_block)
260
+ if args.last.is_a? Hash
261
+ args.last[:session_options] = session_options
262
+ else
263
+ args.push(session_options: session_options)
264
+ end
265
+ query = Capybara::Queries::SelectorQuery.new(*args, &optional_filter_block)
175
266
  synchronize(query.wait) do
176
267
  result = query.resolve_for(self)
177
268
  raise Capybara::ExpectationNotMet, result.failure_message unless result.matches_count?
@@ -195,15 +286,36 @@ module Capybara
195
286
  # @param [Hash] options Additional options; see {#all}
196
287
  # @return [Capybara::Node::Element] The found element or nil
197
288
  #
198
- def first(*args)
199
- if Capybara.wait_on_first_by_default
289
+ def first(*args, &optional_filter_block)
290
+ if session_options.wait_on_first_by_default
200
291
  options = if args.last.is_a?(Hash) then args.pop.dup else {} end
201
292
  args.push({minimum: 1}.merge(options))
202
293
  end
203
- all(*args).first
294
+ all(*args, &optional_filter_block).first
204
295
  rescue Capybara::ExpectationNotMet
205
296
  nil
206
297
  end
298
+
299
+ private
300
+
301
+ def synced_resolve(query)
302
+ synchronize(query.wait) do
303
+ if (query.match == :smart or query.match == :prefer_exact)
304
+ result = query.resolve_for(self, true)
305
+ result = query.resolve_for(self, false) if result.empty? && query.supports_exact? && !query.exact?
306
+ else
307
+ result = query.resolve_for(self)
308
+ end
309
+
310
+ if query.match == :one or query.match == :smart and result.size > 1
311
+ raise Capybara::Ambiguous.new("Ambiguous match, found #{result.size} elements matching #{query.description}")
312
+ end
313
+ if result.empty?
314
+ raise Capybara::ElementNotFound.new("Unable to find #{query.description}")
315
+ end
316
+ result.first
317
+ end.tap(&:allow_reload!)
318
+ end
207
319
  end
208
320
  end
209
321
  end