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.
- data/History.txt +25 -0
- data/README.rdoc +18 -3
- data/lib/capybara.rb +6 -1
- data/lib/capybara/driver/base.rb +4 -0
- data/lib/capybara/dsl.rb +23 -1
- data/lib/capybara/node/base.rb +19 -3
- data/lib/capybara/node/element.rb +39 -16
- data/lib/capybara/node/finders.rb +21 -29
- data/lib/capybara/node/matchers.rb +6 -6
- data/lib/capybara/node/simple.rb +3 -7
- data/lib/capybara/rack_test/browser.rb +21 -13
- data/lib/capybara/rack_test/driver.rb +1 -1
- data/lib/capybara/rack_test/node.rb +2 -1
- data/lib/capybara/selenium/driver.rb +8 -1
- data/lib/capybara/session.rb +16 -3
- data/lib/capybara/spec/driver.rb +5 -5
- data/lib/capybara/spec/public/test.js +5 -0
- data/lib/capybara/spec/session/click_button_spec.rb +1 -1
- data/lib/capybara/spec/session/click_link_spec.rb +6 -0
- data/lib/capybara/spec/session/current_host_spec.rb +6 -0
- data/lib/capybara/spec/session/find_spec.rb +6 -0
- data/lib/capybara/spec/session/javascript.rb +67 -0
- data/lib/capybara/spec/session/within_spec.rb +11 -0
- data/lib/capybara/spec/test_app.rb +13 -1
- data/lib/capybara/spec/views/form.erb +1 -1
- data/lib/capybara/spec/views/with_html.erb +1 -0
- data/lib/capybara/spec/views/with_js.erb +7 -2
- data/lib/capybara/version.rb +1 -1
- data/spec/driver/rack_test_driver_spec.rb +6 -0
- data/spec/driver/selenium_driver_spec.rb +21 -0
- data/spec/dsl_spec.rb +44 -0
- data/spec/fixtures/selenium_driver_rspec_failure.rb +8 -0
- data/spec/fixtures/selenium_driver_rspec_success.rb +8 -0
- data/spec/rspec_spec.rb +0 -1
- data/spec/session/rack_test_session_spec.rb +12 -0
- data/spec/spec_helper.rb +3 -0
- metadata +114 -173
- data/lib/capybara/spec/public/jquery-ui.js +0 -35
- 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
|
-
|
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::
|
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
|
-
|
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::
|
87
|
+
rescue Capybara::ExpectationNotMet
|
88
88
|
return false
|
89
89
|
end
|
90
90
|
|
data/lib/capybara/node/simple.rb
CHANGED
@@ -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
|
128
|
-
|
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 :
|
4
|
+
attr_reader :driver
|
5
5
|
attr_accessor :current_host
|
6
6
|
|
7
|
-
def initialize(
|
8
|
-
@
|
9
|
-
|
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
|
-
|
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
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
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
|
@@ -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"]
|
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 =>
|
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
|
data/lib/capybara/session.rb
CHANGED
@@ -173,11 +173,20 @@ module Capybara
|
|
173
173
|
# fill_in('Street', :with => '12 Main Street')
|
174
174
|
# end
|
175
175
|
#
|
176
|
-
# @
|
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 =
|
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
|
data/lib/capybara/spec/driver.rb
CHANGED
@@ -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
|
});
|
@@ -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
|