capybara 0.4.1.2 → 1.0.0.beta1

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 (69) hide show
  1. data/History.txt +46 -0
  2. data/README.rdoc +211 -64
  3. data/lib/capybara.rb +31 -15
  4. data/lib/capybara/cucumber.rb +12 -16
  5. data/lib/capybara/dsl.rb +65 -28
  6. data/lib/capybara/node/actions.rb +7 -5
  7. data/lib/capybara/node/document.rb +8 -0
  8. data/lib/capybara/node/finders.rb +11 -7
  9. data/lib/capybara/node/matchers.rb +32 -6
  10. data/lib/capybara/node/simple.rb +20 -0
  11. data/lib/capybara/rack_test/browser.rb +115 -0
  12. data/lib/capybara/rack_test/driver.rb +77 -0
  13. data/lib/capybara/rack_test/form.rb +80 -0
  14. data/lib/capybara/rack_test/node.rb +101 -0
  15. data/lib/capybara/rspec.rb +11 -3
  16. data/lib/capybara/rspec/features.rb +22 -0
  17. data/lib/capybara/rspec/matchers.rb +146 -0
  18. data/lib/capybara/selector.rb +27 -8
  19. data/lib/capybara/selenium/driver.rb +148 -0
  20. data/lib/capybara/selenium/node.rb +91 -0
  21. data/lib/capybara/session.rb +42 -15
  22. data/lib/capybara/spec/driver.rb +55 -1
  23. data/lib/capybara/spec/fixtures/capybara.jpg +0 -0
  24. data/lib/capybara/spec/public/test.js +7 -2
  25. data/lib/capybara/spec/session.rb +51 -7
  26. data/lib/capybara/spec/session/attach_file_spec.rb +9 -6
  27. data/lib/capybara/spec/session/click_button_spec.rb +35 -0
  28. data/lib/capybara/spec/session/current_host_spec.rb +62 -0
  29. data/lib/capybara/spec/session/fill_in_spec.rb +6 -0
  30. data/lib/capybara/spec/session/find_spec.rb +23 -1
  31. data/lib/capybara/spec/session/first_spec.rb +39 -6
  32. data/lib/capybara/spec/session/has_css_spec.rb +30 -0
  33. data/lib/capybara/spec/session/has_field_spec.rb +47 -11
  34. data/lib/capybara/spec/session/javascript.rb +0 -1
  35. data/lib/capybara/spec/session/text_spec.rb +19 -0
  36. data/lib/capybara/spec/test_app.rb +9 -0
  37. data/lib/capybara/spec/views/form.erb +8 -3
  38. data/lib/capybara/spec/views/header_links.erb +7 -0
  39. data/lib/capybara/spec/views/host_links.erb +12 -0
  40. data/lib/capybara/spec/views/with_html.erb +6 -2
  41. data/lib/capybara/spec/views/with_html_entities.erb +1 -0
  42. data/lib/capybara/spec/views/with_js.erb +4 -0
  43. data/lib/capybara/util/save_and_open_page.rb +7 -3
  44. data/lib/capybara/util/timeout.rb +2 -2
  45. data/lib/capybara/version.rb +1 -1
  46. data/spec/capybara_spec.rb +1 -1
  47. data/spec/driver/rack_test_driver_spec.rb +24 -2
  48. data/spec/driver/selenium_driver_spec.rb +2 -1
  49. data/spec/dsl_spec.rb +56 -4
  50. data/spec/rspec/features_spec.rb +45 -0
  51. data/spec/rspec/matchers_spec.rb +451 -0
  52. data/spec/rspec_spec.rb +9 -2
  53. data/spec/save_and_open_page_spec.rb +9 -13
  54. data/spec/server_spec.rb +4 -0
  55. data/spec/session/rack_test_session_spec.rb +2 -2
  56. data/spec/session/selenium_session_spec.rb +1 -1
  57. data/spec/spec_helper.rb +0 -14
  58. data/spec/string_spec.rb +1 -1
  59. metadata +60 -69
  60. data/lib/capybara/driver/celerity_driver.rb +0 -164
  61. data/lib/capybara/driver/culerity_driver.rb +0 -26
  62. data/lib/capybara/driver/rack_test_driver.rb +0 -303
  63. data/lib/capybara/driver/selenium_driver.rb +0 -161
  64. data/spec/driver/celerity_driver_spec.rb +0 -13
  65. data/spec/driver/culerity_driver_spec.rb +0 -14
  66. data/spec/driver/remote_culerity_driver_spec.rb +0 -22
  67. data/spec/driver/remote_selenium_driver_spec.rb +0 -16
  68. data/spec/session/celerity_session_spec.rb +0 -24
  69. data/spec/session/culerity_session_spec.rb +0 -26
