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
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionPauser
4
+ def initialize(mouse, keyboard)
5
+ super
6
+ @devices[:pauser] = Pauser.new
7
+ end
8
+
9
+ def pause(duration)
10
+ @actions << [:pauser, :pause, [duration]]
11
+ self
12
+ end
13
+
14
+ class Pauser
15
+ def pause(duration)
16
+ sleep duration
17
+ end
18
+ end
19
+
20
+ private_constant :Pauser
21
+ end
22
+
23
+ if defined?(::Selenium::WebDriver::VERSION) && (::Selenium::WebDriver::VERSION.to_f < 4) &&
24
+ defined?(::Selenium::WebDriver::ActionBuilder)
25
+ ::Selenium::WebDriver::ActionBuilder.prepend(ActionPauser)
26
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CapybaraAtoms
4
+ private
5
+
6
+ def read_atom(function)
7
+ @atoms ||= Hash.new do |hash, key|
8
+ hash[key] = begin
9
+ File.read(File.expand_path("../../atoms/#{key}.min.js", __FILE__))
10
+ rescue Errno::ENOENT
11
+ super
12
+ end
13
+ end
14
+ @atoms[function]
15
+ end
16
+ end
17
+
18
+ ::Selenium::WebDriver::Remote::Bridge.prepend CapybaraAtoms unless ENV['DISABLE_CAPYBARA_SELENIUM_OPTIMIZATIONS']
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Capybara
4
+ module Selenium
5
+ module IsDisplayed
6
+ def commands(command)
7
+ case command
8
+ when :is_element_displayed
9
+ [:get, 'session/:session_id/element/:id/displayed']
10
+ else
11
+ super
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Capybara
4
+ module Selenium
5
+ module ChromeLogs
6
+ LOG_MSG = <<~MSG
7
+ Chromedriver 75+ defaults to W3C mode. Please upgrade to chromedriver >= \
8
+ 75.0.3770.90 if you need to access logs while in W3C compliant mode.
9
+ MSG
10
+
11
+ COMMANDS = {
12
+ get_available_log_types: [:get, 'session/:session_id/se/log/types'],
13
+ get_log: [:post, 'session/:session_id/se/log'],
14
+ get_log_legacy: [:post, 'session/:session_id/log']
15
+ }.freeze
16
+
17
+ def commands(command)
18
+ COMMANDS[command] || super
19
+ end
20
+
21
+ def available_log_types
22
+ types = execute :get_available_log_types
23
+ Array(types).map(&:to_sym)
24
+ rescue ::Selenium::WebDriver::Error::UnknownCommandError
25
+ raise NotImplementedError, LOG_MSG
26
+ end
27
+
28
+ def log(type)
29
+ data = begin
30
+ execute :get_log, {}, type: type.to_s
31
+ rescue ::Selenium::WebDriver::Error::UnknownCommandError
32
+ execute :get_log_legacy, {}, type: type.to_s
33
+ end
34
+
35
+ Array(data).map do |l|
36
+ ::Selenium::WebDriver::LogEntry.new l.fetch('level', 'UNKNOWN'), l.fetch('timestamp'), l.fetch('message')
37
+ rescue KeyError
38
+ next
39
+ end
40
+ rescue ::Selenium::WebDriver::Error::UnknownCommandError
41
+ raise NotImplementedError, LOG_MSG
42
+ end
43
+ end
44
+ end
45
+ end
@@ -24,7 +24,9 @@ module Capybara
24
24
  host: Capybara.server_host,
25
25
  reportable_errors: Capybara.server_errors,
26
26
  extra_middleware: [])
27
- warn 'Positional arguments, other than the application, to Server#new are deprecated, please use keyword arguments' unless deprecated_options.empty?
27
+ unless deprecated_options.empty?
28
+ warn 'Positional arguments, other than the application, to Server#new are deprecated, please use keyword arguments'
29
+ end
28
30
  @app = app
29
31
  @extra_middleware = extra_middleware
30
32
  @server_thread = nil # suppress warnings
@@ -61,7 +63,7 @@ module Capybara
61
63
  def wait_for_pending_requests
62
64
  timer = Capybara::Helpers.timer(expire_in: 60)
63
65
  while pending_requests?
64
- raise 'Requests did not finish in 60 seconds' if timer.expired?
66
+ raise "Requests did not finish in 60 seconds: #{middleware.pending_requests}" if timer.expired?
65
67
 
66
68
  sleep 0.01
67
69
  end
@@ -86,6 +88,10 @@ module Capybara
86
88
  self
87
89
  end
88
90
 
91
+ def base_url
92
+ "http#{'s' if using_ssl?}://#{host}:#{port}"
93
+ end
94
+
89
95
  private
90
96
 
91
97
  def middleware
