capybara 3.16.1 → 3.33.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (197) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +1 -0
  3. data/History.md +321 -0
  4. data/README.md +51 -60
  5. data/lib/capybara.rb +71 -114
  6. data/lib/capybara/config.rb +8 -5
  7. data/lib/capybara/cucumber.rb +1 -1
  8. data/lib/capybara/driver/node.rb +15 -3
  9. data/lib/capybara/dsl.rb +10 -2
  10. data/lib/capybara/helpers.rb +5 -3
  11. data/lib/capybara/minitest.rb +242 -141
  12. data/lib/capybara/minitest/spec.rb +159 -90
  13. data/lib/capybara/node/actions.rb +85 -74
  14. data/lib/capybara/node/base.rb +4 -4
  15. data/lib/capybara/node/document.rb +2 -2
  16. data/lib/capybara/node/document_matchers.rb +3 -3
  17. data/lib/capybara/node/element.rb +216 -117
  18. data/lib/capybara/node/finders.rb +65 -65
  19. data/lib/capybara/node/matchers.rb +228 -126
  20. data/lib/capybara/node/simple.rb +9 -4
  21. data/lib/capybara/queries/ancestor_query.rb +5 -7
  22. data/lib/capybara/queries/base_query.rb +2 -1
  23. data/lib/capybara/queries/current_path_query.rb +1 -1
  24. data/lib/capybara/queries/selector_query.rb +296 -30
  25. data/lib/capybara/queries/sibling_query.rb +5 -4
  26. data/lib/capybara/queries/style_query.rb +2 -2
  27. data/lib/capybara/queries/text_query.rb +13 -1
  28. data/lib/capybara/queries/title_query.rb +1 -1
  29. data/lib/capybara/rack_test/browser.rb +7 -2
  30. data/lib/capybara/rack_test/driver.rb +1 -1
  31. data/lib/capybara/rack_test/form.rb +1 -1
  32. data/lib/capybara/rack_test/node.rb +43 -7
  33. data/lib/capybara/registration_container.rb +44 -0
  34. data/lib/capybara/registrations/drivers.rb +36 -0
  35. data/lib/capybara/registrations/patches/puma_ssl.rb +27 -0
  36. data/lib/capybara/registrations/servers.rb +44 -0
  37. data/lib/capybara/result.rb +36 -8
  38. data/lib/capybara/rspec/matcher_proxies.rb +6 -4
  39. data/lib/capybara/rspec/matchers.rb +100 -63
  40. data/lib/capybara/rspec/matchers/base.rb +23 -10
  41. data/lib/capybara/rspec/matchers/count_sugar.rb +37 -0
  42. data/lib/capybara/rspec/matchers/have_ancestor.rb +28 -0
  43. data/lib/capybara/rspec/matchers/have_current_path.rb +2 -2
  44. data/lib/capybara/rspec/matchers/have_selector.rb +16 -8
  45. data/lib/capybara/rspec/matchers/have_sibling.rb +27 -0
  46. data/lib/capybara/rspec/matchers/have_text.rb +4 -4
  47. data/lib/capybara/rspec/matchers/have_title.rb +2 -2
  48. data/lib/capybara/rspec/matchers/match_selector.rb +3 -3
  49. data/lib/capybara/rspec/matchers/match_style.rb +2 -2
  50. data/lib/capybara/rspec/matchers/spatial_sugar.rb +39 -0
  51. data/lib/capybara/selector.rb +219 -588
  52. data/lib/capybara/selector/builders/css_builder.rb +10 -6
  53. data/lib/capybara/selector/builders/xpath_builder.rb +1 -1
  54. data/lib/capybara/selector/css.rb +4 -2
  55. data/lib/capybara/selector/definition.rb +277 -0
  56. data/lib/capybara/selector/definition/button.rb +52 -0
  57. data/lib/capybara/selector/definition/checkbox.rb +26 -0
  58. data/lib/capybara/selector/definition/css.rb +10 -0
  59. data/lib/capybara/selector/definition/datalist_input.rb +35 -0
  60. data/lib/capybara/selector/definition/datalist_option.rb +25 -0
  61. data/lib/capybara/selector/definition/element.rb +27 -0
  62. data/lib/capybara/selector/definition/field.rb +40 -0
  63. data/lib/capybara/selector/definition/fieldset.rb +14 -0
  64. data/lib/capybara/selector/definition/file_field.rb +13 -0
  65. data/lib/capybara/selector/definition/fillable_field.rb +33 -0
  66. data/lib/capybara/selector/definition/frame.rb +17 -0
  67. data/lib/capybara/selector/definition/id.rb +6 -0
  68. data/lib/capybara/selector/definition/label.rb +62 -0
  69. data/lib/capybara/selector/definition/link.rb +54 -0
  70. data/lib/capybara/selector/definition/link_or_button.rb +16 -0
  71. data/lib/capybara/selector/definition/option.rb +27 -0
  72. data/lib/capybara/selector/definition/radio_button.rb +27 -0
  73. data/lib/capybara/selector/definition/select.rb +81 -0
  74. data/lib/capybara/selector/definition/table.rb +109 -0
  75. data/lib/capybara/selector/definition/table_row.rb +21 -0
  76. data/lib/capybara/selector/definition/xpath.rb +5 -0
  77. data/lib/capybara/selector/filter_set.rb +13 -9
  78. data/lib/capybara/selector/filters/base.rb +11 -2
  79. data/lib/capybara/selector/filters/locator_filter.rb +13 -3
  80. data/lib/capybara/selector/regexp_disassembler.rb +9 -2
  81. data/lib/capybara/selector/selector.rb +43 -448
  82. data/lib/capybara/selenium/atoms/getAttribute.min.js +1 -0
  83. data/lib/capybara/selenium/atoms/isDisplayed.min.js +1 -0
  84. data/lib/capybara/selenium/atoms/src/getAttribute.js +161 -0
  85. data/lib/capybara/selenium/atoms/src/isDisplayed.js +454 -0
  86. data/lib/capybara/selenium/driver.rb +125 -56
  87. data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +73 -17
  88. data/lib/capybara/selenium/driver_specializations/edge_driver.rb +124 -0
  89. data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +41 -2
  90. data/lib/capybara/selenium/driver_specializations/internet_explorer_driver.rb +14 -1
  91. data/lib/capybara/selenium/driver_specializations/safari_driver.rb +14 -5
  92. data/lib/capybara/selenium/extensions/file_input_click_emulation.rb +34 -0
  93. data/lib/capybara/selenium/extensions/find.rb +67 -45
  94. data/lib/capybara/selenium/extensions/html5_drag.rb +152 -36
  95. data/lib/capybara/selenium/extensions/modifier_keys_stack.rb +28 -0
  96. data/lib/capybara/selenium/logger_suppressor.rb +34 -0
  97. data/lib/capybara/selenium/node.rb +227 -56
  98. data/lib/capybara/selenium/nodes/chrome_node.rb +93 -8
  99. data/lib/capybara/selenium/nodes/edge_node.rb +104 -0
  100. data/lib/capybara/selenium/nodes/firefox_node.rb +37 -59
  101. data/lib/capybara/selenium/nodes/ie_node.rb +22 -0
  102. data/lib/capybara/selenium/nodes/safari_node.rb +27 -54
  103. data/lib/capybara/selenium/patches/action_pauser.rb +26 -0
  104. data/lib/capybara/selenium/patches/atoms.rb +18 -0
  105. data/lib/capybara/selenium/patches/is_displayed.rb +16 -0
  106. data/lib/capybara/selenium/patches/logs.rb +45 -0
  107. data/lib/capybara/server.rb +19 -3
  108. data/lib/capybara/server/animation_disabler.rb +2 -2
  109. data/lib/capybara/server/checker.rb +6 -2
  110. data/lib/capybara/server/middleware.rb +23 -13
  111. data/lib/capybara/session.rb +124 -106
  112. data/lib/capybara/session/config.rb +12 -10
  113. data/lib/capybara/session/matchers.rb +6 -6
  114. data/lib/capybara/spec/public/offset.js +6 -0
  115. data/lib/capybara/spec/public/test.js +94 -5
  116. data/lib/capybara/spec/session/all_spec.rb +84 -6
  117. data/lib/capybara/spec/session/ancestor_spec.rb +5 -0
  118. data/lib/capybara/spec/session/assert_current_path_spec.rb +5 -2
  119. data/lib/capybara/spec/session/assert_text_spec.rb +9 -5
  120. data/lib/capybara/spec/session/attach_file_spec.rb +14 -6
  121. data/lib/capybara/spec/session/check_spec.rb +10 -4
  122. data/lib/capybara/spec/session/choose_spec.rb +8 -2
  123. data/lib/capybara/spec/session/click_button_spec.rb +44 -1
  124. data/lib/capybara/spec/session/click_link_spec.rb +11 -0
  125. data/lib/capybara/spec/session/evaluate_script_spec.rb +12 -0
  126. data/lib/capybara/spec/session/fill_in_spec.rb +37 -2
  127. data/lib/capybara/spec/session/find_spec.rb +60 -6
  128. data/lib/capybara/spec/session/first_spec.rb +1 -1
  129. data/lib/capybara/spec/session/frame/switch_to_frame_spec.rb +14 -1
  130. data/lib/capybara/spec/session/frame/within_frame_spec.rb +12 -1
  131. data/lib/capybara/spec/session/has_ancestor_spec.rb +46 -0
  132. data/lib/capybara/spec/session/has_button_spec.rb +16 -0
  133. data/lib/capybara/spec/session/has_css_spec.rb +35 -6
  134. data/lib/capybara/spec/session/has_current_path_spec.rb +6 -4
  135. data/lib/capybara/spec/session/has_field_spec.rb +34 -0
  136. data/lib/capybara/spec/session/has_select_spec.rb +32 -4
  137. data/lib/capybara/spec/session/has_selector_spec.rb +4 -4
  138. data/lib/capybara/spec/session/has_sibling_spec.rb +50 -0
  139. data/lib/capybara/spec/session/has_table_spec.rb +51 -5
  140. data/lib/capybara/spec/session/has_text_spec.rb +47 -0
  141. data/lib/capybara/spec/session/matches_style_spec.rb +2 -2
  142. data/lib/capybara/spec/session/node_spec.rb +574 -16
  143. data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +2 -2
  144. data/lib/capybara/spec/session/save_screenshot_spec.rb +4 -4
  145. data/lib/capybara/spec/session/scroll_spec.rb +1 -1
  146. data/lib/capybara/spec/session/select_spec.rb +5 -10
  147. data/lib/capybara/spec/session/selectors_spec.rb +24 -3
  148. data/lib/capybara/spec/session/uncheck_spec.rb +2 -2
  149. data/lib/capybara/spec/session/unselect_spec.rb +1 -1
  150. data/lib/capybara/spec/session/window/window_spec.rb +10 -9
  151. data/lib/capybara/spec/spec_helper.rb +7 -2
  152. data/lib/capybara/spec/test_app.rb +26 -21
  153. data/lib/capybara/spec/views/animated.erb +49 -0
  154. data/lib/capybara/spec/views/form.erb +25 -4
  155. data/lib/capybara/spec/views/frame_child.erb +2 -1
  156. data/lib/capybara/spec/views/frame_one.erb +1 -0
  157. data/lib/capybara/spec/views/obscured.erb +9 -9
  158. data/lib/capybara/spec/views/offset.erb +32 -0
  159. data/lib/capybara/spec/views/react.erb +45 -0
  160. data/lib/capybara/spec/views/spatial.erb +31 -0
  161. data/lib/capybara/spec/views/with_animation.erb +29 -1
  162. data/lib/capybara/spec/views/with_dragula.erb +24 -0
  163. data/lib/capybara/spec/views/with_html.erb +28 -2
  164. data/lib/capybara/spec/views/with_js.erb +2 -1
  165. data/lib/capybara/spec/views/with_jstree.erb +26 -0
  166. data/lib/capybara/spec/views/with_sortable_js.erb +21 -0
  167. data/lib/capybara/version.rb +1 -1
  168. data/lib/capybara/window.rb +10 -10
  169. data/spec/basic_node_spec.rb +6 -6
  170. data/spec/capybara_spec.rb +28 -28
  171. data/spec/dsl_spec.rb +16 -3
  172. data/spec/filter_set_spec.rb +5 -5
  173. data/spec/fixtures/selenium_driver_rspec_failure.rb +1 -1
  174. data/spec/fixtures/selenium_driver_rspec_success.rb +1 -1
  175. data/spec/minitest_spec.rb +12 -2
  176. data/spec/minitest_spec_spec.rb +56 -45
  177. data/spec/rack_test_spec.rb +25 -12
  178. data/spec/regexp_dissassembler_spec.rb +53 -39
  179. data/spec/result_spec.rb +50 -54
  180. data/spec/rspec/features_spec.rb +1 -0
  181. data/spec/rspec/shared_spec_matchers.rb +78 -62
  182. data/spec/rspec_spec.rb +5 -5
  183. data/spec/sauce_spec_chrome.rb +1 -0
  184. data/spec/selector_spec.rb +26 -16
  185. data/spec/selenium_spec_chrome.rb +84 -5
  186. data/spec/selenium_spec_chrome_remote.rb +23 -8
  187. data/spec/selenium_spec_edge.rb +23 -8
  188. data/spec/selenium_spec_firefox.rb +16 -21
  189. data/spec/selenium_spec_firefox_remote.rb +4 -13
  190. data/spec/selenium_spec_ie.rb +23 -15
  191. data/spec/selenium_spec_safari.rb +17 -17
  192. data/spec/server_spec.rb +87 -42
  193. data/spec/session_spec.rb +11 -4
  194. data/spec/shared_selenium_node.rb +83 -0
  195. data/spec/shared_selenium_session.rb +62 -72
  196. data/spec/spec_helper.rb +43 -5
  197. metadata +114 -16
