bjeanes-capybara 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
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