Capybara/Rspec - 在 Ajax 请求的第二种情况下会话丢失
Capybara/Rspec - Session Lost on second scenario with Ajax request
我正在努力解决这个问题。我有一个正在编写的功能规范,其中我编写了多个场景,我希望它们在同一个用户会话中全部 运行。测试 运行 很好,直到它到达最后一个发出单独 AJAX 请求的场景,然后在该请求上产生 401 Unauthorized
。我已经在互联网上研究了几个小时,但没有运气。希望有人能指出这里发生了什么。
我正在使用 Devise 进行身份验证,Selenium-webdriver & Rails 6.
这是我的相关文件:
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
所有断言都通过,直到最后一个。这是打开控制台选项卡时的屏幕截图。
在遵循@ThomasWalpole 的建议后,我删除了在 capybara.rb
中设置 instance_variable_set
的代码,并将 before(:all)
更改为 before(:each)
以每次加载新会话。数据库清理实际上是罪魁祸首,它删除了我设置用户所需的参考数据。
简而言之,这是我的代码问题,而不是整个水豚设置的问题。
我正在努力解决这个问题。我有一个正在编写的功能规范,其中我编写了多个场景,我希望它们在同一个用户会话中全部 运行。测试 运行 很好,直到它到达最后一个发出单独 AJAX 请求的场景,然后在该请求上产生 401 Unauthorized
。我已经在互联网上研究了几个小时,但没有运气。希望有人能指出这里发生了什么。
我正在使用 Devise 进行身份验证,Selenium-webdriver & Rails 6.
这是我的相关文件:
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
所有断言都通过,直到最后一个。这是打开控制台选项卡时的屏幕截图。
在遵循@ThomasWalpole 的建议后,我删除了在 capybara.rb
中设置 instance_variable_set
的代码,并将 before(:all)
更改为 before(:each)
以每次加载新会话。数据库清理实际上是罪魁祸首,它删除了我设置用户所需的参考数据。
简而言之,这是我的代码问题,而不是整个水豚设置的问题。