capybara 0.4.1.2 → 1.0.0.beta1

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 (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
@@ -0,0 +1,77 @@
1
+ require 'rack/test'
2
+ require 'rack/utils'
3
+ require 'mime/types'
4
+ require 'nokogiri'
5
+ require 'cgi'
6
+
7
+ class Capybara::RackTest::Driver < Capybara::Driver::Base
8
+ attr_reader :app, :options
9
+
10
+ def initialize(app, options={})
11
+ raise ArgumentError, "rack-test requires a rack application, but none was given" unless app
12
+ @app = app
13
+ @options = options
14
+ end
15
+
16
+ def browser
17
+ @browser ||= Capybara::RackTest::Browser.new(app, options)
18
+ end
19
+
20
+ def response
21
+ browser.last_response
22
+ end
23
+
24
+ def request
25
+ browser.last_request
26
+ end
27
+
28
+ def visit(path, attributes = {})
29
+ browser.visit(path, attributes)
30
+ end
31
+
32
+ def submit(method, path, attributes)
33
+ browser.submit(method, path, attributes)
34
+ end
35
+
36
+ def follow(method, path, attributes = {})
37
+ browser.follow(method, path, attributes)
38
+ end
39
+
40
+ def current_url
41
+ browser.current_url
42
+ end
43
+
44
+ def response_headers
45
+ response.headers
46
+ end
47
+
48
+ def status_code
49
+ response.status
50
+ end
51
+
52
+ def find(selector)
53
+ browser.find(selector)
54
+ end
55
+
56
+ def body
57
+ browser.body
58
+ end
59
+
60
+ def source
61
+ browser.source
62
+ end
63
+
64
+ def dom
65
+ browser.dom
66
+ end
67
+
68
+ def reset!
69
+ @browser = nil
70
+ end
71
+
72
+ def get(*args, &block); browser.get(*args, &block); end
73
+ def post(*args, &block); browser.post(*args, &block); end
74
+ def put(*args, &block); browser.put(*args, &block); end
75
+ def delete(*args, &block); browser.delete(*args, &block); end
76
+ def header(key, value); browser.header(key, value); end
77
+ end
@@ -0,0 +1,80 @@
1
+ class Capybara::RackTest::Form < Capybara::RackTest::Node
2
+ # This only needs to inherit from Rack::Test::UploadedFile because Rack::Test checks for
3
+ # the class specifically when determing whether to consturct the request as multipart.
4
+ # That check should be based solely on the form element's 'enctype' attribute value,
5
+ # which should probably be provided to Rack::Test in its non-GET request methods.
6
+ class NilUploadedFile < Rack::Test::UploadedFile
7
+ def initialize
8
+ @empty_file = Tempfile.new("nil_uploaded_file")
9
+ @empty_file.close
10
+ end
11
+
12
+ def original_filename; ""; end
13
+ def content_type; "application/octet-stream"; end
14
+ def path; @empty_file.path; end
15
+ end
16
+
17
+ def params(button)
18
+ params = {}
19
+
20
+ native.xpath("(.//input|.//select|.//textarea)[not(@disabled)]").map do |field|
21
+ case field.name
22
+ when 'input'
23
+ if %w(radio checkbox).include? field['type']
24
+ merge_param!(params, field['name'].to_s, field['value'].to_s) if field['checked']
25
+ elsif %w(submit image).include? field['type']
26
+ # TO DO identify the click button here (in document order, rather
27
+ # than leaving until the end of the params)
28
+ elsif field['type'] =='file'
29
+ if multipart?
30
+ file = \
31
+ if (value = field['value']).to_s.empty?
32
+ NilUploadedFile.new
33
+ else
34
+ content_type = MIME::Types.type_for(value).first.to_s
35
+ Rack::Test::UploadedFile.new(value, content_type)
36
+ end
37
+ merge_param!(params, field['name'].to_s, file)
38
+ else
39
+ merge_param!(params, field['name'].to_s, File.basename(field['value'].to_s))
40
+ end
41
+ else
42
+ merge_param!(params, field['name'].to_s, field['value'].to_s)
43
+ end
44
+ when 'select'
45
+ if field['multiple'] == 'multiple'
46
+ options = field.xpath(".//option[@selected]")
47
+ options.each do |option|
48
+ merge_param!(params, field['name'].to_s, (option['value'] || option.text).to_s)
49
+ end
50
+ else
51
+ option = field.xpath(".//option[@selected]").first
52
+ option ||= field.xpath('.//option').first
53
+ merge_param!(params, field['name'].to_s, (option['value'] || option.text).to_s) if option
54
+ end
55
+ when 'textarea'
56
+ merge_param!(params, field['name'].to_s, field.text.to_s)
57
+ end
58
+ end
59
+ merge_param!(params, button[:name], button[:value] || "") if button[:name]
60
+ params
61
+ end
62
+
63
+ def submit(button)
64
+ driver.submit(method, native['action'].to_s, params(button))
65
+ end
66
+
67
+ def multipart?
68
+ self[:enctype] == "multipart/form-data"
69
+ end
70
+
71
+ private
72
+
73
+ def method
74
+ self[:method] =~ /post/i ? :post : :get
75
+ end
76
+
77
+ def merge_param!(params, key, value)
78
+ Rack::Utils.normalize_params(params, key, value)
79
+ end
80
+ end
@@ -0,0 +1,101 @@
1
+ class Capybara::RackTest::Node < Capybara::Driver::Node
2
+ def text
3
+ native.text
4
+ end
5
+
6
+ def [](name)
7
+ string_node[name]
8
+ end
9
+
10
+ def value
11
+ string_node.value
12
+ end
13
+
14
+ def set(value)
15
+ if tag_name == 'input' and type == 'radio'
16
+ other_radios_xpath = XPath.generate { |x| x.anywhere(:input)[x.attr(:name).equals(self[:name])] }.to_s
17
+ driver.dom.xpath(other_radios_xpath).each { |node| node.remove_attribute("checked") }
18
+ native['checked'] = 'checked'
19
+ elsif tag_name == 'input' and type == 'checkbox'
20
+ if value && !native['checked']
21
+ native['checked'] = 'checked'
22
+ elsif !value && native['checked']
23
+ native.remove_attribute('checked')
24
+ end
25
+ elsif tag_name == 'input'
26
+ if (type == 'text' || type == 'password') && self[:maxlength]
27
+ value = value[0...self[:maxlength].to_i]
28
+ end
29
+ native['value'] = value.to_s
30
+ elsif tag_name == "textarea"
31
+ native.content = value.to_s
32
+ end
33
+ end
34
+
35
+ def select_option
36
+ if select_node['multiple'] != 'multiple'
37
+ select_node.find(".//option[@selected]").each { |node| node.native.remove_attribute("selected") }
38
+ end
39
+ native["selected"] = 'selected'
40
+ end
41
+
42
+ def unselect_option
43
+ if select_node['multiple'] != 'multiple'
44
+ raise Capybara::UnselectNotAllowed, "Cannot unselect option from single select box."
45
+ end
46
+ native.remove_attribute('selected')
47
+ end
48
+
49
+ def click
50
+ if tag_name == 'a'
51
+ method = self["data-method"] || :get
52
+ driver.follow(method, self[:href].to_s)
53
+ elsif (tag_name == 'input' and %w(submit image).include?(type)) or
54
+ ((tag_name == 'button') and type.nil? or type == "submit")
55
+ Capybara::RackTest::Form.new(driver, form).submit(self)
56
+ end
57
+ end
58
+
59
+ def tag_name
60
+ native.node_name
61
+ end
62
+
63
+ def visible?
64
+ string_node.visible?
65
+ end
66
+
67
+ def checked?
68
+ string_node.checked?
69
+ end
70
+
71
+ def selected?
72
+ string_node.selected?
73
+ end
74
+
75
+ def path
76
+ native.path
77
+ end
78
+
79
+ def find(locator)
80
+ native.xpath(locator).map { |n| self.class.new(driver, n) }
81
+ end
82
+
83
+ private
84
+
85
+ def string_node
86
+ @string_node ||= Capybara::Node::Simple.new(native)
87
+ end
88
+
89
+ # a reference to the select node if this is an option node
90
+ def select_node
91
+ find('./ancestor::select').first
92
+ end
93
+
94
+ def type
95
+ native[:type]
96
+ end
97
+
98
+ def form
99
+ native.ancestors('form').first
100
+ end
101
+ end
@@ -1,16 +1,24 @@
1
1
  require 'capybara'
2
2
  require 'capybara/dsl'
3
+ require 'rspec/core'
4
+ require 'capybara/rspec/matchers'
5
+ require 'capybara/rspec/features'
3
6
 
4
7
  RSpec.configure do |config|
5
- config.include Capybara, :type => :acceptance
8
+ config.include Capybara::DSL, :type => :request
9
+ config.include Capybara::DSL, :type => :acceptance
10
+ config.include Capybara::RSpecMatchers, :type => :request
11
+ config.include Capybara::RSpecMatchers, :type => :acceptance
12
+ # The before and after blocks must run instantaneously, because Capybara
13
+ # might not actually be used in all examples where it's included.
6
14
  config.after do
7
- if example.metadata[:type] == :acceptance
15
+ if self.class.include?(Capybara::DSL)
8
16
  Capybara.reset_sessions!
9
17
  Capybara.use_default_driver
10
18
  end
11
19
  end
12
20
  config.before do
13
- if example.metadata[:type] == :acceptance
21
+ if self.class.include?(Capybara::DSL)
14
22
  Capybara.current_driver = Capybara.javascript_driver if example.metadata[:js]
15
23
  Capybara.current_driver = example.metadata[:driver] if example.metadata[:driver]
16
24
  end
@@ -0,0 +1,22 @@
1
+ module Capybara
2
+ module Features
3
+ def self.included(base)
4
+ base.instance_eval do
5
+ alias :background :before
6
+ alias :scenario :it
7
+ end
8
+ end
9
+ end
10
+ end
11
+
12
+ def self.feature(*args, &block)
13
+ options = if args.last.is_a?(Hash) then args.pop else {} end
14
+ options[:capybara_feature] = true
15
+ options[:type] = :request
16
+ options[:caller] ||= caller
17
+ args.push(options)
18
+
19
+ describe(*args, &block)
20
+ end
21
+
22
+ RSpec.configuration.include Capybara::Features, :capybara_feature => true
@@ -0,0 +1,146 @@
1
+ module Capybara
2
+ module RSpecMatchers
3
+ class HaveSelector
4
+ def initialize(*args)
5
+ @args = args
6
+ end
7
+
8
+ def matches?(actual)
9
+ @actual = wrap(actual)
10
+ @actual.has_selector?(*@args)
11
+ end
12
+
13
+ def does_not_match?(actual)
14
+ @actual = wrap(actual)
15
+ @actual.has_no_selector?(*@args)
16
+ end
17
+
18
+ def failure_message_for_should
19
+ if normalized.failure_message
20
+ normalized.failure_message.call(@actual, normalized)
21
+ else
22
+ "expected #{selector_name} to return something"
23
+ end
24
+ end
25
+
26
+ def failure_message_for_should_not
27
+ "expected #{selector_name} not to return anything"
28
+ end
29
+
30
+ def selector_name
31
+ name = "#{normalized.name} #{normalized.locator.inspect}"
32
+ name << " with text #{normalized.options[:text].inspect}" if normalized.options[:text]
33
+ name
34
+ end
35
+
36
+ def wrap(actual)
37
+ if actual.respond_to?("has_selector?")
38
+ actual
39
+ else
40
+ Capybara.string(actual.to_s)
41
+ end
42
+ end
43
+
44
+ def normalized
45
+ @normalized ||= Capybara::Selector.normalize(*@args)
46
+ end
47
+ end
48
+
49
+ class HaveMatcher
50
+ attr_reader :name, :locator, :options, :failure_message, :actual
51
+
52
+ def initialize(name, locator, options={}, &block)
53
+ @name = name
54
+ @locator = locator
55
+ @options = options
56
+ @failure_message = block
57
+ end
58
+
59
+ def arguments
60
+ if options.empty? then [locator] else [locator, options] end
61
+ end
62
+
63
+ def matches?(actual)
64
+ @actual = wrap(actual)
65
+ @actual.send(:"has_#{name}?", *arguments)
66
+ end
67
+
68
+ def does_not_match?(actual)
69
+ @actual = wrap(actual)
70
+ @actual.send(:"has_no_#{name}?", *arguments)
71
+ end
72
+
73
+ def failure_message_for_should
74
+ if failure_message
75
+ failure_message.call(actual, self)
76
+ else
77
+ "expected #{selector_name} to return something"
78
+ end
79
+ end
80
+
81
+ def failure_message_for_should_not
82
+ "expected #{selector_name} not to return anything"
83
+ end
84
+
85
+ def selector_name
86
+ selector_name = "#{name} #{locator.inspect}"
87
+ selector_name << " with text #{options[:text].inspect}" if options[:text]
88
+ selector_name
89
+ end
90
+
91
+ def wrap(actual)
92
+ if actual.respond_to?("has_selector?")
93
+ actual
94
+ else
95
+ Capybara.string(actual.to_s)
96
+ end
97
+ end
98
+ end
99
+
100
+ def have_selector(*args)
101
+ HaveSelector.new(*args)
102
+ end
103
+
104
+ def have_xpath(xpath, options={})
105
+ HaveMatcher.new(:xpath, xpath, options)
106
+ end
107
+
108
+ def have_css(css, options={})
109
+ HaveMatcher.new(:css, css, options)
110
+ end
111
+
112
+ def have_content(text)
113
+ HaveMatcher.new(:content, text.to_s) do |page, matcher|
114
+ %(expected there to be content #{matcher.locator.inspect} in #{page.text.inspect})
115
+ end
116
+ end
117
+
118
+ def have_link(locator, options={})
119
+ HaveMatcher.new(:link, locator, options)
120
+ end
121
+
122
+ def have_button(locator)
123
+ HaveMatcher.new(:button, locator)
124
+ end
125
+
126
+ def have_field(locator, options={})
127
+ HaveMatcher.new(:field, locator, options)
128
+ end
129
+
130
+ def have_checked_field(locator)
131
+ HaveMatcher.new(:checked_field, locator)
132
+ end
133
+
134
+ def have_unchecked_field(locator)
135
+ HaveMatcher.new(:unchecked_field, locator)
136
+ end
137
+
138
+ def have_select(locator, options={})
139
+ HaveMatcher.new(:select, locator, options)
140
+ end
141
+
142
+ def have_table(locator, options={})
143
+ HaveMatcher.new(:table, locator, options)
144
+ end
145
+ end
146
+ end