capybara 2.13.0 → 2.18.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 (122) hide show
  1. checksums.yaml +5 -5
  2. data/History.md +218 -18
  3. data/README.md +54 -23
  4. data/lib/capybara/config.rb +132 -0
  5. data/lib/capybara/cucumber.rb +1 -0
  6. data/lib/capybara/driver/base.rb +14 -0
  7. data/lib/capybara/dsl.rb +1 -3
  8. data/lib/capybara/helpers.rb +3 -3
  9. data/lib/capybara/minitest/spec.rb +14 -37
  10. data/lib/capybara/minitest.rb +95 -114
  11. data/lib/capybara/node/actions.rb +10 -10
  12. data/lib/capybara/node/base.rb +7 -2
  13. data/lib/capybara/node/element.rb +9 -3
  14. data/lib/capybara/node/finders.rb +92 -18
  15. data/lib/capybara/node/matchers.rb +21 -9
  16. data/lib/capybara/node/simple.rb +5 -0
  17. data/lib/capybara/queries/ancestor_query.rb +25 -0
  18. data/lib/capybara/queries/base_query.rb +12 -3
  19. data/lib/capybara/queries/current_path_query.rb +13 -9
  20. data/lib/capybara/queries/selector_query.rb +62 -23
  21. data/lib/capybara/queries/sibling_query.rb +25 -0
  22. data/lib/capybara/queries/text_query.rb +10 -5
  23. data/lib/capybara/queries/title_query.rb +1 -0
  24. data/lib/capybara/rack_test/browser.rb +13 -5
  25. data/lib/capybara/rack_test/driver.rb +6 -1
  26. data/lib/capybara/rack_test/form.rb +4 -3
  27. data/lib/capybara/rack_test/node.rb +1 -1
  28. data/lib/capybara/rspec/compound.rb +95 -0
  29. data/lib/capybara/rspec/matcher_proxies.rb +45 -0
  30. data/lib/capybara/rspec/matchers.rb +108 -7
  31. data/lib/capybara/rspec.rb +3 -1
  32. data/lib/capybara/selector/filter.rb +13 -41
  33. data/lib/capybara/selector/filter_set.rb +30 -4
  34. data/lib/capybara/selector/filters/base.rb +33 -0
  35. data/lib/capybara/selector/filters/expression_filter.rb +40 -0
  36. data/lib/capybara/selector/filters/node_filter.rb +27 -0
  37. data/lib/capybara/selector/selector.rb +36 -15
  38. data/lib/capybara/selector.rb +63 -42
  39. data/lib/capybara/selenium/driver.rb +177 -33
  40. data/lib/capybara/selenium/node.rb +106 -55
  41. data/lib/capybara/server.rb +6 -5
  42. data/lib/capybara/session/config.rb +114 -0
  43. data/lib/capybara/session/matchers.rb +15 -4
  44. data/lib/capybara/session.rb +178 -65
  45. data/lib/capybara/spec/fixtures/no_extension +1 -0
  46. data/lib/capybara/spec/public/test.js +18 -3
  47. data/lib/capybara/spec/session/accept_alert_spec.rb +9 -1
  48. data/lib/capybara/spec/session/accept_prompt_spec.rb +29 -1
  49. data/lib/capybara/spec/session/all_spec.rb +13 -1
  50. data/lib/capybara/spec/session/ancestor_spec.rb +85 -0
  51. data/lib/capybara/spec/session/assert_all_of_selectors_spec.rb +24 -8
  52. data/lib/capybara/spec/session/assert_selector.rb +1 -1
  53. data/lib/capybara/spec/session/assert_text.rb +8 -0
  54. data/lib/capybara/spec/session/assert_title.rb +22 -9
  55. data/lib/capybara/spec/session/attach_file_spec.rb +8 -1
  56. data/lib/capybara/spec/session/check_spec.rb +4 -4
  57. data/lib/capybara/spec/session/choose_spec.rb +2 -2
  58. data/lib/capybara/spec/session/click_button_spec.rb +1 -1
  59. data/lib/capybara/spec/session/click_link_or_button_spec.rb +3 -3
  60. data/lib/capybara/spec/session/click_link_spec.rb +1 -1
  61. data/lib/capybara/spec/session/current_url_spec.rb +3 -3
  62. data/lib/capybara/spec/session/dismiss_confirm_spec.rb +3 -3
  63. data/lib/capybara/spec/session/dismiss_prompt_spec.rb +1 -1
  64. data/lib/capybara/spec/session/evaluate_async_script_spec.rb +22 -0
  65. data/lib/capybara/spec/session/evaluate_script_spec.rb +1 -1
  66. data/lib/capybara/spec/session/fill_in_spec.rb +8 -2
  67. data/lib/capybara/spec/session/find_field_spec.rb +1 -0
  68. data/lib/capybara/spec/session/find_spec.rb +8 -6
  69. data/lib/capybara/spec/session/first_spec.rb +10 -5
  70. data/lib/capybara/spec/session/has_all_selectors_spec.rb +69 -0
  71. data/lib/capybara/spec/session/has_css_spec.rb +11 -0
  72. data/lib/capybara/spec/session/has_current_path_spec.rb +52 -7
  73. data/lib/capybara/spec/session/has_link_spec.rb +4 -4
  74. data/lib/capybara/spec/session/has_none_selectors_spec.rb +76 -0
  75. data/lib/capybara/spec/session/has_select_spec.rb +64 -6
  76. data/lib/capybara/spec/session/has_selector_spec.rb +1 -3
  77. data/lib/capybara/spec/session/has_text_spec.rb +5 -3
  78. data/lib/capybara/spec/session/has_title_spec.rb +4 -2
  79. data/lib/capybara/spec/session/has_xpath_spec.rb +5 -3
  80. data/lib/capybara/spec/session/node_spec.rb +50 -26
  81. data/lib/capybara/spec/session/refresh_spec.rb +28 -0
  82. data/lib/capybara/spec/session/reset_session_spec.rb +3 -3
  83. data/lib/capybara/spec/session/select_spec.rb +3 -2
  84. data/lib/capybara/spec/session/sibling_spec.rb +52 -0
  85. data/lib/capybara/spec/session/uncheck_spec.rb +2 -2
  86. data/lib/capybara/spec/session/unselect_spec.rb +2 -2
  87. data/lib/capybara/spec/session/visit_spec.rb +56 -1
  88. data/lib/capybara/spec/session/window/become_closed_spec.rb +11 -11
  89. data/lib/capybara/spec/session/window/switch_to_window_spec.rb +11 -9
  90. data/lib/capybara/spec/session/window/window_opened_by_spec.rb +4 -4
  91. data/lib/capybara/spec/session/window/within_window_spec.rb +27 -2
  92. data/lib/capybara/spec/spec_helper.rb +28 -4
  93. data/lib/capybara/spec/test_app.rb +3 -1
  94. data/lib/capybara/spec/views/form.erb +27 -1
  95. data/lib/capybara/spec/views/initial_alert.erb +10 -0
  96. data/lib/capybara/spec/views/with_fixed_header_footer.erb +17 -0
  97. data/lib/capybara/spec/views/with_hover.erb +5 -0
  98. data/lib/capybara/spec/views/with_html.erb +33 -2
  99. data/lib/capybara/spec/views/with_js.erb +12 -0
  100. data/lib/capybara/spec/views/with_windows.erb +4 -0
  101. data/lib/capybara/version.rb +1 -1
  102. data/lib/capybara/window.rb +1 -1
  103. data/lib/capybara.rb +102 -124
  104. data/spec/capybara_spec.rb +43 -21
  105. data/spec/dsl_spec.rb +1 -0
  106. data/spec/filter_set_spec.rb +28 -0
  107. data/spec/minitest_spec.rb +9 -1
  108. data/spec/minitest_spec_spec.rb +19 -5
  109. data/spec/per_session_config_spec.rb +67 -0
  110. data/spec/result_spec.rb +20 -0
  111. data/spec/rspec/shared_spec_matchers.rb +148 -44
  112. data/spec/rspec/views_spec.rb +4 -0
  113. data/spec/rspec_matchers_spec.rb +46 -0
  114. data/spec/rspec_spec.rb +77 -0
  115. data/spec/selector_spec.rb +2 -1
  116. data/spec/selenium_spec_chrome.rb +25 -17
  117. data/spec/selenium_spec_firefox.rb +2 -1
  118. data/spec/selenium_spec_marionette.rb +18 -5
  119. data/spec/session_spec.rb +44 -0
  120. data/spec/shared_selenium_session.rb +72 -8
  121. data/spec/spec_helper.rb +4 -0
  122. metadata +55 -8
