Merge remote branch 'trammel/master'

This commit is contained in:
Ingemar Edsborn 2010-06-03 08:55:15 +02:00
commit 46962e1278
89 changed files with 870 additions and 766 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
.idea/
.DS_Store
pkg
*~

View File

@ -1,4 +1,87 @@
=== 0.0.1 2009-11-04
# Version 0.3.8
Release date: 2010-05-12
### Added
* Within_frame method to execute a block of code within a particular iframe (Selenium only!)
### Fixed
* Single quotes are properly escaped with `select` under rack-test and Selenium.
* The :text option for searches now escapes regexp special characters when a string is given.
* Selenium now correctly checks already checked checkboxes (same with uncheck)
* Timing issue which caused Selenium to hang under certain circumstances.
* Selenium now resolves attributes even if they are given as a Symbol
# Version 0.3.7
Release date: 2010-04-09
This is a drop in compatible maintainance release. It's mostly
important for driver authors.
### Added
* RackTest scans for data-method which rails3 uses to change the request method
### Fixed
* Don't hang when starting server on Windoze
### Changed
* The driver and session specs are now located inside lib! Driver authors can simply require them.
# Version 0.3.6
Release date: 2010-03-22
This is a maintainance release with minor bug fixes, should be
drop in compatible.
### Added
* It's now possible to load in external drivers
### Fixed
* has_content? ignores whitespace
* Trigger events when choosing radios and checking checkboxes under Selenium
* Make Capybara.app totally optional when running without server
* Changed fallback host so it matches the one set up by Rails' integration tests
# Version 0.3.5
Release date: 2010-02-26
This is a mostly backwards compatible release, it does break
the API in some minor places, which should hopefully not affect
too many users, please read the release notes carefully!
### Breaking
* Relative searching in a node (e.g. find('//p').all('//a')) will now follow XPath standard
this means that if you want to find descendant nodes only, you'll need to prefix a dot!
* `visit` now accepts fully qualified URLs for drivers that support it.
* Capybara will always try to run a rack server, unless you set Capybara.run_sever = false
### Changed
* thin is preferred over mongrel and webrick, since it is Ruby 1.9 compatible
* click_button and click will find <input type="button">, clicking them does nothing in RackTest
### Added
* Much improved error messages in a multitude of places
* More semantic page querying with has_link?, has_button?, etc...
* Option to ignore hidden elements when querying and interacting with the page
* Support for multiple selects
### Fixed
* find_by_id is no longer broken
* clicking links where the image's alt attribute contains the text is now possible
* within_fieldset and within_table work when the default selector is CSS
* boolean attributes work the same across drivers (return true/false)
* 1 major enhancement:
* Initial release

View File

@ -1,87 +0,0 @@
History.txt
Manifest.txt
README.rdoc
Rakefile
config.ru
lib/capybara.rb
lib/capybara/cucumber.rb
lib/capybara/driver/base.rb
lib/capybara/driver/celerity_driver.rb
lib/capybara/driver/culerity_driver.rb
lib/capybara/driver/rack_test_driver.rb
lib/capybara/driver/selenium_driver.rb
lib/capybara/dsl.rb
lib/capybara/node.rb
lib/capybara/rails.rb
lib/capybara/save_and_open_page.rb
lib/capybara/searchable.rb
lib/capybara/server.rb
lib/capybara/session.rb
lib/capybara/wait_until.rb
lib/capybara/xpath.rb
script/console
script/destroy
script/generate
spec/capybara_spec.rb
spec/driver/celerity_driver_spec.rb
spec/driver/culerity_driver_spec.rb
spec/driver/rack_test_driver_spec.rb
spec/driver/remote_culerity_driver_spec.rb
spec/driver/remote_selenium_driver_spec.rb
spec/driver/selenium_driver_spec.rb
spec/drivers_spec.rb
spec/dsl/all_spec.rb
spec/dsl/attach_file_spec.rb
spec/dsl/check_spec.rb
spec/dsl/choose_spec.rb
spec/dsl/click_button_spec.rb
spec/dsl/click_link_spec.rb
spec/dsl/click_spec.rb
spec/dsl/current_url_spec.rb
spec/dsl/fill_in_spec.rb
spec/dsl/find_button_spec.rb
spec/dsl/find_by_id_spec.rb
spec/dsl/find_field_spec.rb
spec/dsl/find_link_spec.rb
spec/dsl/find_spec.rb
spec/dsl/has_button_spec.rb
spec/dsl/has_content_spec.rb
spec/dsl/has_css_spec.rb
spec/dsl/has_field_spec.rb
spec/dsl/has_link_spec.rb
spec/dsl/has_xpath_spec.rb
spec/dsl/locate_spec.rb
spec/dsl/select_spec.rb
spec/dsl/uncheck_spec.rb
spec/dsl/within_spec.rb
spec/dsl_spec.rb
spec/fixtures/capybara.jpg
spec/fixtures/test_file.txt
spec/public/jquery-ui.js
spec/public/jquery.js
spec/public/test.js
spec/save_and_open_page_spec.rb
spec/searchable_spec.rb
spec/server_spec.rb
spec/session/celerity_session_spec.rb
spec/session/culerity_session_spec.rb
spec/session/rack_test_session_spec.rb
spec/session/selenium_session_spec.rb
spec/session_spec.rb
spec/session_with_headers_support_spec.rb
spec/session_with_javascript_support_spec.rb
spec/session_without_headers_support_spec.rb
spec/session_without_javascript_support_spec.rb
spec/spec_helper.rb
spec/test_app.rb
spec/views/buttons.erb
spec/views/fieldsets.erb
spec/views/form.erb
spec/views/postback.erb
spec/views/tables.erb
spec/views/with_html.erb
spec/views/with_js.erb
spec/views/with_scope.erb
spec/views/with_simple_html.erb
spec/wait_until_spec.rb
spec/xpath_spec.rb

View File

