Use diffferent queries for searching

* Add assert_text, assert_no_text, assert_title, assert_no_title
* Refactor query resolving
* Improve failure messages for have_text, have_title RSpec matchers (they will be the same as for added assert_* methods)
* Add Simple#inspect
This commit is contained in:
Andrey Botalov 2014-02-16 20:13:58 +03:00
parent d4dcd3b1d1
commit 06c4955e56
19 changed files with 726 additions and 223 deletions

View File

@ -323,13 +323,18 @@ module Capybara
require 'capybara/window'
require 'capybara/server'
require 'capybara/selector'
require 'capybara/query'
require 'capybara/result'
require 'capybara/version'
require 'capybara/queries/base_query'
require 'capybara/query'
require 'capybara/queries/text_query'
require 'capybara/queries/title_query'
require 'capybara/node/finders'
require 'capybara/node/matchers'
require 'capybara/node/actions'
require 'capybara/node/document_matchers'
require 'capybara/node/simple'
require 'capybara/node/base'
require 'capybara/node/element'

View File

@ -97,6 +97,16 @@ module Capybara
end
end
# @api private
def find_css(css)
base.find_css(css)
end
# @api private
def find_xpath(xpath)
base.find_xpath(xpath)
end
protected
def catch_error?(error, errors = nil)

View File

@ -9,6 +9,8 @@ module Capybara
# @see Capybara::Node
#
class Document < Base
include Capybara::Node::DocumentMatchers
def inspect
%(#<Capybara::Document>)
end

View File

@ -0,0 +1,68 @@
module Capybara
module Node
module DocumentMatchers
##
# Asserts that the page has the given title.
#
# @!macro title_query_params
# @overload $0(string, options = {})
# @param string [String] The string that title should include
# @overload $0(regexp, options = {})
# @param regexp [Regexp] The regexp that title should match to
# @option options [Numeric] :wait (Capybara.default_wait_time) Time that Capybara will wait for title to eq/match given string/regexp argument
# @raise [Capybara::ExpectationNotMet] if the assertion hasn't succeeded during wait time
# @return [true]
#
def assert_title(title, options = {})
query = Capybara::Queries::TitleQuery.new(title, options)
synchronize(query.wait) do
unless query.resolves_for?(self)
raise Capybara::ExpectationNotMet, query.failure_message
end
end
return true
end
##
# Asserts that the page doesn't have the given title.
#
# @macro title_query_params
# @raise [Capybara::ExpectationNotMet] if the assertion hasn't succeeded during wait time
# @return [true]
#
def assert_no_title(title, options = {})
query = Capybara::Queries::TitleQuery.new(title, options)
synchronize(query.wait) do
if query.resolves_for?(self)
raise Capybara::ExpectationNotMet, query.negative_failure_message
end
end
return true
end
##
# Checks if the page has the given title.
#
# @macro title_query_params
# @return [Boolean]
#
def has_title?(title, options = {})
assert_title(title, options)
rescue Capybara::ExpectationNotMet
return false
end
##
# Checks if the page doesn't have the given title.
#
# @macro title_query_params
# @return [Boolean]
#
def has_no_title?(title, options = {})
assert_no_title(title, options)
rescue Capybara::ExpectationNotMet
return false
end
end
end
end

View File

@ -29,10 +29,10 @@ module Capybara
query = Capybara::Query.new(*args)
synchronize(query.wait) do
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?
result = query.resolve_for(self, true)
result = query.resolve_for(self, false) if result.size == 0 && !query.exact?
else
result = resolve_query(query)
result = query.resolve_for(self)
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}")
@ -144,8 +144,8 @@ module Capybara
def all(*args)
query = Capybara::Query.new(*args)
synchronize(query.wait) do
result = resolve_query(query)
raise(Capybara::ExpectationNotMet, result.failure_message) unless result.matches_count?
result = query.resolve_for(self)
raise Capybara::ExpectationNotMet, result.failure_message unless result.matches_count?
result
end
end
@ -164,21 +164,6 @@ module Capybara
def first(*args)
all(*args).first
end
private
def resolve_query(query, exact=nil)
synchronize do
elements = if query.selector.format==:css
base.find_css(query.css)
else
base.find_xpath(query.xpath(exact))
end.map do |node|
Capybara::Node::Element.new(session, node, self, query)
end
Capybara::Result.new(elements, query)
end
end
end
end
end

View File

