← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~cjwatson/launchpad:bs4-new-method-names into launchpad:master

 

Colin Watson has proposed merging ~cjwatson/launchpad:bs4-new-method-names into launchpad:master.

Commit message:
Use BS4-style method names consistently

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

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

See https://www.crummy.com/software/BeautifulSoup/bs4/doc/#method-names.
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/launchpad:bs4-new-method-names into launchpad:master.
diff --git a/lib/lp/answers/browser/tests/views.txt b/lib/lp/answers/browser/tests/views.txt
index 7fe71fc..d481a9c 100644
--- a/lib/lp/answers/browser/tests/views.txt
+++ b/lib/lp/answers/browser/tests/views.txt
@@ -939,7 +939,7 @@ Summary, Created, Submitter, Assignee, and Status.
     False
 
     >>> table = find_tag_by_id(view.render(), 'question-listing')
-    >>> for row in table.findAll('tr'):
+    >>> for row in table.find_all('tr'):
     ...     print(extract_text(row))
     Summary                Created     Submitter      Assignee  Status
     6 Newly installed...  2005-10-14   Sample Person  —         Answered ...
@@ -956,7 +956,7 @@ package is displayed if it exists.
     False
 
     >>> table = find_tag_by_id(view.render(), 'question-listing')
-    >>> for row in table.findAll('tr'):
+    >>> for row in table.find_all('tr'):
     ...     print(extract_text(row))
     Summary  Created     Submitter      Source Package   Assignee  Status ...
     8 ...    2006-07-20  Sample Person  mozilla-firefox  —         Answered
@@ -976,7 +976,7 @@ ProjectGroups display the "In" column to show the product name.
     True
 
     >>> table = find_tag_by_id(view.render(), 'question-listing')
-    >>> for row in table.findAll('tr'):
+    >>> for row in table.find_all('tr'):
     ...     print(extract_text(row))
     Summary  Created     Submitter      In               Assignee  Status
     6 ...    2005-10-14  Sample Person  Mozilla Firefox  —         Answered...
@@ -996,7 +996,7 @@ to the question, or an m-dash if there is no assignee.
     False
 
     >>> table = find_tag_by_id(view.render(), 'question-listing')
-    >>> for row in table.findAll('tr'):
+    >>> for row in table.find_all('tr'):
     ...     print(extract_text(row))
     Summary  Created     Submitter      Assignee  Status
     6 ...    2005-10-14  Sample Person  Bob       Answered
diff --git a/lib/lp/answers/stories/faq-browse-and-search.txt b/lib/lp/answers/stories/faq-browse-and-search.txt
index 1308adf..51ba8eb 100644
--- a/lib/lp/answers/stories/faq-browse-and-search.txt
+++ b/lib/lp/answers/stories/faq-browse-and-search.txt
@@ -72,7 +72,7 @@ a pop-up:
     >>> browser.getLink('First').click()
     >>> faq_link = find_main_content(browser.contents).find(
     ...     'a', text=re.compile('How can I play MP3/Divx/DVDs'))
-    >>> print(faq_link.findParent('li')['title'])
+    >>> print(faq_link.find_parent('li')['title'])
     Playing many common formats such as DVIX, MP3, DVD, or Flash ...
 
 She clicks on FAQ's title to display the complete FAQ content:
@@ -136,7 +136,7 @@ Following the link will show the questions results:
     Questions : Ubuntu
 
     >>> questions = find_tag_by_id(browser.contents, 'question-listing')
-    >>> for question in questions.findAll('td', 'questionTITLE'):
+    >>> for question in questions.find_all('td', 'questionTITLE'):
     ...     print(question.find('a').decode_contents())
     Installation of Java Runtime Environment for Mozilla
 
diff --git a/lib/lp/answers/stories/project-add-question.txt b/lib/lp/answers/stories/project-add-question.txt
index dba5767..eb3703c 100644
--- a/lib/lp/answers/stories/project-add-question.txt
+++ b/lib/lp/answers/stories/project-add-question.txt
@@ -78,7 +78,7 @@ that they submitted:
 
     >>> similar_questions = find_tag_by_id(
     ...     user_browser.contents, 'similar-questions')
-    >>> for row in similar_questions.findAll('li'):
+    >>> for row in similar_questions.find_all('li'):
     ...     print(row.a.decode_contents())
     2: Problem showing the SVG demo on W3C site
 
@@ -177,7 +177,7 @@ speaks Japanese, so we will use him.
     ...                        'Mozilla Thunderbird').selected = True
     >>> daf_browser.getControl('Continue').click()
     >>> content = find_main_content(daf_browser.contents)
-    >>> for message in content.findAll('div', 'informational message'):
+    >>> for message in content.find_all('div', 'informational message'):
     ...      print(message.decode_contents())
     You have been added as an answer contact for Mozilla Thunderbird.
 
diff --git a/lib/lp/answers/stories/question-add-in-other-languages.txt b/lib/lp/answers/stories/question-add-in-other-languages.txt
index a153f2b..9ec7b8d 100644
--- a/lib/lp/answers/stories/question-add-in-other-languages.txt
+++ b/lib/lp/answers/stories/question-add-in-other-languages.txt
@@ -49,7 +49,7 @@ understood by any member of the support community.
     able to test this case again. Note that in production this exact use case
     already finds nothing.
 
-    #>>> for row in similar_questions.findAll('tr', 'noted'):
+    #>>> for row in similar_questions.find_all('tr', 'noted'):
     #...     row.find('a').decode_contents()
     #'Installation of Java Runtime Environment for Mozilla'
     #'Problema al recompilar kernel con soporte smp (doble-n\xc3\xbacleo)'
diff --git a/lib/lp/answers/stories/question-add.txt b/lib/lp/answers/stories/question-add.txt
index 73e5ac1..e7debb4 100644
--- a/lib/lp/answers/stories/question-add.txt
+++ b/lib/lp/answers/stories/question-add.txt
@@ -102,7 +102,7 @@ mouse is left over the question's title.
     >>> import re
     >>> question_link = contents.find(
     ...     'a', text=re.compile('Installation of Java'))
-    >>> print(question_link.findParent('li')['title'])
+    >>> print(question_link.find_parent('li')['title'])
     <BLANKLINE>
     When opening http://www.gotomypc.com/ with Mozilla, a java run time
     ennvironment plugin is requested.
@@ -117,7 +117,7 @@ hovers on the FAQ's title:
 
     >>> faq_link = contents.find(
     ...     'a', text=re.compile('How can I play MP3/Divx'))
-    >>> print(faq_link.findParent('li')['title'])
+    >>> print(faq_link.find_parent('li')['title'])
     Playing many common formats such as DVIX, MP3, DVD, or Flash
     animations require the installation of plugins.
     <BLANKLINE>
diff --git a/lib/lp/answers/stories/question-browse-and-search.txt b/lib/lp/answers/stories/question-browse-and-search.txt
index e537ec6..6c5bc69 100644
--- a/lib/lp/answers/stories/question-browse-and-search.txt
+++ b/lib/lp/answers/stories/question-browse-and-search.txt
@@ -58,7 +58,7 @@ Answers page and goes there to check.
 He sees a listing of the current questions posted on Ubuntu:
 
     >>> soup = find_main_content(browser.contents)
-    >>> for question in soup.findAll('td', 'questionTITLE'):
+    >>> for question in soup.find_all('td', 'questionTITLE'):
     ...     print(question.find('a').decode_contents())
     Continue playing after shutdown
     Play DVDs in Totem
@@ -74,7 +74,7 @@ results. There, he finds only one other question:
     >>> print(browser.title)
     Questions : Ubuntu
     >>> soup = find_main_content(browser.contents)
-    >>> for question in soup.findAll('td', 'questionTITLE'):
+    >>> for question in soup.find_all('td', 'questionTITLE'):
     ...     print(question.find('a').decode_contents())
     Installation failed
 
@@ -125,11 +125,11 @@ description appears in a small pop-up:
     >>> import re
     >>> soup = find_main_content(browser.contents)
     >>> question_link = soup.find('a', text=re.compile('Play DVDs'))
-    >>> print(question_link.findParent('tr')['title'])
+    >>> print(question_link.find_parent('tr')['title'])
     How do you play DVDs in Totem..........?
 
     >>> question_link = soup.find('a', text=re.compile('Slow system'))
-    >>> print(question_link.findParent('tr')['title'])
+    >>> print(question_link.find_parent('tr')['title'])
     I get really poor hard drive performance.
 
 He clicks on the question title to obtain the question page where the
@@ -229,7 +229,7 @@ selected. He adds 'Invalid' to the selection, and run his search again.
 This time, the search returns one item.
 
     >>> soup = find_main_content(browser.contents)
-    >>> for question in soup.findAll('td', 'questionTITLE'):
+    >>> for question in soup.find_all('td', 'questionTITLE'):
     ...     print(question.find('a').decode_contents())
     Firefox is slow and consumes too much RAM
 
@@ -304,7 +304,7 @@ recent questions on Mozilla Firefox.
     >>> print(browser.title)
     Questions : Mozilla Firefox
     >>> questions = find_tag_by_id(browser.contents, 'question-listing')
-    >>> for question in questions.findAll('td', 'questionTITLE'):
+    >>> for question in questions.find_all('td', 'questionTITLE'):
     ...     print(question.find('a').decode_contents())
     Firefox loses focus and gets stuck
     Problem showing the SVG demo on W3C site
@@ -324,7 +324,7 @@ problems:
     >>> browser.getControl(name='field.search_text').value = 'plugin'
     >>> browser.getControl('Search', index=0).click()
     >>> questions = find_tag_by_id(browser.contents, 'question-listing')
-    >>> for question in questions.findAll('td', 'questionTITLE'):
+    >>> for question in questions.find_all('td', 'questionTITLE'):
     ...     print(question.find('a').decode_contents())
     Problem showing the SVG demo on W3C site
 
@@ -344,7 +344,7 @@ questions.)
     >>> [strip_label(status) for status in statuses]
     ['Answered', 'Solved']
     >>> questions = find_tag_by_id(browser.contents, 'question-listing')
-    >>> for question in questions.findAll('td', 'questionTITLE'):
+    >>> for question in questions.find_all('td', 'questionTITLE'):
     ...     print(question.find('a').decode_contents())
     Play DVDs in Totem
     mailto: problem in webpage
@@ -390,7 +390,7 @@ They need to login to access that page:
     'Questions you asked about mozilla-firefox in Ubuntu : Questions : mozilla-firefox package : Ubuntu'
     >>> questions = find_tag_by_id(
     ...     sample_person_browser.contents, 'question-listing')
-    >>> for question in questions.findAll('td', 'questionTITLE'):
+    >>> for question in questions.find_all('td', 'questionTITLE'):
     ...     print(question.find('a').decode_contents())
     mailto: problem in webpage
     Installation of Java Runtime Environment for Mozilla
@@ -412,7 +412,7 @@ The exact question they were searching for is displayed!
 
     >>> questions = find_tag_by_id(
     ...     sample_person_browser.contents, 'question-listing')
-    >>> for question in questions.findAll('td', 'questionTITLE'):
+    >>> for question in questions.find_all('td', 'questionTITLE'):
     ...     print(question.find('a').decode_contents())
     mailto: problem in webpage
 
@@ -459,7 +459,7 @@ They need to login to access that page:
     Questions needing your attention for Ubuntu : Questions : Ubuntu
     >>> questions = find_tag_by_id(
     ...     sample_person_browser.contents, 'question-listing')
-    >>> for question in questions.findAll('td', 'questionTITLE'):
+    >>> for question in questions.find_all('td', 'questionTITLE'):
     ...     print(question.find('a').decode_contents())
     Play DVDs in Totem
     Installation of Java Runtime Environment for Mozilla
@@ -504,7 +504,7 @@ commented on.
     Questions : Foo Bar
 
     >>> questions = find_tag_by_id(browser.contents, 'question-listing')
-    >>> for question in questions.findAll('td', 'questionTITLE'):
+    >>> for question in questions.find_all('td', 'questionTITLE'):
     ...     print(question.find('a').decode_contents())
     Continue playing after shutdown
     Play DVDs in Totem
@@ -520,7 +520,7 @@ That listing is batched when there are many questions:
 The listing contains a 'In' column that shows the context where the
 questions was made.
 
-    >>> for question in questions.findAll('td', 'question-target'):
+    >>> for question in questions.find_all('td', 'question-target'):
     ...     print(question.find('a').decode_contents())
     Ubuntu
     Ubuntu
@@ -544,7 +544,7 @@ questions to a particular status:
     ...     'Solved', 'Invalid']
     >>> browser.getControl('Search', index=0).click()
     >>> questions = find_tag_by_id(browser.contents, 'question-listing')
-    >>> for question in questions.findAll('td', 'questionTITLE'):
+    >>> for question in questions.find_all('td', 'questionTITLE'):
     ...     print(question.find('a').decode_contents())
     Firefox is slow and consumes too much RAM
     mailto: problem in webpage
@@ -574,7 +574,7 @@ answerer.
     >>> print(browser.title)
     Questions for Foo Bar : Questions : Foo Bar
     >>> questions = find_tag_by_id(browser.contents, 'question-listing')
-    >>> for question in questions.findAll('td', 'questionTITLE'):
+    >>> for question in questions.find_all('td', 'questionTITLE'):
     ...     print(question.find('a').decode_contents())
     mailto: problem in webpage
 
@@ -588,7 +588,7 @@ questions commented on by the person.
     >>> print(browser.title)
     Questions for Foo Bar : Questions : Foo Bar
     >>> questions = find_tag_by_id(browser.contents, 'question-listing')
-    >>> for question in questions.findAll('td', 'questionTITLE'):
+    >>> for question in questions.find_all('td', 'questionTITLE'):
     ...     print(question.find('a').decode_contents())
     Continue playing after shutdown
     Play DVDs in Totem
@@ -606,7 +606,7 @@ asked by the person.
     >>> print(browser.title)
     Questions for Foo Bar : Questions : Foo Bar
     >>> questions = find_tag_by_id(browser.contents, 'question-listing')
-    >>> for question in questions.findAll('td', 'questionTITLE'):
+    >>> for question in questions.find_all('td', 'questionTITLE'):
     ...     print(question.find('a').decode_contents())
     Slow system
     Firefox loses focus and gets stuck
@@ -621,7 +621,7 @@ the attention of that person.
     >>> print(browser.title)
     Questions for Foo Bar : Questions : Foo Bar
     >>> questions = find_tag_by_id(browser.contents, 'question-listing')
-    >>> for question in questions.findAll('td', 'questionTITLE'):
+    >>> for question in questions.find_all('td', 'questionTITLE'):
     ...     print(question.find('a').decode_contents())
     Continue playing after shutdown
     Slow system
@@ -636,7 +636,7 @@ visiting the 'Subscribed' link in the 'Answers' facet.
     >>> print(browser.title)
     Questions for Foo Bar : Questions : Foo Bar
     >>> questions = find_tag_by_id(browser.contents, 'question-listing')
-    >>> for question in questions.findAll('td', 'questionTITLE'):
+    >>> for question in questions.find_all('td', 'questionTITLE'):
     ...     print(question.find('a').decode_contents())
     Slow system
 
@@ -656,7 +656,7 @@ there is an 'In' column displaying where the questions were filed.
 
     >>> def print_questions_with_target(contents):
     ...     questions = find_tag_by_id(contents, 'question-listing')
-    ...     for question in questions.tbody.findAll('tr'):
+    ...     for question in questions.tbody.find_all('tr'):
     ...         question_title = question.find(
     ...             'td', 'questionTITLE').find('a').decode_contents()
     ...         question_target = question.find(
@@ -674,7 +674,7 @@ That listing is searchable:
     >>> browser.getControl('Search', index=0).click()
 
     >>> questions = find_tag_by_id(browser.contents, 'question-listing')
-    >>> for question in questions.findAll('td', 'questionTITLE'):
+    >>> for question in questions.find_all('td', 'questionTITLE'):
     ...     print(question.find('a').decode_contents())
     Problem showing the SVG demo on W3C site
 
diff --git a/lib/lp/answers/stories/question-message.txt b/lib/lp/answers/stories/question-message.txt
index 84444ce..0afd96b 100644
--- a/lib/lp/answers/stories/question-message.txt
+++ b/lib/lp/answers/stories/question-message.txt
@@ -40,7 +40,7 @@ authenticated already, so they will see 'human@xxxxxxxxxxxxx'.
     Question #11 :  ...
     >>> text = find_tags_by_class(
     ...     user_browser.contents, 'boardCommentBody')[-1]
-    >>> print(extract_text(text.findAll('p')[-1]))
+    >>> print(extract_text(text.find_all('p')[-1]))
     --
     ______________________
     human@xxxxxxxxxxxxx
@@ -56,7 +56,7 @@ see the obfuscated email address (<email address hidden>).
 
     >>> text = find_tags_by_class(
     ...     anon_browser.contents, 'boardCommentBody')[-1]
-    >>> print(extract_text(text.findAll('p')[-1]))
+    >>> print(extract_text(text.find_all('p')[-1]))
     --
     ______________________
     &lt;email address hidden&gt;
@@ -74,7 +74,7 @@ Signatures are identified by paragraphs with a starting line like '--'.
 The entire content of the paragraph is wrapped by a tag of 'foldable'
 class.
 
-    >>> print(text.findAll('p')[-1])
+    >>> print(text.find_all('p')[-1])
     <p><span class="foldable">--...
     &lt;email address hidden&gt;<br/>
     Witty signatures rock!
@@ -87,7 +87,7 @@ preceded by a citation line. Only the quoted lines are wrapped with a
 tag of 'foldable' class, citation lines are always displayed. Again
 we can continue with the anonymous user to see the markup.
 
-    >>> print(text.findAll('p')[-2])
+    >>> print(text.find_all('p')[-2])
     <p>Somebody said sometime ago:<br/>
     <span class="foldable-quoted">
     &gt; 1. Remove the letters  c, j, q, x, w<br/>
diff --git a/lib/lp/answers/stories/question-obfuscation.txt b/lib/lp/answers/stories/question-obfuscation.txt
index 51f8028..87133b1 100644
--- a/lib/lp/answers/stories/question-obfuscation.txt
+++ b/lib/lp/answers/stories/question-obfuscation.txt
@@ -15,7 +15,7 @@ front page.
     >>> user_browser.open('http://answers.launchpad.test/')
     >>> question_portlet = find_tag_by_id(
     ...     user_browser.contents, 'latest-questions-solved')
-    >>> for li in question_portlet.findAll('li'):
+    >>> for li in question_portlet.find_all('li'):
     ...     print(li['title'])
     I am not able to ... if i click on a mailto:user@xxxxxxxxxx link ...
 
@@ -30,7 +30,7 @@ in the question's description.
 
     >>> question_listing = find_tag_by_id(
     ...     user_browser.contents, 'question-listing')
-    >>> for li in question_portlet.findAll('li'):
+    >>> for li in question_portlet.find_all('li'):
     ...     print(li['title'])
     I am not able to ... if i click on a mailto:user@xxxxxxxxxx link ...
 
@@ -81,7 +81,7 @@ description. They can then see the email address in the tooltip in the
     >>> user_browser.open('http://answers.launchpad.test/')
     >>> question_portlet = find_tag_by_id(
     ...     user_browser.contents, 'latest-questions-asked')
-    >>> for li in question_portlet.findAll('li'):
+    >>> for li in question_portlet.find_all('li'):
     ...     print(li['title'])
     The clicking mailto:user@xxxxxxxxxx crashes the browser.
     ...
@@ -98,14 +98,14 @@ page.
 
     >>> question_portlet = find_tag_by_id(
     ...     anon_browser.contents, 'latest-questions-solved')
-    >>> for li in question_portlet.findAll('li'):
+    >>> for li in question_portlet.find_all('li'):
     ...     print(li['title'])
     I am not able to ... if i click on a
     mailto:<email address hidden> ...
 
     >>> question_portlet = find_tag_by_id(
     ...     anon_browser.contents, 'latest-questions-asked')
-    >>> for li in question_portlet.findAll('li'):
+    >>> for li in question_portlet.find_all('li'):
     ...     print(li['title'])
     The clicking mailto:<email address hidden> crashes the browser.
     ...
@@ -123,7 +123,7 @@ They cannot see the address reading the question either.
 
     >>> question_listing = find_tag_by_id(
     ...     anon_browser.contents, 'question-listing')
-    >>> for tr in question_listing.tbody.findAll('tr'):
+    >>> for tr in question_listing.tbody.find_all('tr'):
     ...     print(tr['title'])
     I am not able to ... if i click on a mailto:<email address hidden>
     link ...
diff --git a/lib/lp/answers/stories/question-reject-and-change-status.txt b/lib/lp/answers/stories/question-reject-and-change-status.txt
index 44467a4..ece123c 100644
--- a/lib/lp/answers/stories/question-reject-and-change-status.txt
+++ b/lib/lp/answers/stories/question-reject-and-change-status.txt
@@ -68,7 +68,8 @@ its status is changed to 'Invalid';
 and the rejection message is added to the question board.
 
     >>> content = find_main_content(admin_browser.contents)
-    >>> print(content.findAll('div', 'boardCommentBody')[-1].decode_contents())
+    >>> print(
+    ...     content.find_all('div', 'boardCommentBody')[-1].decode_contents())
     <p>Rejecting because it's a duplicate of <a...>bug #1</a>.</p>
 
 The call to help with this problem is not displayed.
@@ -155,6 +156,7 @@ its status is updated;
 and the explanation message is added to the question discussion:
 
     >>> content = find_main_content(admin_browser.contents)
-    >>> print(content.findAll('div', 'boardCommentBody')[-1].decode_contents())
+    >>> print(
+    ...     content.find_all('div', 'boardCommentBody')[-1].decode_contents())
     <p>Setting status back to 'Open'. Questions similar to a
     bug report should be linked to it, not rejected.</p>
diff --git a/lib/lp/answers/stories/question-search-multiple-languages.txt b/lib/lp/answers/stories/question-search-multiple-languages.txt
index e83655f..aaa1ccc 100644
--- a/lib/lp/answers/stories/question-search-multiple-languages.txt
+++ b/lib/lp/answers/stories/question-search-multiple-languages.txt
@@ -20,7 +20,7 @@ information.
 
     >>> anon_browser.open('http://launchpad.test/distros/ubuntu/+questions')
     >>> soup = find_main_content(anon_browser.contents)
-    >>> for question in soup.findAll('td', 'questionTITLE'):
+    >>> for question in soup.find_all('td', 'questionTITLE'):
     ...     print(question.find('a').decode_contents())
     Continue playing after shutdown
     Play DVDs in Totem
@@ -31,7 +31,7 @@ information.
     # Since we have more than 5 results, some of them are in the second batch.
     >>> anon_browser.getLink('Next').click()
     >>> soup = find_main_content(anon_browser.contents)
-    >>> for question in soup.findAll('td', 'questionTITLE'):
+    >>> for question in soup.find_all('td', 'questionTITLE'):
     ...     print(question.find('a').decode_contents())
     Installation failed
 
@@ -50,7 +50,7 @@ the anonymous user will see a Spanish question.
     >>> anon_browser.getControl('English (en)').selected = False
     >>> anon_browser.getControl('Search', index=0).click()
     >>> table = find_tag_by_id(anon_browser.contents, 'question-listing')
-    >>> for question in table.findAll('td', 'questionTITLE'):
+    >>> for question in table.find_all('td', 'questionTITLE'):
     ...     print(question.find('a').decode_contents())
     Problema al recompilar kernel con soporte smp (doble-núcleo)
     Continue playing after shutdown
@@ -62,7 +62,7 @@ While the user might recognize the first question above is in Spanish,
 browsers and search engine robots need help. Each row in the list
 of questions declares its language and text direction.
 
-    >>> for question in table.findAll('tr', lang=True):
+    >>> for question in table.find_all('tr', lang=True):
     ...     print('lang="%s" dir="%s"' % (question['lang'], question['dir']))
     lang="es" dir="ltr"
     lang="en" dir="ltr"
@@ -76,13 +76,13 @@ Arabic and is written from right-to-left.
 
     >>> anon_browser.getLink('Next').click()
     >>> table = find_tag_by_id(anon_browser.contents, 'question-listing')
-    >>> for question in table.findAll('td', 'questionTITLE'):
+    >>> for question in table.find_all('td', 'questionTITLE'):
     ...     print(question.find('a').decode_contents())
     Slow system
     Installation failed
     عكس ...
 
-    >>> for question in table.findAll('tr', lang=True):
+    >>> for question in table.find_all('tr', lang=True):
     ...     print('lang="%s" dir="%s"' % (question['lang'], question['dir']))
     lang="en" dir="ltr"
     lang="en" dir="ltr"
@@ -137,7 +137,7 @@ then questions with those language will be displayed:
     True
 
     >>> soup = find_main_content(anon_browser.contents)
-    >>> for question in soup.findAll('td', 'questionTITLE'):
+    >>> for question in soup.find_all('td', 'questionTITLE'):
     ...     print(question.find('a').decode_contents())
     Problema al recompilar kernel con soporte smp (doble-núcleo)
     Continue playing after shutdown
@@ -148,7 +148,7 @@ then questions with those language will be displayed:
     # Since we have more than 5 results, some of them are in the second batch.
     >>> anon_browser.getLink('Next').click()
     >>> soup = find_main_content(anon_browser.contents)
-    >>> for question in soup.findAll('td', 'questionTITLE'):
+    >>> for question in soup.find_all('td', 'questionTITLE'):
     ...     print(question.find('a').decode_contents())
     Slow system
     Installation failed
@@ -212,7 +212,7 @@ the search results.
     >>> browser.getControl('English (en)').selected = False
     >>> browser.getControl('Search', index=0).click()
     >>> content = find_main_content(browser.contents)
-    >>> for question in content.findAll('td', 'questionTITLE'):
+    >>> for question in content.find_all('td', 'questionTITLE'):
     ...     print(question.find('a').decode_contents())
     Problema al recompilar kernel con soporte smp (doble-núcleo)
 
@@ -252,7 +252,7 @@ English, and can use it to locate English questions.
     >>> daf_browser.getControl('English (en)').selected = True
     >>> daf_browser.getControl('Search', index=0).click()
     >>> content = find_main_content(daf_browser.contents)
-    >>> for question in content.findAll('td', 'questionTITLE'):
+    >>> for question in content.find_all('td', 'questionTITLE'):
     ...     print(question.find('a').decode_contents())
     Continue playing after shutdown
     Play DVDs in Totem
@@ -301,7 +301,7 @@ the preceding page.
     ['Open']
 
     >>> content = find_main_content(user_browser.contents)
-    >>> for question in content.findAll('td', 'questionTITLE'):
+    >>> for question in content.find_all('td', 'questionTITLE'):
     ...     print(question.find('a').decode_contents())
     Problemas de Impressão no Firefox
 
@@ -330,7 +330,7 @@ project. They can see the question on the second page of "My questions"...
     lang: ar
     dir: rtl
 
-    >>> for question in questions.findAll('td', {'class': 'questionTITLE'}):
+    >>> for question in questions.find_all('td', {'class': 'questionTITLE'}):
     ...     print(question.find('a').decode_contents())
     عكس التغ...
 
diff --git a/lib/lp/answers/stories/question-workflow.txt b/lib/lp/answers/stories/question-workflow.txt
index 08de35a..984aaf4 100755
--- a/lib/lp/answers/stories/question-workflow.txt
+++ b/lib/lp/answers/stories/question-workflow.txt
@@ -22,7 +22,7 @@ the Firefox product which was filed by 'Sample Person'.
 
     >>> def  find_last_comment(contents):
     ...     soup = find_main_content(contents)
-    ...     return soup.findAll('div', 'boardCommentBody')[-1]
+    ...     return soup.find_all('div', 'boardCommentBody')[-1]
 
     >>> def print_last_comment(contents):
     ...     print(extract_text(find_last_comment(contents)))
@@ -179,7 +179,7 @@ When the owner comes back on the question page, they will now see a new
     >>> owner_browser.open(
     ...     'http://launchpad.test/firefox/+question/2')
     >>> soup = find_main_content(owner_browser.contents)
-    >>> soup.findAll('div', 'boardComment')[-1].find('input', type='submit')
+    >>> soup.find_all('div', 'boardComment')[-1].find('input', type='submit')
     <input name="field.actions.confirm" type="submit"
      value="This Solved My Problem"/>
 
@@ -214,7 +214,7 @@ confirmation message was appended to the question discussion:
 The confirmed answer is also highlighted.
 
     >>> soup = find_main_content(owner_browser.contents)
-    >>> bestAnswer = soup.findAll('div', 'boardComment')[-2]
+    >>> bestAnswer = soup.find_all('div', 'boardComment')[-2]
     >>> print(bestAnswer.find('img'))
     <img ... src="/@@/favourite-yes" ... title="Marked as best answer"/>
 
@@ -285,7 +285,7 @@ This also removes the highlighting from the previous answer and sets the
 answerer back to None.
 
     >>> soup = find_main_content(owner_browser.contents)
-    >>> bestAnswer = soup.findAll('div', 'boardComment')[-4]
+    >>> bestAnswer = soup.find_all('div', 'boardComment')[-4]
     >>> bestAnswer.find('strong') is None
     True
 
@@ -332,7 +332,7 @@ A message is displayed to the user confirming that the question is
 solved and suggesting that the user choose an answer that helped the
 question owner to solve their problem.
 
-    >>> for message in soup.findAll('div', 'informational message'):
+    >>> for message in soup.find_all('div', 'informational message'):
     ...     print(extract_text(message))
     Your question is solved. If a particular message helped you solve the
     problem, use the 'This solved my problem' button.
@@ -382,15 +382,15 @@ It lists all the actions performed through workflow on the question:
 
     >>> soup = find_main_content(anon_browser.contents)
     >>> action_listing = soup.find('table', 'listing')
-    >>> for header in action_listing.findAll('th'):
+    >>> for header in action_listing.find_all('th'):
     ...     print(header.decode_contents())
     When
     Who
     Action
     New State
 
-    >>> for row in action_listing.find('tbody').findAll('tr'):
-    ...     cells = row.findAll('td')
+    >>> for row in action_listing.find('tbody').find_all('tr'):
+    ...     cells = row.find_all('td')
     ...     who = extract_text(cells[1].find('a'))
     ...     action = cells[2].decode_contents()
     ...     new_status = cells[3].decode_contents()
@@ -435,7 +435,7 @@ choosing an answer that helped him solve his problem.
     Status: Solved ...
 
     >>> content = find_main_content(carlos_browser.contents)
-    >>> messages = content.findAll('div', 'informational message')
+    >>> messages = content.find_all('div', 'informational message')
     >>> messages
     []
 
diff --git a/lib/lp/answers/stories/questions-index.txt b/lib/lp/answers/stories/questions-index.txt
index 3285409..99969d7 100644
--- a/lib/lp/answers/stories/questions-index.txt
+++ b/lib/lp/answers/stories/questions-index.txt
@@ -26,7 +26,7 @@ It shows the 5 latest questions asked:
     ...     anon_browser.contents, 'latest-questions-asked')
     >>> print(latest_questions_asked.find('h2').decode_contents())
     Latest questions asked
