← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~wallyworld/launchpad/filebug-loses-data into lp:launchpad

 

Ian Booth has proposed merging lp:~wallyworld/launchpad/filebug-loses-data into lp:launchpad.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)
Related bugs:
  Bug #513591 in Launchpad itself: "Assignee selector on +filebug trashes form input due to missing javascript"
  https://bugs.launchpad.net/launchpad/+bug/513591
  Bug #735290 in Launchpad itself: "changing Project drop down in project group +filebug loses all your work"
  https://bugs.launchpad.net/launchpad/+bug/735290

For more details, see:
https://code.launchpad.net/~wallyworld/launchpad/filebug-loses-data/+merge/58410

This branch addresses 2 issues which cause major problems when filing bug reports. The issues are:
1. In Chrome, the Assignee field popup is broken. This has the effect that you only see the non-ajax Find... link which takes you to the /people page and when you come back you have lost all your work.
2. When you file a bug against a project group, and you choose a project from the drop down, it clears any work you have already entered.

The implementation which causes the above issues also had xss holes which have been fixed along the way.

== Implementation ==

The root cause for both of the above issues was the way the bug details entry form was being rendered after the initial duplicate search had been performed. An XHR call was made to fetch the entire form contents and this was rendered using a set innerHTML. Ugh. On Chrome, doing this also meant that the Javascript to set up the assignee picker was not run.

The approach now is to render the bug details entry form at the start, but keep it hidden until needed. This also allows the browser back button to be used when required and the user's previously entered data is retained (this is only really relevant in the non ajax case where the assignee field has the Find... link).

Tweaks have also been made to hopefully improve the user experience. Unlike currently, when the user chooses a new project from the drop down, the existing details form elements are not hidden (and content deleted) but rather given a 20% opacity so they are still visible but clearly shown as "out of action" until the user completes the required duplicate search against the newly selected project. When "Check again" is pressed, if there are no duplicates the user's data in the bug details form is presented back to them how it was before they changed projects. If there are duplicates, the user can choose to use an existing bug as expected, but if they hit "No, I need to file a new bug", again, their data is retained.

Note that when a new project is chosen, the security contact message and bug reporting guidelines are correctly updated in the details entry form using an XHR call.

Note also that the changes only affect the case where Javascript is enabled. Non Javascript functionality is identical to how it was before.

== Demo ==

http://people.canonical.com/~ianb/filebug.ogv

Huw has looked at the demo and thinks it's ok.

== Tests ==

Existing bugtask browser/page tests were run. Windmill/YUI tests were also enabled and run.
  bin/test -vvt test_bug (Total: 1662 tests)
  bin/test --layer=BugsWindmillLayer
  bin/test -vvt xx-bug (Total: 43 tests)
  bin/test -vvt filebug (Total: 31 tests)

A number of tests required small fixes. The main issue was that the bug filing details widgets are now rendered (albeit hidden) right from the start rather than being fetched via an XHR call and so even though the widgets are not visible, when tests did things like - user_browser.getControl('Summary') - it failed because there was more than one 'Summary' element. So in many places the above call and others like it were changed to: user_browser.getControl('Summary', index=0)

New Javascript tests were written:
  lib/lp/bugs/javascript/filebug_dupefinder.js
  lib/lp/bugs/javascript/filebug_dupefinder.html
These tests ensure that the hiding and display of the various bug details entry widgets occurs as expected when the user searches for duplicates as part of submitting a bug report. They also check the expected XHR calls are made and the results rendered.

New view test:
  TestFileBugExtraInformationView (checks rendering of +filebug-reporting-details)

== Lint ==

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/bugs/browser/bugtarget.py
  lib/lp/bugs/browser/configure.zcml
  lib/lp/bugs/browser/tests/test_bugtarget_filebug.py
  lib/lp/bugs/javascript/filebug_dupefinder.js
  lib/lp/bugs/javascript/tests/test_filebug_dupfinder.html
  lib/lp/bugs/javascript/tests/test_filebug_dupfinder.js
  lib/lp/bugs/stories/bug-privacy/xx-presenting-private-bugs.txt
  lib/lp/bugs/stories/bugs/xx-add-comment-distribution-no-current-release.txt
  lib/lp/bugs/stories/distribution/xx-distribution-filebug-error-handling.txt
  lib/lp/bugs/stories/guided-filebug/xx-bug-reporting-guidelines.txt
  lib/lp/bugs/stories/guided-filebug/xx-bug-reporting-tools.txt
  lib/lp/bugs/stories/guided-filebug/xx-displaying-similar-bugs.txt
  lib/lp/bugs/stories/guided-filebug/xx-distro-guided-filebug-tags.txt
  lib/lp/bugs/stories/guided-filebug/xx-distro-guided-filebug.txt
  lib/lp/bugs/stories/guided-filebug/xx-distro-sourcepackage-guided-filebug.txt
  lib/lp/bugs/stories/guided-filebug/xx-filebug-attachments.txt
  lib/lp/bugs/stories/guided-filebug/xx-filebug-tags.txt
  lib/lp/bugs/stories/guided-filebug/xx-options-for-bug-supervisors.txt
  lib/lp/bugs/stories/guided-filebug/xx-product-guided-filebug.txt
  lib/lp/bugs/stories/guided-filebug/xx-project-guided-filebug.txt
  lib/lp/bugs/stories/guided-filebug/xx-sorting-by-relevance.txt
  lib/lp/bugs/stories/standalone/xx-filebug-package-chooser-radio-buttons.txt
  lib/lp/bugs/stories/upstream-bugprivacy/10-file-private-upstream-bug.txt
  lib/lp/bugs/templates/bugtarget-filebug-guidelines.pt
  lib/lp/bugs/templates/bugtarget-filebug-search.pt
  lib/lp/bugs/templates/bugtarget-macros-filebug.pt

./lib/lp/bugs/javascript/filebug_dupefinder.js
     143: Expected '===' and instead saw '=='.
     165: Expected '===' and instead saw '=='.
     349: Expected '===' and instead saw '=='.
     466: Expected '===' and instead saw '=='.
./lib/lp/bugs/javascript/tests/test_filebug_dupfinder.js
      36: Avoid 'arguments.callee'.
      37: Avoid 'arguments.callee'.
      40: Avoid 'arguments.callee'.
      42: Avoid 'arguments.callee'.
      43: Avoid 'arguments.callee'.
     120: Line exceeds 78 characters.
     307: Line exceeds 78 characters.
     308: Line exceeds 78 characters.
     309: Line exceeds 78 characters.
./lib/lp/bugs/stories/bugs/xx-add-comment-distribution-no-current-release.txt
       1: narrative uses a moin header.
./lib/lp/bugs/stories/guided-filebug/xx-bug-reporting-guidelines.txt
     109: want exceeds 78 characters.
     115: want exceeds 78 characters.
./lib/lp/bugs/stories/guided-filebug/xx-bug-reporting-tools.txt
       1: narrative uses a moin header.
      57: narrative uses a moin header.
     108: source exceeds 78 characters.
     152: narrative uses a moin header.
     180: narrative uses a moin header.
     237: narrative uses a moin header.
./lib/lp/bugs/stories/guided-filebug/xx-displaying-similar-bugs.txt
       1: narrative uses a moin header.
      24: narrative uses a moin header.
      91: narrative uses a moin header.
     110: source has bad indentation.
     115: source has bad indentation.
     120: source has bad indentation.
     132: source has bad indentation.
     136: source has bad indentation.
     144: source has bad indentation.
     150: source has bad indentation.
     154: source has bad indentation.
     167: source has bad indentation.
     173: source has bad indentation.
     181: source has bad indentation.
     186: source has bad indentation.
     192: source has bad indentation.
./lib/lp/bugs/stories/guided-filebug/xx-distro-guided-filebug-tags.txt
       1: narrative uses a moin header.
./lib/lp/bugs/stories/guided-filebug/xx-distro-guided-filebug.txt
       1: narrative uses a moin header.
      77: narrative uses a moin header.
./lib/lp/bugs/stories/guided-filebug/xx-filebug-attachments.txt
       1: narrative uses a moin header.
       8: narrative uses a moin header.
      54: narrative uses a moin header.
      63: source exceeds 78 characters.
./lib/lp/bugs/stories/guided-filebug/xx-filebug-tags.txt
       1: narrative uses a moin header.
       4: narrative uses a moin header.
      28: narrative uses a moin header.
./lib/lp/bugs/stories/guided-filebug/xx-options-for-bug-supervisors.txt
       1: narrative uses a moin header.
      54: narrative uses a moin header.
./lib/lp/bugs/stories/guided-filebug/xx-sorting-by-relevance.txt
       1: narrative uses a moin header.
       8: narrative exceeds 78 characters.
./lib/lp/bugs/stories/upstream-bugprivacy/10-file-private-upstream-bug.txt
      12: want exceeds 78 characters.
      83: want exceeds 78 characters.
     106: narrative uses a moin header.
     118: narrative exceeds 78 characters.
     140: source exceeds 78 characters.

-- 
https://code.launchpad.net/~wallyworld/launchpad/filebug-loses-data/+merge/58410
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~wallyworld/launchpad/filebug-loses-data into lp:launchpad.
=== modified file 'lib/lp/bugs/browser/bugtarget.py'
--- lib/lp/bugs/browser/bugtarget.py	2011-04-15 02:56:03 +0000
+++ lib/lp/bugs/browser/bugtarget.py	2011-04-26 13:10:29 +0000
@@ -12,6 +12,7 @@
     "BugTargetBugTagsView",
     "BugTargetBugsView",
     "FileBugAdvancedView",
+    "FileBugExtraInformation",
     "FileBugGuidedView",
     "FileBugViewBase",
     "IProductBugConfiguration",
