← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~cjwatson/launchpad:py3-registry-exception-modules into launchpad:master

 

Colin Watson has proposed merging ~cjwatson/launchpad:py3-registry-exception-modules into launchpad:master.

Commit message:
lp.registry: Use IGNORE_EXCEPTION_MODULE_IN_PYTHON2

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/+git/launchpad/+merge/397314

This allows doctests that test tracebacks to work on both Python 2 and 3.
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/launchpad:py3-registry-exception-modules into launchpad:master.
diff --git a/lib/lp/registry/browser/tests/gpg-views.txt b/lib/lp/registry/browser/tests/gpg-views.txt
index 0d77b02..b0c4828 100644
--- a/lib/lp/registry/browser/tests/gpg-views.txt
+++ b/lib/lp/registry/browser/tests/gpg-views.txt
@@ -131,8 +131,9 @@ validation.
 In some unknown way, the action sent to the form can be None (see bug 520476).
 
     >>> view = post_fingerprint(good_fingerprint, action=None)
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
      ...
-    UnexpectedFormData: Action not permitted: None
+    lp.app.errors.UnexpectedFormData: Action not permitted: None
 
     >>> tac.tearDown()
diff --git a/lib/lp/registry/browser/tests/product-views.txt b/lib/lp/registry/browser/tests/product-views.txt
index 486824c..f604e49 100644
--- a/lib/lp/registry/browser/tests/product-views.txt
+++ b/lib/lp/registry/browser/tests/product-views.txt
@@ -126,9 +126,10 @@ cannot access the page.
     >>> view = create_initialized_view(firefox, name='+index')
 
     >>> view = create_initialized_view(firefox, name='+review-license')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    Unauthorized: (<Product..., 'project_reviewed', 'launchpad.Moderate')
+    zope.security.interfaces.Unauthorized: (<Product..., 'project_reviewed', 'launchpad.Moderate')
 
 Mark is in the registry admins team and is allowed to access the page.
 
diff --git a/lib/lp/registry/browser/tests/team-views.txt b/lib/lp/registry/browser/tests/team-views.txt
index 553cd78..07232ef 100644
--- a/lib/lp/registry/browser/tests/team-views.txt
+++ b/lib/lp/registry/browser/tests/team-views.txt
@@ -61,10 +61,10 @@ Posting malformed data to the team home page raises an error.
     >>> team_home = getMultiAdapter(
     ...     (ubuntu_team, broken_request), name='+index')
     >>> team_home.initialize()
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    UnexpectedFormData: The mailing list form did not receive the expected
-    form fields.
+    lp.app.errors.UnexpectedFormData: The mailing list form did not receive the expected form fields.
 
 
 Contacting the team
diff --git a/lib/lp/registry/doc/announcement.txt b/lib/lp/registry/doc/announcement.txt
index c2ed962..399b4b5 100644
--- a/lib/lp/registry/doc/announcement.txt
+++ b/lib/lp/registry/doc/announcement.txt
@@ -182,17 +182,20 @@ modify() method.
 
     >>> login('mark@xxxxxxxxxxx')
     >>> kubuntu_release.title = 'Foo'
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
       ...
-    ForbiddenAttribute:...
+    zope.security.interfaces.ForbiddenAttribute: ...
     >>> kubuntu_release.summary = 'Foo'
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
       ...
-    ForbiddenAttribute:...
+    zope.security.interfaces.ForbiddenAttribute: ...
     >>> kubuntu_release.url = 'http://Foo.com/foo'
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
       ...
-    ForbiddenAttribute:...
+    zope.security.interfaces.ForbiddenAttribute: ...
     >>> print kubuntu_release.date_last_modified
     None
     >>> kubuntu_release.modify(title='Foo!', summary='Foo',
diff --git a/lib/lp/registry/doc/commercialsubscription.txt b/lib/lp/registry/doc/commercialsubscription.txt
index dffd2fa..4d0d070 100644
--- a/lib/lp/registry/doc/commercialsubscription.txt
+++ b/lib/lp/registry/doc/commercialsubscription.txt
@@ -360,9 +360,10 @@ No Privileges Person cannot access 'forReview'.
     >>> check_permission('launchpad.Moderate', product_set)
     False
     >>> gnome =  product_set.forReview(commercial_member, search_text='gnome')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    Unauthorized:... 'forReview', 'launchpad.Moderate'...
+    zope.security.interfaces.Unauthorized: ... 'forReview', 'launchpad.Moderate'...
 
 Members of the registry experts celebrity have permission to review
 IProduct and IProjectGroup objects and access an IProjectGroupSet.
diff --git a/lib/lp/registry/doc/distribution-mirror.txt b/lib/lp/registry/doc/distribution-mirror.txt
index aeb5b73..432dffc 100644
--- a/lib/lp/registry/doc/distribution-mirror.txt
+++ b/lib/lp/registry/doc/distribution-mirror.txt
@@ -876,9 +876,10 @@ Only mirrors which have never been probed can be deleted this way.
 
     >>> ignored = login_person(cdimage_mirror.owner)
     >>> cdimage_mirror.destroySelf()
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    Unauthorized: ...
+    zope.security.interfaces.Unauthorized: ...
 
     >>> login('karl@xxxxxxxxxxxxx')
     >>> cdimage_mirror.last_probe_record is not None
@@ -923,10 +924,10 @@ for the status, however, they may not change it:
     >>> de_archive_mirror.canTransitionToCountryMirror()
     True
     >>> de_archive_mirror.transitionToCountryMirror(True)
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    Unauthorized: (<DistributionMirror at ...>, 'transitionToCountryMirror',
-      'launchpad.Admin')
+    zope.security.interfaces.Unauthorized: (<DistributionMirror at ...>, 'transitionToCountryMirror', 'launchpad.Admin')
 
 Mirror listing administrators may change the status however:
 
@@ -965,10 +966,10 @@ There cannot be multiple country mirrors of one type for one country:
 
     >>> davis_station_archive.transitionToCountryMirror(True)
     >>> archive_mirror2.transitionToCountryMirror(True)
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    CountryMirrorAlreadySet: Antarctica already has a country Archive mirror
-      set.
+    lp.registry.errors.CountryMirrorAlreadySet: Antarctica already has a country Archive mirror set.
 
 Mirrors which have not been probed may not be marked as country mirrors:
 
@@ -978,10 +979,10 @@ Mirrors which have not been probed may not be marked as country mirrors:
     ...     official_candidate=True)
     >>> linux_au_mirror.status = MirrorStatus.OFFICIAL
     >>> linux_au_mirror.transitionToCountryMirror(True)
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    MirrorNotProbed: This mirror may not be set as a country mirror as it has
-      not been probed.
+    lp.registry.errors.MirrorNotProbed: This mirror may not be set as a country mirror as it has not been probed.
 
 Mirrors which are not official or do not have an HTTP URL may not be set as
 country mirrors:
@@ -999,14 +1000,14 @@ country mirrors:
     False
 
     >>> osuosl_mirror.transitionToCountryMirror(None)
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    NoneError: None isn't acceptable as a value for
-      DistributionMirror.country_dns_mirror
+    storm.exceptions.NoneError: None isn't acceptable as a value for DistributionMirror.country_dns_mirror
 
     >>> osuosl_mirror.transitionToCountryMirror(True)
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    MirrorHasNoHTTPURL: This mirror may not be set as a country mirror as it
-      does not have an HTTP URL set.
+    lp.registry.errors.MirrorHasNoHTTPURL: This mirror may not be set as a country mirror as it does not have an HTTP URL set.
     >>> logout()
diff --git a/lib/lp/registry/doc/distribution.txt b/lib/lp/registry/doc/distribution.txt
index e707908..22ed60f 100644
--- a/lib/lp/registry/doc/distribution.txt
+++ b/lib/lp/registry/doc/distribution.txt
@@ -304,9 +304,10 @@ Distribution can do that for us.
 
 If we ask for a totally unknown distroseries, we raise NotFoundError
     >>> ubuntu.getDistroSeriesAndPocket('unknown')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    NotFoundError: 'unknown'
+    lp.app.errors.NotFoundError: 'unknown'
 
 If we ask for a plain distroseries, it should come back with the RELEASE
 pocket as the pocket.
@@ -334,9 +335,10 @@ Find the backports pocket, too:
 If we ask for a valid distroseries which doesn't have a given pocket it should
 raise NotFoundError for us
     >>> ubuntu.getDistroSeriesAndPocket('hoary-bullshit')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    NotFoundError: 'hoary-bullshit'
+    lp.app.errors.NotFoundError: 'hoary-bullshit'
 
 
 Upload related stuff
@@ -463,14 +465,17 @@ But others can't.
 
     >>> login('no-priv@xxxxxxxxxxxxx')
     >>> debian.blueprints_usage = ServiceUsage.LAUNCHPAD
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
-    Unauthorized: (..., 'blueprints_usage', 'launchpad.Edit')
+    zope.security.interfaces.Unauthorized: (..., 'blueprints_usage', 'launchpad.Edit')
     >>> debian.official_malone = True
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
-    Unauthorized: (..., 'official_malone', 'launchpad.Edit')
+    zope.security.interfaces.Unauthorized: (..., 'official_malone', 'launchpad.Edit')
     >>> debian.translations_usage = ServiceUsage.LAUNCHPAD
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
-    Unauthorized: (..., 'translations_usage', 'launchpad.TranslationsAdmin')
+    zope.security.interfaces.Unauthorized: (..., 'translations_usage', 'launchpad.TranslationsAdmin')
 
 
 Specification Listings
@@ -584,9 +589,10 @@ Milestones for distros can only be created by distro owners or admins.
     >>> login('no-priv@xxxxxxxxxxxxx')
     >>> woody.newMilestone(
     ...     name='impossible', dateexpected=datetime(2028, 10, 1))
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    Unauthorized: (<DistroSeries u'woody'>, 'newMilestone', 'launchpad.Edit')
+    zope.security.interfaces.Unauthorized: (<DistroSeries u'woody'>, 'newMilestone', 'launchpad.Edit')
     >>> login('mark@xxxxxxxxxxx')
     >>> debian_milestone = woody.newMilestone(
     ...     name='woody-rc1', dateexpected=datetime(2028, 10, 1))
