Skip to content

Commit

Permalink
Merge branch 'capy21'
Browse files Browse the repository at this point in the history
Conflicts:
	spec/basic_node_spec.rb
  • Loading branch information
jnicklas committed Feb 15, 2013
2 parents 02d1289 + c6cd6ac commit e330e88
Show file tree
Hide file tree
Showing 13 changed files with 275 additions and 49 deletions.
4 changes: 3 additions & 1 deletion lib/capybara.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class InfiniteRedirectError < CapybaraError; end

class << self
attr_accessor :asset_root, :app_host, :run_server, :default_host, :always_include_port
attr_accessor :server_host, :server_port
attr_accessor :server_host, :server_port, :exact, :match
attr_accessor :default_selector, :default_wait_time, :ignore_hidden_elements
attr_accessor :save_and_open_page_path, :automatic_reload
attr_writer :default_driver, :current_driver, :javascript_driver, :session_name
Expand Down Expand Up @@ -344,6 +344,8 @@ module Selenium
config.ignore_hidden_elements = true
config.default_host = "http://www.example.com"
config.automatic_reload = true
config.match = :smart
config.exact = false
end

Capybara.register_driver :rack_test do |app|
Expand Down
36 changes: 28 additions & 8 deletions lib/capybara/node/finders.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,22 @@ module Finders
# @raise [Capybara::ElementNotFound] If the element can't be found before time expires
#
def find(*args)
synchronize { all(*args).find! }.tap(&:allow_reload!)
synchronize do
query = Capybara::Query.new(*args)
if query.match == :smart or query.match == :prefer_exact
result = resolve_query(query, true)
result = resolve_query(query, false) if result.size == 0 and not query.exact
else
result = resolve_query(query)
end
if query.match == :one or query.match == :smart and result.size > 1
raise Capybara::Ambiguous.new("Ambiguous match, found #{result.size} elements matching #{query.description}")
end
if result.size == 0
raise Capybara::ElementNotFound.new("Unable to find #{query.description}")
end
result.first
end.tap(&:allow_reload!)
end

##
Expand Down Expand Up @@ -108,13 +123,7 @@ def find_by_id(id)
# @return [Capybara::Result] A collection of found elements
#
def all(*args)
query = Capybara::Query.new(*args)
elements = synchronize do
base.find(query.xpath).map do |node|
Capybara::Node::Element.new(session, node, self, query)
end
end
Capybara::Result.new(elements, query)
resolve_query(Capybara::Query.new(*args))
end

##
Expand All @@ -131,6 +140,17 @@ def all(*args)
def first(*args)
all(*args).first
end

private

def resolve_query(query, exact=nil)
elements = synchronize do
base.find(query.xpath(exact)).map do |node|
Capybara::Node::Element.new(session, node, self, query)
end
end
Capybara::Result.new(elements, query)
end
end
end
end
7 changes: 4 additions & 3 deletions lib/capybara/node/simple.rb
Original file line number Diff line number Diff line change
Expand Up @@ -139,9 +139,10 @@ def unsynchronized
yield # simple nodes don't need to wait
end

def all(*args)
query = Capybara::Query.new(*args)
elements = native.xpath(query.xpath).map do |node|
private

def resolve_query(query, exact=nil)
elements = native.xpath(query.xpath(exact)).map do |node|
self.class.new(node)
end
Capybara::Result.new(elements, query)
Expand Down
31 changes: 28 additions & 3 deletions lib/capybara/query.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ module Capybara
class Query
attr_accessor :selector, :locator, :options, :xpath, :find, :negative

VALID_KEYS = [:text, :visible, :between, :count, :maximum, :minimum]
VALID_KEYS = [:text, :visible, :between, :count, :maximum, :minimum, :exact, :match]

def initialize(*args)
@options = if args.last.is_a?(Hash) then args.pop.dup else {} end
Expand All @@ -11,6 +11,14 @@ def initialize(*args)
@options[:visible] = Capybara.ignore_hidden_elements
end

unless options.has_key?(:exact)
@options[:exact] = Capybara.exact
end

unless options.has_key?(:match)
@options[:match] = Capybara.match
end

if args[0].is_a?(Symbol)
@selector = Selector.all[args[0]]
@locator = args[1]
Expand All @@ -20,8 +28,7 @@ def initialize(*args)
end
@selector ||= Selector.all[Capybara.default_selector]

@xpath = @selector.call(@locator).to_s

@xpath = @selector.call(@locator)
assert_valid_keys!
end

Expand Down Expand Up @@ -63,6 +70,24 @@ def matches_count?(count)
end
end

def exact
@options[:exact]
end

def match
@options[:match]
end

def xpath(exact=nil)
exact = @options[:exact] if exact == nil

if @xpath.respond_to?(:to_xpath) and exact
@xpath.to_xpath(:exact)
else
@xpath.to_s
end
end

