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,14 +1,15 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require "uri"
4
+ require "English"
3
5
 
4
6
  class Capybara::Selenium::Driver < Capybara::Driver::Base
5
-
6
7
  DEFAULT_OPTIONS = {
7
- :browser => :firefox,
8
+ browser: :firefox,
8
9
  clear_local_storage: false,
9
10
  clear_session_storage: false
10
- }
11
- SPECIAL_OPTIONS = [:browser, :clear_local_storage, :clear_session_storage]
11
+ }.freeze
12
+ SPECIAL_OPTIONS = %i[browser clear_local_storage clear_session_storage].freeze
12
13
 
13
14
  attr_reader :app, :options
14
15
 
@@ -16,19 +17,19 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
16
17
  unless @browser
17
18
  if firefox?
18
19
  options[:desired_capabilities] ||= {}
19
- options[:desired_capabilities].merge!({ unexpectedAlertBehaviour: "ignore" })
20
+ options[:desired_capabilities][:unexpectedAlertBehaviour] = "ignore"
20
21
  end
21
22
 
22
- @processed_options = options.reject { |key,_val| SPECIAL_OPTIONS.include?(key) }
23
+ @processed_options = options.reject { |key, _val| SPECIAL_OPTIONS.include?(key) }
23
24
  @browser = Selenium::WebDriver.for(options[:browser], @processed_options)
24
25
 
25
26
  @w3c = ((defined?(Selenium::WebDriver::Remote::W3CCapabilities) && @browser.capabilities.is_a?(Selenium::WebDriver::Remote::W3CCapabilities)) ||
26
27
  (defined?(Selenium::WebDriver::Remote::W3C::Capabilities) && @browser.capabilities.is_a?(Selenium::WebDriver::Remote::W3C::Capabilities)))
27
-
28
28
  main = Process.pid
29
+
29
30
  at_exit do
30
31
  # Store the exit status of the test run since it goes away after calling the at_exit proc...
31
- @exit_status = $!.status if $!.is_a?(SystemExit)
32
+ @exit_status = $ERROR_INFO.status if $ERROR_INFO.is_a?(SystemExit)
32
33
  quit if Process.pid == main
33
34
  exit @exit_status if @exit_status # Force exit with stored status
34
35
  end
@@ -36,26 +37,9 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
36
37
  @browser
37
38
  end
38
39
 
39
- def initialize(app, options={})
40
+ def initialize(app, **options)
41
+ load_selenium
40
42
  @session = nil
41
- begin
42
- require 'selenium-webdriver'
43
- # Fix for selenium-webdriver 3.4.0 which misnamed these
44
- if !defined?(::Selenium::WebDriver::Error::ElementNotInteractableError)
45
- ::Selenium::WebDriver::Error.const_set('ElementNotInteractableError', Class.new(::Selenium::WebDriver::Error::WebDriverError))
46
- end
47
- if !defined?(::Selenium::WebDriver::Error::ElementClickInterceptedError)
48
- ::Selenium::WebDriver::Error.const_set('ElementClickInterceptedError', Class.new(::Selenium::WebDriver::Error::WebDriverError))
49
- end
50
- rescue LoadError => e
51
- if e.message =~ /selenium-webdriver/
52
- raise LoadError, "Capybara's selenium driver is unable to load `selenium-webdriver`, please install the gem and add `gem 'selenium-webdriver'` to your Gemfile if you are using bundler."
53
- else
54
- raise e
55
- end
56
- end
57
-
58
-
59
43
  @app = app
60
44
  @browser = nil
61
45
  @exit_status = nil
@@ -68,10 +52,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
68
52
  end
69
53
 
70
54
  def refresh
71
- accept_modal(nil, wait: 0.1) do
72
- browser.navigate.refresh
73
- end
74
- rescue Capybara::ModalNotFound
55
+ browser.navigate.refresh
75
56
  end
76
57
 
77
58
  def go_back
@@ -106,7 +87,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
106
87
  def needs_server?; true; end
107
88
 
108
89
  def execute_script(script, *args)
109
- browser.execute_script(script, *args.map { |arg| arg.is_a?(Capybara::Selenium::Node) ? arg.native : arg} )
90
+ browser.execute_script(script, *args.map { |arg| arg.is_a?(Capybara::Selenium::Node) ? arg.native : arg })
110
91
  end
111
92
 
112
93
  def evaluate_script(script, *args)
@@ -114,62 +95,71 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
114
95
  unwrap_script_result(result)
115
96
  end