@@ -2,15 +2,16 @@
2
2
 
3
3
  module Capybara
4
4
  module Queries
5
- class SiblingQuery < MatchQuery
5
+ class SiblingQuery < SelectorQuery
6
6
  # @api private
7
7
  def resolve_for(node, exact = nil)
8
8
  @sibling_node = node
9
9
  node.synchronize do
10
10
  match_results = super(node.session.current_scope, exact)
11
- node.all(:xpath, XPath.preceding_sibling + XPath.following_sibling) do |el|
12
- match_results.include?(el)
13
- end
11
+ siblings = node.find_xpath((XPath.preceding_sibling + XPath.following_sibling).to_s)
12
+ .map(&method(:to_element))
13
+ .select { |el| match_results.include?(el) }
14
+ Capybara::Result.new(ordered_results(siblings), self)
14
15
  end
15
16
  end
16
17
 
@@ -19,7 +19,7 @@ module Capybara
19
19
  @actual_styles = node.style(*@expected_styles.keys)
20
20
  @expected_styles.all? do |style, value|
21
21
  if value.is_a? Regexp
22
- @actual_styles[style] =~ value
22
+ value.match? @actual_styles[style]
23
23
  else
24
24
  @actual_styles[style] == value
25
25
  end
@@ -34,7 +34,7 @@ module Capybara
34
34
  private
