capybara 3.23.0 → 3.35.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (183) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +264 -11
  3. data/README.md +10 -6
  4. data/lib/capybara.rb +20 -8
  5. data/lib/capybara/config.rb +10 -8
  6. data/lib/capybara/cucumber.rb +1 -1
  7. data/lib/capybara/driver/base.rb +4 -0
  8. data/lib/capybara/driver/node.rb +4 -0
  9. data/lib/capybara/dsl.rb +10 -2
  10. data/lib/capybara/helpers.rb +28 -2
  11. data/lib/capybara/minitest.rb +232 -144
  12. data/lib/capybara/minitest/spec.rb +156 -97
  13. data/lib/capybara/node/actions.rb +36 -36
  14. data/lib/capybara/node/base.rb +6 -6
  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 +77 -33
  18. data/lib/capybara/node/finders.rb +24 -17
  19. data/lib/capybara/node/matchers.rb +79 -64
  20. data/lib/capybara/node/simple.rb +11 -4
  21. data/lib/capybara/queries/ancestor_query.rb +6 -10
  22. data/lib/capybara/queries/base_query.rb +2 -1
  23. data/lib/capybara/queries/current_path_query.rb +14 -4
  24. data/lib/capybara/queries/selector_query.rb +259 -23
  25. data/lib/capybara/queries/sibling_query.rb +5 -11
  26. data/lib/capybara/queries/style_query.rb +1 -1
  27. data/lib/capybara/queries/text_query.rb +13 -1
  28. data/lib/capybara/rack_test/browser.rb +13 -4
  29. data/lib/capybara/rack_test/driver.rb +2 -1
  30. data/lib/capybara/rack_test/form.rb +2 -2
  31. data/lib/capybara/rack_test/node.rb +42 -6
  32. data/lib/capybara/registration_container.rb +44 -0
  33. data/lib/capybara/registrations/drivers.rb +18 -12
  34. data/lib/capybara/registrations/patches/puma_ssl.rb +29 -0
  35. data/lib/capybara/registrations/servers.rb +9 -2
  36. data/lib/capybara/result.rb +39 -19
  37. data/lib/capybara/rspec.rb +2 -0
  38. data/lib/capybara/rspec/matcher_proxies.rb +5 -5
  39. data/lib/capybara/rspec/matchers.rb +97 -74
  40. data/lib/capybara/rspec/matchers/base.rb +19 -6
  41. data/lib/capybara/rspec/matchers/count_sugar.rb +2 -1
  42. data/lib/capybara/rspec/matchers/have_ancestor.rb +5 -7
  43. data/lib/capybara/rspec/matchers/have_current_path.rb +2 -2
  44. data/lib/capybara/rspec/matchers/have_selector.rb +15 -10
  45. data/lib/capybara/rspec/matchers/have_sibling.rb +4 -7
  46. data/lib/capybara/rspec/matchers/have_text.rb +4 -7
  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 +7 -2
  50. data/lib/capybara/rspec/matchers/spatial_sugar.rb +39 -0
  51. data/lib/capybara/selector.rb +46 -19
  52. data/lib/capybara/selector/builders/css_builder.rb +10 -6
  53. data/lib/capybara/selector/builders/xpath_builder.rb +4 -2
  54. data/lib/capybara/selector/css.rb +1 -1
  55. data/lib/capybara/selector/definition.rb +13 -11
  56. data/lib/capybara/selector/definition/button.rb +32 -15
  57. data/lib/capybara/selector/definition/checkbox.rb +2 -2
  58. data/lib/capybara/selector/definition/css.rb +3 -1
  59. data/lib/capybara/selector/definition/datalist_input.rb +2 -2
  60. data/lib/capybara/selector/definition/datalist_option.rb +1 -1
  61. data/lib/capybara/selector/definition/element.rb +3 -2
  62. data/lib/capybara/selector/definition/field.rb +1 -1
  63. data/lib/capybara/selector/definition/file_field.rb +1 -1
  64. data/lib/capybara/selector/definition/fillable_field.rb +2 -2
  65. data/lib/capybara/selector/definition/label.rb +5 -3
  66. data/lib/capybara/selector/definition/link.rb +8 -0
  67. data/lib/capybara/selector/definition/option.rb +1 -1
  68. data/lib/capybara/selector/definition/radio_button.rb +2 -2
  69. data/lib/capybara/selector/definition/select.rb +33 -14
  70. data/lib/capybara/selector/definition/table.rb +6 -3
  71. data/lib/capybara/selector/definition/table_row.rb +2 -2
  72. data/lib/capybara/selector/filter_set.rb +13 -11
  73. data/lib/capybara/selector/filters/base.rb +6 -1
  74. data/lib/capybara/selector/filters/locator_filter.rb +1 -1
  75. data/lib/capybara/selector/regexp_disassembler.rb +7 -0
  76. data/lib/capybara/selector/selector.rb +13 -3
  77. data/lib/capybara/selenium/atoms/getAttribute.min.js +1 -1
  78. data/lib/capybara/selenium/atoms/isDisplayed.min.js +1 -1
  79. data/lib/capybara/selenium/atoms/src/getAttribute.js +1 -1
  80. data/lib/capybara/selenium/atoms/src/isDisplayed.js +10 -10
  81. data/lib/capybara/selenium/driver.rb +86 -24
  82. data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +24 -21
  83. data/lib/capybara/selenium/driver_specializations/edge_driver.rb +21 -19
  84. data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +17 -1
  85. data/lib/capybara/selenium/driver_specializations/safari_driver.rb +0 -4
  86. data/lib/capybara/selenium/extensions/file_input_click_emulation.rb +34 -0
  87. data/lib/capybara/selenium/extensions/find.rb +37 -26
  88. data/lib/capybara/selenium/extensions/html5_drag.rb +55 -11
  89. data/lib/capybara/selenium/extensions/modifier_keys_stack.rb +28 -0
  90. data/lib/capybara/selenium/extensions/scroll.rb +8 -10
  91. data/lib/capybara/selenium/logger_suppressor.rb +8 -2
  92. data/lib/capybara/selenium/node.rb +160 -40
  93. data/lib/capybara/selenium/nodes/chrome_node.rb +72 -12
  94. data/lib/capybara/selenium/nodes/edge_node.rb +32 -14
  95. data/lib/capybara/selenium/nodes/firefox_node.rb +28 -32
  96. data/lib/capybara/selenium/nodes/safari_node.rb +5 -29
  97. data/lib/capybara/selenium/patches/action_pauser.rb +26 -0
  98. data/lib/capybara/selenium/patches/atoms.rb +4 -4
  99. data/lib/capybara/selenium/patches/is_displayed.rb +16 -0
  100. data/lib/capybara/selenium/patches/logs.rb +32 -7
  101. data/lib/capybara/server.rb +19 -3
  102. data/lib/capybara/server/animation_disabler.rb +8 -3
  103. data/lib/capybara/server/checker.rb +1 -1
  104. data/lib/capybara/server/middleware.rb +22 -10
  105. data/lib/capybara/session.rb +66 -40
  106. data/lib/capybara/session/config.rb +11 -3
  107. data/lib/capybara/session/matchers.rb +11 -11
  108. data/lib/capybara/spec/public/offset.js +6 -0
  109. data/lib/capybara/spec/public/test.js +75 -7
  110. data/lib/capybara/spec/session/accept_alert_spec.rb +1 -1
  111. data/lib/capybara/spec/session/all_spec.rb +60 -5
  112. data/lib/capybara/spec/session/ancestor_spec.rb +5 -0
  113. data/lib/capybara/spec/session/assert_text_spec.rb +9 -5
  114. data/lib/capybara/spec/session/check_spec.rb +6 -0
  115. data/lib/capybara/spec/session/click_button_spec.rb +16 -0
  116. data/lib/capybara/spec/session/click_link_or_button_spec.rb +9 -0
  117. data/lib/capybara/spec/session/current_url_spec.rb +11 -1
  118. data/lib/capybara/spec/session/fill_in_spec.rb +29 -0
  119. data/lib/capybara/spec/session/find_spec.rb +55 -0
  120. data/lib/capybara/spec/session/has_ancestor_spec.rb +2 -0
  121. data/lib/capybara/spec/session/has_button_spec.rb +51 -0
  122. data/lib/capybara/spec/session/has_css_spec.rb +26 -4
  123. data/lib/capybara/spec/session/has_current_path_spec.rb +15 -2
  124. data/lib/capybara/spec/session/has_field_spec.rb +34 -0
  125. data/lib/capybara/spec/session/has_select_spec.rb +32 -4
  126. data/lib/capybara/spec/session/has_selector_spec.rb +4 -4
  127. data/lib/capybara/spec/session/has_table_spec.rb +51 -5
  128. data/lib/capybara/spec/session/has_text_spec.rb +30 -0
  129. data/lib/capybara/spec/session/html_spec.rb +1 -1
  130. data/lib/capybara/spec/session/matches_style_spec.rb +2 -2
  131. data/lib/capybara/spec/session/node_spec.rb +394 -9
  132. data/lib/capybara/spec/session/refresh_spec.rb +2 -1
  133. data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +2 -2
  134. data/lib/capybara/spec/session/save_page_spec.rb +4 -4
  135. data/lib/capybara/spec/session/save_screenshot_spec.rb +4 -15
  136. data/lib/capybara/spec/session/selectors_spec.rb +16 -3
  137. data/lib/capybara/spec/session/window/switch_to_window_spec.rb +1 -1
  138. data/lib/capybara/spec/session/window/window_opened_by_spec.rb +1 -1
  139. data/lib/capybara/spec/session/window/window_spec.rb +8 -8
  140. data/lib/capybara/spec/session/window/windows_spec.rb +1 -1
  141. data/lib/capybara/spec/spec_helper.rb +14 -14
  142. data/lib/capybara/spec/test_app.rb +27 -21
  143. data/lib/capybara/spec/views/form.erb +47 -4
  144. data/lib/capybara/spec/views/offset.erb +32 -0
  145. data/lib/capybara/spec/views/spatial.erb +31 -0
  146. data/lib/capybara/spec/views/with_animation.erb +37 -1
  147. data/lib/capybara/spec/views/with_dragula.erb +24 -0
  148. data/lib/capybara/spec/views/with_html.erb +24 -2
  149. data/lib/capybara/spec/views/with_jquery_animation.erb +24 -0
  150. data/lib/capybara/spec/views/with_js.erb +4 -1
  151. data/lib/capybara/spec/views/with_jstree.erb +26 -0
  152. data/lib/capybara/spec/views/with_sortable_js.erb +1 -1
  153. data/lib/capybara/version.rb +1 -1
  154. data/lib/capybara/window.rb +3 -7
  155. data/spec/basic_node_spec.rb +15 -14
  156. data/spec/capybara_spec.rb +28 -28
  157. data/spec/dsl_spec.rb +16 -3
  158. data/spec/filter_set_spec.rb +5 -5
  159. data/spec/fixtures/selenium_driver_rspec_failure.rb +1 -1
  160. data/spec/fixtures/selenium_driver_rspec_success.rb +1 -1
  161. data/spec/minitest_spec.rb +3 -2
  162. data/spec/minitest_spec_spec.rb +46 -46
  163. data/spec/rack_test_spec.rb +38 -15
  164. data/spec/regexp_dissassembler_spec.rb +52 -38
  165. data/spec/result_spec.rb +43 -32
  166. data/spec/rspec/features_spec.rb +4 -1
  167. data/spec/rspec/scenarios_spec.rb +4 -0
  168. data/spec/rspec/shared_spec_matchers.rb +68 -56
  169. data/spec/rspec_spec.rb +9 -5
  170. data/spec/selector_spec.rb +32 -17
  171. data/spec/selenium_spec_chrome.rb +78 -11
  172. data/spec/selenium_spec_chrome_remote.rb +23 -6
  173. data/spec/selenium_spec_edge.rb +15 -12
  174. data/spec/selenium_spec_firefox.rb +24 -19
  175. data/spec/selenium_spec_firefox_remote.rb +0 -8
  176. data/spec/selenium_spec_ie.rb +1 -6
  177. data/spec/server_spec.rb +106 -44
  178. data/spec/session_spec.rb +5 -5
  179. data/spec/shared_selenium_node.rb +56 -2
  180. data/spec/shared_selenium_session.rb +122 -15
  181. data/spec/spec_helper.rb +2 -2
  182. metadata +63 -17
  183. data/lib/capybara/spec/session/source_spec.rb +0 -0