private

def assert_valid_keys!
Expand Down
13 changes: 0 additions & 13 deletions lib/capybara/result.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,19 +35,6 @@ def matches_count?
@query.matches_count?(@result.size)
end

def find!
raise find_error if @result.size != 1
@result.first
end

def find_error
if @result.size == 0
Capybara::ElementNotFound.new("Unable to find #{@query.description}")
elsif @result.size > 1
Capybara::Ambiguous.new("Ambiguous match, found #{size} elements matching #{@query.description}")
end
end

def failure_message
message = if @query.options[:count]
"expected #{@query.description} to be found #{@query.options[:count]} #{declension("time", "times", @query.options[:count])}"
Expand Down
17 changes: 9 additions & 8 deletions lib/capybara/spec/session/all_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,22 +48,23 @@
@session.all('h1').first.text.should == 'This is a test'
@session.all("input[id='test_field']").first[:value].should == 'monkey'
end
after { Capybara.default_selector = :xpath }
end

context "with visible filter" do
after { Capybara.ignore_hidden_elements = true }
it "should only find visible nodes" do
@session.all(:css, "a.simple").should have(1).elements
Capybara.ignore_hidden_elements = false
@session.all(:css, "a.simple").should have(2).elements
it "should only find visible nodes when true" do
@session.all(:css, "a.simple", :visible => true).should have(1).elements
end

it "should only find invisible nodes" do
Capybara.ignore_hidden_elements = true
it "should find nodes regardless of whether they are invisible when false" do
@session.all(:css, "a.simple", :visible => false).should have(2).elements
end

it "should default to Capybara.ignore_hidden_elements" do
Capybara.ignore_hidden_elements = true
@session.all(:css, "a.simple").should have(1).elements
Capybara.ignore_hidden_elements = false
@session.all(:css, "a.simple").should have(2).elements
end
end

context "within a scope" do
Expand Down
1 change: 0 additions & 1 deletion lib/capybara/spec/session/find_by_id_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

it "should find any element by id" do
@session.find_by_id('red').tag_name.should == 'a'
@session.find_by_id('hidden_via_ancestor').tag_name.should == 'div'
end

it "casts to string" do
Expand Down
173 changes: 173 additions & 0 deletions lib/capybara/spec/session/find_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,179 @@
@session.find(@xpath).value.should == 'John'
end

context "with :exact option" do
it "matches exactly when true" do
@session.find(:xpath, XPath.descendant(:input)[XPath.attr(:id).is("test_field")], :exact => true).value.should == "monkey"
expect do
@session.find(:xpath, XPath.descendant(:input)[XPath.attr(:id).is("est_fiel")], :exact => true)
end.to raise_error(Capybara::ElementNotFound)
end

it "matches loosely when false" do
@session.find(:xpath, XPath.descendant(:input)[XPath.attr(:id).is("test_field")], :exact => false).value.should == "monkey"
@session.find(:xpath, XPath.descendant(:input)[XPath.attr(:id).is("est_fiel")], :exact => false).value.should == "monkey"
end

it "defaults to `Capybara.exact`" do
Capybara.exact = true
expect do
@session.find(:xpath, XPath.descendant(:input)[XPath.attr(:id).is("est_fiel")])
end.to raise_error(Capybara::ElementNotFound)
Capybara.exact = false
@session.find(:xpath, XPath.descendant(:input)[XPath.attr(:id).is("est_fiel")])
end
end

context "with :match option" do
context "when set to `one`" do
it "raises an error when multiple matches exist" do
expect do
@session.find(:css, ".multiple", :match => :one)
end.to raise_error(Capybara::Ambiguous)
end
it "raises an error even if there the match is exact and the others are inexact" do
expect do
@session.find(:xpath, XPath.descendant[XPath.attr(:class).is("almost_singular")], :exact => false, :match => :one)
end.to raise_error(Capybara::Ambiguous)
end
it "returns the element if there is only one" do
@session.find(:css, ".singular", :match => :one).text.should == "singular"
end
it "raises an error if there is no match" do
expect do
@session.find(:css, ".does-not-exist", :match => :any)
end.to raise_error(Capybara::ElementNotFound)
end
end

context "when set to `any`" do
it "returns the first matched element" do
@session.find(:css, ".multiple", :match => :any).text.should == "multiple one"
end
it "raises an error if there is no match" do
expect do
@session.find(:css, ".does-not-exist", :match => :any)
end.to raise_error(Capybara::ElementNotFound)
end
end