35
35
 
36
36
  def stringify_keys(hsh)
37
- hsh.each_with_object({}) { |(k, v), str_keys| str_keys[k.to_s] = v }
37
+ hsh.transform_keys(&:to_s)
38
38
  end
39
39
 
40
40
  def valid_keys
@@ -6,11 +6,19 @@ module Capybara
6
6
  class TextQuery < BaseQuery
7
7
  def initialize(type = nil, expected_text, session_options:, **options) # rubocop:disable Style/OptionalArguments
8
8
  @type = type.nil? ? default_type : type
9
- @expected_text = expected_text.is_a?(Regexp) ? expected_text : expected_text.to_s
9
+ raise ArgumentError, '${@type} is not a valid type for a text query' unless valid_types.include?(@type)
10
+
10
11
  @options = options
11
12
  super(@options)
12
13
  self.session_options = session_options
13
14
 
15
+ if expected_text.nil? && !exact?
16
+ warn 'Checking for expected text of nil is confusing and/or pointless since it will always match. '\
17
+ 'Please specify a string or regexp instead.'
18
+ end
19
+
20
+ @expected_text = expected_text.is_a?(Regexp) ? expected_text : expected_text.to_s
21
+
14
22
  @search_regexp = Capybara::Helpers.to_regexp(@expected_text, exact: exact?)
