bjeanes-capybara 0.3.1

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 (88) hide show
  1. data/History.txt +4 -0
  2. data/Manifest.txt +87 -0
  3. data/README.rdoc +404 -0
  4. data/Rakefile +29 -0
  5. data/config.ru +6 -0
  6. data/lib/capybara.rb +53 -0
  7. data/lib/capybara/cucumber.rb +32 -0
  8. data/lib/capybara/driver/base.rb +37 -0
  9. data/lib/capybara/driver/celerity_driver.rb +135 -0
  10. data/lib/capybara/driver/culerity_driver.rb +25 -0
  11. data/lib/capybara/driver/rack_test_driver.rb +244 -0
  12. data/lib/capybara/driver/selenium_driver.rb +137 -0
  13. data/lib/capybara/dsl.rb +60 -0
  14. data/lib/capybara/node.rb +69 -0
  15. data/lib/capybara/rails.rb +11 -0
  16. data/lib/capybara/save_and_open_page.rb +33 -0
  17. data/lib/capybara/searchable.rb +53 -0
  18. data/lib/capybara/server.rb +111 -0
  19. data/lib/capybara/session.rb +274 -0
  20. data/lib/capybara/wait_until.rb +23 -0
  21. data/lib/capybara/xpath.rb +176 -0
  22. data/script/console +10 -0
  23. data/script/destroy +14 -0
  24. data/script/generate +14 -0
  25. data/spec/capybara_spec.rb +18 -0
  26. data/spec/driver/celerity_driver_spec.rb +17 -0
  27. data/spec/driver/culerity_driver_spec.rb +13 -0
  28. data/spec/driver/rack_test_driver_spec.rb +12 -0
  29. data/spec/driver/remote_culerity_driver_spec.rb +26 -0
  30. data/spec/driver/remote_selenium_driver_spec.rb +18 -0
  31. data/spec/driver/selenium_driver_spec.rb +12 -0
  32. data/spec/drivers_spec.rb +139 -0
  33. data/spec/dsl/all_spec.rb +69 -0
  34. data/spec/dsl/attach_file_spec.rb +64 -0
  35. data/spec/dsl/check_spec.rb +39 -0
  36. data/spec/dsl/choose_spec.rb +26 -0
  37. data/spec/dsl/click_button_spec.rb +218 -0
  38. data/spec/dsl/click_link_spec.rb +108 -0
  39. data/spec/dsl/click_spec.rb +24 -0
  40. data/spec/dsl/current_url_spec.rb +8 -0
  41. data/spec/dsl/fill_in_spec.rb +91 -0
  42. data/spec/dsl/find_button_spec.rb +16 -0
  43. data/spec/dsl/find_by_id_spec.rb +16 -0
  44. data/spec/dsl/find_field_spec.rb +22 -0
  45. data/spec/dsl/find_link_spec.rb +17 -0
  46. data/spec/dsl/find_spec.rb +57 -0
  47. data/spec/dsl/has_button_spec.rb +32 -0
  48. data/spec/dsl/has_content_spec.rb +101 -0
  49. data/spec/dsl/has_css_spec.rb +107 -0
  50. data/spec/dsl/has_field_spec.rb +96 -0
  51. data/spec/dsl/has_link_spec.rb +33 -0
  52. data/spec/dsl/has_xpath_spec.rb +123 -0
  53. data/spec/dsl/locate_spec.rb +59 -0
  54. data/spec/dsl/select_spec.rb +71 -0
  55. data/spec/dsl/uncheck_spec.rb +21 -0
  56. data/spec/dsl/within_spec.rb +153 -0
  57. data/spec/dsl_spec.rb +140 -0
  58. data/spec/fixtures/capybara.jpg +0 -0
  59. data/spec/fixtures/test_file.txt +1 -0
  60. data/spec/public/jquery-ui.js +35 -0
  61. data/spec/public/jquery.js +19 -0
  62. data/spec/public/test.js +30 -0
  63. data/spec/save_and_open_page_spec.rb +43 -0
  64. data/spec/searchable_spec.rb +61 -0
  65. data/spec/server_spec.rb +47 -0
  66. data/spec/session/celerity_session_spec.rb +27 -0
  67. data/spec/session/culerity_session_spec.rb +25 -0
  68. data/spec/session/rack_test_session_spec.rb +25 -0
  69. data/spec/session/selenium_session_spec.rb +25 -0
  70. data/spec/session_spec.rb +77 -0
  71. data/spec/session_with_headers_support_spec.rb +13 -0
  72. data/spec/session_with_javascript_support_spec.rb +182 -0
  73. data/spec/session_without_headers_support_spec.rb +15 -0
  74. data/spec/session_without_javascript_support_spec.rb +15 -0
  75. data/spec/spec_helper.rb +27 -0
  76. data/spec/test_app.rb +71 -0
  77. data/spec/views/buttons.erb +4 -0
  78. data/spec/views/fieldsets.erb +29 -0
  79. data/spec/views/form.erb +226 -0
  80. data/spec/views/postback.erb +13 -0
  81. data/spec/views/tables.erb +122 -0
  82. data/spec/views/with_html.erb +38 -0
  83. data/spec/views/with_js.erb +34 -0
  84. data/spec/views/with_scope.erb +36 -0
  85. data/spec/views/with_simple_html.erb +1 -0
  86. data/spec/wait_until_spec.rb +28 -0
  87. data/spec/xpath_spec.rb +271 -0
  88. metadata +239 -0
