diff -pruN 4.0.2-1/bootstrap.py 4.0.4-0ubuntu1/bootstrap.py
--- 4.0.2-1/bootstrap.py	2011-05-25 07:17:20.000000000 +0000
+++ 4.0.4-0ubuntu1/bootstrap.py	2013-10-11 07:12:28.000000000 +0000
@@ -18,99 +18,260 @@ The script accepts buildout command-line
 use the -c option to specify an alternate configuration file.
 """
 
-import os, shutil, sys, tempfile, urllib2
+import os, shutil, sys, tempfile, urllib, urllib2, subprocess
 from optparse import OptionParser
 
-tmpeggs = tempfile.mkdtemp()
+if sys.platform == 'win32':
+    def quote(c):
+        if ' ' in c:
+            return '"%s"' % c  # work around spawn lamosity on windows
+        else:
+            return c
+else:
+    quote = str
+
+# See zc.buildout.easy_install._has_broken_dash_S for motivation and comments.
+stdout, stderr = subprocess.Popen(
+    [sys.executable, '-Sc',
+     'try:\n'
+     '    import ConfigParser\n'
+     'except ImportError:\n'
+     '    print 1\n'
+     'else:\n'
+     '    print 0\n'],
+    stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
+has_broken_dash_S = bool(int(stdout.strip()))
+
+# In order to be more robust in the face of system Pythons, we want to
+# run without site-packages loaded.  This is somewhat tricky, in
+# particular because Python 2.6's distutils imports site, so starting
+# with the -S flag is not sufficient.  However, we'll start with that:
+if not has_broken_dash_S and 'site' in sys.modules:
+    # We will restart with python -S.
+    args = sys.argv[:]
+    args[0:0] = [sys.executable, '-S']
+    args = map(quote, args)
+    os.execv(sys.executable, args)
+# Now we are running with -S.  We'll get the clean sys.path, import site
+# because distutils will do it later, and then reset the path and clean
+# out any namespace packages from site-packages that might have been
+# loaded by .pth files.
+clean_path = sys.path[:]
+import site  # imported because of its side effects
+sys.path[:] = clean_path
+for k, v in sys.modules.items():
+    if k in ('setuptools', 'pkg_resources') or (
+        hasattr(v, '__path__') and
+        len(v.__path__) == 1 and
+        not os.path.exists(os.path.join(v.__path__[0], '__init__.py'))):
+        # This is a namespace package.  Remove it.
+        sys.modules.pop(k)
 
 is_jython = sys.platform.startswith('java')
 
+setuptools_source = 'http://peak.telecommunity.com/dist/ez_setup.py'
+distribute_source = 'http://python-distribute.org/distribute_setup.py'
+
+
 # parsing arguments
-parser = OptionParser()
+def normalize_to_url(option, opt_str, value, parser):
+    if value:
+        if '://' not in value:  # It doesn't smell like a URL.
+            value = 'file://%s' % (
+                urllib.pathname2url(
+                    os.path.abspath(os.path.expanduser(value))),)
+        if opt_str == '--download-base' and not value.endswith('/'):
+            # Download base needs a trailing slash to make the world happy.
+            value += '/'
+    else:
+        value = None
+    name = opt_str[2:].replace('-', '_')
+    setattr(parser.values, name, value)
+
+usage = '''\
+[DESIRED PYTHON FOR BUILDOUT] bootstrap.py [options]
+
+Bootstraps a buildout-based project.
+
+Simply run this script in a directory containing a buildout.cfg, using the
+Python that you want bin/buildout to use.
+
+Note that by using --setup-source and --download-base to point to
+local resources, you can keep this script from going over the network.
+'''
+
+parser = OptionParser(usage=usage)
 parser.add_option("-v", "--version", dest="version",
                           help="use a specific zc.buildout version")
 parser.add_option("-d", "--distribute",
-                   action="store_true", dest="distribute", default=False,
-                   help="Use Disribute rather than Setuptools.")
-
+                   action="store_true", dest="use_distribute", default=False,
+                   help="Use Distribute rather than Setuptools.")
+parser.add_option("--setup-source", action="callback", dest="setup_source",
+                  callback=normalize_to_url, nargs=1, type="string",
+                  help=("Specify a URL or file location for the setup file. "
+                        "If you use Setuptools, this will default to " +
+                        setuptools_source + "; if you use Distribute, this "
+                        "will default to " + distribute_source + "."))
+parser.add_option("--download-base", action="callback", dest="download_base",
+                  callback=normalize_to_url, nargs=1, type="string",
+                  help=("Specify a URL or directory for downloading "
+                        "zc.buildout and either Setuptools or Distribute. "
+                        "Defaults to PyPI."))
+parser.add_option("--eggs",
+                  help=("Specify a directory for storing eggs.  Defaults to "
+                        "a temporary directory that is deleted when the "
+                        "bootstrap script completes."))
+parser.add_option("-t", "--accept-buildout-test-releases",
+                  dest='accept_buildout_test_releases',
+                  action="store_true", default=False,
+                  help=("Normally, if you do not specify a --version, the "
+                        "bootstrap script and buildout gets the newest "
+                        "*final* versions of zc.buildout and its recipes and "
+                        "extensions for you.  If you use this flag, "
+                        "bootstrap and buildout will get the newest releases "
+                        "even if they are alphas or betas."))
 parser.add_option("-c", None, action="store", dest="config_file",
                    help=("Specify the path to the buildout configuration "
                          "file to be used."))
 
 options, args = parser.parse_args()
 
-# if -c was provided, we push it back into args for buildout' main function
-if options.config_file is not None:
-    args += ['-c', options.config_file]
-
-if options.version is not None:
-    VERSION = '==%s' % options.version
+if options.eggs:
+    eggs_dir = os.path.abspath(os.path.expanduser(options.eggs))
 else:
-    VERSION = ''
+    eggs_dir = tempfile.mkdtemp()
 
-USE_DISTRIBUTE = options.distribute
-args = args + ['bootstrap']
+if options.setup_source is None:
+    if options.use_distribute:
+        options.setup_source = distribute_source
+    else:
+        options.setup_source = setuptools_source
+
+if options.accept_buildout_test_releases:
+    args.insert(0, 'buildout:accept-buildout-test-releases=true')
 
 try:
     import pkg_resources
-    import setuptools
+    import setuptools  # A flag.  Sometimes pkg_resources is installed alone.
     if not hasattr(pkg_resources, '_distribute'):
         raise ImportError
 except ImportError:
+    ez_code = urllib2.urlopen(
+        options.setup_source).read().replace('\r\n', '\n')
     ez = {}
-    if USE_DISTRIBUTE:
-        exec urllib2.urlopen('http://python-distribute.org/distribute_setup.py'
-                         ).read() in ez
-        ez['use_setuptools'](to_dir=tmpeggs, download_delay=0, no_fake=True)
-    else:
-        exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py'
-                             ).read() in ez
-        ez['use_setuptools'](to_dir=tmpeggs, download_delay=0)
-
-    reload(sys.modules['pkg_resources'])
+    exec ez_code in ez
+    setup_args = dict(to_dir=eggs_dir, download_delay=0)
+    if options.download_base:
+        setup_args['download_base'] = options.download_base
+    if options.use_distribute:
+        setup_args['no_fake'] = True
+        if sys.version_info[:2] == (2, 4):
+            setup_args['version'] = '0.6.32'
+    ez['use_setuptools'](**setup_args)
+    if 'pkg_resources' in sys.modules:
+        reload(sys.modules['pkg_resources'])
     import pkg_resources
+    # This does not (always?) update the default working set.  We will
+    # do it.
+    for path in sys.path:
+        if path not in pkg_resources.working_set.entries:
+            pkg_resources.working_set.add_entry(path)
+
+cmd = [quote(sys.executable),
+       '-c',
+       quote('from setuptools.command.easy_install import main; main()'),
+       '-mqNxd',
+       quote(eggs_dir)]
+
+if not has_broken_dash_S:
+    cmd.insert(1, '-S')
+
+find_links = options.download_base
+if not find_links:
+    find_links = os.environ.get('bootstrap-testing-find-links')
+if not find_links and options.accept_buildout_test_releases:
+    find_links = 'http://downloads.buildout.org/'
+if find_links:
+    cmd.extend(['-f', quote(find_links)])
 
-if sys.platform == 'win32':
-    def quote(c):
-        if ' ' in c:
-            return '"%s"' % c # work around spawn lamosity on windows
-        else:
-            return c
+if options.use_distribute:
+    setup_requirement = 'distribute'
 else:
-    def quote (c):
-        return c
+    setup_requirement = 'setuptools'
+ws = pkg_resources.working_set
+setup_requirement_path = ws.find(
+    pkg_resources.Requirement.parse(setup_requirement)).location
+env = dict(
+    os.environ,
+    PYTHONPATH=setup_requirement_path)
+
+requirement = 'zc.buildout'
+version = options.version
+if version is None and not options.accept_buildout_test_releases:
+    # Figure out the most recent final version of zc.buildout.
+    import setuptools.package_index
+    _final_parts = '*final-', '*final'
+
+    def _final_version(parsed_version):
+        for part in parsed_version:
+            if (part[:1] == '*') and (part not in _final_parts):
+                return False
+        return True
+    index = setuptools.package_index.PackageIndex(
+        search_path=[setup_requirement_path])
+    if find_links:
+        index.add_find_links((find_links,))
+    req = pkg_resources.Requirement.parse(requirement)
+    if index.obtain(req) is not None:
+        best = []
+        bestv = None
+        for dist in index[req.project_name]:
+            distv = dist.parsed_version
+            if distv >= pkg_resources.parse_version('2dev'):
+                continue
+            if _final_version(distv):
+                if bestv is None or distv > bestv:
+                    best = [dist]
+                    bestv = distv
+                elif distv == bestv:
+                    best.append(dist)
+        if best:
+            best.sort()
+            version = best[-1].version
 
-cmd = 'from setuptools.command.easy_install import main; main()'
-ws  = pkg_resources.working_set
-
-if USE_DISTRIBUTE:
-    requirement = 'distribute'
+if version:
+    requirement += '=='+version
 else:
-    requirement = 'setuptools'
+    requirement += '<2dev'
+
+cmd.append(requirement)
 
 if is_jython:
     import subprocess
+    exitcode = subprocess.Popen(cmd, env=env).wait()
+else:  # Windows prefers this, apparently; otherwise we would prefer subprocess
+    exitcode = os.spawnle(*([os.P_WAIT, sys.executable] + cmd + [env]))
+if exitcode != 0:
+    sys.stdout.flush()
+    sys.stderr.flush()
+    print ("An error occurred when trying to install zc.buildout. "
+           "Look above this message for any errors that "
+           "were output by easy_install.")
+    sys.exit(exitcode)
 
-    assert subprocess.Popen([sys.executable] + ['-c', quote(cmd), '-mqNxd',
-           quote(tmpeggs), 'zc.buildout' + VERSION],
-           env=dict(os.environ,
-               PYTHONPATH=
-               ws.find(pkg_resources.Requirement.parse(requirement)).location
-               ),
-           ).wait() == 0
+ws.add_entry(eggs_dir)
+ws.require(requirement)
+import zc.buildout.buildout
 
-else:
-    assert os.spawnle(
-        os.P_WAIT, sys.executable, quote (sys.executable),
-        '-c', quote (cmd), '-mqNxd', quote (tmpeggs), 'zc.buildout' + VERSION,
-        dict(os.environ,
-            PYTHONPATH=
-            ws.find(pkg_resources.Requirement.parse(requirement)).location
-            ),
-        ) == 0
+# If there isn't already a command in the args, add bootstrap
+if not [a for a in args if '=' not in a]:
+    args.append('bootstrap')
+
+
+# if -c was provided, we push it back into args for buildout's main function
+if options.config_file is not None:
+    args[0:0] = ['-c', options.config_file]
 
-ws.add_entry(tmpeggs)
-ws.require('zc.buildout' + VERSION)
-import zc.buildout.buildout
 zc.buildout.buildout.main(args)
-shutil.rmtree(tmpeggs)
+if not options.eggs:  # clean up temporary egg directory
+    shutil.rmtree(eggs_dir)
diff -pruN 4.0.2-1/buildout.cfg 4.0.4-0ubuntu1/buildout.cfg
--- 4.0.2-1/buildout.cfg	2011-05-25 07:17:20.000000000 +0000
+++ 4.0.4-0ubuntu1/buildout.cfg	2013-10-11 07:12:28.000000000 +0000
@@ -1,6 +1,6 @@
 [buildout]
 develop = .
-parts = test test_bbb interpreter
+parts = test test_bbb coverage-test coverage-report interpreter
 
 [test]
 recipe = zc.recipe.testrunner
@@ -12,6 +12,19 @@ recipe = zc.recipe.testrunner
 defaults = ['--tests-pattern', '^f?tests$']
 eggs = zope.testbrowser [test,test_bbb]
 
+[coverage-test]
+recipe = zc.recipe.testrunner
+eggs = zope.testbrowser [test]
+defaults = ['--coverage', '${buildout:directory}/coverage']
+
+[coverage-report]
+recipe = zc.recipe.egg
+eggs =
+    z3c.coverage
+scripts = coveragereport
+arguments = ('${buildout:directory}/coverage',
+             '${buildout:directory}/coverage/report')
+
 [interpreter]
 recipe = zc.recipe.egg
 eggs = zope.testbrowser
diff -pruN 4.0.2-1/CHANGES.rst 4.0.4-0ubuntu1/CHANGES.rst
--- 4.0.2-1/CHANGES.rst	1970-01-01 00:00:00.000000000 +0000
+++ 4.0.4-0ubuntu1/CHANGES.rst	2013-10-11 07:12:28.000000000 +0000
@@ -0,0 +1,273 @@
+=======
+CHANGES
+=======
+
+4.0.4 (2013-10-11)
+------------------
+
+- Removed the 'WebTest <= 1.3.4' version pin, fixed tests to work with modern
+  WebTest versions
+  (https://github.com/zopefoundation/zope.testbrowser/issues/10).
+
+
+4.0.3 (2013-09-04)
+------------------
+
+- pinning version 'WebTest <= 1.3.4', because of some incompatibility and
+  test failures
+
+- Make zope.testbrowser installable via pip
+  (https://github.com/zopefoundation/zope.testbrowser/issues/6).
+
+- When ``Browser.handleErrors`` is False, also add ``x-wsgiorg.throw_errors``
+  to the environment. http://wsgi.org/wsgi/Specifications/throw_errors
+
+- Prevent WebTest from always sending ``paste.throw_errors=True`` in the
+  environment by setting it to ``None`` when ``Browser.handleErrors`` is
+  ``True``.  This makes it easier to test error pages.
+
+- Made Browser.submit() handle ``raiseHttpErrors``
+  (https://github.com/zopefoundation/zope.testbrowser/pull/4).
+
+- More friendly error messages from getControl() et al:
+
+  - when you specify an index that is out of bounds, show the available
+    choices
+
+  - when you fail to find anything, show all the available items
+
+
+4.0.2 (2011-05-25)
+------------------
+
+- Remove test dependency on zope.pagetemplate.
+
+
+4.0.1 (2011-05-04)
+------------------
+
+- Added a hint in documentation how to use ``zope.testbrowser.wsgi.Browser``
+  to test a Zope 2/Zope 3/Bluebream WSGI application.
+
+4.0.0 (2011-03-14)
+------------------
+
+- LP #721252: AmbiguityError now shows all matching controls.
+
+- Integrate with WebTest. ``zope.testbrowser.wsgi.Browser`` is a
+  ``Browser`` implementation that uses ``webtest.TestApp`` to drive a WSGI
+  application. This this replaces the wsgi_intercept support added in 3.11.
+
+- Re-write the test application as a pure WSGI application using WebOb. Run the
+  existing tests using the WebTest based Browser
+
+- Move zope.app.testing based Browser into ``zope.app.testing`` (leaving
+  backwards compatibility imports in-place). Released in ``zope.app.testing``
+  3.9.0.
+
+
+3.11.1 (2011-01-24)
+-------------------
+
+- Fixing brown bag release 3.11.0.
+
+
+3.11.0 (2011-01-24)
+-------------------
+
+- Added `wsgi_intercept` support (came from ``zope.app.wsgi.testlayer``).
+
+
+3.10.4 (2011-01-14)
+-------------------
+
+- Move the over-the-wire.txt doctest out of the TestBrowserLayer as it doesn't
+  need or use it.
+
+- Fix test compatibility with zope.app.testing 3.8.1.
+
+3.10.3 (2010-10-15)
+-------------------
+
+- Fixed backwards compatibility with ``zope.app.wsgi.testlayer``.
+
+
+3.10.2 (2010-10-15)
+-------------------
+
+- Fixed Python 2.7 compatibility in Browser.handleErrors.
+
+
+3.10.1 (2010-09-21)
+-------------------
+
+- Fixed a bug that caused the ``Browser`` to keep it's previous ``contents``
+  The places are:
+  - Link.click()
+  - SubmitControl.click()
+  - ImageControl.click()
+  - Form.submit()
+
+- Also adjusted exception messages at the above places to match
+  pre version 3.4.1 messages.
+
+
+3.10.0 (2010-09-14)
+-------------------
+
+- LP #98437: use mechanize's built-in ``submit()`` to submit forms, allowing
+  mechanize to set the "Referer:" (sic) header appropriately.
+
+- Fixed tests to run with ``zope.app.testing`` 3.8 and above.
+
+
+3.9.0 (2010-05-17)
+------------------
+
+- LP #568806: Update dependency ``mechanize >= 0.2.0``, which now includes
+  the ``ClientForm`` APIs.  Remove use of ``urllib2`` APIs (incompatible
+  with ``mechanize 0.2.0``) in favor of ``mechanize`` equivalents.
+  Thanks to John J. Lee for the patch.
+
+- Use stdlib ``doctest`` module, instead of ``zope.testing.doctest``.
+
+- **Caution:** This version is no longer fully compatible with Python 2.4:
+  ``handleErrors = False`` no longer works.
+
+
+3.8.1 (2010-04-19)
+------------------
+
+- Pinned dependency on mechanize to prevent use of the upcoming
+  0.2.0 release before we have time to adjust to its API changes.
+
+- LP #98396: testbrowser resolves relative URLs incorrectly.
+
+
+3.8.0 (2010-03-05)
+------------------
+
+- Added ``follow`` convenience method which gets and follows a link.
+
+
+3.7.0 (2009-12-17)
+------------------
+
+- Moved zope.app.testing dependency into the scope of the PublisherConnection
+  class. Zope2 specifies its own PublisherConnection which isn't dependent on
+  zope.app.testing.
+
+- Fixed LP #419119: return None when the browser has no contents instead of
+  raising an exception.
+
+
+3.7.0a1 (2009-08-29)
+--------------------
+
+- Remove dependency on zope.app.publisher in favor of zope.browserpage,
+  zope.browserresource and zope.ptresource.
+
+- Remove dependencies on zope.app.principalannotation and zope.securitypolicy
+  by using the simple PermissiveSecurityPolicy. We aren't testing security
+  in our tests.
+
+- Replaced the testing dependency on zope.app.zcmlfiles with explicit
+  dependencies of a minimal set of packages.
+
+- Remove unneeded zope.app.authentication from ftesting.zcml.
+
+- Test dependency on zope.securitypolicy instead of its app variant.
+
+
+3.6.0a2 (2009-01-31)
+--------------------
+
+- Test dependency on zope.site.folder instead of zope.app.folder.
+
+- Remove useless test dependency in zope.app.component.
+
+
+3.6.0a1 (2009-01-08)
+--------------------
+
+- Author e-mail to zope-dev rather than zope3-dev.
+
+- New lines are no longer stripped in XML and HTML code contained in a
+  textarea; fix requires ClientForm >= 0.2.10 (LP #268139).
+
+- Added ``cookies`` attribute to browser for easy manipulation of browser
+  cookies.  See brief example in main documentation, plus new ``cookies.txt``
+  documentation.
+
+
+3.5.1 (2008-10-10)
+------------------
+
+- Provide a work around for a mechanize/urllib2 bug on Python 2.6
+  missing 'timeout' attribute on 'Request' base class.
+
+- Provide a work around for a mechanize/urllib2 bug in creating request
+  objects that won't handle fragment URLs correctly.
+
+
+3.5.0 (2008-03-30)
+------------------
+
+- Added a zope.testbrowser.testing.Browser.post method that allows
+  tests to supply a body and a content type.  This is handy for
+  testing Ajax requests with non-form input (e.g. JSON).
+
+- Remove vendor import of mechanize.
+
+- Fix bug that caused HTTP exception tracebacks to differ between version 3.4.0
+  and 3.4.1.
+
+- Workaround for bug in Python Cookie.SimpleCookie when handling unicode
+  strings.
+
+- Fix bug introduced in 3.4.1 that created incompatible tracebacks in doctests.
+  This necessitated adding a patched mechanize to the source tree; patches have
+  been sent to the mechanize project.
+
+- Fix https://bugs.launchpad.net/bugs/149517 by adding zope.interface and
+  zope.schema as real dependencies
+
+- Fix browser.getLink documentation that was not updated since the last API
+  modification.
+
+- Move tests for fixed bugs to a separate file.
+
+- Removed non-functional and undocumented code intended to help test servers
+  using virtual hosting.
+
+
+3.4.2 (2007-10-31)
+------------------
+
+- Resolve ``ZopeSecurityPolicy`` deprecation warning.
+
+
+3.4.1 (2007-09-01)
+------------------
+
+* Updated to mechanize 0.1.7b and ClientForm 0.2.7.  These are now
+  pulled in via egg dependencies.
+
+* ``zope.testbrowser`` now works on Python 2.5.
+
+
+3.4.0 (2007-06-04)
+------------------
+
+* Added the ability to suppress raising exceptions on HTTP errors
+  (``raiseHttpErrors`` attribute).
+
+* Made the tests more resilient to HTTP header formatting changes with
+  the REnormalizer.
+
+
+3.4.0a1 (2007-04-22)
+--------------------
+
+Initial release as a separate project, corresponds to zope.testbrowser
+from Zope 3.4.0a1
diff -pruN 4.0.2-1/CHANGES.txt 4.0.4-0ubuntu1/CHANGES.txt
--- 4.0.2-1/CHANGES.txt	2011-05-25 07:17:20.000000000 +0000
+++ 4.0.4-0ubuntu1/CHANGES.txt	1970-01-01 00:00:00.000000000 +0000
@@ -1,238 +0,0 @@
-=======
-CHANGES
-=======
-
-4.0.2 (2011-05-25)
-------------------
-
-- Remove test dependency on zope.pagetemplate.
-
-
-4.0.1 (2011-05-04)
-------------------
-
-- Added a hint in documentation how to use ``zope.testbrowser.wsgi.Browser``
-  to test a Zope 2/Zope 3/Bluebream WSGI application.
-
-4.0.0 (2011-03-14)
-------------------
-
-- LP #721252: AmbiguityError now shows all matching controls.
-
-- Integrate with WebTest. ``zope.testbrowser.wsgi.Browser`` is a
-  ``Browser`` implementation that uses ``webtest.TestApp`` to drive a WSGI
-  application. This this replaces the wsgi_intercept support added in 3.11.
-
-- Re-write the test application as a pure WSGI application using WebOb. Run the
-  existing tests using the WebTest based Browser
-
-- Move zope.app.testing based Browser into ``zope.app.testing`` (leaving
-  backwards compatibility imports in-place). Released in ``zope.app.testing``
-  3.9.0.
-
-
-3.11.1 (2011-01-24)
--------------------
-
-- Fixing brown bag release 3.11.0.
-
-
-3.11.0 (2011-01-24)
--------------------
-
-- Added `wsgi_intercept` support (came from ``zope.app.wsgi.testlayer``).
-
-
-3.10.4 (2011-01-14)
--------------------
-
-- Move the over-the-wire.txt doctest out of the TestBrowserLayer as it doesn't
-  need or use it.
-
-- Fix test compatibility with zope.app.testing 3.8.1.
-
-3.10.3 (2010-10-15)
--------------------
-
-- Fixed backwards compatibility with ``zope.app.wsgi.testlayer``.
-
-
-3.10.2 (2010-10-15)
--------------------
-
-- Fixed Python 2.7 compatibility in Browser.handleErrors.
-
-
-3.10.1 (2010-09-21)
--------------------
-
-- Fixed a bug that caused the ``Browser`` to keep it's previous ``contents``
-  The places are:
-  - Link.click()
-  - SubmitControl.click()
-  - ImageControl.click()
-  - Form.submit()
-
-- Also adjusted exception messages at the above places to match
-  pre version 3.4.1 messages.
-
-
-3.10.0 (2010-09-14)
--------------------
-
-- LP #98437: use mechanize's built-in ``submit()`` to submit forms, allowing
-  mechanize to set the "Referer:" (sic) header appropriately.
-
-- Fixed tests to run with ``zope.app.testing`` 3.8 and above.
-
-
-3.9.0 (2010-05-17)
-------------------
-
-- LP #568806: Update dependency ``mechanize >= 0.2.0``, which now includes
-  the ``ClientForm`` APIs.  Remove use of ``urllib2`` APIs (incompatible
-  with ``mechanize 0.2.0``) in favor of ``mechanize`` equivalents.
-  Thanks to John J. Lee for the patch.
-
-- Use stdlib ``doctest`` module, instead of ``zope.testing.doctest``.
-
-- **Caution:** This version is no longer fully compatible with Python 2.4:
-  ``handleErrors = False`` no longer works.
-
-
-3.8.1 (2010-04-19)
-------------------
-
-- Pinned dependency on mechanize to prevent use of the upcoming
-  0.2.0 release before we have time to adjust to its API changes.
-
-- LP #98396: testbrowser resolves relative URLs incorrectly.
-
-
-3.8.0 (2010-03-05)
-------------------
-
-- Added ``follow`` convenience method which gets and follows a link.
-
-
-3.7.0 (2009-12-17)
-------------------
-
-- Moved zope.app.testing dependency into the scope of the PublisherConnection
-  class. Zope2 specifies its own PublisherConnection which isn't dependent on
-  zope.app.testing.
-
-- Fixed LP #419119: return None when the browser has no contents instead of
-  raising an exception.
-
-
-3.7.0a1 (2009-08-29)
---------------------
-
-- Remove dependency on zope.app.publisher in favor of zope.browserpage,
-  zope.browserresource and zope.ptresource.
-
-- Remove dependencies on zope.app.principalannotation and zope.securitypolicy
-  by using the simple PermissiveSecurityPolicy. We aren't testing security
-  in our tests.
-
-- Replaced the testing dependency on zope.app.zcmlfiles with explicit
-  dependencies of a minimal set of packages.
-
-- Remove unneeded zope.app.authentication from ftesting.zcml.
-
-- Test dependency on zope.securitypolicy instead of its app variant.
-
-
-3.6.0a2 (2009-01-31)
---------------------
-
-- Test dependency on zope.site.folder instead of zope.app.folder.
-
-- Remove useless test dependency in zope.app.component.
-
-
-3.6.0a1 (2009-01-08)
---------------------
-
-- Author e-mail to zope-dev rather than zope3-dev.
-
-- New lines are no longer stripped in XML and HTML code contained in a
-  textarea; fix requires ClientForm >= 0.2.10 (LP #268139).
-
-- Added ``cookies`` attribute to browser for easy manipulation of browser
-  cookies.  See brief example in main documentation, plus new ``cookies.txt``
-  documentation.
-
-
-3.5.1 (2008-10-10)
-------------------
-
-- Provide a work around for a mechanize/urllib2 bug on Python 2.6
-  missing 'timeout' attribute on 'Request' base class.
-
-- Provide a work around for a mechanize/urllib2 bug in creating request
-  objects that won't handle fragment URLs correctly.
-
-
-3.5.0 (2008-03-30)
-------------------
-
-- Added a zope.testbrowser.testing.Browser.post method that allows
-  tests to supply a body and a content type.  This is handy for
-  testing Ajax requests with non-form input (e.g. JSON).
-
-- Remove vendor import of mechanize.
-
-- Fix bug that caused HTTP exception tracebacks to differ between version 3.4.0
-  and 3.4.1.
-
-- Workaround for bug in Python Cookie.SimpleCookie when handling unicode
-  strings.
-
-- Fix bug introduced in 3.4.1 that created incompatible tracebacks in doctests.
-  This necessitated adding a patched mechanize to the source tree; patches have
-  been sent to the mechanize project.
-
-- Fix https://bugs.launchpad.net/bugs/149517 by adding zope.interface and
-  zope.schema as real dependencies
-
-- Fix browser.getLink documentation that was not updated since the last API
-  modification.
-
-- Move tests for fixed bugs to a separate file.
-
-- Removed non-functional and undocumented code intended to help test servers
-  using virtual hosting.
-
-
-3.4.2 (2007-10-31)
-------------------
-
-- Resolve ``ZopeSecurityPolicy`` deprecation warning.
-
-
-3.4.1 (2007-09-01)
-------------------
-
-* Updated to mechanize 0.1.7b and ClientForm 0.2.7.  These are now
-  pulled in via egg dependencies.
-
-* ``zope.testbrowser`` now works on Python 2.5.
-
-
-3.4.0 (2007-06-04)
-------------------
-
-* Added the ability to suppress raising exceptions on HTTP errors
-  (``raiseHttpErrors`` attribute).
-
-* Made the tests more resilient to HTTP header formatting changes with
-  the REnormalizer.
-
-
-3.4.0a1 (2007-04-22)
---------------------
-
-Initial release as a separate project, corresponds to zope.testbrowser
-from Zope 3.4.0a1
diff -pruN 4.0.2-1/COPYRIGHT.rst 4.0.4-0ubuntu1/COPYRIGHT.rst
--- 4.0.2-1/COPYRIGHT.rst	1970-01-01 00:00:00.000000000 +0000
+++ 4.0.4-0ubuntu1/COPYRIGHT.rst	2013-10-11 07:12:28.000000000 +0000
@@ -0,0 +1 @@
+Zope Foundation and Contributors
\ No newline at end of file
diff -pruN 4.0.2-1/COPYRIGHT.txt 4.0.4-0ubuntu1/COPYRIGHT.txt
--- 4.0.2-1/COPYRIGHT.txt	2011-05-25 07:17:20.000000000 +0000
+++ 4.0.4-0ubuntu1/COPYRIGHT.txt	1970-01-01 00:00:00.000000000 +0000
@@ -1 +0,0 @@
-Zope Foundation and Contributors
\ No newline at end of file
diff -pruN 4.0.2-1/debian/changelog 4.0.4-0ubuntu1/debian/changelog
--- 4.0.2-1/debian/changelog	2011-09-07 03:21:35.000000000 +0000
+++ 4.0.4-0ubuntu1/debian/changelog	2013-10-30 17:26:55.000000000 +0000
@@ -1,3 +1,17 @@
+zope.testbrowser (4.0.4-0ubuntu1) trusty; urgency=low
+
+  * New upstream release.
+  * debian/watch: add .zip
+
+ -- Gediminas Paulauskas <menesis@pov.lt>  Wed, 30 Oct 2013 19:16:59 +0200
+
+zope.testbrowser (4.0.2-1ubuntu1) precise; urgency=low
+
+  * debian/control: add Provides: python-zope.testbrowser-wsgi for the new
+    functionality added in version 3.11
+
+ -- Gediminas Paulauskas <menesis@pov.lt>  Sat, 07 Jan 2012 20:56:48 +0200
+
 zope.testbrowser (4.0.2-1) unstable; urgency=low
 
   [ Arnaud Fontaine ]
diff -pruN 4.0.2-1/debian/control 4.0.4-0ubuntu1/debian/control
--- 4.0.2-1/debian/control	2011-09-07 02:57:34.000000000 +0000
+++ 4.0.4-0ubuntu1/debian/control	2013-10-30 17:19:47.000000000 +0000
@@ -1,14 +1,15 @@
 Source: zope.testbrowser
 Section: python
 Priority: extra
-Maintainer: Debian/Ubuntu Zope Team <pkg-zope-developers@lists.alioth.debian.org>
+Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
+XSBC-Original-Maintainer: Debian/Ubuntu Zope Team <pkg-zope-developers@lists.alioth.debian.org>
 Uploaders: Brian Sutherland <brian@vanguardistas.net>,
            Fabio Tranchitella <kobold@debian.org>,
            Arnaud Fontaine <arnau@debian.org>
 Build-Depends: debhelper (>= 7),
                python-all (>= 2.6.6-3~),
                python-setuptools
-Standards-Version: 3.9.2
+Standards-Version: 3.9.4
 X-Python-Version: >= 2.5
 Vcs-Browser: http://svn.debian.org/viewsvn/pkg-zope/zope.testbrowser/trunk
 Vcs-Svn: svn://svn.debian.org/pkg-zope/zope.testbrowser/trunk
@@ -20,8 +21,7 @@ Depends: ${python:Depends},
          ${misc:Depends},
          python-webtest,
          python-mechanize (>= 0.2.0)
-Provides: ${python:Provides}
-Conflicts: zope3
+Provides: python-zope.testbrowser-wsgi
 Description: Programmable browser for functional black-box tests
  Provides an easy-to-use programmable web browser with special focus on
  testing. It is used in Zope, but it's not Zope specific at all. For
diff -pruN 4.0.2-1/debian/tests/control 4.0.4-0ubuntu1/debian/tests/control
--- 4.0.2-1/debian/tests/control	2011-09-06 09:33:20.000000000 +0000
+++ 4.0.4-0ubuntu1/debian/tests/control	2013-10-30 17:19:51.000000000 +0000
@@ -1,3 +1,2 @@
 Tests: all
-Features: no-build-needed
-Depends: @, python-zope.testing, python-zope.testrunner, python-all
+Depends: @, python-zope.testrunner, python-webtest, python-zope.testing
diff -pruN 4.0.2-1/debian/watch 4.0.4-0ubuntu1/debian/watch
--- 4.0.2-1/debian/watch	2011-09-06 09:33:20.000000000 +0000
+++ 4.0.4-0ubuntu1/debian/watch	2013-10-30 17:17:05.000000000 +0000
@@ -1,2 +1,2 @@
 version=3
-http://pypi.python.org/packages/source/z/zope.testbrowser/zope.testbrowser-([0-9\.]*)\.tar\.gz
+http://pypi.python.org/packages/source/z/zope.testbrowser/zope.testbrowser-([0-9\.]*)\.(?:tar\.gz|zip)
diff -pruN 4.0.2-1/LICENSE.rst 4.0.4-0ubuntu1/LICENSE.rst
--- 4.0.2-1/LICENSE.rst	1970-01-01 00:00:00.000000000 +0000
+++ 4.0.4-0ubuntu1/LICENSE.rst	2013-10-11 07:12:28.000000000 +0000
@@ -0,0 +1,44 @@
+Zope Public License (ZPL) Version 2.1
+
+A copyright notice accompanies this license document that identifies the
+copyright holders.
+
+This license has been certified as open source. It has also been designated as
+GPL compatible by the Free Software Foundation (FSF).
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions in source code must retain the accompanying copyright
+notice, this list of conditions, and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the accompanying copyright
+notice, this list of conditions, and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+3. Names of the copyright holders must not be used to endorse or promote
+products derived from this software without prior written permission from the
+copyright holders.
+
+4. The right to distribute this software or to use it for any purpose does not
+give you the right to use Servicemarks (sm) or Trademarks (tm) of the
+copyright
+holders. Use of them is covered by separate agreement with the copyright
+holders.
+
+5. If any files are modified, you must cause the modified files to carry
+prominent notices stating that you changed the files and the date of any
+change.
+
+Disclaimer
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED
+OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff -pruN 4.0.2-1/LICENSE.txt 4.0.4-0ubuntu1/LICENSE.txt
--- 4.0.2-1/LICENSE.txt	2011-05-25 07:17:20.000000000 +0000
+++ 4.0.4-0ubuntu1/LICENSE.txt	1970-01-01 00:00:00.000000000 +0000
@@ -1,44 +0,0 @@
-Zope Public License (ZPL) Version 2.1
-
-A copyright notice accompanies this license document that identifies the
-copyright holders.
-
-This license has been certified as open source. It has also been designated as
-GPL compatible by the Free Software Foundation (FSF).
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are met:
-
-1. Redistributions in source code must retain the accompanying copyright
-notice, this list of conditions, and the following disclaimer.
-
-2. Redistributions in binary form must reproduce the accompanying copyright
-notice, this list of conditions, and the following disclaimer in the
-documentation and/or other materials provided with the distribution.
-
-3. Names of the copyright holders must not be used to endorse or promote
-products derived from this software without prior written permission from the
-copyright holders.
-
-4. The right to distribute this software or to use it for any purpose does not
-give you the right to use Servicemarks (sm) or Trademarks (tm) of the
-copyright
-holders. Use of them is covered by separate agreement with the copyright
-holders.
-
-5. If any files are modified, you must cause the modified files to carry
-prominent notices stating that you changed the files and the date of any
-change.
-
-Disclaimer
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED
-OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
-OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
-EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT,
-INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
-PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
-LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
-NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
-EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff -pruN 4.0.2-1/MANIFEST.in 4.0.4-0ubuntu1/MANIFEST.in
--- 4.0.2-1/MANIFEST.in	1970-01-01 00:00:00.000000000 +0000
+++ 4.0.4-0ubuntu1/MANIFEST.in	2013-10-11 07:12:28.000000000 +0000
@@ -0,0 +1,8 @@
+include *.py
+include *.rst
+include .travis.yml
+include tox.ini
+include buildout.cfg
+recursive-include src *.gif
+recursive-include src *.html
+recursive-include src *.txt
diff -pruN 4.0.2-1/.pc/fix-inaccurate-pystones-per-second-calculation.patch/src/zope/testbrowser/browser.py 4.0.4-0ubuntu1/.pc/fix-inaccurate-pystones-per-second-calculation.patch/src/zope/testbrowser/browser.py
--- 4.0.2-1/.pc/fix-inaccurate-pystones-per-second-calculation.patch/src/zope/testbrowser/browser.py	2011-05-25 07:17:20.000000000 +0000
+++ 4.0.4-0ubuntu1/.pc/fix-inaccurate-pystones-per-second-calculation.patch/src/zope/testbrowser/browser.py	2013-10-11 07:12:28.000000000 +0000
@@ -33,7 +33,7 @@ RegexType = type(re.compile(''))
 _compress_re = re.compile(r"\s+")
 compressText = lambda text: _compress_re.sub(' ', text.strip())
 
-def disambiguate(intermediate, msg, index, choice_repr=None):
+def disambiguate(intermediate, msg, index, choice_repr=None, available=None):
     if intermediate:
         if index is None:
             if len(intermediate) > 1:
@@ -47,8 +47,19 @@ def disambiguate(intermediate, msg, inde
         else:
             try:
                 return intermediate[index]
-            except KeyError:
-                msg = '%s index %d' % (msg, index)
+            except IndexError:
+                msg = '%s\nIndex %d out of range, available choices are 0...%d' % (
+                            msg, index, len(intermediate) - 1)
+                if choice_repr:
+                    msg += ''.join(['\n  %d: %s' % (n, choice_repr(choice))
+                                    for n, choice in enumerate(intermediate)])
+    else:
+        if available:
+            msg += '\navailable items:' + ''.join([
+                '\n  %s' % choice_repr(choice)
+                for choice in available])
+        elif available is not None: # empty list
+            msg += '\n(there are no form items in the HTML)'
     raise LookupError(msg)
 
 def control_form_tuple_repr((ctrl, form)):
@@ -344,29 +355,27 @@ class Browser(SetattrErrorsMixin):
         """Select a link and follow it."""
         self.getLink(*args, **kw).click()
 
-    def _findByLabel(self, label, forms, include_subcontrols=False):
-        # forms are iterable of mech_forms
-        matches = re.compile(r'(^|\b|\W)%s(\b|\W|$)'
-                             % re.escape(compressText(label))).search
-        found = []
+    def _findAllControls(self, forms, include_subcontrols=False):
         for f in forms:
             for control in f.controls:
                 phantom = control.type in ('radio', 'checkbox')
                 if not phantom:
-                    for l in control.get_labels():
-                        if matches(l.text):
-                            found.append((control, f))
-                            break
+                    yield (control, f)
                 if include_subcontrols and (
                     phantom or control.type=='select'):
-
                     for i in control.items:
-                        for l in i.get_labels():
-                            if matches(l.text):
-                                found.append((i, f))
-                                found_one = True
-                                break
+                        yield (i, f)
 
+    def _findByLabel(self, label, forms, include_subcontrols=False):
+        # forms are iterable of mech_forms
+        matches = re.compile(r'(^|\b|\W)%s(\b|\W|$)'
+                             % re.escape(compressText(label))).search
+        found = []
+        for control, form in self._findAllControls(forms, include_subcontrols):
+            for l in control.get_labels():
+                if matches(l.text):
+                    found.append((control, form))
+                    break
         return found
 
     def _findByName(self, name, forms):
@@ -379,22 +388,29 @@ class Browser(SetattrErrorsMixin):
 
     def getControl(self, label=None, name=None, index=None):
         """See zope.testbrowser.interfaces.IBrowser"""
-        intermediate, msg = self._get_all_controls(
+        intermediate, msg, available = self._get_all_controls(
             label, name, self.mech_browser.forms(), include_subcontrols=True)
         control, form = disambiguate(intermediate, msg, index,
-                                     control_form_tuple_repr)
+                                     control_form_tuple_repr,
+                                     available)
         return controlFactory(control, form, self)
 
     def _get_all_controls(self, label, name, forms, include_subcontrols=False):
         onlyOne([label, name], '"label" and "name"')
 
+        forms = list(forms) # might be an iterator, and we need to iterate twice
+
+        available = None
         if label is not None:
             res = self._findByLabel(label, forms, include_subcontrols)
             msg = 'label %r' % label
         elif name is not None:
+            include_subcontrols = False
             res = self._findByName(name, forms)
             msg = 'name %r' % name
-        return res, msg
+        if not res:
+            available = list(self._findAllControls(forms, include_subcontrols))
+        return res, msg, available
 
     def getForm(self, id=None, name=None, action=None, index=None):
         zeroOrOne([id, name, action], '"id", "name", and "action"')
@@ -763,13 +779,14 @@ class Form(SetattrErrorsMixin):
         form = self.mech_form
         try:
             if label is not None or name is not None:
-                intermediate, msg = self.browser._get_all_controls(
+                intermediate, msg, available = self.browser._get_all_controls(
                     label, name, (form,))
                 intermediate = [
                     (control, form) for (control, form) in intermediate if
                     control.type in ('submit', 'submitbutton', 'image')]
                 control, form = disambiguate(intermediate, msg, index,
-                                             control_form_tuple_repr)
+                                             control_form_tuple_repr,
+                                             available)
                 self.browser._clickSubmit(form, control, coord)
             else: # JavaScript sort of submit
                 if index is not None or coord != (1,1):
@@ -780,6 +797,10 @@ class Form(SetattrErrorsMixin):
                 try:
                     try:
                         self.browser.mech_browser.open(request)
+                    except mechanize.HTTPError, e:
+                        if self.browser.raiseHttpErrors:
+                            fix_exception_name(e)
+                            raise
                     except Exception, e:
                         fix_exception_name(e)
                         raise
@@ -792,8 +813,9 @@ class Form(SetattrErrorsMixin):
         """See zope.testbrowser.interfaces.IBrowser"""
         if self._browser_counter != self.browser._counter:
             raise zope.testbrowser.interfaces.ExpiredError
-        intermediate, msg = self.browser._get_all_controls(
+        intermediate, msg, available = self.browser._get_all_controls(
             label, name, (self.mech_form,), include_subcontrols=True)
         control, form = disambiguate(intermediate, msg, index,
-                                     control_form_tuple_repr)
+                                     control_form_tuple_repr,
+                                     available)
         return controlFactory(control, form, self.browser)
diff -pruN 4.0.2-1/PKG-INFO 4.0.4-0ubuntu1/PKG-INFO
--- 4.0.2-1/PKG-INFO	2011-05-25 07:17:51.000000000 +0000
+++ 4.0.4-0ubuntu1/PKG-INFO	2013-10-11 07:12:56.000000000 +0000
@@ -1,6 +1,6 @@
-Metadata-Version: 1.0
+Metadata-Version: 1.1
 Name: zope.testbrowser
-Version: 4.0.2
+Version: 4.0.4
 Summary: Programmable browser for functional black-box tests
 Home-page: http://pypi.python.org/pypi/zope.testbrowser
 Author: Zope Corporation and Contributors
@@ -8,11 +8,15 @@ Author-email: zope-dev@zope.org
 License: ZPL 2.1
 Description: .. contents::
         
+        |buildstatus|_
+        
         ``zope.testbrowser`` provides an easy-to-use programmable web browser
         with special focus on testing.  It is used in Zope, but it's not Zope
         specific at all.  For instance, it can be used to test or otherwise
         interact with any web site.
         
+        .. |buildstatus| image:: https://api.travis-ci.org/zopefoundation/zope.testbrowser.png?branch=master
+        .. _buildstatus: https://travis-ci.org/zopefoundation/zope.testbrowser
         
         
         ======================
@@ -28,8 +32,8 @@ Description: .. contents::
         The ``zope.testbrowser.browser`` module exposes a ``Browser`` class that
         simulates a web browser similar to Mozilla Firefox or IE.
         
-        >>> from zope.testbrowser.browser import Browser
-        >>> browser = Browser()
+            >>> from zope.testbrowser.browser import Browser
+            >>> browser = Browser()
         
         This version of the browser object can be used to access any web site just as
         you would do using a normal web browser.
@@ -44,32 +48,32 @@ Description: .. contents::
         `WebTest`_ and can be used to do functional testing of WSGI
         applications. It can be imported from ``zope.testbrowser.wsgi``:
         
-        >>> from zope.testbrowser.wsgi import Browser
-        >>> from wsgiref.simple_server import demo_app
-        >>> browser = Browser('http://localhost/', wsgi_app=demo_app)
-        >>> print browser.contents
-        Hello world!
-        ...
+            >>> from zope.testbrowser.wsgi import Browser
+            >>> from zope.testbrowser.tests.test_wsgi import demo_app
+            >>> browser = Browser('http://localhost/', wsgi_app=demo_app)
+            >>> print browser.contents
+            Hello world!
+            ...
         
         .. _`WebTest`: http://pypi.python.org/pypi/WebTest
         
         To use this browser you have to:
         
-        * use the `wsgi` extra of the ``zope.testbrowser`` egg,
+          * use the `wsgi` extra of the ``zope.testbrowser`` egg,
         
         You can also use it with zope layers by:
         
-        * write a subclass of ``zope.testbrowser.wsgi.Layer`` and override the
-        ``make_wsgi_app`` method,
+          * write a subclass of ``zope.testbrowser.wsgi.Layer`` and override the
+            ``make_wsgi_app`` method,
         
-        * use an instance of the class as the test layer of your test.
+          * use an instance of the class as the test layer of your test.
         
         Example:
         
-        >>> import zope.testbrowser.wsgi
-        >>> class SimpleLayer(zope.testbrowser.wsgi.Layer):
-        ...     def make_wsgi_app(self):
-        ...         return simple_app
+            >>> import zope.testbrowser.wsgi
+            >>> class SimpleLayer(zope.testbrowser.wsgi.Layer):
+            ...     def make_wsgi_app(self):
+            ...         return simple_app
         
         Where ``simple_app`` is the callable of your WSGI application.
         
@@ -84,10 +88,10 @@ Description: .. contents::
         
         Example when using the layer:
         
-        >>> import zope.testbrowser.wsgi
-        >>> class ZopeSimpleLayer(zope.testbrowser.wsgi.Layer):
-        ...     def make_wsgi_app(self):
-        ...         return zope.testbrowser.wsgi.AuthorizationMiddleware(simple_app)
+            >>> import zope.testbrowser.wsgi
+            >>> class ZopeSimpleLayer(zope.testbrowser.wsgi.Layer):
+            ...     def make_wsgi_app(self):
+            ...         return zope.testbrowser.wsgi.AuthorizationMiddleware(simple_app)
         
         There is also a BrowserLayer in `zope.app.wsgi.testlayer`_ which does this
         for you and includes a ``TransactionMiddleware``, too, which could be handy
@@ -101,28 +105,28 @@ Description: .. contents::
         
         We will test this browser against a WSGI test application:
         
-        >>> from zope.testbrowser.ftests.wsgitestapp import WSGITestApplication
-        >>> wsgi_app = WSGITestApplication()
+            >>> from zope.testbrowser.ftests.wsgitestapp import WSGITestApplication
+            >>> wsgi_app = WSGITestApplication()
         
         An initial page to load can be passed to the ``Browser`` constructor:
         
-        >>> browser = Browser('http://localhost/@@/testbrowser/simple.html', wsgi_app=wsgi_app)
-        >>> browser.url
-        'http://localhost/@@/testbrowser/simple.html'
+            >>> browser = Browser('http://localhost/@@/testbrowser/simple.html', wsgi_app=wsgi_app)
+            >>> browser.url
+            'http://localhost/@@/testbrowser/simple.html'
         
         The browser can send arbitrary headers; this is helpful for setting the
         "Authorization" header or a language value, so that your tests format values
         the way you expect in your tests, if you rely on zope.i18n locale-based
         formatting or a similar approach.
         
-        >>> browser.addHeader('Authorization', 'Basic mgr:mgrpw')
-        >>> browser.addHeader('Accept-Language', 'en-US')
+            >>> browser.addHeader('Authorization', 'Basic mgr:mgrpw')
+            >>> browser.addHeader('Accept-Language', 'en-US')
         
         An existing browser instance can also `open` web pages:
         
-        >>> browser.open('http://localhost/@@/testbrowser/simple.html')
-        >>> browser.url
-        'http://localhost/@@/testbrowser/simple.html'
+            >>> browser.open('http://localhost/@@/testbrowser/simple.html')
+            >>> browser.url
+            'http://localhost/@@/testbrowser/simple.html'
         
         Once you have opened a web page initially, best practice for writing
         testbrowser doctests suggests using 'click' to navigate further (as discussed
@@ -131,10 +135,10 @@ Description: .. contents::
         The test browser complies with the IBrowser interface; see
         ``zope.testbrowser.interfaces`` for full details on the interface.
         
-        >>> from zope.testbrowser import interfaces
-        >>> from zope.interface.verify import verifyObject
-        >>> verifyObject(interfaces.IBrowser, browser)
-        True
+            >>> from zope.testbrowser import interfaces
+            >>> from zope.interface.verify import verifyObject
+            >>> verifyObject(interfaces.IBrowser, browser)
+            True
         
         
         Page Contents
@@ -142,25 +146,25 @@ Description: .. contents::
         
         The contents of the current page are available:
         
-        >>> print browser.contents
-        <html>
-        <head>
-        <title>Simple Page</title>
-        </head>
-        <body>
-        <h1>Simple Page</h1>
-        </body>
-        </html>
+            >>> print browser.contents
+            <html>
+              <head>
+                <title>Simple Page</title>
+              </head>
+              <body>
+                <h1>Simple Page</h1>
+              </body>
+            </html>
         
         Making assertions about page contents is easy.
         
-        >>> '<h1>Simple Page</h1>' in browser.contents
-        True
+            >>> '<h1>Simple Page</h1>' in browser.contents
+            True
         
         Utilizing the doctest facilities, it also possible to do:
         
-        >>> browser.contents
-        '...<h1>Simple Page</h1>...'
+            >>> browser.contents
+            '...<h1>Simple Page</h1>...'
         
         Note: Unfortunately, ellipsis (...) cannot be used at the beginning of the
         output (this is a limitation of doctest).
@@ -172,15 +176,15 @@ Description: .. contents::
         
         Not all URLs return HTML.  Of course our simple page does:
         
-        >>> browser.open('http://localhost/@@/testbrowser/simple.html')
-        >>> browser.isHtml
-        True
+            >>> browser.open('http://localhost/@@/testbrowser/simple.html')
+            >>> browser.isHtml
+            True
         
         But if we load an image (or other binary file), we do not get HTML:
         
-        >>> browser.open('http://localhost/@@/testbrowser/zope3logo.gif')
-        >>> browser.isHtml
-        False
+            >>> browser.open('http://localhost/@@/testbrowser/zope3logo.gif')
+            >>> browser.isHtml
+            False
         
         
         
@@ -189,23 +193,23 @@ Description: .. contents::
         
         Another useful helper property is the title:
         
-        >>> browser.open('http://localhost/@@/testbrowser/simple.html')
-        >>> browser.title
-        'Simple Page'
+            >>> browser.open('http://localhost/@@/testbrowser/simple.html')
+            >>> browser.title
+            'Simple Page'
         
         If a page does not provide a title, it is simply ``None``:
         
-        >>> browser.open('http://localhost/@@/testbrowser/notitle.html')
-        >>> browser.title
+            >>> browser.open('http://localhost/@@/testbrowser/notitle.html')
+            >>> browser.title
         
         However, if the output is not HTML, then an error will occur trying to access
         the title:
         
-        >>> browser.open('http://localhost/@@/testbrowser/zope3logo.gif')
-        >>> browser.title
-        Traceback (most recent call last):
-        ...
-        BrowserStateError: not viewing HTML
+            >>> browser.open('http://localhost/@@/testbrowser/zope3logo.gif')
+            >>> browser.title
+            Traceback (most recent call last):
+            ...
+            BrowserStateError: not viewing HTML
         
         
         Headers
@@ -216,21 +220,21 @@ Description: .. contents::
         ``httplib.HTTPMessage`` instance (httplib is a part of Python's standard
         library):
         
-        >>> browser.open('http://localhost/@@/testbrowser/simple.html')
-        >>> browser.headers
-        <httplib.HTTPMessage instance...>
+            >>> browser.open('http://localhost/@@/testbrowser/simple.html')
+            >>> browser.headers
+            <httplib.HTTPMessage instance...>
         
         The headers can be accessed as a string:
         
-        >>> print browser.headers
-        Status: 200 OK
-        Content-Length: 123
-        Content-Type: text/html;charset=utf-8
+            >>> print browser.headers
+            Status: 200 OK
+            Content-Length: 123
+            Content-Type: text/html;charset=utf-8
         
         Or as a mapping:
         
-        >>> browser.headers['content-type']
-        'text/html;charset=utf-8'
+            >>> browser.headers['content-type']
+            'text/html;charset=utf-8'
         
         
         Cookies
@@ -240,47 +244,47 @@ Description: .. contents::
         above.  Here, we use a view that will make the server set cookies with the
         values we provide.
         
-        >>> browser.open('http://localhost/set_cookie.html?name=foo&value=bar')
-        >>> browser.headers['set-cookie'].replace(';', '')
-        'foo=bar'
+            >>> browser.open('http://localhost/set_cookie.html?name=foo&value=bar')
+            >>> browser.headers['set-cookie'].replace(';', '')
+            'foo=bar'
         
         It is also available in the browser's ``cookies`` attribute.  This is
         an extended mapping interface that allows getting, setting, and deleting the
         cookies that the browser is remembering *for the current url*.  Here are
         a few examples.
         
-        >>> browser.cookies['foo']
-        'bar'
-        >>> browser.cookies.keys()
-        ['foo']
-        >>> browser.cookies.values()
-        ['bar']
-        >>> browser.cookies.items()
-        [('foo', 'bar')]
-        >>> 'foo' in browser.cookies
-        True
-        >>> 'bar' in browser.cookies
-        False
-        >>> len(browser.cookies)
-        1
-        >>> print(dict(browser.cookies))
-        {'foo': 'bar'}
-        >>> browser.cookies['sha'] = 'zam'
-        >>> len(browser.cookies)
-        2
-        >>> sorted(browser.cookies.items())
-        [('foo', 'bar'), ('sha', 'zam')]
-        >>> browser.open('http://localhost/get_cookie.html')
-        >>> print browser.headers.get('set-cookie')
-        None
-        >>> print browser.contents # server got the cookie change
-        foo: bar
-        sha: zam
-        >>> sorted(browser.cookies.items())
-        [('foo', 'bar'), ('sha', 'zam')]
-        >>> browser.cookies.clearAll()
-        >>> len(browser.cookies)
-        0
+            >>> browser.cookies['foo']
+            'bar'
+            >>> browser.cookies.keys()
+            ['foo']
+            >>> browser.cookies.values()
+            ['bar']
+            >>> browser.cookies.items()
+            [('foo', 'bar')]
+            >>> 'foo' in browser.cookies
+            True
+            >>> 'bar' in browser.cookies
+            False
+            >>> len(browser.cookies)
+            1
+            >>> print(dict(browser.cookies))
+            {'foo': 'bar'}
+            >>> browser.cookies['sha'] = 'zam'
+            >>> len(browser.cookies)
+            2
+            >>> sorted(browser.cookies.items())
+            [('foo', 'bar'), ('sha', 'zam')]
+            >>> browser.open('http://localhost/get_cookie.html')
+            >>> print browser.headers.get('set-cookie')
+            None
+            >>> print browser.contents # server got the cookie change
+            foo: bar
+            sha: zam
+            >>> sorted(browser.cookies.items())
+            [('foo', 'bar'), ('sha', 'zam')]
+            >>> browser.cookies.clearAll()
+            >>> len(browser.cookies)
+            0
         
         Many more examples, and a discussion of the additional methods available, can
         be found in cookies.txt.
@@ -297,154 +301,154 @@ Description: .. contents::
         the text you would see in a browser (text and url searches are substring
         searches):
         
-        >>> browser.open('http://localhost/@@/testbrowser/navigate.html')
-        >>> browser.contents
-        '...<a href="navigate.html?message=By+Link+Text">Link Text</a>...'
-        >>> link = browser.getLink('Link Text')
-        >>> link
-        <Link text='Link Text'
-        url='http://localhost/@@/testbrowser/navigate.html?message=By+Link+Text'>
+            >>> browser.open('http://localhost/@@/testbrowser/navigate.html')
+            >>> browser.contents
+            '...<a href="navigate.html?message=By+Link+Text">Link Text</a>...'
+            >>> link = browser.getLink('Link Text')
+            >>> link
+            <Link text='Link Text'
+              url='http://localhost/@@/testbrowser/navigate.html?message=By+Link+Text'>
         
         Link objects comply with the ILink interface.
         
-        >>> verifyObject(interfaces.ILink, link)
-        True
+            >>> verifyObject(interfaces.ILink, link)
+            True
         
         Links expose several attributes for easy access.
         
-        >>> link.text
-        'Link Text'
-        >>> link.tag # links can also be image maps.
-        'a'
-        >>> link.url # it's normalized
-        'http://localhost/@@/testbrowser/navigate.html?message=By+Link+Text'
-        >>> link.attrs
-        {'href': 'navigate.html?message=By+Link+Text'}
+            >>> link.text
+            'Link Text'
+            >>> link.tag # links can also be image maps.
+            'a'
+            >>> link.url # it's normalized
+            'http://localhost/@@/testbrowser/navigate.html?message=By+Link+Text'
+            >>> link.attrs
+            {'href': 'navigate.html?message=By+Link+Text'}
         
         Links can be "clicked" and the browser will navigate to the referenced URL.
         
-        >>> link.click()
-        >>> browser.url
-        'http://localhost/@@/testbrowser/navigate.html?message=By+Link+Text'
-        >>> browser.contents
-        '...Message: <em>By Link Text</em>...'
+            >>> link.click()
+            >>> browser.url
+            'http://localhost/@@/testbrowser/navigate.html?message=By+Link+Text'
+            >>> browser.contents
+            '...Message: <em>By Link Text</em>...'
         
         When finding a link by its text, whitespace is normalized.
         
-        >>> browser.open('http://localhost/@@/testbrowser/navigate.html')
-        >>> browser.contents
-        '...> Link Text \n    with     Whitespace\tNormalization (and parens) </...'
-        >>> link = browser.getLink('Link Text with Whitespace Normalization '
-        ...                        '(and parens)')
-        >>> link
-        <Link text='Link Text with Whitespace Normalization (and parens)'...>
-        >>> link.text
-        'Link Text with Whitespace Normalization (and parens)'
-        >>> link.click()
-        >>> browser.url
-        'http://localhost/@@/testbrowser/navigate.html?message=By+Link+Text+with+Normalization'
-        >>> browser.contents
-        '...Message: <em>By Link Text with Normalization</em>...'
+            >>> browser.open('http://localhost/@@/testbrowser/navigate.html')
+            >>> browser.contents
+            '...> Link Text \n    with     Whitespace\tNormalization (and parens) </...'
+            >>> link = browser.getLink('Link Text with Whitespace Normalization '
+            ...                        '(and parens)')
+            >>> link
+            <Link text='Link Text with Whitespace Normalization (and parens)'...>
+            >>> link.text
+            'Link Text with Whitespace Normalization (and parens)'
+            >>> link.click()
+            >>> browser.url
+            'http://localhost/@@/testbrowser/navigate.html?message=By+Link+Text+with+Normalization'
+            >>> browser.contents
+            '...Message: <em>By Link Text with Normalization</em>...'
         
         When a link text matches more than one link, by default the first one is
         chosen. You can, however, specify the index of the link and thus retrieve a
         later matching link:
         
-        >>> browser.getLink('Link Text')
-        <Link text='Link Text' ...>
+            >>> browser.getLink('Link Text')
+            <Link text='Link Text' ...>
         
-        >>> browser.getLink('Link Text', index=1)
-        <Link text='Link Text with Whitespace Normalization (and parens)' ...>
+            >>> browser.getLink('Link Text', index=1)
+            <Link text='Link Text with Whitespace Normalization (and parens)' ...>
         
         Note that clicking a link object after its browser page has expired will
         generate an error.
         
-        >>> link.click()
-        Traceback (most recent call last):
-        ...
-        ExpiredError
+            >>> link.click()
+            Traceback (most recent call last):
+            ...
+            ExpiredError
         
         You can also find the link by its URL,
         
-        >>> browser.open('http://localhost/@@/testbrowser/navigate.html')
-        >>> browser.contents
-        '...<a href="navigate.html?message=By+URL">Using the URL</a>...'
-        
-        >>> browser.getLink(url='?message=By+URL').click()
-        >>> browser.url
-        'http://localhost/@@/testbrowser/navigate.html?message=By+URL'
-        >>> browser.contents
-        '...Message: <em>By URL</em>...'
+            >>> browser.open('http://localhost/@@/testbrowser/navigate.html')
+            >>> browser.contents
+            '...<a href="navigate.html?message=By+URL">Using the URL</a>...'
+        
+            >>> browser.getLink(url='?message=By+URL').click()
+            >>> browser.url
+            'http://localhost/@@/testbrowser/navigate.html?message=By+URL'
+            >>> browser.contents
+            '...Message: <em>By URL</em>...'
         
         or its id:
         
-        >>> browser.open('http://localhost/@@/testbrowser/navigate.html')
-        >>> browser.contents
-        '...<a href="navigate.html?message=By+Id"
-        id="anchorid">By Anchor Id</a>...'
-        
-        >>> browser.getLink(id='anchorid').click()
-        >>> browser.url
-        'http://localhost/@@/testbrowser/navigate.html?message=By+Id'
-        >>> browser.contents
-        '...Message: <em>By Id</em>...'
+            >>> browser.open('http://localhost/@@/testbrowser/navigate.html')
+            >>> browser.contents
+            '...<a href="navigate.html?message=By+Id"
+            id="anchorid">By Anchor Id</a>...'
+        
+            >>> browser.getLink(id='anchorid').click()
+            >>> browser.url
+            'http://localhost/@@/testbrowser/navigate.html?message=By+Id'
+            >>> browser.contents
+            '...Message: <em>By Id</em>...'
         
         You thought we were done here? Not so quickly.  The `getLink` method also
         supports image maps, though not by specifying the coordinates, but using the
         area's id:
         
-        >>> browser.open('http://localhost/@@/testbrowser/navigate.html')
-        >>> link = browser.getLink(id='zope3')
-        >>> link.tag
-        'area'
-        >>> link.click()
-        >>> browser.url
-        'http://localhost/@@/testbrowser/navigate.html?message=Zope+3+Name'
-        >>> browser.contents
-        '...Message: <em>Zope 3 Name</em>...'
+            >>> browser.open('http://localhost/@@/testbrowser/navigate.html')
+            >>> link = browser.getLink(id='zope3')
+            >>> link.tag
+            'area'
+            >>> link.click()
+            >>> browser.url
+            'http://localhost/@@/testbrowser/navigate.html?message=Zope+3+Name'
+            >>> browser.contents
+            '...Message: <em>Zope 3 Name</em>...'
         
         Getting a nonexistent link raises an exception.
         
-        >>> browser.open('http://localhost/@@/testbrowser/navigate.html')
-        >>> browser.getLink('This does not exist')
-        Traceback (most recent call last):
-        ...
-        LinkNotFoundError
+            >>> browser.open('http://localhost/@@/testbrowser/navigate.html')
+            >>> browser.getLink('This does not exist')
+            Traceback (most recent call last):
+            ...
+            LinkNotFoundError
         
         A convenience method is provided to follow links; this uses the same
         arguments as `getLink`, but clicks on the link instead of returning the
         link object.
         
-        >>> browser.open('http://localhost/@@/testbrowser/navigate.html')
-        >>> browser.contents
-        '...<a href="navigate.html?message=By+Link+Text">Link Text</a>...'
-        >>> browser.follow('Link Text')
-        >>> browser.url
-        'http://localhost/@@/testbrowser/navigate.html?message=By+Link+Text'
-        >>> browser.contents
-        '...Message: <em>By Link Text</em>...'
-        
-        >>> browser.open('http://localhost/@@/testbrowser/navigate.html')
-        >>> browser.follow(url='?message=By+URL')
-        >>> browser.url
-        'http://localhost/@@/testbrowser/navigate.html?message=By+URL'
-        >>> browser.contents
-        '...Message: <em>By URL</em>...'
-        
-        >>> browser.open('http://localhost/@@/testbrowser/navigate.html')
-        >>> browser.follow(id='zope3')
-        >>> browser.url
-        'http://localhost/@@/testbrowser/navigate.html?message=Zope+3+Name'
-        >>> browser.contents
-        '...Message: <em>Zope 3 Name</em>...'
+            >>> browser.open('http://localhost/@@/testbrowser/navigate.html')
+            >>> browser.contents
+            '...<a href="navigate.html?message=By+Link+Text">Link Text</a>...'
+            >>> browser.follow('Link Text')
+            >>> browser.url
+            'http://localhost/@@/testbrowser/navigate.html?message=By+Link+Text'
+            >>> browser.contents
+            '...Message: <em>By Link Text</em>...'
+        
+            >>> browser.open('http://localhost/@@/testbrowser/navigate.html')
+            >>> browser.follow(url='?message=By+URL')
+            >>> browser.url
+            'http://localhost/@@/testbrowser/navigate.html?message=By+URL'
+            >>> browser.contents
+            '...Message: <em>By URL</em>...'
+        
+            >>> browser.open('http://localhost/@@/testbrowser/navigate.html')
+            >>> browser.follow(id='zope3')
+            >>> browser.url
+            'http://localhost/@@/testbrowser/navigate.html?message=Zope+3+Name'
+            >>> browser.contents
+            '...Message: <em>Zope 3 Name</em>...'
         
         Attempting to follow links that don't exist raises the same exception as
         asking for the link object:
         
-        >>> browser.follow('This does not exist')
-        Traceback (most recent call last):
-        ...
-        LinkNotFoundError
+            >>> browser.follow('This does not exist')
+            Traceback (most recent call last):
+            ...
+            LinkNotFoundError
         
         
         Other Navigation
@@ -452,21 +456,21 @@ Description: .. contents::
         
         Like in any normal browser, you can reload a page:
         
-        >>> browser.open('http://localhost/@@/testbrowser/simple.html')
-        >>> browser.url
-        'http://localhost/@@/testbrowser/simple.html'
-        >>> browser.reload()
-        >>> browser.url
-        'http://localhost/@@/testbrowser/simple.html'
+            >>> browser.open('http://localhost/@@/testbrowser/simple.html')
+            >>> browser.url
+            'http://localhost/@@/testbrowser/simple.html'
+            >>> browser.reload()
+            >>> browser.url
+            'http://localhost/@@/testbrowser/simple.html'
         
         You can also go back:
         
-        >>> browser.open('http://localhost/@@/testbrowser/notitle.html')
-        >>> browser.url
-        'http://localhost/@@/testbrowser/notitle.html'
-        >>> browser.goBack()
-        >>> browser.url
-        'http://localhost/@@/testbrowser/simple.html'
+            >>> browser.open('http://localhost/@@/testbrowser/notitle.html')
+            >>> browser.url
+            'http://localhost/@@/testbrowser/notitle.html'
+            >>> browser.goBack()
+            >>> browser.url
+            'http://localhost/@@/testbrowser/simple.html'
         
         
         Controls
@@ -476,7 +480,7 @@ Description: .. contents::
         and fill in values for the controls of input forms.  To do so, let's first open
         a page that has a bunch of controls:
         
-        >>> browser.open('http://localhost/@@/testbrowser/controls.html')
+            >>> browser.open('http://localhost/@@/testbrowser/controls.html')
         
         
         Obtaining a Control
@@ -486,53 +490,65 @@ Description: .. contents::
         argument is 'label', and looks up the form on the basis of any associated
         label.
         
-        >>> control = browser.getControl('Text Control')
-        >>> control
-        <Control name='text-value' type='text'>
-        >>> browser.getControl(label='Text Control') # equivalent
-        <Control name='text-value' type='text'>
+            >>> control = browser.getControl('Text Control')
+            >>> control
+            <Control name='text-value' type='text'>
+            >>> browser.getControl(label='Text Control') # equivalent
+            <Control name='text-value' type='text'>
         
         If you request a control that doesn't exist, the code raises a LookupError:
         
-        >>> browser.getControl('Does Not Exist')
-        Traceback (most recent call last):
-        ...
-        LookupError: label 'Does Not Exist'
+            >>> browser.getControl('Does Not Exist')
+            Traceback (most recent call last):
+            ...
+            LookupError: label 'Does Not Exist'
+            available items:
+              <TextControl(text-value=Some Text)>
+              <PasswordControl(password-value=Password)>
+              <HiddenControl(hidden-value=Hidden) (readonly)>
+              ...
         
         If you request a control with an ambiguous lookup, the code raises an
         AmbiguityError.
         
-        >>> browser.getControl('Ambiguous Control')
-        Traceback (most recent call last):
-        ...
-        AmbiguityError: label 'Ambiguous Control' matches:
-        <TextControl(ambiguous-control-name=First)>
-        <TextControl(ambiguous-control-name=Second)>
+            >>> browser.getControl('Ambiguous Control')
+            Traceback (most recent call last):
+            ...
+            AmbiguityError: label 'Ambiguous Control' matches:
+              <TextControl(ambiguous-control-name=First)>
+              <TextControl(ambiguous-control-name=Second)>
         
         This is also true if an option in a control is ambiguous in relation to
         the control itself.
         
-        >>> browser.getControl('Sub-control Ambiguity')
-        Traceback (most recent call last):
-        ...
-        AmbiguityError: label 'Sub-control Ambiguity' matches:
-        <SelectControl(ambiguous-subcontrol=[*, ambiguous])>
-        <Item name='ambiguous' id=None contents='Sub-control Ambiguity Exemplified' value='ambiguous' label='Sub-control Ambiguity Exemplified'>
+            >>> browser.getControl('Sub-control Ambiguity')
+            Traceback (most recent call last):
+            ...
+            AmbiguityError: label 'Sub-control Ambiguity' matches:
+              <SelectControl(ambiguous-subcontrol=[*, ambiguous])>
+              <Item name='ambiguous' id=None contents='Sub-control Ambiguity Exemplified' value='ambiguous' label='Sub-control Ambiguity Exemplified'>
         
         Ambiguous controls may be specified using an index value.  We use the control's
         value attribute to show the two controls; this attribute is properly introduced
         below.
         
-        >>> browser.getControl('Ambiguous Control', index=0)
-        <Control name='ambiguous-control-name' type='text'>
-        >>> browser.getControl('Ambiguous Control', index=0).value
-        'First'
-        >>> browser.getControl('Ambiguous Control', index=1).value
-        'Second'
-        >>> browser.getControl('Sub-control Ambiguity', index=0)
-        <ListControl name='ambiguous-subcontrol' type='select'>
-        >>> browser.getControl('Sub-control Ambiguity', index=1).optionValue
-        'ambiguous'
+            >>> browser.getControl('Ambiguous Control', index=0)
+            <Control name='ambiguous-control-name' type='text'>
+            >>> browser.getControl('Ambiguous Control', index=0).value
+            'First'
+            >>> browser.getControl('Ambiguous Control', index=1).value
+            'Second'
+            >>> browser.getControl('Sub-control Ambiguity', index=0)
+            <ListControl name='ambiguous-subcontrol' type='select'>
+            >>> browser.getControl('Sub-control Ambiguity', index=1).optionValue
+            'ambiguous'
+            >>> browser.getControl('Sub-control Ambiguity', index=2)
+            Traceback (most recent call last):
+            ...
+            LookupError: label 'Sub-control Ambiguity'
+            Index 2 out of range, available choices are 0...1
+              0: <SelectControl(ambiguous-subcontrol=[*, ambiguous])>
+              1: <Item name='ambiguous' id=None contents='Sub-control Ambiguity Exemplified' value='ambiguous' label='Sub-control Ambiguity Exemplified'>
         
         Label searches are against stripped, whitespace-normalized, no-tag versions of
         the text. Text applied to searches is also stripped and whitespace normalized.
@@ -540,69 +556,74 @@ Description: .. contents::
         text in a label.  Thus, for instance, a search for 'Add' will match the label
         'Add a Client' but not 'Address'.  Case is honored.
         
-        >>> browser.getControl('Label Needs Whitespace Normalization')
-        <Control name='label-needs-normalization' type='text'>
-        >>> browser.getControl('label needs whitespace normalization')
-        Traceback (most recent call last):
-        ...
-        LookupError: label 'label needs whitespace normalization'
-        >>> browser.getControl(' Label  Needs Whitespace    ')
-        <Control name='label-needs-normalization' type='text'>
-        >>> browser.getControl('Whitespace')
-        <Control name='label-needs-normalization' type='text'>
-        >>> browser.getControl('hitespace')
-        Traceback (most recent call last):
-        ...
-        LookupError: label 'hitespace'
-        >>> browser.getControl('[non word characters should not confuse]')
-        <Control name='non-word-characters' type='text'>
+            >>> browser.getControl('Label Needs Whitespace Normalization')
+            <Control name='label-needs-normalization' type='text'>
+            >>> browser.getControl('label needs whitespace normalization')
+            Traceback (most recent call last):
+            ...
+            LookupError: label 'label needs whitespace normalization'
+            ...
+            >>> browser.getControl(' Label  Needs Whitespace    ')
+            <Control name='label-needs-normalization' type='text'>
+            >>> browser.getControl('Whitespace')
+            <Control name='label-needs-normalization' type='text'>
+            >>> browser.getControl('hitespace')
+            Traceback (most recent call last):
+            ...
+            LookupError: label 'hitespace'
+            ...
+            >>> browser.getControl('[non word characters should not confuse]')
+            <Control name='non-word-characters' type='text'>
         
         Multiple labels can refer to the same control (simply because that is possible
         in the HTML 4.0 spec).
         
-        >>> browser.getControl('Multiple labels really')
-        <Control name='two-labels' type='text'>
-        >>> browser.getControl('really are possible')
-        <Control name='two-labels' type='text'>
-        >>> browser.getControl('really') # OK: ambiguous labels, but not ambiguous control
-        <Control name='two-labels' type='text'>
+            >>> browser.getControl('Multiple labels really')
+            <Control name='two-labels' type='text'>
+            >>> browser.getControl('really are possible')
+            <Control name='two-labels' type='text'>
+            >>> browser.getControl('really') # OK: ambiguous labels, but not ambiguous control
+            <Control name='two-labels' type='text'>
         
         A label can be connected with a control using the 'for' attribute and also by
         containing a control.
         
-        >>> browser.getControl(
-        ...     'Labels can be connected by containing their respective fields')
-        <Control name='contained-in-label' type='text'>
+            >>> browser.getControl(
+            ...     'Labels can be connected by containing their respective fields')
+            <Control name='contained-in-label' type='text'>
         
         Get also accepts one other search argument, 'name'.  Only one of 'label' and
         'name' may be used at a time.  The 'name' keyword searches form field names.
         
-        >>> browser.getControl(name='text-value')
-        <Control name='text-value' type='text'>
-        >>> browser.getControl(name='ambiguous-control-name')
-        Traceback (most recent call last):
-        ...
-        AmbiguityError: name 'ambiguous-control-name' matches:
-        <TextControl(ambiguous-control-name=First)>
-        <TextControl(ambiguous-control-name=Second)>
-        >>> browser.getControl(name='does-not-exist')
-        Traceback (most recent call last):
-        ...
-        LookupError: name 'does-not-exist'
-        >>> browser.getControl(name='ambiguous-control-name', index=1).value
-        'Second'
+            >>> browser.getControl(name='text-value')
+            <Control name='text-value' type='text'>
+            >>> browser.getControl(name='ambiguous-control-name')
+            Traceback (most recent call last):
+            ...
+            AmbiguityError: name 'ambiguous-control-name' matches:
+              <TextControl(ambiguous-control-name=First)>
+              <TextControl(ambiguous-control-name=Second)>
+            >>> browser.getControl(name='does-not-exist')
+            Traceback (most recent call last):
+            ...
+            LookupError: name 'does-not-exist'
+            available items:
+              <TextControl(text-value=Some Text)>
+              ...
+            >>> browser.getControl(name='ambiguous-control-name', index=1).value
+            'Second'
         
         Combining 'label' and 'name' raises a ValueError, as does supplying neither of
         them.
         
-        >>> browser.getControl(label='Ambiguous Control', name='ambiguous-control-name')
-        Traceback (most recent call last):
-        ...
-        ValueError: Supply one and only one of "label" and "name" as arguments
-        >>> browser.getControl()
-        Traceback (most recent call last):
-        ...
-        ValueError: Supply one and only one of "label" and "name" as arguments
+            >>> browser.getControl(label='Ambiguous Control', name='ambiguous-control-name')
+            Traceback (most recent call last):
+            ...
+            ValueError: Supply one and only one of "label" and "name" as arguments
+            >>> browser.getControl()
+            Traceback (most recent call last):
+            ...
+            ValueError: Supply one and only one of "label" and "name" as arguments
         
         Radio and checkbox fields are unusual in that their labels and names may point
         to different objects: names point to logical collections of radio buttons or
@@ -611,14 +632,14 @@ Description: .. contents::
         different object than obtaining the radio collection by name.  Select options
         may also be searched by label.
         
-        >>> browser.getControl(name='radio-value')
-        <ListControl name='radio-value' type='radio'>
-        >>> browser.getControl('Zwei')
-        <ItemControl name='radio-value' type='radio' optionValue='2' selected=True>
-        >>> browser.getControl('One')
-        <ItemControl name='multi-checkbox-value' type='checkbox' optionValue='1' selected=True>
-        >>> browser.getControl('Tres')
-        <ItemControl name='single-select-value' type='select' optionValue='3' selected=False>
+            >>> browser.getControl(name='radio-value')
+            <ListControl name='radio-value' type='radio'>
+            >>> browser.getControl('Zwei')
+            <ItemControl name='radio-value' type='radio' optionValue='2' selected=True>
+            >>> browser.getControl('One')
+            <ItemControl name='multi-checkbox-value' type='checkbox' optionValue='1' selected=True>
+            >>> browser.getControl('Tres')
+            <ItemControl name='single-select-value' type='select' optionValue='3' selected=False>
         
         Characteristics of controls and subcontrols are discussed below.
         
@@ -628,106 +649,106 @@ Description: .. contents::
         
         Controls provide IControl.
         
-        >>> ctrl = browser.getControl('Text Control')
-        >>> ctrl
-        <Control name='text-value' type='text'>
-        >>> verifyObject(interfaces.IControl, ctrl)
-        True
+            >>> ctrl = browser.getControl('Text Control')
+            >>> ctrl
+            <Control name='text-value' type='text'>
+            >>> verifyObject(interfaces.IControl, ctrl)
+            True
         
         They have several useful attributes:
         
-        - the name as which the control is known to the form:
+          - the name as which the control is known to the form:
         
-        >>> ctrl.name
-        'text-value'
+            >>> ctrl.name
+            'text-value'
         
-        - the value of the control, which may also be set:
+          - the value of the control, which may also be set:
         
-        >>> ctrl.value
-        'Some Text'
-        >>> ctrl.value = 'More Text'
-        >>> ctrl.value
-        'More Text'
+            >>> ctrl.value
+            'Some Text'
+            >>> ctrl.value = 'More Text'
+            >>> ctrl.value
+            'More Text'
         
-        - the type of the control:
+          - the type of the control:
         
-        >>> ctrl.type
-        'text'
+            >>> ctrl.type
+            'text'
         
-        - a flag describing whether the control is disabled:
+          - a flag describing whether the control is disabled:
         
-        >>> ctrl.disabled
-        False
+            >>> ctrl.disabled
+            False
         
-        - and a flag to tell us whether the control can have multiple values:
+          - and a flag to tell us whether the control can have multiple values:
         
-        >>> ctrl.multiple
-        False
+            >>> ctrl.multiple
+            False
         
         Additionally, controllers for select, radio, and checkbox provide IListControl.
         These fields have four other attributes and an additional method:
         
-        >>> ctrl = browser.getControl('Multiple Select Control')
-        >>> ctrl
-        <ListControl name='multi-select-value' type='select'>
-        >>> ctrl.disabled
-        False
-        >>> ctrl.multiple
-        True
-        >>> verifyObject(interfaces.IListControl, ctrl)
-        True
-        
-        - 'options' lists all available value options.
-        
-        >>> ctrl.options
-        ['1', '2', '3']
-        
-        - 'displayOptions' lists all available options by label.  The 'label'
-        attribute on an option has precedence over its contents, which is why
-        our last option is 'Third' in the display.
-        
-        >>> ctrl.displayOptions
-        ['Un', 'Deux', 'Third']
-        
-        - 'displayValue' lets you get and set the displayed values of the control
-        of the select box, rather than the actual values.
-        
-        >>> ctrl.value
-        []
-        >>> ctrl.displayValue
-        []
-        >>> ctrl.displayValue = ['Un', 'Deux']
-        >>> ctrl.displayValue
-        ['Un', 'Deux']
-        >>> ctrl.value
-        ['1', '2']
-        
-        - 'controls' gives you a list of the subcontrol objects in the control
-        (subcontrols are discussed below).
-        
-        >>> ctrl.controls
-        [<ItemControl name='multi-select-value' type='select' optionValue='1' selected=True>,
-        <ItemControl name='multi-select-value' type='select' optionValue='2' selected=True>,
-        <ItemControl name='multi-select-value' type='select' optionValue='3' selected=False>]
-        
-        - The 'getControl' method lets you get subcontrols by their label or their value.
-        
-        >>> ctrl.getControl('Un')
-        <ItemControl name='multi-select-value' type='select' optionValue='1' selected=True>
-        >>> ctrl.getControl('Deux')
-        <ItemControl name='multi-select-value' type='select' optionValue='2' selected=True>
-        >>> ctrl.getControl('Trois') # label attribute
-        <ItemControl name='multi-select-value' type='select' optionValue='3' selected=False>
-        >>> ctrl.getControl('Third') # contents
-        <ItemControl name='multi-select-value' type='select' optionValue='3' selected=False>
-        >>> browser.getControl('Third') # ambiguous in the browser, so useful
-        Traceback (most recent call last):
-        ...
-        AmbiguityError: label 'Third' matches:
-        <Item name='3' id=None contents='Tres' value='3' label='Third'>
-        <Item name='3' id=None contents='Trois' value='3' label='Third'>
-        <Item name='3' id='multi-checkbox-value-3' __label={'__text': 'Three\n        '} checked='checked' name='multi-checkbox-value' type='checkbox' id='multi-checkbox-value-3' value='3'>
-        <Item name='3' id='radio-value-3' __label={'__text': ' Drei'} type='radio' name='radio-value' value='3' id='radio-value-3'>
+            >>> ctrl = browser.getControl('Multiple Select Control')
+            >>> ctrl
+            <ListControl name='multi-select-value' type='select'>
+            >>> ctrl.disabled
+            False
+            >>> ctrl.multiple
+            True
+            >>> verifyObject(interfaces.IListControl, ctrl)
+            True
+        
+          - 'options' lists all available value options.
+        
+            >>> ctrl.options
+            ['1', '2', '3']
+        
+          - 'displayOptions' lists all available options by label.  The 'label'
+            attribute on an option has precedence over its contents, which is why
+            our last option is 'Third' in the display.
+        
+            >>> ctrl.displayOptions
+            ['Un', 'Deux', 'Third']
+        
+          - 'displayValue' lets you get and set the displayed values of the control
+            of the select box, rather than the actual values.
+        
+            >>> ctrl.value
+            []
+            >>> ctrl.displayValue
+            []
+            >>> ctrl.displayValue = ['Un', 'Deux']
+            >>> ctrl.displayValue
+            ['Un', 'Deux']
+            >>> ctrl.value
+            ['1', '2']
+        
+          - 'controls' gives you a list of the subcontrol objects in the control
+            (subcontrols are discussed below).
+        
+            >>> ctrl.controls
+            [<ItemControl name='multi-select-value' type='select' optionValue='1' selected=True>,
+             <ItemControl name='multi-select-value' type='select' optionValue='2' selected=True>,
+             <ItemControl name='multi-select-value' type='select' optionValue='3' selected=False>]
+        
+          - The 'getControl' method lets you get subcontrols by their label or their value.
+        
+            >>> ctrl.getControl('Un')
+            <ItemControl name='multi-select-value' type='select' optionValue='1' selected=True>
+            >>> ctrl.getControl('Deux')
+            <ItemControl name='multi-select-value' type='select' optionValue='2' selected=True>
+            >>> ctrl.getControl('Trois') # label attribute
+            <ItemControl name='multi-select-value' type='select' optionValue='3' selected=False>
+            >>> ctrl.getControl('Third') # contents
+            <ItemControl name='multi-select-value' type='select' optionValue='3' selected=False>
+            >>> browser.getControl('Third') # ambiguous in the browser, so useful
+            Traceback (most recent call last):
+            ...
+            AmbiguityError: label 'Third' matches:
+              <Item name='3' id=None contents='Tres' value='3' label='Third'>
+              <Item name='3' id=None contents='Trois' value='3' label='Third'>
+              <Item name='3' id='multi-checkbox-value-3' __label={'__text': 'Three\n        '} checked='checked' name='multi-checkbox-value' type='checkbox' id='multi-checkbox-value-3' value='3'>
+              <Item name='3' id='radio-value-3' __label={'__text': ' Drei'} type='radio' name='radio-value' value='3' id='radio-value-3'>
         
         Finally, submit controls provide ISubmitControl, and image controls provide
         IImageSubmitControl, which extents ISubmitControl.  These both simply add a
@@ -743,25 +764,25 @@ Description: .. contents::
         radio button or checkbox collection returns item controls, which are parents.
         Manipulating the value of these controls affects the parent control.
         
-        >>> browser.getControl(name='radio-value').value
-        ['2']
-        >>> browser.getControl('Zwei').optionValue # read-only.
-        '2'
-        >>> browser.getControl('Zwei').selected
-        True
-        >>> verifyObject(interfaces.IItemControl, browser.getControl('Zwei'))
-        True
-        >>> browser.getControl('Ein').selected = True
-        >>> browser.getControl('Ein').selected
-        True
-        >>> browser.getControl('Zwei').selected
-        False
-        >>> browser.getControl(name='radio-value').value
-        ['1']
-        >>> browser.getControl('Ein').selected = False
-        >>> browser.getControl(name='radio-value').value
-        []
-        >>> browser.getControl('Zwei').selected = True
+            >>> browser.getControl(name='radio-value').value
+            ['2']
+            >>> browser.getControl('Zwei').optionValue # read-only.
+            '2'
+            >>> browser.getControl('Zwei').selected
+            True
+            >>> verifyObject(interfaces.IItemControl, browser.getControl('Zwei'))
+            True
+            >>> browser.getControl('Ein').selected = True
+            >>> browser.getControl('Ein').selected
+            True
+            >>> browser.getControl('Zwei').selected
+            False
+            >>> browser.getControl(name='radio-value').value
+            ['1']
+            >>> browser.getControl('Ein').selected = False
+            >>> browser.getControl(name='radio-value').value
+            []
+            >>> browser.getControl('Zwei').selected = True
         
         Checkbox collections behave similarly, as shown below.
         
@@ -773,333 +794,333 @@ Description: .. contents::
         
         The various types of controls are demonstrated here.
         
-        - Text Control
+          - Text Control
         
-        The text control we already introduced above.
+            The text control we already introduced above.
         
-        - Password Control
+          - Password Control
         
-        >>> ctrl = browser.getControl('Password Control')
-        >>> ctrl
-        <Control name='password-value' type='password'>
-        >>> verifyObject(interfaces.IControl, ctrl)
-        True
-        >>> ctrl.value
-        'Password'
-        >>> ctrl.value = 'pass now'
-        >>> ctrl.value
-        'pass now'
-        >>> ctrl.disabled
-        False
-        >>> ctrl.multiple
-        False
-        
-        - Hidden Control
-        
-        >>> ctrl = browser.getControl(name='hidden-value')
-        >>> ctrl
-        <Control name='hidden-value' type='hidden'>
-        >>> verifyObject(interfaces.IControl, ctrl)
-        True
-        >>> ctrl.value
-        'Hidden'
-        >>> ctrl.value = 'More Hidden'
-        >>> ctrl.disabled
-        False
-        >>> ctrl.multiple
-        False
-        
-        - Text Area Control
-        
-        >>> ctrl = browser.getControl('Text Area Control')
-        >>> ctrl
-        <Control name='textarea-value' type='textarea'>
-        >>> verifyObject(interfaces.IControl, ctrl)
-        True
-        >>> ctrl.value
-        '        Text inside\n        area!\n      '
-        >>> ctrl.value = 'A lot of\n text.'
-        >>> ctrl.disabled
-        False
-        >>> ctrl.multiple
-        False
-        
-        - File Control
-        
-        File controls are used when a form has a file-upload field.
-        To specify data, call the add_file method, passing:
-        
-        - A file-like object
-        
-        - a content type, and
-        
-        - a file name
-        
-        >>> ctrl = browser.getControl('File Control')
-        >>> ctrl
-        <Control name='file-value' type='file'>
-        >>> verifyObject(interfaces.IControl, ctrl)
-        True
-        >>> ctrl.value is None
-        True
-        >>> import cStringIO
-        
-        >>> ctrl.add_file(cStringIO.StringIO('File contents'),
-        ...               'text/plain', 'test.txt')
-        
-        The file control (like the other controls) also knows if it is disabled
-        or if it can have multiple values.
-        
-        >>> ctrl.disabled
-        False
-        >>> ctrl.multiple
-        False
-        
-        - Selection Control (Single-Valued)
-        
-        >>> ctrl = browser.getControl('Single Select Control')
-        >>> ctrl
-        <ListControl name='single-select-value' type='select'>
-        >>> verifyObject(interfaces.IListControl, ctrl)
-        True
-        >>> ctrl.value
-        ['1']
-        >>> ctrl.value = ['2']
-        >>> ctrl.disabled
-        False
-        >>> ctrl.multiple
-        False
-        >>> ctrl.options
-        ['1', '2', '3']
-        >>> ctrl.displayOptions
-        ['Uno', 'Dos', 'Third']
-        >>> ctrl.displayValue
-        ['Dos']
-        >>> ctrl.displayValue = ['Tres']
-        >>> ctrl.displayValue
-        ['Third']
-        >>> ctrl.displayValue = ['Dos']
-        >>> ctrl.displayValue
-        ['Dos']
-        >>> ctrl.displayValue = ['Third']
-        >>> ctrl.displayValue
-        ['Third']
-        >>> ctrl.value
-        ['3']
-        
-        - Selection Control (Multi-Valued)
-        
-        This was already demonstrated in the introduction to control objects above.
-        
-        - Checkbox Control (Single-Valued; Unvalued)
-        
-        >>> ctrl = browser.getControl(name='single-unvalued-checkbox-value')
-        >>> ctrl
-        <ListControl name='single-unvalued-checkbox-value' type='checkbox'>
-        >>> verifyObject(interfaces.IListControl, ctrl)
-        True
-        >>> ctrl.value
-        True
-        >>> ctrl.value = False
-        >>> ctrl.disabled
-        False
-        >>> ctrl.multiple
-        True
-        >>> ctrl.options
-        [True]
-        >>> ctrl.displayOptions
-        ['Single Unvalued Checkbox']
-        >>> ctrl.displayValue
-        []
-        >>> verifyObject(
-        ...     interfaces.IItemControl,
-        ...     browser.getControl('Single Unvalued Checkbox'))
-        True
-        >>> browser.getControl('Single Unvalued Checkbox').optionValue
-        'on'
-        >>> browser.getControl('Single Unvalued Checkbox').selected
-        False
-        >>> ctrl.displayValue = ['Single Unvalued Checkbox']
-        >>> ctrl.displayValue
-        ['Single Unvalued Checkbox']
-        >>> browser.getControl('Single Unvalued Checkbox').selected
-        True
-        >>> browser.getControl('Single Unvalued Checkbox').selected = False
-        >>> browser.getControl('Single Unvalued Checkbox').selected
-        False
-        >>> ctrl.displayValue
-        []
-        >>> browser.getControl(
-        ...     name='single-disabled-unvalued-checkbox-value').disabled
-        True
-        
-        - Checkbox Control (Single-Valued, Valued)
-        
-        >>> ctrl = browser.getControl(name='single-valued-checkbox-value')
-        >>> ctrl
-        <ListControl name='single-valued-checkbox-value' type='checkbox'>
-        >>> verifyObject(interfaces.IListControl, ctrl)
-        True
-        >>> ctrl.value
-        ['1']
-        >>> ctrl.value = []
-        >>> ctrl.disabled
-        False
-        >>> ctrl.multiple
-        True
-        >>> ctrl.options
-        ['1']
-        >>> ctrl.displayOptions
-        ['Single Valued Checkbox']
-        >>> ctrl.displayValue
-        []
-        >>> verifyObject(
-        ...     interfaces.IItemControl,
-        ...     browser.getControl('Single Valued Checkbox'))
-        True
-        >>> browser.getControl('Single Valued Checkbox').selected
-        False
-        >>> browser.getControl('Single Valued Checkbox').optionValue
-        '1'
-        >>> ctrl.displayValue = ['Single Valued Checkbox']
-        >>> ctrl.displayValue
-        ['Single Valued Checkbox']
-        >>> browser.getControl('Single Valued Checkbox').selected
-        True
-        >>> browser.getControl('Single Valued Checkbox').selected = False
-        >>> browser.getControl('Single Valued Checkbox').selected
-        False
-        >>> ctrl.displayValue
-        []
-        
-        - Checkbox Control (Multi-Valued)
-        
-        >>> ctrl = browser.getControl(name='multi-checkbox-value')
-        >>> ctrl
-        <ListControl name='multi-checkbox-value' type='checkbox'>
-        >>> verifyObject(interfaces.IListControl, ctrl)
-        True
-        >>> ctrl.value
-        ['1', '3']
-        >>> ctrl.value = ['1', '2']
-        >>> ctrl.disabled
-        False
-        >>> ctrl.multiple
-        True
-        >>> ctrl.options
-        ['1', '2', '3']
-        >>> ctrl.displayOptions
-        ['One', 'Two', 'Three']
-        >>> ctrl.displayValue
-        ['One', 'Two']
-        >>> ctrl.displayValue = ['Two']
-        >>> ctrl.value
-        ['2']
-        >>> browser.getControl('Two').optionValue
-        '2'
-        >>> browser.getControl('Two').selected
-        True
-        >>> verifyObject(interfaces.IItemControl, browser.getControl('Two'))
-        True
-        >>> browser.getControl('Three').selected = True
-        >>> browser.getControl('Three').selected
-        True
-        >>> browser.getControl('Two').selected
-        True
-        >>> ctrl.value
-        ['2', '3']
-        >>> browser.getControl('Two').selected = False
-        >>> ctrl.value
-        ['3']
-        >>> browser.getControl('Three').selected = False
-        >>> ctrl.value
-        []
-        
-        - Radio Control
-        
-        This is how you get a radio button based control:
-        
-        >>> ctrl = browser.getControl(name='radio-value')
-        
-        This shows the existing value of the control, as it was in the
-        HTML received from the server:
-        
-        >>> ctrl.value
-        ['2']
-        
-        We can then unselect it:
-        
-        >>> ctrl.value = []
-        >>> ctrl.value
-        []
-        
-        We can also reselect it:
-        
-        >>> ctrl.value = ['2']
-        >>> ctrl.value
-        ['2']
-        
-        displayValue shows the text the user would see next to the
-        control:
-        
-        >>> ctrl.displayValue
-        ['Zwei']
-        
-        This is just unit testing:
-        
-        >>> ctrl
-        <ListControl name='radio-value' type='radio'>
-        >>> verifyObject(interfaces.IListControl, ctrl)
-        True
-        >>> ctrl.disabled
-        False
-        >>> ctrl.multiple
-        False
-        >>> ctrl.options
-        ['1', '2', '3']
-        >>> ctrl.displayOptions
-        ['Ein', 'Zwei', 'Drei']
-        >>> ctrl.displayValue = ['Ein']
-        >>> ctrl.value
-        ['1']
-        >>> ctrl.displayValue
-        ['Ein']
-        
-        The radio control subcontrols were illustrated above.
-        
-        - Image Control
-        
-        >>> ctrl = browser.getControl(name='image-value')
-        >>> ctrl
-        <ImageControl name='image-value' type='image'>
-        >>> verifyObject(interfaces.IImageSubmitControl, ctrl)
-        True
-        >>> ctrl.value
-        ''
-        >>> ctrl.disabled
-        False
-        >>> ctrl.multiple
-        False
-        
-        - Submit Control
-        
-        >>> ctrl = browser.getControl(name='submit-value')
-        >>> ctrl
-        <SubmitControl name='submit-value' type='submit'>
-        >>> browser.getControl('Submit This') # value of submit button is a label
-        <SubmitControl name='submit-value' type='submit'>
-        >>> browser.getControl('Standard Submit Control') # label tag is legal
-        <SubmitControl name='submit-value' type='submit'>
-        >>> browser.getControl('Submit') # multiple labels, but same control
-        <SubmitControl name='submit-value' type='submit'>
-        >>> verifyObject(interfaces.ISubmitControl, ctrl)
-        True
-        >>> ctrl.value
-        'Submit This'
-        >>> ctrl.disabled
-        False
-        >>> ctrl.multiple
-        False
+            >>> ctrl = browser.getControl('Password Control')
+            >>> ctrl
+            <Control name='password-value' type='password'>
+            >>> verifyObject(interfaces.IControl, ctrl)
+            True
+            >>> ctrl.value
+            'Password'
+            >>> ctrl.value = 'pass now'
+            >>> ctrl.value
+            'pass now'
+            >>> ctrl.disabled
+            False
+            >>> ctrl.multiple
+            False
+        
+          - Hidden Control
+        
+            >>> ctrl = browser.getControl(name='hidden-value')
+            >>> ctrl
+            <Control name='hidden-value' type='hidden'>
+            >>> verifyObject(interfaces.IControl, ctrl)
+            True
+            >>> ctrl.value
+            'Hidden'
+            >>> ctrl.value = 'More Hidden'
+            >>> ctrl.disabled
+            False
+            >>> ctrl.multiple
+            False
+        
+          - Text Area Control
+        
+            >>> ctrl = browser.getControl('Text Area Control')
+            >>> ctrl
+            <Control name='textarea-value' type='textarea'>
+            >>> verifyObject(interfaces.IControl, ctrl)
+            True
+            >>> ctrl.value
+            '        Text inside\n        area!\n      '
+            >>> ctrl.value = 'A lot of\n text.'
+            >>> ctrl.disabled
+            False
+            >>> ctrl.multiple
+            False
+        
+          - File Control
+        
+            File controls are used when a form has a file-upload field.
+            To specify data, call the add_file method, passing:
+        
+            - A file-like object
+        
+            - a content type, and
+        
+            - a file name
+        
+            >>> ctrl = browser.getControl('File Control')
+            >>> ctrl
+            <Control name='file-value' type='file'>
+            >>> verifyObject(interfaces.IControl, ctrl)
+            True
+            >>> ctrl.value is None
+            True
+            >>> import cStringIO
+        
+            >>> ctrl.add_file(cStringIO.StringIO('File contents'),
+            ...               'text/plain', 'test.txt')
+        
+            The file control (like the other controls) also knows if it is disabled
+            or if it can have multiple values.
+        
+            >>> ctrl.disabled
+            False
+            >>> ctrl.multiple
+            False
+        
+          - Selection Control (Single-Valued)
+        
+            >>> ctrl = browser.getControl('Single Select Control')
+            >>> ctrl
+            <ListControl name='single-select-value' type='select'>
+            >>> verifyObject(interfaces.IListControl, ctrl)
+            True
+            >>> ctrl.value
+            ['1']
+            >>> ctrl.value = ['2']
+            >>> ctrl.disabled
+            False
+            >>> ctrl.multiple
+            False
+            >>> ctrl.options
+            ['1', '2', '3']
+            >>> ctrl.displayOptions
+            ['Uno', 'Dos', 'Third']
+            >>> ctrl.displayValue
+            ['Dos']
+            >>> ctrl.displayValue = ['Tres']
+            >>> ctrl.displayValue
+            ['Third']
+            >>> ctrl.displayValue = ['Dos']
+            >>> ctrl.displayValue
+            ['Dos']
+            >>> ctrl.displayValue = ['Third']
+            >>> ctrl.displayValue
+            ['Third']
+            >>> ctrl.value
+            ['3']
+        
+          - Selection Control (Multi-Valued)
+        
+            This was already demonstrated in the introduction to control objects above.
+        
+          - Checkbox Control (Single-Valued; Unvalued)
+        
+            >>> ctrl = browser.getControl(name='single-unvalued-checkbox-value')
+            >>> ctrl
+            <ListControl name='single-unvalued-checkbox-value' type='checkbox'>
+            >>> verifyObject(interfaces.IListControl, ctrl)
+            True
+            >>> ctrl.value
+            True
+            >>> ctrl.value = False
+            >>> ctrl.disabled
+            False
+            >>> ctrl.multiple
+            True
+            >>> ctrl.options
+            [True]
+            >>> ctrl.displayOptions
+            ['Single Unvalued Checkbox']
+            >>> ctrl.displayValue
+            []
+            >>> verifyObject(
+            ...     interfaces.IItemControl,
+            ...     browser.getControl('Single Unvalued Checkbox'))
+            True
+            >>> browser.getControl('Single Unvalued Checkbox').optionValue
+            'on'
+            >>> browser.getControl('Single Unvalued Checkbox').selected
+            False
+            >>> ctrl.displayValue = ['Single Unvalued Checkbox']
+            >>> ctrl.displayValue
+            ['Single Unvalued Checkbox']
+            >>> browser.getControl('Single Unvalued Checkbox').selected
+            True
+            >>> browser.getControl('Single Unvalued Checkbox').selected = False
+            >>> browser.getControl('Single Unvalued Checkbox').selected
+            False
+            >>> ctrl.displayValue
+            []
+            >>> browser.getControl(
+            ...     name='single-disabled-unvalued-checkbox-value').disabled
+            True
+        
+          - Checkbox Control (Single-Valued, Valued)
+        
+            >>> ctrl = browser.getControl(name='single-valued-checkbox-value')
+            >>> ctrl
+            <ListControl name='single-valued-checkbox-value' type='checkbox'>
+            >>> verifyObject(interfaces.IListControl, ctrl)
+            True
+            >>> ctrl.value
+            ['1']
+            >>> ctrl.value = []
+            >>> ctrl.disabled
+            False
+            >>> ctrl.multiple
+            True
+            >>> ctrl.options
+            ['1']
+            >>> ctrl.displayOptions
+            ['Single Valued Checkbox']
+            >>> ctrl.displayValue
+            []
+            >>> verifyObject(
+            ...     interfaces.IItemControl,
+            ...     browser.getControl('Single Valued Checkbox'))
+            True
+            >>> browser.getControl('Single Valued Checkbox').selected
+            False
+            >>> browser.getControl('Single Valued Checkbox').optionValue
+            '1'
+            >>> ctrl.displayValue = ['Single Valued Checkbox']
+            >>> ctrl.displayValue
+            ['Single Valued Checkbox']
+            >>> browser.getControl('Single Valued Checkbox').selected
+            True
+            >>> browser.getControl('Single Valued Checkbox').selected = False
+            >>> browser.getControl('Single Valued Checkbox').selected
+            False
+            >>> ctrl.displayValue
+            []
+        
+          - Checkbox Control (Multi-Valued)
+        
+            >>> ctrl = browser.getControl(name='multi-checkbox-value')
+            >>> ctrl
+            <ListControl name='multi-checkbox-value' type='checkbox'>
+            >>> verifyObject(interfaces.IListControl, ctrl)
+            True
+            >>> ctrl.value
+            ['1', '3']
+            >>> ctrl.value = ['1', '2']
+            >>> ctrl.disabled
+            False
+            >>> ctrl.multiple
+            True
+            >>> ctrl.options
+            ['1', '2', '3']
+            >>> ctrl.displayOptions
+            ['One', 'Two', 'Three']
+            >>> ctrl.displayValue
+            ['One', 'Two']
+            >>> ctrl.displayValue = ['Two']
+            >>> ctrl.value
+            ['2']
+            >>> browser.getControl('Two').optionValue
+            '2'
+            >>> browser.getControl('Two').selected
+            True
+            >>> verifyObject(interfaces.IItemControl, browser.getControl('Two'))
+            True
+            >>> browser.getControl('Three').selected = True
+            >>> browser.getControl('Three').selected
+            True
+            >>> browser.getControl('Two').selected
+            True
+            >>> ctrl.value
+            ['2', '3']
+            >>> browser.getControl('Two').selected = False
+            >>> ctrl.value
+            ['3']
+            >>> browser.getControl('Three').selected = False
+            >>> ctrl.value
+            []
+        
+          - Radio Control
+        
+            This is how you get a radio button based control:
+        
+            >>> ctrl = browser.getControl(name='radio-value')
+        
+            This shows the existing value of the control, as it was in the
+            HTML received from the server:
+        
+            >>> ctrl.value
+            ['2']
+        
+            We can then unselect it:
+        
+            >>> ctrl.value = []
+            >>> ctrl.value
+            []
+        
+            We can also reselect it:
+        
+            >>> ctrl.value = ['2']
+            >>> ctrl.value
+            ['2']
+        
+            displayValue shows the text the user would see next to the
+            control:
+        
+            >>> ctrl.displayValue
+            ['Zwei']
+        
+            This is just unit testing:
+        
+            >>> ctrl
+            <ListControl name='radio-value' type='radio'>
+            >>> verifyObject(interfaces.IListControl, ctrl)
+            True
+            >>> ctrl.disabled
+            False
+            >>> ctrl.multiple
+            False
+            >>> ctrl.options
+            ['1', '2', '3']
+            >>> ctrl.displayOptions
+            ['Ein', 'Zwei', 'Drei']
+            >>> ctrl.displayValue = ['Ein']
+            >>> ctrl.value
+            ['1']
+            >>> ctrl.displayValue
+            ['Ein']
+        
+            The radio control subcontrols were illustrated above.
+        
+          - Image Control
+        
+            >>> ctrl = browser.getControl(name='image-value')
+            >>> ctrl
+            <ImageControl name='image-value' type='image'>
+            >>> verifyObject(interfaces.IImageSubmitControl, ctrl)
+            True
+            >>> ctrl.value
+            ''
+            >>> ctrl.disabled
+            False
+            >>> ctrl.multiple
+            False
+        
+          - Submit Control
+        
+            >>> ctrl = browser.getControl(name='submit-value')
+            >>> ctrl
+            <SubmitControl name='submit-value' type='submit'>
+            >>> browser.getControl('Submit This') # value of submit button is a label
+            <SubmitControl name='submit-value' type='submit'>
+            >>> browser.getControl('Standard Submit Control') # label tag is legal
+            <SubmitControl name='submit-value' type='submit'>
+            >>> browser.getControl('Submit') # multiple labels, but same control
+            <SubmitControl name='submit-value' type='submit'>
+            >>> verifyObject(interfaces.ISubmitControl, ctrl)
+            True
+            >>> ctrl.value
+            'Submit This'
+            >>> ctrl.disabled
+            False
+            >>> ctrl.multiple
+            False
         
         
         Using Submitting Controls
@@ -1107,69 +1128,82 @@ Description: .. contents::
         
         Both the submit and image type should be clickable and submit the form:
         
-        >>> browser.getControl('Text Control').value = 'Other Text'
-        >>> browser.getControl('Submit').click()
-        >>> print browser.contents
-        <html>
-        ...
-        <em>Other Text</em>
-        <input type="text" name="text-value" id="text-value" value="Some Text" />
-        ...
-        <em>Submit This</em>
-        <input type="submit" name="submit-value" id="submit-value" value="Submit This" />
-        ...
-        </html>
+            >>> browser.getControl('Text Control').value = 'Other Text'
+            >>> browser.getControl('Submit').click()
+            >>> print browser.contents
+            <html>
+            ...
+            <em>Other Text</em>
+            <input type="text" name="text-value" id="text-value" value="Some Text" />
+            ...
+            <em>Submit This</em>
+            <input type="submit" name="submit-value" id="submit-value" value="Submit This" />
+            ...
+            </html>
         
         Note that if you click a submit object after the associated page has expired,
         you will get an error.
         
-        >>> browser.open('http://localhost/@@/testbrowser/controls.html')
-        >>> ctrl = browser.getControl('Submit')
-        >>> ctrl.click()
-        >>> ctrl.click()
-        Traceback (most recent call last):
-        ...
-        ExpiredError
+            >>> browser.open('http://localhost/@@/testbrowser/controls.html')
+            >>> ctrl = browser.getControl('Submit')
+            >>> ctrl.click()
+            >>> ctrl.click()
+            Traceback (most recent call last):
+            ...
+            ExpiredError
         
         All the above also holds true for the image control:
         
-        >>> browser.open('http://localhost/@@/testbrowser/controls.html')
-        >>> browser.getControl('Text Control').value = 'Other Text'
-        >>> browser.getControl(name='image-value').click()
-        >>> print browser.contents
-        <html>
-        ...
-        <em>Other Text</em>
-        <input type="text" name="text-value" id="text-value" value="Some Text" />
-        ...
-        <em>1</em>
-        <em>1</em>
-        <input type="image" name="image-value" id="image-value"
-        src="zope3logo.gif" />
-        ...
-        </html>
-        
-        >>> browser.open('http://localhost/@@/testbrowser/controls.html')
-        >>> ctrl = browser.getControl(name='image-value')
-        >>> ctrl.click()
-        >>> ctrl.click()
-        Traceback (most recent call last):
-        ...
-        ExpiredError
+            >>> browser.open('http://localhost/@@/testbrowser/controls.html')
+            >>> browser.getControl('Text Control').value = 'Other Text'
+            >>> browser.getControl(name='image-value').click()
+            >>> print browser.contents
+            <html>
+            ...
+            <em>Other Text</em>
+            <input type="text" name="text-value" id="text-value" value="Some Text" />
+            ...
+            <em>1</em>
+            <em>1</em>
+            <input type="image" name="image-value" id="image-value"
+                   src="zope3logo.gif" />
+            ...
+            </html>
+        
+            >>> browser.open('http://localhost/@@/testbrowser/controls.html')
+            >>> ctrl = browser.getControl(name='image-value')
+            >>> ctrl.click()
+            >>> ctrl.click()
+            Traceback (most recent call last):
+            ...
+            ExpiredError
         
         But when sending an image, you can also specify the coordinate you clicked:
         
-        >>> browser.open('http://localhost/@@/testbrowser/controls.html')
-        >>> browser.getControl(name='image-value').click((50,25))
-        >>> print browser.contents
-        <html>
-        ...
-        <em>50</em>
-        <em>25</em>
-        <input type="image" name="image-value" id="image-value"
-        src="zope3logo.gif" />
-        ...
-        </html>
+            >>> browser.open('http://localhost/@@/testbrowser/controls.html')
+            >>> browser.getControl(name='image-value').click((50,25))
+            >>> print browser.contents
+            <html>
+            ...
+            <em>50</em>
+            <em>25</em>
+            <input type="image" name="image-value" id="image-value"
+                   src="zope3logo.gif" />
+            ...
+            </html>
+        
+        
+        Pages Without Controls
+        ~~~~~~~~~~~~~~~~~~~~~~
+        
+        What would happen if we tried to look up a control on a page that has none?
+        
+            >>> browser.open('http://localhost/@@/testbrowser/simple.html')
+            >>> browser.getControl('anything')
+            Traceback (most recent call last):
+            ...
+            LookupError: label 'anything'
+            (there are no form items in the HTML)
         
         
         Forms
@@ -1180,51 +1214,51 @@ Description: .. contents::
         be used to do so.  The key value is the form's name or id.  If more than one
         form has the same name or id, the first one will be returned.
         
-        >>> browser.open('http://localhost/@@/testbrowser/forms.html')
-        >>> form = browser.getForm(name='one')
+            >>> browser.open('http://localhost/@@/testbrowser/forms.html')
+            >>> form = browser.getForm(name='one')
         
         Form instances conform to the IForm interface.
         
-        >>> verifyObject(interfaces.IForm, form)
-        True
+            >>> verifyObject(interfaces.IForm, form)
+            True
         
         The form exposes several attributes related to forms:
         
-        - The name of the form:
+          - The name of the form:
         
-        >>> form.name
-        'one'
+            >>> form.name
+            'one'
         
-        - The id of the form:
+          - The id of the form:
         
-        >>> form.id
-        '1'
+            >>> form.id
+            '1'
         
-        - The action (target URL) when the form is submitted:
+          - The action (target URL) when the form is submitted:
         
-        >>> form.action
-        'http://localhost/@@/testbrowser/forms.html'
+            >>> form.action
+            'http://localhost/@@/testbrowser/forms.html'
         
-        - The method (HTTP verb) used to transmit the form data:
+          - The method (HTTP verb) used to transmit the form data:
         
-        >>> form.method
-        'GET'
+            >>> form.method
+            'GET'
         
         Besides those attributes, you have also a couple of methods.  Like for the
         browser, you can get control objects, but limited to the current form...
         
-        >>> form.getControl(name='text-value')
-        <Control name='text-value' type='text'>
+            >>> form.getControl(name='text-value')
+            <Control name='text-value' type='text'>
         
         ...and submit the form.
         
-        >>> form.submit('Submit')
-        >>> print browser.contents
-        <html>
-        ...
-        <em>First Text</em>
-        ...
-        </html>
+            >>> form.submit('Submit')
+            >>> print browser.contents
+            <html>
+            ...
+            <em>First Text</em>
+            ...
+            </html>
         
         Submitting also works without specifying a control, as shown below, which is
         it's primary reason for existing in competition with the control submission
@@ -1234,66 +1268,66 @@ Description: .. contents::
         the `forms.html` template, we have four forms all having a text control named
         `text-value`.  Now, if I use the browser's `get` method,
         
-        >>> browser.getControl(name='text-value')
-        Traceback (most recent call last):
-        ...
-        AmbiguityError: name 'text-value' matches:
-        <TextControl(text-value=First Text)>
-        <TextControl(text-value=Second Text)>
-        <TextControl(text-value=Third Text)>
-        <TextControl(text-value=Fourth Text)>
-        >>> browser.getControl('Text Control')
-        Traceback (most recent call last):
-        ...
-        AmbiguityError: label 'Text Control' matches:
-        <TextControl(text-value=Third Text)>
-        <TextControl(text-value=Fourth Text)>
+            >>> browser.getControl(name='text-value')
+            Traceback (most recent call last):
+            ...
+            AmbiguityError: name 'text-value' matches:
+              <TextControl(text-value=First Text)>
+              <TextControl(text-value=Second Text)>
+              <TextControl(text-value=Third Text)>
+              <TextControl(text-value=Fourth Text)>
+            >>> browser.getControl('Text Control')
+            Traceback (most recent call last):
+            ...
+            AmbiguityError: label 'Text Control' matches:
+              <TextControl(text-value=Third Text)>
+              <TextControl(text-value=Fourth Text)>
         
         I'll always get an ambiguous form field.  I can use the index argument, or
         with the `getForm` method I can disambiguate by searching only within a given
         form:
         
-        >>> form = browser.getForm('2')
-        >>> form.getControl(name='text-value').value
-        'Second Text'
-        >>> form.submit('Submit')
-        >>> browser.contents
-        '...<em>Second Text</em>...'
-        >>> form = browser.getForm('2')
-        >>> form.getControl('Submit').click()
-        >>> browser.contents
-        '...<em>Second Text</em>...'
-        >>> browser.getForm('3').getControl('Text Control').value
-        'Third Text'
+            >>> form = browser.getForm('2')
+            >>> form.getControl(name='text-value').value
+            'Second Text'
+            >>> form.submit('Submit')
+            >>> browser.contents
+            '...<em>Second Text</em>...'
+            >>> form = browser.getForm('2')
+            >>> form.getControl('Submit').click()
+            >>> browser.contents
+            '...<em>Second Text</em>...'
+            >>> browser.getForm('3').getControl('Text Control').value
+            'Third Text'
         
         The last form on the page does not have a name, an id, or a submit button.
         Working with it is still easy, thanks to a index attribute that guarantees
         order.  (Forms without submit buttons are sometimes useful for JavaScript.)
         
-        >>> form = browser.getForm(index=3)
-        >>> form.submit()
-        >>> browser.contents
-        '...<em>Fourth Text</em>...<em>Submitted without the submit button.</em>...'
+            >>> form = browser.getForm(index=3)
+            >>> form.submit()
+            >>> browser.contents
+            '...<em>Fourth Text</em>...<em>Submitted without the submit button.</em>...'
         
         If a form is requested that does not exists, an exception will be raised.
         
-        >>> form = browser.getForm('does-not-exist')
-        Traceback (most recent call last):
-        LookupError
+            >>> form = browser.getForm('does-not-exist')
+            Traceback (most recent call last):
+            LookupError
         
         If the HTML page contains only one form, no arguments to `getForm` are
         needed:
         
-        >>> oneform = Browser(wsgi_app=wsgi_app)
-        >>> oneform.open('http://localhost/@@/testbrowser/oneform.html')
-        >>> form = oneform.getForm()
+            >>> oneform = Browser(wsgi_app=wsgi_app)
+            >>> oneform.open('http://localhost/@@/testbrowser/oneform.html')
+            >>> form = oneform.getForm()
         
         If the HTML page contains more than one form, `index` is needed to
         disambiguate if no other arguments are provided:
         
-        >>> browser.getForm()
-        Traceback (most recent call last):
-        ValueError: if no other arguments are given, index is required.
+            >>> browser.getForm()
+            Traceback (most recent call last):
+            ValueError: if no other arguments are given, index is required.
         
         
         Submitting a posts body directly
@@ -1305,50 +1339,50 @@ Description: .. contents::
         
         Let's visit a page that echos some interesting values from it's request:
         
-        >>> browser.open('http://localhost/echo.html')
-        >>> print browser.contents
-        HTTP_ACCEPT_LANGUAGE: en-US
-        HTTP_CONNECTION: close
-        HTTP_HOST: localhost
-        HTTP_USER_AGENT: Python-urllib/2.4
-        PATH_INFO: /echo.html
-        REQUEST_METHOD: GET
-        Body: ''
+            >>> browser.open('http://localhost/echo.html')
+            >>> print browser.contents
+            HTTP_ACCEPT_LANGUAGE: en-US
+            HTTP_CONNECTION: close
+            HTTP_HOST: localhost
+            HTTP_USER_AGENT: Python-urllib/2.4
+            PATH_INFO: /echo.html
+            REQUEST_METHOD: GET
+            Body: ''
         
         Now, we'll try a post.  The post method takes a URL, a data string,
         and an optional content type.  If we just pass a string, then
         a URL-encoded query string is assumed:
         
-        >>> browser.post('http://localhost/echo.html', 'x=1&y=2')
-        >>> print browser.contents
-        CONTENT_LENGTH: 7
-        CONTENT_TYPE: application/x-www-form-urlencoded
-        HTTP_ACCEPT_LANGUAGE: en-US
-        HTTP_CONNECTION: close
-        HTTP_HOST: localhost
-        HTTP_USER_AGENT: Python-urllib/2.4
-        PATH_INFO: /echo.html
-        REQUEST_METHOD: POST
-        x: 1
-        y: 2
-        Body: ''
+            >>> browser.post('http://localhost/echo.html', 'x=1&y=2')
+            >>> print browser.contents
+            CONTENT_LENGTH: 7
+            CONTENT_TYPE: application/x-www-form-urlencoded
+            HTTP_ACCEPT_LANGUAGE: en-US
+            HTTP_CONNECTION: close
+            HTTP_HOST: localhost
+            HTTP_USER_AGENT: Python-urllib/2.4
+            PATH_INFO: /echo.html
+            REQUEST_METHOD: POST
+            x: 1
+            y: 2
+            Body: ''
         
         The body is empty because it is consumed to get form data.
         
         We can pass a content-type explicitly:
         
-        >>> browser.post('http://localhost/echo.html',
-        ...              '{"x":1,"y":2}', 'application/x-javascript')
-        >>> print browser.contents
-        CONTENT_LENGTH: 13
-        CONTENT_TYPE: application/x-javascript
-        HTTP_ACCEPT_LANGUAGE: en-US
-        HTTP_CONNECTION: close
-        HTTP_HOST: localhost
-        HTTP_USER_AGENT: Python-urllib/2.4
-        PATH_INFO: /echo.html
-        REQUEST_METHOD: POST
-        Body: '{"x":1,"y":2}'
+            >>> browser.post('http://localhost/echo.html',
+            ...              '{"x":1,"y":2}', 'application/x-javascript')
+            >>> print browser.contents
+            CONTENT_LENGTH: 13
+            CONTENT_TYPE: application/x-javascript
+            HTTP_ACCEPT_LANGUAGE: en-US
+            HTTP_CONNECTION: close
+            HTTP_HOST: localhost
+            HTTP_USER_AGENT: Python-urllib/2.4
+            PATH_INFO: /echo.html
+            REQUEST_METHOD: POST
+            Body: '{"x":1,"y":2}'
         
         Here, the body is left in place because it isn't form data.
         
@@ -1361,11 +1395,11 @@ Description: .. contents::
         Be very careful using raw seconds, cross-machine differences can be huge,
         pystones is usually a better choice.
         
-        >>> browser.open('http://localhost/@@/testbrowser/simple.html')
-        >>> browser.lastRequestSeconds < 10 # really big number for safety
-        True
-        >>> browser.lastRequestPystones < 10000 # really big number for safety
-        True
+            >>> browser.open('http://localhost/@@/testbrowser/simple.html')
+            >>> browser.lastRequestSeconds < 10 # really big number for safety
+            True
+            >>> browser.lastRequestPystones < 10000 # really big number for safety
+            True
         
         
         Handling Errors
@@ -1374,10 +1408,10 @@ Description: .. contents::
         Often WSGI middleware or the application itself gracefully handle application
         errors, such as invalid URLs:
         
-        >>> browser.open('http://localhost/invalid')
-        Traceback (most recent call last):
-        ...
-        HTTPError: HTTP Error 404: Not Found
+            >>> browser.open('http://localhost/invalid')
+            Traceback (most recent call last):
+            ...
+            HTTPError: HTTP Error 404: Not Found
         
         Note that the above error was thrown by ``mechanize`` and not by the
         application.  For debugging purposes, however, it can be very useful to see the
@@ -1385,24 +1419,24 @@ Description: .. contents::
         ``handleErrors`` property of the browser to ``False``.  It is defaulted to
         ``True``:
         
-        >>> browser.handleErrors
-        True
+            >>> browser.handleErrors
+            True
         
         So when we tell the application not to handle the errors,
         
-        >>> browser.handleErrors = False
+            >>> browser.handleErrors = False
         
         we get a different, internal error from the application:
         
-        >>> browser.open('http://localhost/invalid')
-        Traceback (most recent call last):
-        ...
-        NotFound: /invalid
+            >>> browser.open('http://localhost/invalid')
+            Traceback (most recent call last):
+            ...
+            NotFound: /invalid
         
         NB: Setting the handleErrors attribute to False will only change anything if
-        the WSGI application obeys the wsgi.handleErrors or paste.throw_errors
-        WSGI environment variables. i.e. it does not catch and handle the original
-        exception when these are set appropriately.
+            the WSGI application obeys the wsgi.handleErrors or paste.throw_errors
+            WSGI environment variables. i.e. it does not catch and handle the original
+            exception when these are set appropriately.
         
         When the testbrowser is raising HttpErrors, the errors still hit the test.
         Sometimes we don't want that to happen, in situations where there are edge
@@ -1411,28 +1445,28 @@ Description: .. contents::
         
         To get around this, one can set the raiseHttpErrors to False.
         
-        >>> browser.handleErrors = True
-        >>> browser.raiseHttpErrors = False
+            >>> browser.handleErrors = True
+            >>> browser.raiseHttpErrors = False
         
         This will cause HttpErrors not to propagate.
         
-        >>> browser.open('http://localhost/invalid')
+            >>> browser.open('http://localhost/invalid')
         
         The headers are still there, though.
         
-        >>> '404 Not Found' in str(browser.headers)
-        True
+            >>> '404 Not Found' in str(browser.headers)
+            True
         
         If we don't handle the errors, and allow internal ones to propagate, however,
         this flag doesn't affect things.
         
-        >>> browser.handleErrors = False
-        >>> browser.open('http://localhost/invalid')
-        Traceback (most recent call last):
-        ...
-        NotFound: /invalid
+            >>> browser.handleErrors = False
+            >>> browser.open('http://localhost/invalid')
+            Traceback (most recent call last):
+            ...
+            NotFound: /invalid
         
-        >>> browser.raiseHttpErrors = True
+            >>> browser.raiseHttpErrors = True
         
         
         Hand-Holding
@@ -1441,25 +1475,25 @@ Description: .. contents::
         Instances of the various objects ensure that users don't set incorrect
         instance attributes accidentally.
         
-        >>> browser.nonexistant = None
-        Traceback (most recent call last):
-        ...
-        AttributeError: 'Browser' object has no attribute 'nonexistant'
-        
-        >>> form.nonexistant = None
-        Traceback (most recent call last):
-        ...
-        AttributeError: 'Form' object has no attribute 'nonexistant'
-        
-        >>> control.nonexistant = None
-        Traceback (most recent call last):
-        ...
-        AttributeError: 'Control' object has no attribute 'nonexistant'
-        
-        >>> link.nonexistant = None
-        Traceback (most recent call last):
-        ...
-        AttributeError: 'Link' object has no attribute 'nonexistant'
+            >>> browser.nonexistant = None
+            Traceback (most recent call last):
+            ...
+            AttributeError: 'Browser' object has no attribute 'nonexistant'
+        
+            >>> form.nonexistant = None
+            Traceback (most recent call last):
+            ...
+            AttributeError: 'Form' object has no attribute 'nonexistant'
+        
+            >>> control.nonexistant = None
+            Traceback (most recent call last):
+            ...
+            AttributeError: 'Control' object has no attribute 'nonexistant'
+        
+            >>> link.nonexistant = None
+            Traceback (most recent call last):
+            ...
+            AttributeError: 'Link' object has no attribute 'nonexistant'
         
         
         HTTPS support
@@ -1468,13 +1502,13 @@ Description: .. contents::
         Depending on the scheme of the request the variable wsgi.url_scheme will be set
         correctly on the request:
         
-        >>> browser.open('http://localhost/echo_one.html?var=wsgi.url_scheme')
-        >>> print browser.contents
-        'http'
-        
-        >>> browser.open('https://localhost/echo_one.html?var=wsgi.url_scheme')
-        >>> print browser.contents
-        'https'
+            >>> browser.open('http://localhost/echo_one.html?var=wsgi.url_scheme')
+            >>> print browser.contents
+            'http'
+        
+            >>> browser.open('https://localhost/echo_one.html?var=wsgi.url_scheme')
+            >>> print browser.contents
+            'https'
         
         see http://www.python.org/dev/peps/pep-3333/ for details.
         
@@ -1483,6 +1517,41 @@ Description: .. contents::
         CHANGES
         =======
         
+        4.0.4 (2013-10-11)
+        ------------------
+        
+        - Removed the 'WebTest <= 1.3.4' version pin, fixed tests to work with modern
+          WebTest versions
+          (https://github.com/zopefoundation/zope.testbrowser/issues/10).
+        
+        
+        4.0.3 (2013-09-04)
+        ------------------
+        
+        - pinning version 'WebTest <= 1.3.4', because of some incompatibility and
+          test failures
+        
+        - Make zope.testbrowser installable via pip
+          (https://github.com/zopefoundation/zope.testbrowser/issues/6).
+        
+        - When ``Browser.handleErrors`` is False, also add ``x-wsgiorg.throw_errors``
+          to the environment. http://wsgi.org/wsgi/Specifications/throw_errors
+        
+        - Prevent WebTest from always sending ``paste.throw_errors=True`` in the
+          environment by setting it to ``None`` when ``Browser.handleErrors`` is
+          ``True``.  This makes it easier to test error pages.
+        
+        - Made Browser.submit() handle ``raiseHttpErrors``
+          (https://github.com/zopefoundation/zope.testbrowser/pull/4).
+        
+        - More friendly error messages from getControl() et al:
+        
+          - when you specify an index that is out of bounds, show the available
+            choices
+        
+          - when you fail to find anything, show all the available items
+        
+        
         4.0.2 (2011-05-25)
         ------------------
         
@@ -1493,7 +1562,7 @@ Description: .. contents::
         ------------------
         
         - Added a hint in documentation how to use ``zope.testbrowser.wsgi.Browser``
-        to test a Zope 2/Zope 3/Bluebream WSGI application.
+          to test a Zope 2/Zope 3/Bluebream WSGI application.
         
         4.0.0 (2011-03-14)
         ------------------
@@ -1501,15 +1570,15 @@ Description: .. contents::
         - LP #721252: AmbiguityError now shows all matching controls.
         
         - Integrate with WebTest. ``zope.testbrowser.wsgi.Browser`` is a
-        ``Browser`` implementation that uses ``webtest.TestApp`` to drive a WSGI
-        application. This this replaces the wsgi_intercept support added in 3.11.
+          ``Browser`` implementation that uses ``webtest.TestApp`` to drive a WSGI
+          application. This this replaces the wsgi_intercept support added in 3.11.
         
         - Re-write the test application as a pure WSGI application using WebOb. Run the
-        existing tests using the WebTest based Browser
+          existing tests using the WebTest based Browser
         
         - Move zope.app.testing based Browser into ``zope.app.testing`` (leaving
-        backwards compatibility imports in-place). Released in ``zope.app.testing``
-        3.9.0.
+          backwards compatibility imports in-place). Released in ``zope.app.testing``
+          3.9.0.
         
         
         3.11.1 (2011-01-24)
@@ -1528,7 +1597,7 @@ Description: .. contents::
         -------------------
         
         - Move the over-the-wire.txt doctest out of the TestBrowserLayer as it doesn't
-        need or use it.
+          need or use it.
         
         - Fix test compatibility with zope.app.testing 3.8.1.
         
@@ -1548,21 +1617,21 @@ Description: .. contents::
         -------------------
         
         - Fixed a bug that caused the ``Browser`` to keep it's previous ``contents``
-        The places are:
-        - Link.click()
-        - SubmitControl.click()
-        - ImageControl.click()
-        - Form.submit()
+          The places are:
+          - Link.click()
+          - SubmitControl.click()
+          - ImageControl.click()
+          - Form.submit()
         
         - Also adjusted exception messages at the above places to match
-        pre version 3.4.1 messages.
+          pre version 3.4.1 messages.
         
         
         3.10.0 (2010-09-14)
         -------------------
         
         - LP #98437: use mechanize's built-in ``submit()`` to submit forms, allowing
-        mechanize to set the "Referer:" (sic) header appropriately.
+          mechanize to set the "Referer:" (sic) header appropriately.
         
         - Fixed tests to run with ``zope.app.testing`` 3.8 and above.
         
@@ -1571,21 +1640,21 @@ Description: .. contents::
         ------------------
         
         - LP #568806: Update dependency ``mechanize >= 0.2.0``, which now includes
-        the ``ClientForm`` APIs.  Remove use of ``urllib2`` APIs (incompatible
-        with ``mechanize 0.2.0``) in favor of ``mechanize`` equivalents.
-        Thanks to John J. Lee for the patch.
+          the ``ClientForm`` APIs.  Remove use of ``urllib2`` APIs (incompatible
+          with ``mechanize 0.2.0``) in favor of ``mechanize`` equivalents.
+          Thanks to John J. Lee for the patch.
         
         - Use stdlib ``doctest`` module, instead of ``zope.testing.doctest``.
         
         - **Caution:** This version is no longer fully compatible with Python 2.4:
-        ``handleErrors = False`` no longer works.
+          ``handleErrors = False`` no longer works.
         
         
         3.8.1 (2010-04-19)
         ------------------
         
         - Pinned dependency on mechanize to prevent use of the upcoming
-        0.2.0 release before we have time to adjust to its API changes.
+          0.2.0 release before we have time to adjust to its API changes.
         
         - LP #98396: testbrowser resolves relative URLs incorrectly.
         
@@ -1600,25 +1669,25 @@ Description: .. contents::
         ------------------
         
         - Moved zope.app.testing dependency into the scope of the PublisherConnection
-        class. Zope2 specifies its own PublisherConnection which isn't dependent on
-        zope.app.testing.
+          class. Zope2 specifies its own PublisherConnection which isn't dependent on
+          zope.app.testing.
         
         - Fixed LP #419119: return None when the browser has no contents instead of
-        raising an exception.
+          raising an exception.
         
         
         3.7.0a1 (2009-08-29)
         --------------------
         
         - Remove dependency on zope.app.publisher in favor of zope.browserpage,
-        zope.browserresource and zope.ptresource.
+          zope.browserresource and zope.ptresource.
         
         - Remove dependencies on zope.app.principalannotation and zope.securitypolicy
-        by using the simple PermissiveSecurityPolicy. We aren't testing security
-        in our tests.
+          by using the simple PermissiveSecurityPolicy. We aren't testing security
+          in our tests.
         
         - Replaced the testing dependency on zope.app.zcmlfiles with explicit
-        dependencies of a minimal set of packages.
+          dependencies of a minimal set of packages.
         
         - Remove unneeded zope.app.authentication from ftesting.zcml.
         
@@ -1639,52 +1708,52 @@ Description: .. contents::
         - Author e-mail to zope-dev rather than zope3-dev.
         
         - New lines are no longer stripped in XML and HTML code contained in a
-        textarea; fix requires ClientForm >= 0.2.10 (LP #268139).
+          textarea; fix requires ClientForm >= 0.2.10 (LP #268139).
         
         - Added ``cookies`` attribute to browser for easy manipulation of browser
-        cookies.  See brief example in main documentation, plus new ``cookies.txt``
-        documentation.
+          cookies.  See brief example in main documentation, plus new ``cookies.txt``
+          documentation.
         
         
         3.5.1 (2008-10-10)
         ------------------
         
         - Provide a work around for a mechanize/urllib2 bug on Python 2.6
-        missing 'timeout' attribute on 'Request' base class.
+          missing 'timeout' attribute on 'Request' base class.
         
         - Provide a work around for a mechanize/urllib2 bug in creating request
-        objects that won't handle fragment URLs correctly.
+          objects that won't handle fragment URLs correctly.
         
         
         3.5.0 (2008-03-30)
         ------------------
         
         - Added a zope.testbrowser.testing.Browser.post method that allows
-        tests to supply a body and a content type.  This is handy for
-        testing Ajax requests with non-form input (e.g. JSON).
+          tests to supply a body and a content type.  This is handy for
+          testing Ajax requests with non-form input (e.g. JSON).
         
         - Remove vendor import of mechanize.
         
         - Fix bug that caused HTTP exception tracebacks to differ between version 3.4.0
-        and 3.4.1.
+          and 3.4.1.
         
         - Workaround for bug in Python Cookie.SimpleCookie when handling unicode
-        strings.
+          strings.
         
         - Fix bug introduced in 3.4.1 that created incompatible tracebacks in doctests.
-        This necessitated adding a patched mechanize to the source tree; patches have
-        been sent to the mechanize project.
+          This necessitated adding a patched mechanize to the source tree; patches have
+          been sent to the mechanize project.
         
         - Fix https://bugs.launchpad.net/bugs/149517 by adding zope.interface and
-        zope.schema as real dependencies
+          zope.schema as real dependencies
         
         - Fix browser.getLink documentation that was not updated since the last API
-        modification.
+          modification.
         
         - Move tests for fixed bugs to a separate file.
         
         - Removed non-functional and undocumented code intended to help test servers
-        using virtual hosting.
+          using virtual hosting.
         
         
         3.4.2 (2007-10-31)
@@ -1697,7 +1766,7 @@ Description: .. contents::
         ------------------
         
         * Updated to mechanize 0.1.7b and ClientForm 0.2.7.  These are now
-        pulled in via egg dependencies.
+          pulled in via egg dependencies.
         
         * ``zope.testbrowser`` now works on Python 2.5.
         
@@ -1706,10 +1775,10 @@ Description: .. contents::
         ------------------
         
         * Added the ability to suppress raising exceptions on HTTP errors
-        (``raiseHttpErrors`` attribute).
+          (``raiseHttpErrors`` attribute).
         
         * Made the tests more resilient to HTTP header formatting changes with
-        the REnormalizer.
+          the REnormalizer.
         
         
         3.4.0a1 (2007-04-22)
@@ -1722,6 +1791,8 @@ Platform: UNKNOWN
 Classifier: Environment :: Web Environment
 Classifier: Intended Audience :: Developers
 Classifier: License :: OSI Approved :: Zope Public License
-Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2.5
+Classifier: Programming Language :: Python :: 2.6
+Classifier: Programming Language :: Python :: 2.7
 Classifier: Topic :: Software Development :: Testing
 Classifier: Topic :: Internet :: WWW/HTTP
diff -pruN 4.0.2-1/README.rst 4.0.4-0ubuntu1/README.rst
--- 4.0.2-1/README.rst	1970-01-01 00:00:00.000000000 +0000
+++ 4.0.4-0ubuntu1/README.rst	2013-10-11 07:12:28.000000000 +0000
@@ -0,0 +1,9 @@
+|buildstatus|_
+
+``zope.testbrowser`` provides an easy-to-use programmable web browser
+with special focus on testing.  It is used in Zope, but it's not Zope
+specific at all.  For instance, it can be used to test or otherwise
+interact with any web site.
+
+.. |buildstatus| image:: https://api.travis-ci.org/zopefoundation/zope.testbrowser.png?branch=master
+.. _buildstatus: https://travis-ci.org/zopefoundation/zope.testbrowser
diff -pruN 4.0.2-1/README.txt 4.0.4-0ubuntu1/README.txt
--- 4.0.2-1/README.txt	2011-05-25 07:17:20.000000000 +0000
+++ 4.0.4-0ubuntu1/README.txt	1970-01-01 00:00:00.000000000 +0000
@@ -1,5 +0,0 @@
-``zope.testbrowser`` provides an easy-to-use programmable web browser
-with special focus on testing.  It is used in Zope, but it's not Zope
-specific at all.  For instance, it can be used to test or otherwise
-interact with any web site.
-
diff -pruN 4.0.2-1/setup.py 4.0.4-0ubuntu1/setup.py
--- 4.0.2-1/setup.py	2011-05-25 07:17:20.000000000 +0000
+++ 4.0.4-0ubuntu1/setup.py	2013-10-11 07:12:28.000000000 +0000
@@ -16,61 +16,68 @@
 import os
 from setuptools import setup, find_packages
 
+
 long_description = (
     '.. contents::\n\n'
-    + open('README.txt').read()
+    + open('README.rst').read()
     + '\n\n'
     + open(os.path.join('src', 'zope', 'testbrowser', 'README.txt')).read()
     + '\n\n'
-    + open('CHANGES.txt').read()
-    )
+    + open('CHANGES.rst').read()
+)
+
+
+tests_require = [
+    'zope.testing',
+    'WebTest',
+]
 
-tests_require = ['zope.testing',
-                 'WebTest']
 
 setup(
-    name = 'zope.testbrowser',
-    version='4.0.2',
-    url = 'http://pypi.python.org/pypi/zope.testbrowser',
-    license = 'ZPL 2.1',
-    description = 'Programmable browser for functional black-box tests',
-    author = 'Zope Corporation and Contributors',
-    author_email = 'zope-dev@zope.org',
-    long_description = long_description,
+    name='zope.testbrowser',
+    version='4.0.4',
+    url='http://pypi.python.org/pypi/zope.testbrowser',
+    license='ZPL 2.1',
+    description='Programmable browser for functional black-box tests',
+    author='Zope Corporation and Contributors',
+    author_email='zope-dev@zope.org',
+    long_description=long_description,
     classifiers=[
         'Environment :: Web Environment',
         'Intended Audience :: Developers',
         'License :: OSI Approved :: Zope Public License',
-        'Programming Language :: Python',
+        'Programming Language :: Python :: 2.5',
+        'Programming Language :: Python :: 2.6',
+        'Programming Language :: Python :: 2.7',
         'Topic :: Software Development :: Testing',
         'Topic :: Internet :: WWW/HTTP',
-        ],
+    ],
 
-    packages = find_packages('src'),
-    package_dir = {'': 'src'},
-    namespace_packages = ['zope',],
-    test_suite = 'zope.testbrowser.tests',
-    tests_require = tests_require,
-    install_requires = [
+    packages=find_packages('src'),
+    package_dir={'': 'src'},
+    namespace_packages=['zope'],
+    test_suite='zope.testbrowser.tests',
+    tests_require=tests_require,
+    install_requires=[
         # mechanize 0.2.0 folds in ClientForm, makes incompatible API changes
         'mechanize>=0.2.0',
         'setuptools',
         'zope.interface',
         'zope.schema',
-        'pytz',
-        ],
-    extras_require = {
+        'pytz > dev',
+    ],
+    extras_require={
         'test': tests_require,
         'test_bbb': [
             'zope.testbrowser [test,zope-functional-testing]',
-            ],
+        ],
         'zope-functional-testing': [
             'zope.app.testing >= 3.9.0dev',
-            ],
+        ],
         'wsgi': [
             'WebTest',
-            ]
-        },
-    include_package_data = True,
-    zip_safe = False,
-    )
+        ]
+    },
+    include_package_data=True,
+    zip_safe=False,
+)
diff -pruN 4.0.2-1/src/zope/testbrowser/browser.py 4.0.4-0ubuntu1/src/zope/testbrowser/browser.py
--- 4.0.2-1/src/zope/testbrowser/browser.py	2013-10-30 19:07:33.000000000 +0000
+++ 4.0.4-0ubuntu1/src/zope/testbrowser/browser.py	2013-10-30 19:07:33.000000000 +0000
@@ -33,7 +33,7 @@ RegexType = type(re.compile(''))
 _compress_re = re.compile(r"\s+")
 compressText = lambda text: _compress_re.sub(' ', text.strip())
 
-def disambiguate(intermediate, msg, index, choice_repr=None):
+def disambiguate(intermediate, msg, index, choice_repr=None, available=None):
     if intermediate:
         if index is None:
             if len(intermediate) > 1:
@@ -47,8 +47,19 @@ def disambiguate(intermediate, msg, inde
         else:
             try:
                 return intermediate[index]
-            except KeyError:
-                msg = '%s index %d' % (msg, index)
+            except IndexError:
+                msg = '%s\nIndex %d out of range, available choices are 0...%d' % (
+                            msg, index, len(intermediate) - 1)
+                if choice_repr:
+                    msg += ''.join(['\n  %d: %s' % (n, choice_repr(choice))
+                                    for n, choice in enumerate(intermediate)])
+    else:
+        if available:
+            msg += '\navailable items:' + ''.join([
+                '\n  %s' % choice_repr(choice)
+                for choice in available])
+        elif available is not None: # empty list
+            msg += '\n(there are no form items in the HTML)'
     raise LookupError(msg)
 
 def control_form_tuple_repr((ctrl, form)):
@@ -344,29 +355,27 @@ class Browser(SetattrErrorsMixin):
         """Select a link and follow it."""
         self.getLink(*args, **kw).click()
 
-    def _findByLabel(self, label, forms, include_subcontrols=False):
-        # forms are iterable of mech_forms
-        matches = re.compile(r'(^|\b|\W)%s(\b|\W|$)'
-                             % re.escape(compressText(label))).search
-        found = []
+    def _findAllControls(self, forms, include_subcontrols=False):
         for f in forms:
             for control in f.controls:
                 phantom = control.type in ('radio', 'checkbox')
                 if not phantom:
-                    for l in control.get_labels():
-                        if matches(l.text):
-                            found.append((control, f))
-                            break
+                    yield (control, f)
                 if include_subcontrols and (
                     phantom or control.type=='select'):
-
                     for i in control.items:
-                        for l in i.get_labels():
-                            if matches(l.text):
-                                found.append((i, f))
-                                found_one = True
-                                break
+                        yield (i, f)
 
+    def _findByLabel(self, label, forms, include_subcontrols=False):
+        # forms are iterable of mech_forms
+        matches = re.compile(r'(^|\b|\W)%s(\b|\W|$)'
+                             % re.escape(compressText(label))).search
+        found = []
+        for control, form in self._findAllControls(forms, include_subcontrols):
+            for l in control.get_labels():
+                if matches(l.text):
+                    found.append((control, form))
+                    break
         return found
 
     def _findByName(self, name, forms):
@@ -379,22 +388,29 @@ class Browser(SetattrErrorsMixin):
 
     def getControl(self, label=None, name=None, index=None):
         """See zope.testbrowser.interfaces.IBrowser"""
-        intermediate, msg = self._get_all_controls(
+        intermediate, msg, available = self._get_all_controls(
             label, name, self.mech_browser.forms(), include_subcontrols=True)
         control, form = disambiguate(intermediate, msg, index,
-                                     control_form_tuple_repr)
+                                     control_form_tuple_repr,
+                                     available)
         return controlFactory(control, form, self)
 
     def _get_all_controls(self, label, name, forms, include_subcontrols=False):
         onlyOne([label, name], '"label" and "name"')
 
+        forms = list(forms) # might be an iterator, and we need to iterate twice
+
+        available = None
         if label is not None:
             res = self._findByLabel(label, forms, include_subcontrols)
             msg = 'label %r' % label
         elif name is not None:
+            include_subcontrols = False
             res = self._findByName(name, forms)
             msg = 'name %r' % name
-        return res, msg
+        if not res:
+            available = list(self._findAllControls(forms, include_subcontrols))
+        return res, msg, available
 
     def getForm(self, id=None, name=None, action=None, index=None):
         zeroOrOne([id, name, action], '"id", "name", and "action"')
@@ -763,13 +779,14 @@ class Form(SetattrErrorsMixin):
         form = self.mech_form
         try:
             if label is not None or name is not None:
-                intermediate, msg = self.browser._get_all_controls(
+                intermediate, msg, available = self.browser._get_all_controls(
                     label, name, (form,))
                 intermediate = [
                     (control, form) for (control, form) in intermediate if
                     control.type in ('submit', 'submitbutton', 'image')]
                 control, form = disambiguate(intermediate, msg, index,
-                                             control_form_tuple_repr)
+                                             control_form_tuple_repr,
+                                             available)
                 self.browser._clickSubmit(form, control, coord)
             else: # JavaScript sort of submit
                 if index is not None or coord != (1,1):
@@ -780,6 +797,10 @@ class Form(SetattrErrorsMixin):
                 try:
                     try:
                         self.browser.mech_browser.open(request)
+                    except mechanize.HTTPError, e:
+                        if self.browser.raiseHttpErrors:
+                            fix_exception_name(e)
+                            raise
                     except Exception, e:
                         fix_exception_name(e)
                         raise
@@ -792,8 +813,9 @@ class Form(SetattrErrorsMixin):
         """See zope.testbrowser.interfaces.IBrowser"""
         if self._browser_counter != self.browser._counter:
             raise zope.testbrowser.interfaces.ExpiredError
-        intermediate, msg = self.browser._get_all_controls(
+        intermediate, msg, available = self.browser._get_all_controls(
             label, name, (self.mech_form,), include_subcontrols=True)
         control, form = disambiguate(intermediate, msg, index,
-                                     control_form_tuple_repr)
+                                     control_form_tuple_repr,
+                                     available)
         return controlFactory(control, form, self.browser)
diff -pruN 4.0.2-1/src/zope/testbrowser/ftests/forms.html 4.0.4-0ubuntu1/src/zope/testbrowser/ftests/forms.html
--- 4.0.2-1/src/zope/testbrowser/ftests/forms.html	2011-05-25 07:17:20.000000000 +0000
+++ 4.0.4-0ubuntu1/src/zope/testbrowser/ftests/forms.html	2013-10-11 07:12:28.000000000 +0000
@@ -32,5 +32,10 @@
       <input type="hidden" name="hidden-4" value="marker" />
     </form>
 
+    <form id="5" name="redirect" action="/redirect.html">
+      <input type="text" name="to" value="/set_status.html" />
+      <input type="submit" name="submit-5" value="Submit" />
+    </form>
+
   </body>
 </html>
diff -pruN 4.0.2-1/src/zope/testbrowser/README.txt 4.0.4-0ubuntu1/src/zope/testbrowser/README.txt
--- 4.0.2-1/src/zope/testbrowser/README.txt	2011-05-25 07:17:20.000000000 +0000
+++ 4.0.4-0ubuntu1/src/zope/testbrowser/README.txt	2013-10-11 07:12:28.000000000 +0000
@@ -28,7 +28,7 @@ There is also a special version of the `
 applications. It can be imported from ``zope.testbrowser.wsgi``:
 
     >>> from zope.testbrowser.wsgi import Browser
-    >>> from wsgiref.simple_server import demo_app
+    >>> from zope.testbrowser.tests.test_wsgi import demo_app
     >>> browser = Browser('http://localhost/', wsgi_app=demo_app)
     >>> print browser.contents
     Hello world!
@@ -481,6 +481,11 @@ If you request a control that doesn't ex
     Traceback (most recent call last):
     ...
     LookupError: label 'Does Not Exist'
+    available items:
+      <TextControl(text-value=Some Text)>
+      <PasswordControl(password-value=Password)>
+      <HiddenControl(hidden-value=Hidden) (readonly)>
+      ...
 
 If you request a control with an ambiguous lookup, the code raises an
 AmbiguityError.
@@ -516,6 +521,13 @@ below.
     <ListControl name='ambiguous-subcontrol' type='select'>
     >>> browser.getControl('Sub-control Ambiguity', index=1).optionValue
     'ambiguous'
+    >>> browser.getControl('Sub-control Ambiguity', index=2)
+    Traceback (most recent call last):
+    ...
+    LookupError: label 'Sub-control Ambiguity'
+    Index 2 out of range, available choices are 0...1
+      0: <SelectControl(ambiguous-subcontrol=[*, ambiguous])>
+      1: <Item name='ambiguous' id=None contents='Sub-control Ambiguity Exemplified' value='ambiguous' label='Sub-control Ambiguity Exemplified'>
 
 Label searches are against stripped, whitespace-normalized, no-tag versions of
 the text. Text applied to searches is also stripped and whitespace normalized.
@@ -529,6 +541,7 @@ text in a label.  Thus, for instance, a
     Traceback (most recent call last):
     ...
     LookupError: label 'label needs whitespace normalization'
+    ...
     >>> browser.getControl(' Label  Needs Whitespace    ')
     <Control name='label-needs-normalization' type='text'>
     >>> browser.getControl('Whitespace')
@@ -537,6 +550,7 @@ text in a label.  Thus, for instance, a
     Traceback (most recent call last):
     ...
     LookupError: label 'hitespace'
+    ...
     >>> browser.getControl('[non word characters should not confuse]')
     <Control name='non-word-characters' type='text'>
 
@@ -572,6 +586,9 @@ Get also accepts one other search argume
     Traceback (most recent call last):
     ...
     LookupError: name 'does-not-exist'
+    available items:
+      <TextControl(text-value=Some Text)>
+      ...
     >>> browser.getControl(name='ambiguous-control-name', index=1).value
     'Second'
 
@@ -1155,6 +1172,19 @@ But when sending an image, you can also
     </html>
 
 
+Pages Without Controls
+~~~~~~~~~~~~~~~~~~~~~~
+
+What would happen if we tried to look up a control on a page that has none?
+
+    >>> browser.open('http://localhost/@@/testbrowser/simple.html')
+    >>> browser.getControl('anything')
+    Traceback (most recent call last):
+    ...
+    LookupError: label 'anything'
+    (there are no form items in the HTML)
+
+
 Forms
 -----
 
diff -pruN 4.0.2-1/src/zope/testbrowser/tests/helper.py 4.0.4-0ubuntu1/src/zope/testbrowser/tests/helper.py
--- 4.0.2-1/src/zope/testbrowser/tests/helper.py	2011-05-25 07:17:19.000000000 +0000
+++ 4.0.4-0ubuntu1/src/zope/testbrowser/tests/helper.py	2013-10-11 07:12:28.000000000 +0000
@@ -30,9 +30,7 @@ checker = zope.testing.renormalizing.REN
     (re.compile(r'Content-[Ll]ength:.*'), 'Content-Length: 123'),
     (re.compile(r'Status: 200.*'), 'Status: 200 OK'),
     (win32CRLFtransformer(), None),
-    (re.compile(r'User-Agent: Python-urllib/2.5'),
-     'User-agent: Python-urllib/2.4'),
-    (re.compile(r'User-Agent: Python-urllib/2.6'),
+    (re.compile(r'User-Agent: Python-urllib/2.[567]'),
      'User-agent: Python-urllib/2.4'),
     (re.compile(r'Host: localhost'), 'Connection: close'),
     (re.compile(r'Content-Type: '), 'Content-type: '),
diff -pruN 4.0.2-1/src/zope/testbrowser/tests/test_doctests.py 4.0.4-0ubuntu1/src/zope/testbrowser/tests/test_doctests.py
--- 4.0.2-1/src/zope/testbrowser/tests/test_doctests.py	2011-05-25 07:17:19.000000000 +0000
+++ 4.0.4-0ubuntu1/src/zope/testbrowser/tests/test_doctests.py	2013-10-11 07:12:28.000000000 +0000
@@ -17,6 +17,8 @@ import unittest
 
 import zope.testbrowser.ftests.wsgitestapp
 import zope.testbrowser.wsgi
+import zope.testbrowser.tests.helper
+
 
 def test_suite():
     flags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
diff -pruN 4.0.2-1/src/zope/testbrowser/tests/test_wsgi.py 4.0.4-0ubuntu1/src/zope/testbrowser/tests/test_wsgi.py
--- 4.0.2-1/src/zope/testbrowser/tests/test_wsgi.py	2011-05-25 07:17:19.000000000 +0000
+++ 4.0.4-0ubuntu1/src/zope/testbrowser/tests/test_wsgi.py	2013-10-11 07:12:28.000000000 +0000
@@ -14,12 +14,27 @@
 
 import unittest
 from urllib import urlencode
-from wsgiref.simple_server import demo_app
 
 import zope.testbrowser.wsgi
 from zope.testbrowser.ftests.wsgitestapp import WSGITestApplication
 
 
+def demo_app(environ, start_response):
+    # Based on wsgiref.simple_server.demo_app, except it doesn't
+    # emit unicode in the response stream even if WSGI environ contains
+    # unicode keys.  Fixes GH#10.
+    from StringIO import StringIO
+    stdout = StringIO()
+    print >> stdout, "Hello world!"
+    print >> stdout
+    h = environ.items()
+    h.sort()
+    for k, v in h:
+        print >> stdout, str(k), '=', repr(v)
+    start_response("200 OK", [('Content-Type', 'text/plain')])
+    return [stdout.getvalue()]
+
+
 class SimpleLayer(zope.testbrowser.wsgi.Layer):
 
     def make_wsgi_app(self):
@@ -56,6 +71,41 @@ class TestBrowser(unittest.TestCase):
                           'http://localhost/redirect.html?%s'
                           % urlencode(dict(to='http://www.google.com/', type='301')))
 
+        # we're also automatically redirected on submit
+        browser.open('http://localhost/@@/testbrowser/forms.html')
+        self.assertEquals(browser.headers.get('status'), '200 OK')
+        form = browser.getForm(name='redirect')
+        form.submit()
+        self.assertEquals(browser.headers.get('status'), '200 OK')
+        self.assertEquals(browser.url, 'http://localhost/set_status.html')
+
+    def test_no_redirect(self):
+        app = WSGITestApplication()
+        browser = zope.testbrowser.wsgi.Browser(wsgi_app=app)
+
+        # tell testbrowser to not handle redirects automatically
+        browser.mech_browser.set_handle_redirect(False)
+
+        # and tell zope.testbrowser to not raise HTTP errors (everything but
+        # 20x responses is considered an error)
+        browser.raiseHttpErrors = False
+
+        url = ('http://localhost/redirect.html?%s'
+               % urlencode(dict(to='/set_status.html')))
+        browser.open(url)
+
+        # see - we're not redirected
+        self.assertEquals(browser.url, url)
+        self.assertEquals(browser.headers.get('status'), '302 Found')
+
+        # the same should happen on submit (issue #4)
+        browser.open('http://localhost/@@/testbrowser/forms.html')
+        self.assertEquals(browser.headers.get('status'), '200 OK')
+        form = browser.getForm(name='redirect')
+        form.submit()
+        self.assertEquals(browser.headers.get('status'), '302 Found')
+        self.assertEquals(browser.url, url)
+
     def test_allowed_domains(self):
         browser = zope.testbrowser.wsgi.Browser(wsgi_app=demo_app)
         # external domains are not allowed
@@ -77,6 +127,23 @@ class TestBrowser(unittest.TestCase):
         browser.open('http://bar.example.net')
         self.assertTrue(browser.contents.startswith('Hello world!\n'))
 
+    def test_handle_errors(self):
+        # http://wsgi.org/wsgi/Specifications/throw_errors
+        app = WSGITestApplication()
+        browser = zope.testbrowser.wsgi.Browser(wsgi_app=app)
+        browser.open('http://localhost/echo_one.html?var=x-wsgiorg.throw_errors')
+        self.assertEquals(browser.contents, 'None')
+        browser.open('http://localhost/echo_one.html?var=paste.throw_errors')
+        self.assertEquals(browser.contents, 'None')
+        browser.open('http://localhost/echo_one.html?var=wsgi.handleErrors')
+        self.assertEquals(browser.contents, 'None')
+        browser.handleErrors = False
+        browser.open('http://localhost/echo_one.html?var=x-wsgiorg.throw_errors')
+        self.assertEquals(browser.contents, 'True')
+        browser.open('http://localhost/echo_one.html?var=paste.throw_errors')
+        self.assertEquals(browser.contents, 'True')
+        browser.open('http://localhost/echo_one.html?var=wsgi.handleErrors')
+        self.assertEquals(browser.contents, 'False')
 
 class TestWSGILayer(unittest.TestCase):
 
diff -pruN 4.0.2-1/src/zope/testbrowser/wsgi.py 4.0.4-0ubuntu1/src/zope/testbrowser/wsgi.py
--- 4.0.2-1/src/zope/testbrowser/wsgi.py	2011-05-25 07:17:20.000000000 +0000
+++ 4.0.4-0ubuntu1/src/zope/testbrowser/wsgi.py	2013-10-11 07:12:28.000000000 +0000
@@ -75,11 +75,16 @@ class WSGIConnection(object):
 
         # Translate string to boolean.
         handle_errors = {'False': False}.get(handle_errors_header, True)
-        extra_environ = {}
+
+        # WebTest always sets 'paste.throw_errors' to True. Setting it to None
+        # here overrides that, but is UGLY. sigh.
+        extra_environ = {'paste.throw_errors': None}
+
         if not handle_errors:
             # There doesn't seem to be a "Right Way" to do this
             extra_environ['wsgi.handleErrors'] = False # zope.app.wsgi does this
             extra_environ['paste.throw_errors'] = True # the paste way of doing this
+            extra_environ['x-wsgiorg.throw_errors'] = True # http://wsgi.org/wsgi/Specifications/throw_errors
 
         scheme_key = 'X-Zope-Scheme'
         extra_environ['wsgi.url_scheme'] = headers.get(scheme_key, 'http')
diff -pruN 4.0.2-1/src/zope.testbrowser.egg-info/PKG-INFO 4.0.4-0ubuntu1/src/zope.testbrowser.egg-info/PKG-INFO
--- 4.0.2-1/src/zope.testbrowser.egg-info/PKG-INFO	2011-05-25 07:17:51.000000000 +0000
+++ 4.0.4-0ubuntu1/src/zope.testbrowser.egg-info/PKG-INFO	2013-10-11 07:12:50.000000000 +0000
@@ -1,6 +1,6 @@
-Metadata-Version: 1.0
+Metadata-Version: 1.1
 Name: zope.testbrowser
-Version: 4.0.2
+Version: 4.0.4
 Summary: Programmable browser for functional black-box tests
 Home-page: http://pypi.python.org/pypi/zope.testbrowser
 Author: Zope Corporation and Contributors
@@ -8,11 +8,15 @@ Author-email: zope-dev@zope.org
 License: ZPL 2.1
 Description: .. contents::
         
+        |buildstatus|_
+        
         ``zope.testbrowser`` provides an easy-to-use programmable web browser
         with special focus on testing.  It is used in Zope, but it's not Zope
         specific at all.  For instance, it can be used to test or otherwise
         interact with any web site.
         
+        .. |buildstatus| image:: https://api.travis-ci.org/zopefoundation/zope.testbrowser.png?branch=master
+        .. _buildstatus: https://travis-ci.org/zopefoundation/zope.testbrowser
         
         
         ======================
@@ -28,8 +32,8 @@ Description: .. contents::
         The ``zope.testbrowser.browser`` module exposes a ``Browser`` class that
         simulates a web browser similar to Mozilla Firefox or IE.
         
-        >>> from zope.testbrowser.browser import Browser
-        >>> browser = Browser()
+            >>> from zope.testbrowser.browser import Browser
+            >>> browser = Browser()
         
         This version of the browser object can be used to access any web site just as
         you would do using a normal web browser.
@@ -44,32 +48,32 @@ Description: .. contents::
         `WebTest`_ and can be used to do functional testing of WSGI
         applications. It can be imported from ``zope.testbrowser.wsgi``:
         
-        >>> from zope.testbrowser.wsgi import Browser
-        >>> from wsgiref.simple_server import demo_app
-        >>> browser = Browser('http://localhost/', wsgi_app=demo_app)
-        >>> print browser.contents
-        Hello world!
-        ...
+            >>> from zope.testbrowser.wsgi import Browser
+            >>> from zope.testbrowser.tests.test_wsgi import demo_app
+            >>> browser = Browser('http://localhost/', wsgi_app=demo_app)
+            >>> print browser.contents
+            Hello world!
+            ...
         
         .. _`WebTest`: http://pypi.python.org/pypi/WebTest
         
         To use this browser you have to:
         
-        * use the `wsgi` extra of the ``zope.testbrowser`` egg,
+          * use the `wsgi` extra of the ``zope.testbrowser`` egg,
         
         You can also use it with zope layers by:
         
-        * write a subclass of ``zope.testbrowser.wsgi.Layer`` and override the
-        ``make_wsgi_app`` method,
+          * write a subclass of ``zope.testbrowser.wsgi.Layer`` and override the
+            ``make_wsgi_app`` method,
         
-        * use an instance of the class as the test layer of your test.
+          * use an instance of the class as the test layer of your test.
         
         Example:
         
-        >>> import zope.testbrowser.wsgi
-        >>> class SimpleLayer(zope.testbrowser.wsgi.Layer):
-        ...     def make_wsgi_app(self):
-        ...         return simple_app
+            >>> import zope.testbrowser.wsgi
+            >>> class SimpleLayer(zope.testbrowser.wsgi.Layer):
+            ...     def make_wsgi_app(self):
+            ...         return simple_app
         
         Where ``simple_app`` is the callable of your WSGI application.
         
@@ -84,10 +88,10 @@ Description: .. contents::
         
         Example when using the layer:
         
-        >>> import zope.testbrowser.wsgi
-        >>> class ZopeSimpleLayer(zope.testbrowser.wsgi.Layer):
-        ...     def make_wsgi_app(self):
-        ...         return zope.testbrowser.wsgi.AuthorizationMiddleware(simple_app)
+            >>> import zope.testbrowser.wsgi
+            >>> class ZopeSimpleLayer(zope.testbrowser.wsgi.Layer):
+            ...     def make_wsgi_app(self):
+            ...         return zope.testbrowser.wsgi.AuthorizationMiddleware(simple_app)
         
         There is also a BrowserLayer in `zope.app.wsgi.testlayer`_ which does this
         for you and includes a ``TransactionMiddleware``, too, which could be handy
@@ -101,28 +105,28 @@ Description: .. contents::
         
         We will test this browser against a WSGI test application:
         
-        >>> from zope.testbrowser.ftests.wsgitestapp import WSGITestApplication
-        >>> wsgi_app = WSGITestApplication()
+            >>> from zope.testbrowser.ftests.wsgitestapp import WSGITestApplication
+            >>> wsgi_app = WSGITestApplication()
         
         An initial page to load can be passed to the ``Browser`` constructor:
         
-        >>> browser = Browser('http://localhost/@@/testbrowser/simple.html', wsgi_app=wsgi_app)
-        >>> browser.url
-        'http://localhost/@@/testbrowser/simple.html'
+            >>> browser = Browser('http://localhost/@@/testbrowser/simple.html', wsgi_app=wsgi_app)
+            >>> browser.url
+            'http://localhost/@@/testbrowser/simple.html'
         
         The browser can send arbitrary headers; this is helpful for setting the
         "Authorization" header or a language value, so that your tests format values
         the way you expect in your tests, if you rely on zope.i18n locale-based
         formatting or a similar approach.
         
-        >>> browser.addHeader('Authorization', 'Basic mgr:mgrpw')
-        >>> browser.addHeader('Accept-Language', 'en-US')
+            >>> browser.addHeader('Authorization', 'Basic mgr:mgrpw')
+            >>> browser.addHeader('Accept-Language', 'en-US')
         
         An existing browser instance can also `open` web pages:
         
-        >>> browser.open('http://localhost/@@/testbrowser/simple.html')
-        >>> browser.url
-        'http://localhost/@@/testbrowser/simple.html'
+            >>> browser.open('http://localhost/@@/testbrowser/simple.html')
+            >>> browser.url
+            'http://localhost/@@/testbrowser/simple.html'
         
         Once you have opened a web page initially, best practice for writing
         testbrowser doctests suggests using 'click' to navigate further (as discussed
@@ -131,10 +135,10 @@ Description: .. contents::
         The test browser complies with the IBrowser interface; see
         ``zope.testbrowser.interfaces`` for full details on the interface.
         
-        >>> from zope.testbrowser import interfaces
-        >>> from zope.interface.verify import verifyObject
-        >>> verifyObject(interfaces.IBrowser, browser)
-        True
+            >>> from zope.testbrowser import interfaces
+            >>> from zope.interface.verify import verifyObject
+            >>> verifyObject(interfaces.IBrowser, browser)
+            True
         
         
         Page Contents
@@ -142,25 +146,25 @@ Description: .. contents::
         
         The contents of the current page are available:
         
-        >>> print browser.contents
-        <html>
-        <head>
-        <title>Simple Page</title>
-        </head>
-        <body>
-        <h1>Simple Page</h1>
-        </body>
-        </html>
+            >>> print browser.contents
+            <html>
+              <head>
+                <title>Simple Page</title>
+              </head>
+              <body>
+                <h1>Simple Page</h1>
+              </body>
+            </html>
         
         Making assertions about page contents is easy.
         
-        >>> '<h1>Simple Page</h1>' in browser.contents
-        True
+            >>> '<h1>Simple Page</h1>' in browser.contents
+            True
         
         Utilizing the doctest facilities, it also possible to do:
         
-        >>> browser.contents
-        '...<h1>Simple Page</h1>...'
+            >>> browser.contents
+            '...<h1>Simple Page</h1>...'
         
         Note: Unfortunately, ellipsis (...) cannot be used at the beginning of the
         output (this is a limitation of doctest).
@@ -172,15 +176,15 @@ Description: .. contents::
         
         Not all URLs return HTML.  Of course our simple page does:
         
-        >>> browser.open('http://localhost/@@/testbrowser/simple.html')
-        >>> browser.isHtml
-        True
+            >>> browser.open('http://localhost/@@/testbrowser/simple.html')
+            >>> browser.isHtml
+            True
         
         But if we load an image (or other binary file), we do not get HTML:
         
-        >>> browser.open('http://localhost/@@/testbrowser/zope3logo.gif')
-        >>> browser.isHtml
-        False
+            >>> browser.open('http://localhost/@@/testbrowser/zope3logo.gif')
+            >>> browser.isHtml
+            False
         
         
         
@@ -189,23 +193,23 @@ Description: .. contents::
         
         Another useful helper property is the title:
         
-        >>> browser.open('http://localhost/@@/testbrowser/simple.html')
-        >>> browser.title
-        'Simple Page'
+            >>> browser.open('http://localhost/@@/testbrowser/simple.html')
+            >>> browser.title
+            'Simple Page'
         
         If a page does not provide a title, it is simply ``None``:
         
-        >>> browser.open('http://localhost/@@/testbrowser/notitle.html')
-        >>> browser.title
+            >>> browser.open('http://localhost/@@/testbrowser/notitle.html')
+            >>> browser.title
         
         However, if the output is not HTML, then an error will occur trying to access
         the title:
         
-        >>> browser.open('http://localhost/@@/testbrowser/zope3logo.gif')
-        >>> browser.title
-        Traceback (most recent call last):
-        ...
-        BrowserStateError: not viewing HTML
+            >>> browser.open('http://localhost/@@/testbrowser/zope3logo.gif')
+            >>> browser.title
+            Traceback (most recent call last):
+            ...
+            BrowserStateError: not viewing HTML
         
         
         Headers
@@ -216,21 +220,21 @@ Description: .. contents::
         ``httplib.HTTPMessage`` instance (httplib is a part of Python's standard
         library):
         
-        >>> browser.open('http://localhost/@@/testbrowser/simple.html')
-        >>> browser.headers
-        <httplib.HTTPMessage instance...>
+            >>> browser.open('http://localhost/@@/testbrowser/simple.html')
+            >>> browser.headers
+            <httplib.HTTPMessage instance...>
         
         The headers can be accessed as a string:
         
-        >>> print browser.headers
-        Status: 200 OK
-        Content-Length: 123
-        Content-Type: text/html;charset=utf-8
+            >>> print browser.headers
+            Status: 200 OK
+            Content-Length: 123
+            Content-Type: text/html;charset=utf-8
         
         Or as a mapping:
         
-        >>> browser.headers['content-type']
-        'text/html;charset=utf-8'
+            >>> browser.headers['content-type']
+            'text/html;charset=utf-8'
         
         
         Cookies
@@ -240,47 +244,47 @@ Description: .. contents::
         above.  Here, we use a view that will make the server set cookies with the
         values we provide.
         
-        >>> browser.open('http://localhost/set_cookie.html?name=foo&value=bar')
-        >>> browser.headers['set-cookie'].replace(';', '')
-        'foo=bar'
+            >>> browser.open('http://localhost/set_cookie.html?name=foo&value=bar')
+            >>> browser.headers['set-cookie'].replace(';', '')
+            'foo=bar'
         
         It is also available in the browser's ``cookies`` attribute.  This is
         an extended mapping interface that allows getting, setting, and deleting the
         cookies that the browser is remembering *for the current url*.  Here are
         a few examples.
         
-        >>> browser.cookies['foo']
-        'bar'
-        >>> browser.cookies.keys()
-        ['foo']
-        >>> browser.cookies.values()
-        ['bar']
-        >>> browser.cookies.items()
-        [('foo', 'bar')]
-        >>> 'foo' in browser.cookies
-        True
-        >>> 'bar' in browser.cookies
-        False
-        >>> len(browser.cookies)
-        1
-        >>> print(dict(browser.cookies))
-        {'foo': 'bar'}
-        >>> browser.cookies['sha'] = 'zam'
-        >>> len(browser.cookies)
-        2
-        >>> sorted(browser.cookies.items())
-        [('foo', 'bar'), ('sha', 'zam')]
-        >>> browser.open('http://localhost/get_cookie.html')
-        >>> print browser.headers.get('set-cookie')
-        None
-        >>> print browser.contents # server got the cookie change
-        foo: bar
-        sha: zam
-        >>> sorted(browser.cookies.items())
-        [('foo', 'bar'), ('sha', 'zam')]
-        >>> browser.cookies.clearAll()
-        >>> len(browser.cookies)
-        0
+            >>> browser.cookies['foo']
+            'bar'
+            >>> browser.cookies.keys()
+            ['foo']
+            >>> browser.cookies.values()
+            ['bar']
+            >>> browser.cookies.items()
+            [('foo', 'bar')]
+            >>> 'foo' in browser.cookies
+            True
+            >>> 'bar' in browser.cookies
+            False
+            >>> len(browser.cookies)
+            1
+            >>> print(dict(browser.cookies))
+            {'foo': 'bar'}
+            >>> browser.cookies['sha'] = 'zam'
+            >>> len(browser.cookies)
+            2
+            >>> sorted(browser.cookies.items())
+            [('foo', 'bar'), ('sha', 'zam')]
+            >>> browser.open('http://localhost/get_cookie.html')
+            >>> print browser.headers.get('set-cookie')
+            None
+            >>> print browser.contents # server got the cookie change
+            foo: bar
+            sha: zam
+            >>> sorted(browser.cookies.items())
+            [('foo', 'bar'), ('sha', 'zam')]
+            >>> browser.cookies.clearAll()
+            >>> len(browser.cookies)
+            0
         
         Many more examples, and a discussion of the additional methods available, can
         be found in cookies.txt.
@@ -297,154 +301,154 @@ Description: .. contents::
         the text you would see in a browser (text and url searches are substring
         searches):
         
-        >>> browser.open('http://localhost/@@/testbrowser/navigate.html')
-        >>> browser.contents
-        '...<a href="navigate.html?message=By+Link+Text">Link Text</a>...'
-        >>> link = browser.getLink('Link Text')
-        >>> link
-        <Link text='Link Text'
-        url='http://localhost/@@/testbrowser/navigate.html?message=By+Link+Text'>
+            >>> browser.open('http://localhost/@@/testbrowser/navigate.html')
+            >>> browser.contents
+            '...<a href="navigate.html?message=By+Link+Text">Link Text</a>...'
+            >>> link = browser.getLink('Link Text')
+            >>> link
+            <Link text='Link Text'
+              url='http://localhost/@@/testbrowser/navigate.html?message=By+Link+Text'>
         
         Link objects comply with the ILink interface.
         
-        >>> verifyObject(interfaces.ILink, link)
-        True
+            >>> verifyObject(interfaces.ILink, link)
+            True
         
         Links expose several attributes for easy access.
         
-        >>> link.text
-        'Link Text'
-        >>> link.tag # links can also be image maps.
-        'a'
-        >>> link.url # it's normalized
-        'http://localhost/@@/testbrowser/navigate.html?message=By+Link+Text'
-        >>> link.attrs
-        {'href': 'navigate.html?message=By+Link+Text'}
+            >>> link.text
+            'Link Text'
+            >>> link.tag # links can also be image maps.
+            'a'
+            >>> link.url # it's normalized
+            'http://localhost/@@/testbrowser/navigate.html?message=By+Link+Text'
+            >>> link.attrs
+            {'href': 'navigate.html?message=By+Link+Text'}
         
         Links can be "clicked" and the browser will navigate to the referenced URL.
         
-        >>> link.click()
-        >>> browser.url
-        'http://localhost/@@/testbrowser/navigate.html?message=By+Link+Text'
-        >>> browser.contents
-        '...Message: <em>By Link Text</em>...'
+            >>> link.click()
+            >>> browser.url
+            'http://localhost/@@/testbrowser/navigate.html?message=By+Link+Text'
+            >>> browser.contents
+            '...Message: <em>By Link Text</em>...'
         
         When finding a link by its text, whitespace is normalized.
         
-        >>> browser.open('http://localhost/@@/testbrowser/navigate.html')
-        >>> browser.contents
-        '...> Link Text \n    with     Whitespace\tNormalization (and parens) </...'
-        >>> link = browser.getLink('Link Text with Whitespace Normalization '
-        ...                        '(and parens)')
-        >>> link
-        <Link text='Link Text with Whitespace Normalization (and parens)'...>
-        >>> link.text
-        'Link Text with Whitespace Normalization (and parens)'
-        >>> link.click()
-        >>> browser.url
-        'http://localhost/@@/testbrowser/navigate.html?message=By+Link+Text+with+Normalization'
-        >>> browser.contents
-        '...Message: <em>By Link Text with Normalization</em>...'
+            >>> browser.open('http://localhost/@@/testbrowser/navigate.html')
+            >>> browser.contents
+            '...> Link Text \n    with     Whitespace\tNormalization (and parens) </...'
+            >>> link = browser.getLink('Link Text with Whitespace Normalization '
+            ...                        '(and parens)')
+            >>> link
+            <Link text='Link Text with Whitespace Normalization (and parens)'...>
+            >>> link.text
+            'Link Text with Whitespace Normalization (and parens)'
+            >>> link.click()
+            >>> browser.url
+            'http://localhost/@@/testbrowser/navigate.html?message=By+Link+Text+with+Normalization'
+            >>> browser.contents
+            '...Message: <em>By Link Text with Normalization</em>...'
         
         When a link text matches more than one link, by default the first one is
         chosen. You can, however, specify the index of the link and thus retrieve a
         later matching link:
         
-        >>> browser.getLink('Link Text')
-        <Link text='Link Text' ...>
+            >>> browser.getLink('Link Text')
+            <Link text='Link Text' ...>
         
-        >>> browser.getLink('Link Text', index=1)
-        <Link text='Link Text with Whitespace Normalization (and parens)' ...>
+            >>> browser.getLink('Link Text', index=1)
+            <Link text='Link Text with Whitespace Normalization (and parens)' ...>
         
         Note that clicking a link object after its browser page has expired will
         generate an error.
         
-        >>> link.click()
-        Traceback (most recent call last):
-        ...
-        ExpiredError
+            >>> link.click()
+            Traceback (most recent call last):
+            ...
+            ExpiredError
         
         You can also find the link by its URL,
         
-        >>> browser.open('http://localhost/@@/testbrowser/navigate.html')
-        >>> browser.contents
-        '...<a href="navigate.html?message=By+URL">Using the URL</a>...'
-        
-        >>> browser.getLink(url='?message=By+URL').click()
-        >>> browser.url
-        'http://localhost/@@/testbrowser/navigate.html?message=By+URL'
-        >>> browser.contents
-        '...Message: <em>By URL</em>...'
+            >>> browser.open('http://localhost/@@/testbrowser/navigate.html')
+            >>> browser.contents
+            '...<a href="navigate.html?message=By+URL">Using the URL</a>...'
+        
+            >>> browser.getLink(url='?message=By+URL').click()
+            >>> browser.url
+            'http://localhost/@@/testbrowser/navigate.html?message=By+URL'
+            >>> browser.contents
+            '...Message: <em>By URL</em>...'
         
         or its id:
         
-        >>> browser.open('http://localhost/@@/testbrowser/navigate.html')
-        >>> browser.contents
-        '...<a href="navigate.html?message=By+Id"
-        id="anchorid">By Anchor Id</a>...'
-        
-        >>> browser.getLink(id='anchorid').click()
-        >>> browser.url
-        'http://localhost/@@/testbrowser/navigate.html?message=By+Id'
-        >>> browser.contents
-        '...Message: <em>By Id</em>...'
+            >>> browser.open('http://localhost/@@/testbrowser/navigate.html')
+            >>> browser.contents
+            '...<a href="navigate.html?message=By+Id"
+            id="anchorid">By Anchor Id</a>...'
+        
+            >>> browser.getLink(id='anchorid').click()
+            >>> browser.url
+            'http://localhost/@@/testbrowser/navigate.html?message=By+Id'
+            >>> browser.contents
+            '...Message: <em>By Id</em>...'
         
         You thought we were done here? Not so quickly.  The `getLink` method also
         supports image maps, though not by specifying the coordinates, but using the
         area's id:
         
-        >>> browser.open('http://localhost/@@/testbrowser/navigate.html')
-        >>> link = browser.getLink(id='zope3')
-        >>> link.tag
-        'area'
-        >>> link.click()
-        >>> browser.url
-        'http://localhost/@@/testbrowser/navigate.html?message=Zope+3+Name'
-        >>> browser.contents
-        '...Message: <em>Zope 3 Name</em>...'
+            >>> browser.open('http://localhost/@@/testbrowser/navigate.html')
+            >>> link = browser.getLink(id='zope3')
+            >>> link.tag
+            'area'
+            >>> link.click()
+            >>> browser.url
+            'http://localhost/@@/testbrowser/navigate.html?message=Zope+3+Name'
+            >>> browser.contents
+            '...Message: <em>Zope 3 Name</em>...'
         
         Getting a nonexistent link raises an exception.
         
-        >>> browser.open('http://localhost/@@/testbrowser/navigate.html')
-        >>> browser.getLink('This does not exist')
-        Traceback (most recent call last):
-        ...
-        LinkNotFoundError
+            >>> browser.open('http://localhost/@@/testbrowser/navigate.html')
+            >>> browser.getLink('This does not exist')
+            Traceback (most recent call last):
+            ...
+            LinkNotFoundError
         
         A convenience method is provided to follow links; this uses the same
         arguments as `getLink`, but clicks on the link instead of returning the
         link object.
         
-        >>> browser.open('http://localhost/@@/testbrowser/navigate.html')
-        >>> browser.contents
-        '...<a href="navigate.html?message=By+Link+Text">Link Text</a>...'
-        >>> browser.follow('Link Text')
-        >>> browser.url
-        'http://localhost/@@/testbrowser/navigate.html?message=By+Link+Text'
-        >>> browser.contents
-        '...Message: <em>By Link Text</em>...'
-        
-        >>> browser.open('http://localhost/@@/testbrowser/navigate.html')
-        >>> browser.follow(url='?message=By+URL')
-        >>> browser.url
-        'http://localhost/@@/testbrowser/navigate.html?message=By+URL'
-        >>> browser.contents
-        '...Message: <em>By URL</em>...'
-        
-        >>> browser.open('http://localhost/@@/testbrowser/navigate.html')
-        >>> browser.follow(id='zope3')
-        >>> browser.url
-        'http://localhost/@@/testbrowser/navigate.html?message=Zope+3+Name'
-        >>> browser.contents
-        '...Message: <em>Zope 3 Name</em>...'
+            >>> browser.open('http://localhost/@@/testbrowser/navigate.html')
+            >>> browser.contents
+            '...<a href="navigate.html?message=By+Link+Text">Link Text</a>...'
+            >>> browser.follow('Link Text')
+            >>> browser.url
+            'http://localhost/@@/testbrowser/navigate.html?message=By+Link+Text'
+            >>> browser.contents
+            '...Message: <em>By Link Text</em>...'
+        
+            >>> browser.open('http://localhost/@@/testbrowser/navigate.html')
+            >>> browser.follow(url='?message=By+URL')
+            >>> browser.url
+            'http://localhost/@@/testbrowser/navigate.html?message=By+URL'
+            >>> browser.contents
+            '...Message: <em>By URL</em>...'
+        
+            >>> browser.open('http://localhost/@@/testbrowser/navigate.html')
+            >>> browser.follow(id='zope3')
+            >>> browser.url
+            'http://localhost/@@/testbrowser/navigate.html?message=Zope+3+Name'
+            >>> browser.contents
+            '...Message: <em>Zope 3 Name</em>...'
         
         Attempting to follow links that don't exist raises the same exception as
         asking for the link object:
         
-        >>> browser.follow('This does not exist')
-        Traceback (most recent call last):
-        ...
-        LinkNotFoundError
+            >>> browser.follow('This does not exist')
+            Traceback (most recent call last):
+            ...
+            LinkNotFoundError
         
         
         Other Navigation
@@ -452,21 +456,21 @@ Description: .. contents::
         
         Like in any normal browser, you can reload a page:
         
-        >>> browser.open('http://localhost/@@/testbrowser/simple.html')
-        >>> browser.url
-        'http://localhost/@@/testbrowser/simple.html'
-        >>> browser.reload()
-        >>> browser.url
-        'http://localhost/@@/testbrowser/simple.html'
+            >>> browser.open('http://localhost/@@/testbrowser/simple.html')
+            >>> browser.url
+            'http://localhost/@@/testbrowser/simple.html'
+            >>> browser.reload()
+            >>> browser.url
+            'http://localhost/@@/testbrowser/simple.html'
         
         You can also go back:
         
-        >>> browser.open('http://localhost/@@/testbrowser/notitle.html')
-        >>> browser.url
-        'http://localhost/@@/testbrowser/notitle.html'
-        >>> browser.goBack()
-        >>> browser.url
-        'http://localhost/@@/testbrowser/simple.html'
+            >>> browser.open('http://localhost/@@/testbrowser/notitle.html')
+            >>> browser.url
+            'http://localhost/@@/testbrowser/notitle.html'
+            >>> browser.goBack()
+            >>> browser.url
+            'http://localhost/@@/testbrowser/simple.html'
         
         
         Controls
@@ -476,7 +480,7 @@ Description: .. contents::
         and fill in values for the controls of input forms.  To do so, let's first open
         a page that has a bunch of controls:
         
-        >>> browser.open('http://localhost/@@/testbrowser/controls.html')
+            >>> browser.open('http://localhost/@@/testbrowser/controls.html')
         
         
         Obtaining a Control
@@ -486,53 +490,65 @@ Description: .. contents::
         argument is 'label', and looks up the form on the basis of any associated
         label.
         
-        >>> control = browser.getControl('Text Control')
-        >>> control
-        <Control name='text-value' type='text'>
-        >>> browser.getControl(label='Text Control') # equivalent
-        <Control name='text-value' type='text'>
+            >>> control = browser.getControl('Text Control')
+            >>> control
+            <Control name='text-value' type='text'>
+            >>> browser.getControl(label='Text Control') # equivalent
+            <Control name='text-value' type='text'>
         
         If you request a control that doesn't exist, the code raises a LookupError:
         
-        >>> browser.getControl('Does Not Exist')
-        Traceback (most recent call last):
-        ...
-        LookupError: label 'Does Not Exist'
+            >>> browser.getControl('Does Not Exist')
+            Traceback (most recent call last):
+            ...
+            LookupError: label 'Does Not Exist'
+            available items:
+              <TextControl(text-value=Some Text)>
+              <PasswordControl(password-value=Password)>
+              <HiddenControl(hidden-value=Hidden) (readonly)>
+              ...
         
         If you request a control with an ambiguous lookup, the code raises an
         AmbiguityError.
         
-        >>> browser.getControl('Ambiguous Control')
-        Traceback (most recent call last):
-        ...
-        AmbiguityError: label 'Ambiguous Control' matches:
-        <TextControl(ambiguous-control-name=First)>
-        <TextControl(ambiguous-control-name=Second)>
+            >>> browser.getControl('Ambiguous Control')
+            Traceback (most recent call last):
+            ...
+            AmbiguityError: label 'Ambiguous Control' matches:
+              <TextControl(ambiguous-control-name=First)>
+              <TextControl(ambiguous-control-name=Second)>
         
         This is also true if an option in a control is ambiguous in relation to
         the control itself.
         
-        >>> browser.getControl('Sub-control Ambiguity')
-        Traceback (most recent call last):
-        ...
-        AmbiguityError: label 'Sub-control Ambiguity' matches:
-        <SelectControl(ambiguous-subcontrol=[*, ambiguous])>
-        <Item name='ambiguous' id=None contents='Sub-control Ambiguity Exemplified' value='ambiguous' label='Sub-control Ambiguity Exemplified'>
+            >>> browser.getControl('Sub-control Ambiguity')
+            Traceback (most recent call last):
+            ...
+            AmbiguityError: label 'Sub-control Ambiguity' matches:
+              <SelectControl(ambiguous-subcontrol=[*, ambiguous])>
+              <Item name='ambiguous' id=None contents='Sub-control Ambiguity Exemplified' value='ambiguous' label='Sub-control Ambiguity Exemplified'>
         
         Ambiguous controls may be specified using an index value.  We use the control's
         value attribute to show the two controls; this attribute is properly introduced
         below.
         
-        >>> browser.getControl('Ambiguous Control', index=0)
-        <Control name='ambiguous-control-name' type='text'>
-        >>> browser.getControl('Ambiguous Control', index=0).value
-        'First'
-        >>> browser.getControl('Ambiguous Control', index=1).value
-        'Second'
-        >>> browser.getControl('Sub-control Ambiguity', index=0)
-        <ListControl name='ambiguous-subcontrol' type='select'>
-        >>> browser.getControl('Sub-control Ambiguity', index=1).optionValue
-        'ambiguous'
+            >>> browser.getControl('Ambiguous Control', index=0)
+            <Control name='ambiguous-control-name' type='text'>
+            >>> browser.getControl('Ambiguous Control', index=0).value
+            'First'
+            >>> browser.getControl('Ambiguous Control', index=1).value
+            'Second'
+            >>> browser.getControl('Sub-control Ambiguity', index=0)
+            <ListControl name='ambiguous-subcontrol' type='select'>
+            >>> browser.getControl('Sub-control Ambiguity', index=1).optionValue
+            'ambiguous'
+            >>> browser.getControl('Sub-control Ambiguity', index=2)
+            Traceback (most recent call last):
+            ...
+            LookupError: label 'Sub-control Ambiguity'
+            Index 2 out of range, available choices are 0...1
+              0: <SelectControl(ambiguous-subcontrol=[*, ambiguous])>
+              1: <Item name='ambiguous' id=None contents='Sub-control Ambiguity Exemplified' value='ambiguous' label='Sub-control Ambiguity Exemplified'>
         
         Label searches are against stripped, whitespace-normalized, no-tag versions of
         the text. Text applied to searches is also stripped and whitespace normalized.
@@ -540,69 +556,74 @@ Description: .. contents::
         text in a label.  Thus, for instance, a search for 'Add' will match the label
         'Add a Client' but not 'Address'.  Case is honored.
         
-        >>> browser.getControl('Label Needs Whitespace Normalization')
-        <Control name='label-needs-normalization' type='text'>
-        >>> browser.getControl('label needs whitespace normalization')
-        Traceback (most recent call last):
-        ...
-        LookupError: label 'label needs whitespace normalization'
-        >>> browser.getControl(' Label  Needs Whitespace    ')
-        <Control name='label-needs-normalization' type='text'>
-        >>> browser.getControl('Whitespace')
-        <Control name='label-needs-normalization' type='text'>
-        >>> browser.getControl('hitespace')
-        Traceback (most recent call last):
-        ...
-        LookupError: label 'hitespace'
-        >>> browser.getControl('[non word characters should not confuse]')
-        <Control name='non-word-characters' type='text'>
+            >>> browser.getControl('Label Needs Whitespace Normalization')
+            <Control name='label-needs-normalization' type='text'>
+            >>> browser.getControl('label needs whitespace normalization')
+            Traceback (most recent call last):
+            ...
+            LookupError: label 'label needs whitespace normalization'
+            ...
+            >>> browser.getControl(' Label  Needs Whitespace    ')
+            <Control name='label-needs-normalization' type='text'>
+            >>> browser.getControl('Whitespace')
+            <Control name='label-needs-normalization' type='text'>
+            >>> browser.getControl('hitespace')
+            Traceback (most recent call last):
+            ...
+            LookupError: label 'hitespace'
+            ...
+            >>> browser.getControl('[non word characters should not confuse]')
+            <Control name='non-word-characters' type='text'>
         
         Multiple labels can refer to the same control (simply because that is possible
         in the HTML 4.0 spec).
         
-        >>> browser.getControl('Multiple labels really')
-        <Control name='two-labels' type='text'>
-        >>> browser.getControl('really are possible')
-        <Control name='two-labels' type='text'>
-        >>> browser.getControl('really') # OK: ambiguous labels, but not ambiguous control
-        <Control name='two-labels' type='text'>
+            >>> browser.getControl('Multiple labels really')
+            <Control name='two-labels' type='text'>
+            >>> browser.getControl('really are possible')
+            <Control name='two-labels' type='text'>
+            >>> browser.getControl('really') # OK: ambiguous labels, but not ambiguous control
+            <Control name='two-labels' type='text'>
         
         A label can be connected with a control using the 'for' attribute and also by
         containing a control.
         
-        >>> browser.getControl(
-        ...     'Labels can be connected by containing their respective fields')
-        <Control name='contained-in-label' type='text'>
+            >>> browser.getControl(
+            ...     'Labels can be connected by containing their respective fields')
+            <Control name='contained-in-label' type='text'>
         
         Get also accepts one other search argument, 'name'.  Only one of 'label' and
         'name' may be used at a time.  The 'name' keyword searches form field names.
         
-        >>> browser.getControl(name='text-value')
-        <Control name='text-value' type='text'>
-        >>> browser.getControl(name='ambiguous-control-name')
-        Traceback (most recent call last):
-        ...
-        AmbiguityError: name 'ambiguous-control-name' matches:
-        <TextControl(ambiguous-control-name=First)>
-        <TextControl(ambiguous-control-name=Second)>
-        >>> browser.getControl(name='does-not-exist')
-        Traceback (most recent call last):
-        ...
-        LookupError: name 'does-not-exist'
-        >>> browser.getControl(name='ambiguous-control-name', index=1).value
-        'Second'
+            >>> browser.getControl(name='text-value')
+            <Control name='text-value' type='text'>
+            >>> browser.getControl(name='ambiguous-control-name')
+            Traceback (most recent call last):
+            ...
+            AmbiguityError: name 'ambiguous-control-name' matches:
+              <TextControl(ambiguous-control-name=First)>
+              <TextControl(ambiguous-control-name=Second)>
+            >>> browser.getControl(name='does-not-exist')
+            Traceback (most recent call last):
+            ...
+            LookupError: name 'does-not-exist'
+            available items:
+              <TextControl(text-value=Some Text)>
+              ...
+            >>> browser.getControl(name='ambiguous-control-name', index=1).value
+            'Second'
         
         Combining 'label' and 'name' raises a ValueError, as does supplying neither of
         them.
         
-        >>> browser.getControl(label='Ambiguous Control', name='ambiguous-control-name')
-        Traceback (most recent call last):
-        ...
-        ValueError: Supply one and only one of "label" and "name" as arguments
-        >>> browser.getControl()
-        Traceback (most recent call last):
-        ...
-        ValueError: Supply one and only one of "label" and "name" as arguments
+            >>> browser.getControl(label='Ambiguous Control', name='ambiguous-control-name')
+            Traceback (most recent call last):
+            ...
+            ValueError: Supply one and only one of "label" and "name" as arguments
+            >>> browser.getControl()
+            Traceback (most recent call last):
+            ...
+            ValueError: Supply one and only one of "label" and "name" as arguments
         
         Radio and checkbox fields are unusual in that their labels and names may point
         to different objects: names point to logical collections of radio buttons or
@@ -611,14 +632,14 @@ Description: .. contents::
         different object than obtaining the radio collection by name.  Select options
         may also be searched by label.
         
-        >>> browser.getControl(name='radio-value')
-        <ListControl name='radio-value' type='radio'>
-        >>> browser.getControl('Zwei')
-        <ItemControl name='radio-value' type='radio' optionValue='2' selected=True>
-        >>> browser.getControl('One')
-        <ItemControl name='multi-checkbox-value' type='checkbox' optionValue='1' selected=True>
-        >>> browser.getControl('Tres')
-        <ItemControl name='single-select-value' type='select' optionValue='3' selected=False>
+            >>> browser.getControl(name='radio-value')
+            <ListControl name='radio-value' type='radio'>
+            >>> browser.getControl('Zwei')
+            <ItemControl name='radio-value' type='radio' optionValue='2' selected=True>
+            >>> browser.getControl('One')
+            <ItemControl name='multi-checkbox-value' type='checkbox' optionValue='1' selected=True>
+            >>> browser.getControl('Tres')
+            <ItemControl name='single-select-value' type='select' optionValue='3' selected=False>
         
         Characteristics of controls and subcontrols are discussed below.
         
@@ -628,106 +649,106 @@ Description: .. contents::
         
         Controls provide IControl.
         
-        >>> ctrl = browser.getControl('Text Control')
-        >>> ctrl
-        <Control name='text-value' type='text'>
-        >>> verifyObject(interfaces.IControl, ctrl)
-        True
+            >>> ctrl = browser.getControl('Text Control')
+            >>> ctrl
+            <Control name='text-value' type='text'>
+            >>> verifyObject(interfaces.IControl, ctrl)
+            True
         
         They have several useful attributes:
         
-        - the name as which the control is known to the form:
+          - the name as which the control is known to the form:
         
-        >>> ctrl.name
-        'text-value'
+            >>> ctrl.name
+            'text-value'
         
-        - the value of the control, which may also be set:
+          - the value of the control, which may also be set:
         
-        >>> ctrl.value
-        'Some Text'
-        >>> ctrl.value = 'More Text'
-        >>> ctrl.value
-        'More Text'
+            >>> ctrl.value
+            'Some Text'
+            >>> ctrl.value = 'More Text'
+            >>> ctrl.value
+            'More Text'
         
-        - the type of the control:
+          - the type of the control:
         
-        >>> ctrl.type
-        'text'
+            >>> ctrl.type
+            'text'
         
-        - a flag describing whether the control is disabled:
+          - a flag describing whether the control is disabled:
         
-        >>> ctrl.disabled
-        False
+            >>> ctrl.disabled
+            False
         
-        - and a flag to tell us whether the control can have multiple values:
+          - and a flag to tell us whether the control can have multiple values:
         
-        >>> ctrl.multiple
-        False
+            >>> ctrl.multiple
+            False
         
         Additionally, controllers for select, radio, and checkbox provide IListControl.
         These fields have four other attributes and an additional method:
         
-        >>> ctrl = browser.getControl('Multiple Select Control')
-        >>> ctrl
-        <ListControl name='multi-select-value' type='select'>
-        >>> ctrl.disabled
-        False
-        >>> ctrl.multiple
-        True
-        >>> verifyObject(interfaces.IListControl, ctrl)
-        True
-        
-        - 'options' lists all available value options.
-        
-        >>> ctrl.options
-        ['1', '2', '3']
-        
-        - 'displayOptions' lists all available options by label.  The 'label'
-        attribute on an option has precedence over its contents, which is why
-        our last option is 'Third' in the display.
-        
-        >>> ctrl.displayOptions
-        ['Un', 'Deux', 'Third']
-        
-        - 'displayValue' lets you get and set the displayed values of the control
-        of the select box, rather than the actual values.
-        
-        >>> ctrl.value
-        []
-        >>> ctrl.displayValue
-        []
-        >>> ctrl.displayValue = ['Un', 'Deux']
-        >>> ctrl.displayValue
-        ['Un', 'Deux']
-        >>> ctrl.value
-        ['1', '2']
-        
-        - 'controls' gives you a list of the subcontrol objects in the control
-        (subcontrols are discussed below).
-        
-        >>> ctrl.controls
-        [<ItemControl name='multi-select-value' type='select' optionValue='1' selected=True>,
-        <ItemControl name='multi-select-value' type='select' optionValue='2' selected=True>,
-        <ItemControl name='multi-select-value' type='select' optionValue='3' selected=False>]
-        
-        - The 'getControl' method lets you get subcontrols by their label or their value.
-        
-        >>> ctrl.getControl('Un')
-        <ItemControl name='multi-select-value' type='select' optionValue='1' selected=True>
-        >>> ctrl.getControl('Deux')
-        <ItemControl name='multi-select-value' type='select' optionValue='2' selected=True>
-        >>> ctrl.getControl('Trois') # label attribute
-        <ItemControl name='multi-select-value' type='select' optionValue='3' selected=False>
-        >>> ctrl.getControl('Third') # contents
-        <ItemControl name='multi-select-value' type='select' optionValue='3' selected=False>
-        >>> browser.getControl('Third') # ambiguous in the browser, so useful
-        Traceback (most recent call last):
-        ...
-        AmbiguityError: label 'Third' matches:
-        <Item name='3' id=None contents='Tres' value='3' label='Third'>
-        <Item name='3' id=None contents='Trois' value='3' label='Third'>
-        <Item name='3' id='multi-checkbox-value-3' __label={'__text': 'Three\n        '} checked='checked' name='multi-checkbox-value' type='checkbox' id='multi-checkbox-value-3' value='3'>
-        <Item name='3' id='radio-value-3' __label={'__text': ' Drei'} type='radio' name='radio-value' value='3' id='radio-value-3'>
+            >>> ctrl = browser.getControl('Multiple Select Control')
+            >>> ctrl
+            <ListControl name='multi-select-value' type='select'>
+            >>> ctrl.disabled
+            False
+            >>> ctrl.multiple
+            True
+            >>> verifyObject(interfaces.IListControl, ctrl)
+            True
+        
+          - 'options' lists all available value options.
+        
+            >>> ctrl.options
+            ['1', '2', '3']
+        
+          - 'displayOptions' lists all available options by label.  The 'label'
+            attribute on an option has precedence over its contents, which is why
+            our last option is 'Third' in the display.
+        
+            >>> ctrl.displayOptions
+            ['Un', 'Deux', 'Third']
+        
+          - 'displayValue' lets you get and set the displayed values of the control
+            of the select box, rather than the actual values.
+        
+            >>> ctrl.value
+            []
+            >>> ctrl.displayValue
+            []
+            >>> ctrl.displayValue = ['Un', 'Deux']
+            >>> ctrl.displayValue
+            ['Un', 'Deux']
+            >>> ctrl.value
+            ['1', '2']
+        
+          - 'controls' gives you a list of the subcontrol objects in the control
+            (subcontrols are discussed below).
+        
+            >>> ctrl.controls
+            [<ItemControl name='multi-select-value' type='select' optionValue='1' selected=True>,
+             <ItemControl name='multi-select-value' type='select' optionValue='2' selected=True>,
+             <ItemControl name='multi-select-value' type='select' optionValue='3' selected=False>]
+        
+          - The 'getControl' method lets you get subcontrols by their label or their value.
+        
+            >>> ctrl.getControl('Un')
+            <ItemControl name='multi-select-value' type='select' optionValue='1' selected=True>
+            >>> ctrl.getControl('Deux')
+            <ItemControl name='multi-select-value' type='select' optionValue='2' selected=True>
+            >>> ctrl.getControl('Trois') # label attribute
+            <ItemControl name='multi-select-value' type='select' optionValue='3' selected=False>
+            >>> ctrl.getControl('Third') # contents
+            <ItemControl name='multi-select-value' type='select' optionValue='3' selected=False>
+            >>> browser.getControl('Third') # ambiguous in the browser, so useful
+            Traceback (most recent call last):
+            ...
+            AmbiguityError: label 'Third' matches:
+              <Item name='3' id=None contents='Tres' value='3' label='Third'>
+              <Item name='3' id=None contents='Trois' value='3' label='Third'>
+              <Item name='3' id='multi-checkbox-value-3' __label={'__text': 'Three\n        '} checked='checked' name='multi-checkbox-value' type='checkbox' id='multi-checkbox-value-3' value='3'>
+              <Item name='3' id='radio-value-3' __label={'__text': ' Drei'} type='radio' name='radio-value' value='3' id='radio-value-3'>
         
         Finally, submit controls provide ISubmitControl, and image controls provide
         IImageSubmitControl, which extents ISubmitControl.  These both simply add a
@@ -743,25 +764,25 @@ Description: .. contents::
         radio button or checkbox collection returns item controls, which are parents.
         Manipulating the value of these controls affects the parent control.
         
-        >>> browser.getControl(name='radio-value').value
-        ['2']
-        >>> browser.getControl('Zwei').optionValue # read-only.
-        '2'
-        >>> browser.getControl('Zwei').selected
-        True
-        >>> verifyObject(interfaces.IItemControl, browser.getControl('Zwei'))
-        True
-        >>> browser.getControl('Ein').selected = True
-        >>> browser.getControl('Ein').selected
-        True
-        >>> browser.getControl('Zwei').selected
-        False
-        >>> browser.getControl(name='radio-value').value
-        ['1']
-        >>> browser.getControl('Ein').selected = False
-        >>> browser.getControl(name='radio-value').value
-        []
-        >>> browser.getControl('Zwei').selected = True
+            >>> browser.getControl(name='radio-value').value
+            ['2']
+            >>> browser.getControl('Zwei').optionValue # read-only.
+            '2'
+            >>> browser.getControl('Zwei').selected
+            True
+            >>> verifyObject(interfaces.IItemControl, browser.getControl('Zwei'))
+            True
+            >>> browser.getControl('Ein').selected = True
+            >>> browser.getControl('Ein').selected
+            True
+            >>> browser.getControl('Zwei').selected
+            False
+            >>> browser.getControl(name='radio-value').value
+            ['1']
+            >>> browser.getControl('Ein').selected = False
+            >>> browser.getControl(name='radio-value').value
+            []
+            >>> browser.getControl('Zwei').selected = True
         
         Checkbox collections behave similarly, as shown below.
         
@@ -773,333 +794,333 @@ Description: .. contents::
         
         The various types of controls are demonstrated here.
         
-        - Text Control
+          - Text Control
         
-        The text control we already introduced above.
+            The text control we already introduced above.
         
-        - Password Control
+          - Password Control
         
-        >>> ctrl = browser.getControl('Password Control')
-        >>> ctrl
-        <Control name='password-value' type='password'>
-        >>> verifyObject(interfaces.IControl, ctrl)
-        True
-        >>> ctrl.value
-        'Password'
-        >>> ctrl.value = 'pass now'
-        >>> ctrl.value
-        'pass now'
-        >>> ctrl.disabled
-        False
-        >>> ctrl.multiple
-        False
-        
-        - Hidden Control
-        
-        >>> ctrl = browser.getControl(name='hidden-value')
-        >>> ctrl
-        <Control name='hidden-value' type='hidden'>
-        >>> verifyObject(interfaces.IControl, ctrl)
-        True
-        >>> ctrl.value
-        'Hidden'
-        >>> ctrl.value = 'More Hidden'
-        >>> ctrl.disabled
-        False
-        >>> ctrl.multiple
-        False
-        
-        - Text Area Control
-        
-        >>> ctrl = browser.getControl('Text Area Control')
-        >>> ctrl
-        <Control name='textarea-value' type='textarea'>
-        >>> verifyObject(interfaces.IControl, ctrl)
-        True
-        >>> ctrl.value
-        '        Text inside\n        area!\n      '
-        >>> ctrl.value = 'A lot of\n text.'
-        >>> ctrl.disabled
-        False
-        >>> ctrl.multiple
-        False
-        
-        - File Control
-        
-        File controls are used when a form has a file-upload field.
-        To specify data, call the add_file method, passing:
-        
-        - A file-like object
-        
-        - a content type, and
-        
-        - a file name
-        
-        >>> ctrl = browser.getControl('File Control')
-        >>> ctrl
-        <Control name='file-value' type='file'>
-        >>> verifyObject(interfaces.IControl, ctrl)
-        True
-        >>> ctrl.value is None
-        True
-        >>> import cStringIO
-        
-        >>> ctrl.add_file(cStringIO.StringIO('File contents'),
-        ...               'text/plain', 'test.txt')
-        
-        The file control (like the other controls) also knows if it is disabled
-        or if it can have multiple values.
-        
-        >>> ctrl.disabled
-        False
-        >>> ctrl.multiple
-        False
-        
-        - Selection Control (Single-Valued)
-        
-        >>> ctrl = browser.getControl('Single Select Control')
-        >>> ctrl
-        <ListControl name='single-select-value' type='select'>
-        >>> verifyObject(interfaces.IListControl, ctrl)
-        True
-        >>> ctrl.value
-        ['1']
-        >>> ctrl.value = ['2']
-        >>> ctrl.disabled
-        False
-        >>> ctrl.multiple
-        False
-        >>> ctrl.options
-        ['1', '2', '3']
-        >>> ctrl.displayOptions
-        ['Uno', 'Dos', 'Third']
-        >>> ctrl.displayValue
-        ['Dos']
-        >>> ctrl.displayValue = ['Tres']
-        >>> ctrl.displayValue
-        ['Third']
-        >>> ctrl.displayValue = ['Dos']
-        >>> ctrl.displayValue
-        ['Dos']
-        >>> ctrl.displayValue = ['Third']
-        >>> ctrl.displayValue
-        ['Third']
-        >>> ctrl.value
-        ['3']
-        
-        - Selection Control (Multi-Valued)
-        
-        This was already demonstrated in the introduction to control objects above.
-        
-        - Checkbox Control (Single-Valued; Unvalued)
-        
-        >>> ctrl = browser.getControl(name='single-unvalued-checkbox-value')
-        >>> ctrl
-        <ListControl name='single-unvalued-checkbox-value' type='checkbox'>
-        >>> verifyObject(interfaces.IListControl, ctrl)
-        True
-        >>> ctrl.value
-        True
-        >>> ctrl.value = False
-        >>> ctrl.disabled
-        False
-        >>> ctrl.multiple
-        True
-        >>> ctrl.options
-        [True]
-        >>> ctrl.displayOptions
-        ['Single Unvalued Checkbox']
-        >>> ctrl.displayValue
-        []
-        >>> verifyObject(
-        ...     interfaces.IItemControl,
-        ...     browser.getControl('Single Unvalued Checkbox'))
-        True
-        >>> browser.getControl('Single Unvalued Checkbox').optionValue
-        'on'
-        >>> browser.getControl('Single Unvalued Checkbox').selected
-        False
-        >>> ctrl.displayValue = ['Single Unvalued Checkbox']
-        >>> ctrl.displayValue
-        ['Single Unvalued Checkbox']
-        >>> browser.getControl('Single Unvalued Checkbox').selected
-        True
-        >>> browser.getControl('Single Unvalued Checkbox').selected = False
-        >>> browser.getControl('Single Unvalued Checkbox').selected
-        False
-        >>> ctrl.displayValue
-        []
-        >>> browser.getControl(
-        ...     name='single-disabled-unvalued-checkbox-value').disabled
-        True
-        
-        - Checkbox Control (Single-Valued, Valued)
-        
-        >>> ctrl = browser.getControl(name='single-valued-checkbox-value')
-        >>> ctrl
-        <ListControl name='single-valued-checkbox-value' type='checkbox'>
-        >>> verifyObject(interfaces.IListControl, ctrl)
-        True
-        >>> ctrl.value
-        ['1']
-        >>> ctrl.value = []
-        >>> ctrl.disabled
-        False
-        >>> ctrl.multiple
-        True
-        >>> ctrl.options
-        ['1']
-        >>> ctrl.displayOptions
-        ['Single Valued Checkbox']
-        >>> ctrl.displayValue
-        []
-        >>> verifyObject(
-        ...     interfaces.IItemControl,
-        ...     browser.getControl('Single Valued Checkbox'))
-        True
-        >>> browser.getControl('Single Valued Checkbox').selected
-        False
-        >>> browser.getControl('Single Valued Checkbox').optionValue
-        '1'
-        >>> ctrl.displayValue = ['Single Valued Checkbox']
-        >>> ctrl.displayValue
-        ['Single Valued Checkbox']
-        >>> browser.getControl('Single Valued Checkbox').selected
-        True
-        >>> browser.getControl('Single Valued Checkbox').selected = False
-        >>> browser.getControl('Single Valued Checkbox').selected
-        False
-        >>> ctrl.displayValue
-        []
-        
-        - Checkbox Control (Multi-Valued)
-        
-        >>> ctrl = browser.getControl(name='multi-checkbox-value')
-        >>> ctrl
-        <ListControl name='multi-checkbox-value' type='checkbox'>
-        >>> verifyObject(interfaces.IListControl, ctrl)
-        True
-        >>> ctrl.value
-        ['1', '3']
-        >>> ctrl.value = ['1', '2']
-        >>> ctrl.disabled
-        False
-        >>> ctrl.multiple
-        True
-        >>> ctrl.options
-        ['1', '2', '3']
-        >>> ctrl.displayOptions
-        ['One', 'Two', 'Three']
-        >>> ctrl.displayValue
-        ['One', 'Two']
-        >>> ctrl.displayValue = ['Two']
-        >>> ctrl.value
-        ['2']
-        >>> browser.getControl('Two').optionValue
-        '2'
-        >>> browser.getControl('Two').selected
-        True
-        >>> verifyObject(interfaces.IItemControl, browser.getControl('Two'))
-        True
-        >>> browser.getControl('Three').selected = True
-        >>> browser.getControl('Three').selected
-        True
-        >>> browser.getControl('Two').selected
-        True
-        >>> ctrl.value
-        ['2', '3']
-        >>> browser.getControl('Two').selected = False
-        >>> ctrl.value
-        ['3']
-        >>> browser.getControl('Three').selected = False
-        >>> ctrl.value
-        []
-        
-        - Radio Control
-        
-        This is how you get a radio button based control:
-        
-        >>> ctrl = browser.getControl(name='radio-value')
-        
-        This shows the existing value of the control, as it was in the
-        HTML received from the server:
-        
-        >>> ctrl.value
-        ['2']
-        
-        We can then unselect it:
-        
-        >>> ctrl.value = []
-        >>> ctrl.value
-        []
-        
-        We can also reselect it:
-        
-        >>> ctrl.value = ['2']
-        >>> ctrl.value
-        ['2']
-        
-        displayValue shows the text the user would see next to the
-        control:
-        
-        >>> ctrl.displayValue
-        ['Zwei']
-        
-        This is just unit testing:
-        
-        >>> ctrl
-        <ListControl name='radio-value' type='radio'>
-        >>> verifyObject(interfaces.IListControl, ctrl)
-        True
-        >>> ctrl.disabled
-        False
-        >>> ctrl.multiple
-        False
-        >>> ctrl.options
-        ['1', '2', '3']
-        >>> ctrl.displayOptions
-        ['Ein', 'Zwei', 'Drei']
-        >>> ctrl.displayValue = ['Ein']
-        >>> ctrl.value
-        ['1']
-        >>> ctrl.displayValue
-        ['Ein']
-        
-        The radio control subcontrols were illustrated above.
-        
-        - Image Control
-        
-        >>> ctrl = browser.getControl(name='image-value')
-        >>> ctrl
-        <ImageControl name='image-value' type='image'>
-        >>> verifyObject(interfaces.IImageSubmitControl, ctrl)
-        True
-        >>> ctrl.value
-        ''
-        >>> ctrl.disabled
-        False
-        >>> ctrl.multiple
-        False
-        
-        - Submit Control
-        
-        >>> ctrl = browser.getControl(name='submit-value')
-        >>> ctrl
-        <SubmitControl name='submit-value' type='submit'>
-        >>> browser.getControl('Submit This') # value of submit button is a label
-        <SubmitControl name='submit-value' type='submit'>
-        >>> browser.getControl('Standard Submit Control') # label tag is legal
-        <SubmitControl name='submit-value' type='submit'>
-        >>> browser.getControl('Submit') # multiple labels, but same control
-        <SubmitControl name='submit-value' type='submit'>
-        >>> verifyObject(interfaces.ISubmitControl, ctrl)
-        True
-        >>> ctrl.value
-        'Submit This'
-        >>> ctrl.disabled
-        False
-        >>> ctrl.multiple
-        False
+            >>> ctrl = browser.getControl('Password Control')
+            >>> ctrl
+            <Control name='password-value' type='password'>
+            >>> verifyObject(interfaces.IControl, ctrl)
+            True
+            >>> ctrl.value
+            'Password'
+            >>> ctrl.value = 'pass now'
+            >>> ctrl.value
+            'pass now'
+            >>> ctrl.disabled
+            False
+            >>> ctrl.multiple
+            False
+        
+          - Hidden Control
+        
+            >>> ctrl = browser.getControl(name='hidden-value')
+            >>> ctrl
+            <Control name='hidden-value' type='hidden'>
+            >>> verifyObject(interfaces.IControl, ctrl)
+            True
+            >>> ctrl.value
+            'Hidden'
+            >>> ctrl.value = 'More Hidden'
+            >>> ctrl.disabled
+            False
+            >>> ctrl.multiple
+            False
+        
+          - Text Area Control
+        
+            >>> ctrl = browser.getControl('Text Area Control')
+            >>> ctrl
+            <Control name='textarea-value' type='textarea'>
+            >>> verifyObject(interfaces.IControl, ctrl)
+            True
+            >>> ctrl.value
+            '        Text inside\n        area!\n      '
+            >>> ctrl.value = 'A lot of\n text.'
+            >>> ctrl.disabled
+            False
+            >>> ctrl.multiple
+            False
+        
+          - File Control
+        
+            File controls are used when a form has a file-upload field.
+            To specify data, call the add_file method, passing:
+        
+            - A file-like object
+        
+            - a content type, and
+        
+            - a file name
+        
+            >>> ctrl = browser.getControl('File Control')
+            >>> ctrl
+            <Control name='file-value' type='file'>
+            >>> verifyObject(interfaces.IControl, ctrl)
+            True
+            >>> ctrl.value is None
+            True
+            >>> import cStringIO
+        
+            >>> ctrl.add_file(cStringIO.StringIO('File contents'),
+            ...               'text/plain', 'test.txt')
+        
+            The file control (like the other controls) also knows if it is disabled
+            or if it can have multiple values.
+        
+            >>> ctrl.disabled
+            False
+            >>> ctrl.multiple
+            False
+        
+          - Selection Control (Single-Valued)
+        
+            >>> ctrl = browser.getControl('Single Select Control')
+            >>> ctrl
+            <ListControl name='single-select-value' type='select'>
+            >>> verifyObject(interfaces.IListControl, ctrl)
+            True
+            >>> ctrl.value
+            ['1']
+            >>> ctrl.value = ['2']
+            >>> ctrl.disabled
+            False
+            >>> ctrl.multiple
+            False
+            >>> ctrl.options
+            ['1', '2', '3']
+            >>> ctrl.displayOptions
+            ['Uno', 'Dos', 'Third']
+            >>> ctrl.displayValue
+            ['Dos']
+            >>> ctrl.displayValue = ['Tres']
+            >>> ctrl.displayValue
+            ['Third']
+            >>> ctrl.displayValue = ['Dos']
+            >>> ctrl.displayValue
+            ['Dos']
+            >>> ctrl.displayValue = ['Third']
+            >>> ctrl.displayValue
+            ['Third']
+            >>> ctrl.value
+            ['3']
+        
+          - Selection Control (Multi-Valued)
+        
+            This was already demonstrated in the introduction to control objects above.
+        
+          - Checkbox Control (Single-Valued; Unvalued)
+        
+            >>> ctrl = browser.getControl(name='single-unvalued-checkbox-value')
+            >>> ctrl
+            <ListControl name='single-unvalued-checkbox-value' type='checkbox'>
+            >>> verifyObject(interfaces.IListControl, ctrl)
+            True
+            >>> ctrl.value
+            True
+            >>> ctrl.value = False
+            >>> ctrl.disabled
+            False
+            >>> ctrl.multiple
+            True
+            >>> ctrl.options
+            [True]
+            >>> ctrl.displayOptions
+            ['Single Unvalued Checkbox']
+            >>> ctrl.displayValue
+            []
+            >>> verifyObject(
+            ...     interfaces.IItemControl,
+            ...     browser.getControl('Single Unvalued Checkbox'))
+            True
+            >>> browser.getControl('Single Unvalued Checkbox').optionValue
+            'on'
+            >>> browser.getControl('Single Unvalued Checkbox').selected
+            False
+            >>> ctrl.displayValue = ['Single Unvalued Checkbox']
+            >>> ctrl.displayValue
+            ['Single Unvalued Checkbox']
+            >>> browser.getControl('Single Unvalued Checkbox').selected
+            True
+            >>> browser.getControl('Single Unvalued Checkbox').selected = False
+            >>> browser.getControl('Single Unvalued Checkbox').selected
+            False
+            >>> ctrl.displayValue
+            []
+            >>> browser.getControl(
+            ...     name='single-disabled-unvalued-checkbox-value').disabled
+            True
+        
+          - Checkbox Control (Single-Valued, Valued)
+        
+            >>> ctrl = browser.getControl(name='single-valued-checkbox-value')
+            >>> ctrl
+            <ListControl name='single-valued-checkbox-value' type='checkbox'>
+            >>> verifyObject(interfaces.IListControl, ctrl)
+            True
+            >>> ctrl.value
+            ['1']
+            >>> ctrl.value = []
+            >>> ctrl.disabled
+            False
+            >>> ctrl.multiple
+            True
+            >>> ctrl.options
+            ['1']
+            >>> ctrl.displayOptions
+            ['Single Valued Checkbox']
+            >>> ctrl.displayValue
+            []
+            >>> verifyObject(
+            ...     interfaces.IItemControl,
+            ...     browser.getControl('Single Valued Checkbox'))
+            True
+            >>> browser.getControl('Single Valued Checkbox').selected
+            False
+            >>> browser.getControl('Single Valued Checkbox').optionValue
+            '1'
+            >>> ctrl.displayValue = ['Single Valued Checkbox']
+            >>> ctrl.displayValue
+            ['Single Valued Checkbox']
+            >>> browser.getControl('Single Valued Checkbox').selected
+            True
+            >>> browser.getControl('Single Valued Checkbox').selected = False
+            >>> browser.getControl('Single Valued Checkbox').selected
+            False
+            >>> ctrl.displayValue
+            []
+        
+          - Checkbox Control (Multi-Valued)
+        
+            >>> ctrl = browser.getControl(name='multi-checkbox-value')
+            >>> ctrl
+            <ListControl name='multi-checkbox-value' type='checkbox'>
+            >>> verifyObject(interfaces.IListControl, ctrl)
+            True
+            >>> ctrl.value
+            ['1', '3']
+            >>> ctrl.value = ['1', '2']
+            >>> ctrl.disabled
+            False
+            >>> ctrl.multiple
+            True
+            >>> ctrl.options
+            ['1', '2', '3']
+            >>> ctrl.displayOptions
+            ['One', 'Two', 'Three']
+            >>> ctrl.displayValue
+            ['One', 'Two']
+            >>> ctrl.displayValue = ['Two']
+            >>> ctrl.value
+            ['2']
+            >>> browser.getControl('Two').optionValue
+            '2'
+            >>> browser.getControl('Two').selected
+            True
+            >>> verifyObject(interfaces.IItemControl, browser.getControl('Two'))
+            True
+            >>> browser.getControl('Three').selected = True
+            >>> browser.getControl('Three').selected
+            True
+            >>> browser.getControl('Two').selected
+            True
+            >>> ctrl.value
+            ['2', '3']
+            >>> browser.getControl('Two').selected = False
+            >>> ctrl.value
+            ['3']
+            >>> browser.getControl('Three').selected = False
+            >>> ctrl.value
+            []
+        
+          - Radio Control
+        
+            This is how you get a radio button based control:
+        
+            >>> ctrl = browser.getControl(name='radio-value')
+        
+            This shows the existing value of the control, as it was in the
+            HTML received from the server:
+        
+            >>> ctrl.value
+            ['2']
+        
+            We can then unselect it:
+        
+            >>> ctrl.value = []
+            >>> ctrl.value
+            []
+        
+            We can also reselect it:
+        
+            >>> ctrl.value = ['2']
+            >>> ctrl.value
+            ['2']
+        
+            displayValue shows the text the user would see next to the
+            control:
+        
+            >>> ctrl.displayValue
+            ['Zwei']
+        
+            This is just unit testing:
+        
+            >>> ctrl
+            <ListControl name='radio-value' type='radio'>
+            >>> verifyObject(interfaces.IListControl, ctrl)
+            True
+            >>> ctrl.disabled
+            False
+            >>> ctrl.multiple
+            False
+            >>> ctrl.options
+            ['1', '2', '3']
+            >>> ctrl.displayOptions
+            ['Ein', 'Zwei', 'Drei']
+            >>> ctrl.displayValue = ['Ein']
+            >>> ctrl.value
+            ['1']
+            >>> ctrl.displayValue
+            ['Ein']
+        
+            The radio control subcontrols were illustrated above.
+        
+          - Image Control
+        
+            >>> ctrl = browser.getControl(name='image-value')
+            >>> ctrl
+            <ImageControl name='image-value' type='image'>
+            >>> verifyObject(interfaces.IImageSubmitControl, ctrl)
+            True
+            >>> ctrl.value
+            ''
+            >>> ctrl.disabled
+            False
+            >>> ctrl.multiple
+            False
+        
+          - Submit Control
+        
+            >>> ctrl = browser.getControl(name='submit-value')
+            >>> ctrl
+            <SubmitControl name='submit-value' type='submit'>
+            >>> browser.getControl('Submit This') # value of submit button is a label
+            <SubmitControl name='submit-value' type='submit'>
+            >>> browser.getControl('Standard Submit Control') # label tag is legal
+            <SubmitControl name='submit-value' type='submit'>
+            >>> browser.getControl('Submit') # multiple labels, but same control
+            <SubmitControl name='submit-value' type='submit'>
+            >>> verifyObject(interfaces.ISubmitControl, ctrl)
+            True
+            >>> ctrl.value
+            'Submit This'
+            >>> ctrl.disabled
+            False
+            >>> ctrl.multiple
+            False
         
         
         Using Submitting Controls
@@ -1107,69 +1128,82 @@ Description: .. contents::
         
         Both the submit and image type should be clickable and submit the form:
         
-        >>> browser.getControl('Text Control').value = 'Other Text'
-        >>> browser.getControl('Submit').click()
-        >>> print browser.contents
-        <html>
-        ...
-        <em>Other Text</em>
-        <input type="text" name="text-value" id="text-value" value="Some Text" />
-        ...
-        <em>Submit This</em>
-        <input type="submit" name="submit-value" id="submit-value" value="Submit This" />
-        ...
-        </html>
+            >>> browser.getControl('Text Control').value = 'Other Text'
+            >>> browser.getControl('Submit').click()
+            >>> print browser.contents
+            <html>
+            ...
+            <em>Other Text</em>
+            <input type="text" name="text-value" id="text-value" value="Some Text" />
+            ...
+            <em>Submit This</em>
+            <input type="submit" name="submit-value" id="submit-value" value="Submit This" />
+            ...
+            </html>
         
         Note that if you click a submit object after the associated page has expired,
         you will get an error.
         
-        >>> browser.open('http://localhost/@@/testbrowser/controls.html')
-        >>> ctrl = browser.getControl('Submit')
-        >>> ctrl.click()
-        >>> ctrl.click()
-        Traceback (most recent call last):
-        ...
-        ExpiredError
+            >>> browser.open('http://localhost/@@/testbrowser/controls.html')
+            >>> ctrl = browser.getControl('Submit')
+            >>> ctrl.click()
+            >>> ctrl.click()
+            Traceback (most recent call last):
+            ...
+            ExpiredError
         
         All the above also holds true for the image control:
         
-        >>> browser.open('http://localhost/@@/testbrowser/controls.html')
-        >>> browser.getControl('Text Control').value = 'Other Text'
-        >>> browser.getControl(name='image-value').click()
-        >>> print browser.contents
-        <html>
-        ...
-        <em>Other Text</em>
-        <input type="text" name="text-value" id="text-value" value="Some Text" />
-        ...
-        <em>1</em>
-        <em>1</em>
-        <input type="image" name="image-value" id="image-value"
-        src="zope3logo.gif" />
-        ...
-        </html>
-        
-        >>> browser.open('http://localhost/@@/testbrowser/controls.html')
-        >>> ctrl = browser.getControl(name='image-value')
-        >>> ctrl.click()
-        >>> ctrl.click()
-        Traceback (most recent call last):
-        ...
-        ExpiredError
+            >>> browser.open('http://localhost/@@/testbrowser/controls.html')
+            >>> browser.getControl('Text Control').value = 'Other Text'
+            >>> browser.getControl(name='image-value').click()
+            >>> print browser.contents
+            <html>
+            ...
+            <em>Other Text</em>
+            <input type="text" name="text-value" id="text-value" value="Some Text" />
+            ...
+            <em>1</em>
+            <em>1</em>
+            <input type="image" name="image-value" id="image-value"
+                   src="zope3logo.gif" />
+            ...
+            </html>
+        
+            >>> browser.open('http://localhost/@@/testbrowser/controls.html')
+            >>> ctrl = browser.getControl(name='image-value')
+            >>> ctrl.click()
+            >>> ctrl.click()
+            Traceback (most recent call last):
+            ...
+            ExpiredError
         
         But when sending an image, you can also specify the coordinate you clicked:
         
-        >>> browser.open('http://localhost/@@/testbrowser/controls.html')
-        >>> browser.getControl(name='image-value').click((50,25))
-        >>> print browser.contents
-        <html>
-        ...
-        <em>50</em>
-        <em>25</em>
-        <input type="image" name="image-value" id="image-value"
-        src="zope3logo.gif" />
-        ...
-        </html>
+            >>> browser.open('http://localhost/@@/testbrowser/controls.html')
+            >>> browser.getControl(name='image-value').click((50,25))
+            >>> print browser.contents
+            <html>
+            ...
+            <em>50</em>
+            <em>25</em>
+            <input type="image" name="image-value" id="image-value"
+                   src="zope3logo.gif" />
+            ...
+            </html>
+        
+        
+        Pages Without Controls
+        ~~~~~~~~~~~~~~~~~~~~~~
+        
+        What would happen if we tried to look up a control on a page that has none?
+        
+            >>> browser.open('http://localhost/@@/testbrowser/simple.html')
+            >>> browser.getControl('anything')
+            Traceback (most recent call last):
+            ...
+            LookupError: label 'anything'
+            (there are no form items in the HTML)
         
         
         Forms
@@ -1180,51 +1214,51 @@ Description: .. contents::
         be used to do so.  The key value is the form's name or id.  If more than one
         form has the same name or id, the first one will be returned.
         
-        >>> browser.open('http://localhost/@@/testbrowser/forms.html')
-        >>> form = browser.getForm(name='one')
+            >>> browser.open('http://localhost/@@/testbrowser/forms.html')
+            >>> form = browser.getForm(name='one')
         
         Form instances conform to the IForm interface.
         
-        >>> verifyObject(interfaces.IForm, form)
-        True
+            >>> verifyObject(interfaces.IForm, form)
+            True
         
         The form exposes several attributes related to forms:
         
-        - The name of the form:
+          - The name of the form:
         
-        >>> form.name
-        'one'
+            >>> form.name
+            'one'
         
-        - The id of the form:
+          - The id of the form:
         
-        >>> form.id
-        '1'
+            >>> form.id
+            '1'
         
-        - The action (target URL) when the form is submitted:
+          - The action (target URL) when the form is submitted:
         
-        >>> form.action
-        'http://localhost/@@/testbrowser/forms.html'
+            >>> form.action
+            'http://localhost/@@/testbrowser/forms.html'
         
-        - The method (HTTP verb) used to transmit the form data:
+          - The method (HTTP verb) used to transmit the form data:
         
-        >>> form.method
-        'GET'
+            >>> form.method
+            'GET'
         
         Besides those attributes, you have also a couple of methods.  Like for the
         browser, you can get control objects, but limited to the current form...
         
-        >>> form.getControl(name='text-value')
-        <Control name='text-value' type='text'>
+            >>> form.getControl(name='text-value')
+            <Control name='text-value' type='text'>
         
         ...and submit the form.
         
-        >>> form.submit('Submit')
-        >>> print browser.contents
-        <html>
-        ...
-        <em>First Text</em>
-        ...
-        </html>
+            >>> form.submit('Submit')
+            >>> print browser.contents
+            <html>
+            ...
+            <em>First Text</em>
+            ...
+            </html>
         
         Submitting also works without specifying a control, as shown below, which is
         it's primary reason for existing in competition with the control submission
@@ -1234,66 +1268,66 @@ Description: .. contents::
         the `forms.html` template, we have four forms all having a text control named
         `text-value`.  Now, if I use the browser's `get` method,
         
-        >>> browser.getControl(name='text-value')
-        Traceback (most recent call last):
-        ...
-        AmbiguityError: name 'text-value' matches:
-        <TextControl(text-value=First Text)>
-        <TextControl(text-value=Second Text)>
-        <TextControl(text-value=Third Text)>
-        <TextControl(text-value=Fourth Text)>
-        >>> browser.getControl('Text Control')
-        Traceback (most recent call last):
-        ...
-        AmbiguityError: label 'Text Control' matches:
-        <TextControl(text-value=Third Text)>
-        <TextControl(text-value=Fourth Text)>
+            >>> browser.getControl(name='text-value')
+            Traceback (most recent call last):
+            ...
+            AmbiguityError: name 'text-value' matches:
+              <TextControl(text-value=First Text)>
+              <TextControl(text-value=Second Text)>
+              <TextControl(text-value=Third Text)>
+              <TextControl(text-value=Fourth Text)>
+            >>> browser.getControl('Text Control')
+            Traceback (most recent call last):
+            ...
+            AmbiguityError: label 'Text Control' matches:
+              <TextControl(text-value=Third Text)>
+              <TextControl(text-value=Fourth Text)>
         
         I'll always get an ambiguous form field.  I can use the index argument, or
         with the `getForm` method I can disambiguate by searching only within a given
         form:
         
-        >>> form = browser.getForm('2')
-        >>> form.getControl(name='text-value').value
-        'Second Text'
-        >>> form.submit('Submit')
-        >>> browser.contents
-        '...<em>Second Text</em>...'
-        >>> form = browser.getForm('2')
-        >>> form.getControl('Submit').click()
-        >>> browser.contents
-        '...<em>Second Text</em>...'
-        >>> browser.getForm('3').getControl('Text Control').value
-        'Third Text'
+            >>> form = browser.getForm('2')
+            >>> form.getControl(name='text-value').value
+            'Second Text'
+            >>> form.submit('Submit')
+            >>> browser.contents
+            '...<em>Second Text</em>...'
+            >>> form = browser.getForm('2')
+            >>> form.getControl('Submit').click()
+            >>> browser.contents
+            '...<em>Second Text</em>...'
+            >>> browser.getForm('3').getControl('Text Control').value
+            'Third Text'
         
         The last form on the page does not have a name, an id, or a submit button.
         Working with it is still easy, thanks to a index attribute that guarantees
         order.  (Forms without submit buttons are sometimes useful for JavaScript.)
         
-        >>> form = browser.getForm(index=3)
-        >>> form.submit()
-        >>> browser.contents
-        '...<em>Fourth Text</em>...<em>Submitted without the submit button.</em>...'
+            >>> form = browser.getForm(index=3)
+            >>> form.submit()
+            >>> browser.contents
+            '...<em>Fourth Text</em>...<em>Submitted without the submit button.</em>...'
         
         If a form is requested that does not exists, an exception will be raised.
         
-        >>> form = browser.getForm('does-not-exist')
-        Traceback (most recent call last):
-        LookupError
+            >>> form = browser.getForm('does-not-exist')
+            Traceback (most recent call last):
+            LookupError
         
         If the HTML page contains only one form, no arguments to `getForm` are
         needed:
         
-        >>> oneform = Browser(wsgi_app=wsgi_app)
-        >>> oneform.open('http://localhost/@@/testbrowser/oneform.html')
-        >>> form = oneform.getForm()
+            >>> oneform = Browser(wsgi_app=wsgi_app)
+            >>> oneform.open('http://localhost/@@/testbrowser/oneform.html')
+            >>> form = oneform.getForm()
         
         If the HTML page contains more than one form, `index` is needed to
         disambiguate if no other arguments are provided:
         
-        >>> browser.getForm()
-        Traceback (most recent call last):
-        ValueError: if no other arguments are given, index is required.
+            >>> browser.getForm()
+            Traceback (most recent call last):
+            ValueError: if no other arguments are given, index is required.
         
         
         Submitting a posts body directly
@@ -1305,50 +1339,50 @@ Description: .. contents::
         
         Let's visit a page that echos some interesting values from it's request:
         
-        >>> browser.open('http://localhost/echo.html')
-        >>> print browser.contents
-        HTTP_ACCEPT_LANGUAGE: en-US
-        HTTP_CONNECTION: close
-        HTTP_HOST: localhost
-        HTTP_USER_AGENT: Python-urllib/2.4
-        PATH_INFO: /echo.html
-        REQUEST_METHOD: GET
-        Body: ''
+            >>> browser.open('http://localhost/echo.html')
+            >>> print browser.contents
+            HTTP_ACCEPT_LANGUAGE: en-US
+            HTTP_CONNECTION: close
+            HTTP_HOST: localhost
+            HTTP_USER_AGENT: Python-urllib/2.4
+            PATH_INFO: /echo.html
+            REQUEST_METHOD: GET
+            Body: ''
         
         Now, we'll try a post.  The post method takes a URL, a data string,
         and an optional content type.  If we just pass a string, then
         a URL-encoded query string is assumed:
         
-        >>> browser.post('http://localhost/echo.html', 'x=1&y=2')
-        >>> print browser.contents
-        CONTENT_LENGTH: 7
-        CONTENT_TYPE: application/x-www-form-urlencoded
-        HTTP_ACCEPT_LANGUAGE: en-US
-        HTTP_CONNECTION: close
-        HTTP_HOST: localhost
-        HTTP_USER_AGENT: Python-urllib/2.4
-        PATH_INFO: /echo.html
-        REQUEST_METHOD: POST
-        x: 1
-        y: 2
-        Body: ''
+            >>> browser.post('http://localhost/echo.html', 'x=1&y=2')
+            >>> print browser.contents
+            CONTENT_LENGTH: 7
+            CONTENT_TYPE: application/x-www-form-urlencoded
+            HTTP_ACCEPT_LANGUAGE: en-US
+            HTTP_CONNECTION: close
+            HTTP_HOST: localhost
+            HTTP_USER_AGENT: Python-urllib/2.4
+            PATH_INFO: /echo.html
+            REQUEST_METHOD: POST
+            x: 1
+            y: 2
+            Body: ''
         
         The body is empty because it is consumed to get form data.
         
         We can pass a content-type explicitly:
         
-        >>> browser.post('http://localhost/echo.html',
-        ...              '{"x":1,"y":2}', 'application/x-javascript')
-        >>> print browser.contents
-        CONTENT_LENGTH: 13
-        CONTENT_TYPE: application/x-javascript
-        HTTP_ACCEPT_LANGUAGE: en-US
-        HTTP_CONNECTION: close
-        HTTP_HOST: localhost
-        HTTP_USER_AGENT: Python-urllib/2.4
-        PATH_INFO: /echo.html
-        REQUEST_METHOD: POST
-        Body: '{"x":1,"y":2}'
+            >>> browser.post('http://localhost/echo.html',
+            ...              '{"x":1,"y":2}', 'application/x-javascript')
+            >>> print browser.contents
+            CONTENT_LENGTH: 13
+            CONTENT_TYPE: application/x-javascript
+            HTTP_ACCEPT_LANGUAGE: en-US
+            HTTP_CONNECTION: close
+            HTTP_HOST: localhost
+            HTTP_USER_AGENT: Python-urllib/2.4
+            PATH_INFO: /echo.html
+            REQUEST_METHOD: POST
+            Body: '{"x":1,"y":2}'
         
         Here, the body is left in place because it isn't form data.
         
@@ -1361,11 +1395,11 @@ Description: .. contents::
         Be very careful using raw seconds, cross-machine differences can be huge,
         pystones is usually a better choice.
         
-        >>> browser.open('http://localhost/@@/testbrowser/simple.html')
-        >>> browser.lastRequestSeconds < 10 # really big number for safety
-        True
-        >>> browser.lastRequestPystones < 10000 # really big number for safety
-        True
+            >>> browser.open('http://localhost/@@/testbrowser/simple.html')
+            >>> browser.lastRequestSeconds < 10 # really big number for safety
+            True
+            >>> browser.lastRequestPystones < 10000 # really big number for safety
+            True
         
         
         Handling Errors
@@ -1374,10 +1408,10 @@ Description: .. contents::
         Often WSGI middleware or the application itself gracefully handle application
         errors, such as invalid URLs:
         
-        >>> browser.open('http://localhost/invalid')
-        Traceback (most recent call last):
-        ...
-        HTTPError: HTTP Error 404: Not Found
+            >>> browser.open('http://localhost/invalid')
+            Traceback (most recent call last):
+            ...
+            HTTPError: HTTP Error 404: Not Found
         
         Note that the above error was thrown by ``mechanize`` and not by the
         application.  For debugging purposes, however, it can be very useful to see the
@@ -1385,24 +1419,24 @@ Description: .. contents::
         ``handleErrors`` property of the browser to ``False``.  It is defaulted to
         ``True``:
         
-        >>> browser.handleErrors
-        True
+            >>> browser.handleErrors
+            True
         
         So when we tell the application not to handle the errors,
         
-        >>> browser.handleErrors = False
+            >>> browser.handleErrors = False
         
         we get a different, internal error from the application:
         
-        >>> browser.open('http://localhost/invalid')
-        Traceback (most recent call last):
-        ...
-        NotFound: /invalid
+            >>> browser.open('http://localhost/invalid')
+            Traceback (most recent call last):
+            ...
+            NotFound: /invalid
         
         NB: Setting the handleErrors attribute to False will only change anything if
-        the WSGI application obeys the wsgi.handleErrors or paste.throw_errors
-        WSGI environment variables. i.e. it does not catch and handle the original
-        exception when these are set appropriately.
+            the WSGI application obeys the wsgi.handleErrors or paste.throw_errors
+            WSGI environment variables. i.e. it does not catch and handle the original
+            exception when these are set appropriately.
         
         When the testbrowser is raising HttpErrors, the errors still hit the test.
         Sometimes we don't want that to happen, in situations where there are edge
@@ -1411,28 +1445,28 @@ Description: .. contents::
         
         To get around this, one can set the raiseHttpErrors to False.
         
-        >>> browser.handleErrors = True
-        >>> browser.raiseHttpErrors = False
+            >>> browser.handleErrors = True
+            >>> browser.raiseHttpErrors = False
         
         This will cause HttpErrors not to propagate.
         
-        >>> browser.open('http://localhost/invalid')
+            >>> browser.open('http://localhost/invalid')
         
         The headers are still there, though.
         
-        >>> '404 Not Found' in str(browser.headers)
-        True
+            >>> '404 Not Found' in str(browser.headers)
+            True
         
         If we don't handle the errors, and allow internal ones to propagate, however,
         this flag doesn't affect things.
         
-        >>> browser.handleErrors = False
-        >>> browser.open('http://localhost/invalid')
-        Traceback (most recent call last):
-        ...
-        NotFound: /invalid
+            >>> browser.handleErrors = False
+            >>> browser.open('http://localhost/invalid')
+            Traceback (most recent call last):
+            ...
+            NotFound: /invalid
         
-        >>> browser.raiseHttpErrors = True
+            >>> browser.raiseHttpErrors = True
         
         
         Hand-Holding
@@ -1441,25 +1475,25 @@ Description: .. contents::
         Instances of the various objects ensure that users don't set incorrect
         instance attributes accidentally.
         
-        >>> browser.nonexistant = None
-        Traceback (most recent call last):
-        ...
-        AttributeError: 'Browser' object has no attribute 'nonexistant'
-        
-        >>> form.nonexistant = None
-        Traceback (most recent call last):
-        ...
-        AttributeError: 'Form' object has no attribute 'nonexistant'
-        
-        >>> control.nonexistant = None
-        Traceback (most recent call last):
-        ...
-        AttributeError: 'Control' object has no attribute 'nonexistant'
-        
-        >>> link.nonexistant = None
-        Traceback (most recent call last):
-        ...
-        AttributeError: 'Link' object has no attribute 'nonexistant'
+            >>> browser.nonexistant = None
+            Traceback (most recent call last):
+            ...
+            AttributeError: 'Browser' object has no attribute 'nonexistant'
+        
+            >>> form.nonexistant = None
+            Traceback (most recent call last):
+            ...
+            AttributeError: 'Form' object has no attribute 'nonexistant'
+        
+            >>> control.nonexistant = None
+            Traceback (most recent call last):
+            ...
+            AttributeError: 'Control' object has no attribute 'nonexistant'
+        
+            >>> link.nonexistant = None
+            Traceback (most recent call last):
+            ...
+            AttributeError: 'Link' object has no attribute 'nonexistant'
         
         
         HTTPS support
@@ -1468,13 +1502,13 @@ Description: .. contents::
         Depending on the scheme of the request the variable wsgi.url_scheme will be set
         correctly on the request:
         
-        >>> browser.open('http://localhost/echo_one.html?var=wsgi.url_scheme')
-        >>> print browser.contents
-        'http'
-        
-        >>> browser.open('https://localhost/echo_one.html?var=wsgi.url_scheme')
-        >>> print browser.contents
-        'https'
+            >>> browser.open('http://localhost/echo_one.html?var=wsgi.url_scheme')
+            >>> print browser.contents
+            'http'
+        
+            >>> browser.open('https://localhost/echo_one.html?var=wsgi.url_scheme')
+            >>> print browser.contents
+            'https'
         
         see http://www.python.org/dev/peps/pep-3333/ for details.
         
@@ -1483,6 +1517,41 @@ Description: .. contents::
         CHANGES
         =======
         
+        4.0.4 (2013-10-11)
+        ------------------
+        
+        - Removed the 'WebTest <= 1.3.4' version pin, fixed tests to work with modern
+          WebTest versions
+          (https://github.com/zopefoundation/zope.testbrowser/issues/10).
+        
+        
+        4.0.3 (2013-09-04)
+        ------------------
+        
+        - pinning version 'WebTest <= 1.3.4', because of some incompatibility and
+          test failures
+        
+        - Make zope.testbrowser installable via pip
+          (https://github.com/zopefoundation/zope.testbrowser/issues/6).
+        
+        - When ``Browser.handleErrors`` is False, also add ``x-wsgiorg.throw_errors``
+          to the environment. http://wsgi.org/wsgi/Specifications/throw_errors
+        
+        - Prevent WebTest from always sending ``paste.throw_errors=True`` in the
+          environment by setting it to ``None`` when ``Browser.handleErrors`` is
+          ``True``.  This makes it easier to test error pages.
+        
+        - Made Browser.submit() handle ``raiseHttpErrors``
+          (https://github.com/zopefoundation/zope.testbrowser/pull/4).
+        
+        - More friendly error messages from getControl() et al:
+        
+          - when you specify an index that is out of bounds, show the available
+            choices
+        
+          - when you fail to find anything, show all the available items
+        
+        
         4.0.2 (2011-05-25)
         ------------------
         
@@ -1493,7 +1562,7 @@ Description: .. contents::
         ------------------
         
         - Added a hint in documentation how to use ``zope.testbrowser.wsgi.Browser``
-        to test a Zope 2/Zope 3/Bluebream WSGI application.
+          to test a Zope 2/Zope 3/Bluebream WSGI application.
         
         4.0.0 (2011-03-14)
         ------------------
@@ -1501,15 +1570,15 @@ Description: .. contents::
         - LP #721252: AmbiguityError now shows all matching controls.
         
         - Integrate with WebTest. ``zope.testbrowser.wsgi.Browser`` is a
-        ``Browser`` implementation that uses ``webtest.TestApp`` to drive a WSGI
-        application. This this replaces the wsgi_intercept support added in 3.11.
+          ``Browser`` implementation that uses ``webtest.TestApp`` to drive a WSGI
+          application. This this replaces the wsgi_intercept support added in 3.11.
         
         - Re-write the test application as a pure WSGI application using WebOb. Run the
-        existing tests using the WebTest based Browser
+          existing tests using the WebTest based Browser
         
         - Move zope.app.testing based Browser into ``zope.app.testing`` (leaving
-        backwards compatibility imports in-place). Released in ``zope.app.testing``
-        3.9.0.
+          backwards compatibility imports in-place). Released in ``zope.app.testing``
+          3.9.0.
         
         
         3.11.1 (2011-01-24)
@@ -1528,7 +1597,7 @@ Description: .. contents::
         -------------------
         
         - Move the over-the-wire.txt doctest out of the TestBrowserLayer as it doesn't
-        need or use it.
+          need or use it.
         
         - Fix test compatibility with zope.app.testing 3.8.1.
         
@@ -1548,21 +1617,21 @@ Description: .. contents::
         -------------------
         
         - Fixed a bug that caused the ``Browser`` to keep it's previous ``contents``
-        The places are:
-        - Link.click()
-        - SubmitControl.click()
-        - ImageControl.click()
-        - Form.submit()
+          The places are:
+          - Link.click()
+          - SubmitControl.click()
+          - ImageControl.click()
+          - Form.submit()
         
         - Also adjusted exception messages at the above places to match
-        pre version 3.4.1 messages.
+          pre version 3.4.1 messages.
         
         
         3.10.0 (2010-09-14)
         -------------------
         
         - LP #98437: use mechanize's built-in ``submit()`` to submit forms, allowing
-        mechanize to set the "Referer:" (sic) header appropriately.
+          mechanize to set the "Referer:" (sic) header appropriately.
         
         - Fixed tests to run with ``zope.app.testing`` 3.8 and above.
         
@@ -1571,21 +1640,21 @@ Description: .. contents::
         ------------------
         
         - LP #568806: Update dependency ``mechanize >= 0.2.0``, which now includes
-        the ``ClientForm`` APIs.  Remove use of ``urllib2`` APIs (incompatible
-        with ``mechanize 0.2.0``) in favor of ``mechanize`` equivalents.
-        Thanks to John J. Lee for the patch.
+          the ``ClientForm`` APIs.  Remove use of ``urllib2`` APIs (incompatible
+          with ``mechanize 0.2.0``) in favor of ``mechanize`` equivalents.
+          Thanks to John J. Lee for the patch.
         
         - Use stdlib ``doctest`` module, instead of ``zope.testing.doctest``.
         
         - **Caution:** This version is no longer fully compatible with Python 2.4:
-        ``handleErrors = False`` no longer works.
+          ``handleErrors = False`` no longer works.
         
         
         3.8.1 (2010-04-19)
         ------------------
         
         - Pinned dependency on mechanize to prevent use of the upcoming
-        0.2.0 release before we have time to adjust to its API changes.
+          0.2.0 release before we have time to adjust to its API changes.
         
         - LP #98396: testbrowser resolves relative URLs incorrectly.
         
@@ -1600,25 +1669,25 @@ Description: .. contents::
         ------------------
         
         - Moved zope.app.testing dependency into the scope of the PublisherConnection
-        class. Zope2 specifies its own PublisherConnection which isn't dependent on
-        zope.app.testing.
+          class. Zope2 specifies its own PublisherConnection which isn't dependent on
+          zope.app.testing.
         
         - Fixed LP #419119: return None when the browser has no contents instead of
-        raising an exception.
+          raising an exception.
         
         
         3.7.0a1 (2009-08-29)
         --------------------
         
         - Remove dependency on zope.app.publisher in favor of zope.browserpage,
-        zope.browserresource and zope.ptresource.
+          zope.browserresource and zope.ptresource.
         
         - Remove dependencies on zope.app.principalannotation and zope.securitypolicy
-        by using the simple PermissiveSecurityPolicy. We aren't testing security
-        in our tests.
+          by using the simple PermissiveSecurityPolicy. We aren't testing security
+          in our tests.
         
         - Replaced the testing dependency on zope.app.zcmlfiles with explicit
-        dependencies of a minimal set of packages.
+          dependencies of a minimal set of packages.
         
         - Remove unneeded zope.app.authentication from ftesting.zcml.
         
@@ -1639,52 +1708,52 @@ Description: .. contents::
         - Author e-mail to zope-dev rather than zope3-dev.
         
         - New lines are no longer stripped in XML and HTML code contained in a
-        textarea; fix requires ClientForm >= 0.2.10 (LP #268139).
+          textarea; fix requires ClientForm >= 0.2.10 (LP #268139).
         
         - Added ``cookies`` attribute to browser for easy manipulation of browser
-        cookies.  See brief example in main documentation, plus new ``cookies.txt``
-        documentation.
+          cookies.  See brief example in main documentation, plus new ``cookies.txt``
+          documentation.
         
         
         3.5.1 (2008-10-10)
         ------------------
         
         - Provide a work around for a mechanize/urllib2 bug on Python 2.6
-        missing 'timeout' attribute on 'Request' base class.
+          missing 'timeout' attribute on 'Request' base class.
         
         - Provide a work around for a mechanize/urllib2 bug in creating request
-        objects that won't handle fragment URLs correctly.
+          objects that won't handle fragment URLs correctly.
         
         
         3.5.0 (2008-03-30)
         ------------------
         
         - Added a zope.testbrowser.testing.Browser.post method that allows
-        tests to supply a body and a content type.  This is handy for
-        testing Ajax requests with non-form input (e.g. JSON).
+          tests to supply a body and a content type.  This is handy for
+          testing Ajax requests with non-form input (e.g. JSON).
         
         - Remove vendor import of mechanize.
         
         - Fix bug that caused HTTP exception tracebacks to differ between version 3.4.0
-        and 3.4.1.
+          and 3.4.1.
         
         - Workaround for bug in Python Cookie.SimpleCookie when handling unicode
-        strings.
+          strings.
         
         - Fix bug introduced in 3.4.1 that created incompatible tracebacks in doctests.
-        This necessitated adding a patched mechanize to the source tree; patches have
-        been sent to the mechanize project.
+          This necessitated adding a patched mechanize to the source tree; patches have
+          been sent to the mechanize project.
         
         - Fix https://bugs.launchpad.net/bugs/149517 by adding zope.interface and
-        zope.schema as real dependencies
+          zope.schema as real dependencies
         
         - Fix browser.getLink documentation that was not updated since the last API
-        modification.
+          modification.
         
         - Move tests for fixed bugs to a separate file.
         
         - Removed non-functional and undocumented code intended to help test servers
-        using virtual hosting.
+          using virtual hosting.
         
         
         3.4.2 (2007-10-31)
@@ -1697,7 +1766,7 @@ Description: .. contents::
         ------------------
         
         * Updated to mechanize 0.1.7b and ClientForm 0.2.7.  These are now
-        pulled in via egg dependencies.
+          pulled in via egg dependencies.
         
         * ``zope.testbrowser`` now works on Python 2.5.
         
@@ -1706,10 +1775,10 @@ Description: .. contents::
         ------------------
         
         * Added the ability to suppress raising exceptions on HTTP errors
-        (``raiseHttpErrors`` attribute).
+          (``raiseHttpErrors`` attribute).
         
         * Made the tests more resilient to HTTP header formatting changes with
-        the REnormalizer.
+          the REnormalizer.
         
         
         3.4.0a1 (2007-04-22)
@@ -1722,6 +1791,8 @@ Platform: UNKNOWN
 Classifier: Environment :: Web Environment
 Classifier: Intended Audience :: Developers
 Classifier: License :: OSI Approved :: Zope Public License
-Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2.5
+Classifier: Programming Language :: Python :: 2.6
+Classifier: Programming Language :: Python :: 2.7
 Classifier: Topic :: Software Development :: Testing
 Classifier: Topic :: Internet :: WWW/HTTP
diff -pruN 4.0.2-1/src/zope.testbrowser.egg-info/requires.txt 4.0.4-0ubuntu1/src/zope.testbrowser.egg-info/requires.txt
--- 4.0.2-1/src/zope.testbrowser.egg-info/requires.txt	2011-05-25 07:17:51.000000000 +0000
+++ 4.0.4-0ubuntu1/src/zope.testbrowser.egg-info/requires.txt	2013-10-11 07:12:50.000000000 +0000
@@ -2,7 +2,7 @@ mechanize>=0.2.0
 setuptools
 zope.interface
 zope.schema
-pytz
+pytz > dev
 
 [test]
 zope.testing
diff -pruN 4.0.2-1/src/zope.testbrowser.egg-info/SOURCES.txt 4.0.4-0ubuntu1/src/zope.testbrowser.egg-info/SOURCES.txt
--- 4.0.2-1/src/zope.testbrowser.egg-info/SOURCES.txt	2011-05-25 07:17:51.000000000 +0000
+++ 4.0.4-0ubuntu1/src/zope.testbrowser.egg-info/SOURCES.txt	2013-10-11 07:12:50.000000000 +0000
@@ -1,10 +1,13 @@
-CHANGES.txt
-COPYRIGHT.txt
-LICENSE.txt
-README.txt
+.travis.yml
+CHANGES.rst
+COPYRIGHT.rst
+LICENSE.rst
+MANIFEST.in
+README.rst
 bootstrap.py
 buildout.cfg
 setup.py
+tox.ini
 src/zope/__init__.py
 src/zope.testbrowser.egg-info/PKG-INFO
 src/zope.testbrowser.egg-info/SOURCES.txt
diff -pruN 4.0.2-1/tox.ini 4.0.4-0ubuntu1/tox.ini
--- 4.0.2-1/tox.ini	1970-01-01 00:00:00.000000000 +0000
+++ 4.0.4-0ubuntu1/tox.ini	2013-10-11 07:12:28.000000000 +0000
@@ -0,0 +1,10 @@
+[tox]
+envlist =
+    py26,py27
+
+[testenv]
+deps =
+    zope.testing
+    WebTest
+commands =
+    python setup.py test -q
diff -pruN 4.0.2-1/.travis.yml 4.0.4-0ubuntu1/.travis.yml
--- 4.0.2-1/.travis.yml	1970-01-01 00:00:00.000000000 +0000
+++ 4.0.4-0ubuntu1/.travis.yml	2013-10-11 07:12:28.000000000 +0000
@@ -0,0 +1,10 @@
+language: python
+python:
+    - 2.6
+    - 2.7
+install:
+    - pip install tox
+script:
+    - tox -e py${TRAVIS_PYTHON_VERSION//[.]/}
+notifications:
+    email: false
