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

File 2879.selenium-support.3.diff, 24.6 KB (added by Julien Phalip, 13 years ago)

Works nicer with Python 2.7

  • django/conf/global_settings.py

    diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py
    index 6b09be2..0ed3773 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_ADDRESS = 'localhost'
     565LIVE_TEST_SERVER_PORT = 8080
     566
     567# For Selenium
     568SELENIUM_SERVER_ADDRESS = 'localhost'
     569SELENIUM_SERVER_PORT = 4444
     570SELENIUM_BROWSER = '*firefox'
     571
    563572############
    564573# FIXTURES #
    565574############
  • django/test/testcases.py

    diff --git a/django/test/testcases.py b/django/test/testcases.py
    index ee22ac2..30eab11 100644
    a b  
    11from __future__ import with_statement
    22
     3import httplib
    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
    1116from django.core.exceptions import ValidationError
     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.utils.unittest import skipUnless
    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
     745class QuietWSGIRequestHandler(WSGIRequestHandler):
     746    """
     747    Just a regular WSGIRequestHandler except it doesn't log to the standard
     748    output any of the requests received, so as to not clutter the output for
     749    the tests' results.
     750    """
     751    def log_message(*args):
     752        pass
     753
     754class StoppableWSGIServer(WSGIServer):
     755    """
     756    The code in this class is borrowed from the `SocketServer.BaseServer` class
     757    in Python 2.6. The important functionality here is that the server is non-
     758    blocking and that it can be shut down at any moment. This is made possible
     759    by the server regularly polling the socket and checking if it has been
     760    asked to stop.
     761    Note for the future: Once Django stops supporting Python 2.5, this class
     762    can be removed as `WSGIServer` will have this ability to shutdown on
     763    demand.
     764    """
     765
     766    def __init__(self, *args, **kwargs):
     767        super(StoppableWSGIServer, self).__init__(*args, **kwargs)
     768        self.__is_shut_down = threading.Event()
     769        self.__serving = False
     770
     771    def serve_forever(self, poll_interval=0.5):
     772        """Handle one request at a time until shutdown.
     773
     774        Polls for shutdown every poll_interval seconds.
     775        """
     776        self.__serving = True
     777        self.__is_shut_down.clear()
     778        while self.__serving:
     779            r, w, e = select.select([self], [], [], poll_interval)
     780            if r:
     781                self._handle_request_noblock()
     782        self.__is_shut_down.set()
     783
     784    def shutdown(self):
     785        """Stops the serve_forever loop.
     786
     787        Blocks until the loop has finished. This must be called while
     788        serve_forever() is running in another thread, or it will
     789        deadlock.
     790        """
     791        self.__serving = False
     792        self.__is_shut_down.wait()
     793
     794    def handle_request(self):
     795        """Handle one request, possibly blocking.
     796        """
     797        fd_sets = select.select([self], [], [], None)
     798        if not fd_sets[0]:
     799            return
     800        self._handle_request_noblock()
     801
     802    def _handle_request_noblock(self):
     803        """Handle one request, without blocking.
     804
     805        I assume that select.select has returned that the socket is
     806        readable before this function was called, so there should be
     807        no risk of blocking in get_request().
     808        """
     809        try:
     810            request, client_address = self.get_request()
     811        except socket.error:
     812            return
     813        if self.verify_request(request, client_address):
     814            try:
     815                self.process_request(request, client_address)
     816            except Exception:
     817                self.handle_error(request, client_address)
     818                self.close_request(request)
     819
     820class LiveServerThread(threading.Thread):
     821    """
     822    Thread for running a live http server while the tests are running.
     823    """
     824
     825    def __init__(self, address, port, fixtures):
     826        self.address = address
     827        self.port = port
     828        self.fixtures = fixtures
     829        self.is_ready = threading.Event()
     830        self.error = None
     831        super(LiveServerThread, self).__init__()
     832
     833    def run(self):
     834        """
     835        Sets up live server and database and loops over handling http requests.
     836        """
     837        try:
     838            # Instantiate and start the server
     839            self.httpd = StoppableWSGIServer(
     840                (self.address, self.port), QuietWSGIRequestHandler)
     841            handler = StaticFilesHandler(WSGIHandler())
     842            self.httpd.set_app(handler)
     843
     844            # If the database is in memory we must reload the data in this new
     845            # thread.
     846            if (settings.DATABASES['default']['ENGINE'] == 'django.db.backends.sqlite3' or
     847                settings.DATABASES['default']['TEST_NAME']):
     848                connection.creation.create_test_db(0)
     849                # Import the fixtures into the test database
     850                if hasattr(self, 'fixtures'):
     851                    call_command('loaddata', *self.fixtures,
     852                        **{'verbosity': 0})
     853            self.is_ready.set()
     854            self.httpd.serve_forever()
     855        except WSGIServerException, e:
     856            self.error = e
     857            self.is_ready.set()
     858
     859    def join(self, timeout=None):
     860        self.httpd.shutdown()
     861        self.httpd.server_close()
     862        super(LiveServerThread, self).join(timeout)
     863
     864class LiveServerTestCase(TestCase):
     865    """
     866    Does basically the same as TestCase but also launches a live http server in
     867    a separate thread so that the tests may use another testing framework, such
     868    as Selenium for example, instead of the built-in dummy client.
     869    """
     870
     871    fixtures = []
     872
     873    def setUp(self):
     874        # Launch the Django live server's thread
     875        self.server_thread = LiveServerThread(
     876            settings.LIVE_TEST_SERVER_ADDRESS,
     877            int(settings.LIVE_TEST_SERVER_PORT),
     878            fixtures=self.fixtures)
     879        self.server_thread.start()
     880        super(LiveServerTestCase, self).setUp()
     881
     882    def tearDown(self):
     883        # Terminate the Django server's thread
     884        self.server_thread.join()
     885        super(LiveServerTestCase, self).tearDown()
     886
     887try:
     888    # Check if the 'selenium' package is installed
     889    from selenium import selenium
     890    selenium_installed = True
     891except ImportError:
     892    selenium_installed = False
     893
     894# Check if the Selenium server is running
     895try:
     896    conn = httplib.HTTPConnection(settings.SELENIUM_SERVER_ADDRESS,
     897        settings.SELENIUM_SERVER_PORT)
     898    try:
     899        conn.request("GET", "/selenium-server/driver/", '', {})
     900    finally:
     901        conn.close()
     902    selenium_server_running = True
     903except socket.error:
     904    selenium_server_running = False
     905
     906class SeleniumTestCase(LiveServerTestCase):
     907    """
     908    Does basically the same as TestServerTestCase but also connects to the
     909    Selenium server. The selenium client is then available with
     910    'self.selenium'. The requirements are to have the 'selenium' installed in
     911    the python path and to have the Selenium server running. If those
     912    requirements are not filled then the tests will be skipped.
     913    """
     914
     915    def setUp(self):
     916        super(SeleniumTestCase, self).setUp()
     917        # Launch the Selenium server
     918        selenium_browser_url = 'http://%s:%s' % (
     919            settings.LIVE_TEST_SERVER_ADDRESS, settings.LIVE_TEST_SERVER_PORT)
     920        self.selenium = selenium(
     921            settings.SELENIUM_SERVER_ADDRESS,
     922            int(settings.SELENIUM_SERVER_PORT),
     923            settings.SELENIUM_BROWSER,
     924            selenium_browser_url)
     925        self.selenium.start()
     926        # Wait for the Django server to be ready
     927        self.server_thread.is_ready.wait()
     928        if self.server_thread.error:
     929            raise self.server_thread.error
     930
     931    def tearDown(self):
     932        super(SeleniumTestCase, self).tearDown()
     933        self.selenium.stop()
     934
     935SeleniumTestCase = skipUnless(selenium_installed,
     936    'The \'selenium\' package isn\'t installed')(SeleniumTestCase)
     937SeleniumTestCase = skipUnless(selenium_server_running,
     938    'Can\'t connect to the Selenium server using address %s and port %s' % (
     939    settings.SELENIUM_SERVER_ADDRESS, settings.SELENIUM_SERVER_PORT)
     940)(SeleniumTestCase)
     941 No newline at end of file
  • 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..87a7d7f 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 the 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
     137You may also run the tests headless (i.e. with a virtual display). On Linux:
     138
     139    Xvfb :99 -ac & && DISPLAY=:99 java -jar selenium-server-standalone-2.12.0.jar
     140
     141*TODO: Running headless on the Mac and Windows...*
     142
     143Then, run the tests normally, for example:
     144
     145.. code-block:: bash
     146
     147    ./runtests.py --settings=test_sqlite admin_inlines
     148
    125149Running all the tests
    126150~~~~~~~~~~~~~~~~~~~~~
    127151
    dependencies:  
    135159*  setuptools_
    136160*  memcached_, plus a :ref:`supported Python binding <memcached>`
    137161*  gettext_ (:ref:`gettext_on_windows`)
     162*  selenium_ plus the `Selenium server (>2.12.0)`_
    138163
    139164If you want to test the memcached cache backend, you'll also need to define
    140165a :setting:`CACHES` setting that points at your memcached instance.
    associated tests will be skipped.  
    149174.. _setuptools: http://pypi.python.org/pypi/setuptools/
    150175.. _memcached: http://www.danga.com/memcached/
    151176.. _gettext: http://www.gnu.org/software/gettext/manual/gettext.html
     177.. _selenium: http://pypi.python.org/pypi/selenium
     178.. _Selenium server (>2.12.0): http://seleniumhq.org/download/
    152179
    153180.. _contrib-apps:
    154181
  • docs/ref/settings.txt

    diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt
    index 20366e3..5fed888 100644
    a b all cache keys used by the Django server.  
    195195
    196196See the :ref:`cache documentation <cache_key_prefixing>` for more information.
    197197
     198LIVE_TEST_SERVER_ADDRESS
     199~~~~~~~~~~~~~~~~~~~~~~~~
     200
     201Default: ``'localhost'``
     202
     203TODO: Description...
     204
     205LIVE_TEST_SERVER_PORT
     206~~~~~~~~~~~~~~~~~~~~~
     207
     208Default: ``8080``
     209
     210TODO: Description...
     211
    198212.. setting:: CACHES-LOCATION
    199213
    200214LOCATION
    default port. Not used with SQLite.  
    498512
    499513.. setting:: USER
    500514
     515SELENIUM_SERVER_ADDRESS
     516~~~~~~~~~~~~~~~~~~~~~~~
     517
     518Default: ``localhost``
     519
     520Address where the Selenium server can be accessed.
     521
     522SELENIUM_SERVER_PORT
     523~~~~~~~~~~~~~~~~~~~~
     524
     525Default: ``4444``
     526
     527Port where the Selenium server can be accessed.
     528
     529SELENIUM_BROWSER
     530~~~~~~~~~~~~~~~~
     531
     532Default: ``'*firefox'``
     533
     534Browser to be used when running Selenium tests. Note that the prefixing star
     535('``*``') is required. Possible values include:
     536
     537*  ``'*firefox'``
     538*  ``'*googlechrome'``
     539*  ``'*safari'``
     540*  ``'*mock'``
     541*  ``'*firefoxproxy'``
     542*  ``'*pifirefox'``
     543*  ``'*chrome'``
     544*  ``'*iexploreproxy'``
     545*  ``'*iexplore'``
     546*  ``'*safariproxy'``
     547*  ``'*konqueror'``
     548*  ``'*firefox2'``
     549*  ``'*firefox3'``
     550*  ``'*firefoxchrome'``
     551*  ``'*piiexplore'``
     552*  ``'*opera'``
     553*  ``'*iehta'``
     554*  ``'*custom'``
     555
    501556USER
    502557~~~~
    503558
  • docs/topics/testing.txt

    diff --git a/docs/topics/testing.txt b/docs/topics/testing.txt
    index dc5bf7e..47638f3 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.SeleniumTestCase`.
    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.testcases
     1835
     1836.. versionadded::1.4
     1837
     1838.. class:: LiveServerTestCase()
     1839
     1840**This is a stub**
     1841
     1842``LiveServerTestCase`` does basically the same as ``TestCase`` but also
     1843launches a live http server in a separate thread so that the tests may use
     1844another testing framework, such as Selenium_ or Windmill_ for example, instead
     1845of the built-in :ref:`dummy client <test-client>`.
     1846
     1847Selenium
     1848--------
     1849
     1850.. versionadded::1.4
     1851
     1852.. class:: SeleniumTestCase()
     1853
     1854**This is a stub**
     1855
     1856Django provides out-of-the box support for Selenium_, as Django itself uses it
     1857in its own test suite for the admin.
     1858
     1859TODO:
     1860
     1861*  settings
     1862*  requirements
     1863*  code sample
     1864
     1865.. _Windmill: http://www.getwindmill.com/
     1866.. _Selenium: http://seleniumhq.org/
    18311867
    18321868Attributes
    18331869~~~~~~~~~~
  • tests/regressiontests/admin_inlines/tests.py

    diff --git a/tests/regressiontests/admin_inlines/tests.py b/tests/regressiontests/admin_inlines/tests.py
    index c2e3bbc..942ece6 100644
    a b from django.test import TestCase  
    77
    88# local test models
    99from .admin import InnerInline
     10from django.test.testcases import SeleniumTestCase
    1011from .models import (Holder, Inner, Holder2, Inner2, Holder3, Inner3, Person,
    1112    OutfitItem, Fashionista, Teacher, Parent, Child, Author, Book)
    1213
    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(SeleniumTestCase):
     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'))
     480 No newline at end of file
  • tests/regressiontests/admin_widgets/tests.py

    diff --git a/tests/regressiontests/admin_widgets/tests.py b/tests/regressiontests/admin_widgets/tests.py
    index 08a1a59..c0704d5 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.test.testcases import SeleniumTestCase
    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(SeleniumTestCase):
     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')
     440 No newline at end of file
Back to Top