@ -27,7 +27,8 @@ On OSX you may have to install libffi, you can install it via MacPorts with:
* Report issues on {GitHub Issues}[http://github.com/jnicklas/capybara/issues]
Pull requests are very welcome! Make sure your patches are well tested, Capybara is
a testing tool after all.
a testing tool after all. Please create a topic branch for every separate change
you make.
== Using Capybara with Cucumber
@ -55,6 +56,9 @@ Now you can use it in your steps:
click_link 'Sign in'
end
Please note that while Capybara uses XPath selectors by default, Cucumber explicitly
changes this to CSS in `env.rb`. See "XPath and CSS" below.
== Default and current driver
You can set up a default driver for your features. For example if you'd prefer
@ -98,6 +102,11 @@ At the moment, Capybara supports Webdriver, also called Selenium 2.0, *not*
Selenium RC. Provided Firefox is installed, everything is set up for you, and
you should be able to start using Selenium right away.
If desired, you can change Selenium browser to :chrome or :ie:
require "selenium-webdriver"
Selenium::WebDriver.for :chrome
== Celerity
Celerity only runs on JRuby, so you'll need to install the celerity gem under
@ -151,7 +160,7 @@ with the various form elements:
fill_in('First Name', :with => 'John')
fill_in('Password', :with => 'Seekrit')
fill_in('Description', :with => 'Really Long Text…')
choose('An Option')
choose('A Radio Button')
check('A Checkbox')
uncheck('A Checkbox')
attach_file('Image', '/path/to/image.jpg')
@ -177,7 +186,7 @@ You can choose which kind of selector Capybara uses by default, by setting
There are special methods for restricting the scope to a specific fieldset,
identified by either an id or the text of the fieldet's legend tag, and to a
specific table, identified by either idea or text of the table's caption tag.
specific table, identified by either id or text of the table's caption tag.
within_fieldset('Employee') do
fill_in 'Name', :with => 'Jimmy'
@ -214,7 +223,7 @@ You can also find specific elements, in order to manipulate them:
find_button('Send').click
find('//table/tr').click
wait_for("//*[@id='overlay'").find("//h1").click
locate("//*[@id='overlay'").find("//h1").click
all('a').each { |a| a[:href] }
=== Scripting
@ -255,7 +264,7 @@ is (the default is 2 seconds):
Capybara.default_wait_time = 5
Be aware that because of this behaviour, the following two statements are *not*
identical, and you should *always* use the latter!
equivalent, and you should *always* use the latter!
page.should_not have_xpath('//a')
page.should have_no_xpath('//a')
@ -263,36 +272,37 @@ identical, and you should *always* use the latter!
The former would incorrectly wait for the content to appear, since the
asynchronous process has not yet removed the element from the page, it would
therefore fail, even though the code might be working correctly. The latter
correctly wait for the element to disappear from the page.
correctly waits for the element to disappear from the page.
== Using the DSL outside cucumber
You can mix the DSL into any context, for example you could use it in RSpec
examples. Just load the dsl and include it anywhere:
examples. Just load the DSL and include it anywhere:
require 'capybara'
require 'capybara/dsl'
include Capybara
Capybara.default_driver = :culerity
within("//form[@id='session']") do
fill_in 'Login', :with => 'user@example.com'
fill_in 'Password', :with => 'password'
module MyModule
include Capybara
def login!
within("//form[@id='session']") do
fill_in 'Login', :with => 'user@example.com'
fill_in 'Password', :with => 'password'
end
click_link 'Sign in'
end
end
click_link 'Sign in'
== Calling remote servers
Normally Capybara expects to be testing an in-process Rack application, but you can also use it to talk to a web server running anywhere on the internets, by setting app_host:
require 'capybara'
require 'capybara/dsl'
include Capybara
Capybara.current_driver = :selenium
Capybara.app_host = 'http://www.google.com'
...
visit('/')
Note that rack-test does not support running against a remote server. With drivers that support it, you can also visit any URL directly:
@ -353,31 +363,6 @@ moving from Webrat and used CSS a lot, or simply generally prefer CSS:
<tt><a href="/same/url#"></tt> instead. You can achieve this in Rails with
<tt>link_to('foo', :anchor => '')</tt>
== Contributors:
The following people have dedicated their time and effort to Capybara:
* Jonas Nicklas
* Dennis Rogenius
* Rob Holland
* Wincent Colaiuta
* Andrea Fazzi
* Aslak Hellesøy
* Andrew Brown
* Lenny Marks
* Aaron Patterson
* Dan Dofter
* Thorbjørn Hermansen
* Louis T.
* Stephan Hagemann
* Graham Ashton
* Joseph Wilk
* Matt Wynne
* Piotr Sarnacki
* Pavel Gabriel
* Bodaniel Jeanes
* Carl Porth
== License:
(The MIT License)

View File

@ -1,29 +1,9 @@
require 'rubygems'
require 'spec/rake/spectask'
gem 'hoe', '>= 2.1.0'
require 'hoe'
Hoe.plugin :newgem
# Generate all the Rake tasks
# Run 'rake -T' to see list of generated tasks (from gem root directory)
Hoe.spec 'capybara' do
developer 'Jonas Nicklas', 'jonas.nicklas@gmail.com'
self.readme_file = 'README.rdoc'
self.extra_rdoc_files = Dir['*.rdoc']
self.extra_deps = [
['nokogiri', '>= 1.3.3'],
['mime-types', '>= 1.16'],
['culerity', '>= 0.2.4'],
['selenium-webdriver', '>= 0.0.3'],
['rack', '>= 1.0.0'],
['rack-test', '>= 0.5.2'],
]
self.extra_dev_deps = [
['sinatra', '>= 0.9.4'],
['rspec', '>= 1.2.9']
]
desc "Run all examples"
Spec::Rake::SpecTask.new('spec') do |t|
t.spec_files = FileList['spec/**/*.rb']
end
task :default => :spec

34
capybara.gemspec Normal file
View File

@ -0,0 +1,34 @@
# -*- encoding: utf-8 -*-
lib = File.expand_path('../lib/', __FILE__)
$:.unshift lib unless $:.include?(lib)
require 'capybara/version'
Gem::Specification.new do |s|
s.name = "capybara"
s.rubyforge_project = "capybara"
s.version = Capybara::VERSION
s.authors = ["Jonas Nicklas"]
s.email = ["jonas.nicklas@gmail.com"]
s.description = "Capybara is an integration testing tool for rack based web applications. It simulates how a user would interact with a website"
s.files = Dir.glob("{lib,spec}/**/*") + %w(README.rdoc History.txt)
s.extra_rdoc_files = ["README.rdoc"]
s.homepage = "http://github.com/jnicklas/capybara"
s.rdoc_options = ["--main", "README.rdoc"]
s.require_paths = ["lib"]
s.rubygems_version = "1.3.6"
s.summary = "Capybara aims to simplify the process of integration testing Rack applications, such as Rails, Sinatra or Merb"
s.add_runtime_dependency("nokogiri", [">= 1.3.3"])
s.add_runtime_dependency("mime-types", [">= 1.16"])
s.add_runtime_dependency("culerity", [">= 0.2.4"])
s.add_runtime_dependency("selenium-webdriver", [">= 0.0.3"])
s.add_runtime_dependency("rack", [">= 1.0.0"])
s.add_runtime_dependency("rack-test", [">= 0.5.2"])
s.add_development_dependency("sinatra", [">= 0.9.4"])
s.add_development_dependency("rspec", [">= 1.2.9"])
end

View File

@ -1,6 +1,6 @@
## This is not needed for Thin > 1.0.0
ENV['RACK_ENV'] = "production"
require File.expand_path('spec/test_app', File.dirname(__FILE__))
require File.expand_path('lib/capybara/spec/test_app', File.dirname(__FILE__))
run TestApp
run TestApp

View File

@ -2,8 +2,6 @@ require 'timeout'
require 'nokogiri'
module Capybara
VERSION = '0.3.0'
class CapybaraError < StandardError; end
class DriverNotFoundError < CapybaraError; end
class ElementNotFound < CapybaraError; end
@ -15,7 +13,7 @@ module Capybara
class InfiniteRedirectError < TimeoutError; end
class << self
attr_accessor :debug, :asset_root, :app_host, :run_server
attr_accessor :debug, :asset_root, :app_host, :run_server, :default_host
attr_accessor :default_selector, :default_wait_time, :ignore_hidden_elements
def default_selector
@ -37,6 +35,7 @@ module Capybara
autoload :Node, 'capybara/node'
autoload :XPath, 'capybara/xpath'
autoload :Searchable, 'capybara/searchable'
autoload :VERSION, 'capybara/version'
module Driver
autoload :Base, 'capybara/driver/base'

View File

@ -19,6 +19,9 @@ class Capybara::Driver::Base
false
end
def wait_until *args
end
def response_headers
raise Capybara::NotSupportedByDriverError
end
@ -27,6 +30,10 @@ class Capybara::Driver::Base
raise NotImplementedError
end
def within_frame(frame_id)
raise Capybara::NotSupportedByDriverError
end
def source
raise NotImplementedError
end
@ -34,4 +41,8 @@ class Capybara::Driver::Base
def cleanup!
end
def has_shortcircuit_timeout?
false
end
end

View File

@ -14,7 +14,7 @@ class Capybara::Driver::Celerity < Capybara::Driver::Base
end
def value
if node.type == 'select-multiple'
if tag_name == "select" and node.multiple?
node.selected_options
else
super
@ -77,6 +77,14 @@ class Capybara::Driver::Celerity < Capybara::Driver::Base
node.fire_event(event.to_s)
end
private
def all_unfiltered(locator)
noko_node = Nokogiri::HTML(driver.body).xpath(node.xpath).first
all_nodes = noko_node.xpath(locator).map { |n| n.path }.join(' | ')
driver.find(all_nodes)
end
end
attr_reader :app, :rack_server

View File

@ -28,17 +28,17 @@ class Capybara::Driver::RackTest < Capybara::Driver::Base
def set(value)
if tag_name == 'input' and %w(text password hidden file).include?(type)
node['value'] = value.to_s
elsif tag_name == 'input' and type == 'radio'
driver.html.xpath("//input[@name='#{self[:name]}']").each { |node| node.remove_attribute("checked") }
if tag_name == 'input' and type == 'radio'
driver.html.xpath("//input[@name=#{Capybara::XPath.escape(self[:name])}]").each { |node| node.remove_attribute("checked") }
node['checked'] = 'checked'
elsif tag_name == 'input' and type == 'checkbox'
if value
if value && !node['checked']
node['checked'] = 'checked'
else
elsif !value && node['checked']
node.remove_attribute('checked')
end
elsif tag_name == 'input'
node['value'] = value.to_s
elsif tag_name == "textarea"
node.content = value.to_s
end
@ -49,8 +49,8 @@ class Capybara::Driver::RackTest < Capybara::Driver::Base
node.xpath(".//option[@selected]").each { |node| node.remove_attribute("selected") }
end
if option_node = node.xpath(".//option[text()='#{option}']").first ||
node.xpath(".//option[contains(.,'#{option}')]").first
if option_node = node.xpath(".//option[text()=#{Capybara::XPath.escape(option)}]").first ||
node.xpath(".//option[contains(.,#{Capybara::XPath.escape(option)})]").first
option_node["selected"] = 'selected'
else
options = node.xpath(".//option").map { |o| "'#{o.text}'" }.join(', ')
@ -63,8 +63,8 @@ class Capybara::Driver::RackTest < Capybara::Driver::Base
raise Capybara::UnselectNotAllowed, "Cannot unselect option '#{option}' from single select box."
end
if option_node = node.xpath(".//option[text()='#{option}']").first ||
node.xpath(".//option[contains(.,'#{option}')]").first
if option_node = node.xpath(".//option[text()=#{Capybara::XPath.escape(option)}]").first ||
node.xpath(".//option[contains(.,#{Capybara::XPath.escape(option)})]").first
option_node.remove_attribute('selected')
else
options = node.xpath(".//option").map { |o| "'#{o.text}'" }.join(', ')
@ -74,7 +74,8 @@ class Capybara::Driver::RackTest < Capybara::Driver::Base
def click
if tag_name == 'a'
driver.visit(self[:href].to_s)
method = self["data-method"] || :get
driver.process(method, self[:href].to_s)
elsif (tag_name == 'input' or tag_name == 'button') and %w(submit image).include?(type)
Form.new(driver, form).submit(self)
end
@ -94,6 +95,10 @@ class Capybara::Driver::RackTest < Capybara::Driver::Base
private
def all_unfiltered(locator)
node.xpath(locator).map { |n| self.class.new(driver, n) }
end
def type
node[:type]
end
@ -106,10 +111,8 @@ class Capybara::Driver::RackTest < Capybara::Driver::Base
class Form < Node
def params(button)
params = {}
text_fields = %w[text hidden password url color tel email search].map{|f| "@type='#{f}'"}.join(' or ')
node.xpath(".//input[#{text_fields}]").map do |input|
node.xpath(".//input[@type!='radio' and @type!='checkbox' and @type!='submit']").map do |input|
merge_param!(params, input['name'].to_s, input['value'].to_s)
end
node.xpath(".//textarea").map do |textarea|
@ -141,7 +144,7 @@ class Capybara::Driver::RackTest < Capybara::Driver::Base
end
end
end
merge_param!(params, button[:name], button[:value]) if button[:name]
merge_param!(params, button[:name], button[:value] || "") if button[:name]
params
end
@ -154,7 +157,7 @@ class Capybara::Driver::RackTest < Capybara::Driver::Base
end
private
def method
self[:method] =~ /post/i ? :post : :get
end
@ -174,21 +177,24 @@ class Capybara::Driver::RackTest < Capybara::Driver::Base
end
include ::Rack::Test::Methods
attr_reader :app, :html, :body
attr_reader :app
alias_method :response, :last_response
alias_method :request, :last_request
alias_method :source, :body
def initialize(app)
raise ArgumentError, "rack-test requires a rack application, but none was given" unless app
@app = app
end
def visit(path, attributes = {})
process(:get, path, attributes)
end
def process(method, path, attributes = {})
return if path.gsub(/^#{current_path}/, '') =~ /^#/
get(path, attributes, env)
send(method, path, attributes, env)
follow_redirects!
cache_body
end
def current_url
@ -200,18 +206,40 @@ class Capybara::Driver::RackTest < Capybara::Driver::Base
end
def submit(method, path, attributes)
path = current_path if not path or path.empty?
path = current_path if not path or path.empty?
send(method, path, attributes, env)
follow_redirects!
cache_body
end
def find(selector)
html.xpath(selector).map { |node| Node.new(self, node) }
end
def body
@body ||= response.body
end
def html
@html ||= Nokogiri::HTML(body)
end
alias_method :source, :body
def get(*args, &block); reset_cache; super; end
def post(*args, &block); reset_cache; super; end
def put(*args, &block); reset_cache; super; end
def delete(*args, &block); reset_cache; super; end
private
def reset_cache
@body = nil
@html = nil
end
def build_rack_mock_session # :nodoc:
Rack::MockSession.new(app, Capybara.default_host || "www.example.com")
end
def current_path
request.path rescue ""
end
@ -236,9 +264,9 @@ private
env
end
def cache_body
@body = response.body
@html = Nokogiri::HTML(body)
def reset_cache
@body = nil
@html = nil
end
end

View File

@ -10,25 +10,33 @@ class Capybara::Driver::Selenium < Capybara::Driver::Base
if name == :value
node.value
else
node.attribute(name)
node.attribute(name.to_s)
end
rescue Selenium::WebDriver::Error::WebDriverError
nil
end
def value
if tag_name == "select" and self[:multiple]
node.find_elements(:xpath, ".//option").select { |n| n.selected? }.map { |n| n.text }
else
super
end
end
def set(value)
if tag_name == 'textarea' or (tag_name == 'input' and %w(text password hidden file).include?(type))
node.clear
node.send_keys(value.to_s)
elsif tag_name == 'input' and type == 'radio'
node.select
node.click
elsif tag_name == 'input' and type == 'checkbox'
node.toggle
node.click if node.attribute('checked') != value
end
end
def select(option)
option_node = node.find_element(:xpath, ".//option[text()='#{option}']") || node.find_element(:xpath, ".//option[contains(.,'#{option}')]")
option_node = node.find_element(:xpath, ".//option[normalize-space(text())=#{Capybara::XPath.escape(option)}]") || node.find_element(:xpath, ".//option[contains(.,#{Capybara::XPath.escape(option)})]")
option_node.select
rescue
options = node.find_elements(:xpath, "//option").map { |o| "'#{o.text}'" }.join(', ')
@ -41,7 +49,7 @@ class Capybara::Driver::Selenium < Capybara::Driver::Base
end
begin
option_node = node.find_element(:xpath, ".//option[text()='#{option}']") || node.find_element(:xpath, ".//option[contains(.,'#{option}')]")
option_node = node.find_element(:xpath, ".//option[normalize-space(text())=#{Capybara::XPath.escape(option)}]") || node.find_element(:xpath, ".//option[contains(.,#{Capybara::XPath.escape(option)})]")
option_node.clear
rescue
options = node.find_elements(:xpath, "//option").map { |o| "'#{o.text}'" }.join(', ')
@ -70,6 +78,10 @@ class Capybara::Driver::Selenium < Capybara::Driver::Base
private
def all_unfiltered(locator)
node.find_elements(:xpath, locator).map { |n| self.class.new(driver, n) }
end
def type
self[:type]
end
@ -128,6 +140,13 @@ class Capybara::Driver::Selenium < Capybara::Driver::Base
browser.manage.delete_all_cookies
end
def within_frame(frame_id)
old_window = browser.window_handle
browser.switch_to.frame(frame_id)
yield
browser.switch_to.window old_window
end
private
def url(path)

View File

@ -56,14 +56,5 @@ module Capybara
def trigger(event)
raise NotSupportedByDriverError
end
private
def all_unfiltered(locator)
nodes = XPath.wrap(locator).scope(path).paths.map do |path|
driver.find(path)
end.flatten
end
end
end

View File

@ -3,9 +3,15 @@ require 'capybara/dsl'
Capybara.app = Rack::Builder.new do
map "/" do
use Rails::Rack::Static
run ActionController::Dispatcher.new
if Rails.version.to_f >= 3.0
ActionDispatch::Static
run Rails.application
else # Rails 2
use Rails::Rack::Static
run ActionController::Dispatcher.new
end
end
end.to_app
Capybara.asset_root = Rails.root.join('public')

View File

@ -33,6 +33,7 @@ module Capybara
results = all_unfiltered(locator)
if options[:text]
options[:text] = Regexp.escape(options[:text]) unless options[:text].kind_of?(Regexp)
results = results.select { |n| n.text.match(options[:text]) }
end

View File

@ -55,20 +55,23 @@ class Capybara::Server
end
def boot
return self unless @app
find_available_port
Capybara.log "application has already booted" and return self if responsive?
Capybara.log "booting Rack applicartion on port #{port}"
Timeout.timeout(10) do
Thread.new do
handler.run(Identify.new(@app), :Port => port, :AccessLog => [])
end
Capybara.log "checking if application has booted"
Thread.new do
handler.run(Identify.new(@app), :Port => port, :AccessLog => [])
end
Capybara.log "checking if application has booted"
loop do
Capybara.log("application has booted") and break if responsive?
Capybara.log("waiting for application to boot...")
Capybara::WaitUntil.timeout(10) do
if responsive?
Capybara.log("application has booted")
true
else
sleep 0.5
false
end
end
self
@ -90,7 +93,7 @@ private
if res.is_a?(Net::HTTPSuccess) or res.is_a?(Net::HTTPRedirection)
return res.body == @app.object_id.to_s
end
rescue Errno::ECONNREFUSED
rescue Errno::ECONNREFUSED, Errno::EBADF
return false
end

View File

@ -1,54 +1,43 @@
require 'forwardable'
require 'capybara/wait_until'
module Capybara
class Session
extend Forwardable
include Searchable
DSL_METHODS = [
:all, :attach_file, :body, :check, :choose, :click, :click_button, :click_link, :current_url, :drag, :evaluate_script,
:field_labeled, :fill_in, :find, :find_button, :find_by_id, :find_field, :find_link, :has_content?, :has_css?,
:has_no_content?, :has_no_css?, :has_no_xpath?, :has_xpath?, :locate, :save_and_open_page, :select, :source, :uncheck,
:visit, :wait_until, :within, :within_fieldset, :within_table, :has_link?, :has_no_link?, :has_button?, :has_no_button?,
:has_field?, :has_no_field?, :has_checked_field?, :has_unchecked_field?
:visit, :wait_until, :within, :within_fieldset, :within_table, :within_frame, :has_link?, :has_no_link?, :has_button?,
:has_no_button?, :has_field?, :has_no_field?, :has_checked_field?, :has_unchecked_field?, :has_no_table?, :has_table?,
:unselect, :has_select?, :has_no_select?
]
attr_reader :mode, :app
def initialize(mode, app)
def initialize(mode, app=nil)
@mode = mode
@app = app
end
def driver
@driver ||= case mode
when :rack_test
Capybara::Driver::RackTest.new(app)
when :selenium
Capybara::Driver::Selenium.new(app)
when :celerity
Capybara::Driver::Celerity.new(app)
when :culerity
Capybara::Driver::Culerity.new(app)
else
@driver ||= begin
string = mode.to_s
string.gsub!(%r{(^.)|(_.)}) { |m| m[m.length-1,1].upcase }
Capybara::Driver.const_get(string.to_sym).new(app)
rescue NameError
raise Capybara::DriverNotFoundError, "no driver called #{mode} was found"
end
end
def cleanup!
driver.cleanup!
end
def current_url
driver.current_url
end
def response_headers
driver.response_headers
end
def visit(path)
driver.visit(path)
end
def_delegator :driver, :cleanup!
def_delegator :driver, :current_url
def_delegator :driver, :response_headers
def_delegator :driver, :visit
def_delegator :driver, :body
def_delegator :driver, :source
def click(locator)
msg = "no link or button '#{locator}' found"
@ -73,6 +62,7 @@ module Capybara
def fill_in(locator, options={})
msg = "cannot fill in, no text field, text area or password field with id, name, or label '#{locator}' found"
raise "Must pass a hash containing 'with'" if not options.is_a?(Hash) or not options.has_key?(:with)
locate(:xpath, XPath.fillable_field(locator), msg).set(options[:with])
end
@ -106,14 +96,6 @@ module Capybara
locate(:xpath, XPath.file_field(locator), msg).set(path)
end
def body
driver.body
end
def source
driver.source
end
def within(kind, scope=nil)
kind, scope = Capybara.default_selector, kind unless scope
scope = XPath.from_css(scope) if kind == :css
@ -138,6 +120,12 @@ module Capybara
end
end
def within_frame(frame_id)
driver.within_frame(frame_id) do
yield
end
end
def has_xpath?(path, options={})
wait_conditionally_until do
results = all(:xpath, path, options)
@ -244,7 +232,7 @@ module Capybara
end
def wait_until(timeout = Capybara.default_wait_time)
WaitUntil.timeout(timeout) { yield }
WaitUntil.timeout(timeout,driver) { yield }
end
def evaluate_script(script)

View File

@ -1,4 +1,8 @@
require File.expand_path('spec_helper', File.dirname(__FILE__))
require 'capybara/spec/test_app'
Dir[File.dirname(__FILE__)+'/driver/*'].each { |group|
require group
}
shared_examples_for 'driver' do
@ -72,6 +76,26 @@ shared_examples_for 'driver' do
end
end
describe "node relative searching" do
before do
@driver.visit('/tables')
@node = @driver.find('//body').first
end
it "should be able to navigate/search child node" do
@node.all('//table').size.should == 5
@node.find('//form').all('.//table').size.should == 1
@node.find('//form').find('.//table//caption').text.should == 'Agent'
if @driver.class == Capybara::Driver::Selenium
pending("Selenium gets this wrong, see http://code.google.com/p/selenium/issues/detail?id=403") do
@node.find('//form').all('//table').size.should == 5
end
else
@node.find('//form').all('//table').size.should == 5
end
end
end
end
shared_examples_for "driver with javascript support" do
@ -97,7 +121,6 @@ shared_examples_for "driver with javascript support" do
@driver.evaluate_script('1+1').should == 2
end
end
end
shared_examples_for "driver with header support" do
@ -107,33 +130,33 @@ shared_examples_for "driver with header support" do
end
end
shared_examples_for "driver with node path support" do
describe "node relative searching" do
before do
@driver.visit('/tables')
@node = @driver.find('//body').first
shared_examples_for "driver with frame support" do
describe '#within_frame' do
before(:each) do
@driver.visit('/within_frames')
end
it "should be able to navigate/search child nodes" do
@node.all('//table').size.should == 3
@node.find('//form').all('//table').size.should == 1
@node.find('//form').find('//table//caption').text.should == 'Agent'
end
end
end
shared_examples_for "driver without node path support" do
describe "node relative searching" do
before do
@driver.visit('/tables')
@node = @driver.find('//body').first
it "should find the div in frameOne" do
@driver.within_frame("frameOne") do
@driver.find("//*[@id='divInFrameOne']")[0].text.should eql 'This is the text of divInFrameOne'
end
end
it "should get NotSupportedByDriverError" do
running do
@node.all('//form')
end.should raise_error(Capybara::NotSupportedByDriverError)
it "should find the div in FrameTwo" do
@driver.within_frame("frameTwo") do
@driver.find("//*[@id='divInFrameTwo']")[0].text.should eql 'This is the text of divInFrameTwo'
end
end
it "should find the text div in the main window after finding text in frameOne" do
@driver.within_frame("frameOne") do
@driver.find("//*[@id='divInFrameOne']")[0].text.should eql 'This is the text of divInFrameOne'
end
@driver.find("//*[@id='divInMainWindow']")[0].text.should eql 'This is the text for divInMainWindow'
end
it "should find the text div in the main window after finding text in frameTwo" do
@driver.within_frame("frameTwo") do
@driver.find("//*[@id='divInFrameTwo']")[0].text.should eql 'This is the text of divInFrameTwo'
end
@driver.find("//*[@id='divInMainWindow']")[0].text.should eql 'This is the text for divInMainWindow'
end
end
end

View File

Before

Width:  |  Height:  |  Size: 332 B

After

Width:  |  Height:  |  Size: 332 B

View File

@ -27,4 +27,7 @@ $(function() {
$('#with_focus_event').focus(function() {
$('body').append('<p id="focus_event_triggered">Focus Event triggered</p>')
});
});
$('#checkbox_with_event').click(function() {
$('body').append('<p id="checkbox_event_triggered">Checkbox event triggered</p>')
});
});

