capybara 2.0.3 → 2.1.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 (81) hide show
  1. data.tar.gz.sig +0 -0
  2. data/History.md +73 -0
  3. data/README.md +52 -5
  4. data/lib/capybara.rb +44 -62
  5. data/lib/capybara/cucumber.rb +4 -1
  6. data/lib/capybara/driver/base.rb +13 -9
  7. data/lib/capybara/driver/node.rb +18 -6
  8. data/lib/capybara/helpers.rb +111 -22
  9. data/lib/capybara/node/actions.rb +24 -19
  10. data/lib/capybara/node/base.rb +25 -32
  11. data/lib/capybara/node/document.rb +6 -2
  12. data/lib/capybara/node/element.rb +45 -7
  13. data/lib/capybara/node/finders.rb +48 -21
  14. data/lib/capybara/node/matchers.rb +38 -21
  15. data/lib/capybara/node/simple.rb +29 -7
  16. data/lib/capybara/query.rb +72 -26
  17. data/lib/capybara/rack_test/browser.rb +11 -3
  18. data/lib/capybara/rack_test/css_handlers.rb +8 -0
  19. data/lib/capybara/rack_test/driver.rb +11 -3
  20. data/lib/capybara/rack_test/form.rb +8 -1
  21. data/lib/capybara/rack_test/node.rb +92 -36
  22. data/lib/capybara/rails.rb +1 -2
  23. data/lib/capybara/result.rb +21 -37
  24. data/lib/capybara/rspec/matchers.rb +60 -31
  25. data/lib/capybara/selector.rb +46 -13
  26. data/lib/capybara/selenium/driver.rb +42 -6
  27. data/lib/capybara/selenium/node.rb +23 -5
  28. data/lib/capybara/session.rb +46 -11
  29. data/lib/capybara/spec/public/test.js +12 -0
  30. data/lib/capybara/spec/session/all_spec.rb +9 -8
  31. data/lib/capybara/spec/session/attach_file_spec.rb +14 -0
  32. data/lib/capybara/spec/session/check_spec.rb +14 -0
  33. data/lib/capybara/spec/session/choose_spec.rb +14 -0
  34. data/lib/capybara/spec/session/click_button_spec.rb +77 -1
  35. data/lib/capybara/spec/session/click_link_or_button_spec.rb +65 -0
  36. data/lib/capybara/spec/session/click_link_spec.rb +24 -0
  37. data/lib/capybara/spec/session/current_scope_spec.rb +29 -0
  38. data/lib/capybara/spec/session/fill_in_spec.rb +14 -0
  39. data/lib/capybara/spec/session/find_button_spec.rb +12 -0
  40. data/lib/capybara/spec/session/find_by_id_spec.rb +12 -1
  41. data/lib/capybara/spec/session/find_field_spec.rb +30 -0
  42. data/lib/capybara/spec/session/find_link_spec.rb +12 -0
  43. data/lib/capybara/spec/session/find_spec.rb +258 -16
  44. data/lib/capybara/spec/session/first_spec.rb +25 -8
  45. data/lib/capybara/spec/session/has_css_spec.rb +2 -2
  46. data/lib/capybara/spec/session/has_field_spec.rb +10 -2
  47. data/lib/capybara/spec/session/has_selector_spec.rb +9 -0
  48. data/lib/capybara/spec/session/has_text_spec.rb +96 -0
  49. data/lib/capybara/spec/session/has_title_spec.rb +47 -0
  50. data/lib/capybara/spec/session/node_spec.rb +43 -0
  51. data/lib/capybara/spec/session/reset_session_spec.rb +10 -2
  52. data/lib/capybara/spec/session/save_page_spec.rb +30 -0
  53. data/lib/capybara/spec/session/select_spec.rb +65 -0
  54. data/lib/capybara/spec/session/text_spec.rb +31 -0
  55. data/lib/capybara/spec/session/title_spec.rb +16 -0
  56. data/lib/capybara/spec/session/uncheck_spec.rb +14 -0
  57. data/lib/capybara/spec/session/unselect_spec.rb +42 -0
  58. data/lib/capybara/spec/session/visit_spec.rb +3 -3
  59. data/lib/capybara/spec/session/within_frame_spec.rb +14 -0
  60. data/lib/capybara/spec/session/within_spec.rb +3 -3
  61. data/lib/capybara/spec/spec_helper.rb +6 -1
  62. data/lib/capybara/spec/views/form.erb +39 -2
  63. data/lib/capybara/spec/views/frame_child.erb +9 -0
  64. data/lib/capybara/spec/views/frame_parent.erb +8 -0
  65. data/lib/capybara/spec/views/with_base_tag.erb +10 -0
  66. data/lib/capybara/spec/views/with_count.erb +7 -0
  67. data/lib/capybara/spec/views/with_hover.erb +17 -0
  68. data/lib/capybara/spec/views/with_html.erb +21 -2
  69. data/lib/capybara/spec/views/with_js.erb +5 -0
  70. data/lib/capybara/spec/views/with_scope.erb +6 -1
  71. data/lib/capybara/spec/views/with_title.erb +1 -0
  72. data/lib/capybara/spec/views/within_frames.erb +1 -0
  73. data/lib/capybara/version.rb +1 -1
  74. data/spec/basic_node_spec.rb +75 -24
  75. data/spec/dsl_spec.rb +2 -1
  76. data/spec/rack_test_spec.rb +4 -3
  77. data/spec/rspec/matchers_spec.rb +105 -17
  78. data/spec/server_spec.rb +8 -8
  79. data/spec/spec_helper.rb +2 -1
  80. metadata +83 -23
  81. metadata.gz.sig +0 -0
