capybara 3.13.2 → 3.40.0
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/.yardopts +1 -0
- data/History.md +587 -16
- data/README.md +240 -90
- data/lib/capybara/config.rb +24 -11
- data/lib/capybara/cucumber.rb +1 -1
- data/lib/capybara/driver/base.rb +8 -0
- data/lib/capybara/driver/node.rb +20 -4
- data/lib/capybara/dsl.rb +5 -3
- data/lib/capybara/helpers.rb +25 -4
- data/lib/capybara/minitest/spec.rb +174 -90
- data/lib/capybara/minitest.rb +256 -142
- data/lib/capybara/node/actions.rb +123 -77
- data/lib/capybara/node/base.rb +20 -12
- data/lib/capybara/node/document.rb +2 -2
- data/lib/capybara/node/document_matchers.rb +3 -3
- data/lib/capybara/node/element.rb +223 -117
- data/lib/capybara/node/finders.rb +81 -71
- data/lib/capybara/node/matchers.rb +271 -134
- data/lib/capybara/node/simple.rb +18 -5
- data/lib/capybara/node/whitespace_normalizer.rb +81 -0
- data/lib/capybara/queries/active_element_query.rb +18 -0
- data/lib/capybara/queries/ancestor_query.rb +8 -9
- data/lib/capybara/queries/base_query.rb +3 -2
- data/lib/capybara/queries/current_path_query.rb +15 -5
- data/lib/capybara/queries/selector_query.rb +364 -54
- data/lib/capybara/queries/sibling_query.rb +8 -6
- data/lib/capybara/queries/style_query.rb +2 -2
- data/lib/capybara/queries/text_query.rb +13 -1
- data/lib/capybara/queries/title_query.rb +1 -1
- data/lib/capybara/rack_test/browser.rb +76 -11
- data/lib/capybara/rack_test/driver.rb +10 -5
- data/lib/capybara/rack_test/errors.rb +6 -0
- data/lib/capybara/rack_test/form.rb +31 -9
- data/lib/capybara/rack_test/node.rb +74 -23
- data/lib/capybara/registration_container.rb +41 -0
- data/lib/capybara/registrations/drivers.rb +42 -0
- data/lib/capybara/registrations/patches/puma_ssl.rb +29 -0
- data/lib/capybara/registrations/servers.rb +66 -0
- data/lib/capybara/result.rb +44 -20
- data/lib/capybara/rspec/matcher_proxies.rb +13 -11
- data/lib/capybara/rspec/matchers/base.rb +31 -16
- data/lib/capybara/rspec/matchers/compound.rb +1 -1
- data/lib/capybara/rspec/matchers/count_sugar.rb +37 -0
- data/lib/capybara/rspec/matchers/have_ancestor.rb +28 -0
- data/lib/capybara/rspec/matchers/have_current_path.rb +2 -2
- data/lib/capybara/rspec/matchers/have_selector.rb +21 -21
- data/lib/capybara/rspec/matchers/have_sibling.rb +27 -0
- data/lib/capybara/rspec/matchers/have_text.rb +4 -4
- 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/rspec/matchers.rb +111 -68
- data/lib/capybara/rspec.rb +2 -0
- data/lib/capybara/selector/builders/css_builder.rb +11 -7
- data/lib/capybara/selector/builders/xpath_builder.rb +5 -3
- data/lib/capybara/selector/css.rb +11 -9
- data/lib/capybara/selector/definition/button.rb +68 -0
- data/lib/capybara/selector/definition/checkbox.rb +26 -0
- data/lib/capybara/selector/definition/css.rb +10 -0
- data/lib/capybara/selector/definition/datalist_input.rb +35 -0
- data/lib/capybara/selector/definition/datalist_option.rb +25 -0
- data/lib/capybara/selector/definition/element.rb +28 -0
- data/lib/capybara/selector/definition/field.rb +40 -0
- data/lib/capybara/selector/definition/fieldset.rb +14 -0
- data/lib/capybara/selector/definition/file_field.rb +13 -0
- data/lib/capybara/selector/definition/fillable_field.rb +33 -0
- data/lib/capybara/selector/definition/frame.rb +17 -0
- data/lib/capybara/selector/definition/id.rb +6 -0
- data/lib/capybara/selector/definition/label.rb +62 -0
- data/lib/capybara/selector/definition/link.rb +55 -0
- data/lib/capybara/selector/definition/link_or_button.rb +16 -0
- data/lib/capybara/selector/definition/option.rb +27 -0
- data/lib/capybara/selector/definition/radio_button.rb +27 -0
- data/lib/capybara/selector/definition/select.rb +81 -0
- data/lib/capybara/selector/definition/table.rb +109 -0
- data/lib/capybara/selector/definition/table_row.rb +21 -0
- data/lib/capybara/selector/definition/xpath.rb +5 -0
- data/lib/capybara/selector/definition.rb +280 -0
- data/lib/capybara/selector/filter_set.rb +19 -18
- data/lib/capybara/selector/filters/base.rb +11 -2
- data/lib/capybara/selector/filters/locator_filter.rb +13 -3
- data/lib/capybara/selector/regexp_disassembler.rb +11 -7
- data/lib/capybara/selector/selector.rb +50 -440
- data/lib/capybara/selector/xpath_extensions.rb +17 -0
- data/lib/capybara/selector.rb +473 -482
- data/lib/capybara/selenium/atoms/getAttribute.min.js +1 -0
- data/lib/capybara/selenium/atoms/isDisplayed.min.js +1 -0
- data/lib/capybara/selenium/atoms/src/getAttribute.js +161 -0
- data/lib/capybara/selenium/atoms/src/isDisplayed.js +454 -0
- data/lib/capybara/selenium/driver.rb +174 -62
- data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +74 -18
- data/lib/capybara/selenium/driver_specializations/edge_driver.rb +128 -0
- data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +37 -3
- data/lib/capybara/selenium/driver_specializations/internet_explorer_driver.rb +14 -1
- data/lib/capybara/selenium/driver_specializations/safari_driver.rb +24 -0
- data/lib/capybara/selenium/extensions/file_input_click_emulation.rb +34 -0
- data/lib/capybara/selenium/extensions/find.rb +68 -45
- data/lib/capybara/selenium/extensions/html5_drag.rb +192 -22
- data/lib/capybara/selenium/extensions/modifier_keys_stack.rb +28 -0
- data/lib/capybara/selenium/extensions/scroll.rb +8 -10
- data/lib/capybara/selenium/node.rb +268 -72
- data/lib/capybara/selenium/nodes/chrome_node.rb +105 -9
- data/lib/capybara/selenium/nodes/edge_node.rb +110 -0
- data/lib/capybara/selenium/nodes/firefox_node.rb +51 -61
- data/lib/capybara/selenium/nodes/ie_node.rb +22 -0
- data/lib/capybara/selenium/nodes/safari_node.rb +118 -0
- data/lib/capybara/selenium/patches/atoms.rb +18 -0
- data/lib/capybara/selenium/patches/is_displayed.rb +16 -0
- data/lib/capybara/selenium/patches/logs.rb +45 -0
- data/lib/capybara/selenium/patches/pause_duration_fix.rb +1 -1
- data/lib/capybara/selenium/patches/persistent_client.rb +20 -0
- data/lib/capybara/server/animation_disabler.rb +43 -21
- data/lib/capybara/server/checker.rb +6 -2
- data/lib/capybara/server/middleware.rb +25 -13
- data/lib/capybara/server.rb +20 -4
- data/lib/capybara/session/config.rb +15 -11
- data/lib/capybara/session/matchers.rb +11 -11
- data/lib/capybara/session.rb +162 -131
- data/lib/capybara/spec/public/offset.js +6 -0
- data/lib/capybara/spec/public/test.js +105 -6
- 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 +89 -15
- data/lib/capybara/spec/session/ancestor_spec.rb +5 -0
- data/lib/capybara/spec/session/assert_current_path_spec.rb +5 -2
- data/lib/capybara/spec/session/assert_text_spec.rb +26 -22
- data/lib/capybara/spec/session/attach_file_spec.rb +64 -31
- data/lib/capybara/spec/session/check_spec.rb +26 -4
- data/lib/capybara/spec/session/choose_spec.rb +14 -2
- data/lib/capybara/spec/session/click_button_spec.rb +109 -61
- data/lib/capybara/spec/session/click_link_or_button_spec.rb +9 -0
- data/lib/capybara/spec/session/click_link_spec.rb +23 -1
- data/lib/capybara/spec/session/current_scope_spec.rb +1 -1
- data/lib/capybara/spec/session/current_url_spec.rb +11 -1
- data/lib/capybara/spec/session/element/matches_selector_spec.rb +40 -39
- data/lib/capybara/spec/session/evaluate_script_spec.rb +12 -0
- data/lib/capybara/spec/session/fill_in_spec.rb +46 -5
- data/lib/capybara/spec/session/find_link_spec.rb +10 -0
- data/lib/capybara/spec/session/find_spec.rb +80 -7
- data/lib/capybara/spec/session/first_spec.rb +2 -2
- data/lib/capybara/spec/session/frame/switch_to_frame_spec.rb +14 -1
- data/lib/capybara/spec/session/frame/within_frame_spec.rb +14 -1
- data/lib/capybara/spec/session/has_all_selectors_spec.rb +5 -5
- data/lib/capybara/spec/session/has_ancestor_spec.rb +46 -0
- data/lib/capybara/spec/session/has_any_selectors_spec.rb +6 -2
- data/lib/capybara/spec/session/has_button_spec.rb +81 -0
- data/lib/capybara/spec/session/has_css_spec.rb +45 -8
- data/lib/capybara/spec/session/has_current_path_spec.rb +22 -7
- data/lib/capybara/spec/session/has_element_spec.rb +47 -0
- data/lib/capybara/spec/session/has_field_spec.rb +59 -1
- data/lib/capybara/spec/session/has_link_spec.rb +40 -0
- data/lib/capybara/spec/session/has_none_selectors_spec.rb +7 -7
- data/lib/capybara/spec/session/has_select_spec.rb +42 -8
- data/lib/capybara/spec/session/has_selector_spec.rb +19 -4
- data/lib/capybara/spec/session/has_sibling_spec.rb +50 -0
- data/lib/capybara/spec/session/has_table_spec.rb +177 -0
- data/lib/capybara/spec/session/has_text_spec.rb +31 -3
- data/lib/capybara/spec/session/html_spec.rb +1 -1
- data/lib/capybara/spec/session/matches_style_spec.rb +6 -4
- data/lib/capybara/spec/session/node_spec.rb +697 -23
- data/lib/capybara/spec/session/node_wrapper_spec.rb +1 -1
- data/lib/capybara/spec/session/refresh_spec.rb +2 -1
- data/lib/capybara/spec/session/reset_session_spec.rb +21 -7
- 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 +9 -7
- data/lib/capybara/spec/session/select_spec.rb +5 -10
- data/lib/capybara/spec/session/selectors_spec.rb +24 -3
- data/lib/capybara/spec/session/uncheck_spec.rb +3 -3
- data/lib/capybara/spec/session/unselect_spec.rb +1 -1
- data/lib/capybara/spec/session/visit_spec.rb +20 -0
- data/lib/capybara/spec/session/window/become_closed_spec.rb +20 -17
- 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 +54 -57
- data/lib/capybara/spec/session/window/windows_spec.rb +2 -2
- data/lib/capybara/spec/session/within_spec.rb +36 -0
- data/lib/capybara/spec/spec_helper.rb +30 -19
- data/lib/capybara/spec/test_app.rb +122 -34
- data/lib/capybara/spec/views/animated.erb +49 -0
- data/lib/capybara/spec/views/form.erb +86 -8
- data/lib/capybara/spec/views/frame_child.erb +3 -2
- data/lib/capybara/spec/views/frame_one.erb +2 -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 +10 -10
- data/lib/capybara/spec/views/offset.erb +33 -0
- 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 +45 -0
- data/lib/capybara/spec/views/scroll.erb +2 -1
- data/lib/capybara/spec/views/spatial.erb +31 -0
- data/lib/capybara/spec/views/tables.erb +67 -0
- data/lib/capybara/spec/views/with_animation.erb +39 -4
- data/lib/capybara/spec/views/with_base_tag.erb +2 -2
- data/lib/capybara/spec/views/with_dragula.erb +24 -0
- data/lib/capybara/spec/views/with_fixed_header_footer.erb +2 -1
- data/lib/capybara/spec/views/with_hover.erb +3 -2
- data/lib/capybara/spec/views/with_hover1.erb +10 -0
- data/lib/capybara/spec/views/with_html.erb +34 -6
- data/lib/capybara/spec/views/with_jquery_animation.erb +24 -0
- data/lib/capybara/spec/views/with_js.erb +7 -4
- data/lib/capybara/spec/views/with_jstree.erb +26 -0
- data/lib/capybara/spec/views/with_namespace.erb +1 -0
- data/lib/capybara/spec/views/with_scope.erb +2 -2
- data/lib/capybara/spec/views/with_scope_other.erb +6 -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 +21 -0
- 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 +14 -18
- data/lib/capybara.rb +91 -126
- data/spec/basic_node_spec.rb +30 -16
- data/spec/capybara_spec.rb +40 -28
- data/spec/counter_spec.rb +35 -0
- data/spec/css_builder_spec.rb +3 -1
- data/spec/css_splitter_spec.rb +1 -1
- data/spec/dsl_spec.rb +33 -22
- data/spec/filter_set_spec.rb +5 -5
- data/spec/fixtures/selenium_driver_rspec_failure.rb +3 -3
- data/spec/fixtures/selenium_driver_rspec_success.rb +3 -3
- data/spec/minitest_spec.rb +24 -2
- data/spec/minitest_spec_spec.rb +60 -45
- data/spec/per_session_config_spec.rb +1 -1
- data/spec/rack_test_spec.rb +131 -98
- data/spec/regexp_dissassembler_spec.rb +53 -39
- data/spec/result_spec.rb +68 -66
- data/spec/rspec/features_spec.rb +9 -4
- data/spec/rspec/scenarios_spec.rb +6 -2
- data/spec/rspec/shared_spec_matchers.rb +137 -98
- data/spec/rspec_matchers_spec.rb +25 -0
- data/spec/rspec_spec.rb +23 -21
- data/spec/sauce_spec_chrome.rb +43 -0
- data/spec/selector_spec.rb +77 -21
- data/spec/selenium_spec_chrome.rb +141 -39
- data/spec/selenium_spec_chrome_remote.rb +32 -17
- data/spec/selenium_spec_edge.rb +36 -8
- data/spec/selenium_spec_firefox.rb +110 -68
- data/spec/selenium_spec_firefox_remote.rb +22 -15
- data/spec/selenium_spec_ie.rb +29 -22
- data/spec/selenium_spec_safari.rb +162 -0
- data/spec/server_spec.rb +153 -81
- data/spec/session_spec.rb +11 -4
- data/spec/shared_selenium_node.rb +79 -0
- data/spec/shared_selenium_session.rb +179 -74
- data/spec/spec_helper.rb +80 -5
- data/spec/whitespace_normalizer_spec.rb +54 -0
- data/spec/xpath_builder_spec.rb +3 -1
- metadata +218 -30
- data/lib/capybara/spec/session/source_spec.rb +0 -0
- data/lib/capybara/spec/views/with_title.erb +0 -5
@@ -0,0 +1,110 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'capybara/selenium/extensions/html5_drag'
|
4
|
+
|
5
|
+
class Capybara::Selenium::EdgeNode < Capybara::Selenium::Node
|
6
|
+
include Html5Drag
|
7
|
+
|
8
|
+
def set_text(value, clear: nil, **_unused)
|
9
|
+
return super unless chrome_edge?
|
10
|
+
|
11
|
+
super.tap do
|
12
|
+
# React doesn't see the chromedriver element clear
|
13
|
+
send_keys(:space, :backspace) if value.to_s.empty? && clear.nil?
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def set_file(value) # rubocop:disable Naming/AccessorMethodName
|
18
|
+
# In Chrome 75+ files are appended (due to WebDriver spec - why?) so we have to clear here if its multiple and already set
|
19
|
+
if chrome_edge?
|
20
|
+
driver.execute_script(<<~JS, self)
|
21
|
+
if (arguments[0].multiple && arguments[0].files.length){
|
22
|
+
arguments[0].value = null;
|
23
|
+
}
|
24
|
+
JS
|
25
|
+
end
|
26
|
+
super
|
27
|
+
end
|
28
|
+
|
29
|
+
def drop(*args)
|
30
|
+
return super unless chrome_edge?
|
31
|
+
|
32
|
+
html5_drop(*args)
|
33
|
+
end
|
34
|
+
|
35
|
+
def click(*, **)
|
36
|
+
super
|
37
|
+
rescue Selenium::WebDriver::Error::InvalidArgumentError => e
|
38
|
+
tag_name, type = attrs(:tagName, :type).map { |val| val&.downcase }
|
39
|
+
if tag_name == 'input' && type == 'file'
|
40
|
+
raise Selenium::WebDriver::Error::InvalidArgumentError, "EdgeChrome can't click on file inputs.\n#{e.message}"
|
41
|
+
end
|
42
|
+
|
43
|
+
raise
|
44
|
+
end
|
45
|
+
|
46
|
+
def disabled?
|
47
|
+
return super unless chrome_edge?
|
48
|
+
|
49
|
+
driver.evaluate_script("arguments[0].matches(':disabled, select:disabled *')", self)
|
50
|
+
end
|
51
|
+
|
52
|
+
def select_option
|
53
|
+
return super unless chrome_edge?
|
54
|
+
|
55
|
+
# To optimize to only one check and then click
|
56
|
+
selected_or_disabled = driver.evaluate_script(<<~JS, self)
|
57
|
+
arguments[0].matches(':disabled, select:disabled *, :checked')
|
58
|
+
JS
|
59
|
+
click unless selected_or_disabled
|
60
|
+
end
|
61
|
+
|
62
|
+
def visible?
|
63
|
+
return super unless chrome_edge? && native_displayed?
|
64
|
+
|
65
|
+
begin
|
66
|
+
bridge.send(:execute, :is_element_displayed, id: native_id)
|
67
|
+
rescue Selenium::WebDriver::Error::UnknownCommandError
|
68
|
+
# If the is_element_displayed command is unknown, no point in trying again
|
69
|
+
driver.options[:native_displayed] = false
|
70
|
+
super
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def send_keys(*args)
|
75
|
+
args.chunk { |inp| inp.is_a?(String) && inp.match?(/\p{Emoji Presentation}/) }
|
76
|
+
.each do |contains_emoji, inputs|
|
77
|
+
if contains_emoji
|
78
|
+
inputs.join.grapheme_clusters.chunk { |gc| gc.match?(/\p{Emoji Presentation}/) }
|
79
|
+
.each do |emoji, clusters|
|
80
|
+
if emoji
|
81
|
+
driver.send(:execute_cdp, 'Input.insertText', text: clusters.join)
|
82
|
+
else
|
83
|
+
super(clusters.join)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
else
|
87
|
+
super(*inputs)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
def browser_version
|
95
|
+
@browser_version ||= begin
|
96
|
+
caps = driver.browser.capabilities
|
97
|
+
(caps[:browser_version] || caps[:version]).to_f
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def chrome_edge?
|
102
|
+
browser_version >= 75
|
103
|
+
end
|
104
|
+
|
105
|
+
def native_displayed?
|
106
|
+
(driver.options[:native_displayed] != false) &&
|
107
|
+
# chromedriver_supports_displayed_endpoint? &&
|
108
|
+
(!ENV['DISABLE_CAPYBARA_SELENIUM_OPTIMIZATIONS'])
|
109
|
+
end
|
110
|
+
end
|
@@ -1,39 +1,35 @@
|
|
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::FirefoxNode < Capybara::Selenium::Node
|
6
7
|
include Html5Drag
|
8
|
+
include FileInputClickEmulation
|
7
9
|
|
8
10
|
def click(keys = [], **options)
|
9
11
|
super
|
10
12
|
rescue ::Selenium::WebDriver::Error::ElementNotInteractableError
|
11
13
|
if tag_name == 'tr'
|
12
|
-
warn 'You are attempting to click a table row which has issues in geckodriver/marionette - '\
|
13
|
-
'see https://github.com/mozilla/geckodriver/issues/1228
|
14
|
+
warn 'You are attempting to click a table row which has issues in geckodriver/marionette - ' \
|
15
|
+
'see https://github.com/mozilla/geckodriver/issues/1228 - Your test should probably be ' \
|
14
16
|
'clicking on a table cell like a user would. Clicking the first cell in the row instead.'
|
15
|
-
return find_css('th:first-child,td:first-child')[0].click(keys, options)
|
17
|
+
return find_css('th:first-child,td:first-child')[0].click(keys, **options)
|
16
18
|
end
|
17
19
|
raise
|
18
20
|
end
|
19
21
|
|
20
22
|
def disabled?
|
21
|
-
|
22
|
-
return super unless browser_version < 61.0
|
23
|
-
|
24
|
-
return true if super
|
25
|
-
|
26
|
-
# workaround for selenium-webdriver/geckodriver reporting elements as enabled when they are nested in disabling elements
|
27
|
-
if %w[option optgroup].include? tag_name
|
28
|
-
find_xpath('parent::*[self::optgroup or self::select]')[0].disabled?
|
29
|
-
else
|
30
|
-
!find_xpath(DISABLED_BY_FIELDSET_XPATH).empty?
|
31
|
-
end
|
23
|
+
driver.evaluate_script("arguments[0].matches(':disabled, select:disabled *')", self)
|
32
24
|
end
|
33
25
|
|
34
26
|
def set_file(value) # rubocop:disable Naming/AccessorMethodName
|
35
27
|
# By default files are appended so we have to clear here if its multiple and already set
|
36
|
-
|
28
|
+
driver.execute_script(<<~JS, self)
|
29
|
+
if (arguments[0].multiple && arguments[0].files.length){
|
30
|
+
arguments[0].value = null;
|
31
|
+
}
|
32
|
+
JS
|
37
33
|
return super if browser_version >= 62.0
|
38
34
|
|
39
35
|
# Workaround lack of support for multiple upload by uploading one at a time
|
@@ -44,23 +40,57 @@ class Capybara::Selenium::FirefoxNode < Capybara::Selenium::Node
|
|
44
40
|
path_names.each { |path| native.send_keys(path) }
|
45
41
|
end
|
46
42
|
|
43
|
+
def focused?
|
44
|
+
driver.evaluate_script('arguments[0] == document.activeElement', self)
|
45
|
+
end
|
46
|
+
|
47
47
|
def send_keys(*args)
|
48
48
|
# https://github.com/mozilla/geckodriver/issues/846
|
49
|
-
return super(*args.map { |arg| arg == :space ? ' ' : arg }) if args.none?
|
49
|
+
return super(*args.map { |arg| arg == :space ? ' ' : arg }) if args.none?(Array)
|
50
|
+
|
51
|
+
native.click unless focused?
|
50
52
|
|
51
|
-
native.click
|
52
53
|
_send_keys(args).perform
|
53
54
|
end
|
54
55
|
|
55
|
-
def
|
56
|
-
|
56
|
+
def drop(*args)
|
57
|
+
html5_drop(*args)
|
58
|
+
end
|
57
59
|
|
58
|
-
|
60
|
+
def hover
|
61
|
+
return super unless browser_version >= 65.0
|
62
|
+
|
63
|
+
# work around issue 2156 - https://github.com/teamcapybara/capybara/issues/2156
|
64
|
+
scroll_if_needed { browser_action.move_to(native, 0, 0).move_to(native).perform }
|
65
|
+
end
|
66
|
+
|
67
|
+
def select_option
|
68
|
+
# To optimize to only one check and then click
|
69
|
+
selected_or_disabled = driver.evaluate_script(<<~JS, self)
|
70
|
+
arguments[0].matches(':disabled, select:disabled *, :checked')
|
71
|
+
JS
|
72
|
+
click unless selected_or_disabled
|
73
|
+
end
|
74
|
+
|
75
|
+
def visible?
|
76
|
+
return super unless native_displayed?
|
77
|
+
|
78
|
+
begin
|
79
|
+
bridge.send(:execute, :is_element_displayed, id: native_id)
|
80
|
+
rescue Selenium::WebDriver::Error::UnknownCommandError
|
81
|
+
# If the is_element_displayed command is unknown, no point in trying again
|
82
|
+
driver.options[:native_displayed] = false
|
83
|
+
super
|
84
|
+
end
|
59
85
|
end
|
60
86
|
|
61
87
|
private
|
62
88
|
|
63
|
-
def
|
89
|
+
def native_displayed?
|
90
|
+
(driver.options[:native_displayed] != false) && !ENV['DISABLE_CAPYBARA_SELENIUM_OPTIMIZATIONS']
|
91
|
+
end
|
92
|
+
|
93
|
+
def perform_with_options(click_options)
|
64
94
|
# Firefox/marionette has an issue clicking with offset near viewport edge
|
65
95
|
# scroll element to middle just in case
|
66
96
|
scroll_to_center if click_options.coords?
|
@@ -92,10 +122,6 @@ private
|
|
92
122
|
actions
|
93
123
|
end
|
94
124
|
|
95
|
-
def bridge
|
96
|
-
driver.browser.send(:bridge)
|
97
|
-
end
|
98
|
-
|
99
125
|
def upload(local_file)
|
100
126
|
return nil unless local_file
|
101
127
|
raise ArgumentError, "You may only upload files: #{local_file.inspect}" unless File.file?(local_file)
|
@@ -107,40 +133,4 @@ private
|
|
107
133
|
def browser_version
|
108
134
|
driver.browser.capabilities[:browser_version].to_f
|
109
135
|
end
|
110
|
-
|
111
|
-
DISABLED_BY_FIELDSET_XPATH = XPath.generate do |x|
|
112
|
-
x.parent(:fieldset)[
|
113
|
-
x.attr(:disabled)
|
114
|
-
] + x.ancestor[
|
115
|
-
~x.self(:legned) |
|
116
|
-
x.preceding_sibling(:legend)
|
117
|
-
][
|
118
|
-
x.parent(:fieldset)[
|
119
|
-
x.attr(:disabled)
|
120
|
-
]
|
121
|
-
]
|
122
|
-
end.to_s.freeze
|
123
|
-
|
124
|
-
class ModifierKeysStack
|
125
|
-
def initialize
|
126
|
-
@stack = []
|
127
|
-
end
|
128
|
-
|
129
|
-
def include?(key)
|
130
|
-
@stack.flatten.include?(key)
|
131
|
-
end
|
132
|
-
|
133
|
-
def press(key)
|
134
|
-
@stack.last.push(key)
|
135
|
-
end
|
136
|
-
|
137
|
-
def push
|
138
|
-
@stack.push []
|
139
|
-
end
|
140
|
-
|
141
|
-
def pop
|
142
|
-
@stack.pop
|
143
|
-
end
|
144
|
-
end
|
145
|
-
private_constant :ModifierKeysStack
|
146
136
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'capybara/selenium/extensions/html5_drag'
|
4
|
+
|
5
|
+
class Capybara::Selenium::IENode < Capybara::Selenium::Node
|
6
|
+
def disabled?
|
7
|
+
# super
|
8
|
+
# optimize to one script call
|
9
|
+
driver.evaluate_script <<~JS.delete("\n"), self
|
10
|
+
arguments[0].msMatchesSelector('
|
11
|
+
:disabled,
|
12
|
+
select:disabled *,
|
13
|
+
optgroup:disabled *,
|
14
|
+
fieldset[disabled],
|
15
|
+
fieldset[disabled] > *:not(legend),
|
16
|
+
fieldset[disabled] > *:not(legend) *,
|
17
|
+
fieldset[disabled] > legend:nth-of-type(n+2),
|
18
|
+
fieldset[disabled] > legend:nth-of-type(n+2) *
|
19
|
+
')
|
20
|
+
JS
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# require 'capybara/selenium/extensions/html5_drag'
|
4
|
+
require 'capybara/selenium/extensions/modifier_keys_stack'
|
5
|
+
|
6
|
+
class Capybara::Selenium::SafariNode < Capybara::Selenium::Node
|
7
|
+
# include Html5Drag
|
8
|
+
|
9
|
+
def click(keys = [], **options)
|
10
|
+
# driver.execute_script('arguments[0].scrollIntoViewIfNeeded({block: "center"})', self)
|
11
|
+
super
|
12
|
+
rescue ::Selenium::WebDriver::Error::ElementNotInteractableError
|
13
|
+
if tag_name == 'tr'
|
14
|
+
warn 'You are attempting to click a table row which has issues in safaridriver - ' \
|
15
|
+
'Your test should probably be clicking on a table cell like a user would. ' \
|
16
|
+
'Clicking the first cell in the row instead.'
|
17
|
+
return find_css('th:first-child,td:first-child')[0].click(keys, **options)
|
18
|
+
end
|
19
|
+
raise
|
20
|
+
rescue ::Selenium::WebDriver::Error::WebDriverError => e
|
21
|
+
raise unless e.instance_of? ::Selenium::WebDriver::Error::WebDriverError
|
22
|
+
|
23
|
+
# Safari doesn't return a specific error here - assume it's an ElementNotInteractableError
|
24
|
+
raise ::Selenium::WebDriver::Error::ElementNotInteractableError,
|
25
|
+
'Non distinct error raised in #click, translated to ElementNotInteractableError for retry'
|
26
|
+
end
|
27
|
+
|
28
|
+
def select_option
|
29
|
+
# To optimize to only one check and then click
|
30
|
+
selected_or_disabled = driver.execute_script(<<~JS, self)
|
31
|
+
arguments[0].closest('select').scrollIntoView();
|
32
|
+
return arguments[0].matches(':disabled, select:disabled *, :checked');
|
33
|
+
JS
|
34
|
+
click unless selected_or_disabled
|
35
|
+
end
|
36
|
+
|
37
|
+
def unselect_option
|
38
|
+
driver.execute_script("arguments[0].closest('select').scrollIntoView()", self)
|
39
|
+
super
|
40
|
+
end
|
41
|
+
|
42
|
+
def visible_text
|
43
|
+
return '' unless visible?
|
44
|
+
|
45
|
+
vis_text = driver.execute_script('return arguments[0].innerText', self)
|
46
|
+
vis_text.squeeze(' ')
|
47
|
+
.gsub(/[\ \n]*\n[\ \n]*/, "\n")
|
48
|
+
.gsub(/\A[[:space:]&&[^\u00a0]]+/, '')
|
49
|
+
.gsub(/[[:space:]&&[^\u00a0]]+\z/, '')
|
50
|
+
.tr("\u00a0", ' ')
|
51
|
+
end
|
52
|
+
|
53
|
+
def disabled?
|
54
|
+
driver.evaluate_script("arguments[0].matches(':disabled, select:disabled *')", self)
|
55
|
+
end
|
56
|
+
|
57
|
+
def set_file(value) # rubocop:disable Naming/AccessorMethodName
|
58
|
+
# By default files are appended so we have to clear here if its multiple and already set
|
59
|
+
native.clear if multiple? && driver.evaluate_script('arguments[0].files', self).any?
|
60
|
+
super
|
61
|
+
end
|
62
|
+
|
63
|
+
def send_keys(*args)
|
64
|
+
if args.none? { |arg| arg.is_a?(Array) || (arg.is_a?(Symbol) && MODIFIER_KEYS.include?(arg)) }
|
65
|
+
return super(*args.map { |arg| arg == :space ? ' ' : arg })
|
66
|
+
end
|
67
|
+
|
68
|
+
native.click
|
69
|
+
_send_keys(args).perform
|
70
|
+
end
|
71
|
+
|
72
|
+
def set_text(value, clear: nil, **_unused)
|
73
|
+
value = value.to_s
|
74
|
+
if clear == :backspace
|
75
|
+
# Clear field by sending the correct number of backspace keys.
|
76
|
+
backspaces = [:backspace] * self.value.to_s.length
|
77
|
+
send_keys([:control, 'e'], *backspaces, value)
|
78
|
+
else
|
79
|
+
super.tap do
|
80
|
+
# React doesn't see the safaridriver element clear
|
81
|
+
send_keys(:space, :backspace) if value.to_s.empty? && clear.nil?
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def hover
|
87
|
+
# Workaround issue where hover would sometimes fail - possibly due to mouse not having moved
|
88
|
+
scroll_if_needed { browser_action.move_to(native, 0, 0).move_to(native).perform }
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
def _send_keys(keys, actions = browser_action, down_keys = ModifierKeysStack.new)
|
94
|
+
case keys
|
95
|
+
when *MODIFIER_KEYS
|
96
|
+
down_keys.press(keys)
|
97
|
+
actions.key_down(keys)
|
98
|
+
when String
|
99
|
+
keys = keys.upcase if down_keys&.include?(:shift)
|
100
|
+
actions.send_keys(keys)
|
101
|
+
when Symbol
|
102
|
+
actions.send_keys(keys)
|
103
|
+
when Array
|
104
|
+
down_keys.push
|
105
|
+
keys.each { |sub_keys| _send_keys(sub_keys, actions, down_keys) }
|
106
|
+
down_keys.pop.reverse_each { |key| actions.key_up(key) }
|
107
|
+
else
|
108
|
+
raise ArgumentError, 'Unknown keys type'
|
109
|
+
end
|
110
|
+
actions
|
111
|
+
end
|
112
|
+
|
113
|
+
MODIFIER_KEYS = %i[control left_control right_control
|
114
|
+
alt left_alt right_alt
|
115
|
+
shift left_shift right_shift
|
116
|
+
meta left_meta right_meta
|
117
|
+
command].freeze
|
118
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CapybaraAtoms
|
4
|
+
private
|
5
|
+
|
6
|
+
def read_atom(function)
|
7
|
+
@atoms ||= Hash.new do |hash, key|
|
8
|
+
hash[key] = begin
|
9
|
+
File.read(File.expand_path("../../atoms/#{key}.min.js", __FILE__))
|
10
|
+
rescue Errno::ENOENT
|
11
|
+
super
|
12
|
+
end
|
13
|
+
end
|
14
|
+
@atoms[function]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
Selenium::WebDriver::Remote::Bridge.prepend CapybaraAtoms unless ENV['DISABLE_CAPYBARA_SELENIUM_OPTIMIZATIONS']
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Capybara
|
4
|
+
module Selenium
|
5
|
+
module IsDisplayed
|
6
|
+
def commands(command)
|
7
|
+
case command
|
8
|
+
when :is_element_displayed
|
9
|
+
[:get, 'session/:session_id/element/:id/displayed']
|
10
|
+
else
|
11
|
+
super
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Capybara
|
4
|
+
module Selenium
|
5
|
+
module ChromeLogs
|
6
|
+
LOG_MSG = <<~MSG
|
7
|
+
Chromedriver 75+ defaults to W3C mode. Please upgrade to chromedriver >= \
|
8
|
+
75.0.3770.90 if you need to access logs while in W3C compliant mode.
|
9
|
+
MSG
|
10
|
+
|
11
|
+
COMMANDS = {
|
12
|
+
get_available_log_types: [:get, 'session/:session_id/se/log/types'],
|
13
|
+
get_log: [:post, 'session/:session_id/se/log'],
|
14
|
+
get_log_legacy: [:post, 'session/:session_id/log']
|
15
|
+
}.freeze
|
16
|
+
|
17
|
+
def commands(command)
|
18
|
+
COMMANDS[command] || super
|
19
|
+
end
|
20
|
+
|
21
|
+
def available_log_types
|
22
|
+
types = execute :get_available_log_types
|
23
|
+
Array(types).map(&:to_sym)
|
24
|
+
rescue ::Selenium::WebDriver::Error::UnknownCommandError
|
25
|
+
raise NotImplementedError, LOG_MSG
|
26
|
+
end
|
27
|
+
|
28
|
+
def log(type)
|
29
|
+
data = begin
|
30
|
+
execute :get_log, {}, type: type.to_s
|
31
|
+
rescue ::Selenium::WebDriver::Error::UnknownCommandError
|
32
|
+
execute :get_log_legacy, {}, type: type.to_s
|
33
|
+
end
|
34
|
+
|
35
|
+
Array(data).map do |l|
|
36
|
+
::Selenium::WebDriver::LogEntry.new l.fetch('level', 'UNKNOWN'), l.fetch('timestamp'), l.fetch('message')
|
37
|
+
rescue KeyError
|
38
|
+
next
|
39
|
+
end
|
40
|
+
rescue ::Selenium::WebDriver::Error::UnknownCommandError
|
41
|
+
raise NotImplementedError, LOG_MSG
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Capybara
|
4
|
+
module Selenium
|
5
|
+
class PersistentClient < ::Selenium::WebDriver::Remote::Http::Default
|
6
|
+
def close
|
7
|
+
super
|
8
|
+
@http.finish if @http&.started?
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def http
|
14
|
+
super.tap do |http|
|
15
|
+
http.start unless http.started?
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -16,43 +16,65 @@ module Capybara
|
|
16
16
|
|
17
17
|
def initialize(app)
|
18
18
|
@app = app
|
19
|
-
@
|
19
|
+
@disable_css_markup = format(DISABLE_CSS_MARKUP_TEMPLATE,
|
20
|
+
selector: self.class.selector_for(Capybara.disable_animation))
|
21
|
+
@disable_js_markup = +DISABLE_JS_MARKUP_TEMPLATE
|
20
22
|
end
|
21
23
|
|
22
24
|
def call(env)
|
23
|
-
|
24
|
-
return [
|
25
|
+
status, headers, body = @app.call(env)
|
26
|
+
return [status, headers, body] unless html_content?(headers)
|
25
27
|
|
26
|
-
|
28
|
+
nonces = directive_nonces(headers).transform_values { |nonce| "nonce=\"#{nonce}\"" if nonce && !nonce.empty? }
|
29
|
+
response = Rack::Response.new([], status, headers)
|
27
30
|
|
28
|
-
|
29
|
-
|
31
|
+
body.each { |html| response.write insert_disable(html, nonces) }
|
32
|
+
body.close if body.respond_to?(:close)
|
30
33
|
|
31
34
|
response.finish
|
32
35
|
end
|
33
36
|
|
34
37
|
private
|
35
38
|
|
36
|
-
attr_reader :
|
39
|
+
attr_reader :disable_css_markup, :disable_js_markup
|
37
40
|
|
38
|
-
def html_content?
|
39
|
-
|
41
|
+
def html_content?(headers)
|
42
|
+
/html/.match?(headers['Content-Type']) # rubocop:todo Performance/StringInclude
|
40
43
|
end
|
41
44
|
|
42
|
-
def insert_disable(html)
|
43
|
-
html.sub(%r{(</head>)},
|
45
|
+
def insert_disable(html, nonces)
|
46
|
+
html.sub(%r{(</head>)}, "<style #{nonces['style-src']}>#{disable_css_markup}</style>\\1")
|
47
|
+
.sub(%r{(</body>)}, "<script #{nonces['script-src']}>#{disable_js_markup}</script>\\1")
|
44
48
|
end
|
45
49
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
50
|
+
def directive_nonces(headers)
|
51
|
+
headers.fetch('Content-Security-Policy', '')
|
52
|
+
.split(';')
|
53
|
+
.map(&:split) # rubocop:disable Style/MapToHash
|
54
|
+
.to_h do |s|
|
55
|
+
[
|
56
|
+
s[0], s[1..].filter_map do |value|
|
57
|
+
/^'nonce-(?<nonce>.+)'/ =~ value
|
58
|
+
nonce
|
59
|
+
end[0]
|
60
|
+
]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
DISABLE_CSS_MARKUP_TEMPLATE = <<~CSS
|
65
|
+
%<selector>s, %<selector>s::before, %<selector>s::after {
|
66
|
+
transition: none !important;
|
67
|
+
animation-duration: 0s !important;
|
68
|
+
animation-delay: 0s !important;
|
69
|
+
scroll-behavior: auto !important;
|
70
|
+
}
|
71
|
+
CSS
|
72
|
+
|
73
|
+
DISABLE_JS_MARKUP_TEMPLATE = <<~SCRIPT
|
74
|
+
//<![CDATA[
|
75
|
+
(typeof jQuery !== 'undefined') && (jQuery.fx.off = true);
|
76
|
+
//]]>
|
77
|
+
SCRIPT
|
56
78
|
end
|
57
79
|
end
|
58
80
|
end
|
@@ -25,11 +25,15 @@ module Capybara
|
|
25
25
|
private
|
26
26
|
|
27
27
|
def http_request(&block)
|
28
|
-
|
28
|
+
make_request(read_timeout: 2, &block)
|
29
29
|
end
|
30
30
|
|
31
31
|
def https_request(&block)
|
32
|
-
|
32
|
+
make_request(**ssl_options, &block)
|
33
|
+
end
|
34
|
+
|
35
|
+
def make_request(**options, &block)
|
36
|
+
Net::HTTP.start(@host, @port, options.merge(max_retries: 0), &block)
|
33
37
|
end
|
34
38
|
|
35
39
|
def ssl_options
|