How can I test the page title with Capybara 2.0?

20,961

Solution 1

I had the same issues when I upgraded to Capybara 2.0, and managed to solve them by creating the following custom RSpec matcher using Capybara.string:

spec/support/utilities.rb

RSpec::Matchers::define :have_title do |text|
  match do |page|
    Capybara.string(page.body).has_selector?('title', text: text)
  end
end

Now, in a spec file where subject { page }, I can use:

it { should have_title("My Title") }
it { should_not have_title("My Title") }

As an aside, the conversation on this question was extremely helpful in getting me to this answer, so thank you!

Update 1:

If you don't want to create a custom RSpec matcher, thanks to this StackOverflow answer by user dimuch I now know that you can call have_selector directly on page.source (aliased page.body) to test for non-visible DOM elements:

it "should show the correct title" do
  page.source.should have_selector('title', text: 'My Title')
end

or where subject { page }:

its(:source) { should have_selector('title', text: 'My Title') }

Update 2:

From Capybara 2.1, there are built-in have_title/has_title? matchers, negating the need for the custom RSpec matcher in this answer. Furthermore, the its(:source) { ... } tests described in Update 1 seem to break under Capybara 2.1; I've confirmed the have_title/has_title? work as expected, so it's probably best to go with the following syntax if you plan on upgrading:

When subject { page }:

it { should have_title("My Title") }
it { should_not have_title("My Title") }

When using expect syntax within an it/scenario block:

expect(page).to have_title("My Title")
expect(page).to_not have_title("My Title")

Solution 2

Capybara 2.1 changed its support for querying the title element. So using have selector in to query for the title element in the head of the html doc in this fashion will fail "page.should have_selector('title', :text => 'Some text'). From what i understand the preferred method in capybara 2.1 new API is to use "page.should have_title('Some text')" to query the title element should work.

Solution 3

i ran into the same issue when upgrading capybara from version 1.x to > 2.0

The problem is that Capybara ignores invisible text in its matchers. The cleanest way to address this issue is to use the :visible => false option on finders.

e.g.

it { should have_selector('title', text: 'My Title', visible: false) }

Another (global) option is to set:

Capybara.ignore_hidden_elements = false

Solution 4

I had this problem only when using 'shared_examples_for', maybe because of other changes. I also noticed that when I placed

<title>My Title</title>
in the body of the page (where it doesn't belong and won't render), the test passed. Not a good thing!

This worked:

it{should have_title("My Title")}

(capybara 2.1.0, launchy 2.3.0, nokogiri 1.5.9, rspec 2.13)

Solution 5

I see two possible issues here:

  1. The <title> tag is present on the page but the text My Title is somewhere else on the page, not within the tag. That would explain why the other tests pass: there is a <title> tag on the page so have_selector('title') passes, and there is the My Title text on the page so have_text(base_title) passes.
  2. The <title> tag contains the text My Title as well as something else, which would also explain the results you see: there is a <title> tag on the page so have_selector('title') passes, and there is the text My Title so the have_text(base_title) also passes, however the text option in have_selector is strict, therefore it will fail if it finds a string which does not exactly equal My Title.

You can check the latter of these two possibilities using the xpath selector: should have_xpath("//title[contains(.,'#{base_title}')]"). If that passes, then you probably have some whitespace or newlines around your title text which are tripping have_selector up (I thought it ignored those, but I could be wrong).

Hope that helps.

Share:
20,961
Meltemi
Author by

Meltemi

Updated on July 05, 2022

