capybara 3.8.1 → 3.33.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (242) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +1 -0
  3. data/History.md +465 -0
  4. data/License.txt +1 -1
  5. data/README.md +58 -57
  6. data/lib/capybara/config.rb +10 -4
  7. data/lib/capybara/cucumber.rb +1 -1
  8. data/lib/capybara/driver/base.rb +2 -2
  9. data/lib/capybara/driver/node.rb +26 -5
  10. data/lib/capybara/dsl.rb +12 -4
  11. data/lib/capybara/helpers.rb +8 -4
  12. data/lib/capybara/minitest/spec.rb +162 -85
  13. data/lib/capybara/minitest.rb +248 -148
  14. data/lib/capybara/node/actions.rb +149 -96
  15. data/lib/capybara/node/base.rb +27 -10
  16. data/lib/capybara/node/document.rb +12 -0
  17. data/lib/capybara/node/document_matchers.rb +9 -5
  18. data/lib/capybara/node/element.rb +254 -109
  19. data/lib/capybara/node/finders.rb +83 -76
  20. data/lib/capybara/node/matchers.rb +279 -141
  21. data/lib/capybara/node/simple.rb +25 -6
  22. data/lib/capybara/queries/ancestor_query.rb +5 -7
  23. data/lib/capybara/queries/base_query.rb +11 -5
  24. data/lib/capybara/queries/current_path_query.rb +3 -3
  25. data/lib/capybara/queries/match_query.rb +1 -0
  26. data/lib/capybara/queries/selector_query.rb +467 -103
  27. data/lib/capybara/queries/sibling_query.rb +5 -4
  28. data/lib/capybara/queries/style_query.rb +6 -2
  29. data/lib/capybara/queries/text_query.rb +17 -3
  30. data/lib/capybara/queries/title_query.rb +2 -2
  31. data/lib/capybara/rack_test/browser.rb +22 -15
  32. data/lib/capybara/rack_test/driver.rb +10 -1
  33. data/lib/capybara/rack_test/errors.rb +6 -0
  34. data/lib/capybara/rack_test/form.rb +33 -28
  35. data/lib/capybara/rack_test/node.rb +74 -6
  36. data/lib/capybara/registration_container.rb +44 -0
  37. data/lib/capybara/registrations/drivers.rb +36 -0
  38. data/lib/capybara/registrations/patches/puma_ssl.rb +27 -0
  39. data/lib/capybara/registrations/servers.rb +44 -0
  40. data/lib/capybara/result.rb +55 -23
  41. data/lib/capybara/rspec/features.rb +4 -4
  42. data/lib/capybara/rspec/matcher_proxies.rb +36 -15
  43. data/lib/capybara/rspec/matchers/base.rb +111 -0
  44. data/lib/capybara/rspec/matchers/become_closed.rb +33 -0
  45. data/lib/capybara/rspec/matchers/compound.rb +88 -0
  46. data/lib/capybara/rspec/matchers/count_sugar.rb +37 -0
  47. data/lib/capybara/rspec/matchers/have_ancestor.rb +28 -0
  48. data/lib/capybara/rspec/matchers/have_current_path.rb +29 -0
  49. data/lib/capybara/rspec/matchers/have_selector.rb +77 -0
  50. data/lib/capybara/rspec/matchers/have_sibling.rb +27 -0
  51. data/lib/capybara/rspec/matchers/have_text.rb +33 -0
  52. data/lib/capybara/rspec/matchers/have_title.rb +29 -0
  53. data/lib/capybara/rspec/matchers/match_selector.rb +27 -0
  54. data/lib/capybara/rspec/matchers/match_style.rb +38 -0
  55. data/lib/capybara/rspec/matchers/spatial_sugar.rb +39 -0
  56. data/lib/capybara/rspec/matchers.rb +117 -311
  57. data/lib/capybara/selector/builders/css_builder.rb +84 -0
  58. data/lib/capybara/selector/builders/xpath_builder.rb +69 -0
  59. data/lib/capybara/selector/css.rb +17 -15
  60. data/lib/capybara/selector/definition/button.rb +52 -0
  61. data/lib/capybara/selector/definition/checkbox.rb +26 -0
  62. data/lib/capybara/selector/definition/css.rb +10 -0
  63. data/lib/capybara/selector/definition/datalist_input.rb +35 -0
  64. data/lib/capybara/selector/definition/datalist_option.rb +25 -0
  65. data/lib/capybara/selector/definition/element.rb +27 -0
  66. data/lib/capybara/selector/definition/field.rb +40 -0
  67. data/lib/capybara/selector/definition/fieldset.rb +14 -0
  68. data/lib/capybara/selector/definition/file_field.rb +13 -0
  69. data/lib/capybara/selector/definition/fillable_field.rb +33 -0
  70. data/lib/capybara/selector/definition/frame.rb +17 -0
  71. data/lib/capybara/selector/definition/id.rb +6 -0
  72. data/lib/capybara/selector/definition/label.rb +62 -0
  73. data/lib/capybara/selector/definition/link.rb +54 -0
  74. data/lib/capybara/selector/definition/link_or_button.rb +16 -0
  75. data/lib/capybara/selector/definition/option.rb +27 -0
  76. data/lib/capybara/selector/definition/radio_button.rb +27 -0
  77. data/lib/capybara/selector/definition/select.rb +81 -0
  78. data/lib/capybara/selector/definition/table.rb +109 -0
  79. data/lib/capybara/selector/definition/table_row.rb +21 -0
  80. data/lib/capybara/selector/definition/xpath.rb +5 -0
  81. data/lib/capybara/selector/definition.rb +277 -0
  82. data/lib/capybara/selector/filter.rb +1 -0
  83. data/lib/capybara/selector/filter_set.rb +26 -19
  84. data/lib/capybara/selector/filters/base.rb +24 -5
  85. data/lib/capybara/selector/filters/expression_filter.rb +3 -3
  86. data/lib/capybara/selector/filters/locator_filter.rb +29 -0
  87. data/lib/capybara/selector/filters/node_filter.rb +16 -2
  88. data/lib/capybara/selector/regexp_disassembler.rb +214 -0
  89. data/lib/capybara/selector/selector.rb +73 -367
  90. data/lib/capybara/selector/xpath_extensions.rb +17 -0
  91. data/lib/capybara/selector.rb +221 -480
  92. data/lib/capybara/selenium/atoms/getAttribute.min.js +1 -0
  93. data/lib/capybara/selenium/atoms/isDisplayed.min.js +1 -0
  94. data/lib/capybara/selenium/atoms/src/getAttribute.js +161 -0
  95. data/lib/capybara/selenium/atoms/src/isDisplayed.js +454 -0
  96. data/lib/capybara/selenium/driver.rb +203 -86
  97. data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +88 -14
  98. data/lib/capybara/selenium/driver_specializations/edge_driver.rb +124 -0
  99. data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +89 -0
  100. data/lib/capybara/selenium/driver_specializations/internet_explorer_driver.rb +26 -0
  101. data/lib/capybara/selenium/driver_specializations/safari_driver.rb +24 -0
  102. data/lib/capybara/selenium/extensions/file_input_click_emulation.rb +34 -0
  103. data/lib/capybara/selenium/extensions/find.rb +110 -0
  104. data/lib/capybara/selenium/extensions/html5_drag.rb +191 -22
  105. data/lib/capybara/selenium/extensions/modifier_keys_stack.rb +28 -0
  106. data/lib/capybara/selenium/extensions/scroll.rb +78 -0
  107. data/lib/capybara/selenium/logger_suppressor.rb +34 -0
  108. data/lib/capybara/selenium/node.rb +298 -93
  109. data/lib/capybara/selenium/nodes/chrome_node.rb +100 -8
  110. data/lib/capybara/selenium/nodes/edge_node.rb +104 -0
  111. data/lib/capybara/selenium/nodes/firefox_node.rb +131 -0
  112. data/lib/capybara/selenium/nodes/ie_node.rb +22 -0
  113. data/lib/capybara/selenium/nodes/safari_node.rb +118 -0
  114. data/lib/capybara/selenium/patches/action_pauser.rb +26 -0
  115. data/lib/capybara/selenium/patches/atoms.rb +18 -0
  116. data/lib/capybara/selenium/patches/is_displayed.rb +16 -0
  117. data/lib/capybara/selenium/patches/logs.rb +45 -0
  118. data/lib/capybara/selenium/patches/pause_duration_fix.rb +1 -3
  119. data/lib/capybara/selenium/patches/persistent_client.rb +20 -0
  120. data/lib/capybara/server/animation_disabler.rb +4 -3
  121. data/lib/capybara/server/checker.rb +6 -2
  122. data/lib/capybara/server/middleware.rb +23 -13
  123. data/lib/capybara/server.rb +30 -7
  124. data/lib/capybara/session/config.rb +14 -10
  125. data/lib/capybara/session/matchers.rb +11 -7
  126. data/lib/capybara/session.rb +152 -111
  127. data/lib/capybara/spec/public/offset.js +6 -0
  128. data/lib/capybara/spec/public/test.js +101 -10
  129. data/lib/capybara/spec/session/all_spec.rb +96 -6
  130. data/lib/capybara/spec/session/ancestor_spec.rb +5 -0
  131. data/lib/capybara/spec/session/assert_all_of_selectors_spec.rb +29 -0
  132. data/lib/capybara/spec/session/assert_current_path_spec.rb +5 -2
  133. data/lib/capybara/spec/session/assert_selector_spec.rb +0 -10
  134. data/lib/capybara/spec/session/assert_style_spec.rb +4 -4
  135. data/lib/capybara/spec/session/assert_text_spec.rb +9 -5
  136. data/lib/capybara/spec/session/attach_file_spec.rb +63 -36
  137. data/lib/capybara/spec/session/check_spec.rb +10 -4
  138. data/lib/capybara/spec/session/choose_spec.rb +8 -2
  139. data/lib/capybara/spec/session/click_button_spec.rb +117 -61
  140. data/lib/capybara/spec/session/click_link_or_button_spec.rb +16 -0
  141. data/lib/capybara/spec/session/click_link_spec.rb +17 -6
  142. data/lib/capybara/spec/session/element/matches_selector_spec.rb +40 -39
  143. data/lib/capybara/spec/session/evaluate_script_spec.rb +13 -0
  144. data/lib/capybara/spec/session/execute_script_spec.rb +1 -0
  145. data/lib/capybara/spec/session/fill_in_spec.rb +47 -6
  146. data/lib/capybara/spec/session/find_field_spec.rb +1 -1
  147. data/lib/capybara/spec/session/find_spec.rb +74 -4
  148. data/lib/capybara/spec/session/first_spec.rb +1 -1
  149. data/lib/capybara/spec/session/frame/switch_to_frame_spec.rb +13 -1
  150. data/lib/capybara/spec/session/frame/within_frame_spec.rb +12 -1
  151. data/lib/capybara/spec/session/has_all_selectors_spec.rb +1 -1
  152. data/lib/capybara/spec/session/has_ancestor_spec.rb +46 -0
  153. data/lib/capybara/spec/session/has_any_selectors_spec.rb +25 -0
  154. data/lib/capybara/spec/session/has_button_spec.rb +16 -0
  155. data/lib/capybara/spec/session/has_css_spec.rb +122 -12
  156. data/lib/capybara/spec/session/has_current_path_spec.rb +6 -4
  157. data/lib/capybara/spec/session/has_field_spec.rb +55 -0
  158. data/lib/capybara/spec/session/has_select_spec.rb +34 -6
  159. data/lib/capybara/spec/session/has_selector_spec.rb +11 -4
  160. data/lib/capybara/spec/session/has_sibling_spec.rb +50 -0
  161. data/lib/capybara/spec/session/has_table_spec.rb +166 -0
  162. data/lib/capybara/spec/session/has_text_spec.rb +48 -1
  163. data/lib/capybara/spec/session/has_xpath_spec.rb +17 -0
  164. data/lib/capybara/spec/session/html_spec.rb +7 -0
  165. data/lib/capybara/spec/session/matches_style_spec.rb +35 -0
  166. data/lib/capybara/spec/session/node_spec.rb +643 -18
  167. data/lib/capybara/spec/session/node_wrapper_spec.rb +1 -1
  168. data/lib/capybara/spec/session/refresh_spec.rb +4 -0
  169. data/lib/capybara/spec/session/reset_session_spec.rb +23 -8
  170. data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +2 -2
  171. data/lib/capybara/spec/session/save_screenshot_spec.rb +4 -4
  172. data/lib/capybara/spec/session/scroll_spec.rb +117 -0
  173. data/lib/capybara/spec/session/select_spec.rb +10 -10
  174. data/lib/capybara/spec/session/selectors_spec.rb +36 -5
  175. data/lib/capybara/spec/session/uncheck_spec.rb +2 -2
  176. data/lib/capybara/spec/session/unselect_spec.rb +1 -1
  177. data/lib/capybara/spec/session/window/become_closed_spec.rb +20 -17
  178. data/lib/capybara/spec/session/window/switch_to_window_spec.rb +4 -0
  179. data/lib/capybara/spec/session/window/window_opened_by_spec.rb +4 -0
  180. data/lib/capybara/spec/session/window/window_spec.rb +59 -58
  181. data/lib/capybara/spec/session/window/windows_spec.rb +4 -0
  182. data/lib/capybara/spec/session/within_spec.rb +23 -0
  183. data/lib/capybara/spec/spec_helper.rb +16 -6
  184. data/lib/capybara/spec/test_app.rb +28 -23
  185. data/lib/capybara/spec/views/animated.erb +49 -0
  186. data/lib/capybara/spec/views/form.erb +48 -7
  187. data/lib/capybara/spec/views/frame_child.erb +3 -2
  188. data/lib/capybara/spec/views/frame_one.erb +1 -0
  189. data/lib/capybara/spec/views/obscured.erb +47 -0
  190. data/lib/capybara/spec/views/offset.erb +32 -0
  191. data/lib/capybara/spec/views/react.erb +45 -0
  192. data/lib/capybara/spec/views/scroll.erb +20 -0
  193. data/lib/capybara/spec/views/spatial.erb +31 -0
  194. data/lib/capybara/spec/views/tables.erb +67 -0
  195. data/lib/capybara/spec/views/with_animation.erb +29 -1
  196. data/lib/capybara/spec/views/with_dragula.erb +24 -0
  197. data/lib/capybara/spec/views/with_hover.erb +1 -0
  198. data/lib/capybara/spec/views/with_hover1.erb +10 -0
  199. data/lib/capybara/spec/views/with_html.erb +32 -6
  200. data/lib/capybara/spec/views/with_js.erb +3 -1
  201. data/lib/capybara/spec/views/with_jstree.erb +26 -0
  202. data/lib/capybara/spec/views/with_scope_other.erb +6 -0
  203. data/lib/capybara/spec/views/with_sortable_js.erb +21 -0
  204. data/lib/capybara/version.rb +1 -1
  205. data/lib/capybara/window.rb +11 -11
  206. data/lib/capybara.rb +118 -111
  207. data/spec/basic_node_spec.rb +14 -3
  208. data/spec/capybara_spec.rb +29 -29
  209. data/spec/css_builder_spec.rb +101 -0
  210. data/spec/dsl_spec.rb +46 -21
  211. data/spec/filter_set_spec.rb +5 -5
  212. data/spec/fixtures/selenium_driver_rspec_failure.rb +1 -1
  213. data/spec/fixtures/selenium_driver_rspec_success.rb +1 -1
  214. data/spec/minitest_spec.rb +18 -4
  215. data/spec/minitest_spec_spec.rb +59 -44
  216. data/spec/rack_test_spec.rb +117 -89
  217. data/spec/regexp_dissassembler_spec.rb +250 -0
  218. data/spec/result_spec.rb +51 -49
  219. data/spec/rspec/features_spec.rb +3 -0
  220. data/spec/rspec/shared_spec_matchers.rb +112 -97
  221. data/spec/rspec_spec.rb +35 -17
  222. data/spec/sauce_spec_chrome.rb +43 -0
  223. data/spec/selector_spec.rb +244 -28
  224. data/spec/selenium_spec_chrome.rb +125 -54
  225. data/spec/selenium_spec_chrome_remote.rb +26 -12
  226. data/spec/selenium_spec_edge.rb +23 -8
  227. data/spec/selenium_spec_firefox.rb +208 -0
  228. data/spec/selenium_spec_firefox_remote.rb +15 -18
  229. data/spec/selenium_spec_ie.rb +82 -13
  230. data/spec/selenium_spec_safari.rb +148 -0
  231. data/spec/server_spec.rb +118 -77
  232. data/spec/session_spec.rb +19 -3
  233. data/spec/shared_selenium_node.rb +83 -0
  234. data/spec/shared_selenium_session.rb +110 -65
  235. data/spec/spec_helper.rb +57 -9
  236. data/spec/xpath_builder_spec.rb +93 -0
  237. metadata +257 -17
  238. data/lib/capybara/rspec/compound.rb +0 -94
  239. data/lib/capybara/selenium/driver_specializations/marionette_driver.rb +0 -49
  240. data/lib/capybara/selenium/nodes/marionette_node.rb +0 -121
  241. data/lib/capybara/spec/session/has_style_spec.rb +0 -25
  242. data/spec/selenium_spec_marionette.rb +0 -172
