Joys and rants of a Python programmer | Pow! Wham, bam, kapow!

Dec/09

1

Running browser in the middle of a Pylons test

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.

  • Digg
  • Reddit
  • Delicious
  • StumbleUpon
  • Share/Bookmark

RSS Feed

3 Comments for Running browser in the middle of a Pylons test

Author comment by Marius Gedminas | December 1, 2009 at 21:08

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.)

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.

Leave a comment!

<< Formatting and processing text in TAL templates

Testing your translations for bugs >>

Find it!

Theme Design by devolux.org