capybara 2.7.1 → 2.8.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 (58) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +22 -0
  3. data/README.md +27 -3
  4. data/lib/capybara.rb +19 -4
  5. data/lib/capybara/driver/base.rb +6 -2
  6. data/lib/capybara/driver/node.rb +13 -5
  7. data/lib/capybara/helpers.rb +2 -2
  8. data/lib/capybara/node/actions.rb +116 -17
  9. data/lib/capybara/node/base.rb +7 -1
  10. data/lib/capybara/node/element.rb +23 -3
  11. data/lib/capybara/node/finders.rb +45 -29
  12. data/lib/capybara/node/matchers.rb +13 -15
  13. data/lib/capybara/queries/selector_query.rb +22 -5
  14. data/lib/capybara/queries/text_query.rb +42 -6
  15. data/lib/capybara/rack_test/node.rb +13 -1
  16. data/lib/capybara/result.rb +80 -8
  17. data/lib/capybara/rspec/features.rb +13 -6
  18. data/lib/capybara/selector.rb +98 -71
  19. data/lib/capybara/selector/filter_set.rb +46 -0
  20. data/lib/capybara/selenium/driver.rb +22 -23
  21. data/lib/capybara/selenium/node.rb +14 -6
  22. data/lib/capybara/server.rb +20 -10
  23. data/lib/capybara/session.rb +44 -8
  24. data/lib/capybara/spec/session/all_spec.rb +4 -4
  25. data/lib/capybara/spec/session/assert_text.rb +23 -0
  26. data/lib/capybara/spec/session/check_spec.rb +66 -8
  27. data/lib/capybara/spec/session/choose_spec.rb +20 -0
  28. data/lib/capybara/spec/session/click_button_spec.rb +0 -3
  29. data/lib/capybara/spec/session/click_link_spec.rb +7 -0
  30. data/lib/capybara/spec/session/execute_script_spec.rb +6 -1
  31. data/lib/capybara/spec/session/find_button_spec.rb +19 -1
  32. data/lib/capybara/spec/session/find_field_spec.rb +21 -1
  33. data/lib/capybara/spec/session/find_link_spec.rb +19 -1
  34. data/lib/capybara/spec/session/find_spec.rb +32 -4
  35. data/lib/capybara/spec/session/first_spec.rb +4 -4
  36. data/lib/capybara/spec/session/has_field_spec.rb +4 -0
  37. data/lib/capybara/spec/session/has_text_spec.rb +2 -2
  38. data/lib/capybara/spec/session/node_spec.rb +24 -3
  39. data/lib/capybara/spec/session/reset_session_spec.rb +7 -0
  40. data/lib/capybara/spec/session/selectors_spec.rb +14 -0
  41. data/lib/capybara/spec/session/uncheck_spec.rb +39 -0
  42. data/lib/capybara/spec/session/window/switch_to_window_spec.rb +4 -2
  43. data/lib/capybara/spec/session/window/window_opened_by_spec.rb +2 -1
  44. data/lib/capybara/spec/session/window/window_spec.rb +36 -22
  45. data/lib/capybara/spec/session/within_frame_spec.rb +19 -0
  46. data/lib/capybara/spec/spec_helper.rb +3 -0
  47. data/lib/capybara/spec/views/form.erb +34 -6
  48. data/lib/capybara/spec/views/with_html.erb +5 -1
  49. data/lib/capybara/spec/views/with_unload_alert.erb +3 -1
  50. data/lib/capybara/spec/views/with_windows.erb +2 -0
  51. data/lib/capybara/version.rb +1 -1
  52. data/spec/capybara_spec.rb +34 -0
  53. data/spec/rack_test_spec.rb +24 -0
  54. data/spec/result_spec.rb +25 -0
  55. data/spec/rspec/features_spec.rb +3 -3
  56. data/spec/selenium_spec.rb +6 -3
  57. data/spec/server_spec.rb +2 -2
  58. metadata +18 -4
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  if RSpec::Core::Version::STRING.to_f >= 3.0
3
- RSpec.shared_context "Capybara Features", :capybara_feature => true do
3
+ RSpec.shared_context "Capybara Features", capybara_feature: true do
4
4
  instance_eval do
5
5
  alias background before
6
6
  alias given let
@@ -8,13 +8,20 @@ if RSpec::Core::Version::STRING.to_f >= 3.0
8
8
  end
9
9
  end
10
10
 
11
+ # ensure shared_context is included if default shared_context_metadata_behavior is changed
12
+ if RSpec::Core::Version::STRING.to_f >= 3.5
13
+ RSpec.configure do |config|
14
+ config.include_context "Capybara Features", capybara_feature: true
15
+ end
16
+ end
17
+
11
18
  RSpec.configure do |config|
12
- config.alias_example_group_to :feature, :capybara_feature => true, :type => :feature
13
- config.alias_example_group_to :xfeature, :capybara_feature => true, :type => :feature, :skip => "Temporarily disabled with xfeature"
14
- config.alias_example_group_to :ffeature, :capybara_feature => true, :type => :feature, :focus => true
19
+ config.alias_example_group_to :feature, capybara_feature: true, type: :feature
20
+ config.alias_example_group_to :xfeature, capybara_feature: true, type: :feature, skip: "Temporarily disabled with xfeature"
21
+ config.alias_example_group_to :ffeature, capybara_feature: true, type: :feature, focus: true
15
22
  config.alias_example_to :scenario
16
- config.alias_example_to :xscenario, :skip => "Temporarily disabled with xscenario"
17
- config.alias_example_to :fscenario, :focus => true
23
+ config.alias_example_to :xscenario, skip: "Temporarily disabled with xscenario"
24
+ config.alias_example_to :fscenario, focus: true
18
25
  end
19
26
  else
20
27
  module Capybara
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
- require 'capybara/selector/filter'
2
+ require 'capybara/selector/filter_set'
3
3
 
4
4
  module Capybara
5
5
  class Selector
6
6
 
7
- attr_reader :name, :custom_filters, :format
7
+ attr_reader :name, :format
8
8
 
9
9
  class << self
10
10
  def all
@@ -26,7 +26,7 @@ module Capybara
26
26
 
27
27
  def initialize(name, &block)
28
28
  @name = name
29
- @custom_filters = {}
29
+ @filter_set = FilterSet.add(name){}
30
30
  @match = nil
31
31
  @label = nil
32
32
  @failure_message = nil
@@ -36,6 +36,10 @@ module Capybara
36
36
  instance_eval(&block)
37
37
  end
38
38
 
39
+ def custom_filters
40
+ @filter_set.filters
41
+ end
42
+
39
43
  def xpath(&block)
40
44
  @format, @expression = :xpath, block if block
41
45
  format == :xpath ? @expression : nil
@@ -57,7 +61,7 @@ module Capybara
57
61
  end
58
62
 
59
63
  def description(options={})
60
- (@description && @description.call(options)).to_s
64
+ @filter_set.description(options)
61
65
  end
62
66
 
63
67
  def call(locator)
@@ -73,20 +77,31 @@ module Capybara
73
77
  end
74
78
 
75
79
  def filter(name, options={}, &block)
76
- @custom_filters[name] = Filter.new(name, block, options)
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 }
77
89
  end
78
90
 
79
91
  def describe &block
80
- @description = block
92
+ @filter_set.describe &block
81
93
  end
82
94
 
83
95
  private
84
96
 
85
97
  def locate_field(xpath, locator)
86
- locate_field = xpath[XPath.attr(:id).equals(locator) |
87
- XPath.attr(:name).equals(locator) |
88
- XPath.attr(:placeholder).equals(locator) |
89
- XPath.attr(:id).equals(XPath.anywhere(:label)[XPath.string.n.is(locator)].attr(:for))]
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
103
+
104
+ locate_field = xpath[attr_matchers]
90
105
  locate_field += XPath.descendant(:label)[XPath.string.n.is(locator)].descendant(xpath)
91
106
  locate_field
92
107
  end
@@ -105,35 +120,55 @@ Capybara.add_selector(:id) do
105
120
  xpath { |id| XPath.descendant[XPath.attr(:id) == id.to_s] }
106
121
  end
107
122
 
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
+
108
147
  Capybara.add_selector(:field) do
109
148
  xpath do |locator|
110
149
  xpath = XPath.descendant(:input, :textarea, :select)[~XPath.attr(:type).one_of('submit', 'image', 'hidden')]
111
150
  xpath = locate_field(xpath, locator.to_s) unless locator.nil?
112
151
  xpath
113
152
  end
114
- filter(:checked, boolean: true) { |node, value| not(value ^ node.checked?) }
115
- filter(:unchecked, boolean: true) { |node, value| (value ^ node.checked?) }
116
- filter(:disabled, default: false, boolean: true, skip_if: :all) { |node, value| not(value ^ node.disabled?) }
117
- filter(:readonly, boolean: true) { |node, value| not(value ^ node[:readonly]) }
118
- filter(:with) { |node, with| node.value == with.to_s }
153
+
154
+ filter_set(:_field)
155
+
156
+ filter(:readonly, boolean: true) { |node, value| not(value ^ node.readonly?) }
157
+ filter(:with) do |node, with|
158
+ with.is_a?(Regexp) ? node.value =~ with : node.value == with.to_s
159
+ end
119
160
  filter(:type) do |node, type|
161
+ type = type.to_s
120
162
  if ['textarea', 'select'].include?(type)
121
163
  node.tag_name == type
122
164
  else
123
165
  node[:type] == type
124
166
  end
125
167
  end
126
- filter(:multiple, boolean: true) { |node, value| !(value ^ node[:multiple]) }
127
168
  describe do |options|
128
169
  desc, states = String.new, []
129
170
  desc << " of type #{options[:type].inspect}" if options[:type]
130
171
  desc << " with value #{options[:with].to_s.inspect}" if options.has_key?(:with)
131
- states << 'checked' if options[:checked] || (options.has_key?(:unchecked) && !options[:unchecked])
132
- states << 'not checked' if options[:unchecked] || (options.has_key?(:checked) && !options[:checked])
133
- states << 'disabled' if options[:disabled] == true
134
- desc << " that is #{states.join(' and ')}" unless states.empty?
135
- desc << " with the multiple attribute" if options[:multiple] == true
136
- desc << " without the multiple attribute" if options[:multiple] === false
137
172
  desc
138
173
  end
139
174
  end
@@ -151,10 +186,12 @@ Capybara.add_selector(:link) do
151
186
  xpath = XPath.descendant(:a)[XPath.attr(:href)]
152
187
  unless locator.nil?
153
188
  locator = locator.to_s
154
- xpath = xpath[XPath.attr(:id).equals(locator) |
155
- XPath.string.n.is(locator) |
156
- XPath.attr(:title).is(locator) |
157
- XPath.descendant(:img)[XPath.attr(:alt).is(locator)]]
189
+ matchers = XPath.attr(:id).equals(locator) |
190
+ XPath.string.n.is(locator) |
191
+ XPath.attr(:title).is(locator) |
192
+ XPath.descendant(:img)[XPath.attr(:alt).is(locator)]
193
+ matchers |= XPath.attr(:'aria-label').is(locator) if Capybara.enable_aria_label
194
+ xpath = xpath[matchers]
158
195
  end
159
196
  xpath
160
197
  end
@@ -178,9 +215,16 @@ Capybara.add_selector(:button) do
178
215
 
179
216
  unless locator.nil?
180
217
  locator = locator.to_s
181
- input_btn_xpath = input_btn_xpath[XPath.attr(:id).equals(locator) | XPath.attr(:value).is(locator) | XPath.attr(:title).is(locator)]
182
- btn_xpath = btn_xpath[XPath.attr(:id).equals(locator) | XPath.attr(:value).is(locator) | XPath.string.n.is(locator) | XPath.attr(:title).is(locator)]
183
- image_btn_xpath = image_btn_xpath[XPath.attr(:alt).is(locator)]
218
+ locator_matches = XPath.attr(:id).equals(locator) | XPath.attr(:value).is(locator) | XPath.attr(:title).is(locator)
219
+ locator_matches |= XPath.attr(:'aria-label').is(locator) if Capybara.enable_aria_label
220
+
221
+ input_btn_xpath = input_btn_xpath[locator_matches]
222
+
223
+ btn_xpath = btn_xpath[locator_matches | XPath.string.n.is(locator)]
224
+
225
+ alt_matches = XPath.attr(:alt).is(locator)
226
+ alt_matches |= XPath.attr(:'aria-label').is(locator) if Capybara.enable_aria_label
227
+ image_btn_xpath = image_btn_xpath[alt_matches]
184
228
  end
185
229
 
186
230
  input_btn_xpath + btn_xpath + image_btn_xpath
@@ -197,7 +241,7 @@ Capybara.add_selector(:link_or_button) do
197
241
  self.class.all.values_at(:link, :button).map {|selector| selector.xpath.call(locator)}.reduce(:+)
198
242
  end
199
243
 
200
- filter(:disabled, default: false, boolean: true) { |node, value| node.tag_name == "a" or not(value ^ node.disabled?) }
244
+ filter(:disabled, default: false, boolean: true, skip_if: :all) { |node, value| node.tag_name == "a" or not(value ^ node.disabled?) }
201
245
 
202
246
  describe { |options| " that is disabled" if options[:disabled] }
203
247
  end
@@ -210,16 +254,7 @@ Capybara.add_selector(:fillable_field) do
210
254
  xpath
211
255
  end
212
256
 
213
- filter(:disabled, default: false, boolean: true, skip_if: :all) { |node, value| not(value ^ node.disabled?) }
214
- filter(:multiple, boolean: true) { |node, value| !(value ^ node[:multiple]) }
215
-
216
- describe do |options|
217
- desc = String.new
218
- desc << " that is disabled" if options[:disabled] == true
219
- desc << " with the multiple attribute" if options[:multiple] == true
220
- desc << " without the multiple attribute" if options[:multiple] === false
221
- desc
222
- end
257
+ filter_set(:_field, [:id, :name, :placeholder, :disabled, :multiple])
223
258
  end
224
259
 
225
260
  Capybara.add_selector(:radio_button) do
@@ -230,18 +265,13 @@ Capybara.add_selector(:radio_button) do
230
265
  xpath
231
266
  end
232
267
 
233
- filter(:checked, boolean: true) { |node, value| not(value ^ node.checked?) }
234
- filter(:unchecked, boolean: true) { |node, value| (value ^ node.checked?) }
268
+ filter_set(:_field, [:id, :name, :checked, :unchecked, :disabled])
269
+
235
270
  filter(:option) { |node, value| node.value == value.to_s }
236
- filter(:disabled, default: false, boolean: true, skip_if: :all) { |node, value| not(value ^ node.disabled?) }
237
271
 
238
272
  describe do |options|
239
- desc, states = String.new, []
273
+ desc = String.new
240
274
  desc << " with value #{options[:option].inspect}" if options[:option]
241
- states << 'checked' if options[:checked] || (options.has_key?(:unchecked) && !options[:unchecked])
242
- states << 'not checked' if options[:unchecked] || (options.has_key?(:checked) && !options[:checked])
243
- states << 'disabled' if options[:disabled] == true
244
- desc << " that is #{states.join(' and ')}" unless states.empty?
245
275
  desc
246
276
  end
247
277
  end
@@ -253,18 +283,13 @@ Capybara.add_selector(:checkbox) do
253
283
  xpath
254
284
  end
255
285
 
256
- filter(:checked, boolean: true) { |node, value| not(value ^ node.checked?) }
257
- filter(:unchecked, boolean: true) { |node, value| (value ^ node.checked?) }
286
+ filter_set(:_field, [:id, :name, :checked, :unchecked, :disabled])
287
+
258
288
  filter(:option) { |node, value| node.value == value.to_s }
259
- filter(:disabled, default: false, boolean: true, skip_if: :all) { |node, value| not(value ^ node.disabled?) }
260
289
 
261
290
  describe do |options|
262
- desc, states = String.new, []
291
+ desc = String.new
263
292
  desc << " with value #{options[:option].inspect}" if options[:option]
264
- states << 'checked' if options[:checked] || (options.has_key?(:unchecked) && !options[:unchecked])
265
- states << 'not checked' if options[:unchecked] || (options.has_key?(:checked) && !options[:checked])
266
- states << 'disabled' if options[:disabled] == true
267
- desc << " that is #{states.join(' and ')}" unless states.empty?
268
293
  desc
269
294
  end
270
295
  end
@@ -277,6 +302,8 @@ Capybara.add_selector(:select) do
277
302
  xpath
278
303
  end
279
304
 
305
+ filter_set(:_field, [:id, :name, :placeholder, :disabled, :multiple])
306
+
280
307
  filter(:options) do |node, options|
281
308
  if node.visible?
282
309
  actual = node.all(:xpath, './/option').map { |option| option.text }
@@ -296,17 +323,12 @@ Capybara.add_selector(:select) do
296
323
  actual = node.all(:xpath, './/option', visible: false).select { |option| option.selected? }.map { |option| option.text(:all) }
297
324
  [selected].flatten.sort == actual.sort
298
325
  end
299
- filter(:disabled, default: false, boolean: true, skip_if: :all) { |node, value| not(value ^ node.disabled?) }
300
- filter(:multiple, boolean: true) { |node, value| !(value ^ node[:multiple]) }
301
326
 
302
327
  describe do |options|
303
328
  desc = String.new
304
329
  desc << " with options #{options[:options].inspect}" if options[:options]
305
330
  desc << " with at least options #{options[:with_options].inspect}" if options[:with_options]
306
331
  desc << " with #{options[:selected].inspect} selected" if options[:selected]
307
- desc << " that is disabled" if options[:disabled] == true
308
- desc << " that allows multiple selection" if options[:multiple] == true
309
- desc << " that only allows single selection" if options[:multiple] === false
310
332
  desc
311
333
  end
312
334
  end
@@ -337,16 +359,7 @@ Capybara.add_selector(:file_field) do
337
359
  xpath
338
360
  end
339
361
 
340
- filter(:disabled, default: false, boolean: true, skip_if: :all) { |node, value| not(value ^ node.disabled?) }
341
- filter(:multiple, boolean: true) { |node, value| !(value ^ node[:multiple]) }
342
-
343
- describe do |options|
344
- desc = String.new
345
- desc << " that is disabled" if options[:disabled] == true
346
- desc << " that allows multiple files" if options[:multiple] == true
347
- desc << " that only allows a single file" if options[:multiple] === false
348
- desc
349
- end
362
+ filter_set(:_field, [:id, :name, :disabled, :multiple])
350
363
  end
351
364
 
352
365
  Capybara.add_selector(:label) do
@@ -368,6 +381,12 @@ Capybara.add_selector(:label) do
368
381
  node[:for] == field_or_value.to_s
369
382
  end
370
383
  end
384
+
385
+ describe do |options|
386
+ desc = String.new
387
+ desc << " for #{options[:for]}" if options[:for]
388
+ desc
389
+ end
371
390
  end
372
391
 
373
392
  Capybara.add_selector(:table) do
@@ -377,3 +396,11 @@ Capybara.add_selector(:table) do
377
396
  xpath
378
397
  end
379
398
  end
