capybara 2.15.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. checksums.yaml +5 -5
  2. data/History.md +137 -2
  3. data/README.md +36 -25
  4. data/lib/capybara/config.rb +11 -57
  5. data/lib/capybara/cucumber.rb +2 -3
  6. data/lib/capybara/driver/base.rb +19 -16
  7. data/lib/capybara/driver/node.rb +5 -4
  8. data/lib/capybara/dsl.rb +1 -0
  9. data/lib/capybara/helpers.rb +19 -29
  10. data/lib/capybara/minitest/spec.rb +16 -13
  11. data/lib/capybara/minitest.rb +140 -137
  12. data/lib/capybara/node/actions.rb +68 -89
  13. data/lib/capybara/node/base.rb +11 -18
  14. data/lib/capybara/node/document.rb +2 -2
  15. data/lib/capybara/node/document_matchers.rb +8 -8
  16. data/lib/capybara/node/element.rb +32 -42
  17. data/lib/capybara/node/finders.rb +64 -71
  18. data/lib/capybara/node/matchers.rb +50 -71
  19. data/lib/capybara/node/simple.rb +11 -17
  20. data/lib/capybara/queries/ancestor_query.rb +12 -8
  21. data/lib/capybara/queries/base_query.rb +22 -18
  22. data/lib/capybara/queries/current_path_query.rb +12 -25
  23. data/lib/capybara/queries/match_query.rb +3 -7
  24. data/lib/capybara/queries/selector_query.rb +100 -96
  25. data/lib/capybara/queries/sibling_query.rb +5 -5
  26. data/lib/capybara/queries/text_query.rb +35 -35
  27. data/lib/capybara/queries/title_query.rb +8 -11
  28. data/lib/capybara/rack_test/browser.rb +15 -18
  29. data/lib/capybara/rack_test/css_handlers.rb +6 -4
  30. data/lib/capybara/rack_test/driver.rb +6 -10
  31. data/lib/capybara/rack_test/form.rb +52 -39
  32. data/lib/capybara/rack_test/node.rb +93 -63
  33. data/lib/capybara/rails.rb +2 -6
  34. data/lib/capybara/result.rb +22 -22
  35. data/lib/capybara/rspec/compound.rb +5 -10
  36. data/lib/capybara/rspec/features.rb +17 -48
  37. data/lib/capybara/rspec/matcher_proxies.rb +31 -15
  38. data/lib/capybara/rspec/matchers.rb +116 -58
  39. data/lib/capybara/rspec.rb +5 -10
  40. data/lib/capybara/selector/css.rb +6 -11
  41. data/lib/capybara/selector/filter.rb +1 -17
  42. data/lib/capybara/selector/filter_set.rb +18 -15
  43. data/lib/capybara/selector/filters/base.rb +7 -6
  44. data/lib/capybara/selector/filters/expression_filter.rb +6 -23
  45. data/lib/capybara/selector/filters/node_filter.rb +2 -12
  46. data/lib/capybara/selector/selector.rb +28 -34
  47. data/lib/capybara/selector.rb +129 -117
  48. data/lib/capybara/selenium/driver.rb +172 -163
  49. data/lib/capybara/selenium/node.rb +218 -104
  50. data/lib/capybara/server.rb +3 -2
  51. data/lib/capybara/session/config.rb +47 -59
  52. data/lib/capybara/session/matchers.rb +23 -14
  53. data/lib/capybara/session.rb +175 -229
  54. data/lib/capybara/spec/fixtures/no_extension +1 -0
  55. data/lib/capybara/spec/public/test.js +38 -6
  56. data/lib/capybara/spec/session/accept_alert_spec.rb +1 -0
  57. data/lib/capybara/spec/session/accept_confirm_spec.rb +3 -2
  58. data/lib/capybara/spec/session/accept_prompt_spec.rb +30 -1
  59. data/lib/capybara/spec/session/all_spec.rb +31 -18
  60. data/lib/capybara/spec/session/ancestor_spec.rb +6 -8
  61. data/lib/capybara/spec/session/assert_all_of_selectors_spec.rb +6 -5
  62. data/lib/capybara/spec/session/assert_current_path.rb +12 -11
  63. data/lib/capybara/spec/session/assert_selector.rb +1 -0
  64. data/lib/capybara/spec/session/assert_text.rb +31 -23
  65. data/lib/capybara/spec/session/assert_title.rb +13 -3
  66. data/lib/capybara/spec/session/attach_file_spec.rb +57 -29
  67. data/lib/capybara/spec/session/body_spec.rb +1 -0
  68. data/lib/capybara/spec/session/check_spec.rb +7 -6
  69. data/lib/capybara/spec/session/choose_spec.rb +5 -4
  70. data/lib/capybara/spec/session/click_button_spec.rb +24 -32
  71. data/lib/capybara/spec/session/click_link_or_button_spec.rb +8 -7
  72. data/lib/capybara/spec/session/click_link_spec.rb +8 -7
  73. data/lib/capybara/spec/session/current_scope_spec.rb +4 -3
  74. data/lib/capybara/spec/session/current_url_spec.rb +19 -8
  75. data/lib/capybara/spec/session/dismiss_confirm_spec.rb +1 -1
  76. data/lib/capybara/spec/session/dismiss_prompt_spec.rb +1 -0
  77. data/lib/capybara/spec/session/element/assert_match_selector.rb +1 -1
  78. data/lib/capybara/spec/session/element/match_xpath_spec.rb +1 -1
  79. data/lib/capybara/spec/session/element/matches_selector_spec.rb +5 -5
  80. data/lib/capybara/spec/session/evaluate_async_script_spec.rb +23 -0
  81. data/lib/capybara/spec/session/evaluate_script_spec.rb +5 -4
  82. data/lib/capybara/spec/session/execute_script_spec.rb +4 -3
  83. data/lib/capybara/spec/session/fill_in_spec.rb +30 -5
  84. data/lib/capybara/spec/session/find_button_spec.rb +4 -3
  85. data/lib/capybara/spec/session/find_by_id_spec.rb +2 -1
  86. data/lib/capybara/spec/session/find_field_spec.rb +9 -15
  87. data/lib/capybara/spec/session/find_link_spec.rb +6 -5
  88. data/lib/capybara/spec/session/find_spec.rb +37 -31
  89. data/lib/capybara/spec/session/first_spec.rb +60 -33
  90. data/lib/capybara/spec/session/frame/frame_title_spec.rb +23 -0
  91. data/lib/capybara/spec/session/frame/frame_url_spec.rb +23 -0
  92. data/lib/capybara/spec/session/frame/switch_to_frame_spec.rb +2 -1
  93. data/lib/capybara/spec/session/frame/within_frame_spec.rb +9 -16
  94. data/lib/capybara/spec/session/go_back_spec.rb +1 -0
  95. data/lib/capybara/spec/session/go_forward_spec.rb +1 -0
  96. data/lib/capybara/spec/session/has_all_selectors_spec.rb +69 -0
  97. data/lib/capybara/spec/session/has_button_spec.rb +2 -1
  98. data/lib/capybara/spec/session/has_css_spec.rb +3 -2
  99. data/lib/capybara/spec/session/has_current_path_spec.rb +49 -22
  100. data/lib/capybara/spec/session/has_field_spec.rb +4 -3
  101. data/lib/capybara/spec/session/has_link_spec.rb +5 -4
  102. data/lib/capybara/spec/session/has_none_selectors_spec.rb +76 -0
  103. data/lib/capybara/spec/session/has_select_spec.rb +32 -31
  104. data/lib/capybara/spec/session/has_selector_spec.rb +5 -4
  105. data/lib/capybara/spec/session/has_table_spec.rb +2 -1
  106. data/lib/capybara/spec/session/has_text_spec.rb +9 -13
  107. data/lib/capybara/spec/session/has_title_spec.rb +1 -0
  108. data/lib/capybara/spec/session/has_xpath_spec.rb +1 -0
  109. data/lib/capybara/spec/session/headers.rb +2 -1
  110. data/lib/capybara/spec/session/html_spec.rb +1 -0
  111. data/lib/capybara/spec/session/node_spec.rb +107 -58
  112. data/lib/capybara/spec/session/node_wrapper_spec.rb +36 -0
  113. data/lib/capybara/spec/session/refresh_spec.rb +6 -2
  114. data/lib/capybara/spec/session/reset_session_spec.rb +19 -0
  115. data/lib/capybara/spec/session/response_code.rb +1 -0
  116. data/lib/capybara/spec/session/save_and_open_page_spec.rb +1 -0
  117. data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +6 -11
  118. data/lib/capybara/spec/session/save_page_spec.rb +1 -17
  119. data/lib/capybara/spec/session/save_screenshot_spec.rb +3 -3
  120. data/lib/capybara/spec/session/select_spec.rb +21 -20
  121. data/lib/capybara/spec/session/selectors_spec.rb +2 -2
  122. data/lib/capybara/spec/session/sibling_spec.rb +1 -1
  123. data/lib/capybara/spec/session/text_spec.rb +17 -3
  124. data/lib/capybara/spec/session/title_spec.rb +11 -1
  125. data/lib/capybara/spec/session/uncheck_spec.rb +4 -3
  126. data/lib/capybara/spec/session/unselect_spec.rb +7 -6
  127. data/lib/capybara/spec/session/visit_spec.rb +64 -3
  128. data/lib/capybara/spec/session/window/become_closed_spec.rb +2 -1
  129. data/lib/capybara/spec/session/window/current_window_spec.rb +1 -0
  130. data/lib/capybara/spec/session/window/open_new_window_spec.rb +1 -0
  131. data/lib/capybara/spec/session/window/switch_to_window_spec.rb +2 -1
  132. data/lib/capybara/spec/session/window/window_opened_by_spec.rb +2 -1
  133. data/lib/capybara/spec/session/window/window_spec.rb +12 -12
  134. data/lib/capybara/spec/session/window/windows_spec.rb +2 -3
  135. data/lib/capybara/spec/session/window/within_window_spec.rb +15 -71
  136. data/lib/capybara/spec/session/within_spec.rb +1 -0
  137. data/lib/capybara/spec/spec_helper.rb +36 -18
  138. data/lib/capybara/spec/test_app.rb +17 -9
  139. data/lib/capybara/spec/views/form.erb +7 -0
  140. data/lib/capybara/spec/views/initial_alert.erb +10 -0
  141. data/lib/capybara/spec/views/with_fixed_header_footer.erb +17 -0
  142. data/lib/capybara/spec/views/with_hover.erb +5 -0
  143. data/lib/capybara/spec/views/with_html.erb +27 -1
  144. data/lib/capybara/spec/views/with_js.erb +11 -0
  145. data/lib/capybara/spec/views/within_frames.erb +4 -1
  146. data/lib/capybara/version.rb +2 -1
  147. data/lib/capybara/window.rb +6 -10
  148. data/lib/capybara.rb +29 -26
  149. data/spec/basic_node_spec.rb +1 -0
  150. data/spec/capybara_spec.rb +16 -69
  151. data/spec/dsl_spec.rb +5 -13
  152. data/spec/filter_set_spec.rb +5 -4
  153. data/spec/fixtures/selenium_driver_rspec_failure.rb +2 -1
  154. data/spec/fixtures/selenium_driver_rspec_success.rb +3 -2
  155. data/spec/minitest_spec.rb +13 -4
  156. data/spec/minitest_spec_spec.rb +12 -3
  157. data/spec/per_session_config_spec.rb +9 -8
  158. data/spec/rack_test_spec.rb +21 -20
  159. data/spec/result_spec.rb +17 -16
  160. data/spec/rspec/features_spec.rb +17 -14
  161. data/spec/rspec/scenarios_spec.rb +5 -7
  162. data/spec/rspec/shared_spec_matchers.rb +96 -99
  163. data/spec/rspec/views_spec.rb +2 -1
  164. data/spec/rspec_matchers_spec.rb +18 -2
  165. data/spec/rspec_spec.rb +11 -15
  166. data/spec/selector_spec.rb +5 -6
  167. data/spec/selenium_spec_chrome.rb +20 -11
  168. data/spec/selenium_spec_edge.rb +27 -0
  169. data/spec/selenium_spec_ie.rb +31 -0
  170. data/spec/selenium_spec_marionette.rb +38 -12
  171. data/spec/server_spec.rb +33 -33
  172. data/spec/session_spec.rb +2 -1
  173. data/spec/shared_selenium_session.rb +82 -22
  174. data/spec/spec_helper.rb +3 -6
  175. metadata +76 -81
  176. data/lib/capybara/query.rb +0 -7
  177. data/spec/selenium_spec_firefox.rb +0 -68
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'capybara/session/matchers'
3
4
  require 'addressable/uri'
