As part of my integration tests for a website I am using cucumber with capybara. It seems that capybara cannot emulate the use of cookies.
For example I set the cookie when the user signs in:
def sign_in(user)
cookies.permanent.signed[:remember_token] = [user.id, user.salt]
current_user = user
end
However when I later fetch the value of cookies using cookies.inspect it returns {}
Is this a known limiting of capybara? How can I test a signed in user over multiple requests if this is the case?
I should add my test:
Scenario: User is signed in when they press Sign In
Given I have an existing account with email "bob#jones.com" and password "123456"
And I am on the signin page
When I fill in "Email" with "bob#jones.com"
And I fill in "Password" with "123456"
And I press "Sign In"
Then I should see "Welcome Bob Jones"
Here's a step that works for me. It sets a cookie "admin_cta_choice" to be equal to a model id derived from the input value.
Given /I have selected CTA "([^"]+)"/ do |cta_name|
cta = Cta.find_by_name(cta_name)
raise "no cta with name '#{cta_name}'" if cta.nil?
k = "admin_cta_choice"
v = cta.id
case Capybara.current_session.driver
when Capybara::Poltergeist::Driver
page.driver.set_cookie(k,v)
when Capybara::RackTest::Driver
headers = {}
Rack::Utils.set_cookie_header!(headers,k,v)
cookie_string = headers['Set-Cookie']
Capybara.current_session.driver.browser.set_cookie(cookie_string)
when Capybara::Selenium::Driver
page.driver.browser.manage.add_cookie(:name=>k, :value=>v)
else
raise "no cookie-setter implemented for driver #{Capybara.current_session.driver.class.name}"
end
end
here's a nice way to show the content of the cookies while running your feature
https://gist.github.com/484787
Why don't you just run the test with selenium? Just add #selenium tag before the scenario you want to run with a real browser. :)
That gist didn't work for me as of date of this posting, however this does, and is somewhat simpler:
http://collectiveidea.com/blog/archives/2012/01/05/capybara-cucumber-and-how-the-cookie-crumbles/
Capybara doesn't have an API for reading and setting cookies.
However, you can simulate logging in with Capyabara very easily - just visit the login link. That will log you in, including setting all cookies for you.
To see this in action, just look at my example Rails app.
Related
I am trying to write system tests. I want to run a classic login page test.
I have a field for email, password, and submit button.
It is working in production env and alive without any problem.
my test file is like this:
it "can login" do
user = User.create(email: 'mail#mail.com', password: 'password', role:1, name: 'test user')
user.save!
visit '/'
fill_in(:user_email, with: user.email)
fill_in(:user_password, with: 'password')
find(:button, 'Sign in').click
expect(page).to have_content('Signed in.')
end
Whenever I tried to create a user and try to use it in the system testing, it is not working. It is visiting the page, filling the places and clicking the button as it should but it cannot log in, giving error that email or password is not correct.
I believe there is a problem with password encryption or somehow I cannot match the passwords properly.
I have printed out the user after creation in the test case, I have a valid user but somehow I cannot reach its password. I checked the model, there is not a 'password' field. ( I am working on a company project, that is why I am having difficulty to find the problem )
I can assign a password with using user.password = ... but I cannot call it back it seems. (I tried this in rails console, assigning worked, calling back did not and I could use the user and the password for logging in manually)
EDIT:
I found out that the problem is database matching. I could create the user but the test is not using that user...
I found the problem. It was because of data transaction. I changed it truncation for system testing and it worked! It was basically flushing all the data before using it. That was why I was getting error.
I have a Ruby on Rails program with feature tests in Cucumber.
I just implemented a feature where an admin can create a new password for a client-user. Now, on the "edit client" page, there's an additional button that allows the admin to set the password. Now, I just need to make a cucumber test.
I am trying to base this off of the normal test for client changing password, and the test for admin changing the user's information. What I have is this:
Feature: Analyst changes client's password
As an Analyst
I want to change client's password
So that I can reset the client's account
Background:
Given the following client accounts
| email | password |
| user1#someorg.com | password |
And I am logged in as an admin
#javascript
Scenario: Update a Client user
Given I navigate to the Clients Management Page
When I edit the Client User "user1#someorg.com"
And I click on "button"
Then I should be on the Clients Password Page
#javascript
Scenario: Can change password if confirmation matches
Given I navigate to the Clients Password Page
And I enter "Password1" as the password
And I enter "Password1" as the password confirmation
And I submit the form
Then I should be taken to the Client Landing Page
And The client's password should be "Password1"
In the steps, I have:
Given /^I navigate to the Clients Password Page$/ do
client_management_index_page = ClientsPasswordPage.new Capybara.current_session
client_management_index_page.visit
end
Then /^I should be on the Clients Password Page$/ do
client_password_page = ClientsPasswordPage.new Capybara.current_session
expect(client_password_page).to be_current_page
end
and ClientsPaswordPage:
class ClientsPasswordPage
include PageMixin
include Rails.application.routes.url_helpers
def initialize session
initialize_page session, edit_admin_client_password_path
end
end
except that edit_admin_client_password_path takes an :id, for the user who's being edited. I can't figure out how to get that information into it.
In case it matters, I'm using Devise for the security stuff...
There are a few ways to do this. The simplest is to realize that you're only creating one client during the test so
Client.first # whatever class represents clients
will always be that client. Obviously that doesn't work if you have tests where you create one more than client, so then you can create instance variables in your cucumber steps which get set on the World and can then be accessed from other steps and passed to your page objects
When I edit the Client User "user1#someorg.com"
#current_client = Client.find_by(email: "user1#someorg.com") # obviously would actually be a parameter to the step
...
end
Then /^I should be on the Clients Password Page$/ do
client_password_page = ClientsPasswordPage.new Capybara.current_session, #current_client
expect(client_password_page).to be_current_page
end
of course without the page object overhead this would just become
Then /^I should be on the Clients Password Page$/ do
expect(page).to have_current_path(edit_admin_client_password_path(#current_client))
end
There are a number of things you can do to simplify this scenario. If you have simpler scenarios, with simpler step definitions then it will be easier to solve implementation problems like how you get a client in one step to be available in a second step.
The main way to simplify scenarios is to not have anything at all in the scenario that explains HOW you have implemented the functionality. If you take all the clicking on buttons, filling in fields, and visiting pages out of your scenarios you can focus on the business problem.
So how about
Background
Given there is a client
And I am logged in as an admin
Scenario: Change clients password
When I change the clients password
Then the client should have a new password
Note: This immediately raises the question 'How does the client find out about there new password?', which is what good simple scenarios do, they make you ask valuable questions. Answering this is probably out of scope here.
Now lets have a look at the implementation.
Given 'there is a client' do
#client = create_new_client
end
When 'I change the clients password' do
visit admin_change_password_path(#client)
change_client_password(client: #client)
end
Just this might be sufficient to get you on the right path. In addition something like
Given 'I am logged in as an admin' do
#i = create_admin_user
login_as(user: #i)
end
would help.
What we have done here is
Push the HOW down your stack so that now the code you right to make this work is out of your scenarios and step definitions
Used variable to communicate between steps the line #client = create_new_client creates a global (actually global to Cucumber::World) variable that is available in all step definitions
You can create helper methods by adding modules to Cucumber world and defining methods in them. Note these methods are global so you have to think carefully about names (there are very good reasons why these methods are global). So
module UserStepHelper
def create_new_client
...
end
def create_admin_user
...
end
def change_client_password(client: )
...
end
end
World UserStepHelper
Will create a helper method you can use in any of your step definitions.
You can see an example of this approach here. A project I used for a talk at CukeUp 2013. Perhaps you could use this as your tutorial example.
I have a Shopify Rails app and I am trying to test some of the functionality of my "pro" plan, but having trouble updating the test shop plan. I can login no problem, but when I try to update my shops plan via capybara, I get redirected to the login page.
I've done some troubleshooting, but I really have no idea where this issue is stemming from because it works fine when I try it manually in my browser. Maybe database_cleaner or caching issue?
Here's my cucumber steps (basically just login to the app, choose a plan):
Background:
Given I am a logged in user
When I am on the pro plan
Capybara:
When "I am a logged in user" do
step "I visit the login page"
step "I supply my shopify url"
step "I get taken to the app index page"
end
When /^I am on the (.+) plan$/ do |plan|
click_link_or_button "Settings & Notifications"
click_link_or_button "edit plan"
choose("shop_plan_#{plan}")
click_link_or_button "Update Plan"
click_link_or_button "Approve charge"
end
The driver successfully authenticates into the app, visits the edit plan page, visits the Shopify "approve charge" authorization page. But after clicking "approve charge", the browser is redirected to the login page instead of the action I am expecting.
When I try this manually in my own browser, I am redirected to the correct page.
Here's the actual controller action when a user updates their plan:
Step 1. User selects plan from settings page - posts to this action, which will redirect user to a page with embedded JS which redirects user to a Shopify authentication page (has to be done this way to escape the embedded app iframe).
def update_plan_step_1
#plan = shop_params[:plan]
redirect_url = current_shop.confirm_plan(#plan)
gon.authorization_url = redirect_url
render :redirect_to_shopify_auth
end
And here is the confirm_plan method. Basically this creates a new Shopify Charge object - Shopify is going to respond with a unique expiring URL for the user to confirm the charge. We need to provide the price, name, and return_url for Shopify to redirect the user after they approve the charge:
def confirm_plan(shopify_plan)
price = Plan.cost(shopify_plan)
name = shopify_plan + "Plan"
return_url = update_plan_step_2_url(:host => Figaro.env.root_uri)
response = ShopifyAPI::RecurringApplicationCharge.create({
:name => name,
:price => price,
:return_url => return_url,
:test=> !Rails.env.production?
})
response.confirmation_url
end
When I pry into this, I can see the return_url is set to the proper location: http://localhost:23456/shop/plans/update_plan_step_2 (shops#update_plan_step_2).
After user approves the charge on Shopify authentication page, they are supposed to be redirected to this action:
def update_plan_step_2
#some code to update our shop record
end
But when I pry into this action, I can see that it's not even being called in the Test, so I know the issue is happening before this.
To summarize, it looks like everything is working until the user is supposed to be redirected to http://localhost:23456/shop/plans/update_plan_step_2. Instead, they are redirected to the authentication page.
Why would this happen in the test, but not when I try doing it manually? Any ideas on where the issue lies?
Logs:
Started GET "/shop/plans/update_plan_step_2?charge_id=12345" for 127.0.0.1 at 2015-10-30 11:09:58 -0700
Processing by ShopsController#update_plan_step_2 as HTML
Parameters: {"charge_id"=>"12345"}
Redirected to http://localhost:23456/login
So we can see user is being redirected to authenticate. Why would this be happening only in test? Could it be a caching issue where the shop session is not being stored in the test? And the session is destroyed when the user is taken off the app to the Shopify authentication page?
EDIT: I know exactly where it's being redirected (in an before action in the controller)
def shopify_session
if shop_session
begin
ShopifyAPI::Base.activate_session(shop_session)
yield
ensure
ShopifyAPI::Base.clear_session
end
else
redirect_to_login ## REDIRECTED HERE
end
end
Which means after user authenticates via Shopify, the shopify_session no longer exists.
Capybara.default_host defaults to 127.0.0.1 which means that all access to your app occurs over http://127.0.0.1/some/path by default when visiting paths in your app with Capybara. When your app redirects to http://localhost/some/path the session cookies stored for the hostname 127.0.0.1 are not valid for hostname localhost so the app redirects to login. Either change your return_url to use a hostname of 127.0.0.1 or change Capybara.default_host to 'localhost' (using 'localhost' for default_host has a few small gotchas when using selenium though, so better to change the return_url)
What I what to accomplish is to use (rely on) current_user method while defining Cucumber steps. I'm using Clearance in my project.
First of all I tried to use sign_in but it didn't work (I guess Cucumber World doesn't know about Clearance methods...).
So how do I make Cuckes recognize current_user and sign_in/sign_out methods?
Your Cucumber features should be driving your application through the public user interface. Something like:
Given /^I am signed in as "([^\"]*)"%/ do |username|
visit 'sign_in'
fill_in 'Username', :with => username
click 'Sign In'
end
Since the current_user method isn't available to the browser, you shouldn't be using it in your spec.
You could fake it in your steps by storing #current_user in the above step and then providing an attribute reader for it.
I disagree with the idea that every acceptance test (cucumber or otherwise) must exercise the login logic. Luckily, if you agree, Clearance has added a back door in tests that lets you skip the sign in steps.
user = create(:user)
visit posts_path(as: user)
Now you can leave your login-related features driving the login ui as a user would and skip that for features that aren't directly related to logging in.
I started writing functional tests for my rails app today. I use the RESTful authentication plugin. I ran into a couple confusing things I hope someone can clarify for me.
1) I wrote a quick login function because most of the functions in my rails app require authentication.
def login_as(user)
#request.session[:user_id] = user ? user.id : nil
end
The issue I see with this function, is it basically fakes authentication. Should I be worried about this? Maybe it is okay to go this route as long as I test the true authentication method somewhere. Or maybe this is terrible practice.
2) The second confusing thing is that in some places in my functional tests, I need the full authentication process to happen. When a user is activated, I have the do_activate method create some initial objects for the user. It is analogous to the creation of a blank notebook object and pen object for a student application, if that makes sense.
So in order to properly test my application, I need the user to hit that activation state so those objects are created. I am currently using Factory Girl to create the user, and then calling the login_as function above to fake authentication.
I guess another option would be to skip the full authentication sequence and just create the blank objects with Factory Girl. I could test the proper authentication somewhere else.
What do you think? If I should go through the proper sequence, why isn't the code below invoking the do_activate function?
user = Factory.create(:user)
user.active = 1
user.save
Thank you!
Faking it is perfectly acceptable.
However, write other tests that ensure that the things you want protected are protected. So
test "it should show the profile page" do
user = Factory(:user)
login_as(user)
get :show, :id => user
assert_response :success
end
test "it should not show the profile page cos I'm not logged in" do
user = Factory(:user)
get :show, :id => user
assert_response :redirect
end
Feel free to hit me up for followups!