capybara 1.0.1 → 1.1.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
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