Testowanie aplikacji Django z Selenium

Testy oparte o przeglądarke umożliwiające testowanie frontendu

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:
pip install selenium
Po czym możemy zabrać się za testy. Oto szkielet klasy testującej z pomocą 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)
Nagłówek H1 ma ID "site-name" więc użyłem find_element_by_id by znaleźć element. Jest to prosty przykład, który równie dobrze obsłużą zwykłe testy.

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:
from selenium.common import exceptions
I można tworzyć takie testy:
    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).

blog comments powered by Disqus

Kategorie

Strony