Testing in Django with Selenium

May 29, 2020, 2:52 p.m.

Django Python · 7 min read

Testing in Django with Selenium

Testing in Django

If you are new to Django you have probably experienced a cycle of quitting your server, changing a bit of code, and testing the same feature over again.  The task can get quite repetitive and frustrating after a while.  Luckily Django includes a test framework for writing tests to check specific features.  However, the test framework is limited and cannot replicate the behavior of a manually checking a feature on your development server.  To accomplish this, we use Selenium.

 

What is Selenium?

Originally developed by Jason Huggins in 2004, Selenium is a framework for testing web applications and automating web browsers.  Unlike Django's testing framework, Selenium actually automates user interaction on a given website as if a real user is performing the actions.  Of course, this means Selenium can be used for reasons other than testing, such as automating an e-commerce's checkout to create a sneaker bot.  

For our purposes, we will focus on using Selenium to help with testing our Django web app's functionality.  Skip below to the last section if you already have a solid understanding of setting up a model form in Django.

 

Django Setup

Start by setting up a virtual environment and creating a basic Django project.  We'll create a new virtual environment called formtest. 

C:\Users\Owner\Desktop\code>py -m venv formtest

 

Next change the directory to the virtual environment, install Django and set up your project and app.

C:\Users\Owner\Desktop\code>cd formtest

C:\Users\Owner\Desktop\code\formtest>Scripts\activate

(formtest) C:\Users\Owner\Desktop\code\formtest>pip install Django

(formtest) C:\Users\Owner\Desktop\code\formtest>django-admin startproject mysite

(formtest) C:\Users\Owner\Desktop\code\formtest\mysite>py manage.py startapp main

 

Add the main app to settings.py in mysite.

settings.py