-    >>> for row in latest_questions_asked.findAll('li'):
+    >>> for row in latest_questions_asked.find_all('li'):
     ...     print(row.find('a').decode_contents())
     13: Problemas de Impressão no Firefox
     12: Problema al recompilar kernel con soporte smp (doble-núcleo)
@@ -40,7 +40,7 @@ As well as the 5 latest questions solved:
     ...     anon_browser.contents, 'latest-questions-solved')
     >>> print(latest_questions_solved.find('h2').decode_contents())
     Latest questions solved
-    >>> for row in latest_questions_solved.findAll('li'):
+    >>> for row in latest_questions_solved.find_all('li'):
     ...     print(row.find('a').decode_contents())
     9: mailto: problem in webpage
 
diff --git a/lib/lp/answers/stories/this-is-a-faq.txt b/lib/lp/answers/stories/this-is-a-faq.txt
index 56168bf..3164116 100644
--- a/lib/lp/answers/stories/this-is-a-faq.txt
+++ b/lib/lp/answers/stories/this-is-a-faq.txt
@@ -51,7 +51,7 @@ no FAQ is associated to the question, the 'No existing FAQs are
 relevant' option is selected.
 
     >>> def printFAQOptions(contents):
-    ...     buttons =  find_main_content(contents).findAll(
+    ...     buttons =  find_main_content(contents).find_all(
     ...         'input', {'name': 'field.faq'})
     ...     for button in buttons:
     ...         label = extract_text(button.parent)
@@ -60,7 +60,7 @@ relevant' option is selected.
     ...         else:
     ...             radio = '( )'
     ...         if button['value']:
-    ...             link = button.findNext('a').decode_contents()
+    ...             link = button.find_next('a').decode_contents()
     ...         else:
     ...             link = ''
     ...         print(radio, label, link)
diff --git a/lib/lp/answers/tests/test_question_webservice.py b/lib/lp/answers/tests/test_question_webservice.py
index 1dd2e42..e36d199 100644
--- a/lib/lp/answers/tests/test_question_webservice.py
+++ b/lib/lp/answers/tests/test_question_webservice.py
@@ -110,7 +110,7 @@ class TestQuestionRepresentation(TestCaseWithFactory):
         """Find the question title field in an XHTML document fragment."""
         soup = BeautifulSoup(response.body)
         dt = soup.find('dt', text="title")
-        dd = dt.findNextSibling('dd')
+        dd = dt.find_next_sibling('dd')
         return str(dd.contents.pop())
 
     def test_top_level_question_get(self):
diff --git a/lib/lp/app/browser/doc/base-layout.txt b/lib/lp/app/browser/doc/base-layout.txt
index f9eb8b7..f091fc9 100644
--- a/lib/lp/app/browser/doc/base-layout.txt
+++ b/lib/lp/app/browser/doc/base-layout.txt
@@ -140,7 +140,7 @@ Page Footers
     >>> bugs_request.setPrincipal(user)
     >>> view = BugsMainSideView(user, bugs_request)
     >>> footer = find_tag_by_id(html, 'footer')
-    >>> for tag in footer.findAll('a'):
+    >>> for tag in footer.find_all('a'):
     ...     print(tag.string, tag['href'])
     None http://launchpad.test/
     Take the tour http://launchpad.test/+tour
diff --git a/lib/lp/app/browser/tests/test_launchpadroot.py b/lib/lp/app/browser/tests/test_launchpadroot.py
index ece132e..f51ef2b 100644
--- a/lib/lp/app/browser/tests/test_launchpadroot.py
+++ b/lib/lp/app/browser/tests/test_launchpadroot.py
@@ -179,7 +179,7 @@ class LaunchpadRootIndexViewTestCase(TestCaseWithFactory):
         markup = BeautifulSoup(
             result, parse_only=SoupStrainer(id='homepage-blogposts'))
         self.assertEqual(['called'], calls)
-        items = markup.findAll('li', 'news')
+        items = markup.find_all('li', 'news')
         # Notice about launchpad being opened is always added at the end
         self.assertEqual(3, len(items))
         a = items[-1].find("a")
@@ -226,7 +226,7 @@ class LaunchpadRootIndexViewTestCase(TestCaseWithFactory):
             result = view()
         markup = BeautifulSoup(
             result, parse_only=SoupStrainer(id='homepage-blogposts'))
-        items = markup.findAll('li', 'news')
+        items = markup.find_all('li', 'news')
         self.assertEqual(3, len(items))
 
     def test_featured_projects_query_count(self):
diff --git a/lib/lp/app/doc/hierarchical-menu.txt b/lib/lp/app/doc/hierarchical-menu.txt
index e9f411e..380d1bb 100644
--- a/lib/lp/app/doc/hierarchical-menu.txt
+++ b/lib/lp/app/doc/hierarchical-menu.txt
@@ -254,7 +254,7 @@ location bar.
     # Borrowed from lp.testing.pages.print_location()
     >>> def print_hierarchy(html):
     ...     soup = BeautifulSoup(html)
-    ...     hierarchy = soup.find(attrs={'class': 'breadcrumbs'}).findAll(
+    ...     hierarchy = soup.find(attrs={'class': 'breadcrumbs'}).find_all(
     ...         recursive=False)
     ...     segments = [extract_text(step) for step in hierarchy]
     ...     print('Location:', ' > '.join(segments))
diff --git a/lib/lp/app/doc/launchpadform.txt b/lib/lp/app/doc/launchpadform.txt
index e02851b..e79a5ec 100644
--- a/lib/lp/app/doc/launchpadform.txt
+++ b/lib/lp/app/doc/launchpadform.txt
@@ -491,7 +491,7 @@ By default, all widgets are visible.
 
   >>> from lp.services.beautifulsoup import BeautifulSoup
   >>> soup = BeautifulSoup(view())
-  >>> for input in soup.findAll('input'):
+  >>> for input in soup.find_all('input'):
   ...     print(input)
   <input ... name="field.displayname" ... type="text" ...
 
@@ -505,7 +505,7 @@ using its hidden() method, which should return a hidden <input> tag.
   >>> view = TestWidgetVisibility2(context, request)
 
   >>> soup = BeautifulSoup(view())
-  >>> for input in soup.findAll('input'):
+  >>> for input in soup.find_all('input'):
   ...     print(input)
   <input ... name="field.displayname" type="hidden" ...
 
diff --git a/lib/lp/app/doc/tales.txt b/lib/lp/app/doc/tales.txt
index a6b31fe..0d56ac6 100644
--- a/lib/lp/app/doc/tales.txt
+++ b/lib/lp/app/doc/tales.txt
@@ -670,7 +670,7 @@ size), plus extra links for the MD5 hash and signature of that PRF.
     >>> from lp.services.beautifulsoup import BeautifulSoup
     >>> def print_hrefs_with_titles(html):
     ...     soup = BeautifulSoup(html)
-    ...     for link in soup.findAll('a'):
+    ...     for link in soup.find_all('a'):
     ...         attrs = dict(link.attrs)
     ...         print("%s: %s" % (attrs.get('href'), attrs.get('title', '')))
 
diff --git a/lib/lp/app/stories/launchpad-root/xx-featuredprojects.txt b/lib/lp/app/stories/launchpad-root/xx-featuredprojects.txt
index 2ca6482..bf0dd16 100644
--- a/lib/lp/app/stories/launchpad-root/xx-featuredprojects.txt
+++ b/lib/lp/app/stories/launchpad-root/xx-featuredprojects.txt
@@ -33,7 +33,7 @@ projects' pages in Launchpad. The "project of the day" is listed separately.
     GNOME
 
     >>> featured_list = featured.find('', 'featured-projects-list')
-    >>> for link in featured_list.findAll('a'):
+    >>> for link in featured_list.find_all('a'):
     ...     print(extract_text(link))
     Gnome Applets
     Bazaar
@@ -100,7 +100,7 @@ is now at index '4' and is therefore displayed as the top project:
     Gentoo
 
     >>> featured_list = featured.find('', 'featured-projects-list')
-    >>> for link in featured_list.findAll('a'):
+    >>> for link in featured_list.find_all('a'):
     ...     print(extract_text(link))
     Apache
     Gnome Applets
@@ -128,7 +128,7 @@ that Apache has been removed:
 
     >>> anon_browser.open('http://launchpad.test/')
     >>> featured = find_tag_by_id(anon_browser.contents, 'homepage-featured')
-    >>> for link in featured.findAll('a'):
+    >>> for link in featured.find_all('a'):
     ...     print(extract_text(link))
     GNOME
     Gnome Applets
diff --git a/lib/lp/app/widgets/doc/launchpad-radio-widget.txt b/lib/lp/app/widgets/doc/launchpad-radio-widget.txt
index ac23c43..017d69d 100644
--- a/lib/lp/app/widgets/doc/launchpad-radio-widget.txt
+++ b/lib/lp/app/widgets/doc/launchpad-radio-widget.txt
@@ -24,7 +24,7 @@ The widget is rendered as a collection of labels with the radio
 buttons inside.
 
     >>> html = BeautifulSoup(radio_widget())
-    >>> for label in html.findAll('label'):
+    >>> for label in html.find_all('label'):
     ...     print(label.decode_contents(formatter='html'))
     <input checked="checked" class="radioType" id="field.branch_type.0"
            name="field.branch_type" type="radio" value="HOSTED"/>&nbsp;Hosted
diff --git a/lib/lp/app/widgets/tests/test_launchpadtarget.py b/lib/lp/app/widgets/tests/test_launchpadtarget.py
index 147e44d..13030cd 100644
--- a/lib/lp/app/widgets/tests/test_launchpadtarget.py
+++ b/lib/lp/app/widgets/tests/test_launchpadtarget.py
@@ -318,6 +318,6 @@ class LaunchpadTargetWidgetTestCase(TestCaseWithFactory):
             'field.target.product',
             ]
         soup = BeautifulSoup(markup)
-        fields = soup.findAll(['input', 'select'], {'id': re.compile('.*')})
+        fields = soup.find_all(['input', 'select'], {'id': re.compile('.*')})
         ids = [field['id'] for field in fields]
         self.assertContentEqual(expected_ids, ids)
diff --git a/lib/lp/blueprints/browser/tests/test_person_upcomingwork.py b/lib/lp/blueprints/browser/tests/test_person_upcomingwork.py
index 8aa2365..e5c79b2 100644
--- a/lib/lp/blueprints/browser/tests/test_person_upcomingwork.py
+++ b/lib/lp/blueprints/browser/tests/test_person_upcomingwork.py
@@ -296,7 +296,7 @@ class TestPersonUpcomingWork(BrowserTestCase):
         groups = find_tags_by_class(browser.contents, 'collapsible-body')
         self.assertEqual(1, len(groups))
         tbody = groups[0]
-        title_td = tbody.findChildren('td')[0]
+        title_td = tbody.find_all('td')[0]
         self.assertEqual(
             "<td>\n<span>&lt;script&gt;window.alert('XSS')&lt;/script&gt;"
             "</span>\n</td>", str(title_td))
diff --git a/lib/lp/blueprints/stories/sprints/sprint-settopics.txt b/lib/lp/blueprints/stories/sprints/sprint-settopics.txt
index 9bf8a84..9598918 100644
--- a/lib/lp/blueprints/stories/sprints/sprint-settopics.txt
+++ b/lib/lp/blueprints/stories/sprints/sprint-settopics.txt
@@ -58,7 +58,7 @@ First choose a driver for the UDS Guacamole sprint.
   'http://launchpad.test/sprints/uds-guacamole'
 
   >>> meeting_drivers = find_tag_by_id(browser.contents, 'meeting-drivers')
-  >>> print(extract_text(meeting_drivers.findNext('a')))
+  >>> print(extract_text(meeting_drivers.find_next('a')))
   Ubuntu Team
 
 Any member of the Ubuntu-Team can now approve and/or decline items to the UDS 
diff --git a/lib/lp/blueprints/stories/sprints/xx-sprint-meeting-export.txt b/lib/lp/blueprints/stories/sprints/xx-sprint-meeting-export.txt
index f9e2b34..2351560 100644
--- a/lib/lp/blueprints/stories/sprints/xx-sprint-meeting-export.txt
+++ b/lib/lp/blueprints/stories/sprints/xx-sprint-meeting-export.txt
@@ -33,7 +33,7 @@ The attendees element contains a list of person elements.
     >>> import operator
     >>> from lp.services.beautifulsoup import BeautifulSoup
     >>> soup = BeautifulSoup(browser.contents, 'xml')
-    >>> people = soup.find('attendees').findAll('person')
+    >>> people = soup.find('attendees').find_all('person')
     >>> for person in sorted(people, key=operator.itemgetter("displayname")):
     ...     print("%(displayname)s, %(name)s, %(start)s -> %(end)s" % person)
     Celso Providelo, cprov, 2005-10-07T23:30:00Z -> 2005-11-17T00:11:00Z
@@ -43,7 +43,7 @@ The <unscheduled /> element contains a list of meetings. Each of these
 actually refers to a Specification.
 
     >>> soup = BeautifulSoup(browser.contents, 'xml')
-    >>> meetings = soup.find('unscheduled').findAll('meeting')
+    >>> meetings = soup.find('unscheduled').find_all('meeting')
     >>> for meeting in meetings:
     ...     print("%(id)s: %(name)s, %(lpurl)s" % meeting)
     3: svg-support, .../+spec/svg-support
diff --git a/lib/lp/blueprints/stories/sprints/xx-sprints.txt b/lib/lp/blueprints/stories/sprints/xx-sprints.txt
index f826a65..0068d91 100644
--- a/lib/lp/blueprints/stories/sprints/xx-sprints.txt
+++ b/lib/lp/blueprints/stories/sprints/xx-sprints.txt
@@ -266,7 +266,7 @@ We should be able to see the workload of a sprint:
 We should be able to see the spec assignment table of a sprint:
 
     >>> mainarea = find_main_content(anon_browser.contents)
-    >>> for header in mainarea.findAll('th'):
+    >>> for header in mainarea.find_all('th'):
     ...     print(header.decode_contents())
     Priority
     Name
@@ -381,7 +381,7 @@ Now, Sample Person should be listed as an attendee.
     >>> def print_attendees(sprint_page):
     ...     """Print the attendees listed in the attendees portlet."""
     ...     attendees_portlet = find_portlet(sprint_page, 'Attendees')
-    ...     for li in attendees_portlet.findAll('ul')[0].findAll('li'):
+    ...     for li in attendees_portlet.find_all('ul')[0].find_all('li'):
     ...         print(li.a.string)
 
     >>> print_attendees(browser.contents)
diff --git a/lib/lp/blueprints/stories/standalone/subscribing.txt b/lib/lp/blueprints/stories/standalone/subscribing.txt
index 9116856..f3feaef 100644
--- a/lib/lp/blueprints/stories/standalone/subscribing.txt
+++ b/lib/lp/blueprints/stories/standalone/subscribing.txt
@@ -166,7 +166,7 @@ based on the subscription change we have made above.
 
     >>> subscribers = find_tags_by_class(browser.contents, 'subscriber')
     >>> for subscriber in subscribers:
-    ...     a_tags = subscriber.findAll('a')
+    ...     a_tags = subscriber.find_all('a')
     ...     img = a_tags[0].find('img')
     ...     print(img['src'], end=' ')
     ...     print(a_tags[1].string)
@@ -191,7 +191,7 @@ based on the subscription change we have made above.
 
     >>> subscribers = find_tags_by_class(browser.contents, 'subscriber')
     >>> for subscriber in subscribers:
-    ...     a_tags = subscriber.findAll('a')
+    ...     a_tags = subscriber.find_all('a')
     ...     img = a_tags[0].find('img')
     ...     print(img['src'], end=' ')
     ...     print(a_tags[1].string)
@@ -324,7 +324,7 @@ not.
     ...   "http://blueprints.launchpad.test/firefox/+spec/svg-support";)
     >>> subscribers = find_tags_by_class(browser.contents, 'subscriber')
     >>> for subscriber in subscribers:
-    ...     a_tags = subscriber.findAll('a')
+    ...     a_tags = subscriber.find_all('a')
     ...     img = a_tags[0].find('img')
     ...     print(img['src'], end=' ')
     ...     print(a_tags[1].string)
diff --git a/lib/lp/bugs/browser/tests/test_bugtarget_filebug.py b/lib/lp/bugs/browser/tests/test_bugtarget_filebug.py
index 9468505..c80a5e9 100644
--- a/lib/lp/bugs/browser/tests/test_bugtarget_filebug.py
+++ b/lib/lp/bugs/browser/tests/test_bugtarget_filebug.py
@@ -269,7 +269,7 @@ class TestBugTargetFileBugConfirmationMessage(TestCaseWithFactory):
         main_content = find_main_content(html)
         filebug_form = main_content.find(id='filebug-form')
         self.assertIsNot(None, filebug_form)
-        filebug_form_container = filebug_form.findParents(
+        filebug_form_container = filebug_form.find_parents(
             id='filebug-form-container')[0]
         class_attrs = [item.strip()
                        for item in filebug_form_container['class']]
diff --git a/lib/lp/bugs/browser/tests/test_bugtask.py b/lib/lp/bugs/browser/tests/test_bugtask.py
index 075bf72..c71af4f 100644
--- a/lib/lp/bugs/browser/tests/test_bugtask.py
+++ b/lib/lp/bugs/browser/tests/test_bugtask.py
@@ -1120,7 +1120,7 @@ class TestBugTaskDeleteView(TestCaseWithFactory):
             view.request.response.getHeader('content-type'), 'text/html')
         table = find_tag_by_id(result_html, 'affected-software')
         self.assertIsNotNone(table)
-        [row] = table.tbody.findAll('tr', {'class': 'highlight'})
+        [row] = table.tbody.find_all('tr', {'class': 'highlight'})
         target_link = row.find('a', {'class': 'sprite product'})
         self.assertIn(
             bug.default_bugtask.bugtargetdisplayname, target_link)
diff --git a/lib/lp/bugs/externalbugtracker/mantis.py b/lib/lp/bugs/externalbugtracker/mantis.py
index 6673378..c3257a5 100644
--- a/lib/lp/bugs/externalbugtracker/mantis.py
+++ b/lib/lp/bugs/externalbugtracker/mantis.py
@@ -359,7 +359,7 @@ class Mantis(ExternalBugTracker):
                                and not isinstance(node, Comment)))
         if app_error:
             app_error_code = ''.join(c for c in app_error if c.isdigit())
-            app_error_message = app_error.findNext('p')
+            app_error_message = app_error.find_next('p')
             if app_error_message is not None:
                 app_error_message = app_error_message.string
             return app_error_code, app_error_message
@@ -388,7 +388,7 @@ class Mantis(ExternalBugTracker):
         if key_node is None:
             raise UnparsableBugData("Key %r not found." % (key,))
 
-        value_cell = key_node.findNext('td')
+        value_cell = key_node.find_next('td')
         if value_cell is None:
             raise UnparsableBugData(
                 "Value cell for key %r not found." % (key,))
diff --git a/lib/lp/bugs/externalbugtracker/sourceforge.py b/lib/lp/bugs/externalbugtracker/sourceforge.py
index 1d259c8..41b83dc 100644
--- a/lib/lp/bugs/externalbugtracker/sourceforge.py
+++ b/lib/lp/bugs/externalbugtracker/sourceforge.py
@@ -61,7 +61,7 @@ class SourceForge(ExternalBugTracker):
                 # Happily, BeautifulSoup will turn the contents of this tag
                 # into a newline-delimited list from which we can then
                 # extract the requisite data.
-                status_row = status_tag.findParent().findParent()
+                status_row = status_tag.find_parent().find_parent()
                 status = status_row.contents[-1]
                 status = status.strip()
             else:
@@ -84,7 +84,7 @@ class SourceForge(ExternalBugTracker):
             # find it it's not critical.
             resolution_tag = soup.find(text=re.compile('Resolution:'))
             if resolution_tag:
-                resolution_row = resolution_tag.findParent().findParent()
+                resolution_row = resolution_tag.find_parent().find_parent()
                 resolution = resolution_row.contents[-1]
                 resolution = resolution.strip()
             else:
diff --git a/lib/lp/bugs/scripts/bzremotecomponentfinder.py b/lib/lp/bugs/scripts/bzremotecomponentfinder.py
index 8286e73..2dac377 100644
--- a/lib/lp/bugs/scripts/bzremotecomponentfinder.py
+++ b/lib/lp/bugs/scripts/bzremotecomponentfinder.py
@@ -80,7 +80,7 @@ class BugzillaRemoteComponentScraper:
                     'versions': None,
                     })
 
-        for script_text in soup.findAll(name="script"):
+        for script_text in soup.find_all(name="script"):
             if script_text is None or script_text.string is None:
                 continue
             for line in script_text.string.split(";"):
diff --git a/lib/lp/bugs/stories/bug-also-affects/xx-bug-also-affects.txt b/lib/lp/bugs/stories/bug-also-affects/xx-bug-also-affects.txt
index 293dc89..3b0f4c5 100644
--- a/lib/lp/bugs/stories/bug-also-affects/xx-bug-also-affects.txt
+++ b/lib/lp/bugs/stories/bug-also-affects/xx-bug-also-affects.txt
@@ -506,7 +506,7 @@ But we're happy, so we add the bug watch.
     >>> bug_watches = find_portlet(
     ...     user_browser.contents, 'Remote bug watches')
     >>> for li in bug_watches('li'):
-    ...     print(li.findAll('a')[0].decode_contents())
+    ...     print(li.find_all('a')[0].decode_contents())
     gnome-bugzilla #42
 
 It's possible to supply an HTTPS URL, even though the bug tracker's base
@@ -532,7 +532,7 @@ The URL was automatically converted to HTTP:
     >>> bug_watches = find_portlet(
     ...     user_browser.contents, 'Remote bug watches')
     >>> for li in bug_watches('li'):
-    ...     print(li.findAll('a')[0]['href'])
+    ...     print(li.find_all('a')[0]['href'])
     http://bugzilla.gnome.org/bugs/show_bug.cgi?id=42
     http://bugzilla.gnome.org/bugs/show_bug.cgi?id=84
 
@@ -597,7 +597,7 @@ registered automatically.
     >>> bug_watches = find_portlet(
     ...     user_browser.contents, 'Remote bug watches')
     >>> for li in bug_watches('li'):
-    ...     print(li.findAll('a')[0].decode_contents())
+    ...     print(li.find_all('a')[0].decode_contents())
     gnome-bugzilla #42
     gnome-bugzilla #84
     auto-new.trac #42
@@ -623,7 +623,7 @@ it to HTTP on their behalf:
     >>> bug_watches = find_portlet(
     ...     user_browser.contents, 'Remote bug watches')
     >>> for li in bug_watches('li'):
-    ...     print(li.findAll('a')[0]['href'])
+    ...     print(li.find_all('a')[0]['href'])
     http://bugzilla.gnome.org/bugs/show_bug.cgi?id=168
     http://bugzilla.gnome.org/bugs/show_bug.cgi?id=42
     http://bugzilla.gnome.org/bugs/show_bug.cgi?id=84
diff --git a/lib/lp/bugs/stories/bug-also-affects/xx-duplicate-bugwatches.txt b/lib/lp/bugs/stories/bug-also-affects/xx-duplicate-bugwatches.txt
index 3210e65..9d380cf 100644
--- a/lib/lp/bugs/stories/bug-also-affects/xx-duplicate-bugwatches.txt
+++ b/lib/lp/bugs/stories/bug-also-affects/xx-duplicate-bugwatches.txt
@@ -20,8 +20,8 @@ Now we can see the added bug watch in the bug watch portlet.
 
     >>> bugwatch_portlet = find_portlet(
     ...     user_browser.contents, 'Remote bug watches')
-    >>> for li_tag in bugwatch_portlet.findAll('li'):
-    ...     print(li_tag.findAll('a')[0].decode_contents())
+    >>> for li_tag in bugwatch_portlet.find_all('li'):
+    ...     print(li_tag.find_all('a')[0].decode_contents())
     debbugs #42
 
 If we add another bug watch, pointing to the same URL, the previous one
@@ -46,8 +46,8 @@ will be used; i.e., another one won't be added.
 
     >>> bugwatch_portlet = find_portlet(
     ...     user_browser.contents, 'Remote bug watches')
-    >>> for li_tag in bugwatch_portlet.findAll('li'):
-    ...     print(li_tag.findAll('a')[0].string)
+    >>> for li_tag in bugwatch_portlet.find_all('li'):
+    ...     print(li_tag.find_all('a')[0].string)
     debbugs #42
 
 Both the thunderbird and gnome-terminal Debian tasks are pointing to the
diff --git a/lib/lp/bugs/stories/bug-tags/xx-official-bug-tags.txt b/lib/lp/bugs/stories/bug-tags/xx-official-bug-tags.txt
index a64997f..9b3596f 100644
--- a/lib/lp/bugs/stories/bug-tags/xx-official-bug-tags.txt
+++ b/lib/lp/bugs/stories/bug-tags/xx-official-bug-tags.txt
@@ -106,7 +106,7 @@ They are grouped together at the beginning of the list.
 
     >>> browser.open(gfoobar_bug_url)
     >>> tags_div = find_tag_by_id(browser.contents, 'bug-tags')
-    >>> print_bug_tag_anchors(tags_div.findAll('a'))
+    >>> print_bug_tag_anchors(tags_div.find_all('a'))
     official-tag alpha
     official-tag charlie
     unofficial-tag bravo
