capybara 3.35.2 → 3.37.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/History.md +59 -4
- data/README.md +5 -1
- data/lib/capybara/config.rb +16 -4
- data/lib/capybara/driver/base.rb +4 -0
- data/lib/capybara/driver/node.rb +5 -1
- data/lib/capybara/dsl.rb +4 -10
- data/lib/capybara/helpers.rb +2 -11
- data/lib/capybara/minitest/spec.rb +2 -2
- data/lib/capybara/node/actions.rb +10 -5
- data/lib/capybara/node/document.rb +2 -2
- data/lib/capybara/node/element.rb +13 -2
- data/lib/capybara/node/finders.rb +2 -2
- data/lib/capybara/node/simple.rb +5 -1
- data/lib/capybara/queries/active_element_query.rb +18 -0
- data/lib/capybara/queries/ancestor_query.rb +2 -1
- data/lib/capybara/queries/current_path_query.rb +1 -1
- data/lib/capybara/queries/selector_query.rb +34 -8
- data/lib/capybara/queries/sibling_query.rb +2 -1
- data/lib/capybara/rack_test/browser.rb +41 -6
- data/lib/capybara/rack_test/driver.rb +4 -4
- data/lib/capybara/rack_test/node.rb +10 -7
- data/lib/capybara/registration_container.rb +0 -3
- data/lib/capybara/registrations/drivers.rb +3 -3
- data/lib/capybara/rspec/matcher_proxies.rb +3 -3
- data/lib/capybara/rspec/matchers/have_selector.rb +5 -5
- data/lib/capybara/rspec/matchers.rb +14 -14
- data/lib/capybara/selector/builders/css_builder.rb +1 -1
- data/lib/capybara/selector/builders/xpath_builder.rb +1 -1
- data/lib/capybara/selector/css.rb +1 -1
- data/lib/capybara/selector/definition/button.rb +9 -4
- data/lib/capybara/selector/definition/checkbox.rb +1 -1
- data/lib/capybara/selector/definition/file_field.rb +1 -1
- data/lib/capybara/selector/definition/fillable_field.rb +1 -1
- data/lib/capybara/selector/definition/radio_button.rb +1 -1
- data/lib/capybara/selector/definition.rb +3 -1
- data/lib/capybara/selector/filter_set.rb +4 -6
- data/lib/capybara/selector.rb +1 -0
- data/lib/capybara/selenium/driver.rb +25 -11
- data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +1 -1
- data/lib/capybara/selenium/driver_specializations/edge_driver.rb +1 -1
- data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +1 -1
- data/lib/capybara/selenium/node.rb +22 -8
- data/lib/capybara/selenium/nodes/chrome_node.rb +1 -1
- data/lib/capybara/selenium/nodes/edge_node.rb +1 -1
- data/lib/capybara/selenium/nodes/firefox_node.rb +1 -1
- data/lib/capybara/selenium/nodes/safari_node.rb +2 -2
- data/lib/capybara/server/animation_disabler.rb +35 -17
- data/lib/capybara/session/config.rb +1 -1
- data/lib/capybara/session.rb +20 -23
- data/lib/capybara/spec/session/active_element_spec.rb +31 -0
- data/lib/capybara/spec/session/all_spec.rb +9 -13
- data/lib/capybara/spec/session/assert_text_spec.rb +17 -17
- data/lib/capybara/spec/session/check_spec.rb +9 -0
- data/lib/capybara/spec/session/choose_spec.rb +6 -0
- data/lib/capybara/spec/session/has_any_selectors_spec.rb +4 -0
- data/lib/capybara/spec/session/has_button_spec.rb +24 -0
- data/lib/capybara/spec/session/has_current_path_spec.rb +2 -2
- data/lib/capybara/spec/session/has_field_spec.rb +25 -1
- data/lib/capybara/spec/session/has_link_spec.rb +24 -0
- data/lib/capybara/spec/session/has_select_spec.rb +4 -4
- data/lib/capybara/spec/session/has_selector_spec.rb +15 -0
- data/lib/capybara/spec/session/has_text_spec.rb +2 -6
- data/lib/capybara/spec/session/node_spec.rb +43 -1
- data/lib/capybara/spec/session/scroll_spec.rb +4 -4
- data/lib/capybara/spec/session/visit_spec.rb +14 -0
- data/lib/capybara/spec/session/window/window_spec.rb +1 -1
- data/lib/capybara/spec/spec_helper.rb +4 -3
- data/lib/capybara/spec/test_app.rb +50 -8
- data/lib/capybara/spec/views/animated.erb +1 -1
- data/lib/capybara/spec/views/form.erb +11 -3
- data/lib/capybara/spec/views/frame_child.erb +1 -1
- data/lib/capybara/spec/views/frame_one.erb +1 -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 +1 -1
- data/lib/capybara/spec/views/offset.erb +2 -1
- 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 +2 -2
- data/lib/capybara/spec/views/scroll.erb +2 -1
- data/lib/capybara/spec/views/spatial.erb +1 -1
- data/lib/capybara/spec/views/with_animation.erb +2 -3
- data/lib/capybara/spec/views/with_base_tag.erb +2 -2
- data/lib/capybara/spec/views/with_dragula.erb +2 -2
- data/lib/capybara/spec/views/with_fixed_header_footer.erb +2 -1
- data/lib/capybara/spec/views/with_hover.erb +2 -2
- data/lib/capybara/spec/views/with_html.erb +1 -1
- data/lib/capybara/spec/views/with_jquery_animation.erb +1 -1
- data/lib/capybara/spec/views/with_js.erb +2 -3
- data/lib/capybara/spec/views/with_jstree.erb +1 -1
- data/lib/capybara/spec/views/with_namespace.erb +1 -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 +2 -2
- 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 +1 -1
- data/lib/capybara.rb +19 -22
- data/spec/basic_node_spec.rb +16 -3
- data/spec/dsl_spec.rb +3 -3
- data/spec/fixtures/selenium_driver_rspec_failure.rb +2 -2
- data/spec/fixtures/selenium_driver_rspec_success.rb +2 -2
- data/spec/rack_test_spec.rb +20 -10
- data/spec/result_spec.rb +32 -35
- data/spec/rspec/features_spec.rb +3 -3
- data/spec/rspec/scenarios_spec.rb +1 -1
- data/spec/rspec/shared_spec_matchers.rb +2 -2
- data/spec/sauce_spec_chrome.rb +3 -3
- data/spec/selector_spec.rb +1 -1
- data/spec/selenium_spec_chrome.rb +9 -10
- data/spec/selenium_spec_chrome_remote.rb +9 -8
- data/spec/selenium_spec_firefox.rb +8 -3
- data/spec/selenium_spec_firefox_remote.rb +2 -2
- data/spec/selenium_spec_ie.rb +3 -6
- data/spec/selenium_spec_safari.rb +31 -19
- data/spec/server_spec.rb +5 -5
- data/spec/shared_selenium_node.rb +0 -4
- data/spec/shared_selenium_session.rb +20 -10
- data/spec/spec_helper.rb +1 -1
- metadata +37 -14
- data/lib/capybara/spec/views/with_title.erb +0 -5
|
@@ -108,6 +108,13 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
|
|
|
108
108
|
end
|
|
109
109
|
end
|
|
110
110
|
|
|
111
|
+
def readonly?
|
|
112
|
+
# readonly attribute not valid on these input types
|
|
113
|
+
return false if input_field? && %w[hidden range color checkbox radio file submit image reset button].include?(type)
|
|
114
|
+
|
|
115
|
+
super
|
|
116
|
+
end
|
|
117
|
+
|
|
111
118
|
def path
|
|
112
119
|
native.path
|
|
113
120
|
end
|
|
@@ -139,10 +146,6 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
|
|
|
139
146
|
end
|
|
140
147
|
end
|
|
141
148
|
|
|
142
|
-
def ==(other)
|
|
143
|
-
native == other.native
|
|
144
|
-
end
|
|
145
|
-
|
|
146
149
|
protected
|
|
147
150
|
|
|
148
151
|
# @api private
|
|
@@ -159,7 +162,7 @@ protected
|
|
|
159
162
|
end.join || ''
|
|
160
163
|
text = "\n#{text}\n" if BLOCK_ELEMENTS.include?(tag_name)
|
|
161
164
|
text
|
|
162
|
-
else
|
|
165
|
+
else # rubocop:disable Lint/DuplicateBranch
|
|
163
166
|
''
|
|
164
167
|
end
|
|
165
168
|
end
|
|
@@ -213,7 +216,7 @@ private
|
|
|
213
216
|
min, max, step = (native['min'] || 0).to_f, (native['max'] || 100).to_f, (native['step'] || 1).to_f
|
|
214
217
|
value = value.to_f
|
|
215
218
|
value = value.clamp(min, max)
|
|
216
|
-
value = ((value - min) / step).round * step + min
|
|
219
|
+
value = (((value - min) / step).round * step) + min
|
|
217
220
|
native['value'] = value.clamp(min, max)
|
|
218
221
|
end
|
|
219
222
|
|
|
@@ -241,7 +244,7 @@ private
|
|
|
241
244
|
end
|
|
242
245
|
|
|
243
246
|
def follow_link
|
|
244
|
-
method = self['data-method'] if driver.options[:respect_data_method]
|
|
247
|
+
method = self['data-method'] || self['data-turbo-method'] if driver.options[:respect_data_method]
|
|
245
248
|
method ||= :get
|
|
246
249
|
driver.follow(method, self[:href].to_s)
|
|
247
250
|
end
|
|
@@ -19,9 +19,6 @@ module Capybara
|
|
|
19
19
|
def method_missing(method_name, *args, **options, &block)
|
|
20
20
|
if @registered.respond_to?(method_name)
|
|
21
21
|
Capybara::Helpers.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
22
|
return @registered.public_send(method_name, *args, **options, &block)
|
|
26
23
|
end
|
|
27
24
|
super
|
|
@@ -14,7 +14,7 @@ Capybara.register_driver :selenium_headless do |app|
|
|
|
14
14
|
browser_options = ::Selenium::WebDriver::Firefox::Options.new.tap do |opts|
|
|
15
15
|
opts.add_argument '-headless'
|
|
16
16
|
end
|
|
17
|
-
Capybara::Selenium::Driver.new(app, **
|
|
17
|
+
Capybara::Selenium::Driver.new(app, **{ :browser => :firefox, options_key => browser_options })
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
Capybara.register_driver :selenium_chrome do |app|
|
|
@@ -25,7 +25,7 @@ Capybara.register_driver :selenium_chrome do |app|
|
|
|
25
25
|
opts.add_argument('--disable-site-isolation-trials')
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
-
Capybara::Selenium::Driver.new(app, **
|
|
28
|
+
Capybara::Selenium::Driver.new(app, **{ :browser => :chrome, options_key => browser_options })
|
|
29
29
|
end
|
|
30
30
|
|
|
31
31
|
Capybara.register_driver :selenium_chrome_headless do |app|
|
|
@@ -38,5 +38,5 @@ Capybara.register_driver :selenium_chrome_headless do |app|
|
|
|
38
38
|
opts.add_argument('--disable-site-isolation-trials')
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
-
Capybara::Selenium::Driver.new(app, **
|
|
41
|
+
Capybara::Selenium::Driver.new(app, **{ :browser => :chrome, options_key => browser_options })
|
|
42
42
|
end
|
|
@@ -23,7 +23,7 @@ end
|
|
|
23
23
|
if RUBY_ENGINE == 'jruby'
|
|
24
24
|
# :nocov:
|
|
25
25
|
module Capybara::DSL
|
|
26
|
-
class <<self
|
|
26
|
+
class << self
|
|
27
27
|
remove_method :included
|
|
28
28
|
|
|
29
29
|
def included(base)
|
|
@@ -55,7 +55,7 @@ else
|
|
|
55
55
|
end
|
|
56
56
|
|
|
57
57
|
def self.prepended(base)
|
|
58
|
-
class <<base
|
|
58
|
+
class << base
|
|
59
59
|
prepend ClassMethods
|
|
60
60
|
end
|
|
61
61
|
end
|
|
@@ -70,7 +70,7 @@ else
|
|
|
70
70
|
end
|
|
71
71
|
|
|
72
72
|
def self.prepended(base)
|
|
73
|
-
class <<base
|
|
73
|
+
class << base
|
|
74
74
|
prepend ClassMethods
|
|
75
75
|
end
|
|
76
76
|
end
|
|
@@ -8,10 +8,10 @@ module Capybara
|
|
|
8
8
|
class HaveSelector < CountableWrappedElementMatcher
|
|
9
9
|
def initialize(*args, **kw_args, &filter_block)
|
|
10
10
|
super
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
return unless (@args.size < 2) && @kw_args.keys.any?(String)
|
|
12
|
+
|
|
13
|
+
@args.push(@kw_args)
|
|
14
|
+
@kw_args = {}
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
def element_matches?(el)
|
|
@@ -64,7 +64,7 @@ module Capybara
|
|
|
64
64
|
el.assert_any_of_selectors(*@args, **session_query_options, &@filter_block)
|
|
65
65
|
end
|
|
66
66
|
|
|
67
|
-
def does_not_match?(
|
|
67
|
+
def does_not_match?(el)
|
|
68
68
|
el.assert_none_of_selectors(*@args, **session_query_options, &@filter_block)
|
|
69
69
|
end
|
|
70
70
|
|
|
@@ -15,36 +15,36 @@ module Capybara
|
|
|
15
15
|
# RSpec matcher for whether the element(s) matching a given selector exist.
|
|
16
16
|
#
|
|
17
17
|
# @see Capybara::Node::Matchers#assert_selector
|
|
18
|
-
def have_selector(
|
|
19
|
-
Matchers::HaveSelector.new(
|
|
18
|
+
def have_selector(...)
|
|
19
|
+
Matchers::HaveSelector.new(...)
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
# RSpec matcher for whether the element(s) matching a group of selectors exist.
|
|
23
23
|
#
|
|
24
24
|
# @see Capybara::Node::Matchers#assert_all_of_selectors
|
|
25
|
-
def have_all_of_selectors(
|
|
26
|
-
Matchers::HaveAllSelectors.new(
|
|
25
|
+
def have_all_of_selectors(...)
|
|
26
|
+
Matchers::HaveAllSelectors.new(...)
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
# RSpec matcher for whether no element(s) matching a group of selectors exist.
|
|
30
30
|
#
|
|
31
31
|
# @see Capybara::Node::Matchers#assert_none_of_selectors
|
|
32
|
-
def have_none_of_selectors(
|
|
33
|
-
Matchers::HaveNoSelectors.new(
|
|
32
|
+
def have_none_of_selectors(...)
|
|
33
|
+
Matchers::HaveNoSelectors.new(...)
|
|
34
34
|
end
|
|
35
35
|
|
|
36
36
|
# RSpec matcher for whether the element(s) matching any of a group of selectors exist.
|
|
37
37
|
#
|
|
38
38
|
# @see Capybara::Node::Matchers#assert_any_of_selectors
|
|
39
|
-
def have_any_of_selectors(
|
|
40
|
-
Matchers::HaveAnySelectors.new(
|
|
39
|
+
def have_any_of_selectors(...)
|
|
40
|
+
Matchers::HaveAnySelectors.new(...)
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
# RSpec matcher for whether the current element matches a given selector.
|
|
44
44
|
#
|
|
45
45
|
# @see Capybara::Node::Matchers#assert_matches_selector
|
|
46
|
-
def match_selector(
|
|
47
|
-
Matchers::MatchSelector.new(
|
|
46
|
+
def match_selector(...)
|
|
47
|
+
Matchers::MatchSelector.new(...)
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
%i[css xpath].each do |selector|
|
|
@@ -177,15 +177,15 @@ module Capybara
|
|
|
177
177
|
# RSpec matcher for whether sibling element(s) matching a given selector exist.
|
|
178
178
|
#
|
|
179
179
|
# @see Capybara::Node::Matchers#assert_sibling
|
|
180
|
-
def have_sibling(
|
|
181
|
-
Matchers::HaveSibling.new(
|
|
180
|
+
def have_sibling(...)
|
|
181
|
+
Matchers::HaveSibling.new(...)
|
|
182
182
|
end
|
|
183
183
|
|
|
184
184
|
# RSpec matcher for whether ancestor element(s) matching a given selector exist.
|
|
185
185
|
#
|
|
186
186
|
# @see Capybara::Node::Matchers#assert_ancestor
|
|
187
|
-
def have_ancestor(
|
|
188
|
-
Matchers::HaveAncestor.new(
|
|
187
|
+
def have_ancestor(...)
|
|
188
|
+
Matchers::HaveAncestor.new(...)
|
|
189
189
|
end
|
|
190
190
|
|
|
191
191
|
##
|
|
@@ -76,7 +76,7 @@ module Capybara
|
|
|
76
76
|
else
|
|
77
77
|
cls = Array(classes).reject { |c| c.is_a? Regexp }.group_by { |cl| cl.match?(/^!(?!!!)/) }
|
|
78
78
|
[(cls[false].to_a.map { |cl| ".#{Capybara::Selector::CSS.escape(cl.sub(/^!!/, ''))}" } +
|
|
79
|
-
cls[true].to_a.map { |cl| ":not(.#{Capybara::Selector::CSS.escape(cl.slice(1
|
|
79
|
+
cls[true].to_a.map { |cl| ":not(.#{Capybara::Selector::CSS.escape(cl.slice(1..))})" }).join]
|
|
80
80
|
end
|
|
81
81
|
end
|
|
82
82
|
end
|
|
@@ -51,7 +51,7 @@ module Capybara
|
|
|
51
51
|
else
|
|
52
52
|
Array(classes).reject { |c| c.is_a? Regexp }.map do |klass|
|
|
53
53
|
if klass.match?(/^!(?!!!)/)
|
|
54
|
-
!XPath.attr(:class).contains_word(klass.slice(1
|
|
54
|
+
!XPath.attr(:class).contains_word(klass.slice(1..))
|
|
55
55
|
else
|
|
56
56
|
XPath.attr(:class).contains_word(klass.sub(/^!!/, ''))
|
|
57
57
|
end
|
|
@@ -14,16 +14,17 @@ Capybara.add_selector(:button, locator_type: [String, Symbol]) do
|
|
|
14
14
|
XPath.string.n.is(locator) |
|
|
15
15
|
XPath.descendant(:img)[XPath.attr(:alt).is(locator)]
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
label_contains_xpath = locate_label(locator).descendant[labellable_elements]
|
|
18
|
+
input_btn_xpath = input_btn_xpath[locator_matchers]
|
|
19
|
+
btn_xpath = btn_xpath[btn_matchers]
|
|
19
20
|
aria_btn_xpath = aria_btn_xpath[btn_matchers]
|
|
20
21
|
|
|
21
22
|
alt_matches = XPath.attr(:alt).is(locator)
|
|
22
23
|
alt_matches |= XPath.attr(:'aria-label').is(locator) if enable_aria_label
|
|
23
|
-
image_btn_xpath = image_btn_xpath[alt_matches]
|
|
24
|
+
image_btn_xpath = image_btn_xpath[alt_matches]
|
|
24
25
|
end
|
|
25
26
|
|
|
26
|
-
btn_xpaths = [input_btn_xpath, btn_xpath, image_btn_xpath]
|
|
27
|
+
btn_xpaths = [input_btn_xpath, btn_xpath, image_btn_xpath, label_contains_xpath].compact
|
|
27
28
|
btn_xpaths << aria_btn_xpath if enable_aria_role
|
|
28
29
|
|
|
29
30
|
%i[value title type].inject(btn_xpaths.inject(&:union)) do |memo, ef|
|
|
@@ -60,4 +61,8 @@ Capybara.add_selector(:button, locator_type: [String, Symbol]) do
|
|
|
60
61
|
(XPath.attr(config.test_id) == locator if config.test_id)
|
|
61
62
|
].compact.inject(&:|)
|
|
62
63
|
end
|
|
64
|
+
|
|
65
|
+
def labellable_elements
|
|
66
|
+
(XPath.self(:input) & XPath.attr(:type).one_of('submit', 'reset', 'image', 'button')) | XPath.self(:button)
|
|
67
|
+
end
|
|
63
68
|
end
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Capybara.add_selector(:checkbox, locator_type: [String, Symbol]) do
|
|
4
4
|
xpath do |locator, allow_self: nil, **options|
|
|
5
|
-
xpath = XPath.axis(allow_self ? :
|
|
5
|
+
xpath = XPath.axis(allow_self ? :'descendant-or-self' : :descendant, :input)[
|
|
6
6
|
XPath.attr(:type) == 'checkbox'
|
|
7
7
|
]
|
|
8
8
|
locate_field(xpath, locator, **options)
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Capybara.add_selector(:file_field, locator_type: [String, Symbol]) do
|
|
4
4
|
label 'file field'
|
|
5
5
|
xpath do |locator, allow_self: nil, **options|
|
|
6
|
-
xpath = XPath.axis(allow_self ? :
|
|
6
|
+
xpath = XPath.axis(allow_self ? :'descendant-or-self' : :descendant, :input)[
|
|
7
7
|
XPath.attr(:type) == 'file'
|
|
8
8
|
]
|
|
9
9
|
locate_field(xpath, locator, **options)
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Capybara.add_selector(:fillable_field, locator_type: [String, Symbol]) do
|
|
4
4
|
label 'field'
|
|
5
5
|
xpath do |locator, allow_self: nil, **options|
|
|
6
|
-
xpath = XPath.axis(allow_self ? :
|
|
6
|
+
xpath = XPath.axis(allow_self ? :'descendant-or-self' : :descendant, :input, :textarea)[
|
|
7
7
|
!XPath.attr(:type).one_of('submit', 'image', 'radio', 'checkbox', 'hidden', 'file')
|
|
8
8
|
]
|
|
9
9
|
locate_field(xpath, locator, **options)
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Capybara.add_selector(:radio_button, locator_type: [String, Symbol]) do
|
|
4
4
|
label 'radio button'
|
|
5
5
|
xpath do |locator, allow_self: nil, **options|
|
|
6
|
-
xpath = XPath.axis(allow_self ? :
|
|
6
|
+
xpath = XPath.axis(allow_self ? :'descendant-or-self' : :descendant, :input)[
|
|
7
7
|
XPath.attr(:type) == 'radio'
|
|
8
8
|
]
|
|
9
9
|
locate_field(xpath, locator, **options)
|
|
@@ -260,7 +260,9 @@ module Capybara
|
|
|
260
260
|
|
|
261
261
|
def parameter_names(block)
|
|
262
262
|
key_types = %i[key keyreq]
|
|
263
|
-
|
|
263
|
+
# user filter_map when we drop dupport for 2.6
|
|
264
|
+
# block.parameters.select { |(type, _name)| key_types.include? type }.map { |(_, name)| name }
|
|
265
|
+
block.parameters.filter_map { |(type, name)| name if key_types.include? type }
|
|
264
266
|
end
|
|
265
267
|
|
|
266
268
|
def expression(type, allowed_filters, &block)
|
|
@@ -101,13 +101,11 @@ module Capybara
|
|
|
101
101
|
private
|
|
102
102
|
|
|
103
103
|
def options_with_defaults(options)
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
end
|
|
104
|
+
expression_filters.chain(node_filters)
|
|
105
|
+
.select { |_n, filter| filter.default? }
|
|
106
|
+
.each_with_object(options.dup) do |(name, filter), opts|
|
|
107
|
+
opts[name] = filter.default unless opts.key?(name)
|
|
109
108
|
end
|
|
110
|
-
options
|
|
111
109
|
end
|
|
112
110
|
|
|
113
111
|
def add_filter(name, filter_class, *types, matcher: nil, **options, &block)
|
data/lib/capybara/selector.rb
CHANGED
|
@@ -14,6 +14,7 @@ require 'capybara/selector/definition'
|
|
|
14
14
|
# * :left_of (Element) - Match elements left of the passed element on the page
|
|
15
15
|
# * :right_of (Element) - Match elements right of the passed element on the page
|
|
16
16
|
# * :near (Element) - Match elements near (within 50px) the passed element on the page
|
|
17
|
+
# * :focused (Boolean) - Match elements with focus (requires driver support)
|
|
17
18
|
#
|
|
18
19
|
# ### Built-in Selectors
|
|
19
20
|
#
|
|
@@ -12,7 +12,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
|
12
12
|
clear_session_storage: nil
|
|
13
13
|
}.freeze
|
|
14
14
|
SPECIAL_OPTIONS = %i[browser clear_local_storage clear_session_storage timeout native_displayed].freeze
|
|
15
|
-
CAPS_VERSION = Gem::Requirement.new('
|
|
15
|
+
CAPS_VERSION = Gem::Requirement.new('>= 4.0.0.alpha6')
|
|
16
16
|
|
|
17
17
|
attr_reader :app, :options
|
|
18
18
|
|
|
@@ -43,7 +43,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
|
43
43
|
Gem::Version.new(Selenium::WebDriver::VERSION)
|
|
44
44
|
end
|
|
45
45
|
|
|
46
|
-
unless Gem::Requirement.new('>= 3.
|
|
46
|
+
unless Gem::Requirement.new('>= 3.142.7').satisfied_by? @selenium_webdriver_version
|
|
47
47
|
warn "Warning: You're using an unsupported version of selenium-webdriver, please upgrade."
|
|
48
48
|
end
|
|
49
49
|
|
|
@@ -148,8 +148,13 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
|
148
148
|
unwrap_script_result(result)
|
|
149
149
|
end
|
|
150
150
|
|
|
151
|
+
def active_element
|
|
152
|
+
build_node(native_active_element)
|
|
153
|
+
end
|
|
154
|
+
|
|
151
155
|
def send_keys(*args)
|
|
152
|
-
|
|
156
|
+
# Should this call the specialized nodes rather than native???
|
|
157
|
+
native_active_element.send_keys(*args)
|
|
153
158
|
end
|
|
154
159
|
|
|
155
160
|
def save_screenshot(path, **_options)
|
|
@@ -249,7 +254,13 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
|
249
254
|
end
|
|
250
255
|
|
|
251
256
|
def open_new_window(kind = :tab)
|
|
252
|
-
browser.
|
|
257
|
+
if browser.switch_to.respond_to?(:new_window)
|
|
258
|
+
handle = current_window_handle
|
|
259
|
+
browser.switch_to.new_window(kind)
|
|
260
|
+
switch_to_window(handle)
|
|
261
|
+
else
|
|
262
|
+
browser.manage.new_window(kind)
|
|
263
|
+
end
|
|
253
264
|
rescue NoMethodError, Selenium::WebDriver::Error::WebDriverError
|
|
254
265
|
# If not supported by the driver or browser default to using JS
|
|
255
266
|
browser.execute_script('window.open();')
|
|
@@ -293,7 +304,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
|
293
304
|
end
|
|
294
305
|
|
|
295
306
|
def invalid_element_errors
|
|
296
|
-
@invalid_element_errors ||=
|
|
307
|
+
@invalid_element_errors ||=
|
|
297
308
|
[
|
|
298
309
|
::Selenium::WebDriver::Error::StaleElementReferenceError,
|
|
299
310
|
::Selenium::WebDriver::Error::ElementNotInteractableError,
|
|
@@ -313,7 +324,6 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
|
313
324
|
end
|
|
314
325
|
end
|
|
315
326
|
end
|
|
316
|
-
end
|
|
317
327
|
end
|
|
318
328
|
|
|
319
329
|
def no_such_window_error
|
|
@@ -330,6 +340,10 @@ private
|
|
|
330
340
|
args.map { |arg| arg.is_a?(Capybara::Selenium::Node) ? arg.native : arg }
|
|
331
341
|
end
|
|
332
342
|
|
|
343
|
+
def native_active_element
|
|
344
|
+
browser.switch_to.active_element
|
|
345
|
+
end
|
|
346
|
+
|
|
333
347
|
def clear_browser_state
|
|
334
348
|
delete_all_cookies
|
|
335
349
|
clear_storage
|
|
@@ -459,12 +473,16 @@ private
|
|
|
459
473
|
end
|
|
460
474
|
|
|
461
475
|
def unwrap_script_result(arg)
|
|
476
|
+
# TODO: move into the case when we drop support for Selenium < 4.1
|
|
477
|
+
element_types = [Selenium::WebDriver::Element]
|
|
478
|
+
element_types.push(Selenium::WebDriver::ShadowRoot) if defined?(Selenium::WebDriver::ShadowRoot)
|
|
479
|
+
|
|
462
480
|
case arg
|
|
463
481
|
when Array
|
|
464
482
|
arg.map { |arr| unwrap_script_result(arr) }
|
|
465
483
|
when Hash
|
|
466
484
|
arg.transform_values! { |value| unwrap_script_result(value) }
|
|
467
|
-
when
|
|
485
|
+
when *element_types
|
|
468
486
|
build_node(arg)
|
|
469
487
|
else
|
|
470
488
|
arg
|
|
@@ -475,10 +493,6 @@ private
|
|
|
475
493
|
browser
|
|
476
494
|
end
|
|
477
495
|
|
|
478
|
-
def active_element
|
|
479
|
-
browser.switch_to.active_element
|
|
480
|
-
end
|
|
481
|
-
|
|
482
496
|
def build_node(native_node, initial_cache = {})
|
|
483
497
|
::Capybara::Selenium::Node.new(self, native_node, initial_cache)
|
|
484
498
|
end
|
|
@@ -38,7 +38,7 @@ module Capybara::Selenium::Driver::ChromeDriver
|
|
|
38
38
|
return unless @browser
|
|
39
39
|
|
|
40
40
|
switch_to_window(window_handles.first)
|
|
41
|
-
window_handles.slice(1
|
|
41
|
+
window_handles.slice(1..).each { |win| close_window(win) }
|
|
42
42
|
return super if chromedriver_version < 73
|
|
43
43
|
|
|
44
44
|
timer = Capybara::Helpers.timer(expire_in: 10)
|
|
@@ -39,7 +39,7 @@ module Capybara::Selenium::Driver::EdgeDriver
|
|
|
39
39
|
return unless @browser
|
|
40
40
|
|
|
41
41
|
switch_to_window(window_handles.first)
|
|
42
|
-
window_handles.slice(1
|
|
42
|
+
window_handles.slice(1..).each { |win| close_window(win) }
|
|
43
43
|
|
|
44
44
|
timer = Capybara::Helpers.timer(expire_in: 10)
|
|
45
45
|
begin
|
|
@@ -14,7 +14,7 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
|
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def all_text
|
|
17
|
-
text = driver.evaluate_script('arguments[0].textContent', self)
|
|
17
|
+
text = driver.evaluate_script('arguments[0].textContent', self) || ''
|
|
18
18
|
text.gsub(/[\u200b\u200e\u200f]/, '')
|
|
19
19
|
.gsub(/[\ \n\f\t\v\u2028\u2029]+/, ' ')
|
|
20
20
|
.gsub(/\A[[:space:]&&[^\u00a0]]+/, '')
|
|
@@ -53,6 +53,11 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
|
|
|
53
53
|
# :none => append the new value to the existing value <br/>
|
|
54
54
|
# :backspace => send backspace keystrokes to clear the field <br/>
|
|
55
55
|
# Array => an array of keys to send before the value being set, e.g. [[:command, 'a'], :backspace]
|
|
56
|
+
# @option options [Boolean] :rapid (nil) Whether setting text inputs should use a faster "rapid" mode<br/>
|
|
57
|
+
# nil => Text inputs with length greater than 30 characters will be set using a faster driver script mode<br/>
|
|
58
|
+
# true => Rapid mode will be used regardless of input length<br/>
|
|
59
|
+
# false => Sends keys via conventional mode. This may be required to avoid losing key-presses if you have certain
|
|
60
|
+
# Javascript interactions on form inputs<br/>
|
|
56
61
|
def set(value, **options)
|
|
57
62
|
if value.is_a?(Array) && !multiple?
|
|
58
63
|
raise ArgumentError, "Value cannot be an Array when 'multiple' attribute is not present. Not a #{value.class}"
|
|
@@ -199,10 +204,6 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
|
|
|
199
204
|
native.attribute('isContentEditable') == 'true'
|
|
200
205
|
end
|
|
201
206
|
|
|
202
|
-
def ==(other)
|
|
203
|
-
native == other.native
|
|
204
|
-
end
|
|
205
|
-
|
|
206
207
|
def path
|
|
207
208
|
driver.evaluate_script GET_XPATH_SCRIPT, self
|
|
208
209
|
end
|
|
@@ -218,6 +219,13 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
|
|
|
218
219
|
native.rect
|
|
219
220
|
end
|
|
220
221
|
|
|
222
|
+
def shadow_root
|
|
223
|
+
raise_error 'You must be using Selenium 4.1+ for shadow_root support' unless native.respond_to? :shadow_root
|
|
224
|
+
|
|
225
|
+
root = native.shadow_root
|
|
226
|
+
root && build_node(native.shadow_root)
|
|
227
|
+
end
|
|
228
|
+
|
|
221
229
|
protected
|
|
222
230
|
|
|
223
231
|
def scroll_if_needed
|
|
@@ -274,7 +282,7 @@ private
|
|
|
274
282
|
elsif clear == :backspace
|
|
275
283
|
# Clear field by sending the correct number of backspace keys.
|
|
276
284
|
backspaces = [:backspace] * self.value.to_s.length
|
|
277
|
-
send_keys(
|
|
285
|
+
send_keys(:end, *backspaces, value)
|
|
278
286
|
elsif clear.is_a? Array
|
|
279
287
|
send_keys(*clear, value)
|
|
280
288
|
else
|
|
@@ -282,7 +290,7 @@ private
|
|
|
282
290
|
if rapid == true || ((value.length > auto_rapid_set_length) && rapid != false)
|
|
283
291
|
send_keys(value[0..3])
|
|
284
292
|
driver.execute_script RAPID_APPEND_TEXT, self, value[4...-3]
|
|
285
|
-
send_keys(value[-3
|
|
293
|
+
send_keys(value[-3..])
|
|
286
294
|
else
|
|
287
295
|
send_keys(value)
|
|
288
296
|
end
|
|
@@ -298,7 +306,7 @@ private
|
|
|
298
306
|
|
|
299
307
|
scroll_if_needed do
|
|
300
308
|
action_with_modifiers(click_options) do |action|
|
|
301
|
-
if
|
|
309
|
+
if block
|
|
302
310
|
yield action
|
|
303
311
|
else
|
|
304
312
|
click_options.coords? ? action.click : action.click(native)
|
|
@@ -488,6 +496,12 @@ private
|
|
|
488
496
|
JS
|
|
489
497
|
end
|
|
490
498
|
|
|
499
|
+
def native_id
|
|
500
|
+
# Selenium 3 -> 4 changed the return of ref
|
|
501
|
+
type_or_id, id = native.ref
|
|
502
|
+
id || type_or_id
|
|
503
|
+
end
|
|
504
|
+
|
|
491
505
|
GET_XPATH_SCRIPT = <<~'JS'
|
|
492
506
|
(function(el, xml){
|
|
493
507
|
var xpath = '';
|
|
@@ -65,7 +65,7 @@ class Capybara::Selenium::ChromeNode < Capybara::Selenium::Node
|
|
|
65
65
|
return super unless native_displayed?
|
|
66
66
|
|
|
67
67
|
begin
|
|
68
|
-
bridge.send(:execute, :is_element_displayed, id:
|
|
68
|
+
bridge.send(:execute, :is_element_displayed, id: native_id)
|
|
69
69
|
rescue Selenium::WebDriver::Error::UnknownCommandError
|
|
70
70
|
# If the is_element_displayed command is unknown, no point in trying again
|
|
71
71
|
driver.options[:native_displayed] = false
|
|
@@ -69,7 +69,7 @@ class Capybara::Selenium::EdgeNode < Capybara::Selenium::Node
|
|
|
69
69
|
return super unless chrome_edge? && native_displayed?
|
|
70
70
|
|
|
71
71
|
begin
|
|
72
|
-
bridge.send(:execute, :is_element_displayed, id:
|
|
72
|
+
bridge.send(:execute, :is_element_displayed, id: native_id)
|
|
73
73
|
rescue Selenium::WebDriver::Error::UnknownCommandError
|
|
74
74
|
# If the is_element_displayed command is unknown, no point in trying again
|
|
75
75
|
driver.options[:native_displayed] = false
|
|
@@ -76,7 +76,7 @@ class Capybara::Selenium::FirefoxNode < Capybara::Selenium::Node
|
|
|
76
76
|
return super unless native_displayed?
|
|
77
77
|
|
|
78
78
|
begin
|
|
79
|
-
bridge.send(:execute, :is_element_displayed, id:
|
|
79
|
+
bridge.send(:execute, :is_element_displayed, id: native_id)
|
|
80
80
|
rescue Selenium::WebDriver::Error::UnknownCommandError
|
|
81
81
|
# If the is_element_displayed command is unknown, no point in trying again
|
|
82
82
|
driver.options[:native_displayed] = false
|
|
@@ -14,7 +14,7 @@ class Capybara::Selenium::SafariNode < Capybara::Selenium::Node
|
|
|
14
14
|
warn 'You are attempting to click a table row which has issues in safaridriver - '\
|
|
15
15
|
'Your test should probably be clicking on a table cell like a user would. '\
|
|
16
16
|
'Clicking the first cell in the row instead.'
|
|
17
|
-
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)
|
|
18
18
|
end
|
|
19
19
|
raise
|
|
20
20
|
rescue ::Selenium::WebDriver::Error::WebDriverError => e
|
|
@@ -74,7 +74,7 @@ class Capybara::Selenium::SafariNode < Capybara::Selenium::Node
|
|
|
74
74
|
if clear == :backspace
|
|
75
75
|
# Clear field by sending the correct number of backspace keys.
|
|
76
76
|
backspaces = [:backspace] * self.value.to_s.length
|
|
77
|
-
send_keys(
|
|
77
|
+
send_keys([:control, 'e'], *backspaces, value)
|
|
78
78
|
else
|
|
79
79
|
super.tap do
|
|
80
80
|
# React doesn't see the safaridriver element clear
|