capybara 0.2.0 → 0.3.0
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/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
|