@@ -3,25 +3,19 @@
3
3
  module Capybara
4
4
  module Queries
5
5
  class SiblingQuery < SelectorQuery
6
- def initialize(*args)
7
- super
8
- @count_options = {}
9
- COUNT_KEYS.each do |key|
10
- @count_options[key] = @options.delete(key) if @options.key?(key)
11
- end
12
- end
13
-
14
6
  # @api private
15
7
  def resolve_for(node, exact = nil)
16
8
  @sibling_node = node
17
9
  node.synchronize do
18
10
  match_results = super(node.session.current_scope, exact)
19
- xpath = XPath.preceding_sibling + XPath.following_sibling
20
- node.all(:xpath, xpath, **@count_options) { |el| match_results.include?(el) }
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)
21
15
  end
22
16
  end
23
17
 
24
- def description(applied = false)
18
+ def description(applied = false) # rubocop:disable Style/OptionalBooleanParameter
25
19
  desc = super
26
20
  sibling_query = @sibling_node&.instance_variable_get(:@query)
27
21
  desc += " that is a sibling of #{sibling_query.description}" if sibling_query
@@ -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. #{Capybara::Helpers.filter_backtrace(caller)}"
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
@@ -8,6 +8,7 @@ class Capybara::RackTest::Browser
8
8
 
9
9
  def initialize(driver)
10
10
  @driver = driver
11
+ @current_fragment = nil
11
12
  end
12
13
 
13
14
  def app
@@ -30,7 +31,9 @@ class Capybara::RackTest::Browser
30
31
 
31
32
  def submit(method, path, attributes)
32
33
  path = request_path if path.nil? || path.empty?
33
- process_and_follow_redirects(method, path, attributes, 'HTTP_REFERER' => current_url)
34
+ uri = build_uri(path)
35
+ uri.query = '' if method.to_s.casecmp('get').zero?
36
+ process_and_follow_redirects(method, uri.to_s, attributes, 'HTTP_REFERER' => current_url)
34
37
  end
35
38
 
36
39
  def follow(method, path, **attributes)
@@ -40,6 +43,7 @@ class Capybara::RackTest::Browser
40
43
  end
41
44
 
42
45
  def process_and_follow_redirects(method, path, attributes = {}, env = {})
46
+ @current_fragment = build_uri(path).fragment
43
47
  process(method, path, attributes, env)
44
48
 
45
49
  return unless driver.follow_redirects?
@@ -53,14 +57,17 @@ class Capybara::RackTest::Browser
53
57
  end
54
58
  end
55
59
  end
56
- raise Capybara::InfiniteRedirectError, "redirected more than #{driver.redirect_limit} times, check for infinite redirects." if last_response.redirect?
60
+
61
+ if last_response.redirect? # rubocop:disable Style/GuardClause
62
+ raise Capybara::InfiniteRedirectError, "redirected more than #{driver.redirect_limit} times, check for infinite redirects."
63
+ end
57
64
  end
