Test Django with Selenium, pytest and user authentication

When testing a Django app with Selenium, how do you authenticate the user and test pages that require to be logged in?

Of course: StackOverflow has the answer.

The following is the actual code that I use to make this work with pytest. It requires pytest-django.

pytest fixtures

In pytest everything is contained in neat test fixtures.

Browser

The first fixture provides a broser/webdriver instance with an anonymous user. The default way of using Selenium.

# system_tests/conftest.py

from selenium import webdriver

@pytest.fixture(scope='module')
def browser(request):
    """Provide a selenium webdriver instance."""
    # SetUp
    options = webdriver.ChromeOptions()
    options.add_argument('headless')

    browser_ = webdriver.Chrome(chrome_options=options)

    yield browser_

    # TearDown
    browser_.quit()

User

To be able to authenticate, I need a user in the database. Using Factory Boy:

# system_tests/conftest.py

from django.contrib.auth.hashers import make_password
from accounts.factories import UserFactory

TESTEMAIL = 'test-user@example.com'
TESTPASSWORD = 'a-super-secret-password'

@pytest.fixture()
def user(db):
    """Add a test user to the database."""
    user_ = UserFactory.create(
        name='I am a test user',
        email=TESTEMAIL,
        password=make_password(TESTPASSWORD),
    )

    return user_

Authenticated browser

To get the authenticated browser, the first two fixtures are required, plus the Django TestClient and LiveServer fixtures which, for pytest, are provided by pytest-django. Using the code from SO:

# system_tests/conftest.py

@pytest.fixture()
def authenticated_browser(browser, client, live_server, user):
    """Return a browser instance with logged-in user session."""
    client.login(email=TESTEMAIL, password=TESTPASSWORD)
    cookie = client.cookies['sessionid']

    browser.get(live_server.url)
    browser.add_cookie({'name': 'sessionid', 'value': cookie.value, 'secure': False, 'path': '/'})
    browser.refresh()

    return browser

The tests

Now the Selenium test can use the authenticated_browser fixture.

# system_tests/test_django.py

def test_django_with_authenticated_user(live_server, authenticated_browser):
    """A Selenium test."""
    browser = authenticated_browser

    # Open the home page
    browser.get(live_server.url)
    

To test logging in and out of the app, I use the unauthenticated browser instance plus the user fixture.

# system_tests/test_django.py

def test_login_of_anonymous_user(live_server, browser, user):
    # Open the home page
    browser.get(live_server.url)

    # Click the 'login' button
    browser.find_element_by_id('id_link_to_login')).click()
    

Summary

These are the pytest fixtures that I use to test a Django app with an authenticated user.

If there is a better way to do this, please tell me! I want to know.