capybara 2.1.0 → 2.2.0.rc1

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