@@ -5,6 +5,7 @@ module Capybara
5
5
  class CapybaraError < StandardError; end
6
6
  class DriverNotFoundError < CapybaraError; end
7
7
  class ElementNotFound < CapybaraError; end
8
+ class FileNotFound < CapybaraError; end
8
9
  class UnselectNotAllowed < CapybaraError; end
9
10
  class NotSupportedByDriverError < CapybaraError; end
10
11
  class TimeoutError < CapybaraError; end
@@ -14,7 +15,7 @@ module Capybara
14
15
  class << self
15
16
  attr_accessor :asset_root, :app_host, :run_server, :default_host
16
17
  attr_accessor :server_port, :server_boot_timeout
17
- attr_accessor :default_selector, :default_wait_time, :ignore_hidden_elements
18
+ attr_accessor :default_selector, :default_wait_time, :ignore_hidden_elements, :prefer_visible_elements
18
19
  attr_accessor :save_and_open_page_path
19
20
 
20
21
  ##
@@ -34,6 +35,7 @@ module Capybara
34
35
  # [default_selector = :css/:xpath] Methods which take a selector use the given type by default (Default: CSS)
35
36
  # [default_wait_time = Integer] The number of seconds to wait for asynchronous processes to finish (Default: 2)
36
37
  # [ignore_hidden_elements = Boolean] Whether to ignore hidden elements on the page (Default: false)
38
+ # [prefer_visible_elements = Boolean] Whether to prefer visible elements over hidden elements (Default: true)
37
39
  #
38
40
  # === DSL Options
39
41
  #
@@ -198,10 +200,30 @@ module Capybara
198
200
  module Driver
199
201
  autoload :Base, 'capybara/driver/base'
200
202
  autoload :Node, 'capybara/driver/node'
201
- autoload :RackTest, 'capybara/driver/rack_test_driver'
202
- autoload :Celerity, 'capybara/driver/celerity_driver'
203
- autoload :Culerity, 'capybara/driver/culerity_driver'
204
- autoload :Selenium, 'capybara/driver/selenium_driver'
203
+
204
+ class Selenium
205
+ def initialize(*args)
206
+ raise "Capybara::Driver::Selenium has been renamed to Capybara::Selenium::Driver"
207
+ end
208
+ end
209
+
210
+ class RackTest
211
+ def initialize(*args)
212
+ raise "Capybara::Driver::RackTest has been renamed to Capybara::RackTest::Driver"
213
+ end
214
+ end
215
+ end
216
+
217
+ module RackTest
218
+ autoload :Driver, 'capybara/rack_test/driver'
219
+ autoload :Node, 'capybara/rack_test/node'
220
+ autoload :Form, 'capybara/rack_test/form'
221
+ autoload :Browser, 'capybara/rack_test/browser'
222
+ end
223
+
224
+ module Selenium
225
+ autoload :Node, 'capybara/selenium/node'
226
+ autoload :Driver, 'capybara/selenium/driver'
205
227
  end
206
228
  end
207
229
 
@@ -212,20 +234,14 @@ Capybara.configure do |config|
212
234
  config.default_selector = :css
213
235
  config.default_wait_time = 2
214
236
  config.ignore_hidden_elements = false
237
+ config.prefer_visible_elements = true
238
+ config.default_host = "http://www.example.com"
215
239
  end
216
240
 
217
241
  Capybara.register_driver :rack_test do |app|
218
- Capybara::Driver::RackTest.new(app)
219
- end
220
-
221
- Capybara.register_driver :celerity do |app|
222
- Capybara::Driver::Celerity.new(app)
223
- end
224
-
225
- Capybara.register_driver :culerity do |app|
226
- Capybara::Driver::Culerity.new(app)
242
+ Capybara::RackTest::Driver.new(app)
227
243
  end
228
244
 
229
245
  Capybara.register_driver :selenium do |app|
230
- Capybara::Driver::Selenium.new(app)
246
+ Capybara::Selenium::Driver.new(app)
231
247
  end
@@ -1,30 +1,26 @@
1
1
  require 'capybara'
2
+
2
3
  require 'capybara/dsl'
4
+ require 'capybara/rspec/matchers'
3
5
 
4
- World(Capybara)
6
+ World(Capybara::DSL)
7
+ World(Capybara::RSpecMatchers)
5
8
 
6
9
  After do
7
10
  Capybara.reset_sessions!
8
11
  end
9
12
 
10
- Before('@javascript') do
13
+ Before '@javascript' do
11
14
  Capybara.current_driver = Capybara.javascript_driver
12
15
  end
13
16
 
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
17
+ Before do |scenario|
18
+ scenario.source_tag_names.each do |tag|
19
+ driver_name = tag.sub(/^@/, '').to_sym
20
+ if Capybara.drivers.has_key?(driver_name)
21
+ Capybara.current_driver = driver_name
22
+ end
23
+ end
28
24
  end
29
25
 
30
26
  After do
@@ -1,8 +1,13 @@
1
1
  require 'capybara'
2
2
 
3
3
  module Capybara
4
+ def self.included(base)
5
+ base.send(:include, Capybara::DSL)
6
+ warn "`include Capybara` is deprecated please use `include Capybara::DSL` instead."
7
+ end
8
+
4
9
  class << self
5
- attr_writer :default_driver, :current_driver, :javascript_driver
10
+ attr_writer :default_driver, :current_driver, :javascript_driver, :session_name
6
11
 
7
12
  attr_accessor :app
8
13
 
@@ -57,7 +62,7 @@ module Capybara
57
62
  # @return [Capybara::Session] The currently used session
58
63
  #
59
64
  def current_session
60
- session_pool["#{current_driver}#{app.object_id}"] ||= Capybara::Session.new(current_driver, app)
65
+ session_pool["#{current_driver}:#{session_name}:#{app.object_id}"] ||= Capybara::Session.new(current_driver, app)
61
66
  end
62
67
 
63
68
  ##
@@ -70,6 +75,27 @@ module Capybara
70
75
  end
71
76
  alias_method :reset!, :reset_sessions!
72
77
 
78
+ ##
79
+ #
80
+ # The current session name.
81
+ #
82
+ # @return [Symbol] The name of the currently used session.
83
+ #
84
+ def session_name
85
+ @session_name ||= :default
86
+ end
87
+
88
+ ##
89
+ #
90
+ # Yield a block using a specific session name.
91
+ #
92
+ def using_session(name)
93
+ self.session_name = name
94
+ yield
95
+ ensure
96
+ self.session_name = :default
97
+ end
98
+
73
99
  private
74
100
 
75
101
  def session_pool
@@ -77,33 +103,44 @@ module Capybara
77
103
  end
78
104
  end
79
105
 
80
- extend(self)
81
-
82
- ##
83
- #
84
- # Shortcut to accessing the current session. This is useful when Capybara is included in a
85
- # class or module.
86
- #
87
- # class MyClass
88
- # include Capybara
89
- #
90
- # def has_header?
91
- # page.has_css?('h1')
92
- # end
93
- # end
94
- #
95
- # @return [Capybara::Session] The current session object
96
- #
97
- def page
98
- Capybara.current_session
99
- end
106
+ module DSL
107
+
108
+ ##
109
+ #
110
+ # Shortcut to working in a different session. This is useful when Capybara is included
111
+ # in a class or module.
112
+ #
113
+ def using_session(name, &block)
114
+ Capybara.using_session(name, &block)
115
+ end
100
116
 
