capybara 2.3.0 → 2.4.0

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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +21 -0
  3. data/README.md +50 -12
  4. data/lib/capybara.rb +8 -1
  5. data/lib/capybara/driver/base.rb +28 -0
  6. data/lib/capybara/driver/node.rb +3 -2
  7. data/lib/capybara/helpers.rb +2 -3
  8. data/lib/capybara/node/actions.rb +5 -2
  9. data/lib/capybara/node/base.rb +10 -0
  10. data/lib/capybara/node/document.rb +2 -0
  11. data/lib/capybara/node/document_matchers.rb +68 -0
  12. data/lib/capybara/node/element.rb +17 -2
  13. data/lib/capybara/node/finders.rb +5 -20
  14. data/lib/capybara/node/matchers.rb +101 -71
  15. data/lib/capybara/node/simple.rb +9 -15
  16. data/lib/capybara/queries/base_query.rb +29 -0
  17. data/lib/capybara/queries/text_query.rb +56 -0
  18. data/lib/capybara/queries/title_query.rb +40 -0
  19. data/lib/capybara/query.rb +30 -20
  20. data/lib/capybara/rack_test/node.rb +11 -3
  21. data/lib/capybara/result.rb +1 -1
  22. data/lib/capybara/rspec/features.rb +38 -21
  23. data/lib/capybara/rspec/matchers.rb +53 -38
  24. data/lib/capybara/selector.rb +68 -14
  25. data/lib/capybara/selenium/driver.rb +54 -6
  26. data/lib/capybara/selenium/node.rb +4 -2
  27. data/lib/capybara/session.rb +109 -35
  28. data/lib/capybara/spec/public/test.js +34 -1
  29. data/lib/capybara/spec/session/accept_alert_spec.rb +57 -0
  30. data/lib/capybara/spec/session/accept_confirm_spec.rb +19 -0
  31. data/lib/capybara/spec/session/accept_prompt_spec.rb +49 -0
  32. data/lib/capybara/spec/session/assert_text.rb +195 -0
  33. data/lib/capybara/spec/session/assert_title.rb +69 -0
  34. data/lib/capybara/spec/session/dismiss_confirm_spec.rb +35 -0
  35. data/lib/capybara/spec/session/dismiss_prompt_spec.rb +19 -0
  36. data/lib/capybara/spec/session/find_field_spec.rb +6 -0
  37. data/lib/capybara/spec/session/has_text_spec.rb +1 -1
  38. data/lib/capybara/spec/session/node_spec.rb +16 -1
  39. data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +1 -1
  40. data/lib/capybara/spec/session/visit_spec.rb +5 -0
  41. data/lib/capybara/spec/views/with_html.erb +4 -0
  42. data/lib/capybara/spec/views/with_js.erb +17 -0
  43. data/lib/capybara/version.rb +1 -1
  44. data/spec/dsl_spec.rb +3 -1
  45. data/spec/rack_test_spec.rb +12 -1
  46. data/spec/rspec/features_spec.rb +1 -1
  47. data/spec/rspec/matchers_spec.rb +113 -20
  48. data/spec/selenium_spec.rb +10 -1
  49. metadata +13 -2
@@ -27,7 +27,11 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
27
27
  elsif input_field?
28
28
  set_input(value)
29
29
  elsif textarea?
30
- native.content = value.to_s unless self[:readonly]
30
+ if self[:readonly]
31
+ warn "Attempt to set readonly element with value: #{value} \n * This will raise an exception in a future version of Capybara"
32
+ else
33
+ native.content = value.to_s
34
+ end
31
35
  end
32
36
  end
33
37
 
@@ -46,7 +50,7 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
46
50
  end
47
51
 
48
52
  def click
49
- if tag_name == 'a'
53
+ if tag_name == 'a' && !self[:href].nil?
50
54
  method = self["data-method"] if driver.options[:respect_data_method]
51
55
  method ||= :get
52
56
  driver.follow(method, self[:href].to_s)
@@ -165,7 +169,11 @@ private
165
169
  end
166
170
  native.remove
167
171
  else
168
- native['value'] = value.to_s unless self[:readonly]
172
+ if self[:readonly]
173
+ warn "Attempt to set readonly element with value: #{value} \n *This will raise an exception in a future version of Capybara"
174
+ else
175
+ native['value'] = value.to_s
176
+ end
169
177
  end
170
178
  end
171
179
 
@@ -51,7 +51,7 @@ module Capybara
51
51
  end
52
52
 
53
53
  def negative_failure_message
