capybara 3.23.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 +264 -11
- data/README.md +10 -6
- data/lib/capybara.rb +20 -8
- data/lib/capybara/config.rb +10 -8
- data/lib/capybara/cucumber.rb +1 -1
- data/lib/capybara/driver/base.rb +4 -0
- data/lib/capybara/driver/node.rb +4 -0
- data/lib/capybara/dsl.rb +10 -2
- data/lib/capybara/helpers.rb +28 -2
- data/lib/capybara/minitest.rb +232 -144
- data/lib/capybara/minitest/spec.rb +156 -97
- data/lib/capybara/node/actions.rb +36 -36
- data/lib/capybara/node/base.rb +6 -6
- data/lib/capybara/node/document.rb +2 -2
- data/lib/capybara/node/document_matchers.rb +3 -3
- data/lib/capybara/node/element.rb +77 -33
- data/lib/capybara/node/finders.rb +24 -17
- data/lib/capybara/node/matchers.rb +79 -64
- data/lib/capybara/node/simple.rb +11 -4
- data/lib/capybara/queries/ancestor_query.rb +6 -10
- 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 +259 -23
- data/lib/capybara/queries/sibling_query.rb +5 -11
- data/lib/capybara/queries/style_query.rb +1 -1
- data/lib/capybara/queries/text_query.rb +13 -1
- data/lib/capybara/rack_test/browser.rb +13 -4
- data/lib/capybara/rack_test/driver.rb +2 -1
- data/lib/capybara/rack_test/form.rb +2 -2
- data/lib/capybara/rack_test/node.rb +42 -6
- data/lib/capybara/registration_container.rb +44 -0
- data/lib/capybara/registrations/drivers.rb +18 -12
- data/lib/capybara/registrations/patches/puma_ssl.rb +29 -0
- data/lib/capybara/registrations/servers.rb +9 -2
- data/lib/capybara/result.rb +39 -19
- data/lib/capybara/rspec.rb +2 -0
- data/lib/capybara/rspec/matcher_proxies.rb +5 -5
- data/lib/capybara/rspec/matchers.rb +97 -74
- data/lib/capybara/rspec/matchers/base.rb +19 -6
- data/lib/capybara/rspec/matchers/count_sugar.rb +2 -1
- data/lib/capybara/rspec/matchers/have_ancestor.rb +5 -7
- data/lib/capybara/rspec/matchers/have_current_path.rb +2 -2
- data/lib/capybara/rspec/matchers/have_selector.rb +15 -10
- data/lib/capybara/rspec/matchers/have_sibling.rb +4 -7
- data/lib/capybara/rspec/matchers/have_text.rb +4 -7
- 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/selector.rb +46 -19
- data/lib/capybara/selector/builders/css_builder.rb +10 -6
- data/lib/capybara/selector/builders/xpath_builder.rb +4 -2
- data/lib/capybara/selector/css.rb +1 -1
- data/lib/capybara/selector/definition.rb +13 -11
- data/lib/capybara/selector/definition/button.rb +32 -15
- data/lib/capybara/selector/definition/checkbox.rb +2 -2
- data/lib/capybara/selector/definition/css.rb +3 -1
- data/lib/capybara/selector/definition/datalist_input.rb +2 -2
- data/lib/capybara/selector/definition/datalist_option.rb +1 -1
- data/lib/capybara/selector/definition/element.rb +3 -2
- data/lib/capybara/selector/definition/field.rb +1 -1
- data/lib/capybara/selector/definition/file_field.rb +1 -1
- data/lib/capybara/selector/definition/fillable_field.rb +2 -2
- data/lib/capybara/selector/definition/label.rb +5 -3
- data/lib/capybara/selector/definition/link.rb +8 -0
- data/lib/capybara/selector/definition/option.rb +1 -1
- data/lib/capybara/selector/definition/radio_button.rb +2 -2
- data/lib/capybara/selector/definition/select.rb +33 -14
- data/lib/capybara/selector/definition/table.rb +6 -3
- data/lib/capybara/selector/definition/table_row.rb +2 -2
- data/lib/capybara/selector/filter_set.rb +13 -11
- data/lib/capybara/selector/filters/base.rb +6 -1
- data/lib/capybara/selector/filters/locator_filter.rb +1 -1
- data/lib/capybara/selector/regexp_disassembler.rb +7 -0
- data/lib/capybara/selector/selector.rb +13 -3
- data/lib/capybara/selenium/atoms/getAttribute.min.js +1 -1
- data/lib/capybara/selenium/atoms/isDisplayed.min.js +1 -1
- data/lib/capybara/selenium/atoms/src/getAttribute.js +1 -1
- data/lib/capybara/selenium/atoms/src/isDisplayed.js +10 -10
- data/lib/capybara/selenium/driver.rb +86 -24
- data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +24 -21
- data/lib/capybara/selenium/driver_specializations/edge_driver.rb +21 -19
- data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +17 -1
- data/lib/capybara/selenium/driver_specializations/safari_driver.rb +0 -4
- data/lib/capybara/selenium/extensions/file_input_click_emulation.rb +34 -0
- data/lib/capybara/selenium/extensions/find.rb +37 -26
- data/lib/capybara/selenium/extensions/html5_drag.rb +55 -11
- data/lib/capybara/selenium/extensions/modifier_keys_stack.rb +28 -0
- data/lib/capybara/selenium/extensions/scroll.rb +8 -10
- data/lib/capybara/selenium/logger_suppressor.rb +8 -2
- data/lib/capybara/selenium/node.rb +160 -40
- data/lib/capybara/selenium/nodes/chrome_node.rb +72 -12
- data/lib/capybara/selenium/nodes/edge_node.rb +32 -14
- data/lib/capybara/selenium/nodes/firefox_node.rb +28 -32
- data/lib/capybara/selenium/nodes/safari_node.rb +5 -29
- data/lib/capybara/selenium/patches/action_pauser.rb +26 -0
- data/lib/capybara/selenium/patches/atoms.rb +4 -4
- data/lib/capybara/selenium/patches/is_displayed.rb +16 -0
- data/lib/capybara/selenium/patches/logs.rb +32 -7
- data/lib/capybara/server.rb +19 -3
- data/lib/capybara/server/animation_disabler.rb +8 -3
- data/lib/capybara/server/checker.rb +1 -1
- data/lib/capybara/server/middleware.rb +22 -10
- data/lib/capybara/session.rb +66 -40
- data/lib/capybara/session/config.rb +11 -3
- data/lib/capybara/session/matchers.rb +11 -11
- data/lib/capybara/spec/public/offset.js +6 -0
- data/lib/capybara/spec/public/test.js +75 -7
- data/lib/capybara/spec/session/accept_alert_spec.rb +1 -1
- data/lib/capybara/spec/session/all_spec.rb +60 -5
- data/lib/capybara/spec/session/ancestor_spec.rb +5 -0
- data/lib/capybara/spec/session/assert_text_spec.rb +9 -5
- data/lib/capybara/spec/session/check_spec.rb +6 -0
- data/lib/capybara/spec/session/click_button_spec.rb +16 -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 +55 -0
- data/lib/capybara/spec/session/has_ancestor_spec.rb +2 -0
- data/lib/capybara/spec/session/has_button_spec.rb +51 -0
- data/lib/capybara/spec/session/has_css_spec.rb +26 -4
- data/lib/capybara/spec/session/has_current_path_spec.rb +15 -2
- 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_table_spec.rb +51 -5
- data/lib/capybara/spec/session/has_text_spec.rb +30 -0
- 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 +394 -9
- data/lib/capybara/spec/session/refresh_spec.rb +2 -1
- 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 -15
- data/lib/capybara/spec/session/selectors_spec.rb +16 -3
- 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 +14 -14
- data/lib/capybara/spec/test_app.rb +27 -21
- data/lib/capybara/spec/views/form.erb +47 -4
- data/lib/capybara/spec/views/offset.erb +32 -0
- data/lib/capybara/spec/views/spatial.erb +31 -0
- data/lib/capybara/spec/views/with_animation.erb +37 -1
- data/lib/capybara/spec/views/with_dragula.erb +24 -0
- data/lib/capybara/spec/views/with_html.erb +24 -2
- data/lib/capybara/spec/views/with_jquery_animation.erb +24 -0
- data/lib/capybara/spec/views/with_js.erb +4 -1
- data/lib/capybara/spec/views/with_jstree.erb +26 -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 +15 -14
- 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 +3 -2
- data/spec/minitest_spec_spec.rb +46 -46
- data/spec/rack_test_spec.rb +38 -15
- data/spec/regexp_dissassembler_spec.rb +52 -38
- data/spec/result_spec.rb +43 -32
- data/spec/rspec/features_spec.rb +4 -1
- data/spec/rspec/scenarios_spec.rb +4 -0
- data/spec/rspec/shared_spec_matchers.rb +68 -56
- data/spec/rspec_spec.rb +9 -5
- data/spec/selector_spec.rb +32 -17
- data/spec/selenium_spec_chrome.rb +78 -11
- data/spec/selenium_spec_chrome_remote.rb +23 -6
- data/spec/selenium_spec_edge.rb +15 -12
- data/spec/selenium_spec_firefox.rb +24 -19
- data/spec/selenium_spec_firefox_remote.rb +0 -8
- data/spec/selenium_spec_ie.rb +1 -6
- data/spec/server_spec.rb +106 -44
- data/spec/session_spec.rb +5 -5
- data/spec/shared_selenium_node.rb +56 -2
- data/spec/shared_selenium_session.rb +122 -15
- data/spec/spec_helper.rb +2 -2
- metadata +63 -17
- data/lib/capybara/spec/session/source_spec.rb +0 -0
@@ -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
|
@@ -54,7 +54,9 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
|
|
54
54
|
# :backspace => send backspace keystrokes to clear the field <br/>
|
55
55
|
# Array => an array of keys to send before the value being set, e.g. [[:command, 'a'], :backspace]
|
56
56
|
def set(value, **options)
|
57
|
-
|
57
|
+
if value.is_a?(Array) && !multiple?
|
58
|
+
raise ArgumentError, "Value cannot be an Array when 'multiple' attribute is not present. Not a #{value.class}"
|
59
|
+
end
|
58
60
|
|
59
61
|
tag_name, type = attrs(:tagName, :type).map { |val| val&.downcase }
|
60
62
|
@tag_name ||= tag_name
|
@@ -76,11 +78,13 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
|
|
76
78
|
set_datetime_local(value)
|
77
79
|
when 'color'
|
78
80
|
set_color(value)
|
81
|
+
when 'range'
|
82
|
+
set_range(value)
|
79
83
|
else
|
80
|
-
set_text(value, options)
|
84
|
+
set_text(value, **options)
|
81
85
|
end
|
82
86
|
when 'textarea'
|
83
|
-
set_text(value, options)
|
87
|
+
set_text(value, **options)
|
84
88
|
else
|
85
89
|
set_content_editable(value)
|
86
90
|
end
|
@@ -100,10 +104,23 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
|
|
100
104
|
click_options = ClickOptions.new(keys, options)
|
101
105
|
return native.click if click_options.empty?
|
102
106
|
|
103
|
-
|
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
|
104
121
|
rescue StandardError => e
|
105
122
|
if e.is_a?(::Selenium::WebDriver::Error::ElementClickInterceptedError) ||
|
106
|
-
e.message.
|
123
|
+
e.message.include?('Other element would receive the click')
|
107
124
|
scroll_to_center
|
108
125
|
end
|
109
126
|
|
@@ -112,14 +129,26 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
|
|
112
129
|
|
113
130
|
def right_click(keys = [], **options)
|
114
131
|
click_options = ClickOptions.new(keys, options)
|
115
|
-
|
116
|
-
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
|
117
144
|
end
|
118
145
|
end
|
119
146
|
|
120
147
|
def double_click(keys = [], **options)
|
121
148
|
click_options = ClickOptions.new(keys, options)
|
122
|
-
|
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|
|
123
152
|
click_options.coords? ? action.double_click : action.double_click(native)
|
124
153
|
end
|
125
154
|
end
|
@@ -132,11 +161,17 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
|
|
132
161
|
scroll_if_needed { browser_action.move_to(native).perform }
|
133
162
|
end
|
134
163
|
|
135
|
-
def drag_to(element, **)
|
164
|
+
def drag_to(element, drop_modifiers: [], **)
|
165
|
+
drop_modifiers = Array(drop_modifiers)
|
136
166
|
# Due to W3C spec compliance - The Actions API no longer scrolls to elements when necessary
|
137
167
|
# which means Seleniums `drag_and_drop` is now broken - do it manually
|
138
168
|
scroll_if_needed { browser_action.click_and_hold(native).perform }
|
139
|
-
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
|
140
175
|
end
|
141
176
|
|
142
177
|
def drop(*_)
|
@@ -179,6 +214,10 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
|
|
179
214
|
driver.frame_obscured_at?(x: res['x'], y: res['y'])
|
180
215
|
end
|
181
216
|
|
217
|
+
def rect
|
218
|
+
native.rect
|
219
|
+
end
|
220
|
+
|
182
221
|
protected
|
183
222
|
|
184
223
|
def scroll_if_needed
|
@@ -188,6 +227,21 @@ protected
|
|
188
227
|
yield
|
189
228
|
end
|
190
229
|
|
230
|
+
def scroll_to_center
|
231
|
+
script = <<-'JS'
|
232
|
+
try {
|
233
|
+
arguments[0].scrollIntoView({behavior: 'instant', block: 'center', inline: 'center'});
|
234
|
+
} catch(e) {
|
235
|
+
arguments[0].scrollIntoView(true);
|
236
|
+
}
|
237
|
+
JS
|
238
|
+
begin
|
239
|
+
driver.execute_script(script, self)
|
240
|
+
rescue StandardError
|
241
|
+
# Swallow error if scrollIntoView with options isn't supported
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
191
245
|
private
|
192
246
|
|
193
247
|
def sibling_index(parent, node, selector)
|
@@ -213,7 +267,7 @@ private
|
|
213
267
|
find_xpath(XPath.ancestor(:select)[1]).first
|
214
268
|
end
|
215
269
|
|
216
|
-
def set_text(value, clear: nil, **_unused)
|
270
|
+
def set_text(value, clear: nil, rapid: nil, **_unused)
|
217
271
|
value = value.to_s
|
218
272
|
if value.empty? && clear.nil?
|
219
273
|
native.clear
|
@@ -224,15 +278,24 @@ private
|
|
224
278
|
elsif clear.is_a? Array
|
225
279
|
send_keys(*clear, value)
|
226
280
|
else
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
281
|
+
driver.execute_script 'arguments[0].select()', self unless clear == :none
|
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
|
232
289
|
end
|
233
290
|
end
|
234
291
|
|
235
|
-
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
|
+
|
236
299
|
scroll_if_needed do
|
237
300
|
action_with_modifiers(click_options) do |action|
|
238
301
|
if block_given?
|
@@ -244,21 +307,6 @@ private
|
|
244
307
|
end
|
245
308
|
end
|
246
309
|
|
247
|
-
def scroll_to_center
|
248
|
-
script = <<-'JS'
|
249
|
-
try {
|
250
|
-
arguments[0].scrollIntoView({behavior: 'instant', block: 'center', inline: 'center'});
|
251
|
-
} catch(e) {
|
252
|
-
arguments[0].scrollIntoView(true);
|
253
|
-
}
|
254
|
-
JS
|
255
|
-
begin
|
256
|
-
driver.execute_script(script, self)
|
257
|
-
rescue StandardError # rubocop:disable Lint/HandleExceptions
|
258
|
-
# Swallow error if scrollIntoView with options isn't supported
|
259
|
-
end
|
260
|
-
end
|
261
|
-
|
262
310
|
def set_date(value) # rubocop:disable Naming/AccessorMethodName
|
263
311
|
value = SettableValue.new(value)
|
264
312
|
return set_text(value) unless value.dateable?
|
@@ -287,6 +335,10 @@ private
|
|
287
335
|
update_value_js(value)
|
288
336
|
end
|
289
337
|
|
338
|
+
def set_range(value) # rubocop:disable Naming/AccessorMethodName
|
339
|
+
update_value_js(value)
|
340
|
+
end
|
341
|
+
|
290
342
|
def update_value_js(value)
|
291
343
|
driver.execute_script(<<-JS, self, value)
|
292
344
|
if (arguments[0].readOnly) { return };
|
@@ -302,8 +354,31 @@ private
|
|
302
354
|
end
|
303
355
|
|
304
356
|
def set_file(value) # rubocop:disable Naming/AccessorMethodName
|
305
|
-
|
306
|
-
|
357
|
+
with_file_detector do
|
358
|
+
path_names = value.to_s.empty? ? [] : value
|
359
|
+
file_names = Array(path_names).map do |pn|
|
360
|
+
Pathname.new(pn).absolute? ? pn : File.expand_path(pn)
|
361
|
+
end.join("\n")
|
362
|
+
native.send_keys(file_names)
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
def with_file_detector
|
367
|
+
if driver.options[:browser] == :remote &&
|
368
|
+
bridge.respond_to?(:file_detector) &&
|
369
|
+
bridge.file_detector.nil?
|
370
|
+
begin
|
371
|
+
bridge.file_detector = lambda do |(fn, *)|
|
372
|
+
str = fn.to_s
|
373
|
+
str if File.exist?(str)
|
374
|
+
end
|
375
|
+
yield
|
376
|
+
ensure
|
377
|
+
bridge.file_detector = nil
|
378
|
+
end
|
379
|
+
else
|
380
|
+
yield
|
381
|
+
end
|
307
382
|
end
|
308
383
|
|
309
384
|
def set_content_editable(value) # rubocop:disable Naming/AccessorMethodName
|
@@ -331,7 +406,13 @@ private
|
|
331
406
|
end
|
332
407
|
|
333
408
|
def action_with_modifiers(click_options)
|
334
|
-
actions = browser_action.
|
409
|
+
actions = browser_action.tap do |acts|
|
410
|
+
if click_options.center_offset? && click_options.coords?
|
411
|
+
acts.move_to(native).move_by(*click_options.coords)
|
412
|
+
else
|
413
|
+
acts.move_to(native, *click_options.coords)
|
414
|
+
end
|
415
|
+
end
|
335
416
|
modifiers_down(actions, click_options.keys)
|
336
417
|
yield actions
|
337
418
|
modifiers_up(actions, click_options.keys)
|
@@ -343,32 +424,50 @@ private
|
|
343
424
|
|
344
425
|
def modifiers_down(actions, keys)
|
345
426
|
each_key(keys) { |key| actions.key_down(key) }
|
427
|
+
actions
|
346
428
|
end
|
347
429
|
|
348
430
|
def modifiers_up(actions, keys)
|
349
431
|
each_key(keys) { |key| actions.key_up(key) }
|
432
|
+
actions
|
350
433
|
end
|
351
434
|
|
352
435
|
def browser
|
353
436
|
driver.browser
|
354
437
|
end
|
355
438
|
|
439
|
+
def bridge
|
440
|
+
browser.send(:bridge)
|
441
|
+
end
|
442
|
+
|
356
443
|
def browser_action
|
357
444
|
browser.action
|
358
445
|
end
|
359
446
|
|
360
|
-
def
|
361
|
-
|
362
|
-
|
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
|
363
459
|
when :ctrl then :control
|
364
460
|
when :command, :cmd then :meta
|
365
461
|
else
|
366
462
|
key
|
367
463
|
end
|
368
|
-
yield key
|
369
464
|
end
|
370
465
|
end
|
371
466
|
|
467
|
+
def each_key(keys, &block)
|
468
|
+
normalize_keys(keys).each(&block)
|
469
|
+
end
|
470
|
+
|
372
471
|
def find_context
|
373
472
|
native
|
374
473
|
end
|
@@ -394,6 +493,9 @@ private
|
|
394
493
|
var xpath = '';
|
395
494
|
var pos, tempitem2;
|
396
495
|
|
496
|
+
if (el.getRootNode && el.getRootNode() instanceof ShadowRoot) {
|
497
|
+
return "(: Shadow DOM element - no XPath :)";
|
498
|
+
};
|
397
499
|
while(el !== xml.documentElement) {
|
398
500
|
pos = 0;
|
399
501
|
tempitem2 = el;
|
@@ -435,6 +537,16 @@ private
|
|
435
537
|
})(arguments[0], arguments[1], arguments[2])
|
436
538
|
JS
|
437
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
|
+
|
438
550
|
# SettableValue encapsulates time/date field formatting
|
439
551
|
class SettableValue
|
440
552
|
attr_reader :value
|
@@ -486,8 +598,16 @@ private
|
|
486
598
|
[options[:x], options[:y]]
|
487
599
|
end
|
488
600
|
|
601
|
+
def center_offset?
|
602
|
+
options[:offset] == :center
|
603
|
+
end
|
604
|
+
|
489
605
|
def empty?
|
490
|
-
keys.empty? && !coords?
|
606
|
+
keys.empty? && !coords? && delay.zero?
|
607
|
+
end
|
608
|
+
|
609
|
+
def delay
|
610
|
+
options[:delay] || 0
|
491
611
|
end
|
492
612
|
end
|
493
613
|
private_constant :ClickOptions
|
@@ -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
|
@@ -16,14 +18,16 @@ class Capybara::Selenium::ChromeNode < Capybara::Selenium::Node
|
|
16
18
|
# In Chrome 75+ files are appended (due to WebDriver spec - why?) so we have to clear here if its multiple and already set
|
17
19
|
if browser_version >= 75.0
|
18
20
|
driver.execute_script(<<~JS, self)
|
19
|
-
if (arguments[0].multiple &&
|
21
|
+
if (arguments[0].multiple && arguments[0].files.length){
|
20
22
|
arguments[0].value = null;
|
21
23
|
}
|
22
24
|
JS
|
23
25
|
end
|
24
26
|
super
|
25
27
|
rescue *file_errors => e
|
26
|
-
|
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
|
27
31
|
|
28
32
|
raise
|
29
33
|
end
|
@@ -32,11 +36,15 @@ class Capybara::Selenium::ChromeNode < Capybara::Selenium::Node
|
|
32
36
|
html5_drop(*args)
|
33
37
|
end
|
34
38
|
|
35
|
-
def click(
|
39
|
+
def click(*, **)
|
36
40
|
super
|
41
|
+
rescue ::Selenium::WebDriver::Error::ElementClickInterceptedError
|
42
|
+
raise
|
37
43
|
rescue ::Selenium::WebDriver::Error::WebDriverError => e
|
38
44
|
# chromedriver 74 (at least on mac) raises the wrong error for this
|
39
|
-
|
45
|
+
if e.message.include?('element click intercepted')
|
46
|
+
raise ::Selenium::WebDriver::Error::ElementClickInterceptedError, e.message
|
47
|
+
end
|
40
48
|
|
41
49
|
raise
|
42
50
|
end
|
@@ -53,25 +61,77 @@ class Capybara::Selenium::ChromeNode < Capybara::Selenium::Node
|
|
53
61
|
click unless selected_or_disabled
|
54
62
|
end
|
55
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
|
74
|
+
end
|
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
|
+
|
56
94
|
private
|
57
95
|
|
96
|
+
def perform_legacy_drag(element, drop_modifiers)
|
97
|
+
return super if chromedriver_fixed_actions_key_state? || !w3c? || element.obscured?
|
98
|
+
|
99
|
+
raise ArgumentError, 'Modifier keys are not supported while dragging in this version of Chrome.' unless drop_modifiers.empty?
|
100
|
+
|
101
|
+
# W3C Chrome/chromedriver < 77 doesn't maintain mouse button state across actions API performs
|
102
|
+
# https://bugs.chromium.org/p/chromedriver/issues/detail?id=2981
|
103
|
+
browser_action.release.perform
|
104
|
+
browser_action.click_and_hold(native).move_to(element.native).release.perform
|
105
|
+
end
|
106
|
+
|
58
107
|
def file_errors
|
59
108
|
@file_errors = ::Selenium::WebDriver.logger.suppress_deprecations do
|
60
109
|
[::Selenium::WebDriver::Error::ExpectedError]
|
61
110
|
end
|
62
111
|
end
|
63
112
|
|
64
|
-
def
|
65
|
-
|
113
|
+
def browser_version(to_float: true)
|
114
|
+
caps = capabilities
|
115
|
+
ver = (caps[:browser_version] || caps[:version])
|
116
|
+
ver = ver.to_f if to_float
|
117
|
+
ver
|
118
|
+
end
|
119
|
+
|
120
|
+
def chromedriver_fixed_actions_key_state?
|
121
|
+
Gem::Requirement.new('>= 76.0.3809.68').satisfied_by?(chromedriver_version)
|
122
|
+
end
|
123
|
+
|
124
|
+
def chromedriver_supports_displayed_endpoint?
|
125
|
+
Gem::Requirement.new('>= 76.0.3809.25').satisfied_by?(chromedriver_version)
|
66
126
|
end
|
67
127
|
|
68
|
-
def
|
69
|
-
|
70
|
-
driver.browser.capabilities.is_a?(::Selenium::WebDriver::Remote::W3C::Capabilities)
|
128
|
+
def chromedriver_version
|
129
|
+
Gem::Version.new(capabilities['chrome']['chromedriverVersion'].split(' ')[0]) # rubocop:disable Style/RedundantArgument
|
71
130
|
end
|
72
131
|
|
73
|
-
def
|
74
|
-
|
75
|
-
|
132
|
+
def native_displayed?
|
133
|
+
(driver.options[:native_displayed] != false) &&
|
134
|
+
(w3c? && chromedriver_supports_displayed_endpoint?) &&
|
135
|
+
(!ENV['DISABLE_CAPYBARA_SELENIUM_OPTIMIZATIONS'])
|
76
136
|
end
|
77
137
|
end
|