capybara 1.1.4 → 2.0.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.
- data/{History.txt → History.md} +138 -0
- data/License.txt +22 -0
- data/README.md +850 -0
- data/lib/capybara/cucumber.rb +2 -5
- data/lib/capybara/driver/base.rb +6 -6
- data/lib/capybara/driver/node.rb +3 -2
- data/lib/capybara/dsl.rb +13 -124
- data/lib/capybara/helpers.rb +33 -0
- data/lib/capybara/node/actions.rb +16 -30
- data/lib/capybara/node/base.rb +56 -13
- data/lib/capybara/node/element.rb +18 -30
- data/lib/capybara/node/finders.rb +28 -90
- data/lib/capybara/node/matchers.rb +121 -73
- data/lib/capybara/node/simple.rb +13 -11
- data/lib/capybara/query.rb +78 -0
- data/lib/capybara/rack_test/browser.rb +27 -39
- data/lib/capybara/rack_test/driver.rb +13 -3
- data/lib/capybara/rack_test/node.rb +31 -2
- data/lib/capybara/result.rb +72 -0
- data/lib/capybara/rspec/features.rb +4 -1
- data/lib/capybara/rspec/matchers.rb +33 -63
- data/lib/capybara/rspec.rb +7 -4
- data/lib/capybara/selector.rb +97 -34
- data/lib/capybara/selenium/driver.rb +15 -62
- data/lib/capybara/selenium/node.rb +14 -21
- data/lib/capybara/server.rb +32 -27
- data/lib/capybara/session.rb +90 -50
- data/lib/capybara/spec/fixtures/another_test_file.txt +1 -0
- data/lib/capybara/spec/public/jquery-ui.js +791 -0
- data/lib/capybara/spec/public/jquery.js +9046 -0
- data/lib/capybara/spec/public/test.js +3 -0
- data/lib/capybara/spec/session/all_spec.rb +61 -59
- data/lib/capybara/spec/session/assert_selector.rb +123 -0
- data/lib/capybara/spec/session/attach_file_spec.rb +72 -55
- data/lib/capybara/spec/session/body_spec.rb +21 -0
- data/lib/capybara/spec/session/check_spec.rb +68 -48
- data/lib/capybara/spec/session/choose_spec.rb +32 -18
- data/lib/capybara/spec/session/click_button_spec.rb +263 -232
- data/lib/capybara/spec/session/click_link_or_button_spec.rb +40 -29
- data/lib/capybara/spec/session/click_link_spec.rb +96 -96
- data/lib/capybara/spec/session/current_url_spec.rb +88 -10
- data/lib/capybara/spec/session/evaluate_script_spec.rb +6 -0
- data/lib/capybara/spec/session/execute_script_spec.rb +7 -0
- data/lib/capybara/spec/session/fill_in_spec.rb +119 -103
- data/lib/capybara/spec/session/find_button_spec.rb +16 -14
- data/lib/capybara/spec/session/find_by_id_spec.rb +16 -14
- data/lib/capybara/spec/session/find_field_spec.rb +23 -21
- data/lib/capybara/spec/session/find_link_spec.rb +15 -14
- data/lib/capybara/spec/session/find_spec.rb +93 -115
- data/lib/capybara/spec/session/first_spec.rb +51 -85
- data/lib/capybara/spec/session/has_button_spec.rb +22 -24
- data/lib/capybara/spec/session/has_css_spec.rb +190 -205
- data/lib/capybara/spec/session/has_field_spec.rb +170 -144
- data/lib/capybara/spec/session/has_link_spec.rb +26 -29
- data/lib/capybara/spec/session/has_select_spec.rb +161 -109
- data/lib/capybara/spec/session/has_selector_spec.rb +94 -100
- data/lib/capybara/spec/session/has_table_spec.rb +22 -88
- data/lib/capybara/spec/session/has_text_spec.rb +195 -0
- data/lib/capybara/spec/session/has_xpath_spec.rb +100 -96
- data/lib/capybara/spec/session/headers.rb +4 -17
- data/lib/capybara/spec/session/html_spec.rb +15 -0
- data/lib/capybara/spec/session/node_spec.rb +205 -0
- data/lib/capybara/spec/session/reset_session_spec.rb +42 -0
- data/lib/capybara/spec/session/response_code.rb +4 -17
- data/lib/capybara/spec/session/save_page_spec.rb +46 -0
- data/lib/capybara/spec/session/screenshot.rb +13 -0
- data/lib/capybara/spec/session/select_spec.rb +99 -88
- data/lib/capybara/spec/session/source_spec.rb +12 -0
- data/lib/capybara/spec/session/text_spec.rb +15 -12
- data/lib/capybara/spec/session/uncheck_spec.rb +22 -17
- data/lib/capybara/spec/session/unselect_spec.rb +69 -58
- data/lib/capybara/spec/session/visit_spec.rb +74 -0
- data/lib/capybara/spec/session/within_frame_spec.rb +31 -0
- data/lib/capybara/spec/session/within_spec.rb +118 -131
- data/lib/capybara/spec/session/within_window_spec.rb +38 -0
- data/lib/capybara/spec/spec_helper.rb +84 -0
- data/lib/capybara/spec/test_app.rb +32 -6
- data/lib/capybara/spec/views/form.erb +12 -10
- data/lib/capybara/spec/views/host_links.erb +2 -2
- data/lib/capybara/spec/views/tables.erb +6 -66
- data/lib/capybara/spec/views/with_html.erb +9 -4
- data/lib/capybara/spec/views/with_js.erb +11 -7
- data/lib/capybara/version.rb +1 -1
- data/lib/capybara.rb +125 -6
- data/spec/basic_node_spec.rb +17 -5
- data/spec/capybara_spec.rb +9 -0
- data/spec/dsl_spec.rb +31 -17
- data/spec/rack_test_spec.rb +157 -0
- data/spec/result_spec.rb +51 -0
- data/spec/rspec/features_spec.rb +19 -2
- data/spec/rspec/matchers_spec.rb +170 -89
- data/spec/rspec_spec.rb +1 -3
- data/spec/selenium_spec.rb +53 -0
- data/spec/server_spec.rb +37 -25
- data/spec/spec_helper.rb +1 -30
- metadata +39 -31
- data/README.rdoc +0 -722
- data/lib/capybara/spec/driver.rb +0 -301
- data/lib/capybara/spec/session/current_host_spec.rb +0 -68
- data/lib/capybara/spec/session/has_content_spec.rb +0 -106
- data/lib/capybara/spec/session/javascript.rb +0 -306
- data/lib/capybara/spec/session.rb +0 -154
- data/lib/capybara/util/save_and_open_page.rb +0 -44
- data/lib/capybara/util/timeout.rb +0 -27
- data/spec/driver/rack_test_driver_spec.rb +0 -89
- data/spec/driver/selenium_driver_spec.rb +0 -37
- data/spec/save_and_open_page_spec.rb +0 -155
- data/spec/session/rack_test_session_spec.rb +0 -55
- data/spec/session/selenium_session_spec.rb +0 -26
- data/spec/string_spec.rb +0 -77
- data/spec/timeout_spec.rb +0 -28
|
@@ -18,48 +18,45 @@ class Capybara::RackTest::Browser
|
|
|
18
18
|
|
|
19
19
|
def visit(path, attributes = {})
|
|
20
20
|
reset_host!
|
|
21
|
-
|
|
22
|
-
follow_redirects!
|
|
21
|
+
process_and_follow_redirects(:get, path, attributes)
|
|
23
22
|
end
|
|
24
23
|
|
|
25
24
|
def submit(method, path, attributes)
|
|
26
25
|
path = request_path if not path or path.empty?
|
|
27
|
-
|
|
28
|
-
follow_redirects!
|
|
26
|
+
process_and_follow_redirects(method, path, attributes, {'HTTP_REFERER' => current_url})
|
|
29
27
|
end
|
|
30
28
|
|
|
31
29
|
def follow(method, path, attributes = {})
|
|
32
30
|
return if path.gsub(/^#{request_path}/, '').start_with?('#')
|
|
33
|
-
|
|
34
|
-
follow_redirects!
|
|
31
|
+
process_and_follow_redirects(method, path, attributes, {'HTTP_REFERER' => current_url})
|
|
35
32
|
end
|
|
36
33
|
|
|
37
|
-
def
|
|
38
|
-
|
|
39
|
-
|
|
34
|
+
def process_and_follow_redirects(method, path, attributes = {}, env = {})
|
|
35
|
+
process(method, path, attributes, env)
|
|
36
|
+
if driver.follow_redirects?
|
|
37
|
+
driver.redirect_limit.times do
|
|
38
|
+
process(:get, last_response["Location"], {}, env) if last_response.redirect?
|
|
39
|
+
end
|
|
40
|
+
raise Capybara::InfiniteRedirectError, "redirected more than #{driver.redirect_limit} times, check for infinite redirects." if last_response.redirect?
|
|
40
41
|
end
|
|
41
|
-
raise Capybara::InfiniteRedirectError, "redirected more than 5 times, check for infinite redirects." if last_response.redirect?
|
|
42
42
|
end
|
|
43
43
|
|
|
44
|
-
def process(method, path, attributes = {})
|
|
44
|
+
def process(method, path, attributes = {}, env = {})
|
|
45
45
|
new_uri = URI.parse(path)
|
|
46
|
-
|
|
46
|
+
method.downcase! unless method.is_a? Symbol
|
|
47
47
|
|
|
48
|
-
if
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
new_uri.path = request_path if path.start_with?("?")
|
|
49
|
+
new_uri.path = request_path.sub(%r(/[^/]*$), '/') + new_uri.path unless new_uri.path.start_with?('/')
|
|
50
|
+
new_uri.scheme ||= @current_scheme
|
|
51
|
+
new_uri.host ||= @current_host
|
|
52
|
+
new_uri.port ||= @current_port unless new_uri.default_port == @current_port
|
|
51
53
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
elsif not path.start_with?('/')
|
|
56
|
-
path = request_path.sub(%r(/[^/]*$), '/') + path
|
|
57
|
-
end
|
|
58
|
-
path = current_host + path
|
|
59
|
-
end
|
|
54
|
+
@current_scheme = new_uri.scheme
|
|
55
|
+
@current_host = new_uri.host
|
|
56
|
+
@current_port = new_uri.port
|
|
60
57
|
|
|
61
58
|
reset_cache!
|
|
62
|
-
send(method,
|
|
59
|
+
send(method, new_uri.to_s, attributes, env.merge(options[:headers] || {}))
|
|
63
60
|
end
|
|
64
61
|
|
|
65
62
|
def current_url
|
|
@@ -69,14 +66,17 @@ class Capybara::RackTest::Browser
|
|
|
69
66
|
end
|
|
70
67
|
|
|
71
68
|
def reset_host!
|
|
72
|
-
|
|
69
|
+
uri = URI.parse(Capybara.app_host || Capybara.default_host)
|
|
70
|
+
@current_scheme = uri.scheme
|
|
71
|
+
@current_host = uri.host
|
|
72
|
+
@current_port = uri.port
|
|
73
73
|
end
|
|
74
74
|
|
|
75
75
|
def reset_cache!
|
|
76
76
|
@dom = nil
|
|
77
77
|
end
|
|
78
78
|
|
|
79
|
-
def
|
|
79
|
+
def html
|
|
80
80
|
dom.to_xml
|
|
81
81
|
end
|
|
82
82
|
|
|
@@ -91,7 +91,7 @@ class Capybara::RackTest::Browser
|
|
|
91
91
|
def source
|
|
92
92
|
last_response.body
|
|
93
93
|
rescue Rack::Test::Error
|
|
94
|
-
|
|
94
|
+
""
|
|
95
95
|
end
|
|
96
96
|
|
|
97
97
|
protected
|
|
@@ -106,16 +106,4 @@ protected
|
|
|
106
106
|
rescue Rack::Test::Error
|
|
107
107
|
""
|
|
108
108
|
end
|
|
109
|
-
|
|
110
|
-
def env
|
|
111
|
-
env = {}
|
|
112
|
-
begin
|
|
113
|
-
env["HTTP_REFERER"] = last_request.url
|
|
114
|
-
rescue Rack::Test::Error
|
|
115
|
-
# no request yet
|
|
116
|
-
end
|
|
117
|
-
env.merge!(options[:headers]) if options[:headers]
|
|
118
|
-
env
|
|
119
|
-
end
|
|
120
|
-
|
|
121
109
|
end
|
|
@@ -6,7 +6,9 @@ require 'cgi'
|
|
|
6
6
|
|
|
7
7
|
class Capybara::RackTest::Driver < Capybara::Driver::Base
|
|
8
8
|
DEFAULT_OPTIONS = {
|
|
9
|
-
:respect_data_method =>
|
|
9
|
+
:respect_data_method => false,
|
|
10
|
+
:follow_redirects => true,
|
|
11
|
+
:redirect_limit => 5
|
|
10
12
|
}
|
|
11
13
|
attr_reader :app, :options
|
|
12
14
|
|
|
@@ -20,6 +22,14 @@ class Capybara::RackTest::Driver < Capybara::Driver::Base
|
|
|
20
22
|
@browser ||= Capybara::RackTest::Browser.new(self)
|
|
21
23
|
end
|
|
22
24
|
|
|
25
|
+
def follow_redirects?
|
|
26
|
+
@options[:follow_redirects]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def redirect_limit
|
|
30
|
+
@options[:redirect_limit]
|
|
31
|
+
end
|
|
32
|
+
|
|
23
33
|
def response
|
|
24
34
|
browser.last_response
|
|
25
35
|
end
|
|
@@ -56,8 +66,8 @@ class Capybara::RackTest::Driver < Capybara::Driver::Base
|
|
|
56
66
|
browser.find(selector)
|
|
57
67
|
end
|
|
58
68
|
|
|
59
|
-
def
|
|
60
|
-
browser.
|
|
69
|
+
def html
|
|
70
|
+
browser.html
|
|
61
71
|
end
|
|
62
72
|
|
|
63
73
|
def source
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
class Capybara::RackTest::Node < Capybara::Driver::Node
|
|
2
2
|
def text
|
|
3
|
-
|
|
3
|
+
Capybara::Helpers.normalize_whitespace(unnormalized_text)
|
|
4
4
|
end
|
|
5
5
|
|
|
6
6
|
def [](name)
|
|
@@ -12,6 +12,9 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
|
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
def set(value)
|
|
15
|
+
if (Array === value) && !self[:multiple]
|
|
16
|
+
raise ArgumentError.new "Value cannot be an Array when 'multiple' attribute is not present. Not a #{value.class}"
|
|
17
|
+
end
|
|
15
18
|
if tag_name == 'input' and type == 'radio'
|
|
16
19
|
other_radios_xpath = XPath.generate { |x| x.anywhere(:input)[x.attr(:name).equals(self[:name])] }.to_s
|
|
17
20
|
driver.dom.xpath(other_radios_xpath).each { |node| node.remove_attribute("checked") }
|
|
@@ -29,7 +32,17 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
|
|
|
29
32
|
# Firefox, allowing no input
|
|
30
33
|
value = value[0...self[:maxlength].to_i]
|
|
31
34
|
end
|
|
32
|
-
|
|
35
|
+
if Array === value #Assert multiple attribute is present
|
|
36
|
+
value.each do |v|
|
|
37
|
+
new_native = native.clone
|
|
38
|
+
new_native.remove_attribute('value')
|
|
39
|
+
native.add_next_sibling(new_native)
|
|
40
|
+
new_native['value'] = v.to_s
|
|
41
|
+
end
|
|
42
|
+
native.remove
|
|
43
|
+
else
|
|
44
|
+
native['value'] = value.to_s
|
|
45
|
+
end
|
|
33
46
|
elsif tag_name == "textarea"
|
|
34
47
|
native.content = value.to_s
|
|
35
48
|
end
|
|
@@ -84,6 +97,22 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
|
|
|
84
97
|
native.xpath(locator).map { |n| self.class.new(driver, n) }
|
|
85
98
|
end
|
|
86
99
|
|
|
100
|
+
protected
|
|
101
|
+
|
|
102
|
+
def unnormalized_text
|
|
103
|
+
if !visible?
|
|
104
|
+
''
|
|
105
|
+
elsif native.text?
|
|
106
|
+
native.text
|
|
107
|
+
elsif native.element?
|
|
108
|
+
native.children.map do |child|
|
|
109
|
+
Capybara::RackTest::Node.new(driver, child).unnormalized_text
|
|
110
|
+
end.join
|
|
111
|
+
else
|
|
112
|
+
''
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
87
116
|
private
|
|
88
117
|
|
|
89
118
|
def string_node
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
require 'forwardable'
|
|
2
|
+
|
|
3
|
+
module Capybara
|
|
4
|
+
class Result
|
|
5
|
+
include Enumerable
|
|
6
|
+
extend Forwardable
|
|
7
|
+
|
|
8
|
+
def initialize(elements, query)
|
|
9
|
+
@elements = elements
|
|
10
|
+
@result = elements.select { |node| query.matches_filters?(node) }
|
|
11
|
+
@rest = @elements - @result
|
|
12
|
+
@query = query
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def_delegators :@result, :each, :[], :at, :size, :count, :length, :first, :last, :empty?
|
|
16
|
+
|
|
17
|
+
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
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
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
|
|
46
|
+
if count > 0
|
|
47
|
+
message << ", found #{count} #{declension("match", "matches")}: " << @result.map(&:text).map(&:inspect).join(", ")
|
|
48
|
+
else
|
|
49
|
+
message << " but there were no matches"
|
|
50
|
+
end
|
|
51
|
+
unless @rest.empty?
|
|
52
|
+
elements = @rest.map(&:text).map(&:inspect).join(", ")
|
|
53
|
+
message << ". Also found " << elements << ", which matched the selector but not all filters."
|
|
54
|
+
end
|
|
55
|
+
message
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
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
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -4,6 +4,9 @@ module Capybara
|
|
|
4
4
|
base.instance_eval do
|
|
5
5
|
alias :background :before
|
|
6
6
|
alias :scenario :it
|
|
7
|
+
alias :xscenario :xit
|
|
8
|
+
alias :given :let
|
|
9
|
+
alias :given! :let!
|
|
7
10
|
end
|
|
8
11
|
end
|
|
9
12
|
end
|
|
@@ -12,7 +15,7 @@ end
|
|
|
12
15
|
def self.feature(*args, &block)
|
|
13
16
|
options = if args.last.is_a?(Hash) then args.pop else {} end
|
|
14
17
|
options[:capybara_feature] = true
|
|
15
|
-
options[:type] = :
|
|
18
|
+
options[:type] = :feature
|
|
16
19
|
options[:caller] ||= caller
|
|
17
20
|
args.push(options)
|
|
18
21
|
|
|
@@ -6,35 +6,15 @@ module Capybara
|
|
|
6
6
|
end
|
|
7
7
|
|
|
8
8
|
def matches?(actual)
|
|
9
|
-
|
|
10
|
-
@actual.has_selector?(*@args)
|
|
9
|
+
wrap(actual).assert_selector(*@args)
|
|
11
10
|
end
|
|
12
11
|
|
|
13
12
|
def does_not_match?(actual)
|
|
14
|
-
|
|
15
|
-
@actual.has_no_selector?(*@args)
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
def failure_message_for_should
|
|
19
|
-
if normalized.failure_message
|
|
20
|
-
normalized.failure_message.call(@actual, normalized)
|
|
21
|
-
else
|
|
22
|
-
"expected #{selector_name} to return something"
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def failure_message_for_should_not
|
|
27
|
-
"expected #{selector_name} not to return anything"
|
|
13
|
+
wrap(actual).assert_no_selector(*@args)
|
|
28
14
|
end
|
|
29
15
|
|
|
30
16
|
def description
|
|
31
|
-
"
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
def selector_name
|
|
35
|
-
name = "#{normalized.name} #{normalized.locator.inspect}"
|
|
36
|
-
name << " with text #{normalized.options[:text].inspect}" if normalized.options[:text]
|
|
37
|
-
name
|
|
17
|
+
"have #{query.description}"
|
|
38
18
|
end
|
|
39
19
|
|
|
40
20
|
def wrap(actual)
|
|
@@ -45,55 +25,38 @@ module Capybara
|
|
|
45
25
|
end
|
|
46
26
|
end
|
|
47
27
|
|
|
48
|
-
def
|
|
49
|
-
@
|
|
28
|
+
def query
|
|
29
|
+
@query ||= Capybara::Query.new(*@args)
|
|
50
30
|
end
|
|
51
31
|
end
|
|
52
32
|
|
|
53
|
-
class
|
|
54
|
-
attr_reader :
|
|
33
|
+
class HaveText
|
|
34
|
+
attr_reader :text
|
|
55
35
|
|
|
56
|
-
def initialize(
|
|
57
|
-
@
|
|
58
|
-
@locator = locator
|
|
59
|
-
@options = options
|
|
60
|
-
@failure_message = block
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
def arguments
|
|
64
|
-
if options.empty? then [locator] else [locator, options] end
|
|
36
|
+
def initialize(text)
|
|
37
|
+
@text = text
|
|
65
38
|
end
|
|
66
39
|
|
|
67
40
|
def matches?(actual)
|
|
68
41
|
@actual = wrap(actual)
|
|
69
|
-
@actual.
|
|
42
|
+
@actual.has_text?(text)
|
|
70
43
|
end
|
|
71
44
|
|
|
72
45
|
def does_not_match?(actual)
|
|
73
46
|
@actual = wrap(actual)
|
|
74
|
-
@actual.
|
|
47
|
+
@actual.has_no_text?(text)
|
|
75
48
|
end
|
|
76
49
|
|
|
77
50
|
def failure_message_for_should
|
|
78
|
-
|
|
79
|
-
failure_message.call(actual, self)
|
|
80
|
-
else
|
|
81
|
-
"expected #{selector_name} to return something"
|
|
82
|
-
end
|
|
51
|
+
"expected there to be text #{format(text)} in #{format(@actual.text)}"
|
|
83
52
|
end
|
|
84
53
|
|
|
85
54
|
def failure_message_for_should_not
|
|
86
|
-
"expected
|
|
55
|
+
"expected there not to be text #{format(text)} in #{format(@actual.text)}"
|
|
87
56
|
end
|
|
88
57
|
|
|
89
58
|
def description
|
|
90
|
-
"
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
def selector_name
|
|
94
|
-
selector_name = "#{name} #{locator.inspect}"
|
|
95
|
-
selector_name << " with text #{options[:text].inspect}" if options[:text]
|
|
96
|
-
selector_name
|
|
59
|
+
"have text #{format(text)}"
|
|
97
60
|
end
|
|
98
61
|
|
|
99
62
|
def wrap(actual)
|
|
@@ -103,6 +66,11 @@ module Capybara
|
|
|
103
66
|
Capybara.string(actual.to_s)
|
|
104
67
|
end
|
|
105
68
|
end
|
|
69
|
+
|
|
70
|
+
def format(text)
|
|
71
|
+
text = Capybara::Helpers.normalize_whitespace(text) unless text.is_a? Regexp
|
|
72
|
+
text.inspect
|
|
73
|
+
end
|
|
106
74
|
end
|
|
107
75
|
|
|
108
76
|
def have_selector(*args)
|
|
@@ -110,45 +78,47 @@ module Capybara
|
|
|
110
78
|
end
|
|
111
79
|
|
|
112
80
|
def have_xpath(xpath, options={})
|
|
113
|
-
|
|
81
|
+
HaveSelector.new(:xpath, xpath, options)
|
|
114
82
|
end
|
|
115
83
|
|
|
116
84
|
def have_css(css, options={})
|
|
117
|
-
|
|
85
|
+
HaveSelector.new(:css, css, options)
|
|
118
86
|
end
|
|
119
87
|
|
|
120
88
|
def have_content(text)
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
89
|
+
HaveText.new(text)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def have_text(text)
|
|
93
|
+
HaveText.new(text)
|
|
124
94
|
end
|
|
125
95
|
|
|
126
96
|
def have_link(locator, options={})
|
|
127
|
-
|
|
97
|
+
HaveSelector.new(:link, locator, options)
|
|
128
98
|
end
|
|
129
99
|
|
|
130
100
|
def have_button(locator)
|
|
131
|
-
|
|
101
|
+
HaveSelector.new(:button, locator)
|
|
132
102
|
end
|
|
133
103
|
|
|
134
104
|
def have_field(locator, options={})
|
|
135
|
-
|
|
105
|
+
HaveSelector.new(:field, locator, options)
|
|
136
106
|
end
|
|
137
107
|
|
|
138
108
|
def have_checked_field(locator)
|
|
139
|
-
|
|
109
|
+
HaveSelector.new(:field, locator, :checked => true)
|
|
140
110
|
end
|
|
141
111
|
|
|
142
112
|
def have_unchecked_field(locator)
|
|
143
|
-
|
|
113
|
+
HaveSelector.new(:field, locator, :unchecked => true)
|
|
144
114
|
end
|
|
145
115
|
|
|
146
116
|
def have_select(locator, options={})
|
|
147
|
-
|
|
117
|
+
HaveSelector.new(:select, locator, options)
|
|
148
118
|
end
|
|
149
119
|
|
|
150
120
|
def have_table(locator, options={})
|
|
151
|
-
|
|
121
|
+
HaveSelector.new(:table, locator, options)
|
|
152
122
|
end
|
|
153
123
|
end
|
|
154
124
|
end
|
data/lib/capybara/rspec.rb
CHANGED
|
@@ -5,10 +5,8 @@ require 'capybara/rspec/matchers'
|
|
|
5
5
|
require 'capybara/rspec/features'
|
|
6
6
|
|
|
7
7
|
RSpec.configure do |config|
|
|
8
|
-
config.include Capybara::DSL, :type => :
|
|
9
|
-
config.include Capybara::
|
|
10
|
-
config.include Capybara::RSpecMatchers, :type => :request
|
|
11
|
-
config.include Capybara::RSpecMatchers, :type => :acceptance
|
|
8
|
+
config.include Capybara::DSL, :type => :feature
|
|
9
|
+
config.include Capybara::RSpecMatchers, :type => :feature
|
|
12
10
|
# The before and after blocks must run instantaneously, because Capybara
|
|
13
11
|
# might not actually be used in all examples where it's included.
|
|
14
12
|
config.after do
|
|
@@ -24,3 +22,8 @@ RSpec.configure do |config|
|
|
|
24
22
|
end
|
|
25
23
|
end
|
|
26
24
|
end
|
|
25
|
+
|
|
26
|
+
# Override default rack_test driver to respect data-method attributes.
|
|
27
|
+
Capybara.register_driver :rack_test do |app|
|
|
28
|
+
Capybara::RackTest::Driver.new(app, :respect_data_method => true)
|
|
29
|
+
end
|
data/lib/capybara/selector.rb
CHANGED
|
@@ -1,13 +1,7 @@
|
|
|
1
1
|
module Capybara
|
|
2
2
|
class Selector
|
|
3
|
-
attr_reader :name
|
|
3
|
+
attr_reader :name, :custom_filters
|
|
4
4
|
|
|
5
|
-
class Normalized
|
|
6
|
-
attr_accessor :selector, :locator, :options, :xpaths
|
|
7
|
-
|
|
8
|
-
def failure_message; selector.failure_message; end
|
|
9
|
-
def name; selector.name; end
|
|
10
|
-
end
|
|
11
5
|
|
|
12
6
|
class << self
|
|
13
7
|
def all
|
|
@@ -21,32 +15,13 @@ module Capybara
|
|
|
21
15
|
def remove(name)
|
|
22
16
|
all.delete(name.to_sym)
|
|
23
17
|
end
|
|
24
|
-
|
|
25
|
-
def normalize(*args)
|
|
26
|
-
normalized = Normalized.new
|
|
27
|
-
normalized.options = if args.last.is_a?(Hash) then args.pop else {} end
|
|
28
|
-
|
|
29
|
-
if args[1]
|
|
30
|
-
normalized.selector = all[args[0]]
|
|
31
|
-
normalized.locator = args[1]
|
|
32
|
-
else
|
|
33
|
-
normalized.selector = all.values.find { |s| s.match?(args[0]) }
|
|
34
|
-
normalized.locator = args[0]
|
|
35
|
-
end
|
|
36
|
-
normalized.selector ||= all[Capybara.default_selector]
|
|
37
|
-
|
|
38
|
-
xpath = normalized.selector.call(normalized.locator)
|
|
39
|
-
if xpath.respond_to?(:to_xpaths)
|
|
40
|
-
normalized.xpaths = xpath.to_xpaths
|
|
41
|
-
else
|
|
42
|
-
normalized.xpaths = [xpath.to_s].flatten
|
|
43
|
-
end
|
|
44
|
-
normalized
|
|
45
|
-
end
|
|
46
18
|
end
|
|
47
19
|
|
|
48
20
|
def initialize(name, &block)
|
|
49
21
|
@name = name
|
|
22
|
+
@custom_filters = {}
|
|
23
|
+
@match = nil
|
|
24
|
+
@failure_message = nil
|
|
50
25
|
instance_eval(&block)
|
|
51
26
|
end
|
|
52
27
|
|
|
@@ -55,14 +30,22 @@ module Capybara
|
|
|
55
30
|
@xpath
|
|
56
31
|
end
|
|
57
32
|
|
|
33
|
+
# Same as xpath, but wrap in XPath.css().
|
|
34
|
+
def css(&block)
|
|
35
|
+
if block
|
|
36
|
+
@xpath = xpath { |*args| XPath.css(block.call(*args)) }
|
|
37
|
+
end
|
|
38
|
+
@xpath
|
|
39
|
+
end
|
|
40
|
+
|
|
58
41
|
def match(&block)
|
|
59
42
|
@match = block if block
|
|
60
43
|
@match
|
|
61
44
|
end
|
|
62
45
|
|
|
63
|
-
def
|
|
64
|
-
@
|
|
65
|
-
@
|
|
46
|
+
def label(label=nil)
|
|
47
|
+
@label = label if label
|
|
48
|
+
@label
|
|
66
49
|
end
|
|
67
50
|
|
|
68
51
|
def call(locator)
|
|
@@ -72,6 +55,10 @@ module Capybara
|
|
|
72
55
|
def match?(locator)
|
|
73
56
|
@match and @match.call(locator)
|
|
74
57
|
end
|
|
58
|
+
|
|
59
|
+
def filter(name, &block)
|
|
60
|
+
@custom_filters[name] = block
|
|
61
|
+
end
|
|
75
62
|
end
|
|
76
63
|
end
|
|
77
64
|
|
|
@@ -80,10 +67,86 @@ Capybara.add_selector(:xpath) do
|
|
|
80
67
|
end
|
|
81
68
|
|
|
82
69
|
Capybara.add_selector(:css) do
|
|
83
|
-
|
|
70
|
+
css { |css| css }
|
|
84
71
|
end
|
|
85
72
|
|
|
86
73
|
Capybara.add_selector(:id) do
|
|
87
74
|
xpath { |id| XPath.descendant[XPath.attr(:id) == id.to_s] }
|
|
88
|
-
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
Capybara.add_selector(:field) do
|
|
78
|
+
xpath { |locator| XPath::HTML.field(locator) }
|
|
79
|
+
filter(:checked) { |node, value| not(value ^ node.checked?) }
|
|
80
|
+
filter(:unchecked) { |node, value| (value ^ node.checked?) }
|
|
81
|
+
filter(:with) { |node, with| node.value == with }
|
|
82
|
+
filter(:type) { |node, type| node[:type] == type }
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
Capybara.add_selector(:fieldset) do
|
|
86
|
+
xpath { |locator| XPath::HTML.fieldset(locator) }
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
Capybara.add_selector(:link_or_button) do
|
|
90
|
+
label "link or button"
|
|
91
|
+
xpath { |locator| XPath::HTML.link_or_button(locator) }
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
Capybara.add_selector(:link) do
|
|
95
|
+
xpath { |locator| XPath::HTML.link(locator) }
|
|
96
|
+
filter(:href) do |node, href|
|
|
97
|
+
node.first(:xpath, XPath.axis(:self)[XPath.attr(:href).equals(href.to_s)])
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
Capybara.add_selector(:button) do
|
|
102
|
+
xpath { |locator| XPath::HTML.button(locator) }
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
Capybara.add_selector(:fillable_field) do
|
|
106
|
+
label "field"
|
|
107
|
+
xpath { |locator| XPath::HTML.fillable_field(locator) }
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
Capybara.add_selector(:radio_button) do
|
|
111
|
+
label "radio button"
|
|
112
|
+
xpath { |locator| XPath::HTML.radio_button(locator) }
|
|
113
|
+
filter(:checked) { |node, value| not(value ^ node.checked?) }
|
|
114
|
+
filter(:unchecked) { |node, value| (value ^ node.checked?) }
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
Capybara.add_selector(:checkbox) do
|
|
118
|
+
xpath { |locator| XPath::HTML.checkbox(locator) }
|
|
119
|
+
filter(:checked) { |node, value| not(value ^ node.checked?) }
|
|
120
|
+
filter(:unchecked) { |node, value| (value ^ node.checked?) }
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
Capybara.add_selector(:select) do
|
|
124
|
+
label "select box"
|
|
125
|
+
xpath { |locator| XPath::HTML.select(locator) }
|
|
126
|
+
filter(:options) do |node, options|
|
|
127
|
+
actual = node.all(:xpath, './/option').map { |option| option.text }
|
|
128
|
+
options.sort == actual.sort
|
|
129
|
+
end
|
|
130
|
+
filter(:with_options) { |node, options| options.all? { |option| node.first(:option, option) } }
|
|
131
|
+
filter(:selected) do |node, selected|
|
|
132
|
+
actual = node.all(:xpath, './/option').select { |option| option.selected? }.map { |option| option.text }
|
|
133
|
+
[selected].flatten.sort == actual.sort
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
Capybara.add_selector(:option) do
|
|
138
|
+
xpath { |locator| XPath::HTML.option(locator) }
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
Capybara.add_selector(:file_field) do
|
|
142
|
+
label "file field"
|
|
143
|
+
xpath { |locator| XPath::HTML.file_field(locator) }
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
Capybara.add_selector(:content) do
|
|
147
|
+
xpath { |content| XPath::HTML.content(content) }
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
Capybara.add_selector(:table) do
|
|
151
|
+
xpath { |locator| XPath::HTML.table(locator) }
|
|
89
152
|
end
|