@@ -21,7 +21,7 @@ end
21
21
  module Capybara
22
22
  class Selector
23
23
 
24
- attr_reader :name, :format, :expression_filters
24
+ attr_reader :name, :format
25
25
 
26
26
  class << self
27
27
  def all
@@ -50,7 +50,7 @@ module Capybara
50
50
  @description = nil
51
51
  @format = nil
52
52
  @expression = nil
53
- @expression_filters = []
53
+ @expression_filters = {}
54
54
  @default_visibility = nil
55
55
  instance_eval(&block)
56
56
  end
@@ -59,6 +59,14 @@ module Capybara
59
59
  @filter_set.filters
60
60
  end
61
61
 
62
+ def node_filters
63
+ @filter_set.node_filters
64
+ end
65
+
66
+ def expression_filters
67
+ @filter_set.expression_filters
68
+ end
69
+
62
70
  ##
63
71
  #
64
72
  # Define a selector by an xpath expression
@@ -74,7 +82,10 @@ module Capybara
74
82
  # @return [#call] The block that will be called to generate the XPath expression
75
83
  #
76
84
  def xpath(*expression_filters, &block)
77
- @format, @expression_filters, @expression = :xpath, expression_filters.flatten, block if block
85
+ if block
86
+ @format, @expression = :xpath, block
87
+ expression_filters.flatten.each { |ef| custom_filters[ef] = Filters::IdentityExpressionFilter.new }
88
+ end
78
89
  format == :xpath ? @expression : nil
79
90
  end
80
91
 
@@ -93,7 +104,10 @@ module Capybara
93
104
  # @return [#call] The block that will be called to generate the CSS selector
94
105
  #
95
106
  def css(*expression_filters, &block)
96
- @format, @expression_filters, @expression = :css, expression_filters.flatten, block if block
107
+ if block
108
+ @format, @expression = :css, block
109
+ expression_filters.flatten.each { |ef| custom_filters[ef] = nil }
110
+ end
97
111
  format == :css ? @expression : nil
98
112
  end
99
113
 
@@ -172,8 +186,14 @@ module Capybara
172
186
  #
173
187
  def filter(name, *types_and_options, &block)
174
188
  options = types_and_options.last.is_a?(Hash) ? types_and_options.pop.dup : {}
175
- types_and_options.each { |k| options[k] = true}
176
- custom_filters[name] = Filter.new(name, block, options)
189
+ types_and_options.each { |k| options[k] = true }
190
+ custom_filters[name] = Filters::NodeFilter.new(name, block, options)
191
+ end
192
+
193
+ def expression_filter(name, *types_and_options, &block)
194
+ options = types_and_options.last.is_a?(Hash) ? types_and_options.pop.dup : {}
195
+ types_and_options.each { |k| options[k] = true }
196
+ custom_filters[name] = Filters::ExpressionFilter.new(name, block, options)
177
197
  end
178
198
 
179
199
  def filter_set(name, filters_to_use = nil)
@@ -181,6 +201,7 @@ module Capybara
181
201
  f_set.filters.each do |n, filter|
182
202
  custom_filters[n] = filter if filters_to_use.nil? || filters_to_use.include?(n)
183
203
  end
204
+
184
205
  f_set.descriptions.each { |desc| @filter_set.describe(&desc) }
185
206
  end
186
207
 
@@ -201,9 +222,9 @@ module Capybara
201
222
  @default_visibility = default_visibility
202
223
  end
203
224
 
204
- def default_visibility
225
+ def default_visibility(fallback = Capybara.ignore_hidden_elements)
205
226
  if @default_visibility.nil?
206
- Capybara.ignore_hidden_elements
227
+ fallback
207
228
  else
208
229
  @default_visibility
209
230
  end
@@ -215,17 +236,17 @@ module Capybara
215
236
  locate_xpath = xpath #need to save original xpath for the label wrap
216
237
  if locator
217
238
  locator = locator.to_s
218
- attr_matchers = XPath.attr(:id).equals(locator) |
219
- XPath.attr(:name).equals(locator) |
220
- XPath.attr(:placeholder).equals(locator) |
221
- XPath.attr(:id).equals(XPath.anywhere(:label)[XPath.string.n.is(locator)].attr(:for))
222
- attr_matchers |= XPath.attr(:'aria-label').is(locator) if Capybara.enable_aria_label
239
+ attr_matchers = XPath.attr(:id).equals(locator).or(
240
+ XPath.attr(:name).equals(locator)).or(
241
+ XPath.attr(:placeholder).equals(locator)).or(
242
+ XPath.attr(:id).equals(XPath.anywhere(:label)[XPath.string.n.is(locator)].attr(:for)))
243
+ attr_matchers = attr_matchers.or XPath.attr(:'aria-label').is(locator) if options[:enable_aria_label]
223
244
 
224
245
  locate_xpath = locate_xpath[attr_matchers]
225
- locate_xpath += XPath.descendant(:label)[XPath.string.n.is(locator)].descendant(xpath)
246
+ locate_xpath = locate_xpath.union(XPath.descendant(:label)[XPath.string.n.is(locator)].descendant(xpath))
226
247
  end
227
248
 
228
- locate_xpath = [:name, :placeholder].inject(locate_xpath) { |memo, ef| memo[find_by_attr(ef, options[ef])] }
249
+ # locate_xpath = [:name, :placeholder].inject(locate_xpath) { |memo, ef| memo[find_by_attr(ef, options[ef])] }
229
250
  locate_xpath
230
251
  end
231
252
 
@@ -6,11 +6,15 @@ 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)
12
15
  states << 'not checked' if options[:unchecked] || (options[:checked] == false)
13
16
  states << 'disabled' if options[:disabled] == true
17
+ states << 'not disabled' if options[:disabled] == false
14
18
  desc << " that is #{states.join(' and ')}" unless states.empty?
15
19
  desc << " with the multiple attribute" if options[:multiple] == true
16
20
  desc << " without the multiple attribute" if options[:multiple] == false
@@ -65,21 +69,21 @@ end
65
69
  # @filter [Boolean] :disabled Match disabled field?
66
70
  # @filter [Boolean] :multiple Match fields that accept multiple values
67
71
  Capybara.add_selector(:field) do
68
- xpath(:name, :placeholder, :type) do |locator, options|
72
+ xpath do |locator, options|
69
73
  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
74
+ locate_field(xpath, locator, options)
75
+ end
76
+
77
+ expression_filter(:type) do |expr, type|
78
+ type = type.to_s
79
+ if ['textarea', 'select'].include?(type)
80
+ expr.axis(:self, type.to_sym)
81
+ else
82
+ expr[XPath.attr(:type).equals(type)]
77
83
  end
78
- xpath=locate_field(xpath, locator, options)
79
- xpath
80
84
  end
81
85
 
82
- filter_set(:_field) # checked/unchecked/disabled/multiple
86
+ filter_set(:_field) # checked/unchecked/disabled/multiple/name/placeholder
83
87
 
84
88
  filter(:readonly, :boolean) { |node, value| not(value ^ node.readonly?) }
85
89
  filter(:with) do |node, with|
@@ -87,7 +91,7 @@ Capybara.add_selector(:field) do
87
91
  end
88
92
  describe do |options|
89
93
  desc = String.new
90
- (expression_filters - [:type]).each { |ef| desc << " with #{ef} #{options[ef]}" if options.has_key?(ef) }
94
+ (expression_filters.keys - [:type]).each { |ef| desc << " with #{ef} #{options[ef]}" if options.has_key?(ef) }
91
95
  desc << " of type #{options[:type].inspect}" if options[:type]
92
96
  desc << " with value #{options[:with].to_s.inspect}" if options.has_key?(:with)
93
97
  desc
@@ -107,7 +111,7 @@ end
107
111
  Capybara.add_selector(:fieldset) do
108
112
  xpath(:legend) do |locator, options|
109
113
  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?
114
+ xpath = xpath[XPath.attr(:id).equals(locator.to_s).or XPath.child(:legend)[XPath.string.n.is(locator.to_s)]] unless locator.nil?
111
115
  xpath = xpath[XPath.child(:legend)[XPath.string.n.is(options[:legend])]] if options[:legend]
112
116
  xpath
113
117
  end
@@ -135,11 +139,11 @@ Capybara.add_selector(:link) do
135
139
  end
136
140
  unless locator.nil?
137
141
  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 Capybara.enable_aria_label
142
+ matchers = XPath.attr(:id).equals(locator).or(
143
+ XPath.string.n.is(locator)).or(
144
+ XPath.attr(:title).is(locator)).or(
145
+ XPath.descendant(:img)[XPath.attr(:alt).is(locator)])
146
+ matchers = matchers.or XPath.attr(:'aria-label').is(locator) if options[:enable_aria_label]
143
147
  xpath = xpath[matchers]
144
148
  end
145
149
  xpath = [:title].inject(xpath) { |memo, ef| memo[find_by_attr(ef, options[ef])] }
@@ -184,21 +188,21 @@ Capybara.add_selector(:button) do
184
188
 
185
189
  unless locator.nil?
186
190
  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 Capybara.enable_aria_label
191
+ locator_matches = XPath.attr(:id).equals(locator).or XPath.attr(:value).is(locator).or XPath.attr(:title).is(locator)
192
+ locator_matches = locator_matches.or XPath.attr(:'aria-label').is(locator) if options[:enable_aria_label]
189
193
 
190
194
  input_btn_xpath = input_btn_xpath[locator_matches]
191
195
 
192
- btn_xpath = btn_xpath[locator_matches | XPath.string.n.is(locator) | XPath.descendant(:img)[XPath.attr(:alt).is(locator)]]
196
+ btn_xpath = btn_xpath[locator_matches.or XPath.string.n.is(locator).or XPath.descendant(:img)[XPath.attr(:alt).is(locator)]]
193
197
 
194
198
  alt_matches = XPath.attr(:alt).is(locator)
195
- alt_matches |= XPath.attr(:'aria-label').is(locator) if Capybara.enable_aria_label
199
+ alt_matches = alt_matches.or XPath.attr(:'aria-label').is(locator) if options[:enable_aria_label]
196
200
  image_btn_xpath = image_btn_xpath[alt_matches]
197
201
  end
198
202
 
199
- res_xpath = input_btn_xpath + btn_xpath + image_btn_xpath
203
+ res_xpath = input_btn_xpath.union(btn_xpath).union(image_btn_xpath)
200
204
 
201
- res_xpath = expression_filters.inject(res_xpath) { |memo, ef| memo[find_by_attr(ef, options[ef])] }
205
+ res_xpath = expression_filters.keys.inject(res_xpath) { |memo, ef| memo[find_by_attr(ef, options[ef])] }
202
206
 
203
207
  res_xpath
204
208
  end
@@ -220,7 +224,7 @@ end
220
224
  Capybara.add_selector(:link_or_button) do
221
225
  label "link or button"
222
226
  xpath do |locator, options|
223
- self.class.all.values_at(:link, :button).map {|selector| selector.xpath.call(locator, options)}.reduce(:+)
227
+ self.class.all.values_at(:link, :button).map {|selector| selector.xpath.call(locator, options)}.reduce(:union)
224
228
  end
225
229
 
226
230
  filter(:disabled, :boolean, default: false, skip_if: :all) { |node, value| node.tag_name == "a" or not(value ^ node.disabled?) }
@@ -237,18 +241,28 @@ end
237
241
  # @filter [String] :name Matches the name attribute
238
242
  # @filter [String] :placeholder Matches the placeholder attribute
239
243
  # @filter [String] :with Matches the current value of the field
244
+ # @filter [String] :type Matches the type attribute of the field or element type for 'textarea'
240
245
  # @filter [String, Array<String>] :class Matches the class(es) provided
241
246
  # @filter [Boolean] :disabled Match disabled field?
242
247
  # @filter [Boolean] :multiple Match fields that accept multiple values
243
248
  #
244
249
  Capybara.add_selector(:fillable_field) do
245
250
  label "field"
246
- xpath(:name, :placeholder) do |locator, options|
251
+ xpath do |locator, options|
247
252
  xpath = XPath.descendant(:input, :textarea)[~XPath.attr(:type).one_of('submit', 'image', 'radio', 'checkbox', 'hidden', 'file')]
248
253
  locate_field(xpath, locator, options)
249
254
  end
250
255
 
251
- filter_set(:_field, [:disabled, :multiple])
256
+ expression_filter(:type) do |expr, type|
257
+ type = type.to_s
258
+ if ['textarea'].include?(type)
259
+ expr.axis(:self, type.to_sym)
260
+ else
261
+ expr[XPath.attr(:type).equals(type)]
262
+ end
263
+ end
264
+
265
+ filter_set(:_field, [:disabled, :multiple, :name, :placeholder])
252
266
 
253
267
  filter(:with) do |node, with|
254
268
  with.is_a?(Regexp) ? node.value =~ with : node.value == with.to_s
@@ -277,12 +291,12 @@ end
277
291
  #
278
292
  Capybara.add_selector(:radio_button) do
279
293
  label "radio button"
280
- xpath(:name) do |locator, options|
294
+ xpath do |locator, options|
281
295
  xpath = XPath.descendant(:input)[XPath.attr(:type).equals('radio')]
282
296
  locate_field(xpath, locator, options)
283
297
  end
284
298
 
285
- filter_set(:_field, [:checked, :unchecked, :disabled])
299
+ filter_set(:_field, [:checked, :unchecked, :disabled, :name])
286
300
 
287
301
  filter(:option) { |node, value| node.value == value.to_s }
288
302
 
@@ -308,12 +322,12 @@ end
308
322
  # @filter [String] :option Match the value
309
323
  #
310
324
  Capybara.add_selector(:checkbox) do
311
- xpath(:name) do |locator, options|
325
+ xpath do |locator, options|
312
326
  xpath = XPath.descendant(:input)[XPath.attr(:type).equals('checkbox')]
313
327
  locate_field(xpath, locator, options)
314
328
  end
315
329
 
316
- filter_set(:_field, [:checked, :unchecked, :disabled])
330
+ filter_set(:_field, [:checked, :unchecked, :disabled, :name])
317
331
 
318
332
  filter(:option) { |node, value| node.value == value.to_s }
319
333
 
@@ -339,15 +353,16 @@ end
339
353
  # @filter [Array<String>] :options Exact match options
340
354
  # @filter [Array<String>] :with_options Partial match options
341
355
  # @filter [String, Array<String>] :selected Match the selection(s)
356
+ # @filter [String, Array<String>] :with_selected Partial match the selection(s)
342
357
  #
343
358
  Capybara.add_selector(:select) do
344
359
  label "select box"
345
- xpath(:name, :placeholder) do |locator, options|
360
+ xpath do |locator, options|
346
361
  xpath = XPath.descendant(:select)
347
362
  locate_field(xpath, locator, options)
348
363
  end
349
364
 
350
- filter_set(:_field, [:disabled, :multiple])
365
+ filter_set(:_field, [:disabled, :multiple, :name, :placeholder])
351
366
 
352
367
  filter(:options) do |node, options|
353
368
  if node.visible?
@@ -367,8 +382,13 @@ Capybara.add_selector(:select) do
367
382
  end
368
383
 
369
384
  filter(:selected) do |node, selected|