@ -91,8 +91,11 @@ module Capybara
def assert_selector(*args)
query = Capybara::Query.new(*args)
synchronize(query.wait) do
result = all(*args)
raise Capybara::ExpectationNotMet, result.failure_message if result.size == 0 && !Capybara::Helpers.expects_none?(query.options)
result = query.resolve_for(self)
matches_count = Capybara::Helpers.matches_count?(result.size, query.options)
unless matches_count && ((result.size > 0) || Capybara::Helpers.expects_none?(query.options))
raise Capybara::ExpectationNotMet, result.failure_message
end
end
return true
end
@ -116,19 +119,14 @@ module Capybara
def assert_no_selector(*args)
query = Capybara::Query.new(*args)
synchronize(query.wait) do
begin
result = all(*args)
rescue Capybara::ExpectationNotMet => e
return true
else
if result.size > 0 || (result.size == 0 && Capybara::Helpers.expects_none?(query.options))
raise(Capybara::ExpectationNotMet, result.negative_failure_message)
end
result = query.resolve_for(self)
matches_count = Capybara::Helpers.matches_count?(result.size, query.options)
if matches_count && ((result.size > 0) || Capybara::Helpers.expects_none?(query.options))
raise Capybara::ExpectationNotMet, result.negative_failure_message
end
end
return true
end
alias_method :refute_selector, :assert_no_selector
##
@ -215,58 +213,6 @@ module Capybara
has_no_selector?(:css, path, options)
end
##
#
# Checks if the page or current node has the given text content,
# ignoring any HTML tags and normalizing whitespace.
#
# By default it will check if the text occurs at least once,
# but a different number can be specified.
#
# page.has_text?('lorem ipsum', between: 2..4)
#
# This will check if the text occurs from 2 to 4 times.
#
# @overload has_text?([type], text, [options])
# @param [:all, :visible] type Whether to check for only visible or all text
# @param [String, Regexp] text The text/regexp to check for
# @param [Hash] options additional options
# @option options [Integer] :count (nil) Number of times the text should occur
# @option options [Integer] :minimum (nil) Minimum number of times the text should occur
# @option options [Integer] :maximum (nil) Maximum number of times the text should occur
# @option options [Range] :between (nil) Range of times that should contain number of times text occurs
# @return [Boolean] Whether it exists
#
def has_text?(*args)
query = Capybara::Query.new(*args)
synchronize(query.wait) do
raise ExpectationNotMet unless text_found?(*args)
end
return true
rescue Capybara::ExpectationNotMet
return false
end
alias_method :has_content?, :has_text?
##
#
# Checks if the page or current node does not have the given text
# content, ignoring any HTML tags and normalizing whitespace.
#
# @param (see #has_text?)
# @return [Boolean] Whether it doesn't exist
#
def has_no_text?(*args)
query = Capybara::Query.new(*args)
synchronize(query.wait) do
raise ExpectationNotMet if text_found?(*args)
end
return true
rescue Capybara::ExpectationNotMet
return false
end
alias_method :has_no_content?, :has_no_text?
##
#
# Checks if the page or current node has a link with the given
@ -479,18 +425,102 @@ module Capybara
has_no_selector?(:table, locator, options)
end
def ==(other)
self.eql?(other) or (other.respond_to?(:base) and base == other.base)
##
# Asserts that the page or current node has the given text content,
# ignoring any HTML tags.
#
# @!macro text_query_params
# @overload $0(type, text, options = {})
# @param [:all, :visible] type Whether to check for only visible or all text
# @param [String, Regexp] text The string/regexp to check for. If it's a string, text is expected to include it. If it's a regexp, text is expected to match it.
# @option options [Integer] :count (nil) Number of times the text is expected to occur
# @option options [Integer] :minimum (nil) Minimum number of times the text is expected to occur
# @option options [Integer] :maximum (nil) Maximum number of times the text is expected to occur
# @option options [Range] :between (nil) Range of times that is expected to contain number of times text occurs
# @option options [Numeric] :wait (Capybara.default_wait_time) Time that Capybara will wait for text to eq/match given string/regexp argument
# @overload $0(text, options = {})
# @param [String, Regexp] text The string/regexp to check for. If it's a string, text is expected to include it. If it's a regexp, text is expected to match it.
# @option options [Integer] :count (nil) Number of times the text is expected to occur
# @option options [Integer] :minimum (nil) Minimum number of times the text is expected to occur
# @option options [Integer] :maximum (nil) Maximum number of times the text is expected to occur
# @option options [Range] :between (nil) Range of times that is expected to contain number of times text occurs
# @option options [Numeric] :wait (Capybara.default_wait_time) Time that Capybara will wait for text to eq/match given string/regexp argument
# @raise [Capybara::ExpectationNotMet] if the assertion hasn't succeeded during wait time
# @return [true]
#
def assert_text(*args)
query = Capybara::Queries::TextQuery.new(*args)
synchronize(query.wait) do
count = query.resolve_for(self)
matches_count = Capybara::Helpers.matches_count?(count, query.options)
unless matches_count && ((count > 0) || Capybara::Helpers.expects_none?(query.options))
raise Capybara::ExpectationNotMet, query.failure_message
end
end
return true
end
private
##
# Asserts that the page or current node doesn't have the given text content,
# ignoring any HTML tags.
#
# @macro text_query_params
# @raise [Capybara::ExpectationNotMet] if the assertion hasn't succeeded during wait time
# @return [true]
#
def assert_no_text(*args)
query = Capybara::Queries::TextQuery.new(*args)
synchronize(query.wait) do
count = query.resolve_for(self)
matches_count = Capybara::Helpers.matches_count?(count, query.options)
if matches_count && ((count > 0) || Capybara::Helpers.expects_none?(query.options))
raise Capybara::ExpectationNotMet, query.negative_failure_message
end
end
return true
end
def text_found?(*args)
type = args.shift if args.first.is_a?(Symbol) or args.first.nil?
content, options = args
count = Capybara::Helpers.normalize_whitespace(text(type)).scan(Capybara::Helpers.to_regexp(content)).count
##
# Checks if the page or current node has the given text content,
# ignoring any HTML tags.
#
# Whitespaces are normalized in both node's text and passed text parameter.
# Note that whitespace isn't normalized in passed regexp as normalizing whitespace
# in regexp isn't easy and doesn't seem to be worth it.
#
# By default it will check if the text occurs at least once,
# but a different number can be specified.
#
# page.has_text?('lorem ipsum', between: 2..4)
#
# This will check if the text occurs from 2 to 4 times.
#
# @macro text_query_params
# @return [Boolean] Whether it exists
#
def has_text?(*args)
assert_text(*args)
rescue Capybara::ExpectationNotMet
return false
end
alias_method :has_content?, :has_text?
Capybara::Helpers.matches_count?(count, {:minimum=>1}.merge(options || {}))
##
# Checks if the page or current node does not have the given text
# content, ignoring any HTML tags and normalizing whitespace.
#
# @macro text_query_params
# @return [Boolean] Whether it doesn't exist
#
def has_no_text?(*args)
assert_no_text(*args)
rescue Capybara::ExpectationNotMet
return false
end
alias_method :has_no_content?, :has_no_text?
def ==(other)
self.eql?(other) || (other.respond_to?(:base) && base == other.base)
end
end
end

