capybara 2.8.1 → 2.9.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/.yard/templates_custom/default/class/html/selectors.erb +38 -0
- data/.yard/templates_custom/default/class/html/setup.rb +17 -0
- data/.yard/yard_extensions.rb +78 -0
- data/.yardopts +1 -0
- data/History.md +13 -1
- data/README.md +1 -1
- data/lib/capybara/helpers.rb +0 -59
- data/lib/capybara/node/actions.rb +17 -11
- data/lib/capybara/node/finders.rb +9 -0
- data/lib/capybara/node/matchers.rb +54 -15
- data/lib/capybara/queries/base_query.rb +59 -3
- data/lib/capybara/queries/selector_query.rb +7 -16
- data/lib/capybara/queries/text_query.rb +11 -10
- data/lib/capybara/rack_test/form.rb +2 -1
- data/lib/capybara/result.rb +25 -10
- data/lib/capybara/rspec/features.rb +3 -2
- data/lib/capybara/rspec/matchers.rb +1 -1
- data/lib/capybara/selector.rb +277 -175
- data/lib/capybara/selector/filter_set.rb +3 -1
- data/lib/capybara/selector/selector.rb +227 -0
- data/lib/capybara/session.rb +2 -2
- data/lib/capybara/spec/session/element/matches_selector_spec.rb +29 -1
- data/lib/capybara/spec/session/selectors_spec.rb +12 -0
- data/lib/capybara/spec/views/form.erb +11 -0
- data/lib/capybara/version.rb +1 -1
- data/spec/fixtures/capybara.csv +1 -0
- data/spec/fixtures/selenium_driver_rspec_failure.rb +1 -1
- data/spec/fixtures/selenium_driver_rspec_success.rb +1 -1
- data/spec/rack_test_spec.rb +8 -0
- data/spec/result_spec.rb +3 -0
- data/spec/selenium_firefox_spec.rb +44 -0
- data/spec/selenium_spec_chrome.rb +5 -2
- data/spec/{selenium_spec.rb → shared_selenium_session.rb} +9 -43
- metadata +10 -3
@@ -8,15 +8,71 @@ module Capybara
|
|
8
8
|
attr_reader :options
|
9
9
|
|
10
10
|
def wait
|
11
|
-
|
12
|
-
|
11
|
+
self.class.wait(options)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.wait(options)
|
15
|
+
options.fetch(:wait, Capybara.default_max_wait_time) || 0
|
16
|
+
end
|
17
|
+
|
18
|
+
##
|
19
|
+
#
|
20
|
+
# Checks if a count of 0 is valid for the query
|
21
|
+
# Returns false if query does not have any count options specified.
|
22
|
+
#
|
23
|
+
def expects_none?
|
24
|
+
if COUNT_KEYS.any? { |k| options.has_key? k }
|
25
|
+
matches_count?(0)
|
13
26
|
else
|
14
|
-
|
27
|
+
false
|
15
28
|
end
|
16
29
|
end
|
17
30
|
|
31
|
+
##
|
32
|
+
#
|
33
|
+
# Checks if the given count matches the query count options.
|
34
|
+
# Defaults to true if no count options are specified. If multiple
|
35
|
+
# count options exist, it tests that all conditions are met;
|
36
|
+
# however, if :count is specified, all other options are ignored.
|
37
|
+
#
|
38
|
+
# @param [Integer] count The actual number. Should be coercible via Integer()
|
39
|
+
#
|
40
|
+
def matches_count?(count)
|
41
|
+
return (Integer(options[:count]) == count) if options[:count]
|
42
|
+
return false if options[:maximum] && (Integer(options[:maximum]) < count)
|
43
|
+
return false if options[:minimum] && (Integer(options[:minimum]) > count)
|
44
|
+
return false if options[:between] && !(options[:between] === count)
|
45
|
+
return true
|
46
|
+
end
|
47
|
+
|
48
|
+
##
|
49
|
+
#
|
50
|
+
# Generates a failure message from the query description and count options.
|
51
|
+
#
|
52
|
+
def failure_message
|
53
|
+
String.new("expected to find #{description}") << count_message
|
54
|
+
end
|
55
|
+
|
56
|
+
def negative_failure_message
|
57
|
+
String.new("expected not to find #{description}") << count_message
|
58
|
+
end
|
59
|
+
|
18
60
|
private
|
19
61
|
|
62
|
+
def count_message
|
63
|
+
message = String.new()
|
64
|
+
if options[:count]
|
65
|
+
message << " #{options[:count]} #{Capybara::Helpers.declension('time', 'times', options[:count])}"
|
66
|
+
elsif options[:between]
|
67
|
+
message << " between #{options[:between].first} and #{options[:between].last} times"
|
68
|
+
elsif options[:maximum]
|
69
|
+
message << " at most #{options[:maximum]} #{Capybara::Helpers.declension('time', 'times', options[:maximum])}"
|
70
|
+
elsif options[:minimum]
|
71
|
+
message << " at least #{options[:minimum]} #{Capybara::Helpers.declension('time', 'times', options[:minimum])}"
|
72
|
+
end
|
73
|
+
message
|
74
|
+
end
|
75
|
+
|
20
76
|
def assert_valid_keys
|
21
77
|
invalid_keys = @options.keys - valid_keys
|
22
78
|
unless invalid_keys.empty?
|
@@ -4,7 +4,7 @@ module Capybara
|
|
4
4
|
class SelectorQuery < Queries::BaseQuery
|
5
5
|
attr_accessor :selector, :locator, :options, :expression, :find, :negative
|
6
6
|
|
7
|
-
VALID_KEYS = [:text, :visible, :
|
7
|
+
VALID_KEYS = COUNT_KEYS + [:text, :visible, :exact, :match, :wait, :filter_set]
|
8
8
|
VALID_MATCH = [:first, :smart, :prefer_exact, :one]
|
9
9
|
|
10
10
|
def initialize(*args)
|
@@ -26,7 +26,7 @@ module Capybara
|
|
26
26
|
@options[:exact] = true
|
27
27
|
end
|
28
28
|
|
29
|
-
@expression = @selector.call(@locator)
|
29
|
+
@expression = @selector.call(@locator, @options)
|
30
30
|
|
31
31
|
warn_exact_usage
|
32
32
|
|
@@ -83,23 +83,15 @@ module Capybara
|
|
83
83
|
|
84
84
|
def exact?
|
85
85
|
return false if !supports_exact?
|
86
|
-
|
87
|
-
@options[:exact]
|
88
|
-
else
|
89
|
-
Capybara.exact
|
90
|
-
end
|
86
|
+
options.fetch(:exact, Capybara.exact)
|
91
87
|
end
|
92
88
|
|
93
89
|
def match
|
94
|
-
|
95
|
-
@options[:match]
|
96
|
-
else
|
97
|
-
Capybara.match
|
98
|
-
end
|
90
|
+
options.fetch(:match, Capybara.match)
|
99
91
|
end
|
100
92
|
|
101
93
|
def xpath(exact=nil)
|
102
|
-
exact = self.exact? if exact
|
94
|
+
exact = self.exact? if exact.nil?
|
103
95
|
if @expression.respond_to?(:to_xpath) and exact
|
104
96
|
@expression.to_xpath(:exact)
|
105
97
|
else
|
@@ -137,8 +129,7 @@ module Capybara
|
|
137
129
|
private
|
138
130
|
|
139
131
|
def valid_keys
|
140
|
-
|
141
|
-
vk + custom_keys
|
132
|
+
VALID_KEYS + custom_keys
|
142
133
|
end
|
143
134
|
|
144
135
|
def query_filters
|
@@ -150,7 +141,7 @@ module Capybara
|
|
150
141
|
end
|
151
142
|
|
152
143
|
def custom_keys
|
153
|
-
query_filters.keys
|
144
|
+
query_filters.keys + @selector.expression_filters
|
154
145
|
end
|
155
146
|
|
156
147
|
def assert_valid_keys
|
@@ -22,24 +22,25 @@ module Capybara
|
|
22
22
|
end
|
23
23
|
|
24
24
|
def failure_message
|
25
|
-
build_message(true)
|
25
|
+
super << build_message(true)
|
26
26
|
end
|
27
27
|
|
28
28
|
def negative_failure_message
|
29
|
-
build_message(false)
|
29
|
+
super << build_message(false)
|
30
|
+
end
|
31
|
+
|
32
|
+
def description
|
33
|
+
if @expected_text.is_a?(Regexp)
|
34
|
+
"text matching #{@expected_text.inspect}"
|
35
|
+
else
|
36
|
+
"text #{@expected_text.inspect}"
|
37
|
+
end
|
30
38
|
end
|
31
39
|
|
32
40
|
private
|
33
41
|
|
34
42
|
def build_message(report_on_invisible)
|
35
|
-
|
36
|
-
if @expected_text.is_a?(Regexp)
|
37
|
-
"text matching #{@expected_text.inspect}"
|
38
|
-
else
|
39
|
-
"text #{@expected_text.inspect}"
|
40
|
-
end
|
41
|
-
|
42
|
-
message = Capybara::Helpers.failure_message(description, @options)
|
43
|
+
message = String.new()
|
43
44
|
unless (COUNT_KEYS & @options.keys).empty?
|
44
45
|
message << " but found #{@count} #{Capybara::Helpers.declension('time', 'times', @count)}"
|
45
46
|
end
|
@@ -42,7 +42,8 @@ class Capybara::RackTest::Form < Capybara::RackTest::Node
|
|
42
42
|
if (value = field['value']).to_s.empty?
|
43
43
|
NilUploadedFile.new
|
44
44
|
else
|
45
|
-
|
45
|
+
types = MIME::Types.type_for(value)
|
46
|
+
content_type = types.sort_by.with_index { |type, idx| [type.obsolete? ? 1 : 0, idx] }.first.to_s
|
46
47
|
Rack::Test::UploadedFile.new(value, content_type)
|
47
48
|
end
|
48
49
|
merge_param!(params, field['name'].to_s, file)
|
data/lib/capybara/result.rb
CHANGED
@@ -45,7 +45,7 @@ module Capybara
|
|
45
45
|
end
|
46
46
|
|
47
47
|
def [](*args)
|
48
|
-
if (args.size == 1) && ((idx = args[0]).is_a? Integer) && (idx
|
48
|
+
if (args.size == 1) && ((idx = args[0]).is_a? Integer) && (idx >= 0)
|
49
49
|
@result_cache << @results_enum.next while @result_cache.size <= idx
|
50
50
|
@result_cache[idx]
|
51
51
|
else
|
@@ -61,31 +61,48 @@ module Capybara
|
|
61
61
|
end
|
62
62
|
|
63
63
|
def matches_count?
|
64
|
-
|
65
|
-
|
66
|
-
|
64
|
+
# Only check filters for as many elements as necessary to determine result
|
65
|
+
if @query.options[:count]
|
66
|
+
count_opt = Integer(@query.options[:count])
|
67
|
+
loop do
|
68
|
+
break if @result_cache.size > count_opt
|
69
|
+
@result_cache << @results_enum.next
|
70
|
+
end
|
71
|
+
return @result_cache.size == count_opt
|
72
|
+
end
|
67
73
|
|
68
74
|
if @query.options[:minimum]
|
75
|
+
min_opt = Integer(@query.options[:minimum])
|
69
76
|
begin
|
70
|
-
@result_cache << @results_enum.next while @result_cache.size <
|
77
|
+
@result_cache << @results_enum.next while @result_cache.size < min_opt
|
71
78
|
rescue StopIteration
|
72
79
|
return false
|
73
80
|
end
|
74
81
|
end
|
75
82
|
|
76
83
|
if @query.options[:maximum]
|
84
|
+
max_opt = Integer(@query.options[:maximum])
|
77
85
|
begin
|
78
|
-
@result_cache << @results_enum.next while @result_cache.size <=
|
86
|
+
@result_cache << @results_enum.next while @result_cache.size <= max_opt
|
79
87
|
return false
|
80
88
|
rescue StopIteration
|
81
89
|
end
|
82
90
|
end
|
83
91
|
|
92
|
+
if @query.options[:between]
|
93
|
+
max = Integer(@query.options[:between].max)
|
94
|
+
loop do
|
95
|
+
break if @result_cache.size > max
|
96
|
+
@result_cache << @results_enum.next
|
97
|
+
end
|
98
|
+
return false unless (@query.options[:between] === @result_cache.size)
|
99
|
+
end
|
100
|
+
|
84
101
|
return true
|
85
102
|
end
|
86
103
|
|
87
104
|
def failure_message
|
88
|
-
message =
|
105
|
+
message = @query.failure_message
|
89
106
|
if count > 0
|
90
107
|
message << ", found #{count} #{Capybara::Helpers.declension("match", "matches", count)}: " << full_results.map(&:text).map(&:inspect).join(", ")
|
91
108
|
else
|
@@ -105,9 +122,7 @@ module Capybara
|
|
105
122
|
private
|
106
123
|
|
107
124
|
def full_results
|
108
|
-
loop
|
109
|
-
@result_cache << @results_enum.next
|
110
|
-
end
|
125
|
+
loop { @result_cache << @results_enum.next }
|
111
126
|
@result_cache
|
112
127
|
end
|
113
128
|
|
@@ -39,7 +39,6 @@ else
|
|
39
39
|
end
|
40
40
|
end
|
41
41
|
|
42
|
-
|
43
42
|
def self.feature(*args, &block)
|
44
43
|
options = if args.last.is_a?(Hash) then args.pop else {} end
|
45
44
|
options[:capybara_feature] = true
|
@@ -51,5 +50,7 @@ else
|
|
51
50
|
RSpec.describe(*args, &block)
|
52
51
|
end
|
53
52
|
|
54
|
-
RSpec.
|
53
|
+
RSpec.configure do |config|
|
54
|
+
config.include(Capybara::Features, :capybara_feature => true)
|
55
|
+
end
|
55
56
|
end
|
data/lib/capybara/selector.rb
CHANGED
@@ -1,188 +1,134 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
require 'capybara/selector/
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
class << self
|
10
|
-
def all
|
11
|
-
@selectors ||= {}
|
12
|
-
end
|
13
|
-
|
14
|
-
def add(name, &block)
|
15
|
-
all[name.to_sym] = Capybara::Selector.new(name.to_sym, &block)
|
16
|
-
end
|
17
|
-
|
18
|
-
def update(name, &block)
|
19
|
-
all[name.to_sym].instance_eval(&block)
|
20
|
-
end
|
21
|
-
|
22
|
-
def remove(name)
|
23
|
-
all.delete(name.to_sym)
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
def initialize(name, &block)
|
28
|
-
@name = name
|
29
|
-
@filter_set = FilterSet.add(name){}
|
30
|
-
@match = nil
|
31
|
-
@label = nil
|
32
|
-
@failure_message = nil
|
33
|
-
@description = nil
|
34
|
-
@format = nil
|
35
|
-
@expression = nil
|
36
|
-
instance_eval(&block)
|
37
|
-
end
|
38
|
-
|
39
|
-
def custom_filters
|
40
|
-
@filter_set.filters
|
41
|
-
end
|
42
|
-
|
43
|
-
def xpath(&block)
|
44
|
-
@format, @expression = :xpath, block if block
|
45
|
-
format == :xpath ? @expression : nil
|
46
|
-
end
|
47
|
-
|
48
|
-
def css(&block)
|
49
|
-
@format, @expression = :css, block if block
|
50
|
-
format == :css ? @expression : nil
|
51
|
-
end
|
52
|
-
|
53
|
-
def match(&block)
|
54
|
-
@match = block if block
|
55
|
-
@match
|
56
|
-
end
|
57
|
-
|
58
|
-
def label(label=nil)
|
59
|
-
@label = label if label
|
60
|
-
@label
|
61
|
-
end
|
62
|
-
|
63
|
-
def description(options={})
|
64
|
-
@filter_set.description(options)
|
65
|
-
end
|
66
|
-
|
67
|
-
def call(locator)
|
68
|
-
if format
|
69
|
-
@expression.call(locator)
|
70
|
-
else
|
71
|
-
warn "Selector has no format"
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
def match?(locator)
|
76
|
-
@match and @match.call(locator)
|
77
|
-
end
|
78
|
-
|
79
|
-
def filter(name, options={}, &block)
|
80
|
-
custom_filters[name] = Filter.new(name, block, options)
|
81
|
-
end
|
82
|
-
|
83
|
-
def filter_set(name, filters_to_use = nil)
|
84
|
-
f_set = FilterSet.all[name]
|
85
|
-
f_set.filters.each do | name, filter |
|
86
|
-
custom_filters[name] = filter if filters_to_use.nil? || filters_to_use.include?(name)
|
87
|
-
end
|
88
|
-
f_set.descriptions.each { |desc| @filter_set.describe &desc }
|
89
|
-
end
|
90
|
-
|
91
|
-
def describe &block
|
92
|
-
@filter_set.describe &block
|
93
|
-
end
|
94
|
-
|
95
|
-
private
|
96
|
-
|
97
|
-
def locate_field(xpath, locator)
|
98
|
-
attr_matchers = XPath.attr(:id).equals(locator) |
|
99
|
-
XPath.attr(:name).equals(locator) |
|
100
|
-
XPath.attr(:placeholder).equals(locator) |
|
101
|
-
XPath.attr(:id).equals(XPath.anywhere(:label)[XPath.string.n.is(locator)].attr(:for))
|
102
|
-
attr_matchers |= XPath.attr(:'aria-label').is(locator) if Capybara.enable_aria_label
|
2
|
+
require 'capybara/selector/selector'
|
3
|
+
Capybara::Selector::FilterSet.add(:_field) do
|
4
|
+
filter(:checked, :boolean) { |node, value| not(value ^ node.checked?) }
|
5
|
+
filter(:unchecked, :boolean) { |node, value| (value ^ node.checked?) }
|
6
|
+
filter(:disabled, :boolean, default: false, skip_if: :all) { |node, value| not(value ^ node.disabled?) }
|
7
|
+
filter(:multiple, :boolean) { |node, value| !(value ^ node.multiple?) }
|
103
8
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
9
|
+
describe do |options|
|
10
|
+
desc, states = String.new, []
|
11
|
+
states << 'checked' if options[:checked] || (options[:unchecked] === false)
|
12
|
+
states << 'not checked' if options[:unchecked] || (options[:checked] === false)
|
13
|
+
states << 'disabled' if options[:disabled] == true
|
14
|
+
desc << " that is #{states.join(' and ')}" unless states.empty?
|
15
|
+
desc << " with the multiple attribute" if options[:multiple] == true
|
16
|
+
desc << " without the multiple attribute" if options[:multiple] === false
|
17
|
+
desc
|
108
18
|
end
|
109
19
|
end
|
110
20
|
|
21
|
+
##
|
22
|
+
#
|
23
|
+
# Select elements by XPath expression
|
24
|
+
#
|
25
|
+
# @locator An XPath expression
|
26
|
+
#
|
111
27
|
Capybara.add_selector(:xpath) do
|
112
28
|
xpath { |xpath| xpath }
|
113
29
|
end
|
114
30
|
|
31
|
+
##
|
32
|
+
#
|
33
|
+
# Select elements by CSS selector
|
34
|
+
#
|
35
|
+
# @locator A CSS selector
|
36
|
+
#
|
115
37
|
Capybara.add_selector(:css) do
|
116
38
|
css { |css| css }
|
117
39
|
end
|
118
40
|
|
41
|
+
##
|
42
|
+
#
|
43
|
+
# Select element by id
|
44
|
+
#
|
45
|
+
# @locator The id of the element to match
|
46
|
+
#
|
119
47
|
Capybara.add_selector(:id) do
|
120
48
|
xpath { |id| XPath.descendant[XPath.attr(:id) == id.to_s] }
|
121
49
|
end
|
122
50
|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
states << 'disabled' if options[:disabled] == true
|
140
|
-
desc << " that is #{states.join(' and ')}" unless states.empty?
|
141
|
-
desc << " with the multiple attribute" if options[:multiple] == true
|
142
|
-
desc << " without the multiple attribute" if options[:multiple] === false
|
143
|
-
desc
|
144
|
-
end
|
145
|
-
end
|
146
|
-
|
51
|
+
##
|
52
|
+
#
|
53
|
+
# Select field elements (input [not of type submit, image, or hidden], textarea, select)
|
54
|
+
#
|
55
|
+
# @locator Matches against the id, name, or placeholder
|
56
|
+
# @filter [String] :id Matches the id attribute
|
57
|
+
# @filter [String] :name Matches the name attribute
|
58
|
+
# @filter [String] :placeholder Matches the placeholder attribute
|
59
|
+
# @filter [String] :type Matches the type attribute of the field or element type for 'textarea' and 'select'
|
60
|
+
# @filter [Boolean] :readonly
|
61
|
+
# @filter [String] :with Matches the current value of the field
|
62
|
+
# @filter [String, Array<String>] :class Matches the class(es) provided
|
63
|
+
# @filter [Boolean] :checked Match checked fields?
|
64
|
+
# @filter [Boolean] :unchecked Match unchecked fields?
|
65
|
+
# @filter [Boolean] :disabled Match disabled field?
|
66
|
+
# @filter [Boolean] :multiple Match fields that accept multiple values
|
147
67
|
Capybara.add_selector(:field) do
|
148
|
-
xpath do |locator|
|
68
|
+
xpath(:id, :name, :placeholder, :type, :class) do |locator, options|
|
149
69
|
xpath = XPath.descendant(:input, :textarea, :select)[~XPath.attr(:type).one_of('submit', 'image', 'hidden')]
|
150
|
-
|
70
|
+
if options[:type]
|
71
|
+
type=options[:type].to_s
|
72
|
+
if ['textarea', 'select'].include?(type)
|
73
|
+
xpath = XPath.descendant(type.to_sym)
|
74
|
+
else
|
75
|
+
xpath = xpath[XPath.attr(:type).equals(type)]
|
76
|
+
end
|
77
|
+
end
|
78
|
+
xpath=locate_field(xpath, locator, options)
|
151
79
|
xpath
|
152
80
|
end
|
153
81
|
|
154
|
-
filter_set(:_field)
|
82
|
+
filter_set(:_field) # checked/unchecked/disabled/multiple
|
155
83
|
|
156
|
-
filter(:readonly, boolean
|
84
|
+
filter(:readonly, :boolean) { |node, value| not(value ^ node.readonly?) }
|
157
85
|
filter(:with) do |node, with|
|
158
86
|
with.is_a?(Regexp) ? node.value =~ with : node.value == with.to_s
|
159
87
|
end
|
160
|
-
filter(:type) do |node, type|
|
161
|
-
type = type.to_s
|
162
|
-
if ['textarea', 'select'].include?(type)
|
163
|
-
node.tag_name == type
|
164
|
-
else
|
165
|
-
node[:type] == type
|
166
|
-
end
|
167
|
-
end
|
168
88
|
describe do |options|
|
169
|
-
desc
|
89
|
+
desc = String.new
|
90
|
+
(expression_filters - [:type]).each { |ef| desc << " with #{ef.to_s} #{options[ef]}" if options.has_key?(ef) }
|
170
91
|
desc << " of type #{options[:type].inspect}" if options[:type]
|
171
92
|
desc << " with value #{options[:with].to_s.inspect}" if options.has_key?(:with)
|
172
93
|
desc
|
173
94
|
end
|
174
95
|
end
|
175
96
|
|
97
|
+
##
|
98
|
+
#
|
99
|
+
# Select fieldset elements
|
100
|
+
#
|
101
|
+
# @locator Matches id or contents of wrapped legend
|
102
|
+
#
|
103
|
+
# @filter [String] :id Matches id attribute
|
104
|
+
# @filter [String] :legend Matches contents of wrapped legend
|
105
|
+
# @filter [String, Array<String>] :class Matches the class(es) provided
|
106
|
+
#
|
176
107
|
Capybara.add_selector(:fieldset) do
|
177
|
-
xpath do |locator|
|
108
|
+
xpath(:id, :legend, :class) do |locator, options|
|
178
109
|
xpath = XPath.descendant(:fieldset)
|
179
110
|
xpath = xpath[XPath.attr(:id).equals(locator.to_s) | XPath.child(:legend)[XPath.string.n.is(locator.to_s)]] unless locator.nil?
|
111
|
+
xpath = xpath[XPath.attr(:id).equals(options[:id])] if options[:id]
|
112
|
+
xpath = xpath[XPath.child(:legend)[XPath.string.n.is(options[:legend])]] if options[:legend]
|
113
|
+
xpath = xpath[find_by_attr(:class, options[:class])]
|
180
114
|
xpath
|
181
115
|
end
|
182
116
|
end
|
183
117
|
|
118
|
+
##
|
119
|
+
#
|
120
|
+
# Find links ( <a> elements with an href attribute )
|
121
|
+
#
|
122
|
+
# @locator Matches the id or title attributes, or the string content of the link, or the alt attribute of a contained img element
|
123
|
+
#
|
124
|
+
# @filter [String] :id Matches the id attribute
|
125
|
+
# @filter [String] :title Matches the title attribute
|
126
|
+
# @filter [String] :alt Matches the alt attribute of a contained img element
|
127
|
+
# @filter [String] :class Matches the class(es) provided
|
128
|
+
# @filter [String, Regexp] :href Matches the normalized href of the link
|
129
|
+
#
|
184
130
|
Capybara.add_selector(:link) do
|
185
|
-
xpath do |locator|
|
131
|
+
xpath(:id, :title, :alt, :class) do |locator, options={}|
|
186
132
|
xpath = XPath.descendant(:a)[XPath.attr(:href)]
|
187
133
|
unless locator.nil?
|
188
134
|
locator = locator.to_s
|
@@ -193,6 +139,8 @@ Capybara.add_selector(:link) do
|
|
193
139
|
matchers |= XPath.attr(:'aria-label').is(locator) if Capybara.enable_aria_label
|
194
140
|
xpath = xpath[matchers]
|
195
141
|
end
|
142
|
+
xpath = [:id, :title, :class].inject(xpath) { |memo, ef| memo[find_by_attr(ef, options[ef])] }
|
143
|
+
xpath = xpath[XPath.descendant(:img)[XPath.attr(:alt).equals(options[:alt])]] if options[:alt]
|
196
144
|
xpath
|
197
145
|
end
|
198
146
|
|
@@ -207,8 +155,19 @@ Capybara.add_selector(:link) do
|
|
207
155
|
describe { |options| " with href #{options[:href].inspect}" if options[:href] }
|
208
156
|
end
|
209
157
|
|
158
|
+
##
|
159
|
+
#
|
160
|
+
# Find buttons ( input [of type submit, reset, image, button] or button elements )
|
161
|
+
#
|
162
|
+
# @locator Matches the id, value, or title attributes, string content of a button, or the alt attribute of an image type button
|
163
|
+
#
|
164
|
+
# @filter [String] :id Matches the id attribute
|
165
|
+
# @filter [String] :title Matches the title attribute
|
166
|
+
# @filter [String] :class Matches the class(es) provided
|
167
|
+
# @filter [String] :value Matches the value of an input button
|
168
|
+
#
|
210
169
|
Capybara.add_selector(:button) do
|
211
|
-
xpath do |locator|
|
170
|
+
xpath(:id, :value, :title, :class) do |locator, options={}|
|
212
171
|
input_btn_xpath = XPath.descendant(:input)[XPath.attr(:type).one_of('submit', 'reset', 'image', 'button')]
|
213
172
|
btn_xpath = XPath.descendant(:button)
|
214
173
|
image_btn_xpath = XPath.descendant(:input)[XPath.attr(:type).equals('image')]
|
@@ -227,82 +186,158 @@ Capybara.add_selector(:button) do
|
|
227
186
|
image_btn_xpath = image_btn_xpath[alt_matches]
|
228
187
|
end
|
229
188
|
|
230
|
-
input_btn_xpath + btn_xpath + image_btn_xpath
|
189
|
+
res_xpath = input_btn_xpath + btn_xpath + image_btn_xpath
|
190
|
+
|
191
|
+
res_xpath = expression_filters.inject(res_xpath) { |memo, ef| memo[find_by_attr(ef, options[ef])] }
|
192
|
+
|
193
|
+
res_xpath
|
231
194
|
end
|
232
195
|
|
233
|
-
filter(:disabled,
|
196
|
+
filter(:disabled, :boolean, default: false, skip_if: :all) { |node, value| not(value ^ node.disabled?) }
|
234
197
|
|
235
|
-
describe
|
198
|
+
describe do |options|
|
199
|
+
desc = String.new
|
200
|
+
desc << " that is disabled" if options[:disabled] == true
|
201
|
+
expression_filters.each { |ef| desc << " with #{ef.to_s} #{options[ef]}" if options.has_key?(ef) }
|
202
|
+
desc
|
203
|
+
end
|
236
204
|
end
|
237
205
|
|
206
|
+
##
|
207
|
+
#
|
208
|
+
# Find links or buttons
|
209
|
+
#
|
238
210
|
Capybara.add_selector(:link_or_button) do
|
239
211
|
label "link or button"
|
240
|
-
xpath do |locator|
|
241
|
-
self.class.all.values_at(:link, :button).map {|selector| selector.xpath.call(locator)}.reduce(:+)
|
212
|
+
xpath do |locator, options|
|
213
|
+
self.class.all.values_at(:link, :button).map {|selector| selector.xpath.call(locator, options)}.reduce(:+)
|
242
214
|
end
|
243
215
|
|
244
|
-
filter(:disabled,
|
216
|
+
filter(:disabled, :boolean, default: false, skip_if: :all) { |node, value| node.tag_name == "a" or not(value ^ node.disabled?) }
|
245
217
|
|
246
218
|
describe { |options| " that is disabled" if options[:disabled] }
|
247
219
|
end
|
248
220
|
|
221
|
+
##
|
222
|
+
#
|
223
|
+
# Find text fillable fields ( textarea, input [not of type submit, image, radio, checkbox, hidden, file] )
|
224
|
+
#
|
225
|
+
# @locator Matches against the id, name, or placeholder
|
226
|
+
# @filter [String] :id Matches the id attribute
|
227
|
+
# @filter [String] :name Matches the name attribute
|
228
|
+
# @filter [String] :placeholder Matches the placeholder attribute
|
229
|
+
# @filter [String] :with Matches the current value of the field
|
230
|
+
# @filter [String, Array<String>] :class Matches the class(es) provided
|
231
|
+
# @filter [Boolean] :disabled Match disabled field?
|
232
|
+
# @filter [Boolean] :multiple Match fields that accept multiple values
|
233
|
+
#
|
249
234
|
Capybara.add_selector(:fillable_field) do
|
250
235
|
label "field"
|
251
|
-
xpath do |locator|
|
236
|
+
xpath(:id, :name, :placeholder, :class) do |locator, options|
|
252
237
|
xpath = XPath.descendant(:input, :textarea)[~XPath.attr(:type).one_of('submit', 'image', 'radio', 'checkbox', 'hidden', 'file')]
|
253
|
-
|
254
|
-
|
238
|
+
locate_field(xpath, locator, options)
|
239
|
+
end
|
240
|
+
|
241
|
+
filter_set(:_field, [:disabled, :multiple])
|
242
|
+
|
243
|
+
filter(:with) do |node, with|
|
244
|
+
with.is_a?(Regexp) ? node.value =~ with : node.value == with.to_s
|
255
245
|
end
|
256
246
|
|
257
|
-
|
247
|
+
describe do |options|
|
248
|
+
desc = String.new
|
249
|
+
expression_filters.each { |ef| desc << " with #{ef.to_s} #{options[ef]}" if options.has_key?(ef) }
|
250
|
+
desc << " with value #{options[:with].to_s.inspect}" if options.has_key?(:with)
|
251
|
+
desc
|
252
|
+
end
|
258
253
|
end
|
259
254
|
|
255
|
+
##
|
256
|
+
#
|
257
|
+
# Find radio buttons
|
258
|
+
#
|
259
|
+
# @locator Match id, name, or associated label text
|
260
|
+
# @filter [String] :id Matches the id attribute
|
261
|
+
# @filter [String] :name Matches the name attribute
|
262
|
+
# @filter [String, Array<String>] :class Matches the class(es) provided
|
263
|
+
# @filter [Boolean] :checked Match checked fields?
|
264
|
+
# @filter [Boolean] :unchecked Match unchecked fields?
|
265
|
+
# @filter [Boolean] :disabled Match disabled field?
|
266
|
+
# @filter [String] :option Match the value
|
267
|
+
#
|
260
268
|
Capybara.add_selector(:radio_button) do
|
261
269
|
label "radio button"
|
262
|
-
xpath do |locator|
|
270
|
+
xpath(:id, :name, :class) do |locator, options|
|
263
271
|
xpath = XPath.descendant(:input)[XPath.attr(:type).equals('radio')]
|
264
|
-
|
265
|
-
xpath
|
272
|
+
locate_field(xpath, locator, options)
|
266
273
|
end
|
267
274
|
|
268
|
-
filter_set(:_field, [:
|
275
|
+
filter_set(:_field, [:checked, :unchecked, :disabled])
|
269
276
|
|
270
277
|
filter(:option) { |node, value| node.value == value.to_s }
|
271
278
|
|
272
279
|
describe do |options|
|
273
280
|
desc = String.new
|
274
281
|
desc << " with value #{options[:option].inspect}" if options[:option]
|
282
|
+
expression_filters.each { |ef| desc << " with #{ef.to_s} #{options[ef]}" if options.has_key?(ef) }
|
275
283
|
desc
|
276
284
|
end
|
277
285
|
end
|
278
286
|
|
287
|
+
##
|
288
|
+
#
|
289
|
+
# Find checkboxes
|
290
|
+
#
|
291
|
+
# @locator Match id, name, or associated label text
|
292
|
+
# @filter [String] :id Matches the id attribute
|
293
|
+
# @filter [String] :name Matches the name attribute
|
294
|
+
# @filter [String, Array<String>] :class Matches the class(es) provided
|
295
|
+
# @filter [Boolean] :checked Match checked fields?
|
296
|
+
# @filter [Boolean] :unchecked Match unchecked fields?
|
297
|
+
# @filter [Boolean] :disabled Match disabled field?
|
298
|
+
# @filter [String] :option Match the value
|
299
|
+
#
|
279
300
|
Capybara.add_selector(:checkbox) do
|
280
|
-
xpath do |locator|
|
301
|
+
xpath(:id, :name, :class) do |locator, options|
|
281
302
|
xpath = XPath.descendant(:input)[XPath.attr(:type).equals('checkbox')]
|
282
|
-
|
283
|
-
xpath
|
303
|
+
locate_field(xpath, locator, options)
|
284
304
|
end
|
285
305
|
|
286
|
-
filter_set(:_field, [:
|
306
|
+
filter_set(:_field, [:checked, :unchecked, :disabled])
|
287
307
|
|
288
308
|
filter(:option) { |node, value| node.value == value.to_s }
|
289
309
|
|
290
310
|
describe do |options|
|
291
311
|
desc = String.new
|
292
312
|
desc << " with value #{options[:option].inspect}" if options[:option]
|
313
|
+
expression_filters.each { |ef| desc << " with #{ef.to_s} #{options[ef]}" if options.has_key?(ef) }
|
293
314
|
desc
|
294
315
|
end
|
295
316
|
end
|
296
317
|
|
318
|
+
##
|
319
|
+
#
|
320
|
+
# Find select elements
|
321
|
+
#
|
322
|
+
# @locator Match id, name, placeholder, or associated label text
|
323
|
+
# @filter [String] :id Matches the id attribute
|
324
|
+
# @filter [String] :name Matches the name attribute
|
325
|
+
# @filter [String] :placeholder Matches the placeholder attribute
|
326
|
+
# @filter [String, Array<String>] :class Matches the class(es) provided
|
327
|
+
# @filter [Boolean] :disabled Match disabled field?
|
328
|
+
# @filter [Boolean] :multiple Match fields that accept multiple values
|
329
|
+
# @filter [Array<String>] :options Exact match options
|
330
|
+
# @filter [Array<String>] :with_options Partial match options
|
331
|
+
# @filter [String, Array<String>] :selected Match the selection(s)
|
332
|
+
#
|
297
333
|
Capybara.add_selector(:select) do
|
298
334
|
label "select box"
|
299
|
-
xpath do |locator|
|
335
|
+
xpath(:id, :name, :placeholder, :class) do |locator, options|
|
300
336
|
xpath = XPath.descendant(:select)
|
301
|
-
|
302
|
-
xpath
|
337
|
+
locate_field(xpath, locator, options)
|
303
338
|
end
|
304
339
|
|
305
|
-
filter_set(:_field, [:
|
340
|
+
filter_set(:_field, [:disabled, :multiple])
|
306
341
|
|
307
342
|
filter(:options) do |node, options|
|
308
343
|
if node.visible?
|
@@ -329,10 +364,19 @@ Capybara.add_selector(:select) do
|
|
329
364
|
desc << " with options #{options[:options].inspect}" if options[:options]
|
330
365
|
desc << " with at least options #{options[:with_options].inspect}" if options[:with_options]
|
331
366
|
desc << " with #{options[:selected].inspect} selected" if options[:selected]
|
367
|
+
expression_filters.each { |ef| desc << " with #{ef.to_s} #{options[ef]}" if options.has_key?(ef) }
|
332
368
|
desc
|
333
369
|
end
|
334
370
|
end
|
335
371
|
|
372
|
+
##
|
373
|
+
#
|
374
|
+
# Find option elements
|
375
|
+
#
|
376
|
+
# @locator Match text of option
|
377
|
+
# @filter [Boolean] :disabled Match disabled option
|
378
|
+
# @filter [Boolean] :selected Match selected option
|
379
|
+
#
|
336
380
|
Capybara.add_selector(:option) do
|
337
381
|
xpath do |locator|
|
338
382
|
xpath = XPath.descendant(:option)
|
@@ -340,8 +384,8 @@ Capybara.add_selector(:option) do
|
|
340
384
|
xpath
|
341
385
|
end
|
342
386
|
|
343
|
-
filter(:disabled, boolean
|
344
|
-
filter(:selected, boolean
|
387
|
+
filter(:disabled, :boolean) { |node, value| not(value ^ node.disabled?) }
|
388
|
+
filter(:selected, :boolean) { |node, value| not(value ^ node.selected?) }
|
345
389
|
|
346
390
|
describe do |options|
|
347
391
|
desc = String.new
|
@@ -351,17 +395,40 @@ Capybara.add_selector(:option) do
|
|
351
395
|
end
|
352
396
|
end
|
353
397
|
|
398
|
+
##
|
399
|
+
#
|
400
|
+
# Find file input elements
|
401
|
+
#
|
402
|
+
# @locator Match id, name, or associated label text
|
403
|
+
# @filter [String] :id Matches the id attribute
|
404
|
+
# @filter [String] :name Matches the name attribute
|
405
|
+
# @filter [String, Array<String>] :class Matches the class(es) provided
|
406
|
+
# @filter [Boolean] :disabled Match disabled field?
|
407
|
+
# @filter [Boolean] :multiple Match field that accepts multiple values
|
408
|
+
#
|
354
409
|
Capybara.add_selector(:file_field) do
|
355
410
|
label "file field"
|
356
|
-
xpath do |locator|
|
411
|
+
xpath(:id, :name, :class) do |locator, options|
|
357
412
|
xpath = XPath.descendant(:input)[XPath.attr(:type).equals('file')]
|
358
|
-
|
359
|
-
xpath
|
413
|
+
locate_field(xpath, locator, options)
|
360
414
|
end
|
361
415
|
|
362
|
-
filter_set(:_field, [:
|
416
|
+
filter_set(:_field, [:disabled, :multiple])
|
417
|
+
|
418
|
+
describe do |options|
|
419
|
+
desc = String.new
|
420
|
+
expression_filters.each { |ef| desc << " with #{ef.to_s} #{options[ef]}" if options.has_key?(ef) }
|
421
|
+
desc
|
422
|
+
end
|
363
423
|
end
|
364
424
|
|
425
|
+
##
|
426
|
+
#
|
427
|
+
# Find label elements
|
428
|
+
#
|
429
|
+
# @locator Match id or text contents
|
430
|
+
# @filter [Element, String] :for The element or id of the element associated with the label
|
431
|
+
#
|
365
432
|
Capybara.add_selector(:label) do
|
366
433
|
label "label"
|
367
434
|
xpath do |locator|
|
@@ -389,18 +456,53 @@ Capybara.add_selector(:label) do
|
|
389
456
|
end
|
390
457
|
end
|
391
458
|
|
459
|
+
##
|
460
|
+
#
|
461
|
+
# Find table elements
|
462
|
+
#
|
463
|
+
# @locator id or caption text of table
|
464
|
+
# @filter [String] :id Match id attribute of table
|
465
|
+
# @filter [String] :caption Match text of associated caption
|
466
|
+
# @filter [String, Array<String>] :class Matches the class(es) provided
|
467
|
+
#
|
392
468
|
Capybara.add_selector(:table) do
|
393
|
-
xpath do |locator|
|
469
|
+
xpath(:id, :caption, :class) do |locator, options|
|
394
470
|
xpath = XPath.descendant(:table)
|
395
471
|
xpath = xpath[XPath.attr(:id).equals(locator.to_s) | XPath.descendant(:caption).is(locator.to_s)] unless locator.nil?
|
472
|
+
xpath = xpath[XPath.descendant(:caption).equals(options[:caption])] if options[:caption]
|
473
|
+
xpath = [:id, :class].inject(xpath) { |memo, ef| memo[find_by_attr(ef, options[ef])] }
|
396
474
|
xpath
|
397
475
|
end
|
476
|
+
|
477
|
+
describe do |options|
|
478
|
+
desc = String.new
|
479
|
+
desc << " with id #{options[:id]}" if options[:id]
|
480
|
+
desc << " with caption #{options[:caption]}" if options[:caption]
|
481
|
+
desc
|
482
|
+
end
|
398
483
|
end
|
399
484
|
|
485
|
+
##
|
486
|
+
#
|
487
|
+
# Find frame/iframe elements
|
488
|
+
#
|
489
|
+
# @locator Match id or name
|
490
|
+
# @filter [String] :id Match id attribute
|
491
|
+
# @filter [String] :name Match name attribute
|
492
|
+
# @filter [String, Array<String>] :class Matches the class(es) provided
|
493
|
+
#
|
400
494
|
Capybara.add_selector(:frame) do
|
401
|
-
xpath do |locator|
|
495
|
+
xpath(:id, :name, :class) do |locator, options|
|
402
496
|
xpath = XPath.descendant(:iframe) + XPath.descendant(:frame)
|
403
497
|
xpath = xpath[XPath.attr(:id).equals(locator.to_s) | XPath.attr(:name).equals(locator)] unless locator.nil?
|
498
|
+
xpath = expression_filters.inject(xpath) { |memo, ef| memo[find_by_attr(ef, options[ef])] }
|
404
499
|
xpath
|
405
500
|
end
|
501
|
+
|
502
|
+
describe do |options|
|
503
|
+
desc = String.new
|
504
|
+
desc << " with id #{options[:id]}" if options[:id]
|
505
|
+
desc << " with name #{options[:name]}" if options[:name]
|
506
|
+
desc
|
507
|
+
end
|
406
508
|
end
|