capybara 2.3.0 → 2.4.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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +21 -0
  3. data/README.md +50 -12
  4. data/lib/capybara.rb +8 -1
  5. data/lib/capybara/driver/base.rb +28 -0
  6. data/lib/capybara/driver/node.rb +3 -2
  7. data/lib/capybara/helpers.rb +2 -3
  8. data/lib/capybara/node/actions.rb +5 -2
  9. data/lib/capybara/node/base.rb +10 -0
  10. data/lib/capybara/node/document.rb +2 -0
  11. data/lib/capybara/node/document_matchers.rb +68 -0
  12. data/lib/capybara/node/element.rb +17 -2
  13. data/lib/capybara/node/finders.rb +5 -20
  14. data/lib/capybara/node/matchers.rb +101 -71
  15. data/lib/capybara/node/simple.rb +9 -15
  16. data/lib/capybara/queries/base_query.rb +29 -0
  17. data/lib/capybara/queries/text_query.rb +56 -0
  18. data/lib/capybara/queries/title_query.rb +40 -0
  19. data/lib/capybara/query.rb +30 -20
  20. data/lib/capybara/rack_test/node.rb +11 -3
  21. data/lib/capybara/result.rb +1 -1
  22. data/lib/capybara/rspec/features.rb +38 -21
  23. data/lib/capybara/rspec/matchers.rb +53 -38
  24. data/lib/capybara/selector.rb +68 -14
  25. data/lib/capybara/selenium/driver.rb +54 -6
  26. data/lib/capybara/selenium/node.rb +4 -2
  27. data/lib/capybara/session.rb +109 -35
  28. data/lib/capybara/spec/public/test.js +34 -1
  29. data/lib/capybara/spec/session/accept_alert_spec.rb +57 -0
  30. data/lib/capybara/spec/session/accept_confirm_spec.rb +19 -0
  31. data/lib/capybara/spec/session/accept_prompt_spec.rb +49 -0
  32. data/lib/capybara/spec/session/assert_text.rb +195 -0
  33. data/lib/capybara/spec/session/assert_title.rb +69 -0
  34. data/lib/capybara/spec/session/dismiss_confirm_spec.rb +35 -0
  35. data/lib/capybara/spec/session/dismiss_prompt_spec.rb +19 -0
  36. data/lib/capybara/spec/session/find_field_spec.rb +6 -0
  37. data/lib/capybara/spec/session/has_text_spec.rb +1 -1
  38. data/lib/capybara/spec/session/node_spec.rb +16 -1
  39. data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +1 -1
  40. data/lib/capybara/spec/session/visit_spec.rb +5 -0
  41. data/lib/capybara/spec/views/with_html.erb +4 -0
  42. data/lib/capybara/spec/views/with_js.erb +17 -0
  43. data/lib/capybara/version.rb +1 -1
  44. data/spec/dsl_spec.rb +3 -1
  45. data/spec/rack_test_spec.rb +12 -1
  46. data/spec/rspec/features_spec.rb +1 -1
  47. data/spec/rspec/matchers_spec.rb +113 -20
  48. data/spec/selenium_spec.rb +10 -1
  49. metadata +13 -2
@@ -91,8 +91,11 @@ module Capybara
91
91
  def assert_selector(*args)
92
92
  query = Capybara::Query.new(*args)
93
93
  synchronize(query.wait) do
94
- result = all(*args)
95
- raise Capybara::ExpectationNotMet, result.failure_message if result.size == 0 && !Capybara::Helpers.expects_none?(query.options)
94
+ result = query.resolve_for(self)
95
+ matches_count = Capybara::Helpers.matches_count?(result.size, query.options)
96
+ unless matches_count && ((result.size > 0) || Capybara::Helpers.expects_none?(query.options))
97
+ raise Capybara::ExpectationNotMet, result.failure_message
98
+ end
96
99
  end
97
100
  return true
98
101
  end
@@ -116,19 +119,14 @@ module Capybara
116
119
  def assert_no_selector(*args)
117
120
  query = Capybara::Query.new(*args)
118
121
  synchronize(query.wait) do
