Testowanie aplikacji Django z Selenium
Selenium to popularne narzędzie do automatyzowania operacji wykonywanych przez przeglądarkę. Głównym zastosowaniem Selenium są testy aplikacji webowych, w szczególności ich frontentu. Za pomocą tego narzędzia możemy pokryć testami bardziej frontendową część aplikacji, np. kod JavaScript - coś czego zwykłe testy nie są w stanie obsłużyć.
Django 1.4 wprowadziło LiveServerTestCase - TestCase, który odpala własny serwer "deweloperski". To w efekcie umożliwiło łatwą integrację Selenium w testach Django. Selenium z Django 1.4 zaprezentowano m.in w prezentacji na benlopatin.com. W sieci jest wiele rozwiązań dotyczących starszych wersji Django, gdzie wykorzystanie Selenium wyglądało zupełnie inaczej.
W tym artykule przedstawię wykorzystanie Selenium w testach Django.
Podstawy
Na początek instalujemy pythonowy moduł Selenium: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()
Metody tej klasy mogą korzystać z self.selenium do operowania na przeglądarce (w tym przypadku Firefox) - otwierania określonych adresów URL, wyszukiwania elementów, zawartości na stronie, wypełniania formularzy itp. Wspomniana prezentacja na benlopatin.com listuje najbardziej przydatne metody. Można zajrzeć też do źródeł i dokumentacji.
Prosty przykład - sprawdzamy czy pod /admin/ wyświetlony zostanie Panel Admina (a dokładniej czy będzie w kodzie strony nagłówek "Django administration"): 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)
Testujemy logowanie za pomocą Facebooka
A teraz coś czego nie da się przetestować za pomocą zwykłych testów z TestCase - logowanie za pomocą Facebooka. Poniżej prosta strona HTML (zwracany w widoku Django) z logowaniem za pomocą Facebooka (JavaScriptowe API):<!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 : 'ID_TWOJEJ_APLIKACJI_FACEBOOKOWEJ', // 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>
Mamy link "Facebook Login", który po kliknięciu otwiera okno z widżetem logowania Facebooka. Jeżeli się zalogujemy to JavaScrptowe API pobierze dane o użytkowniku i wyświetli jego imię i nazwisko (response.name). By kod działał musimy podać ID aplikacji Facebookowej obsługującej API na naszej stronie. Jak to przetestować za pomocą Selenium?
Na początek dla używanej aplikacji Facebookowej musimy ustawić "Site URL" (Website with Facebook Login) na "http://localhost:8081/" - domyślny adres URL, pod którym odpalany jest serwer w czasie testów z Selenium. Bez tego Facebook nie pozwoli zalogować się za pomocą aplikacji.
Teraz test. Wygląda on tak: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()
Mamy jeden test - test_if_user_logs_in i dwie pomocnicze metody. Pierwsza klika w link logowania, druga wypełnia i wysyła formularz logowania. Jako że to logowanie otwiera nowe okno - musimy się na nie przełączyć za pomocą "window_handles" (lista okien) i "switch_to_window". Funkcja sleep jest po to by asynchroniczne API Facebooka zdążyło pobrać i wyświetlić dane (strona jest załadowana więc selenium nie ma na co czekać).
open_login_page otwiera wskazany adres URL (self.selenium.get), po czym wyszukuje link o podanym ID (self.selenium.find_element_by_id) i go klika co otwiera okno logowania Facebooka.
submit_login_form wyszukuje pola formularza (find_element_by_name) i wpisuje w nie (send_keys) adres email i hasło. Na koniec wyszukuje przycisku typu "submit" i klika go wysyłając formularz.
Gdy czegoś nie ma
Selenium rzuci wyjątek NoSuchElementException, gdy nie znajdzie żądanego elementu. Można to wykorzystać do testów sprawdzających brak elementów. Wystarczy zaimportować wyjątki: 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)
Selenium można użyć do testów akceptacyjnych - gdzie jako użytkownik będzie wykonywać te same czynności co użytkownik na gotowej stronie. Narzędzie to można także użyć do automatyzacji innych czynności wykonywanych w przeglądarce (np. wejść na jakąś stronę w sieci i wykonać jakieś czynności).
Comment article