4
5
 
5
6
  module Capybara
6
-
7
7
  ##
8
8
  #
9
9
  # The Session class represents a single user's interaction with the system. The Session can use
@@ -38,64 +38,63 @@ module Capybara
38
38
  class Session
39
39
  include Capybara::SessionMatchers
40
40
 
41
- NODE_METHODS = [
42
- :all, :first, :attach_file, :text, :check, :choose,
43
- :click_link_or_button, :click_button, :click_link, :field_labeled,
44
- :fill_in, :find, :find_all, :find_button, :find_by_id, :find_field, :find_link,
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,
47
- :has_link?, :has_no_link?, :has_button?, :has_no_button?, :has_field?,
48
- :has_no_field?, :has_checked_field?, :has_unchecked_field?,
49
- :has_no_table?, :has_table?, :unselect, :has_select?, :has_no_select?,
50
- :has_selector?, :has_no_selector?, :click_on, :has_no_checked_field?,
51
- :has_no_unchecked_field?, :query, :assert_selector, :assert_no_selector,
52
- :assert_all_of_selectors, :assert_none_of_selectors,
53
- :refute_selector, :assert_text, :assert_no_text
54
- ]
41
+ NODE_METHODS = %i[
42
+ all first attach_file text check choose
43
+ click_link_or_button click_button click_link
44
+ fill_in find find_all find_button find_by_id find_field find_link
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
47
+ has_link? has_no_link? has_button? has_no_button? has_field?
48
+ has_no_field? has_checked_field? has_unchecked_field?
49
+ has_no_table? has_table? unselect has_select? has_no_select?
50
+ has_selector? has_no_selector? click_on has_no_checked_field?
51
+ has_no_unchecked_field? query assert_selector assert_no_selector
52
+ assert_all_of_selectors assert_none_of_selectors
53
+ refute_selector assert_text assert_no_text
54
+ ].freeze
55
55
  # @api private