View File

@ -14,6 +14,7 @@ module Capybara
class Simple
include Capybara::Node::Finders
include Capybara::Node::Matchers
include Capybara::Node::DocumentMatchers
attr_reader :native
@ -148,25 +149,18 @@ module Capybara
native.xpath("//title").first.text
end
def has_title?(content)
title.match(Capybara::Helpers.to_regexp(content))
def inspect
%(#<Capybara::Node::Simple tag="#{tag_name}" path="#{path}">)
end
def has_no_title?(content)
not has_title?(content)
# @api private
def find_css(css)
native.css(css)
end
private
def resolve_query(query, exact=nil)
elements = if query.selector.format == :css
native.css(query.css)
else
native.xpath(query.xpath(exact))
end.map do |node|
self.class.new(node)
end
Capybara::Result.new(elements, query)
# @api private
def find_xpath(xpath)
native.xpath(xpath)
end
end
end

View File

@ -0,0 +1,29 @@
module Capybara
# @api private
module Queries
class BaseQuery
COUNT_KEYS = [:count, :minimum, :maximum, :between]
attr_reader :options
def wait
if @options.has_key?(:wait)
@options[:wait] || 0
else
Capybara.default_wait_time
end
end
private
def assert_valid_keys
invalid_keys = @options.keys - valid_keys
unless invalid_keys.empty?
invalid_names = invalid_keys.map(&:inspect).join(", ")
valid_names = valid_keys.map(&:inspect).join(", ")
raise ArgumentError, "invalid keys #{invalid_names}, should be one of #{valid_names}"
end
end
end
end
end

View File

@ -0,0 +1,56 @@
module Capybara
# @api private
module Queries
class TextQuery < BaseQuery
def initialize(*args)
@type = args.shift if args.first.is_a?(Symbol) || args.first.nil?
@expected_text, @options = args
unless @expected_text.is_a?(Regexp)
@expected_text = Capybara::Helpers.normalize_whitespace(@expected_text)
end
@search_regexp = Capybara::Helpers.to_regexp(@expected_text)
@options ||= {}
assert_valid_keys
# this is needed to not break existing tests that may use keys supported by `Query` but not supported by `TextQuery`
# can be removed in next minor version (> 2.4)
invalid_keys = @options.keys - (COUNT_KEYS + [:wait])
unless invalid_keys.empty?
invalid_names = invalid_keys.map(&:inspect).join(", ")
valid_names = valid_keys.map(&:inspect).join(", ")
warn "invalid keys #{invalid_names}, should be one of #{valid_names}"
end
end
def resolve_for(node)
@actual_text = Capybara::Helpers.normalize_whitespace(node.text(@type))
@count = @actual_text.scan(@search_regexp).size
end
def failure_message
description =
if @expected_text.is_a?(Regexp)
"text matching #{@expected_text.inspect}"
else
"text #{@expected_text.inspect}"
end
message = Capybara::Helpers.failure_message(description, @options)
unless (COUNT_KEYS & @options.keys).empty?
message << " but found #{@count} #{Capybara::Helpers.declension('time', 'times', @count)}"
end
message << " in #{@actual_text.inspect}"
end
def negative_failure_message
failure_message.sub(/(to find)/, 'not \1')
end
private
def valid_keys
Capybara::Query::VALID_KEYS # can be changed to COUNT_KEYS + [:wait] in next minor version (> 2.4)
end
end
end
end

View File

@ -0,0 +1,40 @@
module Capybara
# @api private
module Queries
class TitleQuery < BaseQuery
def initialize(expected_title, options = {})
@expected_title = expected_title
@options = options
unless @expected_title.is_a?(Regexp)
@expected_title = Capybara::Helpers.normalize_whitespace(@expected_title)
end
@search_regexp = Capybara::Helpers.to_regexp(@expected_title)
assert_valid_keys
end
def resolves_for?(node)
@actual_title = node.title
@actual_title.match(@search_regexp)
end
def failure_message
failure_message_helper
end
def negative_failure_message
failure_message_helper(' not')
end
private
def failure_message_helper(negated = '')
verb = (@expected_title.is_a?(Regexp))? 'match' : 'include'
"expected #{@actual_title.inspect}#{negated} to #{verb} #{@expected_title.inspect}"
end
def valid_keys
[:wait]
end
end
end
end

View File

@ -1,5 +1,7 @@
module Capybara
class Query
# @deprecated This class and its methods are not supposed to be used by users of Capybara's public API.
# It may be removed in future versions of Capybara.
class Query < Queries::BaseQuery
attr_accessor :selector, :locator, :options, :expression, :find, :negative
VALID_KEYS = [:text, :visible, :between, :count, :maximum, :minimum, :exact, :match, :wait]
@ -23,7 +25,7 @@ module Capybara
end
@expression = @selector.call(@locator)
assert_valid_keys!
assert_valid_keys
end
def name; selector.name; end
@ -70,14 +72,6 @@ module Capybara
end
end
def wait
if options.has_key?(:wait)
@options[:wait] or 0
else
Capybara.default_wait_time
end
end
def exact?
if options.has_key?(:exact)
@options[:exact]
@ -107,16 +101,32 @@ module Capybara
@expression
end
private
def assert_valid_keys!
valid_keys = VALID_KEYS + @selector.custom_filters.keys
invalid_keys = @options.keys - valid_keys
unless invalid_keys.empty?
invalid_names = invalid_keys.map(&:inspect).join(", ")
valid_names = valid_keys.map(&:inspect).join(", ")
raise ArgumentError, "invalid keys #{invalid_names}, should be one of #{valid_names}"
# @api private
def resolve_for(node, exact = nil)
node.synchronize do
children = if selector.format == :css
node.find_css(self.css)
else
node.find_xpath(self.xpath(exact))
end.map do |child|
if node.is_a?(Capybara::Node::Base)
Capybara::Node::Element.new(node.session, child, node, self)
else
Capybara::Node::Simple.new(child)
end
end
Capybara::Result.new(children, self)
end
end
private
def valid_keys
COUNT_KEYS + [:text, :visible, :exact, :match, :wait] + @selector.custom_filters.keys
end
def assert_valid_keys
super
unless VALID_MATCH.include?(match)
raise ArgumentError, "invalid option #{match.inspect} for :match, should be one of #{VALID_MATCH.map(&:inspect).join(", ")}"
end

View File

@ -51,7 +51,7 @@ module Capybara
end
def negative_failure_message
failure_message.sub(/(to be found|to find)/, 'not \1')
failure_message.sub(/(to find)/, 'not \1')
end
end
end

View File

@ -2,7 +2,7 @@ module Capybara
module RSpecMatchers
class Matcher
include ::RSpec::Matchers::Composable if defined?(::RSpec::Version) && ::RSpec::Version::STRING.to_f >= 3.0
def wrap(actual)
if actual.respond_to?("has_selector?")
actual
@ -14,7 +14,7 @@ module Capybara
class HaveSelector < Matcher
attr_reader :failure_message, :failure_message_when_negated
def initialize(*args)
@args = args
end
@ -22,7 +22,7 @@ module Capybara
def matches?(actual)
wrap(actual).assert_selector(*@args)
rescue Capybara::ExpectationNotMet => e
@failure_message = e.message
@failure_message = e.message
return false
end
@ -40,46 +40,40 @@ module Capybara
def query
@query ||= Capybara::Query.new(*@args)
end
# RSpec 2 compatibility:
alias_method :failure_message_for_should, :failure_message
alias_method :failure_message_for_should_not, :failure_message_when_negated
end
class HaveText < Matcher
attr_reader :type, :content, :options
attr_reader :failure_message, :failure_message_when_negated
def initialize(*args)
@args = args.dup
# are set just for backwards compatability
@type = args.shift if args.first.is_a?(Symbol)
@content = args.shift
@options = (args.first.is_a?(Hash))? args.first : {}
end
def matches?(actual)
@actual = wrap(actual)
@actual.has_text?(type, content, options)
wrap(actual).assert_text(*@args)
rescue Capybara::ExpectationNotMet => e
@failure_message = e.message
return false
end
def does_not_match?(actual)
@actual = wrap(actual)
@actual.has_no_text?(type, content, options)
wrap(actual).assert_no_text(*@args)
rescue Capybara::ExpectationNotMet => e
@failure_message_when_negated = e.message
return false
end
def failure_message
message = Capybara::Helpers.failure_message(description, options)
message << " in #{format(@actual.text(type))}"
message
end
def failure_message_when_negated
failure_message.sub(/(to find)/, 'not \1')
end
# RSpec 2 compatibility:
alias_method :failure_message_for_should, :failure_message
alias_method :failure_message_for_should_not, :failure_message_when_negated
def description
"text #{format(content)}"
end
@ -88,40 +82,45 @@ module Capybara
content = Capybara::Helpers.normalize_whitespace(content) unless content.is_a? Regexp
content.inspect
end
# RSpec 2 compatibility:
alias_method :failure_message_for_should, :failure_message
alias_method :failure_message_for_should_not, :failure_message_when_negated
end
class HaveTitle < Matcher
attr_reader :title
def initialize(title)
@title = title
attr_reader :failure_message, :failure_message_when_negated
def initialize(*args)
@args = args
# are set just for backwards compatability
@title = args.first
end
def matches?(actual)
@actual = wrap(actual)
@actual.has_title?(title)
wrap(actual).assert_title(*@args)
rescue Capybara::ExpectationNotMet => e
@failure_message = e.message
return false
end
def does_not_match?(actual)
@actual = wrap(actual)
@actual.has_no_title?(title)
wrap(actual).assert_no_title(*@args)
rescue Capybara::ExpectationNotMet => e
@failure_message_when_negated = e.message
return false
end
def failure_message
"expected there to be title #{title.inspect} in #{@actual.title.inspect}"
end
def failure_message_when_negated
"expected there not to be title #{title.inspect} in #{@actual.title.inspect}"
def description
"have title #{title.inspect}"
end
# RSpec 2 compatibility:
alias_method :failure_message_for_should, :failure_message
alias_method :failure_message_for_should_not, :failure_message_when_negated
def description
"have title #{title.inspect}"
end
end
class BecomeClosed
@ -169,8 +168,8 @@ module Capybara
end
alias_method :have_content, :have_text
def have_title(title)
HaveTitle.new(title)
def have_title(title, options = {})
HaveTitle.new(title, options)
end
def have_link(locator, options={})

View File

@ -35,7 +35,11 @@ module Capybara
:has_no_table?, :has_table?, :unselect, :has_select?, :has_no_select?,
:has_selector?, :has_no_selector?, :click_on, :has_no_checked_field?,
:has_no_unchecked_field?, :query, :assert_selector, :assert_no_selector,
:refute_selector
:refute_selector, :assert_text, :assert_no_text
]
# @api private
DOCUMENT_METHODS = [
:title, :assert_title, :assert_no_title, :has_title?, :has_no_title?
]
SESSION_METHODS = [
:body, :html, :source, :current_url, :current_host, :current_path,
@ -44,8 +48,8 @@ module Capybara
:windows, :open_new_window, :switch_to_window, :within_window, :window_opened_by,
:save_page, :save_and_open_page, :save_screenshot,
:save_and_open_screenshot, :reset_session!, :response_headers,
:status_code, :title, :has_title?, :has_no_title?, :current_scope
]
:status_code, :current_scope
] + DOCUMENT_METHODS
MODAL_METHODS = [
:accept_alert, :accept_confirm, :dismiss_confirm, :accept_prompt,
:dismiss_prompt
@ -171,14 +175,6 @@ module Capybara
driver.current_url
end
##
#
# @return [String] Title of the current page
#
def title
driver.title
end
##
#
# Navigate to the given URL. The URL can either be a relative URL or an absolute URL
@ -699,32 +695,16 @@ module Capybara
end
end
DOCUMENT_METHODS.each do |method|
define_method method do |*args, &block|
document.send(method, *args, &block)
end
end
def inspect
%(#<Capybara::Session>)
end
def has_title?(content)
document.synchronize do
unless title.match(Capybara::Helpers.to_regexp(content))
raise ExpectationNotMet
end
end
return true
rescue Capybara::ExpectationNotMet
return false
end
def has_no_title?(content)
document.synchronize do
if title.match(Capybara::Helpers.to_regexp(content))
raise ExpectationNotMet
end
end
return true
rescue Capybara::ExpectationNotMet
return false
end
def current_scope
scopes.last || document
end

View File

@ -53,7 +53,7 @@ $(function() {
$('#reload-list').click(function() {
setTimeout(function() {
$('#the-list').html('<li>Foo</li><li>Bar</li>');
}, 250)
}, 550)
});
$('#change-title').click(function() {
setTimeout(function() {

View File

@ -0,0 +1,195 @@
Capybara::SpecHelper.spec '#assert_text' do
it "should be true if the given text is on the page" do
@session.visit('/with_html')
expect(@session.assert_text('est')).to eq(true)
expect(@session.assert_text('Lorem')).to eq(true)
expect(@session.assert_text('Redirect')).to eq(true)
expect(@session.assert_text(:'Redirect')).to eq(true)
expect(@session.assert_text('text with whitespace')).to eq(true)
expect(@session.assert_text("text with \n\n whitespace")).to eq(true)
end
it "should take scopes into account" do
@session.visit('/with_html')
@session.within("//a[@title='awesome title']") do
expect(@session.assert_text('labore')).to eq(true)
end
end
it "should raise if scoped to an element which does not have the text" do
@session.visit('/with_html')
@session.within("//a[@title='awesome title']") do
expect do
@session.assert_text('monkey')
end.to raise_error(Capybara::ExpectationNotMet, 'expected to find text "monkey" in "labore"')
end
end
it "should be true if :all given and text is invisible." do
@session.visit('/with_html')
expect(@session.assert_text(:all, 'Some of this text is hidden!')).to eq(true)
end
it "should be true if `Capybara.ignore_hidden_elements = true` and text is invisible." do
Capybara.ignore_hidden_elements = false
@session.visit('/with_html')
expect(@session.assert_text('Some of this text is hidden!')).to eq(true)
end
it "should be true if the text in the page matches given regexp" do
@session.visit('/with_html')
expect(@session.assert_text(/Lorem/)).to eq(true)
end
it "should be raise error if the text in the page doesn't match given regexp" do
@session.visit('/with_html')
expect do
@session.assert_text(/xxxxyzzz/)
end.to raise_error(Capybara::ExpectationNotMet, /\Aexpected to find text matching \/xxxxyzzz\/ in "This is a test Header Class(.+)"\Z/)
end
it "should escape any characters that would have special meaning in a regexp" do
@session.visit('/with_html')
expect do
@session.assert_text('.orem')
end.to raise_error(Capybara::ExpectationNotMet)
end
it "should wait for text to appear", :requires => [:js] do
@session.visit('/with_js')
@session.click_link('Click me')
expect(@session.assert_text('Has been clicked')).to eq(true)
end
context "with between" do
it "should be true if the text occurs within the range given" do
@session.visit('/with_count')
expect(@session.assert_text('count', between: 1..3)).to eq(true)
end
it "should be false if the text occurs more or fewer times than range" do
@session.visit('/with_html')
expect do
@session.find(:css, '.number').assert_text(/\d/, between: 0..1)
end.to raise_error(Capybara::ExpectationNotMet, "expected to find text matching /\\d/ between 0 and 1 times but found 2 times in \"42\"")
end
end
context "with wait", :requires => [:js] do
it "should find element if it appears before given wait duration" do
Capybara.using_wait_time(0) do
@session.visit('/with_js')
@session.find(:css, '#reload-list').click
@session.find(:css, '#the-list').assert_text('Foo Bar', wait: 0.9)
end
end
it "should raise error if it appears after given wait duration" do
Capybara.using_wait_time(0) do
@session.visit('/with_js')
@session.find(:css, '#reload-list').click
el = @session.find(:css, '#the-list', visible: false)
expect do
el.assert_text(:all, 'Foo Bar', wait: 0.3)
end.to raise_error(Capybara::ExpectationNotMet)
end
end
end
context 'with multiple count filters' do
before(:each) do
@session.visit('/with_html')
end
it 'ignores other filters when :count is specified' do
o = {:count => 5,
:minimum => 6,
:maximum => 0,
:between => 0..4}
expect { @session.assert_text('Header', o) }.not_to raise_error
end
context 'with no :count expectation' do
it 'fails if :minimum is not met' do
o = {:minimum => 6,
:maximum => 5,
:between => 2..7}
expect { @session.assert_text('Header', o) }.to raise_error(Capybara::ExpectationNotMet)
end
it 'fails if :maximum is not met' do
o = {:minimum => 0,
:maximum => 0,
:between => 2..7}
expect { @session.assert_text('Header', o) }.to raise_error(Capybara::ExpectationNotMet)
end
it 'fails if :between is not met' do
o = {:minimum => 0,
:maximum => 5,
:between => 0..4}
expect { @session.assert_text('Header', o) }.to raise_error(Capybara::ExpectationNotMet)
end
it 'succeeds if all combineable expectations are met' do
o = {:minimum => 0,
:maximum => 5,
:between => 2..7}
expect { @session.assert_text('Header', o) }.not_to raise_error
end
end
end
end
Capybara::SpecHelper.spec '#assert_no_text' do
it "should raise error if the given text is on the page at least once" do
@session.visit('/with_html')
expect do
@session.assert_no_text('Lorem')
end.to raise_error(Capybara::ExpectationNotMet, /\Aexpected not to find text "Lorem" in "This is a test Header Class.+"\Z/)
end
it "should be true if scoped to an element which does not have the text" do
@session.visit('/with_html')
@session.within("//a[@title='awesome title']") do
expect(@session.assert_no_text('monkey')).to eq(true)
end
end
it "should be true if the given text is on the page but not visible" do
@session.visit('/with_html')
expect(@session.assert_no_text('Inside element with hidden ancestor')).to eq(true)
end
it "should raise error if :all given and text is invisible." do
@session.visit('/with_html')
el = @session.find(:css, '#hidden-text', visible: false)
expect do
el.assert_no_text(:all, 'Some of this text is hidden!')
end.to raise_error(Capybara::ExpectationNotMet, 'expected not to find text "Some of this text is hidden!" in "Some of this text is hidden!"')
end
it "should be true if the text in the page doesn't match given regexp" do
@session.visit('/with_html')
@session.assert_no_text(/xxxxyzzz/)
end
context "with count" do
it "should be true if the text occurs within the range given" do
@session.visit('/with_count')
expect(@session.assert_text('count', count: 2)).to eq(true)
end
it "should be false if the text occurs more or fewer times than range" do
@session.visit('/with_html')
expect do
@session.find(:css, '.number').assert_text(/\d/, count: 1)
end.to raise_error(Capybara::ExpectationNotMet, "expected to find text matching /\\d/ 1 time but found 2 times in \"42\"")
end
end
context "with wait", :requires => [:js] do
it "should not find element if it appears after given wait duration" do
@session.visit('/with_js')
@session.click_link('Click me')
@session.find(:css, '#reload-list').click
@session.find(:css, '#the-list').assert_no_text('Foo Bar', :wait => 0.4)
end
end
end

View File

@ -0,0 +1,70 @@
Capybara::SpecHelper.spec '#assert_title' do
before do
@session.visit('/with_js')
end
it "should be true if the page's title contains the given string" do
expect(@session.assert_title('js')).to eq(true)
end
it "should be true when given an empty string" do
expect(@session.assert_title('')).to eq(true)
end
it "should allow regexp matches" do
@session.should have_title(/w[a-z]{3}_js/)
expect(@session.assert_title(/w[a-z]{3}_js/)).to eq(true)
expect do
@session.assert_title(/w[a-z]{10}_js/)
end.to raise_error(Capybara::ExpectationNotMet, 'expected "with_js" to match /w[a-z]{10}_js/')
end
it "should wait for title", :requires => [:js] do
@session.click_link("Change title")
expect(@session.assert_title("changed title")).to eq(true)
end
it "should raise error if the title doesn't contain the given string" do
expect do
@session.assert_title('monkey')
end.to raise_error(Capybara::ExpectationNotMet, 'expected "with_js" to include "monkey"')
end
it "should normalize given title" do
@session.assert_title(' with_js ')
end
it "should normalize given title in error message" do
expect do
@session.assert_title(2)
end.to raise_error(Capybara::ExpectationNotMet, 'expected "with_js" to include "2"')
end
end
Capybara::SpecHelper.spec '#assert_no_title' do
before do
@session.visit('/with_js')
end
it "should raise error if the title contains the given string" do
expect do
@session.assert_no_title('with_j')
end.to raise_error(Capybara::ExpectationNotMet, 'expected "with_js" not to include "with_j"')
end
it "should allow regexp matches" do
expect do
@session.assert_no_title(/w[a-z]{3}_js/)
end.to raise_error(Capybara::ExpectationNotMet, 'expected "with_js" not to match /w[a-z]{3}_js/')
@session.assert_no_title(/monkey/)
end
it "should wait for title to disappear", :requires => [:js] do
@session.click_link("Change title")
expect(@session.assert_no_title('with_js')).to eq(true)
end
it "should be true if the title doesn't contain the given string" do
expect(@session.assert_no_title('monkey')).to eq(true)
end
end

View File

@ -106,7 +106,7 @@ Capybara::SpecHelper.spec '#has_text?' do
end
it "should be true when passed nil" do
# Historical behavior; no particular reason other than compatibility.
# nil is converted to '' when to_s is invoked
@session.visit('/with_html')
expect(@session).to have_text(nil)
end

View File

@ -5,12 +5,12 @@ require 'capybara/rspec/matchers'
RSpec.describe Capybara::RSpecMatchers do
include Capybara::DSL
include Capybara::RSpecMatchers
describe "have_css matcher" do
it "gives proper description" do
expect(have_css('h1').description).to eq("have css \"h1\"")
end
context "on a string" do
context "with should" do
it "passes if has_css? returns true" do
@ -350,20 +350,26 @@ RSpec.describe Capybara::RSpecMatchers do
context "on a string" do
context "with should" do
it "passes if has_text? returns true" do
it "passes if text contains given string" do
expect("<h1>Text</h1>").to have_text('Text')
end
it "passes if has_text? returns true using regexp" do
it "passes if text matches given regexp" do
expect("<h1>Text</h1>").to have_text(/ext/)
end
it "fails if has_text? returns false" do
it "fails if text doesn't contain given string" do
expect do
expect("<h1>Text</h1>").to have_text('No such Text')
end.to raise_error(/expected to find text "No such Text" in "Text"/)
end
it "fails if text doesn't match given regexp" do
expect do
expect("<h1>Text</h1>").to have_text(/No such Text/)
end.to raise_error('expected to find text matching /No such Text/ in "Text"')
end
it "casts Fixnum to string" do
expect do
expect("<h1>Text</h1>").to have_text(3)
@ -373,30 +379,30 @@ RSpec.describe Capybara::RSpecMatchers do
it "fails if matched text count does not equal to expected count" do
expect do
expect("<h1>Text</h1>").to have_text('Text', count: 2)
end.to raise_error(/expected to find text "Text" 2 times in "Text"/)
end.to raise_error('expected to find text "Text" 2 times but found 1 time in "Text"')
end
it "fails if matched text count is less than expected minimum count" do
expect do
expect("<h1>Text</h1>").to have_text('Lorem', minimum: 1)
end.to raise_error(/expected to find text "Lorem" at least 1 time in "Text"/)
end.to raise_error('expected to find text "Lorem" at least 1 time but found 0 times in "Text"')
end
it "fails if matched text count is more than expected maximum count" do
expect do
expect("<h1>Text TextText</h1>").to have_text('Text', maximum: 2)
end.to raise_error(/expected to find text "Text" at most 2 times in "Text TextText"/)
end.to raise_error('expected to find text "Text" at most 2 times but found 3 times in "Text TextText"')
end
it "fails if matched text count does not belong to expected range" do
expect do
expect("<h1>Text</h1>").to have_text('Text', between: 2..3)
end.to raise_error(/expected to find text "Text" between 2 and 3 times in "Text"/)
end.to raise_error('expected to find text "Text" between 2 and 3 times but found 1 time in "Text"')
end
end
context "with should_not" do
it "passes if has_no_text? returns true" do
it "passes if text doesn't contain a string" do
expect("<h1>Text</h1>").not_to have_text('No such Text')
end
@ -404,7 +410,7 @@ RSpec.describe Capybara::RSpecMatchers do
expect("<h1>Text</h1>").not_to have_text('.')
end
it "fails if has_no_text? returns false" do
it "fails if text contains a string" do
expect do
expect("<h1>Text</h1>").not_to have_text('Text')
end.to raise_error(/expected not to find text "Text" in "Text"/)
@ -477,7 +483,7 @@ RSpec.describe Capybara::RSpecMatchers do
it "gives proper description" do
expect(have_link('Just a link').description).to eq("have link \"Just a link\"")
end
it "passes if there is such a button" do
expect(html).to have_link('Just a link')
end
@ -509,23 +515,48 @@ RSpec.describe Capybara::RSpecMatchers do
it "fails if there is no such title" do
expect do
expect(html).to have_title('No such title')
end.to raise_error(/expected there to be title "No such title"/)
end.to raise_error('expected "Just a title" to include "No such title"')
end
it "fails if title doesn't match regexp" do
expect do
expect(html).to have_title(/[[:upper:]]+[[:lower:]]+l{2}o/)
end.to raise_error('expected "Just a title" to match /[[:upper:]]+[[:lower:]]+l{2}o/')
end
end
context "on a page or node" do
before do
visit('/with_js')
end
it "passes if there is such a title" do
visit('/with_js')
expect(page).to have_title('with_js')
end
it "fails if there is no such title" do
visit('/with_js')
expect do
expect(page).to have_title('No such title')
end.to raise_error(/expected there to be title "No such title"/)
end.to raise_error('expected "with_js" to include "No such title"')
end
context 'with wait' do
before(:each) do
@session = TestSessions::Selenium
@session.visit('/with_js')
end
it 'waits if wait time is more than timeout' do
@session.click_link("Change title")
using_wait_time 0 do
expect(@session).to have_title('changed title', wait: 0.5)
end
end
it "doesn't wait if wait time is less than timeout" do
@session.click_link("Change title")
using_wait_time 0 do
expect(@session).not_to have_title('changed title')
end
end
end
end
@ -727,7 +758,7 @@ RSpec.describe Capybara::RSpecMatchers do
it "gives proper description" do
expect(have_table('Lovely table').description).to eq("have table \"Lovely table\"")
end
it "passes if there is such a select" do
expect(html).to have_table('Lovely table')
end
@ -743,4 +774,3 @@ RSpec.describe Capybara::RSpecMatchers do
end if RSpec::Version::STRING.to_f >= 3.0
end
end