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
@@ -16,7 +16,7 @@ module Capybara
|
|
16
16
|
|
17
17
|
def description
|
18
18
|
desc = super
|
19
|
-
sibling_query = @sibling_node
|
19
|
+
sibling_query = @sibling_node&.instance_variable_get(:@query)
|
20
20
|
desc += " that is a sibling of #{sibling_query.description}" if sibling_query
|
21
21
|
desc
|
22
22
|
end
|
@@ -50,15 +50,15 @@ module Capybara
|
|
50
50
|
end
|
51
51
|
|
52
52
|
def build_message(report_on_invisible)
|
53
|
-
message = ""
|
53
|
+
message = +""
|
54
54
|
unless (COUNT_KEYS & @options.keys).empty?
|
55
55
|
message << " but found #{@count} #{Capybara::Helpers.declension('time', 'times', @count)}"
|
56
56
|
end
|
57
57
|
message << " in #{@actual_text.inspect}"
|
58
58
|
|
59
59
|
details_message = []
|
60
|
-
details_message << case_insensitive_message if @node
|
61
|
-
details_message << invisible_message if @node
|
60
|
+
details_message << case_insensitive_message if @node && check_case_insensitive?
|
61
|
+
details_message << invisible_message if @node && check_visible_text? && report_on_invisible
|
62
62
|
details_message.compact!
|
63
63
|
|
64
64
|
message << ". (However, #{details_message.join(' and ')}.)" unless details_message.empty?
|
@@ -75,10 +75,11 @@ module Capybara
|
|
75
75
|
def invisible_message
|
76
76
|
invisible_text = text(@node, :all)
|
77
77
|
invisible_count = invisible_text.scan(@search_regexp).size
|
78
|
-
if invisible_count
|
79
|
-
|
80
|
-
|
81
|
-
|
78
|
+
return if invisible_count == @count
|
79
|
+
"it was found #{invisible_count} #{Capybara::Helpers.declension('time', 'times', invisible_count)} including non-visible text"
|
80
|
+
rescue StandardError
|
81
|
+
# An error getting the non-visible text (if element goes out of scope) should not affect the response
|
82
|
+
nil
|
82
83
|
end
|
83
84
|
|
84
85
|
def valid_keys
|
@@ -89,6 +90,10 @@ module Capybara
|
|
89
90
|
@type == :visible
|
90
91
|
end
|
91
92
|
|
93
|
+
def check_case_insensitive?
|
94
|
+
!@expected_text.is_a?(Regexp)
|
95
|
+
end
|
96
|
+
|
92
97
|
def text(node, query_type)
|
93
98
|
node.text(query_type)
|
94
99
|
end
|
@@ -34,7 +34,7 @@ class Capybara::RackTest::Browser
|
|
34
34
|
end
|
35
35
|
|
36
36
|
def follow(method, path, **attributes)
|
37
|
-
return if
|
37
|
+
return if fragment_or_script?(path)
|
38
38
|
process_and_follow_redirects(method, path, attributes, 'HTTP_REFERER' => current_url)
|
39
39
|
end
|
40
40
|
|
@@ -63,9 +63,7 @@ class Capybara::RackTest::Browser
|
|
63
63
|
new_uri.host ||= @current_host
|
64
64
|
new_uri.port ||= @current_port unless new_uri.default_port == @current_port
|
65
65
|
|
66
|
-
@current_scheme = new_uri.scheme
|
67
|
-
@current_host = new_uri.host
|
68
|
-
@current_port = new_uri.port
|
66
|
+
@current_scheme, @current_host, @current_port = new_uri.select(:scheme, :host, :port)
|
69
67
|
|
70
68
|
reset_cache!
|
71
69
|
send(method, new_uri.to_s, attributes, env.merge(options[:headers] || {}))
|
@@ -79,9 +77,7 @@ class Capybara::RackTest::Browser
|
|
79
77
|
|
80
78
|
def reset_host!
|
81
79
|
uri = URI.parse(driver.session_options.app_host || driver.session_options.default_host)
|
82
|
-
@current_scheme = uri.scheme
|
83
|
-
@current_host = uri.host
|
84
|
-
@current_port = uri.port
|
80
|
+
@current_scheme, @current_host, @current_port = uri.select(:scheme, :host, :port)
|
85
81
|
end
|
86
82
|
|
87
83
|
def reset_cache!
|
@@ -122,4 +118,10 @@ protected
|
|
122
118
|
rescue Rack::Test::Error
|
123
119
|
"/"
|
124
120
|
end
|
121
|
+
|
122
|
+
private
|
123
|
+
|
124
|
+
def fragment_or_script?(path)
|
125
|
+
path.gsub(/^#{Regexp.escape(request_path)}/, '').start_with?('#') || path.downcase.start_with?('javascript:')
|
126
|
+
end
|
125
127
|
end
|
@@ -30,12 +30,9 @@ class Capybara::RackTest::Form < Capybara::RackTest::Node
|
|
30
30
|
|
31
31
|
native.xpath(form_elements_xpath).map do |field|
|
32
32
|
case field.name
|
33
|
-
when 'input'
|
34
|
-
|
35
|
-
when '
|
36
|
-
add_select_param(field, params)
|
37
|
-
when 'textarea'
|
38
|
-
add_textarea_param(field, params)
|
33
|
+
when 'input' then add_input_param(field, params)
|
34
|
+
when 'select' then add_select_param(field, params)
|
35
|
+
when 'textarea' then add_textarea_param(field, params)
|
39
36
|
end
|
40
37
|
end
|
41
38
|
merge_param!(params, button[:name], button[:value] || "") if button[:name]
|
@@ -44,8 +41,8 @@ class Capybara::RackTest::Form < Capybara::RackTest::Node
|
|
44
41
|
end
|
45
42
|
|
46
43
|
def submit(button)
|
47
|
-
action =
|
48
|
-
method =
|
44
|
+
action = button&.[]('formaction') || native['action']
|
45
|
+
method = button&.[]('formmethod') || request_method
|
49
46
|
driver.submit(method, action.to_s, params(button))
|
50
47
|
end
|
51
48
|
|
@@ -66,6 +63,7 @@ private
|
|
66
63
|
end
|
67
64
|
|
68
65
|
def merge_param!(params, key, value)
|
66
|
+
key = key.to_s
|
69
67
|
if Rack::Utils.respond_to?(:default_query_parser)
|
70
68
|
Rack::Utils.default_query_parser.normalize_params(params, key, value, Rack::Utils.param_depth_limit)
|
71
69
|
else
|
@@ -85,7 +83,7 @@ private
|
|
85
83
|
if %w[radio checkbox].include? field['type']
|
86
84
|
if field['checked']
|
87
85
|
node = Capybara::RackTest::Node.new(driver, field)
|
88
|
-
merge_param!(params, field['name']
|
86
|
+
merge_param!(params, field['name'], node.value.to_s)
|
89
87
|
end
|
90
88
|
elsif %w[submit image].include? field['type']
|
91
89
|
# TODO: identify the click button here (in document order, rather
|
@@ -96,29 +94,29 @@ private
|
|
96
94
|
NilUploadedFile.new
|
97
95
|
else
|
98
96
|
mime_info = MiniMime.lookup_by_filename(value)
|
99
|
-
Rack::Test::UploadedFile.new(value,
|
97
|
+
Rack::Test::UploadedFile.new(value, mime_info&.content_type&.to_s)
|
100
98
|
end
|
101
|
-
merge_param!(params, field['name']
|
99
|
+
merge_param!(params, field['name'], file)
|
102
100
|
else
|
103
|
-
merge_param!(params, field['name']
|
101
|
+
merge_param!(params, field['name'], File.basename(field['value'].to_s))
|
104
102
|
end
|
105
103
|
else
|
106
|
-
merge_param!(params, field['name']
|
104
|
+
merge_param!(params, field['name'], field['value'].to_s)
|
107
105
|
end
|
108
106
|
end
|
109
107
|
|
110
108
|
def add_select_param(field, params)
|
111
|
-
if field
|
109
|
+
if field.has_attribute?('multiple')
|
112
110
|
field.xpath(".//option[@selected]").each do |option|
|
113
|
-
merge_param!(params, field['name']
|
111
|
+
merge_param!(params, field['name'], (option['value'] || option.text).to_s)
|
114
112
|
end
|
115
113
|
else
|
116
114
|
option = field.xpath('.//option[@selected]').first || field.xpath('.//option').first
|
117
|
-
merge_param!(params, field['name']
|
115
|
+
merge_param!(params, field['name'], (option['value'] || option.text).to_s) if option
|
118
116
|
end
|
119
117
|
end
|
120
118
|
|
121
119
|
def add_textarea_param(field, params)
|
122
|
-
merge_param!(params, field['name']
|
120
|
+
merge_param!(params, field['name'], field['_capybara_raw_value'].to_s.gsub(/\n/, "\r\n"))
|
123
121
|
end
|
124
122
|
end
|
@@ -55,7 +55,7 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
|
|
55
55
|
native.remove_attribute('selected')
|
56
56
|
end
|
57
57
|
|
58
|
-
def click(keys = [], offset
|
58
|
+
def click(keys = [], **offset)
|
59
59
|
raise ArgumentError, "The RackTest driver does not support click options" unless keys.empty? && offset.empty?
|
60
60
|
|
61
61
|
if link?
|
@@ -218,36 +218,36 @@ private
|
|
218
218
|
end
|
219
219
|
|
220
220
|
def submits?
|
221
|
-
(tag_name == 'input'
|
221
|
+
(tag_name == 'input' && %w[submit image].include?(type)) || (tag_name == 'button' && [nil, "submit"].include?(type))
|
222
222
|
end
|
223
223
|
|
224
224
|
def checkable?
|
225
|
-
tag_name == 'input'
|
225
|
+
tag_name == 'input' && %w[checkbox radio].include?(type)
|
226
226
|
end
|
227
227
|
|
228
228
|
protected
|
229
229
|
|
230
230
|
def checkbox_or_radio?(field = self)
|
231
|
-
field
|
231
|
+
field&.checkbox? || field&.radio?
|
232
232
|
end
|
233
233
|
|
234
234
|
def checkbox?
|
235
235
|
input_field? && type == 'checkbox'
|
236
236
|
end
|
237
237
|
|
238
|
-
def input_field?
|
239
|
-
tag_name == 'input'
|
240
|
-
end
|
241
|
-
|
242
238
|
def radio?
|
243
239
|
input_field? && type == 'radio'
|
244
240
|
end
|
245
241
|
|
246
|
-
def textarea?
|
247
|
-
tag_name == "textarea"
|
248
|
-
end
|
249
|
-
|
250
242
|
def text_or_password?
|
251
243
|
input_field? && (type == 'text' || type == 'password')
|
252
244
|
end
|
245
|
+
|
246
|
+
def input_field?
|
247
|
+
tag_name == 'input'
|
248
|
+
end
|
249
|
+
|
250
|
+
def textarea?
|
251
|
+
tag_name == "textarea"
|
252
|
+
end
|
253
253
|
end
|
data/lib/capybara/result.rb
CHANGED
@@ -47,14 +47,27 @@ module Capybara
|
|
47
47
|
end
|
48
48
|
|
49
49
|
def [](*args)
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
50
|
+
idx, length = args
|
51
|
+
max_idx = case idx
|
52
|
+
when Integer
|
53
|
+
if !idx.negative?
|
54
|
+
length.nil? ? idx : idx + length - 1
|
55
|
+
else
|
56
|
+
nil
|
57
|
+
end
|
58
|
+
when Range
|
59
|
+
idx.max
|
60
|
+
end
|
61
|
+
|
62
|
+
if max_idx.nil?
|
54
63
|
full_results[*args]
|
64
|
+
else
|
65
|
+
loop do
|
66
|
+
break if @result_cache.size > max_idx
|
67
|
+
@result_cache << @results_enum.next
|
68
|
+
end
|
69
|
+
@result_cache[*args]
|
55
70
|
end
|
56
|
-
rescue StopIteration
|
57
|
-
return nil
|
58
71
|
end
|
59
72
|
alias :at :[]
|
60
73
|
|
@@ -84,10 +97,9 @@ module Capybara
|
|
84
97
|
|
85
98
|
if @query.options[:maximum]
|
86
99
|
max_opt = Integer(@query.options[:maximum])
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
rescue StopIteration
|
100
|
+
loop do
|
101
|
+
return 1 if @result_cache.size > max_opt
|
102
|
+
@result_cache << @results_enum.next
|
91
103
|
end
|
92
104
|
end
|
93
105
|
|
@@ -97,12 +109,11 @@ module Capybara
|
|
97
109
|
break if @result_cache.size > max
|
98
110
|
@result_cache << @results_enum.next
|
99
111
|
end
|
100
|
-
return 0 if @query.options[:between].include?
|
101
|
-
return
|
102
|
-
return 1
|
112
|
+
return 0 if @query.options[:between].include? @result_cache.size
|
113
|
+
return @result_cache.size <=> min
|
103
114
|
end
|
104
115
|
|
105
|
-
|
116
|
+
0
|
106
117
|
end
|
107
118
|
|
108
119
|
def matches_count?
|
@@ -117,7 +128,7 @@ module Capybara
|
|
117
128
|
message << ", found #{count} #{Capybara::Helpers.declension('match', 'matches', count)}: " << full_results.map(&:text).map(&:inspect).join(", ")
|
118
129
|
end
|
119
130
|
unless rest.empty?
|
120
|
-
elements = rest.map { |el| el.text rescue "<<ERROR>>" }.map(&:inspect).join(", ")
|
131
|
+
elements = rest.map { |el| el.text rescue "<<ERROR>>" }.map(&:inspect).join(", ") # rubocop:disable Style/RescueModifier
|
121
132
|
message << ". Also found " << elements << ", which matched the selector but not all filters."
|
122
133
|
end
|
123
134
|
message
|
data/lib/capybara/rspec.rb
CHANGED
@@ -20,9 +20,8 @@ RSpec.configure do |config|
|
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
23
|
-
config.before do
|
23
|
+
config.before do |example|
|
24
24
|
if self.class.include?(Capybara::DSL)
|
25
|
-
example = RSpec.current_example
|
26
25
|
Capybara.current_driver = Capybara.javascript_driver if example.metadata[:js]
|
27
26
|
Capybara.current_driver = example.metadata[:driver] if example.metadata[:driver]
|
28
27
|
end
|
@@ -6,7 +6,7 @@ module Capybara
|
|
6
6
|
include ::RSpec::Matchers::Composable
|
7
7
|
|
8
8
|
def and(matcher)
|
9
|
-
|
9
|
+
And.new(self, matcher)
|
10
10
|
end
|
11
11
|
|
12
12
|
def and_then(matcher)
|
@@ -14,7 +14,7 @@ module Capybara
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def or(matcher)
|
17
|
-
|
17
|
+
Or.new(self, matcher)
|
18
18
|
end
|
19
19
|
|
20
20
|
class CapybaraEvaluator
|
@@ -44,7 +44,7 @@ module Capybara
|
|
44
44
|
raise ::Capybara::ElementNotFound unless [matcher_1_matches?, matcher_2_matches?].all?
|
45
45
|
true
|
46
46
|
end
|
47
|
-
rescue
|
47
|
+
rescue StandardError
|
48
48
|
false
|
49
49
|
end
|
50
50
|
end
|
@@ -72,7 +72,7 @@ module Capybara
|
|
72
72
|
raise ::Capybara::ElementNotFound unless [matcher_1_matches?, matcher_2_matches?].any?
|
73
73
|
true
|
74
74
|
end
|
75
|
-
rescue
|
75
|
+
rescue StandardError
|
76
76
|
false
|
77
77
|
end
|
78
78
|
end
|
@@ -25,14 +25,14 @@ module Capybara
|
|
25
25
|
yield(wrap(actual))
|
26
26
|
rescue Capybara::ExpectationNotMet => e
|
27
27
|
@failure_message = e.message
|
28
|
-
|
28
|
+
false
|
29
29
|
end
|
30
30
|
|
31
31
|
def wrap_does_not_match?(actual)
|
32
32
|
yield(wrap(actual))
|
33
33
|
rescue Capybara::ExpectationNotMet => e
|
34
34
|
@failure_message_when_negated = e.message
|
35
|
-
|
35
|
+
false
|
36
36
|
end
|
37
37
|
|
38
38
|
def session_query_args
|
data/lib/capybara/selector.rb
CHANGED
@@ -2,16 +2,16 @@
|
|
2
2
|
|
3
3
|
require 'capybara/selector/selector'
|
4
4
|
Capybara::Selector::FilterSet.add(:_field) do
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
5
|
+
node_filter(:checked, :boolean) { |node, value| !(value ^ node.checked?) }
|
6
|
+
node_filter(:unchecked, :boolean) { |node, value| (value ^ node.checked?) }
|
7
|
+
node_filter(:disabled, :boolean, default: false, skip_if: :all) { |node, value| !(value ^ node.disabled?) }
|
8
|
+
node_filter(:multiple, :boolean) { |node, value| !(value ^ node.multiple?) }
|
9
9
|
|
10
10
|
expression_filter(:name) { |xpath, val| xpath[XPath.attr(:name) == val] }
|
11
11
|
expression_filter(:placeholder) { |xpath, val| xpath[XPath.attr(:placeholder) == val] }
|
12
12
|
|
13
13
|
describe do |checked: nil, unchecked: nil, disabled: nil, multiple: nil, **_options|
|
14
|
-
desc, states = ""
|
14
|
+
desc, states = +"", []
|
15
15
|
states << 'checked' if checked || (unchecked == false)
|
16
16
|
states << 'not checked' if unchecked || (checked == false)
|
17
17
|
states << 'disabled' if disabled == true
|
@@ -24,54 +24,19 @@ Capybara::Selector::FilterSet.add(:_field) do
|
|
24
24
|
end
|
25
25
|
|
26
26
|
# rubocop:disable Metrics/BlockLength
|
27
|
-
|
28
|
-
|
29
|
-
##
|
30
|
-
#
|
31
|
-
# Select elements by XPath expression
|
32
|
-
#
|
33
|
-
# @locator An XPath expression
|
34
|
-
#
|
27
|
+
|
35
28
|
Capybara.add_selector(:xpath) do
|
36
29
|
xpath { |xpath| xpath }
|
37
30
|
end
|
38
31
|
|
39
|
-
##
|
40
|
-
#
|
41
|
-
# Select elements by CSS selector
|
42
|
-
#
|
43
|
-
# @locator A CSS selector
|
44
|
-
#
|
45
32
|
Capybara.add_selector(:css) do
|
46
33
|
css { |css| css }
|
47
34
|
end
|
48
35
|
|
49
|
-
##
|
50
|
-
#
|
51
|
-
# Select element by id
|
52
|
-
#
|
53
|
-
# @locator The id of the element to match
|
54
|
-
#
|
55
36
|
Capybara.add_selector(:id) do
|
56
37
|
xpath { |id| XPath.descendant[XPath.attr(:id) == id.to_s] }
|
57
38
|
end
|
58
39
|
|
59
|
-
##
|
60
|
-
#
|
61
|
-
# Select field elements (input [not of type submit, image, or hidden], textarea, select)
|
62
|
-
#
|
63
|
-
# @locator Matches against the id, name, or placeholder
|
64
|
-
# @filter [String] :id Matches the id attribute
|
65
|
-
# @filter [String] :name Matches the name attribute
|
66
|
-
# @filter [String] :placeholder Matches the placeholder attribute
|
67
|
-
# @filter [String] :type Matches the type attribute of the field or element type for 'textarea' and 'select'
|
68
|
-
# @filter [Boolean] :readonly
|
69
|
-
# @filter [String] :with Matches the current value of the field
|
70
|
-
# @filter [String, Array<String>] :class Matches the class(es) provided
|
71
|
-
# @filter [Boolean] :checked Match checked fields?
|
72
|
-
# @filter [Boolean] :unchecked Match unchecked fields?
|
73
|
-
# @filter [Boolean] :disabled Match disabled field?
|
74
|
-
# @filter [Boolean] :multiple Match fields that accept multiple values
|
75
40
|
Capybara.add_selector(:field) do
|
76
41
|
xpath do |locator, **options|
|
77
42
|
xpath = XPath.descendant(:input, :textarea, :select)[!XPath.attr(:type).one_of('submit', 'image', 'hidden')]
|
@@ -89,12 +54,12 @@ Capybara.add_selector(:field) do
|
|
89
54
|
|
90
55
|
filter_set(:_field) # checked/unchecked/disabled/multiple/name/placeholder
|
91
56
|
|
92
|
-
|
93
|
-
|
57
|
+
node_filter(:readonly, :boolean) { |node, value| !(value ^ node.readonly?) }
|
58
|
+
node_filter(:with) do |node, with|
|
94
59
|
with.is_a?(Regexp) ? node.value =~ with : node.value == with.to_s
|
95
60
|
end
|
96
61
|
describe do |type: nil, **options|
|
97
|
-
desc = ""
|
62
|
+
desc = +""
|
98
63
|
(expression_filters.keys - [:type]).each { |ef| desc << " with #{ef} #{options[ef]}" if options.key?(ef) }
|
99
64
|
desc << " of type #{type.inspect}" if type
|
100
65
|
desc << " with value #{options[:with].to_s.inspect}" if options.key?(:with)
|
@@ -102,16 +67,6 @@ Capybara.add_selector(:field) do
|
|
102
67
|
end
|
103
68
|
end
|
104
69
|
|
105
|
-
##
|
106
|
-
#
|
107
|
-
# Select fieldset elements
|
108
|
-
#
|
109
|
-
# @locator Matches id or contents of wrapped legend
|
110
|
-
#
|
111
|
-
# @filter [String] :id Matches id attribute
|
112
|
-
# @filter [String] :legend Matches contents of wrapped legend
|
113
|
-
# @filter [String, Array<String>] :class Matches the class(es) provided
|
114
|
-
#
|
115
70
|
Capybara.add_selector(:fieldset) do
|
116
71
|
xpath(:legend) do |locator, legend: nil, **_options|
|
117
72
|
xpath = XPath.descendant(:fieldset)
|
@@ -121,18 +76,6 @@ Capybara.add_selector(:fieldset) do
|
|
121
76
|
end
|
122
77
|
end
|
123
78
|
|
124
|
-
##
|
125
|
-
#
|
126
|
-
# Find links ( <a> elements with an href attribute )
|
127
|
-
#
|
128
|
-
# @locator Matches the id or title attributes, or the string content of the link, or the alt attribute of a contained img element
|
129
|
-
#
|
130
|
-
# @filter [String] :id Matches the id attribute
|
131
|
-
# @filter [String] :title Matches the title attribute
|
132
|
-
# @filter [String] :alt Matches the alt attribute of a contained img element
|
133
|
-
# @filter [String] :class Matches the class(es) provided
|
134
|
-
# @filter [String, Regexp,nil] :href Matches the normalized href of the link, if nil will find <a> elements with no href attribute
|
135
|
-
#
|
136
79
|
Capybara.add_selector(:link) do
|
137
80
|
xpath(:title, :alt) do |locator, href: true, enable_aria_label: false, alt: nil, title: nil, **_options|
|
138
81
|
xpath = XPath.descendant(:a)
|
@@ -154,9 +97,9 @@ Capybara.add_selector(:link) do
|
|
154
97
|
matchers = [XPath.attr(:id) == locator,
|
155
98
|
XPath.string.n.is(locator),
|
156
99
|
XPath.attr(:title).is(locator),
|
157
|
-
XPath.descendant(:img)[XPath.attr(:alt).is(locator)]]
|
158
|
-
matchers
|
159
|
-
xpath = xpath[matchers]
|
100
|
+
XPath.descendant(:img)[XPath.attr(:alt).is(locator)]]
|
101
|
+
matchers << XPath.attr(:'aria-label').is(locator) if enable_aria_label
|
102
|
+
xpath = xpath[matchers.reduce(:|)]
|
160
103
|
end
|
161
104
|
|
162
105
|
xpath = xpath[find_by_attr(:title, title)]
|
@@ -164,31 +107,20 @@ Capybara.add_selector(:link) do
|
|
164
107
|
xpath
|
165
108
|
end
|
166
109
|
|
167
|
-
|
110
|
+
node_filter(:href) do |node, href|
|
168
111
|
# If not a Regexp it's been handled in the main XPath
|
169
112
|
href.is_a?(Regexp) ? node[:href].match(href) : true
|
170
113
|
end
|
171
114
|
|
172
115
|
describe do |**options|
|
173
|
-
desc = ""
|
116
|
+
desc = +""
|
174
117
|
desc << " with href #{options[:href].inspect}" if options[:href]
|
175
118
|
desc << " with no href attribute" if options.fetch(:href, true).nil?
|
176
119
|
end
|
177
120
|
end
|
178
121
|
|
179
|
-
##
|
180
|
-
#
|
181
|
-
# Find buttons ( input [of type submit, reset, image, button] or button elements )
|
182
|
-
#
|
183
|
-
# @locator Matches the id, value, or title attributes, string content of a button, or the alt attribute of an image type button or of a descendant image of a button
|
184
|
-
#
|
185
|
-
# @filter [String] :id Matches the id attribute
|
186
|
-
# @filter [String] :title Matches the title attribute
|
187
|
-
# @filter [String] :class Matches the class(es) provided
|
188
|
-
# @filter [String] :value Matches the value of an input button
|
189
|
-
#
|
190
122
|
Capybara.add_selector(:button) do
|
191
|
-
xpath(:value, :title, :type) do |locator, **options|
|
123
|
+
xpath(:value, :title, :type) do |locator, enable_aria_label: false, **options|
|
192
124
|
input_btn_xpath = XPath.descendant(:input)[XPath.attr(:type).one_of('submit', 'reset', 'image', 'button')]
|
193
125
|
btn_xpath = XPath.descendant(:button)
|
194
126
|
image_btn_xpath = XPath.descendant(:input)[XPath.attr(:type) == 'image']
|
@@ -196,14 +128,14 @@ Capybara.add_selector(:button) do
|
|
196
128
|
unless locator.nil?
|
197
129
|
locator = locator.to_s
|
198
130
|
locator_matches = XPath.attr(:id).equals(locator) | XPath.attr(:value).is(locator) | XPath.attr(:title).is(locator)
|
199
|
-
locator_matches |= XPath.attr(:'aria-label').is(locator) if
|
131
|
+
locator_matches |= XPath.attr(:'aria-label').is(locator) if enable_aria_label
|
200
132
|
|
201
133
|
input_btn_xpath = input_btn_xpath[locator_matches]
|
202
134
|
|
203
135
|
btn_xpath = btn_xpath[locator_matches | XPath.string.n.is(locator) | XPath.descendant(:img)[XPath.attr(:alt).is(locator)]]
|
204
136
|
|
205
137
|
alt_matches = XPath.attr(:alt).is(locator)
|
206
|
-
alt_matches |= XPath.attr(:'aria-label').is(locator) if
|
138
|
+
alt_matches |= XPath.attr(:'aria-label').is(locator) if enable_aria_label
|
207
139
|
image_btn_xpath = image_btn_xpath[alt_matches]
|
208
140
|
end
|
209
141
|
|
@@ -214,45 +146,27 @@ Capybara.add_selector(:button) do
|
|
214
146
|
res_xpath
|
215
147
|
end
|
216
148
|
|
217
|
-
|
149
|
+
node_filter(:disabled, :boolean, default: false, skip_if: :all) { |node, value| !(value ^ node.disabled?) }
|
218
150
|
|
219
151
|
describe do |disabled: nil, **options|
|
220
|
-
desc = ""
|
152
|
+
desc = +""
|
221
153
|
desc << " that is disabled" if disabled == true
|
222
154
|
desc << describe_all_expression_filters(options)
|
223
155
|
desc
|
224
156
|
end
|
225
157
|
end
|
226
158
|
|
227
|
-
##
|
228
|
-
#
|
229
|
-
# Find links or buttons
|
230
|
-
#
|
231
159
|
Capybara.add_selector(:link_or_button) do
|
232
160
|
label "link or button"
|
233
161
|
xpath do |locator, **options|
|
234
162
|
self.class.all.values_at(:link, :button).map { |selector| selector.xpath.call(locator, options) }.reduce(:union)
|
235
163
|
end
|
236
164
|
|
237
|
-
|
165
|
+
node_filter(:disabled, :boolean, default: false, skip_if: :all) { |node, value| node.tag_name == "a" || !(value ^ node.disabled?) }
|
238
166
|
|
239
167
|
describe { |disabled: nil, **_options| " that is disabled" if disabled == true }
|
240
168
|
end
|
241
169
|
|
242
|
-
##
|
243
|
-
#
|
244
|
-
# Find text fillable fields ( textarea, input [not of type submit, image, radio, checkbox, hidden, file] )
|
245
|
-
#
|
246
|
-
# @locator Matches against the id, name, or placeholder
|
247
|
-
# @filter [String] :id Matches the id attribute
|
248
|
-
# @filter [String] :name Matches the name attribute
|
249
|
-
# @filter [String] :placeholder Matches the placeholder attribute
|
250
|
-
# @filter [String] :with Matches the current value of the field
|
251
|
-
# @filter [String] :type Matches the type attribute of the field or element type for 'textarea'
|
252
|
-
# @filter [String, Array<String>] :class Matches the class(es) provided
|
253
|
-
# @filter [Boolean] :disabled Match disabled field?
|
254
|
-
# @filter [Boolean] :multiple Match fields that accept multiple values
|
255
|
-
#
|
256
170
|
Capybara.add_selector(:fillable_field) do
|
257
171
|
label "field"
|
258
172
|
|
@@ -272,31 +186,18 @@ Capybara.add_selector(:fillable_field) do
|
|
272
186
|
|
273
187
|
filter_set(:_field, %i[disabled multiple name placeholder])
|
274
188
|
|
275
|
-
|
189
|
+
node_filter(:with) do |node, with|
|
276
190
|
with.is_a?(Regexp) ? node.value =~ with : node.value == with.to_s
|
277
191
|
end
|
278
192
|
|
279
193
|
describe do |options|
|
280
|
-
desc = ""
|
194
|
+
desc = +""
|
281
195
|
desc << describe_all_expression_filters(options)
|
282
196
|
desc << " with value #{options[:with].to_s.inspect}" if options.key?(:with)
|
283
197
|
desc
|
284
198
|
end
|
285
199
|
end
|
286
200
|
|
287
|
-
##
|
288
|
-
#
|
289
|
-
# Find radio buttons
|
290
|
-
#
|
291
|
-
# @locator Match id, name, or associated label text
|
292
|
-
# @filter [String] :id Matches the id attribute
|
293
|
-
# @filter [String] :name Matches the name attribute
|
294
|
-
# @filter [String, Array<String>] :class Matches the class(es) provided
|
295
|
-
# @filter [Boolean] :checked Match checked fields?
|
296
|
-
# @filter [Boolean] :unchecked Match unchecked fields?
|
297
|
-
# @filter [Boolean] :disabled Match disabled field?
|
298
|
-
# @filter [String] :option Match the value
|
299
|
-
#
|
300
201
|
Capybara.add_selector(:radio_button) do
|
301
202
|
label "radio button"
|
302
203
|
|
@@ -307,29 +208,16 @@ Capybara.add_selector(:radio_button) do
|
|
307
208
|
|
308
209
|
filter_set(:_field, %i[checked unchecked disabled name])
|
309
210
|
|
310
|
-
|
211
|
+
node_filter(:option) { |node, value| node.value == value.to_s }
|
311
212
|
|
312
213
|
describe do |option: nil, **options|
|
313
|
-
desc = ""
|
214
|
+
desc = +""
|
314
215
|
desc << " with value #{option.inspect}" if option
|
315
216
|
desc << describe_all_expression_filters(options)
|
316
217
|
desc
|
317
218
|
end
|
318
219
|
end
|
319
220
|
|
320
|
-
##
|
321
|
-
#
|
322
|
-
# Find checkboxes
|
323
|
-
#
|
324
|
-
# @locator Match id, name, or associated label text
|
325
|
-
# @filter [String] :id Matches the id attribute
|
326
|
-
# @filter [String] :name Matches the name attribute
|
327
|
-
# @filter [String, Array<String>] :class Matches the class(es) provided
|
328
|
-
# @filter [Boolean] :checked Match checked fields?
|
329
|
-
# @filter [Boolean] :unchecked Match unchecked fields?
|
330
|
-
# @filter [Boolean] :disabled Match disabled field?
|
331
|
-
# @filter [String] :option Match the value
|
332
|
-
#
|
333
221
|
Capybara.add_selector(:checkbox) do
|
334
222
|
xpath do |locator, **options|
|
335
223
|
xpath = XPath.descendant(:input)[XPath.attr(:type) == 'checkbox']
|
@@ -338,32 +226,16 @@ Capybara.add_selector(:checkbox) do
|
|
338
226
|
|
339
227
|
filter_set(:_field, %i[checked unchecked disabled name])
|
340
228
|
|
341
|
-
|
229
|
+
node_filter(:option) { |node, value| node.value == value.to_s }
|
342
230
|
|
343
231
|
describe do |option: nil, **options|
|
344
|
-
desc = ""
|
232
|
+
desc = +""
|
345
233
|
desc << " with value #{option.inspect}" if option
|
346
234
|
desc << describe_all_expression_filters(options)
|
347
235
|
desc
|
348
236
|
end
|
349
237
|
end
|
350
238
|
|
351
|
-
##
|
352
|
-
#
|
353
|
-
# Find select elements
|
354
|
-
#
|
355
|
-
# @locator Match id, name, placeholder, or associated label text
|
356
|
-
# @filter [String] :id Matches the id attribute
|
357
|
-
# @filter [String] :name Matches the name attribute
|
358
|
-
# @filter [String] :placeholder Matches the placeholder attribute
|
359
|
-
# @filter [String, Array<String>] :class Matches the class(es) provided
|
360
|
-
# @filter [Boolean] :disabled Match disabled field?
|
361
|
-
# @filter [Boolean] :multiple Match fields that accept multiple values
|
362
|
-
# @filter [Array<String>] :options Exact match options
|
363
|
-
# @filter [Array<String>] :with_options Partial match options
|
364
|
-
# @filter [String, Array<String>] :selected Match the selection(s)
|
365
|
-
# @filter [String, Array<String>] :with_selected Partial match the selection(s)
|
366
|
-
#
|
367
239
|
Capybara.add_selector(:select) do
|
368
240
|
label "select box"
|
369
241
|
|
@@ -374,7 +246,7 @@ Capybara.add_selector(:select) do
|
|
374
246
|
|
375
247
|
filter_set(:_field, %i[disabled multiple name placeholder])
|
376
248
|
|
377
|
-
|
249
|
+
node_filter(:options) do |node, options|
|
378
250
|
actual = if node.visible?
|
379
251
|
node.all(:xpath, './/option', wait: false).map(&:text)
|
380
252
|
else
|
@@ -384,24 +256,23 @@ Capybara.add_selector(:select) do
|
|
384
256
|
end
|
385
257
|
|
386
258
|
expression_filter(:with_options) do |expr, options|
|
387
|
-
options.
|
388
|
-
|
259
|
+
options.inject(expr) do |xpath, option|
|
260
|
+
xpath[Capybara::Selector.all[:option].call(option)]
|
389
261
|
end
|
390
|
-
expr
|
391
262
|
end
|
392
263
|
|
393
|
-
|
264
|
+
node_filter(:selected) do |node, selected|
|
394
265
|
actual = node.all(:xpath, './/option', visible: false, wait: false).select(&:selected?).map { |option| option.text(:all) }
|
395
266
|
Array(selected).sort == actual.sort
|
396
267
|
end
|
397
268
|
|
398
|
-
|
269
|
+
node_filter(:with_selected) do |node, selected|
|
399
270
|
actual = node.all(:xpath, './/option', visible: false, wait: false).select(&:selected?).map { |option| option.text(:all) }
|
400
271
|
(Array(selected) - actual).empty?
|
401
272
|
end
|
402
273
|
|
403
274
|
describe do |options: nil, with_options: nil, selected: nil, with_selected: nil, **opts|
|
404
|
-
desc = ""
|
275
|
+
desc = +""
|
405
276
|
desc << " with options #{options.inspect}" if options
|
406
277
|
desc << " with at least options #{with_options.inspect}" if with_options
|
407
278
|
desc << " with #{selected.inspect} selected" if selected
|
@@ -421,20 +292,19 @@ Capybara.add_selector(:datalist_input) do
|
|
421
292
|
|
422
293
|
filter_set(:_field, %i[disabled name placeholder])
|
423
294
|
|
424
|
-
|
295
|
+
node_filter(:options) do |node, options|
|
425
296
|
actual = node.find("//datalist[@id=#{node[:list]}]", visible: :all).all(:datalist_option, wait: false).map(&:value)
|
426
297
|
options.sort == actual.sort
|
427
298
|
end
|
428
299
|
|
429
300
|
expression_filter(:with_options) do |expr, options|
|
430
|
-
options.
|
431
|
-
|
301
|
+
options.inject(expr) do |xpath, option|
|
302
|
+
xpath[XPath.attr(:list) == XPath.anywhere(:datalist)[Capybara::Selector.all[:datalist_option].call(option)].attr(:id)]
|
432
303
|
end
|
433
|
-
expr
|
434
304
|
end
|
435
305
|
|
436
306
|
describe do |options: nil, with_options: nil, **opts|
|
437
|
-
desc = ""
|
307
|
+
desc = +""
|
438
308
|
desc << " with options #{options.inspect}" if options
|
439
309
|
desc << " with at least options #{with_options.inspect}" if with_options
|
440
310
|
desc << describe_all_expression_filters(opts)
|
@@ -442,14 +312,6 @@ Capybara.add_selector(:datalist_input) do
|
|
442
312
|
end
|
443
313
|
end
|
444
314
|
|
445
|
-
##
|
446
|
-
#
|
447
|
-
# Find option elements
|
448
|
-
#
|
449
|
-
# @locator Match text of option
|
450
|
-
# @filter [Boolean] :disabled Match disabled option
|
451
|
-
# @filter [Boolean] :selected Match selected option
|
452
|
-
#
|
453
315
|
Capybara.add_selector(:option) do
|
454
316
|
xpath do |locator|
|
455
317
|
xpath = XPath.descendant(:option)
|
@@ -457,11 +319,11 @@ Capybara.add_selector(:option) do
|
|
457
319
|
xpath
|
458
320
|
end
|
459
321
|
|
460
|
-
|
461
|
-
|
322
|
+
node_filter(:disabled, :boolean) { |node, value| !(value ^ node.disabled?) }
|
323
|
+
node_filter(:selected, :boolean) { |node, value| !(value ^ node.selected?) }
|
462
324
|
|
463
325
|
describe do |**options|
|
464
|
-
desc = ""
|
326
|
+
desc = +""
|
465
327
|
desc << " that is#{' not' unless options[:disabled]} disabled" if options.key?(:disabled)
|
466
328
|
desc << " that is#{' not' unless options[:selected]} selected" if options.key?(:selected)
|
467
329
|
desc
|
@@ -478,26 +340,15 @@ Capybara.add_selector(:datalist_option) do
|
|
478
340
|
xpath
|
479
341
|
end
|
480
342
|
|
481
|
-
|
343
|
+
node_filter(:disabled, :boolean) { |node, value| !(value ^ node.disabled?) }
|
482
344
|
|
483
345
|
describe do |**options|
|
484
|
-
desc = ""
|
346
|
+
desc = +""
|
485
347
|
desc << " that is#{' not' unless options[:disabled]} disabled" if options.key?(:disabled)
|
486
348
|
desc
|
487
349
|
end
|
488
350
|
end
|
489
351
|
|
490
|
-
##
|
491
|
-
#
|
492
|
-
# Find file input elements
|
493
|
-
#
|
494
|
-
# @locator Match id, name, or associated label text
|
495
|
-
# @filter [String] :id Matches the id attribute
|
496
|
-
# @filter [String] :name Matches the name attribute
|
497
|
-
# @filter [String, Array<String>] :class Matches the class(es) provided
|
498
|
-
# @filter [Boolean] :disabled Match disabled field?
|
499
|
-
# @filter [Boolean] :multiple Match field that accepts multiple values
|
500
|
-
#
|
501
352
|
Capybara.add_selector(:file_field) do
|
502
353
|
label "file field"
|
503
354
|
xpath do |locator, options|
|
@@ -508,19 +359,12 @@ Capybara.add_selector(:file_field) do
|
|
508
359
|
filter_set(:_field, %i[disabled multiple name])
|
509
360
|
|
510
361
|
describe do |**options|
|
511
|
-
desc = ""
|
362
|
+
desc = +""
|
512
363
|
desc << describe_all_expression_filters(options)
|
513
364
|
desc
|
514
365
|
end
|
515
366
|
end
|
516
367
|
|
517
|
-
##
|
518
|
-
#
|
519
|
-
# Find label elements
|
520
|
-
#
|
521
|
-
# @locator Match id or text contents
|
522
|
-
# @filter [Element, String] :for The element or id of the element associated with the label
|
523
|
-
#
|
524
368
|
Capybara.add_selector(:label) do
|
525
369
|
label "label"
|
526
370
|
xpath(:for) do |locator, options|
|
@@ -536,7 +380,7 @@ Capybara.add_selector(:label) do
|
|
536
380
|
xpath
|
537
381
|
end
|
538
382
|
|
539
|
-
|
383
|
+
node_filter(:for) do |node, field_or_value|
|
540
384
|
if field_or_value.is_a? Capybara::Node::Element
|
541
385
|
if node[:for]
|
542
386
|
field_or_value[:id] == node[:for]
|
@@ -544,51 +388,32 @@ Capybara.add_selector(:label) do
|
|
544
388
|
field_or_value.find_xpath('./ancestor::label[1]').include? node.base
|
545
389
|
end
|
546
390
|
else
|
547
|
-
# Non element values were handled through the expression filter
|
548
|
-
true
|
391
|
+
true # Non element values were handled through the expression filter
|
549
392
|
end
|
550
393
|
end
|
551
394
|
|
552
395
|
describe do |**options|
|
553
|
-
desc = ""
|
396
|
+
desc = +""
|
554
397
|
desc << " for #{options[:for]}" if options[:for]
|
555
398
|
desc
|
556
399
|
end
|
557
400
|
end
|
558
401
|
|
559
|
-
##
|
560
|
-
#
|
561
|
-
# Find table elements
|
562
|
-
#
|
563
|
-
# @locator id or caption text of table
|
564
|
-
# @filter [String] :id Match id attribute of table
|
565
|
-
# @filter [String] :caption Match text of associated caption
|
566
|
-
# @filter [String, Array<String>] :class Matches the class(es) provided
|
567
|
-
#
|
568
402
|
Capybara.add_selector(:table) do
|
569
|
-
xpath(:caption) do |locator,
|
403
|
+
xpath(:caption) do |locator, caption: nil, **_options|
|
570
404
|
xpath = XPath.descendant(:table)
|
571
405
|
xpath = xpath[(XPath.attr(:id) == locator.to_s) | XPath.descendant(:caption).is(locator.to_s)] unless locator.nil?
|
572
|
-
xpath = xpath[XPath.descendant(:caption) ==
|
406
|
+
xpath = xpath[XPath.descendant(:caption) == caption] if caption
|
573
407
|
xpath
|
574
408
|
end
|
575
409
|
|
576
410
|
describe do |caption: nil, **_options|
|
577
|
-
desc = ""
|
411
|
+
desc = +""
|
578
412
|
desc << " with caption #{caption}" if caption
|
579
413
|
desc
|
580
414
|
end
|
581
415
|
end
|
582
416
|
|
583
|
-
##
|
584
|
-
#
|
585
|
-
# Find frame/iframe elements
|
586
|
-
#
|
587
|
-
# @locator Match id or name
|
588
|
-
# @filter [String] :id Match id attribute
|
589
|
-
# @filter [String] :name Match name attribute
|
590
|
-
# @filter [String, Array<String>] :class Matches the class(es) provided
|
591
|
-
#
|
592
417
|
Capybara.add_selector(:frame) do
|
593
418
|
xpath(:name) do |locator, **options|
|
594
419
|
xpath = XPath.descendant(:iframe).union(XPath.descendant(:frame))
|
@@ -598,11 +423,36 @@ Capybara.add_selector(:frame) do
|
|
598
423
|
end
|
599
424
|
|
600
425
|
describe do |name: nil, **_options|
|
601
|
-
desc = ""
|
426
|
+
desc = +""
|
602
427
|
desc << " with name #{name}" if name
|
603
428
|
desc
|
604
429
|
end
|
605
430
|
end
|
606
431
|
|
432
|
+
Capybara.add_selector(:element) do
|
433
|
+
xpath do |locator, **_options|
|
434
|
+
XPath.descendant((locator || '@').to_sym)
|
435
|
+
end
|
436
|
+
|
437
|
+
expression_filter(:attributes, matcher: /.+/) do |xpath, name, val|
|
438
|
+
case val
|
439
|
+
when Regexp
|
440
|
+
xpath
|
441
|
+
when XPath::Expression
|
442
|
+
xpath[XPath.attr(name)[val]]
|
443
|
+
else
|
444
|
+
xpath[XPath.attr(name.to_sym) == val]
|
445
|
+
end
|
446
|
+
end
|
447
|
+
|
448
|
+
node_filter(:attributes, matcher: /.+/) do |node, name, val|
|
449
|
+
val.is_a?(Regexp) ? node[name] =~ val : true
|
450
|
+
end
|
451
|
+
|
452
|
+
describe do |**options|
|
453
|
+
desc = +""
|
454
|
+
desc << describe_all_expression_filters(options)
|
455
|
+
desc
|
456
|
+
end
|
457
|
+
end
|
607
458
|
# rubocop:enable Metrics/BlockLength
|
608
|
-
# rubocop:enable Metrics/ParameterLists
|