54
- failure_message.sub(/(to be found|to find)/, 'not \1')
54
+ failure_message.sub(/(to find)/, 'not \1')
55
55
  end
56
56
  end
57
57
  end
@@ -1,28 +1,45 @@
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
- alias :xscenario :xit
8
- alias :given :let
9
- alias :given! :let!
10
- alias :feature :describe
1
+ if RSpec::Core::Version::STRING.to_f >= 3.0
2
+ RSpec.shared_context "Capybara Features", :capybara_feature => true do
3
+ instance_eval do
4
+ alias background before
5
+ alias given let
6
+ alias given! let!
7
+ end
8
+ end
9
+
10
+ RSpec.configure do |config|
11
+ config.alias_example_group_to :feature, :capybara_feature => true, :type => :feature
12
+ config.alias_example_to :scenario
13
+ config.alias_example_to :xscenario, :skip => "Temporarily disabled with xscenario"
14
+ # config.alias_example_to :fscenario, :focus => true
15
+ end
16
+ else
17
+ module Capybara
18
+ module Features
19
+ def self.included(base)
20
+ base.instance_eval do
21
+ alias :background :before
22
+ alias :scenario :it
23
+ alias :xscenario :xit
24
+ alias :given :let
25
+ alias :given! :let!
26
+ alias :feature :describe
27
+ end
11
28
  end
12
29
  end
13
30
  end
14
- end
15
31
 
16
32
 
17
- def self.feature(*args, &block)
18
- options = if args.last.is_a?(Hash) then args.pop else {} end
19
- options[:capybara_feature] = true
20
- options[:type] = :feature
21
- options[:caller] ||= caller
22
- args.push(options)
33
+ def self.feature(*args, &block)
34
+ options = if args.last.is_a?(Hash) then args.pop else {} end
35
+ options[:capybara_feature] = true
36
+ options[:type] = :feature
37
+ options[:caller] ||= caller
38
+ args.push(options)
23
39
 
24
- #call describe on RSpec in case user has expose_dsl_globally set to false
25
- RSpec.describe(*args, &block)
26
- end
40
+ #call describe on RSpec in case user has expose_dsl_globally set to false
41
+ RSpec.describe(*args, &block)
42
+ end
27
43
 
28
- RSpec.configuration.include Capybara::Features, :capybara_feature => true
44
+ RSpec.configuration.include Capybara::Features, :capybara_feature => true
45
+ end
@@ -1,6 +1,8 @@
1
1
  module Capybara
2
2
  module RSpecMatchers
3
3
  class Matcher
4
+ include ::RSpec::Matchers::Composable if defined?(::RSpec::Version) && ::RSpec::Version::STRING.to_f >= 3.0
5
+
4
6
  def wrap(actual)
5
7
  if actual.respond_to?("has_selector?")
6
8
  actual
@@ -11,16 +13,24 @@ module Capybara
11
13
  end
12
14
 
13
15
  class HaveSelector < Matcher
16
+ attr_reader :failure_message, :failure_message_when_negated
17
+
14
18
  def initialize(*args)
15
19
  @args = args
16
20
  end
17
21
 
18
22
  def matches?(actual)
19
23
  wrap(actual).assert_selector(*@args)
24
+ rescue Capybara::ExpectationNotMet => e
25
+ @failure_message = e.message
26
+ return false
20
27
  end
21
28
 
22
29
  def does_not_match?(actual)
23
30
  wrap(actual).assert_no_selector(*@args)
31
+ rescue Capybara::ExpectationNotMet => e
32
+ @failure_message_when_negated = e.message
33
+ return false
24
34
  end
25
35
 
26
36
  def description
@@ -30,41 +40,40 @@ module Capybara
30
40
  def query
31
41
  @query ||= Capybara::Query.new(*@args)
32
42
  end
43
+
44
+ # RSpec 2 compatibility:
45
+ alias_method :failure_message_for_should, :failure_message
46
+ alias_method :failure_message_for_should_not, :failure_message_when_negated
33
47
  end
34
48
 
35
49
  class HaveText < Matcher
36
50
  attr_reader :type, :content, :options
37
51
 
52
+ attr_reader :failure_message, :failure_message_when_negated
53
+
38
54
  def initialize(*args)
55
+ @args = args.dup
56
+
57
+ # are set just for backwards compatability
39
58
  @type = args.shift if args.first.is_a?(Symbol)
40
59
  @content = args.shift
41
60
  @options = (args.first.is_a?(Hash))? args.first : {}
42
61
  end
43
62
 
44
63
  def matches?(actual)
45
- @actual = wrap(actual)
46
- @actual.has_text?(type, content, options)
64
+ wrap(actual).assert_text(*@args)
65
+ rescue Capybara::ExpectationNotMet => e
66
+ @failure_message = e.message
67
+ return false
47
68
  end
48
69
 
49
70
  def does_not_match?(actual)
50
- @actual = wrap(actual)
51
- @actual.has_no_text?(type, content, options)
52
- end
53
-
54
- def failure_message
55
- message = Capybara::Helpers.failure_message(description, options)
56
- message << " in #{format(@actual.text(type))}"
57
- message
58
- end
59
-
60
- def failure_message_when_negated
61
- failure_message.sub(/(to find)/, 'not \1')
71
+ wrap(actual).assert_no_text(*@args)
72
+ rescue Capybara::ExpectationNotMet => e
73
+ @failure_message_when_negated = e.message
74
+ return false
62
75
  end
63
76
 
64
- # RSpec 2 compatibility:
65
- alias_method :failure_message_for_should, :failure_message
66
- alias_method :failure_message_for_should_not, :failure_message_when_negated
67
-
68
77
  def description
69
78
  "text #{format(content)}"
70
79
  end
@@ -73,40 +82,45 @@ module Capybara
73
82
  content = Capybara::Helpers.normalize_whitespace(content) unless content.is_a? Regexp
74
83
  content.inspect
75
84
  end
85
+
86
+ # RSpec 2 compatibility:
87
+ alias_method :failure_message_for_should, :failure_message
88
+ alias_method :failure_message_for_should_not, :failure_message_when_negated
76
89
  end
77
90
 
78
91
  class HaveTitle < Matcher
79
92
  attr_reader :title
80
93
 
81
- def initialize(title)
82
- @title = title
94
+ attr_reader :failure_message, :failure_message_when_negated
95
+
96
+ def initialize(*args)
97
+ @args = args
98
+
99
+ # are set just for backwards compatability
100
+ @title = args.first
83
101
  end
84
102
 
85
103
  def matches?(actual)
86
- @actual = wrap(actual)
87
- @actual.has_title?(title)
104
+ wrap(actual).assert_title(*@args)
105
+ rescue Capybara::ExpectationNotMet => e
106
+ @failure_message = e.message
107
+ return false
88
108
  end
89
109
 
90
110
  def does_not_match?(actual)
91
- @actual = wrap(actual)
92
- @actual.has_no_title?(title)
93
- end
94
-
95
- def failure_message
96
- "expected there to be title #{title.inspect} in #{@actual.title.inspect}"
111
+ wrap(actual).assert_no_title(*@args)
112
+ rescue Capybara::ExpectationNotMet => e
113
+ @failure_message_when_negated = e.message
114
+ return false
97
115
  end
98
116
 
99
- def failure_message_when_negated
100
- "expected there not to be title #{title.inspect} in #{@actual.title.inspect}"
117
+ def description
118
+ "have title #{title.inspect}"
101
119
  end
102
120
 
103
121
  # RSpec 2 compatibility:
104
122
  alias_method :failure_message_for_should, :failure_message
105
123
  alias_method :failure_message_for_should_not, :failure_message_when_negated
106
-
107
- def description
108
- "have title #{title.inspect}"
109
- end
110
124
  end
111
125
 
112
126
  class BecomeClosed
@@ -117,10 +131,11 @@ module Capybara
117
131
  def matches?(window)
118
132
  @window = window
119
133
  start_time = Time.now
120
- while window.exists? && (Time.now - start_time) < @wait_time
134
+ while window.exists?
135
+ return false if (Time.now - start_time) > @wait_time
121
136
  sleep 0.05
122
137
  end
123
- window.closed?
138
+ true
124
139
  end
125
140
 
126
141
  def failure_message
@@ -153,8 +168,8 @@ module Capybara
153
168
  end
154
169
  alias_method :have_content, :have_text
155
170
 
156
- def have_title(title)
157
- HaveTitle.new(title)
171
+ def have_title(title, options = {})
172
+ HaveTitle.new(title, options)
158
173
  end
159
174
 
160
175
  def have_link(locator, options={})
@@ -5,6 +5,7 @@ module Capybara
5
5
  @name = name
6
6
  @block = block
7
7
  @options = options
8
+ @options[:valid_values] = [true,false] if options[:boolean]
8
9
  end
9
10
 
10
11
  def default?
@@ -16,6 +17,9 @@ module Capybara
16
17
  end
17
18
 
18
19
  def matches?(node, value)
20
+ if @options.has_key?(:valid_values) && !Array(@options[:valid_values]).include?(value)
21
+ warn "Invalid value #{value.inspect} passed to filter #{@name}"
22
+ end
19
23
  @block.call(node, value)
20
24
  end
21
25
  end
@@ -42,6 +46,7 @@ module Capybara
42
46
  @match = nil
43
47
  @label = nil
44
48
  @failure_message = nil
49
+ @description = nil
45
50
  instance_eval(&block)
46
51
  end
47
52
 
@@ -68,6 +73,10 @@ module Capybara
68
73
  @label
69
74
  end
70
75
 
76
+ def description(options={})
77
+ (@description && @description.call(options)).to_s
78
+ end
79
+
71
80
  def call(locator)
72
81
  if @format==:css
73
82
  @css.call(locator)
@@ -83,6 +92,10 @@ module Capybara
83
92
  def filter(name, options={}, &block)
84
93
  @custom_filters[name] = Filter.new(name, block, options)
85
94
  end
95
+
96
+ def describe &block
97
+ @description = block
98
+ end
86
99
  end
87
100
  end
88
101
 
@@ -100,9 +113,9 @@ end
100
113
 
101
114
  Capybara.add_selector(:field) do
102
115
  xpath { |locator| XPath::HTML.field(locator) }
103
- filter(:checked) { |node, value| not(value ^ node.checked?) }
104
- filter(:unchecked) { |node, value| (value ^ node.checked?) }
105
- filter(:disabled, :default => false) { |node, value| not(value ^ node.disabled?) }
116
+ filter(:checked, boolean: true) { |node, value| not(value ^ node.checked?) }
117
+ filter(:unchecked, boolean: true) { |node, value| (value ^ node.checked?) }
118
+ filter(:disabled, default: false, boolean: true) { |node, value| not(value ^ node.disabled?) }
106
119
  filter(:with) { |node, with| node.value == with.to_s }
107
120
  filter(:type) do |node, type|
108
121
  if ['textarea', 'select'].include?(type)
@@ -111,6 +124,16 @@ Capybara.add_selector(:field) do
111
124
  node[:type] == type
112
125
  end
113
126
  end
127
+ describe do |options|
128
+ desc, states = "", []
129
+ desc << " of type #{options[:type].inspect}" if options[:type]
130
+ desc << " with value #{options[:with].to_s.inspect}" if options.has_key?(:with)
131
+ states << 'checked' if options[:checked] || (options.has_key?(:unchecked) && !options[:unchecked])
132
+ states << 'not checked' if options[:unchecked] || (options.has_key?(:checked) && !options[:checked])
133
+ states << 'disabled' if options[:disabled]
134
+ desc << " that is #{states.join(' and ')}" unless states.empty?
135
+ desc
136
+ end
114
137
  end
115
138
 
116
139
  Capybara.add_selector(:fieldset) do
@@ -120,7 +143,8 @@ end
120
143
  Capybara.add_selector(:link_or_button) do
121
144
  label "link or button"
122
145
  xpath { |locator| XPath::HTML.link_or_button(locator) }
123
- filter(:disabled, :default => false) { |node, value| node.tag_name == "a" or not(value ^ node.disabled?) }
146
+ filter(:disabled, default: false, boolean: true) { |node, value| node.tag_name == "a" or not(value ^ node.disabled?) }
147
+ describe { |options| " that is disabled" if options[:disabled] }
124
148
  end
125
149
 
126
150
  Capybara.add_selector(:link) do
@@ -128,34 +152,55 @@ Capybara.add_selector(:link) do
128
152
  filter(:href) do |node, href|
129
153
  node.first(:xpath, XPath.axis(:self)[XPath.attr(:href).equals(href.to_s)])
130
154
  end
155
+ describe { |options| " with href #{options[:href].inspect}" if options[:href] }
131
156
  end
132
157
 
133
158
  Capybara.add_selector(:button) do
134
159
  xpath { |locator| XPath::HTML.button(locator) }
135
- filter(:disabled, :default => false) { |node, value| not(value ^ node.disabled?) }
160
+ filter(:disabled, default: false, boolean: true) { |node, value| not(value ^ node.disabled?) }
161
+ describe { |options| " that is disabled" if options[:disabled] }
136
162
  end
137
163
 
138
164
  Capybara.add_selector(:fillable_field) do
139
165
  label "field"
140
166
  xpath { |locator| XPath::HTML.fillable_field(locator) }
141
- filter(:disabled, :default => false) { |node, value| not(value ^ node.disabled?) }
167
+ filter(:disabled, default: false, boolean: true) { |node, value| not(value ^ node.disabled?) }
168
+ describe { |options| " that is disabled" if options[:disabled] }
142
169
  end
143
170
 
144
171
  Capybara.add_selector(:radio_button) do
145
172
  label "radio button"
146
173
  xpath { |locator| XPath::HTML.radio_button(locator) }
147
- filter(:checked) { |node, value| not(value ^ node.checked?) }
148
- filter(:unchecked) { |node, value| (value ^ node.checked?) }
174
+ filter(:checked, boolean: true) { |node, value| not(value ^ node.checked?) }
175
+ filter(:unchecked, boolean: true) { |node, value| (value ^ node.checked?) }
149
176
  filter(:option) { |node, value| node.value == value.to_s }
150
- filter(:disabled, :default => false) { |node, value| not(value ^ node.disabled?) }
177
+ filter(:disabled, default: false, boolean: true) { |node, value| not(value ^ node.disabled?) }
178
+ describe do |options|
179
+ desc, states = "", []
180
+ desc << " with value #{options[:option].inspect}" if options[:option]
181
+ states << 'checked' if options[:checked] || (options.has_key?(:unchecked) && !options[:unchecked])
182
+ states << 'not checked' if options[:unchecked] || (options.has_key?(:checked) && !options[:checked])
183
+ states << 'disabled' if options[:disabled]
184
+ desc << " that is #{states.join(' and ')}" unless states.empty?
185
+ desc
186
+ end
151
187
  end
152
188
 
153
189
  Capybara.add_selector(:checkbox) do
154
190
  xpath { |locator| XPath::HTML.checkbox(locator) }
155
- filter(:checked) { |node, value| not(value ^ node.checked?) }
156
- filter(:unchecked) { |node, value| (value ^ node.checked?) }
191
+ filter(:checked, boolean: true) { |node, value| not(value ^ node.checked?) }
192
+ filter(:unchecked, boolean: true) { |node, value| (value ^ node.checked?) }
157
193
  filter(:option) { |node, value| node.value == value.to_s }
158
- filter(:disabled, :default => false) { |node, value| not(value ^ node.disabled?) }
194
+ filter(:disabled, default: false, boolean: true) { |node, value| not(value ^ node.disabled?) }
195
+ describe do |options|
196
+ desc, states = "", []
197
+ desc << " with value #{options[:option].inspect}" if options[:option]
198
+ states << 'checked' if options[:checked] || (options.has_key?(:unchecked) && !options[:unchecked])
199
+ states << 'not checked' if options[:unchecked] || (options.has_key?(:checked) && !options[:checked])
200
+ states << 'disabled' if options[:disabled]
201
+ desc << " that is #{states.join(' and ')}" unless states.empty?
202
+ desc
203
+ end
159
204
  end
160
205
 
161
206
  Capybara.add_selector(:select) do
@@ -170,7 +215,15 @@ Capybara.add_selector(:select) do
170
215
  actual = node.all(:xpath, './/option').select { |option| option.selected? }.map { |option| option.text }
171
216
  [selected].flatten.sort == actual.sort
172
217
  end
173
- filter(:disabled, :default => false) { |node, value| not(value ^ node.disabled?) }
218
+ filter(:disabled, default: false, boolean: true) { |node, value| not(value ^ node.disabled?) }
219
+ describe do |options|
220
+ desc = ""
221
+ desc << " with options #{options[:options].inspect}" if options[:options]
222
+ desc << " with at least options #{options[:with_options].inspect}" if options[:with_options]
223
+ desc << " with #{options[:selected].inspect} selected" if options[:selected]
224
+ desc << " that is disabled" if options[:disabled]
225
+ desc
226
+ end
174
227
  end
175
228
 
176
229
  Capybara.add_selector(:option) do
@@ -180,7 +233,8 @@ end
180
233
  Capybara.add_selector(:file_field) do
181
234
  label "file field"
182
235
  xpath { |locator| XPath::HTML.file_field(locator) }
183
- filter(:disabled, :default => false) { |node, value| not(value ^ node.disabled?) }
236
+ filter(:disabled, default: false, boolean: true) { |node, value| not(value ^ node.disabled?) }
237
+ describe { |options| " that is disabled" if options[:disabled] }
184
238
  end
185
239
 
186
240
  Capybara.add_selector(:table) do