Skip to content

Instantly share code, notes, and snippets.

@andreypronin
Last active July 6, 2016 19:42
Show Gist options
  • Save andreypronin/5058959 to your computer and use it in GitHub Desktop.
Save andreypronin/5058959 to your computer and use it in GitHub Desktop.
A little spec library I use to test session/persistent cookies behavior in Rails apps with Capybara. Plus, example on how I use it. Implemented for Rack::Test and Poltergeist (PhantomJS) drivers only, as these are my default test preferences.
require 'spec_helper'
feature "Login" do
scenario "remembered user" do
password = "12345678"
user = create(:user, password: password)
access_protected_page_should_ask_to_login
current_path.should eq login_path
login_should_succeed user.email, password, true
imitate_new_browser_session # Note the use of function from my cookie-testing mini-library
access_protected_page_should_not_ask_to_login
current_path.should eq protected_page_path
end
end
class SessionCookies
def self.for(session)
klass = drivers[Capybara.current_driver]
if klass
return klass.new(session)
else
# :selenium, :webkit
raise "Session cookies handling is not supported for driver #{Capybara.current_driver.to_s}"
end
end
def self.register(driver,klass)
@drivers ||= {}
@drivers[driver] = klass
end
def self.drivers
@drivers ||= {}
end
def self.next_session_number
@session_number ||= 0
@session_number += 1
end
end
def copy_cookies(options={})
session_to = options[:to] || Capybara.current_session
session_from = options[:from] || Capybara.current_session
partial = options[:only]
if session_to != session_from
SessionCookies.for(session_to).copy_from SessionCookies.for(session_from), partial
end
end
def clear_cookies(options={})
session = options[:in] || Capybara.current_session
partial = options[:only]
SessionCookies.for(session).clear partial
end
def cookie_value(name,options={})
session = options[:in] || Capybara.current_session
SessionCookies.for(session).cookie_value(name)
end
def clear_cookie(name,options={})
session = options[:in] || Capybara.current_session
SessionCookies.for(session).clear_cookie(name)
end
def all_cookies(options={})
session = options[:in] || Capybara.current_session
SessionCookies.for(session).all_cookies
end
def imitate_new_browser_session(options={})
options[:only] = :session
clear_cookies options
end
def start_new_session(options={})
options[:from] ||= Capybara.current_session
options[:name] ||= "secondary-#{SessionCookies.next_session_number}".to_sym
new_session = nil
Capybara.using_session options[:name] do
new_session = Capybara.current_session
copy_cookies to: new_session, from: options[:from], only: :persistent
yield if block_given?
end
new_session
end
# Common code
class SessionCookies
def initialize(session=Capybara.current_session)
@session = session
end
def copy_from(sc,partial=nil)
if (partial == nil) && respond_to?(:copy_all_from,true)
self.copy_all_from sc
else
ck = sc.cookie_objects
if partial==:persistent
ck.reject! { |name,cookie| !cookie_is_persistent(cookie) }
elsif partial==:session
ck.reject! { |name,cookie| !cookie_is_session(cookie) }
end
ck.each do |name,cookie|
add_cookie_object cookie
end
end
end
def clear(partial=nil)
if (partial == nil) && respond_to?(:clear_all,true)
self.clear_all
else
ck = cookie_objects
if partial==:persistent
ck.reject! { |name,cookie| !cookie_is_persistent(cookie) }
elsif partial==:session
ck.reject! { |name,cookie| !cookie_is_session(cookie) }
end
ck.each do |name,cookie|
clear_cookie name
end
end
end
def cookie_value(name)
cookie = cookie_objects[name]
cookie && cookie.value
end
def all_cookies
ck = {}
cookie_objects.each do |name,cookie|
ck.merge!( name => cookie_value(name) )
end
ck
end
def cookie_objects
raise "SessionCookies: cookie_objects should be implemented in a driver-specific class"
end
protected
def cookie_is_session(cookie)
cookie.expires == nil
rescue
true
end
def cookie_is_persistent(cookie)
!cookie_is_session(cookie)
end
end
class PoltergeistSessionCookies < SessionCookies
def initialize(session=Capybara.current_session)
super(session)
end
def clear_cookie(name)
@session.driver.remove_cookie(name)
end
protected
def cookie_objects
@session.driver.cookies
end
def add_cookie_object(cookie)
@session.driver.set_cookie( cookie.name, cookie.value, cookie_options_for(cookie) )
end
def cookie_options_for(cookie)
options = {}
defs = {
:name=>:name,
:value=>:value,
:domain=>:domain,
:path=>:path,
:secure=>:secure?,
:httponly=>:httponly?,
:expires=>:expires
}
defs.each do |name,method|
options[name] = cookie.send(method)
end
options
end
end
SessionCookies.register :poltergeist, PoltergeistSessionCookies
module Rack
module Test
class CookieJar
def cookie_objects
hash_for nil
end
end
end
end
class RackTestSessionCookies < SessionCookies
def initialize(session=Capybara.current_session)
@mock_session = session.driver.browser.current_session.instance_variable_get(:@rack_mock_session)
super(session)
end
def copy_all_from(sc)
self.jar = sc.jar
end
def clear_all
@mock_session.clear_cookies
end
def clear_cookie(name)
jar.delete(name)
end
def cookie_value(name)
jar.to_hash[name]
end
protected
def add_cookie_object(cookie)
jar << cookie
end
def jar
@mock_session.cookie_jar
end
def jar=(value)
@mock_session.cookie_jar = value
end
def cookie_objects
jar.cookie_objects
end
end
SessionCookies.register :rack_test, RackTestSessionCookies
In your spec files you can call:
* copy_cookies [:to => session2,] [:from => session1,] [:only => cookies_type] # copy cookies of type :persistent|:session|:all from session1 to session2
* clear_cookies [:in => session,] [:only => cookies_type] # clear all cookies of a given type in a given session
* cookie_value "name", [:in => session] # get cookie value
* clear_cookie "name", [:in => session] # clear specific cookie
* all_cookies [:in => session] # get all cookies
* imitate_new_browser_session [:only => cookies_type] # clear cookies of specified type in this session, by default only session cookies are cleared, and persistent are preserved, as in real life
* start_new_session [:name=>"session_name",] [:from=>session] [ { yield } ] # create new Capybara session (with a given name), copy persistent cookies there from the specified session (current by default), and execute a block in it if block given
In all cases if session is omitted, the current session is taken, if cookies_type is omitted, :all is assumed (unless stated otherwise).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment