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
@@ -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
|