capybara 2.0.3 → 2.1.0.beta1

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