capybara 2.8.0 → 2.9.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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/.yard/templates_custom/default/class/html/selectors.erb +38 -0
  3. data/.yard/templates_custom/default/class/html/setup.rb +17 -0
  4. data/.yard/yard_extensions.rb +78 -0
  5. data/.yardopts +1 -0
  6. data/History.md +20 -1
  7. data/README.md +1 -1
  8. data/lib/capybara/helpers.rb +0 -59
  9. data/lib/capybara/node/actions.rb +17 -11
  10. data/lib/capybara/node/finders.rb +10 -1
  11. data/lib/capybara/node/matchers.rb +54 -15
  12. data/lib/capybara/queries/base_query.rb +59 -3
  13. data/lib/capybara/queries/selector_query.rb +22 -16
  14. data/lib/capybara/queries/text_query.rb +12 -11
  15. data/lib/capybara/rack_test/form.rb +2 -1
  16. data/lib/capybara/result.rb +25 -10
  17. data/lib/capybara/rspec/features.rb +3 -2
  18. data/lib/capybara/rspec/matchers.rb +1 -1
  19. data/lib/capybara/selector/filter_set.rb +3 -1
  20. data/lib/capybara/selector/selector.rb +227 -0
  21. data/lib/capybara/selector.rb +277 -175
  22. data/lib/capybara/session.rb +2 -2
  23. data/lib/capybara/spec/session/assert_text.rb +7 -0
  24. data/lib/capybara/spec/session/element/matches_selector_spec.rb +29 -1
  25. data/lib/capybara/spec/session/find_spec.rb +10 -4
  26. data/lib/capybara/spec/session/selectors_spec.rb +12 -0
  27. data/lib/capybara/spec/views/form.erb +11 -0
  28. data/lib/capybara/version.rb +1 -1
  29. data/spec/fixtures/capybara.csv +1 -0
  30. data/spec/fixtures/selenium_driver_rspec_failure.rb +1 -1
  31. data/spec/fixtures/selenium_driver_rspec_success.rb +1 -1
  32. data/spec/rack_test_spec.rb +8 -0
  33. data/spec/result_spec.rb +3 -0
  34. data/spec/selenium_firefox_spec.rb +44 -0
  35. data/spec/selenium_spec_chrome.rb +5 -2
  36. data/spec/{selenium_spec.rb → shared_selenium_session.rb} +9 -43
  37. metadata +10 -3
@@ -1,188 +1,134 @@
1
1
  # frozen_string_literal: true
2
- require 'capybara/selector/filter_set'
3
-
4
- module Capybara
5
- class Selector
6
-
7
- attr_reader :name, :format
8
-
9
- class << self
10
- def all
11
- @selectors ||= {}
12
- end
13
-
14
- def add(name, &block)
15
- all[name.to_sym] = Capybara::Selector.new(name.to_sym, &block)
16
- end
17
-
18
- def update(name, &block)
19
- all[name.to_sym].instance_eval(&block)
20
- end
21
-
22
- def remove(name)
23
- all.delete(name.to_sym)
24
- end
25
- end
26
-
27
- def initialize(name, &block)
28
- @name = name
29
- @filter_set = FilterSet.add(name){}
30
- @match = nil
31
- @label = nil
32
- @failure_message = nil
33
- @description = nil
34
- @format = nil
35
- @expression = nil
36
- instance_eval(&block)
37
- end
38
-
39
- def custom_filters
40
- @filter_set.filters
41
- end
42
-
43
- def xpath(&block)
44
- @format, @expression = :xpath, block if block
45
- format == :xpath ? @expression : nil
46
- end
47
-
48
- def css(&block)
49
- @format, @expression = :css, block if block
50
- format == :css ? @expression : nil
51
- end
52
-
53
- def match(&block)
54
- @match = block if block
55
- @match
56
- end
57
-
58
- def label(label=nil)
59
- @label = label if label
60
- @label
61
- end
62
-
63
- def description(options={})
64
- @filter_set.description(options)
65
- end
66
-
67
- def call(locator)
68
- if format
69
- @expression.call(locator)
70
- else
71
- warn "Selector has no format"
72
- end
73
- end
74
-
75
- def match?(locator)
76
- @match and @match.call(locator)
77
- end
78
-
79
- def filter(name, options={}, &block)
80
- custom_filters[name] = Filter.new(name, block, options)
81
- end
82
-
83
- def filter_set(name, filters_to_use = nil)
84
- f_set = FilterSet.all[name]
85
- f_set.filters.each do | name, filter |
86
- custom_filters[name] = filter if filters_to_use.nil? || filters_to_use.include?(name)
87
- end
88
- f_set.descriptions.each { |desc| @filter_set.describe &desc }
89
- end
90
-
91
- def describe &block
92
- @filter_set.describe &block
93
- end
94
-
95
- private
96
-
97
- def locate_field(xpath, locator)
98
- attr_matchers = XPath.attr(:id).equals(locator) |
99
- XPath.attr(:name).equals(locator) |
100
- XPath.attr(:placeholder).equals(locator) |
101
- XPath.attr(:id).equals(XPath.anywhere(:label)[XPath.string.n.is(locator)].attr(:for))
102
- attr_matchers |= XPath.attr(:'aria-label').is(locator) if Capybara.enable_aria_label
2
+ require 'capybara/selector/selector'
3
+ Capybara::Selector::FilterSet.add(:_field) do
4
+ filter(:checked, :boolean) { |node, value| not(value ^ node.checked?) }
5
+ filter(:unchecked, :boolean) { |node, value| (value ^ node.checked?) }
6
+ filter(:disabled, :boolean, default: false, skip_if: :all) { |node, value| not(value ^ node.disabled?) }
7
+ filter(:multiple, :boolean) { |node, value| !(value ^ node.multiple?) }
103
8
 
104
- locate_field = xpath[attr_matchers]
105
- locate_field += XPath.descendant(:label)[XPath.string.n.is(locator)].descendant(xpath)
106
- locate_field
107
- end
9
+ describe do |options|
10
+ desc, states = String.new, []
11
+ states << 'checked' if options[:checked] || (options[:unchecked] === false)
12
+ states << 'not checked' if options[:unchecked] || (options[:checked] === false)
13
+ states << 'disabled' if options[:disabled] == true
14
+ desc << " that is #{states.join(' and ')}" unless states.empty?
15
+ desc << " with the multiple attribute" if options[:multiple] == true
16
+ desc << " without the multiple attribute" if options[:multiple] === false
17
+ desc
108
18
  end
109
19
  end
110
20
 
21
+ ##
22
+ #
23
+ # Select elements by XPath expression
24
+ #
25
+ # @locator An XPath expression
26
+ #
111
27
  Capybara.add_selector(:xpath) do
112
28
  xpath { |xpath| xpath }
113
29
  end
114
30
 
31
+ ##
32
+ #
33
+ # Select elements by CSS selector
34
+ #
35
+ # @locator A CSS selector
36
+ #
115
37
  Capybara.add_selector(:css) do
116
38
  css { |css| css }
117
39
  end
118
40
 
41
+ ##
42
+ #
43
+ # Select element by id
44
+ #
45
+ # @locator The id of the element to match
46
+ #
119
47
  Capybara.add_selector(:id) do
120
48
  xpath { |id| XPath.descendant[XPath.attr(:id) == id.to_s] }
121
49
  end
122
50
 
123
- Capybara::Selector::FilterSet.add(:_field) do
124
- filter(:id) { |node, id| node['id'] == id }
125
- filter(:name) { |node, name| node['name'] == name }
126
- filter(:placeholder) { |node, placeholder| node['placeholder'] == placeholder }
127
- filter(:checked, boolean: true) { |node, value| not(value ^ node.checked?) }
128
- filter(:unchecked, boolean: true) { |node, value| (value ^ node.checked?) }
129
- filter(:disabled, default: false, boolean: true, skip_if: :all) { |node, value| not(value ^ node.disabled?) }
130
- filter(:multiple, boolean: true) { |node, value| !(value ^ node.multiple?) }
131
-
132
- describe do |options|
133
- desc, states = String.new, []
134
- [:id, :name, :placeholder].each do |opt|
135
- desc << " with #{opt.to_s} #{options[opt]}" if options.has_key?(opt)
136
- end
137
- states << 'checked' if options[:checked] || (options[:unchecked] === false)
138
- states << 'not checked' if options[:unchecked] || (options[:checked] === false)
139
- states << 'disabled' if options[:disabled] == true
140
- desc << " that is #{states.join(' and ')}" unless states.empty?
141
- desc << " with the multiple attribute" if options[:multiple] == true
142
- desc << " without the multiple attribute" if options[:multiple] === false
143
- desc
144
- end
145
- end
146
-
51
+ ##
52
+ #
53
+ # Select field elements (input [not of type submit, image, or hidden], textarea, select)
54
+ #
55
+ # @locator Matches against the id, name, or placeholder
56
+ # @filter [String] :id Matches the id attribute
57
+ # @filter [String] :name Matches the name attribute
58
+ # @filter [String] :placeholder Matches the placeholder attribute
59
+ # @filter [String] :type Matches the type attribute of the field or element type for 'textarea' and 'select'
60
+ # @filter [Boolean] :readonly
61
+ # @filter [String] :with Matches the current value of the field
62
+ # @filter [String, Array<String>] :class Matches the class(es) provided
63
+ # @filter [Boolean] :checked Match checked fields?
64
+ # @filter [Boolean] :unchecked Match unchecked fields?
65
+ # @filter [Boolean] :disabled Match disabled field?
66
+ # @filter [Boolean] :multiple Match fields that accept multiple values
147
67
  Capybara.add_selector(:field) do
148
- xpath do |locator|
68
+ xpath(:id, :name, :placeholder, :type, :class) do |locator, options|
149
69
  xpath = XPath.descendant(:input, :textarea, :select)[~XPath.attr(:type).one_of('submit', 'image', 'hidden')]
150
- xpath = locate_field(xpath, locator.to_s) unless locator.nil?
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
77
+ end
78
+ xpath=locate_field(xpath, locator, options)
151
79
  xpath
152
80
  end
153
81
 
154
- filter_set(:_field)
82
+ filter_set(:_field) # checked/unchecked/disabled/multiple
155
83
 
156
- filter(:readonly, boolean: true) { |node, value| not(value ^ node.readonly?) }
84
+ filter(:readonly, :boolean) { |node, value| not(value ^ node.readonly?) }
157
85
  filter(:with) do |node, with|
158
86
  with.is_a?(Regexp) ? node.value =~ with : node.value == with.to_s
159
87
  end
160
- filter(:type) do |node, type|
161
- type = type.to_s
162
- if ['textarea', 'select'].include?(type)
163
- node.tag_name == type
164
- else
165
- node[:type] == type
166
- end
167
- end
168
88
  describe do |options|
169
- desc, states = String.new, []
89
+ desc = String.new
90
+ (expression_filters - [:type]).each { |ef| desc << " with #{ef.to_s} #{options[ef]}" if options.has_key?(ef) }
170
91
  desc << " of type #{options[:type].inspect}" if options[:type]
171
92
  desc << " with value #{options[:with].to_s.inspect}" if options.has_key?(:with)
172
93
  desc
173
94
  end
174
95
  end
175
96
 
97
+ ##
98
+ #
99
+ # Select fieldset elements
100
+ #
101
+ # @locator Matches id or contents of wrapped legend
102
+ #
103
+ # @filter [String] :id Matches id attribute
104
+ # @filter [String] :legend Matches contents of wrapped legend
105
+ # @filter [String, Array<String>] :class Matches the class(es) provided
106
+ #
176
107
  Capybara.add_selector(:fieldset) do
177
- xpath do |locator|
108
+ xpath(:id, :legend, :class) do |locator, options|
178
109
  xpath = XPath.descendant(:fieldset)
179
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
+ xpath = xpath[XPath.child(:legend)[XPath.string.n.is(options[:legend])]] if options[:legend]
113
+ xpath = xpath[find_by_attr(:class, options[:class])]
180
114
  xpath
181
115
  end
182
116
  end
183
117
 
118
+ ##
119
+ #
120
+ # Find links ( <a> elements with an href attribute )
121
+ #
122
+ # @locator Matches the id or title attributes, or the string content of the link, or the alt attribute of a contained img element
123
+ #
124
+ # @filter [String] :id Matches the id attribute
125
+ # @filter [String] :title Matches the title attribute
126
+ # @filter [String] :alt Matches the alt attribute of a contained img element
127
+ # @filter [String] :class Matches the class(es) provided
128
+ # @filter [String, Regexp] :href Matches the normalized href of the link
129
+ #
184
130
  Capybara.add_selector(:link) do
185
- xpath do |locator|
131
+ xpath(:id, :title, :alt, :class) do |locator, options={}|
186
132
  xpath = XPath.descendant(:a)[XPath.attr(:href)]
187
133
  unless locator.nil?
188
134
  locator = locator.to_s
@@ -193,6 +139,8 @@ Capybara.add_selector(:link) do
193
139
  matchers |= XPath.attr(:'aria-label').is(locator) if Capybara.enable_aria_label
194
140
  xpath = xpath[matchers]
195
141
  end
142
+ xpath = [:id, :title, :class].inject(xpath) { |memo, ef| memo[find_by_attr(ef, options[ef])] }
143
+ xpath = xpath[XPath.descendant(:img)[XPath.attr(:alt).equals(options[:alt])]] if options[:alt]
196
144
  xpath
197
145
  end
198
146
 
@@ -207,8 +155,19 @@ Capybara.add_selector(:link) do
207
155
  describe { |options| " with href #{options[:href].inspect}" if options[:href] }
208
156
  end
209
157
 
158
+ ##
159
+ #
160
+ # Find buttons ( input [of type submit, reset, image, button] or button elements )
161
+ #
162
+ # @locator Matches the id, value, or title attributes, string content of a button, or the alt attribute of an image type button
163
+ #
164
+ # @filter [String] :id Matches the id attribute
165
+ # @filter [String] :title Matches the title attribute
166
+ # @filter [String] :class Matches the class(es) provided
167
+ # @filter [String] :value Matches the value of an input button
168
+ #
210
169
  Capybara.add_selector(:button) do
211
- xpath do |locator|
170
+ xpath(:id, :value, :title, :class) do |locator, options={}|
212
171
  input_btn_xpath = XPath.descendant(:input)[XPath.attr(:type).one_of('submit', 'reset', 'image', 'button')]
213
172
  btn_xpath = XPath.descendant(:button)
214
173
  image_btn_xpath = XPath.descendant(:input)[XPath.attr(:type).equals('image')]
@@ -227,82 +186,158 @@ Capybara.add_selector(:button) do
227
186
  image_btn_xpath = image_btn_xpath[alt_matches]
228
187
  end
229
188
 
230
- input_btn_xpath + btn_xpath + image_btn_xpath
189
+ res_xpath = input_btn_xpath + btn_xpath + image_btn_xpath
190
+
191
+ res_xpath = expression_filters.inject(res_xpath) { |memo, ef| memo[find_by_attr(ef, options[ef])] }
192
+
193
+ res_xpath
231
194
  end
232
195
 
233
- filter(:disabled, default: false, boolean: true, skip_if: :all) { |node, value| not(value ^ node.disabled?) }
196
+ filter(:disabled, :boolean, default: false, skip_if: :all) { |node, value| not(value ^ node.disabled?) }
234
197
 
235
- describe { |options| " that is disabled" if options[:disabled] == true }
198
+ describe do |options|
199
+ desc = String.new
200
+ desc << " that is disabled" if options[:disabled] == true
201
+ expression_filters.each { |ef| desc << " with #{ef.to_s} #{options[ef]}" if options.has_key?(ef) }
202
+ desc
203
+ end
236
204
  end
237
205
 
206
+ ##
207
+ #
208
+ # Find links or buttons
209
+ #
238
210
  Capybara.add_selector(:link_or_button) do
239
211
  label "link or button"
240
- xpath do |locator|
241
- self.class.all.values_at(:link, :button).map {|selector| selector.xpath.call(locator)}.reduce(:+)
212
+ xpath do |locator, options|
213
+ self.class.all.values_at(:link, :button).map {|selector| selector.xpath.call(locator, options)}.reduce(:+)
242
214
  end
243
215
 
244
- filter(:disabled, default: false, boolean: true, skip_if: :all) { |node, value| node.tag_name == "a" or not(value ^ node.disabled?) }
216
+ filter(:disabled, :boolean, default: false, skip_if: :all) { |node, value| node.tag_name == "a" or not(value ^ node.disabled?) }
245
217
 
246
218
  describe { |options| " that is disabled" if options[:disabled] }
247
219
  end
248
220
 
221
+ ##
222
+ #
223
+ # Find text fillable fields ( textarea, input [not of type submit, image, radio, checkbox, hidden, file] )
224
+ #
225
+ # @locator Matches against the id, name, or placeholder
226
+ # @filter [String] :id Matches the id attribute
227
+ # @filter [String] :name Matches the name attribute
228
+ # @filter [String] :placeholder Matches the placeholder attribute
229
+ # @filter [String] :with Matches the current value of the field
230
+ # @filter [String, Array<String>] :class Matches the class(es) provided
231
+ # @filter [Boolean] :disabled Match disabled field?
232
+ # @filter [Boolean] :multiple Match fields that accept multiple values
233
+ #
249
234
  Capybara.add_selector(:fillable_field) do
