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.
Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +23 -0
  3. data/README.md +37 -30
  4. data/lib/capybara/node/actions.rb +18 -15
  5. data/lib/capybara/node/base.rb +2 -2
  6. data/lib/capybara/node/element.rb +1 -1
  7. data/lib/capybara/node/finders.rb +37 -32
  8. data/lib/capybara/node/matchers.rb +187 -171
  9. data/lib/capybara/queries/match_query.rb +1 -3
  10. data/lib/capybara/queries/selector_query.rb +48 -19
  11. data/lib/capybara/rack_test/driver.rb +3 -3
  12. data/lib/capybara/rails.rb +1 -1
  13. data/lib/capybara/rspec/features.rb +1 -1
  14. data/lib/capybara/rspec/matchers.rb +31 -22
  15. data/lib/capybara/selector/css.rb +30 -0
  16. data/lib/capybara/selector/selector.rb +24 -1
  17. data/lib/capybara/selector.rb +22 -23
  18. data/lib/capybara/session.rb +4 -4
  19. data/lib/capybara/spec/session/accept_alert_spec.rb +4 -4
  20. data/lib/capybara/spec/session/accept_confirm_spec.rb +1 -1
  21. data/lib/capybara/spec/session/accept_prompt_spec.rb +5 -5
  22. data/lib/capybara/spec/session/all_spec.rb +27 -27
  23. data/lib/capybara/spec/session/assert_current_path.rb +2 -2
  24. data/lib/capybara/spec/session/assert_selector.rb +28 -28
  25. data/lib/capybara/spec/session/assert_text.rb +20 -20
  26. data/lib/capybara/spec/session/assert_title.rb +2 -2
  27. data/lib/capybara/spec/session/attach_file_spec.rb +2 -2
  28. data/lib/capybara/spec/session/check_spec.rb +19 -10
  29. data/lib/capybara/spec/session/choose_spec.rb +4 -4
  30. data/lib/capybara/spec/session/click_button_spec.rb +18 -5
  31. data/lib/capybara/spec/session/click_link_or_button_spec.rb +6 -6
  32. data/lib/capybara/spec/session/click_link_spec.rb +13 -13
  33. data/lib/capybara/spec/session/current_url_spec.rb +2 -2
  34. data/lib/capybara/spec/session/dismiss_confirm_spec.rb +1 -1
  35. data/lib/capybara/spec/session/dismiss_prompt_spec.rb +1 -1
  36. data/lib/capybara/spec/session/element/matches_selector_spec.rb +21 -8
  37. data/lib/capybara/spec/session/evaluate_script_spec.rb +1 -1
  38. data/lib/capybara/spec/session/execute_script_spec.rb +2 -2
  39. data/lib/capybara/spec/session/fill_in_spec.rb +30 -30
  40. data/lib/capybara/spec/session/find_button_spec.rb +5 -5
  41. data/lib/capybara/spec/session/find_by_id_spec.rb +2 -2
  42. data/lib/capybara/spec/session/find_field_spec.rb +11 -7
  43. data/lib/capybara/spec/session/find_link_spec.rb +2 -2
  44. data/lib/capybara/spec/session/find_spec.rb +57 -48
  45. data/lib/capybara/spec/session/first_spec.rb +9 -9
  46. data/lib/capybara/spec/session/go_back_spec.rb +1 -1
  47. data/lib/capybara/spec/session/go_forward_spec.rb +1 -1
  48. data/lib/capybara/spec/session/has_button_spec.rb +12 -12
  49. data/lib/capybara/spec/session/has_css_spec.rb +73 -73
  50. data/lib/capybara/spec/session/has_current_path_spec.rb +2 -2
  51. data/lib/capybara/spec/session/has_field_spec.rb +78 -68
  52. data/lib/capybara/spec/session/has_link_spec.rb +8 -8
  53. data/lib/capybara/spec/session/has_select_spec.rb +70 -61
  54. data/lib/capybara/spec/session/has_selector_spec.rb +28 -28
  55. data/lib/capybara/spec/session/has_text_spec.rb +6 -6
  56. data/lib/capybara/spec/session/has_title_spec.rb +2 -2
  57. data/lib/capybara/spec/session/has_xpath_spec.rb +26 -26
  58. data/lib/capybara/spec/session/headers.rb +1 -1
  59. data/lib/capybara/spec/session/html_spec.rb +3 -3
  60. data/lib/capybara/spec/session/node_spec.rb +15 -15
  61. data/lib/capybara/spec/session/reset_session_spec.rb +4 -4
  62. data/lib/capybara/spec/session/response_code.rb +1 -1
  63. data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +3 -3
  64. data/lib/capybara/spec/session/screenshot_spec.rb +1 -1
  65. data/lib/capybara/spec/session/select_spec.rb +30 -30
  66. data/lib/capybara/spec/session/selectors_spec.rb +6 -2
  67. data/lib/capybara/spec/session/uncheck_spec.rb +2 -2
  68. data/lib/capybara/spec/session/unselect_spec.rb +14 -14
  69. data/lib/capybara/spec/session/visit_spec.rb +2 -2
  70. data/lib/capybara/spec/session/within_frame_spec.rb +2 -2
  71. data/lib/capybara/spec/session/within_spec.rb +6 -6
  72. data/lib/capybara/spec/spec_helper.rb +1 -1
  73. data/lib/capybara/spec/test_app.rb +1 -1
  74. data/lib/capybara/spec/views/form.erb +6 -3
  75. data/lib/capybara/version.rb +1 -1
  76. data/lib/capybara.rb +2 -2
  77. data/spec/basic_node_spec.rb +5 -5
  78. data/spec/capybara_spec.rb +1 -1
  79. data/spec/dsl_spec.rb +1 -1
  80. data/spec/rack_test_spec.rb +7 -7
  81. data/spec/rspec/matchers_spec.rb +6 -6
  82. data/spec/rspec_spec.rb +1 -1
  83. data/spec/selector_spec.rb +94 -3
  84. data/spec/selenium_firefox_spec.rb +1 -1
  85. data/spec/selenium_spec_chrome.rb +1 -1
  86. data/spec/shared_selenium_session.rb +10 -10
  87. 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[args.shift]
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
- if options.has_key?(:visible)
70
- case @options[:visible]
71
- when true then :visible
72
- when false then :all
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
- :respect_data_method => false,
11
- :follow_redirects => true,
12
- :redirect_limit => 5
10
+ respect_data_method: false,
11
+ follow_redirects: true,
12
+ redirect_limit: 5
13
13
  }
