8

In my ruby on Rails 4.2 app, on a page I have a conditional back end rule that translates into the page front end in a change in class

<div id="content">
    <i class='<% if x=true %>glyphicon glyphicon-thumbs-down<% else> glyphicon glyphicon-thumbs-up ><% end %>' </i>
       this is the message.
</div>

How to check presence with rspec 3/capybara that the page contains the class glyphicon-thumbs-down OR the class glyphicon-thumbs-up ?

I tried the code below but it fails:

it "should sheck one of the 2 classes presence" do
  expect(page).to have_css '.glyphicon-thumbs-down' || expect(page).to have_css '.glyphicon-thumbs-up'
end

I am getting the following error message:

syntax error, unexpected tIDENTIFIER, expecting keyword_end (SyntaxError)
Mathieu
  • 4,587
  • 11
  • 57
  • 112
  • Shouldn't you rather prepare the test including the backend rule, so that you know precisely which class will be rendered on the page? – Matouš Borák May 27 '17 at 22:57
  • nope as it's a random rule on the back end (it's game of luck) so i can't predict it inside my test suite – Mathieu May 27 '17 at 22:59

4 Answers4

11

Multiple OR-ed css selectors can be specified separated by a comma. Try the following:

it "should sheck one of the 2 classes presence" do
  expect(page).to have_css '#content i.glyphicon-thumbs-down,#content i.glyphicon-thumbs-up'
end

(I added the #content and i selectors so that the query is more specific.)

However, instead of doing this I would recommend trying to make the test behave in a precisely defined way and test for just a single class in the spec. Have a look at this SO question and its answers for various ways to stub or preset the random number generator in tests.

Matouš Borák
  • 15,606
  • 1
  • 42
  • 53
7

Firstly, you're checking for a class name so you need a . in front of the class names to make it a CSS class selector. Then, you could use the RSpec or matcher combinator

expect(page).to have_css('.glyphicon-thumbs-down').or(have_css '.glyphicon-thumbs-up')

but it has the downside of the first one retrying/waiting for Capybara.default_max_wait_time seconds before checking the second. You could specify a 0/false wait time if you know the page is already loaded and therefore don't need retrying/waiting

 expect(page).to have_css('.glyphicon-thumbs-down', wait: false).or(have_css '.glyphicon-thumbs-up', wait: false)

However, it's probably fine to just check for either element using the normal CSS ,

 expect(page).to have_css('.glyphicon-thumbs-down, .glyphicon-thumbs-up')
Thomas Walpole
  • 48,548
  • 5
  • 64
  • 78
  • thanks a lot for the answer, I gave the points to Borama as his suggestion matched the one I needed (your last suggestion) 2 minutes before:) Indeed i must wait for server ajax response and can't use the 0 wait time, so the bets is to first wait_for_ajax then have_css('.glyphicon-thumbs-down, .glyphicon-thumbs-up') – Mathieu May 27 '17 at 23:28
  • @Mathieu You shouldn't need `wait_for_ajax` at all there, the `have_css` will wait for one of the two selectors to match (up to Capybara.default_max_wait_time seconds) – Thomas Walpole May 27 '17 at 23:30
  • i wait for ajax because i have a ajax request sent to server to check a data and send it back to the view. I know have_css will wait...but sometimes in my specific situation i had to use wait_for_ajax to work. Thanks though for the additional positively watchful information – Mathieu May 27 '17 at 23:39
1

Maybe you can try to use option :count, in expect method like this:

it 'should contains 2 same selector' do
  expect(page).to have_css '.some-class', count: 2
end
0

Your error is from this line:

  expect(page).to have_css 'glyphicon-thumbs-down' || expect(page).to have_css 'glyphicon-thumbs-up'

You just need to add some parens then it will be valid syntax:

expect(page).to(have_css('glyphicon-thumbs-down')) || expect(page).to(have_css('glyphicon-thumbs-up'))

that won't fix your issue though, because if the left condition fails then rspec will exit and not run the second half.

A working approach could be to evaluate the condition to a boolean variable, then pass it to a single rspec expectation. Doing this requires using the core Capybara method has_css to test for css presence, not have_css? from the rspec matchers package:

selectors = ['.glyphicon-thumbs-down', '.glyphicon-thumbs-up']
glyph_exists = selectors.any? do |selector|
  page.has_css? selector
end
expect(glyph_exists).to be true

Note also that I've added . to the selector strings which is necessary since it's a css class.

max pleaner
  • 26,189
  • 9
  • 66
  • 118
  • This has the downside of each call to `has_css?` waiting Capybara.default_max_wait_time seconds before checking the next one if it doesn't match. – Thomas Walpole May 27 '17 at 23:28
  • True. To work around that ive wrapped them in an explicit wait of a very short duration and rescued the error to return false. Not ideal though – max pleaner May 27 '17 at 23:30