capybara 2.3.0 → 2.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +21 -0
  3. data/README.md +50 -12
  4. data/lib/capybara.rb +8 -1
  5. data/lib/capybara/driver/base.rb +28 -0
  6. data/lib/capybara/driver/node.rb +3 -2
  7. data/lib/capybara/helpers.rb +2 -3
  8. data/lib/capybara/node/actions.rb +5 -2
  9. data/lib/capybara/node/base.rb +10 -0
  10. data/lib/capybara/node/document.rb +2 -0
  11. data/lib/capybara/node/document_matchers.rb +68 -0
  12. data/lib/capybara/node/element.rb +17 -2
  13. data/lib/capybara/node/finders.rb +5 -20
  14. data/lib/capybara/node/matchers.rb +101 -71
  15. data/lib/capybara/node/simple.rb +9 -15
  16. data/lib/capybara/queries/base_query.rb +29 -0
  17. data/lib/capybara/queries/text_query.rb +56 -0
  18. data/lib/capybara/queries/title_query.rb +40 -0
  19. data/lib/capybara/query.rb +30 -20
  20. data/lib/capybara/rack_test/node.rb +11 -3
  21. data/lib/capybara/result.rb +1 -1
  22. data/lib/capybara/rspec/features.rb +38 -21
  23. data/lib/capybara/rspec/matchers.rb +53 -38
  24. data/lib/capybara/selector.rb +68 -14
  25. data/lib/capybara/selenium/driver.rb +54 -6
  26. data/lib/capybara/selenium/node.rb +4 -2
  27. data/lib/capybara/session.rb +109 -35
  28. data/lib/capybara/spec/public/test.js +34 -1
  29. data/lib/capybara/spec/session/accept_alert_spec.rb +57 -0
  30. data/lib/capybara/spec/session/accept_confirm_spec.rb +19 -0
  31. data/lib/capybara/spec/session/accept_prompt_spec.rb +49 -0
  32. data/lib/capybara/spec/session/assert_text.rb +195 -0
  33. data/lib/capybara/spec/session/assert_title.rb +69 -0
  34. data/lib/capybara/spec/session/dismiss_confirm_spec.rb +35 -0
  35. data/lib/capybara/spec/session/dismiss_prompt_spec.rb +19 -0
  36. data/lib/capybara/spec/session/find_field_spec.rb +6 -0
  37. data/lib/capybara/spec/session/has_text_spec.rb +1 -1
  38. data/lib/capybara/spec/session/node_spec.rb +16 -1
  39. data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +1 -1
  40. data/lib/capybara/spec/session/visit_spec.rb +5 -0
  41. data/lib/capybara/spec/views/with_html.erb +4 -0
  42. data/lib/capybara/spec/views/with_js.erb +17 -0
  43. data/lib/capybara/version.rb +1 -1
  44. data/spec/dsl_spec.rb +3 -1
  45. data/spec/rack_test_spec.rb +12 -1
  46. data/spec/rspec/features_spec.rb +1 -1
  47. data/spec/rspec/matchers_spec.rb +113 -20
  48. data/spec/selenium_spec.rb +10 -1
  49. metadata +13 -2
@@ -91,13 +91,26 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
91
91
  def reset!
92
92
  # Use instance variable directly so we avoid starting the browser just to reset the session
93
93
  if @browser
94
- begin @browser.manage.delete_all_cookies
95
- rescue Selenium::WebDriver::Error::UnhandledError
96
- # delete_all_cookies fails when we've previously gone
97
- # to about:blank, so we rescue this error and do nothing
98
- # instead.
94
+ begin
95
+ begin @browser.manage.delete_all_cookies
96
+ rescue Selenium::WebDriver::Error::UnhandledError
97
+ # delete_all_cookies fails when we've previously gone
98
+ # to about:blank, so we rescue this error and do nothing
99
+ # instead.
100
+ end
101
+ @browser.navigate.to("about:blank")
102
+ rescue Selenium::WebDriver::Error::UnhandledAlertError
103
+ # This error is thrown if an unhandled alert is on the page
104
+ # Firefox appears to automatically dismiss this alert, chrome does not
105
+ # We'll try to accept it
106
+ begin
107
+ @browser.switch_to.alert.accept
108
+ rescue Selenium::WebDriver::Error::NoAlertPresentError
109
+ # The alert is now gone - nothing to do
110
+ end
111
+ # try cleaning up the browser again
112
+ retry
99
113
  end