diff --git a/lib/lp/bugs/stories/bugattachments/xx-bugattachments.txt b/lib/lp/bugs/stories/bugattachments/xx-bugattachments.txt
index 404810e..564d154 100644
--- a/lib/lp/bugs/stories/bugattachments/xx-bugattachments.txt
+++ b/lib/lp/bugs/stories/bugattachments/xx-bugattachments.txt
@@ -36,7 +36,7 @@ After we added the attachment, we get redirected to the bug page.
 We can check that the attachment is there
 
   >>> attachments = find_portlet(user_browser.contents, 'Bug attachments')
-  >>> for li_tag in attachments.findAll('li', 'download-attachment'):
+  >>> for li_tag in attachments.find_all('li', 'download-attachment'):
   ...   print(li_tag.a.decode_contents())
   Some information
 
@@ -69,7 +69,7 @@ also not necessary to enter a comment in order to add an attachment.
   'http://bugs.launchpad.test/firefox/+bug/1'
 
   >>> attachments = find_portlet(user_browser.contents, 'Bug attachments')
-  >>> for li_tag in attachments.findAll('li', 'download-attachment'):
+  >>> for li_tag in attachments.find_all('li', 'download-attachment'):
   ...   print(li_tag.a.decode_contents())
   Some information
   bar.txt
@@ -146,7 +146,7 @@ listed as an ordinary attachment.
   >>> user_browser.url
   'http://bugs.launchpad.test/firefox/+bug/1'
   >>> attachments = find_portlet(user_browser.contents, 'Bug attachments')
-  >>> for li_tag in attachments.findAll('li', 'download-attachment'):
+  >>> for li_tag in attachments.find_all('li', 'download-attachment'):
   ...   print(li_tag.a.decode_contents())
   Some information
   bar.txt
@@ -203,7 +203,7 @@ Now we are redirected to the main bug page...
 ...and the new attachment is listed as a patch.
 
   >>> patches = find_portlet(user_browser.contents, 'Patches')
-  >>> for li_tag in patches.findAll('li', 'download-attachment'):
+  >>> for li_tag in patches.find_all('li', 'download-attachment'):
   ...   print(li_tag.a.decode_contents())
   A fix for this bug.
   A better icon for foo
@@ -331,7 +331,7 @@ Now we are redirected to the main bug page...
 "Patches"...
 
     >>> patches = find_portlet(user_browser.contents, 'Patches')
-    >>> for li_tag in patches.findAll('li', 'download-attachment'):
+    >>> for li_tag in patches.find_all('li', 'download-attachment'):
     ...   print(li_tag.a.decode_contents())
     Another title
     A fix for this bug.
@@ -343,7 +343,7 @@ Now we are redirected to the main bug page...
 ...while it is gone from the portlet "Bug attachments".
 
     >>> attachments = find_portlet(user_browser.contents, 'Bug attachments')
-    >>> for li_tag in attachments.findAll('li', 'download-attachment'):
+    >>> for li_tag in attachments.find_all('li', 'download-attachment'):
     ...   print(li_tag.a.decode_contents())
     bar.txt
     More data
diff --git a/lib/lp/bugs/stories/bugattachments/xx-delete-bug-attachment.txt b/lib/lp/bugs/stories/bugattachments/xx-delete-bug-attachment.txt
index 459a548..f705c8a 100644
--- a/lib/lp/bugs/stories/bugattachments/xx-delete-bug-attachment.txt
+++ b/lib/lp/bugs/stories/bugattachments/xx-delete-bug-attachment.txt
@@ -17,7 +17,7 @@ The attachment is now visible on the bug page.
     >>> user_browser.open('http://launchpad.test/bugs/2')
     >>> attachment_portlet = find_portlet(
     ...     user_browser.contents, 'Bug attachments')
-    >>> for li in attachment_portlet.findAll('li', 'download-attachment'):
+    >>> for li in attachment_portlet.find_all('li', 'download-attachment'):
     ...     print(li.a.decode_contents())
     Great deal
 
diff --git a/lib/lp/bugs/stories/bugs/xx-bug-comments-truncated.txt b/lib/lp/bugs/stories/bugs/xx-bug-comments-truncated.txt
index 6bd8170..0bc014c 100644
--- a/lib/lp/bugs/stories/bugs/xx-bug-comments-truncated.txt
+++ b/lib/lp/bugs/stories/bugs/xx-bug-comments-truncated.txt
@@ -123,7 +123,7 @@ email addresses in messages.
     Bug #2 (blackhole) ... : Bugs : Tomcat
     >>> text = find_tags_by_class(
     ...     user_browser.contents, 'boardCommentBody')[-1]
-    >>> print(extract_text(text.findAll('p')[-2]))
+    >>> print(extract_text(text.find_all('p')[-2]))
     --
     ______________________
     human@xxxxxxxxxxx
@@ -138,7 +138,7 @@ unauthenticated, so they will see the obfuscated email address.
     Bug #2 (blackhole) ... : Bugs : Tomcat
     >>> text = find_tags_by_class(
     ...     anon_browser.contents, 'boardCommentBody')[-1]
-    >>> print(extract_text(text.findAll('p')[-2]))
+    >>> print(extract_text(text.find_all('p')[-2]))
     --
     ______________________
     &lt;email address hidden&gt;
@@ -155,7 +155,7 @@ class.
 Pagetests cannot test CSS and JS behaviour.  We can only check that the markup
 includes the hooks for the style and script.
 
-    >>> print(text.findAll('p')[-2])
+    >>> print(text.find_all('p')[-2])
     <p><span class="foldable">--...
     &lt;email address hidden&gt;<br/>
     Witty signatures rock!
@@ -169,7 +169,7 @@ are wrapped with a tag of 'foldable' class, citation lines are
 always displayed. Again we can continue with the anonymous user to
 see the markup.
 
-    >>> print(text.findAll('p')[-3])
+    >>> print(text.find_all('p')[-3])
     <p>Somebody said sometime ago:<br/>
     <span class="foldable-quoted">
     &gt; 1. Remove the letters  c, j, q, x, w<br/>
@@ -181,12 +181,12 @@ PGP blocks in signed messages are identified by a paragraph that
 starts with '-----BEGIN PGP'.  There are two kinds of PGP blocks,
 the notice that the message is signed, and the signature.
 
-    >>> print(text.findAll('p')[0])
+    >>> print(text.find_all('p')[0])
     <p><span class="foldable">-----BEGIN PGP SIGNED MESSAGE-----<br/>
     Hash: SHA1
     </span></p>
 
-    >>> print(text.findAll('p')[-1])
+    >>> print(text.find_all('p')[-1])
     <p><span class="foldable">-----BEGIN PGP SIGNATURE-----<br/>
     Version: GnuPG v1.4.1 (GNU/Linux)<br/>
     Comment: Using GnuPG with Thunderbird<br/>
diff --git a/lib/lp/bugs/stories/bugs/xx-incomplete-bugs.txt b/lib/lp/bugs/stories/bugs/xx-incomplete-bugs.txt
index 6df5a07..f93df7f 100644
--- a/lib/lp/bugs/stories/bugs/xx-incomplete-bugs.txt
+++ b/lib/lp/bugs/stories/bugs/xx-incomplete-bugs.txt
@@ -25,7 +25,7 @@ was supplied (through comments) are 'Incomplete (without response)'.
     >>> user_browser.getControl(name='field.status:list').value = (
     ...     ['INCOMPLETE_WITHOUT_RESPONSE'])
     >>> user_browser.getControl('Search', index=1).click()
-    >>> find_tag_by_id(user_browser.contents, 'bugs-table-listing').findChild('a')
+    >>> find_tag_by_id(user_browser.contents, 'bugs-table-listing').find('a')
     <a class="bugtitle" href="http://bugs.launchpad.test/jokosher/+bug/11";>...</a>
 
 Bugs that have been marked incomplete and for which new information was
@@ -62,7 +62,7 @@ They try again to find that bug using the advanced search form.
     >>> user_browser.getControl(name='field.status:list').value = (
     ...     ['INCOMPLETE_WITH_RESPONSE'])
     >>> user_browser.getControl('Search', index=1).click()
-    >>> find_tag_by_id(user_browser.contents, 'bugs-table-listing').findChild('a')
+    >>> find_tag_by_id(user_browser.contents, 'bugs-table-listing').find('a')
     <a class="bugtitle" href="http://bugs.launchpad.test/jokosher/+bug/11";>...</a>
 
 The bug is there, since they supplied new information in a comment. No
diff --git a/lib/lp/bugs/stories/bugs/xx-malone-homepage.txt b/lib/lp/bugs/stories/bugs/xx-malone-homepage.txt
index e5ac687..71d20ff 100644
--- a/lib/lp/bugs/stories/bugs/xx-malone-homepage.txt
+++ b/lib/lp/bugs/stories/bugs/xx-malone-homepage.txt
@@ -10,7 +10,7 @@ Say hello to Bugs. :)
 There are a few related pages linked in a portlet:
 
     >>> related_pages = find_portlet(browser.contents, 'Related pages')
-    >>> for link in related_pages.findAll('a'):
+    >>> for link in related_pages.find_all('a'):
     ...     print("%s\n  --> %s" % (extract_text(link), link.get('href')))
     Bug trackers
     --> http://bugs.launchpad.test/bugs/bugtrackers
diff --git a/lib/lp/bugs/stories/bugs/xx-remote-bug-comments.txt b/lib/lp/bugs/stories/bugs/xx-remote-bug-comments.txt
index 874f315..7fa1e82 100644
--- a/lib/lp/bugs/stories/bugs/xx-remote-bug-comments.txt
+++ b/lib/lp/bugs/stories/bugs/xx-remote-bug-comments.txt
@@ -64,7 +64,7 @@ mark the comment as 'awaiting synchronization' until it makes its way
 to the remote bug tracker.
 
     >>> activity = new_bug_comment.find(attrs={'class': 'boardCommentActivity'})
-    >>> print(extract_text(activity.findAll('td')[1]))
+    >>> print(extract_text(activity.find_all('td')[1]))
     Awaiting synchronization
 
 When the comment is synchronized, it receives a remote comment id, and
diff --git a/lib/lp/bugs/stories/bugs/xx-unique-ids-on-bug-page.txt b/lib/lp/bugs/stories/bugs/xx-unique-ids-on-bug-page.txt
index 0fec67a..5fc9584 100644
--- a/lib/lp/bugs/stories/bugs/xx-unique-ids-on-bug-page.txt
+++ b/lib/lp/bugs/stories/bugs/xx-unique-ids-on-bug-page.txt
@@ -21,7 +21,7 @@ Still, the ids of the fields are unique.
     >>> non_unique_ids = []
     >>> found_ids = set()
     >>> import re
-    >>> for tag in soup.findAll(id=re.compile('.+')):
+    >>> for tag in soup.find_all(id=re.compile('.+')):
     ...    if tag['id'] in found_ids:
     ...        non_unique_ids.append(tag['id'])
     ...    found_ids.add(tag['id'])
diff --git a/lib/lp/bugs/stories/bugtask-searches/xx-listing-basics.txt b/lib/lp/bugs/stories/bugtask-searches/xx-listing-basics.txt
index 7300d25..2076c5b 100644
--- a/lib/lp/bugs/stories/bugtask-searches/xx-listing-basics.txt
+++ b/lib/lp/bugs/stories/bugtask-searches/xx-listing-basics.txt
@@ -205,9 +205,9 @@ We display bug badges for associated branches, specifications, patches, etc.
 
     >>> def names_and_branches(contents):
     ...     listing = find_tag_by_id(contents, 'bugs-table-listing')
-    ...     for row in listing.findAll(None, {'class': 'buglisting-row'}):
+    ...     for row in listing.find_all(None, {'class': 'buglisting-row'}):
     ...         badge_cell = row.find(None, {'class': 'bug-related-icons'})
-    ...         spans = badge_cell.findAll('span')
+    ...         spans = badge_cell.find_all('span')
     ...         for span in spans:
     ...            print("  Badge:", span.get('alt'))
 
diff --git a/lib/lp/bugs/stories/bugtracker/bugtrackers-index.txt b/lib/lp/bugs/stories/bugtracker/bugtrackers-index.txt
index 74bc2e2..b3d888d 100644
--- a/lib/lp/bugs/stories/bugtracker/bugtrackers-index.txt
+++ b/lib/lp/bugs/stories/bugtracker/bugtrackers-index.txt
@@ -12,8 +12,8 @@ The page presents a table with all bugtrackers currently registered:
 
     >>> def print_tracker_table(browser):
     ...     table = find_tag_by_id(browser.contents, 'trackers')
-    ...     for row in table.tbody.findAll('tr'):
-    ...         title, location, linked, type, watches = row.findAll('td')
+    ...     for row in table.tbody.find_all('tr'):
+    ...         title, location, linked, type, watches = row.find_all('td')
     ...         print('------------------------')
     ...         print('\n'.join([
     ...             extract_text(title),
diff --git a/lib/lp/bugs/stories/bugtracker/xx-bugtracker.txt b/lib/lp/bugs/stories/bugtracker/xx-bugtracker.txt
index 44e88a4..1b20aef 100644
--- a/lib/lp/bugs/stories/bugtracker/xx-bugtracker.txt
+++ b/lib/lp/bugs/stories/bugtracker/xx-bugtracker.txt
@@ -664,10 +664,10 @@ Then we can see how logged-in users and anonymous users see the page:
 
     >>> def print_watches(browser):
     ...     watches = find_tag_by_id(
-    ...         browser.contents, 'latestwatches').tbody.findAll('tr')
+    ...         browser.contents, 'latestwatches').tbody.find_all('tr')
     ...     for watch in watches:
     ...         bug, remote_bug, status, last_checked, next_check = (
-    ...             watch.findAll('td'))
+    ...             watch.find_all('td'))
     ...         print(extract_text(bug))
     ...         print('  --> %s: %s' % (
     ...             extract_text(remote_bug),
diff --git a/lib/lp/bugs/stories/bugwatches/xx-bugtask-bugwatch-linkage.txt b/lib/lp/bugs/stories/bugwatches/xx-bugtask-bugwatch-linkage.txt
index f9193ac..a3d31eb 100644
--- a/lib/lp/bugs/stories/bugwatches/xx-bugtask-bugwatch-linkage.txt
+++ b/lib/lp/bugs/stories/bugwatches/xx-bugtask-bugwatch-linkage.txt
@@ -119,8 +119,8 @@ created, instead the old one will be used.
     'http://bugs.launchpad.test/debian/+source/mozilla-firefox/+bug/1'
 
     >>> bugwatch_portlet = find_portlet(browser.contents, 'Remote bug watches')
-    >>> for li_tag in bugwatch_portlet.findAll('li'):
-    ...     print(li_tag.findAll('a')[0].string)
+    >>> for li_tag in bugwatch_portlet.find_all('li'):
+    ...     print(li_tag.find_all('a')[0].string)
     mozilla.org #123543
     mozilla.org #2000
     mozilla.org #42
diff --git a/lib/lp/bugs/stories/cve/cve-linking.txt b/lib/lp/bugs/stories/cve/cve-linking.txt
index 6524905..bdd98f7 100644
--- a/lib/lp/bugs/stories/cve/cve-linking.txt
+++ b/lib/lp/bugs/stories/cve/cve-linking.txt
@@ -75,7 +75,7 @@ forms. This also provides us with a means to test implicitly that bug
 
     >>> admin_browser.open('http://launchpad.test/bugs/cve/1999-8979')
     >>> for form in find_main_content(
-    ...     admin_browser.contents).findAll('form'):
+    ...         admin_browser.contents).find_all('form'):
     ...     print(form.decode_contents())
 
 Similarly, there should be no links allowing the user to mark the bug as
diff --git a/lib/lp/bugs/stories/guided-filebug/xx-bug-reporting-guidelines.txt b/lib/lp/bugs/stories/guided-filebug/xx-bug-reporting-guidelines.txt
index 96782f6..2210164 100644
--- a/lib/lp/bugs/stories/guided-filebug/xx-bug-reporting-guidelines.txt
+++ b/lib/lp/bugs/stories/guided-filebug/xx-bug-reporting-guidelines.txt
@@ -43,7 +43,7 @@ page, because that page does not include a bug description field.
     ...         user_browser.contents, 'informational message')[0]))
 
     >>> def print_visible_guidelines(context_path, guidelines):
-    ...     result = guidelines.findParents(id='filebug-form-container')
+    ...     result = guidelines.find_parents(id='filebug-form-container')
     ...     style_attrs = []
     ...     if result:
     ...         filebug_form_container = result[0]
diff --git a/lib/lp/bugs/stories/guided-filebug/xx-displaying-similar-bugs.txt b/lib/lp/bugs/stories/guided-filebug/xx-displaying-similar-bugs.txt
index bd092de..b1524d2 100644
--- a/lib/lp/bugs/stories/guided-filebug/xx-displaying-similar-bugs.txt
+++ b/lib/lp/bugs/stories/guided-filebug/xx-displaying-similar-bugs.txt
@@ -10,7 +10,7 @@ duplicates.
     >>> def print_similar_bugs(content):
     ...     bugs_list = find_tags_by_class(content, 'similar-bug')
     ...     for node in bugs_list:
-    ...         label = node.findAll('label')[0]
+    ...         label = node.find_all('label')[0]
     ...         label_class = ' '.join(label['class'])
     ...         text_lines = [line.strip() for line in
     ...                       extract_text(node).splitlines()]
diff --git a/lib/lp/bugs/stories/guided-filebug/xx-distro-guided-filebug.txt b/lib/lp/bugs/stories/guided-filebug/xx-distro-guided-filebug.txt
index 167ab3a..f835994 100644
--- a/lib/lp/bugs/stories/guided-filebug/xx-distro-guided-filebug.txt
+++ b/lib/lp/bugs/stories/guided-filebug/xx-distro-guided-filebug.txt
@@ -59,7 +59,7 @@ description.
     >>> import re
 
     >>> page_soup = find_main_content(user_browser.contents)
-    >>> field_labels = page_soup.findAll(
+    >>> field_labels = page_soup.find_all(
     ...     'label', text=re.compile('Further information'))
     >>> for field_label in field_labels:
     ...     print(extract_text(field_label.parent))
diff --git a/lib/lp/bugs/stories/guided-filebug/xx-filebug-attachments.txt b/lib/lp/bugs/stories/guided-filebug/xx-filebug-attachments.txt
index 28be58c..4843c14 100644
--- a/lib/lp/bugs/stories/guided-filebug/xx-filebug-attachments.txt
+++ b/lib/lp/bugs/stories/guided-filebug/xx-filebug-attachments.txt
@@ -42,7 +42,7 @@ was attached.
 No Privileges Person can see the attachment in the attachments portlet.
 
     >>> attachments = find_portlet(user_browser.contents, 'Bug attachments')
-    >>> for li_tag in attachments.findAll('li', 'download-attachment'):
+    >>> for li_tag in attachments.find_all('li', 'download-attachment'):
     ...     print(li_tag.a.decode_contents())
     A description of the attachment
 
diff --git a/lib/lp/bugs/stories/guided-filebug/xx-product-guided-filebug.txt b/lib/lp/bugs/stories/guided-filebug/xx-product-guided-filebug.txt
index f7ca514..1e604c5 100644
--- a/lib/lp/bugs/stories/guided-filebug/xx-product-guided-filebug.txt
+++ b/lib/lp/bugs/stories/guided-filebug/xx-product-guided-filebug.txt
@@ -19,10 +19,10 @@ If no title is entered, the user is asked to supply one.
 
     >>> top_portlet = first_tag_by_class(
     ...     user_browser.contents, 'top-portlet')
-    >>> for message in top_portlet.findAll(attrs={'class': 'error message'}):
+    >>> for message in top_portlet.find_all(attrs={'class': 'error message'}):
     ...     print(message.decode_contents())
     There is 1 error.
-    >>> for message in top_portlet.findAll(
+    >>> for message in top_portlet.find_all(
     ...         lambda node: node.attrs.get('class') == ['message']):
     ...     print(message.decode_contents())
     Required input is missing.
diff --git a/lib/lp/bugs/stories/standalone/xx-show-distribution-cve-report.txt b/lib/lp/bugs/stories/standalone/xx-show-distribution-cve-report.txt
index 2b62bcd..17808ec 100644
--- a/lib/lp/bugs/stories/standalone/xx-show-distribution-cve-report.txt
+++ b/lib/lp/bugs/stories/standalone/xx-show-distribution-cve-report.txt
@@ -8,7 +8,7 @@ distro bugs linked to CVEs.
 
 The report lists the open and closed bugs.
 
-    >>> for h2 in main.findAll('h2'):
+    >>> for h2 in main.find_all('h2'):
     ...     print(extract_text(h2))
     Open bugs
     Resolved bugs
@@ -18,7 +18,7 @@ distro, since it's probably too big.
 
     >>> browser.open('http://launchpad.test/ubuntu/+cve')
     >>> main = find_main_content(browser.contents)
-    >>> [extract_text(h2) for h2 in main.findAll('h2')]
+    >>> [extract_text(h2) for h2 in main.find_all('h2')]
     []
 
 Instead, the links for the specific series reports are shown.
@@ -27,7 +27,7 @@ Instead, the links for the specific series reports are shown.
     CVE reports in releases of Ubuntu
 
     >>> series_list = main.find('div', 'top-portlet').ul
-    >>> for li in series_list.findAll('li'):
+    >>> for li in series_list.find_all('li'):
     ...     print(extract_text(li))
     Breezy Badger Autotest
     Grumpy
diff --git a/lib/lp/bugs/stories/standalone/xx-show-distrorelease-cve-report.txt b/lib/lp/bugs/stories/standalone/xx-show-distrorelease-cve-report.txt
index 069e840..415d755 100644
--- a/lib/lp/bugs/stories/standalone/xx-show-distrorelease-cve-report.txt
+++ b/lib/lp/bugs/stories/standalone/xx-show-distrorelease-cve-report.txt
@@ -38,7 +38,7 @@ sourcepackage value from the bug listed on the previous report.
 
     >>> browser.open('http://launchpad.test/debian/woody/+cve')
     >>> main = find_main_content(browser.contents)
-    >>> for tr in main.table.tbody.findAll('tr'):
+    >>> for tr in main.table.tbody.find_all('tr'):
     ...     print(extract_text(tr))
     Bug #2: Blackhole Trash folder CVE-1999-2345
     Debian Woody New (unassigned)
diff --git a/lib/lp/bugs/tests/bug.py b/lib/lp/bugs/tests/bug.py
index d28fc93..9653159 100644
--- a/lib/lp/bugs/tests/bug.py
+++ b/lib/lp/bugs/tests/bug.py
@@ -82,7 +82,7 @@ def print_bug_affects_table(content, highlighted_only=False):
         tr_attrs = {'class': 'highlight'}
     else:
         tr_attrs = {}
-    tr_tags = affects_table.tbody.findAll(
+    tr_tags = affects_table.tbody.find_all(
         'tr', attrs=tr_attrs, recursive=False)
     for tr in tr_tags:
         if tr.td.table:
@@ -98,11 +98,11 @@ def print_remote_bugtasks(content):
     For each remote bugtask, print the target and the bugwatch.
     """
     affects_table = find_tags_by_class(content, 'listing')[0]
-    for span in affects_table.findAll('span'):
+    for span in affects_table.find_all('span'):
         for key, value in span.attrs.items():
             if 'bug-remote' in value:
-                target = extract_text(span.findAllPrevious('td')[-2])
-                print(target, extract_text(span.findNext('a')))
+                target = extract_text(span.find_all_previous('td')[-2])
+                print(target, extract_text(span.find_next('a')))
 
 
 def print_bugs_list(content, list_id):
@@ -113,7 +113,7 @@ def print_bugs_list(content, list_id):
     becomes more elaborate then this function will be the place to
     cope with it.
     """
-    bugs_list = find_tag_by_id(content, list_id).findAll(
+    bugs_list = find_tag_by_id(content, list_id).find_all(
         None, {'class': 'similar-bug'})
     for node in bugs_list:
         # Also strip zero-width spaces out.
@@ -229,13 +229,13 @@ def print_upstream_linking_form(browser):
 
     link_upstream_how_radio_control = browser.getControl(
         name='field.link_upstream_how')
-    link_upstream_how_buttons = soup.findAll(
+    link_upstream_how_buttons = soup.find_all(
         'input', {'name': 'field.link_upstream_how'})
 
     wrapper = textwrap.TextWrapper(width=65, subsequent_indent='    ')
     for button in link_upstream_how_buttons:
         # Print the radio button.
-        label = button.findParent('label')
+        label = button.find_parent('label')
         if label is None:
             label = soup.find('label', {'for': button['id']})
         if button.get('value') in link_upstream_how_radio_control.value:
@@ -244,7 +244,7 @@ def print_upstream_linking_form(browser):
             print(wrapper.fill('( ) %s' % extract_text(label)))
         # Print related text field, if found. Assumes that the text
         # field is in the same table row as the radio button.
-        text_field = button.findParent('tr').find('input', {'type': 'text'})
+        text_field = button.find_parent('tr').find('input', {'type': 'text'})
         if text_field is not None:
             text_control = browser.getControl(name=text_field.get('name'))
             print('    [%s]' % text_control.value.ljust(10))
@@ -291,7 +291,7 @@ def print_bugfilters_portlet_filled(browser, target):
 def print_ul(ul):
     """Print the data from a list."""
     li_content = []
-    for li in ul.findAll('li'):
+    for li in ul.find_all('li'):
         li_content.append(extract_text(li))
     if len(li_content) > 0:
         print('\n'.join(li_content))
diff --git a/lib/lp/bugs/tests/test_bugs_webservice.py b/lib/lp/bugs/tests/test_bugs_webservice.py
index 099c5ca..fd82656 100644
--- a/lib/lp/bugs/tests/test_bugs_webservice.py
+++ b/lib/lp/bugs/tests/test_bugs_webservice.py
@@ -110,7 +110,7 @@ class TestBugDescriptionRepresentation(TestCaseWithFactory):
         """Find the bug description field in an XHTML document fragment."""
         soup = BeautifulSoup(response.body)
         dt = soup.find('dt', text="description")
-        dd = dt.findNextSibling('dd')
+        dd = dt.find_next_sibling('dd')
         return str(dd.contents.pop())
 
     def test_GET_xhtml_representation(self):
diff --git a/lib/lp/code/browser/codeimport.py b/lib/lp/code/browser/codeimport.py
index dfee026..49c3b3f 100644
--- a/lib/lp/code/browser/codeimport.py
+++ b/lib/lp/code/browser/codeimport.py
@@ -388,7 +388,7 @@ class CodeImportNewView(CodeImportBaseView, CodeImportNameValidationMixin):
         # Extract the radio buttons from the rcs_type widget, so we can
         # display them separately in the form.
         soup = BeautifulSoup(self.widgets['rcs_type']())
-        fields = soup.findAll('input')
+        fields = soup.find_all('input')
         [cvs_button, svn_button, git_button, bzr_button,
             empty_marker] = [
                 field for field in fields
diff --git a/lib/lp/code/browser/tests/test_branch.py b/lib/lp/code/browser/tests/test_branch.py
index b731ac7..2727002 100644
--- a/lib/lp/code/browser/tests/test_branch.py
+++ b/lib/lp/code/browser/tests/test_branch.py
@@ -504,7 +504,7 @@ class TestBranchView(BrowserTestCase):
 
         self.assertTextMatchesExpressionIgnoreWhitespace(expected_text, text)
 
-        links = revision_content.findAll('a')
+        links = revision_content.find_all('a')
         self.assertEqual(mp_url, links[2]['href'])
 
     def test_recent_revisions_with_merge_proposals_and_bug_links(self):
@@ -559,7 +559,7 @@ class TestBranchView(BrowserTestCase):
 
         self.assertTextMatchesExpressionIgnoreWhitespace(expected_text, text)
 
-        links = revision_content.findAll('a')
+        links = revision_content.find_all('a')
         self.assertEqual(mp_url, links[2]['href'])
         self.assertEqual(branch_url, links[3]['href'])
         self.assertEqual(linked_bug_urls[0], links[4]['href'])
diff --git a/lib/lp/code/browser/tests/test_gitlisting.py b/lib/lp/code/browser/tests/test_gitlisting.py
index e7682fc..14239ad 100644
--- a/lib/lp/code/browser/tests/test_gitlisting.py
+++ b/lib/lp/code/browser/tests/test_gitlisting.py
@@ -82,7 +82,7 @@ class TestTargetGitListingView:
             'div', id='default-repository-branches').find('table')
         self.assertContentEqual(
             ['1.0', 'master', 'with#hash', '\N{SNOWMAN}'],
-            [link.find(text=True) for link in table.findAll('a')])
+            [link.find(text=True) for link in table.find_all('a')])
         self.assertEndsWith(
             table.find(text="1.0").parent['href'],
             "/~foowner/%s/+git/foo/+ref/1.0" % self.target_path)
@@ -100,7 +100,7 @@ class TestTargetGitListingView:
             ['lp:%s' % self.target_path,
              'lp:~random/%s/+git/bar' % self.target_path,
              'lp:~contributor/%s' % self.target_path],
-            [link.find(text=True) for link in table.findAll('a')])
+            [link.find(text=True) for link in table.find_all('a')])
         self.assertEndsWith(
             table.find(
                 text="lp:~contributor/%s" % self.target_path).parent['href'],
@@ -149,7 +149,7 @@ class TestTargetGitListingView:
             'div', id='gitrepositories-table-listing').find('table')
         self.assertContentEqual(
             ['lp:~contributor/%s/+git/foo' % self.target_path],
-            [link.find(text=True) for link in table.findAll('a')])
+            [link.find(text=True) for link in table.find_all('a')])
 
     def test_copes_with_private_repos(self):
         invisible_repo = self.factory.makeGitRepository(
@@ -234,7 +234,7 @@ class TestPersonTargetGitListingView:
             'div', id='default-repository-branches').find('table')
         self.assertContentEqual(
             ['master', 'bug-1234'],
-            [link.find(text=True) for link in table.findAll('a')])
+            [link.find(text=True) for link in table.find_all('a')])
         self.assertEndsWith(
             table.find(text="bug-1234").parent['href'],
             "/~dev/%s/+git/foo/+ref/bug-1234" % self.target_path)
