diff --git a/README.rdoc b/README.rdoc index 22e1b007..c89b7968 100644 --- a/README.rdoc +++ b/README.rdoc @@ -183,12 +183,20 @@ with the various form elements: Capybara has a rich set of options for querying the page for the existence of certain elements, and working with and manipulating those elements. + page.has_selector?('table tr') + page.has_selector?(:xpath, '//table/tr') + page.has_no_selector?(:content) + page.has_xpath?('//table/tr') page.has_css?('table tr.foo') page.has_content?('foo') You can these use with RSpec's magic matchers: + page.should have_selector('table tr') + page.should have_selector(:xpath, '//table/tr') + page.should have_no_selector(:content) + page.should have_xpath('//table/tr') page.should have_css('table tr.foo') page.should have_content('foo') diff --git a/lib/capybara/node/matchers.rb b/lib/capybara/node/matchers.rb index 7ad8ea5f..e90f9336 100644 --- a/lib/capybara/node/matchers.rb +++ b/lib/capybara/node/matchers.rb @@ -2,6 +2,74 @@ module Capybara class Node module Matchers + ## + # + # Checks if a given selector is on the page or current node. + # + # page.has_selector?('p#foo') + # page.has_selector?(:xpath, './/p[@id="foo"]') + # page.has_selector?(:foo) + # + # By default it will check if the expression occurs at least once, + # but a different number can be specified. + # + # page.has_selector?('p#foo', :count => 4) + # + # This will check if the expression occurs exactly 4 times. + # + # It also accepts all options that {Capybara::Node::Finders#all} accepts, + # such as :text and :visible. + # + # page.has_selector?('li', :text => 'Horse', :visible => true) + # + # has_selector? can also accept XPath expressions generated by the + # XPath gem: + # + # xpath = XPath.generate { |x| x.descendant(:p) } + # page.has_selector?(:xpath, xpath) + # + # @param (see Capybara::Node::Finders#all) + # @option options [Integer] :count (nil) Number of times the expression should occur + # @return [Boolean] If the expression exists + # + def has_selector?(*args) + options = if args.last.is_a?(Hash) then args.last else {} end + wait_conditionally_until do + results = all(*args) + + if options[:count] + results.size == options[:count] + else + results.size > 0 + end + end + rescue Capybara::TimeoutError + return false + end + + ## + # + # Checks if a given selector is not on the page or current node. + # Usage is identical to Capybara::Node::Matchers#has_selector? + # + # @param (see Capybara::Node::Finders#has_selector?) + # @return [Boolean] + # + def has_no_selector?(*args) + options = if args.last.is_a?(Hash) then args.last else {} end + wait_conditionally_until do + results = all(*args) + + if options[:count] + results.size != options[:count] + else + results.empty? + end + end + rescue Capybara::TimeoutError + return false + end + ## # # Checks if a given XPath expression is on the page or current node. @@ -32,17 +100,7 @@ module Capybara # @return [Boolean] If the expression exists # def has_xpath?(path, options={}) - wait_conditionally_until do - results = all(:xpath, path, options) - - if options[:count] - results.size == options[:count] - else - results.size > 0 - end - end - rescue Capybara::TimeoutError - return false + has_selector?(:xpath, path, options) end ## @@ -54,17 +112,7 @@ module Capybara # @return [Boolean] # def has_no_xpath?(path, options={}) - wait_conditionally_until do - results = all(:xpath, path, options) - - if options[:count] - results.size != options[:count] - else - results.empty? - end - end - rescue Capybara::TimeoutError - return false + has_no_selector?(:xpath, path, options) end ## diff --git a/lib/capybara/spec/session.rb b/lib/capybara/spec/session.rb index 063ab2a4..42042277 100644 --- a/lib/capybara/spec/session.rb +++ b/lib/capybara/spec/session.rb @@ -26,14 +26,14 @@ shared_examples_for "session" do @session.body.should include('Another World') end end - + describe '#body' do it "should return the unmodified page body" do @session.visit('/') @session.body.should include('Hello world!') end end - + describe '#source' do it "should return the unmodified page source" do @session.visit('/') @@ -57,6 +57,7 @@ shared_examples_for "session" do it_should_behave_like "has_content" it_should_behave_like "has_css" it_should_behave_like "has_css" + it_should_behave_like "has_selector" it_should_behave_like "has_xpath" it_should_behave_like "has_link" it_should_behave_like "has_button" diff --git a/lib/capybara/spec/session/has_selector_spec.rb b/lib/capybara/spec/session/has_selector_spec.rb new file mode 100644 index 00000000..18f1e9c2 --- /dev/null +++ b/lib/capybara/spec/session/has_selector_spec.rb @@ -0,0 +1,129 @@ +shared_examples_for "has_selector" do + describe '#has_selector?' do + before do + @session.visit('/with_html') + end + + it "should be true if the given selector is on the page" do + @session.should have_selector(:xpath, "//p") + @session.should have_selector(:css, "p a#foo") + @session.should have_selector(:foo) + @session.should have_selector("//p[contains(.,'est')]") + end + + it "should be false if the given selector is not on the page" do + @session.should_not have_selector(:xpath, "//abbr") + @session.should_not have_selector(:css, "p a#doesnotexist") + @session.should_not have_selector(:doesnotexist) + @session.should_not have_selector("//p[contains(.,'thisstringisnotonpage')]") + end + + it "should use default selector" do + Capybara.default_selector = :css + @session.should_not have_selector("p a#doesnotexist") + @session.should have_selector("p a#foo") + end + + it "should respect scopes" do + @session.within "//p[@id='first']" do + @session.should have_selector(".//a[@id='foo']") + @session.should_not have_selector(".//a[@id='red']") + end + end + + context "with count" do + it "should be true if the content is on the page the given number of times" do + @session.should have_selector("//p", :count => 3) + @session.should have_selector("//p//a[@id='foo']", :count => 1) + @session.should have_selector("//p[contains(.,'est')]", :count => 1) + end + + it "should be false if the content is on the page the given number of times" do + @session.should_not have_selector("//p", :count => 6) + @session.should_not have_selector("//p//a[@id='foo']", :count => 2) + @session.should_not have_selector("//p[contains(.,'est')]", :count => 5) + end + + it "should be false if the content isn't on the page at all" do + @session.should_not have_selector("//abbr", :count => 2) + @session.should_not have_selector("//p//a[@id='doesnotexist']", :count => 1) + end + end + + context "with text" do + it "should discard all matches where the given string is not contained" do + @session.should have_selector("//p//a", :text => "Redirect", :count => 1) + @session.should_not have_selector("//p", :text => "Doesnotexist") + end + + it "should discard all matches where the given regexp is not matched" do + @session.should have_selector("//p//a", :text => /re[dab]i/i, :count => 1) + @session.should_not have_selector("//p//a", :text => /Red$/) + end + end + end + + describe '#has_no_selector?' do + before do + @session.visit('/with_html') + end + + it "should be false if the given selector is on the page" do + @session.should_not have_no_selector(:xpath, "//p") + @session.should_not have_no_selector(:css, "p a#foo") + @session.should_not have_no_selector(:foo) + @session.should_not have_no_selector("//p[contains(.,'est')]") + end + + it "should be true if the given selector is not on the page" do + @session.should have_no_selector(:xpath, "//abbr") + @session.should have_no_selector(:css, "p a#doesnotexist") + @session.should have_no_selector(:doesnotexist) + @session.should have_no_selector("//p[contains(.,'thisstringisnotonpage')]") + end + + it "should use default selector" do + Capybara.default_selector = :css + @session.should have_no_selector("p a#doesnotexist") + @session.should_not have_no_selector("p a#foo") + end + + it "should respect scopes" do + @session.within "//p[@id='first']" do + @session.should_not have_no_selector(".//a[@id='foo']") + @session.should have_no_selector(".//a[@id='red']") + end + end + + context "with count" do + it "should be false if the content is on the page the given number of times" do + @session.should_not have_no_selector("//p", :count => 3) + @session.should_not have_no_selector("//p//a[@id='foo']", :count => 1) + @session.should_not have_no_selector("//p[contains(.,'est')]", :count => 1) + end + + it "should be true if the content is on the page the wrong number of times" do + @session.should have_no_selector("//p", :count => 6) + @session.should have_no_selector("//p//a[@id='foo']", :count => 2) + @session.should have_no_selector("//p[contains(.,'est')]", :count => 5) + end + + it "should be true if the content isn't on the page at all" do + @session.should have_no_selector("//abbr", :count => 2) + @session.should have_no_selector("//p//a[@id='doesnotexist']", :count => 1) + end + end + + context "with text" do + it "should discard all matches where the given string is contained" do + @session.should_not have_no_selector("//p//a", :text => "Redirect", :count => 1) + @session.should have_no_selector("//p", :text => "Doesnotexist") + end + + it "should discard all matches where the given regexp is matched" do + @session.should_not have_no_selector("//p//a", :text => /re[dab]i/i, :count => 1) + @session.should have_no_selector("//p//a", :text => /Red$/) + end + end + end +end