58
65
 
59
66
  def process(method, path, attributes = {}, env = {})
60
67
  method = method.downcase
61
68
  new_uri = build_uri(path)
62
69
  @current_scheme, @current_host, @current_port = new_uri.select(:scheme, :host, :port)
63
-
70
+ @current_fragment = new_uri.fragment || @current_fragment
64
71
  reset_cache!
65
72
  send(method, new_uri.to_s, attributes, env.merge(options[:headers] || {}))
66
73
  end
@@ -78,7 +85,9 @@ class Capybara::RackTest::Browser
78
85
  end
79
86
 
80
87
  def current_url
81
- last_request.url
88
+ uri = build_uri(last_request.url)
89
+ uri.fragment = @current_fragment if @current_fragment
90
+ uri.to_s
82
91
  rescue Rack::Test::Error
83
92
  ''
84
93
  end
@@ -17,6 +17,7 @@ class Capybara::RackTest::Driver < Capybara::Driver::Base
17
17
  def initialize(app, **options)
18
18
  raise ArgumentError, 'rack-test requires a rack application, but none was given' unless app
19
19
 
20
+ super()
20
21
  @app = app
21
22
  @options = DEFAULT_OPTIONS.merge(options)
22
23
  end
@@ -42,7 +43,7 @@ class Capybara::RackTest::Driver < Capybara::Driver::Base
42
43
  end