@@ -245,7 +245,7 @@ class TestPersonTargetGitListingView:
         self.assertContentEqual(
             ['lp:~dev/%s' % self.target_path,
              'lp:~dev/%s/+git/bar' % self.target_path],
-            [link.find(text=True) for link in table.findAll('a')])
+            [link.find(text=True) for link in table.find_all('a')])
         self.assertEndsWith(
             table.find(
                 text="lp:~dev/%s/+git/bar" % self.target_path).parent['href'],
@@ -275,7 +275,7 @@ class TestPersonTargetGitListingView:
             'div', id='gitrepositories-table-listing').find('table')
         self.assertContentEqual(
             ['lp:~dev/%s/+git/foo' % self.target_path],
-            [link.find(text=True) for link in table.findAll('a')])
+            [link.find(text=True) for link in table.find_all('a')])
 
     def test_copes_with_private_repos(self):
         invisible_repo = self.factory.makeGitRepository(
@@ -492,7 +492,7 @@ class TestPlainGitListingView:
             'div', id='gitrepositories-table-listing').find('table')
         self.assertContentEqual(
             [some_repo.git_identity, other_repo.git_identity],
-            [link.find(text=True) for link in table.findAll('a')])
+            [link.find(text=True) for link in table.find_all('a')])
 
     def test_copes_with_private_repos(self):
         # XXX wgrant 2015-06-12: owner is self.user instead of
diff --git a/lib/lp/code/browser/tests/test_gitref.py b/lib/lp/code/browser/tests/test_gitref.py
index 97ef0a3..d49b343 100644
--- a/lib/lp/code/browser/tests/test_gitref.py
+++ b/lib/lp/code/browser/tests/test_gitref.py
@@ -501,7 +501,7 @@ class TestGitRefView(BrowserTestCase):
         view = create_initialized_view(ref, "+index")
         contents = view()
         soup = BeautifulSoup(contents)
-        details = soup.findAll(
+        details = soup.find_all(
             attrs={"class": re.compile(r"commit-details|commit-comment")})
         expected_texts = list(reversed([
             "%.7s...\nby\n%s\non 2015-01-%02d" % (
@@ -513,7 +513,7 @@ class TestGitRefView(BrowserTestCase):
             expected_texts, [extract_text(detail) for detail in details])
         self.assertEqual(
             [canonical_url(mp), canonical_url(mp.merge_source)],
-            [link["href"] for link in details[5].findAll("a")])
+            [link["href"] for link in details[5].find_all("a")])
         self.assertThat(
             contents, Not(soupmatchers.HTMLContains(MissingCommitsNote())))
 
@@ -532,7 +532,7 @@ class TestGitRefView(BrowserTestCase):
         view = create_initialized_view(ref, "+index")
         contents = view()
         soup = BeautifulSoup(contents)
-        details = soup.findAll(
+        details = soup.find_all(
             attrs={"class": re.compile(r"commit-details|commit-comment")})
         expected_texts = list(reversed([
             "%.7s...\nby\n%s\non 2015-01-%02d" % (
@@ -544,7 +544,7 @@ class TestGitRefView(BrowserTestCase):
             expected_texts, [extract_text(detail) for detail in details])
         self.assertEqual(
             [canonical_url(mp)],
-            [link["href"] for link in details[5].findAll("a")])
+            [link["href"] for link in details[5].find_all("a")])
         self.assertThat(
             contents, Not(soupmatchers.HTMLContains(MissingCommitsNote())))
 
@@ -629,7 +629,7 @@ class TestGitRefView(BrowserTestCase):
         view = create_initialized_view(ref, "+index")
         contents = view()
         soup = BeautifulSoup(contents)
-        details = soup.findAll(
+        details = soup.find_all(
             attrs={"class": re.compile(r"commit-details|commit-comment")})
         expected_text = "%.7s...\nby\n%s\non 2015-01-%02d" % (
             log[-1]["sha1"], log[-1]["author"]["name"], len(log))
diff --git a/lib/lp/code/browser/tests/test_gitrepository.py b/lib/lp/code/browser/tests/test_gitrepository.py
index 8dadb9d..843d60a 100644
--- a/lib/lp/code/browser/tests/test_gitrepository.py
+++ b/lib/lp/code/browser/tests/test_gitrepository.py
@@ -1717,7 +1717,7 @@ class TestGitRepositoryPermissionsView(BrowserTestCase):
             repository, name="+permissions", principal=repository.owner)
         rules_tables = find_tags_by_class(view(), "git-rules-table")
         rows = list(chain.from_iterable([
-            rules_table.findAll("tr", {"class": True})
+            rules_table.find_all("tr", {"class": True})
             for rules_table in rules_tables]))
         self.assertThat(rows, MatchesListwise([
             self._matchesRule("1", "refs/heads/stable/*", "stable/*"),
diff --git a/lib/lp/code/browser/tests/test_gitsubscription.py b/lib/lp/code/browser/tests/test_gitsubscription.py
index a887c38..17c9136 100644
--- a/lib/lp/code/browser/tests/test_gitsubscription.py
+++ b/lib/lp/code/browser/tests/test_gitsubscription.py
@@ -86,7 +86,7 @@ class TestGitSubscriptionAddView(BrowserTestCase):
     def _getSubscribers(self, contents):
         subscriptions = find_tags_by_class(
             contents, 'repository-subscribers')[0]
-        for subscriber in subscriptions.findAll('div'):
+        for subscriber in subscriptions.find_all('div'):
             yield extract_text(subscriber.decode_contents())
 
     def _getInformationalMessage(self, contents):
diff --git a/lib/lp/code/browser/tests/test_sourcepackagerecipe.py b/lib/lp/code/browser/tests/test_sourcepackagerecipe.py
index 94de042..5ab4908 100644
--- a/lib/lp/code/browser/tests/test_sourcepackagerecipe.py
+++ b/lib/lp/code/browser/tests/test_sourcepackagerecipe.py
@@ -132,11 +132,11 @@ class BzrMixin:
         if not related_package_branch_info:
             self.assertIs(branch_table, None)
         else:
-            rows = branch_table.tbody.findAll('tr')
+            rows = branch_table.tbody.find_all('tr')
 
             package_branches_info = []
             for row in rows:
-                branch_links = row.findAll('a')
+                branch_links = row.find_all('a')
                 self.assertEqual(2, len(branch_links))
                 package_branches_info.append(
                     '%s%s' % (root_url, branch_links[0]['href']))
@@ -162,11 +162,11 @@ class BzrMixin:
         if not related_series_branch_info:
             self.assertIs(branch_table, None)
         else:
-            rows = branch_table.tbody.findAll('tr')
+            rows = branch_table.tbody.find_all('tr')
 
             series_branches_info = []
             for row in rows:
-                branch_links = row.findAll('a')
+                branch_links = row.find_all('a')
                 self.assertEqual(2, len(branch_links))
                 series_branches_info.append(
                     '%s%s' % (root_url, branch_links[0]['href']))
diff --git a/lib/lp/code/browser/widgets/tests/test_branchtargetwidget.py b/lib/lp/code/browser/widgets/tests/test_branchtargetwidget.py
index 7b78126..e9f871b 100644
--- a/lib/lp/code/browser/widgets/tests/test_branchtargetwidget.py
+++ b/lib/lp/code/browser/widgets/tests/test_branchtargetwidget.py
@@ -244,6 +244,6 @@ class LaunchpadTargetWidgetTestCase(TestCaseWithFactory):
             'field.target.product',
             ]
         soup = BeautifulSoup(markup)
-        fields = soup.findAll(['input', 'select'], {'id': re.compile('.*')})
+        fields = soup.find_all(['input', 'select'], {'id': re.compile('.*')})
         ids = [field['id'] for field in fields]
         self.assertContentEqual(expected_ids, ids)
diff --git a/lib/lp/code/browser/widgets/tests/test_gitgrantee.py b/lib/lp/code/browser/widgets/tests/test_gitgrantee.py
index 9d30eef..28d0011 100644
--- a/lib/lp/code/browser/widgets/tests/test_gitgrantee.py
+++ b/lib/lp/code/browser/widgets/tests/test_gitgrantee.py
@@ -156,7 +156,7 @@ class TestGitGranteeWidgetBase:
         self.assertIn("repository_owner", self.widget.options)
         self.assertIn("person", self.widget.options)
         soup = BeautifulSoup(markup)
-        fields = soup.findAll(["input", "select"], {"id": re.compile(".*")})
+        fields = soup.find_all(["input", "select"], {"id": re.compile(".*")})
         ids = [field["id"] for field in fields]
         self.assertContentEqual(self.expected_ids, ids)
 
diff --git a/lib/lp/code/browser/widgets/tests/test_gitrefwidget.py b/lib/lp/code/browser/widgets/tests/test_gitrefwidget.py
index 9b8e76c..cf61a8f 100644
--- a/lib/lp/code/browser/widgets/tests/test_gitrefwidget.py
+++ b/lib/lp/code/browser/widgets/tests/test_gitrefwidget.py
@@ -306,7 +306,7 @@ class TestGitRefWidget(WithScenarios, TestCaseWithFactory):
         self.assertIsNotNone(self.widget.repository_widget)
         self.assertIsNotNone(self.widget.path_widget)
         soup = BeautifulSoup(markup)
-        fields = soup.findAll("input", id=True)
+        fields = soup.find_all("input", id=True)
         ids = [field["id"] for field in fields]
         self.assertContentEqual(
             ["field.git_ref.repository", "field.git_ref.path"], ids)
diff --git a/lib/lp/code/browser/widgets/tests/test_gitrepositorytargetwidget.py b/lib/lp/code/browser/widgets/tests/test_gitrepositorytargetwidget.py
index ebe6e9a..3d1c5b7 100644
--- a/lib/lp/code/browser/widgets/tests/test_gitrepositorytargetwidget.py
+++ b/lib/lp/code/browser/widgets/tests/test_gitrepositorytargetwidget.py
@@ -253,7 +253,7 @@ class TestGitRepositoryTargetWidgetBase:
         self.assertIn("personal", self.widget.options)
         self.assertIn("package", self.widget.options)
         soup = BeautifulSoup(markup)
-        fields = soup.findAll(["input", "select"], {"id": re.compile(".*")})
+        fields = soup.find_all(["input", "select"], {"id": re.compile(".*")})
         ids = [field["id"] for field in fields]
         self.assertContentEqual(self.expected_ids, ids)
 
diff --git a/lib/lp/code/stories/branches/xx-bazaar-home.txt b/lib/lp/code/stories/branches/xx-bazaar-home.txt
index f2a5868..df4a3df 100644
--- a/lib/lp/code/stories/branches/xx-bazaar-home.txt
+++ b/lib/lp/code/stories/branches/xx-bazaar-home.txt
@@ -34,7 +34,7 @@ with a link to the complete listing.
     Most active projects in the last month
     see all projects…
 
-    >>> print(preview.findAll('a')[-1]['href'])
+    >>> print(preview.find_all('a')[-1]['href'])
     /projects
 
 
@@ -72,7 +72,7 @@ The recently registered branch listing is ordered with the most recently
 registered branches first.
 
     >>> registered = find_tag_by_id(browser.contents, 'recently-registered')
-    >>> print(registered.findAll('a')[-1]['href'])
+    >>> print(registered.find_all('a')[-1]['href'])
     /+recently-registered-branches
 
     >>> browser.getLink(url='recently-registered-branches').click()
@@ -84,7 +84,7 @@ is also shown in the listing.  And since the date that the branch was
 registered is the ordering, the registered date is also shown.
 
     >>> table = find_tag_by_id(browser.contents, 'branchtable')
-    >>> for row in table.thead.findAll('tr'):
+    >>> for row in table.thead.find_all('tr'):
     ...     print(extract_text(row))
     Name
     Status
@@ -105,7 +105,7 @@ recent commits first.
 
     >>> browser.open('http://code.launchpad.test/')
     >>> changed = find_tag_by_id(browser.contents, 'recently-changed')
-    >>> print(changed.findAll('a')[-1]['href'])
+    >>> print(changed.find_all('a')[-1]['href'])
     /+recently-changed-branches
 
     >>> browser.getLink(url='recently-changed-branches').click()
@@ -113,7 +113,7 @@ recent commits first.
     Recently changed branches
 
     >>> table = find_tag_by_id(browser.contents, 'branchtable')
-    >>> for row in table.thead.findAll('tr'):
+    >>> for row in table.thead.find_all('tr'):
     ...     print(extract_text(row))
     Name
     Status
@@ -132,7 +132,7 @@ imported branches.
 
     >>> browser.open('http://code.launchpad.test/')
     >>> imported = find_tag_by_id(browser.contents, 'recent-imports')
-    >>> print(imported.findAll('a')[-1]['href'])
+    >>> print(imported.find_all('a')[-1]['href'])
     /+recently-imported-branches
 
     >>> browser.getLink(url='recently-imported-branches').click()
@@ -144,7 +144,7 @@ of the branches are not normally set, the Author column is not shown for
 imported branch listings.
 
     >>> table = find_tag_by_id(browser.contents, 'branchtable')
-    >>> for row in table.thead.findAll('tr'):
+    >>> for row in table.thead.find_all('tr'):
     ...     print(extract_text(row))
     Name
     Status
diff --git a/lib/lp/code/stories/branches/xx-branch-listings-merge-proposal-badge.txt b/lib/lp/code/stories/branches/xx-branch-listings-merge-proposal-badge.txt
index dee2a96..ce25e5c 100644
--- a/lib/lp/code/stories/branches/xx-branch-listings-merge-proposal-badge.txt
+++ b/lib/lp/code/stories/branches/xx-branch-listings-merge-proposal-badge.txt
@@ -2,13 +2,13 @@
 
     >>> def branchSummary(browser):
     ...     table = find_tag_by_id(browser.contents, 'branchtable')
-    ...     for row in table.tbody.findAll('tr'):
-    ...         cells = row.findAll('td')
+    ...     for row in table.tbody.find_all('tr'):
+    ...         cells = row.find_all('td')
     ...         first_cell = cells[0]
-    ...         anchors = first_cell.findAll('a')
+    ...         anchors = first_cell.find_all('a')
     ...         print(anchors[0].get('href'))
     ...         # Badges in the next cell
-    ...         for img in cells[1].findAll('img'):
+    ...         for img in cells[1].find_all('img'):
     ...             print(img['title'])
 
 
diff --git a/lib/lp/code/stories/branches/xx-branch-listings.txt b/lib/lp/code/stories/branches/xx-branch-listings.txt
index 463fc8d..b1e0399 100644
--- a/lib/lp/code/stories/branches/xx-branch-listings.txt
+++ b/lib/lp/code/stories/branches/xx-branch-listings.txt
@@ -28,7 +28,7 @@ branches in the listings.
     ...1...→...6...of 10 results...
 
     >>> table = find_tag_by_id(browser.contents, 'branchtable')
-    >>> for row in table.thead.findAll('tr'):
+    >>> for row in table.thead.find_all('tr'):
     ...     print(extract_text(row))
     Name
     Status
@@ -39,7 +39,7 @@ Unfortunately our sample data is somewhat lacking in the last commit
 fields.  There are a couple of branches that have them, but most don't
 and are really just branch metadata without the revisions behind them.
 
-    >>> for row in table.tbody.findAll('tr'):
+    >>> for row in table.tbody.find_all('tr'):
     ...     print(extract_text(row))
     lp://dev/~name12/firefox/main                Development  ...
     lp://dev/~name12/gnome-terminal/2.6          Mature       ...
@@ -55,7 +55,7 @@ and are really just branch metadata without the revisions behind them.
     ...7...→...10...of 10 results...
 
     >>> table = find_tag_by_id(browser.contents, 'branchtable')
-    >>> for row in table.tbody.findAll('tr'):
+    >>> for row in table.tbody.find_all('tr'):
     ...     print(extract_text(row))
     lp://dev/~name12/gnome-terminal/klingon          Experimental  ...
     lp://dev/~name12/+junk/junk.contrib              Development   ...
@@ -82,7 +82,7 @@ shown.
 
     >>> browser.open('http://code.launchpad.test/~name12')
     >>> table = find_tag_by_id(browser.contents, 'branchtable')
-    >>> for row in table.tbody.findAll('tr'):
+    >>> for row in table.tbody.find_all('tr'):
     ...     print(extract_text(row))
     lp://dev/~name12/firefox/main                 Development  ...
     lp://dev/~name12/gnome-terminal/2.6           Mature       ...
@@ -117,7 +117,7 @@ Now all types of branches should be shown.
     ...1...→...6...of 12 results...
 
     >>> table = find_tag_by_id(browser.contents, 'branchtable')
-    >>> for row in table.tbody.findAll('tr'):
+    >>> for row in table.tbody.find_all('tr'):
     ...     print(extract_text(row))
     lp://dev/~name12/firefox/main                 Development    ...
     lp://dev/~name12/gnome-terminal/2.4           Abandoned      ...
@@ -133,7 +133,7 @@ Now all types of branches should be shown.
     ...7...→...12...of 12 results...
 
     >>> table = find_tag_by_id(browser.contents, 'branchtable')
-    >>> for row in table.tbody.findAll('tr'):
+    >>> for row in table.tbody.find_all('tr'):
     ...     print(extract_text(row))
     lp://dev/~name12/gnome-terminal/pushed         Development     ...
     lp://dev/~name12/gnome-terminal/scanned        Development     ...
@@ -148,7 +148,7 @@ will cause only branches with that status to be listed.
     >>> browser.getControl(name='field.lifecycle').displayValue = ['Abandoned']
     >>> browser.getControl('Filter').click()
     >>> table = find_tag_by_id(browser.contents, 'branchtable')
-    >>> for row in table.tbody.findAll('tr'):
+    >>> for row in table.tbody.find_all('tr'):
     ...     print(extract_text(row))
     lp://dev/~name12/gnome-terminal/2.4    Abandoned     ...
 
@@ -160,7 +160,7 @@ status value, it will default to current branches.
     >>> browser.getControl(name='field.lifecycle').displayValue
     ['Any active status']
     >>> table = find_tag_by_id(browser.contents, 'branchtable')
-    >>> for row in table.tbody.findAll('tr'):
+    >>> for row in table.tbody.find_all('tr'):
     ...     print(extract_text(row))
     lp://dev/~name12/firefox/main                  Development  ...
     lp://dev/~name12/gnome-terminal/2.6            Mature       ...
@@ -188,7 +188,7 @@ first.
     ...     'http://code.launchpad.test/~name12/+branches?'
     ...     'field.category=subscribed')
     >>> table = find_tag_by_id(browser.contents, 'branchtable')
-    >>> for row in table.tbody.findAll('tr'):
+    >>> for row in table.tbody.find_all('tr'):
     ...     print(extract_text(row))
     lp://dev/~name12/firefox/main ...
     lp://dev/~launchpad/gnome-terminal/launchpad ...
@@ -212,17 +212,17 @@ We display badges for associated bugs.
 
     >>> def branchSummary(browser):
     ...     table = find_tag_by_id(browser.contents, 'branchtable')
-    ...     for row in table.tbody.findAll('tr'):
+    ...     for row in table.tbody.find_all('tr'):
     ...         if row.get_text(strip=True).startswith(
     ...                 'A development focus branch'):
     ...             continue
-    ...         cells = row.findAll('td')
+    ...         cells = row.find_all('td')
     ...         first_cell = cells[0]
-    ...         anchors = first_cell.findAll('a')
+    ...         anchors = first_cell.find_all('a')
     ...         print(anchors[0].get('href'))
     ...         # Badges in the next cell
     ...         if len(cells) > 1:
-    ...             for img in cells[1].findAll('img'):
+    ...             for img in cells[1].find_all('img'):
     ...                 print(img['title'])
 
     >>> browser.open(
@@ -337,7 +337,7 @@ Finally, sorting by a particular criterion has the desired effect.
 
     >>> browser.open('http://code.launchpad.test/gnome-terminal/+branches')
     >>> table = find_tag_by_id(browser.contents, 'branchtable')
-    >>> for row in table.tbody.findAll('tr'):
+    >>> for row in table.tbody.find_all('tr'):
     ...     print(extract_text(row))
     A development focus ...
     lp://dev/~name12/gnome-terminal/2.6            Mature       ...
@@ -350,7 +350,7 @@ Finally, sorting by a particular criterion has the desired effect.
     >>> browser.getControl(name='field.sort_by').value = ['by branch name']
     >>> browser.getControl('Filter').click()
     >>> table = find_tag_by_id(browser.contents, 'branchtable')
-    >>> for row in table.tbody.findAll('tr'):
+    >>> for row in table.tbody.find_all('tr'):
     ...     print(extract_text(row))
     A development focus ...
     lp://dev/~name12/gnome-terminal/2.6             Mature          ...
@@ -383,8 +383,8 @@ both the series link in the first column with the branch unique name.
     >>> browser.open('http://code.launchpad.test/gnome-terminal')
     >>> table = find_tag_by_id(browser.contents, 'branchtable')
     >>> # The development focus is always first.
-    >>> row = table.tbody.findAll('tr')[0]
-    >>> cols = row.findAll('td')
+    >>> row = table.tbody.find_all('tr')[0]
+    >>> cols = row.find_all('td')
     >>> print(extract_text(cols[0]))
     lp://dev/gnome-terminal     Series: trunk
 
@@ -411,7 +411,7 @@ The current development focus is shown first though.
 
     >>> browser.open('http://code.launchpad.test/gnome-terminal')
     >>> table = find_tag_by_id(browser.contents, 'branchtable')
-    >>> print(extract_text(table.tbody.findAll('tr')[0]))
+    >>> print(extract_text(table.tbody.find_all('tr')[0]))
     lp://dev/gnome-terminal  Series: trunk, alpha, pre-1.0  ...
 
 
@@ -424,7 +424,7 @@ Merged or Abandoned branches are not shown.
 
     >>> browser.open('http://code.launchpad.test/gnome-terminal')
     >>> table = find_tag_by_id(browser.contents, 'branchtable')
-    >>> for row in table.tbody.findAll('tr'):
+    >>> for row in table.tbody.find_all('tr'):
     ...     print(extract_text(row))
     lp://dev/gnome-terminal   Series: trunk...    Development  ...
     lp://dev/~name12/gnome-terminal/2.6           Mature       ...
@@ -449,7 +449,7 @@ will cause only branches with that status to be listed.
     >>> browser.getControl(name='field.lifecycle').displayValue = ['Experimental']
     >>> browser.getControl('Filter').click()
     >>> table = find_tag_by_id(browser.contents, 'branchtable')
-    >>> for row in table.tbody.findAll('tr'):
+    >>> for row in table.tbody.find_all('tr'):
     ...     print(extract_text(row))
     lp://dev/~name12/gnome-terminal/klingon       Experimental ...
 
@@ -459,7 +459,7 @@ first.
     >>> browser.getControl(name='field.lifecycle').displayValue = ['Development']
     >>> browser.getControl('Filter').click()
     >>> table = find_tag_by_id(browser.contents, 'branchtable')
-    >>> for row in table.tbody.findAll('tr'):
+    >>> for row in table.tbody.find_all('tr'):
     ...     print(extract_text(row))
     lp://dev/gnome-terminal   Series: trunk...    Development  ...
 
diff --git a/lib/lp/code/stories/branches/xx-branch-tag-cloud.txt b/lib/lp/code/stories/branches/xx-branch-tag-cloud.txt
index aed92a3..7e00f56 100644
--- a/lib/lp/code/stories/branches/xx-branch-tag-cloud.txt
+++ b/lib/lp/code/stories/branches/xx-branch-tag-cloud.txt
@@ -24,7 +24,7 @@ branches associated with it.  The HTML class attribute is used to control how
 the link is shown.
 
     >>> tags = find_tag_by_id(anon_browser.contents, 'project-tags')
-    >>> for anchor in tags.findAll('a'):
+    >>> for anchor in tags.find_all('a'):
     ...     print(anchor.decode_contents(), ' '.join(anchor['class']))
     linux cloud-size-largest cloud-medium
     wibble cloud-size-smallest cloud-dark
diff --git a/lib/lp/code/stories/branches/xx-branchmergeproposals.txt b/lib/lp/code/stories/branches/xx-branchmergeproposals.txt
index 6c8a697..798c6d0 100644
--- a/lib/lp/code/stories/branches/xx-branchmergeproposals.txt
+++ b/lib/lp/code/stories/branches/xx-branchmergeproposals.txt
@@ -376,7 +376,7 @@ branch widget is shown.
     >>> import re
     >>> def get_target_branch_widgets(browser):
     ...     main = find_main_content(browser.contents)
-    ...     return main.findAll(
+    ...     return main.find_all(
     ...         'input', attrs={'name': re.compile('target_branch')})
 
     >>> nopriv_browser.open(
diff --git a/lib/lp/code/stories/branches/xx-code-review-comments.txt b/lib/lp/code/stories/branches/xx-code-review-comments.txt
index 485805b..1c3738d 100644
--- a/lib/lp/code/stories/branches/xx-code-review-comments.txt
+++ b/lib/lp/code/stories/branches/xx-code-review-comments.txt
@@ -115,7 +115,7 @@ are also displayed in the new proposal.
     Posted in a previous version of this proposal #
     >>> details = find_tags_by_class(
     ...     anon_browser.contents, 'boardCommentDetails')[0]
-    >>> links = details.findAll('a')
+    >>> links = details.find_all('a')
     >>> print(links[1]['href'] == merge_proposal_path)
     True
 
@@ -270,7 +270,7 @@ rendered with the following 'data-' attributes:
     ...     result = {}
     ...     comments = find_tags_by_class(contents, 'boardCommentDetails')
     ...     for comment in comments:
-    ...         tds = comment.findAll('td')
+    ...         tds = comment.find_all('td')
     ...         if len(tds) == 0:
     ...            continue
     ...         td = tds[0]
diff --git a/lib/lp/code/stories/branches/xx-nearby-branches.txt b/lib/lp/code/stories/branches/xx-nearby-branches.txt
index 3b10d7c..16927b3 100644
--- a/lib/lp/code/stories/branches/xx-nearby-branches.txt
+++ b/lib/lp/code/stories/branches/xx-nearby-branches.txt
@@ -18,7 +18,7 @@ branches.
 Since the links point to the code subdomain, the links are relative to the root.
 
     >>> div = find_tag_by_id(browser.contents, 'nearby-branches')
-    >>> for anchor in div.findAll('a'):
+    >>> for anchor in div.find_all('a'):
     ...     print(anchor['href'], anchor.string)
     /fooix                      Other Fooix branches
     /~vikings                   Other branches owned by Vikings
diff --git a/lib/lp/code/stories/branches/xx-person-branches.txt b/lib/lp/code/stories/branches/xx-person-branches.txt
index 054c2af..ac50523 100644
--- a/lib/lp/code/stories/branches/xx-person-branches.txt
+++ b/lib/lp/code/stories/branches/xx-person-branches.txt
@@ -46,7 +46,7 @@ A person's owned branches are shown on their code application overview page.
 
     >>> browser.open('http://code.launchpad.test/~name12')
     >>> table = find_tag_by_id(browser.contents, 'branchtable')
-    >>> for row in table.tbody.findAll('tr'):
+    >>> for row in table.tbody.find_all('tr'):
     ...     print(extract_text(row))
     lp://dev/~name12/landscape/feature-x      Development
     ...
@@ -64,7 +64,7 @@ There is also a filter for registered branches.
     >>> print(browser.title)
     Code : Sample Person
     >>> table = find_tag_by_id(browser.contents, 'branchtable')
-    >>> for row in table.tbody.findAll('tr'):
+    >>> for row in table.tbody.find_all('tr'):
     ...     print(extract_text(row))
     lp://dev/~name12/landscape/feature-x      Development
     ...
@@ -83,7 +83,7 @@ subscribed branches.
     >>> print(browser.title)
     Code : Sample Person
     >>> table = find_tag_by_id(browser.contents, 'branchtable')
-    >>> for row in table.tbody.findAll('tr'):
+    >>> for row in table.tbody.find_all('tr'):
     ...     print(extract_text(row))
     lp://dev/~launchpad/gnome-terminal/launchpad  Development           ...
     lp://dev/~name12/+junk/junk.dev               Experimental  ...
diff --git a/lib/lp/code/stories/branches/xx-private-branch-listings.txt b/lib/lp/code/stories/branches/xx-private-branch-listings.txt
index 4fa90cc..a1fbbe3 100644
--- a/lib/lp/code/stories/branches/xx-private-branch-listings.txt
+++ b/lib/lp/code/stories/branches/xx-private-branch-listings.txt
@@ -49,7 +49,7 @@ registered branches.
     >>> def print_recently_registered_branches(browser):
     ...     browser.open('http://code.launchpad.test')
     ...     branches = find_tag_by_id(browser.contents, 'recently-registered')
-    ...     for list_item in branches.ul.findAll('li'):
+    ...     for list_item in branches.ul.find_all('li'):
     ...         print("%r" % list_item.decode_contents())
 
 When there is no logged in user, only public branches should be visible.
@@ -117,7 +117,7 @@ listing tab.
     ...         print(extract_text(find_tag_by_id(
     ...             browser.contents, 'branch-summary')))
     ...     else:
-    ...         for row in table.tbody.findAll('tr'):
+    ...         for row in table.tbody.find_all('tr'):
     ...             print(extract_text(row))
 
     >>> print_landscape_code_listing(anon_browser)
@@ -157,7 +157,7 @@ viewable branches.
     ...     browser.open(full_url)
     ...     table = find_tag_by_id(browser.contents, 'branchtable')
     ...     branches = []
-    ...     for row in table.tbody.findAll('tr'):
+    ...     for row in table.tbody.find_all('tr'):
     ...         branches.append(extract_text(row))
     ...     landscape_branches = [branch for branch in branches
     ...                           if 'landscape' in branch]
diff --git a/lib/lp/code/stories/branches/xx-product-branches.txt b/lib/lp/code/stories/branches/xx-product-branches.txt
index a9af801..9ca90d2 100644
--- a/lib/lp/code/stories/branches/xx-product-branches.txt
+++ b/lib/lp/code/stories/branches/xx-product-branches.txt
@@ -54,7 +54,7 @@ If there are not any branches, a helpful message is shown.
 
 The 'Help' links go to the help wiki.
 
-    >>> for anchor in summary.findAll('a'):
+    >>> for anchor in summary.find_all('a'):
     ...     print(anchor['href'])
     https://help.launchpad.net/Code
 
@@ -97,7 +97,7 @@ shown.
     using the command:
     bzr branch lp://dev/evolution
 
-    >>> links = summary.findAll('a')
+    >>> links = summary.find_all('a')
 
 Both active reviews and approved merges are links allowing the user to
 go to listing views.
@@ -123,7 +123,7 @@ once, but both series are shown in the listing.
 
     >>> browser.open('http://code.launchpad.test/firefox')
     >>> table = find_tag_by_id(browser.contents, 'branchtable')
-    >>> for row in table.tbody.findAll('tr')[0:2]:
+    >>> for row in table.tbody.find_all('tr')[0:2]:
     ...     print(extract_text(row))
     lp://dev/firefox
       Series: trunk, 1.0                     Development ...
@@ -138,7 +138,7 @@ Firstly lets associate release--0.9.1 with the 1.0 series.
 
     >>> browser.open('http://code.launchpad.test/firefox')
     >>> table = find_tag_by_id(browser.contents, 'branchtable')
-    >>> for row in table.tbody.findAll('tr')[0:2]:
+    >>> for row in table.tbody.find_all('tr')[0:2]:
     ...     print(extract_text(row))
     lp://dev/firefox
       Series: trunk                 Development ...
@@ -159,7 +159,7 @@ default listings.
 
     >>> browser.open('http://code.launchpad.test/firefox')
     >>> table = find_tag_by_id(browser.contents, 'branchtable')
-    >>> for row in table.tbody.findAll('tr')[0:2]:
+    >>> for row in table.tbody.find_all('tr')[0:2]:
     ...     print(extract_text(row))
     lp://dev/~mark/firefox/release-0.8     Development ...
 
@@ -187,7 +187,7 @@ The links are only shown if the user has permission to perform the task.
     ...     if links is None:
     ...         print('None')
     ...         return
-    ...     for link in links.findAll('a'):
+    ...     for link in links.find_all('a'):
     ...         print(extract_text(link))
 
     >>> def setup_code_hosting(productname):
diff --git a/lib/lp/code/stories/branches/xx-product-overview.txt b/lib/lp/code/stories/branches/xx-product-overview.txt
index 5c0255c..7f9b8dd 100644
--- a/lib/lp/code/stories/branches/xx-product-overview.txt
+++ b/lib/lp/code/stories/branches/xx-product-overview.txt
@@ -10,7 +10,7 @@ registered branches.
     ...     if branches is None:
     ...         print("No 'Latest branches' portlet found at %s" % (url,))
     ...         return
-    ...     for list_item in branches.findAll('li'):
+    ...     for list_item in branches.find_all('li'):
     ...         print(extract_text(list_item))
 
     >>> def make_branch_on_product(product, branch_name, person_name):
diff --git a/lib/lp/code/stories/branches/xx-project-branches.txt b/lib/lp/code/stories/branches/xx-project-branches.txt
index 941ab3d..baad3b3 100644
--- a/lib/lp/code/stories/branches/xx-project-branches.txt
+++ b/lib/lp/code/stories/branches/xx-project-branches.txt
@@ -24,7 +24,7 @@ If there are branches, then they are displayed.
 
     >>> browser.open('http://code.launchpad.test/mozilla')
     >>> table = find_tag_by_id(browser.contents, 'branchtable')
-    >>> for row in table.tbody.findAll('tr'):
+    >>> for row in table.tbody.find_all('tr'):
     ...     print(extract_text(row))
     lp://dev/~mark/firefox/release--0.9.1  Development   firefox ...
     lp://dev/~mark/firefox/release-0.8     Development   firefox ...
diff --git a/lib/lp/code/stories/branches/xx-propose-for-merging.txt b/lib/lp/code/stories/branches/xx-propose-for-merging.txt
index fb0cb89..c2fdf36 100644
--- a/lib/lp/code/stories/branches/xx-propose-for-merging.txt
+++ b/lib/lp/code/stories/branches/xx-propose-for-merging.txt
@@ -25,7 +25,7 @@ branch.
 
     >>> def print_radio_buttons(browser):
     ...     main = find_main_content(browser.contents)
-    ...     for button in main.findAll('input', attrs={'type': 'radio'}):
+    ...     for button in main.find_all('input', attrs={'type': 'radio'}):
     ...         try:
     ...             if button['checked']:
     ...                 checked = '(*)'
diff --git a/lib/lp/code/stories/branches/xx-source-package-branches-listing.txt b/lib/lp/code/stories/branches/xx-source-package-branches-listing.txt
index 4aab30a..7f8a8f7 100644
--- a/lib/lp/code/stories/branches/xx-source-package-branches-listing.txt
+++ b/lib/lp/code/stories/branches/xx-source-package-branches-listing.txt
@@ -34,7 +34,7 @@ Both of the branches we made appear in the listing.
 
     >>> def print_branches(browser):
     ...    table = find_tag_by_id(browser.contents, 'branchtable')
-    ...    for row in table.tbody.findAll('tr'):
+    ...    for row in table.tbody.find_all('tr'):
     ...        print(extract_text(row))
     >>> print_branches(browser)
     lp://dev/~owner1/distro/series/foo/branch1 ...
diff --git a/lib/lp/code/stories/branches/xx-subscribing-branches.txt b/lib/lp/code/stories/branches/xx-subscribing-branches.txt
index de24148..f240f47 100644
--- a/lib/lp/code/stories/branches/xx-subscribing-branches.txt
+++ b/lib/lp/code/stories/branches/xx-subscribing-branches.txt
@@ -9,7 +9,7 @@ A quick helper function to list the subscribed people.
     ...     if subscriptions == None:
     ...         print(subscriptions)
     ...         return
-    ...     for subscriber in subscriptions.findAll('div'):
+    ...     for subscriber in subscriptions.find_all('div'):
     ...         print(extract_text(subscriber.decode_contents()))
 
 Another to print the informational message.
diff --git a/lib/lp/code/stories/codeimport/xx-codeimport-results.txt b/lib/lp/code/stories/codeimport/xx-codeimport-results.txt
index 039ee60..e78f26c 100644
--- a/lib/lp/code/stories/codeimport/xx-codeimport-results.txt
+++ b/lib/lp/code/stories/codeimport/xx-codeimport-results.txt
@@ -53,7 +53,7 @@ is the text of the failure or success type.
 
     >>> # The ordering here is dependant on the order the status values
     >>> # are declared in the enumeration.
-    >>> for img in import_results.findAll('img'):
+    >>> for img in import_results.find_all('img'):
     ...     print(img)
     <img src="/@@/no" title="Unsupported feature"/>
     <img src="/@@/no" title="Foreign branch invalid"/>
@@ -65,7 +65,7 @@ is the text of the failure or success type.
 
     >>> browser.open(branch_url_2)
     >>> import_results = find_tag_by_id(browser.contents, 'import-results')
-    >>> for img in import_results.findAll('img'):
+    >>> for img in import_results.find_all('img'):
     ...     print(img)
     <img src="/@@/no" title="Job killed"/>
     <img src="/@@/no" title="Job reclaimed"/>
diff --git a/lib/lp/code/stories/sourcepackagerecipes/xx-recipe-listings.txt b/lib/lp/code/stories/sourcepackagerecipes/xx-recipe-listings.txt
index 688e713..a1876cd 100644
--- a/lib/lp/code/stories/sourcepackagerecipes/xx-recipe-listings.txt
+++ b/lib/lp/code/stories/sourcepackagerecipes/xx-recipe-listings.txt
@@ -7,12 +7,12 @@ page template, and views derived from RecipeListingView.
 
     >>> def print_recipe_listing_head(browser):
     ...     table = find_tag_by_id(browser.contents, 'recipetable')
-    ...     for row in table.thead.findAll('tr'):
+    ...     for row in table.thead.find_all('tr'):
     ...         print(extract_text(row))
 
     >>> def print_recipe_listing_contents(browser):
     ...     table = find_tag_by_id(browser.contents, 'recipetable')
-    ...     for row in table.tbody.findAll('tr'):
+    ...     for row in table.tbody.find_all('tr'):
     ...         print(extract_text(row))
 
 
diff --git a/lib/lp/coop/answersbugs/stories/question-makebug.txt b/lib/lp/coop/answersbugs/stories/question-makebug.txt
index e47b617..029f8cb 100644
--- a/lib/lp/coop/answersbugs/stories/question-makebug.txt
+++ b/lib/lp/coop/answersbugs/stories/question-makebug.txt
@@ -53,7 +53,7 @@ The bug page will display a link to the originating question in the 'Related
 questions' portlet:
 
   >>> portlet = find_portlet(browser.contents, 'Related questions')
-  >>> for question in portlet.findAll('li', 'question-row'):
+  >>> for question in portlet.find_all('li', 'question-row'):
   ...     print(question.decode_contents())
   <span class="sprite question">Mozilla Firefox</span>: ...<a href=".../firefox/+question/2">Problem...
 
diff --git a/lib/lp/registry/browser/tests/coc-views.txt b/lib/lp/registry/browser/tests/coc-views.txt
index ced69df..f8a21d5 100644
--- a/lib/lp/registry/browser/tests/coc-views.txt
+++ b/lib/lp/registry/browser/tests/coc-views.txt
@@ -24,7 +24,7 @@ key registered, directions are shown.
     >>> def print_coc_directions(content):
     ...     ol = content.find('ol')
     ...     if ol is not None:
-    ...         for index, li in enumerate(ol.findAll('li')):
+    ...         for index, li in enumerate(ol.find_all('li')):
     ...             print('%s. %s' % ((index+1), extract_text(li)))
     >>> user = factory.makePerson()
     >>> ignored = login_person(user)
diff --git a/lib/lp/registry/browser/tests/packaging-views.txt b/lib/lp/registry/browser/tests/packaging-views.txt
index 98dd27d..42e26a5 100644
--- a/lib/lp/registry/browser/tests/packaging-views.txt
+++ b/lib/lp/registry/browser/tests/packaging-views.txt
@@ -273,7 +273,7 @@ instead.)
 
     # There are links to the +remove-packaging page.
     >>> table = find_tag_by_id(view.render(), 'packages-hotter')
-    >>> for link in table.findAll('a'):
+    >>> for link in table.find_all('a'):
     ...     if '+remove-packaging' in link['href']:
     ...         print(link['href'])
     http://launchpad.test/ubuntu/grumpy/+source/hot/+remove-packaging
diff --git a/lib/lp/registry/browser/tests/sourcepackage-views.txt b/lib/lp/registry/browser/tests/sourcepackage-views.txt
index b7f4140..e15111c 100644
--- a/lib/lp/registry/browser/tests/sourcepackage-views.txt
+++ b/lib/lp/registry/browser/tests/sourcepackage-views.txt
@@ -172,7 +172,7 @@ bonkers project, the portlet will display that information.
     >>> from lp.testing.pages import (
     ...     extract_text, find_tag_by_id)
     >>> content = find_tag_by_id(view.render(), 'upstreams')
-    >>> for link in content.findAll('a'):
+    >>> for link in content.find_all('a'):
     ...     print(link['href'])
     /bonkers
     /bonkers/crazy
diff --git a/lib/lp/registry/browser/tests/test_distroseries.py b/lib/lp/registry/browser/tests/test_distroseries.py
index 063158d..46ab789 100644
--- a/lib/lp/registry/browser/tests/test_distroseries.py
+++ b/lib/lp/registry/browser/tests/test_distroseries.py
@@ -1183,7 +1183,7 @@ class TestDistroSeriesLocalDifferences(TestCaseWithFactory,
         view = self.makeView(derived_series)
 
         soup = BeautifulSoup(view())
-        help_links = soup.findAll(
+        help_links = soup.find_all(
             'a', href='/+help-soyuz/derived-series-syncing.html')
         self.assertEqual(1, len(help_links))
 
@@ -1202,7 +1202,7 @@ class TestDistroSeriesLocalDifferences(TestCaseWithFactory,
         # listing the differences.
         soup = BeautifulSoup(view())
         diff_table = soup.find('table', {'class': 'listing'})
-        rows = diff_table.tbody.findAll('tr')
+        rows = diff_table.tbody.find_all('tr')
 
         self.assertEqual(1, len(rows))
         self.assertIn("Latest comment", six.text_type(rows[0]))
@@ -1217,9 +1217,9 @@ class TestDistroSeriesLocalDifferences(TestCaseWithFactory,
         view = self.makeView(derived_series)
         soup = BeautifulSoup(view())
         diff_table = soup.find('table', {'class': 'listing'})
-        row = diff_table.tbody.findAll('tr')[0]
+        row = diff_table.tbody.find_all('tr')[0]
 
-        links = row.findAll('a', href=canonical_url(difference))
+        links = row.find_all('a', href=canonical_url(difference))
         self.assertEqual(1, len(links))
         self.assertEqual(difference.source_package_name.name, links[0].string)
 
@@ -1289,7 +1289,7 @@ class TestDistroSeriesLocalDifferences(TestCaseWithFactory,
         soup = BeautifulSoup(view())
         diff_table = soup.find('table', {'class': 'listing'})
         row = diff_table.tbody.tr
-        links = row.findAll('a', {'class': 'derived-version'})
+        links = row.find_all('a', {'class': 'derived-version'})
 
         # The version displayed is the version attached to the
         # difference.
@@ -1334,8 +1334,8 @@ class TestDistroSeriesLocalDifferences(TestCaseWithFactory,
 
         # The table feature a simple span since we were unable to fetch a
         # published sourcepackage.
-        derived_span = row.findAll('span', {'class': 'derived-version'})
-        parent_span = row.findAll('span', {'class': 'parent-version'})
+        derived_span = row.find_all('span', {'class': 'derived-version'})
+        parent_span = row.find_all('span', {'class': 'parent-version'})
         self.assertEqual(1, len(derived_span))
         self.assertEqual(1, len(parent_span))
 
@@ -1387,7 +1387,7 @@ class TestDistroSeriesLocalDifferences(TestCaseWithFactory,
         diff_table = soup.find('table', {'class': 'listing'})
         row = diff_table.tbody.tr
 
-        changelog_span = row.findAll('span', {'class': 'lesser'})
+        changelog_span = row.find_all('span', {'class': 'lesser'})
         self.assertEqual(1, len(changelog_span))
         link = changelog_span[0].a
         self.assertEqual("changelog", link.string)
diff --git a/lib/lp/registry/browser/tests/test_distroseriesdifference_views.py b/lib/lp/registry/browser/tests/test_distroseriesdifference_views.py
index 6f138a5..4de09b6 100644
--- a/lib/lp/registry/browser/tests/test_distroseriesdifference_views.py
+++ b/lib/lp/registry/browser/tests/test_distroseriesdifference_views.py
@@ -239,7 +239,7 @@ class DistroSeriesDifferenceTestCase(TestCaseWithFactory):
             view = create_initialized_view(
                 ds_diff, '+listing-distroseries-extra')
             soup = BeautifulSoup(view())
-        tags = soup.find('ul', 'package-diff-status').findAll('span')
+        tags = soup.find('ul', 'package-diff-status').find_all('span')
         self.assertEqual(2, len(tags))
 
     def test_do_not_display_child_diff(self):
@@ -261,7 +261,7 @@ class DistroSeriesDifferenceTestCase(TestCaseWithFactory):
             view = create_initialized_view(
                 ds_diff, '+listing-distroseries-extra')
             soup = BeautifulSoup(view())
-        tags = soup.find('ul', 'package-diff-status').findAll('span')
+        tags = soup.find('ul', 'package-diff-status').find_all('span')
         self.assertEqual(1, len(tags))
 
     def test_do_not_display_parent_diff(self):
@@ -283,7 +283,7 @@ class DistroSeriesDifferenceTestCase(TestCaseWithFactory):
             view = create_initialized_view(
                 ds_diff, '+listing-distroseries-extra')
             soup = BeautifulSoup(view())
-        tags = soup.find('ul', 'package-diff-status').findAll('span')
+        tags = soup.find('ul', 'package-diff-status').find_all('span')
         self.assertEqual(1, len(tags))
 
     def _assertNoRequestLink(self, ds_diff):
@@ -372,7 +372,7 @@ class DistroSeriesDifferenceTemplateTestCase(TestCaseWithFactory):
         else:
             soup = html_or_soup
         class_dict = {'class': re.compile('request-derived-diff')}
-        return len(soup.findAll('span', class_dict))
+        return len(soup.find_all('span', class_dict))
 
     def contains_one_link_to_diff(self, html_or_soup, package_diff):
         """Return whether the html contains a link to the diff content."""
@@ -380,7 +380,7 @@ class DistroSeriesDifferenceTemplateTestCase(TestCaseWithFactory):
             soup = BeautifulSoup(html_or_soup)
         else:
             soup = html_or_soup
-        return 1 == len(soup.findAll(
+        return 1 == len(soup.find_all(
             'a', href=package_diff.diff_content.http_url))
 
     def test_both_request_diff_texts_rendered(self):
@@ -435,7 +435,7 @@ class DistroSeriesDifferenceTemplateTestCase(TestCaseWithFactory):
             # The diff has a css_class class.
             self.assertEqual(
                 1,
-                len(soup.findAll('span', {'class': re.compile(css_class)})))
+                len(soup.find_all('span', {'class': re.compile(css_class)})))
 
     def test_parent_source_diff_rendering_diff_no_link(self):
         # The status of the package is shown if the parent package diff is
@@ -459,7 +459,7 @@ class DistroSeriesDifferenceTemplateTestCase(TestCaseWithFactory):
             # The diff has a css_class class.
             self.assertEqual(
                 1,
-                len(soup.findAll('span', {'class': re.compile(css_class)})))
+                len(soup.find_all('span', {'class': re.compile(css_class)})))
 
     def test_source_diff_rendering_no_source(self):
         # If there is no source pub for this difference, then we don't
@@ -511,9 +511,9 @@ class DistroSeriesDifferenceTemplateTestCase(TestCaseWithFactory):
         soup = BeautifulSoup(view())
 
         self.assertEqual(
-            1, len(soup.findAll('p', text="I'm working on this.")))
+            1, len(soup.find_all('p', text="I'm working on this.")))
         self.assertEqual(
-            1, len(soup.findAll('p', text="Here's another comment.")))
+            1, len(soup.find_all('p', text="Here's another comment.")))
 
     def test_last_common_version_is_linked(self):
         # The "Last Common Version" version text should link to the
@@ -555,7 +555,7 @@ class DistroSeriesDifferenceTemplateTestCase(TestCaseWithFactory):
         soup = BeautifulSoup(view_content)
 
         self.assertEqual(
-            1, len(soup.findAll('div', {'class': 'blacklist-options'})))
+            1, len(soup.find_all('div', {'class': 'blacklist-options'})))
 
     def test_blacklist_options_disabled(self):
         # Blacklist options are disabled to the users who are *not* archive
@@ -568,7 +568,7 @@ class DistroSeriesDifferenceTemplateTestCase(TestCaseWithFactory):
 
         self.assertEqual(
             1,
-            len(soup.findAll('div', {'class': 'blacklist-options-disabled'})))
+            len(soup.find_all('div', {'class': 'blacklist-options-disabled'})))
 
     def test_blacklist_options_initial_values_none(self):
         ds_diff = self.factory.makeDistroSeriesDifference()
diff --git a/lib/lp/registry/browser/tests/test_product.py b/lib/lp/registry/browser/tests/test_product.py
index 70a99ea..235fffd 100644
--- a/lib/lp/registry/browser/tests/test_product.py
+++ b/lib/lp/registry/browser/tests/test_product.py
@@ -1056,7 +1056,7 @@ class TestProductSet(BrowserTestCase):
             results = [
                 re.sub('\nregistered\n.*', '', extract_text(td))
                 for td in find_tag_by_id(
-                    browser.contents, 'latest-registered').findAll('td')]
+                    browser.contents, 'latest-registered').find_all('td')]
             self.assertIn(public.display_name, results)
             self.assertIn(policy_granted.display_name, results)
             self.assertNotIn(artifact_granted.display_name, results)
@@ -1104,7 +1104,7 @@ class TestProductSet(BrowserTestCase):
                 expected_results,
                 [extract_text(td)
                  for td in find_tag_by_id(
-                     browser.contents, 'search-results').findAll('td')])
+                     browser.contents, 'search-results').find_all('td')])
 
 
 class TestProductSetBranchView(TestCaseWithFactory):
diff --git a/lib/lp/registry/doc/product-widgets.txt b/lib/lp/registry/doc/product-widgets.txt
index 35d4471..b656e51 100644
--- a/lib/lp/registry/doc/product-widgets.txt
+++ b/lib/lp/registry/doc/product-widgets.txt
@@ -314,7 +314,7 @@ One licence, the GNU GPL v2, is selected.
 
     >>> def print_checked_items(html, links=False):
     ...     soup = BeautifulSoup(html)
-    ...     for label in soup.findAll('label'):
+    ...     for label in soup.find_all('label'):
     ...         if not isinstance(label.next, Tag):
     ...             continue
     ...         if label.next.get('checked'):
diff --git a/lib/lp/registry/stories/announcements/xx-announcements.txt b/lib/lp/registry/stories/announcements/xx-announcements.txt
index 082256a..9296fb6 100644
--- a/lib/lp/registry/stories/announcements/xx-announcements.txt
+++ b/lib/lp/registry/stories/announcements/xx-announcements.txt
@@ -280,7 +280,7 @@ page that any user can navigate.
     >>> print(extract_text(content.h1))
     Derby announcement headline
 
-    >>> print(extract_text(content.findAll('p')[1]))
+    >>> print(extract_text(content.find_all('p')[1]))
     Derby announcement summary
 
     >>> anon_browser.getLink("Read all announcements").click()
diff --git a/lib/lp/registry/stories/distributionmirror/xx-distribution-mirrors.txt b/lib/lp/registry/stories/distributionmirror/xx-distribution-mirrors.txt
index b1528b3..f288178 100644
--- a/lib/lp/registry/stories/distributionmirror/xx-distribution-mirrors.txt
+++ b/lib/lp/registry/stories/distributionmirror/xx-distribution-mirrors.txt
@@ -11,7 +11,7 @@ on their status and content.
     ...     header = mirrors_table.find('tr')
     ...     country = extract_text(header.find('th'))
     ...     mirrors = []
-    ...     for tr in header.findNextSiblings('tr'):
+    ...     for tr in header.find_next_siblings('tr'):
     ...         if 'head' in str(tr.attrs):
     ...             print("%s: %s" % (country, pretty(mirrors)))
     ...             country = extract_text(tr.find('th'))
@@ -23,7 +23,7 @@ on their status and content.
     ...             # from different countries, so we'll just skip it.
     ...             pass
     ...         else:
-    ...             tds = tuple([extract_text(td) for td in tr.findAll('td')])
+    ...             tds = tuple([extract_text(td) for td in tr.find_all('td')])
     ...             mirrors.append(tds)
 
 Official mirrors
diff --git a/lib/lp/registry/stories/milestone/object-milestones.txt b/lib/lp/registry/stories/milestone/object-milestones.txt
index 5eaf227..0305466 100644
--- a/lib/lp/registry/stories/milestone/object-milestones.txt
+++ b/lib/lp/registry/stories/milestone/object-milestones.txt
@@ -13,11 +13,11 @@ function will print them out:
     ...     if table is None:
     ...         return None
     ...     result = []
-    ...     for tr in table.findAll('tr'):
+    ...     for tr in table.find_all('tr'):
     ...         milestone_date = tr.find('span')
     ...         if len(milestone_date.contents) > 0:
     ...             # Just make sure we don't print an actual date.
-    ...             milestone_date.contents[0].replaceWith('A date')
+    ...             milestone_date.contents[0].replace_with('A date')
     ...         result.append(extract_text(tr))
     ...     return '\n'.join(result)
 
@@ -26,9 +26,9 @@ function will print them out:
     ...     if portlet is None:
     ...         return None
     ...     result = []
-    ...     for tr in portlet.find('table').findAll('tr'):
+    ...     for tr in portlet.find('table').find_all('tr'):
     ...         result.append(
-    ...             ' '.join(text.strip() for text in tr.findAll(text=True)))
+    ...             ' '.join(text.strip() for text in tr.find_all(text=True)))
     ...     return '\n'.join(result)
 
 
@@ -361,7 +361,7 @@ listing:
 
 Each bugtask has one or more badges.
 
-    >>> print(bug_table.findAll('tr')[1])
+    >>> print(bug_table.find_all('tr')[1])
     <tr>...Test Bug 1...<a...alt="milestone test-milestone"...
       class="sprite milestone"...>...
 
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 bc2e2f6..e191f41 100644
--- a/lib/lp/registry/stories/person/xx-admin-person-review.txt
+++ b/lib/lp/registry/stories/person/xx-admin-person-review.txt
@@ -61,7 +61,7 @@ The +reviewaccount page displays account information that is normally
 hidden from the UI.
 
     >>> content = find_main_content(expert_browser.contents)
-    >>> for tr in content.find(id='summary').findAll('tr'):
+    >>> for tr in content.find(id='summary').find_all('tr'):
     ...     print(extract_text(tr))
     Created: 2005-06-06
     Creation reason: Created by the owner themselves, coming from Launchpad.
diff --git a/lib/lp/registry/stories/person/xx-person-edit-jabber-ids.txt b/lib/lp/registry/stories/person/xx-person-edit-jabber-ids.txt
index f2a6112..72666ec 100644
--- a/lib/lp/registry/stories/person/xx-person-edit-jabber-ids.txt
+++ b/lib/lp/registry/stories/person/xx-person-edit-jabber-ids.txt
@@ -45,7 +45,7 @@ it will be associated with their account.
 
     >>> def show_jabberids(browser):
     ...     tags = find_tag_by_id(browser.contents, 'jabber-ids')
-    ...     for dd in tags.findAll('dd'):
+    ...     for dd in tags.find_all('dd'):
     ...         print(extract_text(dd))
 
     >>> show_jabberids(user_browser)
diff --git a/lib/lp/registry/stories/person/xx-person-home.txt b/lib/lp/registry/stories/person/xx-person-home.txt
index fab8a67..28a99ab 100644
--- a/lib/lp/registry/stories/person/xx-person-home.txt
+++ b/lib/lp/registry/stories/person/xx-person-home.txt
@@ -188,9 +188,9 @@ most active and also the areas in which they worked on each project.
 
     >>> anon_browser.open('http://launchpad.test/~name16')
     >>> table = find_tag_by_id(anon_browser.contents, 'contributions')
-    >>> for tr in table.findAll('tr'):
+    >>> for tr in table.find_all('tr'):
     ...     print(tr.find('th').find('a').decode_contents())
-    ...     for td in tr.findAll('td'):
+    ...     for td in tr.find_all('td'):
     ...         img = td.find('img')
     ...         if img is not None:
     ...             print("\t", img['title'])
diff --git a/lib/lp/registry/stories/person/xx-person-karma.txt b/lib/lp/registry/stories/person/xx-person-karma.txt
index 8c1c741..09c759d 100644
--- a/lib/lp/registry/stories/person/xx-person-karma.txt
+++ b/lib/lp/registry/stories/person/xx-person-karma.txt
@@ -24,14 +24,14 @@ The total karma points is also a link to the person's karma summary page.
 Any user can see the categories that a user has earned karma in.
 
     >>> content = find_main_content(anon_browser.contents)
-    >>> for row in find_tag_by_id(content, 'karmapoints').findAll('tr'):
+    >>> for row in find_tag_by_id(content, 'karmapoints').find_all('tr'):
     ...     print(extract_text(row))
     Bug Management           94
     Specification Tracking   44
 
 Any user can see that a user has latest actions.
 
-    >>> for row in find_tag_by_id(content, 'karmaactions').findAll('tr'):
+    >>> for row in find_tag_by_id(content, 'karmaactions').find_all('tr'):
     ...     print(extract_text(row))
     Date        Action
     2001-11-02  Registered Specification
@@ -55,7 +55,7 @@ a that user's last karma actions
     >>> print(extract_text(find_tag_by_id(content, 'no-karma')))
     Karl Tilbury's karma has expired.
 
-    >>> for row in find_tag_by_id(content, 'karmaactions').findAll('tr'):
+    >>> for row in find_tag_by_id(content, 'karmaactions').find_all('tr'):
     ...     print(extract_text(row))
     Date        Action
     2001-08-09  New Bug Filed
diff --git a/lib/lp/registry/stories/person/xx-person-projects.txt b/lib/lp/registry/stories/person/xx-person-projects.txt
index 89a323c..ed5fd27 100644
--- a/lib/lp/registry/stories/person/xx-person-projects.txt
+++ b/lib/lp/registry/stories/person/xx-person-projects.txt
@@ -7,7 +7,7 @@ team.
     >>> anon_browser.open('http://launchpad.test/~ubuntu-team')
     >>> related_projects = find_tag_by_id(
     ...     anon_browser.contents, 'portlet-related-projects')
-    >>> for tr in related_projects.findAll('tr'):
+    >>> for tr in related_projects.find_all('tr'):
     ...     print(extract_text(tr))
     Ubuntu
     ubuntutest
diff --git a/lib/lp/registry/stories/person/xx-person-subscriptions.txt b/lib/lp/registry/stories/person/xx-person-subscriptions.txt
index 8546cb1..97c5d1e 100644
--- a/lib/lp/registry/stories/person/xx-person-subscriptions.txt
+++ b/lib/lp/registry/stories/person/xx-person-subscriptions.txt
@@ -169,8 +169,8 @@ followed by a link to edit the subscription.
 
     >>> subscriptions = find_tag_by_id(
     ...     admin_browser.contents, 'structural-subscriptions')
-    >>> for subscription in subscriptions.findAll("li"):
-    ...     structure_link, modify_link = subscription.findAll("a")[:2]
+    >>> for subscription in subscriptions.find_all("li"):
+    ...     structure_link, modify_link = subscription.find_all("a")[:2]
     ...     print("%s <%s>" % (
     ...         extract_text(structure_link), structure_link.get("href")))
     ...     print("--> %s" % modify_link.get("href"))
@@ -186,7 +186,7 @@ permission to modify those subscriptions.
     ...     "http://launchpad.test/~name16/+structural-subscriptions";)
     >>> subscriptions = find_tag_by_id(
     ...     subscriber_browser.contents, 'structural-subscriptions')
-    >>> for subscription in subscriptions.findAll("li"):
+    >>> for subscription in subscriptions.find_all("li"):
     ...     structure_link = subscription.find("a")
     ...     print("%s <%s>" % (
     ...         extract_text(structure_link), structure_link.get("href")))
@@ -220,7 +220,7 @@ subscription filter.
     >>> def show_create_links(browser):
     ...     subscriptions = find_tag_by_id(
     ...         browser.contents, 'structural-subscriptions')
-    ...     for subscription in subscriptions.findAll("li"):
+    ...     for subscription in subscriptions.find_all("li"):
     ...         structure_link = subscription.find("a")
     ...         print(extract_text(structure_link))
     ...         create_text = subscription.find(text=re.compile("Create"))
@@ -272,7 +272,7 @@ filters are shown with a message stating that there is no filtering.
     ...         "+me/+structural-subscriptions")
     ...     subscriptions = find_tag_by_id(
     ...         nigel_browser.contents, 'structural-subscriptions')
-    ...     for subscription in subscriptions.findAll("li"):
+    ...     for subscription in subscriptions.find_all("li"):
     ...         print(extract_text(subscription.p))
     ...         if subscription.dl is not None:
     ...             print(extract_text(subscription.dl))
diff --git a/lib/lp/registry/stories/pillar/xx-pillar-sprints.txt b/lib/lp/registry/stories/pillar/xx-pillar-sprints.txt
index 6e24e3b..edb6888 100644
--- a/lib/lp/registry/stories/pillar/xx-pillar-sprints.txt
+++ b/lib/lp/registry/stories/pillar/xx-pillar-sprints.txt
@@ -6,7 +6,7 @@ all events relevant to that pillar.
     >>> import re
     >>> def print_sprints(contents):
     ...     maincontent = find_tag_by_id(contents, 'maincontent')
-    ...     for link in maincontent.findAll('a'):
+    ...     for link in maincontent.find_all('a'):
     ...         if re.search('/sprints/[a-z0-9]', link['href']) is not None:
     ...             print(link.decode_contents())
 
diff --git a/lib/lp/registry/stories/product/xx-launchpad-project-search.txt b/lib/lp/registry/stories/product/xx-launchpad-project-search.txt
index 1a22992..e3f6b8b 100644
--- a/lib/lp/registry/stories/product/xx-launchpad-project-search.txt
+++ b/lib/lp/registry/stories/product/xx-launchpad-project-search.txt
@@ -98,5 +98,5 @@ A similar page is available for only searching project groups.
 The search results contains only project-groups.
 
     >>> print(extract_text(
-    ...     find_main_content(anon_browser.contents).findAll('p')[1]))
+    ...     find_main_content(anon_browser.contents).find_all('p')[1]))
     1 project group found matching ...gnome...
diff --git a/lib/lp/registry/stories/product/xx-product-code-trunk.txt b/lib/lp/registry/stories/product/xx-product-code-trunk.txt
index 04ba410..84fb002 100644
--- a/lib/lp/registry/stories/product/xx-product-code-trunk.txt
+++ b/lib/lp/registry/stories/product/xx-product-code-trunk.txt
@@ -26,7 +26,7 @@ Make revisions for the branch so it has a codebrowse link.
     ...     dev_focus = find_tag_by_id(browser.contents, 'development-focus')
     ...     print(extract_text(dev_focus))
     ...     print("Links:")
-    ...     for a in dev_focus.findAll('a'):
+    ...     for a in dev_focus.find_all('a'):
     ...         for content in a.contents:
     ...             print(content)
     ...         title = a.get('title', '')
@@ -40,14 +40,14 @@ Make revisions for the branch so it has a codebrowse link.
     ...     except TypeError:
     ...         return
     ...     print("Links:")
-    ...     for a in code_trunk.findAll('a'):
+    ...     for a in code_trunk.find_all('a'):
     ...         for content in a.contents:
     ...             print(content)
     ...         title = a.get('title', '')
     ...         print("%s (%s)" % (title, a['href']))
     >>> def print_involvement_portlet(browser):
     ...     involvement = find_tag_by_id(browser.contents, 'involvement')
-    ...     for a in involvement.findAll('a'):
+    ...     for a in involvement.find_all('a'):
     ...         for content in a.contents:
     ...             print(content)
     ...         print(a['href'])
diff --git a/lib/lp/registry/stories/product/xx-product-files.txt b/lib/lp/registry/stories/product/xx-product-files.txt
index 11386bb..04f7fc8 100644
--- a/lib/lp/registry/stories/product/xx-product-files.txt
+++ b/lib/lp/registry/stories/product/xx-product-files.txt
@@ -261,7 +261,7 @@ user to see.
 
     >>> anon_browser.open('http://launchpad.test/firefox/+download')
     >>> content = find_main_content(anon_browser.contents)
-    >>> for tr in  content.findAll('table')[1].findAll('tr'):
+    >>> for tr in content.find_all('table')[1].find_all('tr'):
     ...     print(extract_text(tr))
     File                 Description      Downloads
     bar.txt (md5)        Bar installer    -
@@ -275,7 +275,7 @@ an admin can also delete a release file.
     >>> checkbox = admin_browser.getControl(name='checkbox_0')
     >>> checkbox.value = checkbox.options
     >>> table = find_tag_by_id(admin_browser.contents, 'downloads')
-    >>> for tr in table.findAll('tr'):
+    >>> for tr in table.find_all('tr'):
     ...     print(extract_text(tr))
     File                Description    Downloads      Delete
     bar.txt (md5)       Bar installer  -
@@ -286,7 +286,7 @@ an admin can also delete a release file.
     >>> print_feedback_messages(admin_browser.contents)
     1 file has been deleted.
     >>> table = find_tag_by_id(admin_browser.contents, 'downloads')
-    >>> for tr in table.findAll('tr'):
+    >>> for tr in table.find_all('tr'):
     ...     print(extract_text(tr))
     File                Description    Downloads     Delete
     foo.txt (md5, sig)  Foo installer  -
@@ -386,9 +386,9 @@ are listed within series in reverse chronological order, except
     >>> firefox_owner.url
     'http://launchpad.test/firefox/+download'
     >>> content = find_main_content(firefox_owner.contents)
-    >>> rows = content.findAll('tr')
+    >>> rows = content.find_all('tr')
     >>> for row in rows[1:]:
-    ...     a_list = row.findAll('a')
+    ...     a_list = row.find_all('a')
     ...     if len(a_list) > 0:
     ...        print(a_list[0].string)
     firefox_0.9.2.orig.tar.gz
@@ -447,9 +447,9 @@ Ensure the file is no longer listed.
     >>> firefox_owner.url
     'http://launchpad.test/firefox/+download'
     >>> content = find_main_content(firefox_owner.contents)
-    >>> rows = content.findAll('tr')
+    >>> rows = content.find_all('tr')
     >>> for row in rows[1:]:
-    ...     a_list = row.findAll('a')
+    ...     a_list = row.find_all('a')
     ...     if len(a_list) > 0:
     ...        print(a_list[0].string)
     firefox_0.9.2.orig.tar.gz
@@ -466,7 +466,7 @@ Non-administrators do not have the delete option.
 
     >>> non_owner.open('http://launchpad.test/firefox/trunk/0.9')
     >>> table = find_tag_by_id(non_owner.contents, 'downloads')
-    >>> for tr in table.findAll('tr'):
+    >>> for tr in table.find_all('tr'):
     ...     print(extract_text(tr))
     File             Description     Downloads
     foo09.txt (md5)  Foo09 installer -
@@ -485,7 +485,7 @@ so it is not shown.
 
     >>> firefox_owner.open('http://launchpad.test/firefox/trunk/0.9')
     >>> table = find_tag_by_id(firefox_owner.contents, 'downloads')
-    >>> for tr in table.findAll('tr'):
+    >>> for tr in table.find_all('tr'):
     ...     print(extract_text(tr))
     File             Description     Downloads  Delete
     foo09.txt (md5)  Foo09 installer -
diff --git a/lib/lp/registry/stories/product/xx-product-index.txt b/lib/lp/registry/stories/product/xx-product-index.txt
index 28e7079..6aa8dc8 100644
--- a/lib/lp/registry/stories/product/xx-product-index.txt
+++ b/lib/lp/registry/stories/product/xx-product-index.txt
@@ -50,7 +50,7 @@ Let's check it out:
     >>> browser.open('http://launchpad.test/tomcat')
     >>> content = find_main_content(browser.contents)
     >>> external_links = find_tag_by_id(content, 'external-links')
-    >>> for link in external_links.findAll('a'):
+    >>> for link in external_links.find_all('a'):
     ...     print(extract_text(link), link['href'])
     Home page http://home.page/
     Sourceforge project http://sourceforge.net/projects/sf-tomcat
@@ -75,7 +75,7 @@ When the sourceforge URL is identical to the homepage, we omit the homepage:
     >>> browser.open('http://launchpad.test/tomcat')
     >>> content = find_main_content(browser.contents)
     >>> external_links = find_tag_by_id(content, 'external-links')
-    >>> for link in external_links.findAll('a'):
+    >>> for link in external_links.find_all('a'):
     ...     print(extract_text(link), link['href'])
     Sourceforge project http://sourceforge.net/projects/sf-tomcat
     Wiki http://wiki.url/
diff --git a/lib/lp/registry/stories/productseries/xx-productseries-index.txt b/lib/lp/registry/stories/productseries/xx-productseries-index.txt
index 7a4470c..55724b4 100644
--- a/lib/lp/registry/stories/productseries/xx-productseries-index.txt
+++ b/lib/lp/registry/stories/productseries/xx-productseries-index.txt
@@ -50,7 +50,7 @@ Any user can subscribe to bug mail via the link on the page.
 
 The series page lists the milestones and releases for the series.
 
-    >>> rows = find_tag_by_id(content, 'series-trunk').findAll('tr')
+    >>> rows = find_tag_by_id(content, 'series-trunk').find_all('tr')
     >>> for row in rows[0:2]:
     ...     print(extract_text(row))
     Version                          Expected   Released     Summary
@@ -60,7 +60,7 @@ The driver can see a link to set the expected date.
 
     >>> driver_content = find_main_content(driver_browser.contents)
     >>> driver_rows = find_tag_by_id(
-    ...     driver_content, 'series-trunk').findAll('tr')
+    ...     driver_content, 'series-trunk').find_all('tr')
     >>> for row in driver_rows[0:2]:
     ...     print(extract_text(row))
     Version                          Expected             Released    Summary
