capybara 2.5.0 → 2.18.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (205) hide show
  1. checksums.yaml +5 -5
  2. data/.yard/templates_custom/default/class/html/selectors.erb +38 -0
  3. data/.yard/templates_custom/default/class/html/setup.rb +17 -0
  4. data/.yard/yard_extensions.rb +78 -0
  5. data/.yardopts +1 -0
  6. data/History.md +413 -10
  7. data/License.txt +1 -1
  8. data/README.md +237 -130
  9. data/lib/capybara/config.rb +132 -0
  10. data/lib/capybara/cucumber.rb +3 -1
  11. data/lib/capybara/driver/base.rb +27 -6
  12. data/lib/capybara/driver/node.rb +14 -5
  13. data/lib/capybara/dsl.rb +2 -3
  14. data/lib/capybara/helpers.rb +13 -65
  15. data/lib/capybara/minitest/spec.rb +177 -0
  16. data/lib/capybara/minitest.rb +278 -0
  17. data/lib/capybara/node/actions.rb +180 -24
  18. data/lib/capybara/node/base.rb +17 -5
  19. data/lib/capybara/node/document.rb +5 -0
  20. data/lib/capybara/node/document_matchers.rb +15 -14
  21. data/lib/capybara/node/element.rb +55 -7
  22. data/lib/capybara/node/finders.rb +179 -67
  23. data/lib/capybara/node/matchers.rb +301 -105
  24. data/lib/capybara/node/simple.rb +15 -4
  25. data/lib/capybara/queries/ancestor_query.rb +25 -0
  26. data/lib/capybara/queries/base_query.rb +69 -3
  27. data/lib/capybara/queries/current_path_query.rb +17 -8
  28. data/lib/capybara/queries/match_query.rb +19 -0
  29. data/lib/capybara/queries/selector_query.rb +251 -0
  30. data/lib/capybara/queries/sibling_query.rb +25 -0
  31. data/lib/capybara/queries/text_query.rb +67 -16
  32. data/lib/capybara/queries/title_query.rb +4 -2
  33. data/lib/capybara/query.rb +3 -131
  34. data/lib/capybara/rack_test/browser.rb +14 -5
  35. data/lib/capybara/rack_test/css_handlers.rb +1 -0
  36. data/lib/capybara/rack_test/driver.rb +15 -8
  37. data/lib/capybara/rack_test/form.rb +34 -12
  38. data/lib/capybara/rack_test/node.rb +29 -12
  39. data/lib/capybara/rails.rb +3 -3
  40. data/lib/capybara/result.rb +104 -9
  41. data/lib/capybara/rspec/compound.rb +95 -0
  42. data/lib/capybara/rspec/features.rb +17 -6
  43. data/lib/capybara/rspec/matcher_proxies.rb +45 -0
  44. data/lib/capybara/rspec/matchers.rb +199 -80
  45. data/lib/capybara/rspec.rb +4 -2
  46. data/lib/capybara/selector/css.rb +30 -0
  47. data/lib/capybara/selector/filter.rb +20 -0
  48. data/lib/capybara/selector/filter_set.rb +74 -0
  49. data/lib/capybara/selector/filters/base.rb +33 -0
  50. data/lib/capybara/selector/filters/expression_filter.rb +40 -0
  51. data/lib/capybara/selector/filters/node_filter.rb +27 -0
  52. data/lib/capybara/selector/selector.rb +276 -0
  53. data/lib/capybara/selector.rb +452 -157
  54. data/lib/capybara/selenium/driver.rb +282 -81
  55. data/lib/capybara/selenium/node.rb +144 -46
  56. data/lib/capybara/server.rb +59 -16
  57. data/lib/capybara/session/config.rb +114 -0
  58. data/lib/capybara/session/matchers.rb +29 -19
  59. data/lib/capybara/session.rb +378 -143
  60. data/lib/capybara/spec/fixtures/no_extension +1 -0
  61. data/lib/capybara/spec/public/jquery-ui.js +13 -791
  62. data/lib/capybara/spec/public/jquery.js +4 -9045
  63. data/lib/capybara/spec/public/test.js +45 -11
  64. data/lib/capybara/spec/session/accept_alert_spec.rb +30 -7
  65. data/lib/capybara/spec/session/accept_confirm_spec.rb +14 -2
  66. data/lib/capybara/spec/session/accept_prompt_spec.rb +35 -6
  67. data/lib/capybara/spec/session/all_spec.rb +45 -32
  68. data/lib/capybara/spec/session/ancestor_spec.rb +85 -0
  69. data/lib/capybara/spec/session/assert_all_of_selectors_spec.rb +110 -0
  70. data/lib/capybara/spec/session/assert_current_path.rb +15 -2
  71. data/lib/capybara/spec/session/assert_selector.rb +29 -28
  72. data/lib/capybara/spec/session/assert_text.rb +59 -20
  73. data/lib/capybara/spec/session/assert_title.rb +25 -11
  74. data/lib/capybara/spec/session/attach_file_spec.rb +42 -4
  75. data/lib/capybara/spec/session/body_spec.rb +1 -0
  76. data/lib/capybara/spec/session/check_spec.rb +90 -14
  77. data/lib/capybara/spec/session/choose_spec.rb +31 -5
  78. data/lib/capybara/spec/session/click_button_spec.rb +20 -9
  79. data/lib/capybara/spec/session/click_link_or_button_spec.rb +15 -9
  80. data/lib/capybara/spec/session/click_link_spec.rb +39 -15
  81. data/lib/capybara/spec/session/current_scope_spec.rb +2 -1
  82. data/lib/capybara/spec/session/current_url_spec.rb +12 -3
  83. data/lib/capybara/spec/session/dismiss_confirm_spec.rb +6 -5
  84. data/lib/capybara/spec/session/dismiss_prompt_spec.rb +4 -3
  85. data/lib/capybara/spec/session/element/assert_match_selector.rb +36 -0
  86. data/lib/capybara/spec/session/element/match_css_spec.rb +23 -0
  87. data/lib/capybara/spec/session/element/match_xpath_spec.rb +23 -0
  88. data/lib/capybara/spec/session/element/matches_selector_spec.rb +106 -0
  89. data/lib/capybara/spec/session/evaluate_async_script_spec.rb +22 -0
  90. data/lib/capybara/spec/session/evaluate_script_spec.rb +23 -1
  91. data/lib/capybara/spec/session/execute_script_spec.rb +22 -3
  92. data/lib/capybara/spec/session/fill_in_spec.rb +50 -32
  93. data/lib/capybara/spec/session/find_button_spec.rb +43 -2
  94. data/lib/capybara/spec/session/find_by_id_spec.rb +3 -2
  95. data/lib/capybara/spec/session/find_field_spec.rb +42 -6
  96. data/lib/capybara/spec/session/find_link_spec.rb +22 -3
  97. data/lib/capybara/spec/session/find_spec.rb +103 -57
  98. data/lib/capybara/spec/session/first_spec.rb +34 -18
  99. data/lib/capybara/spec/session/frame/switch_to_frame_spec.rb +103 -0
  100. data/lib/capybara/spec/session/{within_frame_spec.rb → frame/within_frame_spec.rb} +44 -2
  101. data/lib/capybara/spec/session/go_back_spec.rb +2 -1
  102. data/lib/capybara/spec/session/go_forward_spec.rb +2 -1
  103. data/lib/capybara/spec/session/has_all_selectors_spec.rb +69 -0
  104. data/lib/capybara/spec/session/has_button_spec.rb +17 -8
  105. data/lib/capybara/spec/session/has_css_spec.rb +85 -73
  106. data/lib/capybara/spec/session/has_current_path_spec.rb +91 -7
  107. data/lib/capybara/spec/session/has_field_spec.rb +93 -58
  108. data/lib/capybara/spec/session/has_link_spec.rb +9 -8
  109. data/lib/capybara/spec/session/has_none_selectors_spec.rb +76 -0
  110. data/lib/capybara/spec/session/has_select_spec.rb +159 -59
  111. data/lib/capybara/spec/session/has_selector_spec.rb +64 -28
  112. data/lib/capybara/spec/session/has_table_spec.rb +1 -0
  113. data/lib/capybara/spec/session/has_text_spec.rb +27 -12
  114. data/lib/capybara/spec/session/has_title_spec.rb +22 -4
  115. data/lib/capybara/spec/session/has_xpath_spec.rb +32 -29
  116. data/lib/capybara/spec/session/headers.rb +2 -1
  117. data/lib/capybara/spec/session/html_spec.rb +4 -3
  118. data/lib/capybara/spec/session/node_spec.rb +198 -38
  119. data/lib/capybara/spec/session/refresh_spec.rb +28 -0
  120. data/lib/capybara/spec/session/reset_session_spec.rb +46 -5
  121. data/lib/capybara/spec/session/response_code.rb +2 -1
  122. data/lib/capybara/spec/session/save_and_open_page_spec.rb +1 -0
  123. data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +6 -5
  124. data/lib/capybara/spec/session/save_page_spec.rb +34 -2
  125. data/lib/capybara/spec/session/save_screenshot_spec.rb +31 -1
  126. data/lib/capybara/spec/session/screenshot_spec.rb +4 -2
  127. data/lib/capybara/spec/session/select_spec.rb +34 -32
  128. data/lib/capybara/spec/session/selectors_spec.rb +65 -0
  129. data/lib/capybara/spec/session/sibling_spec.rb +52 -0
  130. data/lib/capybara/spec/session/text_spec.rb +4 -4
  131. data/lib/capybara/spec/session/title_spec.rb +2 -1
  132. data/lib/capybara/spec/session/uncheck_spec.rb +42 -2
  133. data/lib/capybara/spec/session/unselect_spec.rb +17 -16
  134. data/lib/capybara/spec/session/visit_spec.rb +77 -2
  135. data/lib/capybara/spec/session/window/become_closed_spec.rb +12 -11
  136. data/lib/capybara/spec/session/window/current_window_spec.rb +1 -0
  137. data/lib/capybara/spec/session/window/open_new_window_spec.rb +1 -0
  138. data/lib/capybara/spec/session/window/switch_to_window_spec.rb +16 -11
  139. data/lib/capybara/spec/session/window/window_opened_by_spec.rb +7 -4
  140. data/lib/capybara/spec/session/window/window_spec.rb +36 -29
  141. data/lib/capybara/spec/session/window/windows_spec.rb +1 -0
  142. data/lib/capybara/spec/session/window/within_window_spec.rb +31 -7
  143. data/lib/capybara/spec/session/within_spec.rb +14 -6
  144. data/lib/capybara/spec/spec_helper.rb +37 -4
  145. data/lib/capybara/spec/test_app.rb +15 -3
  146. data/lib/capybara/spec/views/buttons.erb +1 -0
  147. data/lib/capybara/spec/views/fieldsets.erb +2 -1
  148. data/lib/capybara/spec/views/form.erb +169 -9
  149. data/lib/capybara/spec/views/frame_child.erb +10 -2
  150. data/lib/capybara/spec/views/frame_one.erb +2 -1
  151. data/lib/capybara/spec/views/frame_parent.erb +3 -2
  152. data/lib/capybara/spec/views/frame_two.erb +2 -1
  153. data/lib/capybara/spec/views/header_links.erb +1 -0
  154. data/lib/capybara/spec/views/host_links.erb +1 -0
  155. data/lib/capybara/spec/views/initial_alert.erb +10 -0
  156. data/lib/capybara/spec/views/path.erb +1 -0
  157. data/lib/capybara/spec/views/popup_one.erb +1 -0
  158. data/lib/capybara/spec/views/popup_two.erb +1 -0
  159. data/lib/capybara/spec/views/postback.erb +2 -1
  160. data/lib/capybara/spec/views/tables.erb +1 -0
  161. data/lib/capybara/spec/views/with_base_tag.erb +1 -0
  162. data/lib/capybara/spec/views/with_count.erb +2 -1
  163. data/lib/capybara/spec/views/with_fixed_header_footer.erb +17 -0
  164. data/lib/capybara/spec/views/with_hover.erb +7 -1
  165. data/lib/capybara/spec/views/with_html.erb +40 -2
  166. data/lib/capybara/spec/views/with_html_entities.erb +1 -0
  167. data/lib/capybara/spec/views/with_js.erb +32 -1
  168. data/lib/capybara/spec/views/with_scope.erb +1 -0
  169. data/lib/capybara/spec/views/with_simple_html.erb +2 -1
  170. data/lib/capybara/spec/views/with_slow_unload.erb +17 -0
  171. data/lib/capybara/spec/views/with_title.erb +2 -1
  172. data/lib/capybara/spec/views/with_unload_alert.erb +14 -0
  173. data/lib/capybara/spec/views/with_windows.erb +7 -0
  174. data/lib/capybara/spec/views/within_frames.erb +3 -2
  175. data/lib/capybara/version.rb +2 -1
  176. data/lib/capybara/window.rb +20 -3
  177. data/lib/capybara.rb +189 -93
  178. data/spec/basic_node_spec.rb +7 -6
  179. data/spec/capybara_spec.rb +90 -4
  180. data/spec/dsl_spec.rb +3 -1
  181. data/spec/filter_set_spec.rb +28 -0
  182. data/spec/fixtures/capybara.csv +1 -0
  183. data/spec/fixtures/selenium_driver_rspec_failure.rb +5 -1
  184. data/spec/fixtures/selenium_driver_rspec_success.rb +5 -1
  185. data/spec/minitest_spec.rb +130 -0
  186. data/spec/minitest_spec_spec.rb +135 -0
  187. data/spec/per_session_config_spec.rb +67 -0
  188. data/spec/rack_test_spec.rb +50 -7
  189. data/spec/result_spec.rb +76 -0
  190. data/spec/rspec/features_spec.rb +21 -8
  191. data/spec/rspec/scenarios_spec.rb +21 -0
  192. data/spec/rspec/{matchers_spec.rb → shared_spec_matchers.rb} +160 -54
  193. data/spec/rspec/views_spec.rb +5 -0
  194. data/spec/rspec_matchers_spec.rb +46 -0
  195. data/spec/rspec_spec.rb +79 -1
  196. data/spec/selector_spec.rb +199 -0
  197. data/spec/selenium_spec_chrome.rb +54 -9
  198. data/spec/selenium_spec_firefox.rb +68 -0
  199. data/spec/selenium_spec_marionette.rb +127 -0
  200. data/spec/server_spec.rb +102 -14
  201. data/spec/session_spec.rb +54 -0
  202. data/spec/shared_selenium_session.rb +215 -0
  203. data/spec/spec_helper.rb +7 -0
  204. metadata +140 -15
  205. data/spec/selenium_spec.rb +0 -128