56
- DOCUMENT_METHODS = [
57
- :title, :assert_title, :assert_no_title, :has_title?, :has_no_title?
58
- ]
59
- SESSION_METHODS = [
60
- :body, :html, :source, :current_url, :current_host, :current_path,
61
- :execute_script, :evaluate_script, :visit, :refresh, :go_back, :go_forward,
62
- :within, :within_element, :within_fieldset, :within_table, :within_frame, :switch_to_frame,
63
- :current_window, :windows, :open_new_window, :switch_to_window, :within_window, :window_opened_by,
64
- :save_page, :save_and_open_page, :save_screenshot,
65
- :save_and_open_screenshot, :reset_session!, :response_headers,
66
- :status_code, :current_scope,
67
- :assert_current_path, :assert_no_current_path, :has_current_path?, :has_no_current_path?
68
- ] + DOCUMENT_METHODS
69
- MODAL_METHODS = [
70
- :accept_alert, :accept_confirm, :dismiss_confirm, :accept_prompt,
71
- :dismiss_prompt
72
- ]
56
+ DOCUMENT_METHODS = %i[
57
+ title assert_title assert_no_title has_title? has_no_title?
58
+ ].freeze
59
+ SESSION_METHODS = %i[
60
+ body html source current_url current_host current_path
61
+ execute_script evaluate_script visit refresh go_back go_forward
62
+ within within_element within_fieldset within_table within_frame switch_to_frame
63
+ current_window windows open_new_window switch_to_window within_window window_opened_by
64
+ save_page save_and_open_page save_screenshot
65
+ save_and_open_screenshot reset_session! response_headers
66
+ status_code current_scope
67
+ assert_current_path assert_no_current_path has_current_path? has_no_current_path?
68
+ ].freeze + DOCUMENT_METHODS
69
+ MODAL_METHODS = %i[
70
+ accept_alert accept_confirm dismiss_confirm accept_prompt dismiss_prompt
71
+ ].freeze
73
72
  DSL_METHODS = NODE_METHODS + SESSION_METHODS + MODAL_METHODS
