Reputation: 9456
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?
Upvotes: 48
Views: 19621
Reputation: 670
Here's a custom matcher that I currently use to test the order of page contents:
RSpec::Matchers.define :appear_before do |later_content|
match do |earlier_content|
earlier_content_index = page.body.index(earlier_content)
later_content_index = page.body.index(later_content)
@failure_message = "Expected \"#{earlier_content}\" to appear before \"#{later_content}\""
def raise_missing_content_error(content)
@failure_message += " but \"#{content}\" was not found on the page."
raise RSpec::Expectations::ExpectationNotMetError
end
raise_missing_content_error(earlier_content) if earlier_content_index.nil?
raise_missing_content_error(later_content) if later_content_index.nil?
earlier_content_index < later_content_index
end
failure_message { @failure_message }
end
You can then use it like this:
expect('My new post').to appear_before('My old post')
Upvotes: 0
Reputation: 3710
Since page.body.should
is deprecated, we can use regexp matching for a multiline comparing like a Ben said in a comment over there:
expect(page.body).to match /#{item1.name}.*#{item2.name}/m
Upvotes: 0
Reputation: 415
In above solutions, it was very difficult to test order if having multiple elements on page. So, I have found another way.
I have posted the solution here - Test order(sequence) of content using capybara
Mapping the arrays having css specific elements and then match that array with expected array(keep elements in sequence)
Upvotes: 0
Reputation: 5465
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.
Upvotes: 6
Reputation: 4930
Use the orderly
gem, written by the author of the article mentioned previously.
It's as simple as:
expect(this).to appear_before(that)
Upvotes: 1
Reputation: 12520
Using capybara-ui you could use the #widgets
method to get all of the elements in top-down order.
# First define the widget,
# or reusable dom element reference.
# In this case in a role
class UserRole < Capybara::UI::Role
widget :list_item, '.list-item'
end
# Then test the expected order using #widgets
role = UserRole.new
expected_order = ['buy milk', 'get gas', 'call dad']
actual_order = role.widgets(:list_item).map(&:text)
expect(actual_order).to eq(expected_order)
Upvotes: 0
Reputation: 1694
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.
Upvotes: 5
Reputation: 808
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
Upvotes: 28
Reputation: 601
This page has got a very clever way of testing the order of string elements on a page - be they in a list or not:
https://makandracards.com/makandra/789-match-strings-in-a-given-order-with-cucumber-and-capybara
It is in the form of a cucumber step, but you should be able to extract what you need for an Rspec step. It is similar to Johns 'kludgy' solution - but a bit fancier from what I can make out. All of their other cunning capybara testing steps can be found here:
https://github.com/makandra/spreewald
Upvotes: 2
Reputation: 10796
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.
Upvotes: 29
Reputation: 9456
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/
Upvotes: 38
Reputation: 6337
Capybara should support the +
selector (I believe it uses Nokogiri, which certainly supports this). Are you perhaps using an old version? Or are you using a Capybara driver that doesn't support JavaScript, so that the extra element isn't getting rendered at all?
Also, don't use RSpec for testing your HTML. Cucumber is far better at this. For interacting with JavaScript, you'll want to use Selenium or Capybara-Webkit. Or if you have a lot of JavaScript, consider testing it with Jasmine.
Upvotes: -1