@@ -1,17 +1,29 @@
1
+ # frozen_string_literal: true
1
2
  require "uri"
2
3
 
3
4
  class Capybara::Selenium::Driver < Capybara::Driver::Base
5
+
4
6
  DEFAULT_OPTIONS = {
5
- :browser => :firefox
7
+ :browser => :firefox,
8
+ clear_local_storage: false,
9
+ clear_session_storage: false
6
10
  }
7
- SPECIAL_OPTIONS = [:browser]
11
+ SPECIAL_OPTIONS = [:browser, :clear_local_storage, :clear_session_storage]
8
12
 
9
13
  attr_reader :app, :options
10
14
 
11
15
  def browser
12
16
  unless @browser
13
- @browser = Selenium::WebDriver.for(options[:browser], options.reject { |key,val| SPECIAL_OPTIONS.include?(key) })
17
+ if firefox?
18
+ options[:desired_capabilities] ||= {}
19
+ options[:desired_capabilities].merge!({ unexpectedAlertBehaviour: "ignore" })
20
+ end
14
21
 
22
+ @processed_options = options.reject { |key,_val| SPECIAL_OPTIONS.include?(key) }
23
+ @browser = Selenium::WebDriver.for(options[:browser], @processed_options)
24
+
25
+ @w3c = ((defined?(Selenium::WebDriver::Remote::W3CCapabilities) && @browser.capabilities.is_a?(Selenium::WebDriver::Remote::W3CCapabilities)) ||
26
+ (defined?(Selenium::WebDriver::Remote::W3C::Capabilities) && @browser.capabilities.is_a?(Selenium::WebDriver::Remote::W3C::Capabilities)))
15
27
  main = Process.pid