View File

@ -1,6 +1,10 @@
require File.expand_path('spec_helper', File.dirname(__FILE__))
require 'capybara/spec/test_app'
require 'nokogiri'
Dir[File.dirname(__FILE__)+'/session/*'].each { |group|
require group
}
shared_examples_for "session" do
def extract_results(session)
YAML.load Nokogiri::HTML(session.body).xpath("//pre[@id='results']").first.text

View File

@ -0,0 +1,67 @@
module CheckSpec
shared_examples_for "check" do
describe "#check" do
before do
@session.visit('/form')
end
describe "'checked' attribute" do
it "should be true if checked" do
@session.check("Terms of Use")
@session.find(:xpath, "//input[@id='form_terms_of_use']")['checked'].should be_true
end
it "should be false if unchecked" do
@session.find(:xpath, "//input[@id='form_terms_of_use']")['checked'].should be_false
end
end
describe "checking" do
it "should not change an already checked checkbox" do
@session.find(:xpath, "//input[@id='form_pets_dog']")['checked'].should be_true
@session.check('form_pets_dog')
@session.find(:xpath, "//input[@id='form_pets_dog']")['checked'].should be_true
end
it "should check an unchecked checkbox" do
@session.find(:xpath, "//input[@id='form_pets_cat']")['checked'].should be_false
@session.check('form_pets_cat')
@session.find(:xpath, "//input[@id='form_pets_cat']")['checked'].should be_true
end
end
describe "unchecking" do
it "should not change an already unchecked checkbox" do
@session.find(:xpath, "//input[@id='form_pets_cat']")['checked'].should be_false
@session.uncheck('form_pets_cat')
@session.find(:xpath, "//input[@id='form_pets_cat']")['checked'].should be_false
end
it "should uncheck a checked checkbox" do
@session.find(:xpath, "//input[@id='form_pets_dog']")['checked'].should be_true
@session.uncheck('form_pets_dog')
@session.find(:xpath, "//input[@id='form_pets_dog']")['checked'].should be_false
end
end
it "should check a checkbox by id" do
@session.check("form_pets_cat")
@session.click_button('awesome')
extract_results(@session)['pets'].should include('dog', 'cat', 'hamster')
end
it "should check a checkbox by label" do
@session.check("Cat")
@session.click_button('awesome')
extract_results(@session)['pets'].should include('dog', 'cat', 'hamster')
end
context "with a locator that doesn't exist" do
it "should raise an error" do
running { @session.check('does not exist') }.should raise_error(Capybara::ElementNotFound)
end
end
end
end
end

