capybara 2.1.0 → 2.2.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 (57) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/History.md +48 -1
  5. data/README.md +22 -18
  6. data/lib/capybara.rb +28 -3
  7. data/lib/capybara/cucumber.rb +0 -2
  8. data/lib/capybara/driver/base.rb +9 -1
  9. data/lib/capybara/empty.html +4 -0
  10. data/lib/capybara/node/actions.rb +1 -1
  11. data/lib/capybara/node/finders.rb +3 -3
  12. data/lib/capybara/node/matchers.rb +22 -18
  13. data/lib/capybara/node/simple.rb +5 -3
  14. data/lib/capybara/query.rb +1 -0
  15. data/lib/capybara/rack_test/browser.rb +5 -4
  16. data/lib/capybara/rack_test/form.rb +5 -2
  17. data/lib/capybara/rack_test/node.rb +2 -1
  18. data/lib/capybara/result.rb +2 -1
  19. data/lib/capybara/rspec.rb +7 -0
  20. data/lib/capybara/rspec/features.rb +2 -0
  21. data/lib/capybara/rspec/matchers.rb +6 -6
  22. data/lib/capybara/selector.rb +3 -1
  23. data/lib/capybara/selenium/driver.rb +14 -2
  24. data/lib/capybara/selenium/node.rb +18 -5
  25. data/lib/capybara/server.rb +3 -7
  26. data/lib/capybara/session.rb +59 -17
  27. data/lib/capybara/spec/public/test.js +1 -1
  28. data/lib/capybara/spec/session/assert_selector.rb +18 -0
  29. data/lib/capybara/spec/session/attach_file_spec.rb +5 -0
  30. data/lib/capybara/spec/session/check_spec.rb +14 -0
  31. data/lib/capybara/spec/session/choose_spec.rb +14 -0
  32. data/lib/capybara/spec/session/click_button_spec.rb +20 -1
  33. data/lib/capybara/spec/session/fill_in_spec.rb +18 -2
  34. data/lib/capybara/spec/session/go_back_spec.rb +10 -0
  35. data/lib/capybara/spec/session/go_forward_spec.rb +12 -0
  36. data/lib/capybara/spec/session/has_button_spec.rb +24 -0
  37. data/lib/capybara/spec/session/has_field_spec.rb +48 -0
  38. data/lib/capybara/spec/session/has_text_spec.rb +18 -0
  39. data/lib/capybara/spec/session/node_spec.rb +36 -1
  40. data/lib/capybara/spec/session/reset_session_spec.rb +17 -2
  41. data/lib/capybara/spec/session/save_page_spec.rb +10 -4
  42. data/lib/capybara/spec/session/visit_spec.rb +16 -0
  43. data/lib/capybara/spec/session/within_frame_spec.rb +7 -0
  44. data/lib/capybara/spec/session/within_window_spec.rb +7 -0
  45. data/lib/capybara/spec/test_app.rb +1 -0
  46. data/lib/capybara/spec/views/form.erb +40 -0
  47. data/lib/capybara/spec/views/with_html.erb +5 -0
  48. data/lib/capybara/spec/views/with_js.erb +8 -0
  49. data/lib/capybara/version.rb +1 -1
  50. data/spec/capybara_spec.rb +1 -1
  51. data/spec/result_spec.rb +14 -0
  52. data/spec/rspec/features_spec.rb +15 -0
  53. data/spec/rspec/matchers_spec.rb +24 -1
  54. data/spec/selenium_spec.rb +8 -1
  55. data/spec/server_spec.rb +13 -5
  56. metadata +78 -98
  57. metadata.gz.sig +0 -0
@@ -32,6 +32,7 @@ module Capybara
32
32
  def description
33
33
  @description = "#{label} #{locator.inspect}"
34
34
  @description << " with text #{options[:text].inspect}" if options[:text]
35
+ @description << " with value #{options[:with].inspect}" if options[:with]
35
36
  @description
36
37
  end
37
38
 
@@ -46,6 +46,7 @@ class Capybara::RackTest::Browser
46
46
  method.downcase! unless method.is_a? Symbol
47
47
 
48
48
  new_uri.path = request_path if path.start_with?("?")
49
+ new_uri.path = "/" if new_uri.path.empty?
49
50
  new_uri.path = request_path.sub(%r(/[^/]*$), '/') + new_uri.path unless new_uri.path.start_with?('/')
50
51
  new_uri.scheme ||= @current_scheme
51
52
  new_uri.host ||= @current_host
@@ -77,7 +78,7 @@ class Capybara::RackTest::Browser
77
78
  end