diff --git a/lib/lp/registry/doc/distroseries.txt b/lib/lp/registry/doc/distroseries.txt
index f19f972..a7deb34 100644
--- a/lib/lp/registry/doc/distroseries.txt
+++ b/lib/lp/registry/doc/distroseries.txt
@@ -675,9 +675,10 @@ Ubuntu driver can not create series.
     ...     name='finch', display_name='Finch', title='Ubuntu Finch',
     ...     summary='summary', description='description', version='9.06',
     ...     previous_series=warty, owner=ubuntu.driver)
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
      ...
-    Unauthorized: ...
+    zope.security.interfaces.Unauthorized: ...
 
 Owners and admins of base distributions are the only users who can create a
 series.
diff --git a/lib/lp/registry/doc/karmacache.txt b/lib/lp/registry/doc/karmacache.txt
index c408aaa..29f6d6f 100644
--- a/lib/lp/registry/doc/karmacache.txt
+++ b/lib/lp/registry/doc/karmacache.txt
@@ -52,6 +52,7 @@ NotFoundError.
 
     >>> karmacachemanager.updateKarmaValue(
     ...     new_value, person.id, bugs.id, product_id=9999)
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    NotFoundError: ...
+    lp.app.errors.NotFoundError: ...
diff --git a/lib/lp/registry/doc/launchpadlib/project-registry.txt.disabled b/lib/lp/registry/doc/launchpadlib/project-registry.txt.disabled
index 4c9922a..229d096 100644
--- a/lib/lp/registry/doc/launchpadlib/project-registry.txt.disabled
+++ b/lib/lp/registry/doc/launchpadlib/project-registry.txt.disabled
@@ -276,10 +276,10 @@ Attributes can be edited, but not by the anonymous user.
 
     >>> mark = lp_anon.people['mark']
     >>> firefox.driver = mark
-    >>> firefox.lp_save()
+    >>> firefox.lp_save()  # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
       ...
-    HTTPError: HTTP Error 401: Unauthorized...
+    urllib.error.HTTPError: HTTP Error 401: Unauthorized...
 
 A project administrator can modify attributes on the project.
 
@@ -328,12 +328,10 @@ Changing the owner of a project can change other attributes as well.
 Read-only attributes cannot be changed.
 
     >>> firefox.registrant = nopriv
-    >>> firefox.lp_save()
+    >>> firefox.lp_save()  # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
       ...
-    HTTPError: HTTP Error 400: Bad Request
-    ...
-    registrant_link: You tried to modify a read-only attribute...
+    urllib.error.HTTPError: HTTP Error 400: Bad Request ... registrant_link: You tried to modify a read-only attribute...
 
 "get_timeline" returns a list of dictionaries, corresponding to each
 milestone and release.
diff --git a/lib/lp/registry/doc/milestone.txt b/lib/lp/registry/doc/milestone.txt
index b413da9..e4fd929 100644
--- a/lib/lp/registry/doc/milestone.txt
+++ b/lib/lp/registry/doc/milestone.txt
@@ -127,10 +127,10 @@ Now, lets test all of that for DistroSeriess too!
 Trying to retrieve a milestone that doesn't exist will raise a
 zope.exceptions.NotFoundError:
 
-    >>> milestoneset.get(-1)
+    >>> milestoneset.get(-1)  # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
       ...
-    NotFoundError: 'Milestone with ID -1 does not exist'
+    lp.app.errors.NotFoundError: 'Milestone with ID -1 does not exist'
 
 
 ProjectGroup Milestones
diff --git a/lib/lp/registry/doc/person-account.txt b/lib/lp/registry/doc/person-account.txt
index 6e6fb00..9de6e28 100644
--- a/lib/lp/registry/doc/person-account.txt
+++ b/lib/lp/registry/doc/person-account.txt
@@ -33,9 +33,10 @@ the profile. Sample Person cannot claim it.
 
     >>> login('test@xxxxxxxxxxxxx')
     >>> matsubara.account.reactivate(comment="test")
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    Unauthorized: ...'launchpad.View')
+    zope.security.interfaces.Unauthorized: ...'launchpad.View')
 
 Matsubara can.
 
@@ -128,9 +129,10 @@ even launchpad admins can use it.
 
     >>> login('mark@xxxxxxxxxxx')
     >>> foobar.deactivate(comment=comment)
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    Unauthorized: ...'launchpad.Special')
+    zope.security.interfaces.Unauthorized: ...'launchpad.Special')
 
     >>> login('foo.bar@xxxxxxxxxxxxx')
     >>> foobar.deactivate(comment=comment)
diff --git a/lib/lp/registry/doc/person.txt b/lib/lp/registry/doc/person.txt
index 51e83da..8c772ec 100644
--- a/lib/lp/registry/doc/person.txt
+++ b/lib/lp/registry/doc/person.txt
@@ -351,14 +351,16 @@ Non-administrators may not change a person's standing.
 
     >>> login('test@xxxxxxxxxxxxx')
     >>> lifeless.personal_standing = PersonalStanding.POOR
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    Unauthorized: ...
+    zope.security.interfaces.Unauthorized: ...
 
     >>> lifeless.personal_standing_reason = 'Such a cool guy!'
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    Unauthorized: ...
+    zope.security.interfaces.Unauthorized: ...
 
     >>> login('foo.bar@xxxxxxxxxxxxx')
     >>> lifeless.personal_standing
@@ -434,9 +436,10 @@ If the given name is already in use by another team/person, an exception
 is raised.
 
     >>> personset.newTeam(ddaa, 'ddaa', 'Just a new team')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    NameAlreadyTaken:...
+    lp.registry.errors.NameAlreadyTaken: ...
 
 PersonSet.newTeam() will also fire an ObjectCreatedEvent for the newly
 created team.
@@ -524,9 +527,10 @@ account_status is NOACCOUNT, though.
     AssertionError: Only Person entries whose account_status is NOACCOUNT...
 
     >>> not_a_person.convertToTeam(team_owner=landscape_devs)
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    AlreadyConvertedException: foo-o has already been converted to a team.
+    lp.registry.model.person.AlreadyConvertedException: foo-o has already been converted to a team.
 
 
 Team members
@@ -1393,9 +1397,10 @@ error will be raised.
     >>> person_set._newPerson(
     ...     'new-name', 'New Person', True,
     ...     PersonCreationRationale.BUGIMPORT)
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
       ...
-    NameAlreadyTaken: The name 'new-name' is already taken.
+    lp.registry.errors.NameAlreadyTaken: The name 'new-name' is already taken.
 
 If the name passed to _newPerson() isn't valid an InvalidName error will
 be raised.
@@ -1403,9 +1408,10 @@ be raised.
     >>> person_set._newPerson(
     ...     "ThisIsn'tValid", 'New Person', True,
     ...     PersonCreationRationale.BUGIMPORT)
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
       ...
-    InvalidName: ThisIsn'tValid is not a valid name for a person.
+    lp.registry.errors.InvalidName: ThisIsn'tValid is not a valid name for a person.
 
 
 Probationary users
diff --git a/lib/lp/registry/doc/pillar-aliases-field.txt b/lib/lp/registry/doc/pillar-aliases-field.txt
index 66bf94b..f4b2128 100644
--- a/lib/lp/registry/doc/pillar-aliases-field.txt
+++ b/lib/lp/registry/doc/pillar-aliases-field.txt
@@ -28,9 +28,10 @@ to be a valid alias for that pillar, but only for that pillar.
     >>> bound_field.validate(u'iceweasel')
 
     >>> field.bind(getUtility(IProductSet)['bzr']).validate(u'iceweasel')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    LaunchpadValidationError: iceweasel is already used by another project
+    lp.app.validators.LaunchpadValidationError: iceweasel is already used by another project
 
 If existing aliases are not passed to the field's set() method, they will be
 removed.
@@ -52,25 +53,29 @@ If an empty string (or None) is given, all aliases will be removed.
 Each of these aliases must be valid names and must be unique.
 
     >>> bound_field.validate(u'names_cannot_have_underscores')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    LaunchpadValidationError: Invalid name...
+    lp.app.validators.LaunchpadValidationError: Invalid name...
 
     >>> bound_field.validate(u'ubuntu')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    LaunchpadValidationError: ubuntu is already used by another project
+    lp.app.validators.LaunchpadValidationError: ubuntu is already used by another project
 
 Also, they must not be identical to the pillar's own name.
 
     >>> bound_field.validate(firefox.name)
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    LaunchpadValidationError: This is your name: firefox
+    lp.app.validators.LaunchpadValidationError: This is your name: firefox
 
 Black-listed names are not accepted as aliases either.
 
     >>> bound_field.validate(u'blacklisted')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    LaunchpadValidationError: The name &#x27;blacklisted&#x27; has been blocked...
+    lp.app.validators.LaunchpadValidationError: The name &#x27;blacklisted&#x27; has been blocked...
diff --git a/lib/lp/registry/doc/pillar.txt b/lib/lp/registry/doc/pillar.txt
index cdf4c92..8beb155 100644
--- a/lib/lp/registry/doc/pillar.txt
+++ b/lib/lp/registry/doc/pillar.txt
@@ -35,9 +35,10 @@ share their name namespace are Product, ProjectGroup and Distribution.
     >>> 'fnord' in pillar_set
     False
     >>> pillar_set['fnord']
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    NotFoundError: 'fnord'
+    lp.app.errors.NotFoundError: 'fnord'
 
 Inactive projects/project groups are not available through PillarNameSet
 unless we use the special getByName() method which returns active/inactive
@@ -52,9 +53,10 @@ pillars.
     >>> 'gimp' in pillar_set
     False
     >>> pillar_set['gimp']
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    NotFoundError: 'gimp'
+    lp.app.errors.NotFoundError: 'gimp'
     >>> IProjectGroup.providedBy(pillar_set.getByName('gimp'))
     True
 
@@ -68,9 +70,10 @@ It also works if you use Unicode strings.
     >>> u'launchpad' in pillar_set
     False
     >>> pillar_set[u'launchpad']
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    NotFoundError: u'launchpad'
+    lp.app.errors.NotFoundError: u'launchpad'
     >>> IProduct.providedBy(pillar_set.getByName(u'launchpad'))
     True
 
