capybara 3.1.1 → 3.2.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 +19 -0
- data/README.md +1 -1
- data/lib/capybara.rb +2 -0
- data/lib/capybara/config.rb +2 -1
- data/lib/capybara/driver/base.rb +1 -1
- data/lib/capybara/driver/node.rb +3 -3
- data/lib/capybara/node/actions.rb +90 -92
- data/lib/capybara/node/base.rb +2 -2
- data/lib/capybara/node/document_matchers.rb +5 -5
- data/lib/capybara/node/element.rb +47 -16
- data/lib/capybara/node/finders.rb +13 -13
- data/lib/capybara/node/matchers.rb +18 -17
- data/lib/capybara/node/simple.rb +6 -2
- data/lib/capybara/queries/ancestor_query.rb +1 -1
- data/lib/capybara/queries/base_query.rb +3 -3
- data/lib/capybara/queries/current_path_query.rb +1 -1
- data/lib/capybara/queries/match_query.rb +8 -0
- data/lib/capybara/queries/selector_query.rb +97 -42
- data/lib/capybara/queries/sibling_query.rb +1 -1
- data/lib/capybara/queries/text_query.rb +12 -7
- data/lib/capybara/rack_test/browser.rb +9 -7
- data/lib/capybara/rack_test/form.rb +15 -17
- data/lib/capybara/rack_test/node.rb +12 -12
- data/lib/capybara/result.rb +26 -15
- data/lib/capybara/rspec.rb +1 -2
- data/lib/capybara/rspec/compound.rb +4 -4
- data/lib/capybara/rspec/matchers.rb +2 -2
- data/lib/capybara/selector.rb +75 -225
- data/lib/capybara/selector/css.rb +2 -2
- data/lib/capybara/selector/filter_set.rb +17 -21
- data/lib/capybara/selector/filters/base.rb +24 -1
- data/lib/capybara/selector/filters/expression_filter.rb +3 -5
- data/lib/capybara/selector/filters/node_filter.rb +4 -4
- data/lib/capybara/selector/selector.rb +221 -69
- data/lib/capybara/selenium/driver.rb +15 -88
- data/lib/capybara/selenium/node.rb +25 -28
- data/lib/capybara/server.rb +10 -54
- data/lib/capybara/server/animation_disabler.rb +43 -0
- data/lib/capybara/server/middleware.rb +55 -0
- data/lib/capybara/session.rb +29 -30
- data/lib/capybara/session/config.rb +11 -1
- data/lib/capybara/session/matchers.rb +5 -5
- data/lib/capybara/spec/session/assert_text_spec.rb +1 -1
- data/lib/capybara/spec/session/body_spec.rb +10 -12
- data/lib/capybara/spec/session/click_link_spec.rb +3 -3
- data/lib/capybara/spec/session/element/assert_match_selector_spec.rb +1 -1
- data/lib/capybara/spec/session/fill_in_spec.rb +9 -0
- data/lib/capybara/spec/session/find_field_spec.rb +1 -1
- data/lib/capybara/spec/session/find_spec.rb +8 -3
- data/lib/capybara/spec/session/has_link_spec.rb +2 -2
- data/lib/capybara/spec/session/node_spec.rb +50 -0
- data/lib/capybara/spec/session/node_wrapper_spec.rb +5 -5
- data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +1 -1
- data/lib/capybara/spec/session/window/windows_spec.rb +3 -5
- data/lib/capybara/spec/spec_helper.rb +4 -2
- data/lib/capybara/spec/views/with_animation.erb +46 -0
- data/lib/capybara/version.rb +1 -1
- data/lib/capybara/window.rb +3 -2
- data/spec/filter_set_spec.rb +19 -2
- data/spec/result_spec.rb +33 -1
- data/spec/rspec/features_spec.rb +6 -10
- data/spec/rspec/shared_spec_matchers.rb +4 -4
- data/spec/selector_spec.rb +74 -4
- data/spec/selenium_spec_marionette.rb +2 -0
- data/spec/server_spec.rb +1 -1
- data/spec/session_spec.rb +12 -0
- data/spec/shared_selenium_session.rb +30 -0
- metadata +8 -9
- data/.yard/templates_custom/default/class/html/selectors.erb +0 -38
- data/.yard/templates_custom/default/class/html/setup.rb +0 -17
- data/.yard/yard_extensions.rb +0 -78
- data/.yardopts +0 -1
@@ -50,18 +50,12 @@ module Capybara
|
|
50
50
|
# ignored. This behaviour can be overridden by passing `:all` to this
|
51
51
|
# method.
|
52
52
|
#
|
53
|
-
# @param [:all, :visible]
|
53
|
+
# @param type [:all, :visible] Whether to return only visible or all text
|
54
54
|
# @return [String] The text of the element
|
55
55
|
#
|
56
56
|
def text(type = nil)
|
57
|
-
type ||= :all unless session_options.ignore_hidden_elements
|
58
|
-
synchronize
|
59
|
-
if type == :all
|
60
|
-
base.all_text
|
61
|
-
else
|
62
|
-
base.visible_text
|
63
|
-
end
|
64
|
-
end
|
57
|
+
type ||= :all unless session_options.ignore_hidden_elements || session_options.visible_text_only
|
58
|
+
synchronize { type == :all ? base.all_text : base.visible_text }
|
65
59
|
end
|
66
60
|
|
67
61
|
##
|
@@ -90,11 +84,12 @@ module Capybara
|
|
90
84
|
# Set the value of the form element to the given value.
|
91
85
|
#
|
92
86
|
# @param [String] value The new value
|
93
|
-
# @param [Hash{}] options Driver specific options for how to set the value
|
87
|
+
# @param [Hash{}] options Driver specific options for how to set the value. Take default values from {Capybara#default_set_options}
|
94
88
|
#
|
95
89
|
# @return [Capybara::Node::Element] The element
|
96
90
|
def set(value, **options)
|
97
91
|
raise Capybara::ReadOnlyElementError, "Attempt to set readonly element with value: #{value}" if readonly?
|
92
|
+
options = session_options.default_set_options.to_h.merge(options)
|
98
93
|
synchronize { base.set(value, options) }
|
99
94
|
self
|
100
95
|
end
|
@@ -125,9 +120,11 @@ module Capybara
|
|
125
120
|
# Click the Element
|
126
121
|
#
|
127
122
|
# @!macro click_modifiers
|
128
|
-
#
|
129
|
-
#
|
130
|
-
# @param [
|
123
|
+
# Both x: and y: must be specified if an offset is wanted, if not specified the click will occur at the middle of the element
|
124
|
+
# @overload $0(*modifier_keys, **offset)
|
125
|
+
# @param *modifier_keys [:alt, :control, :meta, :shift] ([]) Keys to be held down when clicking
|
126
|
+
# @option offset [Integer] x X coordinate to offset the click location from the top left corner of the element
|
127
|
+
# @option offset [Integer] y Y coordinate to offset the click location from the top left corner of the element
|
131
128
|
# @return [Capybara::Node::Element] The element
|
132
129
|
def click(*keys, **offset)
|
133
130
|
synchronize { base.click(keys, offset) }
|
@@ -161,7 +158,7 @@ module Capybara
|
|
161
158
|
# Send Keystrokes to the Element
|
162
159
|
#
|
163
160
|
# @overload send_keys(keys, ...)
|
164
|
-
# @param [String, Symbol, Array<String,Symbol>]
|
161
|
+
# @param keys [String, Symbol, Array<String,Symbol>]
|
165
162
|
#
|
166
163
|
# Examples:
|
167
164
|
#
|
@@ -350,12 +347,46 @@ module Capybara
|
|
350
347
|
self
|
351
348
|
end
|
352
349
|
|
350
|
+
##
|
351
|
+
#
|
352
|
+
# Execute the given JS in the context of the element not returning a result. This is useful for scripts that return
|
353
|
+
# complex objects, such as jQuery statements. +execute_script+ should be used over
|
354
|
+
# +evaluate_script+ whenever possible. `this` in the script will refer to the element this is called on.
|
355
|
+
#
|
356
|
+
# @param [String] script A string of JavaScript to execute
|
357
|
+
# @param args Optional arguments that will be passed to the script. Driver support for this is optional and types of objects supported may differ between drivers
|
358
|
+
#
|
359
|
+
def execute_script(script, *args)
|
360
|
+
session.execute_script(<<~JS, self, *args)
|
361
|
+
(function (){
|
362
|
+
#{script}
|
363
|
+
}).apply(arguments[0], Array.prototype.slice.call(arguments,1));
|
364
|
+
JS
|
365
|
+
end
|
366
|
+
|
367
|
+
##
|
368
|
+
#
|
369
|
+
# Evaluate the given JS in the context of the element and return the result. Be careful when using this with
|
370
|
+
# scripts that return complex objects, such as jQuery statements. +execute_script+ might
|
371
|
+
# be a better alternative. `this` in the script will refer to the element this is called on.
|
372
|
+
#
|
373
|
+
# @param [String] script A string of JavaScript to evaluate
|
374
|
+
# @return [Object] The result of the evaluated JavaScript (may be driver specific)
|
375
|
+
#
|
376
|
+
def evaluate_script(script, *args)
|
377
|
+
session.evaluate_script(<<~JS, self, *args)
|
378
|
+
(function(){
|
379
|
+
return #{script}
|
380
|
+
}).apply(arguments[0], Array.prototype.slice.call(arguments,1));
|
381
|
+
JS
|
382
|
+
end
|
383
|
+
|
353
384
|
def reload
|
354
385
|
if @allow_reload
|
355
386
|
begin
|
356
387
|
reloaded = query_scope.reload.first(@query.name, @query.locator, @query.options)
|
357
388
|
@base = reloaded.base if reloaded
|
358
|
-
rescue => e
|
389
|
+
rescue StandardError => e
|
359
390
|
raise e unless catch_error?(e)
|
360
391
|
end
|
361
392
|
end
|
@@ -366,7 +397,7 @@ module Capybara
|
|
366
397
|
%(#<Capybara::Node::Element tag="#{base.tag_name}" path="#{base.path}">)
|
367
398
|
rescue NotSupportedByDriverError
|
368
399
|
%(#<Capybara::Node::Element tag="#{base.tag_name}">)
|
369
|
-
rescue => e
|
400
|
+
rescue StandardError => e
|
370
401
|
raise unless session.driver.invalid_element_errors.any? { |et| e.is_a?(et) }
|
371
402
|
|
372
403
|
%(Obsolete #<Capybara::Node::Element>)
|
@@ -55,7 +55,7 @@ module Capybara
|
|
55
55
|
#
|
56
56
|
def ancestor(*args, **options, &optional_filter_block)
|
57
57
|
options[:session_options] = session_options
|
58
|
-
synced_resolve Capybara::Queries::AncestorQuery.new(*args,
|
58
|
+
synced_resolve Capybara::Queries::AncestorQuery.new(*args, options, &optional_filter_block)
|
59
59
|
end
|
60
60
|
|
61
61
|
##
|
@@ -81,14 +81,14 @@ module Capybara
|
|
81
81
|
#
|
82
82
|
def sibling(*args, **options, &optional_filter_block)
|
83
83
|
options[:session_options] = session_options
|
84
|
-
synced_resolve Capybara::Queries::SiblingQuery.new(*args,
|
84
|
+
synced_resolve Capybara::Queries::SiblingQuery.new(*args, options, &optional_filter_block)
|
85
85
|
end
|
86
86
|
|
87
87
|
##
|
88
88
|
#
|
89
89
|
# Find a form field on the page. The field can be found by its name, id or label text.
|
90
90
|
#
|
91
|
-
# @overload find_field([locator], options
|
91
|
+
# @overload find_field([locator], **options)
|
92
92
|
# @param [String] locator name, id, placeholder or text of associated label element
|
93
93
|
#
|
94
94
|
# @macro waiting_behavior
|
@@ -119,7 +119,7 @@ module Capybara
|
|
119
119
|
#
|
120
120
|
# Find a link on the page. The link can be found by its id or text.
|
121
121
|
#
|
122
|
-
# @overload find_link([locator], options
|
122
|
+
# @overload find_link([locator], **options)
|
123
123
|
# @param [String] locator id, title, text, or alt of enclosed img element
|
124
124
|
#
|
125
125
|
# @macro waiting_behavior
|
@@ -142,10 +142,10 @@ module Capybara
|
|
142
142
|
# \<button> element. All buttons can be found by their id, value, or title. \<button> elements can also be found
|
143
143
|
# by their text content, and image \<input> elements by their alt attribute
|
144
144
|
#
|
145
|
-
# @overload find_button([locator], options
|
145
|
+
# @overload find_button([locator], **options)
|
146
146
|
# @param [String] locator id, value, title, text content, alt of image
|
147
147
|
#
|
148
|
-
# @overload find_button(options
|
148
|
+
# @overload find_button(**options)
|
149
149
|
#
|
150
150
|
# @macro waiting_behavior
|
151
151
|
#
|
@@ -178,7 +178,7 @@ module Capybara
|
|
178
178
|
end
|
179
179
|
|
180
180
|
##
|
181
|
-
# @!method all([kind = Capybara.default_selector], locator = nil, options
|
181
|
+
# @!method all([kind = Capybara.default_selector], locator = nil, **options)
|
182
182
|
#
|
183
183
|
# Find all elements on the page matching the given selector
|
184
184
|
# and options.
|
@@ -235,8 +235,8 @@ module Capybara
|
|
235
235
|
# @option options [Range] between Number of matches found must be within the given range
|
236
236
|
# @option options [Boolean] exact Control whether `is` expressions in the given XPath match exactly or partially
|
237
237
|
# @option options [Integer, false] wait (Capybara.default_max_wait_time) The time to wait for matching elements to become available
|
238
|
-
# @overload all([kind = Capybara.default_selector], locator = nil, options
|
239
|
-
# @overload all([kind = Capybara.default_selector], locator = nil, options
|
238
|
+
# @overload all([kind = Capybara.default_selector], locator = nil, **options)
|
239
|
+
# @overload all([kind = Capybara.default_selector], locator = nil, **options, &filter_block)
|
240
240
|
# @yieldparam element [Capybara::Node::Element] The element being considered for inclusion in the results
|
241
241
|
# @yieldreturn [Boolean] Should the element be considered in the results?
|
242
242
|
# @return [Capybara::Result] A collection of found elements
|
@@ -245,7 +245,7 @@ module Capybara
|
|
245
245
|
minimum_specified = options_include_minimum?(options)
|
246
246
|
options = { minimum: 1 }.merge(options) unless minimum_specified
|
247
247
|
options[:session_options] = session_options
|
248
|
-
query = Capybara::Queries::SelectorQuery.new(*args
|
248
|
+
query = Capybara::Queries::SelectorQuery.new(*args, options, &optional_filter_block)
|
249
249
|
result = nil
|
250
250
|
begin
|
251
251
|
synchronize(query.wait) do
|
@@ -276,7 +276,7 @@ module Capybara
|
|
276
276
|
#
|
277
277
|
def first(*args, **options, &optional_filter_block)
|
278
278
|
options = { minimum: 1 }.merge(options) unless options_include_minimum?(options)
|
279
|
-
all(*args,
|
279
|
+
all(*args, options, &optional_filter_block).first
|
280
280
|
end
|
281
281
|
|
282
282
|
private
|
@@ -298,11 +298,11 @@ module Capybara
|
|
298
298
|
end
|
299
299
|
|
300
300
|
def ambiguous?(query, result)
|
301
|
-
|
301
|
+
%i[one smart].include?(query.match) && (result.size > 1)
|
302
302
|
end
|
303
303
|
|
304
304
|
def prefer_exact?(query)
|
305
|
-
|
305
|
+
%i[smart prefer_exact].include?(query.match)
|
306
306
|
end
|
307
307
|
|
308
308
|
def options_include_minimum?(opts)
|
@@ -91,7 +91,7 @@ module Capybara
|
|
91
91
|
#
|
92
92
|
def assert_selector(*args, &optional_filter_block)
|
93
93
|
_verify_selector_result(args, optional_filter_block) do |result, query|
|
94
|
-
unless result.matches_count? && (
|
94
|
+
unless result.matches_count? && (result.any? || query.expects_none?)
|
95
95
|
raise Capybara::ExpectationNotMet, result.failure_message
|
96
96
|
end
|
97
97
|
end
|
@@ -110,11 +110,11 @@ module Capybara
|
|
110
110
|
# The :wait option applies to all of the selectors as a group, so all of the locators must be present
|
111
111
|
# within :wait (Defaults to Capybara.default_max_wait_time) seconds.
|
112
112
|
#
|
113
|
-
# @overload assert_all_of_selectors([kind = Capybara.default_selector], *locators, options
|
113
|
+
# @overload assert_all_of_selectors([kind = Capybara.default_selector], *locators, **options)
|
114
114
|
#
|
115
115
|
def assert_all_of_selectors(*args, wait: nil, **options, &optional_filter_block)
|
116
116
|
wait = session_options.default_max_wait_time if wait.nil?
|
117
|
-
selector = args
|
117
|
+
selector = extract_selector(args)
|
118
118
|
synchronize(wait) do
|
119
119
|
args.each do |locator|
|
120
120
|
assert_selector(selector, locator, options, &optional_filter_block)
|
@@ -135,11 +135,11 @@ module Capybara
|
|
135
135
|
# The :wait option applies to all of the selectors as a group, so none of the locators must be present
|
136
136
|
# within :wait (Defaults to Capybara.default_max_wait_time) seconds.
|
137
137
|
#
|
138
|
-
# @overload assert_none_of_selectors([kind = Capybara.default_selector], *locators, options
|
138
|
+
# @overload assert_none_of_selectors([kind = Capybara.default_selector], *locators, **options)
|
139
139
|
#
|
140
140
|
def assert_none_of_selectors(*args, wait: nil, **options, &optional_filter_block)
|
141
141
|
wait = session_options.default_max_wait_time if wait.nil?
|
142
|
-
selector = args
|
142
|
+
selector = extract_selector(args)
|
143
143
|
synchronize(wait) do
|
144
144
|
args.each do |locator|
|
145
145
|
assert_no_selector(selector, locator, options, &optional_filter_block)
|
@@ -508,7 +508,7 @@ module Capybara
|
|
508
508
|
def matches_selector?(*args, &optional_filter_block)
|
509
509
|
assert_matches_selector(*args, &optional_filter_block)
|
510
510
|
rescue Capybara::ExpectationNotMet
|
511
|
-
|
511
|
+
false
|
512
512
|
end
|
513
513
|
|
514
514
|
##
|
@@ -544,7 +544,7 @@ module Capybara
|
|
544
544
|
def not_matches_selector?(*args, &optional_filter_block)
|
545
545
|
assert_not_matches_selector(*args, &optional_filter_block)
|
546
546
|
rescue Capybara::ExpectationNotMet
|
547
|
-
|
547
|
+
false
|
548
548
|
end
|
549
549
|
|
550
550
|
##
|
@@ -574,7 +574,7 @@ module Capybara
|
|
574
574
|
# ignoring any HTML tags.
|
575
575
|
#
|
576
576
|
# @!macro text_query_params
|
577
|
-
# @overload $0(type, text, options
|
577
|
+
# @overload $0(type, text, **options)
|
578
578
|
# @param [:all, :visible] type Whether to check for only visible or all text. If this parameter is missing or nil then we use the value of `Capybara.ignore_hidden_elements`, which defaults to `true`, corresponding to `:visible`.
|
579
579
|
# @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.
|
580
580
|
# @option options [Integer] :count (nil) Number of times the text is expected to occur
|
@@ -583,7 +583,7 @@ module Capybara
|
|
583
583
|
# @option options [Range] :between (nil) Range of times that is expected to contain number of times text occurs
|
584
584
|
# @option options [Numeric] :wait (Capybara.default_max_wait_time) Maximum time that Capybara will wait for text to eq/match given string/regexp argument
|
585
585
|
# @option options [Boolean] :exact (Capybara.exact_text) Whether text must be an exact match or just substring
|
586
|
-
# @overload $0(text, options
|
586
|
+
# @overload $0(text, **options)
|
587
587
|
# @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.
|
588
588
|
# @option options [Integer] :count (nil) Number of times the text is expected to occur
|
589
589
|
# @option options [Integer] :minimum (nil) Minimum number of times the text is expected to occur
|
@@ -596,7 +596,7 @@ module Capybara
|
|
596
596
|
#
|
597
597
|
def assert_text(*args)
|
598
598
|
_verify_text(args) do |count, query|
|
599
|
-
unless query.matches_count?(count) && (
|
599
|
+
unless query.matches_count?(count) && (count.positive? || query.expects_none?)
|
600
600
|
raise Capybara::ExpectationNotMet, query.failure_message
|
601
601
|
end
|
602
602
|
end
|
@@ -612,7 +612,7 @@ module Capybara
|
|
612
612
|
#
|
613
613
|
def assert_no_text(*args)
|
614
614
|
_verify_text(args) do |count, query|
|
615
|
-
if query.matches_count?(count) && (
|
615
|
+
if query.matches_count?(count) && (count.positive? || query.expects_none?)
|
616
616
|
raise Capybara::ExpectationNotMet, query.negative_failure_message
|
617
617
|
end
|
618
618
|
end
|
@@ -659,12 +659,15 @@ module Capybara
|
|
659
659
|
|
660
660
|
private
|
661
661
|
|
662
|
+
def extract_selector(args)
|
663
|
+
args.first.is_a?(Symbol) ? args.shift : session_options.default_selector
|
664
|
+
end
|
665
|
+
|
662
666
|
def _verify_selector_result(query_args, optional_filter_block)
|
663
667
|
query_args = _set_query_session_options(*query_args)
|
664
668
|
query = Capybara::Queries::SelectorQuery.new(*query_args, &optional_filter_block)
|
665
669
|
synchronize(query.wait) do
|
666
|
-
|
667
|
-
yield result, query
|
670
|
+
yield query.resolve_for(self), query
|
668
671
|
end
|
669
672
|
true
|
670
673
|
end
|
@@ -673,8 +676,7 @@ module Capybara
|
|
673
676
|
query_args = _set_query_session_options(*query_args)
|
674
677
|
query = Capybara::Queries::MatchQuery.new(*query_args, &optional_filter_block)
|
675
678
|
synchronize(query.wait) do
|
676
|
-
|
677
|
-
yield result
|
679
|
+
yield query.resolve_for(query_scope)
|
678
680
|
end
|
679
681
|
true
|
680
682
|
end
|
@@ -683,8 +685,7 @@ module Capybara
|
|
683
685
|
query_args = _set_query_session_options(*query_args)
|
684
686
|
query = Capybara::Queries::TextQuery.new(*query_args)
|
685
687
|
synchronize(query.wait) do
|
686
|
-
|
687
|
-
yield(count, query)
|
688
|
+
yield query.resolve_for(self), query
|
688
689
|
end
|
689
690
|
true
|
690
691
|
end
|
data/lib/capybara/node/simple.rb
CHANGED
@@ -45,7 +45,7 @@ module Capybara
|
|
45
45
|
attr_name = name.to_s
|
46
46
|
if attr_name == 'value'
|
47
47
|
value
|
48
|
-
elsif tag_name == 'input'
|
48
|
+
elsif (tag_name == 'input') && (native[:type] == 'checkbox') && (attr_name == 'checked')
|
49
49
|
native['checked'] == 'checked'
|
50
50
|
else
|
51
51
|
native[attr_name]
|
@@ -78,7 +78,7 @@ module Capybara
|
|
78
78
|
if tag_name == 'textarea'
|
79
79
|
native['_capybara_raw_value']
|
80
80
|
elsif tag_name == 'select'
|
81
|
-
if
|
81
|
+
if multiple?
|
82
82
|
native.xpath(".//option[@selected='selected']").map { |option| option[:value] || option.content }
|
83
83
|
else
|
84
84
|
option = native.xpath(".//option[@selected='selected']").first || native.xpath(".//option").first
|
@@ -139,6 +139,10 @@ module Capybara
|
|
139
139
|
native.has_attribute?('selected')
|
140
140
|
end
|
141
141
|
|
142
|
+
def multiple?
|
143
|
+
native.has_attribute?('multiple')
|
144
|
+
end
|
145
|
+
|
142
146
|
def synchronize(_seconds = nil)
|
143
147
|
yield # simple nodes don't need to wait
|
144
148
|
end
|
@@ -13,7 +13,7 @@ module Capybara
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def description
|
16
|
-
child_query = @child_node
|
16
|
+
child_query = @child_node&.instance_variable_get(:@query)
|
17
17
|
desc = super
|
18
18
|
desc += " that is an ancestor of #{child_query.description}" if child_query
|
19
19
|
desc
|
@@ -59,11 +59,11 @@ module Capybara
|
|
59
59
|
# Generates a failure message from the query description and count options.
|
60
60
|
#
|
61
61
|
def failure_message
|
62
|
-
|
62
|
+
+"expected to find #{description}" << count_message
|
63
63
|
end
|
64
64
|
|
65
65
|
def negative_failure_message
|
66
|
-
|
66
|
+
+"expected not to find #{description}" << count_message
|
67
67
|
end
|
68
68
|
|
69
69
|
private
|
@@ -73,7 +73,7 @@ module Capybara
|
|
73
73
|
end
|
74
74
|
|
75
75
|
def count_message
|
76
|
-
message = ""
|
76
|
+
message = +""
|
77
77
|
if options[:count]
|
78
78
|
message << " #{options[:count]} #{Capybara::Helpers.declension('time', 'times', options[:count])}"
|
79
79
|
elsif options[:between]
|
@@ -19,7 +19,7 @@ module Capybara
|
|
19
19
|
def resolves_for?(session)
|
20
20
|
uri = ::Addressable::URI.parse(session.current_url)
|
21
21
|
uri.query = nil if uri && options[:ignore_query]
|
22
|
-
@actual_path = options[:url] ? uri.to_s : uri
|
22
|
+
@actual_path = options[:url] ? uri.to_s : uri&.request_uri
|
23
23
|
|
24
24
|
if @expected_path.is_a? Regexp
|
25
25
|
@actual_path.to_s.match(@expected_path)
|
@@ -9,6 +9,14 @@ module Capybara
|
|
9
9
|
|
10
10
|
private
|
11
11
|
|
12
|
+
def assert_valid_keys
|
13
|
+
invalid_options = @options.keys & COUNT_KEYS
|
14
|
+
unless invalid_options.empty?
|
15
|
+
raise ArgumentError, "Match queries don't support quantity options. Invalid keys - #{invalid_options.join(', ')}"
|
16
|
+
end
|
17
|
+
super
|
18
|
+
end
|
19
|
+
|
12
20
|
def valid_keys
|
13
21
|
super - COUNT_KEYS
|
14
22
|
end
|
@@ -31,12 +31,12 @@ module Capybara
|
|
31
31
|
def label; selector.label || selector.name; end
|
32
32
|
|
33
33
|
def description
|
34
|
-
@description = ""
|
34
|
+
@description = +""
|
35
35
|
@description << "visible " if visible == :visible
|
36
36
|
@description << "non-visible " if visible == :hidden
|
37
37
|
@description << "#{label} #{locator.inspect}"
|
38
38
|
@description << " with#{' exact' if exact_text == true} text #{options[:text].inspect}" if options[:text]
|
39
|
-
@description << " with exact text #{
|
39
|
+
@description << " with exact text #{exact_text}" if exact_text.is_a?(String)
|
40
40
|
@description << " with id #{options[:id]}" if options[:id]
|
41
41
|
@description << " with classes [#{Array(options[:class]).join(',')}]" if options[:class]
|
42
42
|
@description << selector.description(options)
|
@@ -56,7 +56,7 @@ module Capybara
|
|
56
56
|
|
57
57
|
matches_node_filters?(node) && matches_filter_block?(node)
|
58
58
|
rescue *(node.respond_to?(:session) ? node.session.driver.invalid_element_errors : [])
|
59
|
-
|
59
|
+
false
|
60
60
|
end
|
61
61
|
|
62
62
|
def visible
|
@@ -126,11 +126,19 @@ module Capybara
|
|
126
126
|
end
|
127
127
|
|
128
128
|
def matches_node_filters?(node)
|
129
|
-
|
130
|
-
|
131
|
-
|
129
|
+
unapplied_options = options.keys - valid_keys
|
130
|
+
|
131
|
+
node_filters.all? do |filter_name, filter|
|
132
|
+
if filter.matcher?
|
133
|
+
unapplied_options.select { |option_name| filter.handles_option?(option_name) }.all? do |option_name|
|
134
|
+
unapplied_options.delete(option_name)
|
135
|
+
filter.matches?(node, option_name, options[option_name])
|
136
|
+
end
|
137
|
+
elsif options.key?(filter_name)
|
138
|
+
unapplied_options.delete(filter_name)
|
139
|
+
filter.matches?(node, filter_name, options[filter_name])
|
132
140
|
elsif filter.default?
|
133
|
-
filter.matches?(node, filter.default)
|
141
|
+
filter.matches?(node, filter_name, filter.default)
|
134
142
|
else
|
135
143
|
true
|
136
144
|
end
|
@@ -166,43 +174,86 @@ module Capybara
|
|
166
174
|
end
|
167
175
|
|
168
176
|
def assert_valid_keys
|
169
|
-
|
170
|
-
|
171
|
-
|
177
|
+
unless VALID_MATCH.include?(match)
|
178
|
+
raise ArgumentError, "invalid option #{match.inspect} for :match, should be one of #{VALID_MATCH.map(&:inspect).join(', ')}"
|
179
|
+
end
|
180
|
+
unhandled_options = @options.keys - valid_keys
|
181
|
+
unhandled_options -= @options.keys.select do |option_name|
|
182
|
+
expression_filters.any? { |_nmae, ef| ef.handles_option? option_name } ||
|
183
|
+
node_filters.any? { |_name, nf| nf.handles_option? option_name }
|
184
|
+
end
|
185
|
+
|
186
|
+
return if unhandled_options.empty?
|
187
|
+
invalid_names = unhandled_options.map(&:inspect).join(", ")
|
188
|
+
valid_names = valid_keys.map(&:inspect).join(", ")
|
189
|
+
raise ArgumentError, "invalid keys #{invalid_names}, should be one of #{valid_names}"
|
172
190
|
end
|
173
191
|
|
174
192
|
def filtered_xpath(expr)
|
175
|
-
if options.key?(:id)
|
176
|
-
expr =
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
193
|
+
if options.key?(:id) && !custom_keys.include?(:id)
|
194
|
+
expr = if options[:id].is_a? XPath::Expression
|
195
|
+
"(#{expr})[#{XPath.attr(:id)[options[:id]]}]"
|
196
|
+
else
|
197
|
+
"(#{expr})[#{XPath.attr(:id) == options[:id]}]"
|
198
|
+
end
|
199
|
+
end
|
200
|
+
if options.key?(:class) && !custom_keys.include?(:class)
|
201
|
+
class_xpath = if options[:class].is_a?(XPath::Expression)
|
202
|
+
XPath.attr(:class)[options[:class]]
|
203
|
+
else
|
204
|
+
Array(options[:class]).map do |klass|
|
205
|
+
if klass.start_with?('!')
|
206
|
+
!XPath.attr(:class).contains_word(klass.slice(1))
|
207
|
+
else
|
208
|
+
XPath.attr(:class).contains_word(klass)
|
209
|
+
end
|
181
210
|
end.reduce(:&)
|
182
|
-
expr = "#{expr}[#{class_xpath}]"
|
183
211
|
end
|
212
|
+
expr = "(#{expr})[#{class_xpath}]"
|
184
213
|
end
|
185
214
|
expr
|
186
215
|
end
|
187
216
|
|
188
217
|
def filtered_css(expr)
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
218
|
+
process_id = options.key?(:id) && !custom_keys.include?(:id)
|
219
|
+
process_class = options.key?(:class) && !custom_keys.include?(:class)
|
220
|
+
|
221
|
+
if process_id && options[:id].is_a?(XPath::Expression)
|
222
|
+
raise ArgumentError, "XPath expressions are not supported for the :id filter with CSS based selectors"
|
223
|
+
end
|
224
|
+
if process_class && options[:class].is_a?(XPath::Expression)
|
225
|
+
raise ArgumentError, "XPath expressions are not supported for the :class filter with CSS based selectors"
|
196
226
|
end
|
227
|
+
|
228
|
+
css_selectors = expr.split(',').map(&:rstrip)
|
229
|
+
expr = css_selectors.map do |sel|
|
230
|
+
sel += "##{Capybara::Selector::CSS.escape(options[:id])}" if process_id
|
231
|
+
sel += css_from_classes(Array(options[:class])) if process_class
|
232
|
+
sel
|
233
|
+
end.join(", ")
|
197
234
|
expr
|
198
235
|
end
|
199
236
|
|
237
|
+
def css_from_classes(classes)
|
238
|
+
classes = classes.group_by { |c| c.start_with? '!' }
|
239
|
+
(classes[false].to_a.map { |c| ".#{Capybara::Selector::CSS.escape(c)}" } +
|
240
|
+
classes[true].to_a.map { |c| ":not(.#{Capybara::Selector::CSS.escape(c.slice(1))})" }).join
|
241
|
+
end
|
242
|
+
|
200
243
|
def apply_expression_filters(expr)
|
244
|
+
unapplied_options = options.keys - valid_keys
|
201
245
|
expression_filters.inject(expr) do |memo, (name, ef)|
|
202
|
-
if
|
203
|
-
ef.
|
246
|
+
if ef.matcher?
|
247
|
+
unapplied_options.select { |option_name| ef.handles_option?(option_name) }.each do |option_name|
|
248
|
+
unapplied_options.delete(option_name)
|
249
|
+
memo = ef.apply_filter(memo, option_name, options[option_name])
|
250
|
+
end
|
251
|
+
memo
|
252
|
+
elsif options.key?(name)
|
253
|
+
unapplied_options.delete(name)
|
254
|
+
ef.apply_filter(memo, name, options[name])
|
204
255
|
elsif ef.default?
|
205
|
-
ef.apply_filter(memo, ef.default)
|
256
|
+
ef.apply_filter(memo, name, ef.default)
|
206
257
|
else
|
207
258
|
memo
|
208
259
|
end
|
@@ -219,25 +270,29 @@ module Capybara
|
|
219
270
|
end
|
220
271
|
|
221
272
|
def describe_within?
|
222
|
-
@resolved_node && !(@resolved_node
|
223
|
-
(@resolved_node.is_a?(::Capybara::Node::Simple) && @resolved_node.path == '/'))
|
273
|
+
@resolved_node && !document?(@resolved_node) && !simple_root?(@resolved_node)
|
224
274
|
end
|
225
275
|
|
226
|
-
def
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
276
|
+
def document?(node)
|
277
|
+
node.is_a?(::Capybara::Node::Document)
|
278
|
+
end
|
279
|
+
|
280
|
+
def simple_root?(node)
|
281
|
+
node.is_a?(::Capybara::Node::Simple) && node.path == '/'
|
282
|
+
end
|
283
|
+
|
284
|
+
def matches_text_filter(node, value)
|
285
|
+
return matches_exact_text_filter(node, value) if exact_text == true
|
286
|
+
regexp = value.is_a?(Regexp) ? value : Regexp.escape(value.to_s)
|
287
|
+
matches_text_regexp(node, regexp)
|
288
|
+
end
|
289
|
+
|
290
|
+
def matches_exact_text_filter(node, value)
|
291
|
+
regexp = value.is_a?(Regexp) ? value : /\A#{Regexp.escape(value.to_s)}\z/
|
292
|
+
matches_text_regexp(node, regexp)
|
237
293
|
end
|
238
294
|
|
239
|
-
def
|
240
|
-
regexp = /\A#{Regexp.escape(exact_text_option)}\z/
|
295
|
+
def matches_text_regexp(node, regexp)
|
241
296
|
text_visible = visible
|
242
297
|
text_visible = :all if text_visible == :hidden
|
243
298
|
node.text(text_visible).match(regexp)
|