@@ -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,39 +17,39 @@ 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
40
40
 
41
41
  NODE_METHODS = %i[
42
- all first attach_file text check choose
42
+ all first attach_file text check choose scroll_to scroll_by
43
43
  click_link_or_button click_button click_link
44
44
  fill_in find find_all find_button find_by_id find_field find_link
45
45
  has_content? has_text? has_css? has_no_content? has_no_text?
46
- has_no_css? has_no_xpath? resolve has_xpath? select uncheck
46
+ has_no_css? has_no_xpath? has_xpath? select uncheck
47
47
  has_link? has_no_link? has_button? has_no_button? has_field?
48
48
  has_no_field? has_checked_field? has_unchecked_field?
49
49
  has_no_table? has_table? unselect has_select? has_no_select?
50
50
  has_selector? has_no_selector? click_on has_no_checked_field?
51
51
  has_no_unchecked_field? query assert_selector assert_no_selector
52
- assert_all_of_selectors assert_none_of_selectors
52
+ assert_all_of_selectors assert_none_of_selectors assert_any_of_selectors
53
53
  refute_selector assert_text assert_no_text
54
54
  ].freeze
55
55
  # @api private
@@ -75,26 +75,30 @@ 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)
79
- @@instance_created = true
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
81
+
82
+ @@instance_created = true # rubocop:disable Style/ClassVars
80
83
  @mode = mode