43
44
 
44
45
  def visit(path, **attributes)
45
- browser.visit(path, attributes)
46
+ browser.visit(path, **attributes)
46
47
  end
47
48
 
48
49
  def refresh
@@ -6,7 +6,7 @@ class Capybara::RackTest::Form < Capybara::RackTest::Node
6
6
  # That check should be based solely on the form element's 'enctype' attribute value,
7
7
  # which should probably be provided to Rack::Test in its non-GET request methods.
8
8
  class NilUploadedFile < Rack::Test::UploadedFile
9
- def initialize
9
+ def initialize # rubocop:disable Lint/MissingSuper
10
10
  @empty_file = Tempfile.new('nil_uploaded_file')
11
11
  @empty_file.close
12
12
  end
@@ -56,7 +56,7 @@ private
56
56
  end
57
57
 
58
58
  def request_method
59
- /post/i.match?(self[:method]) ? :post : :get
59
+ /post/i.match?(self[:method] || '') ? :post : :get
60
60
  end
61
61
 
62
62
  def merge_param!(params, key, value)
@@ -15,7 +15,7 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
15
15
  end
16
16
 
17
17
  def visible_text
18
- displayed_text.gsub(/\ +/, ' ')
18
+ displayed_text.squeeze(' ')
19
19
  .gsub(/[\ \n]*\n[\ \n]*/, "\n")
20
20
  .gsub(/\A[[:space:]&&[^\u00a0]]+/, '')
21
21
  .gsub(/[[:space:]&&[^\u00a0]]+\z/, '')
@@ -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
 
@@ -120,9 +124,18 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
120
124
  alias_method "unchecked_#{meth_name}", meth_name
121
125
  private "unchecked_#{meth_name}" # rubocop:disable Style/AccessModifierDeclarations
122
126
 
123
- define_method meth_name do |*args|
124
- stale_check
125
- send("unchecked_#{meth_name}", *args)
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
+ Capybara::Helpers.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
+ Capybara::Helpers.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_all)
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
@@ -9,28 +9,34 @@ Capybara.register_driver :selenium do |app|
9
9
  end
10
10
 
11
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)
12
+ version = Capybara::Selenium::Driver.load_selenium
13
+ options_key = Capybara::Selenium::Driver::CAPS_VERSION.satisfied_by?(version) ? :capabilities : :options
14
+ browser_options = ::Selenium::WebDriver::Firefox::Options.new.tap do |opts|
15
+ opts.add_argument '-headless'
16
+ end
17
+ Capybara::Selenium::Driver.new(app, **Hash[:browser => :firefox, options_key => browser_options])
16
18
  end
17
19
 