View File

@ -13,33 +13,33 @@ shared_examples_for "click_button" do
end
context "with value given on a submit button" do
context "on a form with HTML5 fields" do
context "on a form with HTML5 fields" do
before do
@session.click_button('html5_submit')
@results = extract_results(@session)
end
it "should serialise and submit search fields" do
@results['html5_search'].should == 'what are you looking for'
end
it "should serialise and submit email fields" do
@results['html5_email'].should == 'person@email.com'
end
it "should serialise and submit url fields" do
@results['html5_url'].should == 'http://www.example.com'
end
it "should serialise and submit tel fields" do
@results['html5_tel'].should == '911'
end
it "should serialise and submit color fields" do
@results['html5_color'].should == '#FFF'
end
end
end
context "on an HTML4 form" do
before do
@session.click_button('awesome')
@ -113,6 +113,18 @@ shared_examples_for "click_button" do
@session.body.should include('You landed')
end
end
context "with alt given on an image button" do
it "should submit the associated form" do
@session.click_button('oh hai thar')
extract_results(@session)['first_name'].should == 'John'
end
it "should work with partial matches" do
@session.click_button('hai')
extract_results(@session)['first_name'].should == 'John'
end
end
context "with value given on an image button" do
it "should submit the associated form" do
@ -175,7 +187,7 @@ shared_examples_for "click_button" do
@session.click_button('ck_me')
extract_results(@session)['first_name'].should == 'John'
end
it "should prefer exact matches over partial matches" do
@session.click_button('Just a button')
extract_results(@session)['button'].should == 'Just a button'
@ -190,6 +202,12 @@ shared_examples_for "click_button" do
end
end
it "should serialize and send valueless buttons that were clicked" do
@session.click_button('No Value!')
@results = extract_results(@session)
@results['no_value'].should_not be_nil
end
it "should serialize and send GET forms" do
@session.visit('/form')
@session.click_button('med')
@ -202,13 +220,13 @@ shared_examples_for "click_button" do
@session.click_button('Go FAR')
@session.body.should include('You landed')
end
it "should post pack to the same URL when no action given" do
@session.visit('/postback')
@session.click_button('With no action')
@session.body.should include('Postback')
end
it "should post pack to the same URL when blank action given" do
@session.visit('/postback')
@session.click_button('With blank action')