119
- begin
120
- result = all(*args)
121
- rescue Capybara::ExpectationNotMet => e
122
- return true
123
- else
124
- if result.size > 0 || (result.size == 0 && Capybara::Helpers.expects_none?(query.options))
125
- raise(Capybara::ExpectationNotMet, result.negative_failure_message)
126
- end
122
+ result = query.resolve_for(self)
123
+ matches_count = Capybara::Helpers.matches_count?(result.size, query.options)
124
+ if matches_count && ((result.size > 0) || Capybara::Helpers.expects_none?(query.options))
125
+ raise Capybara::ExpectationNotMet, result.negative_failure_message
127
126
  end
128
127
  end
129
128
  return true
130
129
  end
131
-
132
130
  alias_method :refute_selector, :assert_no_selector
133
131
 
134
132
  ##
@@ -215,58 +213,6 @@ module Capybara
215
213
  has_no_selector?(:css, path, options)
216
214
  end
217
215
 
218
- ##
219
- #
220
- # Checks if the page or current node has the given text content,
221
- # ignoring any HTML tags and normalizing whitespace.
222
- #
223
- # By default it will check if the text occurs at least once,
224
- # but a different number can be specified.
225
- #
226
- # page.has_text?('lorem ipsum', between: 2..4)
227
- #
228
- # This will check if the text occurs from 2 to 4 times.
229
- #
230
- # @overload has_text?([type], text, [options])
231
- # @param [:all, :visible] type Whether to check for only visible or all text
232
- # @param [String, Regexp] text The text/regexp to check for
233
- # @param [Hash] options additional options
234
- # @option options [Integer] :count (nil) Number of times the text should occur
235
- # @option options [Integer] :minimum (nil) Minimum number of times the text should occur
236
- # @option options [Integer] :maximum (nil) Maximum number of times the text should occur
237
- # @option options [Range] :between (nil) Range of times that should contain number of times text occurs
238
- # @return [Boolean] Whether it exists
239
- #
240
- def has_text?(*args)
241
- query = Capybara::Query.new(*args)
242
- synchronize(query.wait) do
243
- raise ExpectationNotMet unless text_found?(*args)
244
- end
245
- return true
246
- rescue Capybara::ExpectationNotMet
247
- return false
248
- end
249
- alias_method :has_content?, :has_text?
250
-
251
- ##
252
- #
253
- # Checks if the page or current node does not have the given text
254
- # content, ignoring any HTML tags and normalizing whitespace.
255
- #
256
- # @param (see #has_text?)
257
- # @return [Boolean] Whether it doesn't exist
258
- #
259
- def has_no_text?(*args)
260
- query = Capybara::Query.new(*args)
261
- synchronize(query.wait) do
262
- raise ExpectationNotMet if text_found?(*args)
263
- end
264
- return true
265
- rescue Capybara::ExpectationNotMet
266
- return false
267
- end
268
- alias_method :has_no_content?, :has_no_text?
269
-
270
216
  ##
271
217
  #
272
218
  # Checks if the page or current node has a link with the given
@@ -479,18 +425,102 @@ module Capybara
479
425
  has_no_selector?(:table, locator, options)
480
426
  end
481
427
 
482
- def ==(other)
483
- self.eql?(other) or (other.respond_to?(:base) and base == other.base)
428
+ ##
429
+ # Asserts that the page or current node has the given text content,
430
+ # ignoring any HTML tags.
431
+ #
432
+ # @!macro text_query_params
433
+ # @overload $0(type, text, options = {})
434
+ # @param [:all, :visible] type Whether to check for only visible or all text
435
+ # @param [String, Regexp] text The string/regexp to check for. If it's a string, text is expected to include it. If it's a regexp, text is expected to match it.
436
+ # @option options [Integer] :count (nil) Number of times the text is expected to occur
437
+ # @option options [Integer] :minimum (nil) Minimum number of times the text is expected to occur
438
+ # @option options [Integer] :maximum (nil) Maximum number of times the text is expected to occur
439
+ # @option options [Range] :between (nil) Range of times that is expected to contain number of times text occurs
440
+ # @option options [Numeric] :wait (Capybara.default_wait_time) Time that Capybara will wait for text to eq/match given string/regexp argument
441
+ # @overload $0(text, options = {})
442
+ # @param [String, Regexp] text The string/regexp to check for. If it's a string, text is expected to include it. If it's a regexp, text is expected to match it.
443
+ # @option options [Integer] :count (nil) Number of times the text is expected to occur
444
+ # @option options [Integer] :minimum (nil) Minimum number of times the text is expected to occur
445
+ # @option options [Integer] :maximum (nil) Maximum number of times the text is expected to occur
446
+ # @option options [Range] :between (nil) Range of times that is expected to contain number of times text occurs
447
+ # @option options [Numeric] :wait (Capybara.default_wait_time) Time that Capybara will wait for text to eq/match given string/regexp argument
448
+ # @raise [Capybara::ExpectationNotMet] if the assertion hasn't succeeded during wait time
449
+ # @return [true]
450
+ #
451
+ def assert_text(*args)
452
+ query = Capybara::Queries::TextQuery.new(*args)
453
+ synchronize(query.wait) do
454
+ count = query.resolve_for(self)
455
+ matches_count = Capybara::Helpers.matches_count?(count, query.options)
456
+ unless matches_count && ((count > 0) || Capybara::Helpers.expects_none?(query.options))
457
+ raise Capybara::ExpectationNotMet, query.failure_message
458
+ end
459
+ end
460
+ return true
484
461
  end