100
- @browser.navigate.to("about:blank")
101
114
  end
102
115
  end
103
116
 
@@ -191,6 +204,23 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
191
204
  browser.switch_to.window(handle) { yield }
192
205
  end
193
206
 
207
+ def accept_modal(type, options={}, &blk)
208
+ yield if block_given?
209
+ modal = find_modal(options)
210
+ modal.send_keys options[:with] if options[:with]
211
+ message = modal.text
212
+ modal.accept
213
+ message
214
+ end
215
+
216
+ def dismiss_modal(type, options={}, &blk)
217
+ yield if block_given?
218
+ modal = find_modal(options)
219
+ message = modal.text
220
+ modal.dismiss
221
+ message
222
+ end
223
+
194
224
  def quit
195
225
  @browser.quit if @browser
196
226
  rescue Errno::ECONNREFUSED
@@ -220,4 +250,22 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
220
250
  result
221
251
  end
222
252
  end
253
+
254
+ def find_modal(options={})
255
+ # Selenium has its own built in wait (2 seconds)for a modal to show up, so this wait is really the minimum time
256
+ # Actual wait time may be longer than specified
257
+ wait = Selenium::WebDriver::Wait.new(
258
+ timeout: (options[:wait] || Capybara.default_wait_time),
259
+ ignore: Selenium::WebDriver::Error::NoAlertPresentError)
260
+ begin
261
+ modal = wait.until do
262
+ alert = @browser.switch_to.alert
263
+ regexp = options[:text].is_a?(Regexp) ? options[:text] : Regexp.escape(options[:text].to_s)
264
+ alert.text.match(regexp) ? alert : nil
265
+ end
266
+ rescue Selenium::WebDriver::Error::TimeOutError
267
+ raise Capybara::ModalNotFound.new("Unable to find modal dialog#{" with #{options[:text]}" if options[:text]}")
268
+ end
269
+ end
270
+
223
271
  end
@@ -37,11 +37,13 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
37
37
  path_names = value.to_s.empty? ? [] : value
38
38
  native.send_keys(*path_names)
39
39
  elsif tag_name == 'textarea' or tag_name == 'input'
40
- if value.to_s.empty?
40
+ if self[:readonly]
41
+ warn "Attempt to set readonly element with value: #{value} \n *This will raise an exception in a future version of Capybara"
42
+ elsif value.to_s.empty?
41
43
  native.clear
42
44
  else
43
45
  #script can change a readonly element which user input cannot, so dont execute if readonly
44
- driver.browser.execute_script "arguments[0].value = ''", native unless self[:readonly]
46
+ driver.browser.execute_script "arguments[0].value = ''", native
45
47
  native.send_keys(value.to_s)
46
48
  end
47
49
  elsif native.attribute('isContentEditable')
@@ -35,7 +35,11 @@ module Capybara
35
35
  :has_no_table?, :has_table?, :unselect, :has_select?, :has_no_select?,
36
36
  :has_selector?, :has_no_selector?, :click_on, :has_no_checked_field?,
37
37
  :has_no_unchecked_field?, :query, :assert_selector, :assert_no_selector,
38
- :refute_selector
38
+ :refute_selector, :assert_text, :assert_no_text
39
+ ]
40
+ # @api private
41
+ DOCUMENT_METHODS = [
42
+ :title, :assert_title, :assert_no_title, :has_title?, :has_no_title?
39
43
  ]
40
44
  SESSION_METHODS = [
41
45
  :body, :html, :source, :current_url, :current_host, :current_path,
@@ -44,9 +48,13 @@ module Capybara
44
48
  :windows, :open_new_window, :switch_to_window, :within_window, :window_opened_by,
45
49
  :save_page, :save_and_open_page, :save_screenshot,
46
50
  :save_and_open_screenshot, :reset_session!, :response_headers,
47
- :status_code, :title, :has_title?, :has_no_title?, :current_scope
51
+ :status_code, :current_scope
52
+ ] + DOCUMENT_METHODS
53
+ MODAL_METHODS = [
54
+ :accept_alert, :accept_confirm, :dismiss_confirm, :accept_prompt,
55
+ :dismiss_prompt
48
56
  ]
49
- DSL_METHODS = NODE_METHODS + SESSION_METHODS
57
+ DSL_METHODS = NODE_METHODS + SESSION_METHODS + MODAL_METHODS
50
58
 
51
59
  attr_reader :mode, :app, :server
52
60
  attr_accessor :synchronized
@@ -78,7 +86,7 @@ module Capybara
78
86
  #
79
87
  # This method does not:
80
88
  #
81
- # * accept modal dialogs if they are present
89
+ # * accept modal dialogs if they are present (Selenium driver now does, others may not)
82
90
  # * clear browser cache/HTML 5 local storage/IndexedDB/Web SQL database/etc.
83
91
  # * modify state of the driver/underlying browser in any other way
84
92
  #
@@ -167,14 +175,6 @@ module Capybara
167
175
  driver.current_url
168
176
  end
169
177
 
170
- ##
171
- #
172
- # @return [String] Title of the current page
173
- #
174
- def title
175
- driver.title
176
- end
177
-
178
178
  ##
179
179
  #
180
180
  # Navigate to the given URL. The URL can either be a relative URL or an absolute URL
@@ -206,12 +206,15 @@ module Capybara
206
206
 
207
207
  @touched = true
208
208
 
209
- if url !~ /^http/ and Capybara.app_host
209
+ url_relative = URI.parse(url.to_s).scheme.nil?
210
+
211
+ if url_relative && Capybara.app_host
210
212
  url = Capybara.app_host + url.to_s
213
+ url_relative = false
211
214
  end
212
215
 
213
216
  if @server
214
- url = "http://#{@server.host}:#{@server.port}" + url.to_s unless url =~ /^http/
217
+ url = "http://#{@server.host}:#{@server.port}" + url.to_s if url_relative
215
218
 
216
219
  if Capybara.always_include_port
217
220
  uri = URI.parse(url)
@@ -523,6 +526,93 @@ module Capybara
523
526
  driver.evaluate_script(script)
524
527
  end
525
528
 
529
+ ##
530
+ #
531
+ # Execute the block, accepting a alert.
532
+ #
533
+ # @!macro modal_params
534
+ # @overload $0(text, options = {}, &blk)
535
+ # @param text [String, Regexp] Text or regex to match against the text in the modal. If not provided any modal is matched
536
+ # @overload $0(options = {}, &blk)
537
+ # @option options [Numeric] :wait How long to wait for the modal to appear after executing the block.
538
+ # @return [String] the message shown in the modal
539
+ # @raise [Capybara::ModalNotFound] if modal dialog hasn't been found
540
+ #
541
+ def accept_alert(text_or_options=nil, options={}, &blk)
542
+ if text_or_options.is_a? Hash
543
+ options=text_or_options
544
+ else
545
+ options[:text]=text_or_options
546
+ end
547
+
548
+ driver.accept_modal(:alert, options, &blk)
549
+ end
550
+
551
+ ##
552
+ #
553
+ # Execute the block, accepting a confirm.
554
+ #
555
+ # @macro modal_params
556
+ #
557
+ def accept_confirm(text_or_options=nil, options={}, &blk)
558
+ if text_or_options.is_a? Hash
559
+ options=text_or_options
560
+ else
561
+ options[:text]=text_or_options
562
+ end
563
+
564
+ driver.accept_modal(:confirm, options, &blk)
565
+ end
566
+
567
+ ##
568
+ #
569
+ # Execute the block, dismissing a confirm.
570
+ #
571
+ # @macro modal_params
572
+ #
573
+ def dismiss_confirm(text_or_options=nil, options={}, &blk)
574
+ if text_or_options.is_a? Hash
575
+ options=text_or_options
576
+ else
577
+ options[:text]=text_or_options
578
+ end
579
+
580
+ driver.dismiss_modal(:confirm, options, &blk)
581
+ end
582
+
583
+ ##
584
+ #
585
+ # Execute the block, accepting a prompt, optionally responding to the prompt.
586
+ #
587
+ # @macro modal_params
588
+ # @option options [String] :with Response to provide to the prompt
589
+ #
590
+ def accept_prompt(text_or_options=nil, options={}, &blk)
591
+ if text_or_options.is_a? Hash
592
+ options=text_or_options
593
+ else
594
+ options[:text]=text_or_options
595
+ end
596
+
597
+ driver.accept_modal(:prompt, options, &blk)
598
+ end
599
+
600
+ ##
601
+ #
602
+ # Execute the block, dismissing a prompt.
603
+ #
604
+ # @macro modal_params
605
+ #
606
+ def dismiss_prompt(text_or_options=nil, options={}, &blk)
607
+ if text_or_options.is_a? Hash
608
+ options=text_or_options
609
+ else
610
+ options[:text]=text_or_options
611
+ end
612
+
613
+ driver.dismiss_modal(:prompt, options, &blk)
614
+ end
615
+
526
616
  ##