15
23
 
16
24
  assert_valid_keys
@@ -83,6 +91,10 @@ module Capybara
83
91
  COUNT_KEYS + %i[wait exact normalize_ws]
84
92
  end
85
93
 
94
+ def valid_types
95
+ %i[all visible]
96
+ end
97
+
86
98
  def check_visible_text?
87
99
  @type == :visible
88
100
  end
@@ -13,7 +13,7 @@ module Capybara
13
13
  end
14
14
 
15
15
  def resolves_for?(node)
16
- (@actual_title = node.title).match(@search_regexp)
16
+ (@actual_title = node.title).match?(@search_regexp)
17
17
  end
18
18
 
19
19
  def failure_message
@@ -30,7 +30,9 @@ class Capybara::RackTest::Browser
30
30
 
31
31
  def submit(method, path, attributes)
32
32
  path = request_path if path.nil? || path.empty?
33
- process_and_follow_redirects(method, path, attributes, 'HTTP_REFERER' => current_url)
33
+ uri = build_uri(path)
34
+ uri.query = '' if method&.to_s&.downcase == 'get'
35
+ process_and_follow_redirects(method, uri.to_s, attributes, 'HTTP_REFERER' => current_url)
34
36
  end
35
37
 
36
38
  def follow(method, path, **attributes)
