I'm beating my head on trying to solve this issue. I have a feature spec that I'm writing where I have multiple scenarios written and I want them all to run within the same user session. The tests run fine until it gets to the last scenario that makes a separate AJAX requests, and then yields 401 Unauthorized
on that request. I've researched for hours on the internet with no luck. Hopefully somebody can point out what's going on here.
I'm using Devise for authentication, Selenium-webdriver & Rails 6.
Here are my relative files:
rails_helper.rb:
# This file is copied to spec/ when you run 'rails generate rspec:install'
ENV["RAILS_ENV"] ||= 'test'
require 'pry'
require 'spec_helper'
require File.expand_path("../../config/environment", __FILE__)
require 'rspec/rails'
require 'shoulda/matchers'
require 'pundit/matchers'
require 'brakeman'
Delayed::Worker.delay_jobs = false
# Add additional requires below this line. Rails is not loaded until this point!
# Requires supporting ruby files with custom matchers and macros, etc, in
# spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are
# run as spec files by default. This means that files in spec/support that end
# in _spec.rb will both be required and run as specs, causing the specs to be
# run twice. It is recommended that you do not name files matching this glob to
# end with _spec.rb. You can configure this pattern with the --pattern
# option on the command line or in ~/.rspec, .rspec or `.rspec-local`.
#
# The following line is provided for convenience purposes. It has the downside
# of increasing the boot-up time by auto-requiring all files in the support
# directory. Alternatively, in the individual `*_spec.rb` files, manually
# require only the support files necessary.
#
Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }
# RSpec Examples run within a transaction
# Preload our constants before any transaction begins
# Constants.constants
RSpec.configure do |config|
config.include Devise::Test::ControllerHelpers, type: :controller
config.expect_with :rspec do |expectations|
expectations.syntax = [:should, :expect]
end
# Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
# config.fixture_path = "#{::Rails.root}/spec/fixtures"
# If you're not using ActiveRecord, or you'd prefer not to run each of your
# examples within a transaction, remove the following line or assign false
# instead of true.
# config.use_transactional_fixtures = false
# RSpec Rails can automatically mix in different behaviours to your tests
# based on their file location, for example enabling you to call `get` and
# `post` in specs under `spec/controllers`.
#
# You can disable this behaviour by removing the line below, and instead
# explicitly tag your specs with their type, e.g.:
#
# RSpec.describe UsersController, :type => :controller do
# # ...
# end
#
# The different available types are documented in the features, such as in
# https://relishapp.com/rspec/rspec-rails/docs
config.infer_spec_type_from_file_location!
end
spec/support/capybara.rb:
# require 'capybara/rails'
require 'capybara/rspec'
Capybara.server = :puma
Capybara.server_host = 'intranet.lvh.me'
Capybara.default_driver = :selenium_chrome
Capybara.javascript_driver = :selenium_chrome
Capybara.default_max_wait_time = 10
def sign_in_helpdesk
user = FactoryBot.create(:user, :helpdesk_technician)
user = user.becomes(HolmanPermissions::User)
user.user_grants << HolmanPermissions::UserGrant.new({
permission: AuthDomain::Permission.find_by(name: 'IT Technician'),
tenant: AuthDomain::Tenant.find_by(name: 'All Locations')
})
login_as(user.becomes(::User), scope: :user, run_callbacks: false)
user
end
RSpec.configure do |config|
config.after(:each, type: :feature) do
Capybara.current_session.instance_variable_set(:@touched, false)
end
config.after(:all, type: :feature) do
Capybara.current_session.instance_variable_set(:@touched, true)
end
end
spec/support/database_cleaner.rb:
Constants.constants
RSpec.configure do |config|
config.use_transactional_fixtures = false
config.include Warden::Test::Helpers
Warden.test_mode!
config.before(:suite) do
if config.use_transactional_fixtures?
raise(<<-MSG)
Delete line `config.use_transactional_fixtures = true` from rails_helper.rb
(or set it to false) to prevent uncommitted transactions being used in
JavaScript-dependent specs.
During testing, the Ruby app server that the JavaScript browser driver
connects to uses a different database connection to the database connection
used by the spec.
This Ruby app server database connection would not be able to see data that
has been setup by the spec's database connection inside an uncommitted
transaction.
Disabling the use_transactional_fixtures setting helps avoid uncommitted
transactions in JavaScript-dependent specs, meaning that the Ruby app server
database connection can see any data set up by the specs.
MSG
end
end
config.before(:suite) do
DatabaseCleaner.clean_with(:truncation)
ReferenceCreator.new
Constants.reload
end
config.before(:each) do
DatabaseCleaner.strategy = :transaction
end
config.before(:each, type: :feature) do
# :rack_test driver's Rack app under test shares database connection
# with the specs, so we can use transaction strategy for speed.
driver_shares_db_connection_with_specs = Capybara.current_driver == :rack_test
if driver_shares_db_connection_with_specs
DatabaseCleaner.strategy = :transaction
else
# Non-:rack_test driver is probably a driver for a JavaScript browser
# with a Rack app under test that does *not* share a database
# connection with the specs, so we must use truncation strategy.
DatabaseCleaner.strategy = :truncation
end
end
config.before(:each) do
DatabaseCleaner.start
end
config.after(:each) do
DatabaseCleaner.clean
Warden.test_reset!
end
end
config/initializers/session_store.rb:
Core::Application.config.session_store :cookie_store, key: '_Core_session', domain: :all
config/environments/test.rb:
Rails.application.configure do
$VERBOSE = nil
# Settings specified here will take precedence over those in config/application.rb.
# RAILS 5.2 CONFIG
# Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"]
# or in config/master.key. This key is used to decrypt credentials (and other encrypted files).
config.require_master_key = true
# RAILS 5.1 CONFIG
# Attempt to read encrypted secrets from `config/secrets.yml.enc`.
# Requires an encryption key in `ENV["RAILS_MASTER_KEY"]` or
# `config/secrets.yml.key`.
config.read_encrypted_secrets = true
# The test environment is used exclusively to run your application's
# test suite. You never need to work with it otherwise. Remember that
# your test database is "scratch space" for the test suite and is wiped
# and recreated between test runs. Don't rely on the data there!
config.cache_classes = true
# Do not eager load code on boot. This avoids loading your whole application
# just for the purpose of running a single test. If you are using a tool that
# preloads Rails for running tests, you may have to set it to true.
config.eager_load = false
# Configure public file server for tests with Cache-Control for performance.
config.public_file_server.enabled = true
config.public_file_server.headers = {
'Cache-Control' => "public, max-age=#{1.hour.to_i}"
}
# Show full error reports and disable caching.
config.consider_all_requests_local = true
config.action_controller.perform_caching = false
config.cache_store = :null_store
# Raise exceptions instead of rendering exception templates.
config.action_dispatch.show_exceptions = true
# Disable request forgery protection in test environment.
config.action_controller.allow_forgery_protection = false
# Store uploaded files on the local file system in a temporary directory
# config.active_storage.service = :test
config.action_mailer.perform_caching = false
# Tell Action Mailer not to deliver emails to the real world.
# The :test delivery method accumulates sent emails in the
# ActionMailer::Base.deliveries array.
config.action_mailer.delivery_method = :test
config.action_mailer.default_url_options = { host: 'intranet.lvh.me:3000' }
# Print deprecation notices to the stderr.
config.active_support.deprecation = :log
config.assets.debug = true
# Raises error for missing translations
# config.action_view.raise_on_missing_translations = true
end
spec/features/helpdesk_spec.rb:
require 'rails_helper'
RSpec.feature "Helpdesk Ticketing System", js: true, type: :feature do
before(:all) do
# Capybara.current_session.instance_variable_set(:@touched, false)
@user = sign_in_helpdesk
@ticket = FactoryBot.create(:ticket, ticket_type_id: Constants.helpdesk.ticket_types.it_id)
@second_helpdesk_tech = FactoryBot.create(:user, first_name: 'Simon', last_name: 'Helpdesk', email: 'simon@helpdesk.com')
@second_helpdesk_tech = @second_helpdesk_tech.becomes(HolmanPermissions::User)
@second_helpdesk_tech.user_grants << HolmanPermissions::UserGrant.new({
permission: AuthDomain::Permission.find_by(name: 'IT Technician'),
tenant: AuthDomain::Tenant.find_by(name: 'All Locations')})
visit tickets_path
end
context 'viewing helpdesk contents' do
scenario 'should have visible helpdesk data' do
expect(page).to have_content('Open Tickets')
expect(page).to have_selector("div.ticket", count: 1)
expect(page).to have_content(@ticket.summary)
end
end
context 'assigning a ticket' do
before do
first('.popover_link').click
popover = first('div.popover')
expect(popover).to have_content('Assign')
first('.link_to_assign').click
sleep 2
end
after do
page.evaluate_script('$("#assign-modal").modal("hide")')
end
scenario 'should show the assign modal' do
expect(page).to have_selector('#assign-modal', visible: true)
end
scenario 'should assign the technician selected' do # This is the scenario that makes an ajax call
assign_modal = find('#assign-modal')
expect(assign_modal).to have_selector('#assign-technician')
within '#assign-modal' do
select(@second_helpdesk_tech.name, from: 'assign-technician')
click_on 'Assign' # ajax call made upon click
sleep 2
end
expect(first('div.ticket .status')).to have_text(@second_helpdesk_tech.name)
end
end
end
All asserts pass until the last one. Here's a screenshot at that point with the console tab open.
After following @ThomasWalpole advice, I removed the code that sets instance_variable_set
in my capybara.rb
and changed before(:all)
to before(:each)
to load a fresh session each time. The database cleaning was in fact the culprit and it was removing my reference data that I needed to set up my user.
In short, it was my code problem not an issue with overall capybara setup.