capybara 0.4.1.2 → 1.0.0.beta1
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 +46 -0
- data/README.rdoc +211 -64
- data/lib/capybara.rb +31 -15
- data/lib/capybara/cucumber.rb +12 -16
- data/lib/capybara/dsl.rb +65 -28
- data/lib/capybara/node/actions.rb +7 -5
- data/lib/capybara/node/document.rb +8 -0
- data/lib/capybara/node/finders.rb +11 -7
- data/lib/capybara/node/matchers.rb +32 -6
- data/lib/capybara/node/simple.rb +20 -0
- data/lib/capybara/rack_test/browser.rb +115 -0
- data/lib/capybara/rack_test/driver.rb +77 -0
- data/lib/capybara/rack_test/form.rb +80 -0
- data/lib/capybara/rack_test/node.rb +101 -0
- data/lib/capybara/rspec.rb +11 -3
- data/lib/capybara/rspec/features.rb +22 -0
- data/lib/capybara/rspec/matchers.rb +146 -0
- data/lib/capybara/selector.rb +27 -8
- data/lib/capybara/selenium/driver.rb +148 -0
- data/lib/capybara/selenium/node.rb +91 -0
- data/lib/capybara/session.rb +42 -15
- data/lib/capybara/spec/driver.rb +55 -1
- data/lib/capybara/spec/fixtures/capybara.jpg +0 -0
- data/lib/capybara/spec/public/test.js +7 -2
- data/lib/capybara/spec/session.rb +51 -7
- data/lib/capybara/spec/session/attach_file_spec.rb +9 -6
- data/lib/capybara/spec/session/click_button_spec.rb +35 -0
- data/lib/capybara/spec/session/current_host_spec.rb +62 -0
- data/lib/capybara/spec/session/fill_in_spec.rb +6 -0
- data/lib/capybara/spec/session/find_spec.rb +23 -1
- data/lib/capybara/spec/session/first_spec.rb +39 -6
- data/lib/capybara/spec/session/has_css_spec.rb +30 -0
- data/lib/capybara/spec/session/has_field_spec.rb +47 -11
- data/lib/capybara/spec/session/javascript.rb +0 -1
- data/lib/capybara/spec/session/text_spec.rb +19 -0
- data/lib/capybara/spec/test_app.rb +9 -0
- data/lib/capybara/spec/views/form.erb +8 -3
- data/lib/capybara/spec/views/header_links.erb +7 -0
- data/lib/capybara/spec/views/host_links.erb +12 -0
- data/lib/capybara/spec/views/with_html.erb +6 -2
- data/lib/capybara/spec/views/with_html_entities.erb +1 -0
- data/lib/capybara/spec/views/with_js.erb +4 -0
- data/lib/capybara/util/save_and_open_page.rb +7 -3
- data/lib/capybara/util/timeout.rb +2 -2
- data/lib/capybara/version.rb +1 -1
- data/spec/capybara_spec.rb +1 -1
- data/spec/driver/rack_test_driver_spec.rb +24 -2
- data/spec/driver/selenium_driver_spec.rb +2 -1
- data/spec/dsl_spec.rb +56 -4
- data/spec/rspec/features_spec.rb +45 -0
- data/spec/rspec/matchers_spec.rb +451 -0
- data/spec/rspec_spec.rb +9 -2
- data/spec/save_and_open_page_spec.rb +9 -13
- data/spec/server_spec.rb +4 -0
- data/spec/session/rack_test_session_spec.rb +2 -2
- data/spec/session/selenium_session_spec.rb +1 -1
- data/spec/spec_helper.rb +0 -14
- data/spec/string_spec.rb +1 -1
- metadata +60 -69
- data/lib/capybara/driver/celerity_driver.rb +0 -164
- data/lib/capybara/driver/culerity_driver.rb +0 -26
- data/lib/capybara/driver/rack_test_driver.rb +0 -303
- data/lib/capybara/driver/selenium_driver.rb +0 -161
- data/spec/driver/celerity_driver_spec.rb +0 -13
- data/spec/driver/culerity_driver_spec.rb +0 -14
- data/spec/driver/remote_culerity_driver_spec.rb +0 -22
- data/spec/driver/remote_selenium_driver_spec.rb +0 -16
- data/spec/session/celerity_session_spec.rb +0 -24
- data/spec/session/culerity_session_spec.rb +0 -26
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'rack/test'
|
2
|
+
require 'rack/utils'
|
3
|
+
require 'mime/types'
|
4
|
+
require 'nokogiri'
|
5
|
+
require 'cgi'
|
6
|
+
|
7
|
+
class Capybara::RackTest::Driver < Capybara::Driver::Base
|
8
|
+
attr_reader :app, :options
|
9
|
+
|
10
|
+
def initialize(app, options={})
|
11
|
+
raise ArgumentError, "rack-test requires a rack application, but none was given" unless app
|
12
|
+
@app = app
|
13
|
+
@options = options
|
14
|
+
end
|
15
|
+
|
16
|
+
def browser
|
17
|
+
@browser ||= Capybara::RackTest::Browser.new(app, options)
|
18
|
+
end
|
19
|
+
|
20
|
+
def response
|
21
|
+
browser.last_response
|
22
|
+
end
|
23
|
+
|
24
|
+
def request
|
25
|
+
browser.last_request
|
26
|
+
end
|
27
|
+
|
28
|
+
def visit(path, attributes = {})
|
29
|
+
browser.visit(path, attributes)
|
30
|
+
end
|
31
|
+
|
32
|
+
def submit(method, path, attributes)
|
33
|
+
browser.submit(method, path, attributes)
|
34
|
+
end
|
35
|
+
|
36
|
+
def follow(method, path, attributes = {})
|
37
|
+
browser.follow(method, path, attributes)
|
38
|
+
end
|
39
|
+
|
40
|
+
def current_url
|
41
|
+
browser.current_url
|
42
|
+
end
|
43
|
+
|
44
|
+
def response_headers
|
45
|
+
response.headers
|
46
|
+
end
|
47
|
+
|
48
|
+
def status_code
|
49
|
+
response.status
|
50
|
+
end
|
51
|
+
|
52
|
+
def find(selector)
|
53
|
+
browser.find(selector)
|
54
|
+
end
|
55
|
+
|
56
|
+
def body
|
57
|
+
browser.body
|
58
|
+
end
|
59
|
+
|
60
|
+
def source
|
61
|
+
browser.source
|
62
|
+
end
|
63
|
+
|
64
|
+
def dom
|
65
|
+
browser.dom
|
66
|
+
end
|
67
|
+
|
68
|
+
def reset!
|
69
|
+
@browser = nil
|
70
|
+
end
|
71
|
+
|
72
|
+
def get(*args, &block); browser.get(*args, &block); end
|
73
|
+
def post(*args, &block); browser.post(*args, &block); end
|
74
|
+
def put(*args, &block); browser.put(*args, &block); end
|
75
|
+
def delete(*args, &block); browser.delete(*args, &block); end
|
76
|
+
def header(key, value); browser.header(key, value); end
|
77
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
class Capybara::RackTest::Form < Capybara::RackTest::Node
|
2
|
+
# This only needs to inherit from Rack::Test::UploadedFile because Rack::Test checks for
|
3
|
+
# the class specifically when determing whether to consturct the request as multipart.
|
4
|
+
# That check should be based solely on the form element's 'enctype' attribute value,
|
5
|
+
# which should probably be provided to Rack::Test in its non-GET request methods.
|
6
|
+
class NilUploadedFile < Rack::Test::UploadedFile
|
7
|
+
def initialize
|
8
|
+
@empty_file = Tempfile.new("nil_uploaded_file")
|
9
|
+
@empty_file.close
|
10
|
+
end
|
11
|
+
|
12
|
+
def original_filename; ""; end
|
13
|
+
def content_type; "application/octet-stream"; end
|
14
|
+
def path; @empty_file.path; end
|
15
|
+
end
|
16
|
+
|
17
|
+
def params(button)
|
18
|
+
params = {}
|
19
|
+
|
20
|
+
native.xpath("(.//input|.//select|.//textarea)[not(@disabled)]").map do |field|
|
21
|
+
case field.name
|
22
|
+
when 'input'
|
23
|
+
if %w(radio checkbox).include? field['type']
|
24
|
+
merge_param!(params, field['name'].to_s, field['value'].to_s) if field['checked']
|
25
|
+
elsif %w(submit image).include? field['type']
|
26
|
+
# TO DO identify the click button here (in document order, rather
|
27
|
+
# than leaving until the end of the params)
|
28
|
+
elsif field['type'] =='file'
|
29
|
+
if multipart?
|
30
|
+
file = \
|
31
|
+
if (value = field['value']).to_s.empty?
|
32
|
+
NilUploadedFile.new
|
33
|
+
else
|
34
|
+
content_type = MIME::Types.type_for(value).first.to_s
|
35
|
+
Rack::Test::UploadedFile.new(value, content_type)
|
36
|
+
end
|
37
|
+
merge_param!(params, field['name'].to_s, file)
|
38
|
+
else
|
39
|
+
merge_param!(params, field['name'].to_s, File.basename(field['value'].to_s))
|
40
|
+
end
|
41
|
+
else
|
42
|
+
merge_param!(params, field['name'].to_s, field['value'].to_s)
|
43
|
+
end
|
44
|
+
when 'select'
|
45
|
+
if field['multiple'] == 'multiple'
|
46
|
+
options = field.xpath(".//option[@selected]")
|
47
|
+
options.each do |option|
|
48
|
+
merge_param!(params, field['name'].to_s, (option['value'] || option.text).to_s)
|
49
|
+
end
|
50
|
+
else
|
51
|
+
option = field.xpath(".//option[@selected]").first
|
52
|
+
option ||= field.xpath('.//option').first
|
53
|
+
merge_param!(params, field['name'].to_s, (option['value'] || option.text).to_s) if option
|
54
|
+
end
|
55
|
+
when 'textarea'
|
56
|
+
merge_param!(params, field['name'].to_s, field.text.to_s)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
merge_param!(params, button[:name], button[:value] || "") if button[:name]
|
60
|
+
params
|
61
|
+
end
|
62
|
+
|
63
|
+
def submit(button)
|
64
|
+
driver.submit(method, native['action'].to_s, params(button))
|
65
|
+
end
|
66
|
+
|
67
|
+
def multipart?
|
68
|
+
self[:enctype] == "multipart/form-data"
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def method
|
74
|
+
self[:method] =~ /post/i ? :post : :get
|
75
|
+
end
|
76
|
+
|
77
|
+
def merge_param!(params, key, value)
|
78
|
+
Rack::Utils.normalize_params(params, key, value)
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
class Capybara::RackTest::Node < Capybara::Driver::Node
|
2
|
+
def text
|
3
|
+
native.text
|
4
|
+
end
|
5
|
+
|
6
|
+
def [](name)
|
7
|
+
string_node[name]
|
8
|
+
end
|
9
|
+
|
10
|
+
def value
|
11
|
+
string_node.value
|
12
|
+
end
|
13
|
+
|
14
|
+
def set(value)
|
15
|
+
if tag_name == 'input' and type == 'radio'
|
16
|
+
other_radios_xpath = XPath.generate { |x| x.anywhere(:input)[x.attr(:name).equals(self[:name])] }.to_s
|
17
|
+
driver.dom.xpath(other_radios_xpath).each { |node| node.remove_attribute("checked") }
|
18
|
+
native['checked'] = 'checked'
|
19
|
+
elsif tag_name == 'input' and type == 'checkbox'
|
20
|
+
if value && !native['checked']
|
21
|
+
native['checked'] = 'checked'
|
22
|
+
elsif !value && native['checked']
|
23
|
+
native.remove_attribute('checked')
|
24
|
+
end
|
25
|
+
elsif tag_name == 'input'
|
26
|
+
if (type == 'text' || type == 'password') && self[:maxlength]
|
27
|
+
value = value[0...self[:maxlength].to_i]
|
28
|
+
end
|
29
|
+
native['value'] = value.to_s
|
30
|
+
elsif tag_name == "textarea"
|
31
|
+
native.content = value.to_s
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def select_option
|
36
|
+
if select_node['multiple'] != 'multiple'
|
37
|
+
select_node.find(".//option[@selected]").each { |node| node.native.remove_attribute("selected") }
|
38
|
+
end
|
39
|
+
native["selected"] = 'selected'
|
40
|
+
end
|
41
|
+
|
42
|
+
def unselect_option
|
43
|
+
if select_node['multiple'] != 'multiple'
|
44
|
+
raise Capybara::UnselectNotAllowed, "Cannot unselect option from single select box."
|
45
|
+
end
|
46
|
+
native.remove_attribute('selected')
|
47
|
+
end
|
48
|
+
|
49
|
+
def click
|
50
|
+
if tag_name == 'a'
|
51
|
+
method = self["data-method"] || :get
|
52
|
+
driver.follow(method, self[:href].to_s)
|
53
|
+
elsif (tag_name == 'input' and %w(submit image).include?(type)) or
|
54
|
+
((tag_name == 'button') and type.nil? or type == "submit")
|
55
|
+
Capybara::RackTest::Form.new(driver, form).submit(self)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def tag_name
|
60
|
+
native.node_name
|
61
|
+
end
|
62
|
+
|
63
|
+
def visible?
|
64
|
+
string_node.visible?
|
65
|
+
end
|
66
|
+
|
67
|
+
def checked?
|
68
|
+
string_node.checked?
|
69
|
+
end
|
70
|
+
|
71
|
+
def selected?
|
72
|
+
string_node.selected?
|
73
|
+
end
|
74
|
+
|
75
|
+
def path
|
76
|
+
native.path
|
77
|
+
end
|
78
|
+
|
79
|
+
def find(locator)
|
80
|
+
native.xpath(locator).map { |n| self.class.new(driver, n) }
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
def string_node
|
86
|
+
@string_node ||= Capybara::Node::Simple.new(native)
|
87
|
+
end
|
88
|
+
|
89
|
+
# a reference to the select node if this is an option node
|
90
|
+
def select_node
|
91
|
+
find('./ancestor::select').first
|
92
|
+
end
|
93
|
+
|
94
|
+
def type
|
95
|
+
native[:type]
|
96
|
+
end
|
97
|
+
|
98
|
+
def form
|
99
|
+
native.ancestors('form').first
|
100
|
+
end
|
101
|
+
end
|
data/lib/capybara/rspec.rb
CHANGED
@@ -1,16 +1,24 @@
|
|
1
1
|
require 'capybara'
|
2
2
|
require 'capybara/dsl'
|
3
|
+
require 'rspec/core'
|
4
|
+
require 'capybara/rspec/matchers'
|
5
|
+
require 'capybara/rspec/features'
|
3
6
|
|
4
7
|
RSpec.configure do |config|
|
5
|
-
config.include Capybara, :type => :
|
8
|
+
config.include Capybara::DSL, :type => :request
|
9
|
+
config.include Capybara::DSL, :type => :acceptance
|
10
|
+
config.include Capybara::RSpecMatchers, :type => :request
|
11
|
+
config.include Capybara::RSpecMatchers, :type => :acceptance
|
12
|
+
# The before and after blocks must run instantaneously, because Capybara
|
13
|
+
# might not actually be used in all examples where it's included.
|
6
14
|
config.after do
|
7
|
-
if
|
15
|
+
if self.class.include?(Capybara::DSL)
|
8
16
|
Capybara.reset_sessions!
|
9
17
|
Capybara.use_default_driver
|
10
18
|
end
|
11
19
|
end
|
12
20
|
config.before do
|
13
|
-
if
|
21
|
+
if self.class.include?(Capybara::DSL)
|
14
22
|
Capybara.current_driver = Capybara.javascript_driver if example.metadata[:js]
|
15
23
|
Capybara.current_driver = example.metadata[:driver] if example.metadata[:driver]
|
16
24
|
end
|
@@ -0,0 +1,22 @@
|
|
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
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.feature(*args, &block)
|
13
|
+
options = if args.last.is_a?(Hash) then args.pop else {} end
|
14
|
+
options[:capybara_feature] = true
|
15
|
+
options[:type] = :request
|
16
|
+
options[:caller] ||= caller
|
17
|
+
args.push(options)
|
18
|
+
|
19
|
+
describe(*args, &block)
|
20
|
+
end
|
21
|
+
|
22
|
+
RSpec.configuration.include Capybara::Features, :capybara_feature => true
|
@@ -0,0 +1,146 @@
|
|
1
|
+
module Capybara
|
2
|
+
module RSpecMatchers
|
3
|
+
class HaveSelector
|
4
|
+
def initialize(*args)
|
5
|
+
@args = args
|
6
|
+
end
|
7
|
+
|
8
|
+
def matches?(actual)
|
9
|
+
@actual = wrap(actual)
|
10
|
+
@actual.has_selector?(*@args)
|
11
|
+
end
|
12
|
+
|
13
|
+
def does_not_match?(actual)
|
14
|
+
@actual = wrap(actual)
|
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"
|
28
|
+
end
|
29
|
+
|
30
|
+
def selector_name
|
31
|
+
name = "#{normalized.name} #{normalized.locator.inspect}"
|
32
|
+
name << " with text #{normalized.options[:text].inspect}" if normalized.options[:text]
|
33
|
+
name
|
34
|
+
end
|
35
|
+
|
36
|
+
def wrap(actual)
|
37
|
+
if actual.respond_to?("has_selector?")
|
38
|
+
actual
|
39
|
+
else
|
40
|
+
Capybara.string(actual.to_s)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def normalized
|
45
|
+
@normalized ||= Capybara::Selector.normalize(*@args)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class HaveMatcher
|
50
|
+
attr_reader :name, :locator, :options, :failure_message, :actual
|
51
|
+
|
52
|
+
def initialize(name, locator, options={}, &block)
|
53
|
+
@name = name
|
54
|
+
@locator = locator
|
55
|
+
@options = options
|
56
|
+
@failure_message = block
|
57
|
+
end
|
58
|
+
|
59
|
+
def arguments
|
60
|
+
if options.empty? then [locator] else [locator, options] end
|
61
|
+
end
|
62
|
+
|
63
|
+
def matches?(actual)
|
64
|
+
@actual = wrap(actual)
|
65
|
+
@actual.send(:"has_#{name}?", *arguments)
|
66
|
+
end
|
67
|
+
|
68
|
+
def does_not_match?(actual)
|
69
|
+
@actual = wrap(actual)
|
70
|
+
@actual.send(:"has_no_#{name}?", *arguments)
|
71
|
+
end
|
72
|
+
|
73
|
+
def failure_message_for_should
|
74
|
+
if failure_message
|
75
|
+
failure_message.call(actual, self)
|
76
|
+
else
|
77
|
+
"expected #{selector_name} to return something"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def failure_message_for_should_not
|
82
|
+
"expected #{selector_name} not to return anything"
|
83
|
+
end
|
84
|
+
|
85
|
+
def selector_name
|
86
|
+
selector_name = "#{name} #{locator.inspect}"
|
87
|
+
selector_name << " with text #{options[:text].inspect}" if options[:text]
|
88
|
+
selector_name
|
89
|
+
end
|
90
|
+
|
91
|
+
def wrap(actual)
|
92
|
+
if actual.respond_to?("has_selector?")
|
93
|
+
actual
|
94
|
+
else
|
95
|
+
Capybara.string(actual.to_s)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def have_selector(*args)
|
101
|
+
HaveSelector.new(*args)
|
102
|
+
end
|
103
|
+
|
104
|
+
def have_xpath(xpath, options={})
|
105
|
+
HaveMatcher.new(:xpath, xpath, options)
|
106
|
+
end
|
107
|
+
|
108
|
+
def have_css(css, options={})
|
109
|
+
HaveMatcher.new(:css, css, options)
|
110
|
+
end
|
111
|
+
|
112
|
+
def have_content(text)
|
113
|
+
HaveMatcher.new(:content, text.to_s) do |page, matcher|
|
114
|
+
%(expected there to be content #{matcher.locator.inspect} in #{page.text.inspect})
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def have_link(locator, options={})
|
119
|
+
HaveMatcher.new(:link, locator, options)
|
120
|
+
end
|
121
|
+
|
122
|
+
def have_button(locator)
|
123
|
+
HaveMatcher.new(:button, locator)
|
124
|
+
end
|
125
|
+
|
126
|
+
def have_field(locator, options={})
|
127
|
+
HaveMatcher.new(:field, locator, options)
|
128
|
+
end
|
129
|
+
|
130
|
+
def have_checked_field(locator)
|
131
|
+
HaveMatcher.new(:checked_field, locator)
|
132
|
+
end
|
133
|
+
|
134
|
+
def have_unchecked_field(locator)
|
135
|
+
HaveMatcher.new(:unchecked_field, locator)
|
136
|
+
end
|
137
|
+
|
138
|
+
def have_select(locator, options={})
|
139
|
+
HaveMatcher.new(:select, locator, options)
|
140
|
+
end
|
141
|
+
|
142
|
+
def have_table(locator, options={})
|
143
|
+
HaveMatcher.new(:table, locator, options)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|