@@ -126,13 +129,15 @@ Also, if the pillar is inactive, it can't be retrieved through any of its
 aliases, in the same way that it can't be retrieved through its name.
 
     >>> pillar_set['iceweasel']
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    NotFoundError:...
+    lp.app.errors.NotFoundError: ...
     >>> pillar_set['firefox']
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    NotFoundError:...
+    lp.app.errors.NotFoundError: ...
 
     # Make firefox active again, to not upset other tests.
     >>> firefox.active = True
@@ -148,9 +153,10 @@ to be able to set its aliases.
     >>> check_permission('launchpad.Edit', firefox)
     True
     >>> firefox.setAliases(['iceweasel'])
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    Unauthorized:...
+    zope.security.interfaces.Unauthorized: ...
 
 Ditto for the Mozilla project.
 
@@ -158,9 +164,10 @@ Ditto for the Mozilla project.
     >>> check_permission('launchpad.Edit', mozilla)
     True
     >>> mozilla.setAliases(['moz'])
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    Unauthorized:...
+    zope.security.interfaces.Unauthorized: ...
 
 And the same is true for Colin Watson on the Guadalinex distribution.
 
@@ -169,9 +176,10 @@ And the same is true for Colin Watson on the Guadalinex distribution.
     >>> check_permission('launchpad.Edit', guadalinex)
     True
     >>> guadalinex.setAliases(['guada'])
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    Unauthorized:...
+    zope.security.interfaces.Unauthorized: ...
 
     # Login as Mark again, to not upset remaining tests.
     >>> login('mark@xxxxxxxxxxx')
diff --git a/lib/lp/registry/doc/private-team-roles.txt b/lib/lp/registry/doc/private-team-roles.txt
index 9126eaa..6289052 100644
--- a/lib/lp/registry/doc/private-team-roles.txt
+++ b/lib/lp/registry/doc/private-team-roles.txt
@@ -195,11 +195,10 @@ or private, can be the project registrant.
     >>> product = factory.makeProduct(registrant=team_owner)
     >>> product = factory.makeProduct(registrant=public_team)
     >>> product = factory.makeProduct(registrant=priv_team)
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    PrivatePersonLinkageError: Cannot link person
-    (name=private-team, visibility=PRIVATE) to
-    <Product at...
+    lp.registry.errors.PrivatePersonLinkageError: Cannot link person (name=private-team, visibility=PRIVATE) to <Product at...
 
 
 Maintainer/Owner
diff --git a/lib/lp/registry/doc/productrelease-file-download.txt b/lib/lp/registry/doc/productrelease-file-download.txt
index 067e5a7..b8939cf 100644
--- a/lib/lp/registry/doc/productrelease-file-download.txt
+++ b/lib/lp/registry/doc/productrelease-file-download.txt
@@ -90,9 +90,10 @@ The alias can be retrieved by name.
 Attempting to retrieve an alias that does not exist is an error.
 
     >>> file_alias = rel.getFileAliasByName('bar.txt')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    NotFoundError: 'bar.txt'
+    lp.app.errors.NotFoundError: 'bar.txt'
 
 The ProductReleaseFile can also be retrieved by name.
 
@@ -104,9 +105,10 @@ Attempting to retrieve a ProductReleaseFile  that does not exist is an
 error.
 
     >>> prf = rel.getProductReleaseFileByName('bar.txt')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    NotFoundError: 'bar.txt'
+    lp.app.errors.NotFoundError: 'bar.txt'
 
 Deleting the release file results in the file count being reduced.
 Only the product owner, product series owner, admins, or experts can
@@ -114,9 +116,10 @@ delete a product file.
 
     >>> login(ANONYMOUS)
     >>> rel.files[0].destroySelf()
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    Unauthorized: (<ProductReleaseFile...>, 'destroySelf', 'launchpad.Edit')
+    zope.security.interfaces.Unauthorized: (<ProductReleaseFile...>, 'destroySelf', 'launchpad.Edit')
     >>> login('foo.bar@xxxxxxxxxxxxx')
     >>> for release_file in rel.files:
     ...     if release_file.libraryfile.id == file_alias.id:
@@ -140,9 +143,10 @@ Deleting files via a GET method is not allowed.
     >>> firefox = getUtility(IProductSet).getByName('firefox')
     >>> view = getMultiAdapter((firefox,request), name='+download')
     >>> view.initialize()
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
      ...
-    UnsafeFormGetSubmissionError: Delete Files
+    lp.services.webapp.interfaces.UnsafeFormGetSubmissionError: Delete Files
 
 There a convenience method for getting all of the releases for a list
 of series.  The releases are returned sorted by release date in