context "when set to `smart`" do
context "and `exact` set to `false`" do
it "raises an error when there are multiple exact matches" do
expect do
@session.find(:xpath, XPath.descendant[XPath.attr(:class).is("multiple")], :match => :smart, :exact => false)
end.to raise_error(Capybara::Ambiguous)
end
it "finds a single exact match when there also are inexact matches" do
result = @session.find(:xpath, XPath.descendant[XPath.attr(:class).is("almost_singular")], :match => :smart, :exact => false)
result.text.should == "almost singular"
end
it "raises an error when there are multiple inexact matches" do
expect do
@session.find(:xpath, XPath.descendant[XPath.attr(:class).is("almost_singul")], :match => :smart, :exact => false)
end.to raise_error(Capybara::Ambiguous)
end
it "finds a single inexact match" do
result = @session.find(:xpath, XPath.descendant[XPath.attr(:class).is("almost_singular but")], :match => :smart, :exact => false)
result.text.should == "almost singular but not quite"
end
it "raises an error if there is no match" do
expect do
@session.find(:css, ".does-not-exist", :match => :smart, :exact => false)
end.to raise_error(Capybara::ElementNotFound)
end
end

context "with `exact` set to `true`" do
it "raises an error when there are multiple exact matches" do
expect do
@session.find(:xpath, XPath.descendant[XPath.attr(:class).is("multiple")], :match => :smart, :exact => true)
end.to raise_error(Capybara::Ambiguous)
end
it "finds a single exact match when there also are inexact matches" do
result = @session.find(:xpath, XPath.descendant[XPath.attr(:class).is("almost_singular")], :match => :smart, :exact => true)
result.text.should == "almost singular"
end
it "raises an error when there are multiple inexact matches" do
expect do
@session.find(:xpath, XPath.descendant[XPath.attr(:class).is("almost_singul")], :match => :smart, :exact => true)
end.to raise_error(Capybara::ElementNotFound)
end
it "raises an error when there is a single inexact matches" do
expect do
result = @session.find(:xpath, XPath.descendant[XPath.attr(:class).is("almost_singular but")], :match => :smart, :exact => true)
end.to raise_error(Capybara::ElementNotFound)
end
it "raises an error if there is no match" do
expect do
@session.find(:css, ".does-not-exist", :match => :smart, :exact => true)
end.to raise_error(Capybara::ElementNotFound)
end
end
end

context "when set to `prefer_exact`" do
context "and `exact` set to `false`" do
it "picks the first one when there are multiple exact matches" do
result = @session.find(:xpath, XPath.descendant[XPath.attr(:class).is("multiple")], :match => :prefer_exact, :exact => false)
result.text.should == "multiple one"
end
it "finds a single exact match when there also are inexact matches" do
result = @session.find(:xpath, XPath.descendant[XPath.attr(:class).is("almost_singular")], :match => :prefer_exact, :exact => false)
result.text.should == "almost singular"
end
it "picks the first one when there are multiple inexact matches" do
result = @session.find(:xpath, XPath.descendant[XPath.attr(:class).is("almost_singul")], :match => :prefer_exact, :exact => false)
result.text.should == "almost singular but not quite"
end
it "finds a single inexact match" do
result = @session.find(:xpath, XPath.descendant[XPath.attr(:class).is("almost_singular but")], :match => :prefer_exact, :exact => false)
result.text.should == "almost singular but not quite"
end
it "raises an error if there is no match" do
expect do
@session.find(:css, ".does-not-exist", :match => :prefer_exact, :exact => false)
end.to raise_error(Capybara::ElementNotFound)
end
end

context "with `exact` set to `true`" do
it "picks the first one when there are multiple exact matches" do
result = @session.find(:xpath, XPath.descendant[XPath.attr(:class).is("multiple")], :match => :prefer_exact, :exact => true)
result.text.should == "multiple one"
end
it "finds a single exact match when there also are inexact matches" do
result = @session.find(:xpath, XPath.descendant[XPath.attr(:class).is("almost_singular")], :match => :prefer_exact, :exact => true)
result.text.should == "almost singular"
end
it "raises an error if there are multiple inexact matches" do
expect do
@session.find(:xpath, XPath.descendant[XPath.attr(:class).is("almost_singul")], :match => :prefer_exact, :exact => true)
end.to raise_error(Capybara::ElementNotFound)
end
it "raises an error if there is a single inexact match" do
expect do
@session.find(:xpath, XPath.descendant[XPath.attr(:class).is("almost_singular but")], :match => :prefer_exact, :exact => true)
end.to raise_error(Capybara::ElementNotFound)
end
it "raises an error if there is no match" do
expect do
@session.find(:css, ".does-not-exist", :match => :prefer_exact, :exact => true)
end.to raise_error(Capybara::ElementNotFound)
end
end
end

it "defaults to `Capybara.match`" do
Capybara.match = :one
expect do
@session.find(:css, ".multiple")
end.to raise_error(Capybara::Ambiguous)
Capybara.match = :any
@session.find(:css, ".multiple").text.should == "multiple one"
end
end

context "within a scope" do
before do
@session.visit('/with_scope')
Expand Down
Loading

0 comments on commit e330e88

Please sign in to comment.