250
235
  label "field"
251
- xpath do |locator|
236
+ xpath(:id, :name, :placeholder, :class) do |locator, options|
252
237
  xpath = XPath.descendant(:input, :textarea)[~XPath.attr(:type).one_of('submit', 'image', 'radio', 'checkbox', 'hidden', 'file')]
253
- xpath = locate_field(xpath, locator.to_s) unless locator.nil?
254
- xpath
238
+ locate_field(xpath, locator, options)
239
+ end
240
+
241
+ filter_set(:_field, [:disabled, :multiple])
242
+
243
+ filter(:with) do |node, with|
244
+ with.is_a?(Regexp) ? node.value =~ with : node.value == with.to_s
255
245
  end
256
246
 
257
- filter_set(:_field, [:id, :name, :placeholder, :disabled, :multiple])
247
+ describe do |options|
248
+ desc = String.new
249
+ expression_filters.each { |ef| desc << " with #{ef.to_s} #{options[ef]}" if options.has_key?(ef) }
250
+ desc << " with value #{options[:with].to_s.inspect}" if options.has_key?(:with)
251
+ desc
252
+ end
258
253
  end
259
254
 
255
+ ##
256
+ #
257
+ # Find radio buttons
258
+ #
259
+ # @locator Match id, name, or associated label text
260
+ # @filter [String] :id Matches the id attribute
261
+ # @filter [String] :name Matches the name attribute
262
+ # @filter [String, Array<String>] :class Matches the class(es) provided
263
+ # @filter [Boolean] :checked Match checked fields?
264
+ # @filter [Boolean] :unchecked Match unchecked fields?
265
+ # @filter [Boolean] :disabled Match disabled field?
266
+ # @filter [String] :option Match the value
267
+ #
260
268
  Capybara.add_selector(:radio_button) do
261
269
  label "radio button"
262
- xpath do |locator|
270
+ xpath(:id, :name, :class) do |locator, options|
263
271
  xpath = XPath.descendant(:input)[XPath.attr(:type).equals('radio')]
264
- xpath = locate_field(xpath, locator.to_s) unless locator.nil?
265
- xpath
272
+ locate_field(xpath, locator, options)
266
273
  end
267
274
 
268
- filter_set(:_field, [:id, :name, :checked, :unchecked, :disabled])
275
+ filter_set(:_field, [:checked, :unchecked, :disabled])
269
276
 
270
277
  filter(:option) { |node, value| node.value == value.to_s }
271
278
 
272
279
  describe do |options|
273
280
  desc = String.new
274
281
  desc << " with value #{options[:option].inspect}" if options[:option]
282
+ expression_filters.each { |ef| desc << " with #{ef.to_s} #{options[ef]}" if options.has_key?(ef) }
275
283
  desc
276
284
  end
277
285
  end
278
286
 
287
+ ##
288
+ #
289
+ # Find checkboxes
290
+ #
291
+ # @locator Match id, name, or associated label text
292
+ # @filter [String] :id Matches the id attribute
293
+ # @filter [String] :name Matches the name attribute
294
+ # @filter [String, Array<String>] :class Matches the class(es) provided
295
+ # @filter [Boolean] :checked Match checked fields?
296
+ # @filter [Boolean] :unchecked Match unchecked fields?
297
+ # @filter [Boolean] :disabled Match disabled field?
298
+ # @filter [String] :option Match the value
299
+ #
279
300
  Capybara.add_selector(:checkbox) do
280
- xpath do |locator|
301
+ xpath(:id, :name, :class) do |locator, options|
281
302
  xpath = XPath.descendant(:input)[XPath.attr(:type).equals('checkbox')]
282
- xpath = locate_field(xpath, locator.to_s) unless locator.nil?
283
- xpath
303
+ locate_field(xpath, locator, options)
284
304
  end
285
305
 
286
- filter_set(:_field, [:id, :name, :checked, :unchecked, :disabled])
306
+ filter_set(:_field, [:checked, :unchecked, :disabled])
287
307
 
288
308
  filter(:option) { |node, value| node.value == value.to_s }
289
309
 
290
310
  describe do |options|
291
311
  desc = String.new
292
312
  desc << " with value #{options[:option].inspect}" if options[:option]