74
73
 
75
74
  attr_reader :mode, :app, :server
76
75
  attr_accessor :synchronized
77
76
 
78
- def initialize(mode, app=nil)
77
+ def initialize(mode, app = nil)
79
78
  raise TypeError, "The second parameter to Session::new should be a rack app if passed." if app && !app.respond_to?(:call)
80
79
  @@instance_created = true
81
80
  @mode = mode
82
81
  @app = app
83
82
  if block_given?
84
83
  raise "A configuration block is only accepted when Capybara.threadsafe == true" unless Capybara.threadsafe
85
- yield config if block_given?
84
+ yield config
86
85
  end
87
- if config.run_server and @app and driver.needs_server?
88
- @server = Capybara::Server.new(@app, config.server_port, config.server_host, config.server_errors).boot
86
+ @server = if config.run_server and @app and driver.needs_server?
87
+ Capybara::Server.new(@app, config.server_port, config.server_host, config.server_errors).boot
89
88
  else
90
- @server = nil
89
+ nil
91
90
  end
92
91
  @touched = false
93
92
  end
94
93
 
95
94
  def driver
96
95
  @driver ||= begin
97
- unless Capybara.drivers.has_key?(mode)
98
- other_drivers = Capybara.drivers.keys.map { |key| key.inspect }
96
+ unless Capybara.drivers.key?(mode)
97
+ other_drivers = Capybara.drivers.keys.map(&:inspect)
99
98
  raise Capybara::DriverNotFoundError, "no driver called #{mode.inspect} was found, available drivers: #{other_drivers.join(', ')}"
100
99
  end
101
100
  driver = Capybara.drivers[mode].call(app)
@@ -145,7 +144,7 @@ module Capybara
145
144
  raise CapybaraError, "Your application server raised an error - It has been raised in your test code because Capybara.raise_server_errors == true"
146
145
  end
147
146
  rescue CapybaraError
148
- #needed to get the cause set correctly in JRuby -- otherwise we could just do raise @server.error
147
+ # needed to get the cause set correctly in JRuby -- otherwise we could just do raise @server.error
149
148
  raise @server.error, @server.error.message, @server.error.backtrace
150
149
  ensure
151
150
  @server.reset_error!
@@ -196,8 +195,9 @@ module Capybara
196
195
 
197
196
  # Addressable doesn't support opaque URIs - we want nil here
198
197
  return nil if uri.scheme == "about"
198
+
199
199
  path = uri.path
200
- path if path and not path.empty?
200
+ path if path && !path.empty?
201
201
  end
202
202
 
203
203
  ##
@@ -247,24 +247,29 @@ module Capybara
247
247
  raise_server_error!
248
248
  @touched = true
249
249
 
250
- visit_uri = URI.parse(visit_uri.to_s)
250
+ visit_uri = ::Addressable::URI.parse(visit_uri.to_s)
251
251
 
252
252
  uri_base = if @server
253
- visit_uri.port = @server.port if config.always_include_port && (visit_uri.port == visit_uri.default_port)
254
- URI.parse(config.app_host || "http://#{@server.host}:#{@server.port}")
253
+ ::Addressable::URI.parse(config.app_host || "http://#{@server.host}:#{@server.port}")
255
254
  else
256
- config.app_host && URI.parse(config.app_host)
255
+ config.app_host && ::Addressable::URI.parse(config.app_host)
257
256
  end
258
257
 
259
- # TODO - this is only for compatability with previous 2.x behavior that concatenated
260
- # Capybara.app_host and a "relative" path - Consider removing in 3.0
261
- # @abotalov brought up a good point about this behavior potentially being useful to people
262
- # deploying to a subdirectory and/or single page apps where only the url fragment changes
263
- if visit_uri.scheme.nil? && uri_base
264
- visit_uri.path = uri_base.path + visit_uri.path
265
- end
258
+ if uri_base && [nil, 'http', 'https'].include?(visit_uri.scheme)
259
+ if visit_uri.relative?
260
+ uri_base.port ||= @server.port if @server && config.always_include_port
261
+
262
+ visit_uri_parts = visit_uri.to_hash.delete_if { |_k, v| v.nil? }
266
263
 
267
- visit_uri = uri_base.merge(visit_uri) unless uri_base.nil?
264
+ # Useful to people deploying to a subdirectory
265
+ # and/or single page apps where only the url fragment changes
266
+ visit_uri_parts[:path] = uri_base.path + visit_uri.path
267
+
268
+ visit_uri = uri_base.merge(visit_uri_parts)
269
+ elsif @server && config.always_include_port
270
+ visit_uri.port ||= @server.port
271
+ end
272
+ end
268
273
 
269
274
  driver.visit(visit_uri.to_s)
270
275
  end
@@ -330,7 +335,7 @@ module Capybara
330
335
  # @raise [Capybara::ElementNotFound] If the scope can't be found before time expires
331
336
  #
332
337
  def within(*args)
333
- new_scope = if args.first.is_a?(Capybara::Node::Base) then args.first else find(*args) end
338
+ new_scope = args.first.respond_to?(:to_capybara_node) ? args.first.to_capybara_node : find(*args)
334
339
  begin
335
340
  scopes.push(new_scope)
336
341
  yield
@@ -347,9 +352,7 @@ module Capybara
347
352
  # @param [String] locator Id or legend of the fieldset
348
353
  #
349
354
  def within_fieldset(locator)
350
- within :fieldset, locator do
351
- yield
352
- end
355
+ within(:fieldset, locator) { yield }
353
356
  end
354
357
 
355
358
  ##