81
84
  @app = app
82
85
  if block_given?
83
86
  raise 'A configuration block is only accepted when Capybara.threadsafe == true' unless Capybara.threadsafe
87
+
84
88
  yield config
85
89
  end
86
90
  @server = if config.run_server && @app && driver.needs_server?
87
91
  server_options = { port: config.server_port, host: config.server_host, reportable_errors: config.server_errors }
88
92
  server_options[:extra_middleware] = [Capybara::Server::AnimationDisabler] if config.disable_animation
89
- Capybara::Server.new(@app, server_options).boot
93
+ Capybara::Server.new(@app, **server_options).boot
90
94
  end
91
95
  @touched = false
92
96
  end
93
97
 
94
98
  def driver
95
99
  @driver ||= begin
96
- unless Capybara.drivers.key?(mode)
97
- other_drivers = Capybara.drivers.keys.map(&:inspect)
100
+ unless Capybara.drivers[mode]
101
+ other_drivers = Capybara.drivers.names.map(&:inspect)
98
102
  raise Capybara::DriverNotFoundError, "no driver called #{mode.inspect} was found, available drivers: #{other_drivers.join(', ')}"
99
103
  end
100
104
  driver = Capybara.drivers[mode].call(app)
@@ -105,21 +109,21 @@ module Capybara
105
109
 