@@ -109,6 +110,7 @@
 from lp.bugs.interfaces.apportjob import IProcessApportBlobJobSource
 from lp.bugs.interfaces.bug import (
     CreateBugParams,
+    IBug,
     IBugAddForm,
     IBugSet,
     IProjectGroupBugAddForm,
@@ -234,7 +236,84 @@
         self.updateContextFromData(data)
 
 
-class FileBugViewBase(LaunchpadFormView):
+class FileBugExtraInformation(LaunchpadFormView):
+    """Provides access to common bug reporting attributes.
+
+    Attributes provided are: security_related and bug_reporting_guidelines.
+
+    This view is a superclass of `FileBugViewBase` so that non-ajax browsers
+    can load the file bug form, and it is also invoked directly via an XHR
+    request to provide an HTML snippet for Javascript enabled browsers.
+    """
+
+    schema = IBug
+
+    @property
+    def field_names(self):
+        """Return the list of field names to display."""
+        return ['security_related']
+
+    def setUpFields(self):
+        """Set up the form fields. See `LaunchpadFormView`."""
+        super(FileBugExtraInformation, self).setUpFields()
+
+        security_related_field = Bool(
+            __name__='security_related',
+            title=_("This bug is a security vulnerability"),
+            required=False, default=False)
+
+        self.form_fields = self.form_fields.omit('security_related')
+        self.form_fields += formlib.form.Fields(security_related_field)
+
+    @property
+    def bug_reporting_guidelines(self):
+        """Guidelines for filing bugs in the current context.
+
+        Returns a list of dicts, with each dict containing values for
+        "preamble" and "content".
+        """
+
+        def target_name(target):
+            # IProjectGroup can be considered the target of a bug during
+            # the bug filing process, but does not extend IBugTarget
+            # and ultimately cannot actually be the target of a
+            # bug. Hence this function to determine a suitable
+            # name/title to display. Hurrumph.
+            if IBugTarget.providedBy(target):
+                return target.bugtargetdisplayname
+            else:
+                return target.title
+
+        guidelines = []
+        bugtarget = self.context
+        if bugtarget is not None:
+            content = bugtarget.bug_reporting_guidelines
+            if content is not None and len(content) > 0:
+                guidelines.append({
+                        "source": target_name(bugtarget),
+                        "content": content,
+                        })
+            # Distribution source packages are shown with both their
+            # own reporting guidelines and those of their
+            # distribution.
+            if IDistributionSourcePackage.providedBy(bugtarget):
+                distribution = bugtarget.distribution
+                content = distribution.bug_reporting_guidelines
+                if content is not None and len(content) > 0:
+                    guidelines.append({
+                            "source": target_name(distribution),
+                            "content": content,
+                            })
+        return guidelines
+
+    def getMainContext(self):
+        if IDistributionSourcePackage.providedBy(self.context):
+            return self.context.distribution
+        else:
+            return self.context
+
+
+class FileBugViewBase(FileBugExtraInformation, LaunchpadFormView):
     """Base class for views related to filing a bug."""
 
     implements(IBrowserPublisher)
@@ -438,14 +517,6 @@
         self.form_fields = self.form_fields.omit('subscribe_to_existing_bug')
         self.form_fields += formlib.form.Fields(subscribe_field)
 
-        security_related_field = Bool(
-            __name__='security_related',
-            title=_("This bug is a security vulnerability"),
-            required=False, default=False)
-
-        self.form_fields = self.form_fields.omit('security_related')
-        self.form_fields += formlib.form.Fields(security_related_field)
-
     def contextUsesMalone(self):
         """Does the context use Malone as its official bugtracker?"""
         if IProjectGroup.providedBy(self.context):
@@ -457,21 +528,6 @@
             bug_tracking_usage = self.getMainContext().bug_tracking_usage
             return bug_tracking_usage == ServiceUsage.LAUNCHPAD
 
-    def getMainContext(self):
-        if IDistributionSourcePackage.providedBy(self.context):
-            return self.context.distribution
-        else:
-            return self.context
-
-    def getSecurityContext(self):
-        """Return the context used for security bugs."""
-        return self.getMainContext()
-
-    @property
-    def can_decide_security_contact(self):
-        """Will we be able to discern a security contact for this?"""
-        return (self.getSecurityContext() is not None)
-
     def shouldSelectPackageName(self):
         """Should the radio button to select a package be selected?"""
         return (
@@ -809,47 +865,6 @@
         """
         return self.context
 
-    @property
-    def bug_reporting_guidelines(self):
-        """Guidelines for filing bugs in the current context.
-
-        Returns a list of dicts, with each dict containing values for
-        "preamble" and "content".
-        """
-
-        def target_name(target):
-            # IProjectGroup can be considered the target of a bug during
-            # the bug filing process, but does not extend IBugTarget
-            # and ultimately cannot actually be the target of a
-            # bug. Hence this function to determine a suitable
-            # name/title to display. Hurrumph.
-            if IBugTarget.providedBy(target):
-                return target.bugtargetdisplayname
-            else:
-                return target.title
-
-        guidelines = []
-        context = self.bugtarget
-        if context is not None:
-            content = context.bug_reporting_guidelines
-            if content is not None and len(content) > 0:
-                guidelines.append({
-                        "source": target_name(context),
-                        "content": content,
-                        })
-            # Distribution source packages are shown with both their
-            # own reporting guidelines and those of their
-            # distribution.
-            if IDistributionSourcePackage.providedBy(context):
-                distribution = context.distribution
-                content = distribution.bug_reporting_guidelines
-                if content is not None and len(content) > 0:
-                    guidelines.append({
-                            "source": target_name(distribution),
-                            "content": content,
-                            })
-        return guidelines
-
     default_bug_reported_acknowledgement = "Thank you for your bug report."
 
     def getAcknowledgementMessage(self, context):
@@ -1154,17 +1169,6 @@
             url = urlappend(url, self.extra_data_token)
         return url
 
-    def _getSelectedProduct(self):
-        """Return the product that's selected."""
-        assert self.widgets['product'].hasValidInput(), (
-            "This method should be called only when we know which"
-            " product the user selected.")
-        return self.widgets['product'].getInputValue()
-
-    def getSecurityContext(self):
-        """See FileBugViewBase."""
-        return self._getSelectedProduct()
-
 
 class BugTargetBugListingView:
     """Helper methods for rendering bug listings."""

=== modified file 'lib/lp/bugs/browser/configure.zcml'
--- lib/lp/bugs/browser/configure.zcml	2011-04-11 06:46:11 +0000
+++ lib/lp/bugs/browser/configure.zcml	2011-04-26 13:10:29 +0000
@@ -108,6 +108,12 @@
             facet="bugs"
             permission="launchpad.AnyPerson"/>
         <browser:page
+            for="lp.bugs.interfaces.bugtarget.IHasBugs"
+            class="lp.bugs.browser.bugtarget.FileBugExtraInformation"
+            template="../templates/bugtarget-filebug-guidelines.pt"
+            permission="launchpad.AnyPerson"
+            name="+filebug-reporting-details"/>
+        <browser:page
             name="+manage-official-tags"
             for="lp.bugs.interfaces.bugtarget.IOfficialBugTagTargetRestricted"
             class="lp.bugs.browser.bugtarget.OfficialBugTagsManageView"

=== modified file 'lib/lp/bugs/browser/tests/test_bugtarget_filebug.py'
--- lib/lp/bugs/browser/tests/test_bugtarget_filebug.py	2011-02-10 20:01:17 +0000
+++ lib/lp/bugs/browser/tests/test_bugtarget_filebug.py	2011-04-26 13:10:29 +0000
@@ -10,7 +10,10 @@
     )
 
 from canonical.launchpad.ftests import login
-from canonical.launchpad.testing.pages import find_tag_by_id
+from canonical.launchpad.testing.pages import (
+    find_main_content,
+    find_tag_by_id,
+    )
 from canonical.launchpad.webapp.servers import LaunchpadTestRequest
 from canonical.testing.layers import DatabaseFunctionalLayer
 from lp.bugs.browser.bugtarget import (
@@ -280,8 +283,16 @@
             product, name='+filebug', principal=user)
         html = view.render()
         self.assertIsNot(None, find_tag_by_id(html, 'filebug-search-form'))
-        # The main bug filing form is not shown.
-        self.assertIs(None, find_tag_by_id(html, 'filebug-form'))
+        # The main bug filing form is rendered but hidden inside an invisible
+        # filebug-container.
+        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(
+            id='filebug-form-container')[0]
+        style_attrs = [item.strip()
+                       for item in filebug_form_container['style'].split(";")]
+        self.assertTrue('display: none' in style_attrs)
 
     def test_bug_filing_view_with_dupe_search_disabled(self):
         # When a user files a bug for a product where searching for
@@ -367,3 +378,18 @@
         view = self.create_initialized_view(form=form)
         self.assertEqual(0, len(view.errors))
         self.assertTrue(view.added_bug is not None)
+
+
+class TestFileBugExtraInformationView(TestCaseWithFactory):
+
+    layer = DatabaseFunctionalLayer
+
+    def test_filebug_reporting_details(self):
+        login('foo.bar@xxxxxxxxxxxxx')
+        product = self.factory.makeProduct()
+        product.bug_reporting_guidelines = "Include bug details"
+        view = create_initialized_view(product, '+filebug-reporting-details')
+        expected_guidelines = [{
+            "source": product.displayname, "content": u"Include bug details",
+            }]
+        self.assertEqual(expected_guidelines, view.bug_reporting_guidelines)

=== modified file 'lib/lp/bugs/javascript/filebug_dupefinder.js'
--- lib/lp/bugs/javascript/filebug_dupefinder.js	2011-04-14 21:10:13 +0000
+++ lib/lp/bugs/javascript/filebug_dupefinder.js	2011-04-26 13:10:29 +0000
@@ -12,9 +12,7 @@
     DISPLAY = 'display',
     EXPANDER_COLLAPSED = '/@@/treeCollapsed',
     EXPANDER_EXPANDED = '/@@/treeExpanded',
-    INLINE = 'inline',
     INNER_HTML = 'innerHTML',
-    LAZR_CLOSED = 'lazr-closed',
     NONE = 'none',
     SRC = 'src',
     UNSEEN = 'unseen';
@@ -22,6 +20,10 @@
 var namespace = Y.namespace('lp.bugs.filebug_dupefinder');
 
 /*
+ * The IO provider to use. A stub may be used for testing.
+ */
+var YIO = Y;
+/*
  * The NodeList of possible duplicates.
  */
 var bug_already_reported_expanders;
@@ -91,21 +93,25 @@
 
 /**
  * Show the bug reporting form and collapse all bug details forms.
- * @param e The Event triggering this function.
  */
-function show_bug_reporting_form(e) {
+function show_bug_reporting_form() {
     // If the bug reporting form is in a hidden container, as it is on
     // the AJAX dupe search, show it.
     var filebug_form_container = Y.one('#filebug-form-container');
     if (Y.Lang.isValue(filebug_form_container)) {
-        filebug_form_container.setStyle(DISPLAY, BLOCK);
+        filebug_form_container.setStyles({
+                    'opacity': '1.0',
+                    'display': 'block'
+        });
     }
 
     // Show the bug reporting form.
     var bug_reporting_form = Y.one('#bug_reporting_form');
     bug_reporting_form.setStyle(DISPLAY, BLOCK);
 
-    Y.one(Y.DOM.byId('field.actions.submit_bug')).focus();
+    var submit_button = Y.one(Y.DOM.byId('field.actions.submit_bug'));
+    submit_button.focus();
+    submit_button.removeAttribute('disabled');
 
     // Focus the relevant elements of the form based on
     // whether the package drop-down is displayed.
@@ -119,6 +125,34 @@
 }
 
 /**
+ * Fade the bug reporting form and optionally hide it also. Only fade the
+ * form if it is already visible.
+ */
+function fade_bug_reporting_form(hide_after_fade) {
+    Y.one(Y.DOM.byId(
+        'field.actions.submit_bug')).setAttribute('disabled', 'true');
+    var filebug_form_container = Y.one('#filebug-form-container');
+    var maybe_hide_form = function() {
+        if (hide_after_fade) {
+            filebug_form_container.setStyle(DISPLAY, NONE);
+        }
+    };
+
+    var form_display = filebug_form_container.getStyle(DISPLAY);
+    if (form_display == BLOCK) {
+        var form_fade_out = new Y.Anim({
+            node: filebug_form_container,
+            to: {opacity: 0.2},
+            duration: 0.5
+        });
+        form_fade_out.on('end', maybe_hide_form);
+        form_fade_out.run();
+    } else {
+        maybe_hide_form();
+    }
+}
+
+/**
  * Search for bugs that may match the text that the user has entered and
  * display them in-line.
  */
@@ -171,6 +205,8 @@
             // If there are duplicates shown, set up the JavaScript of
             // the duplicates that have been returned.
             Y.lp.bugs.filebug_dupefinder.setup_dupes();
+            // And fade out and hide the bug reporting form.
+            fade_bug_reporting_form(true);
         } else {
             // Otherwise, show the bug reporting form.
             show_bug_reporting_form();
@@ -195,11 +231,19 @@
     search_button.addClass(UNSEEN);
     Y.one('#spinner').removeClass(UNSEEN);
     Y.one('#possible-duplicates').set(INNER_HTML, '');
-    Y.one('#bug_reporting_form').setStyle(DISPLAY, NONE);
 
-    config = {on: {success: on_success,
-                   failure: show_failure_message}};
-    Y.io(search_url, config);
+    var filebug_form_container = Y.one('#filebug-form-container');
+    var form_fade_out = new Y.Anim({
+        node: filebug_form_container,
+        to: {opacity: 0.2},
+        duration: 0.2
+    });
+    form_fade_out.on('end', function() {
+        var config = {on: {success: on_success,
+                       failure: show_failure_message}};
+        YIO.io(search_url, config);
+    });
+    form_fade_out.run();
 }
 
 /*
@@ -275,56 +319,56 @@
 }
 
 /**
+ * Use the value of the product field to set the relevant urls elements.
+ */
+function set_product_urls()
+{
+    var product_field = Y.one(Y.DOM.byId('field.product'));
+    if (Y.Lang.isValue(product_field)) {
+        var product = product_field.get('value');
+        filebug_form_url =
+            filebug_base_url + product + '/+filebug-reporting-details';
+        search_url_base =
+            filebug_base_url + product + '/+filebug-show-similar';
+        var filebug_form = Y.one('#filebug-form');
+        var submit_url = [product, '+filebug'].join('/');
+        filebug_form.setAttribute('action', filebug_base_url+submit_url);
+    }
+}
+
+/**
  * Reload the +filebug-inline form for the correct product or package.
  */
 function reload_filebug_form() {
     var config = {
         on: {
             success: function(transaction_id, response, args) {
-                // Hide the filebug form container.
-                var filebug_form_container = Y.one(
-                    '#filebug-form-container');
-                filebug_form_container.setStyle(DISPLAY, NONE);
-                Y.log(filebug_form_container);
+                var filebug_form_container = Y.one('#filebug-form-container');
+                var filebug_form_was_visible =
+                        filebug_form_container.getStyle(DISPLAY) == BLOCK;
+                fade_bug_reporting_form();
+                var extra_filebug_details = Y.one('#extra-filebug-details');
+                extra_filebug_details.set(
+                        INNER_HTML, Y.Escape.html(response.responseText));
 
                 // Clear the contents of the search results.
                 Y.one('#possible-duplicates').set(INNER_HTML, '');
 
-                // Set up the +filebug form.
-                set_up_filebug_form(response.responseText);
-
-                // Change the label on the search button to its default.
-                search_button.set('value', 'Next');
+                if (filebug_form_was_visible) {
+                    // Change the label on the search button to inform the
+                    // user they can re-check for duplicates against the
+                    // currently selected project.
+                    search_button.set('value', 'Check again');
+                } else {
+                    search_button.set('value', 'Next');
+                }
             }
         }
     };
 
-    var product_field = Y.one(Y.DOM.byId('field.product'));
-    if (Y.Lang.isValue(product_field)) {
-        var product = product_field.get('value');
-        filebug_form_url =
-            filebug_base_url + product + '/+filebug-inline-form';
-        search_url_base =
-            filebug_base_url + product + '/+filebug-show-similar';
-    }
-
-    // Reload the filebug form.
-    Y.io(filebug_form_url, config);
-}
-
-/**
- * Set up the filebug form.
- */
-function set_up_filebug_form(form_contents) {
-    var filebug_form_container = Y.one('#filebug-form-container');
-    filebug_form_container.set(INNER_HTML, form_contents);
-
-    // Activate the extra options collapsible section on the bug
-    // reporting form.
-    var bug_reporting_form = Y.one('#bug_reporting_form');
-    if (Y.Lang.isValue(bug_reporting_form)) {
-        activateCollapsibles();
-    }
+    set_product_urls();
+    // Reload the extra bug details for the current product.
+    YIO.io(filebug_form_url, config);
 }
 
 /**
@@ -335,11 +379,8 @@
     // Grab the inline filebug base url and store it.
     filebug_base_url = Y.one('#filebug-base-url').getAttribute('href');
 
-    // Load the +filebug form into its container.
-    set_up_filebug_form(response.responseText);
-
-    // Grab the search_url_base value from the page and store it.
-    search_url_base = Y.one('#duplicate-search-url').getAttribute('href');
+    // Set up the product field change listener and related variables.
+    namespace.setup_product_urls();
 
     // Change the name and id of the search field so that it doesn't
     // confuse the view when we submit a bug report.
@@ -351,7 +392,6 @@
     // reload_filebug_form() function.
     var product_field = Y.one(Y.DOM.byId('field.product'));
     if (Y.Lang.isValue(product_field)) {
-        Y.log(product_field);
         product_field.on('change', reload_filebug_form);
     }
 
@@ -361,7 +401,7 @@
     search_button.set('value', 'Next');
 
     // Set up the handler for the search form.
-    search_form = Y.one('#filebug-search-form');
+    var search_form = Y.one('#filebug-search-form');
     search_form.on('submit', function(e) {
         // Prevent the event from propagating; we don't want to reload
         // the page.
@@ -373,7 +413,7 @@
 namespace.setup_dupes = function() {
     bug_already_reported_expanders = Y.all(
         'img.bug-already-reported-expander');
-    bug_reporting_form = Y.one('#bug_reporting_form');
+    var bug_reporting_form = Y.one('#bug_reporting_form');
 
     if (bug_already_reported_expanders.size() > 0) {
         // Collapse all the details divs, since we don't want them
@@ -444,7 +484,7 @@
         bug_reporting_form.addClass(UNSEEN);
     }
 
-    bug_not_reported_button = Y.one('#bug-not-already-reported');
+    var bug_not_reported_button = Y.one('#bug-not-already-reported');
     if (Y.Lang.isValue(bug_not_reported_button)) {
         // The bug_not_reported_button won't show up if there aren't any
         // possible duplicates.
@@ -465,6 +505,15 @@
     });
 };
 
+namespace.setup_product_urls = function() {
+    // Grab the search_url_base value from the page and store it.
+    search_url_base = Y.one('#duplicate-search-url').getAttribute('href');
+    var product_field = Y.one(Y.DOM.byId('field.product'));
+    if (Y.Lang.isValue(product_field)) {
+        set_product_urls();
+    }
+};
+
 namespace.setup_dupe_finder = function() {
     Y.on('domready', function() {
         config = {on: {success: set_up_dupe_finder,
@@ -478,11 +527,21 @@
         if (Y.Lang.isValue(filebug_form_url_element)) {
             filebug_form_url = filebug_form_url_element.getAttribute(
                 'href');
-            Y.io(filebug_form_url, config);
+            YIO.io(filebug_form_url, config);
         }
     });
 };
 
+/**
+ * Set up and configure the module.
+ */
+ namespace.setup_config = function(config) {
+    if (config.yio !== undefined) {
+        //We can be given an alternative IO provider for use in tests.
+        YIO = config.yio;
+    }
+};
+
 }, "0.1", {"requires": [
-    "base", "io", "oop", "node", "event", "lazr.formoverlay",
+    "base", "io", "oop", "node", "event", "escape", "lazr.formoverlay",
     "lazr.effects"]});