View File

@ -52,6 +52,19 @@ shared_examples_for "fill_in" do
extract_results(@session)['password'].should == 'supasikrit'
end
it "should fill in a field with a custom type" do
pending "selenium doesn't seem to find custom fields" if @session.mode == :selenium
@session.fill_in('Schmooo', :with => 'Schmooo is the game')
@session.click_button('awesome')
extract_results(@session)['schmooo'].should == 'Schmooo is the game'
end
it "should fill in a password field by name" do
@session.fill_in('form[password]', :with => 'supasikrit')
@session.click_button('awesome')
extract_results(@session)['password'].should == 'supasikrit'
end
it "should fill in a password field by label" do
@session.fill_in('Password', :with => 'supasikrit')
@session.click_button('awesome')
@ -69,7 +82,11 @@ shared_examples_for "fill_in" do
@session.click_button('awesome')
extract_results(@session)['name'].should == 'Ford Prefect'
end
it "should throw an exception if a hash containing 'with' is not provided" do
lambda{@session.fill_in 'Name', 'ignu'}.should raise_error
end
context "with ignore_hidden_fields" do
before { Capybara.ignore_hidden_elements = true }
after { Capybara.ignore_hidden_elements = false }

View File

@ -27,6 +27,11 @@ shared_examples_for "has_content" do
@session.should have_content('exercitation ullamco laboris')
end
it "should ignore extra whitespace and newlines" do
@session.visit('/with_html')
@session.should have_content('text with whitespace')
end
it "should be false if the given content is not on the page" do
@session.visit('/with_html')
@session.should_not have_content('xxxxyzzz')

View File

@ -0,0 +1,19 @@
shared_examples_for "session with headers support" do
describe '#response_headers' do
it "should return response headers" do
@session.visit('/with_simple_html')
@session.response_headers['Content-Type'].should == 'text/html'
end
end
end
shared_examples_for "session without headers support" do
describe "#response_headers" do
before{ @session.visit('/with_simple_html') }
it "should raise an error" do
running {
@session.response_headers
}.should raise_error(Capybara::NotSupportedByDriverError)
end
end
end

View File

@ -0,0 +1,204 @@
shared_examples_for "session with javascript support" do
describe 'all JS specs' do
before do
Capybara.default_wait_time = 1
end
after do
Capybara.default_wait_time = 0
end
describe '#find' do
it "should allow triggering of custom JS events" do
pending "cannot figure out how to do this with selenium" if @session.mode == :selenium
@session.visit('/with_js')
@session.find(:css, '#with_focus_event').trigger(:focus)
@session.should have_css('#focus_event_triggered')
end
end
describe '#body' do
it "should return the current state of the page" do
@session.visit('/with_js')
@session.body.should include('I changed it')
@session.body.should_not include('This is text')
end
end
describe '#source' do
it "should return the original, unmodified source of the page" do
pending "cannot figure out how to do this with selenium" if @session.mode == :selenium
@session.visit('/with_js')
@session.source.should include('This is text')
@session.source.should_not include('I changed it')
end
end
describe "#evaluate_script" do
it "should return the evaluated script" do
@session.visit('/with_js')
@session.evaluate_script("1+3").should == 4
end
end
describe '#locate' do
it "should wait for asynchronous load" do
@session.visit('/with_js')
@session.click_link('Click me')
@session.locate("//a[contains(.,'Has been clicked')]")[:href].should == '#'
end
end
describe '#wait_until' do
before do
@default_timeout = Capybara.default_wait_time
end
after do
Capybara.default_wait_time = @default_wait_time
end
it "should wait for block to return true" do
@session.visit('/with_js')
@session.select('My Waiting Option', :from => 'waiter')
@session.evaluate_script('activeRequests == 1').should be_true
@session.wait_until do
@session.evaluate_script('activeRequests == 0')
end
@session.evaluate_script('activeRequests == 0').should be_true
end
it "should raise Capybara::TimeoutError if block doesn't return true within timeout" do
@session.visit('/with_html')
Proc.new do
@session.wait_until(0.1) do
@session.find('//div[@id="nosuchthing"]')
end
end.should raise_error(::Capybara::TimeoutError)
end
it "should accept custom timeout in seconds" do
start = Time.now
Capybara.default_wait_time = 5
begin
@session.wait_until(0.1) { false }
rescue Capybara::TimeoutError; end
(Time.now - start).should be_close(0.1, 0.1)
end
it "should default to Capybara.default_wait_time before timeout" do
@session.driver # init the driver to exclude init timing from test
start = Time.now
Capybara.default_wait_time = 0.2
begin
@session.wait_until { false }
rescue Capybara::TimeoutError; end
if @session.driver.has_shortcircuit_timeout?
(Time.now - start).should be_close(0, 0.1)
else
(Time.now - start).should be_close(0.2, 0.1)
end
end
end
describe '#click' do
it "should wait for asynchronous load" do
@session.visit('/with_js')
@session.click_link('Click me')
@session.click('Has been clicked')
end
end
describe '#click_link' do
it "should wait for asynchronous load" do
@session.visit('/with_js')
@session.click_link('Click me')
@session.click_link('Has been clicked')
end
end
describe '#click_button' do
it "should wait for asynchronous load" do
@session.visit('/with_js')
@session.click_link('Click me')
@session.click_button('New Here')
end
end
describe '#fill_in' do
it "should wait for asynchronous load" do
@session.visit('/with_js')
@session.click_link('Click me')
@session.fill_in('new_field', :with => 'Testing...')
end
end
describe '#check' do
it "should trigger associated events" do
@session.visit('/with_js')
@session.check('checkbox_with_event')
@session.should have_css('#checkbox_event_triggered');
end
end
describe '#has_xpath?' do
it "should wait for content to appear" do
@session.visit('/with_js')
@session.click_link('Click me')
@session.should have_xpath("//input[@type='submit' and @value='New Here']")
end
end
describe '#has_no_xpath?' do
it "should wait for content to disappear" do
@session.visit('/with_js')
@session.click_link('Click me')
@session.should have_no_xpath("//p[@id='change']")
end
end
describe '#has_css?' do
it "should wait for content to appear" do
@session.visit('/with_js')
@session.click_link('Click me')
@session.should have_css("input[type='submit'][value='New Here']")
end
end
describe '#has_no_xpath?' do
it "should wait for content to disappear" do
@session.visit('/with_js')
@session.click_link('Click me')
@session.should have_no_css("p#change")
end
end
describe '#has_content?' do
it "should wait for content to appear" do
@session.visit('/with_js')
@session.click_link('Click me')
@session.should have_content("Has been clicked")
end
end
describe '#has_no_content?' do
it "should wait for content to disappear" do
@session.visit('/with_js')
@session.click_link('Click me')
@session.should have_no_content("I changed it")
end
end
end
end
shared_examples_for "session without javascript support" do
describe "#evaluate_script" do
before{ @session.visit('/with_simple_html') }
it "should raise an error" do
running {
@session.evaluate_script('3 + 3')
}.should raise_error(Capybara::NotSupportedByDriverError)
end
end
end

View File

@ -31,6 +31,18 @@ shared_examples_for "select" do
extract_results(@session)['title'].should == 'Mr'
end
it "should escape quotes" do
@session.select("John's made-up language", :from => 'Locale')
@session.click_button('awesome')
extract_results(@session)['locale'].should == 'jo'
end
it "match labels with preceding or trailing whitespace" do
@session.select("Lojban", :from => 'Locale')
@session.click_button('awesome')
extract_results(@session)['locale'].should == 'jbo'
end
context "with a locator that doesn't exist" do
it "should raise an error" do
running { @session.select('foo', :from => 'does not exist') }.should raise_error(Capybara::ElementNotFound)

