Bug 1144873 - Implement a UI class for notification popups r=maja_zf,sydpolk
authorDave Hunt <dhunt@mozilla.com>
Fri, 15 Apr 2016 15:41:28 +0100
changeset 333443 8871f41bc394c29815e4bf2130be2c7711fcf3db
parent 333442 c6d419054bd9679a7ae8ef711480da1d161cc84a
child 333444 1d139b7422c8bad036d3b21308e0ceef87ae1cf8
push id1146
push userCallek@gmail.com
push dateMon, 25 Jul 2016 16:35:44 +0000
treeherdermozilla-release@a55778f9cd5a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmaja_zf, sydpolk
bugs1144873
milestone48.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1144873 - Implement a UI class for notification popups r=maja_zf,sydpolk MozReview-Commit-ID: EAi5Y1AIitS
testing/firefox-ui/tests/puppeteer/manifest.ini
testing/firefox-ui/tests/puppeteer/mn-restartless-unsigned.xpi
testing/firefox-ui/tests/puppeteer/test_notifications.py
testing/puppeteer/firefox/docs/index.rst
testing/puppeteer/firefox/docs/ui/browser/notifications.rst
testing/puppeteer/firefox/firefox_puppeteer/ui/browser/notifications.py
testing/puppeteer/firefox/firefox_puppeteer/ui/browser/window.py
--- a/testing/firefox-ui/tests/puppeteer/manifest.ini
+++ b/testing/firefox-ui/tests/puppeteer/manifest.ini
@@ -8,13 +8,14 @@ tags = local
 [test_security.py]
 tags = remote
 [test_software_update.py]
 [test_utils.py]
 
 # UI tests
 [test_about_window.py]
 [test_menubar.py]
+[test_notifications.py]
 [test_page_info_window.py]
 [test_tabbar.py]
 [test_toolbars.py]
 tags = remote
 [test_windows.py]
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..c8877b55dec371fa6417fc626ef1a6f3113cc2bc
GIT binary patch
literal 1552
zc$^FHW@Zs#U|`^2xLcX!&XKXBU5t@|;S&(^Fvu_@<>!|amlP!y=w%g$hHx@4I|ev~
zfpBRBHv=Qf3uXoeFtN7(bk<=50oU*Uy1c{uJeFQcbqa5EQfzhD{W56U@r_Mu3@@!v
z<o)^ml;)SM@4Q}|n_Dcg_kj0%p?Q|>-Zu)oD;`)ZNYONB-^7&Y;V;nT%v65;yzqYO
z_q=60Kb{MVNO-N&>f*ge^UliX*5iC^(~e)helD+ju_uR4M(m^)vvjUJda*>mHR;Wq
z<xg3vpYmig?GSu?N|?j#LU)h$+IIi#k9J&dUD@rcty2;j$>&+%CieU?*X$ndcc%jn
zM{qb^+O=wz%o+<mkEBIUHqLt-voK_;_F9dvXTD8jpJnq<?b6m~nyRr6f)sYCzmJr?
zWH5XE>1R(;{7-$}x3ABu>gUUso43^P{MWi=y!~wFzZEe%?^W2()VrU_xBnA=05EL1
z7(hTQBFmit39tjhHaVjxKQ~n$9IR_ZoWis)gEasiIUob#4KJhw0(lugEPyZ|BQ+-{
z9~eKmaFak7%_MDaouGnVjt~4?g{7VReP2pHedi3@5ZV!T%H!<Gli`;F1H8_k_xD`o
z6WX=)nHIAaTQ_s4Mr(SAlclQUO4pTLUILXXiY9PvKeFgb(E_dsA`A$>1&3$3Prjz3
zFArpcFhAUBdbx>tnQ5uTC19870bQks>9XYfypq(s5{2A6-6EjU#G;a%)Z$_VsOPv4
zL68Ov0{z15Lsh`EX#mv74YVgS52zz2N3SR)4Qz*ls8bjSquG%=`LrK6dH&?meltO6
zd86;*E4o4oVmbk;3AvLpH+)duw0z6c*M&ZDtecA7&9N@F4LtQV(DM`LHO|H1AC=BN
zGVNYeR9)>~<GMTaPu~r(DRO-6Ukav5-M(<NXy=khzt1)szv>mL_x_oYQ7-7dq`GM%
zL+P{7XWkwsotte=uhU#{u2%4OVat^5545zSz8G@U8&}61tzO-IFO=i$1n-#|>yGlt
zRE3+bWC`t<r8zf3zo=Vi&TXE>T8q7YIro&*hfZ7C9Uy(r-JzH@$GWtdLG$eDjhpyt
zH2Mo{m(Hu^kP$4oKK)IPYFBhUi;Kbw#f7skWaY_OhlyE8oqkuMY|`-6pmNi)d*K>(
ztn-?R8x~#smM5}hPvi5q%CD!}MOPS{op|xNuz7=duD0vZ<toqRQ!X5<U2OQt`N6aA
z9f=p2SGCl={9ZkG`Siw_na_3X`KtK+4o&F%$0>TEUe7T0!xg5}^&8$<h~4XPY@fEB
z=dB!*f#=2fiK<`svvluc4De=Tl4HhIz({}-5Rl2Rq!GkIEpAvL#SKQD0GWs@TS-7!
z0t{~*V}J(3GZ`x+YhX1DR~AEd-du!XNEr^Fskm|nvZ)qK448R^6_Qu*I2AK1ARD>>
cXedg?V1;B13<Fu&K$=*Aa5hk9JPU{i03S06YXATM
new file mode 100644
--- /dev/null
+++ b/testing/firefox-ui/tests/puppeteer/test_notifications.py
@@ -0,0 +1,77 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import os
+
+from marionette_driver import By
+from marionette_driver.errors import TimeoutException
+
+from firefox_ui_harness.testcases import FirefoxTestCase
+from firefox_puppeteer.ui.browser.notifications import (
+    AddOnInstallFailedNotification)
+
+here = os.path.abspath(os.path.dirname(__file__))
+
+
+class TestNotifications(FirefoxTestCase):
+
+    def setUp(self):
+        FirefoxTestCase.setUp(self)
+        # setting this pref prevents a notification from being displayed
+        # when e10s is enabled. see http://mzl.la/1TVNAXh
+        self.prefs.set_pref('browser.tabs.remote.force-enable', True)
+
+    def tearDown(self):
+        try:
+            if self.browser.notification is not None:
+                self.browser.notification.close()
+        finally:
+            FirefoxTestCase.tearDown(self)
+
+    def test_no_notification(self):
+        """No initial notifications are shown"""
+        self.assertIsNone(self.browser.notification)
+
+    def test_notification_with_origin(self):
+        """Trigger a notification with an origin"""
+        self.trigger_add_on_notification('mn-restartless-unsigned.xpi')
+        self.assertIsNotNone(self.browser.notification.origin)
+        self.assertIsNotNone(self.browser.notification.label)
+
+    def test_close_notification(self):
+        """Trigger and dismiss a notification"""
+        self.trigger_add_on_notification('mn-restartless-unsigned.xpi')
+        self.browser.notification.close()
+        self.assertIsNone(self.browser.notification)
+
+    def test_add_on_failed_notification(self):
+        """Trigger add-on failed notification using an unsigned add-on"""
+        self.trigger_add_on_notification('mn-restartless-unsigned.xpi')
+        self.assertIsInstance(self.browser.notification,
+                              AddOnInstallFailedNotification)
+
+    def test_wait_for_any_notification_timeout(self):
+        """Wait for a notification when one is not shown"""
+        message = 'No notification was shown'
+        with self.assertRaisesRegexp(TimeoutException, message):
+            self.browser.wait_for_notification()
+
+    def test_wait_for_specific_notification_timeout(self):
+        """Wait for a notification when one is not shown"""
+        message = 'AddOnInstallFailedNotification was not shown'
+        with self.assertRaisesRegexp(TimeoutException, message):
+            self.browser.wait_for_notification(AddOnInstallFailedNotification)
+
+    def test_wait_for_no_notification_timeout(self):
+        """Wait for no notification when one is shown"""
+        message = 'Unexpected notification shown'
+        self.trigger_add_on_notification('mn-restartless-unsigned.xpi')
+        with self.assertRaisesRegexp(TimeoutException, message):
+            self.browser.wait_for_notification(None)
+
+    def trigger_add_on_notification(self, add_on):
+        with self.marionette.using_context('content'):
+            self.marionette.navigate('file://{0}'.format(here))
+            self.marionette.find_element(By.LINK_TEXT, add_on).click()
+        self.browser.wait_for_notification()
--- a/testing/puppeteer/firefox/docs/index.rst
+++ b/testing/puppeteer/firefox/docs/index.rst
@@ -33,16 +33,17 @@ The following libraries are currently im
 future. Each library is available from an instance of the FirefoxTestCase class.
 
 .. toctree::
 
    ui/about_window/window
    ui/deck
    ui/menu
    ui/pageinfo/window
+   ui/browser/notifications
    ui/browser/tabbar
    ui/browser/toolbars
    ui/browser/window
    ui/update_wizard/dialog
    ui/windows
    api/appinfo
    api/keys
    api/l10n
new file mode 100644
--- /dev/null
+++ b/testing/puppeteer/firefox/docs/ui/browser/notifications.rst
@@ -0,0 +1,44 @@
+.. py:currentmodule:: firefox_puppeteer.ui.browser.notifications
+
+Notifications
+=============
+
+AddOnInstallBlockedNotification
+-------------------------------
+
+.. autoclass:: AddOnInstallBlockedNotification
+   :members:
+   :inherited-members:
+   :show-inheritance:
+
+AddOnInstallConfirmationNotification
+------------------------------------
+
+.. autoclass:: AddOnInstallConfirmationNotification
+   :members:
+   :inherited-members:
+   :show-inheritance:
+
+AddOnInstallCompleteNotification
+--------------------------------
+
+.. autoclass:: AddOnInstallCompleteNotification
+   :members:
+   :inherited-members:
+   :show-inheritance:
+
+AddOnInstallFailedNotification
+------------------------------
+
+.. autoclass:: AddOnInstallFailedNotification
+   :members:
+   :inherited-members:
+   :show-inheritance:
+
+AddOnProgressNotification
+-------------------------
+
+.. autoclass:: AddOnProgressNotification
+   :members:
+   :inherited-members:
+   :show-inheritance:
new file mode 100644
--- /dev/null
+++ b/testing/puppeteer/firefox/firefox_puppeteer/ui/browser/notifications.py
@@ -0,0 +1,84 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from abc import ABCMeta
+
+from marionette_driver import By
+
+from firefox_puppeteer.ui_base_lib import UIBaseLib
+
+
+class BaseNotification(UIBaseLib):
+    """Abstract base class for any kind of notification."""
+    __metaclass__ = ABCMeta
+
+    @property
+    def label(self):
+        """Provides access to the notification label.
+
+        :returns: The notification label.
+        """
+        return self.element.get_attribute('label')
+
+    @property
+    def origin(self):
+        """Provides access to the notification origin.
+
+        :returns: The notification origin.
+        """
+        return self.element.get_attribute('origin')
+
+    def close(self):
+        """Close the notification."""
+        self.element.find_element(
+            By.ANON_ATTRIBUTE, {'anonid': 'closebutton'}).click()
+
+
+class AddOnInstallBlockedNotification(BaseNotification):
+    """Add-on install blocked notification."""
+
+    def allow(self):
+        """Allow the add-on to be installed."""
+        self.element.find_element(
+            By.ANON_ATTRIBUTE, {'anonid': 'button'}).find_element(
+            By.ANON_ATTRIBUTE, {'anonid': 'button'}).click()
+
+
+class AddOnInstallConfirmationNotification(BaseNotification):
+    """Add-on install confirmation notification."""
+
+    @property
+    def add_on(self):
+        """Provides access to the add-on name.
+
+        :returns: The add-on name.
+        """
+        label = self.element.find_element(
+            By.CSS_SELECTOR, '#addon-install-confirmation-content label')
+        return label.get_attribute('value')
+
+    def cancel(self):
+        """Cancel installation of the add-on."""
+        self.element.find_element(
+            By.ID, 'addon-install-confirmation-cancel').click()
+
+    def install(self):
+        """Proceed with installation of the add-on."""
+        self.element.find_element(
+            By.ID, 'addon-install-confirmation-accept').click()
+
+
+class AddOnInstallCompleteNotification(BaseNotification):
+    """Add-on install complete notification."""
+    pass
+
+
+class AddOnInstallFailedNotification(BaseNotification):
+    """Add-on install failed notification."""
+    pass
+
+
+class AddOnProgressNotification(BaseNotification):
+    """Add-on progress notification."""
+    pass
--- a/testing/puppeteer/firefox/firefox_puppeteer/ui/browser/window.py
+++ b/testing/puppeteer/firefox/firefox_puppeteer/ui/browser/window.py
@@ -1,22 +1,31 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this file,
 # You can obtain one at http://mozilla.org/MPL/2.0/.
 
 import firefox_puppeteer.errors as errors
 
 from marionette_driver import By, Wait
-from marionette_driver.errors import NoSuchWindowException
+from marionette_driver.errors import (
+    NoSuchElementException,
+    NoSuchWindowException)
 from marionette_driver.keys import Keys
 
 from firefox_puppeteer.api.l10n import L10n
 from firefox_puppeteer.api.prefs import Preferences
 from firefox_puppeteer.decorators import use_class_as_property
 from firefox_puppeteer.ui.about_window.window import AboutWindow
+from firefox_puppeteer.ui.browser.notifications import (
+    AddOnInstallBlockedNotification,
+    AddOnInstallConfirmationNotification,
+    AddOnInstallCompleteNotification,
+    AddOnInstallFailedNotification,
+    AddOnProgressNotification,
+    BaseNotification)
 from firefox_puppeteer.ui.browser.tabbar import TabBar
 from firefox_puppeteer.ui.browser.toolbars import NavBar
 from firefox_puppeteer.ui.pageinfo.window import PageInfoWindow
 from firefox_puppeteer.ui.windows import BaseWindow, Windows
 import firefox_puppeteer.errors as errors
 
 
 class BrowserWindow(BaseWindow):
@@ -80,16 +89,50 @@ class BrowserWindow(BaseWindow):
 
         if not self._navbar:
             navbar = self.window_element.find_element(By.ID, 'nav-bar')
             self._navbar = NavBar(lambda: self.marionette, self, navbar)
 
         return self._navbar
 
     @property
+    def notification(self):
+        """Provides access to the currently displayed notification."""
+
+        notifications_map = {
+            'addon-install-blocked-notification': AddOnInstallBlockedNotification,
+            'addon-install-confirmation-notification': AddOnInstallConfirmationNotification,
+            'addon-install-complete-notification': AddOnInstallCompleteNotification,
+            'addon-install-failed-notification': AddOnInstallFailedNotification,
+            'addon-progress-notification': AddOnProgressNotification,
+        }
+
+        try:
+            notification = self.window_element.find_element(
+                By.CSS_SELECTOR, '#notification-popup popupnotification')
+        except NoSuchElementException:
+            return None  # no notification is displayed
+        notification_id = notification.get_attribute('id')
+        return notifications_map[notification_id](
+            lambda: self.marionette, self, notification)
+
+    def wait_for_notification(self, notification_class=BaseNotification):
+        """Waits for the specified notification to be displayed."""
+        if notification_class is None:
+            Wait(self.marionette).until(
+                lambda _: self.notification is None,
+                message='Unexpected notification shown.')
+        else:
+            message = '{0} was not shown.'.format(notification_class.__name__)
+            if notification_class is BaseNotification:
+                message = 'No notification was shown.'
+            Wait(self.marionette).until(lambda _: isinstance(
+                self.notification, notification_class), message=message)
+
+    @property
     def tabbar(self):
         """Provides access to the tab bar.
 
         See the :class:`~ui.browser.tabbar.TabBar` reference.
         """
         self.switch_to()
 
         if not self._tabbar: