How to handle cookies when testing with Webrat? - ruby

I'm writing Cucumber tests for a Sinatra based application using Webrat. For some tests I need to implement a scenario like
Given I am logged in as admin
When I am visiting "/"
Then I should see "Settings"
I define steps like this:
Given /^I am logged in as "(.+)"$/ do |user|
visit "/login"
fill_in "login", :with => user
fill_in "password", :with => "123456"
click_button "Login"
end
When /^I am viewing "(.+)"$/ do |url|
visit(url)
end
Then /^I should see "(.+)"$/ do |text|
response_body.should =~ /#{text}/
end
On success a cookie is created
response.set_cookie(cookie_name, coockie_value)
and then verified in views when user tries to access admin pages via helper method:
def logged_in?
request.cookies[cookie_name] == cookie_value
end
And it looks like Webrat doesn't store cookies. Tests don't report any error, but "logged_in?" in views is always false, like the cookie was not saved.
Am I doing something wrong? If this is just how Webrat works, what is the best workaround?

The real problem is the way Sinatra is treating sessions in the test environment. Search the Google group for the discussion, but the real solution is to simply use:
use Rack::Session::Cookie
and not
enable :sessions
Using Selenium is nice but it's overkill as a solution for the OP's problem.

The workaround is use Webrat with Selenium back end. It runs all tests in a separate Firefox window, so cookies or javascript is not a problem. The downside is extra time and resources required to run Firefox and do all the real clicks, rendering etc.

You could have your "Given /^I am logged in" step hack logged_in?:
Given /^I am logged in as "(.+)"$/ do |user|
visit "/login"
fill_in "login", :with => user
fill_in "password", :with => "123456"
click_button "Login"
ApplicationController.class_eval <<-EOE
def current_user
#current_user ||= User.find_by_name(#{EOE})
end
end
EOE
end
There are two downsides:
It's really hackish to mix view-level and controller-level issues like this.
It'll be difficult to mock up "logout"

Related

Including shared example from rspec inside of cucumber

The end goal is to include the shared_examples.rb which is included in rails_helper.rb. shared_examples.rb is a copy of this file
https://github.com/tinfoil/devise-two-factor/blob/master/lib/devise_two_factor/spec_helpers/two_factor_authenticatable_shared_examples.rb
I want to reference the shared_examples.rb in my cucumber test so I can use the method it_behaves_like 'two_factor_authenticatable'
I have the following folder structure:
Updated to include #morissetcl suggested structure
features
step_definitions
sample_step.rb
support
env.rb
sample.feature
spec
models
user_spec.rb
support
shared_examples
shared_example.rb
rails_helper.rb
spec_helper.rb
Both the features and spec folders are at the root of my rails project.
I am trying to include inside the sample_step.rb file the rails_helper.rb which is located in the spec folder.
I tried using different types of require as shown below inside the sample_step.rb file.
require 'spec/spec_helpers/shared_examples'
require '../../spec/spec_helpers/shared_examples'
require_relative '../../spec/spec_helpers/shared_examples'
I keep getting the following error
undefined method `it_behaves_like' for main:Object (NoMethodError)
it_behaves_like is rspec specific. What it does is allow one spec to run another spec for a particular object, so that is not going to work in Cucumber.
What you need to do is have a test suite that has some cukes and some specs. You cover the two factor authentication in detail in rspec, and if you have to always use two factor authentication to login in your cukes you need to write a helper method so you can do that.
To do this I would do the following
Write some step definitions to support login, that delegate the work to a helper method.
Write the helper method
Have the helper method call additional methods to support two factor auth
Add the helper methods to cucumber's world object so they can be called in a step def
So
# features/step_defintions/login
module LoginStepHelper
def login(user: )
login_fill_form_and_submit(user: user)
login_two_factor(user: user)
submit_form
end
def login_fill_form_and_submit(user: )
fill_in :email, user.email
fill_in :password, user.password
end
def login_two_factor(user: )
code = retrieve_2factor_code(user: user)
fill_in :2factor, code
end
...
end
World(LoginStepHeler)
So now you have to work out how the test can get the 2factor code.
Once you have this done you have a tool your step definitions can use to login, so you can write things like
Given 'I am logged in' do
login user: #i
end
Given 'I login as Fred' do
login user: #fred
end
...
note: how many step defs can use your helper method.
You can find more detail about this approach here https://github.com/diabolo/cuke_up which includes details about how to create the test-users that we are passing into the login function in the above code
The solution I came up with was using some of the suggestions that #diabolist made. I looked into the shared examples file: https://github.com/tinfoil/devise-two-factor/blob/master/lib/devise_two_factor/spec_helpers/two_factor_authenticatable_shared_examples.rb
I then used the #validate_and_consume_otp! scenario and setup the data using the info in the before :each block.
The result was as so in my cucumber spec:
When('I fill in the login form with two factor code') do
Timecop.freeze(Time.current)
otp_secret = '2z6hxkdwi3uvrnpn'
#user.otp_secret = otp_secret
otp = ROTP::TOTP.new(otp_secret).at(Time.now)
#user.save # <- This is important to save it so the user has the otp you will pass
Timecop.return
fill_in 'user_email', with: 'user#example.com'
fill_in 'user_password', with: 'password'
fill_in 'user_otp_attempt', with: otp
click_button 'Login'
end
The setup can be cleaned up and put into a method just as #diabolist described so it can be reused by other tests.

Minitest with Sinatra (Ruby)

I'm trying to use minitest with sintara and my issue is that running the test (ruby test_login.rb) is unable to find the login page and when I print out the document html I get the sinatra 404 page. I have no idea how to connect my web app with this test program and all documentation and previous questions I have scoured have nothing that helps me.
Here is my code:
require 'sinatra/base'
require 'minitest/autorun'
require 'rack/test'
require 'minitest/spec'
require 'nokogiri'
require 'rack/test'
require_relative 'login'
class Test < MiniTest::Test`
include Rack::Test::Methods
def app
Sinatra::Application
end
def test_login
response = get ('/home')
doc = Nokogiri::HTML(response.body)
puts last_response
puts doc
#response = post '/login', username: 'test_user', password: 'password'
#get '/home'
#follow_redirect!()
#puts doc
#assert_equal "Admin", doc.at_css("#admin-block div h1")
end
end
Please do not comment asking me to use a different testing gem.
Thank you
Since you're testing that the login works, you only want to see if the login information that you pass to the login form actually gives a HTTP 200 response.
There is no need for the first part of the code that is not commented in your example.
As a side-note, checking if a response from a page has a certain HTTP code with content of the page body is not considered a 'good' test, but if you consider it to be decent enough, then go ahead with it.
The following test should work if the login information that you pass is valid.
def test_login
response = post '/login', username: 'test_user', password: 'password'
assert_equal response.status, Net::HTTPSuccess
end
EDIT
Update to the question came right after posting my answer, but if you're getting an error when sending a GET request to /home it sounds like that route is not defined in your application.

