capybara 0.4.1.2 → 1.0.0.beta1

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