@@ -192,9 +196,10 @@ Only the product owner can create a new release.
     >>> owner_email = firefox.owner.preferredemail.email
     >>> login(ANONYMOUS)
     >>> trunk.milestones[0].createProductRelease(firefox.owner, now)
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    Unauthorized: (<Milestone ...>, 'createProductRelease', 'launchpad.Edit')
+    zope.security.interfaces.Unauthorized: (<Milestone ...>, 'createProductRelease', 'launchpad.Edit')
     >>> login(owner_email)
     >>> milestone = trunk.newMilestone('8.0', code_name='ralph')
     >>> milestone.createProductRelease(firefox.owner, now,
diff --git a/lib/lp/registry/doc/productseries.txt b/lib/lp/registry/doc/productseries.txt
index 009fad6..e9b90f9 100644
--- a/lib/lp/registry/doc/productseries.txt
+++ b/lib/lp/registry/doc/productseries.txt
@@ -69,9 +69,10 @@ owner or driver can call Product.newSeries().
     >>> series_driver = factory.makePerson(name="driver")
     >>> summary = "Port of Firefox to the Emacs operating system."
     >>> emacs = firefox.newSeries(series_driver , 'emacs', summary)
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    Unauthorized: (..., 'newSeries', 'launchpad.Driver')
+    zope.security.interfaces.Unauthorized: (..., 'newSeries', 'launchpad.Driver')
 
     >>> ignored = login_person(firefox.owner)
     >>> emacs_series = firefox.newSeries(
@@ -162,21 +163,24 @@ that the url uses one of the supported schemes (ftp, http, http).
 Invalid URLs and unsupported schemes raise a LaunchpadValidationError.
 
     >>> validate_release_glob('ftp.gnu.org/gnu/emacs/emacs-21.*.gz')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
      ...
-    LaunchpadValidationError: ...
+    lp.app.validators.LaunchpadValidationError: ...
 
     >>> validate_release_glob('wais://ftp.gnu.org/gnu/emacs/emacs-21.*.gz')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
      ...
-    LaunchpadValidationError: ...
+    lp.app.validators.LaunchpadValidationError: ...
 
 The URL must contain a glob (*) or , and may contain more than one.
 
     >>> validate_release_glob('http://ftp.gnu.org/gnu/emacs/emacs-21.10.1.gz')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
      ...
-    LaunchpadValidationError: ...
+    lp.app.validators.LaunchpadValidationError: ...
 
     >>> validate_release_glob('http://ftp.gnu.org/gnu/*/emacs-21.*.gz')
     True
diff --git a/lib/lp/registry/doc/projectgroup.txt b/lib/lp/registry/doc/projectgroup.txt
index 4831114..fcbe5d1 100644
--- a/lib/lp/registry/doc/projectgroup.txt
+++ b/lib/lp/registry/doc/projectgroup.txt
@@ -78,9 +78,10 @@ If there is no project with the specified name, a NotFoundError will be
 raised.
 
     >>> projectset['non-existant']
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    NotFoundError:...
+    lp.app.errors.NotFoundError: ...
 
 The same will happen if we set a product to be inactive. This is a good
 way of hiding bogus projects, without actually deleting them from the
@@ -94,9 +95,10 @@ the project.
 
 
     >>> gnome = getUtility(IProjectGroupSet)['gnome']
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    NotFoundError:...
+    lp.app.errors.NotFoundError: ...
 
 The inactive project will still be accessible using
 IProjectGroupSet.getByName(), though.
diff --git a/lib/lp/registry/doc/teammembership-email-notification.txt b/lib/lp/registry/doc/teammembership-email-notification.txt
index 201cc48..c73533c 100644
--- a/lib/lp/registry/doc/teammembership-email-notification.txt
+++ b/lib/lp/registry/doc/teammembership-email-notification.txt
@@ -1017,8 +1017,9 @@ membership statues silently.
 
     >>> setStatus(stevea_ubuntu_team_membership,
     ...     TeamMembershipStatus.DEACTIVATED, reviewer=kamion, silent=True)
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
-    UserCannotChangeMembershipSilently: ...
+    lp.registry.errors.UserCannotChangeMembershipSilently: ...
 
     >>> print stevea_ubuntu_team_membership.status.title
     Approved
diff --git a/lib/lp/registry/doc/teammembership.txt b/lib/lp/registry/doc/teammembership.txt
index 6592f15..8dbeb5e 100644
--- a/lib/lp/registry/doc/teammembership.txt
+++ b/lib/lp/registry/doc/teammembership.txt
@@ -94,9 +94,10 @@ given team.
     True
     >>> ignored = login_person(salgado)
     >>> salgado.join(launchpad)
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    JoinNotAllowed: This is a restricted team
+    lp.registry.errors.JoinNotAllowed: This is a restricted team
 
     >>> salgado.join(t3)
     >>> salgado in t3.activemembers
@@ -146,9 +147,10 @@ to a team.
     >>> mark = personset.getByName('mark')
     >>> t3.addMember(salgado, reviewer=mark,
     ...     status=TeamMembershipStatus.ADMIN)
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    Unauthorized:...
+    zope.security.interfaces.Unauthorized: ...
 
     # Log in as the team owner.
     >>> ignored = login_person(t3.teamowner)
@@ -452,14 +454,16 @@ an extra check to ensure that doesn't happen.
     >>> ignored = login_person(foobar)
     >>> membership = foobar.team_memberships[0]
     >>> membership.status = None
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    ForbiddenAttribute: ...
+    zope.security.interfaces.ForbiddenAttribute: ...
 
     >>> membership.dateexpires = None
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    ForbiddenAttribute: ...
+    zope.security.interfaces.ForbiddenAttribute: ...
 
 Foo Bar asked to join Warty Security Team on 2006-01-26 and they've been doing
 good work, so we'll approve their membership.
@@ -662,9 +666,10 @@ The member themselves can't change the expiration date of their membership.
 
     >>> ignored = login_person(karl)
     >>> karl_on_mirroradmins.setExpirationDate(tomorrow, karl)
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    Unauthorized: ...
+    zope.security.interfaces.Unauthorized: ...
 
 Only a team admin can.
 
diff --git a/lib/lp/registry/doc/vocabularies.txt b/lib/lp/registry/doc/vocabularies.txt
index 2ce7469..c1e6185 100644
--- a/lib/lp/registry/doc/vocabularies.txt
+++ b/lib/lp/registry/doc/vocabularies.txt
@@ -139,9 +139,10 @@ display name.
 You cannot get a term by an other object, such as a team.
 
     >>> list_vocabulary.getTerm(team_one)
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    ForbiddenAttribute: ...
+    zope.security.interfaces.ForbiddenAttribute: ...
 
 Given a token, we can get back the term.
 
@@ -171,9 +172,10 @@ You are not allowed to ask whether a non-mailing list object is
 contained in this vocabulary.
 
     >>> team_three in list_vocabulary
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    ForbiddenAttribute: ...
+    zope.security.interfaces.ForbiddenAttribute: ...
 
 Non-ACTIVE mailing lists are also not contained in the vocabulary.
 
diff --git a/lib/lp/registry/model/projectgroup.py b/lib/lp/registry/model/projectgroup.py
index bc6c5e5..d668bf4 100644
--- a/lib/lp/registry/model/projectgroup.py
+++ b/lib/lp/registry/model/projectgroup.py
@@ -539,9 +539,10 @@ class ProjectGroupSet:
         >>> getUtility(IProjectGroupSet).get(1).name
         u'apache'
         >>> getUtility(IProjectGroupSet).get(-1)
+        ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
         Traceback (most recent call last):
         ...
-        NotFoundError: -1
+        lp.app.errors.NotFoundError: -1
         """
         try:
             projectgroup = ProjectGroup.get(projectgroupid)
diff --git a/lib/lp/registry/stories/announcements/xx-announcements.txt b/lib/lp/registry/stories/announcements/xx-announcements.txt
index 64fe2e8..082256a 100644
--- a/lib/lp/registry/stories/announcements/xx-announcements.txt
+++ b/lib/lp/registry/stories/announcements/xx-announcements.txt
@@ -42,27 +42,31 @@ page.
 
     >>> anon_browser.open('http://launchpad.test/firefox')
     >>> anon_browser.getLink('Make announcement')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    LinkNotFoundError
+    zope.testbrowser.browser.LinkNotFoundError
 
     >>> anon_browser.getLink('Read all announcements').click()
     >>> anon_browser.getLink('Make announcement')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    LinkNotFoundError
+    zope.testbrowser.browser.LinkNotFoundError
 
     >>> anon_browser.open('http://launchpad.test/ubuntu')
     >>> anon_browser.getLink('Make announcement')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    LinkNotFoundError
+    zope.testbrowser.browser.LinkNotFoundError
 
     >>> anon_browser.getLink('Read all announcements').click()
     >>> anon_browser.getLink('Make announcement')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    LinkNotFoundError
+    zope.testbrowser.browser.LinkNotFoundError
 
 
 Logged in users can only see it if they have launchpad.Edit on the
@@ -71,15 +75,17 @@ pillar.
     >>> nopriv_browser = setupBrowser(auth="Basic no-priv@xxxxxxxxxxxxx:test")
     >>> nopriv_browser.open('http://launchpad.test/firefox')
     >>> nopriv_browser.getLink('Make announcement')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    LinkNotFoundError
+    zope.testbrowser.browser.LinkNotFoundError
 
     >>> nopriv_browser.getLink('Read all announcements').click()
     >>> nopriv_browser.getLink('Make announcement')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    LinkNotFoundError
+    zope.testbrowser.browser.LinkNotFoundError
 
     >>> priv_browser = setupBrowser(auth="Basic mark@xxxxxxxxxxx:test")
     >>> priv_browser.open('http://launchpad.test/ubuntu')
@@ -254,9 +260,10 @@ anon_browser.
     >>> priv_browser.getLink('Kubuntu announcement headline').click()
     >>> link_url = priv_browser.url
     >>> anon_browser.open(link_url)
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    Unauthorized: ...
+    zope.security.interfaces.Unauthorized: ...
 
 We will show that the anonymous user can see an announcement that was
 published:
diff --git a/lib/lp/registry/stories/distribution/xx-distribution-launchpad-usage.txt b/lib/lp/registry/stories/distribution/xx-distribution-launchpad-usage.txt
index f8dc2c9..891faff 100644
--- a/lib/lp/registry/stories/distribution/xx-distribution-launchpad-usage.txt
+++ b/lib/lp/registry/stories/distribution/xx-distribution-launchpad-usage.txt
@@ -9,17 +9,20 @@ details.
 
     >>> user_browser.open('http://launchpad.test/ubuntu')
     >>> user_browser.getLink('Change details')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
       ...
-    LinkNotFound...
+    zope.testbrowser.browser.LinkNotFoundError
     >>> user_browser.getLink('Configure publisher')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
       ...
-    LinkNotFound...
+    zope.testbrowser.browser.LinkNotFoundError
     >>> user_browser.open('http://launchpad.test/ubuntu/+edit')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
       ...
-    Unauthorized...
+    zope.security.interfaces.Unauthorized: ...
 
 Create a restricted processor.
 
diff --git a/lib/lp/registry/stories/distribution/xx-distribution-overview.txt b/lib/lp/registry/stories/distribution/xx-distribution-overview.txt
index cb14899..8fb8a3a 100644
--- a/lib/lp/registry/stories/distribution/xx-distribution-overview.txt
+++ b/lib/lp/registry/stories/distribution/xx-distribution-overview.txt
@@ -124,9 +124,10 @@ If there is a development series alias, it becomes a redirect.
     >>> from zope.component import getUtility
 
     >>> anon_browser.open("http://launchpad.test/ubuntu/devel";)
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    NotFound: Object: <Distribution ...>, name: u'devel'
+    zope.publisher.interfaces.NotFound: Object: <Distribution ...>, name: u'devel'
 
     >>> with celebrity_logged_in("admin"):
     ...     ubuntu = getUtility(IDistributionSet).getByName(u"ubuntu")
@@ -176,11 +177,13 @@ term) or an unavailable name (only 'primary' and 'partner' exist)
 results in a NotFound error.
 
     >>> anon_browser.open("http://launchpad.test/ubuntu/+archive";)
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    NotFound: Object: <Distribution ...>, name: u'+archive'
+    zope.publisher.interfaces.NotFound: Object: <Distribution ...>, name: u'+archive'
 
     >>> anon_browser.open("http://launchpad.test/ubuntu/+archive/boing";)
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    NotFound: Object: <Distribution ...>, name: u'boing'
+    zope.publisher.interfaces.NotFound: Object: <Distribution ...>, name: u'boing'
diff --git a/lib/lp/registry/stories/distributionmirror/xx-distribution-countrymirrors.txt b/lib/lp/registry/stories/distributionmirror/xx-distribution-countrymirrors.txt
index b81fa99..5b0c870 100644
--- a/lib/lp/registry/stories/distributionmirror/xx-distribution-countrymirrors.txt
+++ b/lib/lp/registry/stories/distributionmirror/xx-distribution-countrymirrors.txt
@@ -60,7 +60,8 @@ Also, the +countrymirrors-archive page is only available for the Ubuntu
 distribution.
 
     >>> browser.open('http://launchpad.test/debian/+countrymirrors-archive')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    HTTPError: HTTP Error 404: Not Found
+    urllib.error.HTTPError: HTTP Error 404: Not Found
 
diff --git a/lib/lp/registry/stories/distributionmirror/xx-distribution-mirrors.txt b/lib/lp/registry/stories/distributionmirror/xx-distribution-mirrors.txt
index 23a661f..0b32846 100644
--- a/lib/lp/registry/stories/distributionmirror/xx-distribution-mirrors.txt
+++ b/lib/lp/registry/stories/distributionmirror/xx-distribution-mirrors.txt
@@ -80,9 +80,10 @@ they were out of date, missing some content, etc). This list can only be
 seen by distro owners, mirror admins of the distro or launchpad admins.
 
     >>> user_browser.open('http://launchpad.test/ubuntu/+disabledmirrors')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    Unauthorized: ...
+    zope.security.interfaces.Unauthorized: ...
 
     >>> browser = setupBrowser(auth='Basic karl@xxxxxxxxxxxxx:test')
     >>> browser.open('http://launchpad.test/ubuntu/+disabledmirrors')
@@ -102,9 +103,10 @@ It's only visible to distro owners, mirror admins of the distro or
 launchpad admins.
 
     >>> user_browser.open('http://launchpad.test/ubuntu/+unofficialmirrors')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    Unauthorized: ...
+    zope.security.interfaces.Unauthorized: ...
 
     >>> browser = setupBrowser(auth='Basic karl@xxxxxxxxxxxxx:test')
     >>> browser.open('http://launchpad.test/ubuntu/+unofficialmirrors')
@@ -124,9 +126,10 @@ type of mirror is shown.  Also the freshness is not visible since
 pending mirrors have never been probed.
 
     >>> user_browser.open('http://launchpad.test/ubuntu/+pendingreviewmirrors')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    Unauthorized: ...
+    zope.security.interfaces.Unauthorized: ...
 
     >>> browser = setupBrowser(auth='Basic karl@xxxxxxxxxxxxx:test')
     >>> # Register an unreviewed archive mirror.
diff --git a/lib/lp/registry/stories/distributionmirror/xx-distributionmirror-prober-logs.txt b/lib/lp/registry/stories/distributionmirror/xx-distributionmirror-prober-logs.txt
index b87badf..0b5313b 100644
--- a/lib/lp/registry/stories/distributionmirror/xx-distributionmirror-prober-logs.txt
+++ b/lib/lp/registry/stories/distributionmirror/xx-distributionmirror-prober-logs.txt
@@ -22,13 +22,15 @@ A random logged in user won't have the rights to see that page.
     >>> user_browser.open(
     ...     'http://launchpad.test/ubuntu/+mirror/archive-mirror2/')
     >>> user_browser.getLink('Content check logs')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    LinkNotFoundError
+    zope.testbrowser.browser.LinkNotFoundError
 
     >>> user_browser.open(
     ...     'http://launchpad.test/ubuntu/+mirror/archive-mirror2/'
     ...     '+prober-logs')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    Unauthorized: ...
+    zope.security.interfaces.Unauthorized: ...
diff --git a/lib/lp/registry/stories/distributionmirror/xx-reassign-distributionmirror.txt b/lib/lp/registry/stories/distributionmirror/xx-reassign-distributionmirror.txt
index 28dd8d0..75a5cd3 100644
--- a/lib/lp/registry/stories/distributionmirror/xx-reassign-distributionmirror.txt
+++ b/lib/lp/registry/stories/distributionmirror/xx-reassign-distributionmirror.txt
@@ -15,13 +15,15 @@ permission on them. Mark is the owner of the archive-mirror.
 
     >>> user_browser.open(context_url)
     >>> user_browser.getLink('Change')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    LinkNotFoundError
+    zope.testbrowser.browser.LinkNotFoundError
     >>> user_browser.open(context_url + "/+reassign")
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    Unauthorized: ...
+    zope.security.interfaces.Unauthorized: ...
 
     >>> browser.addHeader('Authorization', 'Basic mark@xxxxxxxxxxx:test')
     >>> browser.open(context_url)
diff --git a/lib/lp/registry/stories/distroseries/distroseries-admin.txt b/lib/lp/registry/stories/distroseries/distroseries-admin.txt
index 33303f5..051256d 100644
--- a/lib/lp/registry/stories/distroseries/distroseries-admin.txt
+++ b/lib/lp/registry/stories/distroseries/distroseries-admin.txt
@@ -56,16 +56,18 @@ Registry experts do not have access to the 'Change details' link.
     ...     auth='Basic %s:test' % email)
     >>> registry_browser.open('http://launchpad.test/ubuntu/happy')
     >>> registry_browser.getLink('Change details').click()
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    LinkNotFoundError
+    zope.testbrowser.browser.LinkNotFoundError
 
 And navigating directly to +edit is thwarted.
 
     >>> registry_browser.open('http://launchpad.test/ubuntu/happy/+edit')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    Unauthorized...
+    zope.security.interfaces.Unauthorized: ...
 
 Registry experts do have access to the administration page.
 
diff --git a/lib/lp/registry/stories/distroseries/xx-show-distroseries-packaging.txt b/lib/lp/registry/stories/distroseries/xx-show-distroseries-packaging.txt
index 2ac0e42..d491f5c 100644
--- a/lib/lp/registry/stories/distroseries/xx-show-distroseries-packaging.txt
+++ b/lib/lp/registry/stories/distroseries/xx-show-distroseries-packaging.txt
@@ -38,9 +38,10 @@ is linked, but the link to this page is not enabled.
       url='http://launchpad.test/ubuntu/hoary/+needs-packaging'>
 
     >>> anon_browser.getLink('All upstream links')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
      ...
-    LinkNotFoundError
+    zope.testbrowser.browser.LinkNotFoundError
 
 The packaging links are batched so that users can view the thousands of
 links packages. Users can also hack the URL to set their own batch size.
@@ -117,6 +118,7 @@ linked, but the link to this page is not enabled.
       url='http://launchpad.test/ubuntu/hoary/+packaging'>
 
     >>> anon_browser.getLink('Needs upstream links')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
      ...
-    LinkNotFoundError
+    zope.testbrowser.browser.LinkNotFoundError
diff --git a/lib/lp/registry/stories/gpg-coc/xx-ubuntu-codeofconduct-signer.txt b/lib/lp/registry/stories/gpg-coc/xx-ubuntu-codeofconduct-signer.txt
index 58dc5c4..13cda85 100644
--- a/lib/lp/registry/stories/gpg-coc/xx-ubuntu-codeofconduct-signer.txt
+++ b/lib/lp/registry/stories/gpg-coc/xx-ubuntu-codeofconduct-signer.txt
@@ -25,9 +25,10 @@ A regular user can't see the link to Foo Bar's signed codes of conduct.
     Signed Ubuntu Code of Conduct: Yes
 
     >>> browser.getLink(url='+codesofconduct')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    LinkNotFoundError
+    zope.testbrowser.browser.LinkNotFoundError
 
 No-priv hasn't signed the Ubuntu Code of Conduct yet.  Their homepage has a
 link to the Ubuntu Code of Conduct forms.
diff --git a/lib/lp/registry/stories/location/personlocation-edit.txt b/lib/lp/registry/stories/location/personlocation-edit.txt
index a70877e..1ba5e62 100644
--- a/lib/lp/registry/stories/location/personlocation-edit.txt
+++ b/lib/lp/registry/stories/location/personlocation-edit.txt
@@ -13,9 +13,10 @@ A user cannot set another user's +editlocation page.
 
     >>> nopriv_browser = setupBrowser(auth="Basic no-priv@xxxxxxxxxxxxx:test")
     >>> nopriv_browser.open('http://launchpad.test/~zzz/+editlocation')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    Unauthorized:...
+    zope.security.interfaces.Unauthorized: ...
 
 A user can set their own time zone:
 
diff --git a/lib/lp/registry/stories/mailinglists/lifecycle.txt b/lib/lp/registry/stories/mailinglists/lifecycle.txt
index fccd031..56b4c65 100644
--- a/lib/lp/registry/stories/mailinglists/lifecycle.txt
+++ b/lib/lp/registry/stories/mailinglists/lifecycle.txt
@@ -230,25 +230,28 @@ Anonymous users cannot see the link, because they cannot even see the
 private team.
 
     >>> anon_browser.open('http://launchpad.test/~bassists')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    NotFound: Object: <...>, name: u'~bassists'
+    zope.publisher.interfaces.NotFound: Object: <...>, name: u'~bassists'
 
 The same is true for normal users who are not team members.
 
     >>> browser.open('http://launchpad.test/~bassists')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    NotFound: Object: <...>, name: u'~bassists'
+    zope.publisher.interfaces.NotFound: Object: <...>, name: u'~bassists'
 
 Members who are not owners can see the link.
 
     >>> cprov_browser = setupBrowser(
     ...     auth='Basic celso.providelo@xxxxxxxxxxxxx:test')
     >>> cprov_browser.open('http://launchpad.test/~bassists')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    NotFound: Object: <...>, name: u'~bassists'
+    zope.publisher.interfaces.NotFound: Object: <...>, name: u'~bassists'
 
     >>> admin_browser.open('http://launchpad.test/~bassists/+addmember')
     >>> admin_browser.getControl('New member').value = 'cprov'
diff --git a/lib/lp/registry/stories/mailinglists/subscriptions.txt b/lib/lp/registry/stories/mailinglists/subscriptions.txt
index 8d64413..5ab421c 100644
--- a/lib/lp/registry/stories/mailinglists/subscriptions.txt
+++ b/lib/lp/registry/stories/mailinglists/subscriptions.txt
@@ -377,14 +377,16 @@ does not show either link.
     Create a mailing list
 
     >>> carlos_browser.getLink('Subscribe')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    LinkNotFoundError
+    zope.testbrowser.browser.LinkNotFoundError
 
     >>> carlos_browser.getLink('Unsubscribe')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    LinkNotFoundError
+    zope.testbrowser.browser.LinkNotFoundError
 
 
 Team page subscribers link
diff --git a/lib/lp/registry/stories/milestone/xx-create-milestone-on-distribution.txt b/lib/lp/registry/stories/milestone/xx-create-milestone-on-distribution.txt
index d1a8eb1..d1028c7 100644
--- a/lib/lp/registry/stories/milestone/xx-create-milestone-on-distribution.txt
+++ b/lib/lp/registry/stories/milestone/xx-create-milestone-on-distribution.txt
@@ -4,9 +4,10 @@ Team (ubuntu-team).
 
     >>> name12_browser = setupBrowser(auth='Basic test@xxxxxxxxxxxxx:test')
     >>> name12_browser.open('http://launchpad.test/ubuntu/hoary/+addmilestone')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    Unauthorized: ...
+    zope.security.interfaces.Unauthorized: ...
 
 Let's make the sample user the owner of Ubuntu for this test. The owner
 of the distribution should be able to add milestones for it, of course!
diff --git a/lib/lp/registry/stories/milestone/xx-milestone-add-and-edit.txt b/lib/lp/registry/stories/milestone/xx-milestone-add-and-edit.txt
index 2a229aa..73a2270 100644
--- a/lib/lp/registry/stories/milestone/xx-milestone-add-and-edit.txt
+++ b/lib/lp/registry/stories/milestone/xx-milestone-add-and-edit.txt
@@ -29,24 +29,28 @@ see the link to add a milestone nor access the page directly.
 
     >>> user_browser.open('http://launchpad.test/alsa-utils/trunk')
     >>> user_browser.getLink('Create milestone').click()
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
       ...
-    LinkNotFoundError
+    zope.testbrowser.browser.LinkNotFoundError
     >>> user_browser.open(
     ...     'http://launchpad.test/alsa-utils/trunk/+addmilestone')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
       ...
-    Unauthorized: ...
+    zope.security.interfaces.Unauthorized: ...
 
     >>> user_browser.open('http://launchpad.test/ubuntu/hoary')
     >>> user_browser.getLink('Create milestone').click()
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
       ...
-    LinkNotFoundError
+    zope.testbrowser.browser.LinkNotFoundError
     >>> user_browser.open('http://launchpad.test/ubuntu/hoary/+addmilestone')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
       ...
-    Unauthorized: ...
+    zope.security.interfaces.Unauthorized: ...
 
 But Sample Person will be able to use the 'Create milestone' link to
 create a new milestone.
@@ -79,9 +83,10 @@ But we can't subscribe to project milestones, since they are not real objects.
 
     >>> user_browser.open('http://launchpad.test/mozilla/+milestone/1.0')
     >>> user_browser.getLink('Subscribe to bug mail')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    LinkNotFoundError
+    zope.testbrowser.browser.LinkNotFoundError
 
 
 == Deleting milestones ==
diff --git a/lib/lp/registry/stories/object/xx-object-branding.txt b/lib/lp/registry/stories/object/xx-object-branding.txt
index 5258cb0..da26295 100644
--- a/lib/lp/registry/stories/object/xx-object-branding.txt
+++ b/lib/lp/registry/stories/object/xx-object-branding.txt
@@ -17,9 +17,10 @@ Team branding
 
     >>> browser = setupBrowser(auth='Basic no-priv@xxxxxxxxxxxxx:test')
     >>> browser.open('http://launchpad.test/~ubuntu-team/+branding')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    Unauthorized:...
+    zope.security.interfaces.Unauthorized: ...
 
     >>> browser = setupBrowser(auth='Basic mark@xxxxxxxxxxx:test')
     >>> browser.open('http://launchpad.test/~ubuntu-team')
@@ -75,9 +76,10 @@ Distribution branding
 
     >>> browser = setupBrowser(auth='Basic no-priv@xxxxxxxxxxxxx:test')
     >>> browser.open('http://launchpad.test/kubuntu/+edit')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    Unauthorized:...
+    zope.security.interfaces.Unauthorized: ...
 
     >>> browser = setupBrowser(auth='Basic mark@xxxxxxxxxxx:test')
     >>> browser.open('http://launchpad.test/kubuntu')
@@ -131,9 +133,10 @@ ProjectGroup branding
 
     >>> browser = setupBrowser(auth='Basic no-priv@xxxxxxxxxxxxx:test')
     >>> browser.open('http://launchpad.test/mozilla/+branding')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    Unauthorized:...
+    zope.security.interfaces.Unauthorized: ...
 
     >>> browser = setupBrowser(auth='Basic mark@xxxxxxxxxxx:test')
     >>> browser.open('http://launchpad.test/mozilla')
@@ -189,9 +192,10 @@ Product branding
 
     >>> browser = setupBrowser(auth='Basic no-priv@xxxxxxxxxxxxx:test')
     >>> browser.open('http://launchpad.test/jokosher/+branding')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    Unauthorized:...
+    zope.security.interfaces.Unauthorized: ...
 
     >>> browser = setupBrowser(auth='Basic mark@xxxxxxxxxxx:test')
     >>> browser.open('http://launchpad.test/jokosher')
@@ -251,9 +255,10 @@ Again, for Sprints, we have not exposed icon editing through the UI.
 
     >>> browser = setupBrowser(auth='Basic no-priv@xxxxxxxxxxxxx:test')
     >>> browser.open('http://launchpad.test/sprints/futurista/+branding')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    Unauthorized:...
+    zope.security.interfaces.Unauthorized: ...
 
     >>> browser = setupBrowser(auth='Basic mark@xxxxxxxxxxx:test')
     >>> browser.open('http://launchpad.test/sprints/futurista')
diff --git a/lib/lp/registry/stories/person/xx-add-sshkey.txt b/lib/lp/registry/stories/person/xx-add-sshkey.txt
index afcb755..0e80b55 100644
--- a/lib/lp/registry/stories/person/xx-add-sshkey.txt
+++ b/lib/lp/registry/stories/person/xx-add-sshkey.txt
@@ -133,9 +133,10 @@ Launchpad administrators are not allowed to poke at other user's ssh keys.
     >>> logout()
     >>> admin_browser = setupBrowserFreshLogin(foo_bar)
     >>> admin_browser.open('http://launchpad.test/~salgado/+editsshkeys')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    Unauthorized: ...
+    zope.security.interfaces.Unauthorized: ...
 
 Salgado chooses to remove one of his ssh keys from Launchpad. The link
 to edit his keys is on the page.
@@ -172,8 +173,9 @@ belong to him, it will fail with an error message.
 
     >>> browser.getControl(name='key', index=0).value = '1'
     >>> browser.getControl('Remove', index=0).click()
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
-    UnexpectedFormData: ...
+    lp.app.errors.UnexpectedFormData: ...
 
 Similarly, it's not possible to remove an unspecified ssh key, although in
 this case we'll raise an UnexpectedFormData.
@@ -181,9 +183,10 @@ this case we'll raise an UnexpectedFormData.
     >>> browser.open('http://launchpad.test/~salgado/+editsshkeys')
     >>> browser.getControl(name='key', index=0).value = ''
     >>> browser.getControl('Remove', index=0).click()
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    UnexpectedFormData: ...
+    lp.app.errors.UnexpectedFormData: ...
 
 If he removes a key then he will get a security warning email notification
 that the key has been removed.
diff --git a/lib/lp/registry/stories/person/xx-admin-person-review.txt b/lib/lp/registry/stories/person/xx-admin-person-review.txt
index 58c7a88..bc2e2f6 100644
--- a/lib/lp/registry/stories/person/xx-admin-person-review.txt
+++ b/lib/lp/registry/stories/person/xx-admin-person-review.txt
@@ -30,9 +30,10 @@ Registry experts can't change the displayname.
     >>> expert_browser.getControl(
     ...     'Display Name', index=0).value = 'The one and only Salgado'
     >>> expert_browser.getControl('Change').click()
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    Unauthorized: ...
+    zope.security.interfaces.Unauthorized: ...
 
 But Launchpad admins can.
     >>> admin_browser.open('http://launchpad.test/~no-way/+review')
diff --git a/lib/lp/registry/stories/person/xx-adminpeoplemerge.txt b/lib/lp/registry/stories/person/xx-adminpeoplemerge.txt
index 69937cc..cf0b6b8 100644
--- a/lib/lp/registry/stories/person/xx-adminpeoplemerge.txt
+++ b/lib/lp/registry/stories/person/xx-adminpeoplemerge.txt
@@ -7,13 +7,15 @@ for merging people and another one for merging teams, which obviously
 are only accessible to LP admins.
 
     >>> user_browser.open('http://launchpad.test/people/+adminpeoplemerge')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    Unauthorized: ...
+    zope.security.interfaces.Unauthorized: ...
     >>> user_browser.open('http://launchpad.test/people/+adminteammerge')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    Unauthorized: ...
+    zope.security.interfaces.Unauthorized: ...
 
 When there are email addresses associated with the person/team being
 merged into another one, a notification is shown to inform the user
diff --git a/lib/lp/registry/stories/person/xx-approve-members.txt b/lib/lp/registry/stories/person/xx-approve-members.txt
index ee879bb..2dd23c6 100644
--- a/lib/lp/registry/stories/person/xx-approve-members.txt
+++ b/lib/lp/registry/stories/person/xx-approve-members.txt
@@ -72,6 +72,7 @@ as an inactive one.
 And now we see that there are no pending members left.
 
     >>> browser.getLink('Approve or decline members')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    LinkNotFoundError
+    zope.testbrowser.browser.LinkNotFoundError
diff --git a/lib/lp/registry/stories/person/xx-deactivate-account.txt b/lib/lp/registry/stories/person/xx-deactivate-account.txt
index 9b41ec4..dfef5de 100644
--- a/lib/lp/registry/stories/person/xx-deactivate-account.txt
+++ b/lib/lp/registry/stories/person/xx-deactivate-account.txt
@@ -84,7 +84,8 @@ the user themselves --not even Launchpad admins can do that on behalf of other
 people.
 
     >>> admin_browser.open('http://launchpad.test/~cprov/+deactivate-account')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    Unauthorized: ...
+    zope.security.interfaces.Unauthorized: ...
 
diff --git a/lib/lp/registry/stories/person/xx-person-claim-merge.txt b/lib/lp/registry/stories/person/xx-person-claim-merge.txt
index caad3d3..1c8462f 100644
--- a/lib/lp/registry/stories/person/xx-person-claim-merge.txt
+++ b/lib/lp/registry/stories/person/xx-person-claim-merge.txt
@@ -9,10 +9,10 @@ exception that will redirect the user to the login page.
     >>> link = anon_browser.getLink("Are you Diogo Matsubara?")
     >>> print(link.url)
     http://launchpad.test/people/+requestmerge?field.dupe_person=matsubara
-    >>> link.click()
+    >>> link.click()  # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    Unauthorized: ...
+    zope.security.interfaces.Unauthorized: ...
 
 
 Claiming a person while logged in
diff --git a/lib/lp/registry/stories/person/xx-person-home.txt b/lib/lp/registry/stories/person/xx-person-home.txt
index 8332a01..52f5348 100644
--- a/lib/lp/registry/stories/person/xx-person-home.txt
+++ b/lib/lp/registry/stories/person/xx-person-home.txt
@@ -80,9 +80,10 @@ However, when the user visits someone else's page, they see no such URL.
 And there is no helpful link.
 
     >>> print(user_browser.getLink('openid help').url)
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    LinkNotFoundError
+    zope.testbrowser.browser.LinkNotFoundError
 
 
 Jabber IDs
diff --git a/lib/lp/registry/stories/pillar/xx-pillar-deactivation.txt b/lib/lp/registry/stories/pillar/xx-pillar-deactivation.txt
index ba88a94..05bd0de 100644
--- a/lib/lp/registry/stories/pillar/xx-pillar-deactivation.txt
+++ b/lib/lp/registry/stories/pillar/xx-pillar-deactivation.txt
@@ -43,23 +43,27 @@ The projects are now no longer publicly visible:
 
     >>> anon_browser.open('http://launchpad.test/projects/+index?text=mozilla')
     >>> anon_browser.getLink(url='/firefox')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    LinkNotFoundError
+    zope.testbrowser.browser.LinkNotFoundError
     >>> anon_browser.getLink(url='/mozilla')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    LinkNotFoundError
+    zope.testbrowser.browser.LinkNotFoundError
 
     >>> anon_browser.open('http://launchpad.test/firefox')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    NotFound:...
+    zope.publisher.interfaces.NotFound: ...
 
     >>> anon_browser.open('http://launchpad.test/mozilla')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    NotFound:...
+    zope.publisher.interfaces.NotFound: ...
 
 But an administrator can still see them if they traverse directly, and
 they'll see an informative message. They can then reactivate them..
diff --git a/lib/lp/registry/stories/pillar/xx-pillar-traversal.txt b/lib/lp/registry/stories/pillar/xx-pillar-traversal.txt
index 6fb76df..8e17c03 100644
--- a/lib/lp/registry/stories/pillar/xx-pillar-traversal.txt
+++ b/lib/lp/registry/stories/pillar/xx-pillar-traversal.txt
@@ -15,9 +15,10 @@ canonical URL.
     >>> browser.open('http://launchpad.test/firefox')
 
     >>> browser.open('http://launchpad.test/iceweasel')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    NotFound:...
+    zope.publisher.interfaces.NotFound: ...
 
     >>> login('mark@xxxxxxxxxxx')
     >>> getUtility(IProductSet)['firefox'].setAliases(['iceweasel'])
diff --git a/lib/lp/registry/stories/product/xx-product-files.txt b/lib/lp/registry/stories/product/xx-product-files.txt
index 673a34c..a3238f3 100644
--- a/lib/lp/registry/stories/product/xx-product-files.txt
+++ b/lib/lp/registry/stories/product/xx-product-files.txt
@@ -191,9 +191,10 @@ Ensure a non-owner doesn't see the 'Add download file' link.
 
     >>> non_owner.open('http://launchpad.test/firefox/+download')
     >>> non_owner.getLink('1.0.0')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    LinkNotFoundError
+    zope.testbrowser.browser.LinkNotFoundError
 
 To add a download file the release version link is used.
 
@@ -207,9 +208,10 @@ navigating to the product release page.
 
     >>> non_owner.open('http://launchpad.test/firefox/1.0/1.0.0')
     >>> non_owner.getLink('Add download file')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    LinkNotFoundError
+    zope.testbrowser.browser.LinkNotFoundError
 
 To add a download file the +adddownloadfile page is accessed.
 
diff --git a/lib/lp/registry/stories/product/xx-product-package-pages.txt b/lib/lp/registry/stories/product/xx-product-package-pages.txt
index 54261e5..15b7576 100644
--- a/lib/lp/registry/stories/product/xx-product-package-pages.txt
+++ b/lib/lp/registry/stories/product/xx-product-package-pages.txt
@@ -44,9 +44,10 @@ Any logged in users can still see the links to create a packaging link.
 
     >>> anon_browser.open('http://launchpad.test/evolution/+packages')
     >>> anon_browser.getLink(url='/evolution/trunk/+ubuntupkg')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    LinkNotFoundError
+    zope.testbrowser.browser.LinkNotFoundError
 
 
 Deleting packaging links
diff --git a/lib/lp/registry/stories/product/xx-projects-index.txt b/lib/lp/registry/stories/product/xx-projects-index.txt
index 72a8b4a..2ccbef5 100644
--- a/lib/lp/registry/stories/product/xx-projects-index.txt
+++ b/lib/lp/registry/stories/product/xx-projects-index.txt
@@ -5,9 +5,10 @@ Just make sure this page contains the right links:
 This link was removed.
 
     >>> user_browser.getLink("Show all teams").url
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    LinkNotFoundError
+    zope.testbrowser.browser.LinkNotFoundError
 
 The "Show all projects" link is still there.
 
diff --git a/lib/lp/registry/stories/productrelease/xx-productrelease-basics.txt b/lib/lp/registry/stories/productrelease/xx-productrelease-basics.txt
index adf171a..7f43ec2 100644
--- a/lib/lp/registry/stories/productrelease/xx-productrelease-basics.txt
+++ b/lib/lp/registry/stories/productrelease/xx-productrelease-basics.txt
@@ -7,14 +7,16 @@ release nor can access the page directly.
 
     >>> user_browser.open('http://launchpad.test/firefox/+milestone/1.0')
     >>> user_browser.getLink('Create release').click()
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
       ...
-    LinkNotFoundError
+    zope.testbrowser.browser.LinkNotFoundError
     >>> user_browser.open(
     ...     'http://launchpad.test/firefox/+milestone/1.0/+addrelease')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
       ...
-    Unauthorized: ...
+    zope.security.interfaces.Unauthorized: ...
 
 But Sample Person can use the 'Register a release' link to create a new
 release in the series.
@@ -88,9 +90,10 @@ has a productrelease.
 
     >>> browser.open('http://launchpad.test/firefox/+milestone/1.0')
     >>> browser.getLink('Create release').click()
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    LinkNotFoundError
+    zope.testbrowser.browser.LinkNotFoundError
 
     >>> browser.open('http://launchpad.test/firefox/+milestone/1.0/+addrelease')
     >>> browser.url
diff --git a/lib/lp/registry/stories/productrelease/xx-productrelease-delete.txt b/lib/lp/registry/stories/productrelease/xx-productrelease-delete.txt
index f7f11b7..c259a92 100644
--- a/lib/lp/registry/stories/productrelease/xx-productrelease-delete.txt
+++ b/lib/lp/registry/stories/productrelease/xx-productrelease-delete.txt
@@ -21,13 +21,15 @@ link and cannot access the +delete page.
     0.9.2 "One (secure) Tree Hill" : Mozilla Firefox
 
     >>> user_browser.getLink(url='/firefox/trunk/0.9.2/+delete')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    LinkNotFoundError
+    zope.testbrowser.browser.LinkNotFoundError
     >>> user_browser.open('http://launchpad.test/firefox/trunk/0.9.2/+delete')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    Unauthorized:...
+    zope.security.interfaces.Unauthorized: ...
 
 Salgado has the necessary rights, so he sees the link and the +delete page.
 
diff --git a/lib/lp/registry/stories/productseries/xx-productseries-add-and-edit.txt b/lib/lp/registry/stories/productseries/xx-productseries-add-and-edit.txt
index aed8cf7..1359d34 100644
--- a/lib/lp/registry/stories/productseries/xx-productseries-add-and-edit.txt
+++ b/lib/lp/registry/stories/productseries/xx-productseries-add-and-edit.txt
@@ -10,13 +10,15 @@ Person won't even see the link nor can access the page directly.
 
     >>> user_browser.open('http://launchpad.test/firefox')
     >>> user_browser.getLink('Register a series').click()
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
       ...
-    LinkNotFoundError
+    zope.testbrowser.browser.LinkNotFoundError
     >>> user_browser.open('http://launchpad.test/firefox/+addseries')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
       ...
-    Unauthorized: ...
+    zope.security.interfaces.Unauthorized: ...
 
 But Sample Person will and be able to add a series.
 
diff --git a/lib/lp/registry/stories/productseries/xx-productseries-delete.txt b/lib/lp/registry/stories/productseries/xx-productseries-delete.txt
index b1c867d..92bfa76 100644
--- a/lib/lp/registry/stories/productseries/xx-productseries-delete.txt
+++ b/lib/lp/registry/stories/productseries/xx-productseries-delete.txt
@@ -92,9 +92,10 @@ that the series was deleted, and can not see a link to it anymore.
     Series trunk deleted.
 
     >>> owner_browser.getLink('trunk')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
      ...
-    LinkNotFoundError
+    zope.testbrowser.browser.LinkNotFoundError
 
 A series with translations can never be deleted. The project owner or
 release manager sees the explanation when they try to delete the series.
diff --git a/lib/lp/registry/stories/project/xx-project-add.txt b/lib/lp/registry/stories/project/xx-project-add.txt
index 481c57b..0ae33ce 100644
--- a/lib/lp/registry/stories/project/xx-project-add.txt
+++ b/lib/lp/registry/stories/project/xx-project-add.txt
@@ -3,9 +3,10 @@
 Normal users should not be able to do this:
 
   >>> user_browser.open("http://launchpad.test/projectgroups/+new";)
+  ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
   Traceback (most recent call last):
     ...
-  Unauthorized:...
+  zope.security.interfaces.Unauthorized: ...
 
 But an admin user should be able to do it:
 
diff --git a/lib/lp/registry/stories/project/xx-project-edit.txt b/lib/lp/registry/stories/project/xx-project-edit.txt
index 7b5b696..840697c 100644
--- a/lib/lp/registry/stories/project/xx-project-edit.txt
+++ b/lib/lp/registry/stories/project/xx-project-edit.txt
@@ -23,9 +23,10 @@ The maintainer of a project can edit its details.
 Regular users can't access the +review page.
 
     >>> browser.getLink('Administer')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    LinkNotFoundError
+    zope.testbrowser.browser.LinkNotFoundError
 
 But administrators can access the page:
 
@@ -122,16 +123,18 @@ Registry experts are not allowed access to the +edit page.
 
     >>> expert_browser.open('http://launchpad.test/new-name')
     >>> expert_browser.getLink('Change details').click()
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    LinkNotFoundError
+    zope.testbrowser.browser.LinkNotFoundError
 
 And going directly to the URL is not allowed.
 
     >>> expert_browser.open('http://launchpad.test/new-name/+edit')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    Unauthorized...
+    zope.security.interfaces.Unauthorized: ...
 
 Registry experts do have access to administer project groups, though
 there are fewer fields available.
diff --git a/lib/lp/registry/stories/project/xx-project-index.txt b/lib/lp/registry/stories/project/xx-project-index.txt
index 4f7da69..086ce39 100644
--- a/lib/lp/registry/stories/project/xx-project-index.txt
+++ b/lib/lp/registry/stories/project/xx-project-index.txt
@@ -46,15 +46,17 @@ project.
 
     >>> browser.open('http://launchpad.test/mozilla')
     >>> browser.getLink('Register a project in The Mozilla Project')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
       ...
-    LinkNotFoundError...
+    zope.testbrowser.browser.LinkNotFoundError
 
     >>> browser.addHeader('Authorization', 'Basic no-priv@xxxxxxxxxxxxx:test')
     >>> browser.getLink('Register a project in The Mozilla Project')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
       ...
-    LinkNotFoundError...
+    zope.testbrowser.browser.LinkNotFoundError
 
     >>> admin_browser.open('http://launchpad.test/mozilla')
     >>> admin_browser.getLink('Register a project in The Mozilla Project').url
@@ -81,41 +83,49 @@ question' or 'Help translate' buttons.
 
     >>> user_browser.open('http://launchpad.test/a-test-group')
     >>> user_browser.getLink('Report a bug')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
       ..
-    LinkNotFoundError...
+    zope.testbrowser.browser.LinkNotFoundError
     >>> user_browser.getLink('Ask a question')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
       ..
-    LinkNotFoundError...
+    zope.testbrowser.browser.LinkNotFoundError
     >>> user_browser.getLink('Help translate')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
       ..
-    LinkNotFoundError...
+    zope.testbrowser.browser.LinkNotFoundError
 
 Also, the bugs, blueprints, translations and answers facets will be disabled:
 
     >>> user_browser.open('http://launchpad.test/a-test-group')
     >>> user_browser.getLink('Bugs')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
       ..
-    LinkNotFoundError...
+    zope.testbrowser.browser.LinkNotFoundError
     >>> user_browser.getLink('Blueprints')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
       ..
-    LinkNotFoundError...
+    zope.testbrowser.browser.LinkNotFoundError
     >>> user_browser.getLink('Answers')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
       ..
-    LinkNotFoundError...
+    zope.testbrowser.browser.LinkNotFoundError
     >>> user_browser.getLink('Answers')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
       ..
-    LinkNotFoundError...
+    zope.testbrowser.browser.LinkNotFoundError
     >>> user_browser.getLink('Translations')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
       ..
-    LinkNotFoundError...
+    zope.testbrowser.browser.LinkNotFoundError
 
 A warning message will be displayed at the top of the overview page when the
 owner of the project group views it:
diff --git a/lib/lp/registry/stories/team-polls/create-polls.txt b/lib/lp/registry/stories/team-polls/create-polls.txt
index 08120dd..0d2fe56 100644
--- a/lib/lp/registry/stories/team-polls/create-polls.txt
+++ b/lib/lp/registry/stories/team-polls/create-polls.txt
@@ -20,9 +20,10 @@ administrator. There's no link leading to the +newpoll page, but the user can
 easily guess it.
 
     >>> no_priv_browser.open('http://launchpad.test/~ubuntu-team/+newpoll')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    Unauthorized:...
+    zope.security.interfaces.Unauthorized: ...
 
 Now we're logged in as Jeff Waugh which is a team administrator and thus can
 create a new poll.
diff --git a/lib/lp/registry/stories/team-polls/edit-options.txt b/lib/lp/registry/stories/team-polls/edit-options.txt
index f62778b..89190f7 100644
--- a/lib/lp/registry/stories/team-polls/edit-options.txt
+++ b/lib/lp/registry/stories/team-polls/edit-options.txt
@@ -12,9 +12,10 @@ team's administrators:
     OptionA     OptionA     Yes
     ...
     >>> user_browser.getLink('[Edit]')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    LinkNotFoundError
+    zope.testbrowser.browser.LinkNotFoundError
 
 And when the poll already started, administrators cannot change the options
 either:
diff --git a/lib/lp/registry/stories/team/xx-team-add-my-teams.txt b/lib/lp/registry/stories/team/xx-team-add-my-teams.txt
index e61a187..4614aeb 100644
--- a/lib/lp/registry/stories/team/xx-team-add-my-teams.txt
+++ b/lib/lp/registry/stories/team/xx-team-add-my-teams.txt
@@ -102,9 +102,10 @@ your teams as members.
 
     >>> browser.open('http://launchpad.test/~ubuntu-team')
     >>> browser.getLink('Add one of my teams')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
      ...
-    LinkNotFoundError
+    zope.testbrowser.browser.LinkNotFoundError
 
     >>> browser.open('http://launchpad.test/~ubuntu-team/+add-my-teams')
     >>> print(extract_text(
@@ -116,8 +117,9 @@ your teams as members.
 The page is restricted to logged in users.
 
     >>> anon_browser.open('http://launchpad.test/~ubuntu-team/+add-my-teams')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
-    Unauthorized:...
+    zope.security.interfaces.Unauthorized: ...
 
 You also can't propose a team to itself. Here although Colin Watson is
 usually allowed to propose Guadamen in other team, it doesn't appear in
diff --git a/lib/lp/registry/stories/team/xx-team-home.txt b/lib/lp/registry/stories/team/xx-team-home.txt
index 9720e3e..b6f36e0 100644
--- a/lib/lp/registry/stories/team/xx-team-home.txt
+++ b/lib/lp/registry/stories/team/xx-team-home.txt
@@ -159,9 +159,10 @@ As teams do not have OpenID Logins, there is no link in the Contact
 details section for help.
 
     >>> sample_browser.getLink('OpenID help')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
      ...
-    LinkNotFoundError
+    zope.testbrowser.browser.LinkNotFoundError
 
 If the logged in user is an indirect member of the team, we'll say that and
 will even show the path from the user to the team.
diff --git a/lib/lp/registry/stories/teammembership/private-team.txt b/lib/lp/registry/stories/teammembership/private-team.txt
index f725951..4bc27c4 100644
--- a/lib/lp/registry/stories/teammembership/private-team.txt
+++ b/lib/lp/registry/stories/teammembership/private-team.txt
@@ -39,16 +39,18 @@ The page indicates that the team is private.
 A normal user cannot see the team.
 
     >>> user_browser.open('http://launchpad.test/~private-team')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    NotFound: ...
+    zope.publisher.interfaces.NotFound: ...
 
 An anonymous user cannot see the team.
 
     >>> anon_browser.open('http://launchpad.test/~private-team')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    NotFound: ...
+    zope.publisher.interfaces.NotFound: ...
 
 
 == Operations on a private team ==
diff --git a/lib/lp/registry/stories/teammembership/xx-add-member.txt b/lib/lp/registry/stories/teammembership/xx-add-member.txt
index 80e0ead..bdf48f2 100644
--- a/lib/lp/registry/stories/teammembership/xx-add-member.txt
+++ b/lib/lp/registry/stories/teammembership/xx-add-member.txt
@@ -91,9 +91,10 @@ rights to edit the membership in question) can do it.
 
     >>> landscape_admin_browser.open(
     ...    'http://launchpad.test/~launchpad/+invitation/landscape-developers')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    Unauthorized: ...
+    zope.security.interfaces.Unauthorized: ...
 
 First, let's accept the invitation sent on behalf of Landscape Developers to
 the Launchpad Developers.
diff --git a/lib/lp/registry/stories/teammembership/xx-member-renewed-membership.txt b/lib/lp/registry/stories/teammembership/xx-member-renewed-membership.txt
index beb8e96..299b3a3 100644
--- a/lib/lp/registry/stories/teammembership/xx-member-renewed-membership.txt
+++ b/lib/lp/registry/stories/teammembership/xx-member-renewed-membership.txt
@@ -52,9 +52,10 @@ Other users (apart from Karl) can't see that page.
 
     >>> user_browser.open('http://launchpad.test/~karl/+expiringmembership/'
     ...                   'ubuntu-mirror-admins')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    Unauthorized: ...
+    zope.security.interfaces.Unauthorized: ...
 
 If we change the team's renewal policy it still won't be possible for
 the user to renew that membership because it's not about to expire.
@@ -201,6 +202,7 @@ Any user who's not an admin of landscape-developers can't even see that page.
 
     >>> user_browser.open('http://launchpad.test/~landscape-developers'
     ...                   '/+expiringmembership/ubuntu-mirror-admins')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    Unauthorized: ...
+    zope.security.interfaces.Unauthorized: ...
diff --git a/lib/lp/registry/stories/teammembership/xx-team-leave.txt b/lib/lp/registry/stories/teammembership/xx-team-leave.txt
index 8fc7d3d..b991609 100644
--- a/lib/lp/registry/stories/teammembership/xx-team-leave.txt
+++ b/lib/lp/registry/stories/teammembership/xx-team-leave.txt
@@ -68,9 +68,10 @@ team's overview page.
 
     # The 'Leave' link should be gone.
     >>> browser.getLink('Leave the Team')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    LinkNotFoundError
+    zope.testbrowser.browser.LinkNotFoundError
 
     # And the 'Join' link should have returned.
     >>> browser.getLink('Join the team')
@@ -83,6 +84,7 @@ Team owners do not have the option to leave.
     ...     find_tag_by_id(browser.contents, 'your-involvement')))
     You own this team...
     >>> browser.getLink('Leave the Team')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    LinkNotFoundError
+    zope.testbrowser.browser.LinkNotFoundError
diff --git a/lib/lp/registry/stories/teammembership/xx-teammembership.txt b/lib/lp/registry/stories/teammembership/xx-teammembership.txt
index c6fe1d0..c54778e 100644
--- a/lib/lp/registry/stories/teammembership/xx-teammembership.txt
+++ b/lib/lp/registry/stories/teammembership/xx-teammembership.txt
@@ -63,9 +63,10 @@ say that there's no need to join since the user is already a member of that
 team.
 
     >>> browser.getLink('Join the team')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    LinkNotFoundError
+    zope.testbrowser.browser.LinkNotFoundError
     >>> browser.open('http://launchpad.test/~myemail/+join')
     >>> for tag in find_tags_by_class(browser.contents, 'informational'):
     ...     print(tag.decode_contents())
@@ -175,9 +176,10 @@ If it was a restricted team, users wouldn't even see a link to join the team.
     Restricted Team
 
     >>> browser.getLink('Join the team')
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    LinkNotFoundError
+    zope.testbrowser.browser.LinkNotFoundError
 
 If the user manually crafts the URL to the +join page, they'll only see a
 message explaining that this is a restricted team.
diff --git a/lib/lp/registry/tests/person_from_principal.txt b/lib/lp/registry/tests/person_from_principal.txt
index 9ffef36..7ea5701 100644
--- a/lib/lp/registry/tests/person_from_principal.txt
+++ b/lib/lp/registry/tests/person_from_principal.txt
@@ -9,9 +9,10 @@ raised.
     ...     id = 42
     ...     person = None
     >>> person_from_principal(NoLaunchpadPrincipal())
+    ... # doctest: +IGNORE_EXCEPTION_MODULE_IN_PYTHON2
     Traceback (most recent call last):
     ...
-    ComponentLookupError
+    zope.interface.interfaces.ComponentLookupError
 
 If an ILaunchpadPrincipal is passed in, the principal's .person
 attribute is returned. The .person attribute was set when the principal