capybara 2.3.0 → 2.4.0

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