106
110
  ##
107
111
  #
108
- # 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).
109
113
  #
110
114
  # This method does not:
111
115
  #
112
- # * accept modal dialogs if they are present (Selenium driver now does, others may not)
113
- # * clear browser cache/HTML 5 local storage/IndexedDB/Web SQL database/etc.
114
- # * 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
115
119
  #
116
120
  # as doing so will result in performance downsides and it's not needed to do everything from the list above for most apps.
117
121
  #
118
122
  # If you want to do anything from the list above on a general basis you can:
119
123
  #
120
- # * write RSpec/Cucumber/etc. after hook
121
- # * monkeypatch this method
122
- # * use Ruby's `prepend` method
124
+ # * write RSpec/Cucumber/etc. after hook
125
+ # * monkeypatch this method
126
+ # * use Ruby's `prepend` method
123
127
  #
124
128
  def reset!
125
129
  if @touched
@@ -134,10 +138,22 @@ module Capybara
134
138
 
135
139
  ##
136
140
  #
137
- # Raise errors encountered in the server
141
+ # Disconnect from the current driver. A new driver will be instantiated on the next interaction.
142
+ #
143
+ def quit
144
+ @driver.quit if @driver.respond_to? :quit
145
+ @document = @driver = nil
146
+ @touched = false
147
+ @server&.reset_error!
148
+ end
149
+
150
+ ##
151
+ #
152
+ # Raise errors encountered in the server.
138
153
  #
139
154
  def raise_server_error!
140
155
  return unless @server&.error
156
+
141
157
  # Force an explanation for the error being raised as the exception cause
142
158
  begin
143
159
  if config.raise_server_errors
@@ -153,9 +169,9 @@ module Capybara
153
169
 
154
170
  ##
155
171
  #
156
- # 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).
157
173
  #
158
- # @return [Hash{String => String}] A hash of response headers.
174
+ # @return [Hash<String, String>] A hash of response headers.
159
175
  #
160
176
  def response_headers
161
177
  driver.response_headers
@@ -163,7 +179,7 @@ module Capybara
163
179
 
164
180
  ##
165
181
  #
166
- # 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).
167
183
  #
168
184
  # @return [Integer] Current HTTP status code
169
185
  #
@@ -176,7 +192,7 @@ module Capybara
176
192
  # @return [String] A snapshot of the DOM of the current document, as it looks right now (potentially modified by JavaScript).
177
193
  #
178
194
  def html
179
- driver.html
195
+ driver.html || ''
180
196
  end
181
197
  alias_method :body, :html
182
198
  alias_method :source, :html
@@ -223,13 +239,13 @@ module Capybara
223
239
  #
224
240
  # For drivers which can run against an external application, such as the selenium driver
225
241
  # giving an absolute URL will navigate to that page. This allows testing applications
226
- # 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
227
243
  # remote server the default. For example:
228
244
  #
229
245
  # Capybara.app_host = 'http://google.com'
230
246
  # session.visit('/') # visits the google homepage
231
247
  #
232
- # 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
233
249
  # a rack application, then the port that the rack application is running on will automatically
234
250
  # be inserted into the URL. Supposing the app is running on port `4567`, doing something like:
235
251
  #
@@ -248,7 +264,7 @@ module Capybara
248
264
 
249
265
  if base_uri && [nil, 'http', 'https'].include?(visit_uri.scheme)
250
266
  if visit_uri.relative?
251
- visit_uri_parts = visit_uri.to_hash.delete_if { |_k, value| value.nil? }
267
+ visit_uri_parts = visit_uri.to_hash.compact
252
268
 
253
269
  # Useful to people deploying to a subdirectory
254
270
  # and/or single page apps where only the url fragment changes
@@ -264,7 +280,7 @@ module Capybara
264
280
 
265
281
  ##
266
282
  #
267
- # Refresh the page
283
+ # Refresh the page.
268
284
  #
269
285
  def refresh
270
286
  raise_server_error!
@@ -289,8 +305,8 @@ module Capybara
289
305
 
290
306
  ##
291
307
  #
292
- # Executes the given block within the context of a node. `within` takes the
293
- # 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
294
310
  # block, any command to Capybara will be handled as though it were scoped
295
311
  # to the given element.
296
312
  #
@@ -298,18 +314,18 @@ module Capybara
298
314
  # fill_in('Street', with: '12 Main Street')
299
315
  # end
300
316
  #
301
- # Just as with `find`, if multiple elements match the selector given to
302
- # `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
303
319
  # behaviour can be controlled through the `:match` and `:exact` options.
304
320
  #
305
321
  # It is possible to omit the first parameter, in that case, the selector is
306
- # assumed to be of the type set in Capybara.default_selector.
322
+ # assumed to be of the type set in {Capybara.configure default_selector}.
307
323
  #
308
324
  # within('div#delivery-address') do
309
325
  # fill_in('Street', with: '12 Main Street')
310
326
  # end
311
327
  #
312
- # 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
313
329
  # chaining:
314
330
  #
315
331
  # find('div#delivery-address').fill_in('Street', with: '12 Main Street')