INSTALLED_APPS = [
    'main.apps.MainConfig', #add this
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

 

Create urls.py in the main folder and include in mysite > urls.py.

main > urls.py

from django.urls import path
from . import views

app_name = "main"   

urlpatterns = [
	path("", views.homepage, name="homepage"),
	
]

mysite > urls.py

from django.contrib import admin
from django.urls import path, include #add include

urlpatterns = [
	path('', include('main.urls')),  #add path
    path('admin/', admin.site.urls),
]

 

Create a homepage view in views.py.

main > views.py

from django.shortcuts import render

# Create your views here.
def homepage(request):
	return render(request, "home.html")

 

Create a new folder named templates in the main folder. Then add a home.html template in the templates folder.  In our template, we added the Bootstrap CDN so we can easily use Bootstrap components.

main > templates > home.html

<!DOCTYPE html>
<html>
   <head>

    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <!--Bootstrap CSS-->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
 
  </head>
  <body>


 
  </body>
</html>

 

Now that we have our project configured.  Let's add a model class, apply migrations, and create a model form.  We will add the model form to our home template and then use Selenium to fill out the form.  The model will be for a basketball player and have model fields for the player name, height, team, and points per game.  Make sure to add forms.py to the main folder.

models.py

from django.db import models

# Create your models here.
class Player(models.Model):
	name = models.CharField(max_length=100)
	height = models.CharField(max_length=100)
	team = models.CharField(max_length=100)
	ppg = models.DecimalField(max_digits=2, decimal_places= 2)
	

CLI

(formtest) C:\Users\Owner\Desktop\code\formtest\mysite>py manage.py makemigrations

(formtest) C:\Users\Owner\Desktop\code\formtest\mysite>py manage.py migrate

forms.py

from django import forms
from .models import Player

class PlayerForm(forms.ModelForm):

	class Meta:
		model = Player
		fields = ('name','height','team','ppg')

 

Add the form as context to the homepage view.

views.py

from django.shortcuts import render
from .forms import PlayerForm

# Create your views here.
def homepage(request):
	form = PlayerForm()
	return render(request, "home.html", {"form": form})

 

Add the form to home.html.  We also want the form to look a little nicer so we'll quickly install django-crispy-forms first.  We also added a simple Bootstrap navbar.

CLI

(formtest) C:\Users\Owner\Desktop\code\formtest\mysite>pip install django-crispy-forms

settings.py

INSTALLED_APPS = [
    'main.apps.MainConfig',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'crispy_forms',  #add this
]

CRISPY_TEMPLATE_PACK = 'bootstrap4'   #add this 

home.html

<!DOCTYPE html>
<html>
   <head>

    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <!--Bootstrap CSS-->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
 
  </head>
  <body>
      {% load crispy_forms_tags %}

      <nav class="navbar navbar-light bg-light">
        <a class="navbar-brand mx-auto" href="">
          Player Database
        </a>
      </nav>
      <br><br>

      <div class="container">
      <form method="post">
        {% csrf_token %}
        {{ form|crispy }}
        <br>
        <button class ="btn btn-primary" type="submit" id= "submit_button">Submit</button>
      </form>
      </div>
  </body>
</html>

database of players

 

Now let's edit our view to save any new model objects we create from our form.  Also, we will pass in a queryset of Players to render the results as cards. Then we'll be ready to test it out.

views.py

from django.shortcuts import render
from .forms import PlayerForm
from .models import Player

# Create your views here.
def homepage(request):
	if request.method == "POST":
		form = PlayerForm(request.POST)
		if form.is_valid():
			form.save()
	players = Player.objects.all()
	form = PlayerForm()
	return render(request, "home.html", {"form": form, "players":players})

home.html

<!DOCTYPE html>
<html>
   <head>

    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <!--Bootstrap CSS-->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
 
  </head>
  <body>
      {% load crispy_forms_tags %}

      <nav class="navbar navbar-light bg-light">
        <a class="navbar-brand mx-auto" href="">
          Player Database
        </a>
      </nav>
      <br><br>

      <div class="container">
      <form method="post">
        {% csrf_token %}
        {{ form|crispy }}
        <br>
        <button class ="btn btn-primary" type="submit" id= "submit_button">Submit</button>
      </form>
      </div>
      <br><br>
      <div class="container">
        {% for player in players %}
        <div class="card" style="width: 18rem;">
          <div class="card-body">
            <h5 class="card-title">{{ player.name }}</h5>
            <p class="card-text">{{ player.team }}</p>
            <p class="card-text">{{ player.height }}</p>
            <p class="card-text">{{ player.ppg }}</p>
          </div>
        </div>
        {% endfor %}
      </div>

  </body>
</html>

 

Django + Selenium

Alright, we are finally ready to use Selenium to test our form. Remember, with Selenium, we actually simulate user interaction, so you should see your browser launch and perform the operations we specify.  Start by installing Selenium.

(formtest) C:\Users\Owner\Desktop\code\formtest\mysite>pip install selenium

 

Next, install the Webdriver for the browser you will use to test your project.  A WebDriver is an API and protocol for interacting and controlling the behavior of a specific browser.  Since we are using Chrome, we'll download the Chrome driver.  For a list of drivers for different browsers, visit Selenium's documentation here.  You also need to add the driver as an executable to your path, meaning the driver can be run from your command prompt/terminal.  If you're unaware of how to do this, check out this helpful article

 

Once Selenium is installed and the driver is installed and added to our path, we can set up our tests.py file so we can test our ModelForm.  Basically, this test will open our web browser and visit our homepage.  Then it will look for specific elements by their id.  For example, the id of the player name input field is 'id_name'.  Use your developer tools on your browser to identify the names of the IDs you need.  Next, we use the send_keys attribute to actually fill in the data and submit the form.  Finally, using the assert command, we test if the player's name appears on the screen as it should given that we render a queryset of all players and just saved the player to our database.

tests.py

from django.test import LiveServerTestCase
from selenium import webdriver
from selenium.webdriver.common.keys import Keys

# Create your tests here.
class PlayerFormTest(LiveServerTestCase):

	def testform(self):
		selenium = webdriver.Chrome()
		#Choose your url to visit
		selenium.get('http://127.0.0.1:8000/')
		#find the elements you need to submit form
		player_name = selenium.find_element_by_id('id_name')
		player_height = selenium.find_element_by_id('id_height')
		player_team = selenium.find_element_by_id('id_team')
		player_ppg = selenium.find_element_by_id('id_ppg')

		submit = selenium.find_element_by_id('submit_button')

		#populate the form with data
		player_name.send_keys('Lebron James')
		player_team.send_keys('Los Angeles Lakers')
		player_height.send_keys('6 feet 9 inches')
		player_ppg.send_keys('25.7')

		#submit form
		submit.send_keys(Keys.RETURN)

		#check result; page source looks at entire html document
		assert 'Lebron James' in selenium.page_source

inspect form id's

 

To run the test, open another command prompt, make sure your server is running in one of the command prompt windows, and run the following command in the other command prompt:

(formtest) C:\Users\Owner\Desktop\code\formtest\mysite>py manage.py test

 

You should observe the following output unless you receive a traceback of an error.

success:

Creating test database for alias 'default'...
System check identified no issues (0 silenced).

DevTools listening on ws://127.0.0.1:60784/devtools/browser/25ed847e-9f13-44a6-8bd4-dafa18545b3d
[14940:9508:0601/151848.766:ERROR:device_event_log_impl.cc(208)] [15:18:48.766] Bluetooth: bluetooth_adapter_winrt.cc:1060 Getting Default Adapter failed.
.
----------------------------------------------------------------------
Ran 1 test in 6.522s

OK
Destroying test database for alias 'default'...

failure due to no closing apostrophe for string:

======================================================================
ERROR: main.tests (unittest.loader._FailedTest)
----------------------------------------------------------------------
ImportError: Failed to import test module: main.tests
Traceback (most recent call last):
  File "C:\Users\Owner\AppData\Local\Programs\Python\Python37\lib\unittest\loader.py", line 436, in _find_test_path
    module = self._get_module_from_name(name)
  File "C:\Users\Owner\AppData\Local\Programs\Python\Python37\lib\unittest\loader.py", line 377, in _get_module_from_name
    __import__(name)
  File "C:\Users\Owner\Desktop\code\formtest\mysite\main\tests.py", line 23
    player_height.send_keys('6 feet 9 inches)
                                            ^
SyntaxError: EOL while scanning string literal


----------------------------------------------------------------------
Ran 1 test in 0.001s

FAILED (errors=1)

 

 

Thanks for reading, visit Selenium's documentation for more information.


0
Subscribe now

Subscribe to stay current on our latest articles and promos





Post a Comment
Join the community

0 Comments