Kaydet (Commit) 2f02a05f authored tarafından Julien Phalip's avatar Julien Phalip

Fixed #2879 -- Added support for the integration with Selenium and other…

Fixed #2879 -- Added support for the integration with Selenium and other in-browser testing frameworks. Also added the first Selenium tests for `contrib.admin`. Many thanks to everyone for their contributions and feedback: Mikeal Rogers, Dirk Datzert, mir, Simon G., Almad, Russell Keith-Magee, Denis Golomazov, devin, robertrv, andrewbadr, Idan Gazit, voidspace, Tom Christie, hjwp2, Adam Nelson, Jannis Leidel, Anssi Kääriäinen, Preston Holmes, Bruno Renié and Jacob Kaplan-Moss.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@17241 bcc190cf-cafb-0310-a4f2-bffc1f526a37
üst 45e3dff5
import sys
from django.test import LiveServerTestCase
from django.utils.importlib import import_module
from django.utils.unittest import SkipTest
from django.utils.translation import ugettext as _
class AdminSeleniumWebDriverTestCase(LiveServerTestCase):
webdriver_class = 'selenium.webdriver.firefox.webdriver.WebDriver'
@classmethod
def setUpClass(cls):
if sys.version_info < (2, 6):
raise SkipTest('Selenium Webdriver does not support Python < 2.6.')
try:
# Import and start the WebDriver class.
module, attr = cls.webdriver_class.rsplit('.', 1)
mod = import_module(module)
WebDriver = getattr(mod, attr)
cls.selenium = WebDriver()
except Exception:
raise SkipTest('Selenium webdriver "%s" not installed or not '
'operational.' % cls.webdriver_class)
super(AdminSeleniumWebDriverTestCase, cls).setUpClass()
@classmethod
def tearDownClass(cls):
super(AdminSeleniumWebDriverTestCase, cls).tearDownClass()
if hasattr(cls, 'selenium'):
cls.selenium.quit()
def admin_login(self, username, password, login_url='/admin/'):
"""
Helper function to log into the admin.
"""
self.selenium.get('%s%s' % (self.live_server_url, login_url))
username_input = self.selenium.find_element_by_name('username')
username_input.send_keys(username)
password_input = self.selenium.find_element_by_name('password')
password_input.send_keys(password)
login_text = _('Log in')
self.selenium.find_element_by_xpath(
'//input[@value="%s"]' % login_text).click()
def get_css_value(self, selector, attribute):
"""
Helper function that returns the value for the CSS attribute of an
DOM element specified by the given selector. Uses the jQuery that ships
with Django.
"""
return self.selenium.execute_script(
'return django.jQuery("%s").css("%s")' % (selector, attribute))
\ No newline at end of file
import sys
import os
from optparse import make_option, OptionParser
from django.conf import settings
from django.core.management.base import BaseCommand
from optparse import make_option, OptionParser
import sys
from django.test.utils import get_runner
class Command(BaseCommand):
option_list = BaseCommand.option_list + (
make_option('--noinput', action='store_false', dest='interactive', default=True,
make_option('--noinput',
action='store_false', dest='interactive', default=True,
help='Tells Django to NOT prompt the user for input of any kind.'),
make_option('--failfast', action='store_true', dest='failfast', default=False,
help='Tells Django to stop running the test suite after first failed test.'),
make_option('--testrunner', action='store', dest='testrunner',
help='Tells Django to use specified test runner class instead of the one '+
'specified by the TEST_RUNNER setting.')
make_option('--failfast',
action='store_true', dest='failfast', default=False,
help='Tells Django to stop running the test suite after first '
'failed test.'),
make_option('--testrunner',
action='store', dest='testrunner',
help='Tells Django to use specified test runner class instead of '
'the one specified by the TEST_RUNNER setting.'),
make_option('--liveserver',
action='store', dest='liveserver', default=None,
help='Overrides the default address where the live server (used '
'with LiveServerTestCase) is expected to run from. The '
'default value is localhost:8081.'),
)
help = 'Runs the test suite for the specified applications, or the entire site if no apps are specified.'
help = ('Runs the test suite for the specified applications, or the '
'entire site if no apps are specified.')
args = '[appname ...]'
requires_model_validation = False
......@@ -35,7 +47,8 @@ class Command(BaseCommand):
def create_parser(self, prog_name, subcommand):
test_runner_class = get_runner(settings, self.test_runner)
options = self.option_list + getattr(test_runner_class, 'option_list', ())
options = self.option_list + getattr(
test_runner_class, 'option_list', ())
return OptionParser(prog=prog_name,
usage=self.usage(subcommand),
version=self.get_version(),
......@@ -48,6 +61,10 @@ class Command(BaseCommand):
TestRunner = get_runner(settings, options.get('testrunner'))
options['verbosity'] = int(options.get('verbosity'))
if options.get('liveserver') is not None:
os.environ['DJANGO_LIVE_TEST_SERVER_ADDRESS'] = options['liveserver']
del options['liveserver']
test_runner = TestRunner(**options)
failures = test_runner.run_tests(test_labels)
......
......@@ -4,5 +4,6 @@ Django Unit Test and Doctest framework.
from django.test.client import Client, RequestFactory
from django.test.testcases import (TestCase, TransactionTestCase,
SimpleTestCase, skipIfDBFeature, skipUnlessDBFeature)
SimpleTestCase, LiveServerTestCase, skipIfDBFeature,
skipUnlessDBFeature)
from django.test.utils import Approximate
This diff is collapsed.
......@@ -122,6 +122,19 @@ Going beyond that, you can specify an individual test method like this:
./runtests.py --settings=path.to.settings i18n.TranslationTests.test_lazy_objects
Running the Selenium tests
~~~~~~~~~~~~~~~~~~~~~~~~~~
Some admin tests require Selenium 2, Firefox and Python >= 2.6 to work via a
real Web browser. To allow those tests to run and not be skipped, you must
install the selenium_ package (version > 2.13) into your Python path.
Then, run the tests normally, for example:
.. code-block:: bash
./runtests.py --settings=test_sqlite admin_inlines
Running all the tests
~~~~~~~~~~~~~~~~~~~~~
......@@ -135,6 +148,7 @@ dependencies:
* setuptools_
* memcached_, plus a :ref:`supported Python binding <memcached>`
* gettext_ (:ref:`gettext_on_windows`)
* selenium_ (if also using Python >= 2.6)
If you want to test the memcached cache backend, you'll also need to define
a :setting:`CACHES` setting that points at your memcached instance.
......@@ -149,6 +163,7 @@ associated tests will be skipped.
.. _setuptools: http://pypi.python.org/pypi/setuptools/
.. _memcached: http://www.danga.com/memcached/
.. _gettext: http://www.gnu.org/software/gettext/manual/gettext.html
.. _selenium: http://pypi.python.org/pypi/selenium
.. _contrib-apps:
......
......@@ -976,15 +976,22 @@ information.
.. versionadded:: 1.2
.. django-admin-option:: --failfast
Use the :djadminopt:`--failfast` option to stop running tests and report the failure
immediately after a test fails.
The ``--failfast`` option can be used to stop running tests and report the
failure immediately after a test fails.
.. versionadded:: 1.4
.. django-admin-option:: --testrunner
The :djadminopt:`--testrunner` option can be used to control the test runner
class that is used to execute tests. If this value is provided, it overrides
the value provided by the :setting:`TEST_RUNNER` setting.
The ``--testrunner`` option can be used to control the test runner class that
is used to execute tests. If this value is provided, it overrides the value
provided by the :setting:`TEST_RUNNER` setting.
.. versionadded:: 1.4
.. django-admin-option:: --liveserver
The ``--liveserver`` option can be used to override the default address where
the live server (used with :class:`~django.test.LiveServerTestCase`) is
expected to run from. The default value is ``localhost:8081``.
testserver <fixture fixture ...>
--------------------------------
......
......@@ -40,6 +40,19 @@ before the release of Django 1.4.
What's new in Django 1.4
========================
Support for in-browser testing frameworks
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Django 1.4 now supports the integration with in-browser testing frameworks such
as Selenium_ or Windmill_ thanks to the :class:`django.test.LiveServerTestCase`
base class, allowing you to test the interactions between your site's front and
back ends more comprehensively. See the
:class:`documentation<django.test.LiveServerTestCase>` for more details and
concrete examples.
.. _Windmill: http://www.getwindmill.com/
.. _Selenium: http://seleniumhq.org/
``SELECT FOR UPDATE`` support
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
......
......@@ -581,21 +581,20 @@ Some of the things you can do with the test client are:
* Test that a given request is rendered by a given Django template, with
a template context that contains certain values.
Note that the test client is not intended to be a replacement for Twill_,
Note that the test client is not intended to be a replacement for Windmill_,
Selenium_, or other "in-browser" frameworks. Django's test client has
a different focus. In short:
* Use Django's test client to establish that the correct view is being
called and that the view is collecting the correct context data.
* Use in-browser frameworks such as Twill and Selenium to test *rendered*
HTML and the *behavior* of Web pages, namely JavaScript functionality.
* Use in-browser frameworks such as Windmill_ and Selenium_ to test *rendered*
HTML and the *behavior* of Web pages, namely JavaScript functionality. Django
also provides special support for those frameworks; see the section on
:class:`~django.test.LiveServerTestCase` for more details.
A comprehensive test suite should use a combination of both test types.
.. _Twill: http://twill.idyll.org/
.. _Selenium: http://seleniumhq.org/
Overview and a quick example
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
......@@ -1753,6 +1752,97 @@ under MySQL with MyISAM tables)::
def test_transaction_behavior(self):
# ... conditional test code
Live test server
----------------
.. versionadded:: 1.4
.. currentmodule:: django.test
.. class:: LiveServerTestCase()
``LiveServerTestCase`` does basically the same as
:class:`~django.test.TransactionTestCase` with one extra feature: it launches a
live Django server in the background on setup, and shuts it down on teardown.
This allows the use of automated test clients other than the
:ref:`Django dummy client <test-client>` such as, for example, the Selenium_ or
Windmill_ clients, to execute a series of functional tests inside a browser and
simulate a real user's actions.
By default the live server's address is `'localhost:8081'` and the full URL
can be accessed during the tests with ``self.live_server_url``. If you'd like
to change the default address (in the case, for example, where the 8081 port is
already taken) you may pass a different one to the :djadmin:`test` command via
the :djadminopt:`--liveserver` option, for example:
.. code-block:: bash
./manage.py test --liveserver=localhost:8082
Another way of changing the default server address is by setting the
`DJANGO_LIVE_TEST_SERVER_ADDRESS` environment variable.
To demonstrate how to use ``LiveServerTestCase``, let's write a simple Selenium
test. First of all, you need to install the `selenium package`_ into your
Python path:
.. code-block:: bash
pip install selenium
Then, add a ``LiveServerTestCase``-based test to your app's tests module
(for example: ``myapp/tests.py``). The code for this test may look as follows:
.. code-block:: python
from django.test import LiveServerTestCase
from selenium.webdriver.firefox.webdriver import WebDriver
class MySeleniumTests(LiveServerTestCase):
fixtures = ['user-data.json']
@classmethod
def setUpClass(cls):
cls.selenium = WebDriver()
super(MySeleniumTests, cls).setUpClass()
@classmethod
def tearDownClass(cls):
super(MySeleniumTests, cls).tearDownClass()
cls.selenium.quit()
def test_login(self):
self.selenium.get('%s%s' % (self.live_server_url, '/login/'))
username_input = self.selenium.find_element_by_name("username")
username_input.send_keys('myuser')
password_input = self.selenium.find_element_by_name("password")
password_input.send_keys('secret')
self.selenium.find_element_by_xpath('//input[@value="Log in"]').click()
Finally, you may run the test as follows:
.. code-block:: bash
./manage.py test myapp.MySeleniumTests.test_login
This example will automatically open Firefox then go to the login page, enter
the credentials and press the "Log in" button. Selenium offers other drivers in
case you do not have Firefox installed or wish to use another browser. The
example above is just a tiny fraction of what the Selenium client can do; check
out the `full reference`_ for more details.
.. _Windmill: http://www.getwindmill.com/
.. _Selenium: http://seleniumhq.org/
.. _selenium package: http://pypi.python.org/pypi/selenium
.. _full reference: http://readthedocs.org/docs/selenium-python/en/latest/api.html
.. _Firefox: http://www.mozilla.com/firefox/
.. note::
``LiveServerTestCase`` makes use of the :doc:`staticfiles contrib app
</howto/static-files>` so you'll need to have your project configured
accordingly (in particular by setting :setting:`STATIC_URL`).
Using different testing frameworks
==================================
......@@ -1833,11 +1923,9 @@ set up, execute and tear down the test suite.
those options will be added to the list of command-line options that
the :djadmin:`test` command can use.
Attributes
~~~~~~~~~~
.. attribute:: DjangoTestSuiteRunner.option_list
.. versionadded:: 1.4
......
......@@ -109,6 +109,10 @@ class SottoCapoInline(admin.TabularInline):
model = SottoCapo
class ProfileInline(admin.TabularInline):
model = Profile
extra = 1
site.register(TitleCollection, inlines=[TitleInline])
# Test bug #12561 and #12778
# only ModelAdmin media
......@@ -124,3 +128,4 @@ site.register(Fashionista, inlines=[InlineWeakness])
site.register(Holder4, Holder4Admin)
site.register(Author, AuthorAdmin)
site.register(CapoFamiglia, inlines=[ConsigliereInline, SottoCapoInline])
site.register(ProfileCollection, inlines=[ProfileInline])
\ No newline at end of file
......@@ -136,3 +136,13 @@ class Consigliere(models.Model):
class SottoCapo(models.Model):
name = models.CharField(max_length=100)
capo_famiglia = models.ForeignKey(CapoFamiglia, related_name='+')
# Other models
class ProfileCollection(models.Model):
pass
class Profile(models.Model):
collection = models.ForeignKey(ProfileCollection, blank=True, null=True)
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
\ No newline at end of file
from __future__ import absolute_import
from django.contrib.admin.tests import AdminSeleniumWebDriverTestCase
from django.contrib.admin.helpers import InlineAdminForm
from django.contrib.auth.models import User, Permission
from django.contrib.contenttypes.models import ContentType
......@@ -8,7 +9,8 @@ from django.test import TestCase
# local test models
from .admin import InnerInline
from .models import (Holder, Inner, Holder2, Inner2, Holder3, Inner3, Person,
OutfitItem, Fashionista, Teacher, Parent, Child, Author, Book)
OutfitItem, Fashionista, Teacher, Parent, Child, Author, Book, Profile,
ProfileCollection)
class TestInline(TestCase):
......@@ -380,3 +382,105 @@ class TestInlinePermissions(TestCase):
self.assertContains(response, 'value="4" id="id_inner2_set-TOTAL_FORMS"')
self.assertContains(response, '<input type="hidden" name="inner2_set-0-id" value="%i"' % self.inner2_id)
self.assertContains(response, 'id="id_inner2_set-0-DELETE"')
class SeleniumFirefoxTests(AdminSeleniumWebDriverTestCase):
webdriver_class = 'selenium.webdriver.firefox.webdriver.WebDriver'
fixtures = ['admin-views-users.xml']
urls = "regressiontests.admin_inlines.urls"
def test_add_inlines(self):
"""
Ensure that the "Add another XXX" link correctly adds items to the
inline form.
"""
self.admin_login(username='super', password='secret')
self.selenium.get('%s%s' % (self.live_server_url,
'/admin/admin_inlines/profilecollection/add/'))
# Check that there's only one inline to start with and that it has the
# correct ID.
self.failUnlessEqual(len(self.selenium.find_elements_by_css_selector(
'#profile_set-group table tr.dynamic-profile_set')), 1)
self.failUnlessEqual(self.selenium.find_element_by_css_selector(
'.dynamic-profile_set:nth-of-type(1)').get_attribute('id'),
'profile_set-0')
self.failUnlessEqual(len(self.selenium.find_elements_by_css_selector(
'form#profilecollection_form tr.dynamic-profile_set#profile_set-0 input[name=profile_set-0-first_name]')), 1)
self.failUnlessEqual(len(self.selenium.find_elements_by_css_selector(
'form#profilecollection_form tr.dynamic-profile_set#profile_set-0 input[name=profile_set-0-last_name]')), 1)
# Add an inline
self.selenium.find_element_by_link_text('Add another Profile').click()
# Check that the inline has been added, that it has the right id, and
# that it contains the right fields.
self.failUnlessEqual(len(self.selenium.find_elements_by_css_selector(
'#profile_set-group table tr.dynamic-profile_set')), 2)
self.failUnlessEqual(self.selenium.find_element_by_css_selector(
'.dynamic-profile_set:nth-of-type(2)').get_attribute('id'), 'profile_set-1')
self.failUnlessEqual(len(self.selenium.find_elements_by_css_selector(
'form#profilecollection_form tr.dynamic-profile_set#profile_set-1 input[name=profile_set-1-first_name]')), 1)
self.failUnlessEqual(len(self.selenium.find_elements_by_css_selector(
'form#profilecollection_form tr.dynamic-profile_set#profile_set-1 input[name=profile_set-1-last_name]')), 1)
# Let's add another one to be sure
self.selenium.find_element_by_link_text('Add another Profile').click()
self.failUnlessEqual(len(self.selenium.find_elements_by_css_selector(
'#profile_set-group table tr.dynamic-profile_set')), 3)
self.failUnlessEqual(self.selenium.find_element_by_css_selector(
'.dynamic-profile_set:nth-of-type(3)').get_attribute('id'), 'profile_set-2')
self.failUnlessEqual(len(self.selenium.find_elements_by_css_selector(
'form#profilecollection_form tr.dynamic-profile_set#profile_set-2 input[name=profile_set-2-first_name]')), 1)
self.failUnlessEqual(len(self.selenium.find_elements_by_css_selector(
'form#profilecollection_form tr.dynamic-profile_set#profile_set-2 input[name=profile_set-2-last_name]')), 1)
# Enter some data and click 'Save'
self.selenium.find_element_by_name('profile_set-0-first_name').send_keys('0 first name 1')
self.selenium.find_element_by_name('profile_set-0-last_name').send_keys('0 last name 2')
self.selenium.find_element_by_name('profile_set-1-first_name').send_keys('1 first name 1')
self.selenium.find_element_by_name('profile_set-1-last_name').send_keys('1 last name 2')
self.selenium.find_element_by_name('profile_set-2-first_name').send_keys('2 first name 1')
self.selenium.find_element_by_name('profile_set-2-last_name').send_keys('2 last name 2')
self.selenium.find_element_by_xpath('//input[@value="Save"]').click()
# Check that the objects have been created in the database
self.assertEqual(ProfileCollection.objects.all().count(), 1)
self.assertEqual(Profile.objects.all().count(), 3)
def test_delete_inlines(self):
self.admin_login(username='super', password='secret')
self.selenium.get('%s%s' % (self.live_server_url,
'/admin/admin_inlines/profilecollection/add/'))
# Add a few inlines
self.selenium.find_element_by_link_text('Add another Profile').click()
self.selenium.find_element_by_link_text('Add another Profile').click()
self.selenium.find_element_by_link_text('Add another Profile').click()
self.selenium.find_element_by_link_text('Add another Profile').click()
self.failUnlessEqual(len(self.selenium.find_elements_by_css_selector(
'#profile_set-group table tr.dynamic-profile_set')), 5)
self.failUnlessEqual(len(self.selenium.find_elements_by_css_selector(
'form#profilecollection_form tr.dynamic-profile_set#profile_set-0')), 1)
self.failUnlessEqual(len(self.selenium.find_elements_by_css_selector(
'form#profilecollection_form tr.dynamic-profile_set#profile_set-1')), 1)
self.failUnlessEqual(len(self.selenium.find_elements_by_css_selector(
'form#profilecollection_form tr.dynamic-profile_set#profile_set-2')), 1)
self.failUnlessEqual(len(self.selenium.find_elements_by_css_selector(
'form#profilecollection_form tr.dynamic-profile_set#profile_set-3')), 1)
self.failUnlessEqual(len(self.selenium.find_elements_by_css_selector(
'form#profilecollection_form tr.dynamic-profile_set#profile_set-4')), 1)
# Click on a few delete buttons
self.selenium.find_element_by_css_selector(
'form#profilecollection_form tr.dynamic-profile_set#profile_set-1 td.delete a').click()
self.selenium.find_element_by_css_selector(
'form#profilecollection_form tr.dynamic-profile_set#profile_set-2 td.delete a').click()
# Verify that they're gone and that the IDs have been re-sequenced
self.failUnlessEqual(len(self.selenium.find_elements_by_css_selector(
'#profile_set-group table tr.dynamic-profile_set')), 3)
self.failUnlessEqual(len(self.selenium.find_elements_by_css_selector(
'form#profilecollection_form tr.dynamic-profile_set#profile_set-0')), 1)
self.failUnlessEqual(len(self.selenium.find_elements_by_css_selector(
'form#profilecollection_form tr.dynamic-profile_set#profile_set-1')), 1)
self.failUnlessEqual(len(self.selenium.find_elements_by_css_selector(
'form#profilecollection_form tr.dynamic-profile_set#profile_set-2')), 1)
......@@ -13,6 +13,7 @@ import re
from django import conf, bin, get_version
from django.conf import settings
from django.test.simple import DjangoTestSuiteRunner
from django.utils import unittest
......@@ -1058,6 +1059,50 @@ class ManageValidate(AdminScriptTestCase):
self.assertOutput(out, '0 errors found')
class CustomTestRunner(DjangoTestSuiteRunner):
def __init__(self, *args, **kwargs):
assert 'liveserver' not in kwargs
super(CustomTestRunner, self).__init__(*args, **kwargs)
def run_tests(self, test_labels, extra_tests=None, **kwargs):
pass
class ManageTestCommand(AdminScriptTestCase):
def setUp(self):
from django.core.management.commands.test import Command as TestCommand
self.cmd = TestCommand()
def test_liveserver(self):
"""
Ensure that the --liveserver option sets the environment variable
correctly.
Refs #2879.
"""
# Backup original state
address_predefined = 'DJANGO_LIVE_TEST_SERVER_ADDRESS' in os.environ
old_address = os.environ.get('DJANGO_LIVE_TEST_SERVER_ADDRESS')
self.cmd.handle(verbosity=0, testrunner='regressiontests.admin_scripts.tests.CustomTestRunner')
# Original state hasn't changed
self.assertEqual('DJANGO_LIVE_TEST_SERVER_ADDRESS' in os.environ, address_predefined)
self.assertEqual(os.environ.get('DJANGO_LIVE_TEST_SERVER_ADDRESS'), old_address)
self.cmd.handle(verbosity=0, testrunner='regressiontests.admin_scripts.tests.CustomTestRunner',
liveserver='blah')
# Variable was correctly set
self.assertEqual(os.environ['DJANGO_LIVE_TEST_SERVER_ADDRESS'], 'blah')
# Restore original state
if address_predefined:
os.environ['DJANGO_LIVE_TEST_SERVER_ADDRESS'] = old_address
else:
del os.environ['DJANGO_LIVE_TEST_SERVER_ADDRESS']
class ManageRunserver(AdminScriptTestCase):
def setUp(self):
from django.core.management.commands.runserver import BaseRunserverCommand
......
......@@ -7,6 +7,7 @@ from django import forms
from django.conf import settings
from django.contrib import admin
from django.contrib.admin import widgets
from django.contrib.admin.tests import AdminSeleniumWebDriverTestCase
from django.core.files.storage import default_storage
from django.core.files.uploadedfile import SimpleUploadedFile
from django.db.models import DateField
......@@ -407,3 +408,52 @@ class RelatedFieldWidgetWrapperTests(DjangoTestCase):
# Used to fail with a name error.
w = widgets.RelatedFieldWidgetWrapper(w, rel, widget_admin_site)
self.assertFalse(w.can_add_related)
class SeleniumFirefoxTests(AdminSeleniumWebDriverTestCase):
webdriver_class = 'selenium.webdriver.firefox.webdriver.WebDriver'
fixtures = ['admin-widgets-users.xml']
urls = "regressiontests.admin_widgets.urls"
def test_show_hide_date_time_picker_widgets(self):
"""
Ensure that pressing the ESC key closes the date and time picker
widgets.
Refs #17064.
"""
from selenium.webdriver.common.keys import Keys
self.admin_login(username='super', password='secret', login_url='/')
# Open a page that has a date and time picker widgets
self.selenium.get('%s%s' % (self.live_server_url,
'/admin_widgets/member/add/'))
# First, with the date picker widget ---------------------------------
# Check that the date picker is hidden
self.assertEqual(
self.get_css_value('#calendarbox0', 'display'), 'none')
# Click the calendar icon
self.selenium.find_element_by_id('calendarlink0').click()
# Check that the date picker is visible
self.assertEqual(
self.get_css_value('#calendarbox0', 'display'), 'block')
# Press the ESC key
self.selenium.find_element_by_tag_name('html').send_keys([Keys.ESCAPE])
# Check that the date picker is hidden again
self.assertEqual(
self.get_css_value('#calendarbox0', 'display'), 'none')
# Then, with the time picker widget ----------------------------------
# Check that the time picker is hidden
self.assertEqual(
self.get_css_value('#clockbox0', 'display'), 'none')
# Click the time icon
self.selenium.find_element_by_id('clocklink0').click()
# Check that the time picker is visible
self.assertEqual(
self.get_css_value('#clockbox0', 'display'), 'block')
# Press the ESC key
self.selenium.find_element_by_tag_name('html').send_keys([Keys.ESCAPE])
# Check that the time picker is hidden again
self.assertEqual(
self.get_css_value('#clockbox0', 'display'), 'none')
[
{
"pk": 1,
"model": "servers.person",
"fields": {
"name": "jane"
}
},
{
"pk": 2,
"model": "servers.person",
"fields": {
"name": "robert"
}
}
]
\ No newline at end of file
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=256)
......@@ -3,13 +3,17 @@ Tests for django.core.servers.
"""
import os
from urlparse import urljoin
import urllib2
import django
from django.conf import settings
from django.test import TestCase
from django.core.exceptions import ImproperlyConfigured
from django.test import TestCase, LiveServerTestCase
from django.core.handlers.wsgi import WSGIHandler
from django.core.servers.basehttp import AdminMediaHandler
from django.core.servers.basehttp import AdminMediaHandler, WSGIServerException
from django.test.utils import override_settings
from .models import Person
class AdminMediaHandlerTests(TestCase):
......@@ -68,3 +72,146 @@ class AdminMediaHandlerTests(TestCase):
continue
self.fail('URL: %s should have caused a ValueError exception.'
% url)
TEST_ROOT = os.path.dirname(__file__)
TEST_SETTINGS = {
'MEDIA_URL': '/media/',
'MEDIA_ROOT': os.path.join(TEST_ROOT, 'media'),
'STATIC_URL': '/static/',
'STATIC_ROOT': os.path.join(TEST_ROOT, 'static'),
}
class LiveServerBase(LiveServerTestCase):
urls = 'regressiontests.servers.urls'
fixtures = ['testdata.json']
@classmethod
def setUpClass(cls):
# Override settings
cls.settings_override = override_settings(**TEST_SETTINGS)
cls.settings_override.enable()
super(LiveServerBase, cls).setUpClass()
@classmethod
def tearDownClass(cls):
# Restore original settings
cls.settings_override.disable()
super(LiveServerBase, cls).tearDownClass()
def urlopen(self, url):
server_address = os.environ.get(
'DJANGO_LIVE_TEST_SERVER_ADDRESS', 'localhost:8081')
base = 'http://%s' % server_address
return urllib2.urlopen(base + url)
class LiveServerAddress(LiveServerBase):
"""
Ensure that the address set in the environment variable is valid.
Refs #2879.
"""
@classmethod
def setUpClass(cls):
# Backup original environment variable
address_predefined = 'DJANGO_LIVE_TEST_SERVER_ADDRESS' in os.environ
old_address = os.environ.get('DJANGO_LIVE_TEST_SERVER_ADDRESS')
# Just the host is not accepted
os.environ['DJANGO_LIVE_TEST_SERVER_ADDRESS'] = 'localhost'
try:
super(LiveServerAddress, cls).setUpClass()
raise Exception("The line above should have raised an exception")
except ImproperlyConfigured:
pass
# The host must be valid
os.environ['DJANGO_LIVE_TEST_SERVER_ADDRESS'] = 'blahblahblah:8081'
try:
super(LiveServerAddress, cls).setUpClass()
raise Exception("The line above should have raised an exception")
except WSGIServerException:
pass
# If contrib.staticfiles isn't configured properly, the exception
# should bubble up to the main thread.
old_STATIC_URL = TEST_SETTINGS['STATIC_URL']
TEST_SETTINGS['STATIC_URL'] = None
os.environ['DJANGO_LIVE_TEST_SERVER_ADDRESS'] = 'localhost:8081'
try:
super(LiveServerAddress, cls).setUpClass()
raise Exception("The line above should have raised an exception")
except ImproperlyConfigured:
pass
TEST_SETTINGS['STATIC_URL'] = old_STATIC_URL
# Restore original environment variable
if address_predefined:
os.environ['DJANGO_LIVE_TEST_SERVER_ADDRESS'] = old_address
else:
del os.environ['DJANGO_LIVE_TEST_SERVER_ADDRESS']
def test_test_test(self):
# Intentionally empty method so that the test is picked up by the
# test runner and the overriden setUpClass() method is executed.
pass
class LiveServerViews(LiveServerBase):
def test_404(self):
"""
Ensure that the LiveServerTestCase serves 404s.
Refs #2879.
"""
try:
self.urlopen('/')
except urllib2.HTTPError, err:
self.assertEquals(err.code, 404, 'Expected 404 response')
else:
self.fail('Expected 404 response')
def test_view(self):
"""
Ensure that the LiveServerTestCase serves views.
Refs #2879.
"""
f = self.urlopen('/example_view/')
self.assertEquals(f.read(), 'example view')
def test_static_files(self):
"""
Ensure that the LiveServerTestCase serves static files.
Refs #2879.
"""
f = self.urlopen('/static/example_static_file.txt')
self.assertEquals(f.read(), 'example static file\n')
def test_media_files(self):
"""
Ensure that the LiveServerTestCase serves media files.
Refs #2879.
"""
f = self.urlopen('/media/example_media_file.txt')
self.assertEquals(f.read(), 'example media file\n')
class LiveServerDatabase(LiveServerBase):
def test_fixtures_loaded(self):
"""
Ensure that fixtures are properly loaded and visible to the
live server thread.
Refs #2879.
"""
f = self.urlopen('/model_view/')
self.assertEquals(f.read().splitlines(), ['jane', 'robert'])
def test_database_writes(self):
"""
Ensure that data written to the database by a view can be read.
Refs #2879.
"""
self.urlopen('/create_model_instance/')
names = [person.name for person in Person.objects.all()]
self.assertEquals(names, ['jane', 'robert', 'emily'])
from __future__ import absolute_import
from django.conf.urls import patterns, url
from . import views
urlpatterns = patterns('',
url(r'^example_view/$', views.example_view),
url(r'^model_view/$', views.model_view),
url(r'^create_model_instance/$', views.create_model_instance),
)
\ No newline at end of file
from django.http import HttpResponse
from .models import Person
def example_view(request):
return HttpResponse('example view')
def model_view(request):
people = Person.objects.all()
return HttpResponse('\n'.join([person.name for person in people]))
def create_model_instance(request):
person = Person(name='emily')
person.save()
return HttpResponse('')
\ No newline at end of file
......@@ -49,7 +49,10 @@ def geodjango(settings):
def get_test_modules():
modules = []
for loc, dirpath in (MODEL_TESTS_DIR_NAME, MODEL_TEST_DIR), (REGRESSION_TESTS_DIR_NAME, REGRESSION_TEST_DIR), (CONTRIB_DIR_NAME, CONTRIB_DIR):
for loc, dirpath in (
(MODEL_TESTS_DIR_NAME, MODEL_TEST_DIR),
(REGRESSION_TESTS_DIR_NAME, REGRESSION_TEST_DIR),
(CONTRIB_DIR_NAME, CONTRIB_DIR)):
for f in os.listdir(dirpath):
if (f.startswith('__init__') or
f.startswith('.') or
......@@ -150,7 +153,8 @@ def django_tests(verbosity, interactive, failfast, test_labels):
settings.TEST_RUNNER = 'django.test.simple.DjangoTestSuiteRunner'
TestRunner = get_runner(settings)
test_runner = TestRunner(verbosity=verbosity, interactive=interactive, failfast=failfast)
test_runner = TestRunner(verbosity=verbosity, interactive=interactive,
failfast=failfast)
failures = test_runner.run_tests(test_labels, extra_tests=extra_tests)
teardown(state)
......@@ -175,7 +179,8 @@ def bisect_tests(bisection_label, options, test_labels):
except ValueError:
pass
subprocess_args = [sys.executable, __file__, '--settings=%s' % options.settings]
subprocess_args = [
sys.executable, __file__, '--settings=%s' % options.settings]
if options.failfast:
subprocess_args.append('--failfast')
if options.verbosity:
......@@ -235,7 +240,8 @@ def paired_tests(paired_test, options, test_labels):
except ValueError:
pass
subprocess_args = [sys.executable, __file__, '--settings=%s' % options.settings]
subprocess_args = [
sys.executable, __file__, '--settings=%s' % options.settings]
if options.failfast:
subprocess_args.append('--failfast')
if options.verbosity:
......@@ -244,7 +250,8 @@ def paired_tests(paired_test, options, test_labels):
subprocess_args.append('--noinput')
for i, label in enumerate(test_labels):
print '***** %d of %d: Check test pairing with %s' % (i+1, len(test_labels), label)
print '***** %d of %d: Check test pairing with %s' % (
i+1, len(test_labels), label)
failures = subprocess.call(subprocess_args + [label, paired_test])
if failures:
print '***** Found problem pair with',label
......@@ -257,19 +264,36 @@ if __name__ == "__main__":
from optparse import OptionParser
usage = "%prog [options] [module module module ...]"
parser = OptionParser(usage=usage)
parser.add_option('-v','--verbosity', action='store', dest='verbosity', default='1',
parser.add_option(
'-v','--verbosity', action='store', dest='verbosity', default='1',
type='choice', choices=['0', '1', '2', '3'],
help='Verbosity level; 0=minimal output, 1=normal output, 2=all output')
parser.add_option('--noinput', action='store_false', dest='interactive', default=True,
help='Verbosity level; 0=minimal output, 1=normal output, 2=all '
'output')
parser.add_option(
'--noinput', action='store_false', dest='interactive', default=True,
help='Tells Django to NOT prompt the user for input of any kind.')
parser.add_option('--failfast', action='store_true', dest='failfast', default=False,
help='Tells Django to stop running the test suite after first failed test.')
parser.add_option('--settings',
help='Python path to settings module, e.g. "myproject.settings". If this isn\'t provided, the DJANGO_SETTINGS_MODULE environment variable will be used.')
parser.add_option('--bisect', action='store', dest='bisect', default=None,
help="Bisect the test suite to discover a test that causes a test failure when combined with the named test.")
parser.add_option('--pair', action='store', dest='pair', default=None,
help="Run the test suite in pairs with the named test to find problem pairs.")
parser.add_option(
'--failfast', action='store_true', dest='failfast', default=False,
help='Tells Django to stop running the test suite after first failed '
'test.')
parser.add_option(
'--settings',
help='Python path to settings module, e.g. "myproject.settings". If '
'this isn\'t provided, the DJANGO_SETTINGS_MODULE environment '
'variable will be used.')
parser.add_option(
'--bisect', action='store', dest='bisect', default=None,
help='Bisect the test suite to discover a test that causes a test '
'failure when combined with the named test.')
parser.add_option(
'--pair', action='store', dest='pair', default=None,
help='Run the test suite in pairs with the named test to find problem '
'pairs.')
parser.add_option(
'--liveserver', action='store', dest='liveserver', default=None,
help='Overrides the default address where the live server (used with '
'LiveServerTestCase) is expected to run from. The default value '
'is localhost:8081.'),
options, args = parser.parse_args()
if options.settings:
os.environ['DJANGO_SETTINGS_MODULE'] = options.settings
......@@ -279,11 +303,15 @@ if __name__ == "__main__":
else:
options.settings = os.environ['DJANGO_SETTINGS_MODULE']
if options.liveserver is not None:
os.environ['DJANGO_LIVE_TEST_SERVER_ADDRESS'] = options.liveserver
if options.bisect:
bisect_tests(options.bisect, options, args)
elif options.pair:
paired_tests(options.pair, options, args)
else:
failures = django_tests(int(options.verbosity), options.interactive, options.failfast, args)
failures = django_tests(int(options.verbosity), options.interactive,
options.failfast, args)
if failures:
sys.exit(bool(failures))
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment