capybara 2.13.0 → 2.18.0

Sign up to get free protection for your applications and to get access to all the features.
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