capybara 3.10.1 → 3.11.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 +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)
|