capybara 2.3.0 → 2.4.0

Sign up to get free protection for your applications and to get access to all the features.
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