capybara 3.35.3 → 3.37.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/History.md +57 -1
- 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 +3 -12
- 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 +9 -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 +56 -7
- 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/selector.rb +5 -1
- 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/find_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 +30 -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 +20 -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 +66 -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 +2 -2
- 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
@@ -20,6 +20,8 @@ class Capybara::RackTest::Browser
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def visit(path, **attributes)
|
23
|
+
@new_visit_request = true
|
24
|
+
reset_cache!
|
23
25
|
reset_host!
|
24
26
|
process_and_follow_redirects(:get, path, attributes)
|
25
27
|
end
|
@@ -33,19 +35,18 @@ class Capybara::RackTest::Browser
|
|
33
35
|
path = request_path if path.nil? || path.empty?
|
34
36
|
uri = build_uri(path)
|
35
37
|
uri.query = '' if method.to_s.casecmp('get').zero?
|
36
|
-
process_and_follow_redirects(method, uri.to_s, attributes, 'HTTP_REFERER' =>
|
38
|
+
process_and_follow_redirects(method, uri.to_s, attributes, 'HTTP_REFERER' => referer_url)
|
37
39
|
end
|
38
40
|
|
39
41
|
def follow(method, path, **attributes)
|
40
42
|
return if fragment_or_script?(path)
|
41
43
|
|
42
|
-
process_and_follow_redirects(method, path, attributes, 'HTTP_REFERER' =>
|
44
|
+
process_and_follow_redirects(method, path, attributes, 'HTTP_REFERER' => referer_url)
|
43
45
|
end
|
44
46
|
|
45
47
|
def process_and_follow_redirects(method, path, attributes = {}, env = {})
|
46
48
|
@current_fragment = build_uri(path).fragment
|
47
49
|
process(method, path, attributes, env)
|
48
|
-
|
49
50
|
return unless driver.follow_redirects?
|
50
51
|
|
51
52
|
driver.redirect_limit.times do
|
@@ -69,18 +70,23 @@ class Capybara::RackTest::Browser
|
|
69
70
|
@current_scheme, @current_host, @current_port = new_uri.select(:scheme, :host, :port)
|
70
71
|
@current_fragment = new_uri.fragment || @current_fragment
|
71
72
|
reset_cache!
|
73
|
+
@new_visit_request = false
|
72
74
|
send(method, new_uri.to_s, attributes, env.merge(options[:headers] || {}))
|
73
75
|
end
|
74
76
|
|
75
77
|
def build_uri(path)
|
76
|
-
URI.parse(path)
|
77
|
-
|
78
|
-
uri.path = '/' if uri.path.empty?
|
79
|
-
uri.path = request_path.sub(%r{/[^/]*$}, '/') + uri.path unless uri.path.start_with?('/')
|
78
|
+
uri = URI.parse(path)
|
79
|
+
base_uri = base_relative_uri_for(uri)
|
80
80
|
|
81
|
+
uri.path = base_uri.path + uri.path unless uri.absolute? || uri.path.start_with?('/')
|
82
|
+
|
83
|
+
if base_uri.absolute?
|
84
|
+
base_uri.merge(uri)
|
85
|
+
else
|
81
86
|
uri.scheme ||= @current_scheme
|
82
87
|
uri.host ||= @current_host
|
83
88
|
uri.port ||= @current_port unless uri.default_port == @current_port
|
89
|
+
uri
|
84
90
|
end
|
85
91
|
end
|
86
92
|
|
@@ -123,8 +129,39 @@ class Capybara::RackTest::Browser
|
|
123
129
|
dom.title
|
124
130
|
end
|
125
131
|
|
132
|
+
def last_request
|
133
|
+
raise Rack::Test::Error if @new_visit_request
|
134
|
+
|
135
|
+
super
|
136
|
+
end
|
137
|
+
|
138
|
+
def last_response
|
139
|
+
raise Rack::Test::Error if @new_visit_request
|
140
|
+
|
141
|
+
super
|
142
|
+
end
|
143
|
+
|
126
144
|
protected
|
127
145
|
|
146
|
+
def base_href
|
147
|
+
find(:css, 'head > base').first&.[](:href).to_s
|
148
|
+
end
|
149
|
+
|
150
|
+
def base_relative_uri_for(uri)
|
151
|
+
base_uri = URI.parse(base_href)
|
152
|
+
current_uri = URI.parse(safe_last_request&.url.to_s).tap do |c|
|
153
|
+
c.path.sub!(%r{/[^/]*$}, '/') unless uri.path.empty?
|
154
|
+
c.path = '/' if c.path.empty?
|
155
|
+
end
|
156
|
+
|
157
|
+
if [current_uri, base_uri].any?(&:absolute?)
|
158
|
+
current_uri.merge(base_uri)
|
159
|
+
else
|
160
|
+
base_uri.path = current_uri.path if base_uri.path.empty?
|
161
|
+
base_uri
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
128
165
|
def build_rack_mock_session
|
129
166
|
reset_host! unless current_host
|
130
167
|
Rack::MockSession.new(app, current_host)
|
@@ -136,9 +173,21 @@ protected
|
|
136
173
|
'/'
|
137
174
|
end
|
138
175
|
|
176
|
+
def safe_last_request
|
177
|
+
last_request
|
178
|
+
rescue Rack::Test::Error
|
179
|
+
nil
|
180
|
+
end
|
181
|
+
|
139
182
|
private
|
140
183
|
|
141
184
|
def fragment_or_script?(path)
|
142
185
|
path.gsub(/^#{Regexp.escape(request_path)}/, '').start_with?('#') || path.downcase.start_with?('javascript:')
|
143
186
|
end
|
187
|
+
|
188
|
+
def referer_url
|
189
|
+
build_uri(last_request.url).to_s
|
190
|
+
rescue Rack::Test::Error
|
191
|
+
''
|
192
|
+
end
|
144
193
|
end
|
@@ -98,10 +98,10 @@ class Capybara::RackTest::Driver < Capybara::Driver::Base
|
|
98
98
|
@browser = nil
|
99
99
|
end
|
100
100
|
|
101
|
-
def get(
|
102
|
-
def post(
|
103
|
-
def put(
|
104
|
-
def delete(
|
101
|
+
def get(...); browser.get(...); end
|
102
|
+
def post(...); browser.post(...); end
|
103
|
+
def put(...); browser.put(...); end
|
104
|
+
def delete(...); browser.delete(...); end
|
105
105
|
def header(key, value); browser.header(key, value); end
|
106
106
|
|
107
107
|
def invalid_element_errors
|
@@ -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)
|
@@ -66,7 +66,11 @@ module Capybara
|
|
66
66
|
end
|
67
67
|
ensure
|
68
68
|
unless locator_valid?(locator)
|
69
|
-
|
69
|
+
Capybara::Helpers.warn(
|
70
|
+
"Locator #{locator.class}:#{locator.inspect} for selector #{name.inspect} must #{locator_description}. " \
|
71
|
+
'This will raise an error in a future version of Capybara. ' \
|
72
|
+
"Called from: #{Capybara::Helpers.filter_backtrace(caller)}"
|
73
|
+
)
|
70
74
|
end
|
71
75
|
end
|
72
76
|
|
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
|