116
97
 
117
- def save_screenshot(path, _options={})
98
+ def evaluate_async_script(script, *args)
99
+ browser.manage.timeouts.script_timeout = Capybara.default_max_wait_time
100
+ result = browser.execute_async_script(script, *args.map { |arg| arg.is_a?(Capybara::Selenium::Node) ? arg.native : arg })
101
+ unwrap_script_result(result)
102
+ end
103
+
104
+ def save_screenshot(path, **_options)
118
105
  browser.save_screenshot(path)
119
106
  end
120
107
 
121
108
  def reset!
122
109
  # Use instance variable directly so we avoid starting the browser just to reset the session
123
- if @browser
124
- navigated = false
125
- start_time = Capybara::Helpers.monotonic_time
110
+ return unless @browser
111
+
112
+ if firefox? || chrome?
113
+ switch_to_window(window_handles.first)
114
+ window_handles.slice(1..-1).each { |win| close_window(win) }
115
+ end
116
+
117
+ navigated = false
118
+ start_time = Capybara::Helpers.monotonic_time
119
+ begin
120
+ unless navigated
121
+ # Only trigger a navigation if we haven't done it already, otherwise it
122
+ # can trigger an endless series of unload modals
123
+ begin
124
+ @browser.manage.delete_all_cookies
125
+ clear_storage
126
+ rescue Selenium::WebDriver::Error::UnhandledError
127
+ # delete_all_cookies fails when we've previously gone
128
+ # to about:blank, so we rescue this error and do nothing
129
+ # instead.
130
+ end
131
+ @browser.navigate.to("about:blank")
132
+ end
133
+ navigated = true
134
+
135
+ # Ensure the page is empty and trigger an UnhandledAlertError for any modals that appear during unload
136
+ until find_xpath("/html/body/*").empty?
137
+ raise Capybara::ExpectationNotMet, 'Timed out waiting for Selenium session reset' if (Capybara::Helpers.monotonic_time - start_time) >= 10
138
+ sleep 0.05
139
+ end
140
+ rescue Selenium::WebDriver::Error::UnhandledAlertError, Selenium::WebDriver::Error::UnexpectedAlertOpenError
141
+ # This error is thrown if an unhandled alert is on the page
142
+ # Firefox appears to automatically dismiss this alert, chrome does not
143
+ # We'll try to accept it
126
144
  begin
127
- if !navigated
128
- # Only trigger a navigation if we haven't done it already, otherwise it
129
- # can trigger an endless series of unload modals
145
+ @browser.switch_to.alert.accept
146
+ sleep 0.25 # allow time for the modal to be handled
147
+ rescue modal_error
148
+ # The alert is now gone
149
+ if current_url != "about:blank"
130
150
  begin
131
- @browser.manage.delete_all_cookies
132
- if options[:clear_session_storage]
133
- if @browser.respond_to? :session_storage
134
- @browser.session_storage.clear
135
- else
136
- warn "sessionStorage clear requested but is not available for this driver"
137
- end
138
- end
139
- if options[:clear_local_storage]
140
- if @browser.respond_to? :local_storage
141
- @browser.local_storage.clear
142
- else
143
- warn "localStorage clear requested but is not available for this driver"
144
- end
145
- end
146
- rescue Selenium::WebDriver::Error::UnhandledError
147
- # delete_all_cookies fails when we've previously gone
148
- # to about:blank, so we rescue this error and do nothing
149
- # instead.
151
+ # If navigation has not occurred attempt again and accept alert
152
+ # since FF may have dismissed the alert at first attempt
153
+ @browser.navigate.to("about:blank")
154
+ sleep 0.1 # slight wait for alert
155
+ @browser.switch_to.alert.accept
156
+ rescue modal_error # rubocop:disable Metrics/BlockNesting
157
+ # alert now gone, should mean navigation happened
150
158
  end
151
- @browser.navigate.to("about:blank")
152
- end
153
- navigated = true
154
-
155
- #Ensure the page is empty and trigger an UnhandledAlertError for any modals that appear during unload
156
- until find_xpath("/html/body/*").empty? do
157
- raise Capybara::ExpectationNotMet.new('Timed out waiting for Selenium session reset') if (Capybara::Helpers.monotonic_time - start_time) >= 10
158
- sleep 0.05
159
159
  end
