capybara 3.10.1 → 3.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +13 -0
  3. data/README.md +2 -3
  4. data/lib/capybara.rb +16 -6
  5. data/lib/capybara/minitest.rb +8 -9
  6. data/lib/capybara/node/actions.rb +31 -28
  7. data/lib/capybara/node/base.rb +2 -1
  8. data/lib/capybara/node/document_matchers.rb +6 -2
  9. data/lib/capybara/node/element.rb +10 -10
  10. data/lib/capybara/node/finders.rb +13 -14
  11. data/lib/capybara/node/matchers.rb +1 -3
  12. data/lib/capybara/node/simple.rb +10 -2
  13. data/lib/capybara/queries/base_query.rb +7 -3
  14. data/lib/capybara/queries/selector_query.rb +60 -34
  15. data/lib/capybara/queries/style_query.rb +5 -1
  16. data/lib/capybara/queries/text_query.rb +2 -2
  17. data/lib/capybara/queries/title_query.rb +1 -1
  18. data/lib/capybara/rack_test/node.rb +16 -2
  19. data/lib/capybara/result.rb +9 -4
  20. data/lib/capybara/rspec/features.rb +4 -4
  21. data/lib/capybara/rspec/matcher_proxies.rb +3 -1
  22. data/lib/capybara/rspec/matchers.rb +25 -287
  23. data/lib/capybara/rspec/matchers/base.rb +98 -0
  24. data/lib/capybara/rspec/matchers/become_closed.rb +33 -0
  25. data/lib/capybara/rspec/matchers/compound.rb +88 -0
  26. data/lib/capybara/rspec/matchers/have_current_path.rb +29 -0
  27. data/lib/capybara/rspec/matchers/have_selector.rb +69 -0
  28. data/lib/capybara/rspec/matchers/have_style.rb +23 -0
  29. data/lib/capybara/rspec/matchers/have_text.rb +33 -0
  30. data/lib/capybara/rspec/matchers/have_title.rb +29 -0
  31. data/lib/capybara/rspec/matchers/match_selector.rb +27 -0
  32. data/lib/capybara/selector.rb +48 -20
  33. data/lib/capybara/selector/builders/xpath_builder.rb +3 -3
  34. data/lib/capybara/selector/css.rb +5 -5
  35. data/lib/capybara/selector/filters/base.rb +11 -3
  36. data/lib/capybara/selector/filters/expression_filter.rb +3 -3
  37. data/lib/capybara/selector/filters/node_filter.rb +16 -2
  38. data/lib/capybara/selector/regexp_disassembler.rb +116 -17
  39. data/lib/capybara/selector/selector.rb +52 -26
  40. data/lib/capybara/selenium/driver.rb +6 -2
  41. data/lib/capybara/selenium/node.rb +15 -14
  42. data/lib/capybara/selenium/nodes/marionette_node.rb +19 -5
  43. data/lib/capybara/selenium/patches/pause_duration_fix.rb +1 -3
  44. data/lib/capybara/server.rb +6 -1
  45. data/lib/capybara/server/animation_disabler.rb +1 -1
  46. data/lib/capybara/session.rb +4 -2
  47. data/lib/capybara/session/matchers.rb +7 -3
  48. data/lib/capybara/spec/public/test.js +5 -5
  49. data/lib/capybara/spec/session/all_spec.rb +5 -0
  50. data/lib/capybara/spec/session/has_css_spec.rb +4 -4
  51. data/lib/capybara/spec/session/has_field_spec.rb +17 -0
  52. data/lib/capybara/spec/session/node_spec.rb +45 -4
  53. data/lib/capybara/spec/spec_helper.rb +6 -1
  54. data/lib/capybara/spec/views/frame_child.erb +1 -1
  55. data/lib/capybara/spec/views/obscured.erb +44 -0
  56. data/lib/capybara/spec/views/with_html.erb +1 -1
  57. data/lib/capybara/version.rb +1 -1
  58. data/spec/rack_test_spec.rb +15 -0
  59. data/spec/regexp_dissassembler_spec.rb +88 -8
  60. data/spec/selector_spec.rb +3 -0
  61. data/spec/selenium_spec_chrome.rb +9 -15
  62. data/spec/selenium_spec_chrome_remote.rb +3 -2
  63. data/spec/selenium_spec_firefox_remote.rb +6 -2
  64. metadata +54 -3
  65. 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
 
