capybara 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +4 -0
- data/Manifest.txt +42 -0
- data/README.rdoc +208 -0
- data/Rakefile +33 -0
- data/examples/webcat.rb +36 -0
- data/lib/capybara.rb +30 -0
- data/lib/capybara/core_ext/tcp_socket.rb +26 -0
- data/lib/capybara/cucumber.rb +32 -0
- data/lib/capybara/driver/culerity_driver.rb +83 -0
- data/lib/capybara/driver/firewatir_driver.rb +66 -0
- data/lib/capybara/driver/rack_test_driver.rb +151 -0
- data/lib/capybara/driver/safariwatir_driver.rb +67 -0
- data/lib/capybara/driver/selenium_driver.rb +93 -0
- data/lib/capybara/dsl.rb +58 -0
- data/lib/capybara/node.rb +36 -0
- data/lib/capybara/rails.rb +11 -0
- data/lib/capybara/save_and_open_page.rb +25 -0
- data/lib/capybara/server.rb +53 -0
- data/lib/capybara/session.rb +190 -0
- data/script/console +10 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/spec/driver/culerity_driver_spec.rb +10 -0
- data/spec/driver/firewatir_driver_spec.rb +10 -0
- data/spec/driver/rack_test_driver_spec.rb +9 -0
- data/spec/driver/safariwarit_driver_spec.rb +10 -0
- data/spec/driver/selenium_driver_spec.rb +10 -0
- data/spec/drivers_spec.rb +72 -0
- data/spec/dsl_spec.rb +139 -0
- data/spec/fixtures/test_file.txt +1 -0
- data/spec/public/jquery.js +19 -0
- data/spec/session/culerity_session_spec.rb +23 -0
- data/spec/session/rack_test_session_spec.rb +23 -0
- data/spec/session/selenium_session_spec.rb +23 -0
- data/spec/session_spec.rb +611 -0
- data/spec/spec_helper.rb +10 -0
- data/spec/test_app.rb +67 -0
- data/spec/views/form.erb +117 -0
- data/spec/views/with_html.erb +19 -0
- data/spec/views/with_js.erb +20 -0
- data/spec/views/with_scope.erb +36 -0
- data/spec/views/with_simple_html.erb +1 -0
- metadata +164 -0
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'watir'
|
2
|
+
Watir::Browser.default = "firefox"
|
3
|
+
|
4
|
+
class Capybara::Driver::FireWatir
|
5
|
+
class Node < Struct.new(:node)
|
6
|
+
def text
|
7
|
+
node.text
|
8
|
+
end
|
9
|
+
|
10
|
+
def attribute(name)
|
11
|
+
value = if name.to_sym == :class
|
12
|
+
node.class_name
|
13
|
+
else
|
14
|
+
node.send(name.to_sym)
|
15
|
+
end
|
16
|
+
return value if value and not value.empty?
|
17
|
+
end
|
18
|
+
|
19
|
+
def click
|
20
|
+
node.click
|
21
|
+
end
|
22
|
+
|
23
|
+
def tag_name
|
24
|
+
# FIXME: this might be the dumbest way ever of getting the tag name
|
25
|
+
# there has to be something better...
|
26
|
+
node.to_xml[/^\s*<([a-z0-9\-\:]+)/, 1]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
attr_reader :app, :rack_server
|
31
|
+
|
32
|
+
def initialize(app)
|
33
|
+
@app = app
|
34
|
+
@rack_server = Capybara::Server.new(@app)
|
35
|
+
@rack_server.boot
|
36
|
+
end
|
37
|
+
|
38
|
+
def visit(path)
|
39
|
+
browser.goto(url(path))
|
40
|
+
end
|
41
|
+
|
42
|
+
def body
|
43
|
+
browser.html
|
44
|
+
end
|
45
|
+
|
46
|
+
def find(selector)
|
47
|
+
browser.elements_by_xpath(selector).map { |node| Node.new(node) }
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def url(path)
|
53
|
+
rack_server.url(path)
|
54
|
+
end
|
55
|
+
|
56
|
+
def browser
|
57
|
+
unless @_browser
|
58
|
+
@_browser = Watir::Browser.new
|
59
|
+
at_exit do
|
60
|
+
@_browser.exit
|
61
|
+
end
|
62
|
+
end
|
63
|
+
@_browser
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
require 'rack/test'
|
2
|
+
require 'nokogiri'
|
3
|
+
|
4
|
+
class Capybara::Driver::RackTest
|
5
|
+
class Node < Capybara::Node
|
6
|
+
def text
|
7
|
+
node.text
|
8
|
+
end
|
9
|
+
|
10
|
+
def [](name)
|
11
|
+
value = node[name.to_s]
|
12
|
+
return value.to_s if value
|
13
|
+
end
|
14
|
+
|
15
|
+
def set(value)
|
16
|
+
if tag_name == 'input' and %w(text password hidden file).include?(type)
|
17
|
+
node['value'] = value.to_s
|
18
|
+
elsif tag_name == 'input' and type == 'radio'
|
19
|
+
driver.html.xpath("//input[@name='#{self[:name]}']").each { |node| node.remove_attribute("checked") }
|
20
|
+
node['checked'] = 'checked'
|
21
|
+
elsif tag_name == 'input' and type == 'checkbox'
|
22
|
+
if value
|
23
|
+
node['checked'] = 'checked'
|
24
|
+
else
|
25
|
+
node.remove_attribute('checked')
|
26
|
+
end
|
27
|
+
elsif tag_name == "textarea"
|
28
|
+
node.content = value.to_s
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def select(option)
|
33
|
+
node.xpath(".//option").each { |node| node.remove_attribute("selected") }
|
34
|
+
node.xpath(".//option[contains(.,'#{option}')]").first["selected"] = 'selected'
|
35
|
+
end
|
36
|
+
|
37
|
+
def click
|
38
|
+
if tag_name == 'a'
|
39
|
+
driver.visit(self[:href])
|
40
|
+
elsif tag_name == 'input' and %w(submit image).include?(type)
|
41
|
+
Form.new(driver, form).submit(self)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def tag_name
|
46
|
+
node.node_name
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def type
|
52
|
+
self[:type]
|
53
|
+
end
|
54
|
+
|
55
|
+
def form
|
56
|
+
node.ancestors('form').first
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
class Form < Node
|
61
|
+
def params(button)
|
62
|
+
params = []
|
63
|
+
params += node.xpath(".//input[@type='text']", ".//input[@type='hidden']", ".//input[@type='password']").inject([]) do |agg, input|
|
64
|
+
agg << [input['name'].to_s, input['value'].to_s]
|
65
|
+
agg
|
66
|
+
end
|
67
|
+
params += node.xpath(".//textarea").inject([]) do |agg, textarea|
|
68
|
+
agg << [textarea['name'].to_s, textarea.text.to_s]
|
69
|
+
agg
|
70
|
+
end
|
71
|
+
params += node.xpath(".//input[@type='radio']").inject([]) do |agg, input|
|
72
|
+
agg << [input['name'].to_s, input['value'].to_s] if input['checked']
|
73
|
+
agg
|
74
|
+
end
|
75
|
+
params += node.xpath(".//input[@type='checkbox']").inject([]) do |agg, input|
|
76
|
+
agg << [input['name'].to_s, input['value'].to_s] if input['checked']
|
77
|
+
agg
|
78
|
+
end
|
79
|
+
params += node.xpath(".//select").inject([]) do |agg, select|
|
80
|
+
option = select.xpath(".//option[@selected]").first
|
81
|
+
option ||= select.xpath('.//option').first
|
82
|
+
agg << [select['name'].to_s, (option['value'] || option.text).to_s] if option
|
83
|
+
agg
|
84
|
+
end
|
85
|
+
params += node.xpath(".//input[@type='file']").inject([]) do |agg, input|
|
86
|
+
if multipart?
|
87
|
+
agg << [input['name'].to_s, Rack::Test::UploadedFile.new(input['value'].to_s)]
|
88
|
+
else
|
89
|
+
agg << [input['name'].to_s, File.basename(input['value'].to_s)]
|
90
|
+
end
|
91
|
+
agg
|
92
|
+
end
|
93
|
+
params.push [button[:name], button[:value]] if button[:name]
|
94
|
+
if multipart?
|
95
|
+
params.inject({}) { |agg, (key, value)| agg[key] = value; agg }
|
96
|
+
else
|
97
|
+
params.map { |key, value| "#{key}=#{value}" }.join('&')
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def submit(button)
|
102
|
+
if post?
|
103
|
+
driver.submit(node['action'].to_s, params(button))
|
104
|
+
else
|
105
|
+
driver.visit(node['action'].to_s.split('?').first + '?' + params(button))
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def multipart?
|
110
|
+
self[:enctype] == "multipart/form-data"
|
111
|
+
end
|
112
|
+
|
113
|
+
def post?
|
114
|
+
self[:method] =~ /post/i
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
include ::Rack::Test::Methods
|
119
|
+
attr_reader :app, :html, :body
|
120
|
+
|
121
|
+
alias_method :response, :last_response
|
122
|
+
alias_method :request, :last_request
|
123
|
+
|
124
|
+
def initialize(app)
|
125
|
+
@app = app
|
126
|
+
end
|
127
|
+
|
128
|
+
def visit(path)
|
129
|
+
get(path)
|
130
|
+
follow_redirect! while response.redirect?
|
131
|
+
cache_body
|
132
|
+
end
|
133
|
+
|
134
|
+
def submit(path, attributes)
|
135
|
+
post(path, attributes)
|
136
|
+
follow_redirect! while response.redirect?
|
137
|
+
cache_body
|
138
|
+
end
|
139
|
+
|
140
|
+
def find(selector)
|
141
|
+
html.xpath(selector).map { |node| Node.new(self, node) }
|
142
|
+
end
|
143
|
+
|
144
|
+
private
|
145
|
+
|
146
|
+
def cache_body
|
147
|
+
@body = response.body
|
148
|
+
@html = Nokogiri::HTML(body)
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'safariwatir'
|
2
|
+
|
3
|
+
class Capybara::Driver::SafariWatir
|
4
|
+
class Node < Struct.new(:node)
|
5
|
+
def text
|
6
|
+
node.text
|
7
|
+
end
|
8
|
+
|
9
|
+
def attribute(name)
|
10
|
+
value = if name.to_sym == :class
|
11
|
+
node.class_name
|
12
|
+
else
|
13
|
+
node.send(name.to_sym)
|
14
|
+
end
|
15
|
+
return value if value and not value.empty?
|
16
|
+
end
|
17
|
+
|
18
|
+
def click
|
19
|
+
node.click
|
20
|
+
end
|
21
|
+
|
22
|
+
def tag_name
|
23
|
+
# FIXME: this might be the dumbest way ever of getting the tag name
|
24
|
+
# there has to be something better...
|
25
|
+
node.to_xml[/^\s*<([a-z0-9\-\:]+)/, 1]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
attr_reader :app, :rack_server
|
30
|
+
|
31
|
+
def initialize(app)
|
32
|
+
@app = app
|
33
|
+
@rack_server = Capybara::Server.new(@app)
|
34
|
+
@rack_server.boot
|
35
|
+
end
|
36
|
+
|
37
|
+
def visit(path)
|
38
|
+
browser.goto(url(path))
|
39
|
+
end
|
40
|
+
|
41
|
+
def body
|
42
|
+
browser.html
|
43
|
+
end
|
44
|
+
|
45
|
+
def find(selector)
|
46
|
+
foo = Struct.new(:what).new
|
47
|
+
foo.what = selector
|
48
|
+
browser.send(:scripter).operate_by_xpath(foo){}.map { |node| Node.new(node) }
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def url(path)
|
54
|
+
rack_server.url(path)
|
55
|
+
end
|
56
|
+
|
57
|
+
def browser
|
58
|
+
unless @_browser
|
59
|
+
@_browser = Watir::Safari.new
|
60
|
+
at_exit do
|
61
|
+
@_browser.exit
|
62
|
+
end
|
63
|
+
end
|
64
|
+
@_browser
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'selenium-webdriver'
|
2
|
+
|
3
|
+
class Capybara::Driver::Selenium
|
4
|
+
class Node < Capybara::Node
|
5
|
+
def text
|
6
|
+
node.text
|
7
|
+
end
|
8
|
+
|
9
|
+
def [](name)
|
10
|
+
if name == :value
|
11
|
+
node.value
|
12
|
+
else
|
13
|
+
node.attribute(name)
|
14
|
+
end
|
15
|
+
rescue Selenium::WebDriver::Error::WebDriverError
|
16
|
+
nil
|
17
|
+
end
|
18
|
+
|
19
|
+
def set(value)
|
20
|
+
if tag_name == 'input' and %w(text password hidden file).include?(type)
|
21
|
+
node.clear
|
22
|
+
node.send_keys(value.to_s)
|
23
|
+
elsif tag_name == 'input' and type == 'radio'
|
24
|
+
node.select
|
25
|
+
elsif tag_name == 'input' and type == 'checkbox'
|
26
|
+
node.toggle
|
27
|
+
elsif tag_name == "textarea"
|
28
|
+
node.clear
|
29
|
+
node.send_keys(value.to_s)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def select(option)
|
34
|
+
node.find_element(:xpath, ".//option[contains(.,'#{option}')]").select
|
35
|
+
end
|
36
|
+
|
37
|
+
def click
|
38
|
+
node.click
|
39
|
+
end
|
40
|
+
|
41
|
+
def tag_name
|
42
|
+
node.tag_name
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def type
|
48
|
+
self[:type]
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
attr_reader :app, :rack_server
|
54
|
+
|
55
|
+
def self.driver
|
56
|
+
unless @driver
|
57
|
+
@driver = Selenium::WebDriver.for :firefox
|
58
|
+
at_exit do
|
59
|
+
@driver.quit
|
60
|
+
end
|
61
|
+
end
|
62
|
+
@driver
|
63
|
+
end
|
64
|
+
|
65
|
+
def initialize(app)
|
66
|
+
@app = app
|
67
|
+
@rack_server = Capybara::Server.new(@app)
|
68
|
+
@rack_server.boot
|
69
|
+
end
|
70
|
+
|
71
|
+
def visit(path)
|
72
|
+
driver.navigate.to(url(path))
|
73
|
+
end
|
74
|
+
|
75
|
+
def body
|
76
|
+
driver.page_source
|
77
|
+
end
|
78
|
+
|
79
|
+
def find(selector)
|
80
|
+
driver.find_elements(:xpath, selector).map { |node| Node.new(self, node) }
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
def url(path)
|
86
|
+
rack_server.url(path)
|
87
|
+
end
|
88
|
+
|
89
|
+
def driver
|
90
|
+
self.class.driver
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
data/lib/capybara/dsl.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
module Capybara
|
2
|
+
class << self
|
3
|
+
attr_writer :default_driver, :current_driver, :javascript_driver
|
4
|
+
|
5
|
+
attr_accessor :app
|
6
|
+
|
7
|
+
def default_driver
|
8
|
+
@default_driver || :rack_test
|
9
|
+
end
|
10
|
+
|
11
|
+
def current_driver
|
12
|
+
@current_driver || default_driver
|
13
|
+
end
|
14
|
+
alias_method :mode, :current_driver
|
15
|
+
|
16
|
+
def javascript_driver
|
17
|
+
@javascript_driver || :selenium
|
18
|
+
end
|
19
|
+
|
20
|
+
def use_default_driver
|
21
|
+
@current_driver = nil
|
22
|
+
end
|
23
|
+
|
24
|
+
def current_session
|
25
|
+
session_pool["#{current_driver}#{app.object_id}"] ||= Capybara::Session.new(current_driver, app)
|
26
|
+
end
|
27
|
+
|
28
|
+
def reset_sessions!
|
29
|
+
@session_pool = nil
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def session_pool
|
35
|
+
@session_pool ||= {}
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
extend(self)
|
40
|
+
|
41
|
+
def page
|
42
|
+
Capybara.current_session
|
43
|
+
end
|
44
|
+
|
45
|
+
SESSION_METHODS = [
|
46
|
+
:visit, :body, :click_link, :click_button, :fill_in, :choose, :has_xpath?, :has_css?,
|
47
|
+
:check, :uncheck, :attach_file, :select, :has_content?, :within, :save_and_open_page,
|
48
|
+
:find_field, :find_link, :find_button, :field_labeled
|
49
|
+
]
|
50
|
+
SESSION_METHODS.each do |method|
|
51
|
+
class_eval <<-RUBY, __FILE__, __LINE__+1
|
52
|
+
def #{method}(*args, &block)
|
53
|
+
page.#{method}(*args, &block)
|
54
|
+
end
|
55
|
+
RUBY
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
class Capybara::Node
|
2
|
+
attr_reader :driver, :node
|
3
|
+
|
4
|
+
def initialize(driver, node)
|
5
|
+
@driver = driver
|
6
|
+
@node = node
|
7
|
+
end
|
8
|
+
|
9
|
+
def text
|
10
|
+
raise "Not implemented"
|
11
|
+
end
|
12
|
+
|
13
|
+
def [](name)
|
14
|
+
raise "Not implemented"
|
15
|
+
end
|
16
|
+
|
17
|
+
def value
|
18
|
+
self[:value]
|
19
|
+
end
|
20
|
+
|
21
|
+
def set(value)
|
22
|
+
raise "Not implemented"
|
23
|
+
end
|
24
|
+
|
25
|
+
def select(option)
|
26
|
+
raise "Not implemented"
|
27
|
+
end
|
28
|
+
|
29
|
+
def click
|
30
|
+
raise "Not implemented"
|
31
|
+
end
|
32
|
+
|
33
|
+
def tag_name
|
34
|
+
raise "Not implemented"
|
35
|
+
end
|
36
|
+
end
|