16
28
  at_exit do
17
29
  # Store the exit status of the test run since it goes away after calling the at_exit proc...
@@ -24,16 +36,8 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
24
36
  end
25
37
 
26
38
  def initialize(app, options={})
27
- begin
28
- require 'selenium-webdriver'
29
- rescue LoadError => e
30
- if e.message =~ /selenium-webdriver/
31
- 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."
32
- else
33
- raise e
34
- end
35
- end
36
-
39
+ load_selenium
40
+ @session = nil
37
41
  @app = app
38
42
  @browser = nil
39
43
  @exit_status = nil
@@ -45,6 +49,13 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
45
49
  browser.navigate.to(path)
46
50
  end
47
51
 
52
+ def refresh
53
+ accept_modal(nil, wait: 0.1) do
54
+ browser.navigate.refresh
55
+ end
56
+ rescue Capybara::ModalNotFound
57
+ end
58
+
48
59
  def go_back
49
60
  browser.navigate.back
50
61
  end
@@ -76,36 +87,72 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
76
87
  def wait?; true; end
77
88
  def needs_server?; true; end
78
89
 
79
- def execute_script(script)
80
- browser.execute_script script
90
+ def execute_script(script, *args)
91
+ browser.execute_script(script, *args.map { |arg| arg.is_a?(Capybara::Selenium::Node) ? arg.native : arg} )
81
92
  end