485
462
 
486
- private
463
+ ##
464
+ # Asserts that the page or current node doesn't have the given text content,
465
+ # ignoring any HTML tags.
466
+ #
467
+ # @macro text_query_params
468
+ # @raise [Capybara::ExpectationNotMet] if the assertion hasn't succeeded during wait time
469
+ # @return [true]
470
+ #
471
+ def assert_no_text(*args)
472
+ query = Capybara::Queries::TextQuery.new(*args)
473
+ synchronize(query.wait) do
474
+ count = query.resolve_for(self)
475
+ matches_count = Capybara::Helpers.matches_count?(count, query.options)
476
+ if matches_count && ((count > 0) || Capybara::Helpers.expects_none?(query.options))
477
+ raise Capybara::ExpectationNotMet, query.negative_failure_message
478
+ end
479
+ end
480
+ return true
481
+ end
487
482
 
488
- def text_found?(*args)
489
- type = args.shift if args.first.is_a?(Symbol) or args.first.nil?
490
- content, options = args
491
- count = Capybara::Helpers.normalize_whitespace(text(type)).scan(Capybara::Helpers.to_regexp(content)).count
483
+ ##
484
+ # Checks if the page or current node has the given text content,
485
+ # ignoring any HTML tags.
486
+ #
487
+ # Whitespaces are normalized in both node's text and passed text parameter.
488
+ # Note that whitespace isn't normalized in passed regexp as normalizing whitespace
489
+ # in regexp isn't easy and doesn't seem to be worth it.
490
+ #
491
+ # By default it will check if the text occurs at least once,
492
+ # but a different number can be specified.
493
+ #
494
+ # page.has_text?('lorem ipsum', between: 2..4)
495
+ #
496
+ # This will check if the text occurs from 2 to 4 times.
497
+ #
498
+ # @macro text_query_params
499
+ # @return [Boolean] Whether it exists
500
+ #
501
+ def has_text?(*args)
502
+ assert_text(*args)
503
+ rescue Capybara::ExpectationNotMet
504
+ return false
505
+ end
506
+ alias_method :has_content?, :has_text?
492
507
 
493
- Capybara::Helpers.matches_count?(count, {:minimum=>1}.merge(options || {}))
508
+ ##
509
+ # Checks if the page or current node does not have the given text
510
+ # content, ignoring any HTML tags and normalizing whitespace.
511
+ #
512
+ # @macro text_query_params
513
+ # @return [Boolean] Whether it doesn't exist
514
+ #
515
+ def has_no_text?(*args)
516
+ assert_no_text(*args)
517
+ rescue Capybara::ExpectationNotMet
518
+ return false
519
+ end
520
+ alias_method :has_no_content?, :has_no_text?
521
+
522
+ def ==(other)
523
+ self.eql?(other) || (other.respond_to?(:base) && base == other.base)
494
524
  end
495
525
  end
496
526
  end
@@ -14,6 +14,7 @@ module Capybara
14
14
  class Simple
15
15
  include Capybara::Node::Finders
16
16
  include Capybara::Node::Matchers
17
+ include Capybara::Node::DocumentMatchers
17
18
 
18
19
  attr_reader :native
19
20
 
