capybara 3.16.1 → 3.33.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.yardopts +1 -0
- data/History.md +321 -0
- data/README.md +51 -60
- data/lib/capybara.rb +71 -114
- data/lib/capybara/config.rb +8 -5
- data/lib/capybara/cucumber.rb +1 -1
- data/lib/capybara/driver/node.rb +15 -3
- data/lib/capybara/dsl.rb +10 -2
- data/lib/capybara/helpers.rb +5 -3
- data/lib/capybara/minitest.rb +242 -141
- data/lib/capybara/minitest/spec.rb +159 -90
- data/lib/capybara/node/actions.rb +85 -74
- data/lib/capybara/node/base.rb +4 -4
- data/lib/capybara/node/document.rb +2 -2
- data/lib/capybara/node/document_matchers.rb +3 -3
- data/lib/capybara/node/element.rb +216 -117
- data/lib/capybara/node/finders.rb +65 -65
- data/lib/capybara/node/matchers.rb +228 -126
- data/lib/capybara/node/simple.rb +9 -4
- data/lib/capybara/queries/ancestor_query.rb +5 -7
- data/lib/capybara/queries/base_query.rb +2 -1
- data/lib/capybara/queries/current_path_query.rb +1 -1
- data/lib/capybara/queries/selector_query.rb +296 -30
- data/lib/capybara/queries/sibling_query.rb +5 -4
- 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 +7 -2
- data/lib/capybara/rack_test/driver.rb +1 -1
- data/lib/capybara/rack_test/form.rb +1 -1
- data/lib/capybara/rack_test/node.rb +43 -7
- data/lib/capybara/registration_container.rb +44 -0
- data/lib/capybara/registrations/drivers.rb +36 -0
- data/lib/capybara/registrations/patches/puma_ssl.rb +27 -0
- data/lib/capybara/registrations/servers.rb +44 -0
- data/lib/capybara/result.rb +36 -8
- data/lib/capybara/rspec/matcher_proxies.rb +6 -4
- data/lib/capybara/rspec/matchers.rb +100 -63
- data/lib/capybara/rspec/matchers/base.rb +23 -10
- 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 +16 -8
- 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 +2 -2
- data/lib/capybara/rspec/matchers/spatial_sugar.rb +39 -0
- data/lib/capybara/selector.rb +219 -588
- data/lib/capybara/selector/builders/css_builder.rb +10 -6
- data/lib/capybara/selector/builders/xpath_builder.rb +1 -1
- data/lib/capybara/selector/css.rb +4 -2
- data/lib/capybara/selector/definition.rb +277 -0
- data/lib/capybara/selector/definition/button.rb +52 -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 +27 -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 +54 -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/filter_set.rb +13 -9
- 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 +9 -2
- data/lib/capybara/selector/selector.rb +43 -448
- 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 +125 -56
- data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +73 -17
- data/lib/capybara/selenium/driver_specializations/edge_driver.rb +124 -0
- data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +41 -2
- data/lib/capybara/selenium/driver_specializations/internet_explorer_driver.rb +14 -1
- data/lib/capybara/selenium/driver_specializations/safari_driver.rb +14 -5
- data/lib/capybara/selenium/extensions/file_input_click_emulation.rb +34 -0
- data/lib/capybara/selenium/extensions/find.rb +67 -45
- data/lib/capybara/selenium/extensions/html5_drag.rb +152 -36
- data/lib/capybara/selenium/extensions/modifier_keys_stack.rb +28 -0
- data/lib/capybara/selenium/logger_suppressor.rb +34 -0
- data/lib/capybara/selenium/node.rb +227 -56
- data/lib/capybara/selenium/nodes/chrome_node.rb +93 -8
- data/lib/capybara/selenium/nodes/edge_node.rb +104 -0
- data/lib/capybara/selenium/nodes/firefox_node.rb +37 -59
- data/lib/capybara/selenium/nodes/ie_node.rb +22 -0
- data/lib/capybara/selenium/nodes/safari_node.rb +27 -54
- data/lib/capybara/selenium/patches/action_pauser.rb +26 -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/server.rb +19 -3
- data/lib/capybara/server/animation_disabler.rb +2 -2
- data/lib/capybara/server/checker.rb +6 -2
- data/lib/capybara/server/middleware.rb +23 -13
- data/lib/capybara/session.rb +124 -106
- data/lib/capybara/session/config.rb +12 -10
- data/lib/capybara/session/matchers.rb +6 -6
- data/lib/capybara/spec/public/offset.js +6 -0
- data/lib/capybara/spec/public/test.js +94 -5
- data/lib/capybara/spec/session/all_spec.rb +84 -6
- 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 +9 -5
- data/lib/capybara/spec/session/attach_file_spec.rb +14 -6
- data/lib/capybara/spec/session/check_spec.rb +10 -4
- data/lib/capybara/spec/session/choose_spec.rb +8 -2
- data/lib/capybara/spec/session/click_button_spec.rb +44 -1
- data/lib/capybara/spec/session/click_link_spec.rb +11 -0
- data/lib/capybara/spec/session/evaluate_script_spec.rb +12 -0
- data/lib/capybara/spec/session/fill_in_spec.rb +37 -2
- data/lib/capybara/spec/session/find_spec.rb +60 -6
- data/lib/capybara/spec/session/first_spec.rb +1 -1
- data/lib/capybara/spec/session/frame/switch_to_frame_spec.rb +14 -1
- data/lib/capybara/spec/session/frame/within_frame_spec.rb +12 -1
- data/lib/capybara/spec/session/has_ancestor_spec.rb +46 -0
- data/lib/capybara/spec/session/has_button_spec.rb +16 -0
- data/lib/capybara/spec/session/has_css_spec.rb +35 -6
- data/lib/capybara/spec/session/has_current_path_spec.rb +6 -4
- data/lib/capybara/spec/session/has_field_spec.rb +34 -0
- data/lib/capybara/spec/session/has_select_spec.rb +32 -4
- data/lib/capybara/spec/session/has_selector_spec.rb +4 -4
- data/lib/capybara/spec/session/has_sibling_spec.rb +50 -0
- data/lib/capybara/spec/session/has_table_spec.rb +51 -5
- data/lib/capybara/spec/session/has_text_spec.rb +47 -0
- data/lib/capybara/spec/session/matches_style_spec.rb +2 -2
- data/lib/capybara/spec/session/node_spec.rb +574 -16
- data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +2 -2
- data/lib/capybara/spec/session/save_screenshot_spec.rb +4 -4
- data/lib/capybara/spec/session/scroll_spec.rb +1 -1
- 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 +2 -2
- data/lib/capybara/spec/session/unselect_spec.rb +1 -1
- data/lib/capybara/spec/session/window/window_spec.rb +10 -9
- data/lib/capybara/spec/spec_helper.rb +7 -2
- data/lib/capybara/spec/test_app.rb +26 -21
- data/lib/capybara/spec/views/animated.erb +49 -0
- data/lib/capybara/spec/views/form.erb +25 -4
- data/lib/capybara/spec/views/frame_child.erb +2 -1
- data/lib/capybara/spec/views/frame_one.erb +1 -0
- data/lib/capybara/spec/views/obscured.erb +9 -9
- data/lib/capybara/spec/views/offset.erb +32 -0
- data/lib/capybara/spec/views/react.erb +45 -0
- data/lib/capybara/spec/views/spatial.erb +31 -0
- data/lib/capybara/spec/views/with_animation.erb +29 -1
- data/lib/capybara/spec/views/with_dragula.erb +24 -0
- data/lib/capybara/spec/views/with_html.erb +28 -2
- data/lib/capybara/spec/views/with_js.erb +2 -1
- data/lib/capybara/spec/views/with_jstree.erb +26 -0
- data/lib/capybara/spec/views/with_sortable_js.erb +21 -0
- data/lib/capybara/version.rb +1 -1
- data/lib/capybara/window.rb +10 -10
- data/spec/basic_node_spec.rb +6 -6
- data/spec/capybara_spec.rb +28 -28
- data/spec/dsl_spec.rb +16 -3
- data/spec/filter_set_spec.rb +5 -5
- data/spec/fixtures/selenium_driver_rspec_failure.rb +1 -1
- data/spec/fixtures/selenium_driver_rspec_success.rb +1 -1
- data/spec/minitest_spec.rb +12 -2
- data/spec/minitest_spec_spec.rb +56 -45
- data/spec/rack_test_spec.rb +25 -12
- data/spec/regexp_dissassembler_spec.rb +53 -39
- data/spec/result_spec.rb +50 -54
- data/spec/rspec/features_spec.rb +1 -0
- data/spec/rspec/shared_spec_matchers.rb +78 -62
- data/spec/rspec_spec.rb +5 -5
- data/spec/sauce_spec_chrome.rb +1 -0
- data/spec/selector_spec.rb +26 -16
- data/spec/selenium_spec_chrome.rb +84 -5
- data/spec/selenium_spec_chrome_remote.rb +23 -8
- data/spec/selenium_spec_edge.rb +23 -8
- data/spec/selenium_spec_firefox.rb +16 -21
- data/spec/selenium_spec_firefox_remote.rb +4 -13
- data/spec/selenium_spec_ie.rb +23 -15
- data/spec/selenium_spec_safari.rb +17 -17
- data/spec/server_spec.rb +87 -42
- data/spec/session_spec.rb +11 -4
- data/spec/shared_selenium_node.rb +83 -0
- data/spec/shared_selenium_session.rb +62 -72
- data/spec/spec_helper.rb +43 -5
- metadata +114 -16
@@ -1,9 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'capybara/selenium/extensions/html5_drag'
|
4
|
+
require 'capybara/selenium/extensions/file_input_click_emulation'
|
4
5
|
|
5
6
|
class Capybara::Selenium::ChromeNode < Capybara::Selenium::Node
|
6
7
|
include Html5Drag
|
8
|
+
include FileInputClickEmulation
|
7
9
|
|
8
10
|
def set_text(value, clear: nil, **_unused)
|
9
11
|
super.tap do
|
@@ -13,22 +15,105 @@ class Capybara::Selenium::ChromeNode < Capybara::Selenium::Node
|
|
13
15
|
end
|
14
16
|
|
15
17
|
def set_file(value) # rubocop:disable Naming/AccessorMethodName
|
16
|
-
|
17
|
-
|
18
|
-
|
18
|
+
# In Chrome 75+ files are appended (due to WebDriver spec - why?) so we have to clear here if its multiple and already set
|
19
|
+
if browser_version >= 75.0
|
20
|
+
driver.execute_script(<<~JS, self)
|
21
|
+
if (arguments[0].multiple && arguments[0].files.length){
|
22
|
+
arguments[0].value = null;
|
23
|
+
}
|
24
|
+
JS
|
25
|
+
end
|
26
|
+
super
|
27
|
+
rescue *file_errors => e
|
28
|
+
if e.message.match?(/File not found : .+\n.+/m)
|
29
|
+
raise ArgumentError, "Selenium < 3.14 with remote Chrome doesn't support multiple file upload"
|
30
|
+
end
|
19
31
|
|
20
32
|
raise
|
21
33
|
end
|
22
34
|
|
23
|
-
def
|
24
|
-
|
35
|
+
def drop(*args)
|
36
|
+
html5_drop(*args)
|
37
|
+
end
|
38
|
+
|
39
|
+
def click(*, **)
|
40
|
+
super
|
41
|
+
rescue ::Selenium::WebDriver::Error::ElementClickInterceptedError
|
42
|
+
raise
|
43
|
+
rescue ::Selenium::WebDriver::Error::WebDriverError => e
|
44
|
+
# chromedriver 74 (at least on mac) raises the wrong error for this
|
45
|
+
if e.message.match?(/element click intercepted/)
|
46
|
+
raise ::Selenium::WebDriver::Error::ElementClickInterceptedError, e.message
|
47
|
+
end
|
25
48
|
|
26
|
-
|
49
|
+
raise
|
50
|
+
end
|
51
|
+
|
52
|
+
def disabled?
|
53
|
+
driver.evaluate_script("arguments[0].matches(':disabled, select:disabled *')", self)
|
54
|
+
end
|
55
|
+
|
56
|
+
def select_option
|
57
|
+
# To optimize to only one check and then click
|
58
|
+
selected_or_disabled = driver.evaluate_script(<<~JS, self)
|
59
|
+
arguments[0].matches(':disabled, select:disabled *, :checked')
|
60
|
+
JS
|
61
|
+
click unless selected_or_disabled
|
62
|
+
end
|
63
|
+
|
64
|
+
def visible?
|
65
|
+
return super unless native_displayed?
|
66
|
+
|
67
|
+
begin
|
68
|
+
bridge.send(:execute, :is_element_displayed, id: native.ref)
|
69
|
+
rescue Selenium::WebDriver::Error::UnknownCommandError
|
70
|
+
# If the is_element_displayed command is unknown, no point in trying again
|
71
|
+
driver.options[:native_displayed] = false
|
72
|
+
super
|
73
|
+
end
|
27
74
|
end
|
28
75
|
|
29
76
|
private
|
30
77
|
|
31
|
-
def
|
32
|
-
|
78
|
+
def perform_legacy_drag(element, drop_modifiers)
|
79
|
+
return super if chromedriver_fixed_actions_key_state? || !w3c? || element.obscured?
|
80
|
+
|
81
|
+
raise ArgumentError, 'Modifier keys are not supported while dragging in this version of Chrome.' unless drop_modifiers.empty?
|
82
|
+
|
83
|
+
# W3C Chrome/chromedriver < 77 doesn't maintain mouse button state across actions API performs
|
84
|
+
# https://bugs.chromium.org/p/chromedriver/issues/detail?id=2981
|
85
|
+
browser_action.release.perform
|
86
|
+
browser_action.click_and_hold(native).move_to(element.native).release.perform
|
87
|
+
end
|
88
|
+
|
89
|
+
def file_errors
|
90
|
+
@file_errors = ::Selenium::WebDriver.logger.suppress_deprecations do
|
91
|
+
[::Selenium::WebDriver::Error::ExpectedError]
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def browser_version(to_float = true)
|
96
|
+
caps = capabilities
|
97
|
+
ver = (caps[:browser_version] || caps[:version])
|
98
|
+
ver = ver.to_f if to_float
|
99
|
+
ver
|
100
|
+
end
|
101
|
+
|
102
|
+
def chromedriver_fixed_actions_key_state?
|
103
|
+
Gem::Version.new(chromedriver_version) >= Gem::Version.new('76.0.3809.68')
|
104
|
+
end
|
105
|
+
|
106
|
+
def chromedriver_supports_displayed_endpoint?
|
107
|
+
Gem::Version.new(chromedriver_version) >= Gem::Version.new('76.0.3809.25')
|
108
|
+
end
|
109
|
+
|
110
|
+
def chromedriver_version
|
111
|
+
capabilities['chrome']['chromedriverVersion'].split(' ')[0]
|
112
|
+
end
|
113
|
+
|
114
|
+
def native_displayed?
|
115
|
+
(driver.options[:native_displayed] != false) &&
|
116
|
+
(w3c? && chromedriver_supports_displayed_endpoint?) &&
|
117
|
+
(!ENV['DISABLE_CAPYBARA_SELENIUM_OPTIMIZATIONS'])
|
33
118
|
end
|
34
119
|
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'capybara/selenium/extensions/html5_drag'
|
4
|
+
|
5
|
+
class Capybara::Selenium::EdgeNode < Capybara::Selenium::Node
|
6
|
+
include Html5Drag
|
7
|
+
|
8
|
+
def set_text(value, clear: nil, **_unused)
|
9
|
+
return super unless chrome_edge?
|
10
|
+
|
11
|
+
super.tap do
|
12
|
+
# React doesn't see the chromedriver element clear
|
13
|
+
send_keys(:space, :backspace) if value.to_s.empty? && clear.nil?
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def set_file(value) # rubocop:disable Naming/AccessorMethodName
|
18
|
+
# In Chrome 75+ files are appended (due to WebDriver spec - why?) so we have to clear here if its multiple and already set
|
19
|
+
if chrome_edge?
|
20
|
+
driver.execute_script(<<~JS, self)
|
21
|
+
if (arguments[0].multiple && arguments[0].files.length){
|
22
|
+
arguments[0].value = null;
|
23
|
+
}
|
24
|
+
JS
|
25
|
+
end
|
26
|
+
super
|
27
|
+
rescue *file_errors => e
|
28
|
+
if e.message.match?(/File not found : .+\n.+/m)
|
29
|
+
raise ArgumentError, "Selenium < 3.14 with remote Chrome doesn't support multiple file upload"
|
30
|
+
end
|
31
|
+
|
32
|
+
raise
|
33
|
+
end
|
34
|
+
|
35
|
+
def drop(*args)
|
36
|
+
return super unless chrome_edge?
|
37
|
+
|
38
|
+
html5_drop(*args)
|
39
|
+
end
|
40
|
+
|
41
|
+
def click(*)
|
42
|
+
super
|
43
|
+
rescue Selenium::WebDriver::Error::InvalidArgumentError => e
|
44
|
+
tag_name, type = attrs(:tagName, :type).map { |val| val&.downcase }
|
45
|
+
if tag_name == 'input' && type == 'file'
|
46
|
+
raise Selenium::WebDriver::Error::InvalidArgumentError, "EdgeChrome can't click on file inputs.\n#{e.message}"
|
47
|
+
end
|
48
|
+
|
49
|
+
raise
|
50
|
+
end
|
51
|
+
|
52
|
+
def disabled?
|
53
|
+
return super unless chrome_edge?
|
54
|
+
|
55
|
+
driver.evaluate_script("arguments[0].matches(':disabled, select:disabled *')", self)
|
56
|
+
end
|
57
|
+
|
58
|
+
def select_option
|
59
|
+
return super unless chrome_edge?
|
60
|
+
|
61
|
+
# To optimize to only one check and then click
|
62
|
+
selected_or_disabled = driver.evaluate_script(<<~JS, self)
|
63
|
+
arguments[0].matches(':disabled, select:disabled *, :checked')
|
64
|
+
JS
|
65
|
+
click unless selected_or_disabled
|
66
|
+
end
|
67
|
+
|
68
|
+
def visible?
|
69
|
+
return super unless chrome_edge? && native_displayed?
|
70
|
+
|
71
|
+
begin
|
72
|
+
bridge.send(:execute, :is_element_displayed, id: native.ref)
|
73
|
+
rescue Selenium::WebDriver::Error::UnknownCommandError
|
74
|
+
# If the is_element_displayed command is unknown, no point in trying again
|
75
|
+
driver.options[:native_displayed] = false
|
76
|
+
super
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def file_errors
|
83
|
+
@file_errors = ::Selenium::WebDriver.logger.suppress_deprecations do
|
84
|
+
[::Selenium::WebDriver::Error::ExpectedError]
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def browser_version
|
89
|
+
@browser_version ||= begin
|
90
|
+
caps = driver.browser.capabilities
|
91
|
+
(caps[:browser_version] || caps[:version]).to_f
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def chrome_edge?
|
96
|
+
browser_version >= 75
|
97
|
+
end
|
98
|
+
|
99
|
+
def native_displayed?
|
100
|
+
(driver.options[:native_displayed] != false) &&
|
101
|
+
# chromedriver_supports_displayed_endpoint? &&
|
102
|
+
(!ENV['DISABLE_CAPYBARA_SELENIUM_OPTIMIZATIONS'])
|
103
|
+
end
|
104
|
+
end
|
@@ -1,9 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'capybara/selenium/extensions/html5_drag'
|
4
|
+
require 'capybara/selenium/extensions/file_input_click_emulation'
|
4
5
|
|
5
6
|
class Capybara::Selenium::FirefoxNode < Capybara::Selenium::Node
|
6
7
|
include Html5Drag
|
8
|
+
include FileInputClickEmulation
|
7
9
|
|
8
10
|
def click(keys = [], **options)
|
9
11
|
super
|
@@ -12,28 +14,22 @@ class Capybara::Selenium::FirefoxNode < Capybara::Selenium::Node
|
|
12
14
|
warn 'You are attempting to click a table row which has issues in geckodriver/marionette - '\
|
13
15
|
'see https://github.com/mozilla/geckodriver/issues/1228. Your test should probably be '\
|
14
16
|
'clicking on a table cell like a user would. Clicking the first cell in the row instead.'
|
15
|
-
return find_css('th:first-child,td:first-child')[0].click(keys, options)
|
17
|
+
return find_css('th:first-child,td:first-child')[0].click(keys, **options)
|
16
18
|
end
|
17
19
|
raise
|
18
20
|
end
|
19
21
|
|
20
22
|
def disabled?
|
21
|
-
|
22
|
-
return super unless browser_version < 61.0
|
23
|
-
|
24
|
-
return true if super
|
25
|
-
|
26
|
-
# workaround for selenium-webdriver/geckodriver reporting elements as enabled when they are nested in disabling elements
|
27
|
-
if %w[option optgroup].include? tag_name
|
28
|
-
find_xpath('parent::*[self::optgroup or self::select]')[0].disabled?
|
29
|
-
else
|
30
|
-
!find_xpath(DISABLED_BY_FIELDSET_XPATH).empty?
|
31
|
-
end
|
23
|
+
driver.evaluate_script("arguments[0].matches(':disabled, select:disabled *')", self)
|
32
24
|
end
|
33
25
|
|
34
26
|
def set_file(value) # rubocop:disable Naming/AccessorMethodName
|
35
27
|
# By default files are appended so we have to clear here if its multiple and already set
|
36
|
-
|
28
|
+
driver.execute_script(<<~JS, self)
|
29
|
+
if (arguments[0].multiple && arguments[0].files.length){
|
30
|
+
arguments[0].value = null;
|
31
|
+
}
|
32
|
+
JS
|
37
33
|
return super if browser_version >= 62.0
|
38
34
|
|
39
35
|
# Workaround lack of support for multiple upload by uploading one at a time
|
@@ -46,16 +42,14 @@ class Capybara::Selenium::FirefoxNode < Capybara::Selenium::Node
|
|
46
42
|
|
47
43
|
def send_keys(*args)
|
48
44
|
# https://github.com/mozilla/geckodriver/issues/846
|
49
|
-
return super(*args.map { |arg| arg == :space ? ' ' : arg }) if args.none?
|
45
|
+
return super(*args.map { |arg| arg == :space ? ' ' : arg }) if args.none?(Array)
|
50
46
|
|
51
47
|
native.click
|
52
48
|
_send_keys(args).perform
|
53
49
|
end
|
54
50
|
|
55
|
-
def
|
56
|
-
|
57
|
-
|
58
|
-
html5_drag_to(element)
|
51
|
+
def drop(*args)
|
52
|
+
html5_drop(*args)
|
59
53
|
end
|
60
54
|
|
61
55
|
def hover
|
@@ -65,9 +59,33 @@ class Capybara::Selenium::FirefoxNode < Capybara::Selenium::Node
|
|
65
59
|
scroll_if_needed { browser_action.move_to(native, 0, 0).move_to(native).perform }
|
66
60
|
end
|
67
61
|
|
62
|
+
def select_option
|
63
|
+
# To optimize to only one check and then click
|
64
|
+
selected_or_disabled = driver.evaluate_script(<<~JS, self)
|
65
|
+
arguments[0].matches(':disabled, select:disabled *, :checked')
|
66
|
+
JS
|
67
|
+
click unless selected_or_disabled
|
68
|
+
end
|
69
|
+
|
70
|
+
def visible?
|
71
|
+
return super unless native_displayed?
|
72
|
+
|
73
|
+
begin
|
74
|
+
bridge.send(:execute, :is_element_displayed, id: native.ref)
|
75
|
+
rescue Selenium::WebDriver::Error::UnknownCommandError
|
76
|
+
# If the is_element_displayed command is unknown, no point in trying again
|
77
|
+
driver.options[:native_displayed] = false
|
78
|
+
super
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
68
82
|
private
|
69
83
|
|
70
|
-
def
|
84
|
+
def native_displayed?
|
85
|
+
(driver.options[:native_displayed] != false) && !ENV['DISABLE_CAPYBARA_SELENIUM_OPTIMIZATIONS']
|
86
|
+
end
|
87
|
+
|
88
|
+
def perform_with_options(click_options)
|
71
89
|
# Firefox/marionette has an issue clicking with offset near viewport edge
|
72
90
|
# scroll element to middle just in case
|
73
91
|
scroll_to_center if click_options.coords?
|
@@ -99,10 +117,6 @@ private
|
|
99
117
|
actions
|
100
118
|
end
|
101
119
|
|
102
|
-
def bridge
|
103
|
-
driver.browser.send(:bridge)
|
104
|
-
end
|
105
|
-
|
106
120
|
def upload(local_file)
|
107
121
|
return nil unless local_file
|
108
122
|
raise ArgumentError, "You may only upload files: #{local_file.inspect}" unless File.file?(local_file)
|
@@ -114,40 +128,4 @@ private
|
|
114
128
|
def browser_version
|
115
129
|
driver.browser.capabilities[:browser_version].to_f
|
116
130
|
end
|
117
|
-
|
118
|
-
DISABLED_BY_FIELDSET_XPATH = XPath.generate do |x|
|
119
|
-
x.parent(:fieldset)[
|
120
|
-
x.attr(:disabled)
|
121
|
-
] + x.ancestor[
|
122
|
-
~x.self(:legend) |
|
123
|
-
x.preceding_sibling(:legend)
|
124
|
-
][
|
125
|
-
x.parent(:fieldset)[
|
126
|
-
x.attr(:disabled)
|
127
|
-
]
|
128
|
-
]
|
129
|
-
end.to_s.freeze
|
130
|
-
|
131
|
-
class ModifierKeysStack
|
132
|
-
def initialize
|
133
|
-
@stack = []
|
134
|
-
end
|
135
|
-
|
136
|
-
def include?(key)
|
137
|
-
@stack.flatten.include?(key)
|
138
|
-
end
|
139
|
-
|
140
|
-
def press(key)
|
141
|
-
@stack.last.push(key)
|
142
|
-
end
|
143
|
-
|
144
|
-
def push
|
145
|
-
@stack.push []
|
146
|
-
end
|
147
|
-
|
148
|
-
def pop
|
149
|
-
@stack.pop
|
150
|
-
end
|
151
|
-
end
|
152
|
-
private_constant :ModifierKeysStack
|
153
131
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'capybara/selenium/extensions/html5_drag'
|
4
|
+
|
5
|
+
class Capybara::Selenium::IENode < Capybara::Selenium::Node
|
6
|
+
def disabled?
|
7
|
+
# super
|
8
|
+
# optimize to one script call
|
9
|
+
driver.evaluate_script <<~JS.delete("\n"), self
|
10
|
+
arguments[0].msMatchesSelector('
|
11
|
+
:disabled,
|
12
|
+
select:disabled *,
|
13
|
+
optgroup:disabled *,
|
14
|
+
fieldset[disabled],
|
15
|
+
fieldset[disabled] > *:not(legend),
|
16
|
+
fieldset[disabled] > *:not(legend) *,
|
17
|
+
fieldset[disabled] > legend:nth-of-type(n+2),
|
18
|
+
fieldset[disabled] > legend:nth-of-type(n+2) *
|
19
|
+
')
|
20
|
+
JS
|
21
|
+
end
|
22
|
+
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# require 'capybara/selenium/extensions/html5_drag'
|
4
|
+
require 'capybara/selenium/extensions/modifier_keys_stack'
|
4
5
|
|
5
6
|
class Capybara::Selenium::SafariNode < Capybara::Selenium::Node
|
6
7
|
# include Html5Drag
|
@@ -16,11 +17,21 @@ class Capybara::Selenium::SafariNode < Capybara::Selenium::Node
|
|
16
17
|
return find_css('th:first-child,td:first-child')[0].click(keys, options)
|
17
18
|
end
|
18
19
|
raise
|
20
|
+
rescue ::Selenium::WebDriver::Error::WebDriverError => e
|
21
|
+
raise unless e.instance_of? ::Selenium::WebDriver::Error::WebDriverError
|
22
|
+
|
23
|
+
# Safari doesn't return a specific error here - assume it's an ElementNotInteractableError
|
24
|
+
raise ::Selenium::WebDriver::Error::ElementNotInteractableError,
|
25
|
+
'Non distinct error raised in #click, translated to ElementNotInteractableError for retry'
|
19
26
|
end
|
20
27
|
|
21
28
|
def select_option
|
22
|
-
|
23
|
-
|
29
|
+
# To optimize to only one check and then click
|
30
|
+
selected_or_disabled = driver.execute_script(<<~JS, self)
|
31
|
+
arguments[0].closest('select').scrollIntoView();
|
32
|
+
return arguments[0].matches(':disabled, select:disabled *, :checked');
|
33
|
+
JS
|
34
|
+
click unless selected_or_disabled
|
24
35
|
end
|
25
36
|
|
26
37
|
def unselect_option
|
@@ -40,14 +51,7 @@ class Capybara::Selenium::SafariNode < Capybara::Selenium::Node
|
|
40
51
|
end
|
41
52
|
|
42
53
|
def disabled?
|
43
|
-
|
44
|
-
|
45
|
-
# workaround for safaridriver reporting elements as enabled when they are nested in disabling elements
|
46
|
-
if %w[option optgroup].include? tag_name
|
47
|
-
find_xpath('parent::*[self::optgroup or self::select]')[0].disabled?
|
48
|
-
else
|
49
|
-
!find_xpath(DISABLED_BY_FIELDSET_XPATH).empty?
|
50
|
-
end
|
54
|
+
driver.evaluate_script("arguments[0].matches(':disabled, select:disabled *')", self)
|
51
55
|
end
|
52
56
|
|
53
57
|
def set_file(value) # rubocop:disable Naming/AccessorMethodName
|
@@ -57,7 +61,9 @@ class Capybara::Selenium::SafariNode < Capybara::Selenium::Node
|
|
57
61
|
end
|
58
62
|
|
59
63
|
def send_keys(*args)
|
60
|
-
|
64
|
+
if args.none? { |arg| arg.is_a?(Array) || (arg.is_a?(Symbol) && MODIFIER_KEYS.include?(arg)) }
|
65
|
+
return super(*args.map { |arg| arg == :space ? ' ' : arg })
|
66
|
+
end
|
61
67
|
|
62
68
|
native.click
|
63
69
|
_send_keys(args).perform
|
@@ -77,32 +83,16 @@ class Capybara::Selenium::SafariNode < Capybara::Selenium::Node
|
|
77
83
|
end
|
78
84
|
end
|
79
85
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
driver.browser.send(:bridge)
|
86
|
+
def hover
|
87
|
+
# Workaround issue where hover would sometimes fail - possibly due to mouse not having moved
|
88
|
+
scroll_if_needed { browser_action.move_to(native, 0, 0).move_to(native).perform }
|
84
89
|
end
|
85
90
|
|
86
|
-
|
87
|
-
x.parent(:fieldset)[
|
88
|
-
x.attr(:disabled)
|
89
|
-
] + x.ancestor[
|
90
|
-
~x.self(:legend) |
|
91
|
-
x.preceding_sibling(:legend)
|
92
|
-
][
|
93
|
-
x.parent(:fieldset)[
|
94
|
-
x.attr(:disabled)
|
95
|
-
]
|
96
|
-
]
|
97
|
-
end.to_s.freeze
|
91
|
+
private
|
98
92
|
|
99
93
|
def _send_keys(keys, actions = browser_action, down_keys = ModifierKeysStack.new)
|
100
94
|
case keys
|
101
|
-
when
|
102
|
-
:alt, :left_alt, :right_alt,
|
103
|
-
:shift, :left_shift, :right_shift,
|
104
|
-
:meta, :left_meta, :right_meta,
|
105
|
-
:command
|
95
|
+
when *MODIFIER_KEYS
|
106
96
|
down_keys.press(keys)
|
107
97
|
actions.key_down(keys)
|
108
98
|
when String
|
@@ -120,26 +110,9 @@ private
|
|
120
110
|
actions
|
121
111
|
end
|
122
112
|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
def include?(key)
|
129
|
-
@stack.flatten.include?(key)
|
130
|
-
end
|
131
|
-
|
132
|
-
def press(key)
|
133
|
-
@stack.last.push(key)
|
134
|
-
end
|
135
|
-
|
136
|
-
def push
|
137
|
-
@stack.push []
|
138
|
-
end
|
139
|
-
|
140
|
-
def pop
|
141
|
-
@stack.pop
|
142
|
-
end
|
143
|
-
end
|
144
|
-
private_constant :ModifierKeysStack
|
113
|
+
MODIFIER_KEYS = %i[control left_control right_control
|
114
|
+
alt left_alt right_alt
|
115
|
+
shift left_shift right_shift
|
116
|
+
meta left_meta right_meta
|
117
|
+
command].freeze
|
145
118
|
end
|