@@ -359,9 +362,7 @@ module Capybara
359
362
  # @param [String] locator Id or caption of the table
360
363
  #
361
364
  def within_table(locator)
362
- within :table, locator do
363
- yield
364
- end
365
+ within(:table, locator) { yield }
365
366
  end
366
367
 
367
368
  ##
@@ -385,15 +386,19 @@ module Capybara
385
386
  driver.switch_to_frame(frame)
386
387
  scopes.push(:frame)
387
388
  when :parent
388
- raise Capybara::ScopeError, "`switch_to_frame(:parent)` cannot be called from inside a descendant frame's "\
389
- "`within` block." if scopes.last() != :frame
389
+ if scopes.last != :frame
390
+ raise Capybara::ScopeError, "`switch_to_frame(:parent)` cannot be called from inside a descendant frame's "\
391
+ "`within` block."
392
+ end
390
393
  scopes.pop
391
394
  driver.switch_to_frame(:parent)
392
395
  when :top
393
396
  idx = scopes.index(:frame)
394
397
  if idx
395
- raise Capybara::ScopeError, "`switch_to_frame(:top)` cannot be called from inside a descendant frame's "\
396
- "`within` block." if scopes.slice(idx..-1).any? {|scope| ![:frame, nil].include?(scope)}
398
+ if scopes.slice(idx..-1).any? { |scope| ![:frame, nil].include?(scope) }
399
+ raise Capybara::ScopeError, "`switch_to_frame(:top)` cannot be called from inside a descendant frame's "\
400
+ "`within` block."
401
+ end
397
402
  scopes.slice!(idx..-1)
398
403
  driver.switch_to_frame(:top)
399
404
  end
@@ -408,34 +413,16 @@ module Capybara
408
413
  # @overload within_frame(element)
409
414
  # @param [Capybara::Node::Element] frame element
410
415
  # @overload within_frame([kind = :frame], locator, options = {})
411
- # @param [Symobl] kind Optional selector type (:css, :xpath, :field, etc.) - Defaults to :frame
416
+ # @param [Symbol] kind Optional selector type (:css, :xpath, :field, etc.) - Defaults to :frame
412
417
  # @param [String] locator The locator for the given selector kind. For :frame this is the name/id of a frame/iframe element
413
418
  # @overload within_frame(index)
414
419
  # @param [Integer] index index of a frame (0 based)
415
420
  def within_frame(*args)
416
- frame = _find_frame(*args)
417
-
421
+ switch_to_frame(_find_frame(*args))
418
422
  begin
419
- switch_to_frame(frame)
420
- begin
421
- yield
422
- ensure
423
- switch_to_frame(:parent)
424
- end
425
- rescue Capybara::NotSupportedByDriverError
426
- # Support older driver frame API for now
427
- if driver.respond_to?(:within_frame)
428
- begin
429
- scopes.push(:frame)
430
- driver.within_frame(frame) do
431
- yield
432
- end
433
- ensure
434
- scopes.pop
435
- end
436
- else
437
- raise
438
- end
423
+ yield
424
+ ensure
425
+ switch_to_frame(:parent)
439
426
  end
440
427
  end
441
428
 
@@ -481,22 +468,18 @@ module Capybara
481
468
  # @raise [Capybara::WindowError] if no window matches given block
482
469
  # @overload switch_to_window(window)
483
470
  # @param window [Capybara::Window] window that should be switched to
484
- # @raise [Capybara::Driver::Base#no_such_window_error] if non-existent (e.g. closed) window was passed
471
+ # @raise [Capybara::Driver::Base#no_such_window_error] if nonexistent (e.g. closed) window was passed
485
472
  #
486
473
  # @return [Capybara::Window] window that has been switched to
487
474
  # @raise [Capybara::ScopeError] if this method is invoked inside `within` or
488
475
  # `within_frame` methods
489
476
  # @raise [ArgumentError] if both or neither arguments were provided
490
477
  #
491
- def switch_to_window(window = nil, options= {}, &window_locator)
492
- options, window = window, nil if window.is_a? Hash
493
-
478
+ def switch_to_window(window = nil, **options, &window_locator)
494
479
  block_given = block_given?
495
- if window && block_given
496
- raise ArgumentError, "`switch_to_window` can take either a block or a window, not both"
497
- elsif !window && !block_given
498
- raise ArgumentError, "`switch_to_window`: either window or block should be provided"
499
- elsif !scopes.last.nil?
480
+ raise ArgumentError, "`switch_to_window` can take either a block or a window, not both" if window && block_given
481
+ raise ArgumentError, "`switch_to_window`: either window or block should be provided" if !window && !block_given
482
+ unless scopes.last.nil?
500
483
  raise Capybara::ScopeError, "`switch_to_window` is not supposed to be invoked from "\
501
484
  "`within` or `within_frame` blocks."
502
485
  end
@@ -514,58 +497,37 @@ module Capybara
514
497
  # @overload within_window(window) { do_something }
515
498
  # @param window [Capybara::Window] instance of `Capybara::Window` class
516
499
  # that will be switched to
517
- # @raise [driver#no_such_window_error] if unexistent (e.g. closed) window was passed
500
+ # @raise [driver#no_such_window_error] if nonexistent (e.g. closed) window was passed
518
501
  # @overload within_window(proc_or_lambda) { do_something }
519
502
  # @param lambda [Proc] lambda. First window for which lambda
520
503
  # returns a value other than false or nil will be switched to.
521
504
  # @example
522
505
  # within_window(->{ page.title == 'Page title' }) { click_button 'Submit' }
523
506
  # @raise [Capybara::WindowError] if no window matching lambda was found
