capybara 1.0.1 → 1.1.0.rc1

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