capybara 3.9.0 → 3.10.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 +15 -0
- data/License.txt +1 -1
- data/README.md +1 -1
- data/lib/capybara.rb +1 -2
- data/lib/capybara/helpers.rb +3 -2
- data/lib/capybara/minitest.rb +1 -1
- data/lib/capybara/minitest/spec.rb +1 -0
- data/lib/capybara/node/finders.rb +20 -15
- data/lib/capybara/node/matchers.rb +43 -10
- data/lib/capybara/queries/current_path_query.rb +2 -2
- data/lib/capybara/queries/selector_query.rb +9 -2
- data/lib/capybara/rack_test/browser.rb +14 -13
- data/lib/capybara/rack_test/form.rb +32 -27
- data/lib/capybara/result.rb +8 -11
- data/lib/capybara/rspec/compound.rb +16 -26
- data/lib/capybara/rspec/matcher_proxies.rb +28 -11
- data/lib/capybara/rspec/matchers.rb +67 -45
- data/lib/capybara/selector.rb +22 -25
- data/lib/capybara/selector/builders/css_builder.rb +3 -4
- data/lib/capybara/selector/builders/xpath_builder.rb +4 -6
- data/lib/capybara/selector/regexp_disassembler.rb +43 -44
- data/lib/capybara/selector/selector.rb +16 -11
- data/lib/capybara/selenium/driver.rb +28 -21
- data/lib/capybara/selenium/nodes/marionette_node.rb +6 -12
- data/lib/capybara/server.rb +1 -3
- data/lib/capybara/server/animation_disabler.rb +2 -2
- data/lib/capybara/session.rb +1 -1
- data/lib/capybara/spec/session/assert_all_of_selectors_spec.rb +29 -0
- data/lib/capybara/spec/session/click_button_spec.rb +6 -0
- data/lib/capybara/spec/session/click_link_spec.rb +1 -1
- data/lib/capybara/spec/session/has_all_selectors_spec.rb +1 -1
- data/lib/capybara/spec/session/has_any_selectors_spec.rb +25 -0
- data/lib/capybara/spec/session/html_spec.rb +7 -0
- data/lib/capybara/spec/session/node_spec.rb +5 -1
- data/lib/capybara/spec/session/refresh_spec.rb +4 -0
- data/lib/capybara/spec/session/window/switch_to_window_spec.rb +4 -0
- data/lib/capybara/spec/session/window/window_opened_by_spec.rb +4 -0
- data/lib/capybara/spec/session/window/window_spec.rb +4 -0
- data/lib/capybara/spec/session/window/windows_spec.rb +4 -0
- data/lib/capybara/spec/views/form.erb +2 -2
- data/lib/capybara/version.rb +1 -1
- data/spec/minitest_spec.rb +5 -1
- data/spec/minitest_spec_spec.rb +5 -1
- data/spec/regexp_dissassembler_spec.rb +5 -3
- data/spec/rspec_spec.rb +20 -0
- data/spec/selector_spec.rb +89 -3
- data/spec/selenium_spec_chrome.rb +3 -7
- data/spec/selenium_spec_marionette.rb +1 -4
- metadata +20 -6
- data/lib/capybara/xpath_patches.rb +0 -27
@@ -33,16 +33,15 @@ if defined?(::RSpec::Expectations::Version)
|
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
36
|
-
|
37
|
-
|
38
|
-
|
36
|
+
# @api private
|
37
|
+
module Synchronizer
|
39
38
|
def match(_expected, actual)
|
40
39
|
@evaluator = CapybaraEvaluator.new(actual)
|
41
40
|
syncer = sync_element(actual)
|
42
41
|
begin
|
43
42
|
syncer.synchronize do
|
44
43
|
@evaluator.reset
|
45
|
-
raise ::Capybara::ElementNotFound unless
|
44
|
+
raise ::Capybara::ElementNotFound unless synchronized_match?
|
46
45
|
|
47
46
|
true
|
48
47
|
end
|
@@ -62,32 +61,23 @@ if defined?(::RSpec::Expectations::Version)
|
|
62
61
|
end
|
63
62
|
end
|
64
63
|
|
65
|
-
class
|
66
|
-
|
64
|
+
class And < ::RSpec::Matchers::BuiltIn::Compound::And
|
65
|
+
include Synchronizer
|
67
66
|
|
68
|
-
|
69
|
-
@evaluator = CapybaraEvaluator.new(actual)
|
70
|
-
syncer = sync_element(actual)
|
71
|
-
begin
|
72
|
-
syncer.synchronize do
|
73
|
-
@evaluator.reset
|
74
|
-
raise ::Capybara::ElementNotFound unless [matcher_1_matches?, matcher_2_matches?].any?
|
67
|
+
private
|
75
68
|
|
76
|
-
|
77
|
-
|
78
|
-
rescue StandardError
|
79
|
-
false
|
80
|
-
end
|
69
|
+
def synchronized_match?
|
70
|
+
[matcher_1_matches?, matcher_2_matches?].all?
|
81
71
|
end
|
72
|
+
end
|
82
73
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
end
|
74
|
+
class Or < ::RSpec::Matchers::BuiltIn::Compound::Or
|
75
|
+
include Synchronizer
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def synchronized_match?
|
80
|
+
[matcher_1_matches?, matcher_2_matches?].any?
|
91
81
|
end
|
92
82
|
end
|
93
83
|
end
|
@@ -18,13 +18,34 @@ module Capybara
|
|
18
18
|
end
|
19
19
|
end
|
20
20
|
end
|
21
|
+
end
|
22
|
+
|
23
|
+
if RUBY_ENGINE == 'jruby'
|
24
|
+
module Capybara::DSL
|
25
|
+
class <<self
|
26
|
+
remove_method :included
|
27
|
+
|
28
|
+
def included(base)
|
29
|
+
warn 'including Capybara::DSL in the global scope is not recommended!' if base == Object
|
30
|
+
base.send(:include, ::Capybara::RSpecMatcherProxies) if defined?(::RSpec::Matchers) && base.include?(::RSpec::Matchers)
|
31
|
+
super
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
21
35
|
|
22
|
-
|
36
|
+
if defined?(::RSpec::Matchers)
|
37
|
+
module ::RSpec::Matchers
|
38
|
+
def self.included(base)
|
39
|
+
base.send(:include, ::Capybara::RSpecMatcherProxies) if base.include?(::Capybara::DSL)
|
40
|
+
super
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
else
|
45
|
+
module Capybara::DSLRSpecProxyInstaller
|
23
46
|
module ClassMethods
|
24
47
|
def included(base)
|
25
|
-
if defined?(::RSpec::Matchers)
|
26
|
-
base.include(::Capybara::RSpecMatcherProxies) if base.include?(::RSpec::Matchers)
|
27
|
-
end
|
48
|
+
base.include(::Capybara::RSpecMatcherProxies) if defined?(::RSpec::Matchers) && base.include?(::RSpec::Matchers)
|
28
49
|
super
|
29
50
|
end
|
30
51
|
end
|
@@ -36,7 +57,7 @@ module Capybara
|
|
36
57
|
end
|
37
58
|
end
|
38
59
|
|
39
|
-
module RSpecMatcherProxyInstaller
|
60
|
+
module Capybara::RSpecMatcherProxyInstaller
|
40
61
|
module ClassMethods
|
41
62
|
def included(base)
|
42
63
|
base.include(::Capybara::RSpecMatcherProxies) if base.include?(::Capybara::DSL)
|
@@ -51,11 +72,7 @@ module Capybara
|
|
51
72
|
end
|
52
73
|
end
|
53
74
|
|
54
|
-
DSL.prepend ::Capybara::DSLRSpecProxyInstaller
|
55
|
-
end
|
75
|
+
Capybara::DSL.prepend ::Capybara::DSLRSpecProxyInstaller
|
56
76
|
|
57
|
-
if defined?(::RSpec::Matchers)
|
58
|
-
module ::RSpec::Matchers
|
59
|
-
prepend ::Capybara::RSpecMatcherProxyInstaller
|
60
|
-
end
|
77
|
+
::RSpec::Matchers.prepend ::Capybara::RSpecMatcherProxyInstaller if defined?(::RSpec::Matchers)
|
61
78
|
end
|
@@ -25,20 +25,6 @@ module Capybara
|
|
25
25
|
|
26
26
|
private
|
27
27
|
|
28
|
-
def wrap_matches?(actual)
|
29
|
-
yield(wrap(actual))
|
30
|
-
rescue Capybara::ExpectationNotMet => err
|
31
|
-
@failure_message = err.message
|
32
|
-
false
|
33
|
-
end
|
34
|
-
|
35
|
-
def wrap_does_not_match?(actual)
|
36
|
-
yield(wrap(actual))
|
37
|
-
rescue Capybara::ExpectationNotMet => err
|
38
|
-
@failure_message_when_negated = err.message
|
39
|
-
false
|
40
|
-
end
|
41
|
-
|
42
28
|
def session_query_args
|
43
29
|
if @args.last.is_a? Hash
|
44
30
|
@args.last[:session_options] = session_options
|
@@ -60,13 +46,29 @@ module Capybara
|
|
60
46
|
end
|
61
47
|
end
|
62
48
|
|
63
|
-
class
|
49
|
+
class WrappedElementMatcher < Matcher
|
64
50
|
def matches?(actual)
|
65
|
-
|
51
|
+
element_matches?(wrap(actual))
|
52
|
+
rescue Capybara::ExpectationNotMet => err
|
53
|
+
@failure_message = err.message
|
54
|
+
false
|
66
55
|
end
|
67
56
|
|
68
57
|
def does_not_match?(actual)
|
69
|
-
|
58
|
+
element_does_not_match?(wrap(actual))
|
59
|
+
rescue Capybara::ExpectationNotMet => err
|
60
|
+
@failure_message_when_negated = err.message
|
61
|
+
false
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
class HaveSelector < WrappedElementMatcher
|
66
|
+
def element_matches?(el)
|
67
|
+
el.assert_selector(*@args, &@filter_block)
|
68
|
+
end
|
69
|
+
|
70
|
+
def element_does_not_match?(el)
|
71
|
+
el.assert_no_selector(*@args, &@filter_block)
|
70
72
|
end
|
71
73
|
|
72
74
|
def description
|
@@ -78,9 +80,9 @@ module Capybara
|
|
78
80
|
end
|
79
81
|
end
|
80
82
|
|
81
|
-
class HaveAllSelectors <
|
82
|
-
def
|
83
|
-
|
83
|
+
class HaveAllSelectors < WrappedElementMatcher
|
84
|
+
def element_matches?(el)
|
85
|
+
el.assert_all_of_selectors(*@args, &@filter_block)
|
84
86
|
end
|
85
87
|
|
86
88
|
def does_not_match?(_actual)
|
@@ -92,9 +94,9 @@ module Capybara
|
|
92
94
|
end
|
93
95
|
end
|
94
96
|
|
95
|
-
class HaveNoSelectors <
|
96
|
-
def
|
97
|
-
|
97
|
+
class HaveNoSelectors < WrappedElementMatcher
|
98
|
+
def element_matches?(el)
|
99
|
+
el.assert_none_of_selectors(*@args, &@filter_block)
|
98
100
|
end
|
99
101
|
|
100
102
|
def does_not_match?(_actual)
|
@@ -106,13 +108,27 @@ module Capybara
|
|
106
108
|
end
|
107
109
|
end
|
108
110
|
|
111
|
+
class HaveAnySelectors < WrappedElementMatcher
|
112
|
+
def element_matches?(el)
|
113
|
+
el.assert_any_of_selectors(*@args, &@filter_block)
|
114
|
+
end
|
115
|
+
|
116
|
+
def does_not_match?(_actual)
|
117
|
+
el.assert_none_of_selectors(*@args, &@filter_block)
|
118
|
+
end
|
119
|
+
|
120
|
+
def description
|
121
|
+
'have any selectors'
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
109
125
|
class MatchSelector < HaveSelector
|
110
|
-
def
|
111
|
-
|
126
|
+
def element_matches?(el)
|
127
|
+
el.assert_matches_selector(*@args, &@filter_block)
|
112
128
|
end
|
113
129
|
|
114
|
-
def
|
115
|
-
|
130
|
+
def element_does_not_match?(el)
|
131
|
+
el.assert_not_matches_selector(*@args, &@filter_block)
|
116
132
|
end
|
117
133
|
|
118
134
|
def description
|
@@ -124,13 +140,13 @@ module Capybara
|
|
124
140
|
end
|
125
141
|
end
|
126
142
|
|
127
|
-
class HaveText <
|
128
|
-
def
|
129
|
-
|
143
|
+
class HaveText < WrappedElementMatcher
|
144
|
+
def element_matches?(el)
|
145
|
+
el.assert_text(*@args)
|
130
146
|
end
|
131
147
|
|
132
|
-
def
|
133
|
-
|
148
|
+
def element_does_not_match?(el)
|
149
|
+
el.assert_no_text(*@args)
|
134
150
|
end
|
135
151
|
|
136
152
|
def description
|
@@ -148,13 +164,13 @@ module Capybara
|
|
148
164
|
end
|
149
165
|
end
|
150
166
|
|
151
|
-
class HaveTitle <
|
152
|
-
def
|
153
|
-
|
167
|
+
class HaveTitle < WrappedElementMatcher
|
168
|
+
def element_matches?(el)
|
169
|
+
el.assert_title(*@args)
|
154
170
|
end
|
155
171
|
|
156
|
-
def
|
157
|
-
|
172
|
+
def element_does_not_match?(el)
|
173
|
+
el.assert_no_title(*@args)
|
158
174
|
end
|
159
175
|
|
160
176
|
def description
|
@@ -168,13 +184,13 @@ module Capybara
|
|
168
184
|
end
|
169
185
|
end
|
170
186
|
|
171
|
-
class HaveCurrentPath <
|
172
|
-
def
|
173
|
-
|
187
|
+
class HaveCurrentPath < WrappedElementMatcher
|
188
|
+
def element_matches?(el)
|
189
|
+
el.assert_current_path(*@args)
|
174
190
|
end
|
175
191
|
|
176
|
-
def
|
177
|
-
|
192
|
+
def element_does_not_match?(el)
|
193
|
+
el.assert_no_current_path(*@args)
|
178
194
|
end
|
179
195
|
|
180
196
|
def description
|
@@ -217,9 +233,9 @@ module Capybara
|
|
217
233
|
end
|
218
234
|
end
|
219
235
|
|
220
|
-
class HaveStyle <
|
221
|
-
def
|
222
|
-
|
236
|
+
class HaveStyle < WrappedElementMatcher
|
237
|
+
def element_matches?(el)
|
238
|
+
el.assert_style(*@args)
|
223
239
|
end
|
224
240
|
|
225
241
|
def does_not_match?(_actual)
|
@@ -275,6 +291,12 @@ module Capybara
|
|
275
291
|
HaveNoSelectors.new(*args, &optional_filter_block)
|
276
292
|
end
|
277
293
|
|
294
|
+
# RSpec matcher for whether the element(s) matching any of a group of selectors exist
|
295
|
+
# See {Capybara::Node::Matcher#assert_any_of_selectors}
|
296
|
+
def have_any_of_selectors(*args, &optional_filter_block)
|
297
|
+
HaveAnySelectors.new(*args, &optional_filter_block)
|
298
|
+
end
|
299
|
+
|
278
300
|
# RSpec matcher for whether the current element matches a given selector
|
279
301
|
# See {Capybara::Node::Matchers#assert_matches_selector}
|
280
302
|
def match_selector(*args, &optional_filter_block)
|
data/lib/capybara/selector.rb
CHANGED
@@ -64,7 +64,7 @@ Capybara.add_selector(:field) do
|
|
64
64
|
|
65
65
|
describe_expression_filters do |type: nil, **options|
|
66
66
|
desc = +''
|
67
|
-
(expression_filters.keys
|
67
|
+
(expression_filters.keys & options.keys).each { |ef| desc << " with #{ef} #{options[ef]}" }
|
68
68
|
desc << " of type #{type.inspect}" if type
|
69
69
|
desc
|
70
70
|
end
|
@@ -75,11 +75,10 @@ Capybara.add_selector(:field) do
|
|
75
75
|
end
|
76
76
|
|
77
77
|
Capybara.add_selector(:fieldset) do
|
78
|
-
xpath
|
78
|
+
xpath do |locator, legend: nil, **|
|
79
79
|
locator_matchers = (XPath.attr(:id) == locator.to_s) | XPath.child(:legend)[XPath.string.n.is(locator.to_s)]
|
80
|
-
locator_matchers |= XPath.attr(test_id) == locator if test_id
|
81
|
-
xpath = XPath.descendant(:fieldset)
|
82
|
-
xpath = xpath[locator_matchers] unless locator.nil?
|
80
|
+
locator_matchers |= XPath.attr(test_id) == locator.to_s if test_id
|
81
|
+
xpath = XPath.descendant(:fieldset)[locator && locator_matchers]
|
83
82
|
xpath = xpath[XPath.child(:legend)[XPath.string.n.is(legend)]] if legend
|
84
83
|
xpath
|
85
84
|
end
|
@@ -88,7 +87,7 @@ Capybara.add_selector(:fieldset) do
|
|
88
87
|
end
|
89
88
|
|
90
89
|
Capybara.add_selector(:link) do
|
91
|
-
xpath
|
90
|
+
xpath do |locator, href: true, alt: nil, title: nil, **|
|
92
91
|
xpath = XPath.descendant(:a)
|
93
92
|
xpath = xpath[@href_conditions = builder.attribute_conditions(href: href)]
|
94
93
|
|
@@ -192,8 +191,8 @@ end
|
|
192
191
|
Capybara.add_selector(:fillable_field) do
|
193
192
|
label 'field'
|
194
193
|
|
195
|
-
xpath
|
196
|
-
xpath = XPath.axis(
|
194
|
+
xpath do |locator, allow_self: nil, **options|
|
195
|
+
xpath = XPath.axis(allow_self ? :"descendant-or-self" : :descendant, :input, :textarea)[
|
197
196
|
!XPath.attr(:type).one_of('submit', 'image', 'radio', 'checkbox', 'hidden', 'file')
|
198
197
|
]
|
199
198
|
locate_field(xpath, locator, options)
|
@@ -223,8 +222,8 @@ end
|
|
223
222
|
Capybara.add_selector(:radio_button) do
|
224
223
|
label 'radio button'
|
225
224
|
|
226
|
-
xpath
|
227
|
-
xpath = XPath.axis(
|
225
|
+
xpath do |locator, allow_self: nil, **options|
|
226
|
+
xpath = XPath.axis(allow_self ? :"descendant-or-self" : :descendant, :input)[
|
228
227
|
XPath.attr(:type) == 'radio'
|
229
228
|
]
|
230
229
|
locate_field(xpath, locator, options)
|
@@ -241,8 +240,8 @@ Capybara.add_selector(:radio_button) do
|
|
241
240
|
end
|
242
241
|
|
243
242
|
Capybara.add_selector(:checkbox) do
|
244
|
-
xpath
|
245
|
-
xpath = XPath.axis(
|
243
|
+
xpath do |locator, allow_self: nil, **options|
|
244
|
+
xpath = XPath.axis(allow_self ? :"descendant-or-self" : :descendant, :input)[
|
246
245
|
XPath.attr(:type) == 'checkbox'
|
247
246
|
]
|
248
247
|
locate_field(xpath, locator, options)
|
@@ -379,8 +378,8 @@ end
|
|
379
378
|
|
380
379
|
Capybara.add_selector(:file_field) do
|
381
380
|
label 'file field'
|
382
|
-
xpath
|
383
|
-
xpath = XPath.axis(
|
381
|
+
xpath do |locator, allow_self: nil, **options|
|
382
|
+
xpath = XPath.axis(allow_self ? :"descendant-or-self" : :descendant, :input)[
|
384
383
|
XPath.attr(:type) == 'file'
|
385
384
|
]
|
386
385
|
locate_field(xpath, locator, options)
|
@@ -411,14 +410,13 @@ Capybara.add_selector(:label) do
|
|
411
410
|
end
|
412
411
|
|
413
412
|
node_filter(:for) do |node, field_or_value|
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
end
|
413
|
+
# Non element values were handled through the expression filter
|
414
|
+
next true unless field_or_value.is_a? Capybara::Node::Element
|
415
|
+
|
416
|
+
if (for_val = node[:for])
|
417
|
+
field_or_value[:id] == for_val
|
420
418
|
else
|
421
|
-
|
419
|
+
field_or_value.find_xpath('./ancestor::label[1]').include? node.base
|
422
420
|
end
|
423
421
|
end
|
424
422
|
|
@@ -431,7 +429,7 @@ Capybara.add_selector(:label) do
|
|
431
429
|
end
|
432
430
|
|
433
431
|
Capybara.add_selector(:table) do
|
434
|
-
xpath
|
432
|
+
xpath do |locator, caption: nil, **|
|
435
433
|
xpath = XPath.descendant(:table)
|
436
434
|
unless locator.nil?
|
437
435
|
locator_matchers = (XPath.attr(:id) == locator.to_s) | XPath.descendant(:caption).is(locator.to_s)
|
@@ -448,15 +446,14 @@ Capybara.add_selector(:table) do
|
|
448
446
|
end
|
449
447
|
|
450
448
|
Capybara.add_selector(:frame) do
|
451
|
-
xpath
|
449
|
+
xpath do |locator, name: nil, **|
|
452
450
|
xpath = XPath.descendant(:iframe).union(XPath.descendant(:frame))
|
453
451
|
unless locator.nil?
|
454
452
|
locator_matchers = (XPath.attr(:id) == locator.to_s) | (XPath.attr(:name) == locator.to_s)
|
455
453
|
locator_matchers |= XPath.attr(test_id) == locator if test_id
|
456
454
|
xpath = xpath[locator_matchers]
|
457
455
|
end
|
458
|
-
xpath
|
459
|
-
xpath
|
456
|
+
xpath[find_by_attr(:name, name)]
|
460
457
|
end
|
461
458
|
|
462
459
|
describe_expression_filters do |name: nil, **|
|
@@ -35,11 +35,10 @@ module Capybara
|
|
35
35
|
when XPath::Expression
|
36
36
|
raise ArgumentError, 'XPath expressions are not supported for the :class filter with CSS based selectors'
|
37
37
|
when Regexp
|
38
|
-
|
39
|
-
strs.map { |str| "[class*='#{str}'#{' i' if classes.casefold?}]" }.join
|
38
|
+
attribute_conditions(class: classes)
|
40
39
|
else
|
41
|
-
cls = Array(classes).group_by { |cl| cl.start_with?
|
42
|
-
(cls[false].to_a.map { |cl| ".#{Capybara::Selector::CSS.escape(cl)}" } +
|
40
|
+
cls = Array(classes).group_by { |cl| cl.start_with?('!') && !cl.start_with?('!!!') }
|
41
|
+
(cls[false].to_a.map { |cl| ".#{Capybara::Selector::CSS.escape(cl.sub(/^!!/, ''))}" } +
|
43
42
|
cls[true].to_a.map { |cl| ":not(.#{Capybara::Selector::CSS.escape(cl.slice(1..-1))})" }).join
|
44
43
|
end
|
45
44
|
end
|