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
@@ -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
|