Ticket #2879: 2879.selenium-support.12.diff
File 2879.selenium-support.12.diff, 45.9 KB (added by , 13 years ago) |
---|
-
new file django/contrib/admin/tests.py
diff --git a/django/contrib/admin/tests.py b/django/contrib/admin/tests.py new file mode 100644 index 0000000..99f6f55
- + 1 import sys 2 3 from django.test import LiveServerTestCase 4 from django.utils.importlib import import_module 5 from django.utils.unittest import SkipTest 6 7 class AdminSeleniumWebDriverTestCase(LiveServerTestCase): 8 9 webdriver_class = 'selenium.webdriver.firefox.webdriver.WebDriver' 10 11 @classmethod 12 def setUpClass(cls): 13 if sys.version_info < (2, 6): 14 raise SkipTest('Selenium Webdriver does not support Python < 2.6.') 15 try: 16 # Import and start the WebDriver class. 17 module, attr = cls.webdriver_class.rsplit('.', 1) 18 mod = import_module(module) 19 WebDriver = getattr(mod, attr) 20 cls.selenium = WebDriver() 21 except Exception: 22 raise SkipTest('Selenium webdriver "%s" not installed or not ' 23 'operational.' % cls.webdriver_class) 24 super(AdminSeleniumWebDriverTestCase, cls).setUpClass() 25 26 @classmethod 27 def tearDownClass(cls): 28 super(AdminSeleniumWebDriverTestCase, cls).tearDownClass() 29 if hasattr(cls, 'selenium'): 30 cls.selenium.quit() 31 32 def admin_login(self, username, password, login_url='/admin/'): 33 """ 34 Helper function to log into the admin. 35 """ 36 self.selenium.get('%s%s' % (self.live_server_url, login_url)) 37 username_input = self.selenium.find_element_by_name("username") 38 username_input.send_keys(username) 39 password_input = self.selenium.find_element_by_name("password") 40 password_input.send_keys(password) 41 self.selenium.find_element_by_xpath('//input[@value="Log in"]').click() 42 43 def get_css_value(self, selector, attribute): 44 """ 45 Helper function that returns the value for the CSS attribute of an 46 DOM element specified by the given selector. Uses the jQuery that ships 47 with Django. 48 """ 49 return self.selenium.execute_script( 50 'return django.jQuery("%s").css("%s")' % (selector, attribute)) 51 No newline at end of file -
django/core/management/commands/test.py
diff --git a/django/core/management/commands/test.py b/django/core/management/commands/test.py index 2a6dbfc..1a2a469 100644
a b 1 import sys 2 import os 3 from optparse import make_option, OptionParser 4 1 5 from django.conf import settings 2 6 from django.core.management.base import BaseCommand 3 from optparse import make_option, OptionParser4 import sys5 7 from django.test.utils import get_runner 6 8 7 9 class Command(BaseCommand): … … class Command(BaseCommand): 12 14 help='Tells Django to stop running the test suite after first failed test.'), 13 15 make_option('--testrunner', action='store', dest='testrunner', 14 16 help='Tells Django to use specified test runner class instead of the one '+ 15 'specified by the TEST_RUNNER setting.') 17 'specified by the TEST_RUNNER setting.'), 18 make_option('--liveserver', action='store', dest='liveserver', default=None, 19 help='Overrides the default address where the live server (used ' 20 'with LiveServerTestCase) is expected to run from. The ' 21 'default is localhost:8081.'), 16 22 ) 17 23 help = 'Runs the test suite for the specified applications, or the entire site if no apps are specified.' 18 24 args = '[appname ...]' … … class Command(BaseCommand): 48 54 TestRunner = get_runner(settings, options.get('testrunner')) 49 55 options['verbosity'] = int(options.get('verbosity')) 50 56 57 if options.get('liveserver') is not None: 58 os.environ['DJANGO_LIVE_TEST_SERVER_ADDRESS'] = options['liveserver'] 59 del options['liveserver'] 60 51 61 test_runner = TestRunner(**options) 52 62 failures = test_runner.run_tests(test_labels) 53 63 -
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. 4 4 5 5 from django.test.client import Client, RequestFactory 6 6 from django.test.testcases import (TestCase, TransactionTestCase, 7 SimpleTestCase, skipIfDBFeature, skipUnlessDBFeature) 7 SimpleTestCase, LiveServerTestCase, skipIfDBFeature, 8 skipUnlessDBFeature) 8 9 from django.test.utils import Approximate -
django/test/testcases.py
diff --git a/django/test/testcases.py b/django/test/testcases.py index ee22ac2..10bd12f 100644
a b 1 1 from __future__ import with_statement 2 2 3 import os 3 4 import re 4 5 import sys 5 6 from functools import wraps 6 7 from urlparse import urlsplit, urlunsplit 7 8 from xml.dom.minidom import parseString, Node 9 import select 10 import socket 11 import threading 8 12 9 13 from django.conf import settings 14 from django.contrib.staticfiles.handlers import StaticFilesHandler 10 15 from django.core import mail 11 from django.core.exceptions import ValidationError 16 from django.core.exceptions import ValidationError, ImproperlyConfigured 17 from django.core.handlers.wsgi import WSGIHandler 12 18 from django.core.management import call_command 13 19 from django.core.signals import request_started 20 from django.core.servers.basehttp import (WSGIRequestHandler, WSGIServer, 21 WSGIServerException) 14 22 from django.core.urlresolvers import clear_url_caches 15 23 from django.core.validators import EMPTY_VALUES 16 24 from django.db import (transaction, connection, connections, DEFAULT_DB_ALIAS, … … from django.test.utils import (get_warnings_state, restore_warnings_state, 23 31 override_settings) 24 32 from django.utils import simplejson, unittest as ut2 25 33 from django.utils.encoding import smart_str 34 from django.views.static import serve 26 35 27 36 __all__ = ('DocTestRunner', 'OutputChecker', 'TestCase', 'TransactionTestCase', 28 37 'SimpleTestCase', 'skipIfDBFeature', 'skipUnlessDBFeature') … … def skipUnlessDBFeature(feature): 732 741 """ 733 742 return _deferredSkip(lambda: not getattr(connection.features, feature), 734 743 "Database doesn't support feature %s" % feature) 744 745 746 class QuietWSGIRequestHandler(WSGIRequestHandler): 747 """ 748 Just a regular WSGIRequestHandler except it doesn't log to the standard 749 output any of the requests received, so as to not clutter the output for 750 the tests' results. 751 """ 752 753 def log_message(*args): 754 pass 755 756 757 class _ImprovedEvent(threading._Event): 758 """ 759 Does the same as `threading.Event` except it overrides the wait() method 760 with some code borrowed from Python 2.7 to return the set state of the 761 event (see: http://hg.python.org/cpython/rev/b5aa8aa78c0f/). This allows 762 to know whether the wait() method exited normally or because of the 763 timeout. This class can be removed when Django supports only Python >= 2.7. 764 """ 765 766 def wait(self, timeout=None): 767 self._Event__cond.acquire() 768 try: 769 if not self._Event__flag: 770 self._Event__cond.wait(timeout) 771 return self._Event__flag 772 finally: 773 self._Event__cond.release() 774 775 776 class StoppableWSGIServer(WSGIServer): 777 """ 778 The code in this class is borrowed from the `SocketServer.BaseServer` class 779 in Python 2.6. The important functionality here is that the server is non- 780 blocking and that it can be shut down at any moment. This is made possible 781 by the server regularly polling the socket and checking if it has been 782 asked to stop. 783 Note for the future: Once Django stops supporting Python 2.6, this class 784 can be removed as `WSGIServer` will have this ability to shutdown on 785 demand and will not require the use of the _ImprovedEvent class whose code 786 is borrowed from Python 2.7. 787 """ 788 789 def __init__(self, *args, **kwargs): 790 super(StoppableWSGIServer, self).__init__(*args, **kwargs) 791 self.__is_shut_down = _ImprovedEvent() 792 self.__serving = False 793 794 def serve_forever(self, poll_interval=0.5): 795 """Handle one request at a time until shutdown. 796 797 Polls for shutdown every poll_interval seconds. 798 """ 799 self.__serving = True 800 self.__is_shut_down.clear() 801 while self.__serving: 802 r, w, e = select.select([self], [], [], poll_interval) 803 if r: 804 self._handle_request_noblock() 805 self.__is_shut_down.set() 806 807 def shutdown(self): 808 """Stops the serve_forever loop. 809 810 Blocks until the loop has finished. This must be called while 811 serve_forever() is running in another thread, or it will 812 deadlock. 813 """ 814 self.__serving = False 815 if not self.__is_shut_down.wait(2): 816 raise RuntimeError( 817 "Failed to shutdown the live test server in 2 seconds. The " 818 "server might be stuck or generating a slow response.") 819 820 def handle_request(self): 821 """Handle one request, possibly blocking. 822 """ 823 fd_sets = select.select([self], [], [], None) 824 if not fd_sets[0]: 825 return 826 self._handle_request_noblock() 827 828 def _handle_request_noblock(self): 829 """Handle one request, without blocking. 830 831 I assume that select.select has returned that the socket is 832 readable before this function was called, so there should be 833 no risk of blocking in get_request(). 834 """ 835 try: 836 request, client_address = self.get_request() 837 except socket.error: 838 return 839 if self.verify_request(request, client_address): 840 try: 841 self.process_request(request, client_address) 842 except Exception: 843 self.handle_error(request, client_address) 844 self.close_request(request) 845 846 847 class _MediaFilesHandler(StaticFilesHandler): 848 """ 849 Handler for serving the media files. This is a private class that is 850 meant to be used solely as a convenience by LiveServerThread. 851 """ 852 853 def get_base_dir(self): 854 return settings.MEDIA_ROOT 855 856 def get_base_url(self): 857 return settings.MEDIA_URL 858 859 def serve(self, request): 860 return serve(request, self.file_path(request.path), 861 document_root=self.get_base_dir()) 862 863 864 class LiveServerThread(threading.Thread): 865 """ 866 Thread for running a live http server while the tests are running. 867 """ 868 869 def __init__(self, address, port, connections_override=None): 870 self.address = address 871 self.port = port 872 self.is_ready = threading.Event() 873 self.error = None 874 self.connections_override = connections_override 875 super(LiveServerThread, self).__init__() 876 877 def run(self): 878 """ 879 Sets up the live server and databases, and then loops over handling 880 http requests. 881 """ 882 if self.connections_override: 883 from django.db import connections 884 # Override this thread's database connections with the ones 885 # provided by the main thread. 886 for alias, conn in self.connections_override.items(): 887 connections[alias] = conn 888 try: 889 # Create the handler for serving static and media files 890 handler = StaticFilesHandler(_MediaFilesHandler(WSGIHandler())) 891 except Exception, e: 892 self.error = e 893 self.is_ready.set() 894 # Don't go any further and stop. 895 return 896 try: 897 # Instantiate and start the WSGI server 898 self.httpd = StoppableWSGIServer( 899 (self.address, self.port), QuietWSGIRequestHandler) 900 self.httpd.set_app(handler) 901 self.is_ready.set() 902 self.httpd.serve_forever() 903 except WSGIServerException, e: 904 self.error = e 905 self.is_ready.set() 906 907 def join(self, timeout=None): 908 if hasattr(self, 'httpd'): 909 # Stop the WSGI server 910 self.httpd.shutdown() 911 self.httpd.server_close() 912 super(LiveServerThread, self).join(timeout) 913 914 915 class LiveServerTestCase(TransactionTestCase): 916 """ 917 Does basically the same as TransactionTestCase but also launches a live 918 http server in a separate thread so that the tests may use another testing 919 framework, such as Selenium for example, instead of the built-in dummy 920 client. 921 Note that it inherits from TransactionTestCase instead of TestCase because 922 the threads do not share the same transactions (unless if using in-memory 923 sqlite) and each thread needs to commit all their transactions so that the 924 other thread can see the changes. 925 """ 926 927 @property 928 def live_server_url(self): 929 return 'http://%s' % self.__test_server_address 930 931 @classmethod 932 def setUpClass(cls): 933 connections_override = {} 934 for conn in connections.all(): 935 # If using in-memory sqlite databases, pass the connections to 936 # the server thread. 937 if (conn.settings_dict['ENGINE'] == 'django.db.backends.sqlite3' 938 and conn.settings_dict['NAME'] == ':memory:'): 939 # Explicitly enable thread-shareability for this connection 940 conn.allow_thread_sharing = True 941 connections_override[conn.alias] = conn 942 943 # Launch the live server's thread 944 cls.__test_server_address = os.environ.get( 945 'DJANGO_LIVE_TEST_SERVER_ADDRESS', 'localhost:8081') 946 try: 947 host, port = cls.__test_server_address.split(':') 948 except Exception: 949 raise ImproperlyConfigured('Invalid address ("%s") for live ' 950 'server.' % cls.__test_server_address) 951 cls.server_thread = LiveServerThread( 952 host, int(port), connections_override) 953 cls.server_thread.daemon = True 954 cls.server_thread.start() 955 956 # Wait for the live server to be ready 957 cls.server_thread.is_ready.wait() 958 if cls.server_thread.error: 959 raise cls.server_thread.error 960 961 super(LiveServerTestCase, cls).setUpClass() 962 963 @classmethod 964 def tearDownClass(cls): 965 # There may not be a 'server_thread' attribute if setUpClass() for some 966 # reasons has raised an exception. 967 if hasattr(cls, 'server_thread'): 968 # Terminate the live server's thread 969 cls.server_thread.join() 970 super(LiveServerTestCase, cls).tearDownClass() -
docs/internals/contributing/writing-code/unit-tests.txt
diff --git a/docs/internals/contributing/writing-code/unit-tests.txt b/docs/internals/contributing/writing-code/unit-tests.txt index 5ec09fe..275ee15 100644
a b Going beyond that, you can specify an individual test method like this: 122 122 123 123 ./runtests.py --settings=path.to.settings i18n.TranslationTests.test_lazy_objects 124 124 125 Running the Selenium tests 126 ~~~~~~~~~~~~~~~~~~~~~~~~~~ 127 128 Some admin tests require Selenium 2, Firefox and Python >= 2.6 to work via a 129 real Web browser. To allow those tests to run and not be skipped, you must 130 install the selenium_ package (version > 2.13) into your Python path. 131 132 Then, run the tests normally, for example: 133 134 .. code-block:: bash 135 136 ./runtests.py --settings=test_sqlite admin_inlines 137 125 138 Running all the tests 126 139 ~~~~~~~~~~~~~~~~~~~~~ 127 140 … … dependencies: 135 148 * setuptools_ 136 149 * memcached_, plus a :ref:`supported Python binding <memcached>` 137 150 * gettext_ (:ref:`gettext_on_windows`) 151 * selenium_ (if also using Python >= 2.6) 138 152 139 153 If you want to test the memcached cache backend, you'll also need to define 140 154 a :setting:`CACHES` setting that points at your memcached instance. … … associated tests will be skipped. 149 163 .. _setuptools: http://pypi.python.org/pypi/setuptools/ 150 164 .. _memcached: http://www.danga.com/memcached/ 151 165 .. _gettext: http://www.gnu.org/software/gettext/manual/gettext.html 166 .. _selenium: http://pypi.python.org/pypi/selenium 152 167 153 168 .. _contrib-apps: 154 169 -
docs/releases/1.4.txt
diff --git a/docs/releases/1.4.txt b/docs/releases/1.4.txt index 7ffc1aa..38b621f 100644
a b before the release of Django 1.4. 40 40 What's new in Django 1.4 41 41 ======================== 42 42 43 Support for in-browser testing frameworks 44 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 45 46 Django 1.4 now supports the integration with in-browser testing frameworks such 47 as Selenium or Windmill thanks to the :class:`django.test.LiveServerTestCase` 48 base class, allowing you to test the interactions between your site's front and 49 back ends more comprehensively. See the 50 :class:`documentation<django.test.LiveServerTestCase>` for more details and 51 concrete examples. 52 43 53 ``SELECT FOR UPDATE`` support 44 54 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 45 55 -
docs/topics/testing.txt
diff --git a/docs/topics/testing.txt b/docs/topics/testing.txt index 2396365..3632de3 100644
a b Some of the things you can do with the test client are: 581 581 * Test that a given request is rendered by a given Django template, with 582 582 a template context that contains certain values. 583 583 584 Note that the test client is not intended to be a replacement for Twill_,584 Note that the test client is not intended to be a replacement for Windmill_, 585 585 Selenium_, or other "in-browser" frameworks. Django's test client has 586 586 a different focus. In short: 587 587 588 588 * Use Django's test client to establish that the correct view is being 589 589 called and that the view is collecting the correct context data. 590 590 591 * Use in-browser frameworks such as Twill and Selenium to test *rendered* 592 HTML and the *behavior* of Web pages, namely JavaScript functionality. 591 * Use in-browser frameworks such as Windmill_ and Selenium_ to test *rendered* 592 HTML and the *behavior* of Web pages, namely JavaScript functionality. Django 593 also provides special support for those frameworks; see the section on 594 :class:`~django.test.LiveServerTestCase` for more details. 593 595 594 596 A comprehensive test suite should use a combination of both test types. 595 597 596 .. _Twill: http://twill.idyll.org/597 .. _Selenium: http://seleniumhq.org/598 599 598 Overview and a quick example 600 599 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 601 600 … … set up, execute and tear down the test suite. 1831 1830 those options will be added to the list of command-line options that 1832 1831 the :djadmin:`test` command can use. 1833 1832 1833 Live test server 1834 ---------------- 1835 1836 .. versionadded:: 1.4 1837 1838 .. currentmodule:: django.test 1839 1840 .. class:: LiveServerTestCase() 1841 1842 ``LiveServerTestCase`` does basically the same as 1843 :class:`~django.test.TransactionTestCase` with one extra feature: it launches a 1844 live Django server in the background on setup, and shuts it down on teardown. 1845 This allows the use of automated test clients other than the 1846 :ref:`Django dummy client <test-client>` such as, for example, the Selenium_ or 1847 Windmill_ clients, to execute a series of functional tests inside a browser and 1848 simulate a real user's actions. 1849 1850 By default the live server's address is `'localhost:8081'` and the full URL 1851 can be accessed during the tests with ``self.live_server_url``. If you'd like 1852 to change the default address (for example because the 8081 port is already 1853 taken) you may do so either by changing the `DJANGO_LIVE_TEST_SERVER_ADDRESS` 1854 environment variable or by passing a new value via the `--liveserver` option of 1855 the :djadmin:`test` command, for example: 1856 1857 .. code-block:: bash 1858 1859 ./manage.py test --liveserver=localhost:8082 1860 1861 To demonstrate how to use ``LiveServerTestCase``, let's write a simple Selenium 1862 test. First of all, you need to install the `selenium package`_ into your 1863 Python path: 1864 1865 .. code-block:: bash 1866 1867 pip install selenium 1868 1869 Then, add a ``LiveServerTestCase``-based test to your app's tests module 1870 (for example: ``myapp/tests.py``). The code for this test may look as follows: 1871 1872 .. code-block:: python 1873 1874 from django.test import LiveServerTestCase 1875 from selenium.webdriver.firefox.webdriver import WebDriver 1876 1877 class MySeleniumTests(LiveServerTestCase): 1878 fixtures = ['user-data.json'] 1879 1880 @classmethod 1881 def setUpClass(cls): 1882 cls.selenium = WebDriver() 1883 super(MySeleniumTests, cls).setUpClass() 1884 1885 @classmethod 1886 def tearDownClass(cls): 1887 super(MySeleniumTests, cls).tearDownClass() 1888 cls.selenium.quit() 1889 1890 def test_login(self): 1891 self.selenium.get('%s%s' % (self.live_server_url, '/login/')) 1892 username_input = self.selenium.find_element_by_name("username") 1893 username_input.send_keys('myuser') 1894 password_input = self.selenium.find_element_by_name("password") 1895 password_input.send_keys('secret') 1896 self.selenium.find_element_by_xpath('//input[@value="Log in"]').click() 1897 1898 Finally, you may run the test as follows: 1899 1900 .. code-block:: bash 1901 1902 ./manage.py test myapp.MySeleniumTests.test_login 1903 1904 This example will automatically open Firefox then go to the login page, enter 1905 the credentials and press the "Log in" button. Selenium offers other drivers in 1906 case you do not have Firefox installed or wish to use another browser. The 1907 example above is just a tiny fraction of what the Selenium client can do; check 1908 out the `full reference`_ for more details. 1909 1910 .. _Windmill: http://www.getwindmill.com/ 1911 .. _Selenium: http://seleniumhq.org/ 1912 .. _selenium package: http://pypi.python.org/pypi/selenium 1913 .. _full reference: http://readthedocs.org/docs/selenium-python/en/latest/api.html 1914 .. _Firefox: http://www.mozilla.com/firefox/ 1915 1916 .. note:: 1917 1918 ``LiveServerTestCase`` makes use of the :doc:`static files contrib app 1919 </howto/static-files>` so you'll need to have your project configured 1920 accordingly (in particular by setting :setting:`STATIC_URL`). 1834 1921 1835 1922 Attributes 1836 1923 ~~~~~~~~~~ 1837 1924 1838 1839 1925 .. attribute:: DjangoTestSuiteRunner.option_list 1840 1926 1841 1927 .. 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): 109 109 model = SottoCapo 110 110 111 111 112 class ProfileInline(admin.TabularInline): 113 model = Profile 114 extra = 1 115 112 116 site.register(TitleCollection, inlines=[TitleInline]) 113 117 # Test bug #12561 and #12778 114 118 # only ModelAdmin media … … site.register(Fashionista, inlines=[InlineWeakness]) 124 128 site.register(Holder4, Holder4Admin) 125 129 site.register(Author, AuthorAdmin) 126 130 site.register(CapoFamiglia, inlines=[ConsigliereInline, SottoCapoInline]) 131 site.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): 136 136 class SottoCapo(models.Model): 137 137 name = models.CharField(max_length=100) 138 138 capo_famiglia = models.ForeignKey(CapoFamiglia, related_name='+') 139 140 # Other models 141 142 class ProfileCollection(models.Model): 143 pass 144 145 class 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 1 1 from __future__ import absolute_import 2 2 3 from django.contrib.admin.tests import AdminSeleniumWebDriverTestCase 3 4 from django.contrib.admin.helpers import InlineAdminForm 4 5 from django.contrib.auth.models import User, Permission 5 6 from django.contrib.contenttypes.models import ContentType … … from django.test import TestCase 8 9 # local test models 9 10 from .admin import InnerInline 10 11 from .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) 12 14 13 15 14 16 class TestInline(TestCase): … … class TestInlinePermissions(TestCase): 380 382 self.assertContains(response, 'value="4" id="id_inner2_set-TOTAL_FORMS"') 381 383 self.assertContains(response, '<input type="hidden" name="inner2_set-0-id" value="%i"' % self.inner2_id) 382 384 self.assertContains(response, 'id="id_inner2_set-0-DELETE"') 385 386 class SeleniumFirefoxTests(AdminSeleniumWebDriverTestCase): 387 webdriver_class = 'selenium.webdriver.firefox.webdriver.WebDriver' 388 fixtures = ['admin-views-users.xml'] 389 urls = "regressiontests.admin_inlines.urls" 390 391 def test_add_inlines(self): 392 """ 393 Ensure that the "Add another XXX" link correctly adds items to the 394 inline form. 395 """ 396 self.admin_login(username='super', password='secret') 397 self.selenium.get('%s%s' % (self.live_server_url, 398 '/admin/admin_inlines/profilecollection/add/')) 399 400 # Check that there's only one inline to start with and that it has the 401 # correct ID. 402 self.failUnlessEqual(len(self.selenium.find_elements_by_css_selector( 403 '#profile_set-group table tr.dynamic-profile_set')), 1) 404 self.failUnlessEqual(self.selenium.find_element_by_css_selector( 405 '.dynamic-profile_set:nth-of-type(1)').get_attribute('id'), 406 'profile_set-0') 407 self.failUnlessEqual(len(self.selenium.find_elements_by_css_selector( 408 'form#profilecollection_form tr.dynamic-profile_set#profile_set-0 input[name=profile_set-0-first_name]')), 1) 409 self.failUnlessEqual(len(self.selenium.find_elements_by_css_selector( 410 'form#profilecollection_form tr.dynamic-profile_set#profile_set-0 input[name=profile_set-0-last_name]')), 1) 411 412 # Add an inline 413 self.selenium.find_element_by_link_text('Add another Profile').click() 414 415 # Check that the inline has been added, that it has the right id, and 416 # that it contains the right fields. 417 self.failUnlessEqual(len(self.selenium.find_elements_by_css_selector( 418 '#profile_set-group table tr.dynamic-profile_set')), 2) 419 self.failUnlessEqual(self.selenium.find_element_by_css_selector( 420 '.dynamic-profile_set:nth-of-type(2)').get_attribute('id'), 'profile_set-1') 421 self.failUnlessEqual(len(self.selenium.find_elements_by_css_selector( 422 'form#profilecollection_form tr.dynamic-profile_set#profile_set-1 input[name=profile_set-1-first_name]')), 1) 423 self.failUnlessEqual(len(self.selenium.find_elements_by_css_selector( 424 'form#profilecollection_form tr.dynamic-profile_set#profile_set-1 input[name=profile_set-1-last_name]')), 1) 425 426 # Let's add another one to be sure 427 self.selenium.find_element_by_link_text('Add another Profile').click() 428 self.failUnlessEqual(len(self.selenium.find_elements_by_css_selector( 429 '#profile_set-group table tr.dynamic-profile_set')), 3) 430 self.failUnlessEqual(self.selenium.find_element_by_css_selector( 431 '.dynamic-profile_set:nth-of-type(3)').get_attribute('id'), 'profile_set-2') 432 self.failUnlessEqual(len(self.selenium.find_elements_by_css_selector( 433 'form#profilecollection_form tr.dynamic-profile_set#profile_set-2 input[name=profile_set-2-first_name]')), 1) 434 self.failUnlessEqual(len(self.selenium.find_elements_by_css_selector( 435 'form#profilecollection_form tr.dynamic-profile_set#profile_set-2 input[name=profile_set-2-last_name]')), 1) 436 437 # Enter some data and click 'Save' 438 self.selenium.find_element_by_name('profile_set-0-first_name').send_keys('0 first name 1') 439 self.selenium.find_element_by_name('profile_set-0-last_name').send_keys('0 last name 2') 440 self.selenium.find_element_by_name('profile_set-1-first_name').send_keys('1 first name 1') 441 self.selenium.find_element_by_name('profile_set-1-last_name').send_keys('1 last name 2') 442 self.selenium.find_element_by_name('profile_set-2-first_name').send_keys('2 first name 1') 443 self.selenium.find_element_by_name('profile_set-2-last_name').send_keys('2 last name 2') 444 self.selenium.find_element_by_xpath('//input[@value="Save"]').click() 445 446 # Check that the objects have been created in the database 447 self.assertEqual(ProfileCollection.objects.all().count(), 1) 448 self.assertEqual(Profile.objects.all().count(), 3) 449 450 def test_delete_inlines(self): 451 self.admin_login(username='super', password='secret') 452 self.selenium.get('%s%s' % (self.live_server_url, 453 '/admin/admin_inlines/profilecollection/add/')) 454 455 # Add a few inlines 456 self.selenium.find_element_by_link_text('Add another Profile').click() 457 self.selenium.find_element_by_link_text('Add another Profile').click() 458 self.selenium.find_element_by_link_text('Add another Profile').click() 459 self.selenium.find_element_by_link_text('Add another Profile').click() 460 self.failUnlessEqual(len(self.selenium.find_elements_by_css_selector( 461 '#profile_set-group table tr.dynamic-profile_set')), 5) 462 self.failUnlessEqual(len(self.selenium.find_elements_by_css_selector( 463 'form#profilecollection_form tr.dynamic-profile_set#profile_set-0')), 1) 464 self.failUnlessEqual(len(self.selenium.find_elements_by_css_selector( 465 'form#profilecollection_form tr.dynamic-profile_set#profile_set-1')), 1) 466 self.failUnlessEqual(len(self.selenium.find_elements_by_css_selector( 467 'form#profilecollection_form tr.dynamic-profile_set#profile_set-2')), 1) 468 self.failUnlessEqual(len(self.selenium.find_elements_by_css_selector( 469 'form#profilecollection_form tr.dynamic-profile_set#profile_set-3')), 1) 470 self.failUnlessEqual(len(self.selenium.find_elements_by_css_selector( 471 'form#profilecollection_form tr.dynamic-profile_set#profile_set-4')), 1) 472 473 # Click on a few delete buttons 474 self.selenium.find_element_by_css_selector( 475 'form#profilecollection_form tr.dynamic-profile_set#profile_set-1 td.delete a').click() 476 self.selenium.find_element_by_css_selector( 477 'form#profilecollection_form tr.dynamic-profile_set#profile_set-2 td.delete a').click() 478 # Verify that they're gone and that the IDs have been re-sequenced 479 self.failUnlessEqual(len(self.selenium.find_elements_by_css_selector( 480 '#profile_set-group table tr.dynamic-profile_set')), 3) 481 self.failUnlessEqual(len(self.selenium.find_elements_by_css_selector( 482 'form#profilecollection_form tr.dynamic-profile_set#profile_set-0')), 1) 483 self.failUnlessEqual(len(self.selenium.find_elements_by_css_selector( 484 'form#profilecollection_form tr.dynamic-profile_set#profile_set-1')), 1) 485 self.failUnlessEqual(len(self.selenium.find_elements_by_css_selector( 486 'form#profilecollection_form tr.dynamic-profile_set#profile_set-2')), 1) -
tests/regressiontests/admin_scripts/tests.py
diff --git a/tests/regressiontests/admin_scripts/tests.py b/tests/regressiontests/admin_scripts/tests.py index 11beddb..72d47ad 100644
a b import re 13 13 14 14 from django import conf, bin, get_version 15 15 from django.conf import settings 16 from django.test.simple import DjangoTestSuiteRunner 16 17 from django.utils import unittest 17 18 18 19 … … class ManageValidate(AdminScriptTestCase): 1058 1059 self.assertOutput(out, '0 errors found') 1059 1060 1060 1061 1062 class CustomTestRunner(DjangoTestSuiteRunner): 1063 1064 def __init__(self, *args, **kwargs): 1065 assert 'liveserver' not in kwargs 1066 super(CustomTestRunner, self).__init__(*args, **kwargs) 1067 1068 def run_tests(self, test_labels, extra_tests=None, **kwargs): 1069 pass 1070 1071 class ManageTestCommand(AdminScriptTestCase): 1072 def setUp(self): 1073 from django.core.management.commands.test import Command as TestCommand 1074 self.cmd = TestCommand() 1075 1076 def test_liveserver(self): 1077 """ 1078 Ensure that the --liveserver option sets the environment variable 1079 correctly. 1080 Refs #2879. 1081 """ 1082 1083 # Backup original state 1084 address_predefined = 'DJANGO_LIVE_TEST_SERVER_ADDRESS' in os.environ 1085 old_address = os.environ.get('DJANGO_LIVE_TEST_SERVER_ADDRESS') 1086 1087 self.cmd.handle(verbosity=0, testrunner='regressiontests.admin_scripts.tests.CustomTestRunner') 1088 1089 # Original state hasn't changed 1090 self.assertEqual('DJANGO_LIVE_TEST_SERVER_ADDRESS' in os.environ, address_predefined) 1091 self.assertEqual(os.environ.get('DJANGO_LIVE_TEST_SERVER_ADDRESS'), old_address) 1092 1093 self.cmd.handle(verbosity=0, testrunner='regressiontests.admin_scripts.tests.CustomTestRunner', 1094 liveserver='blah') 1095 1096 # Variable was correctly set 1097 self.assertEqual(os.environ['DJANGO_LIVE_TEST_SERVER_ADDRESS'], 'blah') 1098 1099 # Restore original state 1100 if address_predefined: 1101 os.environ['DJANGO_LIVE_TEST_SERVER_ADDRESS'] = old_address 1102 else: 1103 del os.environ['DJANGO_LIVE_TEST_SERVER_ADDRESS'] 1104 1105 1061 1106 class ManageRunserver(AdminScriptTestCase): 1062 1107 def setUp(self): 1063 1108 from django.core.management.commands.runserver import BaseRunserverCommand -
tests/regressiontests/admin_widgets/tests.py
diff --git a/tests/regressiontests/admin_widgets/tests.py b/tests/regressiontests/admin_widgets/tests.py index 37fa7bc..e28df32 100644
a b from django import forms 7 7 from django.conf import settings 8 8 from django.contrib import admin 9 9 from django.contrib.admin import widgets 10 from django.contrib.admin.tests import AdminSeleniumWebDriverTestCase 10 11 from django.core.files.storage import default_storage 11 12 from django.core.files.uploadedfile import SimpleUploadedFile 12 13 from django.db.models import DateField … … class RelatedFieldWidgetWrapperTests(DjangoTestCase): 407 408 # Used to fail with a name error. 408 409 w = widgets.RelatedFieldWidgetWrapper(w, rel, widget_admin_site) 409 410 self.assertFalse(w.can_add_related) 411 412 413 class SeleniumFirefoxTests(AdminSeleniumWebDriverTestCase): 414 webdriver_class = 'selenium.webdriver.firefox.webdriver.WebDriver' 415 fixtures = ['admin-widgets-users.xml'] 416 urls = "regressiontests.admin_widgets.urls" 417 418 def test_show_hide_date_time_picker_widgets(self): 419 """ 420 Ensure that pressing the ESC key closes the date and time picker 421 widgets. 422 Refs #17064. 423 """ 424 from selenium.webdriver.common.keys import Keys 425 426 self.admin_login(username='super', password='secret', login_url='/') 427 # Open a page that has a date and time picker widgets 428 self.selenium.get('%s%s' % (self.live_server_url, 429 '/admin_widgets/member/add/')) 430 431 # First, with the date picker widget --------------------------------- 432 # Check that the date picker is hidden 433 self.assertEqual( 434 self.get_css_value('#calendarbox0', 'display'), 'none') 435 # Click the calendar icon 436 self.selenium.find_element_by_id('calendarlink0').click() 437 # Check that the date picker is visible 438 self.assertEqual( 439 self.get_css_value('#calendarbox0', 'display'), 'block') 440 # Press the ESC key 441 self.selenium.find_element_by_tag_name('html').send_keys([Keys.ESCAPE]) 442 # Check that the date picker is hidden again 443 self.assertEqual( 444 self.get_css_value('#calendarbox0', 'display'), 'none') 445 446 # Then, with the time picker widget ---------------------------------- 447 # Check that the time picker is hidden 448 self.assertEqual( 449 self.get_css_value('#clockbox0', 'display'), 'none') 450 # Click the time icon 451 self.selenium.find_element_by_id('clocklink0').click() 452 # Check that the time picker is visible 453 self.assertEqual( 454 self.get_css_value('#clockbox0', 'display'), 'block') 455 # Press the ESC key 456 self.selenium.find_element_by_tag_name('html').send_keys([Keys.ESCAPE]) 457 # Check that the time picker is hidden again 458 self.assertEqual( 459 self.get_css_value('#clockbox0', 'display'), 'none') -
new file tests/regressiontests/servers/fixtures/testdata.json
diff --git a/tests/regressiontests/servers/fixtures/testdata.json b/tests/regressiontests/servers/fixtures/testdata.json new file mode 100644 index 0000000..d81b225
- + 1 [ 2 { 3 "pk": 1, 4 "model": "servers.person", 5 "fields": { 6 "name": "jane" 7 } 8 }, 9 { 10 "pk": 2, 11 "model": "servers.person", 12 "fields": { 13 "name": "robert" 14 } 15 } 16 ] 17 No newline at end of file -
new file tests/regressiontests/servers/media/example_media_file.txt
diff --git a/tests/regressiontests/servers/media/example_media_file.txt b/tests/regressiontests/servers/media/example_media_file.txt new file mode 100644 index 0000000..dd2dda9
- + 1 example 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 1 from django.db import models 2 3 4 class 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
- + 1 example file -
tests/regressiontests/servers/tests.py
diff --git a/tests/regressiontests/servers/tests.py b/tests/regressiontests/servers/tests.py index b9d24ba..6db005f 100644
a b Tests for django.core.servers. 3 3 """ 4 4 import os 5 5 from urlparse import urljoin 6 import urllib2 6 7 7 8 import django 8 9 from django.conf import settings 9 from django.test import TestCase 10 from django.core.exceptions import ImproperlyConfigured 11 from django.test import TestCase, LiveServerTestCase 10 12 from django.core.handlers.wsgi import WSGIHandler 11 from django.core.servers.basehttp import AdminMediaHandler 13 from django.core.servers.basehttp import AdminMediaHandler, WSGIServerException 14 from django.test.utils import override_settings 12 15 16 from .models import Person 13 17 14 18 class AdminMediaHandlerTests(TestCase): 15 19 … … class AdminMediaHandlerTests(TestCase): 68 72 continue 69 73 self.fail('URL: %s should have caused a ValueError exception.' 70 74 % url) 75 76 77 TEST_ROOT = os.path.dirname(__file__) 78 TEST_SETTINGS = { 79 'MEDIA_URL': '/media/', 80 'MEDIA_ROOT': os.path.join(TEST_ROOT, 'media'), 81 'STATIC_URL': '/static/', 82 'STATIC_ROOT': os.path.join(TEST_ROOT, 'static'), 83 } 84 85 86 class LiveServerBase(LiveServerTestCase): 87 urls = 'regressiontests.servers.urls' 88 fixtures = ['testdata.json'] 89 90 @classmethod 91 def setUpClass(cls): 92 # Override settings 93 cls.settings_override = override_settings(**TEST_SETTINGS) 94 cls.settings_override.enable() 95 super(LiveServerBase, cls).setUpClass() 96 97 @classmethod 98 def tearDownClass(cls): 99 # Restore original settings 100 cls.settings_override.disable() 101 super(LiveServerBase, cls).tearDownClass() 102 103 def urlopen(self, url): 104 server_host = os.environ.get( 105 'DJANGO_LIVE_TEST_SERVER_HOST', 'localhost') 106 server_port = os.environ.get( 107 'DJANGO_LIVE_TEST_SERVER_PORT', 8081) 108 base = 'http://%s:%s' % (server_host, server_port) 109 return urllib2.urlopen(base + url) 110 111 112 class LiveServerAddress(LiveServerBase): 113 """ 114 Ensure that the address set in the environment variable is valid. 115 Refs #2879. 116 """ 117 118 @classmethod 119 def setUpClass(cls): 120 # Backup original environment variable 121 address_predefined = 'DJANGO_LIVE_TEST_SERVER_ADDRESS' in os.environ 122 old_address = os.environ.get('DJANGO_LIVE_TEST_SERVER_ADDRESS') 123 124 # Just the host is not accepted 125 os.environ['DJANGO_LIVE_TEST_SERVER_ADDRESS'] = 'localhost' 126 try: 127 super(LiveServerAddress, cls).setUpClass() 128 raise Exception("The line above should have raised an exception") 129 except ImproperlyConfigured: 130 pass 131 132 # The host must be valid 133 os.environ['DJANGO_LIVE_TEST_SERVER_ADDRESS'] = 'blahblahblah:8081' 134 try: 135 super(LiveServerAddress, cls).setUpClass() 136 raise Exception("The line above should have raised an exception") 137 except WSGIServerException: 138 pass 139 140 # If contrib.staticfiles isn't configured properly, the exception 141 # should bubble up to the main thread. 142 old_STATIC_URL = TEST_SETTINGS['STATIC_URL'] 143 TEST_SETTINGS['STATIC_URL'] = None 144 os.environ['DJANGO_LIVE_TEST_SERVER_ADDRESS'] = 'localhost:8081' 145 try: 146 super(LiveServerAddress, cls).setUpClass() 147 raise Exception("The line above should have raised an exception") 148 except ImproperlyConfigured: 149 pass 150 TEST_SETTINGS['STATIC_URL'] = old_STATIC_URL 151 152 # Restore original environment variable 153 if address_predefined: 154 os.environ['DJANGO_LIVE_TEST_SERVER_ADDRESS'] = old_address 155 else: 156 del os.environ['DJANGO_LIVE_TEST_SERVER_ADDRESS'] 157 158 def test_test_test(self): 159 # Intentionally empty method so that the test is picked up by the 160 # test runner and the overriden setUpClass method is executed. 161 pass 162 163 class LiveServerViews(LiveServerBase): 164 def test_404(self): 165 """ 166 Ensure that the LiveServerTestCase serves 404s. 167 """ 168 try: 169 self.urlopen('/') 170 except urllib2.HTTPError, err: 171 self.assertEquals(err.code, 404, 'Expected 404 response') 172 else: 173 self.fail('Expected 404 response') 174 175 def test_view(self): 176 """ 177 Ensure that the LiveServerTestCase serves views. 178 """ 179 f = self.urlopen('/example_view/') 180 self.assertEquals(f.read(), 'example view') 181 182 def test_static_files(self): 183 """ 184 Ensure that the LiveServerTestCase serves static files. 185 """ 186 f = self.urlopen('/static/example_file.txt') 187 self.assertEquals(f.read(), 'example file\n') 188 189 def test_media_files(self): 190 """ 191 Ensure that the LiveServerTestCase serves media files. 192 """ 193 f = self.urlopen('/media/example_media_file.txt') 194 self.assertEquals(f.read(), 'example media file\n') 195 196 197 class LiveServerDatabase(LiveServerBase): 198 199 def test_fixtures_loaded(self): 200 """ 201 Ensure that fixtures are properly loaded and visible to the 202 live server thread. 203 """ 204 f = self.urlopen('/model_view/') 205 self.assertEquals(f.read().splitlines(), ['jane', 'robert']) 206 207 def test_database_writes(self): 208 """ 209 Ensure that data written to the database by a view can be read. 210 """ 211 self.urlopen('/create_model_instance/') 212 names = [person.name for person in Person.objects.all()] 213 self.assertEquals(names, ['jane', 'robert', 'emily']) -
new file tests/regressiontests/servers/urls.py
diff --git a/tests/regressiontests/servers/urls.py b/tests/regressiontests/servers/urls.py new file mode 100644 index 0000000..c8ca1ac
- + 1 from __future__ import absolute_import 2 3 from django.conf.urls import patterns, url 4 5 from . import views 6 7 8 urlpatterns = patterns('', 9 url(r'^example_view/$', views.example_view), 10 url(r'^model_view/$', views.model_view), 11 url(r'^create_model_instance/$', views.create_model_instance), 12 ) 13 No newline at end of file -
new file tests/regressiontests/servers/views.py
diff --git a/tests/regressiontests/servers/views.py b/tests/regressiontests/servers/views.py new file mode 100644 index 0000000..94a4f2d
- + 1 from django.http import HttpResponse 2 from .models import Person 3 4 5 def example_view(request): 6 return HttpResponse('example view') 7 8 9 def model_view(request): 10 people = Person.objects.all() 11 return HttpResponse('\n'.join([person.name for person in people])) 12 13 14 def create_model_instance(request): 15 person = Person(name='emily') 16 person.save() 17 return HttpResponse('') 18 No newline at end of file -
tests/runtests.py
diff --git a/tests/runtests.py b/tests/runtests.py index c38c4c2..f2e87f2 100755
a b if __name__ == "__main__": 270 270 help="Bisect the test suite to discover a test that causes a test failure when combined with the named test.") 271 271 parser.add_option('--pair', action='store', dest='pair', default=None, 272 272 help="Run the test suite in pairs with the named test to find problem pairs.") 273 parser.add_option('--liveserver', action='store', dest='liveserver', default=None, 274 help='Overrides the default address where the live server (used with ' 275 'LiveServerTestCase) is expected to run from. The default is ' 276 'localhost:8081.'), 273 277 options, args = parser.parse_args() 274 278 if options.settings: 275 279 os.environ['DJANGO_SETTINGS_MODULE'] = options.settings … … if __name__ == "__main__": 279 283 else: 280 284 options.settings = os.environ['DJANGO_SETTINGS_MODULE'] 281 285 286 if options.liveserver is not None: 287 os.environ['DJANGO_LIVE_TEST_SERVER_ADDRESS'] = options.liveserver 288 282 289 if options.bisect: 283 290 bisect_tests(options.bisect, options, args) 284 291 elif options.pair: