Bug 1144873 - Implement a UI class for notification popups r?sydpolk, maja_zf draft
authorDave Hunt <dhunt@mozilla.com>
Fri, 15 Apr 2016 15:41:28 +0100
changeset 352077 3a3975c77d7ad2a9d178cc5584d7782c80e2a182
parent 351979 afd82f887093e5e9e4015115ca5795ec82a6f732
child 518567 568d383e79c6b1605ba3f5c2e767ee5f3b82c8a6
push id15602
push userdhunt@mozilla.com
push dateFri, 15 Apr 2016 15:20:56 +0000
reviewerssydpolk, maja_zf
bugs1144873
milestone48.0a1
Bug 1144873 - Implement a UI class for notification popups r?sydpolk, maja_zf 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 0000000000000000000000000000000000000000..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: