capybara 2.10.2 → 2.11.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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +15 -0
  3. data/README.md +48 -29
  4. data/lib/capybara.rb +8 -9
  5. data/lib/capybara/node/actions.rb +39 -48
  6. data/lib/capybara/node/document.rb +4 -0
  7. data/lib/capybara/node/document_matchers.rb +13 -14
  8. data/lib/capybara/node/element.rb +21 -0
  9. data/lib/capybara/node/finders.rb +3 -3
  10. data/lib/capybara/node/matchers.rb +38 -31
  11. data/lib/capybara/node/simple.rb +3 -0
  12. data/lib/capybara/queries/selector_query.rb +4 -2
  13. data/lib/capybara/rack_test/node.rb +1 -1
  14. data/lib/capybara/result.rb +3 -1
  15. data/lib/capybara/rspec/matchers.rb +53 -95
  16. data/lib/capybara/selector.rb +7 -7
  17. data/lib/capybara/selector/selector.rb +10 -5
  18. data/lib/capybara/selenium/driver.rb +34 -7
  19. data/lib/capybara/selenium/node.rb +5 -1
  20. data/lib/capybara/session.rb +22 -27
  21. data/lib/capybara/session/matchers.rb +12 -14
  22. data/lib/capybara/spec/public/test.js +4 -0
  23. data/lib/capybara/spec/session/accept_alert_spec.rb +1 -1
  24. data/lib/capybara/spec/session/attach_file_spec.rb +1 -1
  25. data/lib/capybara/spec/session/check_spec.rb +8 -0
  26. data/lib/capybara/spec/session/choose_spec.rb +5 -0
  27. data/lib/capybara/spec/session/click_link_or_button_spec.rb +5 -0
  28. data/lib/capybara/spec/session/click_link_spec.rb +5 -0
  29. data/lib/capybara/spec/session/element/assert_match_selector.rb +5 -0
  30. data/lib/capybara/spec/session/element/match_css_spec.rb +6 -0
  31. data/lib/capybara/spec/session/element/matches_selector_spec.rb +2 -0
  32. data/lib/capybara/spec/session/fill_in_spec.rb +5 -0
  33. data/lib/capybara/spec/session/find_spec.rb +1 -1
  34. data/lib/capybara/spec/session/first_spec.rb +10 -0
  35. data/lib/capybara/spec/session/has_selector_spec.rb +11 -0
  36. data/lib/capybara/spec/session/node_spec.rb +3 -3
  37. data/lib/capybara/spec/session/text_spec.rb +3 -4
  38. data/lib/capybara/spec/views/with_js.erb +3 -1
  39. data/lib/capybara/version.rb +1 -1
  40. data/lib/capybara/window.rb +18 -2
  41. data/spec/basic_node_spec.rb +1 -1
  42. data/spec/capybara_spec.rb +4 -1
  43. data/spec/fixtures/selenium_driver_rspec_failure.rb +4 -1
  44. data/spec/fixtures/selenium_driver_rspec_success.rb +4 -1
  45. data/spec/result_spec.rb +28 -2
  46. data/spec/rspec/{matchers_spec.rb → shared_spec_matchers.rb} +4 -3
  47. data/spec/selector_spec.rb +1 -1
  48. data/spec/selenium_spec_chrome.rb +36 -5
  49. data/spec/selenium_spec_firefox.rb +67 -0
  50. data/spec/selenium_spec_marionette.rb +114 -0
  51. data/spec/shared_selenium_session.rb +6 -4
  52. metadata +13 -6
  53. data/spec/selenium_firefox_spec.rb +0 -44
@@ -87,7 +87,7 @@ Capybara.add_selector(:field) do
87
87
  end
88
88
  describe do |options|
89
89
  desc = String.new
90
- (expression_filters - [:type]).each { |ef| desc << " with #{ef.to_s} #{options[ef]}" if options.has_key?(ef) }
90
+ (expression_filters - [:type]).each { |ef| desc << " with #{ef} #{options[ef]}" if options.has_key?(ef) }
91
91
  desc << " of type #{options[:type].inspect}" if options[:type]