82
93
 
83
- def evaluate_script(script)
84
- browser.execute_script "return #{script}"
94
+ def evaluate_script(script, *args)
95
+ result = execute_script("return #{script}", *args)
96
+ unwrap_script_result(result)
85
97
  end
86
98
 
87
- def save_screenshot(path, options={})
99
+ def evaluate_async_script(script, *args)
100
+ browser.manage.timeouts.script_timeout = Capybara.default_max_wait_time
101
+ result = browser.execute_async_script(script, *args.map { |arg| arg.is_a?(Capybara::Selenium::Node) ? arg.native : arg} )
102
+ unwrap_script_result(result)
103
+ end
104
+
105
+ def save_screenshot(path, _options={})
88
106
  browser.save_screenshot(path)
89
107
  end
90
108
 
91
109
  def reset!
92
110
  # Use instance variable directly so we avoid starting the browser just to reset the session
93
111
  if @browser
112
+ navigated = false
113
+ start_time = Capybara::Helpers.monotonic_time
94
114
  begin
95
- begin @browser.manage.delete_all_cookies
96
- rescue Selenium::WebDriver::Error::UnhandledError
97
- # delete_all_cookies fails when we've previously gone
98
- # to about:blank, so we rescue this error and do nothing
99
- # instead.
115
+ if !navigated
116
+ # Only trigger a navigation if we haven't done it already, otherwise it
117
+ # can trigger an endless series of unload modals
118
+ begin
119
+ @browser.manage.delete_all_cookies
120
+ if options[:clear_session_storage]
121
+ if @browser.respond_to? :session_storage
122
+ @browser.session_storage.clear
123
+ else
124
+ warn "sessionStorage clear requested but is not available for this driver"
125
+ end
126
+ end
127
+ if options[:clear_local_storage]
128
+ if @browser.respond_to? :local_storage
129
+ @browser.local_storage.clear
130
+ else
131
+ warn "localStorage clear requested but is not available for this driver"
132
+ end
133
+ end
134
+ rescue Selenium::WebDriver::Error::UnhandledError
135
+ # delete_all_cookies fails when we've previously gone
136
+ # to about:blank, so we rescue this error and do nothing
137
+ # instead.
138
+ end
139
+ @browser.navigate.to("about:blank")
140
+ end
141
+ navigated = true
142
+
143
+ #Ensure the page is empty and trigger an UnhandledAlertError for any modals that appear during unload
144
+ until find_xpath("/html/body/*").empty? do
145
+ raise Capybara::ExpectationNotMet.new('Timed out waiting for Selenium session reset') if (Capybara::Helpers.monotonic_time - start_time) >= 10
146
+ sleep 0.05
100
147
  end
