Reputation: 25567
Capybara's HaveSelector
is not working with RSpec expect
the way I expect. I'm new to Capybara and RSpec so I this may well be a misunderstanding of mine with respect to RSpec or Capybara, or it may be a deficiency in Capybara (version 2.0.2). Please help me either understand my mistake or craft a bug report/feature request.
In my RSpec I wrote:
expect { click('.special-div .submit') }.to have_css('.submitted')
I expected this to be functionally equivalent to
click('.special-div .submit')
page.should have_css('.submitted')
but it is not. Instead, the matcher have_css
tries to match against the string conversion of the proc object rather than the result of calling the proc object. (In other words, click('.special-div .submit')
is never executed.)
Is Capybara's behavior:
Also, I can obviously do what I want by using the 2-line version above, but our team is trying to standardize on expect {}
, so is there a way to use the expect {}
form and get it to do what I want?
EDIT
I inherited the code I'm working with so I did not realize that, as Andrey Botalov pointed out, click
is not a standard part of Capybara. Seems like it should be, but then again click
is already heavily used for other things so it may be better that Capybara doesn't add yet another definition.
Since some people seem skeptical, let me assure you that this code is working fine:
click('.special-div .submit')
page.should have_css('.submitted')
For those wondering about have_css()
, that is RSpec magic for has_css?
. For those wondering about click
, in my project, someone has conveniently created the click
function as follows:
def click(css)
page.execute_script("$('#{css}').first().trigger('click');")
end
Why? Because none of the obvious alternatives worked.
click_on('.special-div .submit') # Fails because click_on does not take CSS
# Cannot use click_button() because we are clicking on a <div>
find('.special-div .submit').click # Raises exception because there are more than one
first('.special-div .submit').click # Fails because the div is not visible
Moving on, @zetetic asked if
expect(click('.special-div .submit')).to have_css('.submitted')
would work. No, it doesn't work for us because we're still on RSpec 2.9 and that syntax was introduced in 2.11, but even if we upgraded it still wouldn't work because click
does not return an object. It would probably work if we upgraded to 2.11 and changed click
to return page
.
Upvotes: 4
Views: 2663
Reputation: 9172
EDIT: The first part of the answer was wrong so I changed it.
I initially said that this code
expect { click('.special-div .submit') }.to have_css('.submitted')
is equivalent to:
click('.special-div .submit').should have_css('.submitted')
But this is not true because the proc passed to expect
will not in fact get evaluated (you can read @OldPro's answer for more details).
The solution is to expect change
:
expect {
click_button('Button label')
}.to change { page.has_css?('.submitted') }.from(false).to(true)
You can find more details on the rspec website.
Upvotes: 1
Reputation: 25567
There are several problems with expect { click('.special-div .submit') }.to have_css('.submitted')
which is why I got so confused.
Most confusing to me is RSpec's expect {...}.to ...
syntax. I thought this syntax would cause matchers to run the code in the {}
and apply the matcher to the result or something like that. Seems to make sense given the example:
expect { something }.to raise_error(SomeError)
Turns out this is not generally true. Only certain matchers expect the "actual" (what comes between 'expect' and '.to') to be a Proc
and then call it. Most matchers expect the "actual" to be an object. So:
expect { 1 + 1 }.to eq(2)
raises an exception trying to compare a Proc
to a Fixnum
.
So Capybara's has_css?
matcher is being completely reasonable in expecting the "actual" to be an object that responds to has_selector?
rather than a Proc
. That was really my main question and with that, Capybara is off the hook.
Furthering my confusion was that Capybara helpfully provides a DSL so that in my examples has_css?
is equivalent to page.has_css?
which led me to expect that have_css(css)
would continue to magically operate against page
.
On to the better way to write the test. @deviousdodo is mostly right, which is why I'm awarding the bonus to that answer. What I wanted was
expect {
click('.special-div .submit')
}.to change { page.has_css?('.submitted') }.from(false).to(true)
Actually, though it's beyond the scope of the original question, what I really want is
expect {
click('.special-div .submit')
page.should have_css('.submitted')
}.to change { Click.count }.by 1
The reason I want this is because what I want to check most is that the click gets recorded in the database (Click.count
increments) but I have to wait for the AJAX call triggered by the click to finish, which is accomplished by has_css?
.
Upvotes: 2