@@ -53,7 +55,10 @@ class Capybara::RackTest::Browser
53
55
  end
54
56
  end
55
57
  end
56
- raise Capybara::InfiniteRedirectError, "redirected more than #{driver.redirect_limit} times, check for infinite redirects." if last_response.redirect?
58
+
59
+ if last_response.redirect? # rubocop:disable Style/GuardClause
60
+ raise Capybara::InfiniteRedirectError, "redirected more than #{driver.redirect_limit} times, check for infinite redirects."
61
+ end
57
62
  end
58
63
 
59
64
  def process(method, path, attributes = {}, env = {})
@@ -42,7 +42,7 @@ class Capybara::RackTest::Driver < Capybara::Driver::Base
42
42
  end
43
43
 
44
44
  def visit(path, **attributes)
45
- browser.visit(path, attributes)
45
+ browser.visit(path, **attributes)
46
46
  end
47
47
 
48
48
  def refresh
@@ -56,7 +56,7 @@ private
56
56
  end
57
57
 
58
58
  def request_method
59
- self[:method].to_s.match?(/post/i) ? :post : :get
59
+ /post/i.match?(self[:method] || '') ? :post : :get
60
60
  end
61
61
 
62
62
  def merge_param!(params, key, value)
@@ -45,6 +45,7 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
45
45
 
46
46
  if radio? then set_radio(value)
47
47
  elsif checkbox? then set_checkbox(value)
48
+ elsif range? then set_range(value)
48
49
  elsif input_field? then set_input(value)
49
50
  elsif textarea? then native['_capybara_raw_value'] = value.to_s
50
51
  end
@@ -63,8 +64,9 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
63
64
  native.remove_attribute('selected')
64
65
  end
65
66
 
66
- def click(keys = [], **offset)
67
- raise ArgumentError, 'The RackTest driver does not support click options' unless keys.empty? && offset.empty?
67
+ def click(keys = [], **options)
68
+ options.delete(:offset)
69
+ raise ArgumentError, 'The RackTest driver does not support click options' unless keys.empty? && options.empty?
68
70
 
69
71
  if link?
70
72
  follow_link
@@ -75,6 +77,8 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
75
77
  set(!checked?)
76
78
  elsif tag_name == 'label'
77
79
  click_label
80
+ elsif (details = native.xpath('.//ancestor-or-self::details').last)
81
+ toggle_details(details)
78
82
  end
79
83
  end
80
84
 