101
- @browser.navigate.to("about:blank")
102
- rescue Selenium::WebDriver::Error::UnhandledAlertError
148
+ rescue Selenium::WebDriver::Error::UnhandledAlertError, Selenium::WebDriver::Error::UnexpectedAlertOpenError
103
149
  # This error is thrown if an unhandled alert is on the page
104
150
  # Firefox appears to automatically dismiss this alert, chrome does not
105
151
  # We'll try to accept it
106
152
  begin
107
153
  @browser.switch_to.alert.accept
108
- rescue Selenium::WebDriver::Error::NoAlertPresentError
154
+ sleep 0.25 # allow time for the modal to be handled
155
+ rescue modal_error
109
156
  # The alert is now gone - nothing to do
110
157
  end
111
158
  # try cleaning up the browser again
@@ -114,35 +161,21 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
114
161
  end
115
162
  end
116
163
 
117
- ##
118
- #
119
- # Webdriver supports frame name, id, index(zero-based) or {Capybara::Node::Element} to find iframe
120
- #
121
- # @overload within_frame(index)
122
- # @param [Integer] index index of a frame
123
- # @overload within_frame(name_or_id)
124
- # @param [String] name_or_id name or id of a frame
125
- # @overload within_frame(element)
126
- # @param [Capybara::Node::Base] a_node frame element
127
- #
128
- def within_frame(frame_handle)
129
- frame_handle = frame_handle.native if frame_handle.is_a?(Capybara::Node::Base)
130
- if !browser.switch_to.respond_to?(:parent_frame)
131
- # Selenium Webdriver < 2.43 doesnt support moving back to the parent
132
- @frame_handles[browser.window_handle] ||= []
133
- @frame_handles[browser.window_handle] << frame_handle
134
- end
135
- browser.switch_to.frame(frame_handle)
136
- yield
137
- ensure
138
- if browser.switch_to.respond_to?(:parent_frame)
139
- browser.switch_to.parent_frame
140
- else
141
- # There doesnt appear to be any way in Selenium Webdriver < 2.43 to move back to a parent frame
142
- # other than going back to the root and then reiterating down
164
+ def switch_to_frame(frame)
165
+ case frame
166
+ when :top
167
+ @frame_handles[browser.window_handle] = []
168
+ browser.switch_to.default_content
169
+ when :parent
170
+ # would love to use browser.switch_to.parent_frame here
171
+ # but it has an issue if the current frame is removed from within it
143
172
  @frame_handles[browser.window_handle].pop
