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

File 2879.selenium-support.9.diff, 42.8 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/db/__init__.py

    diff --git a/django/db/__init__.py b/django/db/__init__.py
    index 8395468..e3fcbbd 100644
    a b  
     1from threading import local
    12from django.conf import settings
    23from django.core import signals
    34from django.core.exceptions import ImproperlyConfigured
    router = ConnectionRouter(settings.DATABASE_ROUTERS)  
    2425# by the PostgreSQL backends.
    2526# we load all these up for backwards compatibility, you should use
    2627# connections['default'] instead.
    27 connection = connections[DEFAULT_DB_ALIAS]
     28class DefaultConnectionProxy(local):
     29    """
     30    Thread-local proxy for the default connection.
     31    """
     32    def __getattr__(self, item):
     33        return getattr(connections[DEFAULT_DB_ALIAS], item)
     34    def __setattr__(self, name, value):
     35        return setattr(connections[DEFAULT_DB_ALIAS], name, value)
     36connection = DefaultConnectionProxy()
    2837backend = load_backend(connection.settings_dict['ENGINE'])
    2938
    3039# Register an event that closes the database connection
  • django/db/backends/__init__.py

    diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py
    index f2bde84..0f4fc31 100644
    a b try:  
    22    import thread
    33except ImportError:
    44    import dummy_thread as thread
    5 from threading import local
    65from contextlib import contextmanager
    76
    87from django.conf import settings
    from django.utils.importlib import import_module  
    1312from django.utils.timezone import is_aware
    1413
    1514
    16 class BaseDatabaseWrapper(local):
     15class BaseDatabaseWrapper(object):
    1716    """
    1817    Represents a database connection.
    1918    """
  • django/db/utils.py

    diff --git a/django/db/utils.py b/django/db/utils.py
    index f0c13e3..41ad6df 100644
    a b  
    11import os
     2from threading import local
    23
    34from django.conf import settings
    45from django.core.exceptions import ImproperlyConfigured
    class ConnectionDoesNotExist(Exception):  
    5051class ConnectionHandler(object):
    5152    def __init__(self, databases):
    5253        self.databases = databases
    53         self._connections = {}
     54        self._connections = local()
    5455
    5556    def ensure_defaults(self, alias):
    5657        """
    class ConnectionHandler(object):  
    7374            conn.setdefault(setting, None)
    7475
    7576    def __getitem__(self, alias):
    76         if alias in self._connections:
    77             return self._connections[alias]
     77        if hasattr(self._connections, alias):
     78            return getattr(self._connections, alias)
    7879
    7980        self.ensure_defaults(alias)
    8081        db = self.databases[alias]
    8182        backend = load_backend(db['ENGINE'])
    8283        conn = backend.DatabaseWrapper(db, alias)
    83         self._connections[alias] = conn
     84        setattr(self._connections, alias, conn)
    8485        return conn
    8586
     87    def __setitem__(self, key, value):
     88        setattr(self._connections, key, value)
     89
    8690    def __iter__(self):
    8791        return iter(self.databases)
    8892
  • 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..c2344d4 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
     744class QuietWSGIRequestHandler(WSGIRequestHandler):
     745    """
     746    Just a regular WSGIRequestHandler except it doesn't log to the standard
     747    output any of the requests received, so as to not clutter the output for
     748    the tests' results.
     749    """
     750    def log_message(*args):
     751        pass
     752
     753class StoppableWSGIServer(WSGIServer):
     754    """
     755    The code in this class is borrowed from the `SocketServer.BaseServer` class
     756    in Python 2.6. The important functionality here is that the server is non-
     757    blocking and that it can be shut down at any moment. This is made possible
     758    by the server regularly polling the socket and checking if it has been
     759    asked to stop.
     760    Note for the future: Once Django stops supporting Python 2.5, this class
     761    can be removed as `WSGIServer` will have this ability to shutdown on
     762    demand.
     763    """
     764
     765    def __init__(self, *args, **kwargs):
     766        super(StoppableWSGIServer, self).__init__(*args, **kwargs)
     767        self.__is_shut_down = threading.Event()
     768        self.__serving = False
     769
     770    def serve_forever(self, poll_interval=0.5):
     771        """Handle one request at a time until shutdown.
     772
     773        Polls for shutdown every poll_interval seconds.
     774        """
     775        self.__serving = True
     776        self.__is_shut_down.clear()
     777        while self.__serving:
     778            r, w, e = select.select([self], [], [], poll_interval)
     779            if r:
     780                self._handle_request_noblock()
     781        self.__is_shut_down.set()
     782
     783    def shutdown(self):
     784        """Stops the serve_forever loop.
     785
     786        Blocks until the loop has finished. This must be called while
     787        serve_forever() is running in another thread, or it will
     788        deadlock.
     789        """
     790        self.__serving = False
     791        self.__is_shut_down.wait()
     792
     793    def handle_request(self):
     794        """Handle one request, possibly blocking.
     795        """
     796        fd_sets = select.select([self], [], [], None)
     797        if not fd_sets[0]:
     798            return
     799        self._handle_request_noblock()
     800
     801    def _handle_request_noblock(self):
     802        """Handle one request, without blocking.
     803
     804        I assume that select.select has returned that the socket is
     805        readable before this function was called, so there should be
     806        no risk of blocking in get_request().
     807        """
     808        try:
     809            request, client_address = self.get_request()
     810        except socket.error:
     811            return
     812        if self.verify_request(request, client_address):
     813            try:
     814                self.process_request(request, client_address)
     815            except Exception:
     816                self.handle_error(request, client_address)
     817                self.close_request(request)
     818
     819class MediaFilesHandler(StaticFilesHandler):
     820    """
     821    Handler for serving the media files.
     822    """
     823
     824    def get_base_dir(self):
     825        return settings.MEDIA_ROOT
     826
     827    def get_base_url(self):
     828        return settings.MEDIA_URL
     829
     830    def serve(self, request):
     831        return serve(request, self.file_path(request.path),
     832            document_root=self.get_base_dir())
     833
     834class LiveServerThread(threading.Thread):
     835    """
     836    Thread for running a live http server while the tests are running.
     837    """
     838
     839    def __init__(self, address, port, connections_override=None):
     840        self.address = address
     841        self.port = port
     842        self.is_ready = threading.Event()
     843        self.error = None
     844        self.connections_override = connections_override
     845        super(LiveServerThread, self).__init__()
     846
     847    def run(self):
     848        """
     849        Sets up live server and database and loops over handling http requests.
     850        """
     851        if self.connections_override:
     852            from django.db import connections
     853            for alias, conn in self.connections_override.items():
     854                connections[alias] = conn
     855        try:
     856            # Instantiate and start the server
     857            self.httpd = StoppableWSGIServer(
     858                (self.address, self.port), QuietWSGIRequestHandler)
     859            handler = StaticFilesHandler(MediaFilesHandler(WSGIHandler()))
     860            self.httpd.set_app(handler)
     861            self.is_ready.set()
     862            self.httpd.serve_forever()
     863        except WSGIServerException, e:
     864            self.error = e
     865            self.is_ready.set()
     866
     867    def join(self, timeout=None):
     868        self.httpd.shutdown()
     869        self.httpd.server_close()
     870        super(LiveServerThread, self).join(timeout)
     871
     872class LiveServerTestCase(TransactionTestCase):
     873    """
     874    Does basically the same as TransactionTestCase but also launches a live
     875    http server in a separate thread so that the tests may use another testing
     876    framework, such as Selenium for example, instead of the built-in dummy
     877    client.
     878    Note that it inherits from TransactionTestCase instead of TestCase because
     879    the threads do not share the same connection's cursor (unless if using
     880    in-memory sqlite).
     881    """
     882
     883    @property
     884    def live_server_url(self):
     885        return 'http://%s:%s' % (settings.LIVE_TEST_SERVER_HOST,
     886                                 settings.LIVE_TEST_SERVER_PORT)
     887
     888    @classmethod
     889    def setUpClass(cls):
     890        connections_override = {}
     891        for conn in connections.all():
     892            if (conn.settings_dict['ENGINE'] == 'django.db.backends.sqlite3'
     893                and conn.settings_dict['NAME'] == ':memory:'):
     894                connections_override[conn.alias] = conn
     895
     896        # Launch the Django live server's thread
     897        cls.server_thread = LiveServerThread(
     898            settings.LIVE_TEST_SERVER_HOST,
     899            int(settings.LIVE_TEST_SERVER_PORT),
     900            connections_override)
     901        cls.server_thread.start()
     902
     903        # Wait for the Django server to be ready
     904        cls.server_thread.is_ready.wait()
     905        if cls.server_thread.error:
     906            raise cls.server_thread.error
     907
     908        super(LiveServerTestCase, cls).setUpClass()
     909
     910    @classmethod
     911    def tearDownClass(cls):
     912        # Terminate the Django server's thread
     913        cls.server_thread.join()
     914        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..baa20a1 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 to work via a real Web browser. To allow
     129those tests to run and not be skipped, you must install the selenium_ package
     130(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_
    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/topics/testing.txt

    diff --git a/docs/topics/testing.txt b/docs/topics/testing.txt
    index 181b4ff..f5044b3 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 thing: it launches a
     1843live Django server in the background on setup, and shuts it down on teardown.
     1844This allows to use other automated test clients 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 setUp(self):
     1874            self.selenium = WebDriver()
     1875            super(MySeleniumTests, self).setUp()
     1876
     1877        def tearDown(self):
     1878            super(MySeleniumTests, self).tearDown()
     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
     1907.. note::
     1908
     1909    When running tests with an in-memory SQLite database, the same database
     1910    connection needs to be shared between the thread in which the tested code
     1911    is run and the thread in which the test case is run. By default, pysqlite
     1912    forbids connections being shared between multiple threads. To allow this
     1913    behavior you need to set the ``check_same_thread`` option in the
     1914    :setting:`DATABASES` setting to ``False``.
     1915
     1916    .. code-block:: python
     1917
     1918        DATABASES = {
     1919            'default': {
     1920                'ENGINE': 'django.db.backends.sqlite3',
     1921                'OPTIONS': {
     1922                    'check_same_thread': False,
     1923                }
     1924            }
     1925        }
    18331926
    18341927Attributes
    18351928~~~~~~~~~~
    18361929
    1837 
    18381930.. attribute:: DjangoTestSuiteRunner.option_list
    18391931
    18401932    .. 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')
  • tests/regressiontests/backends/tests.py

    diff --git a/tests/regressiontests/backends/tests.py b/tests/regressiontests/backends/tests.py
    index 936f010..be83a9a 100644
    a b  
    33from __future__ import with_statement, absolute_import
    44
    55import datetime
     6import threading
    67
    78from django.conf import settings
    89from django.core.management.color import no_style
    class FkConstraintsTests(TransactionTestCase):  
    446447                        connection.check_constraints()
    447448            finally:
    448449                transaction.rollback()
     450
     451class ThreadTests(TestCase):
     452
     453    def test_default_connection_thread_local(self):
     454        """
     455        Ensure that the default connection (i.e. django.db.connection) is
     456        different for each thread.
     457        Refs #17258.
     458        """
     459        connections_set = set()
     460        connection.cursor()
     461        connections_set.add(connection.connection)
     462        def runner():
     463            from django.db import connection
     464            connection.cursor()
     465            connections_set.add(connection.connection)
     466        for x in xrange(2):
     467            t = threading.Thread(target=runner)
     468            t.start()
     469            t.join()
     470        self.assertEquals(len(connections_set), 3)
     471
     472    def test_connections_thread_local(self):
     473        """
     474        Ensure that the connections are different for each thread.
     475        Refs #17258.
     476        """
     477        connections_set = set()
     478        for conn in connections.all():
     479            connections_set.add(conn)
     480        def runner():
     481            from django.db import connections
     482            for conn in connections.all():
     483                connections_set.add(conn)
     484        for x in xrange(2):
     485            t = threading.Thread(target=runner)
     486            t.start()
     487            t.join()
     488        self.assertEquals(len(connections_set), 6)
     489 No newline at end of file
  • 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..83f757f
    - +  
     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)
  • 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..42fafe0
    - +  
     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('')
  • tests/test_sqlite.py

    diff --git a/tests/test_sqlite.py b/tests/test_sqlite.py
    index de8bf93..5af754c 100644
    a b  
    11# This is an example test settings file for use with the Django test suite.
    22#
    33# The 'sqlite3' backend requires only the ENGINE setting (an in-
    4 # memory database will be used). All other backends will require a
    5 # NAME and potentially authentication information. See the
    6 # following section in the docs for more information:
     4# memory database will be used) and the 'check_same_thread' option set to False
     5# so that the in-memory database is shared between threads (which is required
     6# for running Selenium tests). All other backends will require a NAME and
     7# potentially authentication information. See the following section in the docs
     8# for more information:
    79#
    8 # http://docs.djangoproject.com/en/dev/internals/contributing/#unit-tests
     10# https://docs.djangoproject.com/en/dev/internals/contributing/writing-code/unit-tests/
    911#
    1012# The different databases that Django supports behave differently in certain
    1113# situations, so it is recommended to run the test suite against as many
     
    1416
    1517DATABASES = {
    1618    'default': {
    17         'ENGINE': 'django.db.backends.sqlite3'
     19        'ENGINE': 'django.db.backends.sqlite3',
     20        'OPTIONS': {
     21            'check_same_thread': False,
     22        },
    1823    },
    1924    'other': {
    2025        'ENGINE': 'django.db.backends.sqlite3',
     26        'OPTIONS': {
     27            'check_same_thread': False,
     28        },
    2129    }
    2230}
Back to Top