101
- Session::DSL_METHODS.each do |method|
102
- class_eval <<-RUBY, __FILE__, __LINE__+1
103
- def #{method}(*args, &block)
104
- page.#{method}(*args, &block)
105
- end
106
- RUBY
117
+ ##
118
+ #
119
+ # Shortcut to accessing the current session. This is useful when Capybara is included in a
120
+ # class or module.
121
+ #
122
+ # class MyClass
123
+ # include Capybara::DSL
124
+ #
125
+ # def has_header?
126
+ # page.has_css?('h1')
127
+ # end
128
+ # end
129
+ #
130
+ # @return [Capybara::Session] The current session object
131
+ #
132
+ def page
133
+ Capybara.current_session
134
+ end
135
+
136
+ Session::DSL_METHODS.each do |method|
137
+ class_eval <<-RUBY, __FILE__, __LINE__+1
138
+ def #{method}(*args, &block)
139
+ page.#{method}(*args, &block)
140
+ end
141
+ RUBY
142
+ end
107
143
  end
108
144
 
145
+ extend(Capybara::DSL)
109
146
  end
@@ -104,7 +104,7 @@ module Capybara
104
104
  #
105
105
  # page.select 'March', :from => 'Month'
106
106
  #
107
- # @param [String] locator Which check box to uncheck
107
+ # @param [String] value Which option to select
108
108
  # @param [Hash{:from => String}] The id, name or label of the select box
109
109
  #
110
110
  def select(value, options={})
@@ -121,13 +121,14 @@ module Capybara
121
121
 
122
122
  ##
123
123
  #
124
- # Find a select box on the page and select a particular option from it. If the select
125
- # box is a multiple select, +select+ can be called multiple times to select more than
124
+ # Find a select box on the page and unselect a particular option from it. If the select
125
+ # box is a multiple select, +unselect+ can be called multiple times to unselect more than
126
126
  # one option. The select box can be found via its name, id or label text.
127
127
  #
128
- # page.uncheck('German')
128
+ # page.unselect 'March', :from => 'Month'
129
129
  #
130
- # @param [String] locator Which check box to uncheck
130
+ # @param [String] value Which option to unselect
131
+ # @param [Hash{:from => String}] The id, name or label of the select box
131
132
  #
132
133
  def unselect(value, options={})
133
134
  if options.has_key?(:from)
@@ -152,6 +153,7 @@ module Capybara
152
153
  # @param [String] path The path of the file that will be attached
153
154
  #
154
155
  def attach_file(locator, path)
156
+ raise Capybara::FileNotFound, "cannot attach file, #{path} does not exist" unless File.exist?(path.to_s)
155
157
  msg = "cannot attach file, no file field with id, name, or label '#{locator}' found"
156
158
  find(:xpath, XPath::HTML.file_field(locator), :message => msg).set(path)
157
159
  end
@@ -12,6 +12,14 @@ module Capybara
12
12
  def inspect