18
20
  Capybara.register_driver :selenium_chrome do |app|
19
- Capybara::Selenium::Driver.load_selenium
21
+ version = Capybara::Selenium::Driver.load_selenium
22
+ options_key = Capybara::Selenium::Driver::CAPS_VERSION.satisfied_by?(version) ? :capabilities : :options
20
23
  browser_options = ::Selenium::WebDriver::Chrome::Options.new.tap do |opts|
21
24
  # 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'
25
+ opts.add_argument('--disable-site-isolation-trials')
23
26
  end
24
- Capybara::Selenium::Driver.new(app, browser: :chrome, options: browser_options)
27
+
28
+ Capybara::Selenium::Driver.new(app, **Hash[:browser => :chrome, options_key => browser_options])
25
29
  end
26
30
 
27
31
  Capybara.register_driver :selenium_chrome_headless do |app|
28
- Capybara::Selenium::Driver.load_selenium
32
+ version = Capybara::Selenium::Driver.load_selenium
33
+ options_key = Capybara::Selenium::Driver::CAPS_VERSION.satisfied_by?(version) ? :capabilities : :options
29
34
  browser_options = ::Selenium::WebDriver::Chrome::Options.new.tap do |opts|
30
- opts.args << '--headless'
31
- opts.args << '--disable-gpu' if Gem.win_platform?
35
+ opts.add_argument('--headless')
36
+ opts.add_argument('--disable-gpu') if Gem.win_platform?
32
37
  # 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'
38
+ opts.add_argument('--disable-site-isolation-trials')
34
39
  end
35
- Capybara::Selenium::Driver.new(app, browser: :chrome, options: browser_options)
40
+
41
+ Capybara::Selenium::Driver.new(app, **Hash[:browser => :chrome, options_key => browser_options])
36
42
  end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Puma
4
+ module MiniSSL
5
+ class Socket
6
+ def read_nonblock(size, *_)
7
+ wait_states = %i[wait_readable wait_writable]
8
+
9
+ loop do
10
+ output = engine_read_all
11
+ return output if output
12
+
13
+ data = @socket.read_nonblock(size, exception: false)
14
+ raise IO::EAGAINWaitReadable if wait_states.include? data
15
+ return nil if data.nil?
16
+
17
+ @engine.inject(data)
18
+ output = engine_read_all
19
+
20
+ return output if output
21
+
22
+ while (neg_data = @engine.extract)
23
+ @socket.write neg_data
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -7,7 +7,7 @@ end
7
7
  Capybara.register_server :webrick do |app, port, host, **options|
8
8
  require 'rack/handler/webrick'
9
9
  options = { Host: host, Port: port, AccessLog: [], Logger: WEBrick::Log.new(nil, 0) }.merge(options)
10
- Rack::Handler::WEBrick.run(app, options)
10
+ Rack::Handler::WEBrick.run(app, **options)
11
11
  end
12
12
 
13
13
  Capybara.register_server :puma do |app, port, host, **options|
@@ -20,13 +20,20 @@ Capybara.register_server :puma do |app, port, host, **options|
20
20
  raise LoadError, 'Capybara requires `puma` version 3.8.0 or higher, please upgrade `puma` or register and specify your own server block'
21
21
  end
22
22
  end
23
+
23
24
  # If we just run the Puma Rack handler it installs signal handlers which prevent us from being able to interrupt tests.
24
25
  # Therefore construct and run the Server instance ourselves.
25
26
  # Rack::Handler::Puma.run(app, { Host: host, Port: port, Threads: "0:4", workers: 0, daemon: false }.merge(options))
26
- options = { 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
+
27
30
  conf = Rack::Handler::Puma.config(app, options)
31
+ conf.clamp
28
32
  events = conf.options[:Silent] ? ::Puma::Events.strings : ::Puma::Events.stdio
29
33
 
34
+ puma_ver = Gem::Version.new(Puma::Const::PUMA_VERSION)
35
+ require_relative 'patches/puma_ssl' if Gem::Requirement.new('>=4.0.0', '< 4.1.0').satisfied_by?(puma_ver)
36
+
30
37
  events.log 'Capybara starting Puma...'
31
38
  events.log "* Version #{Puma::Const::PUMA_VERSION} , codename: #{Puma::Const::CODE_NAME}"
32
39
  events.log "* Min threads: #{conf.options[:min_threads]}, max threads: #{conf.options[:max_threads]}"
@@ -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
@@ -38,12 +39,12 @@ module Capybara
38
39
  alias index find_index
39
40
 
40
41
  def each(&block)
41
- return enum_for(:each) unless block_given?
42
+ return enum_for(:each) unless block
42
43
 
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
@@ -53,13 +54,18 @@ module Capybara
53
54
  idx, length = args
54
55
  max_idx = case idx
55
56
  when Integer
56
- if !idx.negative?
57
- length.nil? ? idx : idx + length - 1
58
- else
57
+ if idx.negative?
59
58
  nil
59
+ else
60
+ length.nil? ? idx : idx + length - 1
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?
@@ -85,16 +91,14 @@ module Capybara
85
91
  return load_up_to(count + 1) <=> count
86
92
  end
87
93
 
88
- if min && (min = Integer(min))
89
- return -1 if load_up_to(min) < min
90
- end
94
+ return -1 if min && (min = Integer(min)) && (load_up_to(min) < min)
91
95
 
92
- if max && (max = Integer(max))
93
- return 1 if load_up_to(max + 1) > max
94
- end
96
+ return 1 if max && (max = Integer(max)) && (load_up_to(max + 1) > max)
95
97
 
96
98
  if between
97
- min, max = between.min, (between.end && between.max)
99
+ min, max = (between.begin && between.min) || 1, between.end
100
+ max -= 1 if max && between.exclude_end?
101
+
98
102
  size = load_up_to(max ? max + 1 : min)
99
103
  return size <=> min unless between.include?(size)
100
104
  end
@@ -130,13 +134,26 @@ module Capybara
130
134
  @elements.length
131
135
  end
132
136
 
137
+ ##
138
+ # @api private
139
+ #
140
+ def allow_reload!
141
+ @allow_reload = true
142
+ self
143
+ end
144
+
133
145
  private
134
146
 
147
+ def add_to_cache(elem)
148
+ elem.allow_reload!(@result_cache.size) if @allow_reload
149
+ @result_cache << elem
150
+ end
151
+
135
152
  def load_up_to(num)
136
153
  loop do
137
154
  break if @result_cache.size >= num
138
155
 
139
- @result_cache << @results_enum.next
156
+ add_to_cache(@results_enum.next)
140
157
  end
141
158
  @result_cache.size
142
159
  end
@@ -150,15 +167,18 @@ module Capybara
150
167
  @rest ||= @elements - full_results
151
168
  end
152
169
 
153
- def lazy_select_elements(&block)
170
+ if RUBY_PLATFORM == 'java'
154
171
  # JRuby < 9.2.8.0 has an issue with lazy enumerators which
155
172
  # causes a concurrency issue with network requests here
156
173
  # https://github.com/jruby/jruby/issues/4212
157
- if (RUBY_PLATFORM == 'java') && (Gem::Version.new(JRUBY_VERSION) < Gem::Version.new('9.2.8.0'))
158
- # :nocov:
174
+ # while JRuby >= 9.2.8.0 leaks threads when using lazy enumerators
175
+ # https://github.com/teamcapybara/capybara/issues/2349
176
+ # so disable the use and JRuby users will need to pay a performance penalty
177
+ def lazy_select_elements(&block)
159
178
  @elements.select(&block).to_enum # non-lazy evaluation
160
- # :nocov:
161
- else
179
+ end
180
+ else
181
+ def lazy_select_elements(&block)
162
182
  @elements.lazy.select(&block)
163
183
  end
164
184
  end