@@ -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("boolean(./ancestor-or-self::*[contains(@style, 'display:none') or contains(@style, 'display: none') or @hidden or name()='script' or name()='head'])")
107
+ !find_xpath(VISIBILITY_XPATH)
108
108
  else
109
109
  # No need for an xpath if only checking the current element
110
- !(native.has_attribute?('hidden') || (native[:style] =~ /display:\s?none/) || %w[script head].include?(tag_name))
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 << " #{count} #{Capybara::Helpers.declension('time', 'times', count)}"
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 #{maximum} #{Capybara::Helpers.declension('time', 'times', maximum)}"
84
+ message << " at most #{occurrences maximum}"
85
85
  elsif minimum
86
- message << " at least #{minimum} #{Capybara::Helpers.declension('time', 'times', minimum)}"
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
- @expression = selector.call(@locator, @options.merge(selector_config: { enable_aria_label: enable_aria_label, test_id: test_id }))
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(applied = false)
38
+ def description(only_applied = false)
38
39
  desc = +''
39
- if !applied || applied_filters
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 !applied || applied_filters
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: !applied || (applied_filters == :node), **options)
51
- desc << ' that also matches the custom filter block' if @filter_block && (!applied || (applied_filters == :node))
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
- @applied_filters ||= :system
67
+ applied_filters << :system
64
68
  return false unless matches_system_filters?(node)
65
69
 
66
- @applied_filters = :node
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) { @selector.default_visibility(session_options.ignore_hidden_elements, options) })
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
- @applied_filters = false
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 ||= false
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
- node_filters.all? do |filter_name, filter|
160
- if filter.matcher?
161
- unapplied_options.select { |option_name| filter.handles_option?(option_name) }.all? do |option_name|
162
- unapplied_options.delete(option_name)
163
- filter.matches?(node, option_name, options[option_name])
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
- ::Capybara::Selector::FilterSet.all[options[:filter_set]].node_filters
204
+ filter_set(options[:filter_set])
189
205
  else
190
- @selector.node_filters
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 ::Capybara::Selector::FilterSet.all[options[:filter_set]].expression_filters if options.key?(:filter_set)
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 { |option_name| ef.handles_option?(option_name) }.inject(expr) do |memo, option_name|
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.each_with_object({}) { |(style, value), str_keys| str_keys[style.to_s] = value }
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 #{insensitive_count} #{Capybara::Helpers.declension('time', 'times', insensitive_count)} using a case insensitive search"
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 #{invisible_count} #{Capybara::Helpers.declension('time', 'times', invisible_count)} including non-visible text"
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 = Capybara::Helpers.to_regexp(@expected_title, all_whitespace: true, exact: options.fetch(:exact, false))
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('parent::*[self::optgroup or self::select or self::datalist]')[0].disabled?
99
+ find_xpath(OPTION_OWNER_XPATH)[0].disabled?
100
100
  else
101
- !find_xpath('parent::fieldset[@disabled] | ancestor::*[not(self::legend) or preceding-sibling::legend][parent::fieldset[@disabled]]').empty?
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
@@ -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
- @results_enum = lazy_select_elements { |node| query.matches_filters?(node) }
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)}: " << full_results.map(&:text).map(&:inspect).join(', ')
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: true, type: :feature
18
- config.alias_example_group_to :xfeature, capybara_feature: true, type: :feature, skip: 'Temporarily disabled with xfeature'
19
- config.alias_example_group_to :ffeature, capybara_feature: true, type: :feature, focus: true
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: true
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
- base.send(:include, ::Capybara::RSpecMatcherProxies) if defined?(::RSpec::Matchers) && base.include?(::RSpec::Matchers)
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/compound'
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 field checked_field unchecked_field select table].each do |matcher_type|
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