=== added file 'lib/lp/bugs/javascript/tests/test_filebug_dupfinder.html'
--- lib/lp/bugs/javascript/tests/test_filebug_dupfinder.html	1970-01-01 00:00:00 +0000
+++ lib/lp/bugs/javascript/tests/test_filebug_dupfinder.html	2011-04-26 13:10:29 +0000
@@ -0,0 +1,92 @@
+<html>
+<head>
+    <title>File bug functionality</title>
+
+    <!-- YUI 3.0 Setup -->
+    <script type="text/javascript"
+            src="../../../../canonical/launchpad/icing/yui/yui/yui.js"></script>
+    <script type="text/javascript"
+            src="../../../../canonical/launchpad/icing/lazr/build/lazr.js"></script>
+    <link rel="stylesheet"
+          href="../../../../canonical/launchpad/icing/yui/cssreset/reset.css"/>
+    <link rel="stylesheet"
+          href="../../../../canonical/launchpad/icing/yui/cssfonts/fonts.css"/>
+    <link rel="stylesheet"
+          href="../../../../canonical/launchpad/icing/yui/cssbase/base.css"/>
+    <link rel="stylesheet"
+          href="../../../../canonical/launchpad/javascript/test.css" />
+
+    <script type="text/javascript"
+            src="../../../app/javascript/client.js"></script>
+    <script type="text/javascript"
+            src="../../../app/javascript/errors.js"></script>
+
+    <!-- The module under test -->
+    <script type="text/javascript"
+            src="../filebug_dupefinder.js"></script>
+
+    <!-- The test suite -->
+    <script type="text/javascript"
+            src="test_filebug_dupfinder.js"></script>
+
+    <!-- Pretty up the sample html -->
+    <style type="text/css">
+        div#sample {margin:15px; width:200px; border:1px solid #999; padding:10px;}
+    </style>
+</head>
+<body class="yui3-skin-sam">
+<!-- Example markup required by test suite -->
+<div id="test-root">
+    <form id="filebug-search-form" enctype="multipart/form-data" method="post" action="">
+    <table>
+        <tr>
+            <td>
+                <label for="field.product">Project:</label>
+                <select size="1" name="field.product" id="field.product">
+                    <option value="foo">Foo</option>
+                    <option value="bar">Bar</option>
+                </select>
+            </td>
+        </tr>
+        <tr>
+            <td>
+                <div>
+                    <label for="field.title">Summary:</label>
+                    <input type="text" value="" size="40" name="field.title" id="field.title"/>
+                </div>
+            </td>
+            <td>
+                <input type="submit" class="button" value="Next" name="field.actions.search" id="field.actions.search"/>
+            </td>
+        </tr>
+    </table>
+    </form>
+    <div id='spinner'></div>
+    <div id='possible-duplicates'></div>
+    <div style="opacity: 0; display: none" class="transparent" id="filebug-form-container">
+        <div id="bug_reporting_form">
+            <label for="field.title">Summary:</label>
+            <input type="text" value="" size="40" name="field.title" id="field.title"/>
+            <div>
+                <form id="filebug-form" method="post" action="#">
+                    <label for="field.comment">Comment:</label>
+                    <input type="text" value="" size="40" name="field.comment" id="field.comment"/>
+                    <div id="extra-filebug-details"></div>
+                    <div class="actions">
+                        <input type="submit" class="button" value="Submit Bug Report" name="field.actions.submit_bug" id="field.actions.submit_bug"/>
+                    </div>
+                </form>
+            </div>
+        </div>
+    </div>
+    <p style="display: none">
+        <a id="filebug-base-url" href="https://bugs.launchpad.dev/";></a>
+        <a id="filebug-form-url" href="https://bugs.launchpad.dev/firefox/+filebug-inline-form";></a>
+        <a id="duplicate-search-url" href="https://bugs.launchpad.dev/firefox/+filebug-show-similar";></a>
+    </p>
+</div>
+
+<!-- The test output -->
+<div id="log"></div>
+</body>
+</html>