78
79
 
79
80
  def dom
80
- @dom ||= Nokogiri::HTML(html)
81
+ @dom ||= Capybara::HTML(html)
81
82
  end
82
83
 
83
84
  def find(format, selector)
@@ -93,11 +94,11 @@ class Capybara::RackTest::Browser
93
94
  rescue Rack::Test::Error
94
95
  ""
95
96
  end
96
-
97
+
97
98
  def title
98
99
  dom.xpath("//title").text
99
100
  end
100
-
101
+
101
102
  protected
102
103
 
103
104
  def build_rack_mock_session
@@ -108,6 +109,6 @@ protected
108
109
  def request_path
109
110
  last_request.path
110
111
  rescue Rack::Test::Error
111
- ""
112
+ "/"
112
113
  end
113
114
  end
@@ -28,7 +28,10 @@ class Capybara::RackTest::Form < Capybara::RackTest::Node
28
28
  case field.name
29
29
  when 'input'
30
30
  if %w(radio checkbox).include? field['type']
31
- merge_param!(params, field['name'].to_s, field['value'].to_s) if field['checked']
31
+ if field['checked']
32
+ node=Capybara::RackTest::Node.new(self.driver, field)
33
+ merge_param!(params, field['name'].to_s, node.value.to_s)
34
+ end
32
35
  elsif %w(submit image).include? field['type']
33
36
  # TO DO identify the click button here (in document order, rather
34
37
  # than leaving until the end of the params)
@@ -60,7 +63,7 @@ class Capybara::RackTest::Form < Capybara::RackTest::Node
60
63
  merge_param!(params, field['name'].to_s, (option['value'] || option.text).to_s) if option
61
64
  end
62
65
  when 'textarea'
63
- merge_param!(params, field['name'].to_s, field.text.to_s)
66
+ merge_param!(params, field['name'].to_s, field.text.to_s.gsub(/\n/, "\r\n"))
64
67
  end
65
68
  end
66
69
  merge_param!(params, button[:name], button[:value] || "") if button[:name]
@@ -52,7 +52,8 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
52
52
  driver.follow(method, self[:href].to_s)
53
53
  elsif (tag_name == 'input' and %w(submit image).include?(type)) or
54
54
  ((tag_name == 'button') and type.nil? or type == "submit")
55
- Capybara::RackTest::Form.new(driver, form).submit(self)
55
+ associated_form = form
56
+ Capybara::RackTest::Form.new(driver, associated_form).submit(self) if associated_form
56
57
  end
57
58
  end
58
59
 
@@ -29,7 +29,8 @@ module Capybara
29
29
  @query = query
30
30
  end
31
31
 
32
- def_delegators :@result, :each, :[], :at, :size, :count, :length, :first, :last, :empty?
32
+ def_delegators :@result, :each, :[], :at, :size, :count, :length,
33
+ :first, :last, :values_at, :empty?, :inspect, :sample, :index
33
34
 
34
35
  def matches_count?
35
36
  Capybara::Helpers.matches_count?(@result.size, @query.options)
@@ -7,6 +7,12 @@ require 'capybara/rspec/features'
7
7
  RSpec.configure do |config|
8
8
  config.include Capybara::DSL, :type => :feature
9
9
  config.include Capybara::RSpecMatchers, :type => :feature
10
+
11
+ # A work-around to support accessing the current example that works in both
12
+ # RSpec 2 and RSpec 3.
13
+ fetch_current_example = RSpec.respond_to?(:current_example) ?
14
+ proc { RSpec.current_example } : proc { |context| context.example }
15
+
10
16
  # The before and after blocks must run instantaneously, because Capybara
11
17
  # might not actually be used in all examples where it's included.
12
18
  config.after do
@@ -17,6 +23,7 @@ RSpec.configure do |config|
17
23
  end
18
24
  config.before do
19
25
  if self.class.include?(Capybara::DSL)
26
+ example = fetch_current_example.call(self)
20
27
  Capybara.current_driver = Capybara.javascript_driver if example.metadata[:js]
21
28
  Capybara.current_driver = example.metadata[:driver] if example.metadata[:driver]
22
29
  end
@@ -7,11 +7,13 @@ module Capybara
7
7
  alias :xscenario :xit
8
8
  alias :given :let
9
9
  alias :given! :let!
10
+ alias :feature :describe
10
11
  end
11
12
  end
12
13
  end
13
14
  end
14
15
 
16
+
15
17
  def self.feature(*args, &block)
16
18
  options = if args.last.is_a?(Hash) then args.pop else {} end