Comments

  • Meltemi
    Meltemi almost 2 years

    Trying to test that page contains <title>My Title</title> with:

    # spec/features/reports_spec.rb
    require 'spec_helper'
    
    feature "Archive Management" do
      subject { page }
    
      describe "Index Page" do
        before(:all) { 10.times { FactoryGirl.create(:randomreport) } }
        after(:all) { Report.delete_all }
    
        describe "when no search terms present" do
          before { visit reports_path }
    
          it { should have_selector('title', text: 'My Title') } # <= Fails w/Capybara 2.0
          it { should have_selector('title') }                   # <= passes
          it { should have_text('My Title') }                    # <= passes
          it { should have_selector('h2', text: "Welcome") }     # <= passes
        end
      end
    end
    

    Error message:

     Failure/Error: it { should have_selector('title', text: base_title) }
     Capybara::ExpectationNotMet:
       expected to find css "title" with text "My Title" but there were no matches. Also found "", which matched the selector but not all filters.
    

    I know I'm overlooking the painfully obvious but can't figure out what it is? Are <title> tags no longer considered 'selectors'?!? Or...?!?

    Edit (debugger info):

    If I drop down into the debugger as cleverly suggested by @shioyama it is clear that the page.body contains <title>My Title</title>. The same page.body that contains <h2>Welcome to My Title Project</h2> and passes!

    It appears to find the <title>...</title> tag but not My Title within it. But it does find My Title later in the page within <a href=\"/\" class=\"brand\">My Title</a> and/or in <h2>Welcome to The My Title Project</h2>:

    (rdb:1) p page
    #<Capybara::Session>
    (rdb:1) p page.body
    "<!DOCTYPE html>\n<html>\n<head>\n<title>My Title</title>\n
    <meta content='research, report, technology' name='keywords'>\n<meta 
    content='Some Project' name='description'>\n<link href=\"/assets/application.css\"
    ...
    </head>\n<body>\n<header class='navbar navbar-fixed-top 
    navbar-inverse'>\n<div class='navbar-inner'>\n<div class='container'>\n
    <a href=\"/\" class=\"brand\">My Title</a>\n<div class='pull-right'>\n
    <ul class='nav'>\n<li><a href=\"/about\">About</a></li>\n<li><a href=\"/help\">Help</a>
    </li>\n</ul>\n<form accept-charset=\"UTF-8\" action=\"/reports\" 
    class=\"navbar-search\" method=\"get\"><div style=\"margin:0;padding:0;display:inline\">
    <input name=\"utf8\" type=\"hidden\" value=\"&#x2713;\" /></div>\n
    <input class=\"search-query\" id=\"query\" name=\"query\" 
    placeholder=\"Search\" type=\"text\" />\n</form>\n\n</div>\n</div>\n</div>\n</header>\n\n
    <div class='container'>\n<div class='hero-unit center'>\n<h1>My Title</h1>\n
    <h2>Welcome to The My Title Project</h2>\n<p>Lorem ipsum dolor sit amet, consectetur 
    adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 
    ...
    

    What else could I try in the debugger to figure out why have_selector?('title', text: ...) is failing?

    So, what is the proper way to test for a title in Capybara 2.0?

  • Meltemi
    Meltemi over 11 years
    ya but...I can verify that <title>My Title</title> exists on the page being tested and have done so with save_and_open_page so there is text between the <title> tags.
  • Meltemi
    Meltemi over 11 years
    I can remove base_title so that it's not a distraction and replace it with 'My Title' and it still won't pass. There is no unnecessary white space that I can see (unless for some inexplicable reason there are control characters in there that are causing test to fail). I think this is a semantic/syntax issue relating to changes with Capybara 2.0 but I can't figure it out...
  • Chris Salzberg
    Chris Salzberg over 11 years
    Oh I realized now that I'm using Capybara 1.1.2 in the project where I use have_selector in the way that you have here, so perhaps you're right.
  • Meltemi
    Meltemi over 11 years
    This is just getting weirder but I find if I replace subject { page } with subject { page.body } then all specs pass...which is ironic because the <title>... tag is w/in the <head> tag and not the <body>!?! OR, i can replace should ... with body.should ... and it'll pass. What the hell is going on and why isn't this documented? I'm too big of an RSpec newb to wrap my head around it all...
  • Chris Salzberg
    Chris Salzberg over 11 years
    The body in page.body is not the HTML body (i.e. what's in the <body> tags), but the body of the response (a string), which includes the full HTML. So that's not really weird. page itself is an instance of Capybara::Session, so really a totally different type of object.
  • Chris Salzberg
    Chris Salzberg over 11 years
    p.s. I've tested some code using has_selector? in the console and I find that the :text option is actually checking that the content in the selected tags contains the text (does not have to be an exact match). So I can't really explain the behaviour you're observing.
  • Chris Salzberg
    Chris Salzberg over 11 years
    Sorry, my comment above was regarding capybara 1.1.2, I'm currently trying to update to see if I can isolate the issue. Have a read of this if you haven't already: alindeman.github.com/2012/11/11/…
  • Chris Salzberg
    Chris Salzberg over 11 years
    Update: I've now confirmed that what I observed above in the console applies to Capybara 2.0.1 as well. So I don't know what's going on in your case, but I'd suggest adding debugger to your Gemfile, and a line require 'debugger'; debugger' inside your test. Then you can experiment, e.g. p page.has_selector?('title') etc. (p = print).
  • thomasvermaak
    thomasvermaak over 11 years
    thanks for your solution ... I am also testing the update action but I am getting a "NoMethodError: undefined method put" ... Any suggestions? ... Thanks.
  • Paul Fioravanti
    Paul Fioravanti over 11 years
    @railguage48, you might want to consider asking a separate question as there's no way we can tell what your issue is without a lot more details.
  • likethesky
    likethesky almost 11 years
    Per @Paul Fioravanti Update 2, I use the following with Capybara 2.1 (and RSpec 2.12): expect(response.body).to have_title('my title') And this works fine for me.
  • Benissimo
    Benissimo over 10 years
    page.should have_selector('title', text: 'My Title', visible: false) will work too. In fact passing visible: false works with any selector where you need to match hidden content. So while has_title is more elegant for matching title, if you need to match other hidden content, use visible: false. Using expect syntax: expect(response.body).to have_selector('title', text: 'my title', visible: false)