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.
- 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
|