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.
- 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
|