capybara 2.14.0 → 2.14.1

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 (55) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +12 -0
  3. data/README.md +1 -1
  4. data/lib/capybara.rb +16 -15
  5. data/lib/capybara/minitest.rb +92 -113
  6. data/lib/capybara/minitest/spec.rb +12 -37
  7. data/lib/capybara/node/matchers.rb +6 -5
  8. data/lib/capybara/queries/base_query.rb +4 -0
  9. data/lib/capybara/queries/current_path_query.rb +1 -0
  10. data/lib/capybara/queries/selector_query.rb +39 -12
  11. data/lib/capybara/queries/text_query.rb +1 -1
  12. data/lib/capybara/queries/title_query.rb +1 -0
  13. data/lib/capybara/rack_test/driver.rb +1 -0
  14. data/lib/capybara/rspec/matcher_proxies.rb +10 -6
  15. data/lib/capybara/rspec/matchers.rb +29 -0
  16. data/lib/capybara/selector.rb +61 -50
  17. data/lib/capybara/selector/expression_filter.rb +40 -0
  18. data/lib/capybara/selector/filter_set.rb +22 -3
  19. data/lib/capybara/selector/selector.rb +33 -12
  20. data/lib/capybara/selenium/driver.rb +130 -25
  21. data/lib/capybara/selenium/node.rb +3 -3
  22. data/lib/capybara/session/config.rb +29 -23
  23. data/lib/capybara/session/matchers.rb +3 -0
  24. data/lib/capybara/spec/session/accept_alert_spec.rb +1 -1
  25. data/lib/capybara/spec/session/all_spec.rb +2 -1
  26. data/lib/capybara/spec/session/assert_selector.rb +1 -1
  27. data/lib/capybara/spec/session/assert_title.rb +22 -9
  28. data/lib/capybara/spec/session/dismiss_confirm_spec.rb +3 -3
  29. data/lib/capybara/spec/session/dismiss_prompt_spec.rb +1 -1
  30. data/lib/capybara/spec/session/find_spec.rb +3 -2
  31. data/lib/capybara/spec/session/first_spec.rb +10 -5
  32. data/lib/capybara/spec/session/has_css_spec.rb +11 -0
  33. data/lib/capybara/spec/session/has_current_path_spec.rb +5 -3
  34. data/lib/capybara/spec/session/has_select_spec.rb +62 -4
  35. data/lib/capybara/spec/session/has_text_spec.rb +5 -3
  36. data/lib/capybara/spec/session/has_title_spec.rb +4 -2
  37. data/lib/capybara/spec/session/has_xpath_spec.rb +5 -3
  38. data/lib/capybara/spec/session/node_spec.rb +8 -4
  39. data/lib/capybara/spec/session/window/become_closed_spec.rb +4 -4
  40. data/lib/capybara/spec/session/window/window_opened_by_spec.rb +4 -4
  41. data/lib/capybara/spec/spec_helper.rb +1 -1
  42. data/lib/capybara/spec/views/form.erb +22 -1
  43. data/lib/capybara/spec/views/with_html.erb +1 -1
  44. data/lib/capybara/version.rb +1 -1
  45. data/spec/capybara_spec.rb +16 -0
  46. data/spec/filter_set_spec.rb +28 -0
  47. data/spec/minitest_spec_spec.rb +4 -4
  48. data/spec/per_session_config_spec.rb +4 -4
  49. data/spec/result_spec.rb +20 -0
  50. data/spec/selector_spec.rb +2 -1
  51. data/spec/selenium_spec_chrome.rb +12 -1
  52. data/spec/selenium_spec_firefox.rb +2 -1
  53. data/spec/selenium_spec_marionette.rb +4 -3
  54. data/spec/shared_selenium_session.rb +14 -7
  55. metadata +18 -2
@@ -8,6 +8,10 @@ module Capybara
8
8
  attr_reader :options
9
9
  attr_writer :session_options
10
10
 
11
+ def initialize(options)
12
+ @session_options = options.delete(:session_options)
13
+ end
14
+
11
15
  def session_options
12
16
  @session_options || Capybara.session_options
13
17
  end
@@ -6,6 +6,7 @@ module Capybara
6
6
  module Queries
7
7
  class CurrentPathQuery < BaseQuery
8
8
  def initialize(expected_path, options = {})
9
+ super(options)
9
10
  @expected_path = expected_path
10
11
  @options = {
11
12
  url: false,
@@ -9,7 +9,7 @@ module Capybara
9
9
 
10
10
  def initialize(*args, &filter_block)
11
11
  @options = if args.last.is_a?(Hash) then args.pop.dup else {} end
12
- self.session_options = @options.delete(:session_options)
12
+ super(@options)
13
13
 
14
14
  @filter_block = filter_block
15
15
 
@@ -47,7 +47,7 @@ module Capybara
47
47
  @description << " with#{" exact" if exact_text == true} text #{options[:text].inspect}" if options[:text]
48
48
  @description << " with exact text #{options[:exact_text]}" if options[:exact_text].is_a?(String)
49
49
  @description << " with id #{options[:id]}" if options[:id]
50
- @description << " with classes #{Array(options[:class]).join(',')}]" if options[:class]
50
+ @description << " with classes [#{Array(options[:class]).join(',')}]" if options[:class]
51
51
  @description << selector.description(options)
52
52
  @description << " that also matches the custom filter block" if @filter_block
53
53
  @description
@@ -81,7 +81,7 @@ module Capybara
81
81
  when :hidden then return false if node.visible?
82
82
  end
83
83
 
84
- res = query_filters.all? do |name, filter|
84
+ res = node_filters.all? do |name, filter|
85
85
  if options.has_key?(name)
86
86
  filter.matches?(node, options[name])
87
87
  elsif filter.default?
@@ -91,8 +91,16 @@ module Capybara
91
91
  end
92
92
  end
93
93
 
94
- res &&= node.session.using_wait_time(0){ @filter_block.call(node)} unless @filter_block.nil?
94
+ res &&= if node.respond_to?(:session)
95
+ node.session.using_wait_time(0){ @filter_block.call(node) }
96
+ else
97
+ @filter_block.call(node)
98
+ end unless @filter_block.nil?
99
+
95
100
  res
101
+
102
+ rescue *(node.respond_to?(:session) ? node.session.driver.invalid_element_errors : [])
103
+ return false
96
104
  end
97
105
 
98
106
  def visible
@@ -114,16 +122,17 @@ module Capybara
114
122
 
115
123
  def xpath(exact=nil)
116
124
  exact = self.exact? if exact.nil?
117
- expr = if @expression.respond_to?(:to_xpath) and exact
118
- @expression.to_xpath(:exact)
125
+ expr = apply_expression_filters(@expression)
126
+ expr = if expr.respond_to?(:to_xpath) and exact
127
+ expr.to_xpath(:exact)
119
128
  else
120
- @expression.to_s
129
+ expr.to_s
121
130
  end
122
131
  filtered_xpath(expr)
123
132
  end
124
133
 
125
134
  def css
126
- filtered_css(@expression)
135
+ filtered_css(apply_expression_filters(@expression))
127
136
  end
128
137
 
129
138
  # @api private
@@ -155,16 +164,22 @@ module Capybara
155
164
  VALID_KEYS + custom_keys
156
165
  end
157
166
 
158
- def query_filters
167
+ def node_filters
159
168
  if options.has_key?(:filter_set)
160
- Capybara::Selector::FilterSet.all[options[:filter_set]].filters
169
+ ::Capybara::Selector::FilterSet.all[options[:filter_set]].node_filters
161
170
  else
162
- @selector.custom_filters
171
+ @selector.node_filters
163
172
  end
164
173
  end
165
174
 
175
+ def expression_filters
176
+ filters = @selector.expression_filters
177
+ filters.merge ::Capybara::Selector::FilterSet.all[options[:filter_set]].expression_filters if options.has_key?(:filter_set)
178
+ filters
179
+ end
180
+
166
181
  def custom_keys
167
- @custom_keys ||= query_filters.keys + @selector.expression_filters
182
+ @custom_keys ||= node_filters.keys + expression_filters.keys
168
183
  end
169
184
 
170
185
  def assert_valid_keys
@@ -200,6 +215,18 @@ module Capybara
200
215
  expr
201
216
  end
202
217
 
218
+ def apply_expression_filters(expr)
219
+ expression_filters.inject(expr) do |memo, (name, ef)|
220
+ if options.has_key?(name)
221
+ ef.apply_filter(memo, options[name])
222
+ elsif ef.default?
223
+ ef.apply_filter(memo, ef.default)
224
+ else
225
+ memo
226
+ end
227
+ end
228
+ end
229
+
203
230
  def warn_exact_usage
204
231
  if options.has_key?(:exact) && !supports_exact?
205
232
  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,7 +7,7 @@ module Capybara
7
7
  @type = (args.first.is_a?(Symbol) || args.first.nil?) ? args.shift : nil
8
8
  # @type = (Capybara.ignore_hidden_elements or Capybara.visible_text_only) ? :visible : :all if @type.nil?
9
9
  @options = if args.last.is_a?(Hash) then args.pop.dup else {} end
10
- self.session_options = @options.delete(:session_options)
10
+ super(@options)
11
11
 
12
12
  @type = (session_options.ignore_hidden_elements or session_options.visible_text_only) ? :visible : :all if @type.nil?
13
13
 
@@ -6,6 +6,7 @@ module Capybara
6
6
  def initialize(expected_title, options = {})
7
7
  @expected_title = expected_title
8
8
  @options = options
9
+ super(@options)
9
10
  unless @expected_title.is_a?(Regexp)
10
11
  @expected_title = Capybara::Helpers.normalize_whitespace(@expected_title)
11
12
  end
@@ -15,6 +15,7 @@ class Capybara::RackTest::Driver < Capybara::Driver::Base
15
15
 
16
16
  def initialize(app, options={})
17
17
  raise ArgumentError, "rack-test requires a rack application, but none was given" unless app
18
+ @session = nil
18
19
  @app = app
19
20
  @options = DEFAULT_OPTIONS.merge(options)
20
21
  end
@@ -19,14 +19,18 @@ module Capybara
19
19
  end
20
20
 
21
21
  module DSL
22
- def self.included(base)
23
- warn "including Capybara::DSL in the global scope is not recommended!" if base == Object
22
+ class <<self
23
+ remove_method :included
24
24
 
25
- if defined?(::RSpec::Matchers) && base.include?(::RSpec::Matchers)
26
- base.send(:include, ::Capybara::RSpecMatcherProxies)
27
- end
25
+ def included(base)
26
+ warn "including Capybara::DSL in the global scope is not recommended!" if base == Object
28
27
 
29
- super
28
+ if defined?(::RSpec::Matchers) && base.include?(::RSpec::Matchers)
29
+ base.send(:include, ::Capybara::RSpecMatcherProxies)
30
+ end
31
+
32
+ super
33
+ end
30
34
  end
31
35
  end
32
36
  end
@@ -44,6 +44,7 @@ module Capybara
44
44
  end
45
45
 
46
46
  def session_options
47
+ @context_el ||= nil
47
48
  @context_el ? @context_el.session_options : Capybara.session_options
48
49
  end
49
50
  end
@@ -195,10 +196,14 @@ module Capybara
195
196
  alias_method :failure_message_for_should_not, :failure_message_when_negated
196
197
  end
197
198
 
199
+ # RSpec matcher for whether the element(s) matching a given selector exist
200
+ # See {Capybara::Node::Matcher#assert_selector}
198
201
  def have_selector(*args, &optional_filter_block)
199
202
  HaveSelector.new(*args, &optional_filter_block)
200
203
  end
201
204
 
205
+ # RSpec matcher for whether the current element matches a given selector
206
+ # See {Capybara::Node::Matchers#assert_matches_selector}
202
207
  def match_selector(*args, &optional_filter_block)
203
208
  MatchSelector.new(*args, &optional_filter_block)
204
209
  end
@@ -207,22 +212,30 @@ module Capybara
207
212
  ::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'))
208
213
 
209
214
 
215
+ # RSpec matcher for whether elements(s) matching a given xpath selector exist
216
+ # See {Capybara::Node::Matchers#has_xpath?}
210
217
  def have_xpath(xpath, options={}, &optional_filter_block)
211
218
  HaveSelector.new(:xpath, xpath, options, &optional_filter_block)
212
219
  end
213
220
 
221
+ # RSpec matcher for whether the current element matches a given xpath selector
214
222
  def match_xpath(xpath, options={}, &optional_filter_block)
215
223
  MatchSelector.new(:xpath, xpath, options, &optional_filter_block)
216
224
  end
217
225
 
226
+ # RSpec matcher for whether elements(s) matching a given css selector exist
227
+ # See {Capybara::Node::Matchers#has_css?}
218
228
  def have_css(css, options={}, &optional_filter_block)
219
229
  HaveSelector.new(:css, css, options, &optional_filter_block)
220
230
  end
221
231
 
232
+ # RSpec matcher for whether the current element matches a given css selector
222
233
  def match_css(css, options={}, &optional_filter_block)
223
234
  MatchSelector.new(:css, css, options, &optional_filter_block)
224
235
  end
225
236
 
237
+ # RSpec matcher for text on the page
238
+ # See {Capybara::SessionMatchers#assert_text}
226
239
  def have_text(*args)
227
240
  HaveText.new(*args)
228
241
  end
@@ -232,40 +245,56 @@ module Capybara
232
245
  HaveTitle.new(title, options)
233
246
  end
234
247
 
248
+ # RSpec matcher for the current path
249
+ # See {Capybara::SessionMatchers#assert_current_path}
235
250
  def have_current_path(path, options = {})
236
251
  HaveCurrentPath.new(path, options)
237
252
  end
238
253
 
254
+ # RSpec matcher for links
255
+ # See {Capybara::Node::Matchers#has_link?}
239
256
  def have_link(locator=nil, options={}, &optional_filter_block)
240
257
  locator, options = nil, locator if locator.is_a? Hash
241
258
  HaveSelector.new(:link, locator, options, &optional_filter_block)
242
259
  end
243
260
 
261
+ # RSpec matcher for buttons
262
+ # See {Capybara::Node::Matchers#has_button?}
244
263
  def have_button(locator=nil, options={}, &optional_filter_block)
245
264
  locator, options = nil, locator if locator.is_a? Hash
246
265
  HaveSelector.new(:button, locator, options, &optional_filter_block)
247
266
  end
248
267
 
268
+ # RSpec matcher for links
269
+ # See {Capybara::Node::Matchers#has_field?}
249
270
  def have_field(locator=nil, options={}, &optional_filter_block)
250
271
  locator, options = nil, locator if locator.is_a? Hash
251
272
  HaveSelector.new(:field, locator, options, &optional_filter_block)
252
273
  end
253
274
 
275
+ # RSpec matcher for checked fields
276
+ # See {Capybara::Node::Matchers#has_checked_field?}
254
277
  def have_checked_field(locator=nil, options={}, &optional_filter_block)
255
278
  locator, options = nil, locator if locator.is_a? Hash
256
279
  HaveSelector.new(:field, locator, options.merge(checked: true), &optional_filter_block)
257
280
  end
258
281
 
282
+ # RSpec matcher for unchecked fields
283
+ # See {Capybara::Node::Matchers#has_unchecked_field?}
259
284
  def have_unchecked_field(locator=nil, options={}, &optional_filter_block)
260
285
  locator, options = nil, locator if locator.is_a? Hash
261
286
  HaveSelector.new(:field, locator, options.merge(unchecked: true), &optional_filter_block)
262
287
  end
263
288
 
289
+ # RSpec matcher for select elements
290
+ # See {Capybara::Node::Matchers#has_select?}
264
291
  def have_select(locator=nil, options={}, &optional_filter_block)
265
292
  locator, options = nil, locator if locator.is_a? Hash
266
293
  HaveSelector.new(:select, locator, options, &optional_filter_block)
267
294
  end
268
295
 
296
+ # RSpec matcher for table elements
297
+ # See {Capybara::Node::Matchers#has_table?}
269
298
  def have_table(locator=nil, options={}, &optional_filter_block)
270
299
  locator, options = nil, locator if locator.is_a? Hash
271
300
  HaveSelector.new(:table, locator, options, &optional_filter_block)
@@ -6,6 +6,9 @@ Capybara::Selector::FilterSet.add(:_field) do
6
6
  filter(:disabled, :boolean, default: false, skip_if: :all) { |node, value| not(value ^ node.disabled?) }
7
7
  filter(:multiple, :boolean) { |node, value| !(value ^ node.multiple?) }
8
8
 
9
+ expression_filter(:name) { |xpath, val| xpath[XPath.attr(:name).equals(val)] }
10
+ expression_filter(:placeholder) { |xpath, val| xpath[XPath.attr(:placeholder).equals(val)] }
11
+
9
12
  describe do |options|
10
13
  desc, states = String.new, []
11
14
  states << 'checked' if options[:checked] || (options[:unchecked] == false)
@@ -65,21 +68,21 @@ end
65
68
  # @filter [Boolean] :disabled Match disabled field?
66
69
  # @filter [Boolean] :multiple Match fields that accept multiple values
67
70
  Capybara.add_selector(:field) do
68
- xpath(:name, :placeholder, :type) do |locator, options|
71
+ xpath do |locator, options|
69
72
  xpath = XPath.descendant(:input, :textarea, :select)[~XPath.attr(:type).one_of('submit', 'image', 'hidden')]
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
73
+ locate_field(xpath, locator, options)
74
+ end
75
+
76
+ expression_filter(:type) do |expr, type|
77
+ type = type.to_s
78
+ if ['textarea', 'select'].include?(type)
79
+ expr.axis(:self, type.to_sym)
80
+ else
81
+ expr[XPath.attr(:type).equals(type)]
77
82
  end
78
- xpath=locate_field(xpath, locator, options)
79
- xpath
80
83
  end
81
84
 
82
- filter_set(:_field) # checked/unchecked/disabled/multiple
85
+ filter_set(:_field) # checked/unchecked/disabled/multiple/name/placeholder
83
86
 
84
87
  filter(:readonly, :boolean) { |node, value| not(value ^ node.readonly?) }
85
88
  filter(:with) do |node, with|
@@ -87,7 +90,7 @@ Capybara.add_selector(:field) do
87
90
  end
88
91
  describe do |options|
89
92
  desc = String.new
90
- (expression_filters - [:type]).each { |ef| desc << " with #{ef} #{options[ef]}" if options.has_key?(ef) }
93
+ (expression_filters.keys - [:type]).each { |ef| desc << " with #{ef} #{options[ef]}" if options.has_key?(ef) }
91
94
  desc << " of type #{options[:type].inspect}" if options[:type]
92
95
  desc << " with value #{options[:with].to_s.inspect}" if options.has_key?(:with)
93
96
  desc
@@ -107,7 +110,7 @@ end
107
110
  Capybara.add_selector(:fieldset) do
108
111
  xpath(:legend) do |locator, options|
109
112
  xpath = XPath.descendant(:fieldset)
110
- xpath = xpath[XPath.attr(:id).equals(locator.to_s) | XPath.child(:legend)[XPath.string.n.is(locator.to_s)]] unless locator.nil?
113
+ xpath = xpath[XPath.attr(:id).equals(locator.to_s).or XPath.child(:legend)[XPath.string.n.is(locator.to_s)]] unless locator.nil?
111
114
  xpath = xpath[XPath.child(:legend)[XPath.string.n.is(options[:legend])]] if options[:legend]
112
115
  xpath
113
116
  end
@@ -135,11 +138,11 @@ Capybara.add_selector(:link) do
135
138
  end
136
139
  unless locator.nil?
137
140
  locator = locator.to_s
138
- matchers = XPath.attr(:id).equals(locator) |
139
- XPath.string.n.is(locator) |
140
- XPath.attr(:title).is(locator) |
141
- XPath.descendant(:img)[XPath.attr(:alt).is(locator)]
142
- matchers |= XPath.attr(:'aria-label').is(locator) if options[:enable_aria_label]
141
+ matchers = XPath.attr(:id).equals(locator).or(
142
+ XPath.string.n.is(locator)).or(
143
+ XPath.attr(:title).is(locator)).or(
144
+ XPath.descendant(:img)[XPath.attr(:alt).is(locator)])
145
+ matchers = matchers.or XPath.attr(:'aria-label').is(locator) if options[:enable_aria_label]
143
146
  xpath = xpath[matchers]
144
147
  end
145
148
  xpath = [:title].inject(xpath) { |memo, ef| memo[find_by_attr(ef, options[ef])] }
@@ -184,21 +187,21 @@ Capybara.add_selector(:button) do
184
187
 
185
188
  unless locator.nil?
186
189
  locator = locator.to_s
187
- locator_matches = XPath.attr(:id).equals(locator) | XPath.attr(:value).is(locator) | XPath.attr(:title).is(locator)
188
- locator_matches |= XPath.attr(:'aria-label').is(locator) if options[:enable_aria_label]
190
+ locator_matches = XPath.attr(:id).equals(locator).or XPath.attr(:value).is(locator).or XPath.attr(:title).is(locator)
191
+ locator_matches = locator_matches.or XPath.attr(:'aria-label').is(locator) if options[:enable_aria_label]
189
192
 
190
193
  input_btn_xpath = input_btn_xpath[locator_matches]
191
194
 
192
- btn_xpath = btn_xpath[locator_matches | XPath.string.n.is(locator) | XPath.descendant(:img)[XPath.attr(:alt).is(locator)]]
195
+ btn_xpath = btn_xpath[locator_matches.or XPath.string.n.is(locator).or XPath.descendant(:img)[XPath.attr(:alt).is(locator)]]
193
196
 
194
197
  alt_matches = XPath.attr(:alt).is(locator)
195
- alt_matches |= XPath.attr(:'aria-label').is(locator) if options[:enable_aria_label]
198
+ alt_matches = alt_matches.or XPath.attr(:'aria-label').is(locator) if options[:enable_aria_label]
196
199
  image_btn_xpath = image_btn_xpath[alt_matches]
197
200
  end
198
201
 
199
- res_xpath = input_btn_xpath + btn_xpath + image_btn_xpath
202
+ res_xpath = input_btn_xpath.union(btn_xpath).union(image_btn_xpath)
200
203
 
201
- res_xpath = expression_filters.inject(res_xpath) { |memo, ef| memo[find_by_attr(ef, options[ef])] }
204
+ res_xpath = expression_filters.keys.inject(res_xpath) { |memo, ef| memo[find_by_attr(ef, options[ef])] }
202
205
 
203
206
  res_xpath
204
207
  end
@@ -220,7 +223,7 @@ end
220
223
  Capybara.add_selector(:link_or_button) do
221
224
  label "link or button"
222
225
  xpath do |locator, options|
223
- self.class.all.values_at(:link, :button).map {|selector| selector.xpath.call(locator, options)}.reduce(:+)
226
+ self.class.all.values_at(:link, :button).map {|selector| selector.xpath.call(locator, options)}.reduce(:union)
224
227
  end
225
228
 
226
229
  filter(:disabled, :boolean, default: false, skip_if: :all) { |node, value| node.tag_name == "a" or not(value ^ node.disabled?) }
@@ -244,20 +247,21 @@ end
244
247
  #
245
248
  Capybara.add_selector(:fillable_field) do
246
249
  label "field"
247
- xpath(:name, :placeholder, :type) do |locator, options|
250
+ xpath do |locator, options|
248
251
  xpath = XPath.descendant(:input, :textarea)[~XPath.attr(:type).one_of('submit', 'image', 'radio', 'checkbox', 'hidden', 'file')]
249
- if options[:type]
250
- type=options[:type].to_s
251
- if ['textarea'].include?(type)
252
- xpath = XPath.descendant(type.to_sym)
253
- else
254
- xpath = xpath[XPath.attr(:type).equals(type)]
255
- end
256
- end
257
252
  locate_field(xpath, locator, options)
258
253
  end
259
254
 
260
- filter_set(:_field, [:disabled, :multiple])
255
+ expression_filter(:type) do |expr, type|
256
+ type = type.to_s
257
+ if ['textarea'].include?(type)
258
+ expr.axis(:self, type.to_sym)
259
+ else
260
+ expr[XPath.attr(:type).equals(type)]
261
+ end
262
+ end
263
+
264
+ filter_set(:_field, [:disabled, :multiple, :name, :placeholder])
261
265
 
262
266
  filter(:with) do |node, with|
263
267
  with.is_a?(Regexp) ? node.value =~ with : node.value == with.to_s
@@ -286,12 +290,12 @@ end
286
290
  #
287
291
  Capybara.add_selector(:radio_button) do
288
292
  label "radio button"
289
- xpath(:name) do |locator, options|
293
+ xpath do |locator, options|
290
294
  xpath = XPath.descendant(:input)[XPath.attr(:type).equals('radio')]
291
295
  locate_field(xpath, locator, options)
292
296
  end
293
297
 
294
- filter_set(:_field, [:checked, :unchecked, :disabled])
298
+ filter_set(:_field, [:checked, :unchecked, :disabled, :name])
295
299
 
296
300
  filter(:option) { |node, value| node.value == value.to_s }
297
301
 
@@ -317,12 +321,12 @@ end
317
321
  # @filter [String] :option Match the value
318
322
  #
319
323
  Capybara.add_selector(:checkbox) do
320
- xpath(:name) do |locator, options|
324
+ xpath do |locator, options|
321
325
  xpath = XPath.descendant(:input)[XPath.attr(:type).equals('checkbox')]
322
326
  locate_field(xpath, locator, options)
323
327
  end
324
328
 
325
- filter_set(:_field, [:checked, :unchecked, :disabled])
329
+ filter_set(:_field, [:checked, :unchecked, :disabled, :name])
326
330
 
327
331
  filter(:option) { |node, value| node.value == value.to_s }
328
332
 
@@ -348,15 +352,16 @@ end
348
352
  # @filter [Array<String>] :options Exact match options
349
353
  # @filter [Array<String>] :with_options Partial match options
350
354
  # @filter [String, Array<String>] :selected Match the selection(s)
355
+ # @filter [String, Array<String>] :with_selected Partial match the selection(s)
351
356
  #
352
357
  Capybara.add_selector(:select) do
353
358
  label "select box"
354
- xpath(:name, :placeholder) do |locator, options|
359
+ xpath do |locator, options|
355
360
  xpath = XPath.descendant(:select)
356
361
  locate_field(xpath, locator, options)
357
362
  end
358
363
 
359
- filter_set(:_field, [:disabled, :multiple])
364
+ filter_set(:_field, [:disabled, :multiple, :name, :placeholder])
360
365
 
361
366
  filter(:options) do |node, options|
362
367
  if node.visible?
@@ -376,8 +381,13 @@ Capybara.add_selector(:select) do
376
381
  end
377
382
 
378
383
  filter(:selected) do |node, selected|
379
- actual = node.all(:xpath, './/option', visible: false).select { |option| option.selected? }.map { |option| option.text(:all) }
380
- [selected].flatten.sort == actual.sort
384
+ actual = node.all(:xpath, './/option', visible: false).select(&:selected?).map { |option| option.text(:all) }
385
+ Array(selected).sort == actual.sort
386
+ end
387
+
388
+ filter(:with_selected) do |node, selected|
389
+ actual = node.all(:xpath, './/option', visible: false).select(&:selected?).map { |option| option.text(:all) }
390
+ (Array(selected) - actual).empty?
381
391
  end
382
392
 
383
393
  describe do |options|
@@ -385,6 +395,7 @@ Capybara.add_selector(:select) do
385
395
  desc << " with options #{options[:options].inspect}" if options[:options]
386
396
  desc << " with at least options #{options[:with_options].inspect}" if options[:with_options]
387
397
  desc << " with #{options[:selected].inspect} selected" if options[:selected]
398
+ desc << " with at least #{options[:with_selected].inspect} selected" if options[:with_selected]
388
399
  desc << describe_all_expression_filters(options)
389
400
  desc
390
401
  end
@@ -429,12 +440,12 @@ end
429
440
  #
430
441
  Capybara.add_selector(:file_field) do
431
442
  label "file field"
432
- xpath(:name) do |locator, options|
443
+ xpath do |locator, options|
433
444
  xpath = XPath.descendant(:input)[XPath.attr(:type).equals('file')]
434
445
  locate_field(xpath, locator, options)
435
446
  end
436
447
 
437
- filter_set(:_field, [:disabled, :multiple])
448
+ filter_set(:_field, [:disabled, :multiple, :name])
438
449
 
439
450
  describe do |options|
440
451
  desc = String.new
@@ -454,7 +465,7 @@ Capybara.add_selector(:label) do
454
465
  label "label"
455
466
  xpath(:for) do |locator, options|
456
467
  xpath = XPath.descendant(:label)
457
- xpath = xpath[XPath.string.n.is(locator.to_s) | XPath.attr(:id).equals(locator.to_s)] unless locator.nil?
468
+ xpath = xpath[XPath.string.n.is(locator.to_s).or XPath.attr(:id).equals(locator.to_s)] unless locator.nil?
458
469
  if options.has_key?(:for) && !options[:for].is_a?(Capybara::Node::Element)
459
470
  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)]))]
