capybara 1.0.1 → 1.1.0.rc1

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 (39) hide show
  1. data/History.txt +25 -0
  2. data/README.rdoc +18 -3
  3. data/lib/capybara.rb +6 -1
  4. data/lib/capybara/driver/base.rb +4 -0
  5. data/lib/capybara/dsl.rb +23 -1
  6. data/lib/capybara/node/base.rb +19 -3
  7. data/lib/capybara/node/element.rb +39 -16
  8. data/lib/capybara/node/finders.rb +21 -29
  9. data/lib/capybara/node/matchers.rb +6 -6
  10. data/lib/capybara/node/simple.rb +3 -7
  11. data/lib/capybara/rack_test/browser.rb +21 -13
  12. data/lib/capybara/rack_test/driver.rb +1 -1
  13. data/lib/capybara/rack_test/node.rb +2 -1
  14. data/lib/capybara/selenium/driver.rb +8 -1
  15. data/lib/capybara/session.rb +16 -3
  16. data/lib/capybara/spec/driver.rb +5 -5
  17. data/lib/capybara/spec/public/test.js +5 -0
  18. data/lib/capybara/spec/session/click_button_spec.rb +1 -1
  19. data/lib/capybara/spec/session/click_link_spec.rb +6 -0
  20. data/lib/capybara/spec/session/current_host_spec.rb +6 -0
  21. data/lib/capybara/spec/session/find_spec.rb +6 -0
  22. data/lib/capybara/spec/session/javascript.rb +67 -0
  23. data/lib/capybara/spec/session/within_spec.rb +11 -0
  24. data/lib/capybara/spec/test_app.rb +13 -1
  25. data/lib/capybara/spec/views/form.erb +1 -1
  26. data/lib/capybara/spec/views/with_html.erb +1 -0
  27. data/lib/capybara/spec/views/with_js.erb +7 -2
  28. data/lib/capybara/version.rb +1 -1
  29. data/spec/driver/rack_test_driver_spec.rb +6 -0
  30. data/spec/driver/selenium_driver_spec.rb +21 -0
  31. data/spec/dsl_spec.rb +44 -0
  32. data/spec/fixtures/selenium_driver_rspec_failure.rb +8 -0
  33. data/spec/fixtures/selenium_driver_rspec_success.rb +8 -0
  34. data/spec/rspec_spec.rb +0 -1
  35. data/spec/session/rack_test_session_spec.rb +12 -0
  36. data/spec/spec_helper.rb +3 -0
  37. metadata +114 -173
  38. data/lib/capybara/spec/public/jquery-ui.js +0 -35
  39. data/lib/capybara/spec/public/jquery.js +0 -19
@@ -34,7 +34,7 @@ module Capybara
34
34
  #
35
35
  def has_selector?(*args)
36
36
  options = if args.last.is_a?(Hash) then args.last else {} end
37
- wait_conditionally_until do
37
+ wait_until do
38
38
  results = all(*args)
39
39
 
40
40
  case
@@ -50,9 +50,9 @@ module Capybara
50
50
  options[:minimum].to_i <= results.size
51
51
  else
52
52
  results.size > 0
53
- end
53
+ end or raise ExpectationNotMet
54
54
  end
55
- rescue Capybara::TimeoutError
55
+ rescue Capybara::ExpectationNotMet
56
56
  return false
57
57
  end
58
58
 
@@ -66,7 +66,7 @@ module Capybara
66
66
  #
67
67
  def has_no_selector?(*args)
68
68
  options = if args.last.is_a?(Hash) then args.last else {} end
69
- wait_conditionally_until do
69
+ wait_until do
70
70
  results = all(*args)
71
71
 
72
72
  case
@@ -82,9 +82,9 @@ module Capybara
82
82
  not(options[:minimum].to_i <= results.size)
83
83
  else
84
84
  results.empty?
85
- end
85
+ end or raise ExpectationNotMet
86
86
  end
