bjeanes-capybara 0.3.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 +87 -0
- data/README.rdoc +404 -0
- data/Rakefile +29 -0
- data/config.ru +6 -0
- data/lib/capybara.rb +53 -0
- data/lib/capybara/cucumber.rb +32 -0
- data/lib/capybara/driver/base.rb +37 -0
- data/lib/capybara/driver/celerity_driver.rb +135 -0
- data/lib/capybara/driver/culerity_driver.rb +25 -0
- data/lib/capybara/driver/rack_test_driver.rb +244 -0
- data/lib/capybara/driver/selenium_driver.rb +137 -0
- data/lib/capybara/dsl.rb +60 -0
- data/lib/capybara/node.rb +69 -0
- data/lib/capybara/rails.rb +11 -0
- data/lib/capybara/save_and_open_page.rb +33 -0
- data/lib/capybara/searchable.rb +53 -0
- data/lib/capybara/server.rb +111 -0
- data/lib/capybara/session.rb +274 -0
- data/lib/capybara/wait_until.rb +23 -0
- data/lib/capybara/xpath.rb +176 -0
- data/script/console +10 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/spec/capybara_spec.rb +18 -0
- data/spec/driver/celerity_driver_spec.rb +17 -0
- data/spec/driver/culerity_driver_spec.rb +13 -0
- data/spec/driver/rack_test_driver_spec.rb +12 -0
- data/spec/driver/remote_culerity_driver_spec.rb +26 -0
- data/spec/driver/remote_selenium_driver_spec.rb +18 -0
- data/spec/driver/selenium_driver_spec.rb +12 -0
- data/spec/drivers_spec.rb +139 -0
- data/spec/dsl/all_spec.rb +69 -0
- data/spec/dsl/attach_file_spec.rb +64 -0
- data/spec/dsl/check_spec.rb +39 -0
- data/spec/dsl/choose_spec.rb +26 -0
- data/spec/dsl/click_button_spec.rb +218 -0
- data/spec/dsl/click_link_spec.rb +108 -0
- data/spec/dsl/click_spec.rb +24 -0
- data/spec/dsl/current_url_spec.rb +8 -0
- data/spec/dsl/fill_in_spec.rb +91 -0
- data/spec/dsl/find_button_spec.rb +16 -0
- data/spec/dsl/find_by_id_spec.rb +16 -0
- data/spec/dsl/find_field_spec.rb +22 -0
- data/spec/dsl/find_link_spec.rb +17 -0
- data/spec/dsl/find_spec.rb +57 -0
- data/spec/dsl/has_button_spec.rb +32 -0
- data/spec/dsl/has_content_spec.rb +101 -0
- data/spec/dsl/has_css_spec.rb +107 -0
- data/spec/dsl/has_field_spec.rb +96 -0
- data/spec/dsl/has_link_spec.rb +33 -0
- data/spec/dsl/has_xpath_spec.rb +123 -0
- data/spec/dsl/locate_spec.rb +59 -0
- data/spec/dsl/select_spec.rb +71 -0
- data/spec/dsl/uncheck_spec.rb +21 -0
- data/spec/dsl/within_spec.rb +153 -0
- data/spec/dsl_spec.rb +140 -0
- data/spec/fixtures/capybara.jpg +0 -0
- data/spec/fixtures/test_file.txt +1 -0
- data/spec/public/jquery-ui.js +35 -0
- data/spec/public/jquery.js +19 -0
- data/spec/public/test.js +30 -0
- data/spec/save_and_open_page_spec.rb +43 -0
- data/spec/searchable_spec.rb +61 -0
- data/spec/server_spec.rb +47 -0
- data/spec/session/celerity_session_spec.rb +27 -0
- data/spec/session/culerity_session_spec.rb +25 -0
- data/spec/session/rack_test_session_spec.rb +25 -0
- data/spec/session/selenium_session_spec.rb +25 -0
- data/spec/session_spec.rb +77 -0
- data/spec/session_with_headers_support_spec.rb +13 -0
- data/spec/session_with_javascript_support_spec.rb +182 -0
- data/spec/session_without_headers_support_spec.rb +15 -0
- data/spec/session_without_javascript_support_spec.rb +15 -0
- data/spec/spec_helper.rb +27 -0
- data/spec/test_app.rb +71 -0
- data/spec/views/buttons.erb +4 -0
- data/spec/views/fieldsets.erb +29 -0
- data/spec/views/form.erb +226 -0
- data/spec/views/postback.erb +13 -0
- data/spec/views/tables.erb +122 -0
- data/spec/views/with_html.erb +38 -0
- data/spec/views/with_js.erb +34 -0
- data/spec/views/with_scope.erb +36 -0
- data/spec/views/with_simple_html.erb +1 -0
- data/spec/wait_until_spec.rb +28 -0
- data/spec/xpath_spec.rb +271 -0
- metadata +239 -0
@@ -0,0 +1,137 @@
|
|
1
|
+
require 'selenium-webdriver'
|
2
|
+
|
3
|
+
class Capybara::Driver::Selenium < Capybara::Driver::Base
|
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 == 'textarea' or (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
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def select(option)
|
31
|
+
option_node = node.find_element(:xpath, ".//option[text()='#{option}']") || node.find_element(:xpath, ".//option[contains(.,'#{option}')]")
|
32
|
+
option_node.select
|
33
|
+
rescue
|
34
|
+
options = node.find_elements(:xpath, "//option").map { |o| "'#{o.text}'" }.join(', ')
|
35
|
+
raise Capybara::OptionNotFound, "No such option '#{option}' in this select box. Available options: #{options}"
|
36
|
+
end
|
37
|
+
|
38
|
+
def unselect(option)
|
39
|
+
if node['multiple'] != 'multiple'
|
40
|
+
raise Capybara::UnselectNotAllowed, "Cannot unselect option '#{option}' from single select box."
|
41
|
+
end
|
42
|
+
|
43
|
+
begin
|
44
|
+
option_node = node.find_element(:xpath, ".//option[text()='#{option}']") || node.find_element(:xpath, ".//option[contains(.,'#{option}')]")
|
45
|
+
option_node.clear
|
46
|
+
rescue
|
47
|
+
options = node.find_elements(:xpath, "//option").map { |o| "'#{o.text}'" }.join(', ')
|
48
|
+
raise Capybara::OptionNotFound, "No such option '#{option}' in this select box. Available options: #{options}"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def click
|
53
|
+
node.click
|
54
|
+
end
|
55
|
+
|
56
|
+
def drag_to(element)
|
57
|
+
node.drag_and_drop_on(element.node)
|
58
|
+
end
|
59
|
+
|
60
|
+
def tag_name
|
61
|
+
node.tag_name
|
62
|
+
end
|
63
|
+
|
64
|
+
def visible?
|
65
|
+
node.displayed? and node.displayed? != "false"
|
66
|
+
end
|
67
|
+
|
68
|
+
def trigger(event)
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def type
|
74
|
+
self[:type]
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
attr_reader :app, :rack_server
|
80
|
+
|
81
|
+
def self.driver
|
82
|
+
unless @driver
|
83
|
+
@driver = Selenium::WebDriver.for :firefox
|
84
|
+
at_exit do
|
85
|
+
@driver.quit
|
86
|
+
end
|
87
|
+
end
|
88
|
+
@driver
|
89
|
+
end
|
90
|
+
|
91
|
+
def initialize(app)
|
92
|
+
@app = app
|
93
|
+
@rack_server = Capybara::Server.new(@app)
|
94
|
+
@rack_server.boot if Capybara.run_server
|
95
|
+
end
|
96
|
+
|
97
|
+
def visit(path)
|
98
|
+
browser.navigate.to(url(path))
|
99
|
+
end
|
100
|
+
|
101
|
+
def source
|
102
|
+
browser.page_source
|
103
|
+
end
|
104
|
+
|
105
|
+
def body
|
106
|
+
browser.page_source
|
107
|
+
end
|
108
|
+
|
109
|
+
def current_url
|
110
|
+
browser.current_url
|
111
|
+
end
|
112
|
+
|
113
|
+
def find(selector)
|
114
|
+
browser.find_elements(:xpath, selector).map { |node| Node.new(self, node) }
|
115
|
+
end
|
116
|
+
|
117
|
+
def wait?; true; end
|
118
|
+
|
119
|
+
def evaluate_script(script)
|
120
|
+
browser.execute_script "return #{script}"
|
121
|
+
end
|
122
|
+
|
123
|
+
def browser
|
124
|
+
self.class.driver
|
125
|
+
end
|
126
|
+
|
127
|
+
def cleanup!
|
128
|
+
browser.manage.delete_all_cookies
|
129
|
+
end
|
130
|
+
|
131
|
+
private
|
132
|
+
|
133
|
+
def url(path)
|
134
|
+
rack_server.url(path)
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
data/lib/capybara/dsl.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'capybara'
|
2
|
+
|
3
|
+
module Capybara
|
4
|
+
class << self
|
5
|
+
attr_writer :default_driver, :current_driver, :javascript_driver
|
6
|
+
|
7
|
+
attr_accessor :app
|
8
|
+
|
9
|
+
def default_driver
|
10
|
+
@default_driver || :rack_test
|
11
|
+
end
|
12
|
+
|
13
|
+
def current_driver
|
14
|
+
@current_driver || default_driver
|
15
|
+
end
|
16
|
+
alias_method :mode, :current_driver
|
17
|
+
|
18
|
+
def javascript_driver
|
19
|
+
@javascript_driver || :selenium
|
20
|
+
end
|
21
|
+
|
22
|
+
def use_default_driver
|
23
|
+
@current_driver = nil
|
24
|
+
end
|
25
|
+
|
26
|
+
def current_session
|
27
|
+
session_pool["#{current_driver}#{app.object_id}"] ||= Capybara::Session.new(current_driver, app)
|
28
|
+
end
|
29
|
+
|
30
|
+
def current_session?
|
31
|
+
session_pool.has_key?("#{current_driver}#{app.object_id}")
|
32
|
+
end
|
33
|
+
|
34
|
+
def reset_sessions!
|
35
|
+
session_pool.each { |mode, session| session.cleanup! }
|
36
|
+
@session_pool = nil
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def session_pool
|
42
|
+
@session_pool ||= {}
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
extend(self)
|
47
|
+
|
48
|
+
def page
|
49
|
+
Capybara.current_session
|
50
|
+
end
|
51
|
+
|
52
|
+
Session::DSL_METHODS.each do |method|
|
53
|
+
class_eval <<-RUBY, __FILE__, __LINE__+1
|
54
|
+
def #{method}(*args, &block)
|
55
|
+
page.#{method}(*args, &block)
|
56
|
+
end
|
57
|
+
RUBY
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module Capybara
|
2
|
+
class Node
|
3
|
+
include Searchable
|
4
|
+
|
5
|
+
attr_reader :driver, :node
|
6
|
+
|
7
|
+
def initialize(driver, node)
|
8
|
+
@driver = driver
|
9
|
+
@node = node
|
10
|
+
end
|
11
|
+
|
12
|
+
def text
|
13
|
+
raise NotImplementedError
|
14
|
+
end
|
15
|
+
|
16
|
+
def [](name)
|
17
|
+
raise NotImplementedError
|
18
|
+
end
|
19
|
+
|
20
|
+
def value
|
21
|
+
self[:value]
|
22
|
+
end
|
23
|
+
|
24
|
+
def set(value)
|
25
|
+
raise NotImplementedError
|
26
|
+
end
|
27
|
+
|
28
|
+
def select(option)
|
29
|
+
raise NotImplementedError
|
30
|
+
end
|
31
|
+
|
32
|
+
def unselect(option)
|
33
|
+
raise NotImplementedError
|
34
|
+
end
|
35
|
+
|
36
|
+
def click
|
37
|
+
raise NotImplementedError
|
38
|
+
end
|
39
|
+
|
40
|
+
def drag_to(element)
|
41
|
+
raise NotImplementedError
|
42
|
+
end
|
43
|
+
|
44
|
+
def tag_name
|
45
|
+
raise NotImplementedError
|
46
|
+
end
|
47
|
+
|
48
|
+
def visible?
|
49
|
+
raise NotImplementedError
|
50
|
+
end
|
51
|
+
|
52
|
+
def path
|
53
|
+
raise NotSupportedByDriverError
|
54
|
+
end
|
55
|
+
|
56
|
+
def trigger(event)
|
57
|
+
raise NotSupportedByDriverError
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def all_unfiltered(locator)
|
63
|
+
nodes = XPath.wrap(locator).scope(path).paths.map do |path|
|
64
|
+
driver.find(path)
|
65
|
+
end.flatten
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Capybara
|
2
|
+
module SaveAndOpenPage
|
3
|
+
extend(self)
|
4
|
+
|
5
|
+
def save_and_open_page(html)
|
6
|
+
name="capybara-#{Time.new.strftime("%Y%m%d%H%M%S")}.html"
|
7
|
+
|
8
|
+
FileUtils.touch(name) unless File.exist?(name)
|
9
|
+
|
10
|
+
tempfile = File.new(name,'w')
|
11
|
+
tempfile.write(rewrite_css_and_image_references(html))
|
12
|
+
tempfile.close
|
13
|
+
|
14
|
+
open_in_browser(tempfile.path)
|
15
|
+
end
|
16
|
+
|
17
|
+
def open_in_browser(path) # :nodoc
|
18
|
+
require "launchy"
|
19
|
+
Launchy::Browser.run(path)
|
20
|
+
rescue LoadError
|
21
|
+
warn "Sorry, you need to install launchy to open pages: `gem install launchy`"
|
22
|
+
end
|
23
|
+
|
24
|
+
def rewrite_css_and_image_references(response_html) # :nodoc:
|
25
|
+
return response_html unless Capybara.asset_root
|
26
|
+
directories = Dir.new(Capybara.asset_root).entries.inject([]) do |list, name|
|
27
|
+
list << name if File.directory?(name) and not name.to_s =~ /^\./
|
28
|
+
list
|
29
|
+
end
|
30
|
+
response_html.gsub(/("|')\/(#{directories.join('|')})/, '\1' + Capybara.asset_root.to_s + '/\2')
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Capybara
|
2
|
+
module Searchable
|
3
|
+
def find(*args)
|
4
|
+
all(*args).first
|
5
|
+
end
|
6
|
+
|
7
|
+
def find_field(locator)
|
8
|
+
find(:xpath, XPath.field(locator))
|
9
|
+
end
|
10
|
+
alias_method :field_labeled, :find_field
|
11
|
+
|
12
|
+
def find_link(locator)
|
13
|
+
find(:xpath, XPath.link(locator))
|
14
|
+
end
|
15
|
+
|
16
|
+
def find_button(locator)
|
17
|
+
find(:xpath, XPath.button(locator))
|
18
|
+
end
|
19
|
+
|
20
|
+
def find_by_id(id)
|
21
|
+
find(:css, "##{id}")
|
22
|
+
end
|
23
|
+
|
24
|
+
def all(*args)
|
25
|
+
options = if args.last.is_a?(Hash) then args.pop else {} end
|
26
|
+
if args[1].nil?
|
27
|
+
kind, locator = Capybara.default_selector, args.first
|
28
|
+
else
|
29
|
+
kind, locator = args
|
30
|
+
end
|
31
|
+
locator = XPath.from_css(locator) if kind == :css
|
32
|
+
|
33
|
+
results = all_unfiltered(locator)
|
34
|
+
|
35
|
+
if options[:text]
|
36
|
+
results = results.select { |n| n.text.match(options[:text]) }
|
37
|
+
end
|
38
|
+
|
39
|
+
if options[:visible] or Capybara.ignore_hidden_elements
|
40
|
+
results = results.select { |n| n.visible? }
|
41
|
+
end
|
42
|
+
|
43
|
+
results
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def all_unfiltered(locator)
|
49
|
+
raise "Must be overridden"
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'net/http'
|
3
|
+
require 'rack'
|
4
|
+
|
5
|
+
class Capybara::Server
|
6
|
+
class Identify
|
7
|
+
def initialize(app)
|
8
|
+
@app = app
|
9
|
+
end
|
10
|
+
|
11
|
+
def call(env)
|
12
|
+
if env["PATH_INFO"] == "/__identify__"
|
13
|
+
[200, {}, @app.object_id.to_s]
|
14
|
+
else
|
15
|
+
@app.call(env)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_reader :app, :port
|
21
|
+
|
22
|
+
def initialize(app)
|
23
|
+
@app = app
|
24
|
+
end
|
25
|
+
|
26
|
+
def host
|
27
|
+
"localhost"
|
28
|
+
end
|
29
|
+
|
30
|
+
def url(path)
|
31
|
+
if path =~ /^http/
|
32
|
+
path
|
33
|
+
else
|
34
|
+
(Capybara.app_host || "http://#{host}:#{port}") + path.to_s
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def responsive?
|
39
|
+
is_running_on_port?(port)
|
40
|
+
end
|
41
|
+
|
42
|
+
def handler
|
43
|
+
begin
|
44
|
+
require 'rack/handler/thin'
|
45
|
+
Rack::Handler::Thin
|
46
|
+
rescue LoadError
|
47
|
+
begin
|
48
|
+
require 'rack/handler/mongrel'
|
49
|
+
Rack::Handler::Mongrel
|
50
|
+
rescue LoadError
|
51
|
+
require 'rack/handler/webrick'
|
52
|
+
Rack::Handler::WEBrick
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def boot
|
58
|
+
find_available_port
|
59
|
+
Capybara.log "application has already booted" and return self if responsive?
|
60
|
+
Capybara.log "booting Rack applicartion on port #{port}"
|
61
|
+
|
62
|
+
Timeout.timeout(10) do
|
63
|
+
Thread.new do
|
64
|
+
handler.run(Identify.new(@app), :Port => port, :AccessLog => [])
|
65
|
+
end
|
66
|
+
Capybara.log "checking if application has booted"
|
67
|
+
|
68
|
+
loop do
|
69
|
+
Capybara.log("application has booted") and break if responsive?
|
70
|
+
Capybara.log("waiting for application to boot...")
|
71
|
+
sleep 0.5
|
72
|
+
end
|
73
|
+
end
|
74
|
+
self
|
75
|
+
rescue Timeout::Error
|
76
|
+
Capybara.log "Rack application timed out during boot"
|
77
|
+
exit
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def find_available_port
|
83
|
+
@port = 9887
|
84
|
+
@port += 1 while is_port_open?(@port) and not is_running_on_port?(@port)
|
85
|
+
end
|
86
|
+
|
87
|
+
def is_running_on_port?(tested_port)
|
88
|
+
res = Net::HTTP.start(host, tested_port) { |http| http.get('/__identify__') }
|
89
|
+
|
90
|
+
if res.is_a?(Net::HTTPSuccess) or res.is_a?(Net::HTTPRedirection)
|
91
|
+
return res.body == @app.object_id.to_s
|
92
|
+
end
|
93
|
+
rescue Errno::ECONNREFUSED
|
94
|
+
return false
|
95
|
+
end
|
96
|
+
|
97
|
+
def is_port_open?(tested_port)
|
98
|
+
Timeout::timeout(1) do
|
99
|
+
begin
|
100
|
+
s = TCPSocket.new(host, tested_port)
|
101
|
+
s.close
|
102
|
+
return true
|
103
|
+
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
|
104
|
+
return false
|
105
|
+
end
|
106
|
+
end
|
107
|
+
rescue Timeout::Error
|
108
|
+
return false
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|