@@ -3,7 +3,7 @@ require 'capybara/dsl'
3
3
 
4
4
  Capybara.app = Rack::Builder.new do
5
5
  map "/" do
6
- if Gem::Version.new(Rails.version) >= Gem::Version.new("3.0")
6
+ if Rails.version.to_f >= 3.0
7
7
  run Rails.application
8
8
  else # Rails 2
9
9
  use Rails::Rack::Static
@@ -12,7 +12,6 @@ Capybara.app = Rack::Builder.new do
12
12
  end
13
13
  end.to_app
14
14
 
15
- Capybara.asset_root = Rails.root.join('public')
16
15
  Capybara.save_and_open_page_path = Rails.root.join('tmp/capybara')
17
16
 
18
17
  # Override default rack_test driver to respect data-method attributes.
@@ -1,6 +1,23 @@
1
1
  require 'forwardable'
2
2
 
3
3
  module Capybara
4
+
5
+ ##
6
+ # A {Capybara::Result} represents a collection of {Capybara::Element} on the page. It is possible to interact with this
7
+ # collection similar to an Array because it implements Enumerable and offers the following Array methods through delegation:
8
+ #
9
+ # * []
10
+ # * each()
11
+ # * at()
12
+ # * size()
13
+ # * count()
14
+ # * length()
15
+ # * first()
16
+ # * last()
17
+ # * empty?()
18
+ #
19
+ # @see Capybara::Element
20
+ #
4
21
  class Result
5
22
  include Enumerable
6
23
  extend Forwardable
@@ -15,36 +32,13 @@ module Capybara
15
32
  def_delegators :@result, :each, :[], :at, :size, :count, :length, :first, :last, :empty?
16
33
 
17
34
  def matches_count?
18
- @query.matches_count?(@result.size)
19
- end
20
-
21
- def find!
22
- raise find_error if @result.size != 1
23
- @result.first
24
- end
25
-
26
- def find_error
27
- if @result.size == 0
28
- Capybara::ElementNotFound.new("Unable to find #{@query.description}")
29
- elsif @result.size > 1
30
- Capybara::Ambiguous.new("Ambiguous match, found #{size} elements matching #{@query.description}")
31
- end
35
+ Capybara::Helpers.matches_count?(@result.size, @query.options)
32
36
  end
33
37
 
34
38
  def failure_message
35
- message = if @query.options[:count]
36
- "expected #{@query.description} to be found #{@query.options[:count]} #{declension("time", "times", @query.options[:count])}"
37
- elsif @query.options[:between]
38
- "expected #{@query.description} to be found between #{@query.options[:between].first} and #{@query.options[:between].last} times"
39
- elsif @query.options[:maximum]
40
- "expected #{@query.description} to be found at most #{@query.options[:maximum]} #{declension("time", "times", @query.options[:maximum])}"
41
- elsif @query.options[:minimum]
42
- "expected #{@query.description} to be found at least #{@query.options[:minimum]} #{declension("time", "times", @query.options[:minimum])}"
43
- else
44
- "expected to find #{@query.description}"
45
- end
39
+ message = Capybara::Helpers.failure_message(@query.description, @query.options)
46
40
  if count > 0
47
- message << ", found #{count} #{declension("match", "matches")}: " << @result.map(&:text).map(&:inspect).join(", ")
41
+ message << ", found #{count} #{Capybara::Helpers.declension("match", "matches", count)}: " << @result.map(&:text).map(&:inspect).join(", ")
48
42
  else
49
43
  message << " but there were no matches"
50
44
  end
@@ -56,17 +50,7 @@ module Capybara
56
50
  end
57
51
 
58
52
  def negative_failure_message
59
- "expected not to find #{@query.description}, but there #{declension("was", "were")} #{count} #{declension("match", "matches")}"
60
- end
61
-
62
- private
63
-
64
- def declension(singular, plural, count=count)
65
- if count == 1
66
- singular
67
- else
68
- plural
69
- end
53
+ failure_message.sub(/(to be found|to find)/, 'not \1')
70
54
  end
71
55
  end
72
56
  end
@@ -1,6 +1,16 @@
1
1
  module Capybara
2
2
  module RSpecMatchers
3
- class HaveSelector
3
+ class Matcher
4
+ def wrap(actual)
5
+ if actual.respond_to?("has_selector?")
6
+ actual
7
+ else
8
+ Capybara.string(actual.to_s)
9
+ end
10
+ end
11
+ end
12
+
13
+ class HaveSelector < Matcher
4
14
  def initialize(*args)
5
15
  @args = args
6
16
  end
@@ -17,59 +27,77 @@ module Capybara
17
27
  "have #{query.description}"
18
28
  end
19
29
 
20
- def wrap(actual)
21
- if actual.respond_to?("has_selector?")
22
- actual
23
- else
24
- Capybara.string(actual.to_s)
25
- end
26
- end
27
-
28
30
  def query
29
31
  @query ||= Capybara::Query.new(*@args)
30
32
  end
31
33
  end
32
34
 
33
- class HaveText
34
- attr_reader :text
35
+ class HaveText < Matcher
36
+ attr_reader :type, :content, :options
35
37
 
36
- def initialize(text)
37
- @text = text
38
+ def initialize(*args)
39
+ @type = args.shift if args.first.is_a?(Symbol)
40
+ @content = args.shift
41
+ @options = (args.first.is_a?(Hash))? args.first : {}
38
42
  end
39
43
 
40
44
  def matches?(actual)
41
45
  @actual = wrap(actual)
42
- @actual.has_text?(text)
46
+ @actual.has_text?(type, content, options)
43
47
  end
44
48
 
45
49
  def does_not_match?(actual)
46
50
  @actual = wrap(actual)
47
- @actual.has_no_text?(text)
51
+ @actual.has_no_text?(type, content, options)
48
52
  end
49
53
 
50
54
  def failure_message_for_should
