i'm using rspec/capybara in my rails project for tests, which is working fine with the default driver. But when i switch to webkit or selenium i get logged out after every request that i make.
This code is working as expected, i see the logged in page 2 times:
require 'rails_helper'
feature 'test' do
scenario 'this' do
user = FactoryGirl.create :user
login_as(user)
visit root_path
save_and_open_page
visit root_path
save_and_open_page
end
end
When i set webkit or selenium as driver only the first page is the logged in version, on the second page i'm logged out:
require 'rails_helper'
feature 'test' do
scenario 'this', driver: :webkit do
user = FactoryGirl.create :user
login_as(user)
visit root_path
save_and_open_page
visit root_path
save_and_open_page
end
end
How can i fix this?
I was having this exact same problem and eventually came across this question with pretty much the same problem: Why is Capybara discarding my session after one event?
The solution is to include the snippet found here in your rails_helper
class ActiveRecord::Base
mattr_accessor :shared_connection
##shared_connection = nil
def self.connection
##shared_connection || retrieve_connection
end
end
# Forces all threads to share the same connection. This works on
# Capybara because it starts the web server in a thread.
ActiveRecord::Base.shared_connection = ActiveRecord::Base.connection
Related
I set up a simple controller with its according feature_spec:
stamps_controller.rb
class StampsController < ApplicationController
def new
#stamp = Stamp.new
end
def create
#stamp = Stamp.new(stamp_params)
if #stamp.save
redirect_to(stamp_url(#stamp.id), status: 201)
else
render 'new'
end
end
def show
#stamp = Stamp.find(params[:id])
end
private
def stamp_params
params.require(:stamp).permit(::percentage)
end
end
specs/requests/stamps_request_spec.rb
RSpec.describe 'stamp requests', type: :request do
describe 'stamp creation', js: true do
before do
FactoryBot.create_list(:domain, 2)
FactoryBot.create_list(:label, 2)
end
it 'allows users to create new stamps' do
visit new_stamp_path
expect(page).to have_content('Percentage')
find('#stamp_percentage').set('20')
click_button 'Create'
expect(current_path).to eq(stamp_path(Stamp.first.id))
end
end
end
According to the capybara docs:
Capybara automatically follows any redirects, and submits forms associated with buttons.
But this does not happen in the test, instead it throws an error:
expected: "/stamps/1
got: "/stamps"
The results are obvious: it successfully creates the stamp but fails to redirect to the new stamp. I also confirmed this by using binding.pry.
Why wouldn't capybara follow the redirect as described in the docs?
Sidenotes:
it even fails if I use the normal driver instead of js
I've looked into lots of SO questions and docs, finding nothing useful. One potential attempt I was unable to grasp was an answer with no specifics of how to implement it.
my configs:
support/capybara.rb
require 'capybara/rails'
require 'capybara/rspec'
Capybara.server = :puma
Capybara.register_driver :selenium do |app|
Capybara::Selenium::Driver.new(app, browser: :firefox, marionette: true)
end
Capybara.javascript_driver = :selenium
RSpec.configure do |config|
config.include Capybara::DSL
end
spec_helper.rb
ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../config/environment', __dir__)
require 'rspec/rails'
require 'factory_bot_rails'
require 'pundit/matchers'
Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }
RSpec.configure do |config|
# .
# unrelated stuff
# .
end
You have a number of issues in your test.
First, Capybara is not meant to be used in request specs - https://relishapp.com/rspec/rspec-rails/docs/request-specs/request-spec - but should instead be used with feature/system tests. Once you've fixed that you should no longer need to include Capybara into every RSpec test and should remove the config.include Capybara::DSL from you config (when you require capybara/rspec it includes Capybara into the test types it should be included in - https://github.com/teamcapybara/capybara/blob/master/lib/capybara/rspec.rb#L10)
Second, click_button is not guaranteed to wait for any actions it triggers to complete. Because of that you need to wait for a visual page change before attempting to access any database objects that would be created by that action (technically you really shouldn't be doing direct DB access in feature specs at all but if you're going to ...)
click_button 'Create'
expect(page).to have_text('Stamp created!') # Whatever message is shown after creation
# Now you can safely access the DB for the created stamp
Third, as pointed out by #chumakoff you should not be using static matchers with Capybara and should instead be using the matchers provided by Capybara
click_button 'Create'
expect(page).to have_text('Stamp created!') # Whatever message is shown after creation
expect(page).to have_path(stamp_path(Stamp.first.id))
Finally, you should look at your test.log and use save_and_open_screenshot to see what your controllers actually did - It's and there's actually an error being raised on creation which is causing your app to redirect to /stamps and display the error message (would also imply your test DB isn't actually being reset between tests, or the factories you show are creating nested records, etc).
Update: After rereading your controller code I noticed the that you're passing a 201 status code to redirect_to. 201 won't actually do a redirect - From the redirect_to docs - https://api.rubyonrails.org/classes/ActionController/Redirecting.html#method-i-redirect_to
Note that the status code must be a 3xx HTTP code, or redirection will
not occur.
The problem might be that it takes some time for current_path to change after the form is submitted. Your code would work if you put sleep(x) before expect(current_path).
Instead, you should use methods that have so-called "waiting behaviour", such as has_current_path?, have_current_path, assert_current_path:
expect(page).to have_current_path(stamp_path(Stamp.first.id))
or
expect(page.has_current_path?(stamp_path(Stamp.first.id))).to eq true
For anyone else coming here, another possible solution is to increase your wait time. Either globally or per click
# Globally
Capybara.default_max_wait_time = 5
# Per Click
find("#my-button").click(wait: 5)
Configuration
Webpacker application using Rails 5.1 with Vue.js 2.5
I've been trying to diagnose this bug for 3 days now and I am at my wits end. I've started to add system tests using Capybara using standard selenium-webdriver.
Problems occur when I am trying to access authenticatable routes, for example, user settings:
RSpec.describe 'home page', type: :system do
before(:context) do
WebMock.disable_net_connect!(allow_localhost: true)
end
after do
Warden.test_reset!
end
context 'when logged in' do
context 'as an average user' do
before do
#profile = create(:profile)
#user = #profile.user
#user.update(confirmed_at: Time.now)
login_as(#user, scope: :user)
end
it 'redirects to Account Settings page after pressing Account Settings' do
visit '/'
find('.profile-menu').hover
find('.settings').click
expect(page).to have_content 'Account Settings'
end
end
end
end
this is not a link and is handled in my .vue file, which just redirects to the route I need location.href = "/users/edit";
When I am on the root page, I am signed in, current_user returns the user and all is great. But as soon as I press that link, at some point it gets reset as I get redirected to the standard Devise Login page.
If I directly go to that page visit '/users/edit, but current_user is set and its all fine.
This is my test devise setup:
require 'devise'
RSpec.configure do |config|
config.include Warden::Test::Helpers
end
Let me know if you need some extra information, I think I've provided all the crucial bits but could have missed something.
I seem to have fixed this issue. I've tried a couple things mentioned in this SO answer. As far as I can tell, what fixed it was fixing my configuration from:
RSpec.configure do |config|
config.include Warden::Test::Helpers
end
to
RSpec.configure do |config|
config.include Devise::Test::IntegrationHelpers, type: :system
end
This also allowed me to drop:
after(:each) do
Warden.test_reset!
end
in all my tests. I've run quite a few tests and they don't fail anymore, so I am for now assuming this has been indeed fixed.
Given the following simple spec:
require 'spec_helper'
feature 'Feeds', %q{
In order see the latest content of Buurtlink
As a user
I should be able to view a neighborhood or postal_code
} do
background do
#neighborhood = FactoryGirl.create(:neighborhood_with_posts)
end
scenario 'Visitor views a neighborhood' do
visit neighborhood_path(#neighborhood)
find('#header_title').should have_content 'Diemen-Zuid'
10.times do |index|
expect(page).to have_text "Title of new post #{index}"
end
end
end
This test randomly fails. No JS is used on the page, but Capybara seems to visit the neighborhood_path before FactoryGirl is done creating all necessary posts. When looking at the page using save_and_open_page I can see that sometimes not all posts have been created yet.
Simply adding sleep 1 above visit neighborhood_path fixes the problem, but that's not a solution.
I'm using RSpec, Capybara, Spork and DatabaseCleaner. I also monkey-patched ActiveRecord so that it uses a shared connection.
Try this instead of your background block:
let!(:neighborhood){FactoryGirl.create(:neighborhood_with_posts)}
and you can do
visit neighborhood_path(neighborhood)
This was in the ApplicationController:
def load_posts_after_current_time
session[:load_posts_after] = DateTime.now unless params[:page].present?
end
Therefore, errors occurred when fetching newly created records. So added created_at 1.hour.ago to the post factory.
factory :post do
user { FactoryGirl.create(:user) }
created_at 1.hour.ago
factory :neighborhood_post do
association :postable, factory: :_neighborhood_post
end
end
It works!
I have been playing with Rails for a couple of years now and have produced a couple of passable apps that are in production. I've always avoided doing any testing though and I have decided to rectify that. I'm trying to write some tests for an app that I wrote for work that is already up and running but undergoing constant revision. I'm concerned that any changes will break things so I want to get some tests up and running. I've read the RSpec book, watched a few screencasts but am struggling to get started (it strikes me as the sort of thing you only understand once you've actually done it).
I'm trying to write what should be a simple test of my ReportsController. The problem with my app is that pretty much the entire thing sits behind an authentication layer. Nothing works if you're not logged in so I have to simulate a login before I can even send forth a simple get request (although I guess I should write some tests to make sure that nothing works without a login - I'll get to that later).
I've set up a testing environment with RSpec, Capybara, FactoryGirl and Guard (wasn't sure which tools to use so used Railscasts' suggestions). The way I've gone about writing my test so far is to create a user in FactoryGirl like so;
FactoryGirl.define do
sequence(:email) {|n| "user#{n}#example.com"}
sequence(:login) {|n| "user#{n}"}
factory :user do
email {FactoryGirl.generate :email}
login {FactoryGirl.generate :login}
password "abc"
admin false
first_name "Bob"
last_name "Bobson"
end
end
and then write my test like so;
require 'spec_helper'
describe ReportsController do
describe "GET 'index'" do
it "should be successful" do
user = Factory(:user)
visit login_path
fill_in "login", :with => user.login
fill_in "password", :with => user.password
click_button "Log in"
get 'index'
response.should be_success
end
end
end
This fails like so;
1) ReportsController GET 'index' should be successful
Failure/Error: response.should be_success
expected success? to return true, got false
# ./spec/controllers/reports_controller_spec.rb:13:in `block (3 levels) in <top (required)>'
Interestingly if I change my test to response.should be_redirect, the test passes which suggests to me that everything is working up until that point but the login is not being recognised.
So my question is what do I have to do to make this login work. Do I need to create a user in the database that matches the FactoryGirl credentials? If so, what is the point of FactoryGirl here (and should I even be using it)? How do I go about creating this fake user in the testing environment? My authentication system is a very simple self-made one (based on Railscasts episode 250). This logging in behaviour will presumably have to replicated for almost all of my tests so how do I go about doing it once in my code and having it apply everywhere?
I realise this is a big question so I thank you for having a look.
The answer depends on your authentication implementation. Normally, when a user logs in, you'll set a session variable to remember that user, something like session[:user_id]. Your controllers will check for a login in a before_filter and redirect if no such session variable exists. I assume you're already doing something like this.
To get this working in your tests, you have to manually insert the user information into the session. Here's part of what we use at work:
# spec/support/spec_test_helper.rb
module SpecTestHelper
def login_admin
login(:admin)
end
def login(user)
user = User.where(:login => user.to_s).first if user.is_a?(Symbol)
request.session[:user] = user.id
end
def current_user
User.find(request.session[:user])
end
end
# spec/spec_helper.rb
RSpec.configure do |config|
config.include SpecTestHelper, :type => :controller
end
Now in any of our controller examples, we can call login(some_user) to simulate logging in as that user.
I should also mention that it looks like you're doing integration testing in this controller test. As a rule, your controller tests should only be simulating requests to individual controller actions, like:
it 'should be successful' do
get :index
response.should be_success
end
This specifically tests a single controller action, which is what you want in a set of controller tests. Then you can use Capybara/Cucumber for end-to-end integration testing of forms, views, and controllers.
Add helper file in spec/support/controller_helpers.rb and copy content below
module ControllerHelpers
def sign_in(user)
if user.nil?
allow(request.env['warden']).to receive(:authenticate!).and_throw(:warden, {:scope => :user})
allow(controller).to receive(:current_user).and_return(nil)
else
allow(request.env['warden']).to receive(:authenticate!).and_return(user)
allow(controller).to receive(:current_user).and_return(user)
end
end
end
Now add following lines in spec/rails_helper.rb or spec/spec_helper.rb
file
require 'support/controller_helpers'
RSpec.configure do |config|
config.include Devise::TestHelpers, :type => :controller
config.include ControllerHelpers, :type => :controller
end
Now in your controller spec file.
describe "GET #index" do
before :each do
#user=create(:user)
sign_in #user
end
...
end
Devise Official Link
The easiest way to login with a user on feature tests is to use the Warden's helper #login_as
login_as some_user
As I couldn't make #Brandan's answer work, but based on it and on this post, I've came to this solution:
# spec/support/rails_helper.rb
Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f } # Add this at top of file
...
include ControllerMacros # Add at bottom of file
And
# spec/support/controller_macros.rb
module ControllerMacros
def login_as_admin
admin = FactoryGirl.create(:user_admin)
login_as(admin)
end
def login_as(user)
request.session[:user_id] = user.id
end
end
Then on your tests you can use:
it "works" do
login_as(FactoryGirl.create(:user))
expect(request.session[:user_id]).not_to be_nil
end
For those who don't use Devise:
spec/rails_helper.rb:
require_relative "support/login_helpers"
RSpec.configure do |config|
config.include LoginHelpers
end
spec/support/login_helpers.rb:
module LoginHelpers
def login_as(user)
post "/session", params: { session: { email: user.email, password: "password" } }
end
end
and in the specs:
login_as(user)
I want to test my multidomain RoR3 App.
Here's my test_helper.rb
ENV["RAILS_ENV"] = "test"
require File.expand_path('../../config/environment', __FILE__)
require 'rails/test_help'
require 'capybara/rails'
require 'blueprints'
class ActiveSupport::TestCase
end
class ActionDispatch::IntegrationTest
include Capybara
def host
"http://#{subdomain}.lvh.me:3000"
end
def subdomain
#subdomain ? #subdomain : 'demostore'
end
def visit(url)
super("http://#{subdomain}.lvh.me:3000#{url}")
end
end
And my integration test:
require 'test_helper'
class ProductsTest < ActionDispatch::IntegrationTest
def setup
#subdomain = 'demostore'
# creating stuff
end
def teardown
# deleting stuff
end
test "user views product list" do
visit('/')
assert page.has_css?('ul.product-listing')
assert page.has_xpath?("//ul[#class='product-listing']/li", :count => 12)
end
test "user views product page" do
product = Product.first
visit('/')
find(:xpath, "//ul[#class='product-listing']/li/a[1]").click
save_and_open_page
end
end
And I'm sure the link exists. There is problem with clicking and filling stuff.
click_link('Existent link title')
doesn't work too.
I think the default Capybara's driver Rack::Test could have problems with this multidomain stuff?
In your setup, call this rack::test function, which will change your host's value. Well, it changes the host that gets returned about the fake web request.
host! "#{store.subdomain}.example.com"
The problem was that i'm using multidomain stuff so I had to use lvh.me which resolves localhost. You can do the same think by setting in Your /etc/hosts
127.0.0.1 subdomain.yourapp.local
and then use this domain.
I've overwritten Capybara's visit method with sth like that:
def visit(link)
super("mysubdomain.lvh.me:3000#{link}")
end
but problem persisted because when Capybara clicked for example link, the visit method was not used and my host was not requested. Which was? I don't know - probably the default one.
So solution is to set host and port in Capybara settings:
class ActionDispatch::IntegrationTest
include Capybara
Capybara.default_host = "subdomain.yourapp.local"
Capybara.server_port = 3000
# ... rest of stuff here
end
Apparently it's a problem with rack-test.
But there is a fork of it by hassox that just solved it for me.
It's just a couple of commits that really matter, in case you want to check what the changes are.
This is how my Gemfile looks:
group :test, :cucumber do
gem 'rack-test', :git => "https://github.com/hassox/rack-test.git"
gem 'capybara', '= 0.4.1.2'
gem 'capybara-envjs', '= 0.4.0'
gem 'cucumber-rails', '>= 0.3.2'
gem 'pickle', '>= 0.3.4'
end
And then I just make sure to
visit('http://my_subdomain.example.com')
in my steps. Now I'm trying to understand what would make url helpers work with subdomains.
Here's a quick setup that may help you out...
rails 3.2+ testing custom subdomains using cucumber capybara with pow setup:
https://gist.github.com/4465773
I'd like to share what I found to be a great solution for this problem. It involves creating a helper method to prepend URLs with the desired subdomain, doesn't overwrite any Capybara methods, and works with the Rack::Test and capybara-webkit drivers. In fact, it will even work in specs which do not even use Capybara. (source: http://minimul.com/capybara-and-subdomains.html)
The Spec Helper Method
# spec/support/misc.helpers.rb
def hosted_domain(options = {})
path = options[:path] || "/" # use root path by default
subdomain = options[:subdomain] || 'www'
if example.metadata[:js]
port = Capybara.current_session.driver.server_port
url = "http://#{ subdomain }.lvh.me:#{ port }#{ path }"
else
url = "http://#{ subdomain }.example.com#{ path }"
end
end
And to illustrate it's use, here are two examples:
Used in a Feature Spec (with Capybara)
require 'spec_helper'
describe "Accounts" do
# Creates an account using a factory which sequences
# account subdomain names
# Additionally creates users associated with the account
# using FactoryGirl's after callbacks (see FactoryGir docs)
let (:account) { FactoryGirl.create(:account_with_users) })
it "allows users to sign in" do
visit hosted_domain(path: new_sessions_path, subdomain: account.subdomain)
user = account.users.first
fill_in "email", with: user.email
fill_in "password", with: user.password
click_button "commit"
# ... the rest of your specs
end
end
Used in a Request Spec (without Capybara)
#spec/requests/account_management_spec.rb
require "spec_helper"
describe "Account management" do
# creates an account using a factory which sequences
# account subdomain names
let (:account) { FactoryGirl.create(:account) })
it "shows the login page" do
get hosted_domain(path: "/login", subdomain: account.subdomain)
expect(response).to render_template("sessions/new")
end
end
A simple and clean solution is to override the urls you provide to Capybara's visit method. It works well with *.lvh.me domains, which will redirect you to localhost:
describe "Something" do
def with_subdomain(link)
"http://subdomain.lvh.me:3000#{link}"
end
it "should do something" do
visit with_subdomain(some_path)
end
end
Or you could do the same by redefining app_host before a spec:
Capybara.app_host = 'http://sudbomain.lvh.me:3000'
..
visit(some_path)