Problems matching class with Capybara/RSpec - ruby-on-rails

I'm trying to match a class attribute using Capybara with RSpec.
I can very clearly see the elements and their classes but no matter what I try, Capybara seems to know nothing about the classes. But it can find id's no problem. Any clues, please? https://gist.github.com/1428472
visit "/admin/staff?mobile=1"
page.should have_selector("ul") #works
page.should have_selector("body#page") #works
page.should have_selector("html.ui-mobile") #fails
page.should have_selector("body.ui-mobile-viewport") #fails
save_and_open_page # this launches the page so I can see it and verify these attributes are indeed there.
The html:
< html class="ui-mobile" >
< body class="ui-mobile-viewport" id="page" >
I also set a breakpoint and did this stuff which also didn't work right.
#works
p find('body')[:id]
p find(:xpath, '//body[#id="page"]')
# doesn't work
p find('body')[:class]
p find(:xpath, '//html[#class="ui-mobile"]')
What's going on?
UPDATE: It turns out that it does actually work, however the problem here is that the html displayed by save_and_open_page differs from what capybara sees. When I break right after save_and_open_page and puts page.html, it's different. It's generally the same but a bunch of class attributes are gone as well as some other stuff. Very odd.

Related

Rspec/capybara - test presence of a class OR another class

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)
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.
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')
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
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.

How do I re-use Capybara sessions between tests?