399
+
400
+ Capybara.add_selector(:frame) do
401
+ xpath do |locator|
402
+ xpath = XPath.descendant(:iframe) + XPath.descendant(:frame)
403
+ xpath = xpath[XPath.attr(:id).equals(locator.to_s) | XPath.attr(:name).equals(locator)] unless locator.nil?
404
+ xpath
405
+ end
406
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+ require 'capybara/selector/filter'
3
+
4
+ module Capybara
5
+ class Selector
6
+ class FilterSet
7
+ attr_reader :descriptions
8
+
9
+ def initialize(name, &block)
10
+ @name = name
11
+ @descriptions = []
12
+ instance_eval(&block)
13
+ end
14
+
15
+ def filter(name, options={}, &block)
16
+ filters[name] = Filter.new(name, block, options)
17
+ end
18
+
19
+ def describe(&block)
20
+ descriptions.push block
21
+ end
22
+
23
+ def description(options={})
24
+ @descriptions.map {|desc| desc.call(options).to_s }.join
25
+ end
26
+
27
+ def filters
28
+ @filters ||= {}
29
+ end
30
+
31
+ class << self
32
+ def all
33
+ @filter_sets ||= {}
34
+ end
35
+
36
+ def add(name, &block)
37
+ all[name.to_sym] = FilterSet.new(name.to_sym, &block)
38
+ end
39
+
40
+ def remove(name)
41
+ all.delete(name.to_sym)
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -11,6 +11,11 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
11
11
 
12
12
  def browser
13
13
  unless @browser
14
+ if options[:browser].to_s == "firefox"
15
+ options[:desired_capabilities] ||= Selenium::WebDriver::Remote::Capabilities.firefox
16
+ options[:desired_capabilities].merge!({ unexpectedAlertBehaviour: "ignore" })
17
+ end
18
+
14
19
  @browser = Selenium::WebDriver.for(options[:browser], options.reject { |key,val| SPECIAL_OPTIONS.include?(key) })
15
20
 
16
21
  main = Process.pid
@@ -130,29 +135,22 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
130
135
  end
131
136
  end
132
137
 
133
- ##
134
- #
135
- # Webdriver supports frame name, id, index(zero-based) or {Capybara::Node::Element} to find iframe
136
- #
137
- # @overload within_frame(index)
138
- # @param [Integer] index index of a frame
139
- # @overload within_frame(name_or_id)
140
- # @param [String] name_or_id name or id of a frame
141
- # @overload within_frame(element)
142
- # @param [Capybara::Node::Base] a_node frame element
143
- #
144
- def within_frame(frame_handle)
145
- frame_handle = frame_handle.native if frame_handle.is_a?(Capybara::Node::Base)
146
- @frame_handles[browser.window_handle] ||= []
147
- @frame_handles[browser.window_handle] << frame_handle
148
- browser.switch_to.frame(frame_handle)
149
- yield
150
- ensure
151
- # would love to use browser.switch_to.parent_frame here
152
- # but it has an issue if the current frame is removed from within it
153
- @frame_handles[browser.window_handle].pop
154
- browser.switch_to.default_content
155
- @frame_handles[browser.window_handle].each { |fh| browser.switch_to.frame(fh) }
138
+ def switch_to_frame(frame)
139
+ case frame
140
+ when :top
141
+ @frame_handles[browser.window_handle] = []
142
+ browser.switch_to.default_content
143
+ when :parent
144
+ # would love to use browser.switch_to.parent_frame here
145
+ # but it has an issue if the current frame is removed from within it
146
+ @frame_handles[browser.window_handle].pop
147
+ browser.switch_to.default_content
148
+ @frame_handles[browser.window_handle].each { |fh| browser.switch_to.frame(fh) }
149
+ else
150
+ @frame_handles[browser.window_handle] ||= []
151
+ @frame_handles[browser.window_handle] << frame.native
152
+ browser.switch_to.frame(frame.native)
153
+ end
156
154
  end
157
155
 
158
156
  def current_window_handle
@@ -221,6 +219,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
221
219
  end
222
220
 
223
221
  def accept_modal(type, options={}, &blk)
222
+ options = options.dup
224
223
  yield if block_given?
225
224
  modal = find_modal(options)
226
225
  modal.send_keys options[:with] if options[:with]