capybara 0.4.1.2 → 1.0.0.beta1

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