92
92
  desc << " with value #{options[:with].to_s.inspect}" if options.has_key?(:with)
93
93
  desc
@@ -196,7 +196,7 @@ Capybara.add_selector(:button) do
196
196
  describe do |options|
197
197
  desc = String.new
198
198
  desc << " that is disabled" if options[:disabled] == true
199
- expression_filters.each { |ef| desc << " with #{ef.to_s} #{options[ef]}" if options.has_key?(ef) }
199
+ desc << describe_all_expression_filters(options)
200
200
  desc
201
201
  end
202
202
  end
@@ -244,7 +244,7 @@ Capybara.add_selector(:fillable_field) do
244
244
 
245
245
  describe do |options|
246
246
  desc = String.new
247
- expression_filters.each { |ef| desc << " with #{ef.to_s} #{options[ef]}" if options.has_key?(ef) }
247
+ desc << describe_all_expression_filters(options)
248
248
  desc << " with value #{options[:with].to_s.inspect}" if options.has_key?(:with)
249
249
  desc
250
250
  end
@@ -277,7 +277,7 @@ Capybara.add_selector(:radio_button) do
277
277
  describe do |options|
278
278
  desc = String.new
279
279
  desc << " with value #{options[:option].inspect}" if options[:option]
280
- expression_filters.each { |ef| desc << " with #{ef.to_s} #{options[ef]}" if options.has_key?(ef) }
280
+ desc << describe_all_expression_filters(options)
281
281
  desc
282
282
  end
283
283
  end
@@ -308,7 +308,7 @@ Capybara.add_selector(:checkbox) do
308
308
  describe do |options|
309
309
  desc = String.new
310
310
  desc << " with value #{options[:option].inspect}" if options[:option]
311
- expression_filters.each { |ef| desc << " with #{ef.to_s} #{options[ef]}" if options.has_key?(ef) }
311
+ desc << describe_all_expression_filters(options)
312
312
  desc
313
313
  end
314
314
  end
@@ -364,7 +364,7 @@ Capybara.add_selector(:select) do
364
364
  desc << " with options #{options[:options].inspect}" if options[:options]
365
365
  desc << " with at least options #{options[:with_options].inspect}" if options[:with_options]
366
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) }
367
+ desc << describe_all_expression_filters(options)
368
368
  desc
369
369
  end
370
370
  end
@@ -417,7 +417,7 @@ Capybara.add_selector(:file_field) do
417
417
 
418
418
  describe do |options|
419
419
  desc = String.new
420
- expression_filters.each { |ef| desc << " with #{ef.to_s} #{options[ef]}" if options.has_key?(ef) }
420
+ desc << describe_all_expression_filters(options)
421
421
  desc
422
422
  end
423
423
  end
@@ -6,6 +6,7 @@ require 'xpath'
6
6
  #Patch XPath to allow a nil condition in where
7
7
  module XPath
8
8
  class Renderer
9
+ undef :where if method_defined?(:where)
9
10
  def where(on, condition)
10
11
  condition = condition.to_s
11
12
  if !condition.empty?
@@ -177,14 +178,14 @@ module Capybara
177
178
 
178
179
  def filter_set(name, filters_to_use = nil)
179
180
  f_set = FilterSet.all[name]
180
- f_set.filters.each do | name, filter |
181
- custom_filters[name] = filter if filters_to_use.nil? || filters_to_use.include?(name)
181
+ f_set.filters.each do |n, filter|
182
+ custom_filters[n] = filter if filters_to_use.nil? || filters_to_use.include?(n)
182
183
  end
183
- f_set.descriptions.each { |desc| @filter_set.describe &desc }
184
+ f_set.descriptions.each { |desc| @filter_set.describe(&desc) }
184
185
  end
185
186
 
186
187
  def describe &block
187
- @filter_set.describe &block
188
+ @filter_set.describe(&block)
188
189
  end
189
190
 
190
191
  ##
@@ -228,8 +229,12 @@ module Capybara
228
229
  locate_xpath
229
230
  end
230
231
 
232
+ def describe_all_expression_filters(opts={})
233
+ expression_filters.map { |ef| " with #{ef} #{opts[ef]}" if opts.has_key?(ef) }.join
234
+ end
235
+
231
236
  def find_by_attr(attribute, value)
232
- finder_name = "find_by_#{attribute.to_s}_attr"
237
+ finder_name = "find_by_#{attribute}_attr"
233
238
  if respond_to?(finder_name, true)
234
239
  send(finder_name, value)
235
240
  else
@@ -4,9 +4,11 @@ require "uri"
4
4
  class Capybara::Selenium::Driver < Capybara::Driver::Base
5
5
 
6
6
  DEFAULT_OPTIONS = {
7
- :browser => :firefox
7
+ :browser => :firefox,
8
+ clear_local_storage: false,
9
+ clear_session_storage: false
8
10
  }
9
- SPECIAL_OPTIONS = [:browser]
11
+ SPECIAL_OPTIONS = [:browser, :clear_local_storage, :clear_session_storage]
10
12
 
11
13
  attr_reader :app, :options
12
14
 
@@ -17,7 +19,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
17
19
  options[:desired_capabilities].merge!({ unexpectedAlertBehaviour: "ignore" })
18
20
  end
19
21
 
20
- @browser = Selenium::WebDriver.for(options[:browser], options.reject { |key,val| SPECIAL_OPTIONS.include?(key) })
22
+ @browser = Selenium::WebDriver.for(options[:browser], options.reject { |key,_val| SPECIAL_OPTIONS.include?(key) })
21
23
 
22
24
  main = Process.pid
23
25
  at_exit do
@@ -92,7 +94,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
92
94
  browser.execute_script "return #{script}"
93
95
  end
94
96
 
95
- def save_screenshot(path, options={})
97
+ def save_screenshot(path, _options={})
96
98
  browser.save_screenshot(path)
97
99
  end
98
100
 
@@ -107,6 +109,20 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
107
109
  # can trigger an endless series of unload modals
108
110
  begin
109
111
  @browser.manage.delete_all_cookies
112
+ if options[:clear_session_storage]
113
+ if @browser.respond_to? :session_storage
114
+ @browser.session_storage.clear
115
+ else
116
+ warn "sessionStorage clear requested but is not available for this driver"
117
+ end
118
+ end
119
+ if options[:clear_local_storage]
120
+ if @browser.respond_to? :local_storage
121
+ @browser.local_storage.clear
122
+ else
123
+ warn "localStorage clear requested but is not available for this driver"
124
+ end
125
+ end
110
126
  rescue Selenium::WebDriver::Error::UnhandledError
111
127
  # delete_all_cookies fails when we've previously gone
112
128
  # to about:blank, so we rescue this error and do nothing
@@ -220,8 +236,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
220
236
  browser.switch_to.window(handle) { yield }
221
237
  end
222
238
 
223
- def accept_modal(type, options={}, &blk)
224
- options = options.dup
239
+ def accept_modal(_type, options={})
225
240
  yield if block_given?
226
241
  modal = find_modal(options)
227
242
  modal.send_keys options[:with] if options[:with]
@@ -230,7 +245,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
230
245
  message
231
246
  end
232
247
 
233
- def dismiss_modal(type, options={}, &blk)
248
+ def dismiss_modal(_type, options={})
234
249
  yield if block_given?
235
250
  modal = find_modal(options)
236
251
  message = modal.text
@@ -242,6 +257,11 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
242
257
  @browser.quit if @browser
243
258
  rescue Errno::ECONNREFUSED
244
259
  # Browser must have already gone
260
+ rescue Selenium::WebDriver::Error::UnknownError => e
261
+ unless silenced_unknown_error_message?(e.message) # Most likely already gone
262
+ # probably already gone but not sure - so warn
263
+ warn "Ignoring Selenium UnknownError during driver quit: #{e.message}"
264
+ end
245
265
  ensure
246
266
  @browser = nil
247
267
  end
@@ -293,4 +313,11 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
293
313
  end
294
314
  end
295
315
 
316
+ def silenced_unknown_error_message?(msg)
317
+ silenced_unknown_error_messages.any? { |r| msg =~ r }
318
+ end
319
+
320
+ def silenced_unknown_error_messages
321
+ [ /Error communicating with the remote browser/ ]
322
+ end
296
323
  end
@@ -47,7 +47,11 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
47
47
  click if value ^ native.attribute('checked').to_s.eql?("true")
48
48
  elsif tag_name == 'input' and type == 'file'
49
49
  path_names = value.to_s.empty? ? [] : value
50
- native.send_keys(*path_names)
50
+ if driver.options[:browser].to_s == "chrome"
51
+ native.send_keys(Array(path_names).join("\n"))
52
+ else
53
+ native.send_keys(*path_names)
54
+ end
51
55
  elsif tag_name == 'textarea' or tag_name == 'input'
52
56
  if readonly?
53
57
  warn "Attempt to set readonly element with value: #{value} \n *This will raise an exception in a future version of Capybara"
@@ -269,7 +269,7 @@ module Capybara
269
269
  # block, any command to Capybara will be handled as though it were scoped
270
270
  # to the given element.
271
271
  #
272
- # within(:xpath, '//div[@id="delivery-address"]') do
272
+ # within(:xpath, './/div[@id="delivery-address"]') do
273
273
  # fill_in('Street', with: '12 Main Street')
274
274
  # end
275
275
  #
@@ -593,11 +593,7 @@ module Capybara
593
593
  # @raise [Capybara::ModalNotFound] if modal dialog hasn't been found
594
594
  #
595
595
  def accept_alert(text_or_options=nil, options={}, &blk)
596
- text_or_options, options = nil, text_or_options if text_or_options.is_a?(Hash)
597
- options[:text] ||= text_or_options unless text_or_options.nil?
598
- options[:wait] ||= Capybara.default_max_wait_time
599
-
600
- driver.accept_modal(:alert, options, &blk)
596
+ accept_modal(:alert, text_or_options, options, &blk)
601
597
  end
602
598
 
603
599
  ##
@@ -607,11 +603,7 @@ module Capybara
607
603
  # @macro modal_params
608
604
  #
609
605
  def accept_confirm(text_or_options=nil, options={}, &blk)
610
- text_or_options, options = nil, text_or_options if text_or_options.is_a?(Hash)
611
- options[:text] ||= text_or_options unless text_or_options.nil?
612
- options[:wait] ||= Capybara.default_max_wait_time
613
-
614
- driver.accept_modal(:confirm, options, &blk)
606
+ accept_modal(:confirm, text_or_options, options, &blk)
615
607
  end
616
608
 
617
609
  ##
@@ -621,11 +613,7 @@ module Capybara
621
613
  # @macro modal_params
622
614
  #
623
615
  def dismiss_confirm(text_or_options=nil, options={}, &blk)
624
- text_or_options, options = nil, text_or_options if text_or_options.is_a?(Hash)
625
- options[:text] ||= text_or_options unless text_or_options.nil?
626
- options[:wait] ||= Capybara.default_max_wait_time
627
-
628
- driver.dismiss_modal(:confirm, options, &blk)
616
+ dismiss_modal(:confirm, text_or_options, options, &blk)
629
617
  end
630
618
 
631
619
  ##
@@ -636,11 +624,7 @@ module Capybara
636
624
  # @option options [String] :with Response to provide to the prompt
637
625
  #
638
626
  def accept_prompt(text_or_options=nil, options={}, &blk)
639
- text_or_options, options = nil, text_or_options if text_or_options.is_a?(Hash)
640
- options[:text] ||= text_or_options unless text_or_options.nil?
641
- options[:wait] ||= Capybara.default_max_wait_time
642
-
643
- driver.accept_modal(:prompt, options, &blk)
627
+ accept_modal(:prompt, text_or_options, options, &blk)
644
628
  end
645
629
 
646
630
  ##
@@ -650,11 +634,7 @@ module Capybara
650
634
  # @macro modal_params
651
635
  #
652
636
  def dismiss_prompt(text_or_options=nil, options={}, &blk)
653
- text_or_options, options = nil, text_or_options if text_or_options.is_a?(Hash)
654
- options[:text] ||= text_or_options unless text_or_options.nil?
655
- options[:wait] ||= Capybara.default_max_wait_time
656
-
657
- driver.dismiss_modal(:prompt, options, &blk)
637
+ dismiss_modal(:prompt, text_or_options, options, &blk)
658
638
  end
659
639
 
660
640
  ##
@@ -757,6 +737,21 @@ module Capybara
757
737
  end
758
738
 
759
739
  private
740
+ def accept_modal(type, text_or_options, options, &blk)
741
+ driver.accept_modal(type, modal_options(text_or_options, options), &blk)
742
+ end
743
+
744
+ def dismiss_modal(type, text_or_options, options, &blk)
745
+ driver.dismiss_modal(type, modal_options(text_or_options, options), &blk)
746
+ end
747
+
748
+ def modal_options(text_or_options, options)
749
+ text_or_options, options = nil, text_or_options if text_or_options.is_a?(Hash)
750
+ options[:text] ||= text_or_options unless text_or_options.nil?
751
+ options[:wait] ||= Capybara.default_max_wait_time
752
+ options
753
+ end
754
+
760
755
 
761
756
  def open_file(path)
762
757
  begin
@@ -780,7 +775,7 @@ module Capybara
780
775
 
781
776
  def default_fn(extension)
782
777
  timestamp = Time.new.strftime("%Y%m%d%H%M%S")
783
- path = "capybara-#{timestamp}#{rand(10**10)}.#{extension}"
778
+ "capybara-#{timestamp}#{rand(10**10)}.#{extension}"
784
779
  end
785
780
 
786
781
  def scopes
@@ -17,13 +17,7 @@ module Capybara
17
17
  # @return [true]
18
18
  #
19
19
  def assert_current_path(path, options={})
20
- query = Capybara::Queries::CurrentPathQuery.new(path, options)
21
- document.synchronize(query.wait) do
22
- unless query.resolves_for?(self)
23
- raise Capybara::ExpectationNotMet, query.failure_message
24
- end
25
- end
26
- return true
20
+ _verify_current_path(path,options) { |query| raise Capybara::ExpectationNotMet, query.failure_message unless query.resolves_for?(self) }
27
21
  end
28
22
 
29
23
  ##
@@ -34,13 +28,7 @@ module Capybara
34
28
  # @return [true]
35
29
  #
36
30
  def assert_no_current_path(path, options={})
37
- query = Capybara::Queries::CurrentPathQuery.new(path, options)
38
- document.synchronize(query.wait) do
39
- if query.resolves_for?(self)
40
- raise Capybara::ExpectationNotMet, query.negative_failure_message
41
- end
42
- end
43
- return true
31
+ _verify_current_path(path,options) { |query| raise Capybara::ExpectationNotMet, query.negative_failure_message if query.resolves_for?(self) }
44
32
  end
45
33
 
46
34
  ##
@@ -66,5 +54,15 @@ module Capybara
66
54
  rescue Capybara::ExpectationNotMet
67
55
  return false
68
56
  end
57
+
58
+ private
59
+
60
+ def _verify_current_path(path, options)
61
+ query = Capybara::Queries::CurrentPathQuery.new(path, options)
62
+ document.synchronize(query.wait) do
63
+ yield(query)
64
+ end
65
+ return true
66
+ end
69
67
  end
70
68
  end
@@ -123,4 +123,8 @@ $(function() {
123
123
  input.disabled = true;
124
124
  }, 500)
125
125
  })
126
+ $('#set-storage').click(function(e){
127
+ sessionStorage.setItem('session', 'session_value');
128
+ localStorage.setItem('local', 'local value');
129
+ })
126
130
  });
@@ -26,7 +26,7 @@ Capybara::SpecHelper.spec '#accept_alert', requires: [:modals] do
26
26
  end
27
27
 
28
28
  it "should accept the alert if the text matches a regexp" do
29
- @session.accept_alert /op.{2}ed/ do
29
+ @session.accept_alert(/op.{2}ed/) do
30
30
  @session.click_link('Open alert')
31
31
  end
32
32
  expect(@session).to have_xpath("//a[@id='open-alert' and @opened='true']")