13
13
  %(#<Capybara::Document>)
14
14
  end
15
+
16
+ ##
17
+ #
18
+ # @return [String] The text of the document
19
+ #
20
+ def text
21
+ find(:xpath, '/html').text
22
+ end
15
23
  end
16
24
  end
17
25
  end
@@ -29,8 +29,11 @@ module Capybara
29
29
  rescue TimeoutError
30
30
  end
31
31
  unless node
32
- options = if args.last.is_a?(Hash) then args.last else {} end
33
- raise Capybara::ElementNotFound, options[:message] || "Unable to find '#{args[1] || args[0]}'"
32
+ options = extract_normalized_options(args)
33
+ normalized = Capybara::Selector.normalize(*args)
34
+ message = options[:message] || "Unable to find #{normalized.name} #{normalized.locator.inspect}"
35
+ message = normalized.failure_message.call(self, normalized) if normalized.failure_message
36
+ raise Capybara::ElementNotFound, message
34
37
  end
35
38
  return node
36
39
  end
@@ -117,7 +120,7 @@ module Capybara
117
120
  def all(*args)
118
121
  options = extract_normalized_options(args)
119
122
 
120
- Capybara::Selector.normalize(*args).
123
+ Capybara::Selector.normalize(*args).xpaths.
121
124
  map { |path| find_in_base(path) }.flatten.
122
125
  select { |node| matches_options(node, options) }.
123
126
  map { |node| convert_element(node) }
@@ -138,16 +141,17 @@ module Capybara
138
141
  #
139
142
  def first(*args)
140
143
  options = extract_normalized_options(args)
144
+ found_elements = []
141
145
 
142
- Capybara::Selector.normalize(*args).each do |path|
146
+ Capybara::Selector.normalize(*args).xpaths.each do |path|
143
147
  find_in_base(path).each do |node|
144
148
  if matches_options(node, options)
145
- return convert_element(node)
149
+ found_elements << convert_element(node)
150
+ return found_elements.last if not Capybara.prefer_visible_elements or node.visible?
146
151
  end
147
152
  end
148
153
  end
149
-
150
- nil
154
+ found_elements.first
151
155
  end
152
156
 
153
157
  protected
@@ -43,11 +43,11 @@ module Capybara
43
43
  when options[:between]
44
44
  options[:between] === results.size
45
45
  when options[:count]
46
- options[:count] == results.size
46
+ options[:count].to_i == results.size
47
47
  when options[:maximum]
48
- options[:maximum] >= results.size
48
+ options[:maximum].to_i >= results.size
49
49
  when options[:minimum]
50
- options[:minimum] <= results.size
50
+ options[:minimum].to_i <= results.size
51
51
  else
52
52
  results.size > 0
53
53
  end
@@ -75,11 +75,11 @@ module Capybara
75
75
  when options[:between]
76
76
  not(options[:between] === results.size)
77
77
  when options[:count]
78
- not(options[:count] == results.size)
78
+ not(options[:count].to_i == results.size)
79
79
  when options[:maximum]
80
- not(options[:maximum] >= results.size)
80
+ not(options[:maximum].to_i >= results.size)
81
81
  when options[:minimum]
82
- not(options[:minimum] <= results.size)
82
+ not(options[:minimum].to_i <= results.size)
83
83
  else
84
84
  results.empty?
85
85
  end
@@ -293,6 +293,19 @@ module Capybara
293
293
  has_xpath?(XPath::HTML.field(locator), :checked => true)
294
294
  end
295
295
 
296
+ ##
297
+ #
298
+ # Checks if the page or current node has no radio button or
299
+ # checkbox with the given label, value or id, that is currently
300
+ # checked.
301
+ #
302
+ # @param [String] locator The label, name or id of a checked field
303
+ # @return [Boolean] Whether it doesn't exists
304
+ #
305
+ def has_no_checked_field?(locator)
306
+ has_no_xpath?(XPath::HTML.field(locator), :checked => true)
307
+ end
308
+
296
309
  ##
297
310
  #
298
311
  # Checks if the page or current node has a radio button or
@@ -306,6 +319,19 @@ module Capybara
306
319
  has_xpath?(XPath::HTML.field(locator), :unchecked => true)
307
320
  end
308
321
 
322
+ ##
323
+ #
324
+ # Checks if the page or current node has no radio button or
325
+ # checkbox with the given label, value or id, that is currently
326
+ # unchecked.
327
+ #
328
+ # @param [String] locator The label, name or id of an unchecked field
329
+ # @return [Boolean] Whether it doesn't exists
330
+ #
331
+ def has_no_unchecked_field?(locator)
332
+ has_no_xpath?(XPath::HTML.field(locator), :unchecked => true)
333
+ end
334
+
309
335
  ##
310
336
  #
311
337
  # Checks if the page or current node has a select field with the
@@ -98,6 +98,26 @@ module Capybara
98
98
  native.xpath("./ancestor-or-self::*[contains(@style, 'display:none') or contains(@style, 'display: none')]").size == 0
99
99
  end
100
100
 
101
+ ##
102
+ #
103
+ # Whether or not the element is checked.
104
+ #
105
+ # @return [Boolean] Whether the element is checked
106
+ #
107
+ def checked?
108
+ native[:checked]
109
+ end
110
+
111
+ ##
112
+ #
113
+ # Whether or not the element is selected.
114
+ #
115
+ # @return [Boolean] Whether the element is selected
116
+ #
117
+ def selected?
118
+ native[:selected]
119
+ end
120
+
101
121
  protected
102
122
 
103
123
  def find_in_base(xpath)
@@ -0,0 +1,115 @@
1
+ class Capybara::RackTest::Browser
2
+ include ::Rack::Test::Methods
3
+
4
+ attr_reader :app, :options
5
+ attr_accessor :current_host
6
+
7
+ def initialize(app, options={})
8
+ @app = app
9
+ @options = options
10
+ end
11
+
12
+ def visit(path, attributes = {})
13
+ reset_host!
14
+ process(:get, path, attributes)
15
+ end
16
+
17
+ def submit(method, path, attributes)
18
+ path = request_path if not path or path.empty?
19
+ process(method, path, attributes)
20
+ end
21
+
22
+ def follow(method, path, attributes = {})
23
+ return if path.gsub(/^#{request_path}/, '').start_with?('#')
24
+ process(method, path, attributes)
25
+ end
26
+
27
+ def follow_redirects!
28
+ 5.times do
29
+ follow_redirect! if last_response.redirect?
30
+ end
31
+ raise Capybara::InfiniteRedirectError, "redirected more than 5 times, check for infinite redirects." if last_response.redirect?
32
+ end
33
+
34
+ def process(method, path, attributes = {})
35
+ new_uri = URI.parse(path)
36
+ current_uri = URI.parse(current_url)
37
+
38
+ path = request_path + path if path.start_with?('?')
39
+ path = current_host + path if path.start_with?('/')
40
+
41
+ if new_uri.host
42
+ @current_host = new_uri.scheme + '://' + new_uri.host
43
+ end
44
+
45
+ reset_cache!
46
+ send(method, to_binary(path), to_binary( attributes ), env)
47
+ follow_redirects!
48
+ end
49
+
50
+ def current_url
51
+ last_request.url
52
+ rescue Rack::Test::Error
53
+ ""
54
+ end
55
+
56
+ def reset_host!
57
+ @current_host = (Capybara.app_host || Capybara.default_host)
58
+ end
59
+
60
+ def reset_cache!
61
+ @dom = nil
62
+ end
63
+
64
+ def body
65
+ dom.to_xml
66
+ end
67
+
68
+ def dom
69
+ @dom ||= Nokogiri::HTML(source)
70
+ end
71
+
72
+ def find(selector)
73
+ dom.xpath(selector).map { |node| Capybara::RackTest::Node.new(self, node) }
74
+ end
75
+
76
+ def source
77
+ last_response.body
78
+ rescue Rack::Test::Error
79
+ nil
80
+ end
81
+
82
+ protected
83
+
84
+ def to_binary(object)
85
+ return object unless Kernel.const_defined?(:Encoding)
86
+
87
+ if object.respond_to?(:force_encoding)
88
+ object.dup.force_encoding(Encoding::ASCII_8BIT)
89
+ elsif object.respond_to?(:each_pair) #Hash
90
+ {}.tap { |x| object.each_pair {|k,v| x[to_binary(k)] = to_binary(v) } }
91
+ elsif object.respond_to?(:each) #Array
92
+ object.map{|x| to_binary(x)}
93
+ else
94
+ object
95
+ end
96
+ end
97
+
98
+ def request_path
99
+ last_request.path
100
+ rescue Rack::Test::Error
101
+ ""
102
+ end
103
+
104
+ def env
105
+ env = {}
106
+ begin
107
+ env["HTTP_REFERER"] = last_request.url
108
+ rescue Rack::Test::Error
109
+ # no request yet
110
+ end
111
+ env.merge!(options[:headers]) if options[:headers]
112
+ env
113
+ end
114
+
115
+ end