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.
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