diff --git a/lib/lp/registry/stories/team/xx-team-claim.txt b/lib/lp/registry/stories/team/xx-team-claim.txt
index 5491e01..1fca7ec 100644
--- a/lib/lp/registry/stories/team/xx-team-claim.txt
+++ b/lib/lp/registry/stories/team/xx-team-claim.txt
@@ -84,7 +84,7 @@ claim process the team's owner.
     >>> from lp.services.beautifulsoup import BeautifulSoup
     >>> soup = BeautifulSoup(user_browser.contents)
     >>> print(extract_text(
-    ...     soup.find(attrs={'for': 'field.teamowner'}).findPrevious('tr')))
+    ...     soup.find(attrs={'for': 'field.teamowner'}).find_previous('tr')))
     Team Owner: No Privileges Person...
 
     >>> user_browser.getControl('Display Name').value = 'Ubuntu Doc Team'
diff --git a/lib/lp/registry/stories/teammembership/xx-teammembership.txt b/lib/lp/registry/stories/teammembership/xx-teammembership.txt
index acc2b9f..ff6f13d 100644
--- a/lib/lp/registry/stories/teammembership/xx-teammembership.txt
+++ b/lib/lp/registry/stories/teammembership/xx-teammembership.txt
@@ -211,12 +211,12 @@ be there at all.
     'http://launchpad.test/~myemail/+members'
 
     >>> contents = anon_browser.contents
-    >>> for link in find_tag_by_id(contents, 'activemembers').findAll('a'):
+    >>> for link in find_tag_by_id(contents, 'activemembers').find_all('a'):
     ...     print(link.decode_contents())
     Karl Tilbury
     Sample Person
 
-    >>> for link in find_tag_by_id(contents, 'proposedmembers').findAll('a'):
+    >>> for link in find_tag_by_id(contents, 'proposedmembers').find_all('a'):
     ...     print(link.decode_contents())
     Colin Watson
     James Blackwell
@@ -231,7 +231,7 @@ have been invited.
 
     >>> def print_members(contents, type):
     ...     table = find_tag_by_id(contents, type)
-    ...     for link in table.findAll('a'):
+    ...     for link in table.find_all('a'):
     ...         link_contents = link.decode_contents()
     ...         if link_contents != 'Edit' and not link.find('img'):
     ...             print(link_contents)
diff --git a/lib/lp/registry/tests/test_product.py b/lib/lp/registry/tests/test_product.py
index 0f04a93..80f7edd 100644
--- a/lib/lp/registry/tests/test_product.py
+++ b/lib/lp/registry/tests/test_product.py
@@ -1437,9 +1437,9 @@ class TestProductFiles(TestCase):
             [u"Your file 'foo\xa5.txt' has been uploaded."])
         firefox_owner.open('http://launchpad.test/firefox/+download')
         content = find_main_content(firefox_owner.contents)
-        rows = content.findAll('tr')
+        rows = content.find_all('tr')
 
