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