capybara 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. data/Manifest.txt +39 -0
  2. data/README.rdoc +181 -57
  3. data/Rakefile +7 -16
  4. data/config.ru +6 -0
  5. data/lib/capybara.rb +19 -9
  6. data/lib/capybara/cucumber.rb +4 -0
  7. data/lib/capybara/driver/base.rb +25 -8
  8. data/lib/capybara/driver/celerity_driver.rb +108 -0
  9. data/lib/capybara/driver/culerity_driver.rb +1 -70
  10. data/lib/capybara/driver/rack_test_driver.rb +57 -25
  11. data/lib/capybara/driver/selenium_driver.rb +28 -10
  12. data/lib/capybara/dsl.rb +7 -4
  13. data/lib/capybara/node.rb +50 -29
  14. data/lib/capybara/rails.rb +1 -1
  15. data/lib/capybara/save_and_open_page.rb +1 -1
  16. data/lib/capybara/searchable.rb +45 -0
  17. data/lib/capybara/server.rb +11 -4
  18. data/lib/capybara/session.rb +94 -94
  19. data/lib/capybara/wait_until.rb +23 -0
  20. data/lib/capybara/xpath.rb +55 -41
  21. data/spec/capybara_spec.rb +18 -0
  22. data/spec/driver/celerity_driver_spec.rb +17 -0
  23. data/spec/driver/culerity_driver_spec.rb +3 -0
  24. data/spec/driver/rack_test_driver_spec.rb +3 -0
  25. data/spec/driver/remote_culerity_driver_spec.rb +19 -0
  26. data/spec/driver/remote_selenium_driver_spec.rb +18 -0
  27. data/spec/driver/selenium_driver_spec.rb +2 -0
  28. data/spec/drivers_spec.rb +51 -5
  29. data/spec/dsl/all_spec.rb +38 -0
  30. data/spec/dsl/attach_file_spec.rb +66 -0
  31. data/spec/dsl/check_spec.rb +28 -0
  32. data/spec/dsl/choose_spec.rb +28 -0
  33. data/spec/dsl/click_button_spec.rb +183 -0
  34. data/spec/dsl/click_link_spec.rb +88 -0
  35. data/spec/dsl/click_spec.rb +26 -0
  36. data/spec/dsl/current_url_spec.rb +10 -0
  37. data/spec/dsl/fill_in_spec.rb +83 -0
  38. data/spec/dsl/find_button_spec.rb +18 -0
  39. data/spec/dsl/find_field_spec.rb +24 -0
  40. data/spec/dsl/find_link_spec.rb +19 -0
  41. data/spec/dsl/find_spec.rb +36 -0
  42. data/spec/dsl/has_content_spec.rb +103 -0
  43. data/spec/dsl/has_css_spec.rb +109 -0
  44. data/spec/dsl/has_xpath_spec.rb +115 -0
  45. data/spec/dsl/locate_spec.rb +38 -0
  46. data/spec/dsl/select_spec.rb +27 -0
  47. data/spec/dsl/uncheck_spec.rb +29 -0
  48. data/spec/dsl/within_spec.rb +145 -0
  49. data/spec/fixtures/capybara.jpg +0 -0
  50. data/spec/public/test.js +27 -0
  51. data/spec/searchable_spec.rb +61 -0
  52. data/spec/server_spec.rb +47 -0
  53. data/spec/session/celerity_session_spec.rb +27 -0
  54. data/spec/session/culerity_session_spec.rb +1 -0
  55. data/spec/session/rack_test_session_spec.rb +1 -0
  56. data/spec/session/selenium_session_spec.rb +1 -0
  57. data/spec/session_spec.rb +32 -903
  58. data/spec/session_with_headers_support_spec.rb +13 -0
  59. data/spec/session_with_javascript_support_spec.rb +165 -0
  60. data/spec/session_without_headers_support_spec.rb +15 -0
  61. data/spec/session_without_javascript_support_spec.rb +15 -0
  62. data/spec/spec_helper.rb +12 -0
  63. data/spec/test_app.rb +9 -1
  64. data/spec/views/form.erb +38 -0
  65. data/spec/views/postback.erb +13 -0
  66. data/spec/views/with_html.erb +13 -0
  67. data/spec/views/with_js.erb +9 -24
  68. data/spec/views/with_simple_html.erb +1 -1
  69. data/spec/wait_until_spec.rb +28 -0
  70. data/spec/xpath_spec.rb +34 -3
  71. 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
@@ -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).file_field(locator).checkbox(locator).radio_button(locator).select(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
- text_field(locator).password_field(locator).text_area(locator)
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 text_field(locator)
60
- add_field(locator) { |id| "//input[@type='text'][@id=#{id}]" }
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 password_field(locator)
64
- add_field(locator) { |id| "//input[@type='password'][@id=#{id}]" }
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) { |id| "//textarea[@id=#{id}]" }
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) { |id| "//select[@id=#{id}]" }
75
+ add_field(locator, "//select")
81
76
  end
82
77
 
83
- def file_field(locator)
84
- add_field(locator) { |id| "//input[@type='file'][@id=#{id}]" }
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(yield(s(locator)))
107
- xpath = xpath.append(yield("//label[contains(.,#{s(locator)})]/@for"))
108
- xpath.prepend(yield("//label[text()=#{s(locator)}]/@for"))
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
@@ -7,4 +7,7 @@ describe Capybara::Driver::Culerity do
7
7
 
8
8
  it_should_behave_like "driver"
9
9
  it_should_behave_like "driver with javascript support"
10
+ it_should_behave_like "driver with header support"
11
+ it_should_behave_like "driver with node path support"
12
+
10
13
  end
@@ -6,4 +6,7 @@ describe Capybara::Driver::RackTest do
6
6
  end
7
7
 
8
8
  it_should_behave_like "driver"
9
+ it_should_behave_like "driver with header support"
10
+ it_should_behave_like "driver with node path support"
11
+
9
12
  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
@@ -7,4 +7,6 @@ describe Capybara::Driver::Selenium do
7
7
 
8
8
  it_should_behave_like "driver"
9
9
  it_should_behave_like "driver with javascript support"
10
+ it_should_behave_like "driver without node path support"
11
+
10
12
  end
@@ -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('<h1>Bar</h1>')
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