How to make my RSpec describe blocks run independently of one another?

On my Dashboard page, I have a Metrics section where I show the number of goals a user has. I do not show this section for a user who has no goals. When a user creates a goal and after a redirect the Metrics section will appear.
In the RSpec test below, when RSpec randomly runs the first describe first, the test passes because it does not find the Metrics section. However when RSpec runs the second describe block first, the first describe block fails because by that time the redirect has happened and the Metrics section has appeared.
How do I ensure that each block runs separately and passes?
describe "Dashboard Pages", :type => :request do
subject { page }
let(:user) { FactoryGirl.create(:user) }
before(:each) do
sign_in user
end
describe "After user signs in - No Goals added yet" do
it { is_expected.to have_title(full_title('Dashboard')) }
it { is_expected.to have_content('Signed in successfully')}
it "should not show the metrics section" do
expect(page).to_not have_css("div#metrics")
end
end
#
#Notice that this runs using the SELENIUM WebDriver
#
describe "After user signs in - Add a new Goal" do
it "should display the correct metrics in the dashboard", js: true do
click_link "Create Goal"
fill_in "Goal Name", :with=> "Goal - 1"
fill_in "Type a short text describing this goal:", :with => "A random goal!"
click_button "Save Goal"
end
end
end
I think your problem is that the request sent by click_button "Save Goal" arrives at the server after that test completes. Capybara's Javascript drivers are asynchronous and don't wait for the commands that they send to the browser to complete.
The usual way to get Capybara to wait is to expect something about the page that will be true when the command you want to wait for is complete. That's a good idea here anyway since the last test doesn't actually expect that the metrics are shown like it says it does. So expect that they are:
it "should display the correct metrics in the dashboard", js: true do
click_link "Create Goal"
fill_in "Goal Name", :with=> "Goal - 1"
fill_in "Type a short text describing this goal:", :with => "A random goal!"
click_button "Save Goal"
expect(page).to have_css("div#metrics")
end
Also, note that current RSpec and Capybara don't allow you to use Capybara in request specs. Unless you're tied to old versions for some other reason, I suggest upgrading to current RSpec and Capybara and converting your request spec to a feature spec.

Support for the Page Object pattern in Ruby

