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

File 2879.selenium-support.5.diff, 27.3 KB (added by Tom Christie, 12 years ago)
  • django/conf/global_settings.py

    diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py
    index 6b09be2..86924c4 100644
    a b DEFAULT_EXCEPTION_REPORTER_FILTER = 'django.views.debug.SafeExceptionReporterFil  
    560560# The name of the class to use to run the test suite
    561561TEST_RUNNER = 'django.test.simple.DjangoTestSuiteRunner'
    562562
     563# For the live test server (e.g. used for running Selenium tests)
     564LIVE_TEST_SERVER_HOST = 'localhost'
     565LIVE_TEST_SERVER_PORT = 8081
     566
     567# For Selenium RC
     568SELENIUM_RC_HOST = 'localhost'
     569SELENIUM_RC_PORT = 4444
     570SELENIUM_RC_BROWSER = '*firefox'
     571
    563572############
    564573# FIXTURES #
    565574############
  • 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..176f1b8 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,
    class TransactionTestCase(SimpleTestCase):  
    364371        for db in databases:
    365372            call_command('flush', verbosity=0, interactive=False, database=db)
    366373
    367             if hasattr(self, 'fixtures'):
     374            if getattr(self, 'fixtures', None):
    368375                # We have to use this slightly awkward syntax due to the fact
    369376                # that we're using *args and **kwargs together.
    370377                call_command('loaddata', *self.fixtures,
    class TestCase(TransactionTestCase):  
    679686        Site.objects.clear_cache()
    680687
    681688        for db in databases:
    682             if hasattr(self, 'fixtures'):
     689            if getattr(self, 'fixtures', None):
    683690                call_command('loaddata', *self.fixtures,
    684691                             **{
    685692                                'verbosity': 0,
    def skipUnlessDBFeature(feature):  
    732739    """
    733740    return _deferredSkip(lambda: not getattr(connection.features, feature),
    734741                         "Database doesn't support feature %s" % feature)
     742
     743class QuietWSGIRequestHandler(WSGIRequestHandler):
     744    """
     745    Just a regular WSGIRequestHandler except it doesn't log to the standard
     746    output any of the requests received, so as to not clutter the output for
     747    the tests' results.
     748    """
     749    def log_message(*args):
     750        pass
     751
     752class StoppableWSGIServer(WSGIServer):
     753    """
     754    The code in this class is borrowed from the `SocketServer.BaseServer` class
     755    in Python 2.6. The important functionality here is that the server is non-
     756    blocking and that it can be shut down at any moment. This is made possible
     757    by the server regularly polling the socket and checking if it has been
     758    asked to stop.
     759    Note for the future: Once Django stops supporting Python 2.5, this class
     760    can be removed as `WSGIServer` will have this ability to shutdown on
     761    demand.
     762    """
     763
     764    def __init__(self, *args, **kwargs):
     765        super(StoppableWSGIServer, self).__init__(*args, **kwargs)
     766        self.__is_shut_down = threading.Event()
     767        self.__serving = False
     768
     769    def serve_forever(self, poll_interval=0.5):
     770        """Handle one request at a time until shutdown.
     771
     772        Polls for shutdown every poll_interval seconds.
     773        """
     774        self.__serving = True
     775        self.__is_shut_down.clear()
     776        while self.__serving:
     777            r, w, e = select.select([self], [], [], poll_interval)
     778            if r:
     779                self._handle_request_noblock()
     780        self.__is_shut_down.set()
     781
     782    def shutdown(self):
     783        """Stops the serve_forever loop.
     784
     785        Blocks until the loop has finished. This must be called while
     786        serve_forever() is running in another thread, or it will
     787        deadlock.
     788        """
     789        self.__serving = False
     790        self.__is_shut_down.wait()
     791
     792    def handle_request(self):
     793        """Handle one request, possibly blocking.
     794        """
     795        fd_sets = select.select([self], [], [], None)
     796        if not fd_sets[0]:
     797            return
     798        self._handle_request_noblock()
     799
     800    def _handle_request_noblock(self):
     801        """Handle one request, without blocking.
     802
     803        I assume that select.select has returned that the socket is
     804        readable before this function was called, so there should be
     805        no risk of blocking in get_request().
     806        """
     807        try:
     808            request, client_address = self.get_request()
     809        except socket.error:
     810            return
     811        if self.verify_request(request, client_address):
     812            try:
     813                self.process_request(request, client_address)
     814            except Exception:
     815                self.handle_error(request, client_address)
     816                self.close_request(request)
     817
     818class LiveServerThread(threading.Thread):
     819    """
     820    Thread for running a live http server while the tests are running.
     821    """
     822
     823    def __init__(self, address, port, fixtures):
     824        self.address = address
     825        self.port = port
     826        self.fixtures = fixtures
     827        self.is_ready = threading.Event()
     828        self.error = None
     829        super(LiveServerThread, self).__init__()
     830
     831    def run(self):
     832        """
     833        Sets up live server and database and loops over handling http requests.
     834        """
     835        try:
     836            # Instantiate and start the server
     837            self.httpd = StoppableWSGIServer(
     838                (self.address, self.port), QuietWSGIRequestHandler)
     839            handler = StaticFilesHandler(WSGIHandler())
     840            self.httpd.set_app(handler)
     841
     842            # If the database is in memory we must reload the data in this new
     843            # thread.
     844            if (settings.DATABASES['default']['ENGINE'] == 'django.db.backends.sqlite3' or
     845                settings.DATABASES['default']['TEST_NAME']):
     846                connection.creation.create_test_db(0)
     847                # Import the fixtures into the test database
     848                if getattr(self, 'fixtures', None):
     849                    call_command('loaddata', *self.fixtures,
     850                        **{'verbosity': 0})
     851            self.is_ready.set()
     852            self.httpd.serve_forever()
     853        except WSGIServerException, e:
     854            self.error = e
     855            self.is_ready.set()
     856
     857    def join(self, timeout=None):
     858        self.httpd.shutdown()
     859        self.httpd.server_close()
     860        super(LiveServerThread, self).join(timeout)
     861
     862class LiveServerTestCase(TestCase):
     863    """
     864    Does basically the same as TestCase but also launches a live http server in
     865    a separate thread so that the tests may use another testing framework, such
     866    as Selenium for example, instead of the built-in dummy client.
     867    """
     868
     869    fixtures = []
     870
     871    @property
     872    def live_test_server_url(self):
     873        return 'http://%s:%s' % (settings.LIVE_TEST_SERVER_HOST,
     874                                 settings.LIVE_TEST_SERVER_PORT)
     875
     876    def setUp(self):
     877        # Launch the Django live server's thread
     878        self.server_thread = LiveServerThread(
     879            settings.LIVE_TEST_SERVER_HOST,
     880            int(settings.LIVE_TEST_SERVER_PORT),
     881            fixtures=self.fixtures)
     882        self.server_thread.start()
     883
     884        # Wait for the Django server to be ready
     885        self.server_thread.is_ready.wait()
     886        if self.server_thread.error:
     887            raise self.server_thread.error
     888
     889        super(LiveServerTestCase, self).setUp()
     890
     891    def tearDown(self):
     892        # Terminate the Django server's thread
     893        self.server_thread.join()
     894        super(LiveServerTestCase, self).tearDown()
  • 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..cec408c 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 to work via a real Web browser. To allow
     129those tests to run and not be skipped, you must install the selenium_ package
     130into your Python path and download the `Selenium server (>2.12.0)`_. The
     131Selenium server must then be started with the following command:
     132
     133.. code-block:: bash
     134
     135    java -jar selenium-server-standalone-2.12.0.jar
     136
     137If you're using linux, you may also run the tests in headless mode (i.e. with a
     138virtual display) using with the following command instead:
     139
     140.. code-block:: bash
     141
     142    Xvfb :99 -ac & && DISPLAY=:99 java -jar selenium-server-standalone-2.12.0.jar
     143
     144Then, run the tests normally, for example:
     145
     146.. code-block:: bash
     147
     148    ./runtests.py --settings=test_sqlite admin_inlines
     149
    125150Running all the tests
    126151~~~~~~~~~~~~~~~~~~~~~
    127152
    dependencies:  
    135160*  setuptools_
    136161*  memcached_, plus a :ref:`supported Python binding <memcached>`
    137162*  gettext_ (:ref:`gettext_on_windows`)
     163*  selenium_ plus the `Selenium server (>2.12.0)`_
    138164
    139165If you want to test the memcached cache backend, you'll also need to define
    140166a :setting:`CACHES` setting that points at your memcached instance.
    associated tests will be skipped.  
    149175.. _setuptools: http://pypi.python.org/pypi/setuptools/
    150176.. _memcached: http://www.danga.com/memcached/
    151177.. _gettext: http://www.gnu.org/software/gettext/manual/gettext.html
     178.. _selenium: http://pypi.python.org/pypi/selenium
     179.. _Selenium server (>2.12.0): http://seleniumhq.org/download/
    152180
    153181.. _contrib-apps:
    154182
  • docs/ref/settings.txt

    diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt
    index 20366e3..aaaa48c 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: ``8080``
     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
    Default: ``''`` (Empty string)  
    496520The port to use when connecting to the database. An empty string means the
    497521default port. Not used with SQLite.
    498522
     523.. setting:: SELENIUM_RC_HOST
     524
     525SELENIUM_RC_HOST
     526~~~~~~~~~~~~~~~~~~~~
     527
     528Default: ``localhost``
     529
     530Host address where the Selenium server can be accessed.
     531
     532.. setting:: SELENIUM_RC_PORT
     533
     534SELENIUM_RC_PORT
     535~~~~~~~~~~~~~~~~~~~~
     536
     537Default: ``4444``
     538
     539Port where the Selenium server can be accessed.
     540
     541.. setting:: SELENIUM_RC_BROWSER
     542
     543SELENIUM_RC_BROWSER
     544~~~~~~~~~~~~~~~~~~~
     545
     546Default: ``'*firefox'``
     547
     548Browser to be used when running Selenium tests. Note that the prefixing star
     549('``*``') is required. Possible values include:
     550
     551*  ``'*firefox'``
     552*  ``'*googlechrome'``
     553*  ``'*safari'``
     554*  ``'*mock'``
     555*  ``'*firefoxproxy'``
     556*  ``'*pifirefox'``
     557*  ``'*chrome'``
     558*  ``'*iexploreproxy'``
     559*  ``'*iexplore'``
     560*  ``'*safariproxy'``
     561*  ``'*konqueror'``
     562*  ``'*firefox2'``
     563*  ``'*firefox3'``
     564*  ``'*firefoxchrome'``
     565*  ``'*piiexplore'``
     566*  ``'*opera'``
     567*  ``'*iehta'``
     568*  ``'*custom'``
     569
    499570.. setting:: USER
    500571
    501572USER
  • docs/topics/testing.txt

    diff --git a/docs/topics/testing.txt b/docs/topics/testing.txt
    index dc5bf7e..a25b0af 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 sections on
     593  :class:`~django.test.testcases.LiveServerTestCase` and
     594  :class:`~django.test.testcases.SeleniumRCTestCase`.
    592595
    593596A comprehensive test suite should use a combination of both test types.
    594597
    595 .. _Twill: http://twill.idyll.org/
    596 .. _Selenium: http://seleniumhq.org/
    597 
    598598Overview and a quick example
    599599~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    600600
    set up, execute and tear down the test suite.  
    18281828    those options will be added to the list of command-line options that
    18291829    the :djadmin:`test` command can use.
    18301830
     1831Live test server
     1832----------------
     1833
     1834.. currentmodule:: django.test
     1835
     1836.. versionadded::1.4
     1837
     1838.. class:: LiveServerTestCase()
     1839
     1840``LiveServerTestCase`` does basically the same as
     1841:class:`~django.test.TestCase` with one extra thing: it launches a live Django
     1842server in the background on setup, and shuts it down on teardown. This allows
     1843to use other automated test clients than the
     1844:ref:`Django dummy client <test-client>` such as, for example, the Selenium_ or
     1845Windmill_ clients.
     1846
     1847You may control which host and port the live server will run at with
     1848respectively the :setting:`LIVE_TEST_SERVER_HOST` and
     1849:setting:`LIVE_TEST_SERVER_PORT` settings.
     1850
     1851Fixtures defined with the :attr:`~TestCase.fixtures` class attribute will get
     1852loaded at the beginning of each test if you require some initial data to be
     1853present.
     1854
     1855See the section on :class:`SeleniumRCTestCase` for a concrete example of how
     1856``LiveServerTestCase`` can be used.
     1857
     1858Selenium tests
     1859--------------
     1860
     1861.. versionadded::1.4
     1862
     1863.. class:: SeleniumRCTestCase()
     1864
     1865Django provides out-of-the box support for Selenium_ tests with the
     1866``SeleniumRCTestCase`` class. Django itself uses it in its own test suite for
     1867some ``contrib.admin`` tests.
     1868
     1869``SeleniumRCTestCase`` inherits from :class:`LiveServerTestCase`, which means
     1870that a live server is available for the duration of each test. That live server
     1871can then be accessed by the Selenium client to execute a series of functional
     1872tests inside a browser, simulating a real user's actions.
     1873
     1874To get started with Selenium tests, your environment needs to satisfy a number
     1875of requirements:
     1876
     1877*  You must install the `selenium package`_ into your Python path:
     1878
     1879   .. code-block:: bash
     1880
     1881       pip install selenium
     1882
     1883*  You must download the `Selenium server (>2.12.0)`_, and then start it with
     1884   the following command:
     1885
     1886   .. code-block:: bash
     1887
     1888       java -jar selenium-server-standalone-2.12.0.jar
     1889
     1890   If you'd like to run the selenium server at a different port than the
     1891   standard one (i.e. 4444) you may do so as follows:
     1892
     1893   .. code-block:: bash
     1894
     1895       java -jar selenium-server-standalone-2.12.0.jar -port 1234
     1896
     1897*  If the selenium server isn't running at the standard host address or port,
     1898   you need to provide the exact details using respectively the
     1899   :setting:`SELENIUM_RC_HOST` and :setting:`SELENIUM_RC_PORT`
     1900   settings.
     1901
     1902If those requirements are not satisfied, then the tests will be skipped.
     1903
     1904By the default, the tests are run in Firefox_. If you do not have Firefox
     1905installed or simply wish to run the tests in another browser, you may do so by
     1906changing the :setting:`SELENIUM_RC_BROWSER` setting.
     1907
     1908Once your environment is set up, you may start writing your Selenium tests.
     1909Here's an example of how to control the Selenium client (accessible via
     1910``self.selenium``):
     1911
     1912.. code-block:: python
     1913
     1914    from django.contrib.selenium import SeleniumRCTestCase
     1915
     1916    class MySeleniumTests(SeleniumRCTestCase):
     1917
     1918        fixtures = ['user-data.json']
     1919
     1920        def test_login(self):
     1921            self.selenium.open('/login/')
     1922            self.selenium.type('username', username)
     1923            self.selenium.type('password', password)
     1924            self.selenium.click("//input[@value='Log in']")
     1925
     1926This is just a tiny fraction of what the Selenium client can do. Check out the
     1927`full reference`_ for more details.
     1928
     1929.. _Windmill: http://www.getwindmill.com/
     1930.. _Selenium: http://seleniumhq.org/
     1931.. _selenium package: http://pypi.python.org/pypi/selenium
     1932.. _Selenium server (>2.12.0): http://seleniumhq.org/download/
     1933.. _full reference: http://selenium.googlecode.com/svn/trunk/docs/api/py/selenium/selenium.selenium.html
     1934.. _Firefox: http://www.mozilla.com/firefox/
    18311935
    18321936Attributes
    18331937~~~~~~~~~~
    18341938
    1835 
    18361939.. attribute:: DjangoTestSuiteRunner.option_list
    18371940
    18381941    .. versionadded:: 1.4
  • tests/regressiontests/admin_inlines/tests.py

    diff --git a/tests/regressiontests/admin_inlines/tests.py b/tests/regressiontests/admin_inlines/tests.py
    index c2e3bbc..33da854 100644
    a b from __future__ import absolute_import  
    33from django.contrib.admin.helpers import InlineAdminForm
    44from django.contrib.auth.models import User, Permission
    55from django.contrib.contenttypes.models import ContentType
     6from django.contrib.selenium import SeleniumRCTestCase
    67from django.test import TestCase
    78
    89# local test models
    class TestInlinePermissions(TestCase):  
    380381        self.assertContains(response, 'value="4" id="id_inner2_set-TOTAL_FORMS"')
    381382        self.assertContains(response, '<input type="hidden" name="inner2_set-0-id" value="%i"' % self.inner2_id)
    382383        self.assertContains(response, 'id="id_inner2_set-0-DELETE"')
     384
     385
     386class SeleniumTests(SeleniumRCTestCase):
     387    fixtures = ['admin-views-users.xml']
     388    urls = "regressiontests.admin_inlines.urls"
     389
     390    def admin_login(self, username, password):
     391        """
     392        Helper function to log into the admin.
     393        """
     394        self.selenium.open('/admin/')
     395        self.selenium.type('username', username)
     396        self.selenium.type('password', password)
     397        self.selenium.click("//input[@value='Log in']")
     398        self.selenium.wait_for_page_to_load(3000)
     399
     400    def test_add_inlines(self):
     401        """
     402        Ensure that the "Add another XXX" link correctly adds items to the
     403        inline form.
     404        """
     405        self.admin_login(username='super', password='secret')
     406        self.selenium.open('/admin/admin_inlines/titlecollection/add/')
     407
     408        # Check that there's only one inline to start with and that it has the
     409        # correct ID.
     410        self.failUnlessEqual(self.selenium.get_css_count(
     411            'css=#title_set-group table tr.dynamic-title_set'), 1)
     412        self.failUnless(self.selenium.get_attribute(
     413            'css=.dynamic-title_set:nth-of-type(1)@id'), 'title_set-0')
     414        self.failUnless(self.selenium.is_element_present(
     415            'css=form#titlecollection_form tr.dynamic-title_set#title_set-0 input[name=title_set-0-title1]'))
     416        self.failUnless(self.selenium.is_element_present(
     417            'css=form#titlecollection_form tr.dynamic-title_set#title_set-0 input[name=title_set-0-title2]'))
     418
     419        # Add an inline
     420        self.selenium.click("link=Add another Title")
     421
     422        # Check that the inline has been added, that it has the right id, and
     423        # that it contains the right fields.
     424        self.failUnlessEqual(self.selenium.get_css_count(
     425            'css=#title_set-group table tr.dynamic-title_set'), 2)
     426        self.failUnless(self.selenium.get_attribute(
     427            'css=.dynamic-title_set:nth-of-type(2)@id'), 'title_set-1')
     428        self.failUnless(self.selenium.is_element_present(
     429            'css=form#titlecollection_form tr.dynamic-title_set#title_set-1 input[name=title_set-1-title1]'))
     430        self.failUnless(self.selenium.is_element_present(
     431            'css=form#titlecollection_form tr.dynamic-title_set#title_set-1 input[name=title_set-1-title2]'))
     432
     433        # Let's add another one to be sure
     434        self.selenium.click("link=Add another Title")
     435        self.failUnlessEqual(self.selenium.get_css_count(
     436            'css=#title_set-group table tr.dynamic-title_set'), 3)
     437        self.failUnless(self.selenium.get_attribute(
     438            'css=.dynamic-title_set:nth-of-type(3)@id'), 'title_set-2')
     439        self.failUnless(self.selenium.is_element_present(
     440            'css=form#titlecollection_form tr.dynamic-title_set#title_set-2 input[name=title_set-2-title1]'))
     441        self.failUnless(self.selenium.is_element_present(
     442            'css=form#titlecollection_form tr.dynamic-title_set#title_set-2 input[name=title_set-2-title2]'))
     443
     444    def test_delete_inlines(self):
     445        self.admin_login(username='super', password='secret')
     446        self.selenium.open('/admin/admin_inlines/titlecollection/add/')
     447
     448        # Add a few inlines
     449        self.selenium.click("link=Add another Title")
     450        self.selenium.click("link=Add another Title")
     451        self.selenium.click("link=Add another Title")
     452        self.selenium.click("link=Add another Title")
     453        self.failUnlessEqual(self.selenium.get_css_count(
     454            'css=#title_set-group table tr.dynamic-title_set'), 5)
     455        self.failUnless(self.selenium.is_element_present(
     456            'css=form#titlecollection_form tr.dynamic-title_set#title_set-0'))
     457        self.failUnless(self.selenium.is_element_present(
     458            'css=form#titlecollection_form tr.dynamic-title_set#title_set-1'))
     459        self.failUnless(self.selenium.is_element_present(
     460            'css=form#titlecollection_form tr.dynamic-title_set#title_set-2'))
     461        self.failUnless(self.selenium.is_element_present(
     462            'css=form#titlecollection_form tr.dynamic-title_set#title_set-3'))
     463        self.failUnless(self.selenium.is_element_present(
     464            'css=form#titlecollection_form tr.dynamic-title_set#title_set-4'))
     465
     466        # Click on a few delete buttons
     467        self.selenium.click(
     468            'css=form#titlecollection_form tr.dynamic-title_set#title_set-1 td.delete a')
     469        self.selenium.click(
     470            'css=form#titlecollection_form tr.dynamic-title_set#title_set-2 td.delete a')
     471        # Verify that they're gone and that the IDs have been re-sequenced
     472        self.failUnlessEqual(self.selenium.get_css_count(
     473            'css=#title_set-group table tr.dynamic-title_set'), 3)
     474        self.failUnless(self.selenium.is_element_present(
     475            'css=form#titlecollection_form tr.dynamic-title_set#title_set-0'))
     476        self.failUnless(self.selenium.is_element_present(
     477            'css=form#titlecollection_form tr.dynamic-title_set#title_set-1'))
     478        self.failUnless(self.selenium.is_element_present(
     479            'css=form#titlecollection_form tr.dynamic-title_set#title_set-2'))
  • tests/regressiontests/admin_widgets/tests.py

    diff --git a/tests/regressiontests/admin_widgets/tests.py b/tests/regressiontests/admin_widgets/tests.py
    index 08a1a59..0d22710 100644
    a b from django.core.files.storage import default_storage  
    1111from django.core.files.uploadedfile import SimpleUploadedFile
    1212from django.db.models import DateField
    1313from django.test import TestCase as DjangoTestCase
     14from django.contrib.selenium import SeleniumRCTestCase
    1415from django.utils import translation
    1516from django.utils.html import conditional_escape
    1617from django.utils.unittest import TestCase
    class RelatedFieldWidgetWrapperTests(DjangoTestCase):  
    372373        # Used to fail with a name error.
    373374        w = widgets.RelatedFieldWidgetWrapper(w, rel, widget_admin_site)
    374375        self.assertFalse(w.can_add_related)
     376
     377
     378class SeleniumTests(SeleniumRCTestCase):
     379    fixtures = ['admin-widgets-users.xml']
     380    urls = "regressiontests.admin_widgets.urls"
     381
     382    def admin_login(self, username, password):
     383        """
     384        Helper function to log into the admin.
     385        """
     386        self.selenium.open('/')
     387        self.selenium.type('username', username)
     388        self.selenium.type('password', password)
     389        self.selenium.click("//input[@value='Log in']")
     390        self.selenium.wait_for_page_to_load(3000)
     391
     392    def get_css_value(self, selector, attribute):
     393        """
     394        Helper function that returns the value for the CSS attribute of an
     395        DOM element specified by the given selector.
     396        """
     397        return self.selenium.get_eval(
     398            'selenium.browserbot.getCurrentWindow().django'
     399            '.jQuery("%s").css("%s")' % (selector, attribute))
     400
     401    def test_show_hide_date_time_picker_widgets(self):
     402        """
     403        Ensure that pressing the ESC key closes the date and time picker
     404        widgets.
     405        Refs #17064.
     406        """
     407        self.admin_login(username='super', password='secret')
     408        # Open a page that has a date and time picker widgets
     409        self.selenium.open('/admin_widgets/member/add/')
     410
     411        # First, with the date picker widget ---------------------------------
     412        # Check that the date picker is hidden
     413        self.assertEqual(
     414            self.get_css_value('#calendarbox0', 'display'), 'none')
     415        # Click the calendar icon
     416        self.selenium.click('id=calendarlink0')
     417        # Check that the date picker is visible
     418        self.assertEqual(
     419            self.get_css_value('#calendarbox0', 'display'), 'block')
     420        # Press the ESC key
     421        self.selenium.key_up('css=html', '27')
     422        # Check that the date picker is hidden again
     423        self.assertEqual(
     424            self.get_css_value('#calendarbox0', 'display'), 'none')
     425
     426        # Then, with the time picker widget ----------------------------------
     427        # Check that the time picker is hidden
     428        self.assertEqual(
     429            self.get_css_value('#clockbox0', 'display'), 'none')
     430        # Click the time icon
     431        self.selenium.click('id=clocklink0')
     432        # Check that the time picker is visible
     433        self.assertEqual(
     434            self.get_css_value('#clockbox0', 'display'), 'block')
     435        # Press the ESC key
     436        self.selenium.key_up('css=html', '27')
     437        # Check that the time picker is hidden again
     438        self.assertEqual(
     439            self.get_css_value('#clockbox0', 'display'), 'none')
Back to Top