@@ -102,7 +108,17 @@ module Capybara
102
108
 
103
109
  def find_available_port(host)
104
110
  server = TCPServer.new(host, 0)
105
- server.addr[1]
111
+ port = server.addr[1]
112
+ server.close
113
+
114
+ # Workaround issue where some platforms (mac, ???) when passed a host
115
+ # of '0.0.0.0' will return a port that is only available on one of the
116
+ # ip addresses that resolves to, but the next binding to that port requires
117
+ # that port to be available on all ips
118
+ server = TCPServer.new(host, port)
119
+ port
120
+ rescue Errno::EADDRINUSE
121
+ retry
106
122
  ensure
107
123
  server&.close
108
124
  end
@@ -36,7 +36,7 @@ module Capybara
36
36
  attr_reader :disable_markup
37
37
 
38
38
  def html_content?
39
- !!(@headers['Content-Type'] =~ /html/)
39
+ /html/.match?(@headers['Content-Type'])
40
40
  end
41
41
 
42
42
  def insert_disable(html)
@@ -46,7 +46,7 @@ module Capybara
46
46
  DISABLE_MARKUP_TEMPLATE = <<~HTML
47
47
  <script defer>(typeof jQuery !== 'undefined') && (jQuery.fx.off = true);</script>
48
48
  <style>