In Ruby-land we have Capybara and Webrat to drive our web browsers during functional testing with Cucumber.
What I can't find is something like Geb in Groovy/Java-land which seems like it works on one level of abstraction higher than Capybara. This is the description of Geb from the Geb website.
Geb is a browser automation solution.
It brings together the power of WebDriver, the elegance of jQuery
content selection, the robustness of Page Object modelling and the
expressiveness of the Groovy language.
Capybara already brings together WebDriver (usually Selenium) and jQuery-style content selection. But it doesn't have any support for the Page Object idea. (You create classes to represent the pages under test, so the steps carry out actions upon them rather than look at the DOM directly all the time. Like a mini-API for your page.)
To give an example of the kind of useful feature I'm looking for, I understand from a colleague that Geb can automatically assert that the page under test matches the attributes in the virtual page object which represents the page to your Cucumber tests.
I've made use of Site Prism for page-objects in a fairly large application. Cheezy's page-object gem was the other gem that I considered at the time but it didn't make use of Capybara (which when used correctly can aid with timing issues). The page-object gem has it's own "wait" mechanism.
There's also another gem but I suspect it's abandoned.
The page-object gem will give you test code along these lines:
class LoginPage
include PageObject
page_url "http://example.com/login"
text_field(:username, :id => 'username')
text_field(:password, :id => 'password')
button(:login, :id => 'login')
def login_with(username, password)
self.username = username
self.password = password
login
end
end
# in your tests
visit_page LoginPage do |page|
page.login_with('testuser1#example.com', 'incorrect')
page.wait_until do # using default of 30s for this asynch call
page.text.include? 'invalid user or password'
end
expect(page).to have_content 'invalid user or password'
More examples can be seen in this project: https://github.com/JonKernPA/pageobject and on the wiki https://github.com/cheezy/page-object/wiki/Elements
Site Prism looks like this:
class LoginPage < SitePrism::Page
set_url '/login'
element :username_field, '#username'
element :password_field, '#password'
element :login_button, '#login'
def login_with(username, password)
username_field.set username
password_field.set password
login_button.click # this uses capybara to find('#login').click
end
end
# in your tests
#page = LoginPage.new
#page.load
#page.login_with('testuser1#example.com', 'incorrect')
# capybara automatically waits for us
expect(#page).to have_content 'invalid user or password'
The Site Prism README has a lot of good examples. Everything else you need to know is in Capybara's excellent README and documentation.
There are of course far more differences than these small example shows.
I would advise you to take a look at both and decide what your requirements are.

What is a very simple authentication scheme for Sinatra/Rack

I am busy porting a very small web app from ASP.NET MVC 2 to Ruby/Sinatra.
In the MVC app, FormsAuthentication.SetAuthCookie was being used to set a persistent cookie when the users login was validated correctly against the database.
I was wondering what the equivalent of Forms Authentication would be in Sinatra? All the authentication frameworks seem very bulky and not really what I'm looking for.
Here is a very simple authentication scheme for Sinatra.
I’ll explain how it works below.
class App < Sinatra::Base
set :sessions => true
register do
def auth (type)
condition do
redirect "/login" unless send("is_#{type}?")
end
end
end
helpers do
def is_user?
#user != nil
end
end
before do
#user = User.get(session[:user_id])
end
get "/" do
"Hello, anonymous."
end
get "/protected", :auth => :user do
"Hello, #{#user.name}."
end
post "/login" do
session[:user_id] = User.authenticate(params).id
end
get "/logout" do
session[:user_id] = nil
end
end
For any route you want to protect, add the :auth => :user condition to it, as in the /protected example above. That will call the auth method, which adds a condition to the route via condition.
The condition calls the is_user? method, which has been defined as a helper. The method should return true or false depending on whether the session contains a valid account id. (Calling helpers dynamically like this makes it simple to add other types of users with different privileges.)
Finally, the before handler sets up a #user instance variable for every request for things like displaying the user’s name at the top of each page. You can also use the is_user? helper in your views to determine if the user is logged in.
Todd's answer does not work for me, and I found an even simpler solution for one-off dead simple authentication in Sinatra's FAQ:
require 'rubygems'
require 'sinatra'
use Rack::Auth::Basic, "Restricted Area" do |username, password|
[username, password] == ['admin', 'admin']
end
get '/' do
"You're welcome"
end
I thought I would share it just in case anyone wandered this question and needed a non-persistent solution.
I' have found this tutorial and repository with a full example, its working fine for me
https://sklise.com/2013/03/08/sinatra-warden-auth/
https://github.com/sklise/sinatra-warden-example
I used the accepted answer for an app that just had 2 passwords, one for users and one for admins. I just made a login form that takes a password(or pin) and compared that to one that I had set in sinatra's settings (one for admin, one for user). Then I set the session[:current_user] to either admin or user according to which password the user entered and authorized accordingly. I didn't even need a user model. I did have to do something like this:
use Rack::Session::Cookie, :key => 'rack.session',
:domain => 'foo.com',
:path => '/',
:expire_after => 2592000, # In seconds
:secret => 'change_me'
As mentioned in the sinatra documentation to get the session to persist in chrome. With that added to my main file, they persist as expected.
I found JWT to be the simple, modern/secure solution I was searching for. OP mentioned bulky frameworks, so for reference I downloaded the tag of the latest jwt gem at the time of writing (2.2.3) and it's 73 KB zipped and 191 KB unzipped. Seems to be well-maintained and open sourced on GitHub.
Here's a good blog post about it with code and a walkthrough for near-beginners: https://auth0.com/blog/ruby-authentication-secure-rack-apps-with-jwt/

Resources