87
- rescue Capybara::TimeoutError
87
+ rescue Capybara::ExpectationNotMet
88
88
  return false
89
89
  end
90
90
 
@@ -120,16 +120,12 @@ module Capybara
120
120
 
121
121
  protected
122
122
 
123
- def find_in_base(xpath)
123
+ def find_in_base(selector, xpath)
124
124
  native.xpath(xpath).map { |node| self.class.new(node) }
125
125
  end
126
126
 
127
- def convert_element(element)
128
- element
129
- end
130
-
131
- def wait?
132
- false
127
+ def wait_until
128
+ yield # simple nodes don't need to wait
133
129
  end
134
130
  end
135
131
  end
@@ -1,32 +1,42 @@
1
1
  class Capybara::RackTest::Browser
2
2
  include ::Rack::Test::Methods
3
3
 
4
- attr_reader :app, :options
4
+ attr_reader :driver
5
5
  attr_accessor :current_host
6
6
 
7
- def initialize(app, options={})
8
- @app = app
9
- @options = options
7
+ def initialize(driver)
8
+ @driver = driver
9
+ end
10
+
11
+ def app
12
+ driver.app
13
+ end
14
+
15
+ def options
16
+ driver.options
10
17
  end
11
18
 
12
19
  def visit(path, attributes = {})
13
20
  reset_host!
14
21
  process(:get, path, attributes)
22
+ follow_redirects!
15
23
  end
16
24
 
17
25
  def submit(method, path, attributes)
18
26
  path = request_path if not path or path.empty?
19
27
  process(method, path, attributes)
28
+ follow_redirects!
20
29
  end
21
30
 
22
31
  def follow(method, path, attributes = {})
23
32
  return if path.gsub(/^#{request_path}/, '').start_with?('#')
24
33
  process(method, path, attributes)
34
+ follow_redirects!
25
35
  end
26
36
 
27
37
  def follow_redirects!
28
38
  5.times do
29
- follow_redirect! if last_response.redirect?
39
+ process(:get, last_response["Location"]) if last_response.redirect?
30
40
  end
31
41
  raise Capybara::InfiniteRedirectError, "redirected more than 5 times, check for infinite redirects." if last_response.redirect?
32
42
  end
@@ -38,20 +48,18 @@ class Capybara::RackTest::Browser
38
48
  if new_uri.host
39
49
  @current_host = new_uri.scheme + '://' + new_uri.host
40
50
  end
41
-
51
+
42
52
  if new_uri.relative?
43
- path = request_path + path if path.start_with?('?')
44
-
45
- unless path.start_with?('/')
46
- folders = request_path.split('/')
47
- path = (folders[0, folders.size - 1] << path).join('/')
53
+ if path.start_with?('?')
54
+ path = request_path + path
55
+ elsif not path.start_with?('/')
56
+ path = request_path.sub(%r(/[^/]*$), '/') + path
48
57
  end
49
58
  path = current_host + path
50
59
  end
51
-
60
+
52
61
  reset_cache!
53
62
  send(method, path, attributes, env)
54
- follow_redirects!
55
63
  end
56
64
 
57
65
  def current_url
@@ -14,7 +14,7 @@ class Capybara::RackTest::Driver < Capybara::Driver::Base
14
14
  end
15
15
 
16
16
  def browser
17
- @browser ||= Capybara::RackTest::Browser.new(app, options)
17
+ @browser ||= Capybara::RackTest::Browser.new(self)
18
18
  end
19
19
 
20
20
  def response
@@ -51,7 +51,8 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
51
51
 
52
52
  def click
53
53
  if tag_name == 'a'
54
- method = self["data-method"] || :get
54
+ method = self["data-method"] if driver.options[:respect_data_method]
55
+ method ||= :get
55
56
  driver.follow(method, self[:href].to_s)
56
57
  elsif (tag_name == 'input' and %w(submit image).include?(type)) or
57
58
  ((tag_name == 'button') and type.nil? or type == "submit")
@@ -2,7 +2,7 @@ require 'selenium-webdriver'
2
2
 
3
3
  class Capybara::Selenium::Driver < Capybara::Driver::Base
4
4
  DEFAULT_OPTIONS = {
5
- :resynchronize => true,
5
+ :resynchronize => false,
6
6
  :resynchronization_timeout => 10,
7
7
  :browser => :firefox
8
8
  }
@@ -16,7 +16,10 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
16
16
 
17
17
  main = Process.pid
18
18
  at_exit do
19
+ # Store the exit status of the test run since it goes away after calling the at_exit proc...
20
+ @exit_status = $!.status if $!.is_a?(SystemExit)
19
21
  quit if Process.pid == main
22
+ exit @exit_status if @exit_status # Force exit with stored status
20
23
  end
21
24
  end
22
25
  @browser
@@ -118,6 +121,10 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
118
121
  # Browser must have already gone
119
122
  end
120
123
 
124
+ def invalid_element_errors
125
+ [Selenium::WebDriver::Error::ObsoleteElementError]
126
+ end
127
+
121
128
  private
122
129
 
123
130
  def load_wait_for_ajax_support
@@ -173,11 +173,20 @@ module Capybara
173
173
  # fill_in('Street', :with => '12 Main Street')
174
174
  # end
175
175
  #
176
- # @param (see Capybara::Node::Finders#all)
176
+ # @overload within(*find_args)
177
+ # @param (see Capybara::Node::Finders#all)
178
+ #
179
+ # @overload within(a_node)
180
+ # @param [Capybara::Node::Base] a_node The node in whose scope the block should be evaluated
181
+ #
177
182
  # @raise [Capybara::ElementNotFound] If the scope can't be found before time expires
178
183
  #
179
184
  def within(*args)
180
- new_scope = find(*args)
185
+ new_scope = if args.size == 1 && Capybara::Node::Base === args.first
186
+ args.first
187
+ else
188
+ find(*args)
189
+ end
181
190
  begin
182
191
  scopes.push(new_scope)
183
192
  yield
@@ -284,7 +293,7 @@ module Capybara
284
293
  end
285
294
 
286
295
  def document
287
- Capybara::Node::Document.new(self, driver)
296
+ @document ||= Capybara::Node::Document.new(self, driver)
288
297
  end
289
298
 
290
299
  NODE_METHODS.each do |method|
@@ -295,6 +304,10 @@ module Capybara
295
304
  RUBY
296
305
  end
297
306
 
307
+ def inspect
308
+ %(#<Capybara::Session>)
309
+ end
310
+
298
311
  private
299
312
 
300
313
  def current_node
@@ -138,6 +138,7 @@ shared_examples_for "driver with resynchronization support" do
138
138
  before { @driver.visit('/with_js') }
139
139
  describe "#find" do
140
140
  context "with synchronization turned on" do
141
+ before { @driver.options[:resynchronize] = true }
141
142
  it "should wait for all ajax requests to finish" do
142
143
  @driver.find('//input[@id="fire_ajax_request"]').first.click
143
144
  @driver.find('//p[@id="ajax_request_done"]').should_not be_empty
@@ -146,16 +147,14 @@ shared_examples_for "driver with resynchronization support" do
146
147
 
147
148
  context "with resynchronization turned off" do
148
149
  before { @driver.options[:resynchronize] = false }
149
-
150
150
  it "should not wait for ajax requests to finish" do
151
151
  @driver.find('//input[@id="fire_ajax_request"]').first.click
152
152
  @driver.find('//p[@id="ajax_request_done"]').should be_empty
153
153
  end
154
-
155
- after { @driver.options[:resynchronize] = true }
156
154
  end
157
155
 
158
156
  context "with short synchronization timeout" do
157
+ before { @driver.options[:resynchronize] = true }
159
158
  before { @driver.options[:resynchronization_timeout] = 0.1 }
160
159
 
161
160
  it "should raise an error" do
@@ -163,10 +162,11 @@ shared_examples_for "driver with resynchronization support" do
163
162
  @driver.find('//input[@id="fire_ajax_request"]').first.click
164
163
  end.to raise_error(Capybara::TimeoutError, "failed to resynchronize, ajax request timed out")
165
164
  end
166
-
167
- after { @driver.options[:resynchronization_timeout] = 10 }
168
165
  end
169
166
  end
167
+
168
+ after { @driver.options[:resynchronize] = false }
169
+ after { @driver.options[:resynchronization_timeout] = 10 }
170
170
  end
171
171
 
172
172
  shared_examples_for "driver with header support" do
@@ -35,4 +35,9 @@ $(function() {
35
35
  $('body').append('<p id="ajax_request_done">Ajax request done</p>');
36
36
  }});
37
37
  });