@@ -322,8 +338,8 @@ module Capybara
322
338
  #
323
339
  # @raise [Capybara::ElementNotFound] If the scope can't be found before time expires
324
340
  #
325
- def within(*args)
326
- 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)
327
343
  begin
328
344
  scopes.push(new_scope)
329
345
  yield if block_given?
@@ -355,18 +371,18 @@ module Capybara
355
371
 
356
372
  ##
357
373
  #
358
- # Switch to the given frame
374
+ # Switch to the given frame.
359
375
  #
360
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.
361
- # 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.
362
378
  # May not be supported by all drivers.
363
379
  #
364
380
  # @overload switch_to_frame(element)
365
- # @param [Capybara::Node::Element] iframe/frame element to switch to
366
- # @overload switch_to_frame(:parent)
367
- # Switch to the parent frame
368
- # @overload switch_to_frame(:top)
369
- # 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
370
386
  #
371
387
  def switch_to_frame(frame)
372
388
  case frame
@@ -407,8 +423,8 @@ module Capybara
407
423
  # @param [String] locator The locator for the given selector kind. For :frame this is the name/id of a frame/iframe element
408
424
  # @overload within_frame(index)
409
425
  # @param [Integer] index index of a frame (0 based)
410
- def within_frame(*args)
411
- switch_to_frame(_find_frame(*args))
426
+ def within_frame(*args, **kw_args)
427
+ switch_to_frame(_find_frame(*args, **kw_args))
412
428
  begin
413
429
  yield if block_given?
414
430
  ensure
@@ -437,22 +453,28 @@ module Capybara
437
453
  end
438
454
 
439
455
  ##
440
- # Open new window.
441
- # 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.
442
458
  # It should be switched to explicitly.
443
459
  #
444
460
  # @return [Capybara::Window] window that has been opened
445
461
  #
446
- def open_new_window
462
+ def open_new_window(kind = :tab)
447
463
  window_opened_by do
448
- driver.open_new_window
464
+ if driver.method(:open_new_window).arity.zero?
465
+ driver.open_new_window
466
+ else
467
+ driver.open_new_window(kind)
468
+ end
449
469
  end
450
470
  end
451
471
 
452
472
  ##
473
+ # Switch to the given window.
474
+ #
453
475
  # @overload switch_to_window(&block)
454
476
  # Switches to the first window for which given block returns a value other than false or nil.
455
- # 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.
456
478
  # @example
457
479
  # window = switch_to_window { title == 'Page title' }
458
480
  # @raise [Capybara::WindowError] if no window matches given block
@@ -461,19 +483,20 @@ module Capybara
461
483
  # @raise [Capybara::Driver::Base#no_such_window_error] if nonexistent (e.g. closed) window was passed
462
484
  #
463
485
  # @return [Capybara::Window] window that has been switched to
464
- # @raise [Capybara::ScopeError] if this method is invoked inside `within` or
465
- # `within_frame` methods
486
+ # @raise [Capybara::ScopeError] if this method is invoked inside {#within} or
487
+ # {#within_frame} methods
466
488
  # @raise [ArgumentError] if both or neither arguments were provided
467
489
  #
468
490
  def switch_to_window(window = nil, **options, &window_locator)
469
491
  raise ArgumentError, '`switch_to_window` can take either a block or a window, not both' if window && block_given?
470
492
  raise ArgumentError, '`switch_to_window`: either window or block should be provided' if !window && !block_given?
493
+
471
494
  unless scopes.last.nil?
472
495
  raise Capybara::ScopeError, '`switch_to_window` is not supposed to be invoked from '\
473
496
  '`within` or `within_frame` blocks.'
474
497
  end
475
498
 
476
- _switch_to_window(window, options, &window_locator)
499
+ _switch_to_window(window, **options, &window_locator)
477
500
  end
478
501
 
479
502
  ##
@@ -481,20 +504,20 @@ module Capybara
481
504
  #
482
505
  # 1. Switches to the given window (it can be located by window instance/lambda/string).
483
506
  # 2. Executes the given block (within window located at previous step).
484
- # 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).
485
508
  #
486
509
  # @overload within_window(window) { do_something }
487
- # @param window [Capybara::Window] instance of `Capybara::Window` class
510
+ # @param window [Capybara::Window] instance of {Capybara::Window} class
488
511
  # that will be switched to
489
512
  # @raise [driver#no_such_window_error] if nonexistent (e.g. closed) window was passed
490
513
  # @overload within_window(proc_or_lambda) { do_something }
491
- # @param lambda [Proc] lambda. First window for which lambda
514
+ # @param lambda [Proc] First window for which lambda
492
515
  # returns a value other than false or nil will be switched to.
493
516
  # @example
494
517
  # within_window(->{ page.title == 'Page title' }) { click_button 'Submit' }
495
518
  # @raise [Capybara::WindowError] if no window matching lambda was found
496
519
  #
497
- # @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
498
521
  # @return value returned by the block
499
522
  #
500
523
  def within_window(window_or_proc)
@@ -507,7 +530,7 @@ module Capybara
507
530
  when Proc
