88
votes

I know that in Capybara, you can do something like this:

page.should have_css("ol li", :count => 2)

However, assuming that page has for instance only one matching element, the error is not very descriptive:

  1) initial page load shows greetings
 Failure/Error: page.should have_css("ol li", :count => 2)
 expected css "ol li" to return something

Instead of this rather obscure error message, is there a way to write the assertion in such way that error output would be something like 'When matching 'ol li', expected: 2, found: 1'. Obviously I could make a custom logic myself for such a behaviour - I'm asking is there a way to do this 'out of the box'?

For what it's worth, I'm using Selenium driver and RSpec.

6
Just to people know, "page.should have_css("ol li", :count => 2)" was implemented in capybara. I think it is highly usable with scopes: within("ol.users-list") do page.should have_css('li', :count => 3) endrafaelkin
@rafaelkin, just to clarify: does capybara now report e.g. the mismatch in element count with more detail? I haven't followed capybara for a while now, but the issue back then when I made the question was about the format of error message, not that page.should have_css("ol li", :count => 2) would not have been implemented already.merryprankster
folks, I have a feeling that currently accepted answer (=my own) is no longer the best, but do not have time (no longer work with Ruby) to evaluate which of the suggested solutions is the best. I'll change the accepted answer to that of Richard's just because it includes the output of assertion which addresses the original issue.merryprankster

6 Answers

22
votes

Well, as it seems there is no support out-of-the-box, I wrote this custom matcher:

RSpec::Matchers.define :match_exactly do |expected_match_count, selector|
    match do |context|
        matching = context.all(selector)
        @matched = matching.size
        @matched == expected_match_count
    end

    failure_message_for_should do
        "expected '#{selector}' to match exactly #{expected_match_count} elements, but matched #{@matched}"
    end

    failure_message_for_should_not do
        "expected '#{selector}' to NOT match exactly #{expected_match_count} elements, but it did"
    end
end

Now, you can do stuff like:

describe "initial page load", :type => :request do
    it "has 12 inputs" do
        visit "/"
        page.should match_exactly(12, "input")
    end
end

and get output like:

  1) initial page load has 12 inputs
     Failure/Error: page.should match_exactly(12, "input")
       expected 'input' to match exactly 12 elements, but matched 13

It does the trick for now, I will look into making this part of Capybara.

15
votes

I think the following is simpler, gives fairly clear output and eliminates the need for a custom matcher.

page.all("ol li").count.should eql(2)

This then prints out on error:

      expected: 2
       got: 3

  (compared using eql?)
  (RSpec::Expectations::ExpectationNotMetError)
9
votes

Edit: As pointed out by @ThomasWalpole, using all disables Capybara's waiting/retrying, so the answer above by @pandaPower is much better.

How about this?

  within('ol') do
    expect( all('.opportunity_title_wrap').count ).to eq(2)
  end
4
votes

The current (9/2/2013) best practice recommended by Capybara is the following (source):

page.assert_selector('p#foo', :count => 4)

-4
votes

The answer by @pandaPower is very good, but the syntax was slightly different for me:

expect(page).to have_selector('.views-row', :count => 30)