14
14
  attr_reader :app, :options
15
15
 
@@ -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, :respect_data_method => true)
19
+ Capybara::RackTest::Driver.new(app, respect_data_method: true)
20
20
  end
@@ -51,6 +51,6 @@ else
51
51
  end
52
52
 
53
53
  RSpec.configure do |config|
54
- config.include(Capybara::Features, :capybara_feature => true)
54
+ config.include(Capybara::Features, capybara_feature: true)
55
55
  end
56
56
  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
- HaveSelector.new(:link, locator, options)
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
- HaveSelector.new(:button, locator, options)
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
- HaveSelector.new(:field, locator, options)
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
- HaveSelector.new(:field, locator, options.merge(:checked => true))
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
- HaveSelector.new(:field, locator, options.merge(:unchecked => true))
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
- HaveSelector.new(:select, locator, options)
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
- HaveSelector.new(:table, locator, options)
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 = [:id, :name, :placeholder, :class].inject(locate_xpath) { |memo, ef| memo[find_by_attr(ef, options[ef])] }
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
 
@@ -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(:id, :name, :placeholder, :type, :class) do |locator, options|
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(:id, :legend, :class) do |locator, options|
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(:id, :title, :alt, :class) do |locator, options={}|
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 = [:id, :title, :class].inject(xpath) { |memo, ef| memo[find_by_attr(ef, options[ef])] }
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(:id, :value, :title, :class) do |locator, options={}|
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(:id, :name, :placeholder, :class) do |locator, options|
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(:id, :name, :class) do |locator, options|
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(:id, :name, :class) do |locator, options|
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(:id, :name, :placeholder, :class) do |locator, options|
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(:id, :name, :class) do |locator, options|
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 field_or_value[:id] && (field_or_value[:id] == node[:for])
443
- true
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
- node[:for] == field_or_value.to_s
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(:id, :caption, :class) do |locator, options|
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(:id, :name, :class) do |locator, options|
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
@@ -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', :with => 'Capybara')
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', :with => '12 Main 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', :with => '12 Main 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', :with => '12 Main 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', :requires => [:modals] do
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_confirm', :requires => [:modals] do
2
+ Capybara::SpecHelper.spec '#accept_confirm', requires: [:modals] do
3
3
  before do
4
4
  @session.visit('/with_js')
5
5
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
- Capybara::SpecHelper.spec '#accept_prompt', :requires => [:modals] do
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