144
173
  browser.switch_to.default_content
145
174
  @frame_handles[browser.window_handle].each { |fh| browser.switch_to.frame(fh) }
175
+ else
176
+ @frame_handles[browser.window_handle] ||= []
177
+ @frame_handles[browser.window_handle] << frame.native
178
+ browser.switch_to.frame(frame.native)
146
179
  end
147
180
  end
148
181
 
@@ -159,7 +192,12 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
159
192
 
160
193
  def resize_window_to(handle, width, height)
161
194
  within_given_window(handle) do
162
- browser.manage.window.resize_to(width, height)
195
+ # Don't set the size if already set - See https://github.com/mozilla/geckodriver/issues/643
196
+ if marionette? && (window_size(handle) == [width, height])
197
+ {}
198
+ else
199
+ browser.manage.window.resize_to(width, height)
200
+ end
163
201
  end
164
202
  end
165
203
 
@@ -188,39 +226,23 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
188
226
  browser.switch_to.window handle
189
227
  end
190
228
 
191
- # @api private
192
- def find_window(locator)
193
- handles = browser.window_handles
194
- return locator if handles.include? locator
195
-
196
- original_handle = browser.window_handle
197
- handles.each do |handle|
198
- switch_to_window(handle)
199
- if (locator == browser.execute_script("return window.name") ||
200
- browser.title.include?(locator) ||
201
- browser.current_url.include?(locator))
202
- switch_to_window(original_handle)
203
- return handle
204
- end
205
- end
206
- raise Capybara::ElementNotFound, "Could not find a window identified by #{locator}"
207
- end
208
-
209
229
  def within_window(locator)
210
230
  handle = find_window(locator)
211
231
  browser.switch_to.window(handle) { yield }
212
232
  end
213
233
 
214
- def accept_modal(type, options={}, &blk)
234
+ def accept_modal(_type, options={})
215
235
  yield if block_given?
216
236
  modal = find_modal(options)
237
+
217
238
  modal.send_keys options[:with] if options[:with]
239
+
218
240
  message = modal.text
219
241
  modal.accept
220
242
  message
221
243
  end
222
244
 
223
- def dismiss_modal(type, options={}, &blk)
245
+ def dismiss_modal(_type, options={})
224
246
  yield if block_given?
225
247
  modal = find_modal(options)
226
248
  message = modal.text
@@ -230,29 +252,139 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
230
252
 
231
253
  def quit
232
254
  @browser.quit if @browser
233
- rescue Errno::ECONNREFUSED
255
+ rescue Selenium::WebDriver::Error::SessionNotCreatedError, Errno::ECONNREFUSED
234
256
  # Browser must have already gone
257
+ rescue Selenium::WebDriver::Error::UnknownError => e
258
+ unless silenced_unknown_error_message?(e.message) # Most likely already gone
259
+ # probably already gone but not sure - so warn
260
+ warn "Ignoring Selenium UnknownError during driver quit: #{e.message}"
261
+ end
235
262
  ensure