160
- rescue Selenium::WebDriver::Error::UnhandledAlertError, Selenium::WebDriver::Error::UnexpectedAlertOpenError
161
- # This error is thrown if an unhandled alert is on the page
162
- # Firefox appears to automatically dismiss this alert, chrome does not
163
- # We'll try to accept it
164
- begin
165
- @browser.switch_to.alert.accept
166
- sleep 0.25 # allow time for the modal to be handled
167
- rescue Selenium::WebDriver::Error::NoAlertPresentError
168
- # The alert is now gone - nothing to do
169
- end
170
- # try cleaning up the browser again
171
- retry
172
160
  end
161
+ # try cleaning up the browser again
162
+ retry
173
163
  end
174
164
  end
175
165
 
@@ -221,6 +211,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
221
211
  end
222
212
 
223
213
  def close_window(handle)
214
+ raise ArgumentError, "Not allowed to close the primary window" if handle == window_handles.first
224
215
  within_given_window(handle) do
225
216
  browser.close
226
217
  end
@@ -238,43 +229,28 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
238
229
  browser.switch_to.window handle
239
230
  end
240
231
 
241
- def within_window(locator)
242
- handle = find_window(locator)
243
- browser.switch_to.window(handle) { yield }
244
- end
232
+ def accept_modal(_type, **options)
233
+ yield if block_given?
234
+ modal = find_modal(options)
245
235
 
246
- def accept_modal(_type, options={})
247
- if headless_chrome?
248
- insert_modal_handlers(true, options[:with], options[:text])
249
- yield if block_given?
250
- find_headless_modal(options)
251
- else
252
- yield if block_given?
253
- modal = find_modal(options)
254
- modal.send_keys options[:with] if options[:with]
255
- message = modal.text
256
- modal.accept
257
- message
258
- end
236
+ modal.send_keys options[:with] if options[:with]
237
+
238
+ message = modal.text
239
+ modal.accept
240
+ message
259
241
  end
260
242
 
261
- def dismiss_modal(_type, options={})
262
- if headless_chrome?
263
- insert_modal_handlers(false, options[:with], options[:text])
264
- yield if block_given?
265
- find_headless_modal(options)
266
- else
267
- yield if block_given?
268
- modal = find_modal(options)
269
- message = modal.text
270
- modal.dismiss
271
- message
272
- end
243
+ def dismiss_modal(_type, **options)
244
+ yield if block_given?
245
+ modal = find_modal(options)
246
+ message = modal.text
247
+ modal.dismiss
248
+ message
273
249
  end
274
250
 
275
251
  def quit
276
252
  @browser.quit if @browser
277
- rescue Errno::ECONNREFUSED
253
+ rescue Selenium::WebDriver::Error::SessionNotCreatedError, Errno::ECONNREFUSED
278
254
  # Browser must have already gone
279
255
  rescue Selenium::WebDriver::Error::UnknownError => e
280
256
  unless silenced_unknown_error_message?(e.message) # Most likely already gone
@@ -286,14 +262,18 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
286
262
  end
287
263
 
288
264
  def invalid_element_errors
