capybara 1.0.1 → 1.1.0.rc1

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 (39) hide show
  1. data/History.txt +25 -0
  2. data/README.rdoc +18 -3
  3. data/lib/capybara.rb +6 -1
  4. data/lib/capybara/driver/base.rb +4 -0
  5. data/lib/capybara/dsl.rb +23 -1
  6. data/lib/capybara/node/base.rb +19 -3
  7. data/lib/capybara/node/element.rb +39 -16
  8. data/lib/capybara/node/finders.rb +21 -29
  9. data/lib/capybara/node/matchers.rb +6 -6
  10. data/lib/capybara/node/simple.rb +3 -7
  11. data/lib/capybara/rack_test/browser.rb +21 -13
  12. data/lib/capybara/rack_test/driver.rb +1 -1
  13. data/lib/capybara/rack_test/node.rb +2 -1
  14. data/lib/capybara/selenium/driver.rb +8 -1
  15. data/lib/capybara/session.rb +16 -3
  16. data/lib/capybara/spec/driver.rb +5 -5
  17. data/lib/capybara/spec/public/test.js +5 -0
  18. data/lib/capybara/spec/session/click_button_spec.rb +1 -1
  19. data/lib/capybara/spec/session/click_link_spec.rb +6 -0
  20. data/lib/capybara/spec/session/current_host_spec.rb +6 -0
  21. data/lib/capybara/spec/session/find_spec.rb +6 -0
  22. data/lib/capybara/spec/session/javascript.rb +67 -0
  23. data/lib/capybara/spec/session/within_spec.rb +11 -0
  24. data/lib/capybara/spec/test_app.rb +13 -1
  25. data/lib/capybara/spec/views/form.erb +1 -1
  26. data/lib/capybara/spec/views/with_html.erb +1 -0
  27. data/lib/capybara/spec/views/with_js.erb +7 -2
  28. data/lib/capybara/version.rb +1 -1
  29. data/spec/driver/rack_test_driver_spec.rb +6 -0
  30. data/spec/driver/selenium_driver_spec.rb +21 -0
  31. data/spec/dsl_spec.rb +44 -0
  32. data/spec/fixtures/selenium_driver_rspec_failure.rb +8 -0
  33. data/spec/fixtures/selenium_driver_rspec_success.rb +8 -0
  34. data/spec/rspec_spec.rb +0 -1
  35. data/spec/session/rack_test_session_spec.rb +12 -0
  36. data/spec/spec_helper.rb +3 -0
  37. metadata +114 -173
  38. data/lib/capybara/spec/public/jquery-ui.js +0 -35
  39. data/lib/capybara/spec/public/jquery.js +0 -19
@@ -1,3 +1,28 @@
1
+ # Version 1.1.0
2
+
3
+ Release date:
4
+
5
+ ### Fixed
6
+
7
+ * Sensible inspect for Capybara::Session [Jo Liss]
8
+ * Fix headers and host on redirect [Matt Colyer, Jonas Nicklas, Kim Burgestrand]
9
+ * using_driver now restores the old driver instead of reverting to the default [Carol Nichols]
10
+ * Errors when following links relative to the root path under rack-test [Jonas Nicklas, Kim Burgestrand]
11
+ * Make sure exit codes are propagated properly [Edgar Beigarts]
12
+
13
+ ### Changed
14
+
15
+ * resynchronization is off by default under Selenium
16
+
17
+ ### Added
18
+
19
+ * Elements are automatically reloaded (including parents) during wait [Jonas Nicklas]
20
+ * Rescue driver specific element errors, such as the dreaded ObsoleteElementError and retry [Jonas Nicklas]
21
+ * Raise an error if something has frozen time [Jonas Nicklas]
22
+ * Allow within to take a node instead of a selector [Peter Williams]
23
+ * Using wait_time_time to change wait time for a block of code [Jonas Nicklas, Kim Burgestrand]
24
+ * Option for rack-test driver to disable data-method hack [Jonas Nicklas, Kim Burgestrand]
25
+
1
26
  # Version 1.0.1
2
27
 
3
28
  Release date: 2011-08-12