51
- "expected there to be text #{format(text)} in #{format(@actual.text)}"
55
+ message = Capybara::Helpers.failure_message(description, options)
56
+ message << " in #{format(@actual.text(type))}"
57
+ message
52
58
  end
53
59
 
54
60
  def failure_message_for_should_not
55
- "expected there not to be text #{format(text)} in #{format(@actual.text)}"
61
+ failure_message_for_should.sub(/(to find)/, 'not \1')
56
62
  end
57
63
 
58
64
  def description
59
- "have text #{format(text)}"
65
+ "text #{format(content)}"
60
66
  end
61
67
 
62
- def wrap(actual)
63
- if actual.respond_to?("has_selector?")
64
- actual
65
- else
66
- Capybara.string(actual.to_s)
67
- end
68
+ def format(content)
69
+ content = Capybara::Helpers.normalize_whitespace(content) unless content.is_a? Regexp
70
+ content.inspect
71
+ end
72
+ end
73
+
74
+ class HaveTitle < Matcher
75
+ attr_reader :title
76
+
77
+ def initialize(title)
78
+ @title = title
68
79
  end
69
80
 
70
- def format(text)
71
- text = Capybara::Helpers.normalize_whitespace(text) unless text.is_a? Regexp
72
- text.inspect
81
+ def matches?(actual)
82
+ @actual = wrap(actual)
83
+ @actual.has_title?(title)
84
+ end
85
+
86
+ def does_not_match?(actual)
87
+ @actual = wrap(actual)
88
+ @actual.has_no_title?(title)
89
+ end
90
+
91
+ def failure_message_for_should
92
+ "expected there to be title #{title.inspect} in #{@actual.title.inspect}"
93
+ end
94
+
95
+ def failure_message_for_should_not
96
+ "expected there not to be title #{title.inspect} in #{@actual.title.inspect}"
97
+ end
98
+
99
+ def description
100
+ "have title #{title.inspect}"
73
101
  end
74
102
  end
75
103
 
@@ -85,12 +113,13 @@ module Capybara
85
113
  HaveSelector.new(:css, css, options)
86
114
  end
87
115
 
88
- def have_content(text)
89
- HaveText.new(text)
116
+ def have_text(*args)
117
+ HaveText.new(*args)
90
118
  end
119
+ alias_method :have_content, :have_text
91
120
 
92
- def have_text(text)
93
- HaveText.new(text)
121
+ def have_title(title)
122
+ HaveTitle.new(title)
94
123
  end
95
124
 
96
125
  def have_link(locator, options={})
@@ -1,7 +1,26 @@
1
1
  module Capybara
2
2
  class Selector
3
- attr_reader :name, :custom_filters
3
+ class Filter
4
+ def initialize(name, block, options={})
5
+ @name = name
6
+ @block = block
7
+ @options = options
8
+ end
9
+
10
+ def default?
11
+ @options.has_key?(:default)
12
+ end
13
+
14
+ def default
15
+ @options[:default]
16
+ end
17
+
18
+ def matches?(node, value)
19
+ @block.call(node, value)
20
+ end
21
+ end
4
22
 
23
+ attr_reader :name, :custom_filters, :format
5
24
 
6
25
  class << self
7
26
  def all
@@ -27,16 +46,16 @@ module Capybara
27
46
  end
28
47
 
29
48
  def xpath(&block)
49
+ @format = :xpath
30
50
  @xpath = block if block
31
51
  @xpath
32
52
  end
33
53
 
34
54
  # Same as xpath, but wrap in XPath.css().
35
55
  def css(&block)
36
- if block
37
- @xpath = xpath { |*args| XPath.css(block.call(*args)) }
38
- end
39
- @xpath
56
+ @format = :css
57
+ @css = block if block
58
+ @css
40
59
  end
41
60
 
42
61
  def match(&block)
@@ -50,15 +69,19 @@ module Capybara
50
69
  end
51
70
 
52
71
  def call(locator)
53
- @xpath.call(locator)
72
+ if @format==:css
73
+ @css.call(locator)
74
+ else
75
+ @xpath.call(locator)
76
+ end
54
77
  end
55
78
 
56
79
  def match?(locator)
57
80
  @match and @match.call(locator)
58
81
  end
59
82
 
60
- def filter(name, &block)
61
- @custom_filters[name] = block
83
+ def filter(name, options={}, &block)
84
+ @custom_filters[name] = Filter.new(name, block, options)
62
85
  end
63
86
  end
64
87
  end
@@ -79,8 +102,15 @@ Capybara.add_selector(:field) do
79
102
  xpath { |locator| XPath::HTML.field(locator) }
80
103
  filter(:checked) { |node, value| not(value ^ node.checked?) }
81
104
  filter(:unchecked) { |node, value| (value ^ node.checked?) }
105
+ filter(:disabled, :default => false) { |node, value| not(value ^ node.disabled?) }
82
106
  filter(:with) { |node, with| node.value == with }
83
- filter(:type) { |node, type| node[:type] == type }
107
+ filter(:type) do |node, type|
108
+ if ['textarea', 'select'].include?(type)
109
+ node.tag_name == type
110
+ else
111
+ node[:type] == type
112
+ end
113
+ end
84
114
  end
85
115
 
86
116
  Capybara.add_selector(:fieldset) do
@@ -90,6 +120,7 @@ end
90
120
  Capybara.add_selector(:link_or_button) do
91
121
  label "link or button"
92
122
  xpath { |locator| XPath::HTML.link_or_button(locator) }
123
+ filter(:disabled, :default => false) { |node, value| node.tag_name == "a" or not(value ^ node.disabled?) }
93
124
  end
94
125
 
95
126
  Capybara.add_selector(:link) do
@@ -101,11 +132,13 @@ end
101
132
 
102
133
  Capybara.add_selector(:button) do
103
134
  xpath { |locator| XPath::HTML.button(locator) }
135
+ filter(:disabled, :default => false) { |node, value| not(value ^ node.disabled?) }
104
136
  end
105
137
 
106
138
  Capybara.add_selector(:fillable_field) do
107
139
  label "field"
108
140
  xpath { |locator| XPath::HTML.fillable_field(locator) }
141
+ filter(:disabled, :default => false) { |node, value| not(value ^ node.disabled?) }
109
142
  end
110
143
 
111
144
  Capybara.add_selector(:radio_button) do
@@ -113,12 +146,14 @@ Capybara.add_selector(:radio_button) do
113
146
  xpath { |locator| XPath::HTML.radio_button(locator) }
114
147
  filter(:checked) { |node, value| not(value ^ node.checked?) }
115
148
  filter(:unchecked) { |node, value| (value ^ node.checked?) }
149
+ filter(:disabled, :default => false) { |node, value| not(value ^ node.disabled?) }
116
150
  end
117
151
 
118
152
  Capybara.add_selector(:checkbox) do
119
153
  xpath { |locator| XPath::HTML.checkbox(locator) }
120
154
  filter(:checked) { |node, value| not(value ^ node.checked?) }
121
155
  filter(:unchecked) { |node, value| (value ^ node.checked?) }
156
+ filter(:disabled, :default => false) { |node, value| not(value ^ node.disabled?) }
122
157
  end
123
158
 
124
159
  Capybara.add_selector(:select) do
@@ -133,6 +168,7 @@ Capybara.add_selector(:select) do
133
168
  actual = node.all(:xpath, './/option').select { |option| option.selected? }.map { |option| option.text }
134
169
  [selected].flatten.sort == actual.sort
135
170
  end
171
+ filter(:disabled, :default => false) { |node, value| not(value ^ node.disabled?) }
136
172
  end
137
173
 
138
174
  Capybara.add_selector(:option) do
@@ -142,10 +178,7 @@ end
142
178
  Capybara.add_selector(:file_field) do
143
179
  label "file field"
144
180
  xpath { |locator| XPath::HTML.file_field(locator) }
145
- end
146
-
147
- Capybara.add_selector(:content) do
148
- xpath { |content| XPath::HTML.content(content) }
181
+ filter(:disabled, :default => false) { |node, value| not(value ^ node.disabled?) }
149
182
  end
150
183
 
151
184
  Capybara.add_selector(:table) do
@@ -1,4 +1,13 @@
1
- require 'selenium-webdriver'
1
+ begin
2
+ require 'selenium-webdriver'
3
+ rescue LoadError => e
4
+ if e.message =~ /selenium-webdriver/
5
+ raise LoadError, "Capybara's selenium driver is unable to load `selenium-webdriver`, please install the gem and add `gem 'selenium-webdriver'` to your Gemfile if you are using bundler."
6
+ else
7
+ raise e
8
+ end
9
+ end
10
+
2
11
 
3
12
  class Capybara::Selenium::Driver < Capybara::Driver::Base
4
13
  DEFAULT_OPTIONS = {
@@ -27,6 +36,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
27
36
  @app = app
28
37
  @browser = nil
29
38
  @exit_status = nil
39
+ @frame_handles = {}
30
40
  @options = DEFAULT_OPTIONS.merge(options)
31
41
  end
32
42
 
@@ -38,14 +48,22 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
38
48
  browser.page_source
39
49
  end
40
50
 
51
+ def title
52
+ browser.title
53
+ end
54
+
41
55
  def current_url
42
56
  browser.current_url
43
57
  end
44
58
 
45
- def find(selector)
59
+ def find_xpath(selector)
46
60
  browser.find_elements(:xpath, selector).map { |node| Capybara::Selenium::Node.new(self, node) }
47
61
  end
48
62
 
63
+ def find_css(selector)
64
+ browser.find_elements(:css, selector).map { |node| Capybara::Selenium::Node.new(self, node) }
65
+ end
66
+
49
67
  def wait?; true; end
50
68
  def needs_server?; true; end
51
69
 
@@ -74,12 +92,29 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
74
92
  end
75
93
  end
76
94
 
77
- def within_frame(frame_id)
78
- old_window = browser.window_handle
79
- browser.switch_to.frame(frame_id)
95
+ ##
96
+ #
97
+ # Webdriver supports frame name, id, index(zero-based) or {Capybara::Element} to find iframe
98
+ #
99
+ # @overload within_frame(index)
100
+ # @param [Integer] index index of a frame
101
+ # @overload within_frame(name_or_id)
102
+ # @param [String] name_or_id name or id of a frame
103
+ # @overload within_frame(element)
104
+ # @param [Capybara::Node::Base] a_node frame element
105
+ #
106
+ def within_frame(frame_handle)
107
+ @frame_handles[browser.window_handle] ||= []
108
+ frame_handle = frame_handle.native if frame_handle.is_a?(Capybara::Node::Base)
109
+ @frame_handles[browser.window_handle] << frame_handle
110
+ a=browser.switch_to.frame(frame_handle)
80
111
  yield
81
112
  ensure
82
- browser.switch_to.window old_window
113
+ # There doesnt appear to be any way in Webdriver to move back to a parent frame
114
+ # other than going back to the root and then reiterating down
115
+ @frame_handles[browser.window_handle].pop
116
+ browser.switch_to.default_content
117
+ @frame_handles[browser.window_handle].each { |fh| browser.switch_to.frame(fh) }
83
118
  end
84
119
 
85
120
  def find_window( selector )
@@ -111,4 +146,5 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
111
146
  def invalid_element_errors
112
147
  [Selenium::WebDriver::Error::StaleElementReferenceError, Selenium::WebDriver::Error::UnhandledError, Selenium::WebDriver::Error::ElementNotVisibleError]
113
148
  end
149
+
114
150
  end