=== added file 'lib/lp/bugs/javascript/tests/test_filebug_dupfinder.js'
--- lib/lp/bugs/javascript/tests/test_filebug_dupfinder.js	1970-01-01 00:00:00 +0000
+++ lib/lp/bugs/javascript/tests/test_filebug_dupfinder.js	2011-04-26 13:10:29 +0000
@@ -0,0 +1,343 @@
+YUI({
+    base: '../../../../canonical/launchpad/icing/yui/',
+    filter: 'raw', combine: false, fetchCSS: false
+    }).use('test', 'console', 'lp.bugs.filebug_dupefinder',
+        'node-event-simulate', function(Y) {
+
+var suite = new Y.Test.Suite("lp.bugs.filebug_dupefinder Tests");
+var module = Y.lp.bugs.filebug_dupefinder;
+
+/*
+ * A wrapper for the Y.Event.simulate() function.  The wrapper accepts
+ * CSS selectors and Node instances instead of raw nodes.
+ */
+function simulate(widget, selector, evtype, options) {
+    var node_to_use = widget;
+    if (selector !== undefined) {
+        node_to_use = widget.one(selector);
+    }
+    var rawnode = Y.Node.getDOMNode(node_to_use);
+    Y.Event.simulate(rawnode, evtype, options);
+}
+
+/**
+ * A stub io handler.
+ */
+function IOStub(test_case){
+    if (!(this instanceof IOStub)) {
+        throw new Error("Constructor called as a function");
+    }
+    this.calls = [];
+    this.io = function(url, config) {
+        this.calls.push(url);
+        var response = {responseText: ''};
+        // We may have been passed text to use in the response.
+        if (Y.Lang.isValue(arguments.callee.responseText)) {
+            response.responseText = arguments.callee.responseText;
+        }
+        // We currently only support calling the success handler.
+        config.on.success(undefined, response, arguments.callee.args);
+        // After calling the handler, resume the test.
+        if (Y.Lang.isFunction(arguments.callee.doAfter)) {
+            test_case.resume(arguments.callee.doAfter);
+        }
+    };
+}
+
+suite.add(new Y.Test.Case({
+    name: 'Test filebug form manipulation.',
+
+    setUp: function() {
+        // Reset the HTML elements.
+        Y.one("#possible-duplicates").set('innerHTML', '');
+        Y.one(Y.DOM.byId('field.comment')).set('value', '');
+        Y.one(Y.DOM.byId('field.search')).set('value', '');
+        Y.one(Y.DOM.byId('field.title')).set('value', '');
+        Y.one('#filebug-form-container').addClass('transparent')
+                .setStyles({opacity: '0', display: 'none'});
+        Y.one(Y.DOM.byId('field.product')).set('value', 'foo');
+        module.setup_product_urls();
+
+        this.config = {};
+        this.config.yio = new IOStub(this);
+        module.setup_config(this.config);
+    },
+
+    tearDown: function() {
+        Y.one('#filebug-form').set(
+                'action', 'https://bugs.launchpad.dev/foo/+filebug');
+    },
+
+    /**
+     * Some helper functions
+     */
+    selectNode: function(node, selector) {
+        if (!Y.Lang.isValue(node)) {
+            node = Y.one('#test-root');
+        }
+        var node_to_use = node;
+        if (Y.Lang.isValue(selector)) {
+            node_to_use = node.one(selector);
+        }
+        return node_to_use;
+    },
+
+    assertStyleValue: function(node, selector, style, value) {
+        node = this.selectNode(node, selector);
+        Y.Assert.areEqual(value, node.getStyle(style));
+    },
+
+    assertIsVisible: function(node, selector) {
+        this.assertStyleValue(node, selector, 'display', 'block');
+    },
+
+    assertIsNotVisible: function(node, selector) {
+        this.assertStyleValue(node, selector, 'display', 'none');
+    },
+
+    assertNodeText: function(node, selector, text) {
+        node = this.selectNode(node, selector);
+        Y.Assert.areEqual(text, node.get('innerHTML'));
+    },
+
+
+    /**
+     * A user first searches for duplicate bugs. If there are no duplicates
+     * the file bug form should be visible for bug details to be entered.
+     */
+    test_no_dups_search_shows_filebug_form: function() {
+        // filebug container should not initially be visible
+        this.assertIsNotVisible(null, '#filebug-form-container');
+        var search_text = Y.one(Y.DOM.byId('field.search'));
+        search_text.set('value', 'foo');
+        var search_button = Y.one(Y.DOM.byId('field.actions.search'));
+        // The search button should initially say 'Next'
+        Y.Assert.areEqual('Next', search_button.get('value'));
+        this.config.yio.io.responseText = 'No similar bug reports.';
+        this.config.yio.io.doAfter = function() {
+            // Check the expected io calls have been made.
+            Y.ArrayAssert.itemsAreEqual(
+                ['https://bugs.launchpad.dev/foo/+filebug-show-similar?title=foo'],
+                this.config.yio.calls);
+            // filebug container should be visible after the dup search
+            this.assertIsVisible(null, '#filebug-form-container');
+            var dups_node = Y.one("#possible-duplicates");
+            this.assertNodeText(
+                    dups_node, undefined, 'No similar bug reports.');
+        };
+        simulate(search_button, undefined, 'click');
+        this.wait();
+    },
+
+    /**
+     * A user first searches for duplicate bugs. If there are duplicates
+     * the dups should be listed and the file bug form should not be visible.
+     */
+    test_dups_search_shows_dup_info: function() {
+        // filebug container should not initially be visible
+        this.assertIsNotVisible(null, '#filebug-form-container');
+        var search_text = Y.one(Y.DOM.byId('field.search'));
+        search_text.set('value', 'foo');
+        var search_button = Y.one(Y.DOM.byId('field.actions.search'));
+        this.config.yio.io.responseText = ([
+                '<img id="bug-details-expander" ',
+                'class="bug-already-reported-expander" ',
+                'src="/@@/treeCollapsed">',
+                '<input type="button" value="No, I need to report a new bug"',
+                ' name="field.bug_already_reported_as"',
+                ' id="bug-not-already-reported" style="display: block">'
+                ].join(''));
+        this.config.yio.io.doAfter = function() {
+            // filebug container should not be visible when there are dups
+            this.assertIsNotVisible(null, '#filebug-form-container');
+            // we should have a 'new bug' button
+            this.assertIsVisible(null, '#bug-not-already-reported');
+            // The search button should say 'Check again'
+            Y.Assert.areEqual('Check again', search_button.get('value'));
+        };
+        simulate(search_button, undefined, 'click');
+        this.wait();
+    },
+
+    /**
+     * A user first searches for duplicate bugs. They can start typing in some
+     * detail. They can search again for dups and their input should be
+     * retained.
+     */
+    test_dups_search_retains_user_input_when_no_dups: function() {
+        // filebug container should not initially be visible
+        this.assertIsNotVisible(null, '#filebug-form-container');
+        var search_text = Y.one(Y.DOM.byId('field.search'));
+        search_text.set('value', 'foo');
+        var search_button = Y.one(Y.DOM.byId('field.actions.search'));
+        this.config.yio.io.responseText = 'No similar bug reports.';
+        this.config.yio.io.doAfter = function() {
+            var comment_text = Y.one(Y.DOM.byId('field.comment'));
+            comment_text.set('value', 'an error occurred');
+            this.config.yio.io.doAfter = function() {
+                // The user input should be retained
+                Y.Assert.areEqual(
+                    'an error occurred', comment_text.get('value'));
+            };
+            simulate(search_button, undefined, 'click');
+            this.wait();
+        };
+        simulate(search_button, undefined, 'click');
+        this.wait();
+    },
+
+    /**
+     * A user first searches for duplicate bugs and there are none.
+     * They can start typing in some detail. They can search again for dups
+     * and their input should be retained even when there are dups and they
+     * have to click the "No, this is a new bug" button.
+     */
+    test_dups_search_retains_user_input_when_dups: function() {
+        // filebug container should not initially be visible
+        this.assertIsNotVisible(null, '#filebug-form-container');
+        var search_text = Y.one(Y.DOM.byId('field.search'));
+        search_text.set('value', 'foo');
+        var search_button = Y.one(Y.DOM.byId('field.actions.search'));
+        this.config.yio.io.responseText = 'No similar bug reports.';
+        this.config.yio.io.doAfter = function() {
+            var comment_text = Y.one(Y.DOM.byId('field.comment'));
+            comment_text.set('value', 'an error occurred');
+            this.config.yio.io.responseText = ([
+                    '<img id="bug-details-expander" ',
+                    'class="bug-already-reported-expander" ',
+                    'src="/@@/treeCollapsed">',
+                    '<input type="button" value="No, I need to report a bug"',
+                    ' name="field.bug_already_reported_as"',
+                    ' id="bug-not-already-reported" style="display: block">'
+                    ].join(''));
+            this.config.yio.io.doAfter = function() {
+                var new_bug_button = Y.one('#bug-not-already-reported');
+                simulate(new_bug_button, undefined, 'click');
+                // filebug container should be visible
+                this.assertIsVisible(null, '#filebug-form-container');
+                // The user input should be retained
+                Y.Assert.areEqual(
+                    'an error occurred', comment_text.get('value'));
+            };
+            simulate(search_button, undefined, 'click');
+            this.wait();
+        };
+        simulate(search_button, undefined, 'click');
+        this.wait();
+    },
+
+    /**
+     * The filebug form url is correctly set when the page loads.
+     */
+    test_project_initial_filebug_form_action: function() {
+        Y.Assert.areEqual(
+            'https://bugs.launchpad.dev/foo/+filebug',
+            Y.one('#filebug-form').get('action'));
+    },
+
+    /**
+     * The filebug form url is correctly updated when the project changes.
+     */
+    test_project_change_filebug_form_action: function() {
+        var project = Y.one(Y.DOM.byId('field.product'));
+        project.set('value', 'bar');
+        simulate(project, undefined, 'change');
+        Y.Assert.areEqual(
+            'https://bugs.launchpad.dev/bar/+filebug',
+            Y.one('#filebug-form').get('action'));
+        var search_button = (Y.one(Y.DOM.byId('field.actions.search')));
+        Y.Assert.areEqual('Next', search_button.get('value'));
+    },
+
+    /**
+     * The extra filebug details node is correctly updated when the project
+     * changes.
+     */
+    test_project_change_extra_filebug_details: function() {
+        this.config.yio.io.responseText = 'Bug filing details <foo></foo>';
+        var project = Y.one(Y.DOM.byId('field.product'));
+        project.set('value', 'bar');
+        simulate(project, undefined, 'change');
+        // filebug container should not be visible
+        this.assertIsNotVisible(null, '#filebug-form-container');
+        Y.ArrayAssert.itemsAreEqual(
+                ['https://bugs.launchpad.dev/bar/+filebug-reporting-details'],
+                this.config.yio.calls);
+        Y.Assert.areEqual(
+            'Bug filing details <foo></foo>',
+            Y.one('#extra-filebug-details').get('textContent'));
+    },
+
+    /**
+     * A user first searches for duplicate bugs and there are none.
+     * They can start typing in some detail. They change the project and
+     * perform a new search. Their input should be retained.
+     */
+    test_project_change_retains_user_input_after_dups_serach: function() {
+        // filebug container should not initially be visible
+        this.assertIsNotVisible(null, '#filebug-form-container');
+        var search_text = Y.one(Y.DOM.byId('field.search'));
+        search_text.set('value', 'foo');
+        var search_button = Y.one(Y.DOM.byId('field.actions.search'));
+        this.config.yio.io.responseText = 'No similar bug reports.';
+        this.config.yio.io.doAfter = function() {
+            var comment_text = Y.one(Y.DOM.byId('field.comment'));
+            comment_text.set('value', 'an error occurred');
+
+            this.config.yio.io.responseText = 'Bug filing details';
+            var project = Y.one(Y.DOM.byId('field.product'));
+            project.set('value', 'bar');
+            simulate(project, undefined, 'change');
+            // filebug container should be visible
+            this.assertIsVisible(null, '#filebug-form-container');
+
+            // Search button should day 'Check again' because we have already
+            // done a search.
+            var search_button = (Y.one(Y.DOM.byId('field.actions.search')));
+            Y.Assert.areEqual('Check again', search_button.get('value'));
+
+            this.config.yio.io.responseText = 'No similar bug reports.';
+            this.config.yio.io.doAfter = function() {
+                // filebug container should be visible
+                this.assertIsVisible(null, '#filebug-form-container');
+                // The user input should be retained
+                Y.Assert.areEqual(
+                    'an error occurred', comment_text.get('value'));
+                Y.ArrayAssert.itemsAreEqual(
+                        ['https://bugs.launchpad.dev/foo/+filebug-show-similar?title=foo',
+                        'https://bugs.launchpad.dev/bar/+filebug-reporting-details',
+                        'https://bugs.launchpad.dev/bar/+filebug-show-similar?title=foo'],
+                        this.config.yio.calls);
+            };
+            simulate(search_button, undefined, 'click');
+            this.wait();
+        };
+        simulate(search_button, undefined, 'click');
+        this.wait();
+    }
+
+}));
+
+var handle_complete = function(data) {
+    status_node = Y.Node.create(
+        '<p id="complete">Test status: complete</p>');
+    Y.one('body').appendChild(status_node);
+    };
+Y.Test.Runner.on('complete', handle_complete);
+Y.Test.Runner.add(suite);
+
+var console = new Y.Console({newestOnTop: false});
+console.render('#log');
+
+// Configure the javascript module under test. In production, the
+// setup_dupe_finder() is called from the page template. We need to pass in
+// a stub io handler here so that the XHR call made during set up is ignored.
+var config = {};
+config.yio = new IOStub();
+module.setup_config(config);
+module.setup_dupe_finder();
+
+Y.on('domready', function(e) {
+    Y.Test.Runner.run();
+});
+});

