93

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.

merryprankster
  • 3,369
  • 2
  • 24
  • 26
  • 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) end – rafaelkin Apr 18 '13 at 19:47
  • @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 Apr 22 '13 at 12:24
  • 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 Oct 28 '13 at 07:32

6 Answers6

189

I like this much better.

expect(page).to have_selector('input', count: 12)

https://github.com/jnicklas/capybara/blob/415e2db70d3b19b46a4d3d0fe62f50400f9d2b61/spec/rspec/matchers_spec.rb

pandaPowder
  • 2,045
  • 2
  • 13
  • 6
22

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.

merryprankster
  • 3,369
  • 2
  • 24
  • 26
15

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)
Richard
  • 1,162
  • 1
  • 11
  • 19
9

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
Constant Meiring
  • 3,285
  • 3
  • 40
  • 52
  • 3
    This completely defeats Capybaras waiting/retrying and should never be a recommended solution. – Thomas Walpole Jun 02 '17 at 19:21
  • @ThomasWalpole I'm not sure what you're talking about. How does looking for an element within another element in any way touch the waiting/retrying in Capybara? – Constant Meiring Jun 07 '17 at 15:58
  • 2
    @ConstantMeiring It's not the `within`, it's calling `.count` on the results of `all` that disables waiting/retrying. By calling `count` on the results of `all` (which an empty "array" is a valid return for) you convert to an integer and compare it. If that comparison fails the expectation fails. If instead you pass the count option to one of Capybara's matchers, capybara will wait/retry finding the specified selector until the count option matches (or Capybara.default_max_wait_time expires). – Thomas Walpole Jun 07 '17 at 16:08
5

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

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

acconrad
  • 3,201
  • 1
  • 22
  • 31
-5

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

expect(page).to have_selector('.views-row', :count => 30)
Nick
  • 2,803
  • 1
  • 39
  • 59
  • 5
    Using hash rockets does not qualify as "different syntax." – premjg Nov 25 '14 at 01:50
  • 2
    I'm not a ruby dev and didn't realise that the two syntaxes were equivalent. TBH I'm not sure it warrants downvoting. It's a valid alternative. For those not from a Ruby background it may not seem obvious. It wasn't for me. – Nick Sep 02 '15 at 22:38