@@ -8,7 +8,7 @@ Capybara aims to simplify the process of integration testing Rack applications,
8
8
  such as Rails, Sinatra or Merb. Capybara simulates how a real user would
9
9
  interact with a web application. It is agnostic about the driver running your
10
10
  tests and currently comes with Rack::Test and Selenium support built in.
11
- HtmlUnit and env.js are supported through external gems.
11
+ HtmlUnit, WebKit and env.js are supported through external gems.
12
12
 
13
13
  A complete reference is available at
14
14
  {at rubydoc.info}[http://rubydoc.info/github/jnicklas/capybara/master].
@@ -36,7 +36,7 @@ create a topic branch for every separate change you make.
36
36
  Capybara uses bundler in development. To set up a development environment, simply do:
37
37
 
38
38
  git submodule update --init
39
- gem install bundler --pre
39
+ gem install bundler
40
40
  bundle install
41
41
 
42
42
  == Using Capybara with Cucumber
@@ -282,6 +282,21 @@ Ruby 1.8.7 at this time.
282
282
  Note: Envjs does not support transactional fixtures; see the section
283
283
  "Transactional Fixtures" below.
284
284
 
285
+ === Capybara-webkit
286
+
287
+ The {capybara-webkit drive}[https://github.com/thoughtbot/capybara-webkit] is for true headless
288
+ testing. It uses WebKitQt to start a rendering engine process. It can execute JavaScript as well.
289
+ It is significantly faster than drivers like Selenium since it does not load an entire browser.
290
+
291
+ You can install it with:
292
+
293
+ gem install capybara-webkit
294
+
295
+ And you can use it by:
296
+
297
+ Capybara.javascript_driver = :webkit
298
+
299
+
285
300
  == The DSL
286
301
 
287
302
  Capybara's DSL (domain-specific language) is inspired by Webrat. While
@@ -470,7 +485,7 @@ When issuing instructions to the DSL such as:
470
485
 
471
486
  If clicking on the *foo* link causes triggers an asynchronous process, such as
472
487
  an Ajax request, which, when complete will add the *bar* link to the page,
473
- clicking on the *bar* link would be expeced to fail, since that link doesn't
488
+ clicking on the *bar* link would be expected to fail, since that link doesn't
474
489
  exist yet. However Capybara is smart enought to retry finding the link for a
475
490
  brief period of time before giving up and throwing an error. The same is true of
476
491
  the next line, which looks for the content *baz* on the page; it will retry
@@ -4,7 +4,9 @@ require 'xpath'
4
4
  module Capybara
5
5
  class CapybaraError < StandardError; end
6
6
  class DriverNotFoundError < CapybaraError; end
7
+ class FrozenInTime < CapybaraError; end
7
8
  class ElementNotFound < CapybaraError; end
9
+ class ExpectationNotMet < ElementNotFound; end
8
10
  class FileNotFound < CapybaraError; end
9
11
  class UnselectNotAllowed < CapybaraError; end
10
12
  class NotSupportedByDriverError < CapybaraError; end
@@ -16,7 +18,7 @@ module Capybara
16
18
  attr_accessor :asset_root, :app_host, :run_server, :default_host
17
19
  attr_accessor :server_port, :server_boot_timeout
18
20
  attr_accessor :default_selector, :default_wait_time, :ignore_hidden_elements, :prefer_visible_elements
19
- attr_accessor :save_and_open_page_path
21
+ attr_accessor :save_and_open_page_path, :automatic_reload
20
22
 
21
23
  ##
22
24
  #
@@ -36,6 +38,8 @@ module Capybara
36
38
  # [default_wait_time = Integer] The number of seconds to wait for asynchronous processes to finish (Default: 2)
37
39
  # [ignore_hidden_elements = Boolean] Whether to ignore hidden elements on the page (Default: false)
38
40
  # [prefer_visible_elements = Boolean] Whether to prefer visible elements over hidden elements (Default: true)
41
+ # [automatic_reload = Boolean] Whether to automatically reload elements as Capybara is waiting (Default: true)
42
+ # [save_and_open_page_path = String] Where to put pages saved through save_and_open_page (Default: Dir.pwd)
39
43
  #
40
44
  # === DSL Options
41
45
  #
@@ -236,6 +240,7 @@ Capybara.configure do |config|
236
240
  config.ignore_hidden_elements = false
237
241
  config.prefer_visible_elements = true
238
242
  config.default_host = "http://www.example.com"
243
+ config.automatic_reload = true
239
244
  end
240
245
 
241
246
  Capybara.register_driver :rack_test do |app|
@@ -43,6 +43,10 @@ class Capybara::Driver::Base
43
43
  raise Capybara::NotSupportedByDriverError
44
44
  end
45
45
 
46
+ def invalid_element_errors
47
+ []
48
+ end
49
+
46
50
  def wait?
47
51
  false
48
52
  end
@@ -49,10 +49,23 @@ module Capybara
49
49
  # Yield a block using a specific driver
50
50
  #
51
51
  def using_driver(driver)
52
+ previous_driver = Capybara.current_driver
52
53
  Capybara.current_driver = driver
53
54
  yield
54
55
  ensure
55
- Capybara.use_default_driver
56
+ @current_driver = previous_driver
57
+ end
58
+
59
+ ##
60
+ #
61
+ # Yield a block using a specific wait time
62
+ #
63
+ def using_wait_time(seconds)
64
+ previous_wait_time = Capybara.default_wait_time
65
+ Capybara.default_wait_time = seconds
66
+ yield
67
+ ensure
68
+ Capybara.default_wait_time = previous_wait_time
56
69
  end
57
70
 
58
71
  ##
@@ -114,6 +127,15 @@ module Capybara
114
127
  Capybara.using_session(name, &block)
115
128
  end
116
129
 
130
+ ##
131
+ #
132
+ # Shortcut to working in a different session. This is useful when Capybara is included
133
+ # in a class or module.
134
+ #
135
+ def using_wait_time(seconds, &block)
136
+ Capybara.using_wait_time(seconds, &block)
137
+ end
138
+
117
139
  ##
118
140
  #
119
141
  # Shortcut to accessing the current session. This is useful when Capybara is included in a
@@ -22,7 +22,7 @@ module Capybara
22
22
  # session.has_css?('#foobar') # from Capybara::Node::Matchers
23
23
  #
24
24
  class Base
25
- attr_reader :session, :base
25
+ attr_reader :session, :base, :parent
26
26
 
27
27
  include Capybara::Node::Finders
28
28
  include Capybara::Node::Actions
@@ -33,10 +33,26 @@ module Capybara
33
33
  @base = base
34
34
  end
35
35
 
36
+ def reload
37
+ self
38
+ end
39
+
36
40
  protected
37
41
 
38
- def wait?
39
- driver.wait?
42
+ def wait_until(seconds=Capybara.default_wait_time)
43
+ start_time = Time.now
44
+
45
+ begin
46
+ yield
47
+ rescue => e
48
+ raise e unless driver.wait?
49
+ raise e unless (driver.respond_to?(:invalid_element_errors) and driver.invalid_element_errors.include?(e.class)) or e.is_a?(Capybara::ElementNotFound)
50
+ raise e if (Time.now - start_time) >= seconds
51
+ sleep(0.05)
52
+ raise Capybara::FrozenInTime, "time appears to be frozen, Capybara does not work with libraries which freeze time, consider using time travelling instead" if Time.now == start_time
53
+ reload if Capybara.automatic_reload
54
+ retry
55
+ end
40
56
  end
41
57
 
42
58
  def driver
@@ -22,12 +22,18 @@ module Capybara
22
22
  #
23
23
  class Element < Base
24
24
 
25
+ def initialize(session, base, parent, selector)
26
+ super(session, base)
27
+ @parent = parent
28
+ @selector = selector
29
+ end
30
+
25
31
  ##
26
32
  #
27
33
  # @return [Object] The native element from the driver, this allows access to driver specific methods
28
34
  #
29
35
  def native
30
- base.native
36
+ wait_until { base.native }
31
37
  end
32
38
 
33
39
  ##
@@ -35,7 +41,7 @@ module Capybara
35
41
  # @return [String] The text of the element
36
42
  #
37
43
  def text
38
- base.text
44
+ wait_until { base.text }
39
45
  end
40
46
 
41
47
  ##
@@ -48,7 +54,7 @@ module Capybara
48
54
  # @return [String] The value of the attribute
49
55
  #
50
56
  def [](attribute)
51
- base[attribute]
57
+ wait_until { base[attribute] }
52
58
  end
53
59
 
54
60
  ##
@@ -56,7 +62,7 @@ module Capybara
56
62
  # @return [String] The value of the form element
57
63
  #
58
64
  def value
59
- base.value
65
+ wait_until { base.value }
60
66
  end
61
67
 
62
68
  ##
@@ -66,7 +72,7 @@ module Capybara
66
72
  # @param [String] value The new value
67
73
  #
68
74
  def set(value)
69
- base.set(value)
75
+ wait_until { base.set(value) }
70
76
  end
71
77
 
72
78
  ##
@@ -74,7 +80,7 @@ module Capybara
74
80
  # Select this node if is an option element inside a select tag
75
81
  #
76
82
  def select_option
77
- base.select_option
83
+ wait_until { base.select_option }
78
84
  end
79
85
 
80
86
  ##
@@ -82,7 +88,7 @@ module Capybara
82
88
  # Unselect this node if is an option element inside a multiple select tag
83
89
  #
84
90
  def unselect_option
85
- base.unselect_option
91
+ wait_until { base.unselect_option }
86
92
  end
87
93
 
88
94
  ##
@@ -90,7 +96,7 @@ module Capybara
90
96
  # Click the Element
91
97
  #
92
98
  def click
93
- base.click
99
+ wait_until { base.click }
94
100
  end
95
101
 
96
102
  ##
@@ -98,7 +104,7 @@ module Capybara
98
104
  # @return [String] The tag name of the element
99
105
  #
100
106
  def tag_name
101
- base.tag_name
107
+ wait_until { base.tag_name }
102
108
  end
103
109
 
104
110
  ##
@@ -109,7 +115,7 @@ module Capybara
109
115
  # @return [Boolean] Whether the element is visible
110
116
  #
111
117
  def visible?
112
- base.visible?
118
+ wait_until { base.visible? }
113
119
  end
114
120
 
115
121
  ##
@@ -119,7 +125,7 @@ module Capybara
119
125
  # @return [Boolean] Whether the element is checked
120
126
  #
121
127
  def checked?
122
- base.checked?
128
+ wait_until { base.checked? }
123
129
  end
124
130
 
125
131
  ##
@@ -129,7 +135,7 @@ module Capybara
129
135
  # @return [Boolean] Whether the element is selected
130
136
  #
131
137
  def selected?
132
- base.selected?
138
+ wait_until { base.selected? }
133
139
  end
134
140
 
135
141
  ##
@@ -139,7 +145,7 @@ module Capybara
139
145
  # @return [String] An XPath expression
140
146
  #
141
147
  def path
142
- base.path
148
+ wait_until { base.path }
143
149
  end
144
150
 
145
151
  ##
@@ -150,7 +156,7 @@ module Capybara
150
156
  # @param [String] event The name of the event to trigger
151
157
  #
152
158
  def trigger(event)
153
- base.trigger(event)
159
+ wait_until { base.trigger(event) }
154
160
  end
155
161
 
156
162
  ##
@@ -164,7 +170,25 @@ module Capybara
164
170
  # @param [Capybara::Element] node The element to drag to
165
171
  #
166
172
  def drag_to(node)
167
- base.drag_to(node.base)
173
+ wait_until { base.drag_to(node.base) }
174
+ end
175
+
176
+ def find(*args)
177
+ wait_until { super }
178
+ end
179
+
180
+ def first(*args)
181
+ wait_until { super }
182
+ end
183
+
184
+ def all(*args)
185
+ wait_until { super }
186
+ end
187
+
188
+ def reload
189
+ reloaded = parent.reload.first(@selector.name, @selector.locator, @selector.options)
190
+ @base = reloaded.base if reloaded
191
+ self
168
192
  end
169
193
 
170
194
  def inspect
@@ -172,7 +196,6 @@ module Capybara
172
196
  rescue NotSupportedByDriverError
173
197
  %(#<Capybara::Element tag="#{tag_name}">)
174
198
  end
175
-
176
199
  end
177
200
  end
178
201
  end
@@ -15,7 +15,7 @@ module Capybara
15
15
  # +find+ takes the same options as +all+.
16
16
  #
17
17
  # page.find('#foo').find('.bar')
18
- # page.find(:xpath, '//div[contains("bar")]')
18
+ # page.find(:xpath, '//div[contains(., "bar")]')
19
19
  # page.find('li', :text => 'Quox').click_link('Delete')
20
20
  #
21
21
  # @param (see Capybara::Node::Finders#all)
@@ -24,18 +24,7 @@ module Capybara
24
24
  # @raise [Capybara::ElementNotFound] If the element can't be found before time expires
25
25
  #
26
26
  def find(*args)
27
- begin
28
- node = wait_conditionally_until { first(*args) }
29
- rescue TimeoutError
30
- end
31
- unless node
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
37
- end
38
- return node
27
+ wait_until { first(*args) or raise_find_error(*args) }
39
28
  end
40
29
 
41
30
  ##
@@ -120,10 +109,10 @@ module Capybara
120
109
  def all(*args)
121
110
  options = extract_normalized_options(args)
122
111
 
123
- Capybara::Selector.normalize(*args).xpaths.
124
- map { |path| find_in_base(path) }.flatten.
125
- select { |node| matches_options(node, options) }.
126
- map { |node| convert_element(node) }
112
+ selector = Capybara::Selector.normalize(*args)
113
+ selector.xpaths.
114
+ map { |path| find_in_base(selector, path) }.flatten.
115
+ select { |node| matches_options(node, options) }
127
116
  end
128
117
 
129
118
  ##
@@ -143,10 +132,11 @@ module Capybara
143
132
  options = extract_normalized_options(args)
144
133
  found_elements = []
145
134
 
146
- Capybara::Selector.normalize(*args).xpaths.each do |path|
147
- find_in_base(path).each do |node|
135
+ selector = Capybara::Selector.normalize(*args)
136
+ selector.xpaths.each do |path|
137
+ find_in_base(selector, path).each do |node|
148
138
  if matches_options(node, options)
149
- found_elements << convert_element(node)
139
+ found_elements << node
150
140
  return found_elements.last if not Capybara.prefer_visible_elements or node.visible?
151
141
  end
152
142
  end
@@ -156,16 +146,18 @@ module Capybara
156
146
 
157
147
  protected
158
148
 
159
- def find_in_base(xpath)
160
- base.find(xpath)
161
- end
162
-
163
- def convert_element(element)
164
- Capybara::Node::Element.new(session, element)
149
+ def raise_find_error(*args)
150
+ options = extract_normalized_options(args)
151
+ normalized = Capybara::Selector.normalize(*args)
152
+ message = options[:message] || "Unable to find #{normalized.name} #{normalized.locator.inspect}"
153
+ message = normalized.failure_message.call(self, normalized) if normalized.failure_message
154
+ raise Capybara::ElementNotFound, message
165
155
  end
166
156
 
167
- def wait_conditionally_until
168
- if wait? then session.wait_until { yield } else yield end
157
+ def find_in_base(selector, xpath)
158
+ base.find(xpath).map do |node|
159
+ Capybara::Node::Element.new(session, node, self, selector)
160
+ end
169
161
  end
170
162
 
171
163
  def extract_normalized_options(args)
@@ -197,7 +189,7 @@ module Capybara
197
189
  end
198
190
 
199
191
  def has_selected_options?(node, expected)
200
- actual = node.find('.//option').select { |option| option.selected? }.map { |option| option.text }
192
+ actual = node.all(:xpath, './/option').select { |option| option.selected? }.map { |option| option.text }
201
193
  (expected - actual).empty?
202
194
  end
203
195
  end