508
531
  _switch_to_window { window_or_proc.call }
509
532
  else
510
- 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'
511
534
  end
512
535
 
513
536
  begin
@@ -524,11 +547,11 @@ module Capybara
524
547
  # Get the window that has been opened by the passed block.
525
548
  # It will wait for it to be opened (in the same way as other Capybara methods wait).
526
549
  # It's better to use this method than `windows.last`
527
- # {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}.
528
551
  #
529
552
  # @overload window_opened_by(**options, &block)
530
553
  # @param options [Hash]
531
- # @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}
532
555
  # @return [Capybara::Window] the window that has been opened within a block
533
556
  # @raise [Capybara::WindowError] if block passed to window hasn't opened window
534
557
  # or opened more than one window
@@ -550,11 +573,11 @@ module Capybara
550
573
  ##
551
574
  #
552
575
  # Execute the given script, not returning a result. This is useful for scripts that return
553
- # complex objects, such as jQuery statements. +execute_script+ should be used over
554
- # +evaluate_script+ whenever possible.
576
+ # complex objects, such as jQuery statements. {#execute_script} should be used over
577
+ # {#evaluate_script} whenever possible.
555
578
  #
556
579
  # @param [String] script A string of JavaScript to execute
557
- # @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
558
581
  #
559
582
  def execute_script(script, *args)
560
583
  @touched = true
@@ -564,10 +587,11 @@ module Capybara
564
587
  ##
565
588
  #
566
589
  # Evaluate the given JavaScript and return the result. Be careful when using this with
567
- # 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
568
591
  # be a better alternative.
569
592
  #
570
593
  # @param [String] script A string of JavaScript to evaluate
594
+ # @param args Optional arguments that will be passed to the script
571
595
  # @return [Object] The result of the evaluated JavaScript (may be driver specific)
572
596
  #
573
597
  def evaluate_script(script, *args)
@@ -581,6 +605,7 @@ module Capybara
581
605
  # Evaluate the given JavaScript and obtain the result from a callback function which will be passed as the last argument to the script.
582
606
  #
583
607
  # @param [String] script A string of JavaScript to evaluate
608
+ # @param args Optional arguments that will be passed to the script
584
609
  # @return [Object] The result of the evaluated JavaScript (may be driver specific)
585
610
  #
586
611
  def evaluate_async_script(script, *args)
@@ -594,17 +619,17 @@ module Capybara
594
619
  # Execute the block, accepting a alert.
595
620
  #
596
621
  # @!macro modal_params
597
- # 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.
598
623
  # @example
599
624
  # $0 do
600
625
  # click_link('link that triggers appearance of system modal')
601
626
  # end
602
627
  # @overload $0(text, **options, &blk)
603
- # @param text [String, Regexp] Text or regex to match against the text in the modal. If not provided any modal is matched
604
- # @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}.
605
630
  # @yield Block whose actions will trigger the system modal
606
631
  # @overload $0(**options, &blk)
607
- # @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}.
608
633
  # @yield Block whose actions will trigger the system modal
609
634
  # @return [String] the message shown in the modal
610
635
  # @raise [Capybara::ModalNotFound] if modal dialog hasn't been found
@@ -656,12 +681,12 @@ module Capybara
656
681
 
657
682
  ##
658
683
  #
659
- # Save a snapshot of the page. If `Capybara.asset_host` is set it will inject `base` tag
660
- # 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}.
661
686
  #
662
- # If invoked without arguments it will save file to `Capybara.save_path`
663
- # and file will be given randomly generated filename. If invoked with a relative path
664
- # 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}.
665
690
  #
666
691
  # @param [String] path the path to where it should be saved
667
692
  # @return [String] the path to which the file was saved
@@ -676,9 +701,9 @@ module Capybara
676
701
  #
677
702
  # Save a snapshot of the page and open it in a browser for inspection.
678
703
  #
679
- # If invoked without arguments it will save file to `Capybara.save_path`
680
- # and file will be given randomly generated filename. If invoked with a relative path
681
- # 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}.
682
707
  #
683
708
  # @param [String] path the path to where it should be saved
684
709
  #
@@ -690,32 +715,30 @@ module Capybara
690
715
  #
691
716
  # Save a screenshot of page.
692
717
  #
693
- # If invoked without arguments it will save file to `Capybara.save_path`
694
- # and file will be given randomly generated filename. If invoked with a relative path
695
- # 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}.
696
721
  #
697
722
  # @param [String] path the path to where it should be saved
698
723
  # @param [Hash] options a customizable set of options
699
724
  # @return [String] the path to which the file was saved
700
725
  def save_screenshot(path = nil, **options)
701
- 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) }
702
727
  end
703
728
 
704
729
  ##
705
730
  #
706
731
  # Save a screenshot of the page and open it for inspection.
707
732
  #
708
- # If invoked without arguments it will save file to `Capybara.save_path`
709
- # and file will be given randomly generated filename. If invoked with a relative path
710
- # 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}.
711
736
  #
712
737
  # @param [String] path the path to where it should be saved
