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

File 2879.selenium-support.12.diff, 45.9 KB (added by Julien Phalip, 12 years ago)
  • 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..99f6f55
    - +  
     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    @classmethod
     12    def setUpClass(cls):
     13        if sys.version_info < (2, 6):
     14            raise SkipTest('Selenium Webdriver does not support Python < 2.6.')
     15        try:
     16            # Import and start the WebDriver class.
     17            module, attr = cls.webdriver_class.rsplit('.', 1)
     18            mod = import_module(module)
     19            WebDriver = getattr(mod, attr)
     20            cls.selenium = WebDriver()
     21        except Exception:
     22            raise SkipTest('Selenium webdriver "%s" not installed or not '
     23                           'operational.' % cls.webdriver_class)
     24        super(AdminSeleniumWebDriverTestCase, cls).setUpClass()
     25
     26    @classmethod
     27    def tearDownClass(cls):
     28        super(AdminSeleniumWebDriverTestCase, cls).tearDownClass()
     29        if hasattr(cls, 'selenium'):
     30            cls.selenium.quit()
     31
     32    def admin_login(self, username, password, login_url='/admin/'):
     33        """
     34        Helper function to log into the admin.
     35        """
     36        self.selenium.get('%s%s' % (self.live_server_url, login_url))
     37        username_input = self.selenium.find_element_by_name("username")
     38        username_input.send_keys(username)
     39        password_input = self.selenium.find_element_by_name("password")
     40        password_input.send_keys(password)
     41        self.selenium.find_element_by_xpath('//input[@value="Log in"]').click()
     42
     43    def get_css_value(self, selector, attribute):
     44        """
     45        Helper function that returns the value for the CSS attribute of an
     46        DOM element specified by the given selector. Uses the jQuery that ships
     47        with Django.
     48        """
     49        return self.selenium.execute_script(
     50            'return django.jQuery("%s").css("%s")' % (selector, attribute))
     51 No newline at end of file
  • django/core/management/commands/test.py

    diff --git a/django/core/management/commands/test.py b/django/core/management/commands/test.py
    index 2a6dbfc..1a2a469 100644
    a b  
     1import sys
     2import os
     3from optparse import make_option, OptionParser
     4
    15from django.conf import settings
    26from django.core.management.base import BaseCommand
    3 from optparse import make_option, OptionParser
    4 import sys
    57from django.test.utils import get_runner
    68
    79class Command(BaseCommand):
    class Command(BaseCommand):  
    1214            help='Tells Django to stop running the test suite after first failed test.'),
    1315        make_option('--testrunner', action='store', dest='testrunner',
    1416            help='Tells Django to use specified test runner class instead of the one '+
    15                  'specified by the TEST_RUNNER setting.')
     17                 'specified by the TEST_RUNNER setting.'),
     18        make_option('--liveserver', action='store', dest='liveserver', default=None,
     19            help='Overrides the default address where the live server (used '
     20                 'with LiveServerTestCase) is expected to run from. The '
     21                 'default is localhost:8081.'),
    1622    )
    1723    help = 'Runs the test suite for the specified applications, or the entire site if no apps are specified.'
    1824    args = '[appname ...]'
    class Command(BaseCommand):  
    4854        TestRunner = get_runner(settings, options.get('testrunner'))
    4955        options['verbosity'] = int(options.get('verbosity'))
    5056
     57        if options.get('liveserver') is not None:
     58            os.environ['DJANGO_LIVE_TEST_SERVER_ADDRESS'] = options['liveserver']
     59            del options['liveserver']
     60
    5161        test_runner = TestRunner(**options)
    5262        failures = test_runner.run_tests(test_labels)
    5363
  • 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..10bd12f 100644
    a b  
    11from __future__ import with_statement
    22
     3import os
    34import re
    45import sys
    56from functools import wraps
    67from urlparse import urlsplit, urlunsplit
    78from xml.dom.minidom import parseString, Node
     9import select
     10import socket
     11import threading
    812
    913from django.conf import settings
     14from django.contrib.staticfiles.handlers import StaticFilesHandler
    1015from django.core import mail
    11 from django.core.exceptions import ValidationError
     16from django.core.exceptions import ValidationError, ImproperlyConfigured
     17from django.core.handlers.wsgi import WSGIHandler
    1218from django.core.management import call_command
    1319from django.core.signals import request_started
     20from django.core.servers.basehttp import (WSGIRequestHandler, WSGIServer,
     21    WSGIServerException)
    1422from django.core.urlresolvers import clear_url_caches
    1523from django.core.validators import EMPTY_VALUES
    1624from django.db import (transaction, connection, connections, DEFAULT_DB_ALIAS,
    from django.test.utils import (get_warnings_state, restore_warnings_state,  
    2331    override_settings)
    2432from django.utils import simplejson, unittest as ut2
    2533from django.utils.encoding import smart_str
     34from django.views.static import serve
    2635
    2736__all__ = ('DocTestRunner', 'OutputChecker', 'TestCase', 'TransactionTestCase',
    2837           'SimpleTestCase', 'skipIfDBFeature', 'skipUnlessDBFeature')
    def skipUnlessDBFeature(feature):  
    732741    """
    733742    return _deferredSkip(lambda: not getattr(connection.features, feature),
    734743                         "Database doesn't support feature %s" % feature)
     744
     745
     746class QuietWSGIRequestHandler(WSGIRequestHandler):
     747    """
     748    Just a regular WSGIRequestHandler except it doesn't log to the standard
     749    output any of the requests received, so as to not clutter the output for
     750    the tests' results.
     751    """
     752
     753    def log_message(*args):
     754        pass
     755
     756
     757class _ImprovedEvent(threading._Event):
     758    """
     759    Does the same as `threading.Event` except it overrides the wait() method
     760    with some code borrowed from Python 2.7 to return the set state of the
     761    event (see: http://hg.python.org/cpython/rev/b5aa8aa78c0f/). This allows
     762    to know whether the wait() method exited normally or because of the
     763    timeout. This class can be removed when Django supports only Python >= 2.7.
     764    """
     765
     766    def wait(self, timeout=None):
     767        self._Event__cond.acquire()
     768        try:
     769            if not self._Event__flag:
     770                self._Event__cond.wait(timeout)
     771            return self._Event__flag
     772        finally:
     773            self._Event__cond.release()
     774
     775
     776class StoppableWSGIServer(WSGIServer):
     777    """
     778    The code in this class is borrowed from the `SocketServer.BaseServer` class
     779    in Python 2.6. The important functionality here is that the server is non-
     780    blocking and that it can be shut down at any moment. This is made possible
     781    by the server regularly polling the socket and checking if it has been
     782    asked to stop.
     783    Note for the future: Once Django stops supporting Python 2.6, this class
     784    can be removed as `WSGIServer` will have this ability to shutdown on
     785    demand and will not require the use of the _ImprovedEvent class whose code
     786    is borrowed from Python 2.7.
     787    """
     788
     789    def __init__(self, *args, **kwargs):
     790        super(StoppableWSGIServer, self).__init__(*args, **kwargs)
     791        self.__is_shut_down = _ImprovedEvent()
     792        self.__serving = False
     793
     794    def serve_forever(self, poll_interval=0.5):
     795        """Handle one request at a time until shutdown.
     796
     797        Polls for shutdown every poll_interval seconds.
     798        """
     799        self.__serving = True
     800        self.__is_shut_down.clear()
     801        while self.__serving:
     802            r, w, e = select.select([self], [], [], poll_interval)
     803            if r:
     804                self._handle_request_noblock()
     805        self.__is_shut_down.set()
     806
     807    def shutdown(self):
     808        """Stops the serve_forever loop.
     809
     810        Blocks until the loop has finished. This must be called while
     811        serve_forever() is running in another thread, or it will
     812        deadlock.
     813        """
     814        self.__serving = False
     815        if not self.__is_shut_down.wait(2):
     816            raise RuntimeError(
     817                "Failed to shutdown the live test server in 2 seconds. The "
     818                "server might be stuck or generating a slow response.")
     819
     820    def handle_request(self):
     821        """Handle one request, possibly blocking.
     822        """
     823        fd_sets = select.select([self], [], [], None)
     824        if not fd_sets[0]:
     825            return
     826        self._handle_request_noblock()
     827
     828    def _handle_request_noblock(self):
     829        """Handle one request, without blocking.
     830
     831        I assume that select.select has returned that the socket is
     832        readable before this function was called, so there should be
     833        no risk of blocking in get_request().
     834        """
     835        try:
     836            request, client_address = self.get_request()
     837        except socket.error:
     838            return
     839        if self.verify_request(request, client_address):
     840            try:
     841                self.process_request(request, client_address)
     842            except Exception:
     843                self.handle_error(request, client_address)
     844                self.close_request(request)
     845
     846
     847class _MediaFilesHandler(StaticFilesHandler):
     848    """
     849    Handler for serving the media files. This is a private class that is
     850    meant to be used solely as a convenience by LiveServerThread.
     851    """
     852
     853    def get_base_dir(self):
     854        return settings.MEDIA_ROOT
     855
     856    def get_base_url(self):
     857        return settings.MEDIA_URL
     858
     859    def serve(self, request):
     860        return serve(request, self.file_path(request.path),
     861            document_root=self.get_base_dir())
     862
     863
     864class LiveServerThread(threading.Thread):
     865    """
     866    Thread for running a live http server while the tests are running.
     867    """
     868
     869    def __init__(self, address, port, connections_override=None):
     870        self.address = address
     871        self.port = port
     872        self.is_ready = threading.Event()
     873        self.error = None
     874        self.connections_override = connections_override
     875        super(LiveServerThread, self).__init__()
     876
     877    def run(self):
     878        """
     879        Sets up the live server and databases, and then loops over handling
     880        http requests.
     881        """
     882        if self.connections_override:
     883            from django.db import connections
     884            # Override this thread's database connections with the ones
     885            # provided by the main thread.
     886            for alias, conn in self.connections_override.items():
     887                connections[alias] = conn
     888        try:
     889            # Create the handler for serving static and media files
     890            handler = StaticFilesHandler(_MediaFilesHandler(WSGIHandler()))
     891        except Exception, e:
     892            self.error = e
     893            self.is_ready.set()
     894            # Don't go any further and stop.
     895            return
     896        try:
     897            # Instantiate and start the WSGI server
     898            self.httpd = StoppableWSGIServer(
     899                (self.address, self.port), QuietWSGIRequestHandler)
     900            self.httpd.set_app(handler)
     901            self.is_ready.set()
     902            self.httpd.serve_forever()
     903        except WSGIServerException, e:
     904            self.error = e
     905            self.is_ready.set()
     906
     907    def join(self, timeout=None):
     908        if hasattr(self, 'httpd'):
     909            # Stop the WSGI server
     910            self.httpd.shutdown()
     911            self.httpd.server_close()
     912        super(LiveServerThread, self).join(timeout)
     913
     914
     915class LiveServerTestCase(TransactionTestCase):
     916    """
     917    Does basically the same as TransactionTestCase but also launches a live
     918    http server in a separate thread so that the tests may use another testing
     919    framework, such as Selenium for example, instead of the built-in dummy
     920    client.
     921    Note that it inherits from TransactionTestCase instead of TestCase because
     922    the threads do not share the same transactions (unless if using in-memory
     923    sqlite) and each thread needs to commit all their transactions so that the
     924    other thread can see the changes.
     925    """
     926
     927    @property
     928    def live_server_url(self):
     929        return 'http://%s' % self.__test_server_address
     930
     931    @classmethod
     932    def setUpClass(cls):
     933        connections_override = {}
     934        for conn in connections.all():
     935            # If using in-memory sqlite databases, pass the connections to
     936            # the server thread.
     937            if (conn.settings_dict['ENGINE'] == 'django.db.backends.sqlite3'
     938                and conn.settings_dict['NAME'] == ':memory:'):
     939                # Explicitly enable thread-shareability for this connection
     940                conn.allow_thread_sharing = True
     941                connections_override[conn.alias] = conn
     942
     943        # Launch the live server's thread
     944        cls.__test_server_address = os.environ.get(
     945            'DJANGO_LIVE_TEST_SERVER_ADDRESS', 'localhost:8081')
     946        try:
     947            host, port = cls.__test_server_address.split(':')
     948        except Exception:
     949            raise ImproperlyConfigured('Invalid address ("%s") for live '
     950                'server.' % cls.__test_server_address)
     951        cls.server_thread = LiveServerThread(
     952            host, int(port), connections_override)
     953        cls.server_thread.daemon = True
     954        cls.server_thread.start()
     955
     956        # Wait for the live server to be ready
     957        cls.server_thread.is_ready.wait()
     958        if cls.server_thread.error:
     959            raise cls.server_thread.error
     960
     961        super(LiveServerTestCase, cls).setUpClass()
     962
     963    @classmethod
     964    def tearDownClass(cls):
     965        # There may not be a 'server_thread' attribute if setUpClass() for some
     966        # reasons has raised an exception.
     967        if hasattr(cls, 'server_thread'):
     968            # Terminate the live server's thread
     969            cls.server_thread.join()
     970        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/releases/1.4.txt

    diff --git a/docs/releases/1.4.txt b/docs/releases/1.4.txt
    index 7ffc1aa..38b621f 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 supports the integration with 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 2396365..3632de3 100644
    a b Some of the things you can do with the test client are:  
    581581* Test that a given request is rendered by a given Django template, with
    582582  a template context that contains certain values.
    583583
    584 Note that the test client is not intended to be a replacement for Twill_,
     584Note that the test client is not intended to be a replacement for Windmill_,
    585585Selenium_, or other "in-browser" frameworks. Django's test client has
    586586a different focus. In short:
    587587
    588588* Use Django's test client to establish that the correct view is being
    589589  called and that the view is collecting the correct context data.
    590590
    591 * Use in-browser frameworks such as Twill and Selenium to test *rendered*
    592   HTML and the *behavior* of Web pages, namely JavaScript functionality.
     591* Use in-browser frameworks such as Windmill_ and Selenium_ to test *rendered*
     592  HTML and the *behavior* of Web pages, namely JavaScript functionality. Django
     593  also provides special support for those frameworks; see the section on
     594  :class:`~django.test.LiveServerTestCase` for more details.
    593595
    594596A comprehensive test suite should use a combination of both test types.
    595597
    596 .. _Twill: http://twill.idyll.org/
    597 .. _Selenium: http://seleniumhq.org/
    598 
    599598Overview and a quick example
    600599~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    601600
    set up, execute and tear down the test suite.  
    18311830    those options will be added to the list of command-line options that
    18321831    the :djadmin:`test` command can use.
    18331832
     1833Live test server
     1834----------------
     1835
     1836.. versionadded:: 1.4
     1837
     1838.. currentmodule:: django.test
     1839
     1840.. class:: LiveServerTestCase()
     1841
     1842``LiveServerTestCase`` does basically the same as
     1843:class:`~django.test.TransactionTestCase` with one extra feature: it launches a
     1844live Django server in the background on setup, and shuts it down on teardown.
     1845This allows the use of automated test clients other than the
     1846:ref:`Django dummy client <test-client>` such as, for example, the Selenium_ or
     1847Windmill_ clients, to execute a series of functional tests inside a browser and
     1848simulate a real user's actions.
     1849
     1850By default the live server's address is `'localhost:8081'` and the full URL
     1851can be accessed during the tests with ``self.live_server_url``. If you'd like
     1852to change the default address (for example because the 8081 port is already
     1853taken) you may do so either by changing the `DJANGO_LIVE_TEST_SERVER_ADDRESS`
     1854environment variable or by passing a new value via the `--liveserver` option of
     1855the :djadmin:`test` command, for example:
     1856
     1857.. code-block:: bash
     1858
     1859    ./manage.py test --liveserver=localhost:8082
     1860
     1861To demonstrate how to use ``LiveServerTestCase``, let's write a simple Selenium
     1862test. First of all, you need to install the `selenium package`_ into your
     1863Python path:
     1864
     1865.. code-block:: bash
     1866
     1867   pip install selenium
     1868
     1869Then, add a ``LiveServerTestCase``-based test to your app's tests module
     1870(for example: ``myapp/tests.py``). The code for this test may look as follows:
     1871
     1872.. code-block:: python
     1873
     1874    from django.test import LiveServerTestCase
     1875    from selenium.webdriver.firefox.webdriver import WebDriver
     1876
     1877    class MySeleniumTests(LiveServerTestCase):
     1878        fixtures = ['user-data.json']
     1879
     1880        @classmethod
     1881        def setUpClass(cls):
     1882            cls.selenium = WebDriver()
     1883            super(MySeleniumTests, cls).setUpClass()
     1884
     1885        @classmethod
     1886        def tearDownClass(cls):
     1887            super(MySeleniumTests, cls).tearDownClass()
     1888            cls.selenium.quit()
     1889
     1890        def test_login(self):
     1891            self.selenium.get('%s%s' % (self.live_server_url, '/login/'))
     1892            username_input = self.selenium.find_element_by_name("username")
     1893            username_input.send_keys('myuser')
     1894            password_input = self.selenium.find_element_by_name("password")
     1895            password_input.send_keys('secret')
     1896            self.selenium.find_element_by_xpath('//input[@value="Log in"]').click()
     1897
     1898Finally, you may run the test as follows:
     1899
     1900.. code-block:: bash
     1901
     1902    ./manage.py test myapp.MySeleniumTests.test_login
     1903
     1904This example will automatically open Firefox then go to the login page, enter
     1905the credentials and press the "Log in" button. Selenium offers other drivers in
     1906case you do not have Firefox installed or wish to use another browser. The
     1907example above is just a tiny fraction of what the Selenium client can do; check
     1908out the `full reference`_ for more details.
     1909
     1910.. _Windmill: http://www.getwindmill.com/
     1911.. _Selenium: http://seleniumhq.org/
     1912.. _selenium package: http://pypi.python.org/pypi/selenium
     1913.. _full reference: http://readthedocs.org/docs/selenium-python/en/latest/api.html
     1914.. _Firefox: http://www.mozilla.com/firefox/
     1915
     1916.. note::
     1917
     1918    ``LiveServerTestCase`` makes use of the :doc:`static files contrib app
     1919    </howto/static-files>` so you'll need to have your project configured
     1920    accordingly (in particular by setting :setting:`STATIC_URL`).
    18341921
    18351922Attributes
    18361923~~~~~~~~~~
    18371924
    1838 
    18391925.. attribute:: DjangoTestSuiteRunner.option_list
    18401926
    18411927    .. 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_scripts/tests.py

    diff --git a/tests/regressiontests/admin_scripts/tests.py b/tests/regressiontests/admin_scripts/tests.py
    index 11beddb..72d47ad 100644
    a b import re  
    1313
    1414from django import conf, bin, get_version
    1515from django.conf import settings
     16from django.test.simple import DjangoTestSuiteRunner
    1617from django.utils import unittest
    1718
    1819
    class ManageValidate(AdminScriptTestCase):  
    10581059        self.assertOutput(out, '0 errors found')
    10591060
    10601061
     1062class CustomTestRunner(DjangoTestSuiteRunner):
     1063
     1064    def __init__(self, *args, **kwargs):
     1065        assert 'liveserver' not in kwargs
     1066        super(CustomTestRunner, self).__init__(*args, **kwargs)
     1067
     1068    def run_tests(self, test_labels, extra_tests=None, **kwargs):
     1069        pass
     1070
     1071class ManageTestCommand(AdminScriptTestCase):
     1072    def setUp(self):
     1073        from django.core.management.commands.test import Command as TestCommand
     1074        self.cmd = TestCommand()
     1075
     1076    def test_liveserver(self):
     1077        """
     1078        Ensure that the --liveserver option sets the environment variable
     1079        correctly.
     1080        Refs #2879.
     1081        """
     1082
     1083        # Backup original state
     1084        address_predefined = 'DJANGO_LIVE_TEST_SERVER_ADDRESS' in os.environ
     1085        old_address = os.environ.get('DJANGO_LIVE_TEST_SERVER_ADDRESS')
     1086
     1087        self.cmd.handle(verbosity=0, testrunner='regressiontests.admin_scripts.tests.CustomTestRunner')
     1088
     1089        # Original state hasn't changed
     1090        self.assertEqual('DJANGO_LIVE_TEST_SERVER_ADDRESS' in os.environ, address_predefined)
     1091        self.assertEqual(os.environ.get('DJANGO_LIVE_TEST_SERVER_ADDRESS'), old_address)
     1092
     1093        self.cmd.handle(verbosity=0, testrunner='regressiontests.admin_scripts.tests.CustomTestRunner',
     1094                        liveserver='blah')
     1095
     1096        # Variable was correctly set
     1097        self.assertEqual(os.environ['DJANGO_LIVE_TEST_SERVER_ADDRESS'], 'blah')
     1098
     1099        # Restore original state
     1100        if address_predefined:
     1101            os.environ['DJANGO_LIVE_TEST_SERVER_ADDRESS'] = old_address
     1102        else:
     1103            del os.environ['DJANGO_LIVE_TEST_SERVER_ADDRESS']
     1104
     1105
    10611106class ManageRunserver(AdminScriptTestCase):
    10621107    def setUp(self):
    10631108        from django.core.management.commands.runserver import BaseRunserverCommand
  • 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..6db005f 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.core.exceptions import ImproperlyConfigured
     11from django.test import TestCase, LiveServerTestCase
    1012from django.core.handlers.wsgi import WSGIHandler
    11 from django.core.servers.basehttp import AdminMediaHandler
     13from django.core.servers.basehttp import AdminMediaHandler, WSGIServerException
     14from django.test.utils import override_settings
    1215
     16from .models import Person
    1317
    1418class AdminMediaHandlerTests(TestCase):
    1519
    class AdminMediaHandlerTests(TestCase):  
    6872                continue
    6973            self.fail('URL: %s should have caused a ValueError exception.'
    7074                      % url)
     75
     76
     77TEST_ROOT = os.path.dirname(__file__)
     78TEST_SETTINGS = {
     79    'MEDIA_URL': '/media/',
     80    'MEDIA_ROOT': os.path.join(TEST_ROOT, 'media'),
     81    'STATIC_URL': '/static/',
     82    'STATIC_ROOT': os.path.join(TEST_ROOT, 'static'),
     83}
     84
     85
     86class LiveServerBase(LiveServerTestCase):
     87    urls = 'regressiontests.servers.urls'
     88    fixtures = ['testdata.json']
     89
     90    @classmethod
     91    def setUpClass(cls):
     92        # Override settings
     93        cls.settings_override = override_settings(**TEST_SETTINGS)
     94        cls.settings_override.enable()
     95        super(LiveServerBase, cls).setUpClass()
     96
     97    @classmethod
     98    def tearDownClass(cls):
     99        # Restore original settings
     100        cls.settings_override.disable()
     101        super(LiveServerBase, cls).tearDownClass()
     102
     103    def urlopen(self, url):
     104        server_host = os.environ.get(
     105            'DJANGO_LIVE_TEST_SERVER_HOST', 'localhost')
     106        server_port = os.environ.get(
     107            'DJANGO_LIVE_TEST_SERVER_PORT', 8081)
     108        base = 'http://%s:%s' % (server_host, server_port)
     109        return urllib2.urlopen(base + url)
     110
     111
     112class LiveServerAddress(LiveServerBase):
     113    """
     114    Ensure that the address set in the environment variable is valid.
     115    Refs #2879.
     116    """
     117
     118    @classmethod
     119    def setUpClass(cls):
     120        # Backup original environment variable
     121        address_predefined = 'DJANGO_LIVE_TEST_SERVER_ADDRESS' in os.environ
     122        old_address = os.environ.get('DJANGO_LIVE_TEST_SERVER_ADDRESS')
     123
     124        # Just the host is not accepted
     125        os.environ['DJANGO_LIVE_TEST_SERVER_ADDRESS'] = 'localhost'
     126        try:
     127            super(LiveServerAddress, cls).setUpClass()
     128            raise Exception("The line above should have raised an exception")
     129        except ImproperlyConfigured:
     130            pass
     131
     132        # The host must be valid
     133        os.environ['DJANGO_LIVE_TEST_SERVER_ADDRESS'] = 'blahblahblah:8081'
     134        try:
     135            super(LiveServerAddress, cls).setUpClass()
     136            raise Exception("The line above should have raised an exception")
     137        except WSGIServerException:
     138            pass
     139
     140        # If contrib.staticfiles isn't configured properly, the exception
     141        # should bubble up to the main thread.
     142        old_STATIC_URL = TEST_SETTINGS['STATIC_URL']
     143        TEST_SETTINGS['STATIC_URL'] = None
     144        os.environ['DJANGO_LIVE_TEST_SERVER_ADDRESS'] = 'localhost:8081'
     145        try:
     146            super(LiveServerAddress, cls).setUpClass()
     147            raise Exception("The line above should have raised an exception")
     148        except ImproperlyConfigured:
     149            pass
     150        TEST_SETTINGS['STATIC_URL'] = old_STATIC_URL
     151
     152        # Restore original environment variable
     153        if address_predefined:
     154            os.environ['DJANGO_LIVE_TEST_SERVER_ADDRESS'] = old_address
     155        else:
     156            del os.environ['DJANGO_LIVE_TEST_SERVER_ADDRESS']
     157
     158    def test_test_test(self):
     159        # Intentionally empty method so that the test is picked up by the
     160        # test runner and the overriden setUpClass method is executed.
     161        pass
     162
     163class LiveServerViews(LiveServerBase):
     164    def test_404(self):
     165        """
     166        Ensure that the LiveServerTestCase serves 404s.
     167        """
     168        try:
     169            self.urlopen('/')
     170        except urllib2.HTTPError, err:
     171            self.assertEquals(err.code, 404, 'Expected 404 response')
     172        else:
     173            self.fail('Expected 404 response')
     174
     175    def test_view(self):
     176        """
     177        Ensure that the LiveServerTestCase serves views.
     178        """
     179        f = self.urlopen('/example_view/')
     180        self.assertEquals(f.read(), 'example view')
     181
     182    def test_static_files(self):
     183        """
     184        Ensure that the LiveServerTestCase serves static files.
     185        """
     186        f = self.urlopen('/static/example_file.txt')
     187        self.assertEquals(f.read(), 'example file\n')
     188
     189    def test_media_files(self):
     190        """
     191        Ensure that the LiveServerTestCase serves media files.
     192        """
     193        f = self.urlopen('/media/example_media_file.txt')
     194        self.assertEquals(f.read(), 'example media file\n')
     195
     196
     197class LiveServerDatabase(LiveServerBase):
     198
     199    def test_fixtures_loaded(self):
     200        """
     201        Ensure that fixtures are properly loaded and visible to the
     202        live server thread.
     203        """
     204        f = self.urlopen('/model_view/')
     205        self.assertEquals(f.read().splitlines(), ['jane', 'robert'])
     206
     207    def test_database_writes(self):
     208        """
     209        Ensure that data written to the database by a view can be read.
     210        """
     211        self.urlopen('/create_model_instance/')
     212        names = [person.name for person in Person.objects.all()]
     213        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
  • tests/runtests.py

    diff --git a/tests/runtests.py b/tests/runtests.py
    index c38c4c2..f2e87f2 100755
    a b if __name__ == "__main__":  
    270270        help="Bisect the test suite to discover a test that causes a test failure when combined with the named test.")
    271271    parser.add_option('--pair', action='store', dest='pair', default=None,
    272272        help="Run the test suite in pairs with the named test to find problem pairs.")
     273    parser.add_option('--liveserver', action='store', dest='liveserver', default=None,
     274        help='Overrides the default address where the live server (used with '
     275             'LiveServerTestCase) is expected to run from. The default is '
     276             'localhost:8081.'),
    273277    options, args = parser.parse_args()
    274278    if options.settings:
    275279        os.environ['DJANGO_SETTINGS_MODULE'] = options.settings
    if __name__ == "__main__":  
    279283    else:
    280284        options.settings = os.environ['DJANGO_SETTINGS_MODULE']
    281285
     286    if options.liveserver is not None:
     287        os.environ['DJANGO_LIVE_TEST_SERVER_ADDRESS'] = options.liveserver
     288
    282289    if options.bisect:
    283290        bisect_tests(options.bisect, options, args)
    284291    elif options.pair:
Back to Top