5

I'm trying to write a functional test that uses Selenium to test a Django view. When the user comes to a page ("page2"), the view that renders that page expects to find a session variable "uid" (user ID). I've read a half dozen articles on how this is supposed to be done but none of them have worked for me. The code below shows how the Django documentation says it should be done but it doesn't work for me either. When I run the test, the view never completes executing and I get a "server error occurred" message. Could someone please tell me what I'm doing wrong? Thank you.

views.py:

from django.shortcuts import render_to_response

def page2(request):
    uid = request.session['uid']
    return render_to_response('session_tests/page2.html', {'uid': uid})

test.py:

from django.test import LiveServerTestCase
from selenium import webdriver
from django.test.client import Client

class SessionTest(LiveServerTestCase):

    def setUp(self):
        self.browser = webdriver.Firefox()
        self.browser.implicitly_wait(3)
        self.client = Client()
        self.session = self.client.session
        self.session['uid'] = 1

    def tearDown(self):
        self.browser.implicitly_wait(3)
        self.browser.quit()

    def test_session(self):
        self.browser.get(self.live_server_url + '/session_tests/page2/')
        body = self.browser.find_element_by_tag_name('body')
        self.assertIn('Page 2', body.text)
Jim
  • 13,430
  • 26
  • 104
  • 155
  • I can't see where your set you uid into the session, it should be something like request.session['uid'] = uid. The way you recover uid from session is OK, so if you are setting the uid on session is correct then it's not an issue of session variable. – PepperoniPizza Jan 14 '13 at 17:26
  • (I think) I'm adding the uid to my session state with the last line in the setUp function above: self.session['uid'] = 1. – Jim Jan 14 '13 at 18:27
  • Well there is an easy way to test it. In your view try getting the uid from session like this: uid = request.session.get('uid', None), then print or logg uid variable and see if uid is None, which is the default in case key 'uid' is not found on session. – PepperoniPizza Jan 14 '13 at 20:11
  • After considerable research online, I've found how to do this. It does, however, require you to use Django's TestCase class rather than Selenium's webdriver class. I suspect there's a disconnect between the webdriver's browser object and the session state Django allows you to create. If this is really the case, it would negatively impact my testing as it would mean that I can't use Selenium to test any page which assumes some initial session state. Thus, I'm still hoping for some solution. – Jim Jan 14 '13 at 23:59
  • @Robert in similar situations (non-Django) I've set up special URLs just for the test framework to poke stuff into the session. Unpleasant, but does work. Alternatively you may be able to figure out the session ID via the selenium driver (once it's opened the first page), and then look up (and change) that session in your test code. – James Aylett Jan 19 '13 at 14:45
  • (For instance it looks like you can grab cookies via the [Selenium python client](http://selenium.googlecode.com/svn/trunk/docs/api/py/webdriver_remote/selenium.webdriver.remote.webdriver.html).) – James Aylett Jan 19 '13 at 15:00

3 Answers3

3

Here's how to solve this problem. James Aylett hinted at the solution when he mentioned the session ID above. jscn showed how to set up a session but he didn't mention the importance of the session key to a solution and he also didn't discuss how to link the session state to Selenium's browser object.

First, you have to understand that when you create a session key/value pair (e.g. 'uid'=1), Django's middleware will create a session key/data/expiration date record in your backend of choice (database, file, etc.). The response object will then send that session key in a cookie back to the client's browser. When the browser sends a subsequent request, it will send a cookie back that contains that key which is then used by the middleware to lookup the user's session items.

Thus, the solution required 1.) finding a way to obtain the session key that is generated when you create a session item and then; 2.) finding a way to pass that key back in a cookie via Selenium's Firefox webdriver browser object. Here's the code that does that:

selenium_test.py:
-----------------

from django.conf import settings
from django.test import LiveServerTestCase
from selenium import webdriver
from django.test.client import Client
import pdb

def create_session_store():
    """ Creates a session storage object. """

    from django.utils.importlib import import_module
    engine = import_module(settings.SESSION_ENGINE)
    # Implement a database session store object that will contain the session key.
    store = engine.SessionStore()
    store.save()
    return store

class SeleniumTestCase(LiveServerTestCase):

    def setUp(self):
        self.browser = webdriver.Firefox()
        self.browser.implicitly_wait(3)
        self.client = Client()

    def tearDown(self):
        self.browser.implicitly_wait(3)
        self.browser.quit()

    def test_welcome_page(self):
        #pdb.set_trace()
        # Create a session storage object.
        session_store = create_session_store()
        # In pdb, you can do 'session_store.session_key' to view the session key just created.

        # Create a session object from the session store object.
        session_items = session_store

        # Add a session key/value pair.
        session_items['uid'] = 1
        session_items.save()

        # Go to the correct domain.
        self.browser.get(self.live_server_url)

        # Add the session key to the cookie that will be sent back to the server.
        self.browser.add_cookie({'name': settings.SESSION_COOKIE_NAME, 'value': session_store.session_key})
        # In pdb, do 'self.browser.get_cookies() to verify that it's there.'

        # The client sends a request to the view that's expecting the session item.
        self.browser.get(self.live_server_url + '/signup/')
        body = self.browser.find_element_by_tag_name('body')
        self.assertIn('Welcome', body.text)
Jim
  • 13,430
  • 26
  • 104
  • 155
  • I don't think this code example is finished, is it? you're not actually setting any kind of session cookie on the firefox webdriver instance? – hwjp Oct 21 '13 at 14:13
  • In Django 1.7+ on you should use this import path: `from importlib import import_module` (see https://stackoverflow.com/a/32763639) – yofee Dec 22 '17 at 10:17
0

There are a couple of tickets in Django's bug tracker around this kind of problem, the main one seems to be: https://code.djangoproject.com/ticket/10899 which hasn't had any movement on it for a few months. Basically, you need to do some extra set up to get the session to work properly. Here's what worked for me (may not work as is with your particular set up, as I wasn't using Selenium):

def setUp(self):
    from django.conf import settings
    engine = import_module(settings.SESSION_ENGINE)
    store = engine.SessionStore()
    store.save()
    self.client.cookies[settings.SESSION_COOKIE_NAME] = store.session_key

Now you should be able to access self.client.session and it should remember any changes you make to it.

jscn
  • 161
  • 1
  • 5
  • 1
    Thanks, but I know how to do that. My question specifically asked how to do it with Selenium as it's a good functional testing tool and this would seem to be a pretty common situation, expecting a session variable to be set when coming to a page. I do appreciate your taking time to answer, though. – Jim Jan 22 '13 at 15:46
0

Here is my solution for django==2.2.

    from importlib import import_module
    from django.conf import settings
    from django.contrib.auth import get_user_model

    # create the session database instance
    engine = import_module(settings.SESSION_ENGINE)
    session = engine.SessionStore() 

    # create the user and instantly login
    User = get_user_model()
    temp_user = User.objects.create(username='admin')
    temp_user.set_password('password')
    self.client.login(username='admin', password='password')

    # get session object and insert data
    session = self.client.session
    session[key] = value
    session.save()
    # update selenium instance with sessionID
    selenium.add_cookie({'name': 'sessionid', 'value': session._SessionBase__session_key,
                              'secure': False, 'path': '/'})
Igor Alex
  • 841
  • 2
  • 10
  • 21