capybara 2.3.0 → 2.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/History.md +21 -0
- data/README.md +50 -12
- data/lib/capybara.rb +8 -1
- data/lib/capybara/driver/base.rb +28 -0
- data/lib/capybara/driver/node.rb +3 -2
- data/lib/capybara/helpers.rb +2 -3
- data/lib/capybara/node/actions.rb +5 -2
- data/lib/capybara/node/base.rb +10 -0
- data/lib/capybara/node/document.rb +2 -0
- data/lib/capybara/node/document_matchers.rb +68 -0
- data/lib/capybara/node/element.rb +17 -2
- data/lib/capybara/node/finders.rb +5 -20
- data/lib/capybara/node/matchers.rb +101 -71
- data/lib/capybara/node/simple.rb +9 -15
- data/lib/capybara/queries/base_query.rb +29 -0
- data/lib/capybara/queries/text_query.rb +56 -0
- data/lib/capybara/queries/title_query.rb +40 -0
- data/lib/capybara/query.rb +30 -20
- data/lib/capybara/rack_test/node.rb +11 -3
- data/lib/capybara/result.rb +1 -1
- data/lib/capybara/rspec/features.rb +38 -21
- data/lib/capybara/rspec/matchers.rb +53 -38
- data/lib/capybara/selector.rb +68 -14
- data/lib/capybara/selenium/driver.rb +54 -6
- data/lib/capybara/selenium/node.rb +4 -2
- data/lib/capybara/session.rb +109 -35
- data/lib/capybara/spec/public/test.js +34 -1
- data/lib/capybara/spec/session/accept_alert_spec.rb +57 -0
- data/lib/capybara/spec/session/accept_confirm_spec.rb +19 -0
- data/lib/capybara/spec/session/accept_prompt_spec.rb +49 -0
- data/lib/capybara/spec/session/assert_text.rb +195 -0
- data/lib/capybara/spec/session/assert_title.rb +69 -0
- data/lib/capybara/spec/session/dismiss_confirm_spec.rb +35 -0
- data/lib/capybara/spec/session/dismiss_prompt_spec.rb +19 -0
- data/lib/capybara/spec/session/find_field_spec.rb +6 -0
- data/lib/capybara/spec/session/has_text_spec.rb +1 -1
- data/lib/capybara/spec/session/node_spec.rb +16 -1
- data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +1 -1
- data/lib/capybara/spec/session/visit_spec.rb +5 -0
- data/lib/capybara/spec/views/with_html.erb +4 -0
- data/lib/capybara/spec/views/with_js.erb +17 -0
- data/lib/capybara/version.rb +1 -1
- data/spec/dsl_spec.rb +3 -1
- data/spec/rack_test_spec.rb +12 -1
- data/spec/rspec/features_spec.rb +1 -1
- data/spec/rspec/matchers_spec.rb +113 -20
- data/spec/selenium_spec.rb +10 -1
- 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
|
-
|
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
|
-
|
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
|
|
data/lib/capybara/result.rb
CHANGED
@@ -1,28 +1,45 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
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
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
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
|
-
|
25
|
-
|
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
|
-
|
46
|
-
|
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
|
-
|
51
|
-
|
52
|
-
|
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
|
-
|
82
|
-
|
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
|
-
|
87
|
-
|
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
|
-
|
92
|
-
|
93
|
-
|
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
|
100
|
-
"
|
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?
|
134
|
+
while window.exists?
|
135
|
+
return false if (Time.now - start_time) > @wait_time
|
121
136
|
sleep 0.05
|
122
137
|
end
|
123
|
-
|
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={})
|
data/lib/capybara/selector.rb
CHANGED
@@ -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, :
|
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, :
|
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, :
|
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, :
|
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, :
|
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, :
|
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, :
|
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, :
|
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
|