View File

@ -25,6 +25,12 @@ shared_examples_for "unselect" do
extract_results(@session)['underwear'].should include('Commando', 'Boxer Briefs')
extract_results(@session)['underwear'].should_not include('Briefs')
end
it "should escape quotes" do
@session.unselect("Frenchman's Pantalons", :from => 'Underwear')
@session.click_button('awesome')
extract_results(@session)['underwear'].should_not include("Frenchman's Pantalons")
end
end
context "with single select" do

View File

@ -42,6 +42,10 @@ class TestApp < Sinatra::Base
redirect '/redirect_again'
end
delete "/delete" do
"The requested object was deleted"
end
get '/redirect_back' do
redirect back
end

View File

@ -32,6 +32,11 @@
<label for="form_name">Name</label>
<input type="text" name="form[name]" value="John Smith" id="form_name"/>
</p>
<p>
<label for="form_schmooo">Schmooo</label>
<input type="schmooo" name="form[schmooo]" value="This is Schmooo!" id="form_schmooo"/>
</p>
<p>
<label>Street<br/>
@ -71,6 +76,8 @@
<option selected="selected" value="en">English</option>
<option value="fi">Finish</option>
<option value="no">Norwegian</option>
<option value="jo">John's made-up language</option>
<option value="jbo"> Lojban </option>
</select>
</p>
@ -137,6 +144,7 @@
<option>Boxers</option>
<option selected="selected">Briefs</option>
<option selected="selected">Commando</option>
<option selected="selected">Frenchman's Pantalons</option>
</select>
</p>
@ -151,8 +159,9 @@
<input type="button" name="form[fresh]" id="fresh_btn" value="i am fresh"/>
<input type="submit" name="form[awesome]" id="awe123" value="awesome"/>
<input type="submit" name="form[crappy]" id="crap321" value="crappy"/>
<input type="image" name="form[okay]" id="okay556" value="okay"/>
<input type="image" name="form[okay]" id="okay556" value="okay" alt="oh hai thar"/>
<button type="submit" id="click_me_123" value="click_me">Click me!</button>
<button type="submit" name="form[no_value]">No Value!</button>
</p>
</form>

View File

@ -0,0 +1,8 @@
<html>
<head>
<title>This is the title of frame one</title>
</head>
<body>
<div id="divInFrameOne">This is the text of divInFrameOne</div>
</body>
</html>

View File

@ -0,0 +1,8 @@
<html>
<head>
<title>This is the title of frame two</title>
</head>
<body>
<div id="divInFrameTwo">This is the text of divInFrameTwo</div>
</body>
</html>

View File

@ -12,7 +12,10 @@
<p id="second">
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum
dolore eu fugiat <a href="/redirect" id="red">Redirect</a> pariatur. Excepteur sint occaecat cupidatat non proident,
sunt in culpa qui officia deserunt mollit anim id est laborum.
sunt in culpa qui officia
text with
whitespace
id est laborum.
</p>
<p>
@ -20,6 +23,7 @@
<a href="/redirect_back">BackToMyself</a>
<a title="twas a fine link" href="/redirect">A link came first</a>
<a title="a fine link" href="/with_simple_html">A link</a>
<a title="a fine link with data method" data-method="delete" href="/delete">A link with data-method</a>
<a>No Href</a>
<a href="">Blank Href</a>
<a href="#">Blank Anchor</a>

View File

@ -30,5 +30,10 @@
<p>
<input type="text" name="with_focus_event" value="" id="with_focus_event"/>
</p>
<p>
<input type="checkbox" id="checkbox_with_event"/>
</p>
</body>
</html>

View File

@ -0,0 +1,10 @@
<html>
<head>
<title>With Frames</title>
</head>
<body>
<div id="divInMainWindow">This is the text for divInMainWindow</div>
<iframe src="/frame_one" id="frameOne"></iframe>
<iframe src="/frame_two" id="frameTwo"></iframe>
</body>
</html>

3
lib/capybara/version.rb Normal file
View File

@ -0,0 +1,3 @@
module Capybara
VERSION = '0.3.8'
end

View File

@ -4,7 +4,7 @@ module Capybara
class << self
def timeout(seconds = 1, &block)
def timeout(seconds = 1, driver = nil, &block)
start_time = Time.now
result = nil
@ -12,9 +12,14 @@ module Capybara
until result
return result if result = yield
if (Time.now - start_time) > seconds
raise TimeoutError
delay = seconds - (Time.now - start_time)
if delay <= 0
raise TimeoutError
end
driver && driver.wait_until(delay)
sleep(0.05)
end
end

View File