370
- actual = node.all(:xpath, './/option', visible: false).select { |option| option.selected? }.map { |option| option.text(:all) }
371
- [selected].flatten.sort == actual.sort
385
+ actual = node.all(:xpath, './/option', visible: false).select(&:selected?).map { |option| option.text(:all) }
386
+ Array(selected).sort == actual.sort
387
+ end
388
+
389
+ filter(:with_selected) do |node, selected|
390
+ actual = node.all(:xpath, './/option', visible: false).select(&:selected?).map { |option| option.text(:all) }
391
+ (Array(selected) - actual).empty?
372
392
  end
373
393
 
374
394
  describe do |options|
@@ -376,6 +396,7 @@ Capybara.add_selector(:select) do
376
396
  desc << " with options #{options[:options].inspect}" if options[:options]
377
397
  desc << " with at least options #{options[:with_options].inspect}" if options[:with_options]
378
398
  desc << " with #{options[:selected].inspect} selected" if options[:selected]
399
+ desc << " with at least #{options[:with_selected].inspect} selected" if options[:with_selected]
379
400
  desc << describe_all_expression_filters(options)
380
401
  desc
381
402
  end
@@ -420,12 +441,12 @@ end
420
441
  #
421
442
  Capybara.add_selector(:file_field) do
422
443
  label "file field"
423
- xpath(:name) do |locator, options|
444
+ xpath do |locator, options|
424
445
  xpath = XPath.descendant(:input)[XPath.attr(:type).equals('file')]
425
446
  locate_field(xpath, locator, options)
426
447
  end
427
448
 
428
- filter_set(:_field, [:disabled, :multiple])
449
+ filter_set(:_field, [:disabled, :multiple, :name])
429
450
 
430
451
  describe do |options|
431
452
  desc = String.new
@@ -445,7 +466,7 @@ Capybara.add_selector(:label) do
445
466
  label "label"
446
467
  xpath(:for) do |locator, options|
447
468
  xpath = XPath.descendant(:label)
448
- xpath = xpath[XPath.string.n.is(locator.to_s) | XPath.attr(:id).equals(locator.to_s)] unless locator.nil?
469
+ xpath = xpath[XPath.string.n.is(locator.to_s).or XPath.attr(:id).equals(locator.to_s)] unless locator.nil?
449
470
  if options.has_key?(:for) && !options[:for].is_a?(Capybara::Node::Element)
450
471
  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)]))]
451
472
  end
@@ -484,7 +505,7 @@ end
484
505
  Capybara.add_selector(:table) do
485
506
  xpath(:caption) do |locator, options|
486
507
  xpath = XPath.descendant(:table)
487
- xpath = xpath[XPath.attr(:id).equals(locator.to_s) | XPath.descendant(:caption).is(locator.to_s)] unless locator.nil?
508
+ xpath = xpath[XPath.attr(:id).equals(locator.to_s).or XPath.descendant(:caption).is(locator.to_s)] unless locator.nil?
488
509
  xpath = xpath[XPath.descendant(:caption).equals(options[:caption])] if options[:caption]
489
510
  xpath
490
511
  end
@@ -507,9 +528,9 @@ end
507
528
  #
508
529
  Capybara.add_selector(:frame) do
509
530
  xpath(:name) do |locator, options|
510
- xpath = XPath.descendant(:iframe) + XPath.descendant(:frame)
511
- xpath = xpath[XPath.attr(:id).equals(locator.to_s) | XPath.attr(:name).equals(locator)] unless locator.nil?
512
- xpath = expression_filters.inject(xpath) { |memo, ef| memo[find_by_attr(ef, options[ef])] }
531
+ xpath = XPath.descendant(:iframe).union(XPath.descendant(:frame))
532
+ xpath = xpath[XPath.attr(:id).equals(locator.to_s).or XPath.attr(:name).equals(locator)] unless locator.nil?
533
+ xpath = expression_filters.keys.inject(xpath) { |memo, ef| memo[find_by_attr(ef, options[ef])] }
513
534
  xpath
514
535
  end
515
536