capybara 3.1.1 → 3.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
@@ -5,7 +5,7 @@ module Capybara
|
|
5
5
|
class CSS
|
6
6
|
def self.escape(str)
|
7
7
|
value = str.dup
|
8
|
-
out = ""
|
8
|
+
out = +""
|
9
9
|
out << value.slice!(0...1) if value =~ /^[-_]/
|
10
10
|
out << (value[0] =~ NMSTART ? value.slice!(0...1) : escape_char(value.slice!(0...1)))
|
11
11
|
out << value.gsub(/[^a-zA-Z0-9_-]/) { |c| escape_char c }
|
@@ -16,7 +16,7 @@ module Capybara
|
|
16
16
|
c =~ %r{[ -/:-~]} ? "\\#{c}" : format("\\%06x", c.ord)
|
17
17
|
end
|
18
18
|
|
19
|
-
S = '\u{80}-\u{D7FF}\u{E000}-\u{FFFD}\u{10000}-\u{10FFFF}'
|
19
|
+
S = '\u{80}-\u{D7FF}\u{E000}-\u{FFFD}\u{10000}-\u{10FFFF}'
|
20
20
|
H = /[0-9a-fA-F]/
|
21
21
|
UNICODE = /\\#{H}{1,6}[ \t\r\n\f]?/
|
22
22
|
NONASCII = /[#{S}]/
|
@@ -5,17 +5,20 @@ require 'capybara/selector/filter'
|
|
5
5
|
module Capybara
|
6
6
|
class Selector
|
7
7
|
class FilterSet
|
8
|
-
attr_reader :descriptions
|
8
|
+
attr_reader :descriptions, :node_filters, :expression_filters
|
9
9
|
|
10
10
|
def initialize(name, &block)
|
11
11
|
@name = name
|
12
12
|
@descriptions = []
|
13
|
+
@expression_filters = {}
|
14
|
+
@node_filters = {}
|
13
15
|
instance_eval(&block)
|
14
16
|
end
|
15
17
|
|
16
|
-
def
|
18
|
+
def node_filter(name, *types_and_options, &block)
|
17
19
|
add_filter(name, Filters::NodeFilter, *types_and_options, &block)
|
18
20
|
end
|
21
|
+
alias_method :filter, :node_filter
|
19
22
|
|
20
23
|
def expression_filter(name, *types_and_options, &block)
|
21
24
|
add_filter(name, Filters::ExpressionFilter, *types_and_options, &block)
|
@@ -27,21 +30,7 @@ module Capybara
|
|
27
30
|
|
28
31
|
def description(**options)
|
29
32
|
opts = options_with_defaults(options)
|
30
|
-
@descriptions.map
|
31
|
-
desc.call(opts).to_s
|
32
|
-
end.join
|
33
|
-
end
|
34
|
-
|
35
|
-
def filters
|
36
|
-
@filters ||= {}
|
37
|
-
end
|
38
|
-
|
39
|
-
def node_filters
|
40
|
-
filters.reject { |_n, f| f.nil? || f.is_a?(Filters::ExpressionFilter) }.freeze
|
41
|
-
end
|
42
|
-
|
43
|
-
def expression_filters
|
44
|
-
filters.select { |_n, f| f.nil? || f.is_a?(Filters::ExpressionFilter) }.freeze
|
33
|
+
@descriptions.map { |desc| desc.call(opts).to_s }.join
|
45
34
|
end
|
46
35
|
|
47
36
|
class << self
|
@@ -62,15 +51,22 @@ module Capybara
|
|
62
51
|
|
63
52
|
def options_with_defaults(options)
|
64
53
|
options = options.dup
|
65
|
-
|
66
|
-
|
54
|
+
[expression_filters, node_filters].each do |filters|
|
55
|
+
filters.select { |_n, f| f.default? }.each do |name, filter|
|
56
|
+
options[name] = filter.default unless options.key?(name)
|
57
|
+
end
|
67
58
|
end
|
68
59
|
options
|
69
60
|
end
|
70
61
|
|
71
|
-
def add_filter(name, filter_class, *types, **options, &block)
|
62
|
+
def add_filter(name, filter_class, *types, matcher: nil, **options, &block)
|
72
63
|
types.each { |k| options[k] = true }
|
73
|
-
filters
|
64
|
+
raise "ArgumentError", ":default option is not supported for filters with a :matcher option" if matcher && options[:default]
|
65
|
+
if filter_class <= Filters::ExpressionFilter
|
66
|
+
@expression_filters[name] = filter_class.new(name, matcher, block, options)
|
67
|
+
else
|
68
|
+
@node_filters[name] = filter_class.new(name, matcher, block, options)
|
69
|
+
end
|
74
70
|
end
|
75
71
|
end
|
76
72
|
end
|
@@ -4,8 +4,9 @@ module Capybara
|
|
4
4
|
class Selector
|
5
5
|
module Filters
|
6
6
|
class Base
|
7
|
-
def initialize(name, block, **options)
|
7
|
+
def initialize(name, matcher, block, **options)
|
8
8
|
@name = name
|
9
|
+
@matcher = matcher
|
9
10
|
@block = block
|
10
11
|
@options = options
|
11
12
|
@options[:valid_values] = [true, false] if options[:boolean]
|
@@ -23,8 +24,30 @@ module Capybara
|
|
23
24
|
@options.key?(:skip_if) && value == @options[:skip_if]
|
24
25
|
end
|
25
26
|
|
27
|
+
def matcher?
|
28
|
+
!@matcher.nil?
|
29
|
+
end
|
30
|
+
|
31
|
+
def handles_option?(option_name)
|
32
|
+
if matcher?
|
33
|
+
option_name =~ @matcher
|
34
|
+
else
|
35
|
+
@name == option_name
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
26
39
|
private
|
27
40
|
|
41
|
+
def apply(subject, name, value, skip_value)
|
42
|
+
return skip_value if skip?(value)
|
43
|
+
raise ArgumentError, "Invalid value #{value.inspect} passed to #{self.class.name.split('::').last} #{name}#{" : #{@name}" if @name.is_a?(Regexp)}" unless valid_value?(value)
|
44
|
+
if @block.arity == 2
|
45
|
+
@block.call(subject, value)
|
46
|
+
else
|
47
|
+
@block.call(subject, name, value)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
28
51
|
def valid_value?(value)
|
29
52
|
!@options.key?(:valid_values) || Array(@options[:valid_values]).include?(value)
|
30
53
|
end
|
@@ -6,17 +6,15 @@ module Capybara
|
|
6
6
|
class Selector
|
7
7
|
module Filters
|
8
8
|
class ExpressionFilter < Base
|
9
|
-
def apply_filter(expr, value)
|
10
|
-
|
11
|
-
raise "ArgumentError", "Invalid value #{value.inspect} passed to expression filter #{@name}" unless valid_value?(value)
|
12
|
-
@block.call(expr, value)
|
9
|
+
def apply_filter(expr, name, value)
|
10
|
+
apply(expr, name, value, expr)
|
13
11
|
end
|
14
12
|
end
|
15
13
|
|
16
14
|
class IdentityExpressionFilter < ExpressionFilter
|
17
15
|
def initialize; end
|
18
16
|
def default?; false; end
|
19
|
-
def apply_filter(expr, _value); expr; end
|
17
|
+
def apply_filter(expr, _name, _value); expr; end
|
20
18
|
end
|
21
19
|
end
|
22
20
|
end
|
@@ -6,10 +6,10 @@ module Capybara
|
|
6
6
|
class Selector
|
7
7
|
module Filters
|
8
8
|
class NodeFilter < Base
|
9
|
-
def matches?(node, value)
|
10
|
-
|
11
|
-
|
12
|
-
|
9
|
+
def matches?(node, name, value)
|
10
|
+
apply(node, name, value, true)
|
11
|
+
rescue Capybara::ElementNotFound
|
12
|
+
false
|
13
13
|
end
|
14
14
|
end
|
15
15
|
end
|
@@ -1,27 +1,165 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# rubocop:disable Style/AsciiComments
|
4
|
+
|
3
5
|
require 'capybara/selector/filter_set'
|
4
6
|
require 'capybara/selector/css'
|
5
|
-
require 'xpath'
|
6
|
-
|
7
|
-
# Patch XPath to allow a nil condition in where
|
8
|
-
module XPath
|
9
|
-
class Renderer
|
10
|
-
undef :where if method_defined?(:where)
|
11
|
-
def where(on, condition)
|
12
|
-
condition = condition.to_s
|
13
|
-
if !condition.empty?
|
14
|
-
"#{on}[#{condition}]"
|
15
|
-
else
|
16
|
-
on.to_s
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
7
|
|
22
8
|
module Capybara
|
9
|
+
#
|
10
|
+
# ## Built-in Selectors
|
11
|
+
#
|
12
|
+
# * **:xpath** - Select elements by XPath expression
|
13
|
+
# * Locator: An XPath expression
|
14
|
+
#
|
15
|
+
# * **:css** - Select elements by CSS selector
|
16
|
+
# * Locator: A CSS selector
|
17
|
+
#
|
18
|
+
# * **:id** - Select element by id
|
19
|
+
# * Locator: The id of the element to match
|
20
|
+
#
|
21
|
+
# * **:field** - Select field elements (input [not of type submit, image, or hidden], textarea, select)
|
22
|
+
# * Locator: Matches against the id, name, or placeholder
|
23
|
+
# * Filters:
|
24
|
+
# * :id (String) — Matches the id attribute
|
25
|
+
# * :name (String) — Matches the name attribute
|
26
|
+
# * :placeholder (String) — Matches the placeholder attribute
|
27
|
+
# * :type (String) — Matches the type attribute of the field or element type for 'textarea' and 'select'
|
28
|
+
# * :readonly (Boolean)
|
29
|
+
# * :with (String) — Matches the current value of the field
|
30
|
+
# * :class (String, Array<String>) — Matches the class(es) provided
|
31
|
+
# * :checked (Boolean) — Match checked fields?
|
32
|
+
# * :unchecked (Boolean) — Match unchecked fields?
|
33
|
+
# * :disabled (Boolean) — Match disabled field?
|
34
|
+
# * :multiple (Boolean) — Match fields that accept multiple values
|
35
|
+
#
|
36
|
+
# * **:fieldset** - Select fieldset elements
|
37
|
+
# * Locator: Matches id or contents of wrapped legend
|
38
|
+
# * Filters:
|
39
|
+
# * :id (String) — Matches id attribute
|
40
|
+
# * :legend (String) — Matches contents of wrapped legend
|
41
|
+
# * :class (String, Array<String>) — Matches the class(es) provided
|
42
|
+
#
|
43
|
+
# * **:link** - Find links ( <a> elements with an href attribute )
|
44
|
+
# * Locator: Matches the id or title attributes, or the string content of the link, or the alt attribute of a contained img element
|
45
|
+
# * Filters:
|
46
|
+
# * :id (String) — Matches the id attribute
|
47
|
+
# * :title (String) — Matches the title attribute
|
48
|
+
# * :alt (String) — Matches the alt attribute of a contained img element
|
49
|
+
# * :class (String) — Matches the class(es) provided
|
50
|
+
# * :href (String, Regexp, nil) — Matches the normalized href of the link, if nil will find <a> elements with no href attribute
|
51
|
+
#
|
52
|
+
# * **:button** - Find buttons ( input [of type submit, reset, image, button] or button elements )
|
53
|
+
# * 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
|
54
|
+
# * Filters:
|
55
|
+
# * :id (String) — Matches the id attribute
|
56
|
+
# * :title (String) — Matches the title attribute
|
57
|
+
# * :class (String) — Matches the class(es) provided
|
58
|
+
# * :value (String) — Matches the value of an input button
|
59
|
+
# * :type
|
60
|
+
#
|
61
|
+
# * **:link_or_button** - Find links or buttons
|
62
|
+
# * Locator: See :link and :button selectors
|
63
|
+
#
|
64
|
+
# * **:fillable_field** - Find text fillable fields ( textarea, input [not of type submit, image, radio, checkbox, hidden, file] )
|
65
|
+
# * Locator: Matches against the id, name, or placeholder
|
66
|
+
# * Filters:
|
67
|
+
# * :id (String) — Matches the id attribute
|
68
|
+
# * :name (String) — Matches the name attribute
|
69
|
+
# * :placeholder (String) — Matches the placeholder attribute
|
70
|
+
# * :with (String) — Matches the current value of the field
|
71
|
+
# * :type (String) — Matches the type attribute of the field or element type for 'textarea'
|
72
|
+
# * :class (String, Array<String>) — Matches the class(es) provided
|
73
|
+
# * :disabled (Boolean) — Match disabled field?
|
74
|
+
# * :multiple (Boolean) — Match fields that accept multiple values
|
75
|
+
#
|
76
|
+
# * **:radio_button** - Find radio buttons
|
77
|
+
# * Locator: Match id, name, or associated label text
|
78
|
+
# * Filters:
|
79
|
+
# * :id (String) — Matches the id attribute
|
80
|
+
# * :name (String) — Matches the name attribute
|
81
|
+
# * :class (String, Array<String>) — Matches the class(es) provided
|
82
|
+
# * :checked (Boolean) — Match checked fields?
|
83
|
+
# * :unchecked (Boolean) — Match unchecked fields?
|
84
|
+
# * :disabled (Boolean) — Match disabled field?
|
85
|
+
# * :option (String) — Match the value
|
86
|
+
#
|
87
|
+
# * **:checkbox** - Find checkboxes
|
88
|
+
# * Locator: Match id, name, or associated label text
|
89
|
+
# * Filters:
|
90
|
+
# * *:id (String) — Matches the id attribute
|
91
|
+
# * *:name (String) — Matches the name attribute
|
92
|
+
# * *:class (String, Array<String>) — Matches the class(es) provided
|
93
|
+
# * *:checked (Boolean) — Match checked fields?
|
94
|
+
# * *:unchecked (Boolean) — Match unchecked fields?
|
95
|
+
# * *:disabled (Boolean) — Match disabled field?
|
96
|
+
# * *:option (String) — Match the value
|
97
|
+
#
|
98
|
+
# * **:select** - Find select elements
|
99
|
+
# * Locator: Match id, name, placeholder, or associated label text
|
100
|
+
# * Filters:
|
101
|
+
# * :id (String) — Matches the id attribute
|
102
|
+
# * :name (String) — Matches the name attribute
|
103
|
+
# * :placeholder (String) — Matches the placeholder attribute
|
104
|
+
# * :class (String, Array<String>) — Matches the class(es) provided
|
105
|
+
# * :disabled (Boolean) — Match disabled field?
|
106
|
+
# * :multiple (Boolean) — Match fields that accept multiple values
|
107
|
+
# * :options (Array<String>) — Exact match options
|
108
|
+
# * :with_options (Array<String>) — Partial match options
|
109
|
+
# * :selected (String, Array<String>) — Match the selection(s)
|
110
|
+
# * :with_selected (String, Array<String>) — Partial match the selection(s)
|
111
|
+
#
|
112
|
+
# * **:option** - Find option elements
|
113
|
+
# * Locator: Match text of option
|
114
|
+
# * Filters:
|
115
|
+
# * :disabled (Boolean) — Match disabled option
|
116
|
+
# * :selected (Boolean) — Match selected option
|
117
|
+
#
|
118
|
+
# * **:datalist_input**
|
119
|
+
# * Locator:
|
120
|
+
# * Filters:
|
121
|
+
# * :disabled
|
122
|
+
# * :name
|
123
|
+
# * :placeholder
|
124
|
+
#
|
125
|
+
# * **:datalist_option**
|
126
|
+
# * Locator:
|
127
|
+
#
|
128
|
+
# * **:file_field** - Find file input elements
|
129
|
+
# * Locator: Match id, name, or associated label text
|
130
|
+
# * Filters:
|
131
|
+
# * :id (String) — Matches the id attribute
|
132
|
+
# * :name (String) — Matches the name attribute
|
133
|
+
# * :class (String, Array<String>) — Matches the class(es) provided
|
134
|
+
# * :disabled (Boolean) — Match disabled field?
|
135
|
+
# * :multiple (Boolean) — Match field that accepts multiple values
|
136
|
+
#
|
137
|
+
# * **:label** - Find label elements
|
138
|
+
# * Locator: Match id or text contents
|
139
|
+
# * Filters:
|
140
|
+
# * :for (Element, String) — The element or id of the element associated with the label
|
141
|
+
#
|
142
|
+
# * **:table** - Find table elements
|
143
|
+
# * Locator: id or caption text of table
|
144
|
+
# * Filters:
|
145
|
+
# * :id (String) — Match id attribute of table
|
146
|
+
# * :caption (String) — Match text of associated caption
|
147
|
+
# * :class (String, Array<String>) — Matches the class(es) provided
|
148
|
+
#
|
149
|
+
# * **:frame** - Find frame/iframe elements
|
150
|
+
# * Locator: Match id or name
|
151
|
+
# * Filters:
|
152
|
+
# * :id (String) — Match id attribute
|
153
|
+
# * :name (String) — Match name attribute
|
154
|
+
# * :class (String, Array<String>) — Matches the class(es) provided
|
155
|
+
#
|
156
|
+
# * **:element**
|
157
|
+
# * Locator: Type of element ('div', 'a', etc) - if not specified defaults to '*'
|
158
|
+
# * Filters: Matches on any element attribute
|
159
|
+
#
|
23
160
|
class Selector
|
24
161
|
attr_reader :name, :format
|
162
|
+
extend Forwardable
|
25
163
|
|
26
164
|
class << self
|
27
165
|
def all
|
@@ -56,7 +194,8 @@ module Capybara
|
|
56
194
|
end
|
57
195
|
|
58
196
|
def custom_filters
|
59
|
-
|
197
|
+
warn "Deprecated: Selector#custom_filters is not valid when same named expression and node filter exist - don't use"
|
198
|
+
node_filters.merge(expression_filters).freeze
|
60
199
|
end
|
61
200
|
|
62
201
|
def node_filters
|
@@ -81,10 +220,10 @@ module Capybara
|
|
81
220
|
# @overload xpath()
|
82
221
|
# @return [#call] The block that will be called to generate the XPath expression
|
83
222
|
#
|
84
|
-
def xpath(*
|
223
|
+
def xpath(*allowed_filters, &block)
|
85
224
|
if block
|
86
225
|
@format, @expression = :xpath, block
|
87
|
-
|
226
|
+
allowed_filters.flatten.each { |ef| expression_filters[ef] = Filters::IdentityExpressionFilter.new }
|
88
227
|
end
|
89
228
|
format == :xpath ? @expression : nil
|
90
229
|
end
|
@@ -103,10 +242,10 @@ module Capybara
|
|
103
242
|
# @overload css()
|
104
243
|
# @return [#call] The block that will be called to generate the CSS selector
|
105
244
|
#
|
106
|
-
def css(*
|
245
|
+
def css(*allowed_filters, &block)
|
107
246
|
if block
|
108
247
|
@format, @expression = :css, block
|
109
|
-
|
248
|
+
allowed_filters.flatten.each { |ef| expression_filters[ef] = nil }
|
110
249
|
end
|
111
250
|
format == :css ? @expression : nil
|
112
251
|
end
|
@@ -143,12 +282,10 @@ module Capybara
|
|
143
282
|
#
|
144
283
|
# Description of the selector
|
145
284
|
#
|
146
|
-
#
|
147
|
-
#
|
148
|
-
#
|
149
|
-
|
150
|
-
@filter_set.description(options)
|
151
|
-
end
|
285
|
+
# @!method description(options)
|
286
|
+
# @param [Hash] options The options of the query used to generate the description
|
287
|
+
# @return [String] Description of the selector when used with the options passed
|
288
|
+
def_delegator :@filter_set, :description
|
152
289
|
|
153
290
|
def call(locator, **options)
|
154
291
|
if format
|
@@ -168,41 +305,58 @@ module Capybara
|
|
168
305
|
# @return [Boolean] Whether or not to use this selector
|
169
306
|
#
|
170
307
|
def match?(locator)
|
171
|
-
@match
|
308
|
+
@match&.call(locator)
|
172
309
|
end
|
173
310
|
|
174
311
|
##
|
175
312
|
#
|
176
|
-
# Define a
|
313
|
+
# Define a node filter for use with this selector
|
177
314
|
#
|
178
|
-
#
|
179
|
-
# @param [Symbol] name The filter name
|
315
|
+
# @!method node_filter(name, *types, options={}, &block)
|
316
|
+
# @param [Symbol, Regexp] name The filter name
|
180
317
|
# @param [Array<Symbol>] types The types of the filter - currently valid types are [:boolean]
|
181
318
|
# @param [Hash] options ({}) Options of the filter
|
182
319
|
# @option options [Array<>] :valid_values Valid values for this filter
|
183
320
|
# @option options :default The default value of the filter (if any)
|
184
321
|
# @option options :skip_if Value of the filter that will cause it to be skipped
|
322
|
+
# @option options [Regexp] :matcher (nil) A Regexp used to check whether a specific option is handled by this filter. If not provided the filter will be used for options matching the filter name.
|
185
323
|
#
|
186
|
-
|
187
|
-
|
188
|
-
|
324
|
+
# If a Symbol is passed for the name the block should accept | node, option_value |, while if a Regexp
|
325
|
+
# is passed for the name the block should accept | node, option_name, option_value |. In either case
|
326
|
+
# the block should return `true` if the node passes the filer or `false` if it doesn't
|
189
327
|
|
190
|
-
|
191
|
-
|
192
|
-
|
328
|
+
# @!method filter
|
329
|
+
# See {Selector#node_filter}
|
330
|
+
|
331
|
+
##
|
332
|
+
#
|
333
|
+
# Define an expression filter for use with this selector
|
334
|
+
#
|
335
|
+
# @!method expression_filter(name, *types, options={}, &block)
|
336
|
+
# @param [Symbol, Regexp] name The filter name
|
337
|
+
# @param [Regexp] matcher (nil) A Regexp used to check whether a specific option is handled by this filter
|
338
|
+
# @param [Array<Symbol>] types The types of the filter - currently valid types are [:boolean]
|
339
|
+
# @param [Hash] options ({}) Options of the filter
|
340
|
+
# @option options [Array<>] :valid_values Valid values for this filter
|
341
|
+
# @option options :default The default value of the filter (if any)
|
342
|
+
# @option options :skip_if Value of the filter that will cause it to be skipped
|
343
|
+
# @option options [Regexp] :matcher (nil) A Regexp used to check whether a specific option is handled by this filter. If not provided the filter will be used for options matching the filter name.
|
344
|
+
#
|
345
|
+
# If a Symbol is passed for the name the block should accept | current_expression, option_value |, while if a Regexp
|
346
|
+
# is passed for the name the block should accept | current_expression, option_name, option_value |. In either case
|
347
|
+
# the block should return the modified expression
|
348
|
+
|
349
|
+
def_delegators :@filter_set, :node_filter, :expression_filter, :filter
|
193
350
|
|
194
351
|
def filter_set(name, filters_to_use = nil)
|
195
352
|
f_set = FilterSet.all[name]
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
353
|
+
filter_selector = filters_to_use.nil? ? ->(*) { true } : ->(n, _) { filters_to_use.include? n }
|
354
|
+
@filter_set.expression_filters.merge!(f_set.expression_filters.select(&filter_selector))
|
355
|
+
@filter_set.node_filters.merge!(f_set.node_filters.select(&filter_selector))
|
200
356
|
f_set.descriptions.each { |desc| @filter_set.describe(&desc) }
|
201
357
|
end
|
202
358
|
|
203
|
-
|
204
|
-
@filter_set.describe(&block)
|
205
|
-
end
|
359
|
+
def_delegator :@filter_set, :describe
|
206
360
|
|
207
361
|
##
|
208
362
|
#
|
@@ -218,40 +372,36 @@ module Capybara
|
|
218
372
|
end
|
219
373
|
|
220
374
|
def default_visibility(fallback = Capybara.ignore_hidden_elements)
|
221
|
-
|
222
|
-
|
223
|
-
else
|
224
|
-
@default_visibility
|
225
|
-
end
|
375
|
+
return @default_visibility unless @default_visibility.nil?
|
376
|
+
fallback
|
226
377
|
end
|
227
378
|
|
228
379
|
private
|
229
380
|
|
230
|
-
def add_filter(name, filter_class, *types, **options, &block)
|
231
|
-
types.each { |k| options[k] = true }
|
232
|
-
custom_filters[name] = filter_class.new(name, block, options)
|
233
|
-
end
|
234
|
-
|
235
381
|
def locate_field(xpath, locator, enable_aria_label: false, **_options)
|
382
|
+
return xpath if locator.nil?
|
236
383
|
locate_xpath = xpath # Need to save original xpath for the label wrap
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
locate_xpath = locate_xpath.union(XPath.descendant(:label)[XPath.string.n.is(locator)].descendant(xpath))
|
247
|
-
end
|
248
|
-
|
249
|
-
# locate_xpath = [:name, :placeholder].inject(locate_xpath) { |memo, ef| memo[find_by_attr(ef, options[ef])] }
|
250
|
-
locate_xpath
|
384
|
+
locator = locator.to_s
|
385
|
+
attr_matchers = [XPath.attr(:id) == locator,
|
386
|
+
XPath.attr(:name) == locator,
|
387
|
+
XPath.attr(:placeholder) == locator,
|
388
|
+
XPath.attr(:id) == XPath.anywhere(:label)[XPath.string.n.is(locator)].attr(:for)].reduce(:|)
|
389
|
+
attr_matchers |= XPath.attr(:'aria-label').is(locator) if enable_aria_label
|
390
|
+
|
391
|
+
locate_xpath = locate_xpath[attr_matchers]
|
392
|
+
locate_xpath + XPath.descendant(:label)[XPath.string.n.is(locator)].descendant(xpath)
|
251
393
|
end
|
252
394
|
|
253
395
|
def describe_all_expression_filters(**opts)
|
254
|
-
expression_filters.
|
396
|
+
expression_filters.map do |ef_name, ef|
|
397
|
+
if ef.matcher?
|
398
|
+
opts.keys.map do |k|
|
399
|
+
" with #{ef_name}[#{k} => #{opts[k]}]" if ef.handles_option?(k) && !::Capybara::Queries::SelectorQuery::VALID_KEYS.include?(k)
|
400
|
+
end.join
|
401
|
+
elsif opts.key?(ef_name)
|
402
|
+
" with #{ef_name} #{opts[ef_name]}"
|
403
|
+
end
|
404
|
+
end.join
|
255
405
|
end
|
256
406
|
|
257
407
|
def find_by_attr(attribute, value)
|
@@ -268,3 +418,5 @@ module Capybara
|
|
268
418
|
end
|
269
419
|
end
|
270
420
|
end
|
421
|
+
|
422
|
+
# rubocop:enable Style/AsciiComments
|