17
19
  options[:capybara_feature] = true
@@ -126,20 +126,20 @@ module Capybara
126
126
  HaveSelector.new(:link, locator, options)
127
127
  end
128
128
 
129
- def have_button(locator)
130
- HaveSelector.new(:button, locator)
129
+ def have_button(locator, options={})
130
+ HaveSelector.new(:button, locator, options)
131
131
  end
132
132
 
133
133
  def have_field(locator, options={})
134
134
  HaveSelector.new(:field, locator, options)
135
135
  end
136
136
 
137
- def have_checked_field(locator)
138
- HaveSelector.new(:field, locator, :checked => true)
137
+ def have_checked_field(locator, options={})
138
+ HaveSelector.new(:field, locator, options.merge(:checked => true))
139
139
  end
140
140
 
141
- def have_unchecked_field(locator)
142
- HaveSelector.new(:field, locator, :unchecked => true)
141
+ def have_unchecked_field(locator, options={})
142
+ HaveSelector.new(:field, locator, options.merge(:unchecked => true))
143
143
  end
144
144
 
145
145
  def have_select(locator, options={})
@@ -103,7 +103,7 @@ Capybara.add_selector(:field) do
103
103
  filter(:checked) { |node, value| not(value ^ node.checked?) }
104
104
  filter(:unchecked) { |node, value| (value ^ node.checked?) }
105
105
  filter(:disabled, :default => false) { |node, value| not(value ^ node.disabled?) }
106
- filter(:with) { |node, with| node.value == with }
106
+ filter(:with) { |node, with| node.value == with.to_s }
107
107
  filter(:type) do |node, type|
108
108
  if ['textarea', 'select'].include?(type)
109
109
  node.tag_name == type
@@ -146,6 +146,7 @@ Capybara.add_selector(:radio_button) do
146
146
  xpath { |locator| XPath::HTML.radio_button(locator) }
147
147
  filter(:checked) { |node, value| not(value ^ node.checked?) }
148
148
  filter(:unchecked) { |node, value| (value ^ node.checked?) }
149
+ filter(:option) { |node, value| node.value == value.to_s }
149
150
  filter(:disabled, :default => false) { |node, value| not(value ^ node.disabled?) }
150
151
  end
151
152
 
@@ -153,6 +154,7 @@ Capybara.add_selector(:checkbox) do
153
154
  xpath { |locator| XPath::HTML.checkbox(locator) }
154
155
  filter(:checked) { |node, value| not(value ^ node.checked?) }
155
156
  filter(:unchecked) { |node, value| (value ^ node.checked?) }
157
+ filter(:option) { |node, value| node.value == value.to_s }
156
158
  filter(:disabled, :default => false) { |node, value| not(value ^ node.disabled?) }
157
159
  end
158
160
 
@@ -1,3 +1,5 @@
1
+ require "uri"
2
+
1
3
  class Capybara::Selenium::Driver < Capybara::Driver::Base