713
738
  # @param [Hash] options a customizable set of options
714
739
  #
715
740
  def save_and_open_screenshot(path = nil, **options)
716
- # rubocop:disable Lint/Debugger
717
- save_screenshot(path, options).tap { |s_path| open_file(s_path) }
718
- # rubocop:enable Lint/Debugger
741
+ save_screenshot(path, **options).tap { |s_path| open_file(s_path) } # rubocop:disable Lint/Debugger
719
742
  end
720
743
 
721
744
  def document
@@ -723,15 +746,32 @@ module Capybara
723
746
  end
724
747
 
725
748
  NODE_METHODS.each do |method|
726
- define_method method do |*args, &block|
727
- @touched = true
728
- 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
729
761
  end
730
762
  end
731
763
 
732
764
  DOCUMENT_METHODS.each do |method|
733
- define_method method do |*args, &block|
734
- 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
735
775
  end
736
776
  end
737
777
 
@@ -746,7 +786,7 @@ module Capybara
746
786
 
747
787
  ##
748
788
  #
749
- # Yield a block using a specific wait time
789
+ # Yield a block using a specific maximum wait time.
750
790
  #
751
791
  def using_wait_time(seconds)
752
792
  if Capybara.threadsafe
@@ -764,11 +804,12 @@ module Capybara
764
804
 
765
805
  ##
766
806
  #
767
- # Accepts a block to set the configuration options if Capybara.threadsafe == true. Note that some options only have an effect
768
- # 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.
769
809
  #
770
810
  def configure
771
811
  raise 'Session configuration is only supported when Capybara.threadsafe == true' unless Capybara.threadsafe
812
+
772
813
  yield config
773
814
  end
774
815
 
@@ -784,20 +825,24 @@ module Capybara
784
825
  end
785
826
  end
786
827
 
828
+ def server_url
829
+ @server&.base_url
830
+ end
831
+
787
832
  private
788
833
 
789
- @@instance_created = false
834
+ @@instance_created = false # rubocop:disable Style/ClassVars
790
835
 
791
836
  def driver_args(args)
792
837
  args.map { |arg| arg.is_a?(Capybara::Node::Element) ? arg.base : arg }
793
838
  end
794
839
 
795
840
  def accept_modal(type, text_or_options, options, &blk)
796
- driver.accept_modal(type, modal_options(text_or_options, options), &blk)
841
+ driver.accept_modal(type, **modal_options(text_or_options, **options), &blk)
797
842
  end
798
843
 
799
844
  def dismiss_modal(type, text_or_options, options, &blk)
800
- driver.dismiss_modal(type, modal_options(text_or_options, options), &blk)
845
+ driver.dismiss_modal(type, **modal_options(text_or_options, **options), &blk)
801
846
  end
802
847
 
803
848
  def modal_options(text = nil, **options)
@@ -814,7 +859,9 @@ module Capybara
814
859
  end
815
860
 
816
861
  def prepare_path(path, extension)
817
- File.expand_path(path || default_fn(extension), config.save_path).tap { |p_path| FileUtils.mkdir_p(File.dirname(p_path)) }
862
+ File.expand_path(path || default_fn(extension), config.save_path).tap do |p_path|
863
+ FileUtils.mkdir_p(File.dirname(p_path))
864
+ end
818
865
  end
819
866
 
820
867
  def default_fn(extension)
@@ -831,7 +878,7 @@ module Capybara
831
878
  when Array
832
879
  arg.map { |subarg| element_script_result(subarg) }
833
880
  when Hash
834
- arg.each { |key, value| arg[key] = element_script_result(value) }
881
+ arg.transform_values! { |value| element_script_result(value) }
835
882
  when Capybara::Driver::Node
836
883
  Capybara::Node::Element.new(self, arg, nil, nil)
837
884
  else
@@ -839,24 +886,18 @@ module Capybara
839
886
  end
840
887
  end
841
888
 
842
- def server_url
843
- "http#{'s' if @server.using_ssl?}://#{@server.host}:#{@server.port}" if @server
844
- end
845
-
846
889
  def adjust_server_port(uri)
847
890
  uri.port ||= @server.port if @server && config.always_include_port
848
891
  end
849
892
 
850
- def _find_frame(*args)
851
- return find(:frame) if args.length.zero?
852
-
893
+ def _find_frame(*args, **kw_args)
853
894
  case args[0]
854
895
  when Capybara::Node::Element
855
896
  args[0]
856
- when String, Hash
857
- find(:frame, *args)
897
+ when String, nil
898
+ find(:frame, *args, **kw_args)
858
899
  when Symbol
859
- find(*args)
900
+ find(*args, **kw_args)
860
901
  when Integer
861
902
  idx = args[0]
862
903
  all(:frame, minimum: idx + 1)[idx]
@@ -0,0 +1,6 @@
1
+ $(function() {
2
+ $(document).on('click dblclick contextmenu', function(e){
3
+ e.preventDefault();
4
+ $(document.body).append('<div id="has-been-clicked">Has been clicked at ' + e.clientX + ',' + e.clientY + '</div>');
5
+ })
6
+ })