capybara 3.9.0 → 3.10.0

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