Testing Django applications with Selenium
Selenium is a popular tool for browser automation. Main usage for Selenium are browser based tests of web applications. With a real browser and handy API you can test frontend behavior of an application (like JavaScript code).
In Django 1.4 we got LiveServerTestCase - a TestCase that spawns a test server. This allowed much easier Selenium integration in Django tests. You can change a presentation on benlopatin.com. If you will find something different - check the date as it may describe older solutions for older Django versions.
In this article I'll show some basic Selenium usage cases in tests - showcasing the api.
Basics
At start install Python selenium package:from django import test
from selenium.webdriver.firefox import webdriver
class BasicTestWithSelenium(test.LiveServerTestCase):
@classmethod
def setUpClass(cls):
cls.selenium = webdriver.WebDriver()
super(BasicTestWithSelenium, cls).setUpClass()
@classmethod
def tearDownClass(cls):
super(BasicTestWithSelenium, cls).tearDownClass()
cls.selenium.quit()
Methods of this class can use self.selenium to operate via Selenium on a browser (in this case Firefox). You can open URLs, find elements on page, fill and submit forms and more. Aside of mentioned presentation you can look at the sources or documentation for a list of API methods available.
A simple Selenium test checking if "Django administration" header is displayed on /admin/ URL would look like so: def test_if_admin_panel_is_displayed(self):
url = urljoin(self.live_server_url, '/admin/')
self.selenium.get(url)
header = self.selenium.find_element_by_id('site-name').text
self.assertEqual('Django administration', header)
Testing Facebook authentication with Selenium
How to test a Facebook authentication managed by JavaScript SDK? Here is a template (Django view) with the authentication code:<!DOCTYPE html>
<html>
<head>
<title>FB Login</title>
<meta charset="utf-8" />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
</head>
<body>
<div id="fb-root"></div>
<script>
window.fbAsyncInit = function() {
FB.init({
appId : 'YOUR_FACEBOOK_APP_ID', // App ID
status : true, // check login status
cookie : true, // enable cookies to allow the server to access the session
xfbml : true // parse XFBML
});
// Additional initialization code here
};
// Load the SDK Asynchronously
(function(d){
var js, id = 'facebook-jssdk', ref = d.getElementsByTagName('script')[0];
if (d.getElementById(id)) {return;}
js = d.createElement('script'); js.id = id; js.async = true;
js.src = "//connect.facebook.net/en_US/all.js";
ref.parentNode.insertBefore(js, ref);
}(document));
$(document).ready(function() {
$('#login').click(function() {
FB.login(function(response) {
if (response.authResponse) {
$('.status-bar').html($('.status-bar').html() + '<p>FB connected</p>');
FB.api('/me', function(response) {
$('.status-bar').html($('.status-bar').html() + '<p>' + response.name + '</p>');
});
} else {
$('.status-bar').html($('.status-bar').html() + '<p>Not connected to FB</p>');
}
}, {scope: ''});
});
});
</script>
<a href="#" id="login"><b>Facebook Login</b></a><br />
<div class="status-bar"></div>
</body>
</html>
So we have a "Facebook Login" link, that when clicked will open a new window with a Facebook login widget. If user logs in the JavaScript API will query for user name and print it on the page. For this code to work you have to set application ID of you Facebook App.
To test this login process with selenium we will need a test Facebook user and one change in used Facebook app config. The "Site URL" (Website with Facebook Login) needs to be set to "http://localhost:8081/" - which is the default URL of the dev server during tests. As the test user you have to autorize the application prior to running selenium tests (so that they get only the login form and not extra authorization form).
The test looks like so:import time
from urlparse import urljoin
from django import test
from selenium.webdriver.firefox import webdriver
class TestFacebookLoginWithSelenium(test.LiveServerTestCase):
@classmethod
def setUpClass(cls):
cls.selenium = webdriver.WebDriver()
super(TestFacebookLoginWithSelenium, cls).setUpClass()
@classmethod
def tearDownClass(cls):
super(TestFacebookLoginWithSelenium, cls).tearDownClass()
cls.selenium.quit()
def test_if_user_logs_in(self):
facebook_name = 'TEST_ACCOUNT_USER_NAME'
self.open_login_page()
windows = self.selenium.window_handles
self.selenium.switch_to_window(windows[1])
self.submit_login_form()
self.selenium.switch_to_window(windows[0])
time.sleep(5)
response = self.selenium.find_element_by_css_selector('body').text
self.assertTrue(facebook_name in response)
def open_login_page(self):
url = urljoin(self.live_server_url, '/test/')
self.selenium.get(url)
login_link = self.selenium.find_element_by_id('login')
login_link.click()
def submit_login_form(self):
facebook_email = 'TEST_ACCOUNT_EMAIL'
facebook_pass = 'TEST_ACCOUNT_PASSWORD'
email = self.selenium.find_element_by_name('email')
password = self.selenium.find_element_by_name('pass')
email.send_keys(facebook_email)
password.send_keys(facebook_pass)
submit = 'input[type="submit"]'
submit = self.selenium.find_element_by_css_selector(submit)
submit.click()
We have a test_if_user_logs_in test and two helper methods. First method clicks in the login link, the second fills and submits the login form. As Facebook login opens a new window we have to switch between windows with "window_handles" (list of available windows) and "switch_to_window". Sleep function is there to wait for the Facebook asynchronous JavaScript code to execute (the page is loaded so selenium wouldn't wait).
open_login_page opens given url (self.selenium.get) and then finds a link given by ID on that opened page (self.selenium.find_element_by_id). The link is clicked which opens the login window.
submit_login_form finds form fields (find_element_by_name) and fills them with data (send_keys) - email and password. In the end it finds a submit type button and clicks it submitting the form.
When something is not there
Selenium find methods will raise NoSuchElementException if they can't find given element. That can also be used for tests checking if something is not there. Just import the exceptions: def test_if_page_doest_contains_a_tab(self):
url = urljoin(self.live_server_url, '/test/')
self.selenium.get(url)
def find_tab():
self.selenium.find_element_by_id('tab')
self.assertRaises(exceptions.NoSuchElementException, find_tab)
That's how testing with Selenium looks like, at least in the basic form. This tool can also be used for acceptance tests checking user flow on the website or for task automation on external websites (login, check for data or perform some actions).
Comment article