capybara 3.35.2 → 3.37.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|