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.
- checksums.yaml +4 -4
- data/History.md +21 -0
- data/README.md +50 -12
- data/lib/capybara.rb +8 -1
- data/lib/capybara/driver/base.rb +28 -0
- data/lib/capybara/driver/node.rb +3 -2
- data/lib/capybara/helpers.rb +2 -3
- data/lib/capybara/node/actions.rb +5 -2
- data/lib/capybara/node/base.rb +10 -0
- data/lib/capybara/node/document.rb +2 -0
- data/lib/capybara/node/document_matchers.rb +68 -0
- data/lib/capybara/node/element.rb +17 -2
- data/lib/capybara/node/finders.rb +5 -20
- data/lib/capybara/node/matchers.rb +101 -71
- data/lib/capybara/node/simple.rb +9 -15
- data/lib/capybara/queries/base_query.rb +29 -0
- data/lib/capybara/queries/text_query.rb +56 -0
- data/lib/capybara/queries/title_query.rb +40 -0
- data/lib/capybara/query.rb +30 -20
- data/lib/capybara/rack_test/node.rb +11 -3
- data/lib/capybara/result.rb +1 -1
- data/lib/capybara/rspec/features.rb +38 -21
- data/lib/capybara/rspec/matchers.rb +53 -38
- data/lib/capybara/selector.rb +68 -14
- data/lib/capybara/selenium/driver.rb +54 -6
- data/lib/capybara/selenium/node.rb +4 -2
- data/lib/capybara/session.rb +109 -35
- data/lib/capybara/spec/public/test.js +34 -1
- data/lib/capybara/spec/session/accept_alert_spec.rb +57 -0
- data/lib/capybara/spec/session/accept_confirm_spec.rb +19 -0
- data/lib/capybara/spec/session/accept_prompt_spec.rb +49 -0
- data/lib/capybara/spec/session/assert_text.rb +195 -0
- data/lib/capybara/spec/session/assert_title.rb +69 -0
- data/lib/capybara/spec/session/dismiss_confirm_spec.rb +35 -0
- data/lib/capybara/spec/session/dismiss_prompt_spec.rb +19 -0
- data/lib/capybara/spec/session/find_field_spec.rb +6 -0
- data/lib/capybara/spec/session/has_text_spec.rb +1 -1
- data/lib/capybara/spec/session/node_spec.rb +16 -1
- data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +1 -1
- data/lib/capybara/spec/session/visit_spec.rb +5 -0
- data/lib/capybara/spec/views/with_html.erb +4 -0
- data/lib/capybara/spec/views/with_js.erb +17 -0
- data/lib/capybara/version.rb +1 -1
- data/spec/dsl_spec.rb +3 -1
- data/spec/rack_test_spec.rb +12 -1
- data/spec/rspec/features_spec.rb +1 -1
- data/spec/rspec/matchers_spec.rb +113 -20
- data/spec/selenium_spec.rb +10 -1
- 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 =
|
95
|
-
|
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
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
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
|
-
|
483
|
-
|
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
|
-
|
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
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
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
|
-
|
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
|
data/lib/capybara/node/simple.rb
CHANGED
@@ -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
|
152
|
-
|
152
|
+
def inspect
|
153
|
+
%(#<Capybara::Node::Simple tag="#{tag_name}" path="#{path}">)
|
153
154
|
end
|
154
155
|
|
155
|
-
|
156
|
-
|
156
|
+
# @api private
|
157
|
+
def find_css(css)
|
158
|
+
native.css(css)
|
157
159
|
end
|
158
160
|
|
159
|
-
|
160
|
-
|
161
|
-
|
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
|
data/lib/capybara/query.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
module Capybara
|
2
|
-
class
|
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 <<
|
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
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
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
|