313
+ expression_filters.each { |ef| desc << " with #{ef.to_s} #{options[ef]}" if options.has_key?(ef) }
293
314
  desc
294
315
  end
295
316
  end
296
317
 
318
+ ##
319
+ #
320
+ # Find select elements
321
+ #
322
+ # @locator Match id, name, placeholder, or associated label text
323
+ # @filter [String] :id Matches the id attribute
324
+ # @filter [String] :name Matches the name attribute
325
+ # @filter [String] :placeholder Matches the placeholder attribute
326
+ # @filter [String, Array<String>] :class Matches the class(es) provided
327
+ # @filter [Boolean] :disabled Match disabled field?
328
+ # @filter [Boolean] :multiple Match fields that accept multiple values
329
+ # @filter [Array<String>] :options Exact match options
330
+ # @filter [Array<String>] :with_options Partial match options
331
+ # @filter [String, Array<String>] :selected Match the selection(s)
332
+ #
297
333
  Capybara.add_selector(:select) do
298
334
  label "select box"
299
- xpath do |locator|
335
+ xpath(:id, :name, :placeholder, :class) do |locator, options|
300
336
  xpath = XPath.descendant(:select)
301
- xpath = locate_field(xpath, locator.to_s) unless locator.nil?
302
- xpath
337
+ locate_field(xpath, locator, options)
303
338
  end
304
339
 
305
- filter_set(:_field, [:id, :name, :placeholder, :disabled, :multiple])
340
+ filter_set(:_field, [:disabled, :multiple])
306
341
 
307
342
  filter(:options) do |node, options|
308
343
  if node.visible?
@@ -329,10 +364,19 @@ Capybara.add_selector(:select) do
329
364
  desc << " with options #{options[:options].inspect}" if options[:options]
330
365
  desc << " with at least options #{options[:with_options].inspect}" if options[:with_options]
331
366
  desc << " with #{options[:selected].inspect} selected" if options[:selected]
367
+ expression_filters.each { |ef| desc << " with #{ef.to_s} #{options[ef]}" if options.has_key?(ef) }
332
368
  desc
333
369
  end
334
370
  end
335
371
 
372
+ ##
373
+ #
374
+ # Find option elements
375
+ #
376
+ # @locator Match text of option
377
+ # @filter [Boolean] :disabled Match disabled option
378
+ # @filter [Boolean] :selected Match selected option
379
+ #
336
380
  Capybara.add_selector(:option) do
337
381
  xpath do |locator|
338
382
  xpath = XPath.descendant(:option)
@@ -340,8 +384,8 @@ Capybara.add_selector(:option) do
340
384
  xpath
341
385
  end
342
386
 
343
- filter(:disabled, boolean: true) { |node, value| not(value ^ node.disabled?) }
344
- filter(:selected, boolean: true) { |node, value| not(value ^ node.selected?) }
387
+ filter(:disabled, :boolean) { |node, value| not(value ^ node.disabled?) }
388
+ filter(:selected, :boolean) { |node, value| not(value ^ node.selected?) }
345
389
 
346
390
  describe do |options|
347
391
  desc = String.new
@@ -351,17 +395,40 @@ Capybara.add_selector(:option) do
351
395
  end
352
396
  end
353
397
 
398
+ ##
399
+ #
400
+ # Find file input elements
401
+ #
402
+ # @locator Match id, name, or associated label text
403
+ # @filter [String] :id Matches the id attribute
404
+ # @filter [String] :name Matches the name attribute
405
+ # @filter [String, Array<String>] :class Matches the class(es) provided
406
+ # @filter [Boolean] :disabled Match disabled field?
407
+ # @filter [Boolean] :multiple Match field that accepts multiple values
408
+ #
354
409
  Capybara.add_selector(:file_field) do
355
410
  label "file field"
356
- xpath do |locator|
411
+ xpath(:id, :name, :class) do |locator, options|
357
412
  xpath = XPath.descendant(:input)[XPath.attr(:type).equals('file')]
358
- xpath = locate_field(xpath, locator.to_s) unless locator.nil?
359
- xpath
413
+ locate_field(xpath, locator, options)
360
414
  end
361
415
 
362
- filter_set(:_field, [:id, :name, :disabled, :multiple])
416
+ filter_set(:_field, [:disabled, :multiple])
417
+
418
+ describe do |options|
419
+ desc = String.new
420
+ expression_filters.each { |ef| desc << " with #{ef.to_s} #{options[ef]}" if options.has_key?(ef) }
421
+ desc
422
+ end
363
423
  end
