Ticket #2879: 2879.selenium-support.11.diff

File 2879.selenium-support.11.diff, 38.2 KB (added by Julien Phalip, 12 years ago)
  • django/conf/global_settings.py

    diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py
    index 0aee63d..da9d030 100644
    a b DEFAULT_EXCEPTION_REPORTER_FILTER = 'django.views.debug.SafeExceptionReporterFil  
    565565# The name of the class to use to run the test suite
    566566TEST_RUNNER = 'django.test.simple.DjangoTestSuiteRunner'
    567567
     568# For the live test server (e.g. used for running Selenium tests)
     569LIVE_TEST_SERVER_HOST = 'localhost'
     570LIVE_TEST_SERVER_PORT = 8081
     571
    568572############
    569573# FIXTURES #
    570574############
  • new file django/contrib/admin/tests.py

    diff --git a/django/contrib/admin/tests.py b/django/contrib/admin/tests.py
    new file mode 100644
    index 0000000..bd93899
    - +  
     1import sys
     2
     3from django.test import LiveServerTestCase
     4from django.utils.importlib import import_module
     5from django.utils.unittest import SkipTest
     6
     7class AdminSeleniumWebDriverTestCase(LiveServerTestCase):
     8
     9    webdriver_class = 'selenium.webdriver.firefox.webdriver.WebDriver'
     10
     11    def setUp(self):
     12        if sys.version_info < (2, 6):
     13            raise SkipTest('Selenium Webdriver does not support Python < 2.6.')
     14        try:
     15            # Import and start the WebDriver class.
     16            module, attr = self.webdriver_class.rsplit('.', 1)
     17            mod = import_module(module)
     18            WebDriver = getattr(mod, attr)
     19            self.selenium = WebDriver()
     20        except Exception:
     21            raise SkipTest('Selenium webdriver "%s" not installed or not '
     22                           'operational.' % self.webdriver_class)
     23        super(AdminSeleniumWebDriverTestCase, self).setUp()
     24
     25    def tearDown(self):
     26        super(AdminSeleniumWebDriverTestCase, self).tearDown()
     27        if hasattr(self, 'selenium'):
     28            self.selenium.quit()
     29
     30    def admin_login(self, username, password, login_url='/admin/'):
     31        """
     32        Helper function to log into the admin.
     33        """
     34        self.selenium.get('%s%s' % (self.live_server_url, login_url))
     35        username_input = self.selenium.find_element_by_name("username")
     36        username_input.send_keys(username)
     37        password_input = self.selenium.find_element_by_name("password")
     38        password_input.send_keys(password)
     39        self.selenium.find_element_by_xpath('//input[@value="Log in"]').click()
     40
     41    def get_css_value(self, selector, attribute):
     42        """
     43        Helper function that returns the value for the CSS attribute of an
     44        DOM element specified by the given selector. Uses the jQuery that ships
     45        with Django.
     46        """
     47        return self.selenium.execute_script(
     48            'return django.jQuery("%s").css("%s")' % (selector, attribute))
     49 No newline at end of file
  • django/test/__init__.py

    diff --git a/django/test/__init__.py b/django/test/__init__.py
    index a3a03e3..21a4841 100644
    a b Django Unit Test and Doctest framework.  
    44
    55from django.test.client import Client, RequestFactory
    66from django.test.testcases import (TestCase, TransactionTestCase,
    7         SimpleTestCase, skipIfDBFeature, skipUnlessDBFeature)
     7    SimpleTestCase, LiveServerTestCase, skipIfDBFeature,
     8    skipUnlessDBFeature)
    89from django.test.utils import Approximate
  • django/test/testcases.py

    diff --git a/django/test/testcases.py b/django/test/testcases.py
    index ee22ac2..d872087 100644
    a b import sys  
    55from functools import wraps
    66from urlparse import urlsplit, urlunsplit
    77from xml.dom.minidom import parseString, Node
     8import select
     9import socket
     10import threading
    811
    912from django.conf import settings
     13from django.contrib.staticfiles.handlers import StaticFilesHandler
    1014from django.core import mail
    1115from django.core.exceptions import ValidationError
     16from django.core.handlers.wsgi import WSGIHandler
    1217from django.core.management import call_command
    1318from django.core.signals import request_started
     19from django.core.servers.basehttp import (WSGIRequestHandler, WSGIServer,
     20    WSGIServerException)
    1421from django.core.urlresolvers import clear_url_caches
    1522from django.core.validators import EMPTY_VALUES
    1623from django.db import (transaction, connection, connections, DEFAULT_DB_ALIAS,
    from django.test.utils import (get_warnings_state, restore_warnings_state,  
    2330    override_settings)
    2431from django.utils import simplejson, unittest as ut2
    2532from django.utils.encoding import smart_str
     33from django.views.static import serve
    2634
    2735__all__ = ('DocTestRunner', 'OutputChecker', 'TestCase', 'TransactionTestCase',
    2836           'SimpleTestCase', 'skipIfDBFeature', 'skipUnlessDBFeature')
    def skipUnlessDBFeature(feature):  
    732740    """
    733741    return _deferredSkip(lambda: not getattr(connection.features, feature),
    734742                         "Database doesn't support feature %s" % feature)
     743
     744
     745class QuietWSGIRequestHandler(WSGIRequestHandler):
     746    """
     747    Just a regular WSGIRequestHandler except it doesn't log to the standard
     748    output any of the requests received, so as to not clutter the output for
     749    the tests' results.
     750    """
     751
     752    def log_message(*args):
     753        pass
     754
     755
     756class _ImprovedEvent(threading._Event):
     757    """
     758    Does the same as `threading.Event` except it overrides the wait() method
     759    with some code borrowed from Python 2.7 to return the set state of the
     760    event (see: http://hg.python.org/cpython/rev/b5aa8aa78c0f/). This allows
     761    to know whether the wait() method exited normally or because of the
     762    timeout. This class can be removed when Django supports only Python >= 2.7.
     763    """
     764
     765    def wait(self, timeout=None):
     766        self._Event__cond.acquire()
     767        try:
     768            if not self._Event__flag:
     769                self._Event__cond.wait(timeout)
     770            return self._Event__flag
     771        finally:
     772            self._Event__cond.release()
     773
     774
     775class StoppableWSGIServer(WSGIServer):
     776    """
     777    The code in this class is borrowed from the `SocketServer.BaseServer` class
     778    in Python 2.6. The important functionality here is that the server is non-
     779    blocking and that it can be shut down at any moment. This is made possible
     780    by the server regularly polling the socket and checking if it has been
     781    asked to stop.
     782    Note for the future: Once Django stops supporting Python 2.6, this class
     783    can be removed as `WSGIServer` will have this ability to shutdown on
     784    demand and will not require the use of the _ImprovedEvent class whose code
     785    is borrowed from Python 2.7.
     786    """
     787
     788    def __init__(self, *args, **kwargs):
     789        super(StoppableWSGIServer, self).__init__(*args, **kwargs)
     790        self.__is_shut_down = _ImprovedEvent()
     791        self.__serving = False
     792
     793    def serve_forever(self, poll_interval=0.5):
     794        """Handle one request at a time until shutdown.
     795
     796        Polls for shutdown every poll_interval seconds.
     797        """
     798        self.__serving = True
     799        self.__is_shut_down.clear()
     800        while self.__serving:
     801            r, w, e = select.select([self], [], [], poll_interval)
     802            if r:
     803                self._handle_request_noblock()
     804        self.__is_shut_down.set()
     805
     806    def shutdown(self):
     807        """Stops the serve_forever loop.
     808
     809        Blocks until the loop has finished. This must be called while
     810        serve_forever() is running in another thread, or it will
     811        deadlock.
     812        """
     813        self.__serving = False
     814        if not self.__is_shut_down.wait(2):
     815            raise RuntimeError(
     816                "Failed to shutdown the live test server in 2 seconds. The "
     817                "server might be stuck or generating a slow response.")
     818
     819    def handle_request(self):
     820        """Handle one request, possibly blocking.
     821        """
     822        fd_sets = select.select([self], [], [], None)
     823        if not fd_sets[0]:
     824            return
     825        self._handle_request_noblock()
     826
     827    def _handle_request_noblock(self):
     828        """Handle one request, without blocking.
     829
     830        I assume that select.select has returned that the socket is
     831        readable before this function was called, so there should be
     832        no risk of blocking in get_request().
     833        """
     834        try:
     835            request, client_address = self.get_request()
     836        except socket.error:
     837            return
     838        if self.verify_request(request, client_address):
     839            try:
     840                self.process_request(request, client_address)
     841            except Exception:
     842                self.handle_error(request, client_address)
     843                self.close_request(request)
     844
     845
     846class MediaFilesHandler(StaticFilesHandler):
     847    """
     848    Handler for serving the media files.
     849    """
     850
     851    def get_base_dir(self):
     852        return settings.MEDIA_ROOT
     853
     854    def get_base_url(self):
     855        return settings.MEDIA_URL
     856
     857    def serve(self, request):
     858        return serve(request, self.file_path(request.path),
     859            document_root=self.get_base_dir())
     860
     861
     862class LiveServerThread(threading.Thread):
     863    """
     864    Thread for running a live http server while the tests are running.
     865    """
     866
     867    def __init__(self, address, port, connections_override=None):
     868        self.address = address
     869        self.port = port
     870        self.is_ready = threading.Event()
     871        self.error = None
     872        self.connections_override = connections_override
     873        super(LiveServerThread, self).__init__()
     874
     875    def run(self):
     876        """
     877        Sets up live server and database and loops over handling http requests.
     878        """
     879        if self.connections_override:
     880            from django.db import connections
     881            # Override this thread's database connections with the ones
     882            # provided by the main thread.
     883            for alias, conn in self.connections_override.items():
     884                connections[alias] = conn
     885        try:
     886            # Instantiate and start the server
     887            self.httpd = StoppableWSGIServer(
     888                (self.address, self.port), QuietWSGIRequestHandler)
     889            handler = StaticFilesHandler(MediaFilesHandler(WSGIHandler()))
     890            self.httpd.set_app(handler)
     891            self.is_ready.set()
     892            self.httpd.serve_forever()
     893        except WSGIServerException, e:
     894            self.error = e
     895            self.is_ready.set()
     896
     897    def join(self, timeout=None):
     898        self.httpd.shutdown()
     899        self.httpd.server_close()
     900        super(LiveServerThread, self).join(timeout)
     901
     902
     903class LiveServerTestCase(TransactionTestCase):
     904    """
     905    Does basically the same as TransactionTestCase but also launches a live
     906    http server in a separate thread so that the tests may use another testing
     907    framework, such as Selenium for example, instead of the built-in dummy
     908    client.
     909    Note that it inherits from TransactionTestCase instead of TestCase because
     910    the threads do not share the same connection (unless if using in-memory
     911    sqlite) and each thread needs to commit all their transactions so that the
     912    other thread can see the changes.
     913    """
     914
     915    @property
     916    def live_server_url(self):
     917        return 'http://%s:%s' % (settings.LIVE_TEST_SERVER_HOST,
     918                                 settings.LIVE_TEST_SERVER_PORT)
     919
     920    @classmethod
     921    def setUpClass(cls):
     922        connections_override = {}
     923        for conn in connections.all():
     924            # If using in-memory sqlite databases, pass the connections to
     925            # the server thread.
     926            if (conn.settings_dict['ENGINE'] == 'django.db.backends.sqlite3'
     927                and conn.settings_dict['NAME'] == ':memory:'):
     928                # Explicitly enable thread-shareability for this connection
     929                conn.allow_thread_sharing = True
     930                connections_override[conn.alias] = conn
     931
     932        # Launch the Django live server's thread
     933        cls.server_thread = LiveServerThread(
     934            settings.LIVE_TEST_SERVER_HOST,
     935            int(settings.LIVE_TEST_SERVER_PORT),
     936            connections_override)
     937        cls.server_thread.daemon = True
     938        cls.server_thread.start()
     939
     940        # Wait for the Django server to be ready
     941        cls.server_thread.is_ready.wait()
     942        if cls.server_thread.error:
     943            raise cls.server_thread.error
     944
     945        super(LiveServerTestCase, cls).setUpClass()
     946
     947    @classmethod
     948    def tearDownClass(cls):
     949        # Terminate the Django server's thread
     950        cls.server_thread.join()
     951        super(LiveServerTestCase, cls).tearDownClass()
  • docs/internals/contributing/writing-code/unit-tests.txt

    diff --git a/docs/internals/contributing/writing-code/unit-tests.txt b/docs/internals/contributing/writing-code/unit-tests.txt
    index 5ec09fe..275ee15 100644
    a b Going beyond that, you can specify an individual test method like this:  
    122122
    123123    ./runtests.py --settings=path.to.settings i18n.TranslationTests.test_lazy_objects
    124124
     125Running the Selenium tests
     126~~~~~~~~~~~~~~~~~~~~~~~~~~
     127
     128Some admin tests require Selenium 2, Firefox and Python >= 2.6 to work via a
     129real Web browser. To allow those tests to run and not be skipped, you must
     130install the selenium_ package (version > 2.13) into your Python path.
     131
     132Then, run the tests normally, for example:
     133
     134.. code-block:: bash
     135
     136    ./runtests.py --settings=test_sqlite admin_inlines
     137
    125138Running all the tests
    126139~~~~~~~~~~~~~~~~~~~~~
    127140
    dependencies:  
    135148*  setuptools_
    136149*  memcached_, plus a :ref:`supported Python binding <memcached>`
    137150*  gettext_ (:ref:`gettext_on_windows`)
     151*  selenium_ (if also using Python >= 2.6)
    138152
    139153If you want to test the memcached cache backend, you'll also need to define
    140154a :setting:`CACHES` setting that points at your memcached instance.
    associated tests will be skipped.  
    149163.. _setuptools: http://pypi.python.org/pypi/setuptools/
    150164.. _memcached: http://www.danga.com/memcached/
    151165.. _gettext: http://www.gnu.org/software/gettext/manual/gettext.html
     166.. _selenium: http://pypi.python.org/pypi/selenium
    152167
    153168.. _contrib-apps:
    154169
  • docs/ref/settings.txt

    diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt
    index a35d99a..89f2c47 100644
    a b all cache keys used by the Django server.  
    195195
    196196See the :ref:`cache documentation <cache_key_prefixing>` for more information.
    197197
     198.. setting:: LIVE_TEST_SERVER_HOST
     199
     200LIVE_TEST_SERVER_HOST
     201~~~~~~~~~~~~~~~~~~~~~
     202
     203Default: ``'localhost'``
     204
     205Controls the host address at which the live test server gets started when using
     206a :class:`~django.test.LiveServerTestCase`.
     207
     208See also: :setting:`LIVE_TEST_SERVER_PORT`
     209
     210.. setting:: LIVE_TEST_SERVER_PORT
     211
     212LIVE_TEST_SERVER_PORT
     213~~~~~~~~~~~~~~~~~~~~~
     214
     215Default: ``8081``
     216
     217Controls the port at which the live test server gets started when using
     218a :class:`~django.test.LiveServerTestCase`.
     219
     220See also: :setting:`LIVE_TEST_SERVER_HOST`
     221
    198222.. setting:: CACHES-LOCATION
    199223
    200224LOCATION
  • docs/releases/1.4.txt

    diff --git a/docs/releases/1.4.txt b/docs/releases/1.4.txt
    index f614dee..d5e9cab 100644
    a b before the release of Django 1.4.  
    4040What's new in Django 1.4
    4141========================
    4242
     43Support for in-browser testing frameworks
     44~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     45
     46Django 1.4 now includes built-in support for in-browser testing frameworks such
     47as Selenium or Windmill thanks to the :class:`django.test.LiveServerTestCase`
     48base class, allowing you to test the interactions between your site's front and
     49back ends more comprehensively. See the
     50:class:`documentation<django.test.LiveServerTestCase>` for more details and
     51concrete examples.
     52
    4353``SELECT FOR UPDATE`` support
    4454~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    4555
  • docs/topics/testing.txt

    diff --git a/docs/topics/testing.txt b/docs/topics/testing.txt
    index 181b4ff..7a6efcd 100644
    a b Some of the things you can do with the test client are:  
    580580* Test that a given request is rendered by a given Django template, with
    581581  a template context that contains certain values.
    582582
    583 Note that the test client is not intended to be a replacement for Twill_,
     583Note that the test client is not intended to be a replacement for Windmill_,
    584584Selenium_, or other "in-browser" frameworks. Django's test client has
    585585a different focus. In short:
    586586
    587587* Use Django's test client to establish that the correct view is being
    588588  called and that the view is collecting the correct context data.
    589589
    590 * Use in-browser frameworks such as Twill and Selenium to test *rendered*
    591   HTML and the *behavior* of Web pages, namely JavaScript functionality.
     590* Use in-browser frameworks such as Windmill_ and Selenium_ to test *rendered*
     591  HTML and the *behavior* of Web pages, namely JavaScript functionality. Django
     592  also provides special support for those frameworks; see the section on
     593  :class:`~django.test.LiveServerTestCase` for more details.
    592594
    593595A comprehensive test suite should use a combination of both test types.
    594596
    595 .. _Twill: http://twill.idyll.org/
    596 .. _Selenium: http://seleniumhq.org/
    597 
    598597Overview and a quick example
    599598~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    600599
    set up, execute and tear down the test suite.  
    18301829    those options will be added to the list of command-line options that
    18311830    the :djadmin:`test` command can use.
    18321831
     1832Live test server
     1833----------------
     1834
     1835.. versionadded:: 1.4
     1836
     1837.. currentmodule:: django.test
     1838
     1839.. class:: LiveServerTestCase()
     1840
     1841``LiveServerTestCase`` does basically the same as
     1842:class:`~django.test.TransactionTestCase` with one extra feature: it launches a
     1843live Django server in the background on setup, and shuts it down on teardown.
     1844This allows the use of automated test clients other than the
     1845:ref:`Django dummy client <test-client>` such as, for example, the Selenium_ or
     1846Windmill_ clients, to execute a series of functional tests inside a browser and
     1847simulate a real user's actions.
     1848
     1849You may control which host and port the live server will run at with
     1850respectively the :setting:`LIVE_TEST_SERVER_HOST` and
     1851:setting:`LIVE_TEST_SERVER_PORT` settings. The full server url can then be
     1852accessed during the tests with ``self.live_server_url``.
     1853
     1854To demonstrate how to use ``LiveServerTestCase``, let's write a simple Selenium
     1855test. First of all, you need to install the `selenium package`_ into your
     1856Python path:
     1857
     1858.. code-block:: bash
     1859
     1860   pip install selenium
     1861
     1862Then, add the following code to one of your app's tests module (for example:
     1863``myapp/tests.py``):
     1864
     1865.. code-block:: python
     1866
     1867    from django.test import LiveServerTestCase
     1868    from selenium.webdriver.firefox.webdriver import WebDriver
     1869
     1870    class MySeleniumTests(LiveServerTestCase):
     1871        fixtures = ['user-data.json']
     1872
     1873        def setUpClass(self):
     1874            self.selenium = WebDriver()
     1875            super(MySeleniumTests, self).setUpClass()
     1876
     1877        def tearDownClass(self):
     1878            super(MySeleniumTests, self).tearDownClass()
     1879            self.selenium.quit()
     1880
     1881        def test_login(self):
     1882            self.selenium.get('%s%s' % (self.live_server_url, '/login/'))
     1883            username_input = self.selenium.find_element_by_name("username")
     1884            username_input.send_keys('myuser')
     1885            password_input = self.selenium.find_element_by_name("password")
     1886            password_input.send_keys('secret')
     1887            self.selenium.find_element_by_xpath('//input[@value="Log in"]').click()
     1888
     1889Finally, you may run the test as follows:
     1890
     1891.. code-block:: bash
     1892
     1893    ./manage.py test myapp.MySeleniumTests.test_login
     1894
     1895This example will automatically open Firefox then go to the login page, enter
     1896the credentials and press the "Log in" button. Selenium offers other drivers in
     1897case you do not have Firefox installed or wish to use another browser. The
     1898example above is just a tiny fraction of what the Selenium client can do; check
     1899out the `full reference`_ for more details.
     1900
     1901.. _Windmill: http://www.getwindmill.com/
     1902.. _Selenium: http://seleniumhq.org/
     1903.. _selenium package: http://pypi.python.org/pypi/selenium
     1904.. _full reference: http://readthedocs.org/docs/selenium-python/en/latest/api.html
     1905.. _Firefox: http://www.mozilla.com/firefox/
     1906
    18331907
    18341908Attributes
    18351909~~~~~~~~~~
    18361910
    1837 
    18381911.. attribute:: DjangoTestSuiteRunner.option_list
    18391912
    18401913    .. versionadded:: 1.4
  • tests/regressiontests/admin_inlines/admin.py

    diff --git a/tests/regressiontests/admin_inlines/admin.py b/tests/regressiontests/admin_inlines/admin.py
    index 4edd361..508f302 100644
    a b class SottoCapoInline(admin.TabularInline):  
    109109    model = SottoCapo
    110110
    111111
     112class ProfileInline(admin.TabularInline):
     113    model = Profile
     114    extra = 1
     115
    112116site.register(TitleCollection, inlines=[TitleInline])
    113117# Test bug #12561 and #12778
    114118# only ModelAdmin media
    site.register(Fashionista, inlines=[InlineWeakness])  
    124128site.register(Holder4, Holder4Admin)
    125129site.register(Author, AuthorAdmin)
    126130site.register(CapoFamiglia, inlines=[ConsigliereInline, SottoCapoInline])
     131site.register(ProfileCollection, inlines=[ProfileInline])
     132 No newline at end of file
  • tests/regressiontests/admin_inlines/models.py

    diff --git a/tests/regressiontests/admin_inlines/models.py b/tests/regressiontests/admin_inlines/models.py
    index 748280d..f2add00 100644
    a b class Consigliere(models.Model):  
    136136class SottoCapo(models.Model):
    137137    name = models.CharField(max_length=100)
    138138    capo_famiglia = models.ForeignKey(CapoFamiglia, related_name='+')
     139
     140# Other models
     141
     142class ProfileCollection(models.Model):
     143    pass
     144
     145class Profile(models.Model):
     146    collection = models.ForeignKey(ProfileCollection, blank=True, null=True)
     147    first_name = models.CharField(max_length=100)
     148    last_name = models.CharField(max_length=100)
     149 No newline at end of file
  • tests/regressiontests/admin_inlines/tests.py

    diff --git a/tests/regressiontests/admin_inlines/tests.py b/tests/regressiontests/admin_inlines/tests.py
    index c2e3bbc..7b417d8 100644
    a b  
    11from __future__ import absolute_import
    22
     3from django.contrib.admin.tests import AdminSeleniumWebDriverTestCase
    34from django.contrib.admin.helpers import InlineAdminForm
    45from django.contrib.auth.models import User, Permission
    56from django.contrib.contenttypes.models import ContentType
    from django.test import TestCase  
    89# local test models
    910from .admin import InnerInline
    1011from .models import (Holder, Inner, Holder2, Inner2, Holder3, Inner3, Person,
    11     OutfitItem, Fashionista, Teacher, Parent, Child, Author, Book)
     12    OutfitItem, Fashionista, Teacher, Parent, Child, Author, Book, Profile,
     13    ProfileCollection)
    1214
    1315
    1416class TestInline(TestCase):
    class TestInlinePermissions(TestCase):  
    380382        self.assertContains(response, 'value="4" id="id_inner2_set-TOTAL_FORMS"')
    381383        self.assertContains(response, '<input type="hidden" name="inner2_set-0-id" value="%i"' % self.inner2_id)
    382384        self.assertContains(response, 'id="id_inner2_set-0-DELETE"')
     385
     386class SeleniumFirefoxTests(AdminSeleniumWebDriverTestCase):
     387    webdriver_class = 'selenium.webdriver.firefox.webdriver.WebDriver'
     388    fixtures = ['admin-views-users.xml']
     389    urls = "regressiontests.admin_inlines.urls"
     390
     391    def test_add_inlines(self):
     392        """
     393        Ensure that the "Add another XXX" link correctly adds items to the
     394        inline form.
     395        """
     396        self.admin_login(username='super', password='secret')
     397        self.selenium.get('%s%s' % (self.live_server_url,
     398            '/admin/admin_inlines/profilecollection/add/'))
     399
     400        # Check that there's only one inline to start with and that it has the
     401        # correct ID.
     402        self.failUnlessEqual(len(self.selenium.find_elements_by_css_selector(
     403            '#profile_set-group table tr.dynamic-profile_set')), 1)
     404        self.failUnlessEqual(self.selenium.find_element_by_css_selector(
     405            '.dynamic-profile_set:nth-of-type(1)').get_attribute('id'),
     406            'profile_set-0')
     407        self.failUnlessEqual(len(self.selenium.find_elements_by_css_selector(
     408            'form#profilecollection_form tr.dynamic-profile_set#profile_set-0 input[name=profile_set-0-first_name]')), 1)
     409        self.failUnlessEqual(len(self.selenium.find_elements_by_css_selector(
     410            'form#profilecollection_form tr.dynamic-profile_set#profile_set-0 input[name=profile_set-0-last_name]')), 1)
     411
     412        # Add an inline
     413        self.selenium.find_element_by_link_text('Add another Profile').click()
     414
     415        # Check that the inline has been added, that it has the right id, and
     416        # that it contains the right fields.
     417        self.failUnlessEqual(len(self.selenium.find_elements_by_css_selector(
     418            '#profile_set-group table tr.dynamic-profile_set')), 2)
     419        self.failUnlessEqual(self.selenium.find_element_by_css_selector(
     420            '.dynamic-profile_set:nth-of-type(2)').get_attribute('id'), 'profile_set-1')
     421        self.failUnlessEqual(len(self.selenium.find_elements_by_css_selector(
     422            'form#profilecollection_form tr.dynamic-profile_set#profile_set-1 input[name=profile_set-1-first_name]')), 1)
     423        self.failUnlessEqual(len(self.selenium.find_elements_by_css_selector(
     424            'form#profilecollection_form tr.dynamic-profile_set#profile_set-1 input[name=profile_set-1-last_name]')), 1)
     425
     426        # Let's add another one to be sure
     427        self.selenium.find_element_by_link_text('Add another Profile').click()
     428        self.failUnlessEqual(len(self.selenium.find_elements_by_css_selector(
     429            '#profile_set-group table tr.dynamic-profile_set')), 3)
     430        self.failUnlessEqual(self.selenium.find_element_by_css_selector(
     431            '.dynamic-profile_set:nth-of-type(3)').get_attribute('id'), 'profile_set-2')
     432        self.failUnlessEqual(len(self.selenium.find_elements_by_css_selector(
     433            'form#profilecollection_form tr.dynamic-profile_set#profile_set-2 input[name=profile_set-2-first_name]')), 1)
     434        self.failUnlessEqual(len(self.selenium.find_elements_by_css_selector(
     435            'form#profilecollection_form tr.dynamic-profile_set#profile_set-2 input[name=profile_set-2-last_name]')), 1)
     436
     437        # Enter some data and click 'Save'
     438        self.selenium.find_element_by_name('profile_set-0-first_name').send_keys('0 first name 1')
     439        self.selenium.find_element_by_name('profile_set-0-last_name').send_keys('0 last name 2')
     440        self.selenium.find_element_by_name('profile_set-1-first_name').send_keys('1 first name 1')
     441        self.selenium.find_element_by_name('profile_set-1-last_name').send_keys('1 last name 2')
     442        self.selenium.find_element_by_name('profile_set-2-first_name').send_keys('2 first name 1')
     443        self.selenium.find_element_by_name('profile_set-2-last_name').send_keys('2 last name 2')
     444        self.selenium.find_element_by_xpath('//input[@value="Save"]').click()
     445
     446        # Check that the objects have been created in the database
     447        self.assertEqual(ProfileCollection.objects.all().count(), 1)
     448        self.assertEqual(Profile.objects.all().count(), 3)
     449
     450    def test_delete_inlines(self):
     451        self.admin_login(username='super', password='secret')
     452        self.selenium.get('%s%s' % (self.live_server_url,
     453            '/admin/admin_inlines/profilecollection/add/'))
     454
     455        # Add a few inlines
     456        self.selenium.find_element_by_link_text('Add another Profile').click()
     457        self.selenium.find_element_by_link_text('Add another Profile').click()
     458        self.selenium.find_element_by_link_text('Add another Profile').click()
     459        self.selenium.find_element_by_link_text('Add another Profile').click()
     460        self.failUnlessEqual(len(self.selenium.find_elements_by_css_selector(
     461            '#profile_set-group table tr.dynamic-profile_set')), 5)
     462        self.failUnlessEqual(len(self.selenium.find_elements_by_css_selector(
     463            'form#profilecollection_form tr.dynamic-profile_set#profile_set-0')), 1)
     464        self.failUnlessEqual(len(self.selenium.find_elements_by_css_selector(
     465            'form#profilecollection_form tr.dynamic-profile_set#profile_set-1')), 1)
     466        self.failUnlessEqual(len(self.selenium.find_elements_by_css_selector(
     467            'form#profilecollection_form tr.dynamic-profile_set#profile_set-2')), 1)
     468        self.failUnlessEqual(len(self.selenium.find_elements_by_css_selector(
     469            'form#profilecollection_form tr.dynamic-profile_set#profile_set-3')), 1)
     470        self.failUnlessEqual(len(self.selenium.find_elements_by_css_selector(
     471            'form#profilecollection_form tr.dynamic-profile_set#profile_set-4')), 1)
     472
     473        # Click on a few delete buttons
     474        self.selenium.find_element_by_css_selector(
     475            'form#profilecollection_form tr.dynamic-profile_set#profile_set-1 td.delete a').click()
     476        self.selenium.find_element_by_css_selector(
     477            'form#profilecollection_form tr.dynamic-profile_set#profile_set-2 td.delete a').click()
     478        # Verify that they're gone and that the IDs have been re-sequenced
     479        self.failUnlessEqual(len(self.selenium.find_elements_by_css_selector(
     480            '#profile_set-group table tr.dynamic-profile_set')), 3)
     481        self.failUnlessEqual(len(self.selenium.find_elements_by_css_selector(
     482            'form#profilecollection_form tr.dynamic-profile_set#profile_set-0')), 1)
     483        self.failUnlessEqual(len(self.selenium.find_elements_by_css_selector(
     484            'form#profilecollection_form tr.dynamic-profile_set#profile_set-1')), 1)
     485        self.failUnlessEqual(len(self.selenium.find_elements_by_css_selector(
     486            'form#profilecollection_form tr.dynamic-profile_set#profile_set-2')), 1)
  • tests/regressiontests/admin_widgets/tests.py

    diff --git a/tests/regressiontests/admin_widgets/tests.py b/tests/regressiontests/admin_widgets/tests.py
    index 37fa7bc..e28df32 100644
    a b from django import forms  
    77from django.conf import settings
    88from django.contrib import admin
    99from django.contrib.admin import widgets
     10from django.contrib.admin.tests import AdminSeleniumWebDriverTestCase
    1011from django.core.files.storage import default_storage
    1112from django.core.files.uploadedfile import SimpleUploadedFile
    1213from django.db.models import DateField
    class RelatedFieldWidgetWrapperTests(DjangoTestCase):  
    407408        # Used to fail with a name error.
    408409        w = widgets.RelatedFieldWidgetWrapper(w, rel, widget_admin_site)
    409410        self.assertFalse(w.can_add_related)
     411
     412
     413class SeleniumFirefoxTests(AdminSeleniumWebDriverTestCase):
     414    webdriver_class = 'selenium.webdriver.firefox.webdriver.WebDriver'
     415    fixtures = ['admin-widgets-users.xml']
     416    urls = "regressiontests.admin_widgets.urls"
     417
     418    def test_show_hide_date_time_picker_widgets(self):
     419        """
     420        Ensure that pressing the ESC key closes the date and time picker
     421        widgets.
     422        Refs #17064.
     423        """
     424        from selenium.webdriver.common.keys import Keys
     425
     426        self.admin_login(username='super', password='secret', login_url='/')
     427        # Open a page that has a date and time picker widgets
     428        self.selenium.get('%s%s' % (self.live_server_url,
     429            '/admin_widgets/member/add/'))
     430
     431        # First, with the date picker widget ---------------------------------
     432        # Check that the date picker is hidden
     433        self.assertEqual(
     434            self.get_css_value('#calendarbox0', 'display'), 'none')
     435        # Click the calendar icon
     436        self.selenium.find_element_by_id('calendarlink0').click()
     437        # Check that the date picker is visible
     438        self.assertEqual(
     439            self.get_css_value('#calendarbox0', 'display'), 'block')
     440        # Press the ESC key
     441        self.selenium.find_element_by_tag_name('html').send_keys([Keys.ESCAPE])
     442        # Check that the date picker is hidden again
     443        self.assertEqual(
     444            self.get_css_value('#calendarbox0', 'display'), 'none')
     445
     446        # Then, with the time picker widget ----------------------------------
     447        # Check that the time picker is hidden
     448        self.assertEqual(
     449            self.get_css_value('#clockbox0', 'display'), 'none')
     450        # Click the time icon
     451        self.selenium.find_element_by_id('clocklink0').click()
     452        # Check that the time picker is visible
     453        self.assertEqual(
     454            self.get_css_value('#clockbox0', 'display'), 'block')
     455        # Press the ESC key
     456        self.selenium.find_element_by_tag_name('html').send_keys([Keys.ESCAPE])
     457        # Check that the time picker is hidden again
     458        self.assertEqual(
     459            self.get_css_value('#clockbox0', 'display'), 'none')
  • new file tests/regressiontests/servers/fixtures/testdata.json

    diff --git a/tests/regressiontests/servers/fixtures/testdata.json b/tests/regressiontests/servers/fixtures/testdata.json
    new file mode 100644
    index 0000000..d81b225
    - +  
     1[
     2  {
     3    "pk": 1,
     4    "model": "servers.person",
     5    "fields": {
     6      "name": "jane"
     7    }
     8  },
     9  {
     10    "pk": 2,
     11    "model": "servers.person",
     12    "fields": {
     13      "name": "robert"
     14    }
     15  }
     16]
     17 No newline at end of file
  • new file tests/regressiontests/servers/media/example_media_file.txt

    diff --git a/tests/regressiontests/servers/media/example_media_file.txt b/tests/regressiontests/servers/media/example_media_file.txt
    new file mode 100644
    index 0000000..dd2dda9
    - +  
     1example media file
  • tests/regressiontests/servers/models.py

    diff --git a/tests/regressiontests/servers/models.py b/tests/regressiontests/servers/models.py
    index e69de29..6e1414a 100644
    a b  
     1from django.db import models
     2
     3
     4class Person(models.Model):
     5    name = models.CharField(max_length=256)
  • new file tests/regressiontests/servers/static/example_file.txt

    diff --git a/tests/regressiontests/servers/static/example_file.txt b/tests/regressiontests/servers/static/example_file.txt
    new file mode 100644
    index 0000000..5f1cfce
    - +  
     1example file
  • tests/regressiontests/servers/tests.py

    diff --git a/tests/regressiontests/servers/tests.py b/tests/regressiontests/servers/tests.py
    index b9d24ba..04450a9 100644
    a b Tests for django.core.servers.  
    33"""
    44import os
    55from urlparse import urljoin
     6import urllib2
    67
    78import django
    89from django.conf import settings
    9 from django.test import TestCase
     10from django.test import TestCase, LiveServerTestCase
    1011from django.core.handlers.wsgi import WSGIHandler
    1112from django.core.servers.basehttp import AdminMediaHandler
     13from django.test.utils import override_settings
    1214
     15from .models import Person
    1316
    1417class AdminMediaHandlerTests(TestCase):
    1518
    class AdminMediaHandlerTests(TestCase):  
    6871                continue
    6972            self.fail('URL: %s should have caused a ValueError exception.'
    7073                      % url)
     74
     75
     76TEST_ROOT = os.path.dirname(__file__)
     77TEST_SETTINGS = {
     78    'MEDIA_URL': '/media/',
     79    'MEDIA_ROOT': os.path.join(TEST_ROOT, 'media'),
     80    'STATIC_URL': '/static/',
     81    'STATIC_ROOT': os.path.join(TEST_ROOT, 'static'),
     82}
     83
     84
     85class LiveServerBase(LiveServerTestCase):
     86    urls = 'regressiontests.servers.urls'
     87    fixtures = ['testdata.json']
     88
     89    @classmethod
     90    def setUpClass(cls):
     91        cls.settings_override = override_settings(**TEST_SETTINGS)
     92        cls.settings_override.enable()
     93        super(LiveServerBase, cls).setUpClass()
     94
     95    @classmethod
     96    def tearDownClass(cls):
     97        cls.settings_override.disable()
     98        super(LiveServerBase, cls).tearDownClass()
     99
     100    def urlopen(self, url):
     101        base = 'http://%s:%s' % (settings.LIVE_TEST_SERVER_HOST,
     102                                 settings.LIVE_TEST_SERVER_PORT)
     103        return urllib2.urlopen(base + url)
     104
     105
     106class LiveServerViews(LiveServerBase):
     107    def test_404(self):
     108        """
     109        Ensure that the LiveServerTestCase serves 404s.
     110        """
     111        try:
     112            self.urlopen('/')
     113        except urllib2.HTTPError, err:
     114            self.assertEquals(err.code, 404, 'Expected 404 response')
     115        else:
     116            self.fail('Expected 404 response')
     117
     118    def test_view(self):
     119        """
     120        Ensure that the LiveServerTestCase serves views.
     121        """
     122        f = self.urlopen('/example_view/')
     123        self.assertEquals(f.read(), 'example view')
     124
     125    def test_static_files(self):
     126        """
     127        Ensure that the LiveServerTestCase serves static files.
     128        """
     129        f = self.urlopen('/static/example_file.txt')
     130        self.assertEquals(f.read(), 'example file\n')
     131
     132    def test_media_files(self):
     133        """
     134        Ensure that the LiveServerTestCase serves media files.
     135        """
     136        f = self.urlopen('/media/example_media_file.txt')
     137        self.assertEquals(f.read(), 'example media file\n')
     138
     139
     140class LiveServerDatabase(LiveServerBase):
     141
     142    def test_fixtures_loaded(self):
     143        """
     144        Ensure that fixtures are properly loaded and visible to the
     145        live server thread.
     146        """
     147        f = self.urlopen('/model_view/')
     148        self.assertEquals(f.read().splitlines(), ['jane', 'robert'])
     149
     150    def test_database_writes(self):
     151        """
     152        Ensure that data written to the database by a view can be read.
     153        """
     154        self.urlopen('/create_model_instance/')
     155        names = [person.name for person in Person.objects.all()]
     156        self.assertEquals(names, ['jane', 'robert', 'emily'])
  • new file tests/regressiontests/servers/urls.py

    diff --git a/tests/regressiontests/servers/urls.py b/tests/regressiontests/servers/urls.py
    new file mode 100644
    index 0000000..c8ca1ac
    - +  
     1from __future__ import absolute_import
     2
     3from django.conf.urls import patterns, url
     4
     5from . import views
     6
     7
     8urlpatterns = patterns('',
     9    url(r'^example_view/$', views.example_view),
     10    url(r'^model_view/$', views.model_view),
     11    url(r'^create_model_instance/$', views.create_model_instance),
     12)
     13 No newline at end of file
  • new file tests/regressiontests/servers/views.py

    diff --git a/tests/regressiontests/servers/views.py b/tests/regressiontests/servers/views.py
    new file mode 100644
    index 0000000..94a4f2d
    - +  
     1from django.http import HttpResponse
     2from .models import Person
     3
     4
     5def example_view(request):
     6    return HttpResponse('example view')
     7
     8
     9def model_view(request):
     10    people = Person.objects.all()
     11    return HttpResponse('\n'.join([person.name for person in people]))
     12
     13
     14def create_model_instance(request):
     15    person = Person(name='emily')
     16    person.save()
     17    return HttpResponse('')
     18 No newline at end of file
Back to Top