capybara 3.13.2 → 3.40.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.
- checksums.yaml +4 -4
- data/.yardopts +1 -0
- data/History.md +587 -16
- data/README.md +240 -90
- data/lib/capybara/config.rb +24 -11
- data/lib/capybara/cucumber.rb +1 -1
- data/lib/capybara/driver/base.rb +8 -0
- data/lib/capybara/driver/node.rb +20 -4
- data/lib/capybara/dsl.rb +5 -3
- data/lib/capybara/helpers.rb +25 -4
- data/lib/capybara/minitest/spec.rb +174 -90
- data/lib/capybara/minitest.rb +256 -142
- data/lib/capybara/node/actions.rb +123 -77
- data/lib/capybara/node/base.rb +20 -12
- data/lib/capybara/node/document.rb +2 -2
- data/lib/capybara/node/document_matchers.rb +3 -3
- data/lib/capybara/node/element.rb +223 -117
- data/lib/capybara/node/finders.rb +81 -71
- data/lib/capybara/node/matchers.rb +271 -134
- data/lib/capybara/node/simple.rb +18 -5
- data/lib/capybara/node/whitespace_normalizer.rb +81 -0
- data/lib/capybara/queries/active_element_query.rb +18 -0
- data/lib/capybara/queries/ancestor_query.rb +8 -9
- data/lib/capybara/queries/base_query.rb +3 -2
- data/lib/capybara/queries/current_path_query.rb +15 -5
- data/lib/capybara/queries/selector_query.rb +364 -54
- data/lib/capybara/queries/sibling_query.rb +8 -6
- data/lib/capybara/queries/style_query.rb +2 -2
- data/lib/capybara/queries/text_query.rb +13 -1
- data/lib/capybara/queries/title_query.rb +1 -1
- data/lib/capybara/rack_test/browser.rb +76 -11
- data/lib/capybara/rack_test/driver.rb +10 -5
- data/lib/capybara/rack_test/errors.rb +6 -0
- data/lib/capybara/rack_test/form.rb +31 -9
- data/lib/capybara/rack_test/node.rb +74 -23
- data/lib/capybara/registration_container.rb +41 -0
- data/lib/capybara/registrations/drivers.rb +42 -0
- data/lib/capybara/registrations/patches/puma_ssl.rb +29 -0
- data/lib/capybara/registrations/servers.rb +66 -0
- data/lib/capybara/result.rb +44 -20
- data/lib/capybara/rspec/matcher_proxies.rb +13 -11
- data/lib/capybara/rspec/matchers/base.rb +31 -16
- data/lib/capybara/rspec/matchers/compound.rb +1 -1
- data/lib/capybara/rspec/matchers/count_sugar.rb +37 -0
- data/lib/capybara/rspec/matchers/have_ancestor.rb +28 -0
- data/lib/capybara/rspec/matchers/have_current_path.rb +2 -2
- data/lib/capybara/rspec/matchers/have_selector.rb +21 -21
- data/lib/capybara/rspec/matchers/have_sibling.rb +27 -0
- data/lib/capybara/rspec/matchers/have_text.rb +4 -4
- data/lib/capybara/rspec/matchers/have_title.rb +2 -2
- data/lib/capybara/rspec/matchers/match_selector.rb +3 -3
- data/lib/capybara/rspec/matchers/match_style.rb +7 -2
- data/lib/capybara/rspec/matchers/spatial_sugar.rb +39 -0
- data/lib/capybara/rspec/matchers.rb +111 -68
- data/lib/capybara/rspec.rb +2 -0
- data/lib/capybara/selector/builders/css_builder.rb +11 -7
- data/lib/capybara/selector/builders/xpath_builder.rb +5 -3
- data/lib/capybara/selector/css.rb +11 -9
- data/lib/capybara/selector/definition/button.rb +68 -0
- data/lib/capybara/selector/definition/checkbox.rb +26 -0
- data/lib/capybara/selector/definition/css.rb +10 -0
- data/lib/capybara/selector/definition/datalist_input.rb +35 -0
- data/lib/capybara/selector/definition/datalist_option.rb +25 -0
- data/lib/capybara/selector/definition/element.rb +28 -0
- data/lib/capybara/selector/definition/field.rb +40 -0
- data/lib/capybara/selector/definition/fieldset.rb +14 -0
- data/lib/capybara/selector/definition/file_field.rb +13 -0
- data/lib/capybara/selector/definition/fillable_field.rb +33 -0
- data/lib/capybara/selector/definition/frame.rb +17 -0
- data/lib/capybara/selector/definition/id.rb +6 -0
- data/lib/capybara/selector/definition/label.rb +62 -0
- data/lib/capybara/selector/definition/link.rb +55 -0
- data/lib/capybara/selector/definition/link_or_button.rb +16 -0
- data/lib/capybara/selector/definition/option.rb +27 -0
- data/lib/capybara/selector/definition/radio_button.rb +27 -0
- data/lib/capybara/selector/definition/select.rb +81 -0
- data/lib/capybara/selector/definition/table.rb +109 -0
- data/lib/capybara/selector/definition/table_row.rb +21 -0
- data/lib/capybara/selector/definition/xpath.rb +5 -0
- data/lib/capybara/selector/definition.rb +280 -0
- data/lib/capybara/selector/filter_set.rb +19 -18
- data/lib/capybara/selector/filters/base.rb +11 -2
- data/lib/capybara/selector/filters/locator_filter.rb +13 -3
- data/lib/capybara/selector/regexp_disassembler.rb +11 -7
- data/lib/capybara/selector/selector.rb +50 -440
- data/lib/capybara/selector/xpath_extensions.rb +17 -0
- data/lib/capybara/selector.rb +473 -482
- data/lib/capybara/selenium/atoms/getAttribute.min.js +1 -0
- data/lib/capybara/selenium/atoms/isDisplayed.min.js +1 -0
- data/lib/capybara/selenium/atoms/src/getAttribute.js +161 -0
- data/lib/capybara/selenium/atoms/src/isDisplayed.js +454 -0
- data/lib/capybara/selenium/driver.rb +174 -62
- data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +74 -18
- data/lib/capybara/selenium/driver_specializations/edge_driver.rb +128 -0
- data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +37 -3
- data/lib/capybara/selenium/driver_specializations/internet_explorer_driver.rb +14 -1
- data/lib/capybara/selenium/driver_specializations/safari_driver.rb +24 -0
- data/lib/capybara/selenium/extensions/file_input_click_emulation.rb +34 -0
- data/lib/capybara/selenium/extensions/find.rb +68 -45
- data/lib/capybara/selenium/extensions/html5_drag.rb +192 -22
- data/lib/capybara/selenium/extensions/modifier_keys_stack.rb +28 -0
- data/lib/capybara/selenium/extensions/scroll.rb +8 -10
- data/lib/capybara/selenium/node.rb +268 -72
- data/lib/capybara/selenium/nodes/chrome_node.rb +105 -9
- data/lib/capybara/selenium/nodes/edge_node.rb +110 -0
- data/lib/capybara/selenium/nodes/firefox_node.rb +51 -61
- data/lib/capybara/selenium/nodes/ie_node.rb +22 -0
- data/lib/capybara/selenium/nodes/safari_node.rb +118 -0
- data/lib/capybara/selenium/patches/atoms.rb +18 -0
- data/lib/capybara/selenium/patches/is_displayed.rb +16 -0
- data/lib/capybara/selenium/patches/logs.rb +45 -0
- data/lib/capybara/selenium/patches/pause_duration_fix.rb +1 -1
- data/lib/capybara/selenium/patches/persistent_client.rb +20 -0
- data/lib/capybara/server/animation_disabler.rb +43 -21
- data/lib/capybara/server/checker.rb +6 -2
- data/lib/capybara/server/middleware.rb +25 -13
- data/lib/capybara/server.rb +20 -4
- data/lib/capybara/session/config.rb +15 -11
- data/lib/capybara/session/matchers.rb +11 -11
- data/lib/capybara/session.rb +162 -131
- data/lib/capybara/spec/public/offset.js +6 -0
- data/lib/capybara/spec/public/test.js +105 -6
- data/lib/capybara/spec/session/accept_alert_spec.rb +1 -1
- data/lib/capybara/spec/session/active_element_spec.rb +31 -0
- data/lib/capybara/spec/session/all_spec.rb +89 -15
- data/lib/capybara/spec/session/ancestor_spec.rb +5 -0
- data/lib/capybara/spec/session/assert_current_path_spec.rb +5 -2
- data/lib/capybara/spec/session/assert_text_spec.rb +26 -22
- data/lib/capybara/spec/session/attach_file_spec.rb +64 -31
- data/lib/capybara/spec/session/check_spec.rb +26 -4
- data/lib/capybara/spec/session/choose_spec.rb +14 -2
- data/lib/capybara/spec/session/click_button_spec.rb +109 -61
- data/lib/capybara/spec/session/click_link_or_button_spec.rb +9 -0
- data/lib/capybara/spec/session/click_link_spec.rb +23 -1
- data/lib/capybara/spec/session/current_scope_spec.rb +1 -1
- data/lib/capybara/spec/session/current_url_spec.rb +11 -1
- data/lib/capybara/spec/session/element/matches_selector_spec.rb +40 -39
- data/lib/capybara/spec/session/evaluate_script_spec.rb +12 -0
- data/lib/capybara/spec/session/fill_in_spec.rb +46 -5
- data/lib/capybara/spec/session/find_link_spec.rb +10 -0
- data/lib/capybara/spec/session/find_spec.rb +80 -7
- data/lib/capybara/spec/session/first_spec.rb +2 -2
- data/lib/capybara/spec/session/frame/switch_to_frame_spec.rb +14 -1
- data/lib/capybara/spec/session/frame/within_frame_spec.rb +14 -1
- data/lib/capybara/spec/session/has_all_selectors_spec.rb +5 -5
- data/lib/capybara/spec/session/has_ancestor_spec.rb +46 -0
- data/lib/capybara/spec/session/has_any_selectors_spec.rb +6 -2
- data/lib/capybara/spec/session/has_button_spec.rb +81 -0
- data/lib/capybara/spec/session/has_css_spec.rb +45 -8
- data/lib/capybara/spec/session/has_current_path_spec.rb +22 -7
- data/lib/capybara/spec/session/has_element_spec.rb +47 -0
- data/lib/capybara/spec/session/has_field_spec.rb +59 -1
- data/lib/capybara/spec/session/has_link_spec.rb +40 -0
- data/lib/capybara/spec/session/has_none_selectors_spec.rb +7 -7
- data/lib/capybara/spec/session/has_select_spec.rb +42 -8
- data/lib/capybara/spec/session/has_selector_spec.rb +19 -4
- data/lib/capybara/spec/session/has_sibling_spec.rb +50 -0
- data/lib/capybara/spec/session/has_table_spec.rb +177 -0
- data/lib/capybara/spec/session/has_text_spec.rb +31 -3
- data/lib/capybara/spec/session/html_spec.rb +1 -1
- data/lib/capybara/spec/session/matches_style_spec.rb +6 -4
- data/lib/capybara/spec/session/node_spec.rb +697 -23
- data/lib/capybara/spec/session/node_wrapper_spec.rb +1 -1
- data/lib/capybara/spec/session/refresh_spec.rb +2 -1
- data/lib/capybara/spec/session/reset_session_spec.rb +21 -7
- data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +2 -2
- data/lib/capybara/spec/session/save_page_spec.rb +4 -4
- data/lib/capybara/spec/session/save_screenshot_spec.rb +4 -4
- data/lib/capybara/spec/session/scroll_spec.rb +9 -7
- data/lib/capybara/spec/session/select_spec.rb +5 -10
- data/lib/capybara/spec/session/selectors_spec.rb +24 -3
- data/lib/capybara/spec/session/uncheck_spec.rb +3 -3
- data/lib/capybara/spec/session/unselect_spec.rb +1 -1
- data/lib/capybara/spec/session/visit_spec.rb +20 -0
- data/lib/capybara/spec/session/window/become_closed_spec.rb +20 -17
- data/lib/capybara/spec/session/window/switch_to_window_spec.rb +1 -1
- data/lib/capybara/spec/session/window/window_opened_by_spec.rb +1 -1
- data/lib/capybara/spec/session/window/window_spec.rb +54 -57
- data/lib/capybara/spec/session/window/windows_spec.rb +2 -2
- data/lib/capybara/spec/session/within_spec.rb +36 -0
- data/lib/capybara/spec/spec_helper.rb +30 -19
- data/lib/capybara/spec/test_app.rb +122 -34
- data/lib/capybara/spec/views/animated.erb +49 -0
- data/lib/capybara/spec/views/form.erb +86 -8
- data/lib/capybara/spec/views/frame_child.erb +3 -2
- data/lib/capybara/spec/views/frame_one.erb +2 -1
- data/lib/capybara/spec/views/frame_parent.erb +1 -1
- data/lib/capybara/spec/views/frame_two.erb +1 -1
- data/lib/capybara/spec/views/initial_alert.erb +2 -1
- data/lib/capybara/spec/views/layout.erb +10 -0
- data/lib/capybara/spec/views/obscured.erb +10 -10
- data/lib/capybara/spec/views/offset.erb +33 -0
- data/lib/capybara/spec/views/path.erb +2 -2
- data/lib/capybara/spec/views/popup_one.erb +1 -1
- data/lib/capybara/spec/views/popup_two.erb +1 -1
- data/lib/capybara/spec/views/react.erb +45 -0
- data/lib/capybara/spec/views/scroll.erb +2 -1
- data/lib/capybara/spec/views/spatial.erb +31 -0
- data/lib/capybara/spec/views/tables.erb +67 -0
- data/lib/capybara/spec/views/with_animation.erb +39 -4
- data/lib/capybara/spec/views/with_base_tag.erb +2 -2
- data/lib/capybara/spec/views/with_dragula.erb +24 -0
- data/lib/capybara/spec/views/with_fixed_header_footer.erb +2 -1
- data/lib/capybara/spec/views/with_hover.erb +3 -2
- data/lib/capybara/spec/views/with_hover1.erb +10 -0
- data/lib/capybara/spec/views/with_html.erb +34 -6
- data/lib/capybara/spec/views/with_jquery_animation.erb +24 -0
- data/lib/capybara/spec/views/with_js.erb +7 -4
- data/lib/capybara/spec/views/with_jstree.erb +26 -0
- data/lib/capybara/spec/views/with_namespace.erb +1 -0
- data/lib/capybara/spec/views/with_scope.erb +2 -2
- data/lib/capybara/spec/views/with_scope_other.erb +6 -0
- data/lib/capybara/spec/views/with_shadow.erb +31 -0
- data/lib/capybara/spec/views/with_slow_unload.erb +2 -1
- data/lib/capybara/spec/views/with_sortable_js.erb +21 -0
- data/lib/capybara/spec/views/with_unload_alert.erb +1 -0
- data/lib/capybara/spec/views/with_windows.erb +1 -1
- data/lib/capybara/spec/views/within_frames.erb +1 -1
- data/lib/capybara/version.rb +1 -1
- data/lib/capybara/window.rb +14 -18
- data/lib/capybara.rb +91 -126
- data/spec/basic_node_spec.rb +30 -16
- data/spec/capybara_spec.rb +40 -28
- data/spec/counter_spec.rb +35 -0
- data/spec/css_builder_spec.rb +3 -1
- data/spec/css_splitter_spec.rb +1 -1
- data/spec/dsl_spec.rb +33 -22
- data/spec/filter_set_spec.rb +5 -5
- data/spec/fixtures/selenium_driver_rspec_failure.rb +3 -3
- data/spec/fixtures/selenium_driver_rspec_success.rb +3 -3
- data/spec/minitest_spec.rb +24 -2
- data/spec/minitest_spec_spec.rb +60 -45
- data/spec/per_session_config_spec.rb +1 -1
- data/spec/rack_test_spec.rb +131 -98
- data/spec/regexp_dissassembler_spec.rb +53 -39
- data/spec/result_spec.rb +68 -66
- data/spec/rspec/features_spec.rb +9 -4
- data/spec/rspec/scenarios_spec.rb +6 -2
- data/spec/rspec/shared_spec_matchers.rb +137 -98
- data/spec/rspec_matchers_spec.rb +25 -0
- data/spec/rspec_spec.rb +23 -21
- data/spec/sauce_spec_chrome.rb +43 -0
- data/spec/selector_spec.rb +77 -21
- data/spec/selenium_spec_chrome.rb +141 -39
- data/spec/selenium_spec_chrome_remote.rb +32 -17
- data/spec/selenium_spec_edge.rb +36 -8
- data/spec/selenium_spec_firefox.rb +110 -68
- data/spec/selenium_spec_firefox_remote.rb +22 -15
- data/spec/selenium_spec_ie.rb +29 -22
- data/spec/selenium_spec_safari.rb +162 -0
- data/spec/server_spec.rb +153 -81
- data/spec/session_spec.rb +11 -4
- data/spec/shared_selenium_node.rb +79 -0
- data/spec/shared_selenium_session.rb +179 -74
- data/spec/spec_helper.rb +80 -5
- data/spec/whitespace_normalizer_spec.rb +54 -0
- data/spec/xpath_builder_spec.rb +3 -1
- metadata +218 -30
- data/lib/capybara/spec/session/source_spec.rb +0 -0
- data/lib/capybara/spec/views/with_title.erb +0 -5
@@ -11,33 +11,77 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
11
11
|
clear_local_storage: nil,
|
12
12
|
clear_session_storage: nil
|
13
13
|
}.freeze
|
14
|
-
SPECIAL_OPTIONS = %i[browser clear_local_storage clear_session_storage timeout].freeze
|
14
|
+
SPECIAL_OPTIONS = %i[browser clear_local_storage clear_session_storage timeout native_displayed].freeze
|
15
|
+
CAPS_VERSION = Gem::Requirement.new('< 4.8.0')
|
16
|
+
|
15
17
|
attr_reader :app, :options
|
16
18
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
19
|
+
class << self
|
20
|
+
attr_reader :selenium_webdriver_version
|
21
|
+
|
22
|
+
def load_selenium
|
23
|
+
require 'selenium-webdriver'
|
24
|
+
require 'capybara/selenium/patches/atoms'
|
25
|
+
require 'capybara/selenium/patches/is_displayed'
|
26
|
+
|
27
|
+
# Look up the version of `selenium-webdriver` to
|
28
|
+
# see if it's a version we support.
|
29
|
+
#
|
30
|
+
# By default, we use Gem.loaded_specs to determine
|
31
|
+
# the version number. However, in some cases, such
|
32
|
+
# as when loading `selenium-webdriver` outside of
|
33
|
+
# Rubygems, we fall back to referencing
|
34
|
+
# Selenium::WebDriver::VERSION. Ideally we'd
|
35
|
+
# use the constant in all cases, but earlier versions
|
36
|
+
# of `selenium-webdriver` didn't provide the constant.
|
37
|
+
@selenium_webdriver_version =
|
38
|
+
if Gem.loaded_specs['selenium-webdriver']
|
39
|
+
Gem.loaded_specs['selenium-webdriver'].version
|
40
|
+
else
|
41
|
+
Gem::Version.new(Selenium::WebDriver::VERSION)
|
42
|
+
end
|
43
|
+
|
44
|
+
unless Gem::Requirement.new('>= 4.8').satisfied_by? @selenium_webdriver_version
|
45
|
+
warn "Warning: You're using an unsupported version of selenium-webdriver, please upgrade to 4.8+."
|
46
|
+
end
|
47
|
+
|
48
|
+
@selenium_webdriver_version
|
49
|
+
rescue LoadError => e
|
50
|
+
raise e unless e.message.include?('selenium-webdriver')
|
51
|
+
|
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
|
+
end
|
54
|
+
|
55
|
+
attr_reader :specializations
|
22
56
|
|
23
|
-
|
57
|
+
def register_specialization(browser_name, specialization)
|
58
|
+
@specializations ||= {}
|
59
|
+
@specializations[browser_name] = specialization
|
60
|
+
end
|
24
61
|
end
|
25
62
|
|
26
63
|
def browser
|
27
|
-
@browser
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
64
|
+
unless @browser
|
65
|
+
options[:http_client] ||= begin
|
66
|
+
require 'capybara/selenium/patches/persistent_client'
|
67
|
+
if options[:timeout]
|
68
|
+
::Capybara::Selenium::PersistentClient.new(read_timeout: options[:timeout])
|
69
|
+
else
|
70
|
+
::Capybara::Selenium::PersistentClient.new
|
71
|
+
end
|
35
72
|
end
|
73
|
+
processed_options = options.except(*SPECIAL_OPTIONS)
|
74
|
+
|
75
|
+
@browser = Selenium::WebDriver.for(options[:browser], processed_options)
|
76
|
+
|
77
|
+
specialize_driver
|
78
|
+
setup_exit_handler
|
36
79
|
end
|
37
80
|
@browser
|
38
81
|
end
|
39
82
|
|
40
83
|
def initialize(app, **options)
|
84
|
+
super()
|
41
85
|
self.class.load_selenium
|
42
86
|
@app = app
|
43
87
|
@browser = nil
|
@@ -65,6 +109,8 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
65
109
|
|
66
110
|
def html
|
67
111
|
browser.page_source
|
112
|
+
rescue Selenium::WebDriver::Error::JavascriptError => e
|
113
|
+
raise unless e.message.include?('documentElement is null')
|
68
114
|
end
|
69
115
|
|
70
116
|
def title
|
@@ -93,8 +139,17 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
93
139
|
unwrap_script_result(result)
|
94
140
|
end
|
95
141
|
|
96
|
-
def
|
97
|
-
|
142
|
+
def active_element
|
143
|
+
build_node(native_active_element)
|
144
|
+
end
|
145
|
+
|
146
|
+
def send_keys(*args)
|
147
|
+
# Should this call the specialized nodes rather than native???
|
148
|
+
native_active_element.send_keys(*args)
|
149
|
+
end
|
150
|
+
|
151
|
+
def save_screenshot(path, **options)
|
152
|
+
browser.save_screenshot(path, **options)
|
98
153
|
end
|
99
154
|
|
100
155
|
def reset!
|
@@ -110,7 +165,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
110
165
|
navigated = true
|
111
166
|
# Ensure the page is empty and trigger an UnhandledAlertError for any modals that appear during unload
|
112
167
|
wait_for_empty_page(timer)
|
113
|
-
rescue
|
168
|
+
rescue *unhandled_alert_errors
|
114
169
|
# This error is thrown if an unhandled alert is on the page
|
115
170
|
# Firefox appears to automatically dismiss this alert, chrome does not
|
116
171
|
# We'll try to accept it
|
@@ -120,6 +175,18 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
120
175
|
end
|
121
176
|
end
|
122
177
|
|
178
|
+
def frame_obscured_at?(x:, y:)
|
179
|
+
frame = @frame_handles[current_window_handle].last
|
180
|
+
return false unless frame
|
181
|
+
|
182
|
+
switch_to_frame(:parent)
|
183
|
+
begin
|
184
|
+
frame.base.obscured?(x: x, y: y)
|
185
|
+
ensure
|
186
|
+
switch_to_frame(frame)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
123
190
|
def switch_to_frame(frame)
|
124
191
|
handles = @frame_handles[current_window_handle]
|
125
192
|
case frame
|
@@ -130,7 +197,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
130
197
|
handles.pop
|
131
198
|
browser.switch_to.parent_frame
|
132
199
|
else
|
133
|
-
handles << frame
|
200
|
+
handles << frame
|
134
201
|
browser.switch_to.frame(frame.native)
|
135
202
|
end
|
136
203
|
end
|
@@ -177,7 +244,16 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
177
244
|
browser.window_handles
|
178
245
|
end
|
179
246
|
|
180
|
-
def open_new_window
|
247
|
+
def open_new_window(kind = :tab)
|
248
|
+
if browser.switch_to.respond_to?(:new_window)
|
249
|
+
handle = current_window_handle
|
250
|
+
browser.switch_to.new_window(kind)
|
251
|
+
switch_to_window(handle)
|
252
|
+
else
|
253
|
+
browser.manage.new_window(kind)
|
254
|
+
end
|
255
|
+
rescue NoMethodError, Selenium::WebDriver::Error::WebDriverError
|
256
|
+
# If not supported by the driver or browser default to using JS
|
181
257
|
browser.execute_script('window.open();')
|
182
258
|
end
|
183
259
|
|
@@ -187,7 +263,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
187
263
|
|
188
264
|
def accept_modal(_type, **options)
|
189
265
|
yield if block_given?
|
190
|
-
modal = find_modal(options)
|
266
|
+
modal = find_modal(**options)
|
191
267
|
|
192
268
|
modal.send_keys options[:with] if options[:with]
|
193
269
|
|
@@ -198,7 +274,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
198
274
|
|
199
275
|
def dismiss_modal(_type, **options)
|
200
276
|
yield if block_given?
|
201
|
-
modal = find_modal(options)
|
277
|
+
modal = find_modal(**options)
|
202
278
|
message = modal.text
|
203
279
|
modal.dismiss
|
204
280
|
message
|
@@ -206,31 +282,32 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
206
282
|
|
207
283
|
def quit
|
208
284
|
@browser&.quit
|
209
|
-
rescue Selenium::WebDriver::Error::SessionNotCreatedError, Errno::ECONNREFUSED
|
285
|
+
rescue Selenium::WebDriver::Error::SessionNotCreatedError, Errno::ECONNREFUSED,
|
286
|
+
Selenium::WebDriver::Error::InvalidSessionIdError
|
210
287
|
# Browser must have already gone
|
211
|
-
rescue Selenium::WebDriver::Error::UnknownError =>
|
212
|
-
unless silenced_unknown_error_message?(
|
288
|
+
rescue Selenium::WebDriver::Error::UnknownError => e
|
289
|
+
unless silenced_unknown_error_message?(e.message) # Most likely already gone
|
213
290
|
# probably already gone but not sure - so warn
|
214
|
-
warn "Ignoring Selenium UnknownError during driver quit: #{
|
291
|
+
warn "Ignoring Selenium UnknownError during driver quit: #{e.message}"
|
215
292
|
end
|
216
293
|
ensure
|
217
294
|
@browser = nil
|
218
295
|
end
|
219
296
|
|
220
297
|
def invalid_element_errors
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
298
|
+
@invalid_element_errors ||=
|
299
|
+
[
|
300
|
+
::Selenium::WebDriver::Error::StaleElementReferenceError,
|
301
|
+
::Selenium::WebDriver::Error::ElementNotInteractableError,
|
302
|
+
::Selenium::WebDriver::Error::InvalidSelectorError, # Work around chromedriver go_back/go_forward race condition
|
303
|
+
::Selenium::WebDriver::Error::ElementClickInterceptedError,
|
304
|
+
::Selenium::WebDriver::Error::NoSuchElementError, # IE
|
305
|
+
::Selenium::WebDriver::Error::InvalidArgumentError # IE
|
306
|
+
].tap do |errors|
|
307
|
+
if defined?(::Selenium::WebDriver::Error::DetachedShadowRootError)
|
308
|
+
errors.push(::Selenium::WebDriver::Error::DetachedShadowRootError)
|
309
|
+
end
|
310
|
+
end
|
234
311
|
end
|
235
312
|
|
236
313
|
def no_such_window_error
|
@@ -243,15 +320,27 @@ private
|
|
243
320
|
args.map { |arg| arg.is_a?(Capybara::Selenium::Node) ? arg.native : arg }
|
244
321
|
end
|
245
322
|
|
323
|
+
def native_active_element
|
324
|
+
browser.switch_to.active_element
|
325
|
+
end
|
326
|
+
|
246
327
|
def clear_browser_state
|
247
328
|
delete_all_cookies
|
248
329
|
clear_storage
|
249
|
-
rescue
|
330
|
+
rescue *clear_browser_state_errors
|
250
331
|
# delete_all_cookies fails when we've previously gone
|
251
332
|
# to about:blank, so we rescue this error and do nothing
|
252
333
|
# instead.
|
253
334
|
end
|
254
335
|
|
336
|
+
def clear_browser_state_errors
|
337
|
+
@clear_browser_state_errors ||= [Selenium::WebDriver::Error::UnknownError]
|
338
|
+
end
|
339
|
+
|
340
|
+
def unhandled_alert_errors
|
341
|
+
@unhandled_alert_errors ||= [Selenium::WebDriver::Error::UnexpectedAlertOpenError]
|
342
|
+
end
|
343
|
+
|
255
344
|
def delete_all_cookies
|
256
345
|
@browser.manage.delete_all_cookies
|
257
346
|
end
|
@@ -259,7 +348,7 @@ private
|
|
259
348
|
def clear_storage
|
260
349
|
clear_session_storage unless options[:clear_session_storage] == false
|
261
350
|
clear_local_storage unless options[:clear_local_storage] == false
|
262
|
-
rescue Selenium::WebDriver::Error::JavascriptError
|
351
|
+
rescue Selenium::WebDriver::Error::JavascriptError
|
263
352
|
# session/local storage may not be available if on non-http pages (e.g. about:blank)
|
264
353
|
end
|
265
354
|
|
@@ -267,7 +356,13 @@ private
|
|
267
356
|
if @browser.respond_to? :session_storage
|
268
357
|
@browser.session_storage.clear
|
269
358
|
else
|
270
|
-
|
359
|
+
begin
|
360
|
+
@browser&.execute_script('window.sessionStorage.clear()')
|
361
|
+
rescue # rubocop:disable Style/RescueStandardError
|
362
|
+
unless options[:clear_session_storage].nil?
|
363
|
+
warn 'sessionStorage clear requested but is not supported by this driver'
|
364
|
+
end
|
365
|
+
end
|
271
366
|
end
|
272
367
|
end
|
273
368
|
|
@@ -275,7 +370,13 @@ private
|
|
275
370
|
if @browser.respond_to? :local_storage
|
276
371
|
@browser.local_storage.clear
|
277
372
|
else
|
278
|
-
|
373
|
+
begin
|
374
|
+
@browser&.execute_script('window.localStorage.clear()')
|
375
|
+
rescue # rubocop:disable Style/RescueStandardError
|
376
|
+
unless options[:clear_local_storage].nil?
|
377
|
+
warn 'localStorage clear requested but is not supported by this driver'
|
378
|
+
end
|
379
|
+
end
|
279
380
|
end
|
280
381
|
end
|
281
382
|
|
@@ -283,7 +384,7 @@ private
|
|
283
384
|
@browser.navigate.to(url)
|
284
385
|
sleep 0.1 # slight wait for alert
|
285
386
|
@browser.switch_to.alert.accept
|
286
|
-
rescue modal_error
|
387
|
+
rescue modal_error
|
287
388
|
# alert now gone, should mean navigation happened
|
288
389
|
end
|
289
390
|
|
@@ -313,16 +414,25 @@ private
|
|
313
414
|
begin
|
314
415
|
wait.until do
|
315
416
|
alert = @browser.switch_to.alert
|
316
|
-
regexp = text.is_a?(Regexp) ? text : Regexp.escape(text.to_s)
|
317
|
-
alert.text.match(regexp)
|
417
|
+
regexp = text.is_a?(Regexp) ? text : Regexp.new(Regexp.escape(text.to_s))
|
418
|
+
matched = alert.text.match?(regexp)
|
419
|
+
unless matched
|
420
|
+
raise Capybara::ModalNotFound, "Unable to find modal dialog with #{text} - found '#{alert.text}' instead."
|
421
|
+
end
|
422
|
+
|
423
|
+
alert
|
318
424
|
end
|
319
|
-
rescue
|
425
|
+
rescue *find_modal_errors
|
320
426
|
raise Capybara::ModalNotFound, "Unable to find modal dialog#{" with #{text}" if text}"
|
321
427
|
end
|
322
428
|
end
|
323
429
|
|
430
|
+
def find_modal_errors
|
431
|
+
@find_modal_errors ||= [Selenium::WebDriver::Error::TimeoutError]
|
432
|
+
end
|
433
|
+
|
324
434
|
def silenced_unknown_error_message?(msg)
|
325
|
-
silenced_unknown_error_messages.any? { |regex| msg
|
435
|
+
silenced_unknown_error_messages.any? { |regex| msg.match? regex }
|
326
436
|
end
|
327
437
|
|
328
438
|
def silenced_unknown_error_messages
|
@@ -334,8 +444,8 @@ private
|
|
334
444
|
when Array
|
335
445
|
arg.map { |arr| unwrap_script_result(arr) }
|
336
446
|
when Hash
|
337
|
-
arg.
|
338
|
-
when Selenium::WebDriver::Element
|
447
|
+
arg.transform_values! { |value| unwrap_script_result(value) }
|
448
|
+
when Selenium::WebDriver::Element, Selenium::WebDriver::ShadowRoot
|
339
449
|
build_node(arg)
|
340
450
|
else
|
341
451
|
arg
|
@@ -350,20 +460,15 @@ private
|
|
350
460
|
::Capybara::Selenium::Node.new(self, native_node, initial_cache)
|
351
461
|
end
|
352
462
|
|
353
|
-
def
|
354
|
-
|
355
|
-
when :chrome
|
356
|
-
extend ChromeDriver
|
357
|
-
when :firefox
|
358
|
-
require 'capybara/selenium/patches/pause_duration_fix' if pause_broken?(sel_driver)
|
359
|
-
extend FirefoxDriver if sel_driver.capabilities.is_a?(::Selenium::WebDriver::Remote::W3C::Capabilities)
|
360
|
-
when :ie, :internet_explorer
|
361
|
-
extend InternetExplorerDriver
|
362
|
-
end
|
463
|
+
def bridge
|
464
|
+
browser.send(:bridge)
|
363
465
|
end
|
364
466
|
|
365
|
-
def
|
366
|
-
|
467
|
+
def specialize_driver
|
468
|
+
browser_type = browser.browser
|
469
|
+
Capybara::Selenium::Driver.specializations.select { |k, _v| k === browser_type }.each_value do |specialization| # rubocop:disable Style/CaseEquality
|
470
|
+
extend specialization
|
471
|
+
end
|
367
472
|
end
|
368
473
|
|
369
474
|
def setup_exit_handler
|
@@ -386,6 +491,11 @@ private
|
|
386
491
|
raise Capybara::ExpectationNotMet, 'Timed out waiting for Selenium session reset' if timer.expired?
|
387
492
|
|
388
493
|
sleep 0.01
|
494
|
+
|
495
|
+
# It has been observed that it is possible that asynchronous JS code in
|
496
|
+
# the application under test can navigate the browser away from about:blank
|
497
|
+
# if the timing is just right. Ensure we are still at about:blank...
|
498
|
+
@browser.navigate.to('about:blank') unless current_url == 'about:blank'
|
389
499
|
end
|
390
500
|
end
|
391
501
|
|
@@ -403,3 +513,5 @@ end
|
|
403
513
|
require 'capybara/selenium/driver_specializations/chrome_driver'
|
404
514
|
require 'capybara/selenium/driver_specializations/firefox_driver'
|
405
515
|
require 'capybara/selenium/driver_specializations/internet_explorer_driver'
|
516
|
+
require 'capybara/selenium/driver_specializations/safari_driver'
|
517
|
+
require 'capybara/selenium/driver_specializations/edge_driver'
|
@@ -1,25 +1,31 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'capybara/selenium/nodes/chrome_node'
|
4
|
+
require 'capybara/selenium/patches/logs'
|
4
5
|
|
5
6
|
module Capybara::Selenium::Driver::ChromeDriver
|
7
|
+
def self.extended(base)
|
8
|
+
bridge = base.send(:bridge)
|
9
|
+
bridge.extend Capybara::Selenium::ChromeLogs unless bridge.respond_to?(:log)
|
10
|
+
bridge.extend Capybara::Selenium::IsDisplayed unless bridge.send(:commands, :is_element_displayed)
|
11
|
+
base.options[:native_displayed] = false if base.options[:native_displayed].nil?
|
12
|
+
end
|
13
|
+
|
6
14
|
def fullscreen_window(handle)
|
7
15
|
within_given_window(handle) do
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
result['value']
|
15
|
-
end
|
16
|
+
super
|
17
|
+
rescue NoMethodError => e
|
18
|
+
raise unless e.message.include?('full_screen_window')
|
19
|
+
|
20
|
+
result = bridge.http.call(:post, "session/#{bridge.session_id}/window/fullscreen", {})
|
21
|
+
result['value']
|
16
22
|
end
|
17
23
|
end
|
18
24
|
|
19
25
|
def resize_window_to(handle, width, height)
|
20
26
|
super
|
21
|
-
rescue Selenium::WebDriver::Error::UnknownError =>
|
22
|
-
raise unless
|
27
|
+
rescue Selenium::WebDriver::Error::UnknownError => e
|
28
|
+
raise unless e.message.include?('failed to change window state')
|
23
29
|
|
24
30
|
# Chromedriver doesn't wait long enough for state to change when coming out of fullscreen
|
25
31
|
# and raises unnecessary error. Wait a bit and try again.
|
@@ -32,30 +38,80 @@ module Capybara::Selenium::Driver::ChromeDriver
|
|
32
38
|
return unless @browser
|
33
39
|
|
34
40
|
switch_to_window(window_handles.first)
|
35
|
-
window_handles.slice(1
|
36
|
-
super
|
41
|
+
window_handles.slice(1..).each { |win| close_window(win) }
|
42
|
+
return super if chromedriver_version < 73
|
43
|
+
|
44
|
+
timer = Capybara::Helpers.timer(expire_in: 10)
|
45
|
+
begin
|
46
|
+
clear_storage unless uniform_storage_clear?
|
47
|
+
@browser.navigate.to('about:blank')
|
48
|
+
wait_for_empty_page(timer)
|
49
|
+
rescue *unhandled_alert_errors
|
50
|
+
accept_unhandled_reset_alert
|
51
|
+
retry
|
52
|
+
end
|
53
|
+
|
54
|
+
execute_cdp('Storage.clearDataForOrigin', origin: '*', storageTypes: storage_types_to_clear)
|
37
55
|
end
|
38
56
|
|
39
57
|
private
|
40
58
|
|
59
|
+
def storage_types_to_clear
|
60
|
+
types = ['cookies']
|
61
|
+
types << 'local_storage' if clear_all_storage?
|
62
|
+
types.join(',')
|
63
|
+
end
|
64
|
+
|
65
|
+
def clear_all_storage?
|
66
|
+
storage_clears.none? false
|
67
|
+
end
|
68
|
+
|
69
|
+
def uniform_storage_clear?
|
70
|
+
storage_clears.uniq { |s| s == false }.length <= 1
|
71
|
+
end
|
72
|
+
|
73
|
+
def storage_clears
|
74
|
+
options.values_at(:clear_session_storage, :clear_local_storage)
|
75
|
+
end
|
76
|
+
|
77
|
+
def clear_storage
|
78
|
+
# Chrome errors if attempt to clear storage on about:blank
|
79
|
+
# In W3C mode it crashes chromedriver
|
80
|
+
url = current_url
|
81
|
+
super unless url.nil? || url.start_with?('about:')
|
82
|
+
end
|
83
|
+
|
41
84
|
def delete_all_cookies
|
42
85
|
execute_cdp('Network.clearBrowserCookies')
|
43
|
-
rescue
|
86
|
+
rescue *cdp_unsupported_errors
|
44
87
|
# If the CDP clear isn't supported do original limited clear
|
45
88
|
super
|
46
89
|
end
|
47
90
|
|
91
|
+
def cdp_unsupported_errors
|
92
|
+
@cdp_unsupported_errors ||= [Selenium::WebDriver::Error::WebDriverError]
|
93
|
+
end
|
94
|
+
|
48
95
|
def execute_cdp(cmd, params = {})
|
49
|
-
|
50
|
-
|
51
|
-
|
96
|
+
if browser.respond_to? :execute_cdp
|
97
|
+
browser.execute_cdp(cmd, **params)
|
98
|
+
else
|
99
|
+
args = { cmd: cmd, params: params }
|
100
|
+
result = bridge.http.call(:post, "session/#{bridge.session_id}/goog/cdp/execute", args)
|
101
|
+
result['value']
|
102
|
+
end
|
52
103
|
end
|
53
104
|
|
54
105
|
def build_node(native_node, initial_cache = {})
|
55
106
|
::Capybara::Selenium::ChromeNode.new(self, native_node, initial_cache)
|
56
107
|
end
|
57
108
|
|
58
|
-
def
|
59
|
-
|
109
|
+
def chromedriver_version
|
110
|
+
@chromedriver_version ||= begin
|
111
|
+
caps = browser.capabilities
|
112
|
+
caps['chrome']&.fetch('chromedriverVersion', nil).to_f
|
113
|
+
end
|
60
114
|
end
|
61
115
|
end
|
116
|
+
|
117
|
+
Capybara::Selenium::Driver.register_specialization :chrome, Capybara::Selenium::Driver::ChromeDriver
|
@@ -0,0 +1,128 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'capybara/selenium/nodes/edge_node'
|
4
|
+
|
5
|
+
module Capybara::Selenium::Driver::EdgeDriver
|
6
|
+
def self.extended(base)
|
7
|
+
bridge = base.send(:bridge)
|
8
|
+
bridge.extend Capybara::Selenium::IsDisplayed unless bridge.send(:commands, :is_element_displayed)
|
9
|
+
base.options[:native_displayed] = false if base.options[:native_displayed].nil?
|
10
|
+
end
|
11
|
+
|
12
|
+
def fullscreen_window(handle)
|
13
|
+
return super if edgedriver_version < 75
|
14
|
+
|
15
|
+
within_given_window(handle) do
|
16
|
+
super
|
17
|
+
rescue NoMethodError => e
|
18
|
+
raise unless e.message.include?('full_screen_window')
|
19
|
+
|
20
|
+
result = bridge.http.call(:post, "session/#{bridge.session_id}/window/fullscreen", {})
|
21
|
+
result['value']
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def resize_window_to(handle, width, height)
|
26
|
+
super
|
27
|
+
rescue Selenium::WebDriver::Error::UnknownError => e
|
28
|
+
raise unless e.message.include?('failed to change window state')
|
29
|
+
|
30
|
+
# Chromedriver doesn't wait long enough for state to change when coming out of fullscreen
|
31
|
+
# and raises unnecessary error. Wait a bit and try again.
|
32
|
+
sleep 0.25
|
33
|
+
super
|
34
|
+
end
|
35
|
+
|
36
|
+
def reset!
|
37
|
+
return super if edgedriver_version < 75
|
38
|
+
# Use instance variable directly so we avoid starting the browser just to reset the session
|
39
|
+
return unless @browser
|
40
|
+
|
41
|
+
switch_to_window(window_handles.first)
|
42
|
+
window_handles.slice(1..).each { |win| close_window(win) }
|
43
|
+
|
44
|
+
timer = Capybara::Helpers.timer(expire_in: 10)
|
45
|
+
begin
|
46
|
+
clear_storage unless uniform_storage_clear?
|
47
|
+
@browser.navigate.to('about:blank')
|
48
|
+
wait_for_empty_page(timer)
|
49
|
+
rescue *unhandled_alert_errors
|
50
|
+
accept_unhandled_reset_alert
|
51
|
+
retry
|
52
|
+
end
|
53
|
+
|
54
|
+
execute_cdp('Storage.clearDataForOrigin', origin: '*', storageTypes: storage_types_to_clear)
|
55
|
+
end
|
56
|
+
|
57
|
+
def download_path=(path)
|
58
|
+
if @browser.respond_to?(:download_path=)
|
59
|
+
@browser.download_path = path
|
60
|
+
else
|
61
|
+
# Not yet implemented in seleniun-webdriver for edge so do it ourselves
|
62
|
+
execute_cdp('Page.setDownloadBehavior', behavior: 'allow', downloadPath: path)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def storage_types_to_clear
|
69
|
+
types = ['cookies']
|
70
|
+
types << 'local_storage' if clear_all_storage?
|
71
|
+
types.join(',')
|
72
|
+
end
|
73
|
+
|
74
|
+
def clear_all_storage?
|
75
|
+
storage_clears.none? false
|
76
|
+
end
|
77
|
+
|
78
|
+
def uniform_storage_clear?
|
79
|
+
storage_clears.uniq { |s| s == false }.length <= 1
|
80
|
+
end
|
81
|
+
|
82
|
+
def storage_clears
|
83
|
+
options.values_at(:clear_session_storage, :clear_local_storage)
|
84
|
+
end
|
85
|
+
|
86
|
+
def clear_storage
|
87
|
+
# Edgedriver crashes if attempt to clear storage on about:blank
|
88
|
+
url = current_url
|
89
|
+
super unless url.nil? || url.start_with?('about:')
|
90
|
+
end
|
91
|
+
|
92
|
+
def delete_all_cookies
|
93
|
+
return super if edgedriver_version < 75
|
94
|
+
|
95
|
+
execute_cdp('Network.clearBrowserCookies')
|
96
|
+
rescue *cdp_unsupported_errors
|
97
|
+
# If the CDP clear isn't supported do original limited clear
|
98
|
+
super
|
99
|
+
end
|
100
|
+
|
101
|
+
def cdp_unsupported_errors
|
102
|
+
@cdp_unsupported_errors ||= [Selenium::WebDriver::Error::WebDriverError]
|
103
|
+
end
|
104
|
+
|
105
|
+
def execute_cdp(cmd, params = {})
|
106
|
+
if browser.respond_to? :execute_cdp
|
107
|
+
browser.execute_cdp(cmd, **params)
|
108
|
+
else
|
109
|
+
args = { cmd: cmd, params: params }
|
110
|
+
result = bridge.http.call(:post, "session/#{bridge.session_id}/ms/cdp/execute", args)
|
111
|
+
result['value']
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def build_node(native_node, initial_cache = {})
|
116
|
+
::Capybara::Selenium::EdgeNode.new(self, native_node, initial_cache)
|
117
|
+
end
|
118
|
+
|
119
|
+
def edgedriver_version
|
120
|
+
@edgedriver_version ||= begin
|
121
|
+
caps = browser.capabilities
|
122
|
+
caps['msedge']&.fetch('msedgedriverVersion', nil).to_f
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
Capybara::Selenium::Driver.register_specialization :edge, Capybara::Selenium::Driver::EdgeDriver
|
128
|
+
Capybara::Selenium::Driver.register_specialization :edge_chrome, Capybara::Selenium::Driver::EdgeDriver
|