Last active
May 28, 2020 20:38
-
-
Save mcrowe/11071655 to your computer and use it in GitHub Desktop.
Saving and restoring Capybara sessions
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module CapybaraSessionFactory | |
class SessionAlreadyDefined < StandardError; end | |
class SessionNotDefined < StandardError; end | |
# Parse a capybara session mode, which has the form | |
# "[driver]:[session_name]:[application_id]" | |
# | |
class CapybaraModeParser | |
attr_reader :driver, :session_name, :application_id | |
def initialize(mode) | |
@mode = mode | |
parse | |
end | |
private | |
def parse | |
@driver, @session_name, @application_id = @mode.split(':') | |
end | |
end | |
class Session | |
include Capybara::DSL | |
def initialize(name, setup_proc) | |
@name, @setup_proc = name, setup_proc | |
@is_setup = false | |
end | |
def setup? | |
@is_setup | |
end | |
def run_setup | |
return if setup? | |
instance_eval(&@setup_proc) | |
@is_setup = true | |
end | |
def capybara_session_name | |
"capybara_session_factory_#{@name}" | |
end | |
end | |
class << self | |
# Defines a new session and its setup code. | |
# | |
def define_session(name, &setup_proc) | |
raise SessionAlreadyDefined if sessions.key?(name) | |
sessions[name] = Session.new(name, setup_proc) | |
end | |
# Loads a session by name. | |
# The setup code is only run on the first load. | |
# | |
def load_session(name) | |
raise SessionNotDefined unless sessions.key?(name) | |
session = sessions[name] | |
Capybara.using_session(name) do | |
session.run_setup unless session.setup? | |
yield | |
end | |
end | |
private | |
def sessions | |
@sessions ||= {} | |
end | |
# Returns true if the capybara mode string corresponds to a | |
# CapybaraSessionFactory session. | |
# | |
def managed_session_mode?(mode) | |
parser = CapybaraModeParser.new(mode) | |
sessions.key?(parser.session_name) | |
end | |
end | |
end | |
class << Capybara | |
# Force capybara not to reset sessions that are managed by CapybaraSessionFactory. | |
# This let's sessions persist between tests. | |
# | |
# TODO: Capybara doesn't allow management of its "session_pool", so we need to monkey-path. | |
# Capybara should be updated with a session manager strategy so this is not required. | |
# | |
def reset_sessions! | |
session_pool.each do |mode, session| | |
session.reset! unless CapybaraSessionFactory.managed_session_mode?(mode) | |
end | |
end | |
alias_method :reset!, :reset_sessions! | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# spec/features/dashboard_spec.rb | |
require 'spec_helper' | |
describe 'the dashboard' do | |
it 'has a welcome message' do | |
CapybaraSessionFactory.load_session('signed in') do | |
click_link 'Dashboard' | |
expect(page).to have_content 'Welcome to your dashboard' | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# spec/sessions/signed_in.rb | |
CapybaraSessionFactory.define_session 'signed in' do | |
visit '/signup' | |
fill_in 'First name', with: 'Bob' | |
fill_in 'Email', with: '[email protected]' | |
fill_in 'Password', with: 'password' | |
click_button 'Sign Up' | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This is a proof-of-concept that allows you to store and restore sessions in Capybara. Under certain conditions, this could greatly increase the speed of an integration suite without coupling tests.
By default, Capybara resets all session variables after every test. This is good, because it decouples your tests. However, integration suites often end up with a ridiculous amount of duplicated setup steps. For example, its often common to have hundreds of tests that fill out the same login or registration forms with the same credentials just to get to their starting point. Sure, ideally you'd have stubbed out that setup, but in the real world that doesn't always happen. This gist provides a way to share sessions between tests in a safe way, so that you can run setup steps once per suite, and spend cpu cycles where they counts.
This was thrown together pretty quickly. It proved effective on a sample application, but there are probably some tweaks required before its ready for major use. Notably, I will probably need to add the ability to define setup code that must happen every time a session is loaded and not just the first. This would allow you to create any records that must exist for the session.