Bug 1258385 - Improve failure messages for Wait().until() calls in Firefox Puppeteer. r=sydpolk, a=test-only
authorHenrik Skupin <mail@hskupin.info>
Mon, 21 Mar 2016 15:50:59 +0100
changeset 323637 bc8dfbb87a3e81bf111c0c424748aa971a69547f
parent 323636 c99976c9726460929fe402302972d50c516a3920
child 323638 1aef5889d3816b258785fc2ce8c5d7998de64990
push id5913
push userjlund@mozilla.com
push dateMon, 25 Apr 2016 16:57:49 +0000
treeherdermozilla-beta@dcaf0a6fa115 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssydpolk, test-only
bugs1258385
milestone47.0a2
Bug 1258385 - Improve failure messages for Wait().until() calls in Firefox Puppeteer. r=sydpolk, a=test-only MozReview-Commit-ID: GCbVC1ZwUdf
testing/puppeteer/firefox/firefox_puppeteer/ui/browser/tabbar.py
testing/puppeteer/firefox/firefox_puppeteer/ui/browser/toolbars.py
testing/puppeteer/firefox/firefox_puppeteer/ui/pageinfo/deck.py
testing/puppeteer/firefox/firefox_puppeteer/ui/update_wizard/dialog.py
testing/puppeteer/firefox/firefox_puppeteer/ui/update_wizard/wizard.py
testing/puppeteer/firefox/firefox_puppeteer/ui/windows.py
--- a/testing/puppeteer/firefox/firefox_puppeteer/ui/browser/tabbar.py
+++ b/testing/puppeteer/firefox/firefox_puppeteer/ui/browser/tabbar.py
@@ -136,17 +136,18 @@ class TabBar(UIBaseLib):
         elif trigger == 'shortcut':
             self.window.send_shortcut(self.window.get_entity('tabCmd.commandkey'), accel=True)
         # elif - need to add other cases
         else:
             raise ValueError('Unknown opening method: "%s"' % trigger)
 
         # TODO: Needs to be replaced with event handling code (bug 1121705)
         Wait(self.marionette).until(
-            lambda mn: len(mn.window_handles) == len(start_handles) + 1)
+            lambda mn: len(mn.window_handles) == len(start_handles) + 1,
+            message='No new tab has been opened.')
 
         handles = self.marionette.window_handles
         [new_handle] = list(set(handles) - set(start_handles))
         [new_tab] = [tab for tab in self.tabs if tab.handle == new_handle]
 
         # if the new tab is the currently selected tab, switch to it
         if new_tab == self.selected_tab:
             new_tab.switch_to()
@@ -257,23 +258,22 @@ class Tab(UIBaseLib):
     # Properties for helpers when working with tabs #
 
     @property
     def handle(self):
         """The `handle` of the content window.
 
         :returns: content window `handle`.
         """
-        def get_handle(_):
-            self._handle = TabBar.get_handle_for_tab(self.marionette, self.element)
-            return self._handle is not None
+        # If no handle has been set yet, wait until it is available
+        if not self._handle:
+            self._handle = Wait(self.marionette).until(
+                lambda mn: TabBar.get_handle_for_tab(mn, self.element),
+                message='Tab handle could not be found.')
 
-        # With e10s enabled, contentWindowAsCPOW isn't available right away.
-        if self._handle is None:
-            Wait(self.marionette).until(get_handle)
         return self._handle
 
     @property
     def selected(self):
         """Checks if the tab is selected.
 
         :return: `True` if the tab is selected.
         """
@@ -294,16 +294,17 @@ class Tab(UIBaseLib):
 
         :param trigger: Optional, method in how to close the tab. This can
          be a string with one of `button`, `menu` or `shortcut`, or a callback which
          gets triggered with the current :class:`Tab` as parameter. Defaults to `menu`.
 
         :param force: Optional, forces the closing of the window by using the Gecko API.
          Defaults to `False`.
         """
+        handle = self.handle
         start_handles = self.marionette.window_handles
 
         self.switch_to()
 
         if force:
             self.marionette.close()
         elif callable(trigger):
             trigger(self)
@@ -312,28 +313,31 @@ class Tab(UIBaseLib):
         elif trigger == 'menu':
             self.window.menubar.select_by_id('file-menu', 'menu_close')
         elif trigger == 'shortcut':
             self.window.send_shortcut(self.window.get_entity('closeCmd.key'), accel=True)
         else:
             raise ValueError('Unknown closing method: "%s"' % trigger)
 
         Wait(self.marionette).until(
-            lambda _: len(self.window.tabbar.tabs) == len(start_handles) - 1)
+            lambda _: len(self.window.tabbar.tabs) == len(start_handles) - 1,
+            message='Tab with handle "%s" has not been closed.' % handle)
 
         # Ensure to switch to the window handle which represents the new selected tab
         self.window.tabbar.selected_tab.switch_to()
 
     def select(self):
         """Selects the tab and sets the focus to it."""
         self.tab_element.click()
         self.switch_to()
 
         # Bug 1121705: Maybe we have to wait for TabSelect event
-        Wait(self.marionette).until(lambda _: self.selected)
+        Wait(self.marionette).until(
+            lambda _: self.selected,
+            message='Tab with handle "%s" could not be selected.' % self.handle)
 
     def switch_to(self):
         """Switches the context of Marionette to this tab.
 
         Please keep in mind that calling this method will not select the tab.
         Use the :func:`~Tab.select` method instead.
         """
         self.marionette.switch_to_window(self.handle)
--- a/testing/puppeteer/firefox/firefox_puppeteer/ui/browser/toolbars.py
+++ b/testing/puppeteer/firefox/firefox_puppeteer/ui/browser/toolbars.py
@@ -92,17 +92,19 @@ class LocationBar(UIBaseLib):
                                                              self.window, popup)
 
         return self._autocomplete_results
 
     def clear(self):
         """Clears the contents of the url bar (via the DELETE shortcut)."""
         self.focus('shortcut')
         self.urlbar.send_keys(keys.Keys.DELETE)
-        Wait(self.marionette).until(lambda _: self.urlbar.get_attribute('value') == '')
+        Wait(self.marionette).until(
+            lambda _: self.urlbar.get_attribute('value') == '',
+            message='Contents of location bar could not be cleared.')
 
     def close_context_menu(self):
         """Closes the Location Bar context menu by a key event."""
         # TODO: This method should be implemented via the menu API.
         self.contextmenu.send_keys(keys.Keys.ESCAPE)
 
     @property
     def connection_icon(self):
@@ -147,17 +149,19 @@ class LocationBar(UIBaseLib):
         if event == 'click':
             self.urlbar.click()
         elif event == 'shortcut':
             cmd_key = self.window.get_entity('openCmd.commandkey')
             self.window.send_shortcut(cmd_key, accel=True)
         else:
             raise ValueError("An unknown event type was passed: %s" % event)
 
-        Wait(self.marionette).until(lambda _: self.focused)
+        Wait(self.marionette).until(
+            lambda _: self.focused,
+            message='Location bar has not be focused.')
 
     def get_contextmenu_entry(self, action):
         """Retrieves the urlbar context menu entry corresponding
         to the given action.
 
         :param action: The action corresponding to the retrieved value.
         :returns: Reference to the urlbar contextmenu entry.
         """
@@ -227,18 +231,21 @@ class LocationBar(UIBaseLib):
     def notification_popup(self):
         """Provides access to the DOM element notification popup.
 
         :returns: Reference to the notification popup.
         """
         return self.marionette.find_element(By.ID, "notification-popup")
 
     def open_identity_popup(self):
+        """Open the identity popup."""
         self.identity_box.click()
-        Wait(self.marionette).until(lambda _: self.identity_popup.is_open)
+        Wait(self.marionette).until(
+            lambda _: self.identity_popup.is_open,
+            message='Identity popup has not been opened.')
 
     @property
     def reload_button(self):
         """Provides access to the DOM element reload button.
 
         :returns: Reference to the reload button.
         """
         return self.marionette.find_element(By.ID, 'urlbar-reload-button')
@@ -308,17 +315,19 @@ class AutocompleteResults(UIBaseLib):
 
         if force:
             self.marionette.execute_script("""
               arguments[0].hidePopup();
             """, script_args=[self.element])
         else:
             self.element.send_keys(keys.Keys.ESCAPE)
 
-        Wait(self.marionette).until(lambda _: not self.is_open)
+        Wait(self.marionette).until(
+            lambda _: not self.is_open,
+            message='Autocomplete popup has not been closed.')
 
     def get_matching_text(self, result, match_type):
         """Returns an array of strings of the matching text within an autocomplete
         result in the urlbar.
 
         :param result: The result to inspect for matches.
         :param match_type: The type of match to search for (one of `title` or `url`).
         """
@@ -441,17 +450,19 @@ class IdentityPopup(UIBaseLib):
 
         if force:
             self.marionette.execute_script("""
               arguments[0].hidePopup();
             """, script_args=[self.element])
         else:
             self.element.send_keys(keys.Keys.ESCAPE)
 
-        Wait(self.marionette).until(lambda _: not self.is_open)
+        Wait(self.marionette).until(
+            lambda _: not self.is_open,
+            message='Identity popup has not been closed.')
 
     @property
     def view(self):
         """Provides utility members for accessing and manipulating the
         identity popup's multi view.
 
         See the :class:`IdentityPopupMultiView` reference.
         """
--- a/testing/puppeteer/firefox/firefox_puppeteer/ui/pageinfo/deck.py
+++ b/testing/puppeteer/firefox/firefox_puppeteer/ui/pageinfo/deck.py
@@ -113,17 +113,19 @@ class Deck(UIBaseLib):
     def select(self, panel):
         """Selects the specified panel via the tab element.
 
         :param panel: The panel to select.
 
         :returns: :class:`Panel` instance of the selected panel.
         """
         panel.tab.click()
-        Wait(self.marionette).until(lambda _: self.selected_panel == panel)
+        Wait(self.marionette).until(
+            lambda _: self.selected_panel == panel,
+            message='Panel with ID "%s" could not be selected.' % panel)
 
         return panel
 
 
 class PageInfoPanel(Panel):
 
     @property
     def tab(self):
--- a/testing/puppeteer/firefox/firefox_puppeteer/ui/update_wizard/dialog.py
+++ b/testing/puppeteer/firefox/firefox_puppeteer/ui/update_wizard/dialog.py
@@ -36,12 +36,14 @@ class UpdateWizardDialog(BaseWindow):
         wizard = self.marionette.find_element(By.ID, 'updates')
         return Wizard(lambda: self.marionette, self, wizard)
 
     def select_next_page(self):
         """Clicks on "Next" button, and waits for the next page to show up."""
         current_panel = self.wizard.selected_panel
 
         self.wizard.next_button.click()
-        Wait(self.marionette).until(lambda _: self.wizard.selected_panel != current_panel)
+        Wait(self.marionette).until(
+            lambda _: self.wizard.selected_panel != current_panel,
+            message='Next panel has not been selected.')
 
 
 Windows.register_window(UpdateWizardDialog.window_type, UpdateWizardDialog)
--- a/testing/puppeteer/firefox/firefox_puppeteer/ui/update_wizard/wizard.py
+++ b/testing/puppeteer/firefox/firefox_puppeteer/ui/update_wizard/wizard.py
@@ -8,17 +8,19 @@ from firefox_puppeteer.ui_base_lib impor
 from firefox_puppeteer.ui.deck import Panel
 
 
 class Wizard(UIBaseLib):
 
     def __init__(self, *args, **kwargs):
         UIBaseLib.__init__(self, *args, **kwargs)
 
-        Wait(self.marionette).until(lambda _: self.selected_panel)
+        Wait(self.marionette).until(
+            lambda _: self.selected_panel,
+            message='No panel has been selected by default.')
 
     def _create_panel_for_id(self, panel_id):
         """Creates an instance of :class:`Panel` for the specified panel id.
 
         :param panel_id: The ID of the panel to create an instance of.
 
         :returns: :class:`Panel` instance
         """
--- a/testing/puppeteer/firefox/firefox_puppeteer/ui/windows.py
+++ b/testing/puppeteer/firefox/firefox_puppeteer/ui/windows.py
@@ -96,60 +96,77 @@ class Windows(BaseLib):
         """Creates a :class:`BaseWindow` instance for the given chrome window.
 
         :param handle: The handle of the chrome window.
         :param expected_class: Optional, check for the correct window class.
         """
         current_handle = self.marionette.current_chrome_window_handle
         window = None
 
-        try:
-            # Bug 1169180 - Workaround for handling newly opened chrome windows
-            # Once fixed revert back to make use of self.loaded
-            Wait(self.marionette).until(lambda mn: self.marionette.execute_script("""
-              Components.utils.import("resource://gre/modules/Services.jsm");
-              let win = Services.wm.getOuterWindowWithId(Number(arguments[0]));
-              return win.document.readyState == 'complete';
-            """, script_args=[handle]))
+        with self.marionette.using_context('chrome'):
+            try:
+                # Retrieve window type to determine the type of chrome window
+                if handle != self.marionette.current_chrome_window_handle:
+                    self.switch_to(handle)
 
-            # Retrieve window type to determine the type of chrome window
-            if handle != self.marionette.current_chrome_window_handle:
-                self.switch_to(handle)
+                window_type = Wait(self.marionette).until(
+                    lambda mn: mn.get_window_type(),
+                    message='Cannot get window type for chrome window handle "%s"' % handle
+                )
 
-            Wait(self.marionette).until(lambda mn: mn.get_window_type() is not None)
-            window_type = self.marionette.get_window_type()
-        finally:
-            # Ensure to switch back to the original window
-            if handle != current_handle:
-                self.marionette.switch_to_window(current_handle)
+            finally:
+                # Ensure to switch back to the original window
+                if handle != current_handle:
+                    self.switch_to(current_handle)
 
-        if window_type in self.windows_map:
-            window = self.windows_map[window_type](
-                lambda: self.marionette, handle)
-        else:
-            raise errors.UnknownWindowError('Unknown window type "%s" for handle: "%s"' %
-                                            (window_type, handle))
+            if window_type in self.windows_map:
+                window = self.windows_map[window_type](lambda: self.marionette, handle)
+            else:
+                raise errors.UnknownWindowError('Unknown window type "%s" for handle: "%s"' %
+                                                (window_type, handle))
 
-        if expected_class is not None and type(window) is not expected_class:
-            raise errors.UnexpectedWindowTypeError('Expected window "%s" but got "%s"' %
-                                                   (expected_class, type(window)))
+            if expected_class is not None and type(window) is not expected_class:
+                raise errors.UnexpectedWindowTypeError('Expected window "%s" but got "%s"' %
+                                                       (expected_class, type(window)))
+
+            # Before continuing ensure the chrome window has been completed loading
+            Wait(self.marionette).until(
+                lambda _: self.loaded(handle),
+                message='Chrome window with handle "%s" did not finish loading.' % handle)
 
         return window
 
     def focus(self, handle):
         """Focuses the chrome window with the given handle.
 
         :param handle: The handle of the chrome window.
         """
         self.switch_to(handle)
 
         with self.marionette.using_context('chrome'):
             self.marionette.execute_script(""" window.focus(); """)
 
-        Wait(self.marionette).until(lambda _: handle == self.focused_chrome_window_handle)
+        Wait(self.marionette).until(
+            lambda _: handle == self.focused_chrome_window_handle,
+            message='Focus has not been set to chrome window handle "%s".' % handle)
+
+    def loaded(self, handle):
+        """Check if the chrome window with the given handle has been completed loading.
+
+        :param handle: The handle of the chrome window.
+
+        :returns: True, if the chrome window has been loaded.
+        """
+        with self.marionette.using_context('chrome'):
+            return self.marionette.execute_script("""
+              Components.utils.import("resource://gre/modules/Services.jsm");
+
+              let win = Services.wm.getOuterWindowWithId(Number(arguments[0]));
+              return win.document.readyState == 'complete';
+            """, script_args=[handle])
 
     def switch_to(self, target):
         """Switches context to the specified chrome window.
 
         :param target: The window to switch to. `target` can be a `handle` or a
                        callback that returns True in the context of the desired
                        window.
 
@@ -243,21 +260,17 @@ class BaseWindow(BaseLib):
         return self._handle
 
     @property
     def loaded(self):
         """Checks if the window has been fully loaded.
 
         :returns: True, if the window is loaded.
         """
-        self.switch_to()
-
-        return self.marionette.execute_script("""
-          return arguments[0].ownerDocument.readyState === "complete";
-        """, script_args=[self.window_element])
+        self._windows.loaded(self.handle)
 
     @use_class_as_property('ui.menu.MenuBar')
     def menubar(self):
         """Provides access to the menu bar, for example, the **File** menu.
 
         See the :class:`~ui.menu.MenuBar` reference.
         """
 
@@ -284,25 +297,27 @@ class BaseWindow(BaseLib):
          Defaults to `False`.
         """
         self.switch_to()
 
         # Bug 1121698
         # For more stable tests register an observer topic first
         prev_win_count = len(self.marionette.chrome_window_handles)
 
+        handle = self.handle
         if force or callback is None:
-            self._windows.close(self.handle)
+            self._windows.close(handle)
         else:
             callback(self)
 
         # Bug 1121698
         # Observer code should let us ditch this wait code
-        wait = Wait(self.marionette)
-        wait.until(lambda m: len(m.chrome_window_handles) == prev_win_count - 1)
+        Wait(self.marionette).until(
+            lambda m: len(m.chrome_window_handles) == prev_win_count - 1,
+            message='Chrome window with handle "%s" has not been closed.' % handle)
 
     def focus(self):
         """Sets the focus to the current chrome window."""
         return self._windows.focus(self.handle)
 
     def get_entity(self, entity_id):
         """Returns the localized string for the specified DTD entity id.
 
@@ -344,17 +359,19 @@ class BaseWindow(BaseLib):
             if callback is not None:
                 callback(self)
             else:
                 self.marionette.execute_script(""" window.open(); """)
 
         # TODO: Needs to be replaced with observer handling code (bug 1121698)
         def window_opened(mn):
             return len(mn.chrome_window_handles) == len(start_handles) + 1
-        Wait(self.marionette).until(window_opened)
+        Wait(self.marionette).until(
+            window_opened,
+            message='No new chrome window has been opened.')
 
         handles = self.marionette.chrome_window_handles
         [new_handle] = list(set(handles) - set(start_handles))
 
         assert new_handle is not None
 
         window = self._windows.create_window_instance(new_handle, expected_window_class)