2
4
  DEFAULT_OPTIONS = {
3
5
  :browser => :firefox
@@ -43,6 +45,14 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
43
45
  browser.navigate.to(path)
44
46
  end
45
47
 
48
+ def go_back
49
+ browser.navigate.back
50
+ end
51
+
52
+ def go_forward
53
+ browser.navigate.forward
54
+ end
55
+
46
56
  def html
47
57
  browser.page_source
48
58
  end
@@ -87,7 +97,9 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
87
97
  # to about:blank, so we rescue this error and do nothing
88
98
  # instead.
89
99
  end
90
- @browser.navigate.to('about:blank')
100
+ uri = URI(Capybara::EMPTY_HTML_FILE_PATH)
101
+ uri.scheme = "file"
102
+ @browser.navigate.to(uri.to_s)
91
103
  end
92
104
  end
93
105
 
@@ -137,7 +149,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
137
149
  end
138
150
 
139
151
  def quit
140
- @browser.quit
152
+ @browser.quit if @browser
141
153
  rescue Errno::ECONNREFUSED
142
154
  # Browser must have already gone
143
155
  end
@@ -37,8 +37,21 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
37
37
  path_names = value.to_s.empty? ? [] : value
38
38
  native.send_keys(*path_names)
39
39
  elsif tag_name == 'textarea' or tag_name == 'input'
40
- #script can change a readonly element which user input cannot, so dont execute if readonly
41
- driver.browser.execute_script "arguments[0].value = ''", native unless self[:readonly]
40
+ if value.to_s.empty?
41
+ native.clear
42
+ else
43
+ #script can change a readonly element which user input cannot, so dont execute if readonly
44
+ driver.browser.execute_script "arguments[0].value = ''", native unless self[:readonly]
45
+ native.send_keys(value.to_s)
46
+ end
47
+ elsif native.attribute('isContentEditable')
48
+ #ensure we are focused on the element
49
+ script = <<-JS
50
+ var range = document.createRange();
51
+ range.selectNodeContents(arguments[0]);
52
+ window.getSelection().addRange(range);
53
+ JS
54
+ driver.browser.execute_script script, native
42
55
  native.send_keys(value.to_s)
43
56
  end
44
57
  end
@@ -61,7 +74,7 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
61
74
  def hover
62
75
  driver.browser.action.move_to(native).perform
63
76
  end
64
-
77
+
65
78
  def drag_to(element)
66
79
  driver.browser.action.drag_and_drop(native, element.native).perform
67
80
  end
@@ -89,11 +102,11 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
89
102
  def find_xpath(locator)
90
103
  native.find_elements(:xpath, locator).map { |n| self.class.new(driver, n) }
91
104
  end
92
-
105
+
93
106
  def find_css(locator)
94
107
  native.find_elements(:css, locator).map { |n| self.class.new(driver, n) }
95
108
  end
96
-
109
+
97
110
  def ==(other)
98
111
  native == other.native
99
112
  end
@@ -31,13 +31,13 @@ module Capybara
31
31
  end
32
32
  end
33
33
 
34
- attr_reader :app, :port
34
+ attr_reader :app, :port, :host
35
35
 
36
- def initialize(app, port=Capybara.server_port)
36
+ def initialize(app, port=Capybara.server_port, host=Capybara.server_host)
37
37
  @app = app
38
38
  @middleware = Middleware.new(@app)
39
39
  @server_thread = nil # supress warnings
40
- @port = port
40
+ @host, @port = host, port
41
41
  @port ||= Capybara::Server.ports[@app.object_id]
42
42
  @port ||= find_available_port
43
43
  end
@@ -50,10 +50,6 @@ module Capybara
50
50
  @middleware.error
51
51
  end
52
52
 
53
- def host
54
- Capybara.server_host || "127.0.0.1"
55
- end
56
-
57
53
  def responsive?
58
54
  return false if @server_thread && @server_thread.join(0)
59
55
 
@@ -37,10 +37,11 @@ module Capybara
37
37
  :has_no_unchecked_field?, :query, :assert_selector, :assert_no_selector
38
38
  ]
39
39
  SESSION_METHODS = [
40
- :body, :html, :current_url, :current_host, :evaluate_script, :source,
41
- :visit, :within, :within_fieldset, :within_table, :within_frame,
42
- :within_window, :current_path, :save_page, :save_and_open_page,
43
- :save_screenshot, :reset_session!, :response_headers, :status_code,
40
+ :body, :html, :source, :current_url, :current_host, :current_path,
41
+ :execute_script, :evaluate_script, :visit, :go_back, :go_forward,
42
+ :within, :within_fieldset, :within_table, :within_frame, :within_window,
43
+ :save_page, :save_and_open_page, :save_screenshot,
44
+ :reset_session!, :response_headers, :status_code,
44
45
  :title, :has_title?, :has_no_title?, :current_scope
45
46
  ]
46
47
  DSL_METHODS = NODE_METHODS + SESSION_METHODS
@@ -74,8 +75,11 @@ module Capybara
74
75
  # Reset the session, removing all cookies.
75
76
  #
76
77
  def reset!
77
- driver.reset! if @touched
78
- @touched = false
78
+ if @touched
79
+ driver.reset!
80
+ @touched = false
81
+ assert_no_selector :xpath, "/html/body/*"
82
+ end
79
83
  raise @server.error if Capybara.raise_server_errors and @server and @server.error
80
84
  ensure
81
85
  @server.reset_error! if @server
@@ -195,27 +199,54 @@ module Capybara
195
199
 
196
200
  ##
197
201
  #
198
- # Execute the given block for a particular scope on the page. Within will find the first
199
- # element matching the given selector and execute the block scoped to that element:
202
+ # Move back a single entry in the browser's history.
203
+ #
204
+ def go_back
205
+ driver.go_back
206
+ end
207
+
208
+ ##
209
+ #
210
+ # Move forward a single entry in the browser's history.
211
+ #
212
+ def go_forward
213
+ driver.go_forward
214
+ end
215
+
216
+ ##
217
+ #
218
+ # Executes the given block within the context of a node. `within` takes the
219
+ # same options as `find`, as well as a block. For the duration of the
220
+ # block, any command to Capybara will be handled as though it were scoped
221
+ # to the given element.
200
222
  #