=== modified file 'lib/lp/bugs/stories/bug-privacy/xx-presenting-private-bugs.txt'
--- lib/lp/bugs/stories/bug-privacy/xx-presenting-private-bugs.txt	2011-03-23 16:28:51 +0000
+++ lib/lp/bugs/stories/bug-privacy/xx-presenting-private-bugs.txt	2011-04-26 13:10:29 +0000
@@ -32,7 +32,7 @@
 have the full message:
 
     >>> browser.open("http://bugs.launchpad.dev/firefox/+filebug";)
-    >>> browser.getControl('Summary').value = (
+    >>> browser.getControl('Summary', index=0).value = (
     ...     'Firefox crashes when I change the default route')
     >>> browser.getControl('Continue').click()
 

=== modified file 'lib/lp/bugs/stories/bugs/xx-add-comment-distribution-no-current-release.txt'
--- lib/lp/bugs/stories/bugs/xx-add-comment-distribution-no-current-release.txt	2009-08-28 14:31:15 +0000
+++ lib/lp/bugs/stories/bugs/xx-add-comment-distribution-no-current-release.txt	2011-04-26 13:10:29 +0000
@@ -4,7 +4,7 @@
 it's still possible to add comments to the bug.
 
     >>> user_browser.open('http://launchpad.dev/gentoo/+filebug')
-    >>> user_browser.getControl('Summary').value = 'Test bug'
+    >>> user_browser.getControl('Summary', index=0).value = 'Test bug'
     >>> user_browser.getControl('Continue').click()
     >>> user_browser.getControl('Further information').value = 'A test bug.'
     >>> user_browser.getControl('Submit Bug Report').click()

=== modified file 'lib/lp/bugs/stories/distribution/xx-distribution-filebug-error-handling.txt'
--- lib/lp/bugs/stories/distribution/xx-distribution-filebug-error-handling.txt	2009-07-27 10:04:13 +0000
+++ lib/lp/bugs/stories/distribution/xx-distribution-filebug-error-handling.txt	2011-04-26 13:10:29 +0000
@@ -4,7 +4,7 @@
     >>> browser = setupBrowser(auth="Basic foo.bar@xxxxxxxxxxxxx:test")
     >>> browser.open("http://launchpad.dev/ubuntu/+filebug";)
 
-    >>> browser.getControl(name="field.title").value = "test"
+    >>> browser.getControl(name="field.title", index=0).value = "test"
     >>> browser.getControl('Continue').click()
 
     >>> browser.getControl(name="field.comment").value = "test"
@@ -21,7 +21,7 @@
 
     >>> browser.open("http://launchpad.dev/ubuntu/+filebug";)
 
-    >>> browser.getControl(name="field.title").value = "test"
+    >>> browser.getControl(name="field.title", index=0).value = "test"
     >>> browser.getControl('Continue').click()
 
     >>> browser.getControl(name="field.comment").value = "test"

=== modified file 'lib/lp/bugs/stories/guided-filebug/xx-bug-reporting-guidelines.txt'
--- lib/lp/bugs/stories/guided-filebug/xx-bug-reporting-guidelines.txt	2011-02-01 23:50:13 +0000
+++ lib/lp/bugs/stories/guided-filebug/xx-bug-reporting-guidelines.txt	2011-04-26 13:10:29 +0000
@@ -42,6 +42,16 @@
     ...     print extract_text(find_tags_by_class(
     ...         user_browser.contents, 'informational message')[0])
 
+    >>> def print_visible_guidelines(context_path, guidelines):
+    ...     result = guidelines.findParents(id='filebug-form-container')
+    ...     style_attrs = []
+    ...     if result:
+    ...         filebug_form_container = result[0]
+    ...         style_attrs = [item.strip()
+    ...             for item in filebug_form_container['style'].split(";")]
+    ...     if (result and not 'display: none' in style_attrs):
+    ...         print 'Found %s guidelines: %s' % (context_path, guidelines)
+
     >>> for context_name, context_path, view in contexts:
     ...     filebug_url = (
     ...         'http://launchpad.dev/%s/+filebug' % (context_path,))
@@ -49,7 +59,7 @@
     ...     guidelines = find_tag_by_id(
     ...         user_browser.contents, 'bug-reporting-guidelines')
     ...     if guidelines is not None:
-    ...         print 'Found %s guidelines: %s' % (context_path, guidelines)
+    ...         print_visible_guidelines(context_path, guidelines)
 
 But they are displayed once you've got to the step of entering a bug
 description.
@@ -58,7 +68,8 @@
     ...     filebug_url = (
     ...         'http://launchpad.dev/%s/+filebug' % (context_path,))
     ...     user_browser.open(filebug_url)
-    ...     user_browser.getControl('Summary').value = "It doesn't work"
+    ...     user_browser.getControl('Summary', index=0).value = (
+    ...         "It doesn't work")
     ...     user_browser.getControl('Continue').click()
     ...     user_browser.getControl('Further information').value = (
     ...         'please help!')
@@ -111,7 +122,7 @@
 
     >>> user_browser.open(
     ...     'http://launchpad.dev/ubuntu/warty/+filebug')
-    >>> user_browser.getControl('Summary').value = "It doesn't work"
+    >>> user_browser.getControl('Summary', index=0).value = "It doesn't work"
     >>> user_browser.getControl('Continue').click()
     >>> print extract_text(find_tag_by_id(
     ...     user_browser.contents, 'bug-reporting-guidelines'))
@@ -149,7 +160,7 @@
 
     >>> user_browser.open(
     ...     'http://launchpad.dev/ubuntu/+filebug')
-    >>> user_browser.getControl('Summary').value = "It doesn't work"
+    >>> user_browser.getControl('Summary', index=0).value = "It doesn't work"
     >>> user_browser.getControl('Continue').click()
     >>> print extract_text(find_tag_by_id(
     ...     user_browser.contents, 'bug-reporting-guidelines'))

=== modified file 'lib/lp/bugs/stories/guided-filebug/xx-bug-reporting-tools.txt'
--- lib/lp/bugs/stories/guided-filebug/xx-bug-reporting-tools.txt	2010-02-23 15:10:04 +0000
+++ lib/lp/bugs/stories/guided-filebug/xx-bug-reporting-tools.txt	2011-04-26 13:10:29 +0000
@@ -93,9 +93,9 @@
 After the user fills in the summary and click on the button, we'll still
 be on the same URL, with the token present.
 
-    >>> user_browser.getControl('Summary').value
+    >>> user_browser.getControl('Summary', index=0).value
     ''
-    >>> user_browser.getControl('Summary').value = 'A new bug'
+    >>> user_browser.getControl('Summary', index=0).value = 'A new bug'
     >>> user_browser.getControl('Continue').click()
     >>> user_browser.url == filebug_url
     True
@@ -167,14 +167,14 @@
     ...    'http://launchpad.dev/ubuntu/+source/mozilla-firefox/+filebug/'
     ...     '%s' % blob_token)
 