38
+ $('#reload-link').click(function() {
39
+ setTimeout(function() {
40
+ $('#reload-me').replaceWith('<div id="reload-me"><em><a>RELOADED</a></em></div>');
41
+ }, 250)
42
+ });
38
43
  });
@@ -52,7 +52,7 @@ shared_examples_for "click_button" do
52
52
  end
53
53
 
54
54
  it "should serialise and submit color fields" do
55
- @results['html5_color'].should == '#FFF'
55
+ @results['html5_color'].should == '#FFFFFF'
56
56
  end
57
57
  end
58
58
 
@@ -70,6 +70,12 @@ shared_examples_for "click_link" do
70
70
  end
71
71
  end
72
72
 
73
+ it "should follow relative links" do
74
+ @session.visit('/')
75
+ @session.click_link('Relative')
76
+ @session.body.should include('This is a test')
77
+ end
78
+
73
79
  it "should follow redirects" do
74
80
  @session.click_link('Redirect')
75
81
  @session.body.should include('You landed')
@@ -58,5 +58,11 @@ shared_examples_for "current_host" do
58
58
  @session.body.should include('Current host is http://capybara2.elabs.se')
59
59
  @session.current_host.should == 'http://capybara2.elabs.se'
60
60
  end
61
+
62
+ it "is affected by following a redirect" do
63
+ @session.visit('http://capybara-testapp.heroku.com/redirect_secure')
64
+ @session.body.should include('Current host is https://capybara-testapp.heroku.com')
65
+ @session.current_host.should == 'https://capybara-testapp.heroku.com'
66
+ end
61
67
  end
62
68
  end
@@ -32,6 +32,12 @@ shared_examples_for "find" do
32
32
  it "should scope CSS selectors" do
33
33
  @session.find(:css, '#second').should have_no_css('h1')
34
34
  end
35
+
36
+ it "should have a reference to its parent if there is one" do
37
+ @node = @session.find(:css, '#first')
38
+ @node.parent.should == @node.session.document
39
+ @node.find('a').parent.should == @node
40
+ end
35
41
  end
36
42
 
37
43
  context "with css selectors" do
@@ -18,6 +18,64 @@ shared_examples_for "session with javascript support" do
18
18
  end
19
19
  end
20
20
 