@ -5,11 +5,17 @@ module Capybara
class XPath
class << self
def from_css(css)
Nokogiri::CSS.xpath_for(css).first
def escape(string)
if string.include?("'")
string = string.split("'", -1).map do |substr|
"'#{substr}'"
end.join(%q{,"'",})
"concat(#{string})"
else
"'#{string}'"
end
end
alias_method :for_css, :from_css
def wrap(path)
if path.is_a?(self)
path
@ -33,6 +39,27 @@ module Capybara
@paths = paths
end
def scope(scope)
XPath.new(*paths.map { |p| scope + p })
end
def to_s
@paths.join(' | ')
end
def append(path)
XPath.new(*[@paths, XPath.wrap(path).paths].flatten)
end
def prepend(path)
XPath.new(*[XPath.wrap(path).paths, @paths].flatten)
end
def from_css(css)
append(Nokogiri::CSS.xpath_for(css).first)
end
alias_method :for_css, :from_css
def field(locator, options={})
if options[:with]
fillable_field(locator, options)
@ -46,13 +73,11 @@ module Capybara
end
def fillable_field(locator, options={})
[:text, :password, :email, :url, :search, :tel, :color].inject(text_area(locator, options)) do |all, type|
all.input_field(type, locator, options)
end
text_area(locator, options).text_field(locator, options)
end
def content(locator)
append("/descendant-or-self::*[contains(.,#{s(locator)})]")
append("/descendant-or-self::*[contains(normalize-space(.),#{s(locator)})]")
end
def table(locator, options={})
@ -80,9 +105,15 @@ module Capybara
xpath = append("//input[@type='submit' or @type='image' or @type='button'][@id=#{s(locator)} or contains(@value,#{s(locator)})]")
xpath = xpath.append("//button[@id=#{s(locator)} or contains(@value,#{s(locator)}) or contains(.,#{s(locator)})]")
xpath = xpath.prepend("//input[@type='submit' or @type='image' or @type='button'][@value=#{s(locator)}]")
xpath = xpath.prepend("//input[@type='image'][@alt=#{s(locator)} or contains(@alt,#{s(locator)})]")
xpath = xpath.prepend("//button[@value=#{s(locator)} or text()=#{s(locator)}]")
end
def text_field(locator, options={})
options = options.merge(:value => options[:with]) if options.has_key?(:with)
add_field(locator, "//input[@type!='radio' and @type!='checkbox' and @type!='hidden']", options)
end
def text_area(locator, options={})
options = options.merge(:text => options[:with]) if options.has_key?(:with)
add_field(locator, "//textarea", options)
@ -92,27 +123,6 @@ module Capybara
add_field(locator, "//select", options)
end
def input_field(type, locator, options={})
options = options.merge(:value => options[:with]) if options.has_key?(:with)
add_field(locator, "//input[@type='#{type}']", options)
end
def scope(scope)
XPath.new(*paths.map { |p| scope + p })
end
def to_s
@paths.join(' | ')
end
def append(path)
XPath.new(*[@paths, XPath.wrap(path).paths].flatten)
end
def prepend(path)
XPath.new(*[XPath.wrap(path).paths, @paths].flatten)
end
def checkbox(locator, options={})
input_field(:checkbox, locator, options)
end
@ -121,16 +131,17 @@ module Capybara
input_field(:radio, locator, options)
end
[:text, :password, :email, :url, :search, :tel, :color, :file].each do |type|
class_eval <<-RUBY, __FILE__, __LINE__+1
def #{type}_field(locator)
input_field(:#{type}, locator)
end
RUBY
def file_field(locator, options={})
input_field(:file, locator, options)
end
protected
def input_field(type, locator, options={})
options = options.merge(:value => options[:with]) if options.has_key?(:with)
add_field(locator, "//input[@type='#{type}']", options)
end
# place this between to nodes to indicate that they should be siblings
def sibling
'/following-sibling::*[1]/self::'
@ -161,15 +172,7 @@ module Capybara
# Sanitize a String for putting it into an xpath query
def s(string)
if string.include?("'")
string = string.split("'", -1).map do |substr|
"'#{substr}'"
end.join(%q{,"'",})
"concat(#{string})"
else
"'#{string}'"
end
XPath.escape(string)
end
end

View File

@ -1,10 +0,0 @@
#!/usr/bin/env ruby
# File: script/console
irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'
libs = " -r irb/completion"
# Perhaps use a console_lib to store any extra methods I may want available in the cosole
# libs << " -r #{File.dirname(__FILE__) + '/../lib/console_lib/console_logger.rb'}"
libs << " -r #{File.dirname(__FILE__) + '/../lib/capybara.rb'}"
puts "Loading capybara gem"
exec "#{irb} #{libs} --simple-prompt"

View File

@ -1,14 +0,0 @@
#!/usr/bin/env ruby
APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
begin
require 'rubigen'
rescue LoadError
require 'rubygems'
require 'rubigen'
end
require 'rubigen/scripts/destroy'
ARGV.shift if ['--help', '-h'].include?(ARGV[0])
RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
RubiGen::Scripts::Destroy.new.run(ARGV)

View File

@ -1,14 +0,0 @@
#!/usr/bin/env ruby
APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
begin
require 'rubigen'
rescue LoadError
require 'rubygems'
require 'rubigen'
end
require 'rubigen/scripts/generate'
ARGV.shift if ['--help', '-h'].include?(ARGV[0])
RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
RubiGen::Scripts::Generate.new.run(ARGV)

View File

@ -2,16 +2,15 @@ require File.expand_path('../spec_helper', File.dirname(__FILE__))
if RUBY_PLATFORM =~ /java/
describe Capybara::Driver::Celerity do
before do
before(:all) do
@driver = Capybara::Driver::Celerity.new(TestApp)
end
it_should_behave_like "driver"
it_should_behave_like "driver with javascript support"
it_should_behave_like "driver with header support"
it_should_behave_like "driver with node path support"
end
else
puts "#{File.basename(__FILE__)} requires JRuby; skipping.."
end
end

View File

@ -1,13 +1,12 @@
require File.expand_path('../spec_helper', File.dirname(__FILE__))
describe Capybara::Driver::Culerity do
before do
before(:all) do
@driver = Capybara::Driver::Culerity.new(TestApp)
end
it_should_behave_like "driver"
it_should_behave_like "driver with javascript support"
it_should_behave_like "driver with header support"
it_should_behave_like "driver with node path support"
end

View File

@ -4,9 +4,14 @@ describe Capybara::Driver::RackTest do
before do
@driver = Capybara::Driver::RackTest.new(TestApp)
end
it "should throw an error when no rack app is given" do
running do
Capybara::Driver::RackTest.new(nil)
end.should raise_error(ArgumentError)
end
it_should_behave_like "driver"
it_should_behave_like "driver with header support"
it_should_behave_like "driver with node path support"
end

View File

@ -1,13 +1,10 @@
require File.expand_path('../spec_helper', File.dirname(__FILE__))
describe Capybara::Driver::Culerity do
before do
@driver = Capybara::Driver::Culerity.new(TestApp)
end
before(:all) do
Capybara.app_host = "http://capybara-testapp.heroku.com"
Capybara.run_server = false
@driver = Capybara::Driver::Culerity.new(TestApp)
end
after(:all) do

View File

@ -7,6 +7,5 @@ describe Capybara::Driver::Selenium do
it_should_behave_like "driver"
it_should_behave_like "driver with javascript support"
it_should_behave_like "driver without node path support"
it_should_behave_like "driver with frame support"
end

View File

@ -1,39 +0,0 @@
module CheckSpec
shared_examples_for "check" do
describe "#check" do
before do
@session.visit('/form')
end
describe "'checked' attribute" do
it "should be true if checked" do
@session.check("Terms of Use")
@session.find(:xpath, "//input[@id='form_terms_of_use']")['checked'].should be_true
end
it "should be false if unchecked" do
@session.find(:xpath, "//input[@id='form_terms_of_use']")['checked'].should be_false
end
end
it "should check a checkbox by id" do
@session.check("form_pets_cat")
@session.click_button('awesome')
extract_results(@session)['pets'].should include('dog', 'cat', 'hamster')
end
it "should check a checkbox by label" do
@session.check("Cat")
@session.click_button('awesome')
extract_results(@session)['pets'].should include('dog', 'cat', 'hamster')
end
context "with a locator that doesn't exist" do
it "should raise an error" do
running { @session.check('does not exist') }.should raise_error(Capybara::ElementNotFound)
end
end
end
end
end

View File

@ -26,8 +26,8 @@ module Capybara
context "with :text filter" do
before do
@node1 = stub(Node, :text => 'node one text')
@node2 = stub(Node, :text => 'node two text')
@node1 = stub(Node, :text => 'node one text (with parens)')
@node2 = stub(Node, :text => 'node two text [-]')
@searchable.stub(:all_unfiltered).and_return([@node1, @node2])
end
@ -40,6 +40,11 @@ module Capybara
@searchable.all('//x', :text => "node one").should == [@node1]
@searchable.all('//x', :text => "node two").should == [@node2]
end
it "should allow Regexp reserved words in text" do
@searchable.all('//x', :text => "node one text (with parens)").should == [@node1]
@searchable.all('//x', :text => "node two text [-]").should == [@node2]
end
end
context "with :visible filter" do

View File

@ -10,6 +10,12 @@ describe Capybara::Server do
@res.body.should include('Hello Server')
end
it "should do nothing when no server given" do
running do
@server = Capybara::Server.new(nil).boot
end.should_not raise_error
end
it "should find an available port" do
@app1 = proc { |env| [200, {}, "Hello Server!"]}

View File

@ -2,7 +2,7 @@ require File.expand_path('../spec_helper', File.dirname(__FILE__))
if RUBY_PLATFORM =~ /java/
describe Capybara::Driver::Celerity do
before do
before(:all) do
@session = Capybara::Session.new(:celerity, TestApp)
end
@ -24,4 +24,4 @@ if RUBY_PLATFORM =~ /java/
end
else
puts "#{File.basename(__FILE__)} requires JRuby; skipping.."
end
end

View File

@ -2,7 +2,7 @@ require File.expand_path('../spec_helper', File.dirname(__FILE__))
describe Capybara::Session do
context 'with culerity driver' do
before do
before(:all) do
@session = Capybara::Session.new(:culerity, TestApp)
end

View File

@ -18,6 +18,14 @@ describe Capybara::Session do
end
end
describe '#click_link' do
it "should use data-method if available" do
@session.visit "/with_html"
@session.click_link "A link with data-method"
@session.body.should == 'The requested object was deleted'
end
end
it_should_behave_like "session"
it_should_behave_like "session without javascript support"
it_should_behave_like "session with headers support"

View File

@ -1,13 +0,0 @@
require File.expand_path('spec_helper', File.dirname(__FILE__))
shared_examples_for "session with headers support" do
describe '#response_headers' do
it "should return response headers" do
@session.visit('/with_simple_html')
@session.response_headers['Content-Type'].should == 'text/html'
end
end
end

View File

@ -1,182 +0,0 @@
require File.expand_path('spec_helper', File.dirname(__FILE__))
require 'nokogiri'
shared_examples_for "session with javascript support" do
before do
Capybara.default_wait_time = 1
end
after do
Capybara.default_wait_time = 0
end
describe '#find' do
it "should allow triggering of custom JS events" do
pending "cannot figure out how to do this with selenium" if @session.mode == :selenium
@session.visit('/with_js')
@session.find(:css, '#with_focus_event').trigger(:focus)
@session.should have_css('#focus_event_triggered')
end
end
describe '#body' do
it "should return the current state of the page" do
@session.visit('/with_js')
@session.body.should include('I changed it')
@session.body.should_not include('This is text')
end
end
describe '#source' do
it "should return the original, unmodified source of the page" do
pending "cannot figure out how to do this with selenium" if @session.mode == :selenium
@session.visit('/with_js')
@session.source.should include('This is text')
@session.source.should_not include('I changed it')
end
end
describe "#evaluate_script" do
it "should return the evaluated script" do
@session.visit('/with_js')
@session.evaluate_script("1+3").should == 4
end
end
describe '#locate' do
it "should wait for asynchronous load" do
@session.visit('/with_js')
@session.click_link('Click me')
@session.locate("//a[contains(.,'Has been clicked')]")[:href].should == '#'
end
end
describe '#wait_until' do
before do
@default_timeout = Capybara.default_wait_time
end
after do
Capybara.default_wait_time = @default_wait_time
end
it "should wait for block to return true" do
@session.visit('/with_js')
@session.select('My Waiting Option', :from => 'waiter')
@session.evaluate_script('activeRequests == 1').should be_true
@session.wait_until do
@session.evaluate_script('activeRequests == 0')
end
@session.evaluate_script('activeRequests == 0').should be_true
end
it "should raise Capybara::TimeoutError if block doesn't return true within timeout" do
@session.visit('/with_html')
Proc.new do
@session.wait_until(0.1) do
@session.find('//div[@id="nosuchthing"]')
end
end.should raise_error(::Capybara::TimeoutError)
end
it "should accept custom timeout in seconds" do
start = Time.now
Capybara.default_wait_time = 5
begin
@session.wait_until(0.1) { false }
rescue Capybara::TimeoutError; end
(Time.now - start).should be_close(0.1, 0.1)
end
it "should default to Capybara.default_wait_time before timeout" do
start = Time.now
Capybara.default_wait_time = 0.2
begin
@session.wait_until { false }
rescue Capybara::TimeoutError; end
(Time.now - start).should be_close(0.2, 0.1)
end
end
describe '#click' do
it "should wait for asynchronous load" do
@session.visit('/with_js')
@session.click_link('Click me')
@session.click('Has been clicked')
end
end
describe '#click_link' do
it "should wait for asynchronous load" do
@session.visit('/with_js')
@session.click_link('Click me')
@session.click_link('Has been clicked')
end
end
describe '#click_button' do
it "should wait for asynchronous load" do
@session.visit('/with_js')
@session.click_link('Click me')
@session.click_button('New Here')
end
end
describe '#fill_in' do
it "should wait for asynchronous load" do
@session.visit('/with_js')
@session.click_link('Click me')
@session.fill_in('new_field', :with => 'Testing...')
end
end
describe '#has_xpath?' do
it "should wait for content to appear" do
@session.visit('/with_js')
@session.click_link('Click me')
@session.should have_xpath("//input[@type='submit' and @value='New Here']")
end
end
describe '#has_no_xpath?' do
it "should wait for content to disappear" do
@session.visit('/with_js')
@session.click_link('Click me')
@session.should have_no_xpath("//p[@id='change']")
end
end
describe '#has_css?' do
it "should wait for content to appear" do
@session.visit('/with_js')
@session.click_link('Click me')
@session.should have_css("input[type='submit'][value='New Here']")
end
end
describe '#has_no_xpath?' do
it "should wait for content to disappear" do
@session.visit('/with_js')
@session.click_link('Click me')
@session.should have_no_css("p#change")
end
end
describe '#has_content?' do
it "should wait for content to appear" do
@session.visit('/with_js')
@session.click_link('Click me')
@session.should have_content("Has been clicked")
end
end
describe '#has_no_content?' do
it "should wait for content to disappear" do
@session.visit('/with_js')
@session.click_link('Click me')
@session.should have_no_content("I changed it")
end
end
end

View File

@ -1,15 +0,0 @@
require File.expand_path('spec_helper', File.dirname(__FILE__))
require 'nokogiri'
shared_examples_for "session without headers support" do
describe "#evaluate_script" do
before{ @session.visit('/with_simple_html') }
it "should raise an error" do
running {
@session.response_headers
}.should raise_error(Capybara::NotSupportedByDriverError)
end
end
end

View File

@ -1,15 +0,0 @@
require File.expand_path('spec_helper', File.dirname(__FILE__))
require 'nokogiri'
shared_examples_for "session without javascript support" do
describe "#evaluate_script" do
before{ @session.visit('/with_js') }
it "should raise an error" do
running {
@session.evaluate_script("1+5")
}.should raise_error(Capybara::NotSupportedByDriverError)
end
end
end

View File

@ -5,16 +5,8 @@ require 'rubygems'
require 'spec'
require 'spec/autorun'
require 'capybara'
require 'test_app'
require 'drivers_spec'
require 'session_spec'
Dir[File.dirname(__FILE__)+'/dsl/*'].each { |group|
require group
}
require 'session_with_javascript_support_spec'
require 'session_without_javascript_support_spec'
require 'session_with_headers_support_spec'
require 'session_without_headers_support_spec'
require 'capybara/spec/driver'
require 'capybara/spec/session'
alias :running :lambda

View File

@ -83,10 +83,8 @@ describe Capybara::XPath do
end
it "should be chainable" do
@query = @xpath.field('First Name').input_field(:password, 'First Name').to_s
@query = @xpath.field('First Name').button('Click me!').to_s
@driver.find(@query).first.value.should == 'John'
@query = @xpath.field('Password').input_field(:password, 'Password').to_s
@driver.find(@query).first.value.should == 'seeekrit'
end
end
@ -101,45 +99,11 @@ describe Capybara::XPath do
end
it "should be chainable" do
@query = @xpath.fillable_field('First Name').password_field('First Name').to_s
@query = @xpath.fillable_field('First Name').button('Click me!').to_s
@driver.find(@query).first.value.should == 'John'
@query = @xpath.fillable_field('Password').password_field('Password').to_s
@driver.find(@query).first.value.should == 'seeekrit'
end
end
describe '#text_field' do
it "should find a text field by id or label" do
@query = @xpath.text_field('form_first_name').to_s
@driver.find(@query).first.value.should == 'John'
@query = @xpath.text_field('First Name').to_s
@driver.find(@query).first.value.should == 'John'
end
it "should be chainable" do
@query = @xpath.text_field('First Name').password_field('First Name').to_s
@driver.find(@query).first.value.should == 'John'
@query = @xpath.text_field('Password').password_field('Password').to_s
@driver.find(@query).first.value.should == 'seeekrit'
end
end
describe '#password_field' do
it "should find a password field by id or label" do
@query = @xpath.password_field('form_password').to_s
@driver.find(@query).first.value.should == 'seeekrit'
@query = @xpath.password_field('Password').to_s
@driver.find(@query).first.value.should == 'seeekrit'
end
it "should be chainable" do
@query = @xpath.password_field('First Name').text_field('First Name').to_s
@driver.find(@query).first.value.should == 'John'
@query = @xpath.password_field('Password').text_field('Password').to_s
@driver.find(@query).first.value.should == 'seeekrit'
end
end
describe '#text_area' do
it "should find a text area by id or label" do
@query = @xpath.text_area('form_description').to_s
@ -149,10 +113,8 @@ describe Capybara::XPath do
end
it "should be chainable" do
@query = @xpath.text_area('Description').password_field('Description').to_s
@query = @xpath.text_area('Description').button('Click me!').to_s
@driver.find(@query).first.text.should == 'Descriptive text goes here'
@query = @xpath.text_area('Password').password_field('Password').to_s
@driver.find(@query).first.value.should == 'seeekrit'
end
end
@ -182,10 +144,8 @@ describe Capybara::XPath do
end
it "should be chainable" do
@query = @xpath.radio_button('Male').password_field('Male').to_s
@query = @xpath.radio_button('Male').button('Click me!').to_s
@driver.find(@query).first.value.should == 'male'
@query = @xpath.radio_button('Password').password_field('Password').to_s
@driver.find(@query).first.value.should == 'seeekrit'
end
end
@ -198,10 +158,8 @@ describe Capybara::XPath do
end
it "should be chainable" do
@query = @xpath.checkbox('Cat').password_field('Cat').to_s
@query = @xpath.checkbox('Cat').button('Click me!').to_s
@driver.find(@query).first.value.should == 'cat'
@query = @xpath.checkbox('Password').password_field('Password').to_s
@driver.find(@query).first.value.should == 'seeekrit'
end
end
@ -214,58 +172,9 @@ describe Capybara::XPath do
end
it "should be chainable" do
@query = @xpath.select('Region').password_field('Region').to_s
@query = @xpath.select('Region').button('Click me!').to_s
@driver.find(@query).first[:name].should == 'form[region]'
@query = @xpath.select('Password').password_field('Password').to_s
@driver.find(@query).first.value.should == 'seeekrit'
end
end
describe '#file_field' do
it "should find a file field by id or label" do
@query = @xpath.file_field('Document').to_s
@driver.find(@query).first[:name].should == 'form[document]'
@query = @xpath.file_field('form_document').to_s
@driver.find(@query).first[:name].should == 'form[document]'
end
it "should be chainable" do
@query = @xpath.file_field('Document').password_field('Document').to_s
@driver.find(@query).first[:name].should == 'form[document]'
@query = @xpath.file_field('Password').password_field('Password').to_s
@driver.find(@query).first.value.should == 'seeekrit'
end
end
[ [:email_field, 'html5_email', 'Html5 Email', 'person@email.com'],
[:url_field, 'html5_url', 'Html5 Url', 'http://www.example.com'],
[:search_field, 'html5_search', 'Html5 Search', 'what are you looking for'],
[:tel_field, 'html5_tel', 'Html5 Tel', '911'],
[:color_field, 'html5_color', 'Html5 Color', '#FFF']].each do |method, id, label, output|
describe "##{method}" do
it "should find a file field by label" do
@query = @xpath.send(method, label).to_s
@driver.find(@query).first.value.should == output
end
it "should find a file field by id" do
@query = @xpath.send(method, id).to_s
@driver.find(@query).first.value.should == output
end
it "should be chainable" do
@query = @xpath.send(method, label).password_field(label).to_s
@driver.find(@query).first.value.should == output
@query = @xpath.send(method, 'Password').password_field('Password').to_s
@driver.find(@query).first.value.should == 'seeekrit'
end
it "should be a #fillable_field" do
@query = @xpath.fillable_field(label).to_s
@driver.find(@query).first.value.should == output
end
end
end
end