-    >>> user_browser.getControl('Summary').value
+    >>> user_browser.getControl('Summary', index=0).value
     'Initial bug summary'
 
 The user can of course change the summary if he wants to.
 
-    >>> user_browser.getControl('Summary').value = 'Another summary'
+    >>> user_browser.getControl('Summary', index=0).value = 'Another summary'
     >>> user_browser.getControl('Continue').click()
-    >>> user_browser.getControl('Summary').value
+    >>> user_browser.getControl('Summary', index=0).value
     'Another summary'
 
 === Tags ===
@@ -194,7 +194,7 @@
     >>> user_browser.open(
     ...    'http://launchpad.dev/ubuntu/+source/mozilla-firefox/'
     ...     '+filebug/%s' % blob_token)
-    >>> user_browser.getControl('Summary').value = 'Another summary'
+    >>> user_browser.getControl('Summary', index=0).value = 'Another summary'
     >>> user_browser.getControl('Continue').click()
 
     >>> user_browser.getControl('Tags').value
@@ -203,7 +203,7 @@
 The user can of course change the tags if he wants.
 
     >>> user_browser.getControl('Tags').value = 'bar baz'
-    >>> user_browser.getControl('Summary').value = 'Bug Summary'
+    >>> user_browser.getControl('Summary', index=0).value = 'Bug Summary'
     >>> user_browser.getControl('Further information').value = (
     ...     'Bug description.')
     >>> user_browser.getControl('Submit Bug Report').click()
@@ -220,7 +220,7 @@
     >>> user_browser.open(
     ...    'http://launchpad.dev/ubuntu/+source/mozilla-firefox/+filebug/'
     ...     '%s' % blob_token)
-    >>> user_browser.getControl('Summary').value = 'Bug Summary'
+    >>> user_browser.getControl('Summary', index=0).value = 'Bug Summary'
     >>> user_browser.getControl('Continue').click()
 
     >>> user_browser.getControl('Further information').value = (
@@ -264,7 +264,7 @@
     ...    'http://launchpad.dev/ubuntu/+source/mozilla-firefox/+filebug/'
     ...     '%s' % blob_token)
     >>> user_browser.open(filebug_url)
-    >>> user_browser.getControl('Summary').value = 'Another summary'
+    >>> user_browser.getControl('Summary', index=0).value = 'Another summary'
     >>> user_browser.getControl('Continue').click()
     >>> user_browser.getControl('Further information').value = (
     ...     'A bug description.')

=== modified file 'lib/lp/bugs/stories/guided-filebug/xx-displaying-similar-bugs.txt'
--- lib/lp/bugs/stories/guided-filebug/xx-displaying-similar-bugs.txt	2010-04-16 15:06:55 +0000
+++ lib/lp/bugs/stories/guided-filebug/xx-displaying-similar-bugs.txt	2011-04-26 13:10:29 +0000
@@ -28,7 +28,7 @@
 the summary entered (assuming some are found).
 
     >>> user_browser.open("http://bugs.launchpad.dev/firefox/+filebug";)
-    >>> user_browser.getControl("Summary").value = 'a'
+    >>> user_browser.getControl("Summary", index=0).value = 'a'
     >>> user_browser.getControl("Continue").click()
 
 All of the similar bugs have relevant bugtasks:
@@ -71,7 +71,7 @@
 Then, if we match bug 1, only basic details of bug 8 are displayed:
 
     >>> user_browser.open("http://bugs.launchpad.dev/firefox/+filebug";)
-    >>> user_browser.getControl("Summary").value = 'reflow'
+    >>> user_browser.getControl("Summary", index=0).value = 'reflow'
     >>> user_browser.getControl("Continue").click()
 
     >>> print_similar_bugs(user_browser.contents)
@@ -94,8 +94,8 @@
 filing a bug against a project:
 
     >>> user_browser.open("http://bugs.launchpad.dev/gnome/+filebug";)
-    >>> user_browser.getControl("Project").value = ['evolution']
-    >>> user_browser.getControl("Summary").value = 'a'
+    >>> user_browser.getControl("Project", index=0).value = ['evolution']
+    >>> user_browser.getControl("Summary", index=0).value = 'a'
     >>> user_browser.getControl("Continue").click()
 
     >>> print_similar_bugs(user_browser.contents)
@@ -113,8 +113,8 @@
         >>> user_browser.getControl('Change').click()
 
         >>> user_browser.open("http://bugs.launchpad.dev/gnome/+filebug";)
-        >>> user_browser.getControl("Project").value = ['evolution']
-        >>> user_browser.getControl("Summary").value = 'a'
+        >>> user_browser.getControl("Project", index=0).value = ['evolution']
+        >>> user_browser.getControl("Summary", index=0).value = 'a'
         >>> user_browser.getControl("Continue").click()
 
         >>> print_similar_bugs(user_browser.contents)
@@ -130,7 +130,7 @@
     find that their bug has already been reported.
 
         >>> user_browser.open("http://launchpad.dev/ubuntu/+filebug";)
-        >>> user_browser.getControl("Summary").value = 'crashes'
+        >>> user_browser.getControl("Summary", index=0).value = 'crashes'
         >>> user_browser.getControl("Continue").click()
 
         >>> print_similar_bugs(user_browser.contents)
@@ -148,7 +148,7 @@
         >>> user_browser.getControl('Change').click()
 
         >>> user_browser.open("http://launchpad.dev/ubuntu/+filebug";)
-        >>> user_browser.getControl("Summary").value = 'crashes'
+        >>> user_browser.getControl("Summary", index=0).value = 'crashes'
         >>> user_browser.getControl("Continue").click()
 
         >>> print_similar_bugs(user_browser.contents)
@@ -167,7 +167,7 @@
         >>> user_browser.open(
         ...     "http://launchpad.dev/ubuntu/+source/";
         ...     "mozilla-firefox/+filebug")
-        >>> user_browser.getControl("Summary").value = 'a'
+        >>> user_browser.getControl("Summary", index=0).value = 'a'
         >>> user_browser.getControl("Continue").click()
 
         >>> print_similar_bugs(user_browser.contents)
@@ -186,7 +186,7 @@
         >>> user_browser.open(
         ...     "http://launchpad.dev/ubuntu/+source/";
         ...     "mozilla-firefox/+filebug")
-        >>> user_browser.getControl("Summary").value = 'a'
+        >>> user_browser.getControl("Summary", index=0).value = 'a'
         >>> user_browser.getControl("Continue").click()
 
         >>> print_similar_bugs(user_browser.contents)

=== modified file 'lib/lp/bugs/stories/guided-filebug/xx-distro-guided-filebug-tags.txt'
--- lib/lp/bugs/stories/guided-filebug/xx-distro-guided-filebug-tags.txt	2010-06-04 17:47:34 +0000
+++ lib/lp/bugs/stories/guided-filebug/xx-distro-guided-filebug-tags.txt	2011-04-26 13:10:29 +0000
@@ -9,7 +9,8 @@
 
     >>> user_browser.open(
     ...    'http://bugs.launchpad.dev/ubuntu/+filebug?field.tags=new-package')
-    >>> user_browser.getControl('Summary').value = 'Please package CoolApp'
+    >>> user_browser.getControl('Summary', index=0).value = (
+    ...    'Please package CoolApp')
     >>> user_browser.getControl('Continue').click()
 
 On the next page, possible duplicates are displayed as ususal. No

=== modified file 'lib/lp/bugs/stories/guided-filebug/xx-distro-guided-filebug.txt'
--- lib/lp/bugs/stories/guided-filebug/xx-distro-guided-filebug.txt	2010-08-02 02:33:53 +0000
+++ lib/lp/bugs/stories/guided-filebug/xx-distro-guided-filebug.txt	2011-04-26 13:10:29 +0000
@@ -11,7 +11,7 @@
 database to find candidates from, our sample data has no real near-fits,
 see bug 612384 for the overall effort to provide a sensible search facility.
 