236
263
  @browser = nil
237
264
  end
238
265
 
239
266
  def invalid_element_errors
240
- [Selenium::WebDriver::Error::StaleElementReferenceError,
241
- Selenium::WebDriver::Error::UnhandledError,
242
- Selenium::WebDriver::Error::ElementNotVisibleError,
243
- Selenium::WebDriver::Error::InvalidSelectorError] # Work around a race condition that can occur with chromedriver and #go_back/#go_forward
267
+ [::Selenium::WebDriver::Error::StaleElementReferenceError,
268
+ ::Selenium::WebDriver::Error::UnhandledError,
269
+ ::Selenium::WebDriver::Error::ElementNotVisibleError,
270
+ ::Selenium::WebDriver::Error::InvalidSelectorError, # Work around a race condition that can occur with chromedriver and #go_back/#go_forward
271
+ ::Selenium::WebDriver::Error::ElementNotInteractableError,
272
+ ::Selenium::WebDriver::Error::ElementClickInterceptedError,
273
+ ::Selenium::WebDriver::Error::InvalidElementStateError,
274
+ ::Selenium::WebDriver::Error::ElementNotSelectableError,
275
+ ]
244
276
  end
245
277
 
246
278
  def no_such_window_error
247
279
  Selenium::WebDriver::Error::NoSuchWindowError
248
280
  end
249
281
 
282
+ # @api private
283
+ def marionette?
284
+ firefox? && browser && @w3c
285
+ end
286
+
287
+ # @api private
288
+ def firefox?
289
+ browser_name == "firefox"
290
+ end
291
+
292
+ # @api private
293
+ def chrome?
294
+ browser_name == "chrome"
295
+ end
296
+
297
+ # @deprecated This method is being removed
250
298
  def browser_initialized?
251
- !@browser.nil?
299
+ super && !@browser.nil?
252
300
  end
253
301
 
254
302
  private
255
303
 
304
+ # @api private
305
+ def browser_name
306
+ options[:browser].to_s
307
+ end
308
+
309
+ def modal_error
310
+ if defined?(Selenium::WebDriver::Error::NoSuchAlertError)
311
+ Selenium::WebDriver::Error::NoSuchAlertError
312
+ else
313
+ Selenium::WebDriver::Error::NoAlertPresentError
314
+ end
315
+ end
316
+
317
+ def find_window(locator)
318
+ handles = browser.window_handles
319
+ return locator if handles.include? locator
320
+
321
+ original_handle = browser.window_handle
322
+ handles.each do |handle|
323
+ switch_to_window(handle)
324
+ if (locator == browser.execute_script("return window.name") ||
325
+ browser.title.include?(locator) ||
326
+ browser.current_url.include?(locator))
327
+ switch_to_window(original_handle)
328
+ return handle
329
+ end
330
+ end
331
+ raise Capybara::ElementNotFound, "Could not find a window identified by #{locator}"
332
+ end
333
+
334
+ def insert_modal_handlers(accept, response_text)
335
+ prompt_response = if accept
336
+ if response_text.nil?
337
+ "default_text"
338
+ else
339
+ "'#{response_text.gsub("\\", "\\\\\\").gsub("'", "\\\\'")}'"
340
+ end
341
+ else
342
+ 'null'
343
+ end
344
+
345
+ script = <<-JS
346
+ if (typeof window.capybara === 'undefined') {
347
+ window.capybara = {
348
+ modal_handlers: [],
349
+ current_modal_status: function() {
350
+ return [this.modal_handlers[0].called, this.modal_handlers[0].modal_text];
351
+ },
352
+ add_handler: function(handler) {
353
+ this.modal_handlers.unshift(handler);
354
+ },
355
+ remove_handler: function(handler) {
356
+ window.alert = handler.alert;
357
+ window.confirm = handler.confirm;
358
+ window.prompt = handler.prompt;
359
+ },
360
+ handler_called: function(handler, str) {
361
+ handler.called = true;
362
+ handler.modal_text = str;
363
+ this.remove_handler(handler);
364
+ }
365
+ };
366
+ };
367
+
368
+ var modal_handler = {
369
+ prompt: window.prompt,
370
+ confirm: window.confirm,
371
+ alert: window.alert,
372
+ called: false
373
+ }
374
+ window.capybara.add_handler(modal_handler);
375
+
376
+ window.alert = window.confirm = function(str = "") {
377
+ window.capybara.handler_called(modal_handler, str.toString());
378
+ return #{accept ? 'true' : 'false'};
379
+ }
380
+ window.prompt = function(str = "", default_text = "") {
381
+ window.capybara.handler_called(modal_handler, str.toString());
382
+ return #{prompt_response};
383
+ }
384
+ JS
385
+ execute_script script
386
+ end
387
+
256
388
  def within_given_window(handle)