@@ -65,7 +65,7 @@ Capybara::SpecHelper.spec "#attach_file" do
65
65
  end
66
66
 
67
67
  it "should not break when using HTML5 multiple file input uploading multiple files" do
68
- pending "Selenium is buggy on this, see http://code.google.com/p/selenium/issues/detail?id=2239" if @session.respond_to?(:mode) && @session.mode.to_s =~ /^selenium/
68
+ pending "Selenium is buggy on this, see http://code.google.com/p/selenium/issues/detail?id=2239" if @session.respond_to?(:mode) && @session.mode.to_s =~ /^selenium_(firefox|marionette)/
69
69
  @session.attach_file "Multiple Documents", [@test_file_path, @another_test_file_path]
70
70
  @session.click_button('Upload Multiple')
71
71
  expect(@session.body).to include("2 | ")#number of files
@@ -169,6 +169,14 @@ Capybara::SpecHelper.spec "#check" do
169
169
  expect(extract_results(@session)['cars']).to include('tesla')
170
170
  end
171
171
 
172
+ it "should not wait the full time if label can be clicked" do
173
+ expect(@session.find(:checkbox, 'form_cars_tesla', unchecked: true, visible: :hidden)).to be
174
+ start_time = Time.now
175
+ @session.check('form_cars_tesla', allow_label_click: true, wait: 10)
176
+ end_time = Time.now
177
+ expect(end_time - start_time).to be < 10
178
+ end
179
+
172
180
  it "should check via the label if input is moved off the left edge of the page" do
173
181
  expect(@session.find(:checkbox, 'form_cars_pagani', unchecked: true, visible: :all)).to be
174
182
  @session.check('form_cars_pagani', allow_label_click: true)
@@ -86,4 +86,9 @@ Capybara::SpecHelper.spec "#choose" do
86
86
  end
87
87
  end
88
88
  end
89
+
90
+ it "should return the chosen radio button" do
91
+ el = @session.find(:radio_button, 'gender_male')
92
+ expect(@session.choose("gender_male")).to eq el
93
+ end
89
94
  end
@@ -108,6 +108,11 @@ Capybara::SpecHelper.spec '#click_link_or_button' do
108
108
  @session.click_link_or_button('Disabled button', disabled: false)
109
109
  end.to raise_error(Capybara::ElementNotFound)
110
110
  end
111
+ end
111
112
 
113
+ it "should return the element clicked" do
114
+ @session.visit('/with_html')
115
+ link = @session.find(:link, 'Blank Anchor')
116
+ expect(@session.click_link_or_button('Blank Anchor')).to eq link
112
117
  end
113
118
  end
@@ -186,4 +186,9 @@ Capybara::SpecHelper.spec '#click_link' do
186
186
  expect(@session).to have_content('Another World')
187
187
  end
188
188
  end
189
+
190
+ it "should return element clicked" do
191
+ el = @session.find(:link, 'Normal Anchor')
192
+ expect(@session.click_link('Normal Anchor')).to eq el
193
+ end
189
194
  end
@@ -28,4 +28,9 @@ Capybara::SpecHelper.spec '#assert_matches_selector' do
28
28
  it "should not accept count options" do
29
29
  expect { @element.assert_matches_selector(:css, '.number', count: 1) }.to raise_error(ArgumentError)
30
30
  end
31
+
32
+ it "should accept a filter block" do
33
+ @element.assert_matches_selector(:css, 'span') { |el| el[:class] == "number" }
34
+ @element.assert_not_matches_selector(:css,'span') { |el| el[:class] == "not_number" }
35
+ end
31
36
  end
@@ -14,4 +14,10 @@ Capybara::SpecHelper.spec '#match_css?' do
14
14
  expect(@element).not_to match_css("p a#doesnotexist")
15
15
  expect(@element).not_to match_css("p.nosuchclass")
16
16
  end
17
+
18
+ it "should accept an optional filter block" do
19
+ # This would be better done with
20
+ expect(@element).to match_css('span') { |el| el[:class] == "number" }
21
+ expect(@element).not_to match_css('span') { |el| el[:class] == "not_number" }
22
+ end
17
23
  end