1
Running browser in the middle of a Pylons test
3 Comments | Posted by Ignas Mikalajūnas in Programming
Tests are good! writing tests is good! Sometimes they break and you
can’t understand what’s wrong. Pylons is using webtest.TestApp for functional test and
TestResponse has a showbrowser method, a method that
displays the response in your browser, but most of the time it’s
just not enough.
When I started using Pylons, I missed the ability to start a server in the middle of the test so much (I had implemented this functionality for Zope3 functional tests before) that I just had to add it to my Pylons testing environment. As Pylons is a WSGI application, it was even easier than I expected:
import pylons
import webbrowser
import sys
import urllib2
from webtest import TestApp
from wsgiref.simple_server import make_server
def addPortToURL(url, port):
"""Add a port number to the url.
>>> addPortToURL('http://localhost/foo/bar/baz.html', 3000)
'http://localhost:3000/foo/bar/baz.html'
>>> addPortToURL('http://foo.bar.com/index.html?param=some-value', 555)
'http://foo.bar.com:555/index.html?param=some-value'
>>> addPortToURL('http://localhost:666/index.html', 555)
'http://localhost:555/index.html'
"""
(scheme, netloc, url, query, fragment) = urllib2.urlparse.urlsplit(url)
netloc = netloc.split(':')[0]
netloc = "%s:%s" % (netloc, port)
url = urllib2.urlparse.urlunsplit((scheme, netloc, url, query, fragment))
return url
class ServingTestApp(TestApp):
request = None
def do_request(self, req, status, expect_errors):
self.request = req
return super(ServingTestApp, self).do_request(req, status, expect_errors)
def serve(self, page_url=None):
try:
if page_url is None:
page_url = getattr(self.request, 'url', 'http://localhost/')
# XXX we rely on browser being slower than our server
webbrowser.open(addPortToURL(page_url, 5001))
print >> sys.stderr, 'Starting HTTP server...'
srv = make_server('localhost', 5001, pylons.test.pylonsapp)
srv.serve_forever()
except KeyboardInterrupt:
print >> sys.stderr, 'Stopped HTTP server.'
Now instead of the usual TestApp you just
use ServingTestApp
class TestController(TestCase):
def __init__(self, *args, **kwargs):
if pylons.test.pylonsapp:
wsgiapp = pylons.test.pylonsapp
else:
wsgiapp = loadapp('config:%s' % config['__file__'])
# Replace:
# self.app = TestApp(wsgiapp)
# With
self.app = ServingTestApp(wsgiapp)
url._push_object(URLGenerator(config['routes.map'], environ))
TestCase.__init__(self, *args, **kwargs)
And in your code you can do this:
from sample.tests import *
class TestHelloController(TestController):
def test_index(self):
response = self.app.get(url(controller='hello', action='index'))
# Test response...
self.app.serve()
This is immensely useful when you want to find out what to click next, while writing a test for a part of the application that you haven’t worked with for a while. Or browser stress-testing, because now you can just use your testing framework to generate thousands of throw away objects in a database that will be gone when a test is over, and will be there again when you run the test for the second time.
I use it a lot when I want to test the UI of deleting objects, or when I work on things that have a uni-directional workflow. Testing the step number 7 in your browser requires you completing the first 6 steps, doing that every time you want to look at the transition from step 7 to step 8 is just too much work.
3 Comments for Running browser in the middle of a Pylons test
Ian Bicking | December 7, 2009 at 05:06
Consider submitting this as a patch (or even better, a fork/pull request): http://bitbucket.org/ianb/webtest/ (I think you can just use self.app for TestApp instead of pylons.test…)
You might even have something like a flag in TestApp, on_failure_serve, which would start the app if you get an unexpected 500 response, and then replay until that point. (Well, capturing and replaying would be another feature I guess, but a cool feature, and doable — though maybe not entirely within TestApp.)
Ian Bicking | December 7, 2009 at 05:07
Wait… capture and replay is unnecessary now that I think about it.



Nice!
Instead of importing urllib2 and relying on it to import urlparse into its namespace, I suggest you import urlparse directly.
I’d also suggest moving the hardcoded port number (5001) into a ServingTestApp class attribute, just on general principles. (Also it would let you do things like self.app.testing_port = 5002; self.app.serve() in parallel interactive pdb sessions, if you like that sort of multitasking.)