capybara 3.30.0 → 3.35.3
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/History.md +153 -13
- data/README.md +9 -4
- data/lib/capybara.rb +18 -8
- data/lib/capybara/config.rb +4 -6
- data/lib/capybara/cucumber.rb +1 -1
- data/lib/capybara/driver/base.rb +4 -0
- data/lib/capybara/dsl.rb +10 -2
- data/lib/capybara/helpers.rb +25 -1
- data/lib/capybara/minitest.rb +232 -144
- data/lib/capybara/minitest/spec.rb +156 -97
- data/lib/capybara/node/actions.rb +16 -21
- data/lib/capybara/node/base.rb +6 -6
- data/lib/capybara/node/element.rb +14 -13
- data/lib/capybara/node/finders.rb +12 -7
- data/lib/capybara/node/matchers.rb +36 -27
- data/lib/capybara/node/simple.rb +6 -2
- data/lib/capybara/queries/ancestor_query.rb +1 -1
- data/lib/capybara/queries/base_query.rb +2 -1
- data/lib/capybara/queries/current_path_query.rb +14 -4
- data/lib/capybara/queries/selector_query.rb +40 -18
- data/lib/capybara/queries/sibling_query.rb +1 -1
- data/lib/capybara/queries/style_query.rb +1 -1
- data/lib/capybara/queries/text_query.rb +7 -1
- data/lib/capybara/rack_test/browser.rb +9 -3
- data/lib/capybara/rack_test/driver.rb +1 -0
- data/lib/capybara/rack_test/form.rb +1 -1
- data/lib/capybara/rack_test/node.rb +35 -10
- data/lib/capybara/registration_container.rb +44 -0
- data/lib/capybara/registrations/drivers.rb +18 -12
- data/lib/capybara/registrations/patches/puma_ssl.rb +3 -1
- data/lib/capybara/registrations/servers.rb +3 -2
- data/lib/capybara/result.rb +35 -15
- data/lib/capybara/rspec.rb +2 -0
- data/lib/capybara/rspec/matcher_proxies.rb +5 -5
- data/lib/capybara/rspec/matchers.rb +33 -32
- data/lib/capybara/rspec/matchers/base.rb +12 -6
- data/lib/capybara/rspec/matchers/count_sugar.rb +2 -1
- data/lib/capybara/rspec/matchers/have_ancestor.rb +4 -3
- data/lib/capybara/rspec/matchers/have_current_path.rb +2 -2
- data/lib/capybara/rspec/matchers/have_selector.rb +15 -7
- data/lib/capybara/rspec/matchers/have_sibling.rb +3 -3
- data/lib/capybara/rspec/matchers/have_text.rb +3 -3
- 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 +2 -1
- data/lib/capybara/selector.rb +14 -3
- data/lib/capybara/selector/builders/css_builder.rb +1 -1
- data/lib/capybara/selector/builders/xpath_builder.rb +3 -1
- data/lib/capybara/selector/definition.rb +11 -9
- data/lib/capybara/selector/definition/button.rb +26 -14
- data/lib/capybara/selector/definition/css.rb +1 -1
- data/lib/capybara/selector/definition/datalist_input.rb +1 -1
- data/lib/capybara/selector/definition/element.rb +2 -1
- data/lib/capybara/selector/definition/fillable_field.rb +1 -1
- data/lib/capybara/selector/definition/label.rb +2 -2
- data/lib/capybara/selector/definition/link.rb +8 -0
- data/lib/capybara/selector/definition/select.rb +32 -13
- data/lib/capybara/selector/definition/table.rb +1 -1
- data/lib/capybara/selector/definition/table_row.rb +2 -2
- data/lib/capybara/selector/filter_set.rb +2 -2
- data/lib/capybara/selector/selector.rb +9 -1
- data/lib/capybara/selenium/atoms/getAttribute.min.js +1 -1
- data/lib/capybara/selenium/atoms/src/getAttribute.js +1 -1
- data/lib/capybara/selenium/atoms/src/isDisplayed.js +1 -1
- data/lib/capybara/selenium/driver.rb +52 -7
- data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +10 -12
- data/lib/capybara/selenium/driver_specializations/edge_driver.rb +9 -11
- data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +3 -3
- data/lib/capybara/selenium/extensions/find.rb +4 -4
- data/lib/capybara/selenium/extensions/html5_drag.rb +24 -8
- data/lib/capybara/selenium/extensions/scroll.rb +8 -10
- data/lib/capybara/selenium/logger_suppressor.rb +8 -2
- data/lib/capybara/selenium/node.rb +96 -16
- data/lib/capybara/selenium/nodes/chrome_node.rb +27 -16
- data/lib/capybara/selenium/nodes/edge_node.rb +1 -1
- data/lib/capybara/selenium/nodes/firefox_node.rb +9 -4
- data/lib/capybara/selenium/nodes/safari_node.rb +1 -1
- data/lib/capybara/selenium/patches/action_pauser.rb +26 -0
- data/lib/capybara/selenium/patches/atoms.rb +4 -4
- data/lib/capybara/selenium/patches/logs.rb +7 -9
- data/lib/capybara/server/animation_disabler.rb +8 -3
- data/lib/capybara/server/middleware.rb +4 -2
- data/lib/capybara/session.rb +53 -29
- data/lib/capybara/session/config.rb +3 -1
- data/lib/capybara/session/matchers.rb +11 -11
- data/lib/capybara/spec/public/test.js +64 -7
- data/lib/capybara/spec/session/accept_alert_spec.rb +1 -1
- data/lib/capybara/spec/session/all_spec.rb +45 -5
- data/lib/capybara/spec/session/assert_text_spec.rb +5 -5
- data/lib/capybara/spec/session/check_spec.rb +6 -0
- data/lib/capybara/spec/session/click_button_spec.rb +11 -0
- data/lib/capybara/spec/session/click_link_or_button_spec.rb +9 -0
- data/lib/capybara/spec/session/current_url_spec.rb +11 -1
- data/lib/capybara/spec/session/fill_in_spec.rb +29 -0
- data/lib/capybara/spec/session/find_spec.rb +11 -8
- data/lib/capybara/spec/session/has_button_spec.rb +51 -0
- data/lib/capybara/spec/session/has_css_spec.rb +14 -10
- data/lib/capybara/spec/session/has_current_path_spec.rb +15 -2
- data/lib/capybara/spec/session/has_field_spec.rb +16 -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_text_spec.rb +5 -12
- data/lib/capybara/spec/session/html_spec.rb +1 -1
- data/lib/capybara/spec/session/matches_style_spec.rb +2 -2
- data/lib/capybara/spec/session/node_spec.rb +169 -33
- data/lib/capybara/spec/session/refresh_spec.rb +2 -1
- data/lib/capybara/spec/session/save_page_spec.rb +4 -4
- 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 +8 -8
- data/lib/capybara/spec/session/window/windows_spec.rb +1 -1
- data/lib/capybara/spec/spec_helper.rb +13 -14
- data/lib/capybara/spec/test_app.rb +23 -21
- data/lib/capybara/spec/views/form.erb +36 -3
- data/lib/capybara/spec/views/with_animation.erb +8 -0
- data/lib/capybara/spec/views/with_dragula.erb +3 -1
- data/lib/capybara/spec/views/with_html.erb +2 -2
- data/lib/capybara/spec/views/with_jquery_animation.erb +24 -0
- data/lib/capybara/spec/views/with_js.erb +3 -0
- data/lib/capybara/spec/views/with_sortable_js.erb +1 -1
- data/lib/capybara/version.rb +1 -1
- data/lib/capybara/window.rb +3 -7
- data/spec/basic_node_spec.rb +9 -8
- data/spec/capybara_spec.rb +1 -1
- data/spec/dsl_spec.rb +14 -1
- data/spec/fixtures/selenium_driver_rspec_success.rb +1 -1
- data/spec/minitest_spec.rb +3 -2
- data/spec/rack_test_spec.rb +28 -6
- data/spec/regexp_dissassembler_spec.rb +0 -4
- data/spec/result_spec.rb +40 -29
- data/spec/rspec/features_spec.rb +3 -1
- data/spec/rspec/scenarios_spec.rb +4 -0
- data/spec/rspec/shared_spec_matchers.rb +63 -51
- data/spec/rspec_spec.rb +4 -0
- data/spec/selector_spec.rb +17 -2
- data/spec/selenium_spec_chrome.rb +45 -21
- data/spec/selenium_spec_chrome_remote.rb +7 -1
- data/spec/selenium_spec_firefox.rb +15 -13
- data/spec/server_spec.rb +60 -49
- data/spec/shared_selenium_node.rb +18 -0
- data/spec/shared_selenium_session.rb +98 -7
- data/spec/spec_helper.rb +1 -1
- metadata +50 -14
- data/lib/capybara/spec/session/source_spec.rb +0 -0
|
@@ -5,7 +5,7 @@ require 'capybara/selenium/nodes/edge_node'
|
|
|
5
5
|
module Capybara::Selenium::Driver::EdgeDriver
|
|
6
6
|
def self.extended(base)
|
|
7
7
|
bridge = base.send(:bridge)
|
|
8
|
-
bridge.extend Capybara::Selenium::IsDisplayed unless bridge.
|
|
8
|
+
bridge.extend Capybara::Selenium::IsDisplayed unless bridge.send(:commands, :is_element_displayed)
|
|
9
9
|
base.options[:native_displayed] = false if base.options[:native_displayed].nil?
|
|
10
10
|
end
|
|
11
11
|
|
|
@@ -13,21 +13,19 @@ module Capybara::Selenium::Driver::EdgeDriver
|
|
|
13
13
|
return super if edgedriver_version < 75
|
|
14
14
|
|
|
15
15
|
within_given_window(handle) do
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
result['value']
|
|
23
|
-
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']
|
|
24
22
|
end
|
|
25
23
|
end
|
|
26
24
|
|
|
27
25
|
def resize_window_to(handle, width, height)
|
|
28
26
|
super
|
|
29
27
|
rescue Selenium::WebDriver::Error::UnknownError => e
|
|
30
|
-
raise unless e.message.
|
|
28
|
+
raise unless e.message.include?('failed to change window state')
|
|
31
29
|
|
|
32
30
|
# Chromedriver doesn't wait long enough for state to change when coming out of fullscreen
|
|
33
31
|
# and raises unnecessary error. Wait a bit and try again.
|
|
@@ -74,7 +72,7 @@ private
|
|
|
74
72
|
end
|
|
75
73
|
|
|
76
74
|
def clear_all_storage?
|
|
77
|
-
storage_clears.none?
|
|
75
|
+
storage_clears.none? false
|
|
78
76
|
end
|
|
79
77
|
|
|
80
78
|
def uniform_storage_clear?
|
|
@@ -6,7 +6,7 @@ module Capybara::Selenium::Driver::FirefoxDriver
|
|
|
6
6
|
def self.extended(driver)
|
|
7
7
|
driver.extend Capybara::Selenium::Driver::W3CFirefoxDriver if w3c?(driver)
|
|
8
8
|
bridge = driver.send(:bridge)
|
|
9
|
-
bridge.extend Capybara::Selenium::IsDisplayed unless bridge.
|
|
9
|
+
bridge.extend Capybara::Selenium::IsDisplayed unless bridge.send(:commands, :is_element_displayed)
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
def self.w3c?(driver)
|
|
@@ -46,7 +46,7 @@ module Capybara::Selenium::Driver::W3CFirefoxDriver
|
|
|
46
46
|
begin
|
|
47
47
|
# Firefox 68 hangs if we try to switch windows while a modal is visible
|
|
48
48
|
browser.switch_to.alert&.dismiss
|
|
49
|
-
rescue Selenium::WebDriver::Error::NoSuchAlertError
|
|
49
|
+
rescue Selenium::WebDriver::Error::NoSuchAlertError
|
|
50
50
|
# Swallow
|
|
51
51
|
end
|
|
52
52
|
end
|
|
@@ -61,7 +61,7 @@ module Capybara::Selenium::Driver::W3CFirefoxDriver
|
|
|
61
61
|
accept_modal :confirm, wait: 0.1 do
|
|
62
62
|
super
|
|
63
63
|
end
|
|
64
|
-
rescue Capybara::ModalNotFound
|
|
64
|
+
rescue Capybara::ModalNotFound
|
|
65
65
|
# No modal was opened - page has refreshed - ignore
|
|
66
66
|
end
|
|
67
67
|
|
|
@@ -28,7 +28,7 @@ module Capybara
|
|
|
28
28
|
hints_js, functions = build_hints_js(uses_visibility, styles, position)
|
|
29
29
|
return [] unless functions.any?
|
|
30
30
|
|
|
31
|
-
es_context.execute_script(hints_js, elements).map! do |results|
|
|
31
|
+
(es_context.execute_script(hints_js, elements) || []).map! do |results|
|
|
32
32
|
hint = {}
|
|
33
33
|
hint[:style] = results.pop if functions.include?(:style_func)
|
|
34
34
|
hint[:position] = results.pop if functions.include?(:position_func)
|
|
@@ -100,9 +100,9 @@ module Capybara
|
|
|
100
100
|
def is_displayed_atom # rubocop:disable Naming/PredicateName
|
|
101
101
|
@@is_displayed_atom ||= begin # rubocop:disable Style/ClassVars
|
|
102
102
|
browser.send(:bridge).send(:read_atom, 'isDisplayed')
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
103
|
+
rescue StandardError
|
|
104
|
+
# If the atom doesn't exist or other error
|
|
105
|
+
''
|
|
106
106
|
end
|
|
107
107
|
end
|
|
108
108
|
end
|
|
@@ -4,25 +4,32 @@ class Capybara::Selenium::Node
|
|
|
4
4
|
module Html5Drag
|
|
5
5
|
# Implement methods to emulate HTML5 drag and drop
|
|
6
6
|
|
|
7
|
-
def drag_to(element, html5: nil, delay: 0.05)
|
|
7
|
+
def drag_to(element, html5: nil, delay: 0.05, drop_modifiers: [])
|
|
8
|
+
drop_modifiers = Array(drop_modifiers)
|
|
9
|
+
|
|
8
10
|
driver.execute_script MOUSEDOWN_TRACKER
|
|
9
11
|
scroll_if_needed { browser_action.click_and_hold(native).perform }
|
|
10
12
|
html5 = !driver.evaluate_script(LEGACY_DRAG_CHECK, self) if html5.nil?
|
|
11
13
|
if html5
|
|
12
|
-
perform_html5_drag(element, delay)
|
|
14
|
+
perform_html5_drag(element, delay, drop_modifiers)
|
|
13
15
|
else
|
|
14
|
-
perform_legacy_drag(element)
|
|
16
|
+
perform_legacy_drag(element, drop_modifiers)
|
|
15
17
|
end
|
|
16
18
|
end
|
|
17
19
|
|
|
18
20
|
private
|
|
19
21
|
|
|
20
|
-
def perform_legacy_drag(element)
|
|
21
|
-
element.scroll_if_needed
|
|
22
|
+
def perform_legacy_drag(element, drop_modifiers)
|
|
23
|
+
element.scroll_if_needed do
|
|
24
|
+
# browser_action.move_to(element.native).release.perform
|
|
25
|
+
keys_down = modifiers_down(browser_action, drop_modifiers)
|
|
26
|
+
keys_up = modifiers_up(keys_down.move_to(element.native).release, drop_modifiers)
|
|
27
|
+
keys_up.perform
|
|
28
|
+
end
|
|
22
29
|
end
|
|
23
30
|
|
|
24
|
-
def perform_html5_drag(element, delay)
|
|
25
|
-
driver.evaluate_async_script HTML5_DRAG_DROP_SCRIPT, self, element, delay * 1000
|
|
31
|
+
def perform_html5_drag(element, delay, drop_modifiers)
|
|
32
|
+
driver.evaluate_async_script HTML5_DRAG_DROP_SCRIPT, self, element, delay * 1000, normalize_keys(drop_modifiers)
|
|
26
33
|
browser_action.release.perform
|
|
27
34
|
end
|
|
28
35
|
|
|
@@ -153,6 +160,14 @@ class Capybara::Selenium::Node
|
|
|
153
160
|
var targetRect = target.getBoundingClientRect();
|
|
154
161
|
var sourceCenter = rectCenter(source.getBoundingClientRect());
|
|
155
162
|
|
|
163
|
+
for (var i = 0; i < drop_modifier_keys.length; i++) {
|
|
164
|
+
key = drop_modifier_keys[i];
|
|
165
|
+
if (key == "control"){
|
|
166
|
+
key = "ctrl"
|
|
167
|
+
}
|
|
168
|
+
opts[key + 'Key'] = true;
|
|
169
|
+
}
|
|
170
|
+
|
|
156
171
|
// fire 2 dragover events to simulate dragging with a direction
|
|
157
172
|
var entryPoint = pointOnRect(sourceCenter, targetRect)
|
|
158
173
|
var dragOverOpts = Object.assign({clientX: entryPoint.x, clientY: entryPoint.y}, opts);
|
|
@@ -185,7 +200,8 @@ class Capybara::Selenium::Node
|
|
|
185
200
|
var source = arguments[0],
|
|
186
201
|
target = arguments[1],
|
|
187
202
|
step_delay = arguments[2],
|
|
188
|
-
|
|
203
|
+
drop_modifier_keys = arguments[3],
|
|
204
|
+
callback = arguments[4];
|
|
189
205
|
|
|
190
206
|
var dt = new DataTransfer();
|
|
191
207
|
var opts = { cancelable: true, bubbles: true, dataTransfer: dt };
|
|
@@ -45,20 +45,18 @@ module Capybara
|
|
|
45
45
|
JS
|
|
46
46
|
end
|
|
47
47
|
|
|
48
|
+
SCROLL_POSITIONS = {
|
|
49
|
+
top: '0',
|
|
50
|
+
bottom: 'arguments[0].scrollHeight',
|
|
51
|
+
center: '(arguments[0].scrollHeight - arguments[0].clientHeight)/2'
|
|
52
|
+
}.freeze
|
|
53
|
+
|
|
48
54
|
def scroll_to_location(location)
|
|
49
|
-
scroll_y = case location
|
|
50
|
-
when :top
|
|
51
|
-
'0'
|
|
52
|
-
when :bottom
|
|
53
|
-
'arguments[0].scrollHeight'
|
|
54
|
-
when :center
|
|
55
|
-
'(arguments[0].scrollHeight - arguments[0].clientHeight)/2'
|
|
56
|
-
end
|
|
57
55
|
driver.execute_script <<~JS, self
|
|
58
56
|
if (arguments[0].scrollTo){
|
|
59
|
-
arguments[0].scrollTo(0, #{
|
|
57
|
+
arguments[0].scrollTo(0, #{SCROLL_POSITIONS[location]});
|
|
60
58
|
} else {
|
|
61
|
-
arguments[0].scrollTop = #{
|
|
59
|
+
arguments[0].scrollTop = #{SCROLL_POSITIONS[location]};
|
|
62
60
|
}
|
|
63
61
|
JS
|
|
64
62
|
end
|
|
@@ -8,8 +8,14 @@ module Capybara
|
|
|
8
8
|
super
|
|
9
9
|
end
|
|
10
10
|
|
|
11
|
-
def deprecate(*)
|
|
12
|
-
|
|
11
|
+
def deprecate(*args, **opts, &block)
|
|
12
|
+
return if @suppress_for_capybara
|
|
13
|
+
|
|
14
|
+
if opts.empty?
|
|
15
|
+
super(*args, &block) # support Selenium 3
|
|
16
|
+
else
|
|
17
|
+
super
|
|
18
|
+
end
|
|
13
19
|
end
|
|
14
20
|
|
|
15
21
|
def suppress_deprecations
|
|
@@ -78,6 +78,8 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
|
|
|
78
78
|
set_datetime_local(value)
|
|
79
79
|
when 'color'
|
|
80
80
|
set_color(value)
|
|
81
|
+
when 'range'
|
|
82
|
+
set_range(value)
|
|
81
83
|
else
|
|
82
84
|
set_text(value, **options)
|
|
83
85
|
end
|
|
@@ -102,10 +104,23 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
|
|
|
102
104
|
click_options = ClickOptions.new(keys, options)
|
|
103
105
|
return native.click if click_options.empty?
|
|
104
106
|
|
|
105
|
-
|
|
107
|
+
perform_with_options(click_options) do |action|
|
|
108
|
+
target = click_options.coords? ? nil : native
|
|
109
|
+
if click_options.delay.zero?
|
|
110
|
+
action.click(target)
|
|
111
|
+
else
|
|
112
|
+
action.click_and_hold(target)
|
|
113
|
+
if w3c?
|
|
114
|
+
action.pause(action.pointer_inputs.first, click_options.delay)
|
|
115
|
+
else
|
|
116
|
+
action.pause(click_options.delay)
|
|
117
|
+
end
|
|
118
|
+
action.release
|
|
119
|
+
end
|
|
120
|
+
end
|
|
106
121
|
rescue StandardError => e
|
|
107
122
|
if e.is_a?(::Selenium::WebDriver::Error::ElementClickInterceptedError) ||
|
|
108
|
-
e.message.
|
|
123
|
+
e.message.include?('Other element would receive the click')
|
|
109
124
|
scroll_to_center
|
|
110
125
|
end
|
|
111
126
|
|
|
@@ -114,14 +129,26 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
|
|
|
114
129
|
|
|
115
130
|
def right_click(keys = [], **options)
|
|
116
131
|
click_options = ClickOptions.new(keys, options)
|
|
117
|
-
|
|
118
|
-
click_options.coords? ?
|
|
132
|
+
perform_with_options(click_options) do |action|
|
|
133
|
+
target = click_options.coords? ? nil : native
|
|
134
|
+
if click_options.delay.zero?
|
|
135
|
+
action.context_click(target)
|
|
136
|
+
elsif w3c?
|
|
137
|
+
action.move_to(target) if target
|
|
138
|
+
action.pointer_down(:right)
|
|
139
|
+
.pause(action.pointer_inputs.first, click_options.delay)
|
|
140
|
+
.pointer_up(:right)
|
|
141
|
+
else
|
|
142
|
+
raise ArgumentError, 'Delay is not supported when right clicking with legacy (non-w3c) selenium driver'
|
|
143
|
+
end
|
|
119
144
|
end
|
|
120
145
|
end
|
|
121
146
|
|
|
122
147
|
def double_click(keys = [], **options)
|
|
123
148
|
click_options = ClickOptions.new(keys, options)
|
|
124
|
-
|
|
149
|
+
raise ArgumentError, "double_click doesn't support a delay option" unless click_options.delay.zero?
|
|
150
|
+
|
|
151
|
+
perform_with_options(click_options) do |action|
|
|
125
152
|
click_options.coords? ? action.double_click : action.double_click(native)
|
|
126
153
|
end
|
|
127
154
|
end
|
|
@@ -134,11 +161,17 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
|
|
|
134
161
|
scroll_if_needed { browser_action.move_to(native).perform }
|
|
135
162
|
end
|
|
136
163
|
|
|
137
|
-
def drag_to(element, **)
|
|
164
|
+
def drag_to(element, drop_modifiers: [], **)
|
|
165
|
+
drop_modifiers = Array(drop_modifiers)
|
|
138
166
|
# Due to W3C spec compliance - The Actions API no longer scrolls to elements when necessary
|
|
139
167
|
# which means Seleniums `drag_and_drop` is now broken - do it manually
|
|
140
168
|
scroll_if_needed { browser_action.click_and_hold(native).perform }
|
|
141
|
-
element.scroll_if_needed { browser_action.move_to(element.native).release.perform }
|
|
169
|
+
# element.scroll_if_needed { browser_action.move_to(element.native).release.perform }
|
|
170
|
+
element.scroll_if_needed do
|
|
171
|
+
keys_down = modifiers_down(browser_action, drop_modifiers)
|
|
172
|
+
keys_up = modifiers_up(keys_down.move_to(element.native).release, drop_modifiers)
|
|
173
|
+
keys_up.perform
|
|
174
|
+
end
|
|
142
175
|
end
|
|
143
176
|
|
|
144
177
|
def drop(*_)
|
|
@@ -204,7 +237,7 @@ protected
|
|
|
204
237
|
JS
|
|
205
238
|
begin
|
|
206
239
|
driver.execute_script(script, self)
|
|
207
|
-
rescue StandardError
|
|
240
|
+
rescue StandardError
|
|
208
241
|
# Swallow error if scrollIntoView with options isn't supported
|
|
209
242
|
end
|
|
210
243
|
end
|
|
@@ -234,7 +267,7 @@ private
|
|
|
234
267
|
find_xpath(XPath.ancestor(:select)[1]).first
|
|
235
268
|
end
|
|
236
269
|
|
|
237
|
-
def set_text(value, clear: nil, **_unused)
|
|
270
|
+
def set_text(value, clear: nil, rapid: nil, **_unused)
|
|
238
271
|
value = value.to_s
|
|
239
272
|
if value.empty? && clear.nil?
|
|
240
273
|
native.clear
|
|
@@ -246,11 +279,23 @@ private
|
|
|
246
279
|
send_keys(*clear, value)
|
|
247
280
|
else
|
|
248
281
|
driver.execute_script 'arguments[0].select()', self unless clear == :none
|
|
249
|
-
|
|
282
|
+
if rapid == true || ((value.length > auto_rapid_set_length) && rapid != false)
|
|
283
|
+
send_keys(value[0..3])
|
|
284
|
+
driver.execute_script RAPID_APPEND_TEXT, self, value[4...-3]
|
|
285
|
+
send_keys(value[-3..-1])
|
|
286
|
+
else
|
|
287
|
+
send_keys(value)
|
|
288
|
+
end
|
|
250
289
|
end
|
|
251
290
|
end
|
|
252
291
|
|
|
253
|
-
def
|
|
292
|
+
def auto_rapid_set_length
|
|
293
|
+
30
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
def perform_with_options(click_options, &block)
|
|
297
|
+
raise ArgumentError, 'A block must be provided' unless block
|
|
298
|
+
|
|
254
299
|
scroll_if_needed do
|
|
255
300
|
action_with_modifiers(click_options) do |action|
|
|
256
301
|
if block_given?
|
|
@@ -290,6 +335,10 @@ private
|
|
|
290
335
|
update_value_js(value)
|
|
291
336
|
end
|
|
292
337
|
|
|
338
|
+
def set_range(value) # rubocop:disable Naming/AccessorMethodName
|
|
339
|
+
update_value_js(value)
|
|
340
|
+
end
|
|
341
|
+
|
|
293
342
|
def update_value_js(value)
|
|
294
343
|
driver.execute_script(<<-JS, self, value)
|
|
295
344
|
if (arguments[0].readOnly) { return };
|
|
@@ -375,10 +424,12 @@ private
|
|
|
375
424
|
|
|
376
425
|
def modifiers_down(actions, keys)
|
|
377
426
|
each_key(keys) { |key| actions.key_down(key) }
|
|
427
|
+
actions
|
|
378
428
|
end
|
|
379
429
|
|
|
380
430
|
def modifiers_up(actions, keys)
|
|
381
431
|
each_key(keys) { |key| actions.key_up(key) }
|
|
432
|
+
actions
|
|
382
433
|
end
|
|
383
434
|
|
|
384
435
|
def browser
|
|
@@ -393,18 +444,30 @@ private
|
|
|
393
444
|
browser.action
|
|
394
445
|
end
|
|
395
446
|
|
|
396
|
-
def
|
|
397
|
-
|
|
398
|
-
|
|
447
|
+
def capabilities
|
|
448
|
+
browser.capabilities
|
|
449
|
+
end
|
|
450
|
+
|
|
451
|
+
def w3c?
|
|
452
|
+
(defined?(Selenium::WebDriver::VERSION) && (Selenium::WebDriver::VERSION.to_f >= 4)) ||
|
|
453
|
+
capabilities.is_a?(::Selenium::WebDriver::Remote::W3C::Capabilities)
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
def normalize_keys(keys)
|
|
457
|
+
keys.map do |key|
|
|
458
|
+
case key
|
|
399
459
|
when :ctrl then :control
|
|
400
460
|
when :command, :cmd then :meta
|
|
401
461
|
else
|
|
402
462
|
key
|
|
403
463
|
end
|
|
404
|
-
yield key
|
|
405
464
|
end
|
|
406
465
|
end
|
|
407
466
|
|
|
467
|
+
def each_key(keys, &block)
|
|
468
|
+
normalize_keys(keys).each(&block)
|
|
469
|
+
end
|
|
470
|
+
|
|
408
471
|
def find_context
|
|
409
472
|
native
|
|
410
473
|
end
|
|
@@ -430,6 +493,9 @@ private
|
|
|
430
493
|
var xpath = '';
|
|
431
494
|
var pos, tempitem2;
|
|
432
495
|
|
|
496
|
+
if (el.getRootNode && el.getRootNode() instanceof ShadowRoot) {
|
|
497
|
+
return "(: Shadow DOM element - no XPath :)";
|
|
498
|
+
};
|
|
433
499
|
while(el !== xml.documentElement) {
|
|
434
500
|
pos = 0;
|
|
435
501
|
tempitem2 = el;
|
|
@@ -471,6 +537,16 @@ private
|
|
|
471
537
|
})(arguments[0], arguments[1], arguments[2])
|
|
472
538
|
JS
|
|
473
539
|
|
|
540
|
+
RAPID_APPEND_TEXT = <<~'JS'
|
|
541
|
+
(function(el, value) {
|
|
542
|
+
value = el.value + value;
|
|
543
|
+
if (el.maxLength && el.maxLength != -1){
|
|
544
|
+
value = value.slice(0, el.maxLength);
|
|
545
|
+
}
|
|
546
|
+
el.value = value;
|
|
547
|
+
})(arguments[0], arguments[1])
|
|
548
|
+
JS
|
|
549
|
+
|
|
474
550
|
# SettableValue encapsulates time/date field formatting
|
|
475
551
|
class SettableValue
|
|
476
552
|
attr_reader :value
|
|
@@ -527,7 +603,11 @@ private
|
|
|
527
603
|
end
|
|
528
604
|
|
|
529
605
|
def empty?
|
|
530
|
-
keys.empty? && !coords?
|
|
606
|
+
keys.empty? && !coords? && delay.zero?
|
|
607
|
+
end
|
|
608
|
+
|
|
609
|
+
def delay
|
|
610
|
+
options[:delay] || 0
|
|
531
611
|
end
|
|
532
612
|
end
|
|
533
613
|
private_constant :ClickOptions
|
|
@@ -18,7 +18,7 @@ class Capybara::Selenium::ChromeNode < Capybara::Selenium::Node
|
|
|
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
19
|
if browser_version >= 75.0
|
|
20
20
|
driver.execute_script(<<~JS, self)
|
|
21
|
-
if (arguments[0].multiple &&
|
|
21
|
+
if (arguments[0].multiple && arguments[0].files.length){
|
|
22
22
|
arguments[0].value = null;
|
|
23
23
|
}
|
|
24
24
|
JS
|
|
@@ -42,7 +42,7 @@ class Capybara::Selenium::ChromeNode < Capybara::Selenium::Node
|
|
|
42
42
|
raise
|
|
43
43
|
rescue ::Selenium::WebDriver::Error::WebDriverError => e
|
|
44
44
|
# chromedriver 74 (at least on mac) raises the wrong error for this
|
|
45
|
-
if e.message.
|
|
45
|
+
if e.message.include?('element click intercepted')
|
|
46
46
|
raise ::Selenium::WebDriver::Error::ElementClickInterceptedError, e.message
|
|
47
47
|
end
|
|
48
48
|
|
|
@@ -73,11 +73,31 @@ class Capybara::Selenium::ChromeNode < Capybara::Selenium::Node
|
|
|
73
73
|
end
|
|
74
74
|
end
|
|
75
75
|
|
|
76
|
+
def send_keys(*args)
|
|
77
|
+
args.chunk { |inp| inp.is_a?(String) && inp.match?(/\p{Emoji Presentation}/) }
|
|
78
|
+
.each do |contains_emoji, inputs|
|
|
79
|
+
if contains_emoji
|
|
80
|
+
inputs.join.grapheme_clusters.chunk { |gc| gc.match?(/\p{Emoji Presentation}/) }
|
|
81
|
+
.each do |emoji, clusters|
|
|
82
|
+
if emoji
|
|
83
|
+
driver.send(:execute_cdp, 'Input.insertText', text: clusters.join)
|
|
84
|
+
else
|
|
85
|
+
super(clusters.join)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
else
|
|
89
|
+
super(*inputs)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
76
94
|
private
|
|
77
95
|
|
|
78
|
-
def perform_legacy_drag(element)
|
|
96
|
+
def perform_legacy_drag(element, drop_modifiers)
|
|
79
97
|
return super if chromedriver_fixed_actions_key_state? || !w3c? || element.obscured?
|
|
80
98
|
|
|
99
|
+
raise ArgumentError, 'Modifier keys are not supported while dragging in this version of Chrome.' unless drop_modifiers.empty?
|
|
100
|
+
|
|
81
101
|
# W3C Chrome/chromedriver < 77 doesn't maintain mouse button state across actions API performs
|
|
82
102
|
# https://bugs.chromium.org/p/chromedriver/issues/detail?id=2981
|
|
83
103
|
browser_action.release.perform
|
|
@@ -90,12 +110,7 @@ private
|
|
|
90
110
|
end
|
|
91
111
|
end
|
|
92
112
|
|
|
93
|
-
def
|
|
94
|
-
(defined?(Selenium::WebDriver::VERSION) && (Selenium::WebDriver::VERSION.to_f >= 4)) ||
|
|
95
|
-
capabilities.is_a?(::Selenium::WebDriver::Remote::W3C::Capabilities)
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
def browser_version(to_float = true)
|
|
113
|
+
def browser_version(to_float: true)
|
|
99
114
|
caps = capabilities
|
|
100
115
|
ver = (caps[:browser_version] || caps[:version])
|
|
101
116
|
ver = ver.to_f if to_float
|
|
@@ -103,19 +118,15 @@ private
|
|
|
103
118
|
end
|
|
104
119
|
|
|
105
120
|
def chromedriver_fixed_actions_key_state?
|
|
106
|
-
Gem::
|
|
121
|
+
Gem::Requirement.new('>= 76.0.3809.68').satisfied_by?(chromedriver_version)
|
|
107
122
|
end
|
|
108
123
|
|
|
109
124
|
def chromedriver_supports_displayed_endpoint?
|
|
110
|
-
Gem::
|
|
125
|
+
Gem::Requirement.new('>= 76.0.3809.25').satisfied_by?(chromedriver_version)
|
|
111
126
|
end
|
|
112
127
|
|
|
113
128
|
def chromedriver_version
|
|
114
|
-
capabilities['chrome']['chromedriverVersion'].split(' ')[0]
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
def capabilities
|
|
118
|
-
driver.browser.capabilities
|
|
129
|
+
Gem::Version.new(capabilities['chrome']['chromedriverVersion'].split(' ')[0]) # rubocop:disable Style/RedundantArgument
|
|
119
130
|
end
|
|
120
131
|
|
|
121
132
|
def native_displayed?
|