capybara 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Manifest.txt +39 -0
- data/README.rdoc +181 -57
- data/Rakefile +7 -16
- data/config.ru +6 -0
- data/lib/capybara.rb +19 -9
- data/lib/capybara/cucumber.rb +4 -0
- data/lib/capybara/driver/base.rb +25 -8
- data/lib/capybara/driver/celerity_driver.rb +108 -0
- data/lib/capybara/driver/culerity_driver.rb +1 -70
- data/lib/capybara/driver/rack_test_driver.rb +57 -25
- data/lib/capybara/driver/selenium_driver.rb +28 -10
- data/lib/capybara/dsl.rb +7 -4
- data/lib/capybara/node.rb +50 -29
- data/lib/capybara/rails.rb +1 -1
- data/lib/capybara/save_and_open_page.rb +1 -1
- data/lib/capybara/searchable.rb +45 -0
- data/lib/capybara/server.rb +11 -4
- data/lib/capybara/session.rb +94 -94
- data/lib/capybara/wait_until.rb +23 -0
- data/lib/capybara/xpath.rb +55 -41
- data/spec/capybara_spec.rb +18 -0
- data/spec/driver/celerity_driver_spec.rb +17 -0
- data/spec/driver/culerity_driver_spec.rb +3 -0
- data/spec/driver/rack_test_driver_spec.rb +3 -0
- data/spec/driver/remote_culerity_driver_spec.rb +19 -0
- data/spec/driver/remote_selenium_driver_spec.rb +18 -0
- data/spec/driver/selenium_driver_spec.rb +2 -0
- data/spec/drivers_spec.rb +51 -5
- data/spec/dsl/all_spec.rb +38 -0
- data/spec/dsl/attach_file_spec.rb +66 -0
- data/spec/dsl/check_spec.rb +28 -0
- data/spec/dsl/choose_spec.rb +28 -0
- data/spec/dsl/click_button_spec.rb +183 -0
- data/spec/dsl/click_link_spec.rb +88 -0
- data/spec/dsl/click_spec.rb +26 -0
- data/spec/dsl/current_url_spec.rb +10 -0
- data/spec/dsl/fill_in_spec.rb +83 -0
- data/spec/dsl/find_button_spec.rb +18 -0
- data/spec/dsl/find_field_spec.rb +24 -0
- data/spec/dsl/find_link_spec.rb +19 -0
- data/spec/dsl/find_spec.rb +36 -0
- data/spec/dsl/has_content_spec.rb +103 -0
- data/spec/dsl/has_css_spec.rb +109 -0
- data/spec/dsl/has_xpath_spec.rb +115 -0
- data/spec/dsl/locate_spec.rb +38 -0
- data/spec/dsl/select_spec.rb +27 -0
- data/spec/dsl/uncheck_spec.rb +29 -0
- data/spec/dsl/within_spec.rb +145 -0
- data/spec/fixtures/capybara.jpg +0 -0
- data/spec/public/test.js +27 -0
- data/spec/searchable_spec.rb +61 -0
- data/spec/server_spec.rb +47 -0
- data/spec/session/celerity_session_spec.rb +27 -0
- data/spec/session/culerity_session_spec.rb +1 -0
- data/spec/session/rack_test_session_spec.rb +1 -0
- data/spec/session/selenium_session_spec.rb +1 -0
- data/spec/session_spec.rb +32 -903
- data/spec/session_with_headers_support_spec.rb +13 -0
- data/spec/session_with_javascript_support_spec.rb +165 -0
- data/spec/session_without_headers_support_spec.rb +15 -0
- data/spec/session_without_javascript_support_spec.rb +15 -0
- data/spec/spec_helper.rb +12 -0
- data/spec/test_app.rb +9 -1
- data/spec/views/form.erb +38 -0
- data/spec/views/postback.erb +13 -0
- data/spec/views/with_html.erb +13 -0
- data/spec/views/with_js.erb +9 -24
- data/spec/views/with_simple_html.erb +1 -1
- data/spec/wait_until_spec.rb +28 -0
- data/spec/xpath_spec.rb +34 -3
- metadata +54 -5
@@ -0,0 +1,23 @@
|
|
1
|
+
module Capybara
|
2
|
+
#Provides timeout similar to standard library Timeout, but avoids threads
|
3
|
+
class WaitUntil
|
4
|
+
|
5
|
+
class << self
|
6
|
+
|
7
|
+
def timeout(seconds = 1, &block)
|
8
|
+
start_time = Time.now
|
9
|
+
|
10
|
+
result = nil
|
11
|
+
|
12
|
+
until result
|
13
|
+
return result if result = yield
|
14
|
+
|
15
|
+
if (Time.now - start_time) > seconds
|
16
|
+
raise TimeoutError
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/capybara/xpath.rb
CHANGED
@@ -3,7 +3,13 @@ module Capybara
|
|
3
3
|
# Xpath.text_field('foo').link('blah').to_s
|
4
4
|
# this will generate an XPath that matches either a text field or a link
|
5
5
|
class XPath
|
6
|
+
|
6
7
|
class << self
|
8
|
+
def from_css(css)
|
9
|
+
Nokogiri::CSS.xpath_for(css).first
|
10
|
+
end
|
11
|
+
alias_method :for_css, :from_css
|
12
|
+
|
7
13
|
def wrap(path)
|
8
14
|
if path.is_a?(self)
|
9
15
|
path
|
@@ -11,7 +17,7 @@ module Capybara
|
|
11
17
|
new(path.to_s)
|
12
18
|
end
|
13
19
|
end
|
14
|
-
|
20
|
+
|
15
21
|
def respond_to?(method)
|
16
22
|
new.respond_to?(method)
|
17
23
|
end
|
@@ -26,64 +32,53 @@ module Capybara
|
|
26
32
|
def initialize(*paths)
|
27
33
|
@paths = paths
|
28
34
|
end
|
29
|
-
|
35
|
+
|
30
36
|
def field(locator)
|
31
|
-
fillable_field(locator).
|
37
|
+
fillable_field(locator).input_field(:file, locator).checkbox(locator).radio_button(locator).select(locator)
|
32
38
|
end
|
33
39
|
|
34
40
|
def fillable_field(locator)
|
35
|
-
|
41
|
+
[:text, :password, :email, :url, :search, :tel, :color].inject(text_area(locator)) do |all, type|
|
42
|
+
all.input_field(type, locator)
|
43
|
+
end
|
36
44
|
end
|
37
|
-
|
45
|
+
|
38
46
|
def content(locator)
|
39
47
|
append("/descendant-or-self::*[contains(.,#{s(locator)})]")
|
40
48
|
end
|
41
|
-
|
49
|
+
|
42
50
|
def table(locator)
|
43
51
|
append("//table[@id=#{s(locator)} or contains(caption,#{s(locator)})]")
|
44
52
|
end
|
45
|
-
|
53
|
+
|
46
54
|
def fieldset(locator)
|
47
55
|
append("//fieldset[@id=#{s(locator)} or contains(legend,#{s(locator)})]")
|
48
56
|
end
|
49
|
-
|
50
|
-
def link(locator)
|
51
|
-
append("//a[@id=#{s(locator)} or contains(.,#{s(locator)}) or @title=#{s(locator)}]")
|
52
|
-
end
|
53
|
-
|
54
|
-
def button(locator)
|
55
|
-
xpath = append("//input[@type='submit' or @type='image'][@id=#{s(locator)} or @value=#{s(locator)}]")
|
56
|
-
xpath.append("//button[@id=#{s(locator)} or @value=#{s(locator)} or contains(.,#{s(locator)})]")
|
57
|
-
end
|
58
57
|
|
59
|
-
def
|
60
|
-
|
58
|
+
def link(locator)
|
59
|
+
xpath = append("//a[@href][@id=#{s(locator)} or contains(.,#{s(locator)}) or contains(@title,#{s(locator)})]")
|
60
|
+
xpath.prepend("//a[@href][text()=#{s(locator)} or @title=#{s(locator)}]")
|
61
61
|
end
|
62
62
|
|
63
|
-
def
|
64
|
-
|
63
|
+
def button(locator)
|
64
|
+
xpath = append("//input[@type='submit' or @type='image'][@id=#{s(locator)} or contains(@value,#{s(locator)})]")
|
65
|
+
xpath = xpath.append("//button[@id=#{s(locator)} or contains(@value,#{s(locator)}) or contains(.,#{s(locator)})]")
|
66
|
+
xpath = xpath.prepend("//input[@type='submit' or @type='image'][@value=#{s(locator)}]")
|
67
|
+
xpath = xpath.prepend("//button[@value=#{s(locator)} or text()=#{s(locator)}]")
|
65
68
|
end
|
66
69
|
|
67
70
|
def text_area(locator)
|
68
|
-
add_field(locator
|
69
|
-
end
|
70
|
-
|
71
|
-
def radio_button(locator)
|
72
|
-
add_field(locator) { |id| "//input[@type='radio'][@id=#{id}]" }
|
73
|
-
end
|
74
|
-
|
75
|
-
def checkbox(locator)
|
76
|
-
add_field(locator) { |id| "//input[@type='checkbox'][@id=#{id}]" }
|
71
|
+
add_field(locator, "//textarea")
|
77
72
|
end
|
78
73
|
|
79
74
|
def select(locator)
|
80
|
-
add_field(locator
|
75
|
+
add_field(locator, "//select")
|
81
76
|
end
|
82
77
|
|
83
|
-
def
|
84
|
-
add_field(locator
|
78
|
+
def input_field(type, locator)
|
79
|
+
add_field(locator, "//input[@type='#{type}']")
|
85
80
|
end
|
86
|
-
|
81
|
+
|
87
82
|
def scope(scope)
|
88
83
|
XPath.new(*paths.map { |p| scope + p })
|
89
84
|
end
|
@@ -91,23 +86,41 @@ module Capybara
|
|
91
86
|
def to_s
|
92
87
|
@paths.join(' | ')
|
93
88
|
end
|
94
|
-
|
89
|
+
|
95
90
|
def append(path)
|
96
91
|
XPath.new(*[@paths, XPath.wrap(path).paths].flatten)
|
97
92
|
end
|
98
|
-
|
93
|
+
|
99
94
|
def prepend(path)
|
100
95
|
XPath.new(*[XPath.wrap(path).paths, @paths].flatten)
|
101
96
|
end
|
102
97
|
|
98
|
+
def checkbox(locator)
|
99
|
+
input_field(:checkbox, locator)
|
100
|
+
end
|
101
|
+
|
102
|
+
def radio_button(locator)
|
103
|
+
input_field(:radio, locator)
|
104
|
+
end
|
105
|
+
|
106
|
+
[:text, :password, :email, :url, :search, :tel, :color, :file].each do |type|
|
107
|
+
class_eval <<-RUBY, __FILE__, __LINE__+1
|
108
|
+
def #{type}_field(locator)
|
109
|
+
input_field(:#{type}, locator)
|
110
|
+
end
|
111
|
+
RUBY
|
112
|
+
end
|
113
|
+
|
103
114
|
protected
|
104
|
-
|
105
|
-
def add_field(locator)
|
106
|
-
xpath = append(
|
107
|
-
xpath = xpath.append(
|
108
|
-
xpath.
|
115
|
+
|
116
|
+
def add_field(locator, field)
|
117
|
+
xpath = append("#{field}[@id=#{s(locator)}]")
|
118
|
+
xpath = xpath.append("#{field}[@name=#{s(locator)}]")
|
119
|
+
xpath = xpath.append("#{field}[@id=//label[contains(.,#{s(locator)})]/@for]")
|
120
|
+
xpath = xpath.append("//label[contains(.,#{s(locator)})]#{field}")
|
121
|
+
xpath.prepend("#{field}[@id=//label[text()=#{s(locator)}]/@for]")
|
109
122
|
end
|
110
|
-
|
123
|
+
|
111
124
|
# Sanitize a String for putting it into an xpath query
|
112
125
|
def s(string)
|
113
126
|
if string.include?("'")
|
@@ -118,6 +131,7 @@ module Capybara
|
|
118
131
|
else
|
119
132
|
"'#{string}'"
|
120
133
|
end
|
134
|
+
|
121
135
|
end
|
122
136
|
|
123
137
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require File.expand_path('spec_helper', File.dirname(__FILE__))
|
2
|
+
|
3
|
+
require 'capybara'
|
4
|
+
|
5
|
+
describe Capybara do
|
6
|
+
|
7
|
+
describe 'default_wait_time' do
|
8
|
+
after do
|
9
|
+
Capybara.default_wait_time = 2
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should be changeable" do
|
13
|
+
Capybara.default_wait_time = 5
|
14
|
+
Capybara.default_wait_time.should == 5
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require File.expand_path('../spec_helper', File.dirname(__FILE__))
|
2
|
+
|
3
|
+
if RUBY_PLATFORM =~ /java/
|
4
|
+
describe Capybara::Driver::Celerity do
|
5
|
+
before do
|
6
|
+
@driver = Capybara::Driver::Celerity.new(TestApp)
|
7
|
+
end
|
8
|
+
|
9
|
+
it_should_behave_like "driver"
|
10
|
+
it_should_behave_like "driver with javascript support"
|
11
|
+
it_should_behave_like "driver with header support"
|
12
|
+
it_should_behave_like "driver with node path support"
|
13
|
+
|
14
|
+
end
|
15
|
+
else
|
16
|
+
puts "#{File.basename(__FILE__)} requires JRuby; skipping.."
|
17
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require File.expand_path('../spec_helper', File.dirname(__FILE__))
|
2
|
+
|
3
|
+
describe Capybara::Driver::Culerity do
|
4
|
+
before do
|
5
|
+
@driver = Capybara::Driver::Culerity.new(TestApp)
|
6
|
+
end
|
7
|
+
|
8
|
+
before(:all) do
|
9
|
+
Capybara.app_host = "http://capybara-testapp.heroku.com"
|
10
|
+
end
|
11
|
+
|
12
|
+
after(:all) do
|
13
|
+
Capybara.app_host = nil
|
14
|
+
end
|
15
|
+
|
16
|
+
it_should_behave_like "driver"
|
17
|
+
it_should_behave_like "driver with javascript support"
|
18
|
+
it_should_behave_like "driver with header support"
|
19
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require File.expand_path('../spec_helper', File.dirname(__FILE__))
|
2
|
+
|
3
|
+
describe Capybara::Driver::Selenium do
|
4
|
+
before(:all) do
|
5
|
+
Capybara.app_host = "http://capybara-testapp.heroku.com"
|
6
|
+
end
|
7
|
+
|
8
|
+
after(:all) do
|
9
|
+
Capybara.app_host = nil
|
10
|
+
end
|
11
|
+
|
12
|
+
before do
|
13
|
+
@driver = Capybara::Driver::Selenium.new(TestApp)
|
14
|
+
end
|
15
|
+
|
16
|
+
it_should_behave_like "driver"
|
17
|
+
it_should_behave_like "driver with javascript support"
|
18
|
+
end
|
data/spec/drivers_spec.rb
CHANGED
@@ -9,6 +9,11 @@ shared_examples_for 'driver' do
|
|
9
9
|
@driver.visit('/foo')
|
10
10
|
@driver.body.should include('Another World')
|
11
11
|
end
|
12
|
+
|
13
|
+
it "should show the correct URL" do
|
14
|
+
@driver.visit('/foo')
|
15
|
+
@driver.current_url.should include('/foo')
|
16
|
+
end
|
12
17
|
end
|
13
18
|
|
14
19
|
describe '#body' do
|
@@ -19,7 +24,7 @@ shared_examples_for 'driver' do
|
|
19
24
|
|
20
25
|
it "should return the full response html" do
|
21
26
|
@driver.visit('/with_simple_html')
|
22
|
-
@driver.body.should include('
|
27
|
+
@driver.body.should include('Bar')
|
23
28
|
end
|
24
29
|
end
|
25
30
|
|
@@ -29,10 +34,6 @@ shared_examples_for 'driver' do
|
|
29
34
|
@driver.visit('/with_html')
|
30
35
|
end
|
31
36
|
|
32
|
-
it "should find the correct number of elements" do
|
33
|
-
@driver.find('//a').size.should == 3
|
34
|
-
end
|
35
|
-
|
36
37
|
it "should extract node texts" do
|
37
38
|
@driver.find('//a')[0].text.should == 'labore'
|
38
39
|
@driver.find('//a')[1].text.should == 'ullamco'
|
@@ -57,6 +58,13 @@ shared_examples_for 'driver' do
|
|
57
58
|
@driver.find('//a')[1].tag_name.should == 'a'
|
58
59
|
@driver.find('//p')[1].tag_name.should == 'p'
|
59
60
|
end
|
61
|
+
|
62
|
+
it "should extract node visibility" do
|
63
|
+
@driver.find('//a')[0].should be_visible
|
64
|
+
|
65
|
+
@driver.find('//div[@id="hidden"]')[0].should_not be_visible
|
66
|
+
@driver.find('//div[@id="hidden_via_ancestor"]')[0].should_not be_visible
|
67
|
+
end
|
60
68
|
end
|
61
69
|
end
|
62
70
|
|
@@ -87,3 +95,41 @@ shared_examples_for "driver with javascript support" do
|
|
87
95
|
end
|
88
96
|
|
89
97
|
end
|
98
|
+
|
99
|
+
shared_examples_for "driver with header support" do
|
100
|
+
it "should make headers available through response_headers" do
|
101
|
+
@driver.visit('/with_simple_html')
|
102
|
+
@driver.response_headers['Content-Type'].should == 'text/html'
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
shared_examples_for "driver with node path support" do
|
107
|
+
describe "node relative searching" do
|
108
|
+
before do
|
109
|
+
@driver.visit('/tables')
|
110
|
+
@node = @driver.find('//body').first
|
111
|
+
end
|
112
|
+
|
113
|
+
it "should be able to navigate/search child nodes" do
|
114
|
+
@node.all('//table').size.should == 3
|
115
|
+
@node.find('//form').all('//table').size.should == 1
|
116
|
+
@node.find('//form').find('//table//caption').text.should == 'Agent'
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
shared_examples_for "driver without node path support" do
|
122
|
+
describe "node relative searching" do
|
123
|
+
before do
|
124
|
+
@driver.visit('/tables')
|
125
|
+
@node = @driver.find('//body').first
|
126
|
+
end
|
127
|
+
|
128
|
+
it "should get NotSupportedByDriverError" do
|
129
|
+
running do
|
130
|
+
@node.all('//form')
|
131
|
+
end.should raise_error(Capybara::NotSupportedByDriverError)
|
132
|
+
end
|
133
|
+
|
134
|
+
end
|
135
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module AllSpec
|
2
|
+
shared_examples_for "all" do
|
3
|
+
describe '#all' do
|
4
|
+
before do
|
5
|
+
@session.visit('/with_html')
|
6
|
+
end
|
7
|
+
|
8
|
+
it "should find all elements using the given locator" do
|
9
|
+
@session.all('//p').should have(3).elements
|
10
|
+
@session.all('//h1').first.text.should == 'This is a test'
|
11
|
+
@session.all("//input[@id='test_field']").first[:value].should == 'monkey'
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should return an empty array when nothing was found" do
|
15
|
+
@session.all('//div[@id="nosuchthing"]').should be_empty
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should accept an XPath instance" do
|
19
|
+
@session.visit('/form')
|
20
|
+
@xpath = Capybara::XPath.text_field('Name')
|
21
|
+
@result = @session.all(@xpath).map { |r| r.value }
|
22
|
+
@result.should include('Smith', 'John', 'John Smith')
|
23
|
+
end
|
24
|
+
|
25
|
+
context "within a scope" do
|
26
|
+
before do
|
27
|
+
@session.visit('/with_scope')
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should find any element using the given locator" do
|
31
|
+
@session.within(:xpath, "//div[@id='for_bar']") do
|
32
|
+
@session.all('//li').should have(2).elements
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module AttachFileSpec
|
2
|
+
shared_examples_for "attach_file" do
|
3
|
+
|
4
|
+
describe "#attach_file" do
|
5
|
+
before do
|
6
|
+
@session.visit('/form')
|
7
|
+
end
|
8
|
+
|
9
|
+
context "with normal form" do
|
10
|
+
it "should set a file path by id" do
|
11
|
+
@session.attach_file "form_image", __FILE__
|
12
|
+
@session.click_button('awesome')
|
13
|
+
extract_results(@session)['image'].should == File.basename(__FILE__)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should set a file path by label" do
|
17
|
+
@session.attach_file "Image", __FILE__
|
18
|
+
@session.click_button('awesome')
|
19
|
+
extract_results(@session)['image'].should == File.basename(__FILE__)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
context "with multipart form" do
|
24
|
+
before do
|
25
|
+
@test_file_path = File.expand_path('../fixtures/test_file.txt', File.dirname(__FILE__))
|
26
|
+
@test_jpg_file_path = File.expand_path('../fixtures/capybara.jpg', File.dirname(__FILE__))
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should set a file path by id" do
|
30
|
+
@session.attach_file "form_document", @test_file_path
|
31
|
+
@session.click_button('Upload')
|
32
|
+
@session.body.should include(File.read(@test_file_path))
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should set a file path by label" do
|
36
|
+
@session.attach_file "Document", @test_file_path
|
37
|
+
@session.click_button('Upload')
|
38
|
+
@session.body.should include(File.read(@test_file_path))
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should not break if no file is submitted" do
|
42
|
+
@session.click_button('Upload')
|
43
|
+
@session.body.should include('No file uploaded')
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should send content type text/plain when uploading a text file" do
|
47
|
+
@session.attach_file "Document", @test_file_path
|
48
|
+
@session.click_button 'Upload'
|
49
|
+
@session.body.should include('text/plain')
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should send content type image/jpeg when uploading an image" do
|
53
|
+
@session.attach_file "Document", @test_jpg_file_path
|
54
|
+
@session.click_button 'Upload'
|
55
|
+
@session.body.should include('image/jpeg')
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
context "with a locator that doesn't exist" do
|
60
|
+
it "should raise an error" do
|
61
|
+
running { @session.attach_file('does not exist', 'foo.txt') }.should raise_error(Capybara::ElementNotFound)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|