简体   繁体   中英

Capybara/Rspec - Session Lost on second scenario with Ajax request

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.

401-Ajax 调用

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM