Is it possible to test the order of elements via RSpec/Capybara?

17,513

Solution 1

I resolved this issue by testing for a regex match against the body content of the page. A bit kludgy, but it works.

page.body.should =~ /ITEM1.*ITEM2.*ITEM3/

Solution 2

I found a more canonical way of testing this behaviour with CSS. You could user :first-child, :last-child and :nth-child(n) selectors in whichever assert you like.

In your example I'd try these assertions:

page.should have_tag("ul:last-child", :text => "ITEM #1")
pseuo_add_new_li
page.should have_tag("ul:nth-last-child(2)", :text => "ITEM #1")
page.should have_tag("ul:last-child", :text => "ITEM #2")

I hope this helps someone. Read more about this.

Solution 3

this article lists several ways to test sort order in RSpec, the best of which seems to be this matcher:

RSpec::Matchers.define :appear_before do |later_content|
  match do |earlier_content|
    page.body.index(earlier_content) < page.body.index(later_content)
  end
end

Solution 4

I have had the same issue recently and found this neat & ideal solution: http://launchware.com/articles/acceptance-testing-asserting-sort-order

It's even packaged as a tiny gem.

Solution 5

You can use the all finder method to select multiple elements and then use collect to pull out the text into an array:

assert_equal page.all('#navigation ul li').collect(&:text), ['Item 1', 'Item 2', 'Item 3']

If your list isn't visible on the page such as a popup navigation menu, you need to pass visible: false into the all method.

Share:
17,513

Related videos on Youtube

John
Author by

John

Updated on June 07, 2022

Comments

  • John
    John almost 2 years

    I'm using RSpec/Capybara as my test suite. I have some javascript that dynamically appends <li> to the end of a <ul>. I want to write a request spec to ensure that this is happening.

    I tried using the has_css Capybara method and advanced CSS selectors to test for the ordering of the <li> elements, but Capybara doesn't support the + CSS selector.

    Example:

    page.should have_css('li:contains("ITEM #1")')
    pseuo_add_new_li
    page.should have_css('li:contains("ITEM #1")+li:contains("ITEM #2")')
    

    Does anyone know of another way to test for ordering?

  • dgilperez
    dgilperez over 11 years
    nth-last-child selects the nth child from the bottom. quirksmode.org/css/nthlastchild.html
  • JoaoHornburg
    JoaoHornburg about 11 years
    Using Webrat I get "undefined local variable or method `page'"
  • Finn MacCool
    Finn MacCool about 11 years
    @JoaoHornburg sorry, i can't help there. i've only used capybara so far.
  • JoaoHornburg
    JoaoHornburg about 11 years
    I've made it work with webrat. Already forked the project on github, will release my code when I have some time to change the docs and publish the gem
  • Daniel Wright
    Daniel Wright almost 11 years
    I don't know why you got a downvote, but you're perfectly correct about Capybara (well, Nokogiri) supporting the + selector. My suspicion is the new li was not being added adjacent to ITEM #1, and he should have used the ~ selector instead.
  • Drew Verlee
    Drew Verlee almost 10 years
    How is cucumber better at testing html?
  • Marnen Laibow-Koser
    Marnen Laibow-Koser almost 10 years
    @DrewV Cucumber is better at doing assertions about user-facing content in user-facing terms. RSpec is at the wrong level of abstraction for testing UI.
  • user664833
    user664833 over 8 years
    And with plain Minitest: assert page.body =~ /ITEM1.*ITEM2.*ITEM3/
  • lightsaber
    lightsaber almost 7 years
    I'm using this, and it works. I want to check order of items within a particular div, what should do?
  • samjewell
    samjewell about 6 years
    Thanks - that article really helped me (we're using Minitest, not RSpec)
  • dani24
    dani24 almost 6 years
    You could also check for the end of a tag: expect(page) =~ >ITEM1<.*>ITEM2<.*>ITEM3<
  • soupdog
    soupdog about 4 years
    In the OP's case I think this is a flaky test waiting to happen. Mapping the specific elements if at least one already exists on the page prior to the javascript that adds a new one will immediately return only those elements that are currently on the page, which may not include the new one if the javascript takes too long. Better to be explicit and use positional pseudo-class selectors as other answers have suggested.
  • Ben
    Ben almost 4 years
    Thank you, very elegant solution. I ended up using expect(response.body).to match(/ITEM1.*ITEM2.*ITEM3/m) with RSpec 4 and multi-line match
  • alexventuraio
    alexventuraio over 3 years
    The article is really good for further information.
  • Duderino9000
    Duderino9000 over 2 years
    Can someone explain what ITEM represents in these examples? Are those class names?
  • John
    John over 2 years
    @Barryman9000 It was the actual text in each of the <li>