capybara 2.9.0 → 2.10.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 +23 -0
- data/README.md +37 -30
- data/lib/capybara/node/actions.rb +18 -15
- data/lib/capybara/node/base.rb +2 -2
- data/lib/capybara/node/element.rb +1 -1
- data/lib/capybara/node/finders.rb +37 -32
- data/lib/capybara/node/matchers.rb +187 -171
- data/lib/capybara/queries/match_query.rb +1 -3
- data/lib/capybara/queries/selector_query.rb +48 -19
- data/lib/capybara/rack_test/driver.rb +3 -3
- data/lib/capybara/rails.rb +1 -1
- data/lib/capybara/rspec/features.rb +1 -1
- data/lib/capybara/rspec/matchers.rb +31 -22
- data/lib/capybara/selector/css.rb +30 -0
- data/lib/capybara/selector/selector.rb +24 -1
- data/lib/capybara/selector.rb +22 -23
- data/lib/capybara/session.rb +4 -4
- data/lib/capybara/spec/session/accept_alert_spec.rb +4 -4
- data/lib/capybara/spec/session/accept_confirm_spec.rb +1 -1
- data/lib/capybara/spec/session/accept_prompt_spec.rb +5 -5
- data/lib/capybara/spec/session/all_spec.rb +27 -27
- data/lib/capybara/spec/session/assert_current_path.rb +2 -2
- data/lib/capybara/spec/session/assert_selector.rb +28 -28
- data/lib/capybara/spec/session/assert_text.rb +20 -20
- data/lib/capybara/spec/session/assert_title.rb +2 -2
- data/lib/capybara/spec/session/attach_file_spec.rb +2 -2
- data/lib/capybara/spec/session/check_spec.rb +19 -10
- data/lib/capybara/spec/session/choose_spec.rb +4 -4
- data/lib/capybara/spec/session/click_button_spec.rb +18 -5
- data/lib/capybara/spec/session/click_link_or_button_spec.rb +6 -6
- data/lib/capybara/spec/session/click_link_spec.rb +13 -13
- data/lib/capybara/spec/session/current_url_spec.rb +2 -2
- data/lib/capybara/spec/session/dismiss_confirm_spec.rb +1 -1
- data/lib/capybara/spec/session/dismiss_prompt_spec.rb +1 -1
- data/lib/capybara/spec/session/element/matches_selector_spec.rb +21 -8
- data/lib/capybara/spec/session/evaluate_script_spec.rb +1 -1
- data/lib/capybara/spec/session/execute_script_spec.rb +2 -2
- data/lib/capybara/spec/session/fill_in_spec.rb +30 -30
- data/lib/capybara/spec/session/find_button_spec.rb +5 -5
- data/lib/capybara/spec/session/find_by_id_spec.rb +2 -2
- data/lib/capybara/spec/session/find_field_spec.rb +11 -7
- data/lib/capybara/spec/session/find_link_spec.rb +2 -2
- data/lib/capybara/spec/session/find_spec.rb +57 -48
- data/lib/capybara/spec/session/first_spec.rb +9 -9
- data/lib/capybara/spec/session/go_back_spec.rb +1 -1
- data/lib/capybara/spec/session/go_forward_spec.rb +1 -1
- data/lib/capybara/spec/session/has_button_spec.rb +12 -12
- data/lib/capybara/spec/session/has_css_spec.rb +73 -73
- data/lib/capybara/spec/session/has_current_path_spec.rb +2 -2
- data/lib/capybara/spec/session/has_field_spec.rb +78 -68
- data/lib/capybara/spec/session/has_link_spec.rb +8 -8
- data/lib/capybara/spec/session/has_select_spec.rb +70 -61
- data/lib/capybara/spec/session/has_selector_spec.rb +28 -28
- data/lib/capybara/spec/session/has_text_spec.rb +6 -6
- data/lib/capybara/spec/session/has_title_spec.rb +2 -2
- data/lib/capybara/spec/session/has_xpath_spec.rb +26 -26
- data/lib/capybara/spec/session/headers.rb +1 -1
- data/lib/capybara/spec/session/html_spec.rb +3 -3
- data/lib/capybara/spec/session/node_spec.rb +15 -15
- data/lib/capybara/spec/session/reset_session_spec.rb +4 -4
- data/lib/capybara/spec/session/response_code.rb +1 -1
- data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +3 -3
- data/lib/capybara/spec/session/screenshot_spec.rb +1 -1
- data/lib/capybara/spec/session/select_spec.rb +30 -30
- data/lib/capybara/spec/session/selectors_spec.rb +6 -2
- data/lib/capybara/spec/session/uncheck_spec.rb +2 -2
- data/lib/capybara/spec/session/unselect_spec.rb +14 -14
- data/lib/capybara/spec/session/visit_spec.rb +2 -2
- data/lib/capybara/spec/session/within_frame_spec.rb +2 -2
- data/lib/capybara/spec/session/within_spec.rb +6 -6
- data/lib/capybara/spec/spec_helper.rb +1 -1
- data/lib/capybara/spec/test_app.rb +1 -1
- data/lib/capybara/spec/views/form.erb +6 -3
- data/lib/capybara/version.rb +1 -1
- data/lib/capybara.rb +2 -2
- data/spec/basic_node_spec.rb +5 -5
- data/spec/capybara_spec.rb +1 -1
- data/spec/dsl_spec.rb +1 -1
- data/spec/rack_test_spec.rb +7 -7
- data/spec/rspec/matchers_spec.rb +6 -6
- data/spec/rspec_spec.rb +1 -1
- data/spec/selector_spec.rb +94 -3
- data/spec/selenium_firefox_spec.rb +1 -1
- data/spec/selenium_spec_chrome.rb +1 -1
- data/spec/shared_selenium_session.rb +10 -10
- metadata +3 -2
|
@@ -4,14 +4,18 @@ module Capybara
|
|
|
4
4
|
class SelectorQuery < Queries::BaseQuery
|
|
5
5
|
attr_accessor :selector, :locator, :options, :expression, :find, :negative
|
|
6
6
|
|
|
7
|
-
VALID_KEYS = COUNT_KEYS + [:text, :visible, :exact, :match, :wait, :filter_set]
|
|
7
|
+
VALID_KEYS = COUNT_KEYS + [:text, :id, :class, :visible, :exact, :match, :wait, :filter_set]
|
|
8
8
|
VALID_MATCH = [:first, :smart, :prefer_exact, :one]
|
|
9
9
|
|
|
10
|
-
def initialize(*args)
|
|
10
|
+
def initialize(*args, &filter_block)
|
|
11
11
|
@options = if args.last.is_a?(Hash) then args.pop.dup else {} end
|
|
12
|
+
@filter_block = filter_block
|
|
12
13
|
|
|
13
14
|
if args[0].is_a?(Symbol)
|
|
14
|
-
@selector = Selector.all
|
|
15
|
+
@selector = Selector.all.fetch(args.shift) do |selector_type|
|
|
16
|
+
warn "Unknown selector type (:#{selector_type}), defaulting to :#{Capybara.default_selector} - This will raise an exception in a future version of Capybara"
|
|
17
|
+
nil
|
|
18
|
+
end
|
|
15
19
|
@locator = args.shift
|
|
16
20
|
else
|
|
17
21
|
@selector = Selector.all.values.find { |s| s.match?(args[0]) }
|
|
@@ -39,7 +43,10 @@ module Capybara
|
|
|
39
43
|
def description
|
|
40
44
|
@description = String.new("#{label} #{locator.inspect}")
|
|
41
45
|
@description << " with text #{options[:text].inspect}" if options[:text]
|
|
46
|
+
@description << " with id #{options[:id]}" if options[:id]
|
|
47
|
+
@description << " with classes #{Array(options[:class]).join(',')}]" if options[:class]
|
|
42
48
|
@description << selector.description(options)
|
|
49
|
+
@description << " that also matches the custom filter block" if @filter_block
|
|
43
50
|
@description
|
|
44
51
|
end
|
|
45
52
|
|
|
@@ -54,7 +61,7 @@ module Capybara
|
|
|
54
61
|
when :hidden then return false if node.visible?
|
|
55
62
|
end
|
|
56
63
|
|
|
57
|
-
query_filters.all? do |name, filter|
|
|
64
|
+
res = query_filters.all? do |name, filter|
|
|
58
65
|
if options.has_key?(name)
|
|
59
66
|
filter.matches?(node, options[name])
|
|
60
67
|
elsif filter.default?
|
|
@@ -63,21 +70,16 @@ module Capybara
|
|
|
63
70
|
true
|
|
64
71
|
end
|
|
65
72
|
end
|
|
73
|
+
|
|
74
|
+
res &&= @filter_block.call(node) unless @filter_block.nil?
|
|
75
|
+
res
|
|
66
76
|
end
|
|
67
77
|
|
|
68
78
|
def visible
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
else @options[:visible]
|
|
74
|
-
end
|
|
75
|
-
else
|
|
76
|
-
if Capybara.ignore_hidden_elements
|
|
77
|
-
:visible
|
|
78
|
-
else
|
|
79
|
-
:all
|
|
80
|
-
end
|
|
79
|
+
case (vis = options.fetch(:visible){ @selector.default_visibility })
|
|
80
|
+
when true then :visible
|
|
81
|
+
when false then :all
|
|
82
|
+
else vis
|
|
81
83
|
end
|
|
82
84
|
end
|
|
83
85
|
|
|
@@ -92,15 +94,16 @@ module Capybara
|
|
|
92
94
|
|
|
93
95
|
def xpath(exact=nil)
|
|
94
96
|
exact = self.exact? if exact.nil?
|
|
95
|
-
if @expression.respond_to?(:to_xpath) and exact
|
|
97
|
+
expr = if @expression.respond_to?(:to_xpath) and exact
|
|
96
98
|
@expression.to_xpath(:exact)
|
|
97
99
|
else
|
|
98
100
|
@expression.to_s
|
|
99
101
|
end
|
|
102
|
+
filtered_xpath(expr)
|
|
100
103
|
end
|
|
101
104
|
|
|
102
105
|
def css
|
|
103
|
-
@expression
|
|
106
|
+
filtered_css(@expression)
|
|
104
107
|
end
|
|
105
108
|
|
|
106
109
|
# @api private
|
|
@@ -141,7 +144,7 @@ module Capybara
|
|
|
141
144
|
end
|
|
142
145
|
|
|
143
146
|
def custom_keys
|
|
144
|
-
query_filters.keys + @selector.expression_filters
|
|
147
|
+
@custom_keys ||= query_filters.keys + @selector.expression_filters
|
|
145
148
|
end
|
|
146
149
|
|
|
147
150
|
def assert_valid_keys
|
|
@@ -151,6 +154,32 @@ module Capybara
|
|
|
151
154
|
end
|
|
152
155
|
end
|
|
153
156
|
|
|
157
|
+
def filtered_xpath(expr)
|
|
158
|
+
if options.has_key?(:id) || options.has_key?(:class)
|
|
159
|
+
expr = "(#{expr})"
|
|
160
|
+
expr = "#{expr}[#{XPath.attr(:id) == options[:id]}]" if options.has_key?(:id) && !custom_keys.include?(:id)
|
|
161
|
+
if options.has_key?(:class) && !custom_keys.include?(:class)
|
|
162
|
+
class_xpath = Array(options[:class]).map do |klass|
|
|
163
|
+
"contains(concat(' ',normalize-space(@class),' '),' #{klass} ')"
|
|
164
|
+
end.join(" and ")
|
|
165
|
+
expr = "#{expr}[#{class_xpath}]"
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
expr
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def filtered_css(expr)
|
|
172
|
+
if options.has_key?(:id) || options.has_key?(:class)
|
|
173
|
+
css_selectors = expr.split(',').map(&:rstrip)
|
|
174
|
+
expr = css_selectors.map do |sel|
|
|
175
|
+
sel += "##{Capybara::Selector::CSS.escape(options[:id])}" if options.has_key?(:id) && !custom_keys.include?(:id)
|
|
176
|
+
sel += Array(options[:class]).map { |k| ".#{Capybara::Selector::CSS.escape(k)}"}.join if options.has_key?(:class) && !custom_keys.include?(:class)
|
|
177
|
+
sel
|
|
178
|
+
end.join(", ")
|
|
179
|
+
end
|
|
180
|
+
expr
|
|
181
|
+
end
|
|
182
|
+
|
|
154
183
|
def warn_exact_usage
|
|
155
184
|
if options.has_key?(:exact) && !supports_exact?
|
|
156
185
|
warn "The :exact option only has an effect on queries using the XPath#is method. Using it with the query \"#{expression.to_s}\" has no effect."
|
|
@@ -7,9 +7,9 @@ require 'cgi'
|
|
|
7
7
|
|
|
8
8
|
class Capybara::RackTest::Driver < Capybara::Driver::Base
|
|
9
9
|
DEFAULT_OPTIONS = {
|
|
10
|
-
:
|
|
11
|
-
:
|
|
12
|
-
:
|
|
10
|
+
respect_data_method: false,
|
|
11
|
+
follow_redirects: true,
|
|
12
|
+
redirect_limit: 5
|
|
13
13
|
}
|
|
14
14
|
attr_reader :app, :options
|
|
15
15
|
|
data/lib/capybara/rails.rb
CHANGED
|
@@ -16,5 +16,5 @@ Capybara.save_path = Rails.root.join('tmp/capybara')
|
|
|
16
16
|
|
|
17
17
|
# Override default rack_test driver to respect data-method attributes.
|
|
18
18
|
Capybara.register_driver :rack_test do |app|
|
|
19
|
-
Capybara::RackTest::Driver.new(app, :
|
|
19
|
+
Capybara::RackTest::Driver.new(app, respect_data_method: true)
|
|
20
20
|
end
|
|
@@ -16,8 +16,9 @@ module Capybara
|
|
|
16
16
|
class HaveSelector < Matcher
|
|
17
17
|
attr_reader :failure_message, :failure_message_when_negated
|
|
18
18
|
|
|
19
|
-
def initialize(*args)
|
|
19
|
+
def initialize(*args, &filter_block)
|
|
20
20
|
@args = args
|
|
21
|
+
@filter_block = filter_block
|
|
21
22
|
end
|
|
22
23
|
|
|
23
24
|
def matches?(actual)
|
|
@@ -39,7 +40,7 @@ module Capybara
|
|
|
39
40
|
end
|
|
40
41
|
|
|
41
42
|
def query
|
|
42
|
-
@query ||= Capybara::Queries::SelectorQuery.new(*@args)
|
|
43
|
+
@query ||= Capybara::Queries::SelectorQuery.new(*@args, &@filter_block)
|
|
43
44
|
end
|
|
44
45
|
|
|
45
46
|
# RSpec 2 compatibility:
|
|
@@ -221,8 +222,8 @@ module Capybara
|
|
|
221
222
|
alias_method :failure_message_for_should_not, :failure_message_when_negated
|
|
222
223
|
end
|
|
223
224
|
|
|
224
|
-
def have_selector(*args)
|
|
225
|
-
HaveSelector.new(*args)
|
|
225
|
+
def have_selector(*args, &optional_filter_block)
|
|
226
|
+
HaveSelector.new(*args, &optional_filter_block)
|
|
226
227
|
end
|
|
227
228
|
|
|
228
229
|
def match_selector(*args)
|
|
@@ -233,16 +234,16 @@ module Capybara
|
|
|
233
234
|
::RSpec::Matchers.define_negated_matcher :not_match_selector, :match_selector if defined?(::RSpec::Expectations::Version) && (Gem::Version.new(RSpec::Expectations::Version::STRING) >= Gem::Version.new('3.1'))
|
|
234
235
|
|
|
235
236
|
|
|
236
|
-
def have_xpath(xpath, options={})
|
|
237
|
-
HaveSelector.new(:xpath, xpath, options)
|
|
237
|
+
def have_xpath(xpath, options={}, &optional_filter_block)
|
|
238
|
+
HaveSelector.new(:xpath, xpath, options, &optional_filter_block)
|
|
238
239
|
end
|
|
239
240
|
|
|
240
241
|
def match_xpath(xpath, options={})
|
|
241
242
|
MatchSelector.new(:xpath, xpath, options)
|
|
242
243
|
end
|
|
243
244
|
|
|
244
|
-
def have_css(css, options={})
|
|
245
|
-
HaveSelector.new(:css, css, options)
|
|
245
|
+
def have_css(css, options={}, &optional_filter_block)
|
|
246
|
+
HaveSelector.new(:css, css, options, &optional_filter_block)
|
|
246
247
|
end
|
|
247
248
|
|
|
248
249
|
def match_css(css, options={})
|
|
@@ -262,32 +263,39 @@ module Capybara
|
|
|
262
263
|
HaveCurrentPath.new(path, options)
|
|
263
264
|
end
|
|
264
265
|
|
|
265
|
-
def have_link(locator, options={})
|
|
266
|
-
|
|
266
|
+
def have_link(locator=nil, options={}, &optional_filter_block)
|
|
267
|
+
locator, options = nil, locator if locator.is_a? Hash
|
|
268
|
+
HaveSelector.new(:link, locator, options, &optional_filter_block)
|
|
267
269
|
end
|
|
268
270
|
|
|
269
|
-
def have_button(locator, options={})
|
|
270
|
-
|
|
271
|
+
def have_button(locator=nil, options={}, &optional_filter_block)
|
|
272
|
+
locator, options = nil, locator if locator.is_a? Hash
|
|
273
|
+
HaveSelector.new(:button, locator, options, &optional_filter_block)
|
|
271
274
|
end
|
|
272
275
|
|
|
273
|
-
def have_field(locator, options={})
|
|
274
|
-
|
|
276
|
+
def have_field(locator=nil, options={}, &optional_filter_block)
|
|
277
|
+
locator, options = nil, locator if locator.is_a? Hash
|
|
278
|
+
HaveSelector.new(:field, locator, options, &optional_filter_block)
|
|
275
279
|
end
|
|
276
280
|
|
|
277
|
-
def have_checked_field(locator, options={})
|
|
278
|
-
|
|
281
|
+
def have_checked_field(locator=nil, options={}, &optional_filter_block)
|
|
282
|
+
locator, options = nil, locator if locator.is_a? Hash
|
|
283
|
+
HaveSelector.new(:field, locator, options.merge(checked: true), &optional_filter_block)
|
|
279
284
|
end
|
|
280
285
|
|
|
281
|
-
def have_unchecked_field(locator, options={})
|
|
282
|
-
|
|
286
|
+
def have_unchecked_field(locator=nil, options={}, &optional_filter_block)
|
|
287
|
+
locator, options = nil, locator if locator.is_a? Hash
|
|
288
|
+
HaveSelector.new(:field, locator, options.merge(unchecked: true), &optional_filter_block)
|
|
283
289
|
end
|
|
284
290
|
|
|
285
|
-
def have_select(locator, options={})
|
|
286
|
-
|
|
291
|
+
def have_select(locator=nil, options={}, &optional_filter_block)
|
|
292
|
+
locator, options = nil, locator if locator.is_a? Hash
|
|
293
|
+
HaveSelector.new(:select, locator, options, &optional_filter_block)
|
|
287
294
|
end
|
|
288
295
|
|
|
289
|
-
def have_table(locator, options={})
|
|
290
|
-
|
|
296
|
+
def have_table(locator=nil, options={}, &optional_filter_block)
|
|
297
|
+
locator, options = nil, locator if locator.is_a? Hash
|
|
298
|
+
HaveSelector.new(:table, locator, options, &optional_filter_block)
|
|
291
299
|
end
|
|
292
300
|
|
|
293
301
|
##
|
|
@@ -299,5 +307,6 @@ module Capybara
|
|
|
299
307
|
def become_closed(options = {})
|
|
300
308
|
BecomeClosed.new(options)
|
|
301
309
|
end
|
|
310
|
+
|
|
302
311
|
end
|
|
303
312
|
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
module Capybara
|
|
2
|
+
class Selector
|
|
3
|
+
class CSS
|
|
4
|
+
def self.escape(str)
|
|
5
|
+
out = String.new("")
|
|
6
|
+
value = str.dup
|
|
7
|
+
out << value.slice!(0...1) if value =~ /^[-_]/
|
|
8
|
+
out << if value[0] =~ NMSTART
|
|
9
|
+
value.slice!(0...1)
|
|
10
|
+
else
|
|
11
|
+
escape_char(value.slice!(0...1))
|
|
12
|
+
end
|
|
13
|
+
out << value.gsub(/[^a-zA-Z0-9_-]/) {|c| escape_char c}
|
|
14
|
+
out
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.escape_char(c)
|
|
18
|
+
return "\\%06x" % c.ord() unless c =~ %r{[ -/:-~]}
|
|
19
|
+
"\\#{c}"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
S = '\u{80}-\u{D7FF}\u{E000}-\u{FFFD}\u{10000}-\u{10FFFF}'
|
|
23
|
+
H = /[0-9a-fA-F]/
|
|
24
|
+
UNICODE = /\\#{H}{1,6}[ \t\r\n\f]?/
|
|
25
|
+
NONASCII = /[#{S}]/
|
|
26
|
+
ESCAPE = /#{UNICODE}|\\[ -~#{S}]/
|
|
27
|
+
NMSTART = /[_a-zA-Z]|#{NONASCII}|#{ESCAPE}/
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
require 'capybara/selector/filter_set'
|
|
3
|
+
require 'capybara/selector/css'
|
|
3
4
|
require 'xpath'
|
|
4
5
|
|
|
5
6
|
#Patch XPath to allow a nil condition in where
|
|
@@ -49,6 +50,7 @@ module Capybara
|
|
|
49
50
|
@format = nil
|
|
50
51
|
@expression = nil
|
|
51
52
|
@expression_filters = []
|
|
53
|
+
@default_visibility = nil
|
|
52
54
|
instance_eval(&block)
|
|
53
55
|
end
|
|
54
56
|
|
|
@@ -185,6 +187,27 @@ module Capybara
|
|
|
185
187
|
@filter_set.describe &block
|
|
186
188
|
end
|
|
187
189
|
|
|
190
|
+
##
|
|
191
|
+
#
|
|
192
|
+
# Set the default visibility mode that shouble be used if no visibile option is passed when using the selector.
|
|
193
|
+
# If not specified will default to the behavior indicated by Capybara.ignore_hidden_elements
|
|
194
|
+
#
|
|
195
|
+
# @param [Symbol] default_visibility Only find elements with the specified visibility:
|
|
196
|
+
# * :all - finds visible and invisible elements.
|
|
197
|
+
# * :hidden - only finds invisible elements.
|
|
198
|
+
# * :visible - only finds visible elements.
|
|
199
|
+
def visible(default_visibility)
|
|
200
|
+
@default_visibility = default_visibility
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def default_visibility
|
|
204
|
+
if @default_visibility.nil?
|
|
205
|
+
Capybara.ignore_hidden_elements
|
|
206
|
+
else
|
|
207
|
+
@default_visibility
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
188
211
|
private
|
|
189
212
|
|
|
190
213
|
def locate_field(xpath, locator, options={})
|
|
@@ -201,7 +224,7 @@ module Capybara
|
|
|
201
224
|
locate_xpath += XPath.descendant(:label)[XPath.string.n.is(locator)].descendant(xpath)
|
|
202
225
|
end
|
|
203
226
|
|
|
204
|
-
locate_xpath = [:
|
|
227
|
+
locate_xpath = [:name, :placeholder].inject(locate_xpath) { |memo, ef| memo[find_by_attr(ef, options[ef])] }
|
|
205
228
|
locate_xpath
|
|
206
229
|
end
|
|
207
230
|
|
data/lib/capybara/selector.rb
CHANGED
|
@@ -65,7 +65,7 @@ end
|
|
|
65
65
|
# @filter [Boolean] :disabled Match disabled field?
|
|
66
66
|
# @filter [Boolean] :multiple Match fields that accept multiple values
|
|
67
67
|
Capybara.add_selector(:field) do
|
|
68
|
-
xpath(:
|
|
68
|
+
xpath(:name, :placeholder, :type) do |locator, options|
|
|
69
69
|
xpath = XPath.descendant(:input, :textarea, :select)[~XPath.attr(:type).one_of('submit', 'image', 'hidden')]
|
|
70
70
|
if options[:type]
|
|
71
71
|
type=options[:type].to_s
|
|
@@ -105,12 +105,10 @@ end
|
|
|
105
105
|
# @filter [String, Array<String>] :class Matches the class(es) provided
|
|
106
106
|
#
|
|
107
107
|
Capybara.add_selector(:fieldset) do
|
|
108
|
-
xpath(:
|
|
108
|
+
xpath(:legend) do |locator, options|
|
|
109
109
|
xpath = XPath.descendant(:fieldset)
|
|
110
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
111
|
xpath = xpath[XPath.child(:legend)[XPath.string.n.is(options[:legend])]] if options[:legend]
|
|
113
|
-
xpath = xpath[find_by_attr(:class, options[:class])]
|
|
114
112
|
xpath
|
|
115
113
|
end
|
|
116
114
|
end
|
|
@@ -128,7 +126,7 @@ end
|
|
|
128
126
|
# @filter [String, Regexp] :href Matches the normalized href of the link
|
|
129
127
|
#
|
|
130
128
|
Capybara.add_selector(:link) do
|
|
131
|
-
xpath(:
|
|
129
|
+
xpath(:title, :alt) do |locator, options={}|
|
|
132
130
|
xpath = XPath.descendant(:a)[XPath.attr(:href)]
|
|
133
131
|
unless locator.nil?
|
|
134
132
|
locator = locator.to_s
|
|
@@ -139,7 +137,7 @@ Capybara.add_selector(:link) do
|
|
|
139
137
|
matchers |= XPath.attr(:'aria-label').is(locator) if Capybara.enable_aria_label
|
|
140
138
|
xpath = xpath[matchers]
|
|
141
139
|
end
|
|
142
|
-
xpath = [:
|
|
140
|
+
xpath = [:title].inject(xpath) { |memo, ef| memo[find_by_attr(ef, options[ef])] }
|
|
143
141
|
xpath = xpath[XPath.descendant(:img)[XPath.attr(:alt).equals(options[:alt])]] if options[:alt]
|
|
144
142
|
xpath
|
|
145
143
|
end
|
|
@@ -159,7 +157,7 @@ end
|
|
|
159
157
|
#
|
|
160
158
|
# Find buttons ( input [of type submit, reset, image, button] or button elements )
|
|
161
159
|
#
|
|
162
|
-
# @locator Matches the id, value, or title attributes, string content of a button, or the alt attribute of an image type button
|
|
160
|
+
# @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
|
|
163
161
|
#
|
|
164
162
|
# @filter [String] :id Matches the id attribute
|
|
165
163
|
# @filter [String] :title Matches the title attribute
|
|
@@ -167,7 +165,7 @@ end
|
|
|
167
165
|
# @filter [String] :value Matches the value of an input button
|
|
168
166
|
#
|
|
169
167
|
Capybara.add_selector(:button) do
|
|
170
|
-
xpath(:
|
|
168
|
+
xpath(:value, :title) do |locator, options={}|
|
|
171
169
|
input_btn_xpath = XPath.descendant(:input)[XPath.attr(:type).one_of('submit', 'reset', 'image', 'button')]
|
|
172
170
|
btn_xpath = XPath.descendant(:button)
|
|
173
171
|
image_btn_xpath = XPath.descendant(:input)[XPath.attr(:type).equals('image')]
|
|
@@ -179,7 +177,7 @@ Capybara.add_selector(:button) do
|
|
|
179
177
|
|
|
180
178
|
input_btn_xpath = input_btn_xpath[locator_matches]
|
|
181
179
|
|
|
182
|
-
btn_xpath = btn_xpath[locator_matches | XPath.string.n.is(locator)]
|
|
180
|
+
btn_xpath = btn_xpath[locator_matches | XPath.string.n.is(locator) | XPath.descendant(:img)[XPath.attr(:alt).is(locator)]]
|
|
183
181
|
|
|
184
182
|
alt_matches = XPath.attr(:alt).is(locator)
|
|
185
183
|
alt_matches |= XPath.attr(:'aria-label').is(locator) if Capybara.enable_aria_label
|
|
@@ -233,7 +231,7 @@ end
|
|
|
233
231
|
#
|
|
234
232
|
Capybara.add_selector(:fillable_field) do
|
|
235
233
|
label "field"
|
|
236
|
-
xpath(:
|
|
234
|
+
xpath(:name, :placeholder) do |locator, options|
|
|
237
235
|
xpath = XPath.descendant(:input, :textarea)[~XPath.attr(:type).one_of('submit', 'image', 'radio', 'checkbox', 'hidden', 'file')]
|
|
238
236
|
locate_field(xpath, locator, options)
|
|
239
237
|
end
|
|
@@ -267,7 +265,7 @@ end
|
|
|
267
265
|
#
|
|
268
266
|
Capybara.add_selector(:radio_button) do
|
|
269
267
|
label "radio button"
|
|
270
|
-
xpath(:
|
|
268
|
+
xpath(:name) do |locator, options|
|
|
271
269
|
xpath = XPath.descendant(:input)[XPath.attr(:type).equals('radio')]
|
|
272
270
|
locate_field(xpath, locator, options)
|
|
273
271
|
end
|
|
@@ -298,7 +296,7 @@ end
|
|
|
298
296
|
# @filter [String] :option Match the value
|
|
299
297
|
#
|
|
300
298
|
Capybara.add_selector(:checkbox) do
|
|
301
|
-
xpath(:
|
|
299
|
+
xpath(:name) do |locator, options|
|
|
302
300
|
xpath = XPath.descendant(:input)[XPath.attr(:type).equals('checkbox')]
|
|
303
301
|
locate_field(xpath, locator, options)
|
|
304
302
|
end
|
|
@@ -332,7 +330,7 @@ end
|
|
|
332
330
|
#
|
|
333
331
|
Capybara.add_selector(:select) do
|
|
334
332
|
label "select box"
|
|
335
|
-
xpath(:
|
|
333
|
+
xpath(:name, :placeholder) do |locator, options|
|
|
336
334
|
xpath = XPath.descendant(:select)
|
|
337
335
|
locate_field(xpath, locator, options)
|
|
338
336
|
end
|
|
@@ -408,7 +406,7 @@ end
|
|
|
408
406
|
#
|
|
409
407
|
Capybara.add_selector(:file_field) do
|
|
410
408
|
label "file field"
|
|
411
|
-
xpath(:
|
|
409
|
+
xpath(:name) do |locator, options|
|
|
412
410
|
xpath = XPath.descendant(:input)[XPath.attr(:type).equals('file')]
|
|
413
411
|
locate_field(xpath, locator, options)
|
|
414
412
|
end
|
|
@@ -431,21 +429,25 @@ end
|
|
|
431
429
|
#
|
|
432
430
|
Capybara.add_selector(:label) do
|
|
433
431
|
label "label"
|
|
434
|
-
xpath do |locator|
|
|
432
|
+
xpath(:for) do |locator, options|
|
|
435
433
|
xpath = XPath.descendant(:label)
|
|
436
434
|
xpath = xpath[XPath.string.n.is(locator.to_s) | XPath.attr(:id).equals(locator.to_s)] unless locator.nil?
|
|
435
|
+
if options.has_key?(:for) && !options[:for].is_a?(Capybara::Node::Element)
|
|
436
|
+
xpath = xpath[XPath.attr(:for).equals(options[:for].to_s).or((~XPath.attr(:for)).and(XPath.descendant()[XPath.attr(:id).equals(options[:for].to_s)]))]
|
|
437
|
+
end
|
|
437
438
|
xpath
|
|
438
439
|
end
|
|
439
440
|
|
|
440
441
|
filter(:for) do |node, field_or_value|
|
|
441
442
|
if field_or_value.is_a? Capybara::Node::Element
|
|
442
|
-
if
|
|
443
|
-
|
|
443
|
+
if node[:for]
|
|
444
|
+
field_or_value[:id] == node[:for]
|
|
444
445
|
else
|
|
445
446
|
field_or_value.find_xpath('./ancestor::label[1]').include? node.base
|
|
446
447
|
end
|
|
447
448
|
else
|
|
448
|
-
|
|
449
|
+
#Non element values were handled through the expression filter
|
|
450
|
+
true
|
|
449
451
|
end
|
|
450
452
|
end
|
|
451
453
|
|
|
@@ -466,17 +468,15 @@ end
|
|
|
466
468
|
# @filter [String, Array<String>] :class Matches the class(es) provided
|
|
467
469
|
#
|
|
468
470
|
Capybara.add_selector(:table) do
|
|
469
|
-
xpath(:
|
|
471
|
+
xpath(:caption) do |locator, options|
|
|
470
472
|
xpath = XPath.descendant(:table)
|
|
471
473
|
xpath = xpath[XPath.attr(:id).equals(locator.to_s) | XPath.descendant(:caption).is(locator.to_s)] unless locator.nil?
|
|
472
474
|
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])] }
|
|
474
475
|
xpath
|
|
475
476
|
end
|
|
476
477
|
|
|
477
478
|
describe do |options|
|
|
478
479
|
desc = String.new
|
|
479
|
-
desc << " with id #{options[:id]}" if options[:id]
|
|
480
480
|
desc << " with caption #{options[:caption]}" if options[:caption]
|
|
481
481
|
desc
|
|
482
482
|
end
|
|
@@ -492,7 +492,7 @@ end
|
|
|
492
492
|
# @filter [String, Array<String>] :class Matches the class(es) provided
|
|
493
493
|
#
|
|
494
494
|
Capybara.add_selector(:frame) do
|
|
495
|
-
xpath(:
|
|
495
|
+
xpath(:name) do |locator, options|
|
|
496
496
|
xpath = XPath.descendant(:iframe) + XPath.descendant(:frame)
|
|
497
497
|
xpath = xpath[XPath.attr(:id).equals(locator.to_s) | XPath.attr(:name).equals(locator)] unless locator.nil?
|
|
498
498
|
xpath = expression_filters.inject(xpath) { |memo, ef| memo[find_by_attr(ef, options[ef])] }
|
|
@@ -501,7 +501,6 @@ Capybara.add_selector(:frame) do
|
|
|
501
501
|
|
|
502
502
|
describe do |options|
|
|
503
503
|
desc = String.new
|
|
504
|
-
desc << " with id #{options[:id]}" if options[:id]
|
|
505
504
|
desc << " with name #{options[:name]}" if options[:name]
|
|
506
505
|
desc
|
|
507
506
|
end
|
data/lib/capybara/session.rb
CHANGED
|
@@ -20,7 +20,7 @@ module Capybara
|
|
|
20
20
|
# +current_path, and so on. It also delegate a number of methods to a Capybara::Document, representing
|
|
21
21
|
# the current HTML document. This allows interaction:
|
|
22
22
|
#
|
|
23
|
-
# session.fill_in('q', :
|
|
23
|
+
# session.fill_in('q', with: 'Capybara')
|
|
24
24
|
# session.click_button('Search')
|
|
25
25
|
# expect(session).to have_content('Capybara')
|
|
26
26
|
#
|
|
@@ -264,7 +264,7 @@ module Capybara
|
|
|
264
264
|
# to the given element.
|
|
265
265
|
#
|
|
266
266
|
# within(:xpath, '//div[@id="delivery-address"]') do
|
|
267
|
-
# fill_in('Street', :
|
|
267
|
+
# fill_in('Street', with: '12 Main Street')
|
|
268
268
|
# end
|
|
269
269
|
#
|
|
270
270
|
# Just as with `find`, if multiple elements match the selector given to
|
|
@@ -275,13 +275,13 @@ module Capybara
|
|
|
275
275
|
# assumed to be of the type set in Capybara.default_selector.
|
|
276
276
|
#
|
|
277
277
|
# within('div#delivery-address') do
|
|
278
|
-
# fill_in('Street', :
|
|
278
|
+
# fill_in('Street', with: '12 Main Street')
|
|
279
279
|
# end
|
|
280
280
|
#
|
|
281
281
|
# Note that a lot of uses of `within` can be replaced more succinctly with
|
|
282
282
|
# chaining:
|
|
283
283
|
#
|
|
284
|
-
# find('div#delivery-address').fill_in('Street', :
|
|
284
|
+
# find('div#delivery-address').fill_in('Street', with: '12 Main Street')
|
|
285
285
|
#
|
|
286
286
|
# @overload within(*find_args)
|
|
287
287
|
# @param (see Capybara::Node::Finders#all)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
Capybara::SpecHelper.spec '#accept_alert', :
|
|
2
|
+
Capybara::SpecHelper.spec '#accept_alert', requires: [:modals] do
|
|
3
3
|
before do
|
|
4
4
|
@session.visit('/with_js')
|
|
5
5
|
end
|
|
@@ -10,14 +10,14 @@ Capybara::SpecHelper.spec '#accept_alert', :requires => [:modals] do
|
|
|
10
10
|
end
|
|
11
11
|
expect(@session).to have_xpath("//a[@id='open-alert' and @opened='true']")
|
|
12
12
|
end
|
|
13
|
-
|
|
13
|
+
|
|
14
14
|
it "should accept the alert if the text matches" do
|
|
15
15
|
@session.accept_alert 'Alert opened' do
|
|
16
16
|
@session.click_link('Open alert')
|
|
17
17
|
end
|
|
18
18
|
expect(@session).to have_xpath("//a[@id='open-alert' and @opened='true']")
|
|
19
19
|
end
|
|
20
|
-
|
|
20
|
+
|
|
21
21
|
it "should not accept the alert if the text doesnt match" do
|
|
22
22
|
expect do
|
|
23
23
|
@session.accept_alert 'Incorrect Text' do
|
|
@@ -47,7 +47,7 @@ Capybara::SpecHelper.spec '#accept_alert', :requires => [:modals] do
|
|
|
47
47
|
end
|
|
48
48
|
expect(message).to eq('Delayed alert opened')
|
|
49
49
|
end
|
|
50
|
-
|
|
50
|
+
|
|
51
51
|
it "should allow to adjust the delay" do
|
|
52
52
|
@session.accept_alert wait: 4 do
|
|
53
53
|
@session.click_link('Open slow alert')
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
Capybara::SpecHelper.spec '#accept_prompt', :
|
|
2
|
+
Capybara::SpecHelper.spec '#accept_prompt', requires: [:modals] do
|
|
3
3
|
before do
|
|
4
4
|
@session.visit('/with_js')
|
|
5
5
|
end
|
|
@@ -17,21 +17,21 @@ Capybara::SpecHelper.spec '#accept_prompt', :requires => [:modals] do
|
|
|
17
17
|
end
|
|
18
18
|
expect(message).to eq('Prompt opened')
|
|
19
19
|
end
|
|
20
|
-
|
|
20
|
+
|
|
21
21
|
it "should accept the prompt with a response" do
|
|
22
22
|
@session.accept_prompt with: 'the response' do
|
|
23
23
|
@session.click_link('Open prompt')
|
|
24
24
|
end
|
|
25
25
|
expect(@session).to have_xpath("//a[@id='open-prompt' and @response='the response']")
|
|
26
26
|
end
|
|
27
|
-
|
|
27
|
+
|
|
28
28
|
it "should accept the prompt if the message matches" do
|
|
29
29
|
@session.accept_prompt 'Prompt opened', with: 'matched' do
|
|
30
30
|
@session.click_link('Open prompt')
|
|
31
31
|
end
|
|
32
32
|
expect(@session).to have_xpath("//a[@id='open-prompt' and @response='matched']")
|
|
33
33
|
end
|
|
34
|
-
|
|
34
|
+
|
|
35
35
|
it "should not accept the prompt if the message doesn't match" do
|
|
36
36
|
expect do
|
|
37
37
|
@session.accept_prompt 'Incorrect Text', with: 'not matched' do
|
|
@@ -39,7 +39,7 @@ Capybara::SpecHelper.spec '#accept_prompt', :requires => [:modals] do
|
|
|
39
39
|
end
|
|
40
40
|
end.to raise_error(Capybara::ModalNotFound)
|
|
41
41
|
end
|
|
42
|
-
|
|
42
|
+
|
|
43
43
|
|
|
44
44
|
it "should return the message presented" do
|
|
45
45
|
message = @session.accept_prompt with: 'the response' do
|