@@ -148,25 +149,18 @@ module Capybara
148
149
  native.xpath("//title").first.text
149
150
  end
150
151
 
151
- def has_title?(content)
152
- title.match(Capybara::Helpers.to_regexp(content))
152
+ def inspect
153
+ %(#<Capybara::Node::Simple tag="#{tag_name}" path="#{path}">)
153
154
  end
154
155
 
155
- def has_no_title?(content)
156
- not has_title?(content)
156
+ # @api private
157
+ def find_css(css)
158
+ native.css(css)
157
159
  end
158
160
 
159
- private
160
-
161
- def resolve_query(query, exact=nil)
162
- elements = if query.selector.format == :css
163
- native.css(query.css)
164
- else
165
- native.xpath(query.xpath(exact))
166
- end.map do |node|
167
- self.class.new(node)
168
- end
169
- Capybara::Result.new(elements, query)
161
+ # @api private
162
+ def find_xpath(xpath)
163
+ native.xpath(xpath)
170
164
  end
171
165
  end
172
166
  end
@@ -0,0 +1,29 @@
1
+ module Capybara
2
+ # @api private
3
+ module Queries
4
+ class BaseQuery
5
+ COUNT_KEYS = [:count, :minimum, :maximum, :between]
6
+
7
+ attr_reader :options
8
+
9
+ def wait
10
+ if @options.has_key?(:wait)
11
+ @options[:wait] || 0
12
+ else
13
+ Capybara.default_wait_time
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+ def assert_valid_keys
20
+ invalid_keys = @options.keys - valid_keys
21
+ unless invalid_keys.empty?
22
+ invalid_names = invalid_keys.map(&:inspect).join(", ")
23
+ valid_names = valid_keys.map(&:inspect).join(", ")
24
+ raise ArgumentError, "invalid keys #{invalid_names}, should be one of #{valid_names}"
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,56 @@
1
+ module Capybara
2
+ # @api private
3
+ module Queries
4
+ class TextQuery < BaseQuery
5
+ def initialize(*args)
6
+ @type = args.shift if args.first.is_a?(Symbol) || args.first.nil?
7
+ @expected_text, @options = args
8
+ unless @expected_text.is_a?(Regexp)
9
+ @expected_text = Capybara::Helpers.normalize_whitespace(@expected_text)
10
+ end
11
+ @search_regexp = Capybara::Helpers.to_regexp(@expected_text)
12
+ @options ||= {}
13
+ assert_valid_keys
14
+
15
+ # this is needed to not break existing tests that may use keys supported by `Query` but not supported by `TextQuery`
16
+ # can be removed in next minor version (> 2.4)
17
+ invalid_keys = @options.keys - (COUNT_KEYS + [:wait])
18
+ unless invalid_keys.empty?
19
+ invalid_names = invalid_keys.map(&:inspect).join(", ")
20
+ valid_names = valid_keys.map(&:inspect).join(", ")
21
+ warn "invalid keys #{invalid_names}, should be one of #{valid_names}"
22
+ end
23
+ end
24
+
25
+ def resolve_for(node)
26
+ @actual_text = Capybara::Helpers.normalize_whitespace(node.text(@type))
27
+ @count = @actual_text.scan(@search_regexp).size
28
+ end
29
+
30
+ def failure_message
31
+ description =
32
+ if @expected_text.is_a?(Regexp)
33
+ "text matching #{@expected_text.inspect}"
34
+ else
35
+ "text #{@expected_text.inspect}"
36
+ end
37
+
38
+ message = Capybara::Helpers.failure_message(description, @options)
39
+ unless (COUNT_KEYS & @options.keys).empty?
40
+ message << " but found #{@count} #{Capybara::Helpers.declension('time', 'times', @count)}"
41
+ end
42
+ message << " in #{@actual_text.inspect}"
43
+ end
44
+
45
+ def negative_failure_message
46
+ failure_message.sub(/(to find)/, 'not \1')
47
+ end
48
+
49
+ private
50
+
51
+ def valid_keys
52
+ Capybara::Query::VALID_KEYS # can be changed to COUNT_KEYS + [:wait] in next minor version (> 2.4)
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,40 @@
1
+ module Capybara
2
+ # @api private
3
+ module Queries
4
+ class TitleQuery < BaseQuery
5
+ def initialize(expected_title, options = {})
6
+ @expected_title = expected_title
7
+ @options = options
8
+ unless @expected_title.is_a?(Regexp)
9
+ @expected_title = Capybara::Helpers.normalize_whitespace(@expected_title)
10
+ end
11
+ @search_regexp = Capybara::Helpers.to_regexp(@expected_title)
12
+ assert_valid_keys
13
+ end
14
+
15
+ def resolves_for?(node)
16
+ @actual_title = node.title
17
+ @actual_title.match(@search_regexp)
18
+ end
19
+
20
+ def failure_message
21
+ failure_message_helper
22
+ end
23
+
24
+ def negative_failure_message
25
+ failure_message_helper(' not')
26
+ end
27
+
28
+ private
29
+
30
+ def failure_message_helper(negated = '')
31
+ verb = (@expected_title.is_a?(Regexp))? 'match' : 'include'
32
+ "expected #{@actual_title.inspect}#{negated} to #{verb} #{@expected_title.inspect}"
33
+ end
34
+
35
+ def valid_keys
36
+ [:wait]
37
+ end
38
+ end
39
+ end
40
+ end
@@ -1,5 +1,7 @@
1
1
  module Capybara
2
- class Query
2
+ # @deprecated This class and its methods are not supposed to be used by users of Capybara's public API.
3
+ # It may be removed in future versions of Capybara.
4
+ class Query < Queries::BaseQuery
3
5
  attr_accessor :selector, :locator, :options, :expression, :find, :negative
4
6
 
5
7
  VALID_KEYS = [:text, :visible, :between, :count, :maximum, :minimum, :exact, :match, :wait]
@@ -23,7 +25,7 @@ module Capybara
23
25
  end
24
26
 
25
27
  @expression = @selector.call(@locator)
26
- assert_valid_keys!
28
+ assert_valid_keys
27
29
  end
28
30
 
29
31
  def name; selector.name; end
@@ -32,7 +34,7 @@ module Capybara
32
34
  def description
33
35
  @description = "#{label} #{locator.inspect}"
34
36
  @description << " with text #{options[:text].inspect}" if options[:text]
35
- @description << " with value #{options[:with].inspect}" if options[:with]
37
+ @description << selector.description(options)
36
38
  @description
37
39
  end
38
40
 
@@ -70,14 +72,6 @@ module Capybara
70
72
  end
71
73
  end
72
74
 
73
- def wait
74
- if options.has_key?(:wait)
75
- @options[:wait] or 0
76
- else
77
- Capybara.default_wait_time
78
- end
79
- end
80
-
81
75
  def exact?
82
76
  if options.has_key?(:exact)
83
77
  @options[:exact]
@@ -107,16 +101,32 @@ module Capybara
107
101
  @expression
108
102
  end
109
103
 
110
- private
111
-
112
- def assert_valid_keys!
113
- valid_keys = VALID_KEYS + @selector.custom_filters.keys
114
- invalid_keys = @options.keys - valid_keys
115
- unless invalid_keys.empty?
116
- invalid_names = invalid_keys.map(&:inspect).join(", ")
117
- valid_names = valid_keys.map(&:inspect).join(", ")
118
- raise ArgumentError, "invalid keys #{invalid_names}, should be one of #{valid_names}"
104
+ # @api private
105
+ def resolve_for(node, exact = nil)
106
+ node.synchronize do
107
+ children = if selector.format == :css
108
+ node.find_css(self.css)
109
+ else
110
+ node.find_xpath(self.xpath(exact))
111
+ end.map do |child|
112
+ if node.is_a?(Capybara::Node::Base)
113
+ Capybara::Node::Element.new(node.session, child, node, self)
114
+ else
115
+ Capybara::Node::Simple.new(child)
116
+ end
117
+ end
118
+ Capybara::Result.new(children, self)
119
119
  end
120
+ end
121
+
122
+ private
123
+
124
+ def valid_keys
125
+ COUNT_KEYS + [:text, :visible, :exact, :match, :wait] + @selector.custom_filters.keys
126
+ end
127
+
128
+ def assert_valid_keys
129
+ super
120
130
  unless VALID_MATCH.include?(match)
121
131
  raise ArgumentError, "invalid option #{match.inspect} for :match, should be one of #{VALID_MATCH.map(&:inspect).join(", ")}"
122
132
  end