@@ -118,11 +122,20 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
118
122
 
119
123
  public_instance_methods(false).each do |meth_name|
120
124
  alias_method "unchecked_#{meth_name}", meth_name
121
- private "unchecked_#{meth_name}" # rubocop:disable Layout/AccessModifierIndentation,Style/AccessModifierDeclarations
122
-
123
- define_method meth_name do |*args|
124
- stale_check
125
- send("unchecked_#{meth_name}", *args)
125
+ private "unchecked_#{meth_name}" # rubocop:disable Style/AccessModifierDeclarations
126
+
127
+ if RUBY_VERSION >= '2.7'
128
+ class_eval <<~METHOD, __FILE__, __LINE__ + 1
129
+ def #{meth_name}(...)
130
+ stale_check
131
+ method(:"unchecked_#{meth_name}").call(...)
132
+ end
133
+ METHOD
134
+ else
135
+ define_method meth_name do |*args|
136
+ stale_check
137
+ send("unchecked_#{meth_name}", *args)
138
+ end
126
139
  end
127
140
  end
128
141
 
@@ -196,6 +209,14 @@ private
196
209
  end
197
210
  end
198
211
 
212
+ def set_range(value) # rubocop:disable Naming/AccessorMethodName
213
+ min, max, step = (native['min'] || 0).to_f, (native['max'] || 100).to_f, (native['step'] || 1).to_f
214
+ value = value.to_f
215
+ value = value.clamp(min, max)
216
+ value = ((value - min) / step).round * step + min
217
+ native['value'] = value.clamp(min, max)
218
+ end
219
+
199
220
  def set_input(value) # rubocop:disable Naming/AccessorMethodName
200
221
  if text_or_password? && attribute_is_not_blank?(:maxlength)
201
222
  # Browser behavior for maxlength="0" is inconsistent, so we stick with
@@ -235,6 +256,17 @@ private
235
256
  labelled_control.set(!labelled_control.checked?) if checkbox_or_radio?(labelled_control)
236
257
  end
237
258
 
259
+ def toggle_details(details = nil)
260
+ details ||= native.xpath('.//ancestor-or-self::details').last
261
+ return unless details
262
+
263
+ if details.has_attribute?('open')
264
+ details.remove_attribute('open')
265
+ else
266
+ details.set_attribute('open', 'open')
267
+ end
268
+ end
269
+
238
270
  def link?
239
271
  tag_name == 'a' && !self[:href].nil?
240
272
  end
@@ -273,6 +305,10 @@ protected
273
305
  tag_name == 'textarea'
274
306
  end
275
307
 
308
+ def range?
309
+ input_field? && type == 'range'
310
+ end
311
+
276
312
  OPTION_OWNER_XPATH = XPath.parent(:optgroup, :select, :datalist).to_s.freeze
277
313
  DISABLED_BY_FIELDSET_XPATH = XPath.generate do |x|
