capybara 3.8.1 → 3.33.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.yardopts +1 -0
- data/History.md +465 -0
- data/License.txt +1 -1
- data/README.md +58 -57
- data/lib/capybara/config.rb +10 -4
- data/lib/capybara/cucumber.rb +1 -1
- data/lib/capybara/driver/base.rb +2 -2
- data/lib/capybara/driver/node.rb +26 -5
- data/lib/capybara/dsl.rb +12 -4
- data/lib/capybara/helpers.rb +8 -4
- data/lib/capybara/minitest/spec.rb +162 -85
- data/lib/capybara/minitest.rb +248 -148
- data/lib/capybara/node/actions.rb +149 -96
- data/lib/capybara/node/base.rb +27 -10
- data/lib/capybara/node/document.rb +12 -0
- data/lib/capybara/node/document_matchers.rb +9 -5
- data/lib/capybara/node/element.rb +254 -109
- data/lib/capybara/node/finders.rb +83 -76
- data/lib/capybara/node/matchers.rb +279 -141
- data/lib/capybara/node/simple.rb +25 -6
- data/lib/capybara/queries/ancestor_query.rb +5 -7
- data/lib/capybara/queries/base_query.rb +11 -5
- data/lib/capybara/queries/current_path_query.rb +3 -3
- data/lib/capybara/queries/match_query.rb +1 -0
- data/lib/capybara/queries/selector_query.rb +467 -103
- data/lib/capybara/queries/sibling_query.rb +5 -4
- data/lib/capybara/queries/style_query.rb +6 -2
- data/lib/capybara/queries/text_query.rb +17 -3
- data/lib/capybara/queries/title_query.rb +2 -2
- data/lib/capybara/rack_test/browser.rb +22 -15
- data/lib/capybara/rack_test/driver.rb +10 -1
- data/lib/capybara/rack_test/errors.rb +6 -0
- data/lib/capybara/rack_test/form.rb +33 -28
- data/lib/capybara/rack_test/node.rb +74 -6
- data/lib/capybara/registration_container.rb +44 -0
- data/lib/capybara/registrations/drivers.rb +36 -0
- data/lib/capybara/registrations/patches/puma_ssl.rb +27 -0
- data/lib/capybara/registrations/servers.rb +44 -0
- data/lib/capybara/result.rb +55 -23
- data/lib/capybara/rspec/features.rb +4 -4
- data/lib/capybara/rspec/matcher_proxies.rb +36 -15
- data/lib/capybara/rspec/matchers/base.rb +111 -0
- data/lib/capybara/rspec/matchers/become_closed.rb +33 -0
- data/lib/capybara/rspec/matchers/compound.rb +88 -0
- 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 +29 -0
- data/lib/capybara/rspec/matchers/have_selector.rb +77 -0
- data/lib/capybara/rspec/matchers/have_sibling.rb +27 -0
- data/lib/capybara/rspec/matchers/have_text.rb +33 -0
- data/lib/capybara/rspec/matchers/have_title.rb +29 -0
- data/lib/capybara/rspec/matchers/match_selector.rb +27 -0
- data/lib/capybara/rspec/matchers/match_style.rb +38 -0
- data/lib/capybara/rspec/matchers/spatial_sugar.rb +39 -0
- data/lib/capybara/rspec/matchers.rb +117 -311
- data/lib/capybara/selector/builders/css_builder.rb +84 -0
- data/lib/capybara/selector/builders/xpath_builder.rb +69 -0
- data/lib/capybara/selector/css.rb +17 -15
- data/lib/capybara/selector/definition/button.rb +52 -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 +27 -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 +54 -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 +277 -0
- data/lib/capybara/selector/filter.rb +1 -0
- data/lib/capybara/selector/filter_set.rb +26 -19
- data/lib/capybara/selector/filters/base.rb +24 -5
- data/lib/capybara/selector/filters/expression_filter.rb +3 -3
- data/lib/capybara/selector/filters/locator_filter.rb +29 -0
- data/lib/capybara/selector/filters/node_filter.rb +16 -2
- data/lib/capybara/selector/regexp_disassembler.rb +214 -0
- data/lib/capybara/selector/selector.rb +73 -367
- data/lib/capybara/selector/xpath_extensions.rb +17 -0
- data/lib/capybara/selector.rb +221 -480
- 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 +203 -86
- data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +88 -14
- data/lib/capybara/selenium/driver_specializations/edge_driver.rb +124 -0
- data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +89 -0
- data/lib/capybara/selenium/driver_specializations/internet_explorer_driver.rb +26 -0
- 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 +110 -0
- data/lib/capybara/selenium/extensions/html5_drag.rb +191 -22
- data/lib/capybara/selenium/extensions/modifier_keys_stack.rb +28 -0
- data/lib/capybara/selenium/extensions/scroll.rb +78 -0
- data/lib/capybara/selenium/logger_suppressor.rb +34 -0
- data/lib/capybara/selenium/node.rb +298 -93
- data/lib/capybara/selenium/nodes/chrome_node.rb +100 -8
- data/lib/capybara/selenium/nodes/edge_node.rb +104 -0
- data/lib/capybara/selenium/nodes/firefox_node.rb +131 -0
- 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/action_pauser.rb +26 -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 -3
- data/lib/capybara/selenium/patches/persistent_client.rb +20 -0
- data/lib/capybara/server/animation_disabler.rb +4 -3
- data/lib/capybara/server/checker.rb +6 -2
- data/lib/capybara/server/middleware.rb +23 -13
- data/lib/capybara/server.rb +30 -7
- data/lib/capybara/session/config.rb +14 -10
- data/lib/capybara/session/matchers.rb +11 -7
- data/lib/capybara/session.rb +152 -111
- data/lib/capybara/spec/public/offset.js +6 -0
- data/lib/capybara/spec/public/test.js +101 -10
- data/lib/capybara/spec/session/all_spec.rb +96 -6
- data/lib/capybara/spec/session/ancestor_spec.rb +5 -0
- data/lib/capybara/spec/session/assert_all_of_selectors_spec.rb +29 -0
- data/lib/capybara/spec/session/assert_current_path_spec.rb +5 -2
- data/lib/capybara/spec/session/assert_selector_spec.rb +0 -10
- data/lib/capybara/spec/session/assert_style_spec.rb +4 -4
- data/lib/capybara/spec/session/assert_text_spec.rb +9 -5
- data/lib/capybara/spec/session/attach_file_spec.rb +63 -36
- data/lib/capybara/spec/session/check_spec.rb +10 -4
- data/lib/capybara/spec/session/choose_spec.rb +8 -2
- data/lib/capybara/spec/session/click_button_spec.rb +117 -61
- data/lib/capybara/spec/session/click_link_or_button_spec.rb +16 -0
- data/lib/capybara/spec/session/click_link_spec.rb +17 -6
- data/lib/capybara/spec/session/element/matches_selector_spec.rb +40 -39
- data/lib/capybara/spec/session/evaluate_script_spec.rb +13 -0
- data/lib/capybara/spec/session/execute_script_spec.rb +1 -0
- data/lib/capybara/spec/session/fill_in_spec.rb +47 -6
- data/lib/capybara/spec/session/find_field_spec.rb +1 -1
- data/lib/capybara/spec/session/find_spec.rb +74 -4
- data/lib/capybara/spec/session/first_spec.rb +1 -1
- data/lib/capybara/spec/session/frame/switch_to_frame_spec.rb +13 -1
- data/lib/capybara/spec/session/frame/within_frame_spec.rb +12 -1
- data/lib/capybara/spec/session/has_all_selectors_spec.rb +1 -1
- data/lib/capybara/spec/session/has_ancestor_spec.rb +46 -0
- data/lib/capybara/spec/session/has_any_selectors_spec.rb +25 -0
- data/lib/capybara/spec/session/has_button_spec.rb +16 -0
- data/lib/capybara/spec/session/has_css_spec.rb +122 -12
- data/lib/capybara/spec/session/has_current_path_spec.rb +6 -4
- data/lib/capybara/spec/session/has_field_spec.rb +55 -0
- data/lib/capybara/spec/session/has_select_spec.rb +34 -6
- data/lib/capybara/spec/session/has_selector_spec.rb +11 -4
- data/lib/capybara/spec/session/has_sibling_spec.rb +50 -0
- data/lib/capybara/spec/session/has_table_spec.rb +166 -0
- data/lib/capybara/spec/session/has_text_spec.rb +48 -1
- data/lib/capybara/spec/session/has_xpath_spec.rb +17 -0
- data/lib/capybara/spec/session/html_spec.rb +7 -0
- data/lib/capybara/spec/session/matches_style_spec.rb +35 -0
- data/lib/capybara/spec/session/node_spec.rb +643 -18
- data/lib/capybara/spec/session/node_wrapper_spec.rb +1 -1
- data/lib/capybara/spec/session/refresh_spec.rb +4 -0
- data/lib/capybara/spec/session/reset_session_spec.rb +23 -8
- data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +2 -2
- data/lib/capybara/spec/session/save_screenshot_spec.rb +4 -4
- data/lib/capybara/spec/session/scroll_spec.rb +117 -0
- data/lib/capybara/spec/session/select_spec.rb +10 -10
- data/lib/capybara/spec/session/selectors_spec.rb +36 -5
- data/lib/capybara/spec/session/uncheck_spec.rb +2 -2
- data/lib/capybara/spec/session/unselect_spec.rb +1 -1
- data/lib/capybara/spec/session/window/become_closed_spec.rb +20 -17
- data/lib/capybara/spec/session/window/switch_to_window_spec.rb +4 -0
- data/lib/capybara/spec/session/window/window_opened_by_spec.rb +4 -0
- data/lib/capybara/spec/session/window/window_spec.rb +59 -58
- data/lib/capybara/spec/session/window/windows_spec.rb +4 -0
- data/lib/capybara/spec/session/within_spec.rb +23 -0
- data/lib/capybara/spec/spec_helper.rb +16 -6
- data/lib/capybara/spec/test_app.rb +28 -23
- data/lib/capybara/spec/views/animated.erb +49 -0
- data/lib/capybara/spec/views/form.erb +48 -7
- data/lib/capybara/spec/views/frame_child.erb +3 -2
- data/lib/capybara/spec/views/frame_one.erb +1 -0
- data/lib/capybara/spec/views/obscured.erb +47 -0
- data/lib/capybara/spec/views/offset.erb +32 -0
- data/lib/capybara/spec/views/react.erb +45 -0
- data/lib/capybara/spec/views/scroll.erb +20 -0
- 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 +29 -1
- data/lib/capybara/spec/views/with_dragula.erb +24 -0
- data/lib/capybara/spec/views/with_hover.erb +1 -0
- data/lib/capybara/spec/views/with_hover1.erb +10 -0
- data/lib/capybara/spec/views/with_html.erb +32 -6
- data/lib/capybara/spec/views/with_js.erb +3 -1
- data/lib/capybara/spec/views/with_jstree.erb +26 -0
- data/lib/capybara/spec/views/with_scope_other.erb +6 -0
- data/lib/capybara/spec/views/with_sortable_js.erb +21 -0
- data/lib/capybara/version.rb +1 -1
- data/lib/capybara/window.rb +11 -11
- data/lib/capybara.rb +118 -111
- data/spec/basic_node_spec.rb +14 -3
- data/spec/capybara_spec.rb +29 -29
- data/spec/css_builder_spec.rb +101 -0
- data/spec/dsl_spec.rb +46 -21
- 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 +18 -4
- data/spec/minitest_spec_spec.rb +59 -44
- data/spec/rack_test_spec.rb +117 -89
- data/spec/regexp_dissassembler_spec.rb +250 -0
- data/spec/result_spec.rb +51 -49
- data/spec/rspec/features_spec.rb +3 -0
- data/spec/rspec/shared_spec_matchers.rb +112 -97
- data/spec/rspec_spec.rb +35 -17
- data/spec/sauce_spec_chrome.rb +43 -0
- data/spec/selector_spec.rb +244 -28
- data/spec/selenium_spec_chrome.rb +125 -54
- data/spec/selenium_spec_chrome_remote.rb +26 -12
- data/spec/selenium_spec_edge.rb +23 -8
- data/spec/selenium_spec_firefox.rb +208 -0
- data/spec/selenium_spec_firefox_remote.rb +15 -18
- data/spec/selenium_spec_ie.rb +82 -13
- data/spec/selenium_spec_safari.rb +148 -0
- data/spec/server_spec.rb +118 -77
- data/spec/session_spec.rb +19 -3
- data/spec/shared_selenium_node.rb +83 -0
- data/spec/shared_selenium_session.rb +110 -65
- data/spec/spec_helper.rb +57 -9
- data/spec/xpath_builder_spec.rb +93 -0
- metadata +257 -17
- data/lib/capybara/rspec/compound.rb +0 -94
- data/lib/capybara/selenium/driver_specializations/marionette_driver.rb +0 -49
- data/lib/capybara/selenium/nodes/marionette_node.rb +0 -121
- data/lib/capybara/spec/session/has_style_spec.rb +0 -25
- data/spec/selenium_spec_marionette.rb +0 -172
@@ -2,15 +2,16 @@
|
|
2
2
|
|
3
3
|
module Capybara
|
4
4
|
module Queries
|
5
|
-
class SiblingQuery <
|
5
|
+
class SiblingQuery < SelectorQuery
|
6
6
|
# @api private
|
7
7
|
def resolve_for(node, exact = nil)
|
8
8
|
@sibling_node = node
|
9
9
|
node.synchronize do
|
10
10
|
match_results = super(node.session.current_scope, exact)
|
11
|
-
node.
|
12
|
-
|
13
|
-
|
11
|
+
siblings = node.find_xpath((XPath.preceding_sibling + XPath.following_sibling).to_s)
|
12
|
+
.map(&method(:to_element))
|
13
|
+
.select { |el| match_results.include?(el) }
|
14
|
+
Capybara::Result.new(ordered_results(siblings), self)
|
14
15
|
end
|
15
16
|
end
|
16
17
|
|
@@ -5,7 +5,7 @@ module Capybara
|
|
5
5
|
module Queries
|
6
6
|
class StyleQuery < BaseQuery
|
7
7
|
def initialize(expected_styles, session_options:, **options)
|
8
|
-
@expected_styles = expected_styles
|
8
|
+
@expected_styles = stringify_keys(expected_styles)
|
9
9
|
@options = options
|
10
10
|
@actual_styles = {}
|
11
11
|
super(@options)
|
@@ -19,7 +19,7 @@ module Capybara
|
|
19
19
|
@actual_styles = node.style(*@expected_styles.keys)
|
20
20
|
@expected_styles.all? do |style, value|
|
21
21
|
if value.is_a? Regexp
|
22
|
-
@actual_styles[style]
|
22
|
+
value.match? @actual_styles[style]
|
23
23
|
else
|
24
24
|
@actual_styles[style] == value
|
25
25
|
end
|
@@ -33,6 +33,10 @@ module Capybara
|
|
33
33
|
|
34
34
|
private
|
35
35
|
|
36
|
+
def stringify_keys(hsh)
|
37
|
+
hsh.transform_keys(&:to_s)
|
38
|
+
end
|
39
|
+
|
36
40
|
def valid_keys
|
37
41
|
%i[wait]
|
38
42
|
end
|
@@ -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.'
|
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
|
@@ -64,14 +72,16 @@ module Capybara
|
|
64
72
|
insensitive_regexp = Capybara::Helpers.to_regexp(@expected_text, options: Regexp::IGNORECASE)
|
65
73
|
insensitive_count = @actual_text.scan(insensitive_regexp).size
|
66
74
|
return if insensitive_count == @count
|
67
|
-
|
75
|
+
|
76
|
+
"it was found #{occurrences insensitive_count} using a case insensitive search"
|
68
77
|
end
|
69
78
|
|
70
79
|
def invisible_message
|
71
80
|
invisible_text = text(query_type: :all)
|
72
81
|
invisible_count = invisible_text.scan(@search_regexp).size
|
73
82
|
return if invisible_count == @count
|
74
|
-
|
83
|
+
|
84
|
+
"it was found #{occurrences invisible_count} including non-visible text"
|
75
85
|
rescue StandardError
|
76
86
|
# An error getting the non-visible text (if element goes out of scope) should not affect the response
|
77
87
|
nil
|
@@ -81,6 +91,10 @@ module Capybara
|
|
81
91
|
COUNT_KEYS + %i[wait exact normalize_ws]
|
82
92
|
end
|
83
93
|
|
94
|
+
def valid_types
|
95
|
+
%i[all visible]
|
96
|
+
end
|
97
|
+
|
84
98
|
def check_visible_text?
|
85
99
|
@type == :visible
|
86
100
|
end
|
@@ -8,12 +8,12 @@ module Capybara
|
|
8
8
|
@expected_title = expected_title.is_a?(Regexp) ? expected_title : expected_title.to_s
|
9
9
|
@options = options
|
10
10
|
super(@options)
|
11
|
-
@search_regexp =
|
11
|
+
@search_regexp = Helpers.to_regexp(@expected_title, all_whitespace: true, exact: options.fetch(:exact, false))
|
12
12
|
assert_valid_keys
|
13
13
|
end
|
14
14
|
|
15
15
|
def resolves_for?(node)
|
16
|
-
(@actual_title = node.title).match(@search_regexp)
|
16
|
+
(@actual_title = node.title).match?(@search_regexp)
|
17
17
|
end
|
18
18
|
|
19
19
|
def failure_message
|
@@ -30,11 +30,14 @@ class Capybara::RackTest::Browser
|
|
30
30
|
|
31
31
|
def submit(method, path, attributes)
|
32
32
|
path = request_path if path.nil? || path.empty?
|
33
|
-
|
33
|
+
uri = build_uri(path)
|
34
|
+
uri.query = '' if method&.to_s&.downcase == 'get'
|
35
|
+
process_and_follow_redirects(method, uri.to_s, attributes, 'HTTP_REFERER' => current_url)
|
34
36
|
end
|
35
37
|
|
36
38
|
def follow(method, path, **attributes)
|
37
39
|
return if fragment_or_script?(path)
|
40
|
+
|
38
41
|
process_and_follow_redirects(method, path, attributes, 'HTTP_REFERER' => current_url)
|
39
42
|
end
|
40
43
|
|
@@ -52,29 +55,33 @@ class Capybara::RackTest::Browser
|
|
52
55
|
end
|
53
56
|
end
|
54
57
|
end
|
55
|
-
raise Capybara::InfiniteRedirectError, "redirected more than #{driver.redirect_limit} times, check for infinite redirects." if last_response.redirect?
|
56
|
-
end
|
57
58
|
|
58
|
-
|
59
|
-
|
60
|
-
method.downcase! unless method.is_a? Symbol
|
61
|
-
if path.empty?
|
62
|
-
new_uri.path = request_path
|
63
|
-
else
|
64
|
-
new_uri.path = request_path if path.start_with?('?')
|
65
|
-
new_uri.path = '/' if new_uri.path.empty?
|
66
|
-
new_uri.path = request_path.sub(%r{/[^/]*$}, '/') + new_uri.path unless new_uri.path.start_with?('/')
|
59
|
+
if last_response.redirect? # rubocop:disable Style/GuardClause
|
60
|
+
raise Capybara::InfiniteRedirectError, "redirected more than #{driver.redirect_limit} times, check for infinite redirects."
|
67
61
|
end
|
68
|
-
|
69
|
-
new_uri.host ||= @current_host
|
70
|
-
new_uri.port ||= @current_port unless new_uri.default_port == @current_port
|
62
|
+
end
|
71
63
|
|
64
|
+
def process(method, path, attributes = {}, env = {})
|
65
|
+
method = method.downcase
|
66
|
+
new_uri = build_uri(path)
|
72
67
|
@current_scheme, @current_host, @current_port = new_uri.select(:scheme, :host, :port)
|
73
68
|
|
74
69
|
reset_cache!
|
75
70
|
send(method, new_uri.to_s, attributes, env.merge(options[:headers] || {}))
|
76
71
|
end
|
77
72
|
|
73
|
+
def build_uri(path)
|
74
|
+
URI.parse(path).tap do |uri|
|
75
|
+
uri.path = request_path if path.empty? || path.start_with?('?')
|
76
|
+
uri.path = '/' if uri.path.empty?
|
77
|
+
uri.path = request_path.sub(%r{/[^/]*$}, '/') + uri.path unless uri.path.start_with?('/')
|
78
|
+
|
79
|
+
uri.scheme ||= @current_scheme
|
80
|
+
uri.host ||= @current_host
|
81
|
+
uri.port ||= @current_port unless uri.default_port == @current_port
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
78
85
|
def current_url
|
79
86
|
last_request.url
|
80
87
|
rescue Rack::Test::Error
|
@@ -16,6 +16,7 @@ class Capybara::RackTest::Driver < Capybara::Driver::Base
|
|
16
16
|
|
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
|
@app = app
|
20
21
|
@options = DEFAULT_OPTIONS.merge(options)
|
21
22
|
end
|
@@ -41,7 +42,7 @@ class Capybara::RackTest::Driver < Capybara::Driver::Base
|
|
41
42
|
end
|
42
43
|
|
43
44
|
def visit(path, **attributes)
|
44
|
-
browser.visit(path, attributes)
|
45
|
+
browser.visit(path, **attributes)
|
45
46
|
end
|
46
47
|
|
47
48
|
def refresh
|
@@ -74,6 +75,10 @@ class Capybara::RackTest::Driver < Capybara::Driver::Base
|
|
74
75
|
|
75
76
|
def find_css(selector)
|
76
77
|
browser.find(:css, selector)
|
78
|
+
rescue Nokogiri::CSS::SyntaxError
|
79
|
+
raise unless selector.include?(' i]')
|
80
|
+
|
81
|
+
raise ArgumentError, "This driver doesn't support case insensitive attribute matching when using CSS base selectors"
|
77
82
|
end
|
78
83
|
|
79
84
|
def html
|
@@ -97,4 +102,8 @@ class Capybara::RackTest::Driver < Capybara::Driver::Base
|
|
97
102
|
def put(*args, &block); browser.put(*args, &block); end
|
98
103
|
def delete(*args, &block); browser.delete(*args, &block); end
|
99
104
|
def header(key, value); browser.header(key, value); end
|
105
|
+
|
106
|
+
def invalid_element_errors
|
107
|
+
[Capybara::RackTest::Errors::StaleElementReferenceError]
|
108
|
+
end
|
100
109
|
end
|
@@ -19,25 +19,22 @@ class Capybara::RackTest::Form < Capybara::RackTest::Node
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def params(button)
|
22
|
-
|
23
|
-
|
24
|
-
form_element_types = %i[input select textarea]
|
22
|
+
form_element_types = %i[input select textarea button]
|
25
23
|
form_elements_xpath = XPath.generate do |xp|
|
26
24
|
xpath = xp.descendant(*form_element_types).where(!xp.attr(:form))
|
27
25
|
xpath += xp.anywhere(*form_element_types).where(xp.attr(:form) == native[:id]) if native[:id]
|
28
26
|
xpath.where(!xp.attr(:disabled))
|
29
27
|
end.to_s
|
30
28
|
|
31
|
-
native.xpath(form_elements_xpath).
|
29
|
+
form_elements = native.xpath(form_elements_xpath).reject { |el| submitter?(el) && (el != button.native) }
|
30
|
+
|
31
|
+
form_elements.each_with_object(make_params) do |field, params|
|
32
32
|
case field.name
|
33
|
-
when 'input' then add_input_param(field, params)
|
33
|
+
when 'input', 'button' then add_input_param(field, params)
|
34
34
|
when 'select' then add_select_param(field, params)
|
35
35
|
when 'textarea' then add_textarea_param(field, params)
|
36
36
|
end
|
37
|
-
end
|
38
|
-
merge_param!(params, button[:name], button[:value] || '') if button[:name]
|
39
|
-
|
40
|
-
params.to_params_hash
|
37
|
+
end.to_params_hash
|
41
38
|
end
|
42
39
|
|
43
40
|
def submit(button)
|
@@ -59,7 +56,7 @@ private
|
|
59
56
|
end
|
60
57
|
|
61
58
|
def request_method
|
62
|
-
self[:method]
|
59
|
+
/post/i.match?(self[:method] || '') ? :post : :get
|
63
60
|
end
|
64
61
|
|
65
62
|
def merge_param!(params, key, value)
|
@@ -80,28 +77,32 @@ private
|
|
80
77
|
end
|
81
78
|
|
82
79
|
def add_input_param(field, params)
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
80
|
+
name, value = field['name'].to_s, field['value'].to_s
|
81
|
+
return if name.empty?
|
82
|
+
|
83
|
+
value = case field['type']
|
84
|
+
when 'radio', 'checkbox'
|
85
|
+
return unless field['checked']
|
86
|
+
|
87
|
+
Capybara::RackTest::Node.new(driver, field).value.to_s
|
88
|
+
when 'file'
|
92
89
|
if multipart?
|
93
|
-
|
94
|
-
NilUploadedFile.new
|
95
|
-
else
|
96
|
-
mime_info = MiniMime.lookup_by_filename(value)
|
97
|
-
Rack::Test::UploadedFile.new(value, mime_info&.content_type&.to_s)
|
98
|
-
end
|
99
|
-
merge_param!(params, field['name'], file)
|
90
|
+
file_to_upload(value)
|
100
91
|
else
|
101
|
-
|
92
|
+
File.basename(value)
|
102
93
|
end
|
103
94
|
else
|
104
|
-
|
95
|
+
value
|
96
|
+
end
|
97
|
+
merge_param!(params, name, value)
|
98
|
+
end
|
99
|
+
|
100
|
+
def file_to_upload(filename)
|
101
|
+
if filename.empty?
|
102
|
+
NilUploadedFile.new
|
103
|
+
else
|
104
|
+
mime_info = MiniMime.lookup_by_filename(filename)
|
105
|
+
Rack::Test::UploadedFile.new(filename, mime_info&.content_type&.to_s)
|
105
106
|
end
|
106
107
|
end
|
107
108
|
|
@@ -119,4 +120,8 @@ private
|
|
119
120
|
def add_textarea_param(field, params)
|
120
121
|
merge_param!(params, field['name'], field['_capybara_raw_value'].to_s.gsub(/\n/, "\r\n"))
|
121
122
|
end
|
123
|
+
|
124
|
+
def submitter?(el)
|
125
|
+
(%w[submit image].include? el['type']) || (el.name == 'button')
|
126
|
+
end
|
122
127
|
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'capybara/rack_test/errors'
|
4
|
+
|
3
5
|
class Capybara::RackTest::Node < Capybara::Driver::Node
|
4
6
|
BLOCK_ELEMENTS = %w[p h1 h2 h3 h4 h5 h6 ol ul pre address blockquote dl div fieldset form hr noscript table].freeze
|
5
7
|
|
@@ -43,6 +45,7 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
|
|
43
45
|
|
44
46
|
if radio? then set_radio(value)
|
45
47
|
elsif checkbox? then set_checkbox(value)
|
48
|
+
elsif range? then set_range(value)
|
46
49
|
elsif input_field? then set_input(value)
|
47
50
|
elsif textarea? then native['_capybara_raw_value'] = value.to_s
|
48
51
|
end
|
@@ -50,17 +53,20 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
|
|
50
53
|
|
51
54
|
def select_option
|
52
55
|
return if disabled?
|
56
|
+
|
53
57
|
deselect_options unless select_node.multiple?
|
54
58
|
native['selected'] = 'selected'
|
55
59
|
end
|
56
60
|
|
57
61
|
def unselect_option
|
58
62
|
raise Capybara::UnselectNotAllowed, 'Cannot unselect option from single select box.' unless select_node.multiple?
|
63
|
+
|
59
64
|
native.remove_attribute('selected')
|
60
65
|
end
|
61
66
|
|
62
|
-
def click(keys = [], **
|
63
|
-
|
67
|
+
def click(keys = [], **options)
|
68
|
+
options.delete(:offset)
|
69
|
+
raise ArgumentError, 'The RackTest driver does not support click options' unless keys.empty? && options.empty?
|
64
70
|
|
65
71
|
if link?
|
66
72
|
follow_link
|
@@ -71,6 +77,8 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
|
|
71
77
|
set(!checked?)
|
72
78
|
elsif tag_name == 'label'
|
73
79
|
click_label
|
80
|
+
elsif (details = native.xpath('.//ancestor-or-self::details').last)
|
81
|
+
toggle_details(details)
|
74
82
|
end
|
75
83
|
end
|
76
84
|
|
@@ -94,9 +102,9 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
|
|
94
102
|
return true if string_node.disabled?
|
95
103
|
|
96
104
|
if %w[option optgroup].include? tag_name
|
97
|
-
find_xpath(
|
105
|
+
find_xpath(OPTION_OWNER_XPATH)[0].disabled?
|
98
106
|
else
|
99
|
-
!find_xpath(
|
107
|
+
!find_xpath(DISABLED_BY_FIELDSET_XPATH).empty?
|
100
108
|
end
|
101
109
|
end
|
102
110
|
|
@@ -104,14 +112,33 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
|
|
104
112
|
native.path
|
105
113
|
end
|
106
114
|
|
107
|
-
def find_xpath(locator)
|
115
|
+
def find_xpath(locator, **_hints)
|
108
116
|
native.xpath(locator).map { |el| self.class.new(driver, el) }
|
109
117
|
end
|
110
118
|
|
111
|
-
def find_css(locator)
|
119
|
+
def find_css(locator, **_hints)
|
112
120
|
native.css(locator, Capybara::RackTest::CSSHandlers.new).map { |el| self.class.new(driver, el) }
|
113
121
|
end
|
114
122
|
|
123
|
+
public_instance_methods(false).each do |meth_name|
|
124
|
+
alias_method "unchecked_#{meth_name}", meth_name
|
125
|
+
private "unchecked_#{meth_name}" # rubocop:disable Style/AccessModifierDeclarations
|
126
|
+
|
127
|
+
if RUBY_VERSION >= '2.7'
|
128
|
+
class_eval <<~METHOD, __FILE__, __LINE__ + 1
|
129
|
+
def #{meth_name}(...)
|
130
|
+
stale_check
|
131
|
+
method(:"unchecked_#{meth_name}").call(...)
|
132
|
+
end
|
133
|
+
METHOD
|
134
|
+
else
|
135
|
+
define_method meth_name do |*args|
|
136
|
+
stale_check
|
137
|
+
send("unchecked_#{meth_name}", *args)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
115
142
|
def ==(other)
|
116
143
|
native == other.native
|
117
144
|
end
|
@@ -139,6 +166,10 @@ protected
|
|
139
166
|
|
140
167
|
private
|
141
168
|
|
169
|
+
def stale_check
|
170
|
+
raise Capybara::RackTest::Errors::StaleElementReferenceError unless native.document == driver.dom
|
171
|
+
end
|
172
|
+
|
142
173
|
def deselect_options
|
143
174
|
select_node.find_xpath('.//option[@selected]').each { |node| node.native.remove_attribute('selected') }
|
144
175
|
end
|
@@ -178,6 +209,14 @@ private
|
|
178
209
|
end
|
179
210
|
end
|
180
211
|
|
212
|
+
def set_range(value) # rubocop:disable Naming/AccessorMethodName
|
213
|
+
min, max, step = (native['min'] || 0).to_f, (native['max'] || 100).to_f, (native['step'] || 1).to_f
|
214
|
+
value = value.to_f
|
215
|
+
value = value.clamp(min, max)
|
216
|
+
value = ((value - min) / step).round * step + min
|
217
|
+
native['value'] = value.clamp(min, max)
|
218
|
+
end
|
219
|
+
|
181
220
|
def set_input(value) # rubocop:disable Naming/AccessorMethodName
|
182
221
|
if text_or_password? && attribute_is_not_blank?(:maxlength)
|
183
222
|
# Browser behavior for maxlength="0" is inconsistent, so we stick with
|
@@ -217,6 +256,17 @@ private
|
|
217
256
|
labelled_control.set(!labelled_control.checked?) if checkbox_or_radio?(labelled_control)
|
218
257
|
end
|
219
258
|
|
259
|
+
def toggle_details(details = nil)
|
260
|
+
details ||= native.xpath('.//ancestor-or-self::details').last
|
261
|
+
return unless details
|
262
|
+
|
263
|
+
if details.has_attribute?('open')
|
264
|
+
details.remove_attribute('open')
|
265
|
+
else
|
266
|
+
details.set_attribute('open', 'open')
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
220
270
|
def link?
|
221
271
|
tag_name == 'a' && !self[:href].nil?
|
222
272
|
end
|
@@ -254,4 +304,22 @@ protected
|
|
254
304
|
def textarea?
|
255
305
|
tag_name == 'textarea'
|
256
306
|
end
|
307
|
+
|
308
|
+
def range?
|
309
|
+
input_field? && type == 'range'
|
310
|
+
end
|
311
|
+
|
312
|
+
OPTION_OWNER_XPATH = XPath.parent(:optgroup, :select, :datalist).to_s.freeze
|
313
|
+
DISABLED_BY_FIELDSET_XPATH = XPath.generate do |x|
|
314
|
+
x.parent(:fieldset)[
|
315
|
+
XPath.attr(:disabled)
|
316
|
+
] + x.ancestor[
|
317
|
+
~x.self(:legend) |
|
318
|
+
x.preceding_sibling(:legend)
|
319
|
+
][
|
320
|
+
x.parent(:fieldset)[
|
321
|
+
x.attr(:disabled)
|
322
|
+
]
|
323
|
+
]
|
324
|
+
end.to_s.freeze
|
257
325
|
end
|
@@ -0,0 +1,44 @@
|
|
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
|
+
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, *args, **options, &block)
|
20
|
+
if @registered.respond_to?(method_name)
|
21
|
+
warn "DEPRECATED: Calling '#{method_name}' on the drivers/servers container is deprecated without replacement"
|
22
|
+
# RUBY 2.6 will send an empty hash rather than nothing with **options so fix that
|
23
|
+
return @registered.public_send(method_name, *args, &block) if options.empty?
|
24
|
+
|
25
|
+
return @registered.public_send(method_name, *args, **options, &block)
|
26
|
+
end
|
27
|
+
super
|
28
|
+
end
|
29
|
+
|
30
|
+
def respond_to_missing?(method_name, include_private = false)
|
31
|
+
@registered.respond_to?(method_name) || super
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def initialize
|
37
|
+
@registered = {}
|
38
|
+
end
|
39
|
+
|
40
|
+
def register(name, block)
|
41
|
+
@registered[name] = block
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,36 @@
|
|
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
|
+
Capybara::Selenium::Driver.load_selenium
|
13
|
+
browser_options = ::Selenium::WebDriver::Firefox::Options.new
|
14
|
+
browser_options.args << '-headless'
|
15
|
+
Capybara::Selenium::Driver.new(app, browser: :firefox, options: browser_options)
|
16
|
+
end
|
17
|
+
|
18
|
+
Capybara.register_driver :selenium_chrome do |app|
|
19
|
+
Capybara::Selenium::Driver.load_selenium
|
20
|
+
browser_options = ::Selenium::WebDriver::Chrome::Options.new.tap do |opts|
|
21
|
+
# Workaround https://bugs.chromium.org/p/chromedriver/issues/detail?id=2650&q=load&sort=-id&colspec=ID%20Status%20Pri%20Owner%20Summary
|
22
|
+
opts.args << '--disable-site-isolation-trials'
|
23
|
+
end
|
24
|
+
Capybara::Selenium::Driver.new(app, browser: :chrome, options: browser_options)
|
25
|
+
end
|
26
|
+
|
27
|
+
Capybara.register_driver :selenium_chrome_headless do |app|
|
28
|
+
Capybara::Selenium::Driver.load_selenium
|
29
|
+
browser_options = ::Selenium::WebDriver::Chrome::Options.new.tap do |opts|
|
30
|
+
opts.args << '--headless'
|
31
|
+
opts.args << '--disable-gpu' if Gem.win_platform?
|
32
|
+
# Workaround https://bugs.chromium.org/p/chromedriver/issues/detail?id=2650&q=load&sort=-id&colspec=ID%20Status%20Pri%20Owner%20Summary
|
33
|
+
opts.args << '--disable-site-isolation-trials'
|
34
|
+
end
|
35
|
+
Capybara::Selenium::Driver.new(app, browser: :chrome, options: browser_options)
|
36
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Puma
|
4
|
+
module MiniSSL
|
5
|
+
class Socket
|
6
|
+
def read_nonblock(size, *_)
|
7
|
+
loop do
|
8
|
+
output = engine_read_all
|
9
|
+
return output if output
|
10
|
+
|
11
|
+
data = @socket.read_nonblock(size, exception: false)
|
12
|
+
raise IO::EAGAINWaitReadable if %i[wait_readable wait_writable].include? data
|
13
|
+
return nil if data.nil?
|
14
|
+
|
15
|
+
@engine.inject(data)
|
16
|
+
output = engine_read_all
|
17
|
+
|
18
|
+
return output if output
|
19
|
+
|
20
|
+
while (neg_data = @engine.extract)
|
21
|
+
@socket.write neg_data
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
Capybara.register_server :default do |app, port, _host|
|
4
|
+
Capybara.run_default_server(app, port)
|
5
|
+
end
|
6
|
+
|
7
|
+
Capybara.register_server :webrick do |app, port, host, **options|
|
8
|
+
require 'rack/handler/webrick'
|
9
|
+
options = { Host: host, Port: port, AccessLog: [], Logger: WEBrick::Log.new(nil, 0) }.merge(options)
|
10
|
+
Rack::Handler::WEBrick.run(app, **options)
|
11
|
+
end
|
12
|
+
|
13
|
+
Capybara.register_server :puma do |app, port, host, **options|
|
14
|
+
begin
|
15
|
+
require 'rack/handler/puma'
|
16
|
+
rescue LoadError
|
17
|
+
raise LoadError, 'Capybara is unable to load `puma` for its server, please add `puma` to your project or specify a different server via something like `Capybara.server = :webrick`.'
|
18
|
+
else
|
19
|
+
unless Rack::Handler::Puma.respond_to?(:config)
|
20
|
+
raise LoadError, 'Capybara requires `puma` version 3.8.0 or higher, please upgrade `puma` or register and specify your own server block'
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# If we just run the Puma Rack handler it installs signal handlers which prevent us from being able to interrupt tests.
|
25
|
+
# Therefore construct and run the Server instance ourselves.
|
26
|
+
# Rack::Handler::Puma.run(app, { Host: host, Port: port, Threads: "0:4", workers: 0, daemon: false }.merge(options))
|
27
|
+
default_options = { Host: host, Port: port, Threads: '0:4', workers: 0, daemon: false }
|
28
|
+
options = default_options.merge(options)
|
29
|
+
|
30
|
+
conf = Rack::Handler::Puma.config(app, options)
|
31
|
+
events = conf.options[:Silent] ? ::Puma::Events.strings : ::Puma::Events.stdio
|
32
|
+
|
33
|
+
puma_ver = Gem::Version.new(Puma::Const::PUMA_VERSION)
|
34
|
+
require_relative 'patches/puma_ssl' if (Gem::Version.new('4.0.0')...Gem::Version.new('4.1.0')).cover? puma_ver
|
35
|
+
|
36
|
+
events.log 'Capybara starting Puma...'
|
37
|
+
events.log "* Version #{Puma::Const::PUMA_VERSION} , codename: #{Puma::Const::CODE_NAME}"
|
38
|
+
events.log "* Min threads: #{conf.options[:min_threads]}, max threads: #{conf.options[:max_threads]}"
|
39
|
+
|
40
|
+
Puma::Server.new(conf.app, events, conf.options).tap do |s|
|
41
|
+
s.binder.parse conf.options[:binds], s.events
|
42
|
+
s.min_threads, s.max_threads = conf.options[:min_threads], conf.options[:max_threads]
|
43
|
+
end.run.join
|
44
|
+
end
|