524
- # @overload within_window(string) { do_something }
525
- # @deprecated Pass window or lambda instead
526
- # @param [String] handle, name, url or title of the window
527
507
  #
528
508
  # @raise [Capybara::ScopeError] if this method is invoked inside `within_frame` method
529
509
  # @return value returned by the block
530
510
  #
531
- def within_window(window_or_handle)
532
- if window_or_handle.instance_of?(Capybara::Window)
533
- original = current_window
534
- scopes << nil
535
- begin
536
- _switch_to_window(window_or_handle) unless original == window_or_handle
537
- begin
538
- yield
539
- ensure
540
- _switch_to_window(original) unless original == window_or_handle
541
- end
542
- ensure
543
- scopes.pop
544
- end
545
- elsif window_or_handle.is_a?(Proc)
546
- original = current_window
547
- scopes << nil
548
- begin
549
- _switch_to_window { window_or_handle.call }
550
- begin
551
- yield
552
- ensure
553
- _switch_to_window(original)
554
- end
555
- ensure
556
- scopes.pop
511
+ def within_window(window_or_proc)
512
+ original = current_window
513
+ scopes << nil
514
+ begin
515
+ case window_or_proc
516
+ when Capybara::Window
517
+ _switch_to_window(window_or_proc) unless original == window_or_proc
518
+ when Proc
519
+ _switch_to_window { window_or_proc.call }
520
+ else
521
+ raise ArgumentError("`#within_window` requires a `Capybara::Window` instance or a lambda")
557
522
  end
558
- else
559
- offending_line = caller.first
560
- file_line = offending_line.match(/^(.+?):(\d+)/)[0]
561
- warn "DEPRECATION WARNING: Passing string argument to #within_window is deprecated. "\
562
- "Pass window object or lambda. (called from #{file_line})"
523
+
563
524
  begin
564
- scopes << nil
565
- driver.within_window(window_or_handle) { yield }
525
+ yield
566
526
  ensure
567
- scopes.pop
527
+ _switch_to_window(original) unless original == window_or_proc
568
528
  end
529
+ ensure
530
+ scopes.pop
569
531
  end
570
532
  end
571
533
 
@@ -575,15 +537,16 @@ module Capybara
575
537
  # It's better to use this method than `windows.last`
576
538
  # {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}
577
539
  #
578
- # @param options [Hash]
579
- # @option options [Numeric] :wait (Capybara.default_max_wait_time) maximum wait time
580
- # @return [Capybara::Window] the window that has been opened within a block
581
- # @raise [Capybara::WindowError] if block passed to window hasn't opened window
582
- # or opened more than one window
540
+ # @overload window_opened_by(**options, &block)
541
+ # @param options [Hash]
542
+ # @option options [Numeric] :wait (Capybara.default_max_wait_time) maximum wait time
543
+ # @return [Capybara::Window] the window that has been opened within a block
544
+ # @raise [Capybara::WindowError] if block passed to window hasn't opened window
545
+ # or opened more than one window
583
546
  #
584
- def window_opened_by(options = {}, &block)
547
+ def window_opened_by(**options)
585
548
  old_handles = driver.window_handles
586
- block.call
549
+ yield
587
550
 
588
551
  wait_time = Capybara::Queries::BaseQuery.wait(options, config.default_max_wait_time)
589
552
  document.synchronize(wait_time, errors: [Capybara::WindowError]) do
@@ -607,12 +570,7 @@ module Capybara
607
570
  #
608
571
  def execute_script(script, *args)
609
572
  @touched = true
610
- if args.empty?
611
- driver.execute_script(script)
612
- else
613
- raise Capybara::NotSupportedByDriverError, "The current driver does not support execute_script arguments" if driver.method(:execute_script).arity == 1
614
- driver.execute_script(script, *args.map { |arg| arg.is_a?(Capybara::Node::Element) ? arg.base : arg} )
615
- end
573
+ driver.execute_script(script, *args.map { |arg| arg.is_a?(Capybara::Node::Element) ? arg.base : arg })
616
574
  end
617
575
 
618
576
  ##
@@ -626,12 +584,20 @@ module Capybara
626
584
  #
627
585
  def evaluate_script(script, *args)
628
586
  @touched = true
629
- result = if args.empty?
630
- driver.evaluate_script(script)
631
- else
632
- raise Capybara::NotSupportedByDriverError, "The current driver does not support evaluate_script arguments" if driver.method(:evaluate_script).arity == 1
633
- driver.evaluate_script(script, *args.map { |arg| arg.is_a?(Capybara::Node::Element) ? arg.base : arg} )
634
- end
587
+ result = driver.evaluate_script(script, *args.map { |arg| arg.is_a?(Capybara::Node::Element) ? arg.base : arg })
588
+ element_script_result(result)
589
+ end
590
+
591
+ ##
592
+ #
593
+ # Evaluate the given JavaScript and obtain the result from a callback function which will be passed as the last argument to the script.
594
+ #
595
+ # @param [String] script A string of JavaScript to evaluate
596
+ # @return [Object] The result of the evaluated JavaScript (may be driver specific)
597
+ #
598
+ def evaluate_async_script(script, *args)
599
+ @touched = true
600
+ result = driver.evaluate_async_script(script, *args.map { |arg| arg.is_a?(Capybara::Node::Element) ? arg.base : arg })
635
601
  element_script_result(result)
636
602
  end
637
603
 
@@ -640,15 +606,23 @@ module Capybara
640
606
  # Execute the block, accepting a alert.
641
607
  #
642
608
  # @!macro modal_params