460
471
  end
@@ -493,7 +504,7 @@ end
493
504
  Capybara.add_selector(:table) do
494
505
  xpath(:caption) do |locator, options|
495
506
  xpath = XPath.descendant(:table)
496
- xpath = xpath[XPath.attr(:id).equals(locator.to_s) | XPath.descendant(:caption).is(locator.to_s)] unless locator.nil?
507
+ xpath = xpath[XPath.attr(:id).equals(locator.to_s).or XPath.descendant(:caption).is(locator.to_s)] unless locator.nil?
497
508
  xpath = xpath[XPath.descendant(:caption).equals(options[:caption])] if options[:caption]
498
509
  xpath
499
510
  end
@@ -516,9 +527,9 @@ end
516
527
  #
517
528
  Capybara.add_selector(:frame) do
518
529
  xpath(:name) do |locator, options|
519
- xpath = XPath.descendant(:iframe) + XPath.descendant(:frame)
520
- xpath = xpath[XPath.attr(:id).equals(locator.to_s) | XPath.attr(:name).equals(locator)] unless locator.nil?
521
- xpath = expression_filters.inject(xpath) { |memo, ef| memo[find_by_attr(ef, options[ef])] }
530
+ xpath = XPath.descendant(:iframe).union(XPath.descendant(:frame))
531
+ xpath = xpath[XPath.attr(:id).equals(locator.to_s).or XPath.attr(:name).equals(locator)] unless locator.nil?
532
+ xpath = expression_filters.keys.inject(xpath) { |memo, ef| memo[find_by_attr(ef, options[ef])] }
522
533
  xpath
523
534
  end
524
535