-        a_list = rows[-1].findAll('a')
+        a_list = rows[-1].find_all('a')
         # 1st row
         a_element = a_list[0]
         self.assertEqual(
diff --git a/lib/lp/services/feeds/feed.py b/lib/lp/services/feeds/feed.py
index 352e540..e49e988 100644
--- a/lib/lp/services/feeds/feed.py
+++ b/lib/lp/services/feeds/feed.py
@@ -292,7 +292,7 @@ class FeedTypedData:
             # or they will try be served from http://feeds.launchpad.net,
             # which will not work.
             soup = BeautifulSoup(self._content)
-            a_tags = soup.findAll('a')
+            a_tags = soup.find_all('a')
             for a_tag in a_tags:
                 if a_tag['href'].startswith('/'):
                     a_tag['href'] = urljoin(self.root_url, a_tag['href'])
diff --git a/lib/lp/services/feeds/stories/xx-links.txt b/lib/lp/services/feeds/stories/xx-links.txt
index 54f928a..ae5adf4 100644
--- a/lib/lp/services/feeds/stories/xx-links.txt
+++ b/lib/lp/services/feeds/stories/xx-links.txt
@@ -14,7 +14,7 @@ displays the most recent announcements for all the projects.
     >>> from lp.services.beautifulsoup import BeautifulSoup
     >>> browser.open('http://launchpad.test/')
     >>> soup = BeautifulSoup(browser.contents)
-    >>> soup.head.findAll('link', type='application/atom+xml')
+    >>> soup.head.find_all('link', type='application/atom+xml')
     [<link href="http://feeds.launchpad.test/announcements.atom";
         rel="alternate" title="All Announcements"
         type="application/atom+xml"/>]
@@ -25,7 +25,7 @@ feed.
 
     >>> browser.open('http://launchpad.test/+announcements')
     >>> soup = BeautifulSoup(browser.contents)
-    >>> soup.head.findAll('link', type='application/atom+xml')
+    >>> soup.head.find_all('link', type='application/atom+xml')
     [<link href="http://feeds.launchpad.test/announcements.atom";
         rel="alternate" title="All Announcements"
         type="application/atom+xml"/>]
@@ -37,7 +37,7 @@ atom feed for that one bug.
 
     >>> browser.open('http://bugs.launchpad.test/firefox/+bug/1')
     >>> soup = BeautifulSoup(browser.contents)
-    >>> soup.head.findAll('link', type='application/atom+xml')
+    >>> soup.head.find_all('link', type='application/atom+xml')
     [<link href="http://feeds.launchpad.test/bugs/1/bug.atom";
         rel="alternate" title="Bug 1 Feed"
         type="application/atom+xml"/>]
@@ -59,7 +59,7 @@ But if the bug is private, there should be no link.
     http://bugs.launchpad.test/jokosher/+bug/14
 
     >>> soup = BeautifulSoup(auth_browser.contents)
-    >>> soup.head.findAll('link', type='application/atom+xml')
+    >>> soup.head.find_all('link', type='application/atom+xml')
     []
 
 Even so, if they somehow manage to hack the url or use inline ajax editing of
@@ -79,7 +79,7 @@ branches.
 
     >>> browser.open('http://launchpad.test/~stevea')
     >>> soup = BeautifulSoup(browser.contents)
-    >>> soup.head.findAll('link', type='application/atom+xml')
+    >>> soup.head.find_all('link', type='application/atom+xml')
     [<link href="http://feeds.launchpad.test/~stevea/latest-bugs.atom";
         rel="alternate" title="Latest Bugs for Steve Alexander"
         type="application/atom+xml"/>,
@@ -95,7 +95,7 @@ not the branches link.
 
     >>> browser.open('http://bugs.launchpad.test/~stevea')
     >>> soup = BeautifulSoup(browser.contents)
-    >>> soup.head.findAll('link', type='application/atom+xml')
+    >>> soup.head.find_all('link', type='application/atom+xml')
     [<link href="http://feeds.launchpad.test/~stevea/latest-bugs.atom";
         rel="alternate" title="Latest Bugs for Steve Alexander"
         type="application/atom+xml"/>]
@@ -111,7 +111,7 @@ main product page.
 
     >>> browser.open('http://launchpad.test/jokosher')
     >>> soup = BeautifulSoup(browser.contents)
-    >>> soup.head.findAll('link', type='application/atom+xml')
+    >>> soup.head.find_all('link', type='application/atom+xml')
     [<link href="http://feeds.launchpad.test/jokosher/announcements.atom";
         rel="alternate" title="Announcements for Jokosher"
         type="application/atom+xml"/>,
@@ -129,7 +129,7 @@ Only bug feeds should be linked to on bugs.launchpad.test.
 
     >>> browser.open('http://bugs.launchpad.test/jokosher')
     >>> soup = BeautifulSoup(browser.contents)
-    >>> soup.head.findAll('link', type='application/atom+xml')
+    >>> soup.head.find_all('link', type='application/atom+xml')
     [<link href="http://feeds.launchpad.test/jokosher/latest-bugs.atom";
         rel="alternate" title="Latest Bugs for Jokosher"
         type="application/atom+xml"/>]
@@ -159,7 +159,7 @@ it must have quotes and html escaped.
     >>> logout()
     >>> browser.open('http://launchpad.test/bad-displayname')
     >>> soup = BeautifulSoup(browser.contents)
-    >>> soup.head.findAll('link', type='application/atom+xml')
+    >>> soup.head.find_all('link', type='application/atom+xml')
     [<link href="http://feeds.launchpad.test/bad-displayname/announcements.atom";
         rel="alternate"
         title='Announcements for Bad displayname"&gt;&lt;script&gt;alert("h4x0r")&lt;/script&gt;'
@@ -187,7 +187,7 @@ on the main project group page.
 
     >>> browser.open('http://launchpad.test/gnome')
     >>> soup = BeautifulSoup(browser.contents)
-    >>> soup.head.findAll('link', type='application/atom+xml')
+    >>> soup.head.find_all('link', type='application/atom+xml')
     [<link href="http://feeds.launchpad.test/gnome/announcements.atom";
         rel="alternate" title="Announcements for GNOME"
         type="application/atom+xml"/>,
@@ -205,7 +205,7 @@ Only bug feeds should be linked to on bugs.launchpad.test.
 
     >>> browser.open('http://bugs.launchpad.test/gnome')
     >>> soup = BeautifulSoup(browser.contents)
-    >>> soup.head.findAll('link', type='application/atom+xml')
+    >>> soup.head.find_all('link', type='application/atom+xml')
     [<link href="http://feeds.launchpad.test/gnome/latest-bugs.atom";
         rel="alternate" title="Latest Bugs for GNOME"
         type="application/atom+xml"/>]
@@ -221,7 +221,7 @@ that the code does not display the atom feed link here inappropriately.
     ...     '&field.status=Confirmed&field.status=Triaged'
     ...     '&field.status=In+Progress&field.status=Fix+Committed')
     >>> soup = BeautifulSoup(browser.contents)
-    >>> soup.head.findAll('link', type='application/atom+xml')
+    >>> soup.head.find_all('link', type='application/atom+xml')
     []
 
 
@@ -234,7 +234,7 @@ An announcements feed link should also be shown on the main distro page.
 
     >>> browser.open('http://launchpad.test/ubuntu')
     >>> soup = BeautifulSoup(browser.contents)
-    >>> soup.head.findAll('link', type='application/atom+xml')
+    >>> soup.head.find_all('link', type='application/atom+xml')
     [<link href="http://feeds.launchpad.test/ubuntu/announcements.atom";
         rel="alternate" title="Announcements for Ubuntu"
         type="application/atom+xml"/>,
@@ -246,7 +246,7 @@ Only bug feeds should be linked to on bugs.launchpad.test.
 
     >>> browser.open('http://bugs.launchpad.test/ubuntu')
     >>> soup = BeautifulSoup(browser.contents)
-    >>> soup.head.findAll('link', type='application/atom+xml')
+    >>> soup.head.find_all('link', type='application/atom+xml')
     [<link href="http://feeds.launchpad.test/ubuntu/latest-bugs.atom";
         rel="alternate" title="Latest Bugs for Ubuntu"
         type="application/atom+xml"/>]
@@ -259,7 +259,7 @@ show a link to the atom feed for that distroseries' latest bugs.
 
     >>> browser.open('http://bugs.launchpad.test/ubuntu/hoary')
     >>> soup = BeautifulSoup(browser.contents)
-    >>> soup.head.findAll('link', type='application/atom+xml')
+    >>> soup.head.find_all('link', type='application/atom+xml')
     [<link
         href="http://feeds.launchpad.test/ubuntu/hoary/latest-bugs.atom";
         rel="alternate" title="Latest Bugs for Hoary"
@@ -273,7 +273,7 @@ show a link to the atom feed for that product series' latest bugs.
 
     >>> browser.open('http://bugs.launchpad.test/firefox/1.0')
     >>> soup = BeautifulSoup(browser.contents)
-    >>> soup.head.findAll('link', type='application/atom+xml')
+    >>> soup.head.find_all('link', type='application/atom+xml')
     [<link href="http://feeds.launchpad.test/firefox/1.0/latest-bugs.atom";
         rel="alternate" title="Latest Bugs for 1.0"
         type="application/atom+xml"/>]
@@ -286,7 +286,7 @@ show a link to the atom feed for that source package's latest bugs.
 
     >>> browser.open('http://bugs.launchpad.test/ubuntu/+source/cnews')
     >>> soup = BeautifulSoup(browser.contents)
-    >>> soup.head.findAll('link', type='application/atom+xml')
+    >>> soup.head.find_all('link', type='application/atom+xml')
     [<link
         href="http://feeds.launchpad.test/ubuntu/+source/cnews/latest-bugs.atom";
         rel="alternate" title="Latest Bugs for cnews in Ubuntu"
@@ -300,7 +300,7 @@ to the atom feed for that project group's latest branches.
 
     >>> browser.open('http://code.launchpad.test/mozilla')
     >>> soup = BeautifulSoup(browser.contents)
-    >>> soup.head.findAll('link', type='application/atom+xml')
+    >>> soup.head.find_all('link', type='application/atom+xml')
     [<link
         href="http://feeds.launchpad.test/mozilla/branches.atom";
         rel="alternate" title="Latest Branches for The Mozilla Project"
@@ -318,7 +318,7 @@ to the atom feed for that product's latest branches.
 
     >>> browser.open('http://code.launchpad.test/firefox')
     >>> soup = BeautifulSoup(browser.contents)
-    >>> soup.head.findAll('link', type='application/atom+xml')
+    >>> soup.head.find_all('link', type='application/atom+xml')
     [<link href="http://feeds.launchpad.test/firefox/branches.atom";
         rel="alternate" title="Latest Branches for Mozilla Firefox"
         type="application/atom+xml"/>,
@@ -335,7 +335,7 @@ to the atom feed for that person's latest branches.
 
     >>> browser.open('http://code.launchpad.test/~mark')
     >>> soup = BeautifulSoup(browser.contents)
-    >>> soup.head.findAll('link', type='application/atom+xml')
+    >>> soup.head.find_all('link', type='application/atom+xml')
     [<link href="http://feeds.launchpad.test/~mark/branches.atom";
         rel="alternate" title="Latest Branches for Mark Shuttleworth"
         type="application/atom+xml"/>,
@@ -352,7 +352,7 @@ atom feed for that branch's revisions.
     >>> url = 'http://code.launchpad.test/~mark/firefox/release--0.9.1'
     >>> browser.open(url)
     >>> soup = BeautifulSoup(browser.contents)
-    >>> soup.head.findAll('link', type='application/atom+xml')
+    >>> soup.head.find_all('link', type='application/atom+xml')
     [<link
         href="http://feeds.launchpad.test/~mark/firefox/release--0.9.1/branch.atom";
         rel="alternate"
@@ -368,7 +368,7 @@ But if the branch is private, there should be no link.
     >>> auth_browser.open(
     ... 'https://code.launchpad.test/~name12/landscape/feature-x')
     >>> soup = BeautifulSoup(auth_browser.contents)
-    >>> soup.head.findAll('link', type='application/atom+xml')
+    >>> soup.head.find_all('link', type='application/atom+xml')
     []
 
 Even so, if they somehow manage to hack the url, they are redirected to a page
diff --git a/lib/lp/services/oauth/doc/oauth-pages.txt b/lib/lp/services/oauth/doc/oauth-pages.txt
index 5b9cd98..c2ed07f 100644
--- a/lib/lp/services/oauth/doc/oauth-pages.txt
+++ b/lib/lp/services/oauth/doc/oauth-pages.txt
@@ -32,7 +32,7 @@ consumer's request to access Launchpad on their behalf.
     >>> def print_hidden_fields(html):
     ...     soup = BeautifulSoup(
     ...         html, parse_only=SoupStrainer(attrs={'type': 'hidden'}))
-    ...     for tag in soup.findAll(attrs={'type': 'hidden'}):
+    ...     for tag in soup.find_all(attrs={'type': 'hidden'}):
     ...         if tag['value']:
     ...             print(tag['name'], tag['value'])
 
diff --git a/lib/lp/services/oauth/stories/authorize-token.txt b/lib/lp/services/oauth/stories/authorize-token.txt
index 23f4911..a734ee1 100644
--- a/lib/lp/services/oauth/stories/authorize-token.txt
+++ b/lib/lp/services/oauth/stories/authorize-token.txt
@@ -59,7 +59,7 @@ This page contains one submit button for each item of OAuthPermission,
 except for 'Desktop Integration', which must be specifically requested.
 
     >>> def print_access_levels(main_content):
-    ...     actions = main_content.findAll('input', attrs={'type': 'submit'})
+    ...     actions = main_content.find_all('input', attrs={'type': 'submit'})
     ...     for action in actions:
     ...         print(action['value'])
 
@@ -71,7 +71,7 @@ except for 'Desktop Integration', which must be specifically requested.
     Change Anything
 
     >>> from lp.services.webapp.interfaces import OAuthPermission
-    >>> actions = main_content.findAll('input', attrs={'type': 'submit'})
+    >>> actions = main_content.find_all('input', attrs={'type': 'submit'})
     >>> len(actions) == len(OAuthPermission.items) - 1
     True
 
diff --git a/lib/lp/services/oauth/stories/managing-tokens.txt b/lib/lp/services/oauth/stories/managing-tokens.txt
index a75ad04..b90a301 100644
--- a/lib/lp/services/oauth/stories/managing-tokens.txt
+++ b/lib/lp/services/oauth/stories/managing-tokens.txt
@@ -49,31 +49,31 @@ keys stored in hidden <input>s as well as the button to revoke the
 authorization.
 
     >>> li = find_tag_by_id(main_content, 'tokens').find('li')
-    >>> for input in li.find('form').findAll('input'):
+    >>> for input in li.find('form').find_all('input'):
     ...     print(input['name'], input['value'])
     consumer_key System-wide: Ubuntu (mycomputer)
     token_key ...
     token_type access_token
     revoke Revoke Authorization
 
-    >>> li2 = li.findNextSibling('li')
-    >>> for input in li2.find('form').findAll('input'):
+    >>> li2 = li.find_next_sibling('li')
+    >>> for input in li2.find('form').find_all('input'):
     ...     print(input['name'], input['value'])
     consumer_key foobar123451432
     token_key salgado-read-nonprivate
     token_type access_token
     revoke Revoke Authorization
 
-    >>> li3 = li2.findNext('li')
-    >>> for input in li3.find('form').findAll('input'):
+    >>> li3 = li2.find_next('li')
+    >>> for input in li3.find('form').find_all('input'):
     ...     print(input['name'], input['value'])
     consumer_key launchpad-library
     token_key salgado-change-anything
     token_type access_token
     revoke Revoke Authorization
 
-    >>> li4 = li3.findNext('li')
-    >>> for input in li4.find('form').findAll('input'):
+    >>> li4 = li3.find_next('li')
+    >>> for input in li4.find('form').find_all('input'):
     ...     print(input['name'], input['value'])
     consumer_key oauthconsumerkey...
     token_key ...
diff --git a/lib/lp/services/webapp/tests/test_login.py b/lib/lp/services/webapp/tests/test_login.py
index 732257e..ccb7bc7 100644
--- a/lib/lp/services/webapp/tests/test_login.py
+++ b/lib/lp/services/webapp/tests/test_login.py
@@ -521,7 +521,7 @@ class TestOpenIDCallbackView(TestCaseWithFactory):
         discharge_form = find_tag_by_id(html, 'discharge-form')
         self.assertEqual(form['starting_url'], discharge_form['action'])
         self.assertThat(
-            [dict(tag.attrs) for tag in discharge_form.findAll('input')],
+            [dict(tag.attrs) for tag in discharge_form.find_all('input')],
             MatchesListwise([
                 ContainsDict({
                     'type': Equals('hidden'),
diff --git a/lib/lp/snappy/browser/widgets/tests/test_snaparchivewidget.py b/lib/lp/snappy/browser/widgets/tests/test_snaparchivewidget.py
index 20bd018..84a601d 100644
--- a/lib/lp/snappy/browser/widgets/tests/test_snaparchivewidget.py
+++ b/lib/lp/snappy/browser/widgets/tests/test_snaparchivewidget.py
@@ -269,7 +269,7 @@ class TestSnapArchiveWidget(WithScenarios, TestCaseWithFactory):
         self.assertIn("primary", self.widget.options)
         self.assertIn("ppa", self.widget.options)
         soup = BeautifulSoup(markup)
-        fields = soup.findAll(["input", "select"], {"id": re.compile(".*")})
+        fields = soup.find_all(["input", "select"], {"id": re.compile(".*")})
         expected_ids = [
             "field.archive.option.primary",
             "field.archive.option.ppa",
diff --git a/lib/lp/snappy/browser/widgets/tests/test_snapbuildchannelswidget.py b/lib/lp/snappy/browser/widgets/tests/test_snapbuildchannelswidget.py
index b9231d7..201ad85 100644
--- a/lib/lp/snappy/browser/widgets/tests/test_snapbuildchannelswidget.py
+++ b/lib/lp/snappy/browser/widgets/tests/test_snapbuildchannelswidget.py
@@ -181,7 +181,7 @@ class TestSnapBuildChannelsWidget(TestCaseWithFactory):
         self.assertIsNotNone(self.widget.core20_widget)
         self.assertIsNotNone(self.widget.snapcraft_widget)
         soup = BeautifulSoup(markup)
-        fields = soup.findAll(["input"], {"id": re.compile(".*")})
+        fields = soup.find_all(["input"], {"id": re.compile(".*")})
         expected_ids = [
             "field.auto_build_channels.core",
             "field.auto_build_channels.core18",
diff --git a/lib/lp/snappy/browser/widgets/tests/test_storechannelswidget.py b/lib/lp/snappy/browser/widgets/tests/test_storechannelswidget.py
index 32635d4..f6e85fa 100644
--- a/lib/lp/snappy/browser/widgets/tests/test_storechannelswidget.py
+++ b/lib/lp/snappy/browser/widgets/tests/test_storechannelswidget.py
@@ -282,7 +282,7 @@ class TestStoreChannelsWidget(TestCaseWithFactory):
         self.assertIsNotNone(self.widget.track_widget)
         self.assertIsNotNone(self.widget.risks_widget)
         soup = BeautifulSoup(markup)
-        fields = soup.findAll(["input"], {"id": re.compile(".*")})
+        fields = soup.find_all(["input"], {"id": re.compile(".*")})
         expected_ids = [
             "field.channels.risks.%d" % i for i in range(len(self.risks))]
         expected_ids.append("field.channels.track")
diff --git a/lib/lp/soyuz/browser/tests/test_archive_packages.py b/lib/lp/soyuz/browser/tests/test_archive_packages.py
index 3bbff33..fb0c062 100644
--- a/lib/lp/soyuz/browser/tests/test_archive_packages.py
+++ b/lib/lp/soyuz/browser/tests/test_archive_packages.py
@@ -458,7 +458,7 @@ class TestPPAPackagesJobNotifications(TestCaseWithFactory):
             )
         self.assertThat(html, packages_matches)
         self.assertEqual(
-            [], BeautifulSoup(html).findAll(
+            [], BeautifulSoup(html).find_all(
                 'span', text=re.compile('Showing 5 of .')))
 
     def test_job_notifications_display_multiple_is_capped(self):
@@ -468,7 +468,7 @@ class TestPPAPackagesJobNotifications(TestCaseWithFactory):
                 self.archive, "+packages", principal=self.archive.owner)
             soup = BeautifulSoup(view.render())
         self.assertEqual([],
-            soup.findAll(
+            soup.find_all(
                 'div', attrs={'class': 'pending-job', 'job_id': jobs[-1].id}))
         showing_tags = soup.find_all(
             'span', text=re.compile('Showing 5 of .'))
diff --git a/lib/lp/soyuz/stories/ppa/xx-ppa-packages.txt b/lib/lp/soyuz/stories/ppa/xx-ppa-packages.txt
index 296b971..21cd1a5 100644
--- a/lib/lp/soyuz/stories/ppa/xx-ppa-packages.txt
+++ b/lib/lp/soyuz/stories/ppa/xx-ppa-packages.txt
@@ -88,7 +88,7 @@ The detailed Packages list
     >>> def print_archive_package_rows(contents):
     ...     package_table = find_tag_by_id(
     ...         contents, 'packages_list')
-    ...     for ppa_row in package_table.findChildren('tr'):
+    ...     for ppa_row in package_table.find_all('tr'):
     ...         print(extract_text(ppa_row))
 
     >>> print_archive_package_rows(anon_browser.contents)
@@ -418,10 +418,10 @@ Anyone can see the build status for package in Celso's PPA.
 
     >>> def print_build_status(contents):
     ...     rows = find_tags_by_class(contents, 'archive_package_row')
-    ...     headers = rows[0].findAll('th')
+    ...     headers = rows[0].find_all('th')
     ...     print(extract_text(headers[0]), extract_text(headers[-1]))
     ...     for row in rows[1:]:
-    ...         columns = row.findAll('td')
+    ...         columns = row.find_all('td')
     ...         name = extract_text(columns[0])
     ...         built_icon = columns[-1].img['src']
     ...         built_text = columns[-1].a
diff --git a/lib/lp/soyuz/stories/ppa/xx-ubuntu-ppas.txt b/lib/lp/soyuz/stories/ppa/xx-ubuntu-ppas.txt
index 7da7244..cb57997 100644
--- a/lib/lp/soyuz/stories/ppa/xx-ubuntu-ppas.txt
+++ b/lib/lp/soyuz/stories/ppa/xx-ubuntu-ppas.txt
@@ -333,7 +333,7 @@ The source packages list is presented publicly.
     >>> def print_archive_package_rows(contents):
     ...     package_table = find_tag_by_id(
     ...         anon_browser.contents, 'packages_list')
-    ...     for ppa_row in package_table.findChildren('tr'):
+    ...     for ppa_row in package_table.find_all('tr'):
     ...         print(extract_text(ppa_row))
 
     >>> print_archive_package_rows(anon_browser.contents)
diff --git a/lib/lp/soyuz/stories/soyuz/xx-builds-pages.txt b/lib/lp/soyuz/stories/soyuz/xx-builds-pages.txt
index 7a0c950..beb1430 100644
--- a/lib/lp/soyuz/stories/soyuz/xx-builds-pages.txt
+++ b/lib/lp/soyuz/stories/soyuz/xx-builds-pages.txt
@@ -103,7 +103,7 @@ For SourcePackage, it's only possible to filter by state.
 The source package default state is "all states":
 
     >>> soup = find_main_content(anon_browser.contents)
-    >>> [results] = soup.findAll(attrs={'selected': 'selected'})
+    >>> [results] = soup.find_all(attrs={'selected': 'selected'})
     >>> print(extract_text(results))
     All states
 
diff --git a/lib/lp/soyuz/stories/soyuz/xx-distroarchseries-binpackages.txt b/lib/lp/soyuz/stories/soyuz/xx-distroarchseries-binpackages.txt
index c16c379..ab9dc39 100644
--- a/lib/lp/soyuz/stories/soyuz/xx-distroarchseries-binpackages.txt
+++ b/lib/lp/soyuz/stories/soyuz/xx-distroarchseries-binpackages.txt
@@ -31,7 +31,7 @@ this architecture:
     Published on 2006-04-11
     2005-06-18 00:00:00 UTC Published...Warty i386 release main     base     Extra                      0.9
     Published on 2005-06-18
-    >>> print(table.findAll("tr")[2].td["colspan"])
+    >>> print(table.find_all("tr")[2].td["colspan"])
     10
 
 It also provides a link to the currently published version:
diff --git a/lib/lp/soyuz/stories/soyuz/xx-distroseries-sources.txt b/lib/lp/soyuz/stories/soyuz/xx-distroseries-sources.txt
index 0769ed2..05f49cf 100644
--- a/lib/lp/soyuz/stories/soyuz/xx-distroseries-sources.txt
+++ b/lib/lp/soyuz/stories/soyuz/xx-distroseries-sources.txt
@@ -49,7 +49,7 @@ about Firefox's publishing history.
   2006-02-13 12:19:00 UTC   Published  Warty    release  main        web       0.9
   Published on 2006-02-13
   2004-09-27 11:57:13 UTC   Pending    Warty   release   main        editors   0.9
-  >>> print(table.findAll("tr")[2].td["colspan"])
+  >>> print(table.find_all("tr")[2].td["colspan"])
   8
 
 Jump back to the DistributionSourcePackage page to continue the tests:
diff --git a/lib/lp/soyuz/stories/soyuz/xx-packagepublishinghistory.txt b/lib/lp/soyuz/stories/soyuz/xx-packagepublishinghistory.txt
index 6fdba84..dd60f1c 100644
--- a/lib/lp/soyuz/stories/soyuz/xx-packagepublishinghistory.txt
+++ b/lib/lp/soyuz/stories/soyuz/xx-packagepublishinghistory.txt
@@ -25,7 +25,7 @@ shows the complete history of a package in all series.
     ... UTC Published Breezy ... release  main      base    666
     Created ... ago by Foo Bar
     Published ... ago
-    >>> print(table.findAll("tr")[2].td["colspan"])
+    >>> print(table.find_all("tr")[2].td["colspan"])
     8
 
 Copy the package to a new distribution named "foo-distro". The publishing
diff --git a/lib/lp/soyuz/stories/soyuz/xx-person-packages.txt b/lib/lp/soyuz/stories/soyuz/xx-person-packages.txt
index 91de3c5..d4e2382 100644
--- a/lib/lp/soyuz/stories/soyuz/xx-person-packages.txt
+++ b/lib/lp/soyuz/stories/soyuz/xx-person-packages.txt
@@ -417,7 +417,7 @@ Please note also that disabled archives are not viewable by anonymous users.
     >>> def print_archive_package_rows(contents):
     ...     package_table = find_tag_by_id(
     ...         anon_browser.contents, 'packages_list')
-    ...     for ppa_row in package_table.findChildren('tr'):
+    ...     for ppa_row in package_table.find_all('tr'):
     ...         print(extract_text(ppa_row))
 
     >>> anon_browser.open("http://launchpad.test/~cprov/+archive/ppa";)
diff --git a/lib/lp/soyuz/stories/soyuz/xx-queue-pages.txt b/lib/lp/soyuz/stories/soyuz/xx-queue-pages.txt
index 3a93c4d..00d8cca 100644
--- a/lib/lp/soyuz/stories/soyuz/xx-queue-pages.txt
+++ b/lib/lp/soyuz/stories/soyuz/xx-queue-pages.txt
@@ -217,7 +217,7 @@ The 'filelist' is expanded as one or more table rows, right below the
 clicked item:
 
     >>> filelist_body = first_tag_by_class(anon_browser.contents, 'queue-4')
-    >>> filelist = filelist_body.findAll('tr')
+    >>> filelist = filelist_body.find_all('tr')
 
 It contains a list of files related to the queue item clicked, followed
 by its size, one file per line. Expired files have no size.
diff --git a/lib/lp/testing/matchers.py b/lib/lp/testing/matchers.py
index 5b14a38..1f36696 100644
--- a/lib/lp/testing/matchers.py
+++ b/lib/lp/testing/matchers.py
@@ -432,7 +432,7 @@ class MatchesTagText(Matcher):
     def match(self, matchee):
         # Here to avoid circular dependancies.
         from lp.testing.pages import extract_text
-        widgets = self.soup_content.findAll(id=self.tag_id)
+        widgets = self.soup_content.find_all(id=self.tag_id)
         if len(widgets) == 0:
             return MissingElement(self.tag_id, self.soup_content)
         elif len(widgets) > 1:
@@ -456,13 +456,13 @@ class MatchesPickerText(Matcher):
     def match(self, matchee):
         # Here to avoid circular dependancies.
         from lp.testing.pages import extract_text
-        widgets = self.soup_content.findAll(id=self.widget_id)
+        widgets = self.soup_content.find_all(id=self.widget_id)
         if len(widgets) == 0:
             return MissingElement(self.widget_id, self.soup_content)
         elif len(widgets) > 1:
             return MultipleElements(self.widget_id, self.soup_content)
         widget = widgets[0]
-        text = widget.findAll(attrs={'class': 'yui3-activator-data-box'})[0]
+        text = widget.find_all(attrs={'class': 'yui3-activator-data-box'})[0]
         text_matcher = DocTestMatches(extract_text(text))
         return text_matcher.match(matchee)
 
diff --git a/lib/lp/testing/pages.py b/lib/lp/testing/pages.py
index 8e54faa..35a6f10 100644
--- a/lib/lp/testing/pages.py
+++ b/lib/lp/testing/pages.py
@@ -229,7 +229,7 @@ def find_tags_by_class(content, class_, only_first=False):
     if only_first:
         find = BeautifulSoup.find
     else:
-        find = BeautifulSoup.findAll
+        find = BeautifulSoup.find_all
     return find(soup, attrs={'class': class_matcher})
 
 
@@ -289,11 +289,11 @@ def print_table(content, columns=None, skip_rows=None, sep="\t"):
                      None no rows are skipped.
     :param sep       the separator to be used between output items.
     """
-    for row_num, row in enumerate(content.findAll('tr')):
+    for row_num, row in enumerate(content.find_all('tr')):
         if skip_rows is not None and row_num in skip_rows:
             continue
         row_content = []
-        for col_num, item in enumerate(row.findAll('td')):
+        for col_num, item in enumerate(row.find_all('td')):
             if columns is None or col_num in columns:
                 row_content.append(extract_text(item))
         if len(row_content) > 0:
@@ -306,7 +306,7 @@ def get_radio_button_text_for_field(soup, name):
     The resulting output will look something like:
     ['(*) A checked option', '( ) An unchecked option']
     """
-    buttons = soup.findAll(
+    buttons = soup.find_all(
         'input', {'name': 'field.%s' % name})
     for button in buttons:
         if button.parent.name == 'label':
@@ -444,7 +444,7 @@ def parse_relationship_section(content):
     if section is None:
         print('EMPTY SECTION')
         return
-    for li in section.findAll('li'):
+    for li in section.find_all('li'):
         if li.a:
             link = li.a
             content = whitespace_re.sub(' ', link.string.strip())
@@ -461,7 +461,7 @@ def print_action_links(content):
     if actions is None:
         print("No actions portlet")
         return
-    entries = actions.findAll('li')
+    entries = actions.find_all('li')
     for entry in entries:
         if entry.a:
             print('%s: %s' % (entry.a.string, entry.a['href']))
@@ -478,7 +478,7 @@ def print_navigation_links(content):
     title = navigation_links.find('label')
     if title is not None:
         print('= %s =' % title.string)
-    entries = navigation_links.findAll(['strong', 'a'])
+    entries = navigation_links.find_all(['strong', 'a'])
     for entry in entries:
         try:
             print('%s: %s' % (entry.span.string, entry['href']))
@@ -506,7 +506,7 @@ def print_portlet_links(content, name, base=None):
     if portlet_contents is None:
         print("No portlet found with name:", name)
         return
-    portlet_links = portlet_contents.findAll('a')
+    portlet_links = portlet_contents.find_all('a')
     if len(portlet_links) == 0:
         print("No links were found in the portlet.")
         return
@@ -520,7 +520,7 @@ def print_submit_buttons(content):
 
     Use this to check that the buttons on a page match your expectations.
     """
-    buttons = find_main_content(content).findAll(
+    buttons = find_main_content(content).find_all(
         'input', attrs={'class': 'button', 'type': 'submit'})
     if buttons is None:
         print("No buttons found")
@@ -571,9 +571,9 @@ def print_location(contents):
     The main heading is the first <h1> element in the page.
     """
     doc = find_tag_by_id(contents, 'document')
-    heading = doc.find(attrs={'id': 'watermark-heading'}).findAll('a')
+    heading = doc.find(attrs={'id': 'watermark-heading'}).find_all('a')
     container = doc.find(attrs={'class': 'breadcrumbs'})
-    hierarchy = container.findAll(recursive=False) if container else []
+    hierarchy = container.find_all(recursive=False) if container else []
     segments = [extract_text(step) for step in chain(heading, hierarchy)]
 
     if len(segments) == 0:
@@ -598,9 +598,9 @@ def print_location_apps(contents):
     if location_apps is None:
         location_apps = first_tag_by_class(contents, 'watermark-apps-portlet')
         if location_apps is not None:
-            location_apps = location_apps.ul.findAll('li')
+            location_apps = location_apps.ul.find_all('li')
     else:
-        location_apps = location_apps.findAll('span')
+        location_apps = location_apps.find_all('span')
     if location_apps is None:
         print("(Application tabs omitted)")
     elif len(location_apps) == 0:
diff --git a/lib/lp/translations/stories/distribution/xx-distribution-translations.txt b/lib/lp/translations/stories/distribution/xx-distribution-translations.txt
index 5eb3ffa..01d5dde 100644
--- a/lib/lp/translations/stories/distribution/xx-distribution-translations.txt
+++ b/lib/lp/translations/stories/distribution/xx-distribution-translations.txt
@@ -58,7 +58,7 @@ to the right translation focus.
 And the other Ubuntu distributions should be there too.
 
     >>> content = find_main_content(browser.contents)
-    >>> print(extract_text(content.findAll('h2')[1]))
+    >>> print(extract_text(content.find_all('h2')[1]))
     Other versions of Ubuntu
 
     >>> print(extract_text(content.find(id='distroseries-list')))
@@ -112,7 +112,7 @@ languages pointing to the latest release, Hoary.
 And the other Ubuntu distributions should be there too.
 
     >>> content = find_main_content(browser.contents)
-    >>> print(extract_text(content.findAll('h2')[1]))
+    >>> print(extract_text(content.find_all('h2')[1]))
     Other versions of Debian
 
     >>> print(extract_text(content.find(id='distroseries-list')))
diff --git a/lib/lp/translations/stories/distroseries/xx-distroseries-translations.txt b/lib/lp/translations/stories/distroseries/xx-distroseries-translations.txt
index bb3e509..f56e903 100644
--- a/lib/lp/translations/stories/distroseries/xx-distroseries-translations.txt
+++ b/lib/lp/translations/stories/distroseries/xx-distroseries-translations.txt
@@ -90,7 +90,7 @@ the system tells them that they're not allowed to see those pages.
     ...
     urllib.error.HTTPError: HTTP Error 503: Service Unavailable
     >>> main_content = find_main_content(user_browser.contents)
-    >>> print(main_content.findNext('p').decode_contents())
+    >>> print(main_content.find_next('p').decode_contents())
     Translations for this release series are not available yet.
 
     >>> user_browser.handleErrors = False
diff --git a/lib/lp/translations/stories/importqueue/xx-translation-import-queue-filtering.txt b/lib/lp/translations/stories/importqueue/xx-translation-import-queue-filtering.txt
index 2f20ce5..63c2dc1 100644
--- a/lib/lp/translations/stories/importqueue/xx-translation-import-queue-filtering.txt
+++ b/lib/lp/translations/stories/importqueue/xx-translation-import-queue-filtering.txt
@@ -311,7 +311,7 @@ The only entry that shows up now is the one Carlos just uploaded.
 
     >>> import_list = find_tag_by_id(
     ...     user_browser.contents, 'import-entries-list')
-    >>> first_entry = import_list.findNext('tr')
+    >>> first_entry = import_list.find_next('tr')
     >>> print(represent_queue_entry(first_entry))
     foo.pot in evolution in Ubuntu Hoary
     Needs Review
@@ -332,7 +332,7 @@ Ubuntu.
     ...     output = []
     ...     import_list = find_tag_by_id(
     ...         browser.contents, 'import-entries-list') 
-    ...     for entry in import_list.findAll('tr'):
+    ...     for entry in import_list.find_all('tr'):
     ...         output.append(represent_queue_entry(entry))
     ...     return '\n'.join(output)
 
diff --git a/lib/lp/translations/stories/importqueue/xx-translation-import-queue-targets.txt b/lib/lp/translations/stories/importqueue/xx-translation-import-queue-targets.txt
index 6efe24a..6f1a12d 100644
--- a/lib/lp/translations/stories/importqueue/xx-translation-import-queue-targets.txt
+++ b/lib/lp/translations/stories/importqueue/xx-translation-import-queue-targets.txt
@@ -66,13 +66,13 @@ There are two entries in the list.
 
     >>> import_list = find_tag_by_id(
     ...     user_browser.contents, 'import-entries-list')
-    >>> first_entry = import_list.findNext('tr')
+    >>> first_entry = import_list.find_next('tr')
     >>> print(extract_text(first_entry))
     po/evolution-2.2-test.pot in
     Evolution trunk series
     Imported
-    >>> second_entry = first_entry.findNext(
-    ...     'tr').findNext('tr').findNext('tr')
+    >>> second_entry = first_entry.find_next(
+    ...     'tr').find_next('tr').find_next('tr')
     >>> print(extract_text(second_entry))
     po/pt_BR.po in
     Evolution trunk series
@@ -100,13 +100,13 @@ Evolution product are for this trunk series.
 
     >>> import_list = find_tag_by_id(
     ...     user_browser.contents, 'import-entries-list')
-    >>> first_entry = import_list.findNext('tr')
+    >>> first_entry = import_list.find_next('tr')
     >>> print(extract_text(first_entry))
     po/evolution-2.2-test.pot in
     Evolution trunk series
     Imported
-    >>> second_entry = first_entry.findNext(
-    ...     'tr').findNext('tr').findNext('tr')
+    >>> second_entry = first_entry.find_next(
+    ...     'tr').find_next('tr').find_next('tr')
     >>> print(extract_text(second_entry))
     po/pt_BR.po in
     Evolution trunk series
@@ -134,23 +134,22 @@ it here too.
 
     >>> import_list = find_tag_by_id(
     ...     user_browser.contents, 'import-entries-list')
-    >>> first_entry = import_list.findNext('tr')
+    >>> first_entry = import_list.find_next('tr')
     >>> print(extract_text(first_entry))
     po/evolution-2.2-test.pot in
     Evolution trunk series
     Imported
-    >>> first_entry_importer = first_entry.findNext('tr')
+    >>> first_entry_importer = first_entry.find_next('tr')
     >>> print(extract_text(first_entry_importer))
     Uploaded by
     Foo Bar
     on 2006-12-13 22:17:56 CET
-    >>> second_entry = first_entry_importer.findNext(
-    ...     'tr').findNext('tr')
+    >>> second_entry = first_entry_importer.find_next('tr').find_next('tr')
     >>> print(extract_text(second_entry))
     po/pt_BR.po in
     Evolution trunk series
     Imported
-    >>> second_entry_importer = second_entry.findNext('tr')
+    >>> second_entry_importer = second_entry.find_next('tr')
     >>> print(extract_text(second_entry_importer))
     Uploaded by
     Foo Bar
diff --git a/lib/lp/translations/stories/productseries/xx-productseries-translations.txt b/lib/lp/translations/stories/productseries/xx-productseries-translations.txt
index c649f0b..b2b562c 100644
--- a/lib/lp/translations/stories/productseries/xx-productseries-translations.txt
+++ b/lib/lp/translations/stories/productseries/xx-productseries-translations.txt
@@ -33,7 +33,7 @@ Listing languages with translations is best checked with a helper method.
     ...     print("%-25s %13s %13s" % (
     ...         "Language", "Untranslated", "Unreviewed"))
     ...     for row in language_rows:
-    ...         cols = row.findAll('td')
+    ...         cols = row.find_all('td')
     ...         language = extract_text(cols[0])
     ...         untranslated = extract_link_info(cols[2])
     ...         unreviewed = extract_link_info(cols[3])
@@ -50,7 +50,7 @@ being set up for translation.
     Series trunk : Translations : Frobnicator
 
     >>> main_content = find_main_content(anon_browser.contents)
-    >>> print(extract_text(main_content.findAll('h1')[0]))
+    >>> print(extract_text(main_content.find_all('h1')[0]))
     Translation status by language
 
     >>> print_language_stats(anon_browser)
@@ -59,7 +59,7 @@ being set up for translation.
 Explanation is shown to indicate that there are no translations for
 this series.
 
-    >>> print(extract_text(main_content.findAll('p')[0]))
+    >>> print(extract_text(main_content.find_all('p')[0]))
     There are no translations for this release series.
 
 Administrator will also see instructions on how to set up a project for
@@ -67,8 +67,8 @@ translation.
 
     >>> admin_browser.open(frobnicator_trunk_url)
     >>> main_content = find_main_content(admin_browser.contents)
-    >>> paragraphs = main_content.findAll('p')
-    >>> print(extract_text(main_content.findAll('p')[1]))
+    >>> paragraphs = main_content.find_all('p')
+    >>> print(extract_text(main_content.find_all('p')[1]))
     To start translating your project...
 
 With one translatable template (with fake stats for 10 messages), a listing
diff --git a/lib/lp/translations/stories/standalone/xx-licensing.txt b/lib/lp/translations/stories/standalone/xx-licensing.txt
index 669f49c..5f2ef92 100644
--- a/lib/lp/translations/stories/standalone/xx-licensing.txt
+++ b/lib/lp/translations/stories/standalone/xx-licensing.txt
@@ -49,10 +49,10 @@ nor input boxes available.
 
     >>> main_content = find_tag_by_id(
     ...     browser.contents, 'messages_to_translate')
-    >>> for textarea in main_content.findAll('textarea'):
+    >>> for textarea in main_content.find_all('textarea'):
     ...     print('Found textarea:\n%s' % textarea)
 
-    >>> for input in main_content.findAll('input'):
+    >>> for input in main_content.find_all('input'):
     ...     print('Found input:\n%s' % input)
 
 Karl changes his mind. He returns to the licensing page.
diff --git a/lib/lp/translations/stories/standalone/xx-person-activity.txt b/lib/lp/translations/stories/standalone/xx-person-activity.txt
index 3e8640e..7b34e96 100644
--- a/lib/lp/translations/stories/standalone/xx-person-activity.txt
+++ b/lib/lp/translations/stories/standalone/xx-person-activity.txt
@@ -21,7 +21,7 @@ listed which Carlos has contributed to.
 
     # Prints a heading and formatted list of POFiles and latest submissions.
     >>> def print_activity_list(listing):
-    ...     for row in listing.findAll('tr'):
+    ...     for row in listing.find_all('tr'):
     ...         print(extract_text(row))
 
     >>> listing = find_tag_by_id(anon_browser.contents, 'activity-table')
diff --git a/lib/lp/translations/stories/standalone/xx-pofile-details.txt b/lib/lp/translations/stories/standalone/xx-pofile-details.txt
index 8efea71..a54e2a7 100644
--- a/lib/lp/translations/stories/standalone/xx-pofile-details.txt
+++ b/lib/lp/translations/stories/standalone/xx-pofile-details.txt
@@ -52,9 +52,9 @@ a separate method to pretty-print all the translations.
     ...         return ' '.join(contents)
     ...
     ...     table = find_tags_by_class(browser.contents, 'listing')[0]
-    ...     rows = table.findAll('tr')
+    ...     rows = table.find_all('tr')
     ...     for row in rows:
-    ...         cells = row.findAll('td')
+    ...         cells = row.find_all('td')
     ...         type = get_first_defined_class(cells)
     ...         types = { 'englishstring' : 'english',
     ...                   'usedtranslation' : ' used',
diff --git a/lib/lp/translations/stories/standalone/xx-pofile-export.txt b/lib/lp/translations/stories/standalone/xx-pofile-export.txt
index 364c90d..30c77c3 100644
--- a/lib/lp/translations/stories/standalone/xx-pofile-export.txt
+++ b/lib/lp/translations/stories/standalone/xx-pofile-export.txt
@@ -87,7 +87,7 @@ evolution yet; it will be created at the moment the export is requested.
     ...     browser.contents, 'Translation file details')
     >>> carlos = u'Carlos Perell\xf3 Mar\xedn'
     >>> creator = extract_text(
-    ...     translation_portlet.find(text='Creator:').findNext('a'))
+    ...     translation_portlet.find(text='Creator:').find_next('a'))
     >>> carlos in creator
     True
 
diff --git a/lib/lp/translations/stories/standalone/xx-pofile-translate-message-filtering.txt b/lib/lp/translations/stories/standalone/xx-pofile-translate-message-filtering.txt
index f4ce694..35d5b00 100644
--- a/lib/lp/translations/stories/standalone/xx-pofile-translate-message-filtering.txt
+++ b/lib/lp/translations/stories/standalone/xx-pofile-translate-message-filtering.txt
@@ -24,7 +24,7 @@ messages in the batches of 10 items.
     ...
     ...     if soup is None:
     ...         soup = find_main_content(browser.contents)
-    ...     for tag in soup.findAll('label'):
+    ...     for tag in soup.find_all('label'):
     ...         id = tag.get('id')
     ...         if id is not None:
     ...             match = re.match(match_translation_id, id)
diff --git a/lib/lp/translations/stories/standalone/xx-pofile-translate-newlines-check.txt b/lib/lp/translations/stories/standalone/xx-pofile-translate-newlines-check.txt
index d0f8190..4ddbbe3 100644
--- a/lib/lp/translations/stories/standalone/xx-pofile-translate-newlines-check.txt
+++ b/lib/lp/translations/stories/standalone/xx-pofile-translate-newlines-check.txt
@@ -11,7 +11,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=299009
     >>> def print_tags(browser, tags):
     ...     """Find and print [tags] in browser.contents. End each with '--'."""
     ...     soup = find_main_content(browser.contents)
-    ...     for tag in soup.findAll(attrs={'id': tags}):
+    ...     for tag in soup.find_all(attrs={'id': tags}):
     ...         print(u"%s\n--\n" % tag.decode_contents())
 
     >>> browser = setupBrowser(auth='Basic carlos@xxxxxxxxxxxxx:test')
diff --git a/lib/lp/translations/stories/standalone/xx-pofile-translate.txt b/lib/lp/translations/stories/standalone/xx-pofile-translate.txt
index 2d8e3a9..21f81b8 100644
--- a/lib/lp/translations/stories/standalone/xx-pofile-translate.txt
+++ b/lib/lp/translations/stories/standalone/xx-pofile-translate.txt
@@ -38,12 +38,12 @@ The page is rendered in read-only mode, without any textareas for input.
 
     >>> main_content = find_tag_by_id(
     ...     browser.contents, 'messages_to_translate')
-    >>> for textarea in main_content.findAll('textarea'):
+    >>> for textarea in main_content.find_all('textarea'):
     ...     print('Found textarea:\n%s' % textarea)
 
 In fact, no input widgets at all are displayed.
 
-    >>> for input in main_content.findAll('input'):
+    >>> for input in main_content.find_all('input'):
     ...     print('Found input:\n%s' % input)
 
 As an anynoymous user you will have access to the download and details
@@ -139,7 +139,7 @@ they must adhere religiously to an agreed-to format.
     ...     content = find_main_content(browser.contents)
     ...     ids = [
     ...         tag.get(attribute)
-    ...         for tag in content.findAll()
+    ...         for tag in content.find_all()
     ...         if re.match(prefix, tag.get(attribute,''))]
     ...     return sorted(ids)
 
diff --git a/lib/lp/translations/stories/standalone/xx-potemplate-index.txt b/lib/lp/translations/stories/standalone/xx-potemplate-index.txt
index a471655..8b1a394 100644
--- a/lib/lp/translations/stories/standalone/xx-potemplate-index.txt
+++ b/lib/lp/translations/stories/standalone/xx-potemplate-index.txt
@@ -35,11 +35,11 @@ show the number of messages that are untranslated, the percentage
 that represents, when the translation was updated, and by whom.
 
     >>> content = find_main_content(anon_browser.contents)
-    >>> print(extract_text(content.findAll('h1')[0]))
+    >>> print(extract_text(content.find_all('h1')[0]))
     Translation status
 
-    >>> table = content.findAll('table')[0]
-    >>> for row in table.findAll('tr'):
+    >>> table = content.find_all('table')[0]
+    >>> for row in table.find_all('tr'):
     ...     print(extract_text(row, formatter='html'))
     Language        Status  Untranslated Need review Changed Last    Edited By
     Afrikaans               22           ...         ...     &mdash; &mdash;
@@ -58,7 +58,7 @@ English translations, but they are not displayed to the user.
     >>> anon_browser.open('http://translations.launchpad.test/ubuntu/hoary/'
     ...     '+source/mozilla/+pots/pkgconf-mozilla')
     >>> table = find_tag_by_id(anon_browser.contents, 'language-chart')
-    >>> for row in table.findAll('tr')[0:6]:
+    >>> for row in table.find_all('tr')[0:6]:
     ...     print(extract_text(row, formatter='html'))
     Language    Status  Untranslated Need review Changed Last  Edited By
     Afrikaans             9            ...         ...   ...   &mdash;
diff --git a/lib/lp/translations/stories/standalone/xx-product-translations.txt b/lib/lp/translations/stories/standalone/xx-product-translations.txt
index 6ae0603..2850738 100644
--- a/lib/lp/translations/stories/standalone/xx-product-translations.txt
+++ b/lib/lp/translations/stories/standalone/xx-product-translations.txt
@@ -21,7 +21,7 @@ A helper method to print out language chart.
     ...     print("%-25s %13s %13s" % (
     ...         "Language", "Untranslated", "Unreviewed"))
     ...     for row in language_rows:
-    ...         cols = row.findAll('td')
+    ...         cols = row.find_all('td')
     ...         language = extract_text(cols[0])
     ...         untranslated = extract_text(cols[2])
     ...         unreviewed = extract_text(cols[3])
diff --git a/lib/lp/translations/stories/standalone/xx-rosetta-distributionsourcepackage-list.txt b/lib/lp/translations/stories/standalone/xx-rosetta-distributionsourcepackage-list.txt
index 732f11a..2139631 100644
--- a/lib/lp/translations/stories/standalone/xx-rosetta-distributionsourcepackage-list.txt
+++ b/lib/lp/translations/stories/standalone/xx-rosetta-distributionsourcepackage-list.txt
@@ -18,7 +18,7 @@ This page directs users to SourcePackage translations pages.
 
 The focus' two templates are shown.
 
-    >>> template_names = content.findAll('h2')
+    >>> template_names = content.find_all('h2')
     >>> for name in template_names:
     ...     print(extract_text(name))
     Template "evolution-2.2" in Ubuntu Hoary package "evolution"
@@ -27,7 +27,7 @@ The focus' two templates are shown.
 
 Other series are also listed.
 
-    >>> for other in content.find(id='distroseries-list').findAll('li'):
+    >>> for other in content.find(id='distroseries-list').find_all('li'):
     ...     print(extract_text(other))
     Breezy Badger Autotest (6.6.6)
     Grumpy (5.10)
diff --git a/lib/lp/translations/stories/standalone/xx-rosetta-homepage.txt b/lib/lp/translations/stories/standalone/xx-rosetta-homepage.txt
index 7e940c7..238ba73 100644
--- a/lib/lp/translations/stories/standalone/xx-rosetta-homepage.txt
+++ b/lib/lp/translations/stories/standalone/xx-rosetta-homepage.txt
@@ -40,13 +40,13 @@ translated projects.
 
     >>> middle_column = find_tags_by_class(
     ...     browser.contents, 'three column middle')[0]
-    >>> heading = middle_column.findAll('h2')[0]
+    >>> heading = middle_column.find_all('h2')[0]
     >>> print(extract_text(heading))
     Translatable projects
-    >>> for project in middle_column.findAll('span'):
+    >>> for project in middle_column.find_all('span'):
     ...     print(extract_text(project))
     Evolution
-    >>> print(extract_text(middle_column.findAll('div')[0]))
+    >>> print(extract_text(middle_column.find_all('div')[0]))
     » List all translatable projects...
 
 The translation front page list of the user's translatable languages.
@@ -63,5 +63,5 @@ in the right column.
     ...
     Change your preferred languages...
 
-    >>> right_column.findAll('a')[-1]
+    >>> right_column.find_all('a')[-1]
     <a href="/+editmylanguages"...Change your preferred languages...
diff --git a/lib/lp/translations/stories/standalone/xx-rosetta-sourcepackage-list.txt b/lib/lp/translations/stories/standalone/xx-rosetta-sourcepackage-list.txt
index cae0b43..74c35dc 100644
--- a/lib/lp/translations/stories/standalone/xx-rosetta-sourcepackage-list.txt
+++ b/lib/lp/translations/stories/standalone/xx-rosetta-sourcepackage-list.txt
@@ -16,12 +16,12 @@ IP address, since we'll use that later.
     'Hoary (5.04) : Translations : ...evolution...package : Ubuntu'
 
     >>> content = find_main_content(anon_browser.contents)
-    >>> print(backslashreplace(extract_text(content.findAll('h1')[0])))
+    >>> print(backslashreplace(extract_text(content.find_all('h1')[0])))
     Translations for evolution in Ubuntu Hoary
 
 There are two templates for evolution in Ubuntu Hoary
 
-    >>> template_names = content.findAll('h2')
+    >>> template_names = content.find_all('h2')
     >>> for name in template_names:
     ...     print(extract_text(name))
     Template "evolution-2.2" in Ubuntu Hoary package "evolution"
@@ -34,8 +34,8 @@ comprised of the translated languages plus the languages of the user,
 100% of the strings are untranslated. The Last and Edited By columns
 indicate that these languages have never been edited my anyone.
 
-    >>> table = content.findAll('table')[0]
-    >>> for row in table.findAll('tr'):
+    >>> table = content.find_all('table')[0]
+    >>> for row in table.find_all('tr'):
     ...     print(extract_text(row, formatter='html'))
     Language        Status Untranslated Need review Changed Last    Edited By
     Afrikaans              22             ...         ...   &mdash; &mdash;
diff --git a/lib/lp/translations/stories/standalone/xx-translationmessage-translate.txt b/lib/lp/translations/stories/standalone/xx-translationmessage-translate.txt
index ef3da1c..2c8bf6b 100644
--- a/lib/lp/translations/stories/standalone/xx-translationmessage-translate.txt
+++ b/lib/lp/translations/stories/standalone/xx-translationmessage-translate.txt
@@ -29,12 +29,12 @@ We are in read only mode, so there shouldn't be any textareas:
 
     >>> main_content = find_tag_by_id(
     ...     browser.contents, 'messages_to_translate')
-    >>> for textarea in main_content.findAll('textarea'):
+    >>> for textarea in main_content.find_all('textarea'):
     ...     raise AssertionError('Found textarea:\n%s' % textarea)
 
 Neither any input widget:
 
-    >>> for input in main_content.findAll('input'):
+    >>> for input in main_content.find_all('input'):
     ...     raise AssertionError('Found input:\n%s' % input)
 
 As an anynoymous user you will have access to the download and details
@@ -463,7 +463,7 @@ Also, we should still have previous translation:
 But also, the new one should appear in the form.
 
     >>> import re
-    >>> elements = find_main_content(slow_submission.contents).findAll(
+    >>> elements = find_main_content(slow_submission.contents).find_all(
     ...     True, {'id': re.compile(r'^msgset_143_es_suggestion_\d+_0$')})
     >>> for element in elements:
     ...     print(element.decode_contents())
diff --git a/lib/lp/translations/stories/standalone/xx-translations-to-review.txt b/lib/lp/translations/stories/standalone/xx-translations-to-review.txt
index c90ebbe..69bc89a 100644
--- a/lib/lp/translations/stories/standalone/xx-translations-to-review.txt
+++ b/lib/lp/translations/stories/standalone/xx-translations-to-review.txt
@@ -54,8 +54,8 @@ Xowxz is a Khmer reviewer.
     ...     listing = soup.find(id="translations-to-review-table")
     ...     if listing:
     ...         count = 0
-    ...         for tr in listing.findAll('tr'):
-    ...             tds = [td.decode_contents() for td in tr.findAll('td')]
+    ...         for tr in listing.find_all('tr'):
+    ...             tds = [td.decode_contents() for td in tr.find_all('td')]
     ...             print('    '.join(tds))
     ...             count += 1
     ...         print("Listing contains %d translation(s)." % count)
diff --git a/lib/lp/translations/stories/translationgroups/xx-link-to-documentation.txt b/lib/lp/translations/stories/translationgroups/xx-link-to-documentation.txt
index 09c4504..bd2b3db 100644
--- a/lib/lp/translations/stories/translationgroups/xx-link-to-documentation.txt
+++ b/lib/lp/translations/stories/translationgroups/xx-link-to-documentation.txt
@@ -31,11 +31,11 @@ This way he can also set the documentation URL.
 The link now appears in the table next to the name of the team.
 
     >>> team = first_tag_by_class(carlos_browser.contents, 'translator-team')
-    >>> print(extract_text(team.findAll('a')[0]))
+    >>> print(extract_text(team.find_all('a')[0]))
     testing Spanish team
 
     >>> link = first_tag_by_class(carlos_browser.contents, 'translator-link')
-    >>> print(link.findAll('a')[0]['href'])
+    >>> print(link.find_all('a')[0]['href'])
     http://www.ubuntu.com/
 
 Back on the translations page, the link is now present, too.
@@ -148,7 +148,7 @@ to said documentation.
     Before translating, be sure to go through Just a testing team
     instructions.
 
-    >>> links = notification.findAll('a')
+    >>> links = notification.find_all('a')
     >>> print(links[0]['href'])
     https://help.launchpad.net/
 
@@ -161,7 +161,7 @@ Adding the Spanish team documentation URL adds another link.
     Before translating, be sure to go through Just a testing team
     instructions and Spanish guidelines.
 
-    >>> links = notification.findAll('a')
+    >>> links = notification.find_all('a')
     >>> print(links[0]['href'])
     https://help.launchpad.net/
     >>> print(links[1]['href'])
@@ -177,6 +177,6 @@ the narrative is changed a bit to include the full team name.
     Before translating, be sure to go through testing Spanish team
     guidelines.
 
-    >>> links = notification.findAll('a')
+    >>> links = notification.find_all('a')
     >>> print(links[0]['href'])
     https://help.launchpad.net/Spanish
diff --git a/lib/lp/translations/stories/translationgroups/xx-translationgroups.txt b/lib/lp/translations/stories/translationgroups/xx-translationgroups.txt
index 4d8effc..96ebe8f 100644
--- a/lib/lp/translations/stories/translationgroups/xx-translationgroups.txt
+++ b/lib/lp/translations/stories/translationgroups/xx-translationgroups.txt
@@ -168,9 +168,9 @@ The new groups should show up on the "Translation groups" page.
 
     >>> groups_table = find_tag_by_id(
     ...     anon_browser.contents, 'translation-groups')
-    >>> groups = groups_table.find('tbody').findAll('tr')
+    >>> groups = groups_table.find('tbody').find_all('tr')
     >>> for group_row in groups:
-    ...     group = group_row.findNext('td')
+    ...     group = group_row.find_next('td')
     ...     print('%s: %s' % (group.a.string, group.a['href']))
     Just a testing team: ...testing-translation-team
     Single-language Translators: ...monolingua
@@ -461,7 +461,7 @@ group has been assigned as the translator for.
     ...     return find_tag_by_id(browser.contents, "related-projects")
 
     >>> portlet = find_projects_portlet(browser)
-    >>> for link in portlet.findAll('a'):
+    >>> for link in portlet.find_all('a'):
     ...     print('%s: %s' % (link.find(text=True), link['href']))
     Ubuntu: http://launchpad.test/ubuntu
     GNOME: http://launchpad.test/gnome
@@ -496,7 +496,7 @@ They disappear from the listing:
     http://translations.launchpad.test/+groups/polyglot
 
     >>> portlet = find_projects_portlet(browser)
-    >>> for link in portlet.findAll('a'):
+    >>> for link in portlet.find_all('a'):
     ...     print('%s: %s' % (link.string, link['href']))
     Ubuntu: http://launchpad.test/ubuntu
 
@@ -645,11 +645,11 @@ The system allows us to change the translator for a concrete language
 
     >>> portlet = find_tag_by_id(
     ...     anon_browser.contents, "translation-teams-listing")
-    >>> language_rows = portlet.find('tbody').findAll('tr')
+    >>> language_rows = portlet.find('tbody').find_all('tr')
     >>> for language_row in language_rows:
-    ...     cell = language_row.findNext('td')
+    ...     cell = language_row.find_next('td')
     ...     lang_name = extract_text(cell)
-    ...     lang_team = extract_text(cell.findNext('td').findNext('a'))
+    ...     lang_team = extract_text(cell.find_next('td').find_next('a'))
     ...     print('%s: %s' % (lang_name, lang_team))
     Abkhazian (ab): Hoary Gnome Team
     Afrikaans (af): No Privileges Person
@@ -717,11 +717,11 @@ instance Welsh (cy), the change will work.
 
     >>> portlet = find_tag_by_id(
     ...     browser.contents, "translation-teams-listing")
-    >>> language_rows = portlet.find('tbody').findAll('tr')
+    >>> language_rows = portlet.find('tbody').find_all('tr')
     >>> for language_row in language_rows:
-    ...     cell = language_row.findNext('td')
+    ...     cell = language_row.find_next('td')
     ...     lang_name = extract_text(cell)
-    ...     lang_team = extract_text(cell.findNext('td').findNext('a'))
+    ...     lang_team = extract_text(cell.find_next('td').find_next('a'))
     ...     print('%s: %s' % (lang_name, lang_team))
     Abkhazian (ab): Hoary Gnome Team
     Welsh (cy): No Privileges Person
@@ -770,12 +770,12 @@ We are in read only mode, so there shouldn't be any textareas:
 
     >>> main_content = find_tag_by_id(
     ...     browser.contents, 'messages_to_translate')
-    >>> for textarea in main_content.findAll('textarea'):
+    >>> for textarea in main_content.find_all('textarea'):
     ...     print('Found textarea:\n%s' % textarea)
 
 Neither any input widget:
 
-    >>> for input in main_content.findAll('input'):
+    >>> for input in main_content.find_all('input'):
     ...     print('Found input:\n%s' % input)
 
 However, in Welsh, No Privileges Person does have the ability to edit
@@ -1223,7 +1223,7 @@ Let's add a new suggestion as a person without privileges.
 
 Now, we can see the added suggestion + others from the sample data.
 
-    >>> for suggestion in find_main_content(browser.contents).findAll(
+    >>> for suggestion in find_main_content(browser.contents).find_all(
     ...         True, {'id': re.compile('msgset_134_es_suggestion_.*')}):
     ...     print(suggestion)
     <...<samp> </samp>new suggestion...
diff --git a/lib/lp/translations/stories/translations/xx-translations.txt b/lib/lp/translations/stories/translations/xx-translations.txt
index 7c88570..8fb1d3b 100644
--- a/lib/lp/translations/stories/translations/xx-translations.txt
+++ b/lib/lp/translations/stories/translations/xx-translations.txt
@@ -249,7 +249,7 @@ The template title points to the general translate page:
     # We are not using browser.getLink because there is no unique way to
     # get all of the relevant links, and we don't want to pollute the
     # page template with too many IDs useful only for testing.
-    >>> all_links = evolution_line.findAll('a')
+    >>> all_links = evolution_line.find_all('a')
     >>> base_href = browser.url
     >>> unfiltered = all_links[0]
     >>> print(extract_text(unfiltered))
@@ -422,7 +422,7 @@ Language title points to the general translate page:
     # We are not using browser.getLink because there is no unique way to
     # get all of the relevant links, and we don't want to pollute the
     # page template with too many IDs useful only for testing.
-    >>> all_links = spanish_line.findAll('a')
+    >>> all_links = spanish_line.find_all('a')
     >>> base_href = browser.url
     >>> unfiltered = all_links[0]
     >>> print(extract_text(unfiltered))
diff --git a/utilities/roundup-sniffer.py b/utilities/roundup-sniffer.py
index 3035d0e..3d5acf7 100755
--- a/utilities/roundup-sniffer.py
+++ b/utilities/roundup-sniffer.py
@@ -113,7 +113,7 @@ class MplayerStatusSniffer(RoundupSniffer):
         soup = BeautifulSoup(page)
         return tuple(
             node.string for node in
-            soup.find('th', text='Status').findNext('td').findAll('span'))
+            soup.find('th', text='Status').find_next('td').find_all('span'))
 
 
 def get_distinct(things, fields):