21
+ describe 'Node#reload' do
22
+ context "without automatic reload" do
23
+ before { Capybara.automatic_reload = false }
24
+ it "should reload the current context of the node" do
25
+ @session.visit('/with_js')
26
+ node = @session.find(:css, '#reload-me')
27
+ @session.click_link('Reload!')
28
+ sleep(0.3)
29
+ node.reload.text.should == 'RELOADED'
30
+ node.text.should == 'RELOADED'
31
+ end
32
+
33
+ it "should reload a parent node" do
34
+ @session.visit('/with_js')
35
+ node = @session.find(:css, '#reload-me').find(:css, 'em')
36
+ @session.click_link('Reload!')
37
+ sleep(0.3)
38
+ node.reload.text.should == 'RELOADED'
39
+ node.text.should == 'RELOADED'
40
+ end
41
+
42
+ it "should not automatically reload" do
43
+ @session.visit('/with_js')
44
+ node = @session.find(:css, '#reload-me')
45
+ @session.click_link('Reload!')
46
+ sleep(0.3)
47
+ running { node.text.should == 'RELOADED' }.should raise_error
48
+ end
49
+ after { Capybara.automatic_reload = true }
50
+ end
51
+
52
+ context "with automatic reload" do
53
+ it "should reload the current context of the node automatically" do
54
+ @session.visit('/with_js')
55
+ node = @session.find(:css, '#reload-me')
56
+ @session.click_link('Reload!')
57
+ sleep(0.3)
58
+ node.text.should == 'RELOADED'
59
+ end
60
+
61
+ it "should reload a parent node automatically" do
62
+ @session.visit('/with_js')
63
+ node = @session.find(:css, '#reload-me').find(:css, 'em')
64
+ @session.click_link('Reload!')
65
+ sleep(0.3)
66
+ node.text.should == 'RELOADED'
67
+ end
68
+
69
+ it "should reload a node automatically when using find" do
70
+ @session.visit('/with_js')
71
+ node = @session.find(:css, '#reload-me')
72
+ @session.click_link('Reload!')
73
+ sleep(0.3)
74
+ node.find(:css, 'a').text.should == 'RELOADED'
75
+ end
76
+ end
77
+ end
78
+
21
79
  describe '#find' do
22
80
  it "should allow triggering of custom JS events" do
23
81
  pending "cannot figure out how to do this with selenium" if @session.mode == :selenium
@@ -65,6 +123,15 @@ shared_examples_for "session with javascript support" do
65
123
  @session.click_link('Click me')
66
124
  @session.find(:css, "a#has-been-clicked").text.should include('Has been clicked')
67
125
  end
126
+
127
+ context "with frozen time" do
128
+ it "raises an error suggesting that Capybara is stuck in time" do
129
+ @session.visit('/with_js')
130
+ now = Time.now
131
+ Time.stub(:now).and_return(now)
132
+ expect { @session.find('//isnotthere') }.to raise_error(Capybara::FrozenInTime)
133
+ end
134
+ end
68
135
  end
69
136
 
70
137
  describe '#wait_until' do
@@ -38,6 +38,17 @@ shared_examples_for "within" do
38
38
  end
39
39
  end
40
40
 
41
+ context "with Node rather than selector" do
42
+ it "should click links in the given scope" do
43
+ node_of_interest = @session.find(:css, "ul li[contains('With Simple HTML')]")
44
+
45
+ @session.within(node_of_interest) do
46
+ @session.click_link('Go')
47
+ end
48
+ @session.body.should include('Bar')
49
+ end
50
+ end
51
+
41
52
  context "with the default selector set to CSS" do
42
53
  before { Capybara.default_selector = :css }
43
54
  it "should use CSS" do
@@ -7,7 +7,7 @@ class TestApp < Sinatra::Base
7
7
  set :static, true
8
8
 
9
9
  get '/' do
10
- 'Hello world!'
10
+ 'Hello world! <a href="with_html">Relative</a>'
11
11
  end
12
12
 
13
13
  get '/foo' do
@@ -63,10 +63,18 @@ class TestApp < Sinatra::Base
63
63
  "The requested object was deleted"
64
64
  end
65
65
 
66
+ get "/delete" do
67
+ "Not deleted"
68
+ end
69
+
66
70
  get '/redirect_back' do
67
71
  redirect back
68
72
  end
69
73
 
74
+ get '/redirect_secure' do
75
+ redirect "https://#{request.host}/host"
76
+ end
77
+
70
78
  get '/slow_response' do
71
79
  sleep 2
72
80
  'Finally!'
@@ -86,6 +94,10 @@ class TestApp < Sinatra::Base
86
94
  env['HTTP_FOO']
87
95
  end
88
96
 
97
+ get '/get_header_via_redirect' do
98
+ redirect '/get_header'
99
+ end
100
+
89
101
  get '/:view' do |view|
90
102
  erb view.to_sym
91
103
  end