289
- [::Selenium::WebDriver::Error::StaleElementReferenceError,
290
- ::Selenium::WebDriver::Error::UnhandledError,
291
- ::Selenium::WebDriver::Error::ElementNotVisibleError,
292
- ::Selenium::WebDriver::Error::InvalidSelectorError, # Work around a race condition that can occur with chromedriver and #go_back/#go_forward
293
- ::Selenium::WebDriver::Error::ElementNotInteractableError,
294
- ::Selenium::WebDriver::Error::ElementClickInterceptedError,
295
- ::Selenium::WebDriver::Error::InvalidElementStateError,
296
- ::Selenium::WebDriver::Error::ElementNotSelectableError,
265
+ [
266
+ ::Selenium::WebDriver::Error::StaleElementReferenceError,
267
+ ::Selenium::WebDriver::Error::UnhandledError,
268
+ ::Selenium::WebDriver::Error::ElementNotVisibleError,
269
+ ::Selenium::WebDriver::Error::InvalidSelectorError, # Work around a race condition that can occur with chromedriver and #go_back/#go_forward
270
+ ::Selenium::WebDriver::Error::ElementNotInteractableError,
271
+ ::Selenium::WebDriver::Error::ElementClickInterceptedError,
272
+ ::Selenium::WebDriver::Error::InvalidElementStateError,
273
+ ::Selenium::WebDriver::Error::ElementNotSelectableError,
274
+ ::Selenium::WebDriver::Error::ElementNotSelectableError,
275
+ ::Selenium::WebDriver::Error::NoSuchElementError, # IE
276
+ ::Selenium::WebDriver::Error::InvalidArgumentError # IE
297
277
  ]
298
278
  end
299
279
 
@@ -317,47 +297,57 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
317
297
  end
318
298
 
319
299
  # @api private
320
- def headless_chrome?
321
- if chrome?
322
- caps = @processed_options[:desired_capabilities]
323
- chrome_options = caps[:chrome_options] || caps[:chromeOptions] || {}
324
- args = chrome_options['args'] || chrome_options[:args] || []
325
- return args.include?("--headless") || args.include?("headless")
326
- end
327
- return false
300
+ def edge?
301
+ browser_name == "edge"
328
302
  end
329
303
 
330
-
331
- # @deprecated This method is being removed
332
- def browser_initialized?
333
- super && !@browser.nil?
304
+ # @api private
305
+ def ie?
306
+ browser_name == "ie"
334
307
  end
335
308
 
336
- private
309
+ private
337
310
 
338
- # @api private
339
311
  def browser_name
340
312
  options[:browser].to_s
341
313
  end
342
314
 
343
- def find_window(locator)
344
- handles = browser.window_handles
345
- return locator if handles.include? locator
346
-
347
- original_handle = browser.window_handle
348
- handles.each do |handle|
349
- switch_to_window(handle)
350
- if (locator == browser.execute_script("return window.name") ||
351
- browser.title.include?(locator) ||
352
- browser.current_url.include?(locator))
353
- switch_to_window(original_handle)
354
- return handle
315
+ def clear_storage
316
+ if options[:clear_session_storage]
317
+ if @browser.respond_to? :session_storage
318
+ @browser.session_storage.clear
319
+ else
320
+ warn "sessionStorage clear requested but is not available for this driver"
321
+ end
322
+ end
323
+ if options[:clear_local_storage]
324
+ if @browser.respond_to? :local_storage
325
+ @browser.local_storage.clear
326
+ else
327
+ warn "localStorage clear requested but is not available for this driver"
355
328
  end
356
329
  end
357
- raise Capybara::ElementNotFound, "Could not find a window identified by #{locator}"
358
330
  end
359
331
 
360
- def insert_modal_handlers(accept, response_text, expected_text=nil)
332
+ def modal_error
333
+ if defined?(Selenium::WebDriver::Error::NoSuchAlertError)
334
+ Selenium::WebDriver::Error::NoSuchAlertError
335
+ else
336
+ Selenium::WebDriver::Error::NoAlertPresentError
337
+ end
338
+ end
339
+
340
+ def insert_modal_handlers(accept, response_text)
341
+ prompt_response = if accept
342
+ if response_text.nil?
343
+ "default_text"
344
+ else
345
+ "'#{response_text.gsub("\\", "\\\\\\").gsub("'", "\\\\'")}'"
346
+ end
347
+ else
348
+ 'null'
349
+ end
350
+
361
351
  script = <<-JS
362
352
  if (typeof window.capybara === 'undefined') {
363
353
  window.capybara = {
@@ -389,20 +379,20 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
389
379
  }
390
380
  window.capybara.add_handler(modal_handler);
391
381
 
392
- window.alert = window.confirm = function(str) {
393
- window.capybara.handler_called(modal_handler, str);
382
+ window.alert = window.confirm = function(str = "") {
383
+ window.capybara.handler_called(modal_handler, str.toString());
394
384
  return #{accept ? 'true' : 'false'};
395
- };
396
- window.prompt = function(str) {
397
- window.capybara.handler_called(modal_handler, str);
398
- return #{accept ? "'#{response_text}'" : 'null'};
385
+ }
386
+ window.prompt = function(str = "", default_text = "") {
387
+ window.capybara.handler_called(modal_handler, str.toString());
388
+ return #{prompt_response};
399
389
  }
400
390
  JS
401
391
  execute_script script
402
392
  end
403
393
 
404
394
  def within_given_window(handle)
405
- original_handle = self.current_window_handle
395
+ original_handle = current_window_handle
406
396
  if handle == original_handle
407
397
  yield
408
398
  else
@@ -413,39 +403,41 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
413
403
  end
414
404
  end
415
405
 
416
- def find_modal(options={})
406
+ def find_modal(text: nil, **options)
417
407
  # Selenium has its own built in wait (2 seconds)for a modal to show up, so this wait is really the minimum time
418
408
  # Actual wait time may be longer than specified
419
409
  wait = Selenium::WebDriver::Wait.new(
420
- timeout: options.fetch(:wait, session_options.default_max_wait_time) || 0 ,
421
- ignore: Selenium::WebDriver::Error::NoAlertPresentError)
410
+ timeout: options.fetch(:wait, session_options.default_max_wait_time) || 0,
411
+ ignore: modal_error
412
+ )
422
413
  begin
423
414
  wait.until do
424
415
  alert = @browser.switch_to.alert
425
- regexp = options[:text].is_a?(Regexp) ? options[:text] : Regexp.escape(options[:text].to_s)
416
+ regexp = text.is_a?(Regexp) ? text : Regexp.escape(text.to_s)
426
417
  alert.text.match(regexp) ? alert : nil
427
418
  end
428
419
  rescue Selenium::WebDriver::Error::TimeOutError
429
- raise Capybara::ModalNotFound.new("Unable to find modal dialog#{" with #{options[:text]}" if options[:text]}")
420
+ raise Capybara::ModalNotFound, "Unable to find modal dialog#{" with #{text}" if text}"
430
421
  end
431
422
  end
432
423
 
433
- def find_headless_modal(options={})
424
+ def find_headless_modal(text: nil, **options)
434
425
  # Selenium has its own built in wait (2 seconds)for a modal to show up, so this wait is really the minimum time
435
426
  # Actual wait time may be longer than specified
436
427
  wait = Selenium::WebDriver::Wait.new(
437
- timeout: options.fetch(:wait, session_options.default_max_wait_time) || 0 ,
438
- ignore: Selenium::WebDriver::Error::NoAlertPresentError)
428
+ timeout: options.fetch(:wait, session_options.default_max_wait_time) || 0,
429
+ ignore: modal_error
430
+ )
439
431
  begin
440
432
  wait.until do
441
433
  called, alert_text = evaluate_script('window.capybara && window.capybara.current_modal_status()')
442
434
  if called
443
435
  execute_script('window.capybara && window.capybara.modal_handlers.shift()')
444
- regexp = options[:text].is_a?(Regexp) ? options[:text] : Regexp.escape(options[:text].to_s)
436
+ regexp = text.is_a?(Regexp) ? text : Regexp.escape(text.to_s)
445
437
  if alert_text.match(regexp)
446
438
  alert_text
447
439
  else
448
- raise Capybara::ModalNotFound.new("Unable to find modal dialog#{" with #{options[:text]}" if options[:text]}")
440
+ raise Capybara::ModalNotFound, "Unable to find modal dialog#{" with #{text}" if text}"
449
441
  end
450
442
  elsif called.nil?
451
443
  # page changed so modal_handler data has gone away
@@ -456,7 +448,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
456
448
  end
457
449
  end
458
450
  rescue Selenium::WebDriver::Error::TimeOutError
459
- raise Capybara::ModalNotFound.new("Unable to find modal dialog#{" with #{options[:text]}" if options[:text]}")
451
+ raise Capybara::ModalNotFound, "Unable to find modal dialog#{" with #{options[:text]}" if options[:text]}"
460
452
  end
461
453
  end
462
454
 
@@ -465,7 +457,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
465
457
  end
466
458
 
467
459
  def silenced_unknown_error_messages
468
- [ /Error communicating with the remote browser/ ]
460
+ [/Error communicating with the remote browser/]
469
461
  end
470
462
 
471
463
  def unwrap_script_result(arg)
@@ -480,4 +472,21 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
480
472
  arg
481
473
  end
482
474
  end
475
+
476
+ def load_selenium
477
+ require 'selenium-webdriver'
478
+ # Fix for selenium-webdriver 3.4.0 which misnamed these
479
+ unless defined?(::Selenium::WebDriver::Error::ElementNotInteractableError)
480
+ ::Selenium::WebDriver::Error.const_set('ElementNotInteractableError', Class.new(::Selenium::WebDriver::Error::WebDriverError))
481
+ end
482
+ unless defined?(::Selenium::WebDriver::Error::ElementClickInterceptedError)
483
+ ::Selenium::WebDriver::Error.const_set('ElementClickInterceptedError', Class.new(::Selenium::WebDriver::Error::WebDriverError))
484
+ end
485
+ rescue LoadError => e
486
+ if e.message =~ /selenium-webdriver/
487
+ raise LoadError, "Capybara's selenium driver is unable to load `selenium-webdriver`, please install the gem and add `gem 'selenium-webdriver'` to your Gemfile if you are using bundler."
488
+ else
489
+ raise e
490
+ end
491
+ end
483
492
  end