201
223
  # within(:xpath, '//div[@id="delivery-address"]') do
202
224
  # fill_in('Street', :with => '12 Main Street')
203
225
  # end
204
226
  #
205
- # It is possible to omit the first parameter, in that case, the selector is assumed to be
206
- # of the type set in Capybara.default_selector.
227
+ # Just as with `find`, if multiple elements match the selector given to
228
+ # `within`, an error will be raised, and just as with `find`, this
229
+ # behaviour can be controlled through the `:match` and `:exact` options.
230
+ #
231
+ # It is possible to omit the first parameter, in that case, the selector is
232
+ # assumed to be of the type set in Capybara.default_selector.
207
233
  #
208
234
  # within('div#delivery-address') do
209
235
  # fill_in('Street', :with => '12 Main Street')
210
236
  # end
211
237
  #
238
+ # Note that a lot of uses of `within` can be replaced more succinctly with
239
+ # chaining:
240
+ #
241
+ # find('div#delivery-address').fill_in('Street', :with => '12 Main Street')
242
+ #
212
243
  # @overload within(*find_args)
213
244
  # @param (see Capybara::Node::Finders#all)
214
245
  #
215
246
  # @overload within(a_node)
216
247
  # @param [Capybara::Node::Base] a_node The node in whose scope the block should be evaluated
217
248
  #
218
- # @raise [Capybara::ElementNotFound] If the scope can't be found before time expires
249
+ # @raise [Capybara::ElementNotFound] If the scope can't be found before time expires
219
250
  #
220
251
  def within(*args)
221
252
  new_scope = if args.first.is_a?(Capybara::Node::Base) then args.first else find(*args) end
@@ -262,9 +293,12 @@ module Capybara
262
293
  # @param [String] name name of a frame
263
294
  #
264
295
  def within_frame(frame_handle)
296
+ scopes.push(nil)
265
297
  driver.within_frame(frame_handle) do
266
298
  yield
267
299
  end
300
+ ensure
301
+ scopes.pop
268
302
  end
269
303
 
270
304
  ##
@@ -275,7 +309,10 @@ module Capybara
275
309
  # @param [String] handle of the window
276
310
  #
277
311
  def within_window(handle, &blk)
312
+ scopes.push(nil)
278
313
  driver.within_window(handle, &blk)
314
+ ensure
315
+ scopes.pop
279
316
  end
280
317
 
281
318
  ##
@@ -313,7 +350,7 @@ module Capybara
313
350
  #
314
351
  def save_page(path=nil)
315
352
  path ||= "capybara-#{Time.new.strftime("%Y%m%d%H%M%S")}#{rand(10**10)}.html"
316
- path = File.expand_path(path, Capybara.save_and_open_page_path) if Capybara.save_and_open_page_path
353
+ path = File.expand_path(path, Capybara.save_and_open_page_path)
317
354
 
318
355
  FileUtils.mkdir_p(File.dirname(path))
319
356
 
@@ -328,10 +365,15 @@ module Capybara
328
365
  # @param [String] file_name The path to where it should be saved [optional]
329
366
  #
330
367
  def save_and_open_page(file_name=nil)
331
- require "launchy"
332
- Launchy.open(save_page(file_name))
333
- rescue LoadError
334
- warn "Please install the launchy gem to open page with save_and_open_page"
368
+ file_name = save_page(file_name)
369
+
370
+ begin
371
+ require "launchy"
372
+ Launchy.open(file_name)
373
+ rescue LoadError
374
+ warn "Page saved to #{file_name} with save_and_open_page."
375
+ warn "Please install the launchy gem to open page automatically."
376
+ end
335
377
  end
336
378
 
337
379
  ##
@@ -382,7 +424,7 @@ module Capybara
382
424
  end
383
425
 
384
426
  def current_scope
385
- scopes.last
427
+ scopes.last || document
386
428
  end
387
429
 
388
430
  private
@@ -35,7 +35,7 @@ $(function() {
35
35
  $('body').append('<p id="focus_event_triggered">Focus Event triggered</p>');
36
36
  });
37
37
  $('#with_change_event').change(function() {
38
- if($(this).val() == '') $(this).val("Can't be empty");
38
+ $('body').append($('<p class="change_event_triggered"></p>').text(this.value));
39
39
  });
40
40
  $('#checkbox_with_event').click(function() {
41
41
  $('body').append('<p id="checkbox_event_triggered">Checkbox event triggered</p>');