527
617
  #
528
618
  # Save a snapshot of the page.
@@ -586,30 +676,14 @@ module Capybara
586
676
  end
587
677
  end
588
678
 
589
- def inspect
590
- %(#<Capybara::Session>)
591
- end
592
-
593
- def has_title?(content)
594
- document.synchronize do
595
- unless title.match(Capybara::Helpers.to_regexp(content))
596
- raise ExpectationNotMet
597
- end
679
+ DOCUMENT_METHODS.each do |method|
680
+ define_method method do |*args, &block|
681
+ document.send(method, *args, &block)
598
682
  end
599
- return true
600
- rescue Capybara::ExpectationNotMet
601
- return false
602
683
  end
603
684
 
604
- def has_no_title?(content)
605
- document.synchronize do
606
- if title.match(Capybara::Helpers.to_regexp(content))
607
- raise ExpectationNotMet
608
- end
609
- end
610
- return true
611
- rescue Capybara::ExpectationNotMet
612
- return false
685
+ def inspect
686
+ %(#<Capybara::Session>)
613
687
  end
614
688
 
615
689
  def current_scope
@@ -53,7 +53,7 @@ $(function() {
53
53
  $('#reload-list').click(function() {
54
54
  setTimeout(function() {
55
55
  $('#the-list').html('<li>Foo</li><li>Bar</li>');
56
- }, 250)
56
+ }, 550)
57
57
  });
58
58
  $('#change-title').click(function() {
59
59
  setTimeout(function() {
@@ -67,4 +67,37 @@ $(function() {
67
67
  e.preventDefault();
68
68
  $(this).after('<a id="has-been-right-clicked" href="#">Has been right clicked</a>');
69
69
  });
70
+ $('#open-alert').click(function() {
71
+ alert('Alert opened');
72
+ $(this).attr('opened', 'true');
73
+ });
74
+ $('#open-delayed-alert').click(function() {
75
+ var link = this;
76
+ setTimeout(function() {
77
+ alert('Delayed alert opened');
78
+ $(link).attr('opened', 'true');
79
+ }, 250);
80
+ });
81
+ $('#open-slow-alert').click(function() {
82
+ var link = this;
83
+ setTimeout(function() {
84
+ alert('Delayed alert opened');
85
+ $(link).attr('opened', 'true');
86
+ }, 3000);
87
+ });
88
+ $('#open-confirm').click(function() {
89
+ if(confirm('Confirm opened')) {
90
+ $(this).attr('confirmed', 'true');
91
+ } else {
92
+ $(this).attr('confirmed', 'false');
93
+ }
94
+ });
95
+ $('#open-prompt').click(function() {
96
+ var response = prompt('Prompt opened');
97
+ if(response === null) {
98
+ $(this).attr('response', 'dismissed');
99
+ } else {
100
+ $(this).attr('response', response);
101
+ }
102
+ });
70
103
  });
@@ -0,0 +1,57 @@
1
+ Capybara::SpecHelper.spec '#accept_alert', :requires => [:modals] do
2
+ before do
3
+ @session.visit('/with_js')
4
+ end
5
+
6
+ it "should accept the alert" do
7
+ @session.accept_alert do
8
+ @session.click_link('Open alert')
9
+ end
10
+ expect(@session).to have_xpath("//a[@id='open-alert' and @opened='true']")
11
+ end
12
+
13
+ it "should accept the alert if the text matches" do
14
+ @session.accept_alert 'Alert opened' do
15
+ @session.click_link('Open alert')
16
+ end
17
+ expect(@session).to have_xpath("//a[@id='open-alert' and @opened='true']")
18
+ end
19
+
20
+ it "should not accept the alert if the text doesnt match" do
21
+ expect do
22
+ @session.accept_alert 'Incorrect Text' do
23
+ @session.click_link('Open alert')
24
+ end
25
+ end.to raise_error(Capybara::ModalNotFound)
26
+ end
27
+
28
+ it "should return the message presented" do
29
+ message = @session.accept_alert do
30
+ @session.click_link('Open alert')
31
+ end
32
+ expect(message).to eq('Alert opened')
33
+ end
34
+
35
+ context "with an asynchronous alert" do
36
+ it "should accept the alert" do
37
+ @session.accept_alert do
38
+ @session.click_link('Open delayed alert')
39
+ end
40
+ expect(@session).to have_xpath("//a[@id='open-delayed-alert' and @opened='true']")
41
+ end
42
+
43
+ it "should return the message presented" do
44
+ message = @session.accept_alert do
45
+ @session.click_link('Open delayed alert')
46
+ end
47
+ expect(message).to eq('Delayed alert opened')
48
+ end
49
+
50
+ it "should allow to adjust the delay" do
51
+ @session.accept_alert wait: 4 do
52
+ @session.click_link('Open slow alert')
53
+ end
54
+ expect(@session).to have_xpath("//a[@id='open-slow-alert' and @opened='true']")
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,19 @@
1
+ Capybara::SpecHelper.spec '#accept_confirm', :requires => [:modals] do
2
+ before do
3
+ @session.visit('/with_js')
4
+ end
5
+
6
+ it "should accept the confirm" do
7
+ @session.accept_confirm do
8
+ @session.click_link('Open confirm')
9
+ end
10
+ expect(@session).to have_xpath("//a[@id='open-confirm' and @confirmed='true']")
11
+ end
12
+
13
+ it "should return the message presented" do
14
+ message = @session.accept_confirm do
15
+ @session.click_link('Open confirm')
16
+ end
17
+ expect(message).to eq('Confirm opened')
18
+ end
19
+ end
@@ -0,0 +1,49 @@
1
+ Capybara::SpecHelper.spec '#accept_prompt', :requires => [:modals] do
2
+ before do
3
+ @session.visit('/with_js')
4
+ end
5
+
6
+ it "should accept the prompt with no message" do
7
+ @session.accept_prompt do
8
+ @session.click_link('Open prompt')
9
+ end
10
+ expect(@session).to have_xpath("//a[@id='open-prompt' and @response='']")
11
+ end
12
+
13
+ it "should return the message presented" do
14
+ message = @session.accept_prompt do
15
+ @session.click_link('Open prompt')
16
+ end
17
+ expect(message).to eq('Prompt opened')
18
+ end
19
+
20
+ it "should accept the prompt with a response" do
21
+ @session.accept_prompt with: 'the response' do
22
+ @session.click_link('Open prompt')
23
+ end
24
+ expect(@session).to have_xpath("//a[@id='open-prompt' and @response='the response']")
25
+ end
26
+
27
+ it "should accept the prompt if the message matches" do
28
+ @session.accept_prompt 'Prompt opened', with: 'matched' do
29
+ @session.click_link('Open prompt')
30
+ end
31
+ expect(@session).to have_xpath("//a[@id='open-prompt' and @response='matched']")
32
+ end
33
+
34
+ it "should not accept the prompt if the message doesn't match" do
35
+ expect do
36
+ @session.accept_prompt 'Incorrect Text', with: 'not matched' do
37
+ @session.click_link('Open prompt')
38
+ end
39
+ end.to raise_error(Capybara::ModalNotFound)
40
+ end
41
+
42
+
43
+ it "should return the message presented" do
44
+ message = @session.accept_prompt with: 'the response' do
45
+ @session.click_link('Open prompt')
46
+ end
47
+ expect(message).to eq('Prompt opened')
48
+ end
49
+ end