capybara 3.29.0 → 3.37.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/History.md +229 -15
- data/README.md +13 -4
- data/lib/capybara/config.rb +24 -10
- data/lib/capybara/cucumber.rb +1 -1
- data/lib/capybara/driver/base.rb +8 -0
- data/lib/capybara/driver/node.rb +5 -1
- data/lib/capybara/dsl.rb +5 -3
- data/lib/capybara/helpers.rb +19 -2
- data/lib/capybara/minitest/spec.rb +156 -97
- data/lib/capybara/minitest.rb +232 -144
- data/lib/capybara/node/actions.rb +41 -37
- 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 +35 -21
- data/lib/capybara/node/finders.rb +33 -19
- data/lib/capybara/node/matchers.rb +72 -57
- data/lib/capybara/node/simple.rb +13 -3
- data/lib/capybara/queries/active_element_query.rb +18 -0
- data/lib/capybara/queries/ancestor_query.rb +4 -3
- 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 +91 -30
- data/lib/capybara/queries/sibling_query.rb +4 -3
- 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 +68 -10
- data/lib/capybara/rack_test/driver.rb +6 -5
- data/lib/capybara/rack_test/form.rb +2 -2
- data/lib/capybara/rack_test/node.rb +44 -16
- data/lib/capybara/registration_container.rb +41 -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/matcher_proxies.rb +8 -8
- 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 +16 -8
- 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/rspec/matchers.rb +33 -32
- data/lib/capybara/rspec.rb +2 -0
- data/lib/capybara/selector/builders/css_builder.rb +2 -2
- data/lib/capybara/selector/builders/xpath_builder.rb +4 -2
- data/lib/capybara/selector/css.rb +2 -2
- data/lib/capybara/selector/definition/button.rb +35 -13
- data/lib/capybara/selector/definition/checkbox.rb +3 -3
- 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 +2 -2
- data/lib/capybara/selector/definition/fillable_field.rb +3 -3
- data/lib/capybara/selector/definition/label.rb +5 -3
- data/lib/capybara/selector/definition/link.rb +8 -0
- data/lib/capybara/selector/definition/radio_button.rb +3 -3
- 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/definition.rb +15 -11
- data/lib/capybara/selector/filter_set.rb +17 -17
- data/lib/capybara/selector/filters/base.rb +6 -1
- data/lib/capybara/selector/filters/locator_filter.rb +1 -1
- data/lib/capybara/selector/selector.rb +17 -3
- data/lib/capybara/selector.rb +37 -19
- 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 +84 -17
- data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +11 -13
- data/lib/capybara/selenium/driver_specializations/edge_driver.rb +10 -12
- data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +4 -4
- data/lib/capybara/selenium/extensions/find.rb +4 -4
- data/lib/capybara/selenium/extensions/html5_drag.rb +30 -13
- data/lib/capybara/selenium/extensions/scroll.rb +8 -10
- data/lib/capybara/selenium/logger_suppressor.rb +8 -2
- data/lib/capybara/selenium/node.rb +122 -26
- data/lib/capybara/selenium/nodes/chrome_node.rb +34 -19
- data/lib/capybara/selenium/nodes/edge_node.rb +5 -3
- data/lib/capybara/selenium/nodes/firefox_node.rb +11 -6
- data/lib/capybara/selenium/nodes/safari_node.rb +3 -3
- 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 +38 -15
- data/lib/capybara/server/checker.rb +1 -1
- data/lib/capybara/server/middleware.rb +22 -10
- data/lib/capybara/server.rb +15 -3
- data/lib/capybara/session/config.rb +10 -4
- data/lib/capybara/session/matchers.rb +11 -11
- data/lib/capybara/session.rb +62 -39
- 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/active_element_spec.rb +31 -0
- data/lib/capybara/spec/session/all_spec.rb +63 -12
- data/lib/capybara/spec/session/ancestor_spec.rb +5 -0
- data/lib/capybara/spec/session/assert_text_spec.rb +26 -22
- data/lib/capybara/spec/session/check_spec.rb +15 -0
- data/lib/capybara/spec/session/choose_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 +37 -8
- data/lib/capybara/spec/session/has_any_selectors_spec.rb +4 -0
- data/lib/capybara/spec/session/has_button_spec.rb +75 -0
- data/lib/capybara/spec/session/has_css_spec.rb +14 -10
- data/lib/capybara/spec/session/has_current_path_spec.rb +17 -4
- data/lib/capybara/spec/session/has_field_spec.rb +41 -1
- data/lib/capybara/spec/session/has_link_spec.rb +30 -0
- data/lib/capybara/spec/session/has_select_spec.rb +36 -8
- data/lib/capybara/spec/session/has_selector_spec.rb +19 -4
- data/lib/capybara/spec/session/has_table_spec.rb +51 -5
- data/lib/capybara/spec/session/has_text_spec.rb +21 -1
- 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 +226 -33
- 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 -4
- data/lib/capybara/spec/session/scroll_spec.rb +4 -4
- data/lib/capybara/spec/session/selectors_spec.rb +15 -2
- data/lib/capybara/spec/session/visit_spec.rb +20 -0
- 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 +9 -9
- data/lib/capybara/spec/session/window/windows_spec.rb +1 -1
- data/lib/capybara/spec/spec_helper.rb +17 -17
- data/lib/capybara/spec/test_app.rb +89 -29
- data/lib/capybara/spec/views/animated.erb +1 -1
- data/lib/capybara/spec/views/form.erb +52 -6
- data/lib/capybara/spec/views/frame_child.erb +1 -1
- data/lib/capybara/spec/views/frame_one.erb +1 -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 +1 -1
- data/lib/capybara/spec/views/offset.erb +2 -1
- 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 +2 -2
- data/lib/capybara/spec/views/scroll.erb +2 -1
- data/lib/capybara/spec/views/spatial.erb +1 -1
- data/lib/capybara/spec/views/with_animation.erb +10 -3
- data/lib/capybara/spec/views/with_base_tag.erb +2 -2
- data/lib/capybara/spec/views/with_dragula.erb +5 -3
- data/lib/capybara/spec/views/with_fixed_header_footer.erb +2 -1
- data/lib/capybara/spec/views/with_hover.erb +2 -2
- data/lib/capybara/spec/views/with_html.erb +3 -3
- data/lib/capybara/spec/views/with_jquery_animation.erb +24 -0
- data/lib/capybara/spec/views/with_js.erb +5 -3
- data/lib/capybara/spec/views/with_jstree.erb +1 -1
- data/lib/capybara/spec/views/with_namespace.erb +1 -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 +3 -3
- 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 +4 -8
- data/lib/capybara.rb +36 -29
- data/spec/basic_node_spec.rb +25 -11
- data/spec/capybara_spec.rb +1 -1
- data/spec/dsl_spec.rb +18 -5
- data/spec/fixtures/selenium_driver_rspec_failure.rb +2 -2
- data/spec/fixtures/selenium_driver_rspec_success.rb +3 -3
- data/spec/minitest_spec.rb +3 -2
- data/spec/minitest_spec_spec.rb +46 -46
- data/spec/rack_test_spec.rb +43 -11
- data/spec/regexp_dissassembler_spec.rb +40 -36
- data/spec/result_spec.rb +53 -45
- data/spec/rspec/features_spec.rb +7 -4
- data/spec/rspec/scenarios_spec.rb +5 -1
- data/spec/rspec/shared_spec_matchers.rb +68 -56
- data/spec/rspec_spec.rb +8 -4
- data/spec/sauce_spec_chrome.rb +3 -3
- data/spec/selector_spec.rb +19 -4
- data/spec/selenium_spec_chrome.rb +49 -26
- data/spec/selenium_spec_chrome_remote.rb +13 -6
- data/spec/selenium_spec_firefox.rb +29 -17
- data/spec/selenium_spec_firefox_remote.rb +2 -2
- data/spec/selenium_spec_ie.rb +3 -6
- data/spec/selenium_spec_safari.rb +31 -19
- data/spec/server_spec.rb +88 -35
- data/spec/session_spec.rb +1 -1
- data/spec/shared_selenium_node.rb +21 -7
- data/spec/shared_selenium_session.rb +123 -21
- data/spec/spec_helper.rb +2 -2
- metadata +80 -21
- data/lib/capybara/spec/session/source_spec.rb +0 -0
- data/lib/capybara/spec/views/with_title.erb +0 -5
@@ -7,27 +7,25 @@ module Capybara::Selenium::Driver::ChromeDriver
|
|
7
7
|
def self.extended(base)
|
8
8
|
bridge = base.send(:bridge)
|
9
9
|
bridge.extend Capybara::Selenium::ChromeLogs unless bridge.respond_to?(:log)
|
10
|
-
bridge.extend Capybara::Selenium::IsDisplayed unless bridge.
|
10
|
+
bridge.extend Capybara::Selenium::IsDisplayed unless bridge.send(:commands, :is_element_displayed)
|
11
11
|
base.options[:native_displayed] = false if base.options[:native_displayed].nil?
|
12
12
|
end
|
13
13
|
|
14
14
|
def fullscreen_window(handle)
|
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.
|
@@ -40,7 +38,7 @@ module Capybara::Selenium::Driver::ChromeDriver
|
|
40
38
|
return unless @browser
|
41
39
|
|
42
40
|
switch_to_window(window_handles.first)
|
43
|
-
window_handles.slice(1
|
41
|
+
window_handles.slice(1..).each { |win| close_window(win) }
|
44
42
|
return super if chromedriver_version < 73
|
45
43
|
|
46
44
|
timer = Capybara::Helpers.timer(expire_in: 10)
|
@@ -65,7 +63,7 @@ private
|
|
65
63
|
end
|
66
64
|
|
67
65
|
def clear_all_storage?
|
68
|
-
storage_clears.none?
|
66
|
+
storage_clears.none? false
|
69
67
|
end
|
70
68
|
|
71
69
|
def uniform_storage_clear?
|
@@ -96,7 +94,7 @@ private
|
|
96
94
|
|
97
95
|
def execute_cdp(cmd, params = {})
|
98
96
|
if browser.respond_to? :execute_cdp
|
99
|
-
browser.execute_cdp(cmd, params)
|
97
|
+
browser.execute_cdp(cmd, **params)
|
100
98
|
else
|
101
99
|
args = { cmd: cmd, params: params }
|
102
100
|
result = bridge.http.call(:post, "session/#{bridge.session_id}/goog/cdp/execute", args)
|
@@ -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.
|
@@ -41,7 +39,7 @@ module Capybara::Selenium::Driver::EdgeDriver
|
|
41
39
|
return unless @browser
|
42
40
|
|
43
41
|
switch_to_window(window_handles.first)
|
44
|
-
window_handles.slice(1
|
42
|
+
window_handles.slice(1..).each { |win| close_window(win) }
|
45
43
|
|
46
44
|
timer = Capybara::Helpers.timer(expire_in: 10)
|
47
45
|
begin
|
@@ -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,13 +46,13 @@ 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
|
53
53
|
|
54
54
|
switch_to_window(window_handles.first)
|
55
|
-
window_handles.slice(1
|
55
|
+
window_handles.slice(1..).each { |win| close_window(win) }
|
56
56
|
super
|
57
57
|
end
|
58
58
|
|
@@ -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);
|
@@ -166,17 +181,18 @@ class Capybara::Selenium::Node
|
|
166
181
|
var dragOverOpts = Object.assign({clientX: targetCenter.x, clientY: targetCenter.y}, opts);
|
167
182
|
var dragOverEvent = new DragEvent('dragover', dragOverOpts);
|
168
183
|
target.dispatchEvent(dragOverEvent);
|
169
|
-
window.setTimeout(dragLeave, step_delay, dragOverEvent.defaultPrevented);
|
184
|
+
window.setTimeout(dragLeave, step_delay, dragOverEvent.defaultPrevented, dragOverOpts);
|
170
185
|
}
|
171
186
|
|
172
|
-
function dragLeave(drop) {
|
173
|
-
var
|
187
|
+
function dragLeave(drop, dragOverOpts) {
|
188
|
+
var dragLeaveOptions = Object.assign({}, opts, dragOverOpts);
|
189
|
+
var dragLeaveEvent = new DragEvent('dragleave', dragLeaveOptions);
|
174
190
|
target.dispatchEvent(dragLeaveEvent);
|
175
191
|
if (drop) {
|
176
|
-
var dropEvent = new DragEvent('drop',
|
192
|
+
var dropEvent = new DragEvent('drop', dragLeaveOptions);
|
177
193
|
target.dispatchEvent(dropEvent);
|
178
194
|
}
|
179
|
-
var dragEndEvent = new DragEvent('dragend',
|
195
|
+
var dragEndEvent = new DragEvent('dragend', dragLeaveOptions);
|
180
196
|
source.dispatchEvent(dragEndEvent);
|
181
197
|
callback.call(true);
|
182
198
|
}
|
@@ -184,7 +200,8 @@ class Capybara::Selenium::Node
|
|
184
200
|
var source = arguments[0],
|
185
201
|
target = arguments[1],
|
186
202
|
step_delay = arguments[2],
|
187
|
-
|
203
|
+
drop_modifier_keys = arguments[3],
|
204
|
+
callback = arguments[4];
|
188
205
|
|
189
206
|
var dt = new DataTransfer();
|
190
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
|
@@ -14,7 +14,7 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def all_text
|
17
|
-
text = driver.evaluate_script('arguments[0].textContent', self)
|
17
|
+
text = driver.evaluate_script('arguments[0].textContent', self) || ''
|
18
18
|
text.gsub(/[\u200b\u200e\u200f]/, '')
|
19
19
|
.gsub(/[\ \n\f\t\v\u2028\u2029]+/, ' ')
|
20
20
|
.gsub(/\A[[:space:]&&[^\u00a0]]+/, '')
|
@@ -53,8 +53,15 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
|
|
53
53
|
# :none => append the new value to the existing value <br/>
|
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
|
+
# @option options [Boolean] :rapid (nil) Whether setting text inputs should use a faster "rapid" mode<br/>
|
57
|
+
# nil => Text inputs with length greater than 30 characters will be set using a faster driver script mode<br/>
|
58
|
+
# true => Rapid mode will be used regardless of input length<br/>
|
59
|
+
# false => Sends keys via conventional mode. This may be required to avoid losing key-presses if you have certain
|
60
|
+
# Javascript interactions on form inputs<br/>
|
56
61
|
def set(value, **options)
|
57
|
-
|
62
|
+
if value.is_a?(Array) && !multiple?
|
63
|
+
raise ArgumentError, "Value cannot be an Array when 'multiple' attribute is not present. Not a #{value.class}"
|
64
|
+
end
|
58
65
|
|
59
66
|
tag_name, type = attrs(:tagName, :type).map { |val| val&.downcase }
|
60
67
|
@tag_name ||= tag_name
|
@@ -76,11 +83,13 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
|
|
76
83
|
set_datetime_local(value)
|
77
84
|
when 'color'
|
78
85
|
set_color(value)
|
86
|
+
when 'range'
|
87
|
+
set_range(value)
|
79
88
|
else
|
80
|
-
set_text(value, options)
|
89
|
+
set_text(value, **options)
|
81
90
|
end
|
82
91
|
when 'textarea'
|
83
|
-
set_text(value, options)
|
92
|
+
set_text(value, **options)
|
84
93
|
else
|
85
94
|
set_content_editable(value)
|
86
95
|
end
|
@@ -100,10 +109,23 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
|
|
100
109
|
click_options = ClickOptions.new(keys, options)
|
101
110
|
return native.click if click_options.empty?
|
102
111
|
|
103
|
-
|
112
|
+
perform_with_options(click_options) do |action|
|
113
|
+
target = click_options.coords? ? nil : native
|
114
|
+
if click_options.delay.zero?
|
115
|
+
action.click(target)
|
116
|
+
else
|
117
|
+
action.click_and_hold(target)
|
118
|
+
if w3c?
|
119
|
+
action.pause(action.pointer_inputs.first, click_options.delay)
|
120
|
+
else
|
121
|
+
action.pause(click_options.delay)
|
122
|
+
end
|
123
|
+
action.release
|
124
|
+
end
|
125
|
+
end
|
104
126
|
rescue StandardError => e
|
105
127
|
if e.is_a?(::Selenium::WebDriver::Error::ElementClickInterceptedError) ||
|
106
|
-
e.message.
|
128
|
+
e.message.include?('Other element would receive the click')
|
107
129
|
scroll_to_center
|
108
130
|
end
|
109
131
|
|
@@ -112,14 +134,26 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
|
|
112
134
|
|
113
135
|
def right_click(keys = [], **options)
|
114
136
|
click_options = ClickOptions.new(keys, options)
|
115
|
-
|
116
|
-
click_options.coords? ?
|
137
|
+
perform_with_options(click_options) do |action|
|
138
|
+
target = click_options.coords? ? nil : native
|
139
|
+
if click_options.delay.zero?
|
140
|
+
action.context_click(target)
|
141
|
+
elsif w3c?
|
142
|
+
action.move_to(target) if target
|
143
|
+
action.pointer_down(:right)
|
144
|
+
.pause(action.pointer_inputs.first, click_options.delay)
|
145
|
+
.pointer_up(:right)
|
146
|
+
else
|
147
|
+
raise ArgumentError, 'Delay is not supported when right clicking with legacy (non-w3c) selenium driver'
|
148
|
+
end
|
117
149
|
end
|
118
150
|
end
|
119
151
|
|
120
152
|
def double_click(keys = [], **options)
|
121
153
|
click_options = ClickOptions.new(keys, options)
|
122
|
-
|
154
|
+
raise ArgumentError, "double_click doesn't support a delay option" unless click_options.delay.zero?
|
155
|
+
|
156
|
+
perform_with_options(click_options) do |action|
|
123
157
|
click_options.coords? ? action.double_click : action.double_click(native)
|
124
158
|
end
|
125
159
|
end
|
@@ -132,11 +166,17 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
|
|
132
166
|
scroll_if_needed { browser_action.move_to(native).perform }
|
133
167
|
end
|
134
168
|
|
135
|
-
def drag_to(element, **)
|
169
|
+
def drag_to(element, drop_modifiers: [], **)
|
170
|
+
drop_modifiers = Array(drop_modifiers)
|
136
171
|
# Due to W3C spec compliance - The Actions API no longer scrolls to elements when necessary
|
137
172
|
# which means Seleniums `drag_and_drop` is now broken - do it manually
|
138
173
|
scroll_if_needed { browser_action.click_and_hold(native).perform }
|
139
|
-
element.scroll_if_needed { browser_action.move_to(element.native).release.perform }
|
174
|
+
# element.scroll_if_needed { browser_action.move_to(element.native).release.perform }
|
175
|
+
element.scroll_if_needed do
|
176
|
+
keys_down = modifiers_down(browser_action, drop_modifiers)
|
177
|
+
keys_up = modifiers_up(keys_down.move_to(element.native).release, drop_modifiers)
|
178
|
+
keys_up.perform
|
179
|
+
end
|
140
180
|
end
|
141
181
|
|
142
182
|
def drop(*_)
|
@@ -164,10 +204,6 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
|
|
164
204
|
native.attribute('isContentEditable') == 'true'
|
165
205
|
end
|
166
206
|
|
167
|
-
def ==(other)
|
168
|
-
native == other.native
|
169
|
-
end
|
170
|
-
|
171
207
|
def path
|
172
208
|
driver.evaluate_script GET_XPATH_SCRIPT, self
|
173
209
|
end
|
@@ -183,6 +219,13 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
|
|
183
219
|
native.rect
|
184
220
|
end
|
185
221
|
|
222
|
+
def shadow_root
|
223
|
+
raise_error 'You must be using Selenium 4.1+ for shadow_root support' unless native.respond_to? :shadow_root
|
224
|
+
|
225
|
+
root = native.shadow_root
|
226
|
+
root && build_node(native.shadow_root)
|
227
|
+
end
|
228
|
+
|
186
229
|
protected
|
187
230
|
|
188
231
|
def scroll_if_needed
|
@@ -202,7 +245,7 @@ protected
|
|
202
245
|
JS
|
203
246
|
begin
|
204
247
|
driver.execute_script(script, self)
|
205
|
-
rescue StandardError
|
248
|
+
rescue StandardError
|
206
249
|
# Swallow error if scrollIntoView with options isn't supported
|
207
250
|
end
|
208
251
|
end
|
@@ -232,26 +275,38 @@ private
|
|
232
275
|
find_xpath(XPath.ancestor(:select)[1]).first
|
233
276
|
end
|
234
277
|
|
235
|
-
def set_text(value, clear: nil, **_unused)
|
278
|
+
def set_text(value, clear: nil, rapid: nil, **_unused)
|
236
279
|
value = value.to_s
|
237
280
|
if value.empty? && clear.nil?
|
238
281
|
native.clear
|
239
282
|
elsif clear == :backspace
|
240
283
|
# Clear field by sending the correct number of backspace keys.
|
241
284
|
backspaces = [:backspace] * self.value.to_s.length
|
242
|
-
send_keys(
|
285
|
+
send_keys(:end, *backspaces, value)
|
243
286
|
elsif clear.is_a? Array
|
244
287
|
send_keys(*clear, value)
|
245
288
|
else
|
246
289
|
driver.execute_script 'arguments[0].select()', self unless clear == :none
|
247
|
-
|
290
|
+
if rapid == true || ((value.length > auto_rapid_set_length) && rapid != false)
|
291
|
+
send_keys(value[0..3])
|
292
|
+
driver.execute_script RAPID_APPEND_TEXT, self, value[4...-3]
|
293
|
+
send_keys(value[-3..])
|
294
|
+
else
|
295
|
+
send_keys(value)
|
296
|
+
end
|
248
297
|
end
|
249
298
|
end
|
250
299
|
|
251
|
-
def
|
300
|
+
def auto_rapid_set_length
|
301
|
+
30
|
302
|
+
end
|
303
|
+
|
304
|
+
def perform_with_options(click_options, &block)
|
305
|
+
raise ArgumentError, 'A block must be provided' unless block
|
306
|
+
|
252
307
|
scroll_if_needed do
|
253
308
|
action_with_modifiers(click_options) do |action|
|
254
|
-
if
|
309
|
+
if block
|
255
310
|
yield action
|
256
311
|
else
|
257
312
|
click_options.coords? ? action.click : action.click(native)
|
@@ -288,6 +343,10 @@ private
|
|
288
343
|
update_value_js(value)
|
289
344
|
end
|
290
345
|
|
346
|
+
def set_range(value) # rubocop:disable Naming/AccessorMethodName
|
347
|
+
update_value_js(value)
|
348
|
+
end
|
349
|
+
|
291
350
|
def update_value_js(value)
|
292
351
|
driver.execute_script(<<-JS, self, value)
|
293
352
|
if (arguments[0].readOnly) { return };
|
@@ -373,10 +432,12 @@ private
|
|
373
432
|
|
374
433
|
def modifiers_down(actions, keys)
|
375
434
|
each_key(keys) { |key| actions.key_down(key) }
|
435
|
+
actions
|
376
436
|
end
|
377
437
|
|
378
438
|
def modifiers_up(actions, keys)
|
379
439
|
each_key(keys) { |key| actions.key_up(key) }
|
440
|
+
actions
|
380
441
|
end
|
381
442
|
|
382
443
|
def browser
|
@@ -391,18 +452,30 @@ private
|
|
391
452
|
browser.action
|
392
453
|
end
|
393
454
|
|
394
|
-
def
|
395
|
-
|
396
|
-
|
455
|
+
def capabilities
|
456
|
+
browser.capabilities
|
457
|
+
end
|
458
|
+
|
459
|
+
def w3c?
|
460
|
+
(defined?(Selenium::WebDriver::VERSION) && (Selenium::WebDriver::VERSION.to_f >= 4)) ||
|
461
|
+
capabilities.is_a?(::Selenium::WebDriver::Remote::W3C::Capabilities)
|
462
|
+
end
|
463
|
+
|
464
|
+
def normalize_keys(keys)
|
465
|
+
keys.map do |key|
|
466
|
+
case key
|
397
467
|
when :ctrl then :control
|
398
468
|
when :command, :cmd then :meta
|
399
469
|
else
|
400
470
|
key
|
401
471
|
end
|
402
|
-
yield key
|
403
472
|
end
|
404
473
|
end
|
405
474
|
|
475
|
+
def each_key(keys, &block)
|
476
|
+
normalize_keys(keys).each(&block)
|
477
|
+
end
|
478
|
+
|
406
479
|
def find_context
|
407
480
|
native
|
408
481
|
end
|
@@ -423,11 +496,20 @@ private
|
|
423
496
|
JS
|
424
497
|
end
|
425
498
|
|
499
|
+
def native_id
|
500
|
+
# Selenium 3 -> 4 changed the return of ref
|
501
|
+
type_or_id, id = native.ref
|
502
|
+
id || type_or_id
|
503
|
+
end
|
504
|
+
|
426
505
|
GET_XPATH_SCRIPT = <<~'JS'
|
427
506
|
(function(el, xml){
|
428
507
|
var xpath = '';
|
429
508
|
var pos, tempitem2;
|
430
509
|
|
510
|
+
if (el.getRootNode && el.getRootNode() instanceof ShadowRoot) {
|
511
|
+
return "(: Shadow DOM element - no XPath :)";
|
512
|
+
};
|
431
513
|
while(el !== xml.documentElement) {
|
432
514
|
pos = 0;
|
433
515
|
tempitem2 = el;
|
@@ -469,6 +551,16 @@ private
|
|
469
551
|
})(arguments[0], arguments[1], arguments[2])
|
470
552
|
JS
|
471
553
|
|
554
|
+
RAPID_APPEND_TEXT = <<~'JS'
|
555
|
+
(function(el, value) {
|
556
|
+
value = el.value + value;
|
557
|
+
if (el.maxLength && el.maxLength != -1){
|
558
|
+
value = value.slice(0, el.maxLength);
|
559
|
+
}
|
560
|
+
el.value = value;
|
561
|
+
})(arguments[0], arguments[1])
|
562
|
+
JS
|
563
|
+
|
472
564
|
# SettableValue encapsulates time/date field formatting
|
473
565
|
class SettableValue
|
474
566
|
attr_reader :value
|
@@ -525,7 +617,11 @@ private
|
|
525
617
|
end
|
526
618
|
|
527
619
|
def empty?
|
528
|
-
keys.empty? && !coords?
|
620
|
+
keys.empty? && !coords? && delay.zero?
|
621
|
+
end
|
622
|
+
|
623
|
+
def delay
|
624
|
+
options[:delay] || 0
|
529
625
|
end
|
530
626
|
end
|
531
627
|
private_constant :ClickOptions
|