capybara 3.10.1 → 3.11.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 +13 -0
- data/README.md +2 -3
- data/lib/capybara.rb +16 -6
- data/lib/capybara/minitest.rb +8 -9
- data/lib/capybara/node/actions.rb +31 -28
- data/lib/capybara/node/base.rb +2 -1
- data/lib/capybara/node/document_matchers.rb +6 -2
- data/lib/capybara/node/element.rb +10 -10
- data/lib/capybara/node/finders.rb +13 -14
- data/lib/capybara/node/matchers.rb +1 -3
- data/lib/capybara/node/simple.rb +10 -2
- data/lib/capybara/queries/base_query.rb +7 -3
- data/lib/capybara/queries/selector_query.rb +60 -34
- data/lib/capybara/queries/style_query.rb +5 -1
- data/lib/capybara/queries/text_query.rb +2 -2
- data/lib/capybara/queries/title_query.rb +1 -1
- data/lib/capybara/rack_test/node.rb +16 -2
- data/lib/capybara/result.rb +9 -4
- data/lib/capybara/rspec/features.rb +4 -4
- data/lib/capybara/rspec/matcher_proxies.rb +3 -1
- data/lib/capybara/rspec/matchers.rb +25 -287
- data/lib/capybara/rspec/matchers/base.rb +98 -0
- data/lib/capybara/rspec/matchers/become_closed.rb +33 -0
- data/lib/capybara/rspec/matchers/compound.rb +88 -0
- data/lib/capybara/rspec/matchers/have_current_path.rb +29 -0
- data/lib/capybara/rspec/matchers/have_selector.rb +69 -0
- data/lib/capybara/rspec/matchers/have_style.rb +23 -0
- data/lib/capybara/rspec/matchers/have_text.rb +33 -0
- data/lib/capybara/rspec/matchers/have_title.rb +29 -0
- data/lib/capybara/rspec/matchers/match_selector.rb +27 -0
- data/lib/capybara/selector.rb +48 -20
- data/lib/capybara/selector/builders/xpath_builder.rb +3 -3
- data/lib/capybara/selector/css.rb +5 -5
- data/lib/capybara/selector/filters/base.rb +11 -3
- data/lib/capybara/selector/filters/expression_filter.rb +3 -3
- data/lib/capybara/selector/filters/node_filter.rb +16 -2
- data/lib/capybara/selector/regexp_disassembler.rb +116 -17
- data/lib/capybara/selector/selector.rb +52 -26
- data/lib/capybara/selenium/driver.rb +6 -2
- data/lib/capybara/selenium/node.rb +15 -14
- data/lib/capybara/selenium/nodes/marionette_node.rb +19 -5
- data/lib/capybara/selenium/patches/pause_duration_fix.rb +1 -3
- data/lib/capybara/server.rb +6 -1
- data/lib/capybara/server/animation_disabler.rb +1 -1
- data/lib/capybara/session.rb +4 -2
- data/lib/capybara/session/matchers.rb +7 -3
- data/lib/capybara/spec/public/test.js +5 -5
- data/lib/capybara/spec/session/all_spec.rb +5 -0
- data/lib/capybara/spec/session/has_css_spec.rb +4 -4
- data/lib/capybara/spec/session/has_field_spec.rb +17 -0
- data/lib/capybara/spec/session/node_spec.rb +45 -4
- data/lib/capybara/spec/spec_helper.rb +6 -1
- data/lib/capybara/spec/views/frame_child.erb +1 -1
- data/lib/capybara/spec/views/obscured.erb +44 -0
- data/lib/capybara/spec/views/with_html.erb +1 -1
- data/lib/capybara/version.rb +1 -1
- data/spec/rack_test_spec.rb +15 -0
- data/spec/regexp_dissassembler_spec.rb +88 -8
- data/spec/selector_spec.rb +3 -0
- data/spec/selenium_spec_chrome.rb +9 -15
- data/spec/selenium_spec_chrome_remote.rb +3 -2
- data/spec/selenium_spec_firefox_remote.rb +6 -2
- metadata +54 -3
- data/lib/capybara/rspec/compound.rb +0 -86
@@ -100,9 +100,7 @@ module Capybara
|
|
100
100
|
#
|
101
101
|
def assert_selector(*args, &optional_filter_block)
|
102
102
|
_verify_selector_result(args, optional_filter_block) do |result, query|
|
103
|
-
unless result.matches_count? && (result.any? || query.expects_none?)
|
104
|
-
raise Capybara::ExpectationNotMet, result.failure_message
|
105
|
-
end
|
103
|
+
raise Capybara::ExpectationNotMet, result.failure_message unless result.matches_count? && (result.any? || query.expects_none?)
|
106
104
|
end
|
107
105
|
end
|
108
106
|
|
data/lib/capybara/node/simple.rb
CHANGED
@@ -104,10 +104,10 @@ module Capybara
|
|
104
104
|
return false if (tag_name == 'input') && (native[:type] == 'hidden')
|
105
105
|
|
106
106
|
if check_ancestors
|
107
|
-
!find_xpath(
|
107
|
+
!find_xpath(VISIBILITY_XPATH)
|
108
108
|
else
|
109
109
|
# No need for an xpath if only checking the current element
|
110
|
-
!(native.
|
110
|
+
!(native.key?('hidden') || (native[:style] =~ /display:\s?none/) || %w[script head].include?(tag_name))
|
111
111
|
end
|
112
112
|
end
|
113
113
|
|
@@ -185,6 +185,14 @@ module Capybara
|
|
185
185
|
|
186
186
|
option[:value] || option.content
|
187
187
|
end
|
188
|
+
|
189
|
+
VISIBILITY_XPATH = XPath.generate do |x|
|
190
|
+
x.ancestor_or_self[
|
191
|
+
x.attr(:style)[x.contains('display:none') | x.contains('display: none')] |
|
192
|
+
x.attr(:hidden) |
|
193
|
+
x.qname.one_of('script', 'head')
|
194
|
+
].boolean
|
195
|
+
end.to_s.freeze
|
188
196
|
end
|
189
197
|
end
|
190
198
|
end
|
@@ -77,17 +77,21 @@ module Capybara
|
|
77
77
|
message = +''
|
78
78
|
count, between, maximum, minimum = options.values_at(:count, :between, :maximum, :minimum)
|
79
79
|
if count
|
80
|
-
message << " #{
|
80
|
+
message << " #{occurrences count}"
|
81
81
|
elsif between
|
82
82
|
message << " between #{between.first} and #{between.last} times"
|
83
83
|
elsif maximum
|
84
|
-
message << " at most #{
|
84
|
+
message << " at most #{occurrences maximum}"
|
85
85
|
elsif minimum
|
86
|
-
message << " at least #{
|
86
|
+
message << " at least #{occurrences minimum}"
|
87
87
|
end
|
88
88
|
message
|
89
89
|
end
|
90
90
|
|
91
|
+
def occurrences(count)
|
92
|
+
"#{count} #{Capybara::Helpers.declension('time', 'times', count)}"
|
93
|
+
end
|
94
|
+
|
91
95
|
def assert_valid_keys
|
92
96
|
invalid_keys = @options.keys - valid_keys
|
93
97
|
return if invalid_keys.empty?
|
@@ -24,7 +24,8 @@ module Capybara
|
|
24
24
|
|
25
25
|
raise ArgumentError, "Unused parameters passed to #{self.class.name} : #{args}" unless args.empty?
|
26
26
|
|
27
|
-
|
27
|
+
selector_config = { enable_aria_label: enable_aria_label, test_id: test_id }
|
28
|
+
@expression = selector.call(@locator, @options.merge(selector_config: selector_config))
|
28
29
|
|
29
30
|
warn_exact_usage
|
30
31
|
|
@@ -34,21 +35,23 @@ module Capybara
|
|
34
35
|
def name; selector.name; end
|
35
36
|
def label; selector.label || selector.name; end
|
36
37
|
|
37
|
-
def description(
|
38
|
+
def description(only_applied = false)
|
38
39
|
desc = +''
|
39
|
-
|
40
|
+
show_for = show_for_stage(only_applied)
|
41
|
+
|
42
|
+
if show_for[:any]
|
40
43
|
desc << 'visible ' if visible == :visible
|
41
44
|
desc << 'non-visible ' if visible == :hidden
|
42
45
|
end
|
43
46
|
desc << "#{label} #{locator.inspect}"
|
44
|
-
if
|
47
|
+
if show_for[:any]
|
45
48
|
desc << " with#{' exact' if exact_text == true} text #{options[:text].inspect}" if options[:text]
|
46
49
|
desc << " with exact text #{exact_text}" if exact_text.is_a?(String)
|
47
50
|
end
|
48
51
|
desc << " with id #{options[:id]}" if options[:id]
|
49
52
|
desc << " with classes [#{Array(options[:class]).join(',')}]" if options[:class]
|
50
|
-
desc << selector.description(node_filters:
|
51
|
-
desc << ' that also matches the custom filter block' if @filter_block &&
|
53
|
+
desc << selector.description(node_filters: show_for[:node], **options)
|
54
|
+
desc << ' that also matches the custom filter block' if @filter_block && show_for[:node]
|
52
55
|
desc << " within #{@resolved_node.inspect}" if describe_within?
|
53
56
|
desc
|
54
57
|
end
|
@@ -57,20 +60,21 @@ module Capybara
|
|
57
60
|
description(true)
|
58
61
|
end
|
59
62
|
|
60
|
-
def matches_filters?(node)
|
63
|
+
def matches_filters?(node, node_filter_errors = [])
|
61
64
|
return true if (@resolved_node&.== node) && options[:allow_self]
|
65
|
+
return false unless matches_locator_filter?(node)
|
62
66
|
|
63
|
-
|
67
|
+
applied_filters << :system
|
64
68
|
return false unless matches_system_filters?(node)
|
65
69
|
|
66
|
-
|
67
|
-
matches_node_filters?(node) && matches_filter_block?(node)
|
70
|
+
applied_filters << :node
|
71
|
+
matches_node_filters?(node, node_filter_errors) && matches_filter_block?(node)
|
68
72
|
rescue *(node.respond_to?(:session) ? node.session.driver.invalid_element_errors : [])
|
69
73
|
false
|
70
74
|
end
|
71
75
|
|
72
76
|
def visible
|
73
|
-
case (vis = options.fetch(:visible) {
|
77
|
+
case (vis = options.fetch(:visible) { default_visibility })
|
74
78
|
when true then :visible
|
75
79
|
when false then :all
|
76
80
|
else vis
|
@@ -98,7 +102,7 @@ module Capybara
|
|
98
102
|
|
99
103
|
# @api private
|
100
104
|
def resolve_for(node, exact = nil)
|
101
|
-
|
105
|
+
applied_filters.clear
|
102
106
|
@resolved_node = node
|
103
107
|
node.synchronize do
|
104
108
|
children = find_nodes_by_selector_format(node, exact).map(&method(:to_element))
|
@@ -121,8 +125,14 @@ module Capybara
|
|
121
125
|
|
122
126
|
private
|
123
127
|
|
128
|
+
def show_for_stage(only_applied)
|
129
|
+
lambda do |stage = :any|
|
130
|
+
!only_applied || (stage == :any ? applied_filters.any? : applied_filters.include?(stage))
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
124
134
|
def applied_filters
|
125
|
-
@applied_filters ||=
|
135
|
+
@applied_filters ||= []
|
126
136
|
end
|
127
137
|
|
128
138
|
def find_selector(locator)
|
@@ -154,21 +164,23 @@ module Capybara
|
|
154
164
|
VALID_KEYS + custom_keys
|
155
165
|
end
|
156
166
|
|
157
|
-
def matches_node_filters?(node)
|
167
|
+
def matches_node_filters?(node, errors)
|
158
168
|
unapplied_options = options.keys - valid_keys
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
unapplied_options.
|
163
|
-
|
169
|
+
@selector.with_filter_errors(errors) do
|
170
|
+
node_filters.all? do |filter_name, filter|
|
171
|
+
if filter.matcher?
|
172
|
+
unapplied_options.select { |option_name| filter.handles_option?(option_name) }.all? do |option_name|
|
173
|
+
unapplied_options.delete(option_name)
|
174
|
+
filter.matches?(node, option_name, options[option_name], @selector)
|
175
|
+
end
|
176
|
+
elsif options.key?(filter_name)
|
177
|
+
unapplied_options.delete(filter_name)
|
178
|
+
filter.matches?(node, filter_name, options[filter_name], @selector)
|
179
|
+
elsif filter.default?
|
180
|
+
filter.matches?(node, filter_name, filter.default, @selector)
|
181
|
+
else
|
182
|
+
true
|
164
183
|
end
|
165
|
-
elsif options.key?(filter_name)
|
166
|
-
unapplied_options.delete(filter_name)
|
167
|
-
filter.matches?(node, filter_name, options[filter_name])
|
168
|
-
elsif filter.default?
|
169
|
-
filter.matches?(node, filter_name, filter.default)
|
170
|
-
else
|
171
|
-
true
|
172
184
|
end
|
173
185
|
end
|
174
186
|
end
|
@@ -183,17 +195,21 @@ module Capybara
|
|
183
195
|
end
|
184
196
|
end
|
185
197
|
|
198
|
+
def filter_set(name)
|
199
|
+
::Capybara::Selector::FilterSet.all[name]
|
200
|
+
end
|
201
|
+
|
186
202
|
def node_filters
|
187
203
|
if options.key?(:filter_set)
|
188
|
-
|
204
|
+
filter_set(options[:filter_set])
|
189
205
|
else
|
190
|
-
@selector
|
191
|
-
end
|
206
|
+
@selector
|
207
|
+
end.node_filters
|
192
208
|
end
|
193
209
|
|
194
210
|
def expression_filters
|
195
211
|
filters = @selector.expression_filters
|
196
|
-
filters.merge
|
212
|
+
filters.merge filter_set(options[:filter_set]).expression_filters if options.key?(:filter_set)
|
197
213
|
filters
|
198
214
|
end
|
199
215
|
|
@@ -253,15 +269,15 @@ module Capybara
|
|
253
269
|
unapplied_options = options.keys - valid_keys
|
254
270
|
expression_filters.inject(expression) do |expr, (name, ef)|
|
255
271
|
if ef.matcher?
|
256
|
-
unapplied_options.select
|
272
|
+
unapplied_options.select(&ef.method(:handles_option?)).inject(expr) do |memo, option_name|
|
257
273
|
unapplied_options.delete(option_name)
|
258
|
-
ef.apply_filter(memo, option_name, options[option_name])
|
274
|
+
ef.apply_filter(memo, option_name, options[option_name], @selector)
|
259
275
|
end
|
260
276
|
elsif options.key?(name)
|
261
277
|
unapplied_options.delete(name)
|
262
|
-
ef.apply_filter(expr, name, options[name])
|
278
|
+
ef.apply_filter(expr, name, options[name], @selector)
|
263
279
|
elsif ef.default?
|
264
|
-
ef.apply_filter(expr, name, ef.default)
|
280
|
+
ef.apply_filter(expr, name, ef.default, @selector)
|
265
281
|
else
|
266
282
|
expr
|
267
283
|
end
|
@@ -290,6 +306,12 @@ module Capybara
|
|
290
306
|
node.is_a?(::Capybara::Node::Simple) && node.path == '/'
|
291
307
|
end
|
292
308
|
|
309
|
+
def matches_locator_filter?(node)
|
310
|
+
return true if @selector.locator_filter.nil?
|
311
|
+
|
312
|
+
@selector.locator_filter.call(node, @locator)
|
313
|
+
end
|
314
|
+
|
293
315
|
def matches_system_filters?(node)
|
294
316
|
matches_id_filter?(node) &&
|
295
317
|
matches_class_filter?(node) &&
|
@@ -348,6 +370,10 @@ module Capybara
|
|
348
370
|
!!node.text(text_visible, normalize_ws: normalize_ws).match(regexp)
|
349
371
|
end
|
350
372
|
|
373
|
+
def default_visibility
|
374
|
+
@selector.default_visibility(session_options.ignore_hidden_elements, options)
|
375
|
+
end
|
376
|
+
|
351
377
|
def builder
|
352
378
|
selector.builder
|
353
379
|
end
|
@@ -5,7 +5,7 @@ module Capybara
|
|
5
5
|
module Queries
|
6
6
|
class StyleQuery < BaseQuery
|
7
7
|
def initialize(expected_styles, session_options:, **options)
|
8
|
-
@expected_styles = expected_styles
|
8
|
+
@expected_styles = stringify_keys(expected_styles)
|
9
9
|
@options = options
|
10
10
|
@actual_styles = {}
|
11
11
|
super(@options)
|
@@ -33,6 +33,10 @@ module Capybara
|
|
33
33
|
|
34
34
|
private
|
35
35
|
|
36
|
+
def stringify_keys(hsh)
|
37
|
+
hsh.each_with_object({}) { |(k, v), str_keys| str_keys[k.to_s] = v }
|
38
|
+
end
|
39
|
+
|
36
40
|
def valid_keys
|
37
41
|
%i[wait]
|
38
42
|
end
|
@@ -65,7 +65,7 @@ module Capybara
|
|
65
65
|
insensitive_count = @actual_text.scan(insensitive_regexp).size
|
66
66
|
return if insensitive_count == @count
|
67
67
|
|
68
|
-
"it was found #{
|
68
|
+
"it was found #{occurrences insensitive_count} using a case insensitive search"
|
69
69
|
end
|
70
70
|
|
71
71
|
def invisible_message
|
@@ -73,7 +73,7 @@ module Capybara
|
|
73
73
|
invisible_count = invisible_text.scan(@search_regexp).size
|
74
74
|
return if invisible_count == @count
|
75
75
|
|
76
|
-
"it was found #{
|
76
|
+
"it was found #{occurrences invisible_count} including non-visible text"
|
77
77
|
rescue StandardError
|
78
78
|
# An error getting the non-visible text (if element goes out of scope) should not affect the response
|
79
79
|
nil
|
@@ -8,7 +8,7 @@ module Capybara
|
|
8
8
|
@expected_title = expected_title.is_a?(Regexp) ? expected_title : expected_title.to_s
|
9
9
|
@options = options
|
10
10
|
super(@options)
|
11
|
-
@search_regexp =
|
11
|
+
@search_regexp = Helpers.to_regexp(@expected_title, all_whitespace: true, exact: options.fetch(:exact, false))
|
12
12
|
assert_valid_keys
|
13
13
|
end
|
14
14
|
|
@@ -96,9 +96,9 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
|
|
96
96
|
return true if string_node.disabled?
|
97
97
|
|
98
98
|
if %w[option optgroup].include? tag_name
|
99
|
-
find_xpath(
|
99
|
+
find_xpath(OPTION_OWNER_XPATH)[0].disabled?
|
100
100
|
else
|
101
|
-
!find_xpath(
|
101
|
+
!find_xpath(DISABLED_BY_FIELDSET_XPATH).empty?
|
102
102
|
end
|
103
103
|
end
|
104
104
|
|
@@ -256,4 +256,18 @@ protected
|
|
256
256
|
def textarea?
|
257
257
|
tag_name == 'textarea'
|
258
258
|
end
|
259
|
+
|
260
|
+
OPTION_OWNER_XPATH = XPath.parent(:optgroup, :select, :datalist).to_s.freeze
|
261
|
+
DISABLED_BY_FIELDSET_XPATH = XPath.generate do |x|
|
262
|
+
x.parent(:fieldset)[
|
263
|
+
XPath.attr(:disabled)
|
264
|
+
] + x.ancestor[
|
265
|
+
~x.self(:legend) |
|
266
|
+
x.preceding_sibling(:legend)
|
267
|
+
][
|
268
|
+
x.parent(:fieldset)[
|
269
|
+
x.attr(:disabled)
|
270
|
+
]
|
271
|
+
]
|
272
|
+
end.to_s.freeze
|
259
273
|
end
|
data/lib/capybara/result.rb
CHANGED
@@ -7,7 +7,7 @@ module Capybara
|
|
7
7
|
# A {Capybara::Result} represents a collection of {Capybara::Node::Element} on the page. It is possible to interact with this
|
8
8
|
# collection similar to an Array because it implements Enumerable and offers the following Array methods through delegation:
|
9
9
|
#
|
10
|
-
# * []
|
10
|
+
# * \[\]
|
11
11
|
# * each()
|
12
12
|
# * at()
|
13
13
|
# * size()
|
@@ -16,6 +16,8 @@ module Capybara
|
|
16
16
|
# * first()
|
17
17
|
# * last()
|
18
18
|
# * empty?()
|
19
|
+
# * values_at()
|
20
|
+
# * sample()
|
19
21
|
#
|
20
22
|
# @see Capybara::Node::Element
|
21
23
|
#
|
@@ -26,7 +28,8 @@ module Capybara
|
|
26
28
|
def initialize(elements, query)
|
27
29
|
@elements = elements
|
28
30
|
@result_cache = []
|
29
|
-
@
|
31
|
+
@filter_errors = []
|
32
|
+
@results_enum = lazy_select_elements { |node| query.matches_filters?(node, @filter_errors) }
|
30
33
|
@query = query
|
31
34
|
end
|
32
35
|
|
@@ -106,11 +109,13 @@ module Capybara
|
|
106
109
|
if count.zero?
|
107
110
|
message << ' but there were no matches'
|
108
111
|
else
|
109
|
-
message << ", found #{count} #{Capybara::Helpers.declension('match', 'matches', count)}: "
|
112
|
+
message << ", found #{count} #{Capybara::Helpers.declension('match', 'matches', count)}: " \
|
113
|
+
<< full_results.map(&:text).map(&:inspect).join(', ')
|
110
114
|
end
|
111
115
|
unless rest.empty?
|
112
116
|
elements = rest.map { |el| el.text rescue '<<ERROR>>' }.map(&:inspect).join(', ') # rubocop:disable Style/RescueModifier
|
113
|
-
message << '. Also found ' << elements << ', which matched the selector but not all filters.'
|
117
|
+
message << '. Also found ' << elements << ', which matched the selector but not all filters. '
|
118
|
+
message << @filter_errors.join('. ') if (rest.size == 1) && count.zero?
|
114
119
|
end
|
115
120
|
message
|
116
121
|
end
|
@@ -14,10 +14,10 @@ RSpec.configure do |config|
|
|
14
14
|
end
|
15
15
|
|
16
16
|
RSpec.configure do |config|
|
17
|
-
config.alias_example_group_to :feature, capybara_feature
|
18
|
-
config.alias_example_group_to :xfeature, capybara_feature
|
19
|
-
config.alias_example_group_to :ffeature, capybara_feature:
|
17
|
+
config.alias_example_group_to :feature, :capybara_feature, type: :feature
|
18
|
+
config.alias_example_group_to :xfeature, :capybara_feature, type: :feature, skip: 'Temporarily disabled with xfeature'
|
19
|
+
config.alias_example_group_to :ffeature, :capybara_feature, :focus, type: :feature
|
20
20
|
config.alias_example_to :scenario
|
21
21
|
config.alias_example_to :xscenario, skip: 'Temporarily disabled with xscenario'
|
22
|
-
config.alias_example_to :fscenario, focus
|
22
|
+
config.alias_example_to :fscenario, :focus
|
23
23
|
end
|
@@ -27,7 +27,9 @@ if RUBY_ENGINE == 'jruby'
|
|
27
27
|
|
28
28
|
def included(base)
|
29
29
|
warn 'including Capybara::DSL in the global scope is not recommended!' if base == Object
|
30
|
-
|
30
|
+
if defined?(::RSpec::Matchers) && base.include?(::RSpec::Matchers)
|
31
|
+
base.send(:include, ::Capybara::RSpecMatcherProxies)
|
32
|
+
end
|
31
33
|
super
|
32
34
|
end
|
33
35
|
end
|
@@ -1,315 +1,52 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'capybara/rspec/
|
3
|
+
require 'capybara/rspec/matchers/have_selector'
|
4
|
+
require 'capybara/rspec/matchers/match_selector'
|
5
|
+
require 'capybara/rspec/matchers/have_current_path'
|
6
|
+
require 'capybara/rspec/matchers/have_style'
|
7
|
+
require 'capybara/rspec/matchers/have_text'
|
8
|
+
require 'capybara/rspec/matchers/have_title'
|
9
|
+
require 'capybara/rspec/matchers/become_closed'
|
4
10
|
|
5
11
|
module Capybara
|
6
12
|
module RSpecMatchers
|
7
|
-
class Matcher
|
8
|
-
include ::Capybara::RSpecMatchers::Compound if defined?(::Capybara::RSpecMatchers::Compound)
|
9
|
-
|
10
|
-
attr_reader :failure_message, :failure_message_when_negated
|
11
|
-
|
12
|
-
def initialize(*args, &filter_block)
|
13
|
-
@args = args.dup
|
14
|
-
@filter_block = filter_block
|
15
|
-
end
|
16
|
-
|
17
|
-
def wrap(actual)
|
18
|
-
actual = actual.to_capybara_node if actual.respond_to?(:to_capybara_node)
|
19
|
-
@context_el = if actual.respond_to?(:has_selector?)
|
20
|
-
actual
|
21
|
-
else
|
22
|
-
Capybara.string(actual.to_s)
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
private
|
27
|
-
|
28
|
-
def session_query_args
|
29
|
-
if @args.last.is_a? Hash
|
30
|
-
@args.last[:session_options] = session_options
|
31
|
-
else
|
32
|
-
@args.push(session_options: session_options)
|
33
|
-
end
|
34
|
-
@args
|
35
|
-
end
|
36
|
-
|
37
|
-
def session_options
|
38
|
-
@context_el ||= nil
|
39
|
-
if @context_el.respond_to? :session_options
|
40
|
-
@context_el.session_options
|
41
|
-
elsif @context_el.respond_to? :current_scope
|
42
|
-
@context_el.current_scope.session_options
|
43
|
-
else
|
44
|
-
Capybara.session_options
|
45
|
-
end
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
class WrappedElementMatcher < Matcher
|
50
|
-
def matches?(actual)
|
51
|
-
element_matches?(wrap(actual))
|
52
|
-
rescue Capybara::ExpectationNotMet => err
|
53
|
-
@failure_message = err.message
|
54
|
-
false
|
55
|
-
end
|
56
|
-
|
57
|
-
def does_not_match?(actual)
|
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)
|
72
|
-
end
|
73
|
-
|
74
|
-
def description
|
75
|
-
"have #{query.description}"
|
76
|
-
end
|
77
|
-
|
78
|
-
def query
|
79
|
-
@query ||= Capybara::Queries::SelectorQuery.new(*session_query_args, &@filter_block)
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
class HaveAllSelectors < WrappedElementMatcher
|
84
|
-
def element_matches?(el)
|
85
|
-
el.assert_all_of_selectors(*@args, &@filter_block)
|
86
|
-
end
|
87
|
-
|
88
|
-
def does_not_match?(_actual)
|
89
|
-
raise ArgumentError, 'The have_all_selectors matcher does not support use with not_to/should_not'
|
90
|
-
end
|
91
|
-
|
92
|
-
def description
|
93
|
-
'have all selectors'
|
94
|
-
end
|
95
|
-
end
|
96
|
-
|
97
|
-
class HaveNoSelectors < WrappedElementMatcher
|
98
|
-
def element_matches?(el)
|
99
|
-
el.assert_none_of_selectors(*@args, &@filter_block)
|
100
|
-
end
|
101
|
-
|
102
|
-
def does_not_match?(_actual)
|
103
|
-
raise ArgumentError, 'The have_none_of_selectors matcher does not support use with not_to/should_not'
|
104
|
-
end
|
105
|
-
|
106
|
-
def description
|
107
|
-
'have no selectors'
|
108
|
-
end
|
109
|
-
end
|
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
|
-
|
125
|
-
class MatchSelector < HaveSelector
|
126
|
-
def element_matches?(el)
|
127
|
-
el.assert_matches_selector(*@args, &@filter_block)
|
128
|
-
end
|
129
|
-
|
130
|
-
def element_does_not_match?(el)
|
131
|
-
el.assert_not_matches_selector(*@args, &@filter_block)
|
132
|
-
end
|
133
|
-
|
134
|
-
def description
|
135
|
-
"match #{query.description}"
|
136
|
-
end
|
137
|
-
|
138
|
-
def query
|
139
|
-
@query ||= Capybara::Queries::MatchQuery.new(*session_query_args, &@filter_block)
|
140
|
-
end
|
141
|
-
end
|
142
|
-
|
143
|
-
class HaveText < WrappedElementMatcher
|
144
|
-
def element_matches?(el)
|
145
|
-
el.assert_text(*@args)
|
146
|
-
end
|
147
|
-
|
148
|
-
def element_does_not_match?(el)
|
149
|
-
el.assert_no_text(*@args)
|
150
|
-
end
|
151
|
-
|
152
|
-
def description
|
153
|
-
"text #{format(text)}"
|
154
|
-
end
|
155
|
-
|
156
|
-
def format(content)
|
157
|
-
content.inspect
|
158
|
-
end
|
159
|
-
|
160
|
-
private
|
161
|
-
|
162
|
-
def text
|
163
|
-
@args[0].is_a?(Symbol) ? @args[1] : @args[0]
|
164
|
-
end
|
165
|
-
end
|
166
|
-
|
167
|
-
class HaveTitle < WrappedElementMatcher
|
168
|
-
def element_matches?(el)
|
169
|
-
el.assert_title(*@args)
|
170
|
-
end
|
171
|
-
|
172
|
-
def element_does_not_match?(el)
|
173
|
-
el.assert_no_title(*@args)
|
174
|
-
end
|
175
|
-
|
176
|
-
def description
|
177
|
-
"have title #{title.inspect}"
|
178
|
-
end
|
179
|
-
|
180
|
-
private
|
181
|
-
|
182
|
-
def title
|
183
|
-
@args.first
|
184
|
-
end
|
185
|
-
end
|
186
|
-
|
187
|
-
class HaveCurrentPath < WrappedElementMatcher
|
188
|
-
def element_matches?(el)
|
189
|
-
el.assert_current_path(*@args)
|
190
|
-
end
|
191
|
-
|
192
|
-
def element_does_not_match?(el)
|
193
|
-
el.assert_no_current_path(*@args)
|
194
|
-
end
|
195
|
-
|
196
|
-
def description
|
197
|
-
"have current path #{current_path.inspect}"
|
198
|
-
end
|
199
|
-
|
200
|
-
private
|
201
|
-
|
202
|
-
def current_path
|
203
|
-
@args.first
|
204
|
-
end
|
205
|
-
end
|
206
|
-
|
207
|
-
class NegatedMatcher
|
208
|
-
include ::Capybara::RSpecMatchers::Compound if defined?(::Capybara::RSpecMatchers::Compound)
|
209
|
-
|
210
|
-
def initialize(matcher)
|
211
|
-
super()
|
212
|
-
@matcher = matcher
|
213
|
-
end
|
214
|
-
|
215
|
-
def matches?(actual)
|
216
|
-
@matcher.does_not_match?(actual)
|
217
|
-
end
|
218
|
-
|
219
|
-
def does_not_match?(actual)
|
220
|
-
@matcher.matches?(actual)
|
221
|
-
end
|
222
|
-
|
223
|
-
def description
|
224
|
-
"not #{@matcher.description}"
|
225
|
-
end
|
226
|
-
|
227
|
-
def failure_message
|
228
|
-
@matcher.failure_message_when_negated
|
229
|
-
end
|
230
|
-
|
231
|
-
def failure_message_when_negated
|
232
|
-
@matcher.failure_message
|
233
|
-
end
|
234
|
-
end
|
235
|
-
|
236
|
-
class HaveStyle < WrappedElementMatcher
|
237
|
-
def element_matches?(el)
|
238
|
-
el.assert_style(*@args)
|
239
|
-
end
|
240
|
-
|
241
|
-
def does_not_match?(_actual)
|
242
|
-
raise ArgumentError, 'The have_style matcher does not support use with not_to/should_not'
|
243
|
-
end
|
244
|
-
|
245
|
-
def description
|
246
|
-
'have style'
|
247
|
-
end
|
248
|
-
end
|
249
|
-
|
250
|
-
class BecomeClosed
|
251
|
-
def initialize(options)
|
252
|
-
@options = options
|
253
|
-
end
|
254
|
-
|
255
|
-
def matches?(window)
|
256
|
-
@window = window
|
257
|
-
@wait_time = Capybara::Queries::BaseQuery.wait(@options, window.session.config.default_max_wait_time)
|
258
|
-
timer = Capybara::Helpers.timer(expire_in: @wait_time)
|
259
|
-
while window.exists?
|
260
|
-
return false if timer.expired?
|
261
|
-
|
262
|
-
sleep 0.05
|
263
|
-
end
|
264
|
-
true
|
265
|
-
end
|
266
|
-
|
267
|
-
def failure_message
|
268
|
-
"expected #{@window.inspect} to become closed after #{@wait_time} seconds"
|
269
|
-
end
|
270
|
-
|
271
|
-
def failure_message_when_negated
|
272
|
-
"expected #{@window.inspect} not to become closed after #{@wait_time} seconds"
|
273
|
-
end
|
274
|
-
end
|
275
|
-
|
276
13
|
# RSpec matcher for whether the element(s) matching a given selector exist
|
277
14
|
# See {Capybara::Node::Matcher#assert_selector}
|
278
15
|
def have_selector(*args, &optional_filter_block)
|
279
|
-
HaveSelector.new(*args, &optional_filter_block)
|
16
|
+
Matchers::HaveSelector.new(*args, &optional_filter_block)
|
280
17
|
end
|
281
18
|
|
282
19
|
# RSpec matcher for whether the element(s) matching a group of selectors exist
|
283
20
|
# See {Capybara::Node::Matcher#assert_all_of_selectors}
|
284
21
|
def have_all_of_selectors(*args, &optional_filter_block)
|
285
|
-
HaveAllSelectors.new(*args, &optional_filter_block)
|
22
|
+
Matchers::HaveAllSelectors.new(*args, &optional_filter_block)
|
286
23
|
end
|
287
24
|
|
288
25
|
# RSpec matcher for whether no element(s) matching a group of selectors exist
|
289
26
|
# See {Capybara::Node::Matcher#assert_none_of_selectors}
|
290
27
|
def have_none_of_selectors(*args, &optional_filter_block)
|
291
|
-
HaveNoSelectors.new(*args, &optional_filter_block)
|
28
|
+
Matchers::HaveNoSelectors.new(*args, &optional_filter_block)
|
292
29
|
end
|
293
30
|
|
294
31
|
# RSpec matcher for whether the element(s) matching any of a group of selectors exist
|
295
32
|
# See {Capybara::Node::Matcher#assert_any_of_selectors}
|
296
33
|
def have_any_of_selectors(*args, &optional_filter_block)
|
297
|
-
HaveAnySelectors.new(*args, &optional_filter_block)
|
34
|
+
Matchers::HaveAnySelectors.new(*args, &optional_filter_block)
|
298
35
|
end
|
299
36
|
|
300
37
|
# RSpec matcher for whether the current element matches a given selector
|
301
38
|
# See {Capybara::Node::Matchers#assert_matches_selector}
|
302
39
|
def match_selector(*args, &optional_filter_block)
|
303
|
-
MatchSelector.new(*args, &optional_filter_block)
|
40
|
+
Matchers::MatchSelector.new(*args, &optional_filter_block)
|
304
41
|
end
|
305
42
|
|
306
43
|
%i[css xpath].each do |selector|
|
307
44
|
define_method "have_#{selector}" do |expr, **options, &optional_filter_block|
|
308
|
-
HaveSelector.new(selector, expr, options, &optional_filter_block)
|
45
|
+
Matchers::HaveSelector.new(selector, expr, options, &optional_filter_block)
|
309
46
|
end
|
310
47
|
|
311
48
|
define_method "match_#{selector}" do |expr, **options, &optional_filter_block|
|
312
|
-
MatchSelector.new(selector, expr, options, &optional_filter_block)
|
49
|
+
Matchers::MatchSelector.new(selector, expr, options, &optional_filter_block)
|
313
50
|
end
|
314
51
|
end
|
315
52
|
|
@@ -331,7 +68,7 @@ module Capybara
|
|
331
68
|
|
332
69
|
%i[link button field select table].each do |selector|
|
333
70
|
define_method "have_#{selector}" do |locator = nil, **options, &optional_filter_block|
|
334
|
-
HaveSelector.new(selector, locator, options, &optional_filter_block)
|
71
|
+
Matchers::HaveSelector.new(selector, locator, options, &optional_filter_block)
|
335
72
|
end
|
336
73
|
end
|
337
74
|
|
@@ -357,7 +94,7 @@ module Capybara
|
|
357
94
|
|
358
95
|
%i[checked unchecked].each do |state|
|
359
96
|
define_method "have_#{state}_field" do |locator = nil, **options, &optional_filter_block|
|
360
|
-
HaveSelector.new(:field, locator, options.merge(state => true), &optional_filter_block)
|
97
|
+
Matchers::HaveSelector.new(:field, locator, options.merge(state => true), &optional_filter_block)
|
361
98
|
end
|
362
99
|
end
|
363
100
|
|
@@ -372,36 +109,37 @@ module Capybara
|
|
372
109
|
# RSpec matcher for text content
|
373
110
|
# See {Capybara::SessionMatchers#assert_text}
|
374
111
|
def have_text(*args)
|
375
|
-
HaveText.new(*args)
|
112
|
+
Matchers::HaveText.new(*args)
|
376
113
|
end
|
377
114
|
alias_method :have_content, :have_text
|
378
115
|
|
379
116
|
def have_title(title, **options)
|
380
|
-
HaveTitle.new(title, options)
|
117
|
+
Matchers::HaveTitle.new(title, options)
|
381
118
|
end
|
382
119
|
|
383
120
|
# RSpec matcher for the current path
|
384
121
|
# See {Capybara::SessionMatchers#assert_current_path}
|
385
122
|
def have_current_path(path, **options)
|
386
|
-
HaveCurrentPath.new(path, options)
|
123
|
+
Matchers::HaveCurrentPath.new(path, options)
|
387
124
|
end
|
388
125
|
|
389
126
|
# RSpec matcher for element style
|
390
127
|
# See {Capybara::Node::Matchers#has_style?}
|
391
128
|
def have_style(styles, **options)
|
392
|
-
HaveStyle.new(styles, options)
|
129
|
+
Matchers::HaveStyle.new(styles, options)
|
393
130
|
end
|
394
131
|
|
395
|
-
%w[selector css xpath text title current_path link button
|
132
|
+
%w[selector css xpath text title current_path link button
|
133
|
+
field checked_field unchecked_field select table].each do |matcher_type|
|
396
134
|
define_method "have_no_#{matcher_type}" do |*args, &optional_filter_block|
|
397
|
-
NegatedMatcher.new(send("have_#{matcher_type}", *args, &optional_filter_block))
|
135
|
+
Matchers::NegatedMatcher.new(send("have_#{matcher_type}", *args, &optional_filter_block))
|
398
136
|
end
|
399
137
|
end
|
400
138
|
alias_method :have_no_content, :have_no_text
|
401
139
|
|
402
140
|
%w[selector css xpath].each do |matcher_type|
|
403
141
|
define_method "not_match_#{matcher_type}" do |*args, &optional_filter_block|
|
404
|
-
NegatedMatcher.new(send("match_#{matcher_type}", *args, &optional_filter_block))
|
142
|
+
Matchers::NegatedMatcher.new(send("match_#{matcher_type}", *args, &optional_filter_block))
|
405
143
|
end
|
406
144
|
end
|
407
145
|
|
@@ -412,7 +150,7 @@ module Capybara
|
|
412
150
|
# @param options [Hash] optional param
|
413
151
|
# @option options [Numeric] :wait (Capybara.default_max_wait_time) Maximum wait time
|
414
152
|
def become_closed(**options)
|
415
|
-
BecomeClosed.new(options)
|
153
|
+
Matchers::BecomeClosed.new(options)
|
416
154
|
end
|
417
155
|
end
|
418
156
|
end
|