609
+ # Expects a block whose actions will trigger the display modal to appear
610
+ # @example
611
+ # $0 do
612
+ # click_link('link that triggers appearance of system modal')
613
+ # end
643
614
  # @overload $0(text, options = {}, &blk)
644
615
  # @param text [String, Regexp] Text or regex to match against the text in the modal. If not provided any modal is matched
616
+ # @option options [Numeric] :wait (Capybara.default_max_wait_time) Maximum time to wait for the modal to appear after executing the block.
617
+ # @yield Block whose actions will trigger the system modal
645
618
  # @overload $0(options = {}, &blk)
646
- # @option options [Numeric] :wait (Capybara.default_max_wait_time) Maximum time to wait for the modal to appear after executing the block.
619
+ # @option options [Numeric] :wait (Capybara.default_max_wait_time) Maximum time to wait for the modal to appear after executing the block.
620
+ # @yield Block whose actions will trigger the system modal
647
621
  # @return [String] the message shown in the modal
648
622
  # @raise [Capybara::ModalNotFound] if modal dialog hasn't been found
649
623
  #
650
- def accept_alert(text_or_options=nil, options={}, &blk)
651
- accept_modal(:alert, text_or_options, options, &blk)
624
+ def accept_alert(text = nil, **options, &blk)
625
+ accept_modal(:alert, text, options, &blk)
652
626
  end
653
627
 
654
628
  ##
@@ -657,8 +631,8 @@ module Capybara
657
631
  #
658
632
  # @macro modal_params
659
633
  #
660
- def accept_confirm(text_or_options=nil, options={}, &blk)
661
- accept_modal(:confirm, text_or_options, options, &blk)
634
+ def accept_confirm(text = nil, **options, &blk)
635
+ accept_modal(:confirm, text, options, &blk)
662
636
  end
663
637
 
664
638
  ##
@@ -667,8 +641,8 @@ module Capybara
667
641
  #
668
642
  # @macro modal_params
669
643
  #
670
- def dismiss_confirm(text_or_options=nil, options={}, &blk)
671
- dismiss_modal(:confirm, text_or_options, options, &blk)
644
+ def dismiss_confirm(text = nil, **options, &blk)
645
+ dismiss_modal(:confirm, text, options, &blk)
672
646
  end
673
647
 
674
648
  ##
@@ -678,8 +652,8 @@ module Capybara
678
652
  # @macro modal_params
679
653
  # @option options [String] :with Response to provide to the prompt
680
654
  #
681
- def accept_prompt(text_or_options=nil, options={}, &blk)
682
- accept_modal(:prompt, text_or_options, options, &blk)
655
+ def accept_prompt(text = nil, **options, &blk)
656
+ accept_modal(:prompt, text, options, &blk)
683
657
  end
684
658
 
685
659
  ##
@@ -688,8 +662,8 @@ module Capybara
688
662
  #
689
663
  # @macro modal_params
690
664
  #
691
- def dismiss_prompt(text_or_options=nil, options={}, &blk)
692
- dismiss_modal(:prompt, text_or_options, options, &blk)
665
+ def dismiss_prompt(text = nil, **options, &blk)
666
+ dismiss_modal(:prompt, text, options, &blk)
693
667
  end
694
668
 
695
669
  ##
@@ -699,17 +673,15 @@ module Capybara
699
673
  #
700
674
  # If invoked without arguments it will save file to `Capybara.save_path`
701
675
  # and file will be given randomly generated filename. If invoked with a relative path
702
- # the path will be relative to `Capybara.save_path`, which is different from
703
- # the previous behavior with `Capybara.save_and_open_page_path` where the relative path was
704
- # relative to Dir.pwd
676
+ # the path will be relative to `Capybara.save_path`
705
677
  #
706
678
  # @param [String] path the path to where it should be saved
707
679
  # @return [String] the path to which the file was saved
708
680
  #
709
681
  def save_page(path = nil)
710
- path = prepare_path(path, 'html')
711
- File.write(path, Capybara::Helpers.inject_asset_host(body, config.asset_host), mode: 'wb')
712
- path
682
+ prepare_path(path, 'html').tap do |p|
683
+ File.write(p, Capybara::Helpers.inject_asset_host(body, host: config.asset_host), mode: 'wb')
684
+ end
713
685
  end
714
686
 
715
687
  ##
@@ -718,15 +690,12 @@ module Capybara
718
690
  #
719
691
  # If invoked without arguments it will save file to `Capybara.save_path`
720
692
  # and file will be given randomly generated filename. If invoked with a relative path
721
- # the path will be relative to `Capybara.save_path`, which is different from
722
- # the previous behavior with `Capybara.save_and_open_page_path` where the relative path was
723
- # relative to Dir.pwd
693
+ # the path will be relative to `Capybara.save_path`
724
694
  #
725
695
  # @param [String] path the path to where it should be saved
726
696
  #
727
697
  def save_and_open_page(path = nil)
728
- path = save_page(path)
729
- open_file(path)
698
+ save_page(path).tap { |p| open_file(p) }
730
699
  end
731
700
 
732
701
  ##
@@ -735,17 +704,13 @@ module Capybara
735
704
  #
736
705
  # If invoked without arguments it will save file to `Capybara.save_path`
737
706
  # and file will be given randomly generated filename. If invoked with a relative path
738
- # the path will be relative to `Capybara.save_path`, which is different from
739
- # the previous behavior with `Capybara.save_and_open_page_path` where the relative path was
740
- # relative to Dir.pwd
707
+ # the path will be relative to `Capybara.save_path`
741
708
  #
742
709
  # @param [String] path the path to where it should be saved
743
710
  # @param [Hash] options a customizable set of options
