capybara 3.35.3 → 3.37.1
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 +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
|