@@ -0,0 +1,29 @@
1
+ require 'rubygems'
2
+
3
+ gem 'hoe', '>= 2.1.0'
4
+ require 'hoe'
5
+
6
+ Hoe.plugin :newgem
7
+
8
+ # Generate all the Rake tasks
9
+ # Run 'rake -T' to see list of generated tasks (from gem root directory)
10
+ Hoe.spec 'bjeanes-capybara' do
11
+ developer 'Jonas Nicklas', 'jonas.nicklas@gmail.com'
12
+
13
+ self.readme_file = 'README.rdoc'
14
+ self.extra_rdoc_files = Dir['*.rdoc']
15
+
16
+ self.extra_deps = [
17
+ ['nokogiri', '>= 1.3.3'],
18
+ ['mime-types', '>= 1.16'],
19
+ ['culerity', '>= 0.2.4'],
20
+ ['selenium-webdriver', '>= 0.0.3'],
21
+ ['rack', '>= 1.0.0'],
22
+ ['rack-test', '>= 0.5.2'],
23
+ ]
24
+
25
+ self.extra_dev_deps = [
26
+ ['sinatra', '>= 0.9.4'],
27
+ ['rspec', '>= 1.2.9']
28
+ ]
29
+ end
@@ -0,0 +1,6 @@
1
+ ## This is not needed for Thin > 1.0.0
2
+ ENV['RACK_ENV'] = "production"
3
+
4
+ require File.expand_path('spec/test_app', File.dirname(__FILE__))
5
+
6
+ run TestApp
@@ -0,0 +1,53 @@
1
+ require 'timeout'
2
+ require 'nokogiri'
3
+
4
+ module Capybara
5
+ VERSION = '0.3.1'
6
+
7
+ class CapybaraError < StandardError; end
8
+ class DriverNotFoundError < CapybaraError; end
9
+ class ElementNotFound < CapybaraError; end
10
+ class OptionNotFound < ElementNotFound; end
11
+ class UnselectNotAllowed < CapybaraError; end
12
+ class NotSupportedByDriverError < CapybaraError; end
13
+ class TimeoutError < CapybaraError; end
14
+ class LocateHiddenElementError < CapybaraError; end
15
+ class InfiniteRedirectError < TimeoutError; end
16
+
17
+ class << self
18
+ attr_accessor :debug, :asset_root, :app_host, :run_server
19
+ attr_accessor :default_selector, :default_wait_time, :ignore_hidden_elements
20
+
21
+ def default_selector
22
+ @default_selector ||= :xpath
23
+ end
24
+
25
+ def default_wait_time
26
+ @default_wait_time ||= 2
27
+ end
28
+
29
+ def log(message)
30
+ puts "[capybara] #{message}" if debug
31
+ true
32
+ end
33
+ end
34
+
35
+ autoload :Server, 'capybara/server'
36
+ autoload :Session, 'capybara/session'
37
+ autoload :Node, 'capybara/node'
38
+ autoload :XPath, 'capybara/xpath'
39
+ autoload :Searchable, 'capybara/searchable'
40
+
41
+ module Driver
42
+ autoload :Base, 'capybara/driver/base'
43
+ autoload :RackTest, 'capybara/driver/rack_test_driver'
44
+ autoload :Celerity, 'capybara/driver/celerity_driver'
45
+ autoload :Culerity, 'capybara/driver/culerity_driver'
46
+ autoload :Selenium, 'capybara/driver/selenium_driver'
47
+ end
48
+ end
49
+
50
+ Capybara.run_server = true
51
+ Capybara.default_selector = :xpath
52
+ Capybara.default_wait_time = 2
53
+ Capybara.ignore_hidden_elements = false
@@ -0,0 +1,32 @@
1
+ require 'capybara'
2
+ require 'capybara/dsl'
3
+
4
+ World(Capybara)
5
+
6
+ After do
7
+ Capybara.reset_sessions!
8
+ end
9
+
10
+ Before('@javascript') do
11
+ Capybara.current_driver = Capybara.javascript_driver
12
+ end
13
+
14
+ Before('@selenium') do
15
+ Capybara.current_driver = :selenium
16
+ end
17
+
18
+ Before('@celerity') do
19
+ Capybara.current_driver = :celerity
20
+ end
21
+
22
+ Before('@culerity') do
23
+ Capybara.current_driver = :culerity
24
+ end
25
+
26
+ Before('@rack_test') do
27
+ Capybara.current_driver = :rack_test
28
+ end
29
+
30
+ After do
31
+ Capybara.use_default_driver
32
+ end
@@ -0,0 +1,37 @@
1
+ class Capybara::Driver::Base
2
+ def current_url
3
+ raise NotImplementedError
4
+ end
5
+
6
+ def visit(path)
7
+ raise NotImplementedError
8
+ end
9
+
10
+ def find(query)
11
+ raise NotImplementedError
12
+ end
13
+
14
+ def evaluate_script(script)
15
+ raise Capybara::NotSupportedByDriverError
16
+ end
17
+
18
+ def wait?
19
+ false
20
+ end
21
+
22
+ def response_headers
23
+ raise Capybara::NotSupportedByDriverError
24
+ end
25
+
26
+ def body
27
+ raise NotImplementedError
28
+ end
29
+
30
+ def source
31
+ raise NotImplementedError
32
+ end
33
+
34
+ def cleanup!
35
+ end
36
+
37
+ end
@@ -0,0 +1,135 @@
1
+ class Capybara::Driver::Celerity < Capybara::Driver::Base
2
+ class Node < Capybara::Node
3
+ def text
4
+ node.text
5
+ end
6
+
7
+ def [](name)
8
+ value = if name.to_sym == :class
9
+ node.class_name
10
+ else
11
+ node.send(name.to_sym)
12
+ end
13
+ return value if value and not value.to_s.empty?
14
+ end
15
+
16
+ def value
17
+ if node.type == 'select-multiple'
18
+ node.selected_options
19
+ else
20
+ super
21
+ end
22
+ end
23
+
24
+ def set(value)
25
+ node.set(value)
26
+ end
27
+
28
+ def select(option)
29
+ node.select(option)
30
+ rescue
31
+ options = all(:xpath, "//option").map { |o| "'#{o.text}'" }.join(', ')
32
+ raise Capybara::OptionNotFound, "No such option '#{option}' in this select box. Available options: #{options}"
33
+ end
34
+
35
+ def unselect(option)
36
+ unless node.multiple?
37
+ raise Capybara::UnselectNotAllowed, "Cannot unselect option '#{option}' from single select box."
38
+ end
39
+
40
+ # FIXME: couldn't find a clean way to unselect, so clear and reselect
41
+ selected_options = node.selected_options
42
+ if unselect_option = selected_options.detect { |value| value == option } ||
43
+ selected_options.detect { |value| value.index(option) }
44
+ node.clear
45
+ (selected_options - [unselect_option]).each { |value| node.select_value(value) }
46
+ else
47
+ options = all(:xpath, "//option").map { |o| "'#{o.text}'" }.join(', ')
48
+ raise Capybara::OptionNotFound, "No such option '#{option}' in this select box. Available options: #{options}"
49
+ end
50
+ end
51
+
52
+ def click
53
+ node.click
54
+ end
55
+
56
+ def drag_to(element)
57
+ node.fire_event('mousedown')
58
+ element.node.fire_event('mousemove')
59
+ element.node.fire_event('mouseup')
60
+ end
61
+
62
+ def tag_name
63
+ # FIXME: this might be the dumbest way ever of getting the tag name
64
+ # there has to be something better...
65
+ node.to_xml[/^\s*<([a-z0-9\-\:]+)/, 1]
66
+ end
67
+
68
+ def visible?
69
+ node.visible?
70
+ end
71
+
72
+ def path
73
+ node.xpath
74
+ end
75
+
76
+ def trigger(event)
77
+ node.fire_event(event.to_s)
78
+ end
79
+
80
+ end
81
+
82
+ attr_reader :app, :rack_server
83
+
84
+ def initialize(app)
85
+ @app = app
86
+ @rack_server = Capybara::Server.new(@app)
87
+ @rack_server.boot if Capybara.run_server
88
+ end
89
+
90
+ def visit(path)
91
+ browser.goto(url(path))
92
+ end
93
+
94
+ def current_url
95
+ browser.url
96
+ end
97
+
98
+ def source
99
+ browser.html
100
+ end
101
+
102
+ def body
103
+ browser.document.as_xml
104
+ end
105
+
106
+ def response_headers
107
+ browser.response_headers
108
+ end
109
+
110
+ def find(selector)
111
+ browser.elements_by_xpath(selector).map { |node| Node.new(self, node) }
112
+ end
113
+
114
+ def wait?; true; end
115
+
116
+ def evaluate_script(script)
117
+ browser.execute_script "#{script}"
118
+ end
119
+
120
+ def browser
121
+ unless @_browser
122
+ require 'celerity'
123
+ @_browser = ::Celerity::Browser.new(:browser => :firefox, :log_level => :off)
124
+ end
125
+
126
+ @_browser
127
+ end
128
+
129
+ private
130
+
131
+ def url(path)
132
+ rack_server.url(path)
133
+ end
134
+
135
+ end
@@ -0,0 +1,25 @@
1
+ require 'culerity'
2
+
3
+ class Capybara::Driver::Culerity < Capybara::Driver::Celerity
4
+
5
+ def self.server
6
+ unless @_server
7
+ @_server = ::Culerity::run_server
8
+ at_exit do
9
+ @_server.close
10
+ end
11
+ end
12
+ @_server
13
+ end
14
+
15
+ def browser
16
+ unless @_browser
17
+ @_browser = ::Culerity::RemoteBrowserProxy.new self.class.server, {:browser => :firefox, :log_level => :off}
18
+ at_exit do
19
+ @_browser.exit
20
+ end
21
+ end
22
+ @_browser
23
+ end
24
+
25
+ end
@@ -0,0 +1,244 @@
1
+ require 'rack/test'
2
+ require 'mime/types'
3
+ require 'nokogiri'
4
+ require 'cgi'
5
+
6
+ class Capybara::Driver::RackTest < Capybara::Driver::Base
7
+ class Node < Capybara::Node
8
+ def text
9
+ node.text
10
+ end
11
+
12
+ def [](name)
13
+ attr_name = name.to_s
14
+ case
15
+ when 'select' == tag_name && 'value' == attr_name
16
+ if node['multiple'] == 'multiple'
17
+ node.xpath(".//option[@selected='selected']").map { |option| option.content }
18
+ else
19
+ option = node.xpath(".//option[@selected='selected']").first || node.xpath(".//option").first
20
+ option.content if option
21
+ end
22
+ when 'input' == tag_name && 'checkbox' == type && 'checked' == attr_name
23
+ node[attr_name] == 'checked' ? true : false
24
+ else
25
+ node[attr_name]
26
+ end
27
+ end
28
+
29
+
30
+ def set(value)
31
+ if tag_name == 'input' and %w(text password hidden file).include?(type)
32
+ node['value'] = value.to_s
33
+ elsif tag_name == 'input' and type == 'radio'
34
+ driver.html.xpath("//input[@name='#{self[:name]}']").each { |node| node.remove_attribute("checked") }
35
+ node['checked'] = 'checked'
36
+ elsif tag_name == 'input' and type == 'checkbox'
37
+ if value
38
+ node['checked'] = 'checked'
39
+ else
40
+ node.remove_attribute('checked')
41
+ end
42
+ elsif tag_name == "textarea"
43
+ node.content = value.to_s
44
+ end
45
+ end
46
+
47
+ def select(option)
48
+ if node['multiple'] != 'multiple'
49
+ node.xpath(".//option[@selected]").each { |node| node.remove_attribute("selected") }
50
+ end
51
+
52
+ if option_node = node.xpath(".//option[text()='#{option}']").first ||
53
+ node.xpath(".//option[contains(.,'#{option}')]").first
54
+ option_node["selected"] = 'selected'
55
+ else
56
+ options = node.xpath(".//option").map { |o| "'#{o.text}'" }.join(', ')
57
+ raise Capybara::OptionNotFound, "No such option '#{option}' in this select box. Available options: #{options}"
58
+ end
59
+ end
60
+
61
+ def unselect(option)
62
+ if node['multiple'] != 'multiple'
63
+ raise Capybara::UnselectNotAllowed, "Cannot unselect option '#{option}' from single select box."
64
+ end
65
+
66
+ if option_node = node.xpath(".//option[text()='#{option}']").first ||
67
+ node.xpath(".//option[contains(.,'#{option}')]").first
68
+ option_node.remove_attribute('selected')
69
+ else
70
+ options = node.xpath(".//option").map { |o| "'#{o.text}'" }.join(', ')
71
+ raise Capybara::OptionNotFound, "No such option '#{option}' in this select box. Available options: #{options}"
72
+ end
73
+ end
74
+
75
+ def click
76
+ if tag_name == 'a'
77
+ driver.visit(self[:href].to_s)
78
+ elsif (tag_name == 'input' or tag_name == 'button') and %w(submit image).include?(type)
79
+ Form.new(driver, form).submit(self)
80
+ end
81
+ end
82
+
83
+ def tag_name
84
+ node.node_name
85
+ end
86
+
87
+ def visible?
88
+ node.xpath("./ancestor-or-self::*[contains(@style, 'display:none') or contains(@style, 'display: none')]").size == 0
89
+ end
90
+
91
+ def path
92
+ node.path
93
+ end
94
+
95
+ private
96
+
97
+ def type
98
+ node[:type]
99
+ end
100
+
101
+ def form
102
+ node.ancestors('form').first
103
+ end
104
+ end
105
+
106
+ class Form < Node
107
+ def params(button)
108
+ params = {}
109
+
110
+ text_fields = %w[text hidden password url color tel email search].map{|f| "@type='#{f}'"}.join(' or ')
111
+
112
+ node.xpath(".//input[#{text_fields}]").map do |input|
113
+ merge_param!(params, input['name'].to_s, input['value'].to_s)
114
+ end
115
+ node.xpath(".//textarea").map do |textarea|
116
+ merge_param!(params, textarea['name'].to_s, textarea.text.to_s)
117
+ end
118
+ node.xpath(".//input[@type='radio' or @type='checkbox']").map do |input|
119
+ merge_param!(params, input['name'].to_s, input['value'].to_s) if input['checked']
120
+ end
121
+ node.xpath(".//select").map do |select|
122
+ if select['multiple'] == 'multiple'
123
+ options = select.xpath(".//option[@selected]")
124
+ options.each do |option|
125
+ merge_param!(params, select['name'].to_s, (option['value'] || option.text).to_s)
126
+ end
127
+ else
128
+ option = select.xpath(".//option[@selected]").first
129
+ option ||= select.xpath('.//option').first
130
+ merge_param!(params, select['name'].to_s, (option['value'] || option.text).to_s) if option
131
+ end
132
+ end
133
+ node.xpath(".//input[@type='file']").map do |input|
134
+ unless input['value'].to_s.empty?
135
+ if multipart?
136
+ content_type = MIME::Types.type_for(input['value'].to_s).first.to_s
137
+ file = Rack::Test::UploadedFile.new(input['value'].to_s, content_type)
138
+ merge_param!(params, input['name'].to_s, file)
139
+ else
140
+ merge_param!(params, input['name'].to_s, File.basename(input['value'].to_s))
141
+ end
142
+ end
143
+ end
144
+ merge_param!(params, button[:name], button[:value]) if button[:name]
145
+ params
146
+ end
147
+
148
+ def submit(button)
149
+ driver.submit(method, node['action'].to_s, params(button))
150
+ end
151
+
152
+ def multipart?
153
+ self[:enctype] == "multipart/form-data"
154
+ end
155
+
156
+ private
157
+
158
+ def method
159
+ self[:method] =~ /post/i ? :post : :get
160
+ end
161
+
162
+ def merge_param!(params, key, value)
163
+ collection = key.sub!(/\[\]$/, '')
164
+ if collection
165
+ if params[key]
166
+ params[key] << value
167
+ else
168
+ params[key] = [value]
169
+ end
170
+ else
171
+ params[key] = value
172
+ end
173
+ end
174
+ end
175
+
176
+ include ::Rack::Test::Methods
177
+ attr_reader :app, :html, :body
178
+
179
+ alias_method :response, :last_response
180
+ alias_method :request, :last_request
181
+ alias_method :source, :body
182
+
183
+ def initialize(app)
184
+ @app = app
185
+ end
186
+
187
+ def visit(path, attributes = {})
188
+ return if path.gsub(/^#{current_path}/, '') =~ /^#/
189
+ get(path, attributes, env)
190
+ follow_redirects!
191
+ cache_body
192
+ end
193
+
194
+ def current_url
195
+ request.url rescue ""
196
+ end
197
+
198
+ def response_headers
199
+ response.headers
200
+ end
201
+
202
+ def submit(method, path, attributes)
203
+ path = current_path if not path or path.empty?
204
+ send(method, path, attributes, env)
205
+ follow_redirects!
206
+ cache_body
207
+ end
208
+
209
+ def find(selector)
210
+ html.xpath(selector).map { |node| Node.new(self, node) }
211
+ end
212
+
213
+ private
214
+
215
+ def current_path
216
+ request.path rescue ""
217
+ end
218
+
219
+ def follow_redirects!
220
+ Capybara::WaitUntil.timeout(4) do
221
+ redirect = response.redirect?
222
+ follow_redirect! if redirect
223
+ not redirect
224
+ end
225
+ rescue Capybara::TimeoutError
226
+ raise Capybara::InfiniteRedirectError, "infinite redirect detected!"
227
+ end
228
+
229
+ def env
230
+ env = {}
231
+ begin
232
+ env["HTTP_REFERER"] = request.url
233
+ rescue Rack::Test::Error
234
+ # no request yet
235
+ end
236
+ env
237
+ end
238
+
239
+ def cache_body
240
+ @body = response.body
241
+ @html = Nokogiri::HTML(body)
242
+ end
243
+
244
+ end