744
711
  # @return [String] the path to which the file was saved
745
- def save_screenshot(path = nil, options = {})
746
- path = prepare_path(path, 'png')
747
- driver.save_screenshot(path, options)
748
- path
712
+ def save_screenshot(path = nil, **options)
713
+ prepare_path(path, 'png').tap { |p| driver.save_screenshot(p, options) }
749
714
  end
750
715
 
751
716
  ##
@@ -754,16 +719,15 @@ module Capybara
754
719
  #
755
720
  # If invoked without arguments it will save file to `Capybara.save_path`
756
721
  # and file will be given randomly generated filename. If invoked with a relative path
757
- # the path will be relative to `Capybara.save_path`, which is different from
758
- # the previous behavior with `Capybara.save_and_open_page_path` where the relative path was
759
- # relative to Dir.pwd
722
+ # the path will be relative to `Capybara.save_path`
760
723
  #
761
724
  # @param [String] path the path to where it should be saved
762
725
  # @param [Hash] options a customizable set of options
763
726
  #
764
- def save_and_open_screenshot(path = nil, options = {})
765
- path = save_screenshot(path, options)
766
- open_file(path)
727
+ def save_and_open_screenshot(path = nil, **options)
728
+ # rubocop:disable Lint/Debugger
729
+ save_screenshot(path, options).tap { |p| open_file(p) }
730
+ # rubocop:enable Lint/Debugger
767
731
  end
768
732
 
769
733
  def document
@@ -789,8 +753,7 @@ module Capybara
789
753
 
790
754
  def current_scope
791
755
  scope = scopes.last
792
- scope = document if [nil, :frame].include? scope
793
- scope
756
+ [nil, :frame].include?(scope) ? document : scope
794
757
  end
795
758
 
796
759
  ##
@@ -832,6 +795,7 @@ module Capybara
832
795
  Capybara::ReadOnlySessionConfig.new(Capybara.session_options)
833
796
  end
834
797
  end
798
+
835
799
  private
836
800
 
837
801
  @@instance_created = false
@@ -844,32 +808,21 @@ module Capybara
844
808
  driver.dismiss_modal(type, modal_options(text_or_options, options), &blk)
845
809
  end
846
810
 
847
- def modal_options(text_or_options, options)
848
- text_or_options, options = nil, text_or_options if text_or_options.is_a?(Hash)
849
- options[:text] ||= text_or_options unless text_or_options.nil?
811
+ def modal_options(text = nil, **options)
812
+ options[:text] ||= text unless text.nil?
850
813
  options[:wait] ||= config.default_max_wait_time
851
814
  options
852
815
  end
853
816
 
854
-
855
817
  def open_file(path)
856
- begin
857
- require "launchy"
858
- Launchy.open(path)
859
- rescue LoadError
860
- warn "File saved to #{path}."
861
- warn "Please install the launchy gem to open the file automatically."
862
- end
818
+ require "launchy"
819
+ Launchy.open(path)
820
+ rescue LoadError
821
+ warn "File saved to #{path}.\nPlease install the launchy gem to open the file automatically."
863
822
  end
864
823
 
865
824
  def prepare_path(path, extension)
866
- if config.save_path || config.save_and_open_page_path.nil?
867
- path = File.expand_path(path || default_fn(extension), config.save_path)
868
- else
869
- path = File.expand_path(default_fn(extension), config.save_and_open_page_path) if path.nil?
870
- end
871
- FileUtils.mkdir_p(File.dirname(path))
872
- path
825
+ File.expand_path(path || default_fn(extension), config.save_path).tap { |p| FileUtils.mkdir_p(File.dirname(p)) }
873
826
  end
874
827
 
875
828
  def default_fn(extension)
@@ -895,26 +848,22 @@ module Capybara
895
848
  end
896
849
 
897
850
  def _find_frame(*args)
898
- within(document) do # Previous 2.x versions ignored current scope when finding frames - consider changing in 3.0
899
- case args[0]
900
- when Capybara::Node::Element
901
- args[0]
902
- when String, Hash
903
- find(:frame, *args)
904
- when Symbol
905
- find(*args)
906
- when Integer
907
- idx = args[0]
908
- all(:frame, minimum: idx+1)[idx]
909
- else
910
- raise TypeError
911
- end
851
+ case args[0]
852
+ when Capybara::Node::Element
853
+ args[0]
854
+ when String, Hash
855
+ find(:frame, *args)
856
+ when Symbol
857
+ find(*args)
858
+ when Integer
859
+ idx = args[0]
860
+ all(:frame, minimum: idx + 1)[idx]
861
+ else
862
+ raise TypeError
912
863
  end
913
864
  end
914
865
 
915
- def _switch_to_window(window = nil, options= {})
916
- options, window = window, nil if window.is_a? Hash
917
-
866
+ def _switch_to_window(window = nil, **options)
918
867
  raise Capybara::ScopeError, "Window cannot be switched inside a `within_frame` block" if scopes.include?(:frame)
919
868
  raise Capybara::ScopeError, "Window cannot be switch inside a `within` block" unless scopes.last.nil?
920
869
 
@@ -928,9 +877,7 @@ module Capybara
928
877
  begin
929
878
  driver.window_handles.each do |handle|
930
879
  driver.switch_to_window handle
931
- if yield
932
- return Window.new(self, handle)
933
- end
880
+ return Window.new(self, handle) if yield
934
881
  end
935
882
  rescue => e
936
883
  driver.switch_to_window(original_window_handle)
@@ -942,6 +889,5 @@ module Capybara
942
889
  end
943
890
  end
944
891
  end
945
-
946
892
  end
947
893
  end