capybara 3.9.0 → 3.10.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 +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
|