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
@@ -6,11 +6,19 @@ module Capybara
|
|
6
6
|
class TextQuery < BaseQuery
|
7
7
|
def initialize(type = nil, expected_text, session_options:, **options) # rubocop:disable Style/OptionalArguments
|
8
8
|
@type = type.nil? ? default_type : type
|
9
|
-
@
|
9
|
+
raise ArgumentError, "#{@type} is not a valid type for a text query" unless valid_types.include?(@type)
|
10
|
+
|
10
11
|
@options = options
|
11
12
|
super(@options)
|
12
13
|
self.session_options = session_options
|
13
14
|
|
15
|
+
if expected_text.nil? && !exact?
|
16
|
+
warn 'Checking for expected text of nil is confusing and/or pointless since it will always match. ' \
|
17
|
+
"Please specify a string or regexp instead. #{Capybara::Helpers.filter_backtrace(caller)}"
|
18
|
+
end
|
19
|
+
|
20
|
+
@expected_text = expected_text.is_a?(Regexp) ? expected_text : expected_text.to_s
|
21
|
+
|
14
22
|
@search_regexp = Capybara::Helpers.to_regexp(@expected_text, exact: exact?)
|
15
23
|
|
16
24
|
assert_valid_keys
|
@@ -83,6 +91,10 @@ module Capybara
|
|
83
91
|
COUNT_KEYS + %i[wait exact normalize_ws]
|
84
92
|
end
|
85
93
|
|
94
|
+
def valid_types
|
95
|
+
%i[all visible]
|
96
|
+
end
|
97
|
+
|
86
98
|
def check_visible_text?
|
87
99
|
@type == :visible
|
88
100
|
end
|
@@ -8,6 +8,7 @@ class Capybara::RackTest::Browser
|
|
8
8
|
|
9
9
|
def initialize(driver)
|
10
10
|
@driver = driver
|
11
|
+
@current_fragment = nil
|
11
12
|
end
|
12
13
|
|
13
14
|
def app
|
@@ -19,6 +20,8 @@ class Capybara::RackTest::Browser
|
|
19
20
|
end
|
20
21
|
|
21
22
|
def visit(path, **attributes)
|
23
|
+
@new_visit_request = true
|
24
|
+
reset_cache!
|
22
25
|
reset_host!
|
23
26
|
process_and_follow_redirects(:get, path, attributes)
|
24
27
|
end
|
@@ -28,20 +31,29 @@ class Capybara::RackTest::Browser
|
|
28
31
|
request(last_request.fullpath, last_request.env)
|
29
32
|
end
|
30
33
|
|
31
|
-
def submit(method, path, attributes)
|
34
|
+
def submit(method, path, attributes, content_type: nil)
|
32
35
|
path = request_path if path.nil? || path.empty?
|
33
|
-
|
36
|
+
uri = build_uri(path)
|
37
|
+
uri.query = '' if method.to_s.casecmp('get').zero?
|
38
|
+
env = { 'HTTP_REFERER' => referer_url }
|
39
|
+
env['CONTENT_TYPE'] = content_type if content_type
|
40
|
+
process_and_follow_redirects(
|
41
|
+
method,
|
42
|
+
uri.to_s,
|
43
|
+
attributes,
|
44
|
+
env
|
45
|
+
)
|
34
46
|
end
|
35
47
|
|
36
48
|
def follow(method, path, **attributes)
|
37
49
|
return if fragment_or_script?(path)
|
38
50
|
|
39
|
-
process_and_follow_redirects(method, path, attributes, 'HTTP_REFERER' =>
|
51
|
+
process_and_follow_redirects(method, path, attributes, 'HTTP_REFERER' => referer_url)
|
40
52
|
end
|
41
53
|
|
42
54
|
def process_and_follow_redirects(method, path, attributes = {}, env = {})
|
55
|
+
@current_fragment = build_uri(path).fragment
|
43
56
|
process(method, path, attributes, env)
|
44
|
-
|
45
57
|
return unless driver.follow_redirects?
|
46
58
|
|
47
59
|
driver.redirect_limit.times do
|
@@ -53,32 +65,42 @@ class Capybara::RackTest::Browser
|
|
53
65
|
end
|
54
66
|
end
|
55
67
|
end
|
56
|
-
|
68
|
+
|
69
|
+
if last_response.redirect? # rubocop:disable Style/GuardClause
|
70
|
+
raise Capybara::InfiniteRedirectError, "redirected more than #{driver.redirect_limit} times, check for infinite redirects."
|
71
|
+
end
|
57
72
|
end
|
58
73
|
|
59
74
|
def process(method, path, attributes = {}, env = {})
|
60
75
|
method = method.downcase
|
61
76
|
new_uri = build_uri(path)
|
62
77
|
@current_scheme, @current_host, @current_port = new_uri.select(:scheme, :host, :port)
|
63
|
-
|
78
|
+
@current_fragment = new_uri.fragment || @current_fragment
|
64
79
|
reset_cache!
|
80
|
+
@new_visit_request = false
|
65
81
|
send(method, new_uri.to_s, attributes, env.merge(options[:headers] || {}))
|
66
82
|
end
|
67
83
|
|
68
84
|
def build_uri(path)
|
69
|
-
URI.parse(path)
|
70
|
-
|
71
|
-
|
72
|
-
|
85
|
+
uri = URI.parse(path)
|
86
|
+
base_uri = base_relative_uri_for(uri)
|
87
|
+
|
88
|
+
uri.path = base_uri.path + uri.path unless uri.absolute? || uri.path.start_with?('/')
|
73
89
|
|
90
|
+
if base_uri.absolute?
|
91
|
+
base_uri.merge(uri)
|
92
|
+
else
|
74
93
|
uri.scheme ||= @current_scheme
|
75
94
|
uri.host ||= @current_host
|
76
95
|
uri.port ||= @current_port unless uri.default_port == @current_port
|
96
|
+
uri
|
77
97
|
end
|
78
98
|
end
|
79
99
|
|
80
100
|
def current_url
|
81
|
-
last_request.url
|
101
|
+
uri = build_uri(last_request.url)
|
102
|
+
uri.fragment = @current_fragment if @current_fragment
|
103
|
+
uri.to_s
|
82
104
|
rescue Rack::Test::Error
|
83
105
|
''
|
84
106
|
end
|
@@ -114,8 +136,39 @@ class Capybara::RackTest::Browser
|
|
114
136
|
dom.title
|
115
137
|
end
|
116
138
|
|
139
|
+
def last_request
|
140
|
+
raise Rack::Test::Error if @new_visit_request
|
141
|
+
|
142
|
+
super
|
143
|
+
end
|
144
|
+
|
145
|
+
def last_response
|
146
|
+
raise Rack::Test::Error if @new_visit_request
|
147
|
+
|
148
|
+
super
|
149
|
+
end
|
150
|
+
|
117
151
|
protected
|
118
152
|
|
153
|
+
def base_href
|
154
|
+
find(:css, 'head > base').first&.[](:href).to_s
|
155
|
+
end
|
156
|
+
|
157
|
+
def base_relative_uri_for(uri)
|
158
|
+
base_uri = URI.parse(base_href)
|
159
|
+
current_uri = URI.parse(safe_last_request&.url.to_s).tap do |c|
|
160
|
+
c.path.sub!(%r{/[^/]*$}, '/') unless uri.path.empty?
|
161
|
+
c.path = '/' if c.path.empty?
|
162
|
+
end
|
163
|
+
|
164
|
+
if [current_uri, base_uri].any?(&:absolute?)
|
165
|
+
current_uri.merge(base_uri)
|
166
|
+
else
|
167
|
+
base_uri.path = current_uri.path if base_uri.path.empty?
|
168
|
+
base_uri
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
119
172
|
def build_rack_mock_session
|
120
173
|
reset_host! unless current_host
|
121
174
|
Rack::MockSession.new(app, current_host)
|
@@ -127,9 +180,21 @@ protected
|
|
127
180
|
'/'
|
128
181
|
end
|
129
182
|
|
183
|
+
def safe_last_request
|
184
|
+
last_request
|
185
|
+
rescue Rack::Test::Error
|
186
|
+
nil
|
187
|
+
end
|
188
|
+
|
130
189
|
private
|
131
190
|
|
132
191
|
def fragment_or_script?(path)
|
133
192
|
path.gsub(/^#{Regexp.escape(request_path)}/, '').start_with?('#') || path.downcase.start_with?('javascript:')
|
134
193
|
end
|
194
|
+
|
195
|
+
def referer_url
|
196
|
+
build_uri(last_request.url).to_s
|
197
|
+
rescue Rack::Test::Error
|
198
|
+
''
|
199
|
+
end
|
135
200
|
end
|
@@ -17,6 +17,7 @@ class Capybara::RackTest::Driver < Capybara::Driver::Base
|
|
17
17
|
def initialize(app, **options)
|
18
18
|
raise ArgumentError, 'rack-test requires a rack application, but none was given' unless app
|
19
19
|
|
20
|
+
super()
|
20
21
|
@app = app
|
21
22
|
@options = DEFAULT_OPTIONS.merge(options)
|
22
23
|
end
|
@@ -42,7 +43,7 @@ class Capybara::RackTest::Driver < Capybara::Driver::Base
|
|
42
43
|
end
|
43
44
|
|
44
45
|
def visit(path, **attributes)
|
45
|
-
browser.visit(path, attributes)
|
46
|
+
browser.visit(path, **attributes)
|
46
47
|
end
|
47
48
|
|
48
49
|
def refresh
|
@@ -97,9 +98,13 @@ class Capybara::RackTest::Driver < Capybara::Driver::Base
|
|
97
98
|
@browser = nil
|
98
99
|
end
|
99
100
|
|
100
|
-
def get(
|
101
|
-
def post(
|
102
|
-
def put(
|
103
|
-
def delete(
|
101
|
+
def get(...); browser.get(...); end
|
102
|
+
def post(...); browser.post(...); end
|
103
|
+
def put(...); browser.put(...); end
|
104
|
+
def delete(...); browser.delete(...); end
|
104
105
|
def header(key, value); browser.header(key, value); end
|
106
|
+
|
107
|
+
def invalid_element_errors
|
108
|
+
[Capybara::RackTest::Errors::StaleElementReferenceError]
|
109
|
+
end
|
105
110
|
end
|
@@ -6,7 +6,7 @@ class Capybara::RackTest::Form < Capybara::RackTest::Node
|
|
6
6
|
# That check should be based solely on the form element's 'enctype' attribute value,
|
7
7
|
# which should probably be provided to Rack::Test in its non-GET request methods.
|
8
8
|
class NilUploadedFile < Rack::Test::UploadedFile
|
9
|
-
def initialize
|
9
|
+
def initialize # rubocop:disable Lint/MissingSuper
|
10
10
|
@empty_file = Tempfile.new('nil_uploaded_file')
|
11
11
|
@empty_file.close
|
12
12
|
end
|
@@ -16,6 +16,8 @@ class Capybara::RackTest::Form < Capybara::RackTest::Node
|
|
16
16
|
def path; @empty_file.path; end
|
17
17
|
def size; 0; end
|
18
18
|
def read; ''; end
|
19
|
+
def append_to(_); end
|
20
|
+
def set_encoding(_); end # rubocop:disable Naming/AccessorMethodName
|
19
21
|
end
|
20
22
|
|
21
23
|
def params(button)
|
@@ -28,19 +30,31 @@ class Capybara::RackTest::Form < Capybara::RackTest::Node
|
|
28
30
|
|
29
31
|
form_elements = native.xpath(form_elements_xpath).reject { |el| submitter?(el) && (el != button.native) }
|
30
32
|
|
31
|
-
form_elements.each_with_object(
|
33
|
+
form_params = form_elements.each_with_object({}.compare_by_identity) do |field, params|
|
32
34
|
case field.name
|
33
35
|
when 'input', 'button' then add_input_param(field, params)
|
34
36
|
when 'select' then add_select_param(field, params)
|
35
37
|
when 'textarea' then add_textarea_param(field, params)
|
36
38
|
end
|
39
|
+
end
|
40
|
+
|
41
|
+
form_params.each_with_object(make_params) do |(name, value), params|
|
42
|
+
merge_param!(params, name, value)
|
37
43
|
end.to_params_hash
|
44
|
+
|
45
|
+
# form_elements.each_with_object(make_params) do |field, params|
|
46
|
+
# case field.name
|
47
|
+
# when 'input', 'button' then add_input_param(field, params)
|
48
|
+
# when 'select' then add_select_param(field, params)
|
49
|
+
# when 'textarea' then add_textarea_param(field, params)
|
50
|
+
# end
|
51
|
+
# end.to_params_hash
|
38
52
|
end
|
39
53
|
|
40
54
|
def submit(button)
|
41
55
|
action = button&.[]('formaction') || native['action']
|
42
56
|
method = button&.[]('formmethod') || request_method
|
43
|
-
driver.submit(method, action.to_s, params(button))
|
57
|
+
driver.submit(method, action.to_s, params(button), content_type: native['enctype'])
|
44
58
|
end
|
45
59
|
|
46
60
|
def multipart?
|
@@ -56,7 +70,7 @@ private
|
|
56
70
|
end
|
57
71
|
|
58
72
|
def request_method
|
59
|
-
self[:method]
|
73
|
+
/post/i.match?(self[:method] || '') ? :post : :get
|
60
74
|
end
|
61
75
|
|
62
76
|
def merge_param!(params, key, value)
|
@@ -86,6 +100,8 @@ private
|
|
86
100
|
|
87
101
|
Capybara::RackTest::Node.new(driver, field).value.to_s
|
88
102
|
when 'file'
|
103
|
+
return if value.empty? && params.keys.include?(name) && Rack::Test::VERSION.to_f >= 2.0 # rubocop:disable Performance/InefficientHashSearch
|
104
|
+
|
89
105
|
if multipart?
|
90
106
|
file_to_upload(value)
|
91
107
|
else
|
@@ -94,7 +110,8 @@ private
|
|
94
110
|
else
|
95
111
|
value
|
96
112
|
end
|
97
|
-
merge_param!(params, name, value)
|
113
|
+
# merge_param!(params, name, value)
|
114
|
+
params[name] = value
|
98
115
|
end
|
99
116
|
|
100
117
|
def file_to_upload(filename)
|
@@ -107,18 +124,23 @@ private
|
|
107
124
|
end
|
108
125
|
|
109
126
|
def add_select_param(field, params)
|
127
|
+
name = field['name']
|
110
128
|
if field.has_attribute?('multiple')
|
111
|
-
field.xpath('.//option[@selected]').
|
112
|
-
merge_param!(params, field['name'], (option['value'] || option.text).to_s)
|
129
|
+
value = field.xpath('.//option[@selected]').map do |option|
|
130
|
+
# merge_param!(params, field['name'], (option['value'] || option.text).to_s)
|
131
|
+
(option['value'] || option.text).to_s
|
113
132
|
end
|
133
|
+
params[name] = value unless value.empty?
|
114
134
|
else
|
115
135
|
option = field.xpath('.//option[@selected]').first || field.xpath('.//option').first
|
116
|
-
merge_param!(params, field['name'], (option['value'] || option.text).to_s) if option
|
136
|
+
# merge_param!(params, field['name'], (option['value'] || option.text).to_s) if option
|
137
|
+
params[name] = (option['value'] || option.text).to_s if option
|
117
138
|
end
|
118
139
|
end
|
119
140
|
|
120
141
|
def add_textarea_param(field, params)
|
121
|
-
merge_param!(params, field['name'], field['_capybara_raw_value'].to_s.gsub(/\n/, "\r\n"))
|
142
|
+
# merge_param!(params, field['name'], field['_capybara_raw_value'].to_s.gsub(/\r?\n/, "\r\n"))
|
143
|
+
params[field['name']] = field['_capybara_raw_value'].to_s.gsub(/\r?\n/, "\r\n")
|
122
144
|
end
|
123
145
|
|
124
146
|
def submitter?(el)
|
@@ -1,23 +1,19 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'capybara/rack_test/errors'
|
4
|
+
require 'capybara/node/whitespace_normalizer'
|
5
|
+
|
3
6
|
class Capybara::RackTest::Node < Capybara::Driver::Node
|
7
|
+
include Capybara::Node::WhitespaceNormalizer
|
8
|
+
|
4
9
|
BLOCK_ELEMENTS = %w[p h1 h2 h3 h4 h5 h6 ol ul pre address blockquote dl div fieldset form hr noscript table].freeze
|
5
10
|
|
6
11
|
def all_text
|
7
|
-
native.text
|
8
|
-
.gsub(/[\u200b\u200e\u200f]/, '')
|
9
|
-
.gsub(/[\ \n\f\t\v\u2028\u2029]+/, ' ')
|
10
|
-
.gsub(/\A[[:space:]&&[^\u00a0]]+/, '')
|
11
|
-
.gsub(/[[:space:]&&[^\u00a0]]+\z/, '')
|
12
|
-
.tr("\u00a0", ' ')
|
12
|
+
normalize_spacing(native.text)
|
13
13
|
end
|
14
14
|
|
15
15
|
def visible_text
|
16
|
-
displayed_text
|
17
|
-
.gsub(/[\ \n]*\n[\ \n]*/, "\n")
|
18
|
-
.gsub(/\A[[:space:]&&[^\u00a0]]+/, '')
|
19
|
-
.gsub(/[[:space:]&&[^\u00a0]]+\z/, '')
|
20
|
-
.tr("\u00a0", ' ')
|
16
|
+
normalize_visible_spacing(displayed_text)
|
21
17
|
end
|
22
18
|
|
23
19
|
def [](name)
|
@@ -43,6 +39,7 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
|
|
43
39
|
|
44
40
|
if radio? then set_radio(value)
|
45
41
|
elsif checkbox? then set_checkbox(value)
|
42
|
+
elsif range? then set_range(value)
|
46
43
|
elsif input_field? then set_input(value)
|
47
44
|
elsif textarea? then native['_capybara_raw_value'] = value.to_s
|
48
45
|
end
|
@@ -61,8 +58,9 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
|
|
61
58
|
native.remove_attribute('selected')
|
62
59
|
end
|
63
60
|
|
64
|
-
def click(keys = [], **
|
65
|
-
|
61
|
+
def click(keys = [], **options)
|
62
|
+
options.delete(:offset)
|
63
|
+
raise ArgumentError, 'The RackTest driver does not support click options' unless keys.empty? && options.empty?
|
66
64
|
|
67
65
|
if link?
|
68
66
|
follow_link
|
@@ -73,6 +71,8 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
|
|
73
71
|
set(!checked?)
|
74
72
|
elsif tag_name == 'label'
|
75
73
|
click_label
|
74
|
+
elsif (details = native.xpath('.//ancestor-or-self::details').last)
|
75
|
+
toggle_details(details)
|
76
76
|
end
|
77
77
|
end
|
78
78
|
|
@@ -102,20 +102,35 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
|
|
102
102
|
end
|
103
103
|
end
|
104
104
|
|
105
|
+
def readonly?
|
106
|
+
# readonly attribute not valid on these input types
|
107
|
+
return false if input_field? && %w[hidden range color checkbox radio file submit image reset button].include?(type)
|
108
|
+
|
109
|
+
super
|
110
|
+
end
|
111
|
+
|
105
112
|
def path
|
106
113
|
native.path
|
107
114
|
end
|
108
115
|
|
109
|
-
def find_xpath(locator)
|
116
|
+
def find_xpath(locator, **_hints)
|
110
117
|
native.xpath(locator).map { |el| self.class.new(driver, el) }
|
111
118
|
end
|
112
119
|
|
113
|
-
def find_css(locator)
|
120
|
+
def find_css(locator, **_hints)
|
114
121
|
native.css(locator, Capybara::RackTest::CSSHandlers.new).map { |el| self.class.new(driver, el) }
|
115
122
|
end
|
116
123
|
|
117
|
-
|
118
|
-
|
124
|
+
public_instance_methods(false).each do |meth_name|
|
125
|
+
alias_method "unchecked_#{meth_name}", meth_name
|
126
|
+
private "unchecked_#{meth_name}" # rubocop:disable Style/AccessModifierDeclarations
|
127
|
+
|
128
|
+
class_eval <<~METHOD, __FILE__, __LINE__ + 1
|
129
|
+
def #{meth_name}(...)
|
130
|
+
stale_check
|
131
|
+
method(:"unchecked_#{meth_name}").call(...)
|
132
|
+
end
|
133
|
+
METHOD
|
119
134
|
end
|
120
135
|
|
121
136
|
protected
|
@@ -125,22 +140,28 @@ protected
|
|
125
140
|
if !string_node.visible?(check_ancestor)
|
126
141
|
''
|
127
142
|
elsif native.text?
|
128
|
-
native
|
129
|
-
|
130
|
-
|
143
|
+
native
|
144
|
+
.text
|
145
|
+
.delete(REMOVED_CHARACTERS)
|
146
|
+
.tr(SQUEEZED_SPACES, ' ')
|
147
|
+
.squeeze(' ')
|
131
148
|
elsif native.element?
|
132
149
|
text = native.children.map do |child|
|
133
150
|
Capybara::RackTest::Node.new(driver, child).displayed_text(check_ancestor: false)
|
134
151
|
end.join || ''
|
135
152
|
text = "\n#{text}\n" if BLOCK_ELEMENTS.include?(tag_name)
|
136
153
|
text
|
137
|
-
else
|
154
|
+
else # rubocop:disable Lint/DuplicateBranch
|
138
155
|
''
|
139
156
|
end
|
140
157
|
end
|
141
158
|
|
142
159
|
private
|
143
160
|
|
161
|
+
def stale_check
|
162
|
+
raise Capybara::RackTest::Errors::StaleElementReferenceError unless native.document == driver.dom
|
163
|
+
end
|
164
|
+
|
144
165
|
def deselect_options
|
145
166
|
select_node.find_xpath('.//option[@selected]').each { |node| node.native.remove_attribute('selected') }
|
146
167
|
end
|
@@ -180,6 +201,14 @@ private
|
|
180
201
|
end
|
181
202
|
end
|
182
203
|
|
204
|
+
def set_range(value) # rubocop:disable Naming/AccessorMethodName
|
205
|
+
min, max, step = (native['min'] || 0).to_f, (native['max'] || 100).to_f, (native['step'] || 1).to_f
|
206
|
+
value = value.to_f
|
207
|
+
value = value.clamp(min, max)
|
208
|
+
value = (((value - min) / step).round * step) + min
|
209
|
+
native['value'] = value.clamp(min, max)
|
210
|
+
end
|
211
|
+
|
183
212
|
def set_input(value) # rubocop:disable Naming/AccessorMethodName
|
184
213
|
if text_or_password? && attribute_is_not_blank?(:maxlength)
|
185
214
|
# Browser behavior for maxlength="0" is inconsistent, so we stick with
|
@@ -195,7 +224,14 @@ private
|
|
195
224
|
end
|
196
225
|
native.remove
|
197
226
|
else
|
198
|
-
|
227
|
+
value.to_s.tap do |set_value|
|
228
|
+
if set_value.end_with?("\n") && form&.css('input, textarea')&.count == 1
|
229
|
+
native['value'] = set_value.to_s.chop
|
230
|
+
Capybara::RackTest::Form.new(driver, form).submit(self)
|
231
|
+
else
|
232
|
+
native['value'] = set_value
|
233
|
+
end
|
234
|
+
end
|
199
235
|
end
|
200
236
|
end
|
201
237
|
|
@@ -204,7 +240,7 @@ private
|
|
204
240
|
end
|
205
241
|
|
206
242
|
def follow_link
|
207
|
-
method = self['data-method'] if driver.options[:respect_data_method]
|
243
|
+
method = self['data-method'] || self['data-turbo-method'] if driver.options[:respect_data_method]
|
208
244
|
method ||= :get
|
209
245
|
driver.follow(method, self[:href].to_s)
|
210
246
|
end
|
@@ -219,6 +255,17 @@ private
|
|
219
255
|
labelled_control.set(!labelled_control.checked?) if checkbox_or_radio?(labelled_control)
|
220
256
|
end
|
221
257
|
|
258
|
+
def toggle_details(details = nil)
|
259
|
+
details ||= native.xpath('.//ancestor-or-self::details').last
|
260
|
+
return unless details
|
261
|
+
|
262
|
+
if details.has_attribute?('open')
|
263
|
+
details.remove_attribute('open')
|
264
|
+
else
|
265
|
+
details.set_attribute('open', 'open')
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
222
269
|
def link?
|
223
270
|
tag_name == 'a' && !self[:href].nil?
|
224
271
|
end
|
@@ -257,6 +304,10 @@ protected
|
|
257
304
|
tag_name == 'textarea'
|
258
305
|
end
|
259
306
|
|
307
|
+
def range?
|
308
|
+
input_field? && type == 'range'
|
309
|
+
end
|
310
|
+
|
260
311
|
OPTION_OWNER_XPATH = XPath.parent(:optgroup, :select, :datalist).to_s.freeze
|
261
312
|
DISABLED_BY_FIELDSET_XPATH = XPath.generate do |x|
|
262
313
|
x.parent(:fieldset)[
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Capybara
|
4
|
+
# @api private
|
5
|
+
class RegistrationContainer
|
6
|
+
def names
|
7
|
+
@registered.keys
|
8
|
+
end
|
9
|
+
|
10
|
+
def [](name)
|
11
|
+
@registered[name]
|
12
|
+
end
|
13
|
+
|
14
|
+
def []=(name, value)
|
15
|
+
Capybara::Helpers.warn 'DEPRECATED: Directly setting drivers/servers is deprecated, please use Capybara.register_driver/register_server instead'
|
16
|
+
@registered[name] = value
|
17
|
+
end
|
18
|
+
|
19
|
+
def method_missing(method_name, ...)
|
20
|
+
if @registered.respond_to?(method_name)
|
21
|
+
Capybara::Helpers.warn "DEPRECATED: Calling '#{method_name}' on the drivers/servers container is deprecated without replacement"
|
22
|
+
return @registered.public_send(method_name, ...)
|
23
|
+
end
|
24
|
+
super
|
25
|
+
end
|
26
|
+
|
27
|
+
def respond_to_missing?(method_name, include_all)
|
28
|
+
@registered.respond_to?(method_name) || super
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def initialize
|
34
|
+
@registered = {}
|
35
|
+
end
|
36
|
+
|
37
|
+
def register(name, block)
|
38
|
+
@registered[name] = block
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
Capybara.register_driver :rack_test do |app|
|
4
|
+
Capybara::RackTest::Driver.new(app)
|
5
|
+
end
|
6
|
+
|
7
|
+
Capybara.register_driver :selenium do |app|
|
8
|
+
Capybara::Selenium::Driver.new(app)
|
9
|
+
end
|
10
|
+
|
11
|
+
Capybara.register_driver :selenium_headless do |app|
|
12
|
+
version = Capybara::Selenium::Driver.load_selenium
|
13
|
+
options_key = Capybara::Selenium::Driver::CAPS_VERSION.satisfied_by?(version) ? :capabilities : :options
|
14
|
+
browser_options = Selenium::WebDriver::Firefox::Options.new.tap do |opts|
|
15
|
+
opts.add_argument '-headless'
|
16
|
+
end
|
17
|
+
Capybara::Selenium::Driver.new(app, **{ :browser => :firefox, options_key => browser_options })
|
18
|
+
end
|
19
|
+
|
20
|
+
Capybara.register_driver :selenium_chrome do |app|
|
21
|
+
version = Capybara::Selenium::Driver.load_selenium
|
22
|
+
options_key = Capybara::Selenium::Driver::CAPS_VERSION.satisfied_by?(version) ? :capabilities : :options
|
23
|
+
browser_options = Selenium::WebDriver::Chrome::Options.new.tap do |opts|
|
24
|
+
# Workaround https://bugs.chromium.org/p/chromedriver/issues/detail?id=2650&q=load&sort=-id&colspec=ID%20Status%20Pri%20Owner%20Summary
|
25
|
+
opts.add_argument('--disable-site-isolation-trials')
|
26
|
+
end
|
27
|
+
|
28
|
+
Capybara::Selenium::Driver.new(app, **{ :browser => :chrome, options_key => browser_options })
|
29
|
+
end
|
30
|
+
|
31
|
+
Capybara.register_driver :selenium_chrome_headless do |app|
|
32
|
+
version = Capybara::Selenium::Driver.load_selenium
|
33
|
+
options_key = Capybara::Selenium::Driver::CAPS_VERSION.satisfied_by?(version) ? :capabilities : :options
|
34
|
+
browser_options = Selenium::WebDriver::Chrome::Options.new.tap do |opts|
|
35
|
+
opts.add_argument('--headless=new')
|
36
|
+
opts.add_argument('--disable-gpu') if Gem.win_platform?
|
37
|
+
# Workaround https://bugs.chromium.org/p/chromedriver/issues/detail?id=2650&q=load&sort=-id&colspec=ID%20Status%20Pri%20Owner%20Summary
|
38
|
+
opts.add_argument('--disable-site-isolation-trials')
|
39
|
+
end
|
40
|
+
|
41
|
+
Capybara::Selenium::Driver.new(app, **{ :browser => :chrome, options_key => browser_options })
|
42
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Puma
|
4
|
+
module MiniSSL
|
5
|
+
class Socket
|
6
|
+
def read_nonblock(size, *_)
|
7
|
+
wait_states = %i[wait_readable wait_writable]
|
8
|
+
|
9
|
+
loop do
|
10
|
+
output = engine_read_all
|
11
|
+
return output if output
|
12
|
+
|
13
|
+
data = @socket.read_nonblock(size, exception: false)
|
14
|
+
raise IO::EAGAINWaitReadable if wait_states.include? data
|
15
|
+
return nil if data.nil?
|
16
|
+
|
17
|
+
@engine.inject(data)
|
18
|
+
output = engine_read_all
|
19
|
+
|
20
|
+
return output if output
|
21
|
+
|
22
|
+
while (neg_data = @engine.extract)
|
23
|
+
@socket.write neg_data
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|