-    >>> user_browser.getControl("Summary").value = (
+    >>> user_browser.getControl("Summary", index=0).value = (
     ...     "Thunderbird crashes opening")
     >>> user_browser.getControl("Continue").click()
 
@@ -41,7 +41,7 @@
     # We should use goBack() here but can't because of bug #98372:
     # zope.testbrowser truncates document content after goBack().
     >>> user_browser.open("http://launchpad.dev/ubuntu/+filebug";)
-    >>> user_browser.getControl("Summary").value = (
+    >>> user_browser.getControl("Summary", index=0).value = (
     ...     "Thunderbird crashes when opening large e-mails")
     >>> user_browser.getControl("Continue").click()
 
@@ -49,7 +49,7 @@
 
     >>> user_browser.getControl(name="packagename_option").value = ["choose"]
     >>> user_browser.getControl("In what package").value = "mozilla-firefox"
-    >>> user_browser.getControl("Summary").value = "a new ubuntu bug"
+    >>> user_browser.getControl("Summary", index=0).value = "a new ubuntu bug"
     >>> user_browser.getControl("Further information").value = "test"
 
 The comment field ("Further information") is not optional when
@@ -83,7 +83,7 @@
 
 Submitting a distinctive bug title...
 
-    >>> user_browser.getControl("Summary").value = (
+    >>> user_browser.getControl("Summary", index=0).value = (
     ...     "Frobnobulator emits weird noises.")
     >>> user_browser.getControl("Continue").click()
 

=== modified file 'lib/lp/bugs/stories/guided-filebug/xx-distro-sourcepackage-guided-filebug.txt'
--- lib/lp/bugs/stories/guided-filebug/xx-distro-sourcepackage-guided-filebug.txt	2009-08-18 11:04:38 +0000
+++ lib/lp/bugs/stories/guided-filebug/xx-distro-sourcepackage-guided-filebug.txt	2011-04-26 13:10:29 +0000
@@ -6,7 +6,7 @@
     >>> user_browser.open(
     ...     "http://launchpad.dev/ubuntu/+source/mozilla-firefox/";
     ...     "+filebug")
-    >>> user_browser.getControl(name="field.title").value = (
+    >>> user_browser.getControl(name="field.title", index=0).value = (
     ...     "Thunderbird crashes when opening large e-mails")
     >>> user_browser.getControl("Continue").click()
 

=== modified file 'lib/lp/bugs/stories/guided-filebug/xx-filebug-attachments.txt'
--- lib/lp/bugs/stories/guided-filebug/xx-filebug-attachments.txt	2010-07-29 11:20:47 +0000
+++ lib/lp/bugs/stories/guided-filebug/xx-filebug-attachments.txt	2011-04-26 13:10:29 +0000
@@ -11,7 +11,7 @@
 guided filebug form.
 
     >>> user_browser.open('http://bugs.launchpad.dev/firefox/+filebug')
-    >>> user_browser.getControl('Summary').value = ('A totally new '
+    >>> user_browser.getControl('Summary', index=0).value = ('A totally new '
     ...     'bug with attachments')
     >>> user_browser.getControl('Continue').click()
     >>> user_browser.getControl('Further information').value = (

=== modified file 'lib/lp/bugs/stories/guided-filebug/xx-filebug-tags.txt'
--- lib/lp/bugs/stories/guided-filebug/xx-filebug-tags.txt	2010-06-04 17:47:34 +0000
+++ lib/lp/bugs/stories/guided-filebug/xx-filebug-tags.txt	2011-04-26 13:10:29 +0000
@@ -10,7 +10,7 @@
 
     >>> user_browser.open(
     ...     'http://bugs.launchpad.dev/firefox/+filebug')
-    >>> user_browser.getControl('Summary').value = "Bug with tags"
+    >>> user_browser.getControl('Summary', index=0).value = "Bug with tags"
     >>> user_browser.getControl('Continue').click()
     >>> user_browser.getControl('Tags').value = 'foo bar'
     >>> user_browser.getControl('Further information').value = (
@@ -35,7 +35,7 @@
     >>> user_browser.open(
     ...     'http://bugs.launchpad.dev/firefox/'
     ...     '+filebug?field.tags=foo+bar')
-    >>> user_browser.getControl('Summary').value = "Bug with tags"
+    >>> user_browser.getControl('Summary', index=0).value = "Bug with tags"
     >>> user_browser.getControl('Continue').click()
     >>> user_browser.getControl('Tags').value
     'bar foo'

=== modified file 'lib/lp/bugs/stories/guided-filebug/xx-options-for-bug-supervisors.txt'
--- lib/lp/bugs/stories/guided-filebug/xx-options-for-bug-supervisors.txt	2009-08-27 13:45:25 +0000
+++ lib/lp/bugs/stories/guided-filebug/xx-options-for-bug-supervisors.txt	2011-04-26 13:10:29 +0000
@@ -7,7 +7,7 @@
 Users who are not bug supervisors do not see any of these options:
 
     >>> user_browser.open('http://bugs.launchpad.dev/firefox/+filebug')
-    >>> user_browser.getControl('Summary').value = "Bug"
+    >>> user_browser.getControl('Summary', index=0).value = "Bug"
     >>> user_browser.getControl('Continue').click()
 
     >>> user_browser.getControl('Status')
@@ -38,7 +38,7 @@
     >>> admin_browser.getControl('Change').click()
 
     >>> user_browser.open('http://bugs.launchpad.dev/firefox/+filebug')
-    >>> user_browser.getControl('Summary').value = "Bug"
+    >>> user_browser.getControl('Summary', index=0).value = "Bug"
     >>> user_browser.getControl('Continue').click()
 
     >>> user_browser.getControl('Status')
@@ -56,7 +56,7 @@
     >>> from lp.bugs.tests.bug import print_bug_affects_table
 
     >>> user_browser.open('http://bugs.launchpad.dev/firefox/+filebug')
-    >>> user_browser.getControl('Summary').value = "Bug"
+    >>> user_browser.getControl('Summary', index=0).value = "Bug"
     >>> user_browser.getControl('Continue').click()
     >>> user_browser.getControl('Further information').value = "Blah"
 

=== modified file 'lib/lp/bugs/stories/guided-filebug/xx-product-guided-filebug.txt'
--- lib/lp/bugs/stories/guided-filebug/xx-product-guided-filebug.txt	2011-02-11 15:14:37 +0000
+++ lib/lp/bugs/stories/guided-filebug/xx-product-guided-filebug.txt	2011-04-26 13:10:29 +0000
@@ -10,22 +10,26 @@
 
 If no title is entered, the user is asked to supply one.
 
-    >>> user_browser.getControl('Summary').value
+    >>> user_browser.getControl('Summary', index=0).value
     ''
 
     >>> user_browser.getControl("Continue").click()
     >>> user_browser.url
     'http://bugs.launchpad.dev/firefox/+filebug'
 
-    >>> for message in find_tags_by_class(user_browser.contents, 'message'):
+    >>> top_portlet = first_tag_by_class(
+    ...     user_browser.contents, 'top-portlet')
+    >>> for message in top_portlet.findAll(attrs={'class': 'error message'}):
     ...     print message.renderContents()
     There is 1 error.
+    >>> for message in top_portlet.findAll(attrs={'class': 'message'}):
+    ...     print message.renderContents()
     A summary is required.
 
 The user fills in some keywords, and clicks a button to search existing
 bugs.
 
-    >>> user_browser.getControl("Summary").value = (
+    >>> user_browser.getControl("Summary", index=0).value = (
     ...     "SVG images are broken")
     >>> user_browser.getControl("Continue").click()
 
@@ -52,7 +56,7 @@
 will be displayed as well.
 
     >>> user_browser.getControl("Further information").value = "not empty"
-    >>> user_browser.getControl("Summary").value = ''
+    >>> user_browser.getControl("Summary", index=0).value = ''
     >>> user_browser.getControl("Submit Bug Report").click()
     >>> print user_browser.url
     http://bugs.launchpad.dev/firefox/+filebug
@@ -68,7 +72,7 @@
 
 With both values set, the bug is created.
 
-    >>> user_browser.getControl("Summary").value = "a brand new bug"
+    >>> user_browser.getControl("Summary", index=0).value = "a brand new bug"
     >>> user_browser.getControl("Further information").value = "test"
     >>> user_browser.getControl("Submit Bug Report").click()
 
@@ -84,7 +88,7 @@
 "me too" vote.
 
     >>> user_browser.open("http://bugs.launchpad.dev/firefox/+filebug";)
-    >>> user_browser.getControl("Summary").value = (
+    >>> user_browser.getControl("Summary", index=0).value = (
     ...     "SVG images are broken")
     >>> user_browser.getControl("Continue").click()
 
@@ -112,7 +116,7 @@
 testing we'll test it here, too.
 
     >>> user_browser.open("http://bugs.launchpad.dev/firefox/+filebug";)
-    >>> user_browser.getControl("Summary").value = (
+    >>> user_browser.getControl("Summary", index=0).value = (
     ...     "SVG images are broken")
     >>> user_browser.getControl("Continue").click()
 
@@ -140,7 +144,7 @@
 
 Submitting some distinctive details...
 
-    >>> user_browser.getControl('Summary').value = (
+    >>> user_browser.getControl('Summary', index=0).value = (
     ...     "Frankenzombulon reanimated neighbour's dead pet")
     >>> user_browser.getControl('Continue').click()
 

=== modified file 'lib/lp/bugs/stories/guided-filebug/xx-project-guided-filebug.txt'
--- lib/lp/bugs/stories/guided-filebug/xx-project-guided-filebug.txt	2010-12-22 00:07:36 +0000
+++ lib/lp/bugs/stories/guided-filebug/xx-project-guided-filebug.txt	2011-04-26 13:10:29 +0000
@@ -13,14 +13,15 @@
 that it also asks for a Product. Only Products that are using Bugs are
 shown in the list of options.
 
-    >>> user_browser.getControl('Project').options
+    >>> user_browser.getControl('Project', index=0).options
     ['evolution']
 
 After we selected a product and entered a summary, we get presented with
 a list of possible duplicates.
 
-    >>> user_browser.getControl('Project').value = ['evolution']
-    >>> user_browser.getControl('Summary').value = 'Evolution crashes'
+    >>> user_browser.getControl('Project', index=0).value = ['evolution']
+    >>> user_browser.getControl('Summary', index=0).value = (
+    ...     'Evolution crashes')
     >>> user_browser.getControl('Continue').click()
 
     >>> print find_main_content(user_browser.contents).renderContents()
@@ -47,8 +48,9 @@
 "me too" vote.
 
     >>> user_browser.open("http://bugs.launchpad.dev/gnome/+filebug";)
-    >>> user_browser.getControl('Project').value = ['evolution']
-    >>> user_browser.getControl('Summary').value = 'Evolution crashes'
+    >>> user_browser.getControl('Project', index=0).value = ['evolution']
+    >>> user_browser.getControl('Summary', index=0).value = (
+    ...     'Evolution crashes')
     >>> user_browser.getControl('Continue').click()
 
 As before, we get a list of similar bugs to choose from, including the
@@ -84,8 +86,8 @@
 
 Submitting some distinctive details...
 
-    >>> user_browser.getControl('Project').value = ['evolution']
-    >>> user_browser.getControl('Summary').value = (
+    >>> user_browser.getControl('Project', index=0).value = ['evolution']
+    >>> user_browser.getControl('Summary', index=0).value = (
     ...     'Faznambutron dumps core unless clenching')
     >>> user_browser.getControl('Continue').click()
 

=== modified file 'lib/lp/bugs/stories/guided-filebug/xx-sorting-by-relevance.txt'
--- lib/lp/bugs/stories/guided-filebug/xx-sorting-by-relevance.txt	2010-07-25 14:39:52 +0000
+++ lib/lp/bugs/stories/guided-filebug/xx-sorting-by-relevance.txt	2011-04-26 13:10:29 +0000
@@ -12,7 +12,7 @@
 
     >>> user_browser.open("http://launchpad.dev/products/firefox/+filebug";)
 
-    >>> user_browser.getControl("Summary").value = (
+    >>> user_browser.getControl("Summary", index=0).value = (
     ...     "Firefox does not support complex SVG images")
     >>> user_browser.getControl("Continue").click()
 
@@ -27,7 +27,7 @@
 
     >>> user_browser.open("http://launchpad.dev/products/firefox/+filebug";)
 
-    >>> user_browser.getControl("Summary").value = (
+    >>> user_browser.getControl("Summary", index=0).value = (
     ...     "Reflow problems with SVG")
     >>> user_browser.getControl("Continue").click()
 

=== modified file 'lib/lp/bugs/stories/standalone/xx-filebug-package-chooser-radio-buttons.txt'
--- lib/lp/bugs/stories/standalone/xx-filebug-package-chooser-radio-buttons.txt	2011-02-11 21:41:34 +0000
+++ lib/lp/bugs/stories/standalone/xx-filebug-package-chooser-radio-buttons.txt	2011-04-26 13:10:29 +0000
@@ -6,7 +6,7 @@
 will use the advanced filebug form to skip searching for dupes.
 
     >>> user_browser.open("http://launchpad.dev/ubuntu/+filebug";)
-    >>> user_browser.getControl('Summary').value = 'Bug Summary'
+    >>> user_browser.getControl('Summary', index=0).value = 'Bug Summary'
     >>> user_browser.getControl('Continue').click()
 
     >>> print user_browser.getControl(name="packagename_option").value
@@ -34,7 +34,7 @@
 
     >>> user_browser.open(
     ...     "http://launchpad.dev/ubuntu/+source/mozilla-firefox/+filebug";)
-    >>> user_browser.getControl('Summary').value = 'Bug Summary'
+    >>> user_browser.getControl('Summary', index=0).value = 'Bug Summary'
     >>> user_browser.getControl('Continue').click()
 
     >>> print user_browser.getControl(name="packagename_option").value

=== modified file 'lib/lp/bugs/stories/upstream-bugprivacy/10-file-private-upstream-bug.txt'
--- lib/lp/bugs/stories/upstream-bugprivacy/10-file-private-upstream-bug.txt	2011-03-23 16:28:51 +0000
+++ lib/lp/bugs/stories/upstream-bugprivacy/10-file-private-upstream-bug.txt	2011-04-26 13:10:29 +0000
@@ -3,7 +3,7 @@
 
     >>> browser = setupBrowser(auth="Basic foo.bar@xxxxxxxxxxxxx:test")
     >>> browser.open("http://localhost:9000/firefox/+filebug";)
-    >>> browser.getControl('Summary').value = (
+    >>> browser.getControl('Summary', index=0).value = (
     ...     "this is a newly created private bug")
     >>> browser.getControl("Continue").click()
 
@@ -74,7 +74,7 @@
     >>> browser.getControl("Change").click()
 
     >>> browser.open("http://localhost:9000/firefox/+filebug";)
-    >>> browser.getControl('Summary').value = (
+    >>> browser.getControl('Summary', index=0).value = (
     ...     "this is a newly created private bug")
     >>> browser.getControl("Continue").click()
 

=== added file 'lib/lp/bugs/templates/bugtarget-filebug-guidelines.pt'
--- lib/lp/bugs/templates/bugtarget-filebug-guidelines.pt	1970-01-01 00:00:00 +0000
+++ lib/lp/bugs/templates/bugtarget-filebug-guidelines.pt	2011-04-26 13:10:29 +0000
@@ -0,0 +1,51 @@
+<tal:root
+        xmlns:tal="http://xml.zope.org/namespaces/tal";
+        xmlns:metal="http://xml.zope.org/namespaces/metal";
+        xmlns:i18n="http://xml.zope.org/namespaces/i18n";
+        >
+
+  <tr id="extra-filebug-details"><td colspan="2" width="100%"><table><tbody>
+  <tr>
+    <metal:bug_reporting_guidelines
+            use-macro="context/@@+filebug-macros/bug_reporting_guidelines" />
+  </tr>
+
+  <tr tal:define="security_context view/getMainContext">
+    <td colspan="2" width="100%"
+        tal:define="widget nocall: view/widgets/security_related|nothing"
+        tal:condition="widget">
+      <table>
+        <tbody>
+          <tr>
+            <td>
+              <input type="checkbox" tal:replace="structure widget" />
+            </td>
+            <td>
+              <label tal:attributes="for widget/name">
+                This bug is a security vulnerability
+              </label>
+              <div tal:define="security_contact security_context/security_contact|nothing">
+                <tal:security-contact tal:condition="security_contact">
+                  The security contact for
+                  <tal:security-context content="security_context/displayname" />,
+                  <a tal:replace="structure security_contact/fmt:link" />,
+                  will be notified.
+                </tal:security-contact>
+                <tal:maintainer condition="not:security_contact"
+                                define="maintainer security_context/owner">
+                  The maintainer of
+                  <tal:security-context content="security_context/displayname">
+                    Mozilla Firefox</tal:security-context>,
+                  <a tal:replace="structure maintainer/fmt:link">
+                    Sample Person</a>,
+                  will be notified.
+                </tal:maintainer>
+              </div>
+            </td>
+          </tr>
+        </tbody>
+      </table>
+    </td>
+  </tr>
+  </tbody></table></td></tr>
+</tal:root>

=== modified file 'lib/lp/bugs/templates/bugtarget-filebug-search.pt'
--- lib/lp/bugs/templates/bugtarget-filebug-search.pt	2011-04-05 16:05:15 +0000
+++ lib/lp/bugs/templates/bugtarget-filebug-search.pt	2011-04-26 13:10:29 +0000
@@ -125,7 +125,12 @@
 
         <div id="possible-duplicates" style="text-align: left;">
         </div>
-        <div id="filebug-form-container" style="display: none;">
+        <div tal:condition="context/enable_bugfiling_duplicate_search"
+             id="filebug-form-container" class="transparent" style="opacity: 0; display: none">
+          <tal:filebug-form define="launchpad_form_id string:filebug-form">
+            <metal:display-similar-bugs
+                use-macro="context/@@+filebug-macros/inline-filebug-form" />
+          </tal:filebug-form>
         </div>
 
         <p style="display: none">

=== modified file 'lib/lp/bugs/templates/bugtarget-macros-filebug.pt'
--- lib/lp/bugs/templates/bugtarget-macros-filebug.pt	2010-12-16 15:01:38 +0000
+++ lib/lp/bugs/templates/bugtarget-macros-filebug.pt	2011-04-26 13:10:29 +0000
@@ -44,10 +44,12 @@
     </td>
   </tr>
 
-  <tal:product tal:define="widget nocall:view/widgets/product|nothing"
-               tal:condition="widget">
-    <metal:widget use-macro="context/@@launchpad_form/widget_row" />
-  </tal:product>
+  <noscript>
+    <tal:product tal:define="widget nocall:view/widgets/product|nothing"
+                 tal:condition="widget">
+      <metal:widget use-macro="context/@@launchpad_form/widget_row" />
+    </tal:product>
+  </noscript>
 
   <metal:summary tal:define="widget nocall:view/widgets/title">
     <metal:row use-macro="context/@@launchpad_form/widget_row" />
@@ -57,46 +59,8 @@
     <metal:row use-macro="context/@@launchpad_form/widget_row" />
   </metal:description>
 
-  <metal:bug_reporting_guidelines
-      use-macro="context/@@+filebug-macros/bug_reporting_guidelines" />
-
-  <tr tal:define="security_context view/getSecurityContext">
-    <td colspan="2" width="100%"
-        tal:define="widget nocall: view/widgets/security_related">
-      <table>
-        <tbody>
-          <tr>
-            <td>
-              <input type="checkbox" tal:replace="structure widget" />
-            </td>
-            <td>
-              <label tal:attributes="for widget/name">
-                This bug is a security vulnerability
-              </label>
-              <div tal:condition="view/can_decide_security_contact"
-                   tal:define="security_contact security_context/security_contact|nothing">
-                <tal:security-contact tal:condition="security_contact">
-                  The security contact for
-                  <tal:security-context content="security_context/displayname" />,
-                  <a tal:replace="structure security_contact/fmt:link" />,
-                  will be notified.
-                </tal:security-contact>
-                <tal:maintainer condition="not:security_contact"
-                   define="maintainer security_context/owner">
-                  The maintainer of
-                  <tal:security-context content="security_context/displayname">
-                    Mozilla Firefox</tal:security-context>,
-                  <a tal:replace="structure maintainer/fmt:link">
-                    Sample Person</a>,
-                  will be notified.
-                </tal:maintainer>
-              </div>
-            </td>
-          </tr>
-        </tbody>
-      </table>
-    </td>
-  </tr>
+  <tr id="extra-filebug-details"
+    tal:replace="structure context/@@+filebug-reporting-details" />
 
   <tr>
     <td colspan="2">


Follow ups