Index: django/test/testcases.py
===================================================================
--- django/test/testcases.py	(revision 7774)
+++ django/test/testcases.py	(working copy)
@@ -1,13 +1,22 @@
+import os
+import sys
 import re
+import socket
+import threading
 import unittest
 from urlparse import urlsplit, urlunsplit
 
 from django.http import QueryDict
 from django.db import transaction
 from django.core import mail
+from django.core.handlers.wsgi import WSGIHandler
 from django.core.management import call_command
+from django.core.management.color import color_style
+from django.core.servers.basehttp import WSGIServer, WSGIServerException, \
+    AdminMediaHandler, WSGIRequestHandler
 from django.test import _doctest as doctest
 from django.test.client import Client
+from django.test.utils import create_test_db
 
 normalize_long_ints = lambda s: re.sub(r'(?<![\w])(\d+)L(?![\w])', '\\1', s)
 
@@ -47,6 +56,67 @@
         # side effects on other tests.
         transaction.rollback_unless_managed()
 
+class StoppableWSGIServer(WSGIServer):
+    """ WSGIServer with short timout, so that server thread can stop this server. """
+     
+    def server_bind(self):
+        """ Sets timeout to 1 second. """
+        WSGIServer.server_bind(self)
+        self.socket.settimeout(1)
+    
+    def get_request(self):
+        """ Checks for timeout when getting request. """
+        try:
+            sock, address = self.socket.accept()
+            sock.settimeout(None)
+            return (sock, address)
+        except socket.timeout:
+            raise
+
+class TestServerThread(threading.Thread):
+    """ Thread for running a http server while tests are running. """
+    def __init__(self, address, port):
+        self.address = address
+        self.port = port
+        self._stopevent = threading.Event()
+        self.started = threading.Event()
+        self.error = None
+        super(TestServerThread, self).__init__()
+
+    def run(self):
+        """ Sets up test server and database and loops over handling http requests. """
+        try:
+            handler = AdminMediaHandler(WSGIHandler())
+            server_address = (self.address, self.port)
+            httpd = StoppableWSGIServer(server_address, WSGIRequestHandler)
+            httpd.set_app(handler)
+            self.started.set()
+        except WSGIServerException, e:
+            # Use helpful error messages instead of ugly tracebacks.
+            self.error = e
+            self.started.set()
+            return
+        
+        # Must do database stuff in this new thread if database in memory.
+        from django.conf import settings
+        if settings.DATABASE_ENGINE == "sqlite3" \
+            and (not settings.TEST_DATABASE_NAME or settings.TEST_DATABASE_NAME == ":memory:"):
+            db_name = create_test_db(0)
+            # Import the fixture data into the test database.
+            if hasattr(self, 'fixtures'):
+                # We have to use this slightly awkward syntax due to the fact
+                # that we're using *args and **kwargs together.
+                call_command('loaddata', *self.fixtures, **{'verbosity': 0})
+            
+        # Loop until we get a stop event
+        while not self._stopevent.isSet():
+            httpd.handle_request()
+        
+    def join(self, timeout=None):
+        """ Stop the thread and wait for it to end. """
+        self._stopevent.set()
+        threading.Thread.join(self, timeout)
+
 class TestCase(unittest.TestCase):
     def _pre_setup(self):
         """Performs any pre-test setup. This includes:
@@ -79,6 +149,20 @@
             result.addError(self, sys.exc_info())
             return
         super(TestCase, self).__call__(result)
+    
+    def start_test_server(self, address='localhost', port=8000):
+        """
+        Creates a live test server object (instance of WSGIServer).
+        """
+        self.server_thread = TestServerThread(address, port)
+        self.server_thread.start()
+        self.server_thread.started.wait()
+        if self.server_thread.error:
+            raise self.server_thread.error
+    
+    def stop_test_server(self):
+        if self.server_thread:
+            self.server_thread.join()
 
     def assertRedirects(self, response, expected_url, status_code=302,
                         target_status_code=200, host=None):
Index: tests/regressiontests/test_client_regress/models.py
===================================================================
--- tests/regressiontests/test_client_regress/models.py	(revision 7774)
+++ tests/regressiontests/test_client_regress/models.py	(working copy)
@@ -6,6 +6,7 @@
 from django.core.urlresolvers import reverse
 from django.core.exceptions import SuspiciousOperation
 import os
+import urllib
 
 class AssertContainsTests(TestCase):
     def test_contains(self):
@@ -318,3 +319,21 @@
             self.client.get("/test_client_regress/staff_only/")
         except SuspiciousOperation:
             self.fail("Staff should be able to visit this page")
+
+class TestServerTests(TestCase):
+    def setUp(self):
+        self.start_test_server(address='localhost', port=8000)
+
+    def tearDown(self):
+        self.stop_test_server()
+
+    def test_server_up(self):
+        url = urllib.urlopen('http://localhost:8000/')
+        self.assertEqual(url.read(), 'Django Internal Tests: 404 Error')
+        url.close()
+
+    def test_serve_page(self):
+        url = urllib.urlopen('http://localhost:8000/accounts/login/')
+        # Just make sure this isn't a 404, and we've gotten something.
+        self.assertNotEqual(url.read(), 'Django Internal Tests: 404 Error')
+        url.close()
Index: AUTHORS
===================================================================
--- AUTHORS	(revision 7774)
+++ AUTHORS	(working copy)
@@ -269,6 +269,7 @@
     Robert Myers <myer0052@gmail.com>
     Nebojša Dorđević
     Doug Napoleone <doug@dougma.com>
+    Devin Naquin <devin@disqus.com>
     Gopal Narayanan <gopastro@gmail.com>
     Fraser Nevett <mail@nevett.org>
     Sam Newman <http://www.magpiebrain.com/>
Index: docs/testing.txt
===================================================================
--- docs/testing.txt	(revision 7774)
+++ docs/testing.txt	(working copy)
@@ -797,6 +797,52 @@
 .. _dumpdata documentation: ../django-admin/#dumpdata-appname-appname
 .. _loaddata documentation: ../django-admin/#loaddata-fixture-fixture
 
+Running tests with a live test server
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+**New in Django development version**
+
+When running tests that use in-browser frameworks such as Twill_ and
+Selenium_, it's necessary to have a running test server. Django's custom ``TestCase`` class supports starting a live server for these purposes.
+
+``start_test_server(address='localhost', port=8000)``
+    Starts a test server at ``address`` on ``port``. This should be done in the
+	``setUp()`` method of a subclass of ``TestCase``. The server then can be accessed
+	at http://address:port.
+
+``stop_test_server()``
+	Stops the test server that was started with ``start_test_server``. This must be
+	done before ``start_test_server`` can be called again, so this should be done in
+	the ``tearDown()`` method of a subclass of ``TestCase``.
+
+This can be used to start a server that can then be accessed by Twill, Selenium or another in-browser test framework. For example::
+
+	from django.test.testcases import TestCase
+	from selenium import selenium
+	
+	class TestLogin(TestCase):
+		fixtures = ['login']
+		
+		def setUp(self):
+			# start test server and tell selenium where to find it
+			self.start_test_server('localhost', 8000)
+			self.selenium = selenium(addr, '4444', \
+				'*pifirefox', 'http://localhost:8000')
+
+		def tearDown(self):
+			# stop server and selenium
+			self.selenium.stop()
+			self.stop_test_server()
+
+		def testLogin(self):
+			self.selenium.open('/admin/')
+			self.selenium.type('username','admin')
+			self.selenium.type('password','password')
+			self.selenium.click("//input[@value='Log in']")
+
+.. _Twill: http://twill.idyll.org/
+.. _Selenium: http://www.openqa.org/selenium/
+
 Emptying the test outbox
 ~~~~~~~~~~~~~~~~~~~~~~~~
 