49
- %<selector>s {
49
+ %<selector>s, %<selector>s::before, %<selector>s::after {
50
50
  transition: none !important;
51
51
  animation-duration: 0s !important;
52
52
  animation-delay: 0s !important;
@@ -25,11 +25,15 @@ module Capybara
25
25
  private
26
26
 
27
27
  def http_request(&block)
28
- Net::HTTP.start(@host, @port, read_timeout: 2, &block)
28
+ make_request(read_timeout: 2, &block)
29
29
  end
30
30
 
31
31
  def https_request(&block)
32
- Net::HTTP.start(@host, @port, ssl_options, &block)
32
+ make_request(**ssl_options, &block)
33
+ end
34
+
35
+ def make_request(**options, &block)
36
+ Net::HTTP.start(@host, @port, options.merge(max_retries: 0), &block)
33
37
  end
34
38
 
35
39
  def ssl_options
@@ -4,19 +4,25 @@ module Capybara
4
4
  class Server
5
5
  class Middleware
6
6
  class Counter
7
- attr_reader :value
8
-
9
7
  def initialize
10
- @value = 0
8
+ @value = []
11
9
  @mutex = Mutex.new
12
10
  end
13
11
 
14
- def increment
15
- @mutex.synchronize { @value += 1 }
12
+ def increment(uri)
13
+ @mutex.synchronize { @value.push(uri) }
14
+ end
15
+
16
+ def decrement(uri)
17
+ @mutex.synchronize { @value.delete_at(@value.index(uri) || @value.length) }
16
18
  end
17
19
 
18
- def decrement
19
- @mutex.synchronize { @value -= 1 }
20
+ def positive?
21
+ @mutex.synchronize { @value.length.positive? }
22
+ end
23
+
24
+ def value
25
+ @mutex.synchronize { @value.dup }
20
26
  end
21
27
  end
22
28
 
@@ -31,8 +37,12 @@ module Capybara
31
37
  @server_errors = server_errors
32
38
  end
33
39
 
40
+ def pending_requests
41
+ @counter.value
42
+ end
43
+
34
44
  def pending_requests?
35
- @counter.value.positive?
45
+ @counter.positive?
36
46
  end
37
47
 
38
48
  def clear_error
@@ -43,14 +53,14 @@ module Capybara
43
53
  if env['PATH_INFO'] == '/__identify__'
44
54
  [200, {}, [@app.object_id.to_s]]
45
55
  else
46
- @counter.increment
56
+ @counter.increment(env['REQUEST_URI'])
47
57
  begin
48
58
  @extended_app.call(env)
49
- rescue *@server_errors => err
50
- @error ||= err
51
- raise err
59
+ rescue *@server_errors => e
60
+ @error ||= e
61
+ raise e
52
62
  ensure
53
- @counter.decrement
63
+ @counter.decrement(env['REQUEST_URI'])
54
64
  end
55
65
  end
56
66
  end
@@ -6,7 +6,7 @@ require 'addressable/uri'
6
6
  module Capybara
7
7
  ##
8
8
  #
9
- # The Session class represents a single user's interaction with the system. The Session can use
9
+ # The {Session} class represents a single user's interaction with the system. The {Session} can use
10
10
  # any of the underlying drivers. A session can be initialized manually like this:
11
11
  #
12
12
  # session = Capybara::Session.new(:culerity, MyRackApp)
@@ -17,23 +17,23 @@ module Capybara
17
17
  # session = Capybara::Session.new(:culerity)
18
18
  # session.visit('http://www.google.com')
19
19
  #
20
- # When Capybara.threadsafe == true the sessions options will be initially set to the
20
+ # When {Capybara.configure threadsafe} is `true` the sessions options will be initially set to the
21
21
  # current values of the global options and a configuration block can be passed to the session initializer.
22
- # For available options see {Capybara::SessionConfig::OPTIONS}
22
+ # For available options see {Capybara::SessionConfig::OPTIONS}:
23
23
  #
24
24
  # session = Capybara::Session.new(:driver, MyRackApp) do |config|
25
25
  # config.app_host = "http://my_host.dev"
26
26
  # end
27
27
  #
28
- # Session provides a number of methods for controlling the navigation of the page, such as +visit+,
29
- # +current_path, and so on. It also delegates a number of methods to a Capybara::Document, representing
28
+ # The {Session} provides a number of methods for controlling the navigation of the page, such as {#visit},
29
+ # {#current_path}, and so on. It also delegates a number of methods to a {Capybara::Document}, representing
30
30
  # the current HTML document. This allows interaction:
31
31
  #
32
32
  # session.fill_in('q', with: 'Capybara')
33
33
  # session.click_button('Search')
34
34
  # expect(session).to have_content('Capybara')
35
35
  #
36
- # When using capybara/dsl, the Session is initialized automatically for you.
36
+ # When using `capybara/dsl`, the {Session} is initialized automatically for you.
37
37
  #
38
38
  class Session
39
39
  include Capybara::SessionMatchers
@@ -75,9 +75,11 @@ module Capybara
75
75
  attr_accessor :synchronized
76
76
 
77
77
  def initialize(mode, app = nil)
78
- raise TypeError, 'The second parameter to Session::new should be a rack app if passed.' if app && !app.respond_to?(:call)
78
+ if app && !app.respond_to?(:call)
79
+ raise TypeError, 'The second parameter to Session::new should be a rack app if passed.'
80
+ end
79
81
 
80
- @@instance_created = true
82
+ @@instance_created = true # rubocop:disable Style/ClassVars
81
83
  @mode = mode
82
84
  @app = app
83
85
  if block_given?
@@ -88,15 +90,15 @@ module Capybara
88
90
  @server = if config.run_server && @app && driver.needs_server?
89
91
  server_options = { port: config.server_port, host: config.server_host, reportable_errors: config.server_errors }
90
92
  server_options[:extra_middleware] = [Capybara::Server::AnimationDisabler] if config.disable_animation
91
- Capybara::Server.new(@app, server_options).boot
93
+ Capybara::Server.new(@app, **server_options).boot
92
94
  end
93
95
  @touched = false
94
96
  end
95
97
 
96
98
  def driver
97
99
  @driver ||= begin
98
- unless Capybara.drivers.key?(mode)
99
- other_drivers = Capybara.drivers.keys.map(&:inspect)
100
+ unless Capybara.drivers[mode]
101
+ other_drivers = Capybara.drivers.names.map(&:inspect)
100
102
  raise Capybara::DriverNotFoundError, "no driver called #{mode.inspect} was found, available drivers: #{other_drivers.join(', ')}"
101
103
  end
102
104
  driver = Capybara.drivers[mode].call(app)
@@ -107,21 +109,21 @@ module Capybara
107
109
 
108
110
  ##
109
111
  #
110
- # Reset the session (i.e. remove cookies and navigate to blank page)
112
+ # Reset the session (i.e. remove cookies and navigate to blank page).
111
113
  #
112
114
  # This method does not:
113
115
  #
114
- # * accept modal dialogs if they are present (Selenium driver now does, others may not)
115
- # * clear browser cache/HTML 5 local storage/IndexedDB/Web SQL database/etc.
116
- # * modify state of the driver/underlying browser in any other way
116
+ # * accept modal dialogs if they are present (Selenium driver now does, others may not)
117
+ # * clear browser cache/HTML 5 local storage/IndexedDB/Web SQL database/etc.
118
+ # * modify state of the driver/underlying browser in any other way
117
119
  #
118
120
  # as doing so will result in performance downsides and it's not needed to do everything from the list above for most apps.
119
121
  #
120
122
  # If you want to do anything from the list above on a general basis you can:
121
123
  #
122
- # * write RSpec/Cucumber/etc. after hook
123
- # * monkeypatch this method
124
- # * use Ruby's `prepend` method
124
+ # * write RSpec/Cucumber/etc. after hook
125
+ # * monkeypatch this method
126
+ # * use Ruby's `prepend` method
125
127
  #
126
128
  def reset!
127
129
  if @touched
@@ -136,19 +138,18 @@ module Capybara
136
138
 
137
139
  ##
138
140
  #
139
- # Disconnect from the current driver. A new driver will be instantiated on the next interaction
140
- #
141
+ # Disconnect from the current driver. A new driver will be instantiated on the next interaction.
141
142
  #
142
143
  def quit
143
144
  @driver.quit if @driver.respond_to? :quit
144
- @driver = nil
145
+ @document = @driver = nil
145
146
  @touched = false
146
147
  @server&.reset_error!
147
148
  end
148
149
 
149
150
  ##
150
151
  #
151
- # Raise errors encountered in the server
152
+ # Raise errors encountered in the server.
152
153
  #
153
154
  def raise_server_error!
154
155
  return unless @server&.error
@@ -168,9 +169,9 @@ module Capybara
168
169
 
169
170
  ##
170
171
  #
171
- # Returns a hash of response headers. Not supported by all drivers (e.g. Selenium)
172
+ # Returns a hash of response headers. Not supported by all drivers (e.g. Selenium).
172
173
  #
173
- # @return [Hash{String => String}] A hash of response headers.
174
+ # @return [Hash<String, String>] A hash of response headers.
174
175
  #
175
176
  def response_headers
176
177
  driver.response_headers
@@ -178,7 +179,7 @@ module Capybara
178
179
 
179
180
  ##
180
181
  #
181
- # Returns the current HTTP status code as an Integer. Not supported by all drivers (e.g. Selenium)
182
+ # Returns the current HTTP status code as an integer. Not supported by all drivers (e.g. Selenium).
182
183
  #
183
184
  # @return [Integer] Current HTTP status code
184
185
  #
@@ -191,7 +192,7 @@ module Capybara
191
192
  # @return [String] A snapshot of the DOM of the current document, as it looks right now (potentially modified by JavaScript).
192
193
  #
193
194
  def html
194
- driver.html
195
+ driver.html || ''
195
196
  end
196
197
  alias_method :body, :html
197
198
  alias_method :source, :html
@@ -238,13 +239,13 @@ module Capybara
238
239
  #
239
240
  # For drivers which can run against an external application, such as the selenium driver
240
241
  # giving an absolute URL will navigate to that page. This allows testing applications
241
- # running on remote servers. For these drivers, setting {Capybara.app_host} will make the
242
+ # running on remote servers. For these drivers, setting {Capybara.configure app_host} will make the
242
243
  # remote server the default. For example:
243
244
  #
244
245
  # Capybara.app_host = 'http://google.com'
245
246
  # session.visit('/') # visits the google homepage
246
247
  #
247
- # If {Capybara.always_include_port} is set to true and this session is running against
248
+ # If {Capybara.configure always_include_port} is set to `true` and this session is running against
248
249
  # a rack application, then the port that the rack application is running on will automatically
249
250
  # be inserted into the URL. Supposing the app is running on port `4567`, doing something like:
250
251
  #
@@ -279,7 +280,7 @@ module Capybara
279
280
 
280
281
  ##
281
282
  #
282
- # Refresh the page
283
+ # Refresh the page.
283
284
  #
284
285
  def refresh
285
286
  raise_server_error!
@@ -304,8 +305,8 @@ module Capybara
304
305
 
305
306
  ##
306
307
  #
307
- # Executes the given block within the context of a node. `within` takes the
308
- # same options as `find`, as well as a block. For the duration of the
308
+ # Executes the given block within the context of a node. {#within} takes the
309
+ # same options as {Capybara::Node::Finders#find #find}, as well as a block. For the duration of the
309
310
  # block, any command to Capybara will be handled as though it were scoped
310
311
  # to the given element.
311
312
  #
@@ -313,18 +314,18 @@ module Capybara
313
314
  # fill_in('Street', with: '12 Main Street')
314
315
  # end
315
316
  #
316
- # Just as with `find`, if multiple elements match the selector given to
317
- # `within`, an error will be raised, and just as with `find`, this
317
+ # Just as with `#find`, if multiple elements match the selector given to
318
+ # {#within}, an error will be raised, and just as with `#find`, this
318
319
  # behaviour can be controlled through the `:match` and `:exact` options.
319
320
  #
320
321
  # It is possible to omit the first parameter, in that case, the selector is
321
- # assumed to be of the type set in Capybara.default_selector.
322
+ # assumed to be of the type set in {Capybara.configure default_selector}.
322
323
  #
323
324
  # within('div#delivery-address') do
324
325
  # fill_in('Street', with: '12 Main Street')
325
326
  # end
326
327
  #
327
- # Note that a lot of uses of `within` can be replaced more succinctly with
328
+ # Note that a lot of uses of {#within} can be replaced more succinctly with
328
329
  # chaining:
329
330
  #
330
331
  # find('div#delivery-address').fill_in('Street', with: '12 Main Street')
@@ -337,8 +338,8 @@ module Capybara
337
338
  #
338
339
  # @raise [Capybara::ElementNotFound] If the scope can't be found before time expires
339
340
  #
340
- def within(*args)
341
- new_scope = args.first.respond_to?(:to_capybara_node) ? args.first.to_capybara_node : find(*args)
341
+ def within(*args, **kw_args)
342
+ new_scope = args.first.respond_to?(:to_capybara_node) ? args.first.to_capybara_node : find(*args, **kw_args)
342
343
  begin
343
344
  scopes.push(new_scope)
344
345
  yield if block_given?
@@ -370,18 +371,18 @@ module Capybara
370
371
 
371
372
  ##
372
373
  #
373
- # Switch to the given frame
374
+ # Switch to the given frame.
374
375
  #
375
376
  # If you use this method you are responsible for making sure you switch back to the parent frame when done in the frame changed to.
376
- # Capybara::Session#within_frame is preferred over this method and should be used when possible.
377
+ # {#within_frame} is preferred over this method and should be used when possible.
377
378
  # May not be supported by all drivers.
378
379
  #
379
380
  # @overload switch_to_frame(element)
380
- # @param [Capybara::Node::Element] iframe/frame element to switch to
381
- # @overload switch_to_frame(:parent)
382
- # Switch to the parent frame
383
- # @overload switch_to_frame(:top)
384
- # Switch to the top level document
381
+ # @param [Capybara::Node::Element] element iframe/frame element to switch to
382
+ # @overload switch_to_frame(location)
383
+ # @param [Symbol] location relative location of the frame to switch to
384
+ # * :parent - the parent frame
385
+ # * :top - the top level document
385
386
  #
386
387
  def switch_to_frame(frame)
387
388
  case frame
@@ -422,8 +423,8 @@ module Capybara
422
423
  # @param [String] locator The locator for the given selector kind. For :frame this is the name/id of a frame/iframe element
423
424
  # @overload within_frame(index)
424
425
  # @param [Integer] index index of a frame (0 based)
425
- def within_frame(*args)
426
- switch_to_frame(_find_frame(*args))
426
+ def within_frame(*args, **kw_args)
427
+ switch_to_frame(_find_frame(*args, **kw_args))
427
428
  begin
428
429
  yield if block_given?
429
430
  ensure
@@ -452,8 +453,8 @@ module Capybara
452
453
  end
453
454
 
454
455
  ##
455
- # Open new window.
456
- # Current window doesn't change as the result of this call.
456
+ # Open a new window.
457
+ # The current window doesn't change as the result of this call.
457
458
  # It should be switched to explicitly.
458
459
  #
459
460
  # @return [Capybara::Window] window that has been opened
@@ -469,9 +470,11 @@ module Capybara
469
470
  end
470
471
 
471
472
  ##
473
+ # Switch to the given window.
474
+ #
472
475
  # @overload switch_to_window(&block)
473
476
  # Switches to the first window for which given block returns a value other than false or nil.
474
- # If window that matches block can't be found, the window will be switched back and `WindowError` will be raised.
477
+ # If window that matches block can't be found, the window will be switched back and {Capybara::WindowError} will be raised.
475
478
  # @example
476
479
  # window = switch_to_window { title == 'Page title' }
477
480
  # @raise [Capybara::WindowError] if no window matches given block
@@ -480,8 +483,8 @@ module Capybara
480
483
  # @raise [Capybara::Driver::Base#no_such_window_error] if nonexistent (e.g. closed) window was passed
481
484
  #
482
485
  # @return [Capybara::Window] window that has been switched to
483
- # @raise [Capybara::ScopeError] if this method is invoked inside `within` or
484
- # `within_frame` methods
486
+ # @raise [Capybara::ScopeError] if this method is invoked inside {#within} or
487
+ # {#within_frame} methods
485
488
  # @raise [ArgumentError] if both or neither arguments were provided
486
489
  #
487
490
  def switch_to_window(window = nil, **options, &window_locator)
@@ -493,7 +496,7 @@ module Capybara
493
496
  '`within` or `within_frame` blocks.'
494
497
  end
495
498
 
496
- _switch_to_window(window, options, &window_locator)
499
+ _switch_to_window(window, **options, &window_locator)
497
500
  end
498
501
 
499
502
  ##
@@ -501,20 +504,20 @@ module Capybara
501
504
  #
502
505
  # 1. Switches to the given window (it can be located by window instance/lambda/string).
503
506
  # 2. Executes the given block (within window located at previous step).
504
- # 3. Switches back (this step will be invoked even if exception will happen at second step)
507
+ # 3. Switches back (this step will be invoked even if an exception occurs at the second step).
505
508
  #
506
509
  # @overload within_window(window) { do_something }
507
- # @param window [Capybara::Window] instance of `Capybara::Window` class
510
+ # @param window [Capybara::Window] instance of {Capybara::Window} class
508
511
  # that will be switched to
509
512
  # @raise [driver#no_such_window_error] if nonexistent (e.g. closed) window was passed
510
513
  # @overload within_window(proc_or_lambda) { do_something }
511
- # @param lambda [Proc] lambda. First window for which lambda
514
+ # @param lambda [Proc] First window for which lambda
512
515
  # returns a value other than false or nil will be switched to.
513
516
  # @example
514
517
  # within_window(->{ page.title == 'Page title' }) { click_button 'Submit' }
515
518
  # @raise [Capybara::WindowError] if no window matching lambda was found
516
519
  #
517
- # @raise [Capybara::ScopeError] if this method is invoked inside `within_frame` method
520
+ # @raise [Capybara::ScopeError] if this method is invoked inside {#within_frame} method
518
521
  # @return value returned by the block
519
522
  #
520
523
  def within_window(window_or_proc)
@@ -527,7 +530,7 @@ module Capybara
527
530
  when Proc
528
531
  _switch_to_window { window_or_proc.call }
529
532
  else
530
- raise ArgumentError('`#within_window` requires a `Capybara::Window` instance or a lambda')
533
+ raise ArgumentError, '`#within_window` requires a `Capybara::Window` instance or a lambda'
531
534
  end
532
535
 
533
536
  begin
@@ -544,11 +547,11 @@ module Capybara
544
547
  # Get the window that has been opened by the passed block.
545
548
  # It will wait for it to be opened (in the same way as other Capybara methods wait).
546
549
  # It's better to use this method than `windows.last`
547
- # {https://dvcs.w3.org/hg/webdriver/raw-file/default/webdriver-spec.html#h_note_10 as order of windows isn't defined in some drivers}
550
+ # {https://dvcs.w3.org/hg/webdriver/raw-file/default/webdriver-spec.html#h_note_10 as order of windows isn't defined in some drivers}.
548
551
  #
549
552
  # @overload window_opened_by(**options, &block)
550
553
  # @param options [Hash]
551
- # @option options [Numeric] :wait (Capybara.default_max_wait_time) maximum wait time
554
+ # @option options [Numeric] :wait maximum wait time. Defaults to {Capybara.configure default_max_wait_time}
552
555
  # @return [Capybara::Window] the window that has been opened within a block
553
556
  # @raise [Capybara::WindowError] if block passed to window hasn't opened window
554
557
  # or opened more than one window
@@ -570,11 +573,11 @@ module Capybara
570
573
  ##
571
574
  #
572
575
  # Execute the given script, not returning a result. This is useful for scripts that return
573
- # complex objects, such as jQuery statements. +execute_script+ should be used over
574
- # +evaluate_script+ whenever possible.
576
+ # complex objects, such as jQuery statements. {#execute_script} should be used over
577
+ # {#evaluate_script} whenever possible.
575
578
  #
576
579
  # @param [String] script A string of JavaScript to execute
577
- # @param args Optional arguments that will be passed to the script. Driver support for this is optional and types of objects supported may differ between drivers
580
+ # @param args Optional arguments that will be passed to the script. Driver support for this is optional and types of objects supported may differ between drivers
578
581
  #
579
582
  def execute_script(script, *args)
580
583
  @touched = true
@@ -584,10 +587,11 @@ module Capybara
584
587
  ##
585
588
  #
586
589
  # Evaluate the given JavaScript and return the result. Be careful when using this with
587
- # scripts that return complex objects, such as jQuery statements. +execute_script+ might
590
+ # scripts that return complex objects, such as jQuery statements. {#execute_script} might
588
591
  # be a better alternative.
589
592
  #
590
593
  # @param [String] script A string of JavaScript to evaluate
594
+ # @param args Optional arguments that will be passed to the script
591
595
  # @return [Object] The result of the evaluated JavaScript (may be driver specific)
592
596
  #
593
597
  def evaluate_script(script, *args)
@@ -601,6 +605,7 @@ module Capybara
601
605
  # Evaluate the given JavaScript and obtain the result from a callback function which will be passed as the last argument to the script.
602
606
  #
603
607
  # @param [String] script A string of JavaScript to evaluate
608
+ # @param args Optional arguments that will be passed to the script
604
609
  # @return [Object] The result of the evaluated JavaScript (may be driver specific)
605
610
  #
606
611
  def evaluate_async_script(script, *args)
@@ -614,17 +619,17 @@ module Capybara
614
619
  # Execute the block, accepting a alert.
615
620
  #
616
621
  # @!macro modal_params
617
- # Expects a block whose actions will trigger the display modal to appear
622
+ # Expects a block whose actions will trigger the display modal to appear.
618
623
  # @example
619
624
  # $0 do
620
625
  # click_link('link that triggers appearance of system modal')
621
626
  # end
622
627
  # @overload $0(text, **options, &blk)
623
- # @param text [String, Regexp] Text or regex to match against the text in the modal. If not provided any modal is matched
624
- # @option options [Numeric] :wait (Capybara.default_max_wait_time) Maximum time to wait for the modal to appear after executing the block.
628
+ # @param text [String, Regexp] Text or regex to match against the text in the modal. If not provided any modal is matched.
629
+ # @option options [Numeric] :wait Maximum time to wait for the modal to appear after executing the block. Defaults to {Capybara.configure default_max_wait_time}.
625
630
  # @yield Block whose actions will trigger the system modal
626
631
  # @overload $0(**options, &blk)
627
- # @option options [Numeric] :wait (Capybara.default_max_wait_time) Maximum time to wait for the modal to appear after executing the block.
632
+ # @option options [Numeric] :wait Maximum time to wait for the modal to appear after executing the block. Defaults to {Capybara.configure default_max_wait_time}.
628
633
  # @yield Block whose actions will trigger the system modal
629
634
  # @return [String] the message shown in the modal
630
635
  # @raise [Capybara::ModalNotFound] if modal dialog hasn't been found
@@ -676,12 +681,12 @@ module Capybara
676
681
 
677
682
  ##
678
683
  #
679
- # Save a snapshot of the page. If `Capybara.asset_host` is set it will inject `base` tag
680
- # pointing to `asset_host`.
684
+ # Save a snapshot of the page. If {Capybara.configure asset_host} is set it will inject `base` tag
685
+ # pointing to {Capybara.configure asset_host}.
681
686
  #
682
- # If invoked without arguments it will save file to `Capybara.save_path`
683
- # and file will be given randomly generated filename. If invoked with a relative path
684
- # the path will be relative to `Capybara.save_path`
687
+ # If invoked without arguments it will save file to {Capybara.configure save_path}
688
+ # and file will be given randomly generated filename. If invoked with a relative path
689
+ # the path will be relative to {Capybara.configure save_path}.
685
690
  #
686
691
  # @param [String] path the path to where it should be saved
687
692
  # @return [String] the path to which the file was saved
@@ -696,9 +701,9 @@ module Capybara
696
701
  #
697
702
  # Save a snapshot of the page and open it in a browser for inspection.
698
703
  #
699
- # If invoked without arguments it will save file to `Capybara.save_path`
700
- # and file will be given randomly generated filename. If invoked with a relative path
701
- # the path will be relative to `Capybara.save_path`
704
+ # If invoked without arguments it will save file to {Capybara.configure save_path}
705
+ # and file will be given randomly generated filename. If invoked with a relative path
706
+ # the path will be relative to {Capybara.configure save_path}.
702
707
  #
703
708
  # @param [String] path the path to where it should be saved
704
709
  #
@@ -710,32 +715,30 @@ module Capybara
710
715
  #
711
716
  # Save a screenshot of page.
712
717
  #
713
- # If invoked without arguments it will save file to `Capybara.save_path`
714
- # and file will be given randomly generated filename. If invoked with a relative path
715
- # the path will be relative to `Capybara.save_path`
718
+ # If invoked without arguments it will save file to {Capybara.configure save_path}
719
+ # and file will be given randomly generated filename. If invoked with a relative path
720
+ # the path will be relative to {Capybara.configure save_path}.
716
721
  #
717
722
  # @param [String] path the path to where it should be saved
718
723
  # @param [Hash] options a customizable set of options
719
724
  # @return [String] the path to which the file was saved
720
725
  def save_screenshot(path = nil, **options)
721
- prepare_path(path, 'png').tap { |p_path| driver.save_screenshot(p_path, options) }
726
+ prepare_path(path, 'png').tap { |p_path| driver.save_screenshot(p_path, **options) }
722
727
  end
723
728
 
724
729
  ##
725
730
  #
726
731
  # Save a screenshot of the page and open it for inspection.
727
732
  #
728
- # If invoked without arguments it will save file to `Capybara.save_path`
729
- # and file will be given randomly generated filename. If invoked with a relative path
730
- # the path will be relative to `Capybara.save_path`
733
+ # If invoked without arguments it will save file to {Capybara.configure save_path}
734
+ # and file will be given randomly generated filename. If invoked with a relative path
735
+ # the path will be relative to {Capybara.configure save_path}.
731
736
  #
732
737
  # @param [String] path the path to where it should be saved
733
738
  # @param [Hash] options a customizable set of options
734
739
  #
735
740
  def save_and_open_screenshot(path = nil, **options)
736
- # rubocop:disable Lint/Debugger
737
- save_screenshot(path, options).tap { |s_path| open_file(s_path) }
738
- # rubocop:enable Lint/Debugger
741
+ save_screenshot(path, **options).tap { |s_path| open_file(s_path) } # rubocop:disable Lint/Debugger
739
742
  end
740
743
 
741
744
  def document
@@ -743,15 +746,32 @@ module Capybara
743
746
  end
744
747
 
745
748
  NODE_METHODS.each do |method|
746
- define_method method do |*args, &block|
747
- @touched = true
748
- current_scope.send(method, *args, &block)
749
+ if RUBY_VERSION >= '2.7'
750
+ class_eval <<~METHOD, __FILE__, __LINE__ + 1
751
+ def #{method}(...)
752
+ @touched = true
753
+ current_scope.#{method}(...)
754
+ end
755
+ METHOD
756
+ else
757
+ define_method method do |*args, &block|
758
+ @touched = true
759
+ current_scope.send(method, *args, &block)
760
+ end
749
761
  end
750
762
  end
751
763
 
752
764
  DOCUMENT_METHODS.each do |method|
753
- define_method method do |*args, &block|
754
- document.send(method, *args, &block)
765
+ if RUBY_VERSION >= '2.7'
766
+ class_eval <<~METHOD, __FILE__, __LINE__ + 1
767
+ def #{method}(...)
768
+ document.#{method}(...)
769
+ end
770
+ METHOD
771
+ else
772
+ define_method method do |*args, &block|
773
+ document.send(method, *args, &block)
774
+ end
755
775
  end
756
776
  end
757
777
 
@@ -766,7 +786,7 @@ module Capybara
766
786
 
767
787
  ##
768
788
  #
769
- # Yield a block using a specific wait time
789
+ # Yield a block using a specific maximum wait time.
770
790
  #
771
791
  def using_wait_time(seconds)
772
792
  if Capybara.threadsafe
@@ -784,8 +804,8 @@ module Capybara
784
804
 
785
805
  ##
786
806
  #
787
- # Accepts a block to set the configuration options if Capybara.threadsafe == true. Note that some options only have an effect
788
- # if set at initialization time, so look at the configuration block that can be passed to the initializer too
807
+ # Accepts a block to set the configuration options if {Capybara.configure threadsafe} is `true`. Note that some options only have an effect
808
+ # if set at initialization time, so look at the configuration block that can be passed to the initializer too.
789
809
  #
790
810
  def configure
791
811
  raise 'Session configuration is only supported when Capybara.threadsafe == true' unless Capybara.threadsafe
@@ -805,20 +825,24 @@ module Capybara
805
825
  end
806
826
  end
807
827
 
828
+ def server_url
829
+ @server&.base_url
830
+ end
831
+
808
832
  private
809
833
 
810
- @@instance_created = false
834
+ @@instance_created = false # rubocop:disable Style/ClassVars
811
835
 
812
836
  def driver_args(args)
813
837
  args.map { |arg| arg.is_a?(Capybara::Node::Element) ? arg.base : arg }
814
838
  end
815
839
 
816
840
  def accept_modal(type, text_or_options, options, &blk)
817
- driver.accept_modal(type, modal_options(text_or_options, options), &blk)
841
+ driver.accept_modal(type, **modal_options(text_or_options, **options), &blk)
818
842
  end
819
843
 
820
844
  def dismiss_modal(type, text_or_options, options, &blk)
821
- driver.dismiss_modal(type, modal_options(text_or_options, options), &blk)
845
+ driver.dismiss_modal(type, **modal_options(text_or_options, **options), &blk)
822
846
  end
823
847
 
824
848
  def modal_options(text = nil, **options)
@@ -862,24 +886,18 @@ module Capybara
862
886
  end
863
887
  end
864
888
 
865
- def server_url
866
- "http#{'s' if @server.using_ssl?}://#{@server.host}:#{@server.port}" if @server
867
- end
868
-
869
889
  def adjust_server_port(uri)
870
890
  uri.port ||= @server.port if @server && config.always_include_port
871
891
  end
872
892
 
873
- def _find_frame(*args)
874
- return find(:frame) if args.length.zero?
875
-
893
+ def _find_frame(*args, **kw_args)
876
894
  case args[0]
877
895
  when Capybara::Node::Element
878
896
  args[0]
879
- when String, Hash
880
- find(:frame, *args)
897
+ when String, nil
898
+ find(:frame, *args, **kw_args)
881
899
  when Symbol
882
- find(*args)
900
+ find(*args, **kw_args)
883
901
  when Integer
884
902
  idx = args[0]
885
903
  all(:frame, minimum: idx + 1)[idx]