257
389
  original_handle = self.current_window_handle
258
390
  if handle == original_handle
@@ -269,8 +401,8 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
269
401
  # Selenium has its own built in wait (2 seconds)for a modal to show up, so this wait is really the minimum time
270
402
  # Actual wait time may be longer than specified
271
403
  wait = Selenium::WebDriver::Wait.new(
272
- timeout: (options[:wait] || Capybara.default_max_wait_time),
273
- ignore: Selenium::WebDriver::Error::NoAlertPresentError)
404
+ timeout: options.fetch(:wait, session_options.default_max_wait_time) || 0 ,
405
+ ignore: modal_error)
274
406
  begin
275
407
  wait.until do
276
408
  alert = @browser.switch_to.alert
@@ -282,4 +414,73 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
282
414
  end
283
415
  end
284
416
 
417
+ def find_headless_modal(options={})
418
+ # Selenium has its own built in wait (2 seconds)for a modal to show up, so this wait is really the minimum time
419
+ # Actual wait time may be longer than specified
420
+ wait = Selenium::WebDriver::Wait.new(
421
+ timeout: options.fetch(:wait, session_options.default_max_wait_time) || 0 ,
422
+ ignore: modal_error)
423
+ begin
424
+ wait.until do
425
+ called, alert_text = evaluate_script('window.capybara && window.capybara.current_modal_status()')
426
+ if called
427
+ execute_script('window.capybara && window.capybara.modal_handlers.shift()')
428
+ regexp = options[:text].is_a?(Regexp) ? options[:text] : Regexp.escape(options[:text].to_s)
429
+ if alert_text.match(regexp)
430
+ alert_text
431
+ else
432
+ raise Capybara::ModalNotFound.new("Unable to find modal dialog#{" with #{options[:text]}" if options[:text]}")
433
+ end
434
+ elsif called.nil?
435
+ # page changed so modal_handler data has gone away
436
+ warn "Can't verify modal text when page change occurs - ignoring" if options[:text]
437
+ ""
438
+ else
439
+ nil
440
+ end
441
+ end
442
+ rescue Selenium::WebDriver::Error::TimeOutError
443
+ raise Capybara::ModalNotFound.new("Unable to find modal dialog#{" with #{options[:text]}" if options[:text]}")
444
+ end
445
+ end
446
+
447
+ def silenced_unknown_error_message?(msg)
448
+ silenced_unknown_error_messages.any? { |r| msg =~ r }
449
+ end
450
+
451
+ def silenced_unknown_error_messages
452
+ [ /Error communicating with the remote browser/ ]
453
+ end
454
+
455
+ def unwrap_script_result(arg)
456
+ case arg
457
+ when Array
458
+ arg.map { |e| unwrap_script_result(e) }
459
+ when Hash
460
+ arg.each { |k, v| arg[k] = unwrap_script_result(v) }
461
+ when Selenium::WebDriver::Element
462
+ Capybara::Selenium::Node.new(self, arg)
463
+ else
464
+ arg
465
+ end
466
+ end
467
+
468
+ def load_selenium
469
+ begin
470
+ require 'selenium-webdriver'
471
+ # Fix for selenium-webdriver 3.4.0 which misnamed these
472
+ if !defined?(::Selenium::WebDriver::Error::ElementNotInteractableError)
473
+ ::Selenium::WebDriver::Error.const_set('ElementNotInteractableError', Class.new(::Selenium::WebDriver::Error::WebDriverError))
474
+ end
475
+ if !defined?(::Selenium::WebDriver::Error::ElementClickInterceptedError)
476
+ ::Selenium::WebDriver::Error.const_set('ElementClickInterceptedError', Class.new(::Selenium::WebDriver::Error::WebDriverError))
477
+ end
478
+ rescue LoadError => e
479
+ if e.message =~ /selenium-webdriver/
480
+ 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."
481
+ else
482
+ raise e
483
+ end
484
+ end
485
+ end
285
486
  end