278
314
  x.parent(:fieldset)[
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Capybara
4
+ # @api private
5
+ class RegistrationContainer
6
+ def names
7
+ @registered.keys
8
+ end
9
+
10
+ def [](name)
11
+ @registered[name]
12
+ end
13
+
14
+ def []=(name, value)
15
+ warn 'DEPRECATED: Directly setting drivers/servers is deprecated, please use Capybara.register_driver/register_server instead'
16
+ @registered[name] = value
17
+ end
18
+
19
+ def method_missing(method_name, *args, **options, &block)
20
+ if @registered.respond_to?(method_name)
21
+ warn "DEPRECATED: Calling '#{method_name}' on the drivers/servers container is deprecated without replacement"
22
+ # RUBY 2.6 will send an empty hash rather than nothing with **options so fix that
23
+ return @registered.public_send(method_name, *args, &block) if options.empty?
24
+
25
+ return @registered.public_send(method_name, *args, **options, &block)
26
+ end
27
+ super
28
+ end
29
+
30
+ def respond_to_missing?(method_name, include_private = false)
31
+ @registered.respond_to?(method_name) || super
32
+ end
33
+
34
+ private
35
+
36
+ def initialize
37
+ @registered = {}
38
+ end
39
+
40
+ def register(name, block)
41
+ @registered[name] = block
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ Capybara.register_driver :rack_test do |app|
4
+ Capybara::RackTest::Driver.new(app)
5
+ end
6
+
7
+ Capybara.register_driver :selenium do |app|
8
+ Capybara::Selenium::Driver.new(app)
9
+ end
10
+
11
+ Capybara.register_driver :selenium_headless do |app|
12
+ Capybara::Selenium::Driver.load_selenium
13
+ browser_options = ::Selenium::WebDriver::Firefox::Options.new
14
+ browser_options.args << '-headless'
15
+ Capybara::Selenium::Driver.new(app, browser: :firefox, options: browser_options)
16
+ end
17
+
18
+ Capybara.register_driver :selenium_chrome do |app|
19
+ Capybara::Selenium::Driver.load_selenium
20
+ browser_options = ::Selenium::WebDriver::Chrome::Options.new.tap do |opts|
21
+ # Workaround https://bugs.chromium.org/p/chromedriver/issues/detail?id=2650&q=load&sort=-id&colspec=ID%20Status%20Pri%20Owner%20Summary
22
+ opts.args << '--disable-site-isolation-trials'
23
+ end
24
+ Capybara::Selenium::Driver.new(app, browser: :chrome, options: browser_options)
25
+ end
26
+
27
+ Capybara.register_driver :selenium_chrome_headless do |app|
28
+ Capybara::Selenium::Driver.load_selenium
29
+ browser_options = ::Selenium::WebDriver::Chrome::Options.new.tap do |opts|
30
+ opts.args << '--headless'
31
+ opts.args << '--disable-gpu' if Gem.win_platform?
32
+ # Workaround https://bugs.chromium.org/p/chromedriver/issues/detail?id=2650&q=load&sort=-id&colspec=ID%20Status%20Pri%20Owner%20Summary
33
+ opts.args << '--disable-site-isolation-trials'
34
+ end
35
+ Capybara::Selenium::Driver.new(app, browser: :chrome, options: browser_options)
36
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Puma
4
+ module MiniSSL
5
+ class Socket
6
+ def read_nonblock(size, *_)
7
+ loop do
8
+ output = engine_read_all
9
+ return output if output
10
+
11
+ data = @socket.read_nonblock(size, exception: false)
12
+ raise IO::EAGAINWaitReadable if %i[wait_readable wait_writable].include? data
13
+ return nil if data.nil?
14
+
15
+ @engine.inject(data)
16
+ output = engine_read_all
17
+
18
+ return output if output
19
+
20
+ while (neg_data = @engine.extract)
21
+ @socket.write neg_data
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ Capybara.register_server :default do |app, port, _host|
4
+ Capybara.run_default_server(app, port)
5
+ end
6
+
7
+ Capybara.register_server :webrick do |app, port, host, **options|
8
+ require 'rack/handler/webrick'
9
+ options = { Host: host, Port: port, AccessLog: [], Logger: WEBrick::Log.new(nil, 0) }.merge(options)
10
+ Rack::Handler::WEBrick.run(app, **options)
11
+ end
12
+
13
+ Capybara.register_server :puma do |app, port, host, **options|
14
+ begin
15
+ require 'rack/handler/puma'
16
+ rescue LoadError
17
+ raise LoadError, 'Capybara is unable to load `puma` for its server, please add `puma` to your project or specify a different server via something like `Capybara.server = :webrick`.'
18
+ else
19
+ unless Rack::Handler::Puma.respond_to?(:config)
20
+ raise LoadError, 'Capybara requires `puma` version 3.8.0 or higher, please upgrade `puma` or register and specify your own server block'
21
+ end
22
+ end
23
+
24
+ # If we just run the Puma Rack handler it installs signal handlers which prevent us from being able to interrupt tests.
25
+ # Therefore construct and run the Server instance ourselves.
26
+ # Rack::Handler::Puma.run(app, { Host: host, Port: port, Threads: "0:4", workers: 0, daemon: false }.merge(options))
27
+ default_options = { Host: host, Port: port, Threads: '0:4', workers: 0, daemon: false }
28
+ options = default_options.merge(options)
29
+
30
+ conf = Rack::Handler::Puma.config(app, options)
31
+ events = conf.options[:Silent] ? ::Puma::Events.strings : ::Puma::Events.stdio
32
+
33
+ puma_ver = Gem::Version.new(Puma::Const::PUMA_VERSION)
34
+ require_relative 'patches/puma_ssl' if (Gem::Version.new('4.0.0')...Gem::Version.new('4.1.0')).cover? puma_ver
35
+
36
+ events.log 'Capybara starting Puma...'
37
+ events.log "* Version #{Puma::Const::PUMA_VERSION} , codename: #{Puma::Const::CODE_NAME}"
38
+ events.log "* Min threads: #{conf.options[:min_threads]}, max threads: #{conf.options[:max_threads]}"
39
+
40
+ Puma::Server.new(conf.app, events, conf.options).tap do |s|
41
+ s.binder.parse conf.options[:binds], s.events
42
+ s.min_threads, s.max_threads = conf.options[:min_threads], conf.options[:max_threads]
43
+ end.run.join
44
+ end
@@ -31,6 +31,7 @@ module Capybara
31
31
  @filter_errors = []
32
32
  @results_enum = lazy_select_elements { |node| query.matches_filters?(node, @filter_errors) }
33
33
  @query = query
34
+ @allow_reload = false
34
35
  end
35
36
 
36
37
  def_delegators :full_results, :size, :length, :last, :values_at, :inspect, :sample
@@ -43,7 +44,7 @@ module Capybara
43
44
  @result_cache.each(&block)
44
45
  loop do
45
46
  next_result = @results_enum.next
46
- @result_cache << next_result
47
+ add_to_cache(next_result)
47
48
  yield next_result
48
49
  end
49
50
  self
@@ -59,7 +60,12 @@ module Capybara
59
60
  nil
60
61
  end
61
62
  when Range
62
- idx.end && idx.max # endless range will have end == nil
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
63
69
  end
64
70
 
65
71
  if max_idx.nil?
@@ -76,6 +82,8 @@ module Capybara
76
82
  end
77
83
 
78
84
  def compare_count
85
+ return 0 unless @query
86
+
79
87
  count, min, max, between = @query.options.values_at(:count, :minimum, :maximum, :between)
80
88
 
81
89
  # Only check filters for as many elements as necessary to determine result
@@ -92,7 +100,9 @@ module Capybara
92
100
  end
93
101
 
94
102
  if between
95
- min, max = between.min, (between.end && between.max)
103
+ min, max = (between.begin && between.min) || 1, between.end
104
+ max -= 1 if max && between.exclude_end?
105
+
96
106
  size = load_up_to(max ? max + 1 : min)
97
107
  return size <=> min unless between.include?(size)
98
108
  end
@@ -128,13 +138,26 @@ module Capybara
128
138
  @elements.length
129
139
  end
130
140
 
141
+ ##
142
+ # @api private
143
+ #
144
+ def allow_reload!
145
+ @allow_reload = true
146
+ self
147
+ end
148
+
131
149
  private
132
150
 
151
+ def add_to_cache(elem)
152
+ elem.allow_reload!(@result_cache.size) if @allow_reload
153
+ @result_cache << elem
154
+ end
155
+
133
156
  def load_up_to(num)
134
157
  loop do
135
158
  break if @result_cache.size >= num
136
159
 
137
- @result_cache << @results_enum.next
160
+ add_to_cache(@results_enum.next)
138
161
  end
139
162
  @result_cache.size
140
163
  end
@@ -148,13 +171,18 @@ module Capybara
148
171
  @rest ||= @elements - full_results
149
172
  end
150
173
 
151
- def lazy_select_elements(&block)
152
- # 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
153
176
  # causes a concurrency issue with network requests here
154
177
  # https://github.com/jruby/jruby/issues/4212
155
- 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)
156
182
  @elements.select(&block).to_enum # non-lazy evaluation
157
- else
183
+ end
184
+ else
185
+ def lazy_select_elements(&block)
158
186
  @elements.lazy.select(&block)
159
187
  end
160
188
  end