I want to keep on using the same session and by that I mean Rails' session between various Test::Unit integration tests that use Capybara. The Capybara::Session object is the same in all the tests as it is re-used, but when I access another page in another test, I'm immediately logged out.
Digging in I found that capybara_session.driver.browser.manage.all_cookies is cleared between one test and the next.
Any ideas how? or why? or how to avoid it?
Trying to work-around that, I saved the cookie in a class variable and re-added later by running:
capybara_session.driver.browser.manage.add_cookie(##cookie)
and it seems to work, the cookie is there, but when there's a request, the cookie gets replaced for another one, so it had no effect.
Is there any other way of achieving this?
Add the following after your capybara code that interacts with the page:
Capybara.current_session.instance_variable_set(:#touched, false)
or
page.instance_variable_set(:#touched, false)
If that doesn't work, these might help:
https://github.com/railsware/rack_session_access
http://collectiveidea.com/blog/archives/2012/01/05/capybara-cucumber-and-how-the-cookie-crumbles/
If what you are doing is trying to string together individual examples into a story (cucumber style, but without cucumber), you can use a gem called rspec-steps to accomplish this. For example, normally this won't work:
describe "logging in" do
it "when I visit the sign-in page" do
visit "/login"
end
it "and I fill in my registration info and click submit" do
fill_in :username, :with => 'Foo'
fill_in :password, :with => 'foobar'
click_on "Submit"
end
it "should show a successful login" do
page.should have_content("Successfully logged in")
end
end
Because rspec rolls back all of its instance variables, sessions, cookies, etc.
If you install rspec-steps (note: currently not compatible with rspec newer than 2.9), you can replace 'describe' with 'steps' and Rspec and capybara will preserve state between the examples, allowing you to build a longer story, e.g.:
steps "logging in" do
it "when I visit the sign-in page" #... etc.
it "and I fill in" # ... etc.
it "should show a successful" # ... etc.
end
You can prevent the call to #browser.manage.delete_all_cookies that happens between tests by monkey patching the Capybara::Selenium::Driver#reset! method. It's not a clean way of doing it, but it should work...
Add the following code to your project so that it is executed after you require 'capybara':
class Capybara::Selenium::Driver < Capybara::Driver::Base
def reset!
# Use instance variable directly so we avoid starting the browser just to reset the session
if #browser
begin
##browser.manage.delete_all_cookies <= cookie deletion is commented out!
rescue Selenium::WebDriver::Error::UnhandledError => e
# delete_all_cookies fails when we've previously gone
# to about:blank, so we rescue this error and do nothing
# instead.
end
#browser.navigate.to('about:blank')
end
end
end
For interest's sake, the offending line can be seen in Capybara's codebase here: https://github.com/jnicklas/capybara/blob/master/lib/capybara/selenium/driver.rb#L71
It may be worth posting the reason why you need this kind of behaviour. Usually, having the need to monkey patch Capybara, is an indication that you are attempting to use it for something it was not intended for. It is often possible to restructure the tests, so that you don't need the cookies persisted across integration tests.

Undefined method 'should' when using Capybara with Cucumber

I am trying to use both Capybara and Cucumber in my Rails application. So, that's what i did:
Installed gems needed and added them to the Gemfile
Ran the rails generate capybara:install --cucumber
Created a feature and its steps definitions for cucumber
Here's my feature (yeah, i am creating blog application):
Feature: Browse posts
So that I can browse through the posts
As a visitor
I want to see all and/or particular posts
Scenario: Viewing all the posts
Given Posts exist
When I navigate to the website home
Then I should see all the posts
Scenario: Viewing particular post
Given Post #1 exists
When I navigate to /posts/view/1
Then I should see the post with id=1
And here's its step definitions:
Given /^Posts exist$/ do
assert (not Post.all.empty?), "No posts found at all"
end
Given /^Post #(\d+) exists$) do |id|
assert Post.find_by_id(id).valid?, "Post ##{ id } was not found at all"
end
When /^I navigate to (.+)$/ do |url|
if url =~ /^the website home$/ then
visit '/'
else
visit url
end
end
Then /^I should see all(.+)posts$/ do |delimiter|
posts = Post.all
posts.each do |post|
page.should have_content post.title
end
end
Then /^I should see the post with id([^\d]{1,})(\d+)$/ do |delimiter, id|
post = Post.find_by_id id
page.should have_content post.title
end
And when running rake cucumber i get this message:
Then I should see all the posts # features/step_definitions/browsing_posts.rb:13
undefined method `should' for #<Capybara::Session> (NoMethodError)
./features/step_definitions/browsing_posts.rb:17:in `block (2 levels) in <top (required)>'
./features/step_definitions/browsing_posts.rb:16:in `each'
./features/step_definitions/browsing_posts.rb:16:in `/^I should see all(.+)posts$/'
features/browsing_posts.feature:9:in `Then I should see all the posts'
What am I doing wrong? And yeah, are there any mistakes in my feature?
Replace page.should have_content post.title with assert page.has_content?(post.title). If that works, apply that similarly to the other have_content statements
Edit: Those statements involving should are based on rspec expectations, and should be used only if you are already using rspec or some other testing framework that responds_to should. Coming from the test::unit angle, this is probably just another kind of assert.
can you try
page.has_content?('foo')
instead of
page.should have_content post.title
if it works, then you can use this with assert.
after facing the same issue I tried
expect(page).to have_content('foo')
which worked fine for me

Capybara and page.body

I've got Capybara installed on Rails but, if I drop into the debugger in my steps.rb file, and check out the contents of page.body it's always empty.
Completely clueless how to proceed.
Should I check if Capybara is loaded in the environment - if so, how?
Should I initialize Capybara somewhere - if so, how?
Did you visit your page? Sorry, I know it sounds silly but it's all I can think of given the above information. Can you try page.find('body') work instead?
Like this:
describe 'home page' do
before :each do
visit root_path
end
it 'should have a body' do
page.find('body').should_not be_nil
page.find('body').text.should_not == ''
end
.
.
.
end

Webrat has unexpected outcome when 'if' statement is used

I have been using webrat with my rails 3.0.7 project and have been trying to write a test that uses regular expressions. It should fail, yet the if statement seems to corrupt the outcome.
it "should have 'microposts' if 0 posted" do
visit root_path
response.should have_selector('span.microposts') do |span|
if span =~ /\d/
span.should contain('hello')
end
end
end
The test is trying to confirm that the test, yet it still succeeds when an 'if' statement is used. It is trying to match the content '0 microposts'.
When I use these lines in replacement of the 'if' statement:
response.should have_selector('span.microposts') do |counter|
counter.should contain(/hello/)
end
I get the test to finally fail like it is supposed to, but then I don't get to verify the number in front of the content inside of the span like I was trying to above.
Does webrat not hand if statements well, or am I doing something wrong?
Thanks in advance!
It seems you are missing an end but I don't see how that wouldn't bomb.
it "should have 'microposts' if 0 posted" do
visit root_path
response.should have_selector('span.microposts') do |span|
if span =~ /\d/
span.should contain('hello')
end
end
end

Resources