364
424
 
425
+ ##
426
+ #
427
+ # Find label elements
428
+ #
429
+ # @locator Match id or text contents
430
+ # @filter [Element, String] :for The element or id of the element associated with the label
431
+ #
365
432
  Capybara.add_selector(:label) do
366
433
  label "label"
367
434
  xpath do |locator|
@@ -389,18 +456,53 @@ Capybara.add_selector(:label) do
389
456
  end
390
457
  end
391
458
 
459
+ ##
460
+ #
461
+ # Find table elements
462
+ #
463
+ # @locator id or caption text of table
464
+ # @filter [String] :id Match id attribute of table
465
+ # @filter [String] :caption Match text of associated caption
466
+ # @filter [String, Array<String>] :class Matches the class(es) provided
467
+ #
392
468
  Capybara.add_selector(:table) do
393
- xpath do |locator|
469
+ xpath(:id, :caption, :class) do |locator, options|
394
470
  xpath = XPath.descendant(:table)
395
471
  xpath = xpath[XPath.attr(:id).equals(locator.to_s) | XPath.descendant(:caption).is(locator.to_s)] unless locator.nil?
472
+ 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])] }
396
474
  xpath
397
475
  end
476
+
477
+ describe do |options|
478
+ desc = String.new
479
+ desc << " with id #{options[:id]}" if options[:id]
480
+ desc << " with caption #{options[:caption]}" if options[:caption]
481
+ desc
482
+ end
398
483
  end
399
484
 
485
+ ##
486
+ #
487
+ # Find frame/iframe elements
488
+ #
489
+ # @locator Match id or name
490
+ # @filter [String] :id Match id attribute
491
+ # @filter [String] :name Match name attribute
492
+ # @filter [String, Array<String>] :class Matches the class(es) provided
493
+ #
400
494
  Capybara.add_selector(:frame) do
401
- xpath do |locator|
495
+ xpath(:id, :name, :class) do |locator, options|
402
496
  xpath = XPath.descendant(:iframe) + XPath.descendant(:frame)
403
497
  xpath = xpath[XPath.attr(:id).equals(locator.to_s) | XPath.attr(:name).equals(locator)] unless locator.nil?
498
+ xpath = expression_filters.inject(xpath) { |memo, ef| memo[find_by_attr(ef, options[ef])] }
404
499
  xpath
405
500
  end
501
+
502
+ describe do |options|
503
+ desc = String.new
504
+ desc << " with id #{options[:id]}" if options[:id]
505
+ desc << " with name #{options[:name]}" if options[:name]
506
+ desc
507
+ end
406
508
  end
@@ -439,7 +439,7 @@ module Capybara
439
439
  driver.switch_to_window(window.handle)
440
440
  window
441
441
  else
442
- wait_time = Capybara::Queries::SelectorQuery.new(options).wait
442
+ wait_time = Capybara::Queries::BaseQuery.wait(options)
443
443
  document.synchronize(wait_time, errors: [Capybara::WindowError]) do
444
444
  original_window_handle = driver.current_window_handle
445
445
  begin
@@ -536,7 +536,7 @@ module Capybara
536
536
  old_handles = driver.window_handles
537
537
  block.call
538
538
 
539
- wait_time = Capybara::Queries::SelectorQuery.new(options).wait
539
+ wait_time = Capybara::Queries::BaseQuery.wait(options)
540
540
  document.synchronize(wait_time, errors: [Capybara::WindowError]) do
541
541
  opened_handles = (driver.window_handles - old_handles)
542
542
  if opened_handles.size != 1
@@ -52,6 +52,13 @@ Capybara::SpecHelper.spec '#assert_text' do
52
52
  end.to raise_error(Capybara::ExpectationNotMet, /it was found 1 time using a case insensitive search/)
53
53
  end
54
54
 
55
+ it "should raise the correct error if requested text is missing but contains regex special characters" do
56
+ @session.visit('/with_html')
57
+ expect do
58
+ @session.assert_text('[]*.')
59
+ end.to raise_error(Capybara::ExpectationNotMet, /expected to find text "\[\]\*\."/)
60
+ end
61
+
55
62
  it "should be true if the text in the page matches given regexp" do
56
63
  @session.visit('/with_html')
57
64
  expect(@session.assert_text(/Lorem/)).to eq(true)