capybara 0.1
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 +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
|