capybara 3.23.0 → 3.35.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
@@ -3,25 +3,29 @@
|
|
3
3
|
require 'capybara/selenium/nodes/edge_node'
|
4
4
|
|
5
5
|
module Capybara::Selenium::Driver::EdgeDriver
|
6
|
+
def self.extended(base)
|
7
|
+
bridge = base.send(:bridge)
|
8
|
+
bridge.extend Capybara::Selenium::IsDisplayed unless bridge.send(:commands, :is_element_displayed)
|
9
|
+
base.options[:native_displayed] = false if base.options[:native_displayed].nil?
|
10
|
+
end
|
11
|
+
|
6
12
|
def fullscreen_window(handle)
|
7
13
|
return super if edgedriver_version < 75
|
8
14
|
|
9
15
|
within_given_window(handle) do
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
result['value']
|
17
|
-
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']
|
18
22
|
end
|
19
23
|
end
|
20
24
|
|
21
25
|
def resize_window_to(handle, width, height)
|
22
26
|
super
|
23
27
|
rescue Selenium::WebDriver::Error::UnknownError => e
|
24
|
-
raise unless e.message.
|
28
|
+
raise unless e.message.include?('failed to change window state')
|
25
29
|
|
26
30
|
# Chromedriver doesn't wait long enough for state to change when coming out of fullscreen
|
27
31
|
# and raises unnecessary error. Wait a bit and try again.
|
@@ -39,8 +43,8 @@ module Capybara::Selenium::Driver::EdgeDriver
|
|
39
43
|
|
40
44
|
timer = Capybara::Helpers.timer(expire_in: 10)
|
41
45
|
begin
|
42
|
-
@browser.navigate.to('about:blank')
|
43
46
|
clear_storage unless uniform_storage_clear?
|
47
|
+
@browser.navigate.to('about:blank')
|
44
48
|
wait_for_empty_page(timer)
|
45
49
|
rescue *unhandled_alert_errors
|
46
50
|
accept_unhandled_reset_alert
|
@@ -68,17 +72,19 @@ private
|
|
68
72
|
end
|
69
73
|
|
70
74
|
def clear_all_storage?
|
71
|
-
|
75
|
+
storage_clears.none? false
|
72
76
|
end
|
73
77
|
|
74
78
|
def uniform_storage_clear?
|
75
|
-
|
76
|
-
|
79
|
+
storage_clears.uniq { |s| s == false }.length <= 1
|
80
|
+
end
|
81
|
+
|
82
|
+
def storage_clears
|
83
|
+
options.values_at(:clear_session_storage, :clear_local_storage)
|
77
84
|
end
|
78
85
|
|
79
86
|
def clear_storage
|
80
|
-
#
|
81
|
-
# In W3C mode it crashes chromedriver
|
87
|
+
# Edgedriver crashes if attempt to clear storage on about:blank
|
82
88
|
url = current_url
|
83
89
|
super unless url.nil? || url.start_with?('about:')
|
84
90
|
end
|
@@ -106,10 +112,6 @@ private
|
|
106
112
|
::Capybara::Selenium::EdgeNode.new(self, native_node, initial_cache)
|
107
113
|
end
|
108
114
|
|
109
|
-
def bridge
|
110
|
-
browser.send(:bridge)
|
111
|
-
end
|
112
|
-
|
113
115
|
def edgedriver_version
|
114
116
|
@edgedriver_version ||= begin
|
115
117
|
caps = browser.capabilities
|
@@ -5,6 +5,8 @@ require 'capybara/selenium/nodes/firefox_node'
|
|
5
5
|
module Capybara::Selenium::Driver::FirefoxDriver
|
6
6
|
def self.extended(driver)
|
7
7
|
driver.extend Capybara::Selenium::Driver::W3CFirefoxDriver if w3c?(driver)
|
8
|
+
bridge = driver.send(:bridge)
|
9
|
+
bridge.extend Capybara::Selenium::IsDisplayed unless bridge.send(:commands, :is_element_displayed)
|
8
10
|
end
|
9
11
|
|
10
12
|
def self.w3c?(driver)
|
@@ -17,6 +19,7 @@ module Capybara::Selenium::Driver::W3CFirefoxDriver
|
|
17
19
|
class << self
|
18
20
|
def extended(driver)
|
19
21
|
require 'capybara/selenium/patches/pause_duration_fix' if pause_broken?(driver.browser)
|
22
|
+
driver.options[:native_displayed] = false if driver.options[:native_displayed].nil?
|
20
23
|
end
|
21
24
|
|
22
25
|
def pause_broken?(sel_driver)
|
@@ -39,6 +42,15 @@ module Capybara::Selenium::Driver::W3CFirefoxDriver
|
|
39
42
|
# Use instance variable directly so we avoid starting the browser just to reset the session
|
40
43
|
return unless @browser
|
41
44
|
|
45
|
+
if browser_version >= 68
|
46
|
+
begin
|
47
|
+
# Firefox 68 hangs if we try to switch windows while a modal is visible
|
48
|
+
browser.switch_to.alert&.dismiss
|
49
|
+
rescue Selenium::WebDriver::Error::NoSuchAlertError
|
50
|
+
# Swallow
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
42
54
|
switch_to_window(window_handles.first)
|
43
55
|
window_handles.slice(1..-1).each { |win| close_window(win) }
|
44
56
|
super
|
@@ -49,7 +61,7 @@ module Capybara::Selenium::Driver::W3CFirefoxDriver
|
|
49
61
|
accept_modal :confirm, wait: 0.1 do
|
50
62
|
super
|
51
63
|
end
|
52
|
-
rescue Capybara::ModalNotFound
|
64
|
+
rescue Capybara::ModalNotFound
|
53
65
|
# No modal was opened - page has refreshed - ignore
|
54
66
|
end
|
55
67
|
|
@@ -68,6 +80,10 @@ private
|
|
68
80
|
def build_node(native_node, initial_cache = {})
|
69
81
|
::Capybara::Selenium::FirefoxNode.new(self, native_node, initial_cache)
|
70
82
|
end
|
83
|
+
|
84
|
+
def browser_version
|
85
|
+
browser.capabilities[:browser_version].to_f
|
86
|
+
end
|
71
87
|
end
|
72
88
|
|
73
89
|
Capybara::Selenium::Driver.register_specialization :firefox, Capybara::Selenium::Driver::FirefoxDriver
|
@@ -18,10 +18,6 @@ private
|
|
18
18
|
def build_node(native_node, initial_cache = {})
|
19
19
|
::Capybara::Selenium::SafariNode.new(self, native_node, initial_cache)
|
20
20
|
end
|
21
|
-
|
22
|
-
def bridge
|
23
|
-
browser.send(:bridge)
|
24
|
-
end
|
25
21
|
end
|
26
22
|
|
27
23
|
Capybara::Selenium::Driver.register_specialization(/^(safari|Safari_Technology_Preview)$/,
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Capybara::Selenium::Node
|
4
|
+
module FileInputClickEmulation
|
5
|
+
def click(keys = [], **options)
|
6
|
+
super
|
7
|
+
rescue Selenium::WebDriver::Error::InvalidArgumentError
|
8
|
+
return emulate_click if attaching_file? && visible_file_field?
|
9
|
+
|
10
|
+
raise
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def visible_file_field?
|
16
|
+
(attrs(:tagName, :type).map { |val| val&.downcase } == %w[input file]) && visible?
|
17
|
+
end
|
18
|
+
|
19
|
+
def attaching_file?
|
20
|
+
caller_locations.any? { |cl| cl.base_label == 'attach_file' }
|
21
|
+
end
|
22
|
+
|
23
|
+
def emulate_click
|
24
|
+
driver.execute_script(<<~JS, self)
|
25
|
+
arguments[0].dispatchEvent(
|
26
|
+
new MouseEvent('click', {
|
27
|
+
view: window,
|
28
|
+
bubbles: true,
|
29
|
+
cancelable: true
|
30
|
+
}));
|
31
|
+
JS
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -3,42 +3,44 @@
|
|
3
3
|
module Capybara
|
4
4
|
module Selenium
|
5
5
|
module Find
|
6
|
-
def find_xpath(selector, uses_visibility: false, styles: nil, **_options)
|
7
|
-
find_by(:xpath, selector, uses_visibility: uses_visibility, texts: [], styles: styles)
|
6
|
+
def find_xpath(selector, uses_visibility: false, styles: nil, position: false, **_options)
|
7
|
+
find_by(:xpath, selector, uses_visibility: uses_visibility, texts: [], styles: styles, position: position)
|
8
8
|
end
|
9
9
|
|
10
|
-
def find_css(selector, uses_visibility: false, texts: [], styles: nil, **_options)
|
11
|
-
find_by(:css, selector, uses_visibility: uses_visibility, texts: texts, styles: styles)
|
10
|
+
def find_css(selector, uses_visibility: false, texts: [], styles: nil, position: false, **_options)
|
11
|
+
find_by(:css, selector, uses_visibility: uses_visibility, texts: texts, styles: styles, position: position)
|
12
12
|
end
|
13
13
|
|
14
14
|
private
|
15
15
|
|
16
|
-
def find_by(format, selector, uses_visibility:, texts:, styles:)
|
16
|
+
def find_by(format, selector, uses_visibility:, texts:, styles:, position:)
|
17
17
|
els = find_context.find_elements(format, selector)
|
18
18
|
hints = []
|
19
19
|
|
20
20
|
if (els.size > 2) && !ENV['DISABLE_CAPYBARA_SELENIUM_OPTIMIZATIONS']
|
21
|
-
|
22
|
-
|
23
|
-
hints_js, functions = build_hints_js(uses_visibility, styles)
|
24
|
-
|
25
|
-
unless functions.empty?
|
26
|
-
hints = es_context.execute_script(hints_js, els).map! do |results|
|
27
|
-
hint = {}
|
28
|
-
hint[:style] = results.pop if functions.include?(:style_func)
|
29
|
-
hint[:visible] = results.pop if functions.include?(:vis_func)
|
30
|
-
hint
|
31
|
-
end
|
32
|
-
end
|
33
|
-
rescue ::Selenium::WebDriver::Error::StaleElementReferenceError,
|
34
|
-
::Capybara::NotSupportedByDriverError
|
35
|
-
# warn 'Unexpected Stale Element Error - skipping optimization'
|
36
|
-
hints = []
|
37
|
-
end
|
21
|
+
els = filter_by_text(els, texts) unless texts.empty?
|
22
|
+
hints = gather_hints(els, uses_visibility: uses_visibility, styles: styles, position: position)
|
38
23
|
end
|
39
24
|
els.map.with_index { |el, idx| build_node(el, hints[idx] || {}) }
|
40
25
|
end
|
41
26
|
|
27
|
+
def gather_hints(elements, uses_visibility:, styles:, position:)
|
28
|
+
hints_js, functions = build_hints_js(uses_visibility, styles, position)
|
29
|
+
return [] unless functions.any?
|
30
|
+
|
31
|
+
(es_context.execute_script(hints_js, elements) || []).map! do |results|
|
32
|
+
hint = {}
|
33
|
+
hint[:style] = results.pop if functions.include?(:style_func)
|
34
|
+
hint[:position] = results.pop if functions.include?(:position_func)
|
35
|
+
hint[:visible] = results.pop if functions.include?(:vis_func)
|
36
|
+
hint
|
37
|
+
end
|
38
|
+
rescue ::Selenium::WebDriver::Error::StaleElementReferenceError,
|
39
|
+
::Capybara::NotSupportedByDriverError
|
40
|
+
# warn 'Unexpected Stale Element Error - skipping optimization'
|
41
|
+
[]
|
42
|
+
end
|
43
|
+
|
42
44
|
def filter_by_text(elements, texts)
|
43
45
|
es_context.execute_script <<~JS, elements, texts
|
44
46
|
var texts = arguments[1];
|
@@ -49,7 +51,7 @@ module Capybara
|
|
49
51
|
JS
|
50
52
|
end
|
51
53
|
|
52
|
-
def build_hints_js(uses_visibility, styles)
|
54
|
+
def build_hints_js(uses_visibility, styles, position)
|
53
55
|
functions = []
|
54
56
|
hints_js = +''
|
55
57
|
|
@@ -60,6 +62,15 @@ module Capybara
|
|
60
62
|
functions << :vis_func
|
61
63
|
end
|
62
64
|
|
65
|
+
if position
|
66
|
+
hints_js << <<~POSITION_JS
|
67
|
+
var position_func = function(el){
|
68
|
+
return el.getBoundingClientRect();
|
69
|
+
};
|
70
|
+
POSITION_JS
|
71
|
+
functions << :position_func
|
72
|
+
end
|
73
|
+
|
63
74
|
if styles.is_a? Hash
|
64
75
|
hints_js << <<~STYLE_JS
|
65
76
|
var style_func = function(el){
|
@@ -89,9 +100,9 @@ module Capybara
|
|
89
100
|
def is_displayed_atom # rubocop:disable Naming/PredicateName
|
90
101
|
@@is_displayed_atom ||= begin # rubocop:disable Style/ClassVars
|
91
102
|
browser.send(:bridge).send(:read_atom, 'isDisplayed')
|
92
|
-
|
93
|
-
|
94
|
-
|
103
|
+
rescue StandardError
|
104
|
+
# If the atom doesn't exist or other error
|
105
|
+
''
|
95
106
|
end
|
96
107
|
end
|
97
108
|
end
|
@@ -4,19 +4,35 @@ 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, 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
|
-
|
11
|
-
|
12
|
+
html5 = !driver.evaluate_script(LEGACY_DRAG_CHECK, self) if html5.nil?
|
13
|
+
if html5
|
14
|
+
perform_html5_drag(element, delay, drop_modifiers)
|
12
15
|
else
|
13
|
-
|
14
|
-
browser_action.release.perform
|
16
|
+
perform_legacy_drag(element, drop_modifiers)
|
15
17
|
end
|
16
18
|
end
|
17
19
|
|
18
20
|
private
|
19
21
|
|
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
|
29
|
+
end
|
30
|
+
|
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)
|
33
|
+
browser_action.release.perform
|
34
|
+
end
|
35
|
+
|
20
36
|
def html5_drop(*args)
|
21
37
|
if args[0].is_a? String
|
22
38
|
input = driver.evaluate_script ATTACH_FILE
|
@@ -81,11 +97,25 @@ class Capybara::Selenium::Node
|
|
81
97
|
JS
|
82
98
|
|
83
99
|
MOUSEDOWN_TRACKER = <<~JS
|
100
|
+
window.capybara_mousedown_prevented = null;
|
84
101
|
document.addEventListener('mousedown', ev => {
|
85
102
|
window.capybara_mousedown_prevented = ev.defaultPrevented;
|
86
103
|
}, { once: true, passive: true })
|
87
104
|
JS
|
88
105
|
|
106
|
+
LEGACY_DRAG_CHECK = <<~JS
|
107
|
+
(function(el){
|
108
|
+
if ([true, null].indexOf(window.capybara_mousedown_prevented) >= 0){
|
109
|
+
return true;
|
110
|
+
}
|
111
|
+
|
112
|
+
do {
|
113
|
+
if (el.draggable) return false;
|
114
|
+
} while (el = el.parentElement );
|
115
|
+
return true;
|
116
|
+
})(arguments[0])
|
117
|
+
JS
|
118
|
+
|
89
119
|
HTML5_DRAG_DROP_SCRIPT = <<~JS
|
90
120
|
function rectCenter(rect){
|
91
121
|
return new DOMPoint(
|
@@ -130,6 +160,14 @@ class Capybara::Selenium::Node
|
|
130
160
|
var targetRect = target.getBoundingClientRect();
|
131
161
|
var sourceCenter = rectCenter(source.getBoundingClientRect());
|
132
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
|
+
|
133
171
|
// fire 2 dragover events to simulate dragging with a direction
|
134
172
|
var entryPoint = pointOnRect(sourceCenter, targetRect)
|
135
173
|
var dragOverOpts = Object.assign({clientX: entryPoint.x, clientY: entryPoint.y}, opts);
|
@@ -143,17 +181,18 @@ class Capybara::Selenium::Node
|
|
143
181
|
var dragOverOpts = Object.assign({clientX: targetCenter.x, clientY: targetCenter.y}, opts);
|
144
182
|
var dragOverEvent = new DragEvent('dragover', dragOverOpts);
|
145
183
|
target.dispatchEvent(dragOverEvent);
|
146
|
-
window.setTimeout(dragLeave, step_delay, dragOverEvent.defaultPrevented);
|
184
|
+
window.setTimeout(dragLeave, step_delay, dragOverEvent.defaultPrevented, dragOverOpts);
|
147
185
|
}
|
148
186
|
|
149
|
-
function dragLeave(drop) {
|
150
|
-
var
|
187
|
+
function dragLeave(drop, dragOverOpts) {
|
188
|
+
var dragLeaveOptions = Object.assign({}, opts, dragOverOpts);
|
189
|
+
var dragLeaveEvent = new DragEvent('dragleave', dragLeaveOptions);
|
151
190
|
target.dispatchEvent(dragLeaveEvent);
|
152
191
|
if (drop) {
|
153
|
-
var dropEvent = new DragEvent('drop',
|
192
|
+
var dropEvent = new DragEvent('drop', dragLeaveOptions);
|
154
193
|
target.dispatchEvent(dropEvent);
|
155
194
|
}
|
156
|
-
var dragEndEvent = new DragEvent('dragend',
|
195
|
+
var dragEndEvent = new DragEvent('dragend', dragLeaveOptions);
|
157
196
|
source.dispatchEvent(dragEndEvent);
|
158
197
|
callback.call(true);
|
159
198
|
}
|
@@ -161,11 +200,16 @@ class Capybara::Selenium::Node
|
|
161
200
|
var source = arguments[0],
|
162
201
|
target = arguments[1],
|
163
202
|
step_delay = arguments[2],
|
164
|
-
|
203
|
+
drop_modifier_keys = arguments[3],
|
204
|
+
callback = arguments[4];
|
165
205
|
|
166
206
|
var dt = new DataTransfer();
|
167
207
|
var opts = { cancelable: true, bubbles: true, dataTransfer: dt };
|
168
208
|
|
209
|
+
while (source && !source.draggable) {
|
210
|
+
source = source.parentElement;
|
211
|
+
}
|
212
|
+
|
169
213
|
if (source.tagName == 'A'){
|
170
214
|
dt.setData('text/uri-list', source.href);
|
171
215
|
dt.setData('text', source.href);
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Capybara::Selenium::Node
|
4
|
+
#
|
5
|
+
# @api private
|
6
|
+
#
|
7
|
+
class ModifierKeysStack
|
8
|
+
def initialize
|
9
|
+
@stack = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def include?(key)
|
13
|
+
@stack.flatten.include?(key)
|
14
|
+
end
|
15
|
+
|
16
|
+
def press(key)
|
17
|
+
@stack.last.push(key)
|
18
|
+
end
|
19
|
+
|
20
|
+
def push
|
21
|
+
@stack.push []
|
22
|
+
end
|
23
|
+
|
24
|
+
def pop
|
25
|
+
@stack.pop
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -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
|