capybara 3.10.1 → 3.11.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 +13 -0
- data/README.md +2 -3
- data/lib/capybara.rb +16 -6
- data/lib/capybara/minitest.rb +8 -9
- data/lib/capybara/node/actions.rb +31 -28
- data/lib/capybara/node/base.rb +2 -1
- data/lib/capybara/node/document_matchers.rb +6 -2
- data/lib/capybara/node/element.rb +10 -10
- data/lib/capybara/node/finders.rb +13 -14
- data/lib/capybara/node/matchers.rb +1 -3
- data/lib/capybara/node/simple.rb +10 -2
- data/lib/capybara/queries/base_query.rb +7 -3
- data/lib/capybara/queries/selector_query.rb +60 -34
- data/lib/capybara/queries/style_query.rb +5 -1
- data/lib/capybara/queries/text_query.rb +2 -2
- data/lib/capybara/queries/title_query.rb +1 -1
- data/lib/capybara/rack_test/node.rb +16 -2
- data/lib/capybara/result.rb +9 -4
- data/lib/capybara/rspec/features.rb +4 -4
- data/lib/capybara/rspec/matcher_proxies.rb +3 -1
- data/lib/capybara/rspec/matchers.rb +25 -287
- data/lib/capybara/rspec/matchers/base.rb +98 -0
- data/lib/capybara/rspec/matchers/become_closed.rb +33 -0
- data/lib/capybara/rspec/matchers/compound.rb +88 -0
- data/lib/capybara/rspec/matchers/have_current_path.rb +29 -0
- data/lib/capybara/rspec/matchers/have_selector.rb +69 -0
- data/lib/capybara/rspec/matchers/have_style.rb +23 -0
- data/lib/capybara/rspec/matchers/have_text.rb +33 -0
- data/lib/capybara/rspec/matchers/have_title.rb +29 -0
- data/lib/capybara/rspec/matchers/match_selector.rb +27 -0
- data/lib/capybara/selector.rb +48 -20
- data/lib/capybara/selector/builders/xpath_builder.rb +3 -3
- data/lib/capybara/selector/css.rb +5 -5
- data/lib/capybara/selector/filters/base.rb +11 -3
- data/lib/capybara/selector/filters/expression_filter.rb +3 -3
- data/lib/capybara/selector/filters/node_filter.rb +16 -2
- data/lib/capybara/selector/regexp_disassembler.rb +116 -17
- data/lib/capybara/selector/selector.rb +52 -26
- data/lib/capybara/selenium/driver.rb +6 -2
- data/lib/capybara/selenium/node.rb +15 -14
- data/lib/capybara/selenium/nodes/marionette_node.rb +19 -5
- data/lib/capybara/selenium/patches/pause_duration_fix.rb +1 -3
- data/lib/capybara/server.rb +6 -1
- data/lib/capybara/server/animation_disabler.rb +1 -1
- data/lib/capybara/session.rb +4 -2
- data/lib/capybara/session/matchers.rb +7 -3
- data/lib/capybara/spec/public/test.js +5 -5
- data/lib/capybara/spec/session/all_spec.rb +5 -0
- data/lib/capybara/spec/session/has_css_spec.rb +4 -4
- data/lib/capybara/spec/session/has_field_spec.rb +17 -0
- data/lib/capybara/spec/session/node_spec.rb +45 -4
- data/lib/capybara/spec/spec_helper.rb +6 -1
- data/lib/capybara/spec/views/frame_child.erb +1 -1
- data/lib/capybara/spec/views/obscured.erb +44 -0
- data/lib/capybara/spec/views/with_html.erb +1 -1
- data/lib/capybara/version.rb +1 -1
- data/spec/rack_test_spec.rb +15 -0
- data/spec/regexp_dissassembler_spec.rb +88 -8
- data/spec/selector_spec.rb +3 -0
- data/spec/selenium_spec_chrome.rb +9 -15
- data/spec/selenium_spec_chrome_remote.rb +3 -2
- data/spec/selenium_spec_firefox_remote.rb +6 -2
- metadata +54 -3
- data/lib/capybara/rspec/compound.rb +0 -86
@@ -44,9 +44,9 @@ module Capybara
|
|
44
44
|
def regexp_to_xpath_conditions(regexp)
|
45
45
|
condition = XPath.current
|
46
46
|
condition = condition.uppercase if regexp.casefold?
|
47
|
-
Selector::RegexpDisassembler.new(regexp).
|
48
|
-
condition.contains(str)
|
49
|
-
end.reduce(
|
47
|
+
Selector::RegexpDisassembler.new(regexp).alternated_substrings.map do |strs|
|
48
|
+
strs.map { |str| condition.contains(str) }.reduce(:&)
|
49
|
+
end.reduce(:|)
|
50
50
|
end
|
51
51
|
end
|
52
52
|
end
|
@@ -21,11 +21,11 @@ module Capybara
|
|
21
21
|
end
|
22
22
|
|
23
23
|
S = '\u{80}-\u{D7FF}\u{E000}-\u{FFFD}\u{10000}-\u{10FFFF}'
|
24
|
-
H = /[0-9a-fA-F]
|
25
|
-
UNICODE = /\\#{H}{1,6}[ \t\r\n\f]
|
26
|
-
NONASCII = /[#{S}]
|
27
|
-
ESCAPE = /#{UNICODE}|\\[ -~#{S}]
|
28
|
-
NMSTART = /[_a-zA-Z]|#{NONASCII}|#{ESCAPE}
|
24
|
+
H = /[0-9a-fA-F]/.freeze
|
25
|
+
UNICODE = /\\#{H}{1,6}[ \t\r\n\f]?/.freeze
|
26
|
+
NONASCII = /[#{S}]/.freeze
|
27
|
+
ESCAPE = /#{UNICODE}|\\[ -~#{S}]/.freeze
|
28
|
+
NMSTART = /[_a-zA-Z]|#{NONASCII}|#{ESCAPE}/.freeze
|
29
29
|
|
30
30
|
class Splitter
|
31
31
|
def split(css)
|
@@ -28,6 +28,10 @@ module Capybara
|
|
28
28
|
!@matcher.nil?
|
29
29
|
end
|
30
30
|
|
31
|
+
def boolean?
|
32
|
+
!!@options[:boolean]
|
33
|
+
end
|
34
|
+
|
31
35
|
def handles_option?(option_name)
|
32
36
|
if matcher?
|
33
37
|
option_name =~ @matcher
|
@@ -38,17 +42,21 @@ module Capybara
|
|
38
42
|
|
39
43
|
private
|
40
44
|
|
41
|
-
def apply(subject, name, value, skip_value)
|
45
|
+
def apply(subject, name, value, skip_value, ctx)
|
42
46
|
return skip_value if skip?(value)
|
43
47
|
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
48
|
|
45
49
|
if @block.arity == 2
|
46
|
-
|
50
|
+
filter_context(ctx).instance_exec(subject, value, &@block)
|
47
51
|
else
|
48
|
-
|
52
|
+
filter_context(ctx).instance_exec(subject, name, value, &@block)
|
49
53
|
end
|
50
54
|
end
|
51
55
|
|
56
|
+
def filter_context(context)
|
57
|
+
context || @block.binding.receiver
|
58
|
+
end
|
59
|
+
|
52
60
|
def valid_value?(value)
|
53
61
|
return true unless @options.key?(:valid_values)
|
54
62
|
|
@@ -6,8 +6,8 @@ module Capybara
|
|
6
6
|
class Selector
|
7
7
|
module Filters
|
8
8
|
class ExpressionFilter < Base
|
9
|
-
def apply_filter(expr, name, value)
|
10
|
-
apply(expr, name, value, expr)
|
9
|
+
def apply_filter(expr, name, value, selector)
|
10
|
+
apply(expr, name, value, expr, selector)
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
@@ -15,7 +15,7 @@ module Capybara
|
|
15
15
|
def initialize(name); super(name, nil, nil); end
|
16
16
|
def default?; false; end
|
17
17
|
def matcher?; false; end
|
18
|
-
def apply_filter(expr, _name, _value); expr; end
|
18
|
+
def apply_filter(expr, _name, _value, _ctx); expr; end
|
19
19
|
end
|
20
20
|
end
|
21
21
|
end
|
@@ -6,8 +6,22 @@ module Capybara
|
|
6
6
|
class Selector
|
7
7
|
module Filters
|
8
8
|
class NodeFilter < Base
|
9
|
-
def
|
10
|
-
|
9
|
+
def initialize(name, matcher, block, **options)
|
10
|
+
super
|
11
|
+
@block = if boolean?
|
12
|
+
proc do |node, value|
|
13
|
+
error_cnt = errors.size
|
14
|
+
block.call(node, value).tap do |res|
|
15
|
+
add_error("Expected #{name} #{value} but it wasn't") if !res && error_cnt == errors.size
|
16
|
+
end
|
17
|
+
end
|
18
|
+
else
|
19
|
+
block
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def matches?(node, name, value, context = nil)
|
24
|
+
apply(node, name, value, true, context)
|
11
25
|
rescue Capybara::ElementNotFound
|
12
26
|
false
|
13
27
|
end
|
@@ -10,56 +10,155 @@ module Capybara
|
|
10
10
|
@regexp = regexp
|
11
11
|
end
|
12
12
|
|
13
|
+
def alternated_substrings
|
14
|
+
@alternated_substrings ||= begin
|
15
|
+
or_strings = process(alternation: true)
|
16
|
+
remove_or_covered(or_strings)
|
17
|
+
or_strings.any?(&:empty?) ? [] : or_strings
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
13
21
|
def substrings
|
14
22
|
@substrings ||= begin
|
15
|
-
strs =
|
16
|
-
strs
|
17
|
-
strs.reject(&:empty?).uniq
|
23
|
+
strs = process(alternation: false).first
|
24
|
+
remove_and_covered(strs)
|
18
25
|
end
|
19
26
|
end
|
20
27
|
|
21
28
|
private
|
22
29
|
|
30
|
+
def remove_and_covered(strings)
|
31
|
+
# delete_if is documented to modify the array after every block iteration - this doesn't appear to be true
|
32
|
+
# uniq the strings to prevent identical strings from removing each other
|
33
|
+
strings.uniq!
|
34
|
+
|
35
|
+
# If we have "ab" and "abcd" required - only need to check for "abcd"
|
36
|
+
strings.delete_if do |sub_string|
|
37
|
+
strings.any? do |cover_string|
|
38
|
+
next if sub_string.equal? cover_string
|
39
|
+
|
40
|
+
cover_string.include?(sub_string)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def remove_or_covered(or_series)
|
46
|
+
# If we are going to match `("a" and "b") or ("ade" and "bce")` it only makes sense to match ("a" and "b")
|
47
|
+
|
48
|
+
# Ensure minimum sets of strings are being or'd
|
49
|
+
or_series.each { |strs| remove_and_covered(strs) }
|
50
|
+
|
51
|
+
# Remove any of the alternated string series that fully contain any other string series
|
52
|
+
or_series.delete_if do |and_strs|
|
53
|
+
or_series.any? do |and_strs2|
|
54
|
+
next if and_strs.equal? and_strs2
|
55
|
+
|
56
|
+
remove_and_covered(and_strs + and_strs2) == and_strs
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def process(alternation:)
|
62
|
+
strs = extract_strings(Regexp::Parser.parse(@regexp), alternation: alternation)
|
63
|
+
strs = collapse(combine(strs).map(&:flatten))
|
64
|
+
strs.each { |str| str.map!(&:upcase) } if @regexp.casefold?
|
65
|
+
strs
|
66
|
+
end
|
67
|
+
|
23
68
|
def min_repeat(exp)
|
24
69
|
exp.quantifier&.min || 1
|
25
70
|
end
|
26
71
|
|
72
|
+
def max_repeat(exp)
|
73
|
+
exp.quantifier&.max || 1
|
74
|
+
end
|
75
|
+
|
27
76
|
def fixed_repeat?(exp)
|
28
|
-
min_repeat(exp) == (exp
|
77
|
+
min_repeat(exp) == max_repeat(exp)
|
29
78
|
end
|
30
79
|
|
31
80
|
def optional?(exp)
|
32
81
|
min_repeat(exp).zero?
|
33
82
|
end
|
34
83
|
|
35
|
-
def
|
36
|
-
|
37
|
-
|
38
|
-
|
84
|
+
def combine(strs)
|
85
|
+
suffixes = [[]]
|
86
|
+
strs.reverse_each do |str|
|
87
|
+
if str.is_a? Set
|
88
|
+
prefixes = str.each_with_object([]) { |s, memo| memo.concat combine(s) }
|
89
|
+
|
90
|
+
result = []
|
91
|
+
prefixes.product(suffixes) { |pair| result << pair.flatten(1) }
|
92
|
+
suffixes = result
|
93
|
+
else
|
94
|
+
suffixes.each do |arr|
|
95
|
+
arr.unshift str
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
suffixes
|
100
|
+
end
|
101
|
+
|
102
|
+
def collapse(strs)
|
103
|
+
strs.map do |substrings|
|
104
|
+
substrings.slice_before(&:nil?).map(&:join).reject(&:empty?).uniq
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def extract_strings(expression, alternation: false)
|
109
|
+
strings = []
|
110
|
+
expression.each do |exp| # rubocop:disable Metrics/BlockLength
|
111
|
+
if optional?(exp) && !alternation
|
112
|
+
strings.push(nil)
|
113
|
+
next
|
114
|
+
end
|
115
|
+
|
116
|
+
if %i[meta].include?(exp.type) && !exp.terminal? && alternation
|
117
|
+
strings.push(alternative_strings(exp))
|
39
118
|
next
|
40
119
|
end
|
41
120
|
|
42
121
|
if %i[meta set].include?(exp.type)
|
43
|
-
strings.push(
|
122
|
+
strings.push(nil)
|
44
123
|
next
|
45
124
|
end
|
46
125
|
|
47
126
|
if exp.terminal?
|
48
|
-
case exp.type
|
49
|
-
when :literal
|
50
|
-
|
51
|
-
|
52
|
-
strings.
|
127
|
+
text = case exp.type
|
128
|
+
when :literal then exp.text
|
129
|
+
when :escape then exp.char
|
130
|
+
else
|
131
|
+
strings.push(nil)
|
132
|
+
next
|
133
|
+
end
|
134
|
+
|
135
|
+
if optional?(exp)
|
136
|
+
strings.push(Set.new([[''], [text]]))
|
137
|
+
strings.push(nil) unless max_repeat(exp) == 1
|
138
|
+
next
|
53
139
|
else
|
54
|
-
strings.push(
|
140
|
+
strings.push(text * min_repeat(exp))
|
55
141
|
end
|
142
|
+
elsif optional?(exp)
|
143
|
+
strings.push(Set.new([[''], extract_strings(exp, alternation: true)]))
|
144
|
+
strings.push(nil) unless max_repeat(exp) == 1
|
145
|
+
next
|
56
146
|
else
|
57
|
-
min_repeat(exp).times { extract_strings(exp,
|
147
|
+
min_repeat(exp).times { strings.concat extract_strings(exp, alternation: alternation) }
|
58
148
|
end
|
59
|
-
strings.push(
|
149
|
+
strings.push(nil) unless fixed_repeat?(exp)
|
60
150
|
end
|
61
151
|
strings
|
62
152
|
end
|
153
|
+
|
154
|
+
def alternative_strings(expression)
|
155
|
+
alternatives = expression.alternatives.map { |sub_exp| extract_strings(sub_exp, alternation: true) }
|
156
|
+
if alternatives.all?(&:any?)
|
157
|
+
Set.new(alternatives)
|
158
|
+
else
|
159
|
+
nil
|
160
|
+
end
|
161
|
+
end
|
63
162
|
end
|
64
163
|
end
|
65
164
|
end
|
@@ -19,18 +19,18 @@ module Capybara
|
|
19
19
|
# * Locator: A CSS selector
|
20
20
|
#
|
21
21
|
# * **:id** - Select element by id
|
22
|
-
# * Locator: The id of the element to match
|
22
|
+
# * Locator: (String, Regexp, XPath::Expression) The id of the element to match
|
23
23
|
#
|
24
24
|
# * **:field** - Select field elements (input [not of type submit, image, or hidden], textarea, select)
|
25
25
|
# * Locator: Matches against the id, Capybara.test_id attribute, name, or placeholder
|
26
26
|
# * Filters:
|
27
|
-
# * :id (String) — Matches the id attribute
|
27
|
+
# * :id (String, Regexp, XPath::Expression) — Matches the id attribute
|
28
28
|
# * :name (String) — Matches the name attribute
|
29
29
|
# * :placeholder (String) — Matches the placeholder attribute
|
30
30
|
# * :type (String) — Matches the type attribute of the field or element type for 'textarea' and 'select'
|
31
31
|
# * :readonly (Boolean)
|
32
32
|
# * :with (String) — Matches the current value of the field
|
33
|
-
# * :class (String, Array<String
|
33
|
+
# * :class (String, Array<String>, Regexp, XPath::Expression) — Matches the class(es) provided
|
34
34
|
# * :checked (Boolean) — Match checked fields?
|
35
35
|
# * :unchecked (Boolean) — Match unchecked fields?
|
36
36
|
# * :disabled (Boolean) — Match disabled field?
|
@@ -39,25 +39,25 @@ module Capybara
|
|
39
39
|
# * **:fieldset** - Select fieldset elements
|
40
40
|
# * Locator: Matches id or contents of wrapped legend
|
41
41
|
# * Filters:
|
42
|
-
# * :id (String) — Matches id attribute
|
42
|
+
# * :id (String, Regexp, XPath::Expression) — Matches id attribute
|
43
43
|
# * :legend (String) — Matches contents of wrapped legend
|
44
|
-
# * :class (String, Array<String
|
44
|
+
# * :class (String, Array<String>, Regexp, XPath::Expression) — Matches the class(es) provided
|
45
45
|
#
|
46
46
|
# * **:link** - Find links ( <a> elements with an href attribute )
|
47
47
|
# * Locator: Matches the id or title attributes, or the string content of the link, or the alt attribute of a contained img element
|
48
48
|
# * Filters:
|
49
|
-
# * :id (String) — Matches the id attribute
|
49
|
+
# * :id (String, Regexp, XPath::Expression) — Matches the id attribute
|
50
50
|
# * :title (String) — Matches the title attribute
|
51
51
|
# * :alt (String) — Matches the alt attribute of a contained img element
|
52
|
-
# * :class (String) — Matches the class(es) provided
|
52
|
+
# * :class (String, Array<String>, Regexp, XPath::Expression) — Matches the class(es) provided
|
53
53
|
# * :href (String, Regexp, nil) — Matches the normalized href of the link, if nil will find <a> elements with no href attribute
|
54
54
|
#
|
55
55
|
# * **:button** - Find buttons ( input [of type submit, reset, image, button] or button elements )
|
56
56
|
# * Locator: Matches the id, Capybara.test_id attribute, 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
|
57
57
|
# * Filters:
|
58
|
-
# * :id (String) — Matches the id attribute
|
58
|
+
# * :id (String, Regexp, XPath::Expression) — Matches the id attribute
|
59
59
|
# * :title (String) — Matches the title attribute
|
60
|
-
# * :class (String) — Matches the class(es) provided
|
60
|
+
# * :class (String, Array<String>, Regexp, XPath::Expression) — Matches the class(es) provided
|
61
61
|
# * :value (String) — Matches the value of an input button
|
62
62
|
# * :type
|
63
63
|
#
|
@@ -67,21 +67,21 @@ module Capybara
|
|
67
67
|
# * **:fillable_field** - Find text fillable fields ( textarea, input [not of type submit, image, radio, checkbox, hidden, file] )
|
68
68
|
# * Locator: Matches against the id, Capybara.test_id attribute, name, or placeholder
|
69
69
|
# * Filters:
|
70
|
-
# * :id (String) — Matches the id attribute
|
70
|
+
# * :id (String, Regexp, XPath::Expression) — Matches the id attribute
|
71
71
|
# * :name (String) — Matches the name attribute
|
72
72
|
# * :placeholder (String) — Matches the placeholder attribute
|
73
73
|
# * :with (String) — Matches the current value of the field
|
74
74
|
# * :type (String) — Matches the type attribute of the field or element type for 'textarea'
|
75
|
-
# * :class (String, Array<String
|
75
|
+
# * :class (String, Array<String>, Regexp, XPath::Expression) — Matches the class(es) provided
|
76
76
|
# * :disabled (Boolean) — Match disabled field?
|
77
77
|
# * :multiple (Boolean) — Match fields that accept multiple values
|
78
78
|
#
|
79
79
|
# * **:radio_button** - Find radio buttons
|
80
80
|
# * Locator: Match id, Capybara.test_id attribute, name, or associated label text
|
81
81
|
# * Filters:
|
82
|
-
# * :id (String) — Matches the id attribute
|
82
|
+
# * :id (String, Regexp, XPath::Expression) — Matches the id attribute
|
83
83
|
# * :name (String) — Matches the name attribute
|
84
|
-
# * :class (String, Array<String
|
84
|
+
# * :class (String, Array<String>, Regexp, XPath::Expression) — Matches the class(es) provided
|
85
85
|
# * :checked (Boolean) — Match checked fields?
|
86
86
|
# * :unchecked (Boolean) — Match unchecked fields?
|
87
87
|
# * :disabled (Boolean) — Match disabled field?
|
@@ -90,9 +90,9 @@ module Capybara
|
|
90
90
|
# * **:checkbox** - Find checkboxes
|
91
91
|
# * Locator: Match id, Capybara.test_id attribute, name, or associated label text
|
92
92
|
# * Filters:
|
93
|
-
# * *:id (String) — Matches the id attribute
|
93
|
+
# * *:id (String, Regexp, XPath::Expression) — Matches the id attribute
|
94
94
|
# * *:name (String) — Matches the name attribute
|
95
|
-
# * *:class (String, Array<String
|
95
|
+
# * *:class (String, Array<String>, Regexp, XPath::Expression) — Matches the class(es) provided
|
96
96
|
# * *:checked (Boolean) — Match checked fields?
|
97
97
|
# * *:unchecked (Boolean) — Match unchecked fields?
|
98
98
|
# * *:disabled (Boolean) — Match disabled field?
|
@@ -101,10 +101,10 @@ module Capybara
|
|
101
101
|
# * **:select** - Find select elements
|
102
102
|
# * Locator: Match id, Capybara.test_id attribute, name, placeholder, or associated label text
|
103
103
|
# * Filters:
|
104
|
-
# * :id (String) — Matches the id attribute
|
104
|
+
# * :id (String, Regexp, XPath::Expression) — Matches the id attribute
|
105
105
|
# * :name (String) — Matches the name attribute
|
106
106
|
# * :placeholder (String) — Matches the placeholder attribute
|
107
|
-
# * :class (String, Array<String
|
107
|
+
# * :class (String, Array<String>, Regexp, XPath::Expression) — Matches the class(es) provided
|
108
108
|
# * :disabled (Boolean) — Match disabled field?
|
109
109
|
# * :multiple (Boolean) — Match fields that accept multiple values
|
110
110
|
# * :options (Array<String>) — Exact match options
|
@@ -131,9 +131,9 @@ module Capybara
|
|
131
131
|
# * **:file_field** - Find file input elements
|
132
132
|
# * Locator: Match id, Capybara.test_id attribute, name, or associated label text
|
133
133
|
# * Filters:
|
134
|
-
# * :id (String) — Matches the id attribute
|
134
|
+
# * :id (String, Regexp, XPath::Expression) — Matches the id attribute
|
135
135
|
# * :name (String) — Matches the name attribute
|
136
|
-
# * :class (String, Array<String
|
136
|
+
# * :class (String, Array<String>, Regexp, XPath::Expression) — Matches the class(es) provided
|
137
137
|
# * :disabled (Boolean) — Match disabled field?
|
138
138
|
# * :multiple (Boolean) — Match field that accepts multiple values
|
139
139
|
#
|
@@ -145,16 +145,16 @@ module Capybara
|
|
145
145
|
# * **:table** - Find table elements
|
146
146
|
# * Locator: id or caption text of table
|
147
147
|
# * Filters:
|
148
|
-
# * :id (String) — Match id attribute of table
|
148
|
+
# * :id (String, Regexp, XPath::Expression) — Match id attribute of table
|
149
149
|
# * :caption (String) — Match text of associated caption
|
150
|
-
# * :class (String, Array<String
|
150
|
+
# * :class ((String, Array<String>, Regexp, XPath::Expression) — Matches the class(es) provided
|
151
151
|
#
|
152
152
|
# * **:frame** - Find frame/iframe elements
|
153
153
|
# * Locator: Match id or name
|
154
154
|
# * Filters:
|
155
|
-
# * :id (String) — Match id attribute
|
155
|
+
# * :id (String, Regexp, XPath::Expression) — Match id attribute
|
156
156
|
# * :name (String) — Match name attribute
|
157
|
-
# * :class (String, Array<String
|
157
|
+
# * :class (String, Array<String>, Regexp, XPath::Expression) — Matches the class(es) provided
|
158
158
|
#
|
159
159
|
# * **:element**
|
160
160
|
# * Locator: Type of element ('div', 'a', etc) - if not specified defaults to '*'
|
@@ -191,6 +191,7 @@ module Capybara
|
|
191
191
|
@format = nil
|
192
192
|
@expression = nil
|
193
193
|
@expression_filters = {}
|
194
|
+
@locator_filter = nil
|
194
195
|
@default_visibility = nil
|
195
196
|
@config = {
|
196
197
|
enable_aria_label: false,
|
@@ -347,6 +348,11 @@ module Capybara
|
|
347
348
|
|
348
349
|
def_delegators :@filter_set, :node_filter, :expression_filter, :filter
|
349
350
|
|
351
|
+
def locator_filter(&block)
|
352
|
+
@locator_filter = block if block
|
353
|
+
@locator_filter
|
354
|
+
end
|
355
|
+
|
350
356
|
def filter_set(name, filters_to_use = nil)
|
351
357
|
@filter_set.import(name, filters_to_use)
|
352
358
|
end
|
@@ -389,6 +395,10 @@ module Capybara
|
|
389
395
|
vis.nil? ? fallback : vis
|
390
396
|
end
|
391
397
|
|
398
|
+
def add_error(error_msg)
|
399
|
+
errors << error_msg
|
400
|
+
end
|
401
|
+
|
392
402
|
# @api private
|
393
403
|
def builder
|
394
404
|
case format
|
@@ -401,8 +411,20 @@ module Capybara
|
|
401
411
|
end
|
402
412
|
end
|
403
413
|
|
414
|
+
# @api private
|
415
|
+
def with_filter_errors(errors)
|
416
|
+
Thread.current["capybara_#{object_id}_errors"] = errors
|
417
|
+
yield
|
418
|
+
ensure
|
419
|
+
Thread.current["capybara_#{object_id}_errors"] = nil
|
420
|
+
end
|
421
|
+
|
404
422
|
private
|
405
423
|
|
424
|
+
def errors
|
425
|
+
Thread.current["capybara_#{object_id}_errors"] || []
|
426
|
+
end
|
427
|
+
|
406
428
|
def enable_aria_label
|
407
429
|
@config[:enable_aria_label]
|
408
430
|
end
|
@@ -430,15 +452,19 @@ module Capybara
|
|
430
452
|
def describe_all_expression_filters(**opts)
|
431
453
|
expression_filters.map do |ef_name, ef|
|
432
454
|
if ef.matcher?
|
433
|
-
opts.keys.map
|
434
|
-
" with #{ef_name}[#{key} => #{opts[key]}]" if ef.handles_option?(key) && !::Capybara::Queries::SelectorQuery::VALID_KEYS.include?(key)
|
435
|
-
end.join
|
455
|
+
handled_custom_keys(ef, opts.keys).map { |key| " with #{ef_name}[#{key} => #{opts[key]}]" }.join
|
436
456
|
elsif opts.key?(ef_name)
|
437
457
|
" with #{ef_name} #{opts[ef_name]}"
|
438
458
|
end
|
439
459
|
end.join
|
440
460
|
end
|
441
461
|
|
462
|
+
def handled_custom_keys(filter, keys)
|
463
|
+
keys.select do |key|
|
464
|
+
filter.handles_option?(key) && !::Capybara::Queries::SelectorQuery::VALID_KEYS.include?(key)
|
465
|
+
end
|
466
|
+
end
|
467
|
+
|
442
468
|
def find_by_attr(attribute, value)
|
443
469
|
finder_name = "find_by_#{attribute}_attr"
|
444
470
|
if respond_to?(finder_name, true)
|