Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Capybara & Selenium system test support #343

Open
kleinjm opened this issue Apr 14, 2023 · 0 comments
Open

Capybara & Selenium system test support #343

kleinjm opened this issue Apr 14, 2023 · 0 comments

Comments

@kleinjm
Copy link

kleinjm commented Apr 14, 2023

Answering the call here: https://github.com/Sorcery/sorcery/wiki/Testing-Rails#feature-tests-w-selenium-or-webkit

In system tests, we've been stubbing our SSO calls to WorkOS and then visiting our sso callback action. That action ends up calling auto_login. This approach works well but ends up in an extra round trip to the server on every spec. With that in mind, I came up with a way to authenticate in the test env when the first "real" request to a page happens.

The approach is,

  1. Setting a "global" login user in the test env when calling login
  2. Setting the same session keys, as Sorcery sets, in middleware (and clearing the login user variable)

This approach is inspired by Warden's on_next_request hook and rack_session_access middleware.

I tried many many ways to set session before the request, use query params, set headers, page.driver.browser.execute_cdp with "Network.setCookie", setting localstorage, etc. No luck on any of those approaches because capybara fires up the browser at data:, and that url prevents setting anything (or persisting anything) on the actual domain the test is hitting.

The singleton approach is never ideal but in the test environment, it works well. It's a simple and easily manageable way to pass around the "global user" that should be logged in and easy to clear out before the next test. This saves us about ~0.3 seconds per test run by avoiding the server roundtrip.

In spec/support/auth_support.rb

# Singleton class to store the login user which is used in TestUserAuthMiddleware
# to set the current user in the request.
# In tests, this saves a full round trip to the server to authenticate the user.
class TestLoginUser
  include Singleton

  attr_accessor(:login_user)

  def clear_user
    @login_user = nil
  end
end

module AuthSupport
  def login(user = create(:user))
    TestLoginUser.instance.login_user = user
  end
end

RSpec.configure do |config|
  config.include(AuthSupport, type: -> (type) { %i[system request].include?(type) })

  # Clear the login user before each test
  config.before(:each, type: -> (type) { %i[system request].include?(type) }) do
    TestLoginUser.instance.clear_user
  end
end

In lib/middleware/test_user_auth_middleware.rb

# Intercept the request and set the user_id in the session.
# In tests, this saves a full round trip to the server to authenticate the user.
class TestUserAuthMiddleware
  attr_reader(:app)

  def initialize(app)
    @app = app
  end

  def call(env)
    login_user = TestLoginUser.instance.login_user
    if login_user.present?
      env["rack.session"][:user_id] = login_user.id

      TestLoginUser.instance.clear_user
    end

    app.call(env)
  end
end

In config/environments/test.rb

require "middleware/test_user_auth_middleware"

Rails.application.configure do
  config.middleware.use(TestUserAuthMiddleware)
end

Let me know what you think!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant