capybara 2.5.0 → 2.18.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.yard/templates_custom/default/class/html/selectors.erb +38 -0
- data/.yard/templates_custom/default/class/html/setup.rb +17 -0
- data/.yard/yard_extensions.rb +78 -0
- data/.yardopts +1 -0
- data/History.md +413 -10
- data/License.txt +1 -1
- data/README.md +237 -130
- data/lib/capybara/config.rb +132 -0
- data/lib/capybara/cucumber.rb +3 -1
- data/lib/capybara/driver/base.rb +27 -6
- data/lib/capybara/driver/node.rb +14 -5
- data/lib/capybara/dsl.rb +2 -3
- data/lib/capybara/helpers.rb +13 -65
- data/lib/capybara/minitest/spec.rb +177 -0
- data/lib/capybara/minitest.rb +278 -0
- data/lib/capybara/node/actions.rb +180 -24
- data/lib/capybara/node/base.rb +17 -5
- data/lib/capybara/node/document.rb +5 -0
- data/lib/capybara/node/document_matchers.rb +15 -14
- data/lib/capybara/node/element.rb +55 -7
- data/lib/capybara/node/finders.rb +179 -67
- data/lib/capybara/node/matchers.rb +301 -105
- data/lib/capybara/node/simple.rb +15 -4
- data/lib/capybara/queries/ancestor_query.rb +25 -0
- data/lib/capybara/queries/base_query.rb +69 -3
- data/lib/capybara/queries/current_path_query.rb +17 -8
- data/lib/capybara/queries/match_query.rb +19 -0
- data/lib/capybara/queries/selector_query.rb +251 -0
- data/lib/capybara/queries/sibling_query.rb +25 -0
- data/lib/capybara/queries/text_query.rb +67 -16
- data/lib/capybara/queries/title_query.rb +4 -2
- data/lib/capybara/query.rb +3 -131
- data/lib/capybara/rack_test/browser.rb +14 -5
- data/lib/capybara/rack_test/css_handlers.rb +1 -0
- data/lib/capybara/rack_test/driver.rb +15 -8
- data/lib/capybara/rack_test/form.rb +34 -12
- data/lib/capybara/rack_test/node.rb +29 -12
- data/lib/capybara/rails.rb +3 -3
- data/lib/capybara/result.rb +104 -9
- data/lib/capybara/rspec/compound.rb +95 -0
- data/lib/capybara/rspec/features.rb +17 -6
- data/lib/capybara/rspec/matcher_proxies.rb +45 -0
- data/lib/capybara/rspec/matchers.rb +199 -80
- data/lib/capybara/rspec.rb +4 -2
- data/lib/capybara/selector/css.rb +30 -0
- data/lib/capybara/selector/filter.rb +20 -0
- data/lib/capybara/selector/filter_set.rb +74 -0
- data/lib/capybara/selector/filters/base.rb +33 -0
- data/lib/capybara/selector/filters/expression_filter.rb +40 -0
- data/lib/capybara/selector/filters/node_filter.rb +27 -0
- data/lib/capybara/selector/selector.rb +276 -0
- data/lib/capybara/selector.rb +452 -157
- data/lib/capybara/selenium/driver.rb +282 -81
- data/lib/capybara/selenium/node.rb +144 -46
- data/lib/capybara/server.rb +59 -16
- data/lib/capybara/session/config.rb +114 -0
- data/lib/capybara/session/matchers.rb +29 -19
- data/lib/capybara/session.rb +378 -143
- data/lib/capybara/spec/fixtures/no_extension +1 -0
- data/lib/capybara/spec/public/jquery-ui.js +13 -791
- data/lib/capybara/spec/public/jquery.js +4 -9045
- data/lib/capybara/spec/public/test.js +45 -11
- data/lib/capybara/spec/session/accept_alert_spec.rb +30 -7
- data/lib/capybara/spec/session/accept_confirm_spec.rb +14 -2
- data/lib/capybara/spec/session/accept_prompt_spec.rb +35 -6
- data/lib/capybara/spec/session/all_spec.rb +45 -32
- data/lib/capybara/spec/session/ancestor_spec.rb +85 -0
- data/lib/capybara/spec/session/assert_all_of_selectors_spec.rb +110 -0
- data/lib/capybara/spec/session/assert_current_path.rb +15 -2
- data/lib/capybara/spec/session/assert_selector.rb +29 -28
- data/lib/capybara/spec/session/assert_text.rb +59 -20
- data/lib/capybara/spec/session/assert_title.rb +25 -11
- data/lib/capybara/spec/session/attach_file_spec.rb +42 -4
- data/lib/capybara/spec/session/body_spec.rb +1 -0
- data/lib/capybara/spec/session/check_spec.rb +90 -14
- data/lib/capybara/spec/session/choose_spec.rb +31 -5
- data/lib/capybara/spec/session/click_button_spec.rb +20 -9
- data/lib/capybara/spec/session/click_link_or_button_spec.rb +15 -9
- data/lib/capybara/spec/session/click_link_spec.rb +39 -15
- data/lib/capybara/spec/session/current_scope_spec.rb +2 -1
- data/lib/capybara/spec/session/current_url_spec.rb +12 -3
- data/lib/capybara/spec/session/dismiss_confirm_spec.rb +6 -5
- data/lib/capybara/spec/session/dismiss_prompt_spec.rb +4 -3
- data/lib/capybara/spec/session/element/assert_match_selector.rb +36 -0
- data/lib/capybara/spec/session/element/match_css_spec.rb +23 -0
- data/lib/capybara/spec/session/element/match_xpath_spec.rb +23 -0
- data/lib/capybara/spec/session/element/matches_selector_spec.rb +106 -0
- data/lib/capybara/spec/session/evaluate_async_script_spec.rb +22 -0
- data/lib/capybara/spec/session/evaluate_script_spec.rb +23 -1
- data/lib/capybara/spec/session/execute_script_spec.rb +22 -3
- data/lib/capybara/spec/session/fill_in_spec.rb +50 -32
- data/lib/capybara/spec/session/find_button_spec.rb +43 -2
- data/lib/capybara/spec/session/find_by_id_spec.rb +3 -2
- data/lib/capybara/spec/session/find_field_spec.rb +42 -6
- data/lib/capybara/spec/session/find_link_spec.rb +22 -3
- data/lib/capybara/spec/session/find_spec.rb +103 -57
- data/lib/capybara/spec/session/first_spec.rb +34 -18
- data/lib/capybara/spec/session/frame/switch_to_frame_spec.rb +103 -0
- data/lib/capybara/spec/session/{within_frame_spec.rb → frame/within_frame_spec.rb} +44 -2
- data/lib/capybara/spec/session/go_back_spec.rb +2 -1
- data/lib/capybara/spec/session/go_forward_spec.rb +2 -1
- data/lib/capybara/spec/session/has_all_selectors_spec.rb +69 -0
- data/lib/capybara/spec/session/has_button_spec.rb +17 -8
- data/lib/capybara/spec/session/has_css_spec.rb +85 -73
- data/lib/capybara/spec/session/has_current_path_spec.rb +91 -7
- data/lib/capybara/spec/session/has_field_spec.rb +93 -58
- data/lib/capybara/spec/session/has_link_spec.rb +9 -8
- data/lib/capybara/spec/session/has_none_selectors_spec.rb +76 -0
- data/lib/capybara/spec/session/has_select_spec.rb +159 -59
- data/lib/capybara/spec/session/has_selector_spec.rb +64 -28
- data/lib/capybara/spec/session/has_table_spec.rb +1 -0
- data/lib/capybara/spec/session/has_text_spec.rb +27 -12
- data/lib/capybara/spec/session/has_title_spec.rb +22 -4
- data/lib/capybara/spec/session/has_xpath_spec.rb +32 -29
- data/lib/capybara/spec/session/headers.rb +2 -1
- data/lib/capybara/spec/session/html_spec.rb +4 -3
- data/lib/capybara/spec/session/node_spec.rb +198 -38
- data/lib/capybara/spec/session/refresh_spec.rb +28 -0
- data/lib/capybara/spec/session/reset_session_spec.rb +46 -5
- data/lib/capybara/spec/session/response_code.rb +2 -1
- data/lib/capybara/spec/session/save_and_open_page_spec.rb +1 -0
- data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +6 -5
- data/lib/capybara/spec/session/save_page_spec.rb +34 -2
- data/lib/capybara/spec/session/save_screenshot_spec.rb +31 -1
- data/lib/capybara/spec/session/screenshot_spec.rb +4 -2
- data/lib/capybara/spec/session/select_spec.rb +34 -32
- data/lib/capybara/spec/session/selectors_spec.rb +65 -0
- data/lib/capybara/spec/session/sibling_spec.rb +52 -0
- data/lib/capybara/spec/session/text_spec.rb +4 -4
- data/lib/capybara/spec/session/title_spec.rb +2 -1
- data/lib/capybara/spec/session/uncheck_spec.rb +42 -2
- data/lib/capybara/spec/session/unselect_spec.rb +17 -16
- data/lib/capybara/spec/session/visit_spec.rb +77 -2
- data/lib/capybara/spec/session/window/become_closed_spec.rb +12 -11
- data/lib/capybara/spec/session/window/current_window_spec.rb +1 -0
- data/lib/capybara/spec/session/window/open_new_window_spec.rb +1 -0
- data/lib/capybara/spec/session/window/switch_to_window_spec.rb +16 -11
- data/lib/capybara/spec/session/window/window_opened_by_spec.rb +7 -4
- data/lib/capybara/spec/session/window/window_spec.rb +36 -29
- data/lib/capybara/spec/session/window/windows_spec.rb +1 -0
- data/lib/capybara/spec/session/window/within_window_spec.rb +31 -7
- data/lib/capybara/spec/session/within_spec.rb +14 -6
- data/lib/capybara/spec/spec_helper.rb +37 -4
- data/lib/capybara/spec/test_app.rb +15 -3
- data/lib/capybara/spec/views/buttons.erb +1 -0
- data/lib/capybara/spec/views/fieldsets.erb +2 -1
- data/lib/capybara/spec/views/form.erb +169 -9
- data/lib/capybara/spec/views/frame_child.erb +10 -2
- data/lib/capybara/spec/views/frame_one.erb +2 -1
- data/lib/capybara/spec/views/frame_parent.erb +3 -2
- data/lib/capybara/spec/views/frame_two.erb +2 -1
- data/lib/capybara/spec/views/header_links.erb +1 -0
- data/lib/capybara/spec/views/host_links.erb +1 -0
- data/lib/capybara/spec/views/initial_alert.erb +10 -0
- data/lib/capybara/spec/views/path.erb +1 -0
- data/lib/capybara/spec/views/popup_one.erb +1 -0
- data/lib/capybara/spec/views/popup_two.erb +1 -0
- data/lib/capybara/spec/views/postback.erb +2 -1
- data/lib/capybara/spec/views/tables.erb +1 -0
- data/lib/capybara/spec/views/with_base_tag.erb +1 -0
- data/lib/capybara/spec/views/with_count.erb +2 -1
- data/lib/capybara/spec/views/with_fixed_header_footer.erb +17 -0
- data/lib/capybara/spec/views/with_hover.erb +7 -1
- data/lib/capybara/spec/views/with_html.erb +40 -2
- data/lib/capybara/spec/views/with_html_entities.erb +1 -0
- data/lib/capybara/spec/views/with_js.erb +32 -1
- data/lib/capybara/spec/views/with_scope.erb +1 -0
- data/lib/capybara/spec/views/with_simple_html.erb +2 -1
- data/lib/capybara/spec/views/with_slow_unload.erb +17 -0
- data/lib/capybara/spec/views/with_title.erb +2 -1
- data/lib/capybara/spec/views/with_unload_alert.erb +14 -0
- data/lib/capybara/spec/views/with_windows.erb +7 -0
- data/lib/capybara/spec/views/within_frames.erb +3 -2
- data/lib/capybara/version.rb +2 -1
- data/lib/capybara/window.rb +20 -3
- data/lib/capybara.rb +189 -93
- data/spec/basic_node_spec.rb +7 -6
- data/spec/capybara_spec.rb +90 -4
- data/spec/dsl_spec.rb +3 -1
- data/spec/filter_set_spec.rb +28 -0
- data/spec/fixtures/capybara.csv +1 -0
- data/spec/fixtures/selenium_driver_rspec_failure.rb +5 -1
- data/spec/fixtures/selenium_driver_rspec_success.rb +5 -1
- data/spec/minitest_spec.rb +130 -0
- data/spec/minitest_spec_spec.rb +135 -0
- data/spec/per_session_config_spec.rb +67 -0
- data/spec/rack_test_spec.rb +50 -7
- data/spec/result_spec.rb +76 -0
- data/spec/rspec/features_spec.rb +21 -8
- data/spec/rspec/scenarios_spec.rb +21 -0
- data/spec/rspec/{matchers_spec.rb → shared_spec_matchers.rb} +160 -54
- data/spec/rspec/views_spec.rb +5 -0
- data/spec/rspec_matchers_spec.rb +46 -0
- data/spec/rspec_spec.rb +79 -1
- data/spec/selector_spec.rb +199 -0
- data/spec/selenium_spec_chrome.rb +54 -9
- data/spec/selenium_spec_firefox.rb +68 -0
- data/spec/selenium_spec_marionette.rb +127 -0
- data/spec/server_spec.rb +102 -14
- data/spec/session_spec.rb +54 -0
- data/spec/shared_selenium_session.rb +215 -0
- data/spec/spec_helper.rb +7 -0
- metadata +140 -15
- 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
|
-
|
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
|
-
|
28
|
-
|
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
|
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
|
-
|
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
|
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
|
-
|
96
|
-
|
97
|
-
#
|
98
|
-
|
99
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
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
|
-
|
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(
|
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(
|
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
|
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: (
|
273
|
-
ignore:
|
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
|