[Pkg-mozext-commits] [requestpolicy] 51/80: new Marionette library: "addons"

David Prévot taffit at moszumanska.debian.org
Sun Jul 5 15:02:29 UTC 2015


This is an automated email from the git hooks/post-receive script.

taffit pushed a commit to branch master
in repository requestpolicy.

commit 4b4f1bac82cee6100efd14e5bfefb85deef3c592
Author: Martin Kimmerle <dev at 256k.de>
Date:   Thu Jun 18 12:10:46 2015 +0200

    new Marionette library: "addons"
    
    The "addons" Marionette library will allow to install, enable,
    disable and uninstall any bootstrapped (restartless) addon.
    
    It will be used for testing restartlessness. It will also be
    useful for any kind of test that involves installation or
    disable/enable of RequestPolicy.
---
 Makefile                                           |  29 +++
 tests/content/link.html                            |  27 +++
 tests/helper-addons/dummy-ext/bootstrap.js         |   4 +
 tests/helper-addons/dummy-ext/install.rdf          |  23 ++
 tests/marionette/rp_puppeteer/tests/manifest.ini   |   1 +
 tests/marionette/rp_puppeteer/tests/test_addons.py | 147 ++++++++++++
 tests/marionette/rp_puppeteer/ui/addons.py         | 254 +++++++++++++++++++++
 tests/mozrunner-prefs.ini                          |   4 +
 8 files changed, 489 insertions(+)

diff --git a/Makefile b/Makefile
index b663c12..bff9e1e 100644
--- a/Makefile
+++ b/Makefile
@@ -170,6 +170,19 @@ dev_helper__source_files := $(shell find $(dev_helper__source_dirname) -type f -
 dev_helper__xpi_file := $(dist_path)rpc-dev-helper.xpi
 
 
+# _________________________________
+# vars for generating the Dummy XPI
+#
+
+dummy_ext__source_dirname := tests/helper-addons/dummy-ext
+dummy_ext__source_path := $(dummy_ext__source_dirname)/
+
+dummy_ext__source_files := $(shell find $(dummy_ext__source_dirname) -type f -regex ".*\.jsm?") \
+		$(dummy_ext__source_dirname)/install.rdf
+
+dummy_ext__xpi_file := $(dist_path)dummy-ext.xpi
+
+
 # ______________________________
 # vars for mozrunner and mozmill
 #
@@ -286,6 +299,22 @@ dev-helper-xpi $(dev_helper__xpi_file): $(dev_helper__source_files) FORCE | $(di
 	$(ZIP) $(abspath $(dev_helper__xpi_file)) $(patsubst $(dev_helper__source_path)%,%,$(dev_helper__source_files))
 	@echo "Creating 'RPC Dev Helper' XPI: Done!"
 
+
+# ____________________
+# create the Dummy XPI
+#
+
+# For now use FORCE, i.e. create the XPI every time. If the
+# 'FORCE' should be removed, deleted files have to be detected,
+# just like for the other XPIs.
+dev-helper-xpi $(dummy_ext__xpi_file): $(dummy_ext__source_files) FORCE | $(dist_path)
+	@rm -f $(dummy_ext__xpi_file)
+	@echo "Creating 'Dummy' XPI."
+	@cd $(dummy_ext__source_dirname) && \
+	$(ZIP) $(abspath $(dummy_ext__xpi_file)) $(patsubst $(dummy_ext__source_path)%,%,$(dummy_ext__source_files))
+	@echo "Creating 'Dummy' XPI: Done!"
+
+
 # _________________________________________
 # create the XPI from any tag or any commit
 #
diff --git a/tests/content/link.html b/tests/content/link.html
new file mode 100644
index 0000000..1f3d732
--- /dev/null
+++ b/tests/content/link.html
@@ -0,0 +1,27 @@
+<!doctype html>
+<html>
+<head>
+  <meta charset="utf-8" />
+  <title>Link page</title>
+  <script src="inc/jquery.min.js"></script>
+</head>
+<body>
+
+<script>
+  $( document ).ready(function(){
+    if (location.search) {
+      var path = decodeURIComponent(location.search.substr(1));
+      var url = location.protocol + location.hostname + path;
+      $("p").append("<a href='" + path + "'>Link</a>");
+    }
+  });
+</script>
+
+<p style="margin: 2em">
+  <noscript>
+    Please enable JavaScript for this test.
+  </noscript>
+</p>
+
+</body>
+</html>
diff --git a/tests/helper-addons/dummy-ext/bootstrap.js b/tests/helper-addons/dummy-ext/bootstrap.js
new file mode 100644
index 0000000..b728caa
--- /dev/null
+++ b/tests/helper-addons/dummy-ext/bootstrap.js
@@ -0,0 +1,4 @@
+function startup(data, reason) {}
+function shutdown(data, reason) {}
+function install(data, reason) {}
+function uninstall(data, reason) {}
diff --git a/tests/helper-addons/dummy-ext/install.rdf b/tests/helper-addons/dummy-ext/install.rdf
new file mode 100644
index 0000000..a197ef3
--- /dev/null
+++ b/tests/helper-addons/dummy-ext/install.rdf
@@ -0,0 +1,23 @@
+<?xml version="1.0"?>
+<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+  xmlns:em="http://www.mozilla.org/2004/em-rdf#">
+
+  <Description about="urn:mozilla:install-manifest">
+    <em:id>dummy-ext at requestpolicy.org</em:id>
+    <em:version>0.1</em:version>
+    <em:name>Dummy Test Extension (empty)</em:name>
+    <em:type>2</em:type>
+    <em:bootstrap>true</em:bootstrap>
+    <em:multiprocessCompatible>true</em:multiprocessCompatible>
+
+    <!-- Firefox -->
+    <em:targetApplication>
+      <Description>
+        <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
+        <em:minVersion>3.5.*</em:minVersion>
+        <em:maxVersion>*</em:maxVersion>
+      </Description>
+    </em:targetApplication>
+
+  </Description>
+</RDF>
diff --git a/tests/marionette/rp_puppeteer/tests/manifest.ini b/tests/marionette/rp_puppeteer/tests/manifest.ini
index c8c9167..c0e5d78 100644
--- a/tests/marionette/rp_puppeteer/tests/manifest.ini
+++ b/tests/marionette/rp_puppeteer/tests/manifest.ini
@@ -1 +1,2 @@
+[test_addons.py]
 [test_prefs.py]
diff --git a/tests/marionette/rp_puppeteer/tests/test_addons.py b/tests/marionette/rp_puppeteer/tests/test_addons.py
new file mode 100644
index 0000000..61c75ff
--- /dev/null
+++ b/tests/marionette/rp_puppeteer/tests/test_addons.py
@@ -0,0 +1,147 @@
+# 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 firefox_ui_harness import FirefoxTestCase
+
+from rp_puppeteer.ui.addons import Addons, AboutAddonsTab
+
+
+ADDON_ID = "dummy-ext at requestpolicy.org"
+INSTALL_URL = "http://localhost/link.html?.dist/dummy-ext.xpi"
+
+
+class AddonsTestCase(FirefoxTestCase):
+    def selected_tab_is_about_addons(self):
+        location = self.browser.tabbar.selected_tab.location
+        return location == "about:addons"
+
+    def extension_category_is_selected(self):
+        if not self.selected_tab_is_about_addons():
+            return False
+        with self.marionette.using_context("content"):
+
+            # Using `get_attribute` does not work here. It raises
+            # an exception: "Element is not selectable".
+            rv = self.marionette.execute_script(
+                """
+                let category = document.getElementById("category-extension");
+                return category.getAttribute("selected");
+                """);
+            return rv == "true"
+
+    def count_tabs(self):
+        return len(self.browser.tabbar.tabs)
+
+
+class TestAddons(AddonsTestCase):
+    def setUp(self):
+        AddonsTestCase.setUp(self)
+        self.addons = Addons(lambda: self.marionette)
+        self.addons.install_addon(INSTALL_URL)
+
+    def tearDown(self):
+        try:
+            self.addons.remove_addon_by_id(ADDON_ID)
+        finally:
+            AddonsTestCase.tearDown(self)
+
+    def test_using_addon_list(self):
+        num_tabs = self.count_tabs()
+        self.assertFalse(self.selected_tab_is_about_addons())
+        with self.addons.using_addon_list() as about_addons:
+            self.assertIsInstance(about_addons, AboutAddonsTab)
+            self.assertTrue(self.selected_tab_is_about_addons())
+            self.assertTrue(self.extension_category_is_selected())
+            self.assertEqual(self.count_tabs(), num_tabs + 1,
+                             msg="The number of tabs has increased by one.")
+        self.assertFalse(self.selected_tab_is_about_addons())
+        self.assertEqual(self.count_tabs(), num_tabs,
+                         msg="The number of tabs is like in the beginning.")
+
+    def test_uninstall_install(self):
+        def is_installed():
+            with self.addons.using_addon_list() as about_addons:
+                return about_addons.is_addon_installed(ADDON_ID)
+
+        self.assertTrue(is_installed())
+        self.addons.remove_addon_by_id(ADDON_ID)
+        self.assertFalse(is_installed())
+        self.addons.install_addon(INSTALL_URL)
+        self.assertTrue(is_installed())
+
+    def test_multiple_uninstall_install(self):
+        self.addons.remove_addon_by_id(ADDON_ID)
+        self.addons.remove_addon_by_id(ADDON_ID)
+        self.addons.install_addon(INSTALL_URL)
+        self.addons.install_addon(INSTALL_URL)
+
+    def test_disable_enable(self):
+        def is_enabled():
+            with self.addons.using_addon_list() as about_addons:
+                addon = about_addons.get_addon_by_id(ADDON_ID)
+                return addon.is_enabled()
+
+        self.assertTrue(is_enabled())
+        self.addons.disable_addon_by_id(ADDON_ID)
+        self.assertFalse(is_enabled())
+        self.addons.enable_addon_by_id(ADDON_ID)
+        self.assertTrue(is_enabled())
+
+    def test_multiple_disable_enable(self):
+        self.addons.disable_addon_by_id(ADDON_ID)
+        self.addons.disable_addon_by_id(ADDON_ID)
+        self.addons.enable_addon_by_id(ADDON_ID)
+        self.addons.enable_addon_by_id(ADDON_ID)
+
+
+class TestAboutAddons(AddonsTestCase):
+    def setUp(self):
+        FirefoxTestCase.setUp(self)
+
+        Addons(lambda: self.marionette).install_addon(INSTALL_URL)
+
+        self.about_addons = AboutAddonsTab(lambda: self.marionette)
+        self.about_addons.open_tab()
+        self.about_addons.set_category_by_id("extension")
+
+    def tearDown(self):
+        try:
+            if self.about_addons.is_addon_installed(ADDON_ID):
+                self.about_addons.remove_addon(self.addon)
+            self.about_addons.close_tab()
+        finally:
+            FirefoxTestCase.tearDown(self)
+
+    @property
+    def addon(self):
+        return self.about_addons.get_addon_by_id(ADDON_ID)
+
+    def test_close_tab_open_tab(self):
+        self.assertTrue(self.selected_tab_is_about_addons())
+        self.about_addons.close_tab()
+        self.assertFalse(self.selected_tab_is_about_addons())
+        self.about_addons.open_tab()
+        self.assertTrue(self.selected_tab_is_about_addons())
+
+    def test_disable_enable(self):
+        """Test the methods `disable`, `enable` and `is_enabled`.
+        """
+        self.assertTrue(self.addon.is_enabled(), msg="The addon is enabled.")
+        self.about_addons.disable_addon(self.addon)
+        self.assertFalse(self.addon.is_enabled(),
+                         msg="The addon has been disabled.")
+        self.about_addons.enable_addon(self.addon)
+        self.assertTrue(self.addon.is_enabled(),
+                        msg="The addon is enabled again.")
+
+    def test_uninstall(self):
+        def is_installed():
+            return self.about_addons.is_addon_installed(ADDON_ID)
+
+        self.assertTrue(is_installed(), msg="The addon is installed.")
+        self.about_addons.remove_addon(self.addon)
+
+        # The addon should be already uninstalled when `remove`
+        # returns. Therefore there is no "wait until".
+        self.assertFalse(is_installed(), msg="The addon has been removed.")
diff --git a/tests/marionette/rp_puppeteer/ui/addons.py b/tests/marionette/rp_puppeteer/ui/addons.py
new file mode 100644
index 0000000..f1f7161
--- /dev/null
+++ b/tests/marionette/rp_puppeteer/ui/addons.py
@@ -0,0 +1,254 @@
+# 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 marionette_driver.errors import NoSuchElementException
+from marionette_driver.wait import Wait
+from firefox_puppeteer.base import BaseLib
+from firefox_puppeteer.ui.windows import Windows
+from contextlib import contextmanager
+
+
+class _InstallNotifications(object):
+    """This class provides methods for managing the install notificaitons.
+    """
+
+    def __init__(self, marionette):
+        self.marionette = marionette
+        self.wait = Wait(self.marionette)
+
+    def is_hidden(self, notification_id):
+        with self.marionette.using_context("chrome"):
+            try:
+                hidden_attr = (
+                        self.marionette
+                        .find_element("id", notification_id)
+                        .get_attribute("hidden")
+                    )
+                return hidden_attr == "true"
+            except NoSuchElementException:
+                return True
+
+    def wait_until_not_hidden(self, notification_id):
+        self.wait.until(
+            lambda m: not self.is_hidden(notification_id),
+            message=("Notification with ID {} is shown."
+                     .format(notification_id)))
+
+    def wait_until_hidden(self, notification_id):
+        self.wait.until(
+            lambda m: self.is_hidden(notification_id),
+            message=("Notification with ID {} is hidden."
+                     .format(notification_id)))
+
+    @contextmanager
+    def wrap_lifetime(self, notification_id):
+        """Yields as soon as the notification is shown, and
+        finally returns as soon as the notification is hidden."""
+
+        self.wait_until_not_hidden(notification_id)
+        try:
+            yield
+        finally:
+            self.wait_until_hidden(notification_id)
+
+
+class Addons(BaseLib):
+    """With this class, an addon can be installed, enabled, disabled
+    or removed. All actions required, such as opening `about:addons`,
+    is done automatically.
+    """
+
+    def __init__(self, marionette_getter):
+        BaseLib.__init__(self, marionette_getter)
+
+    @contextmanager
+    def using_addon_list(self):
+        about_addons = AboutAddonsTab(lambda: self.marionette)
+        about_addons.open_tab()
+        about_addons.set_category_by_id("extension")
+        try:
+            yield about_addons
+        finally:
+            about_addons.close_tab()
+
+    @contextmanager
+    def install_addon_in_two_steps(self, install_url):
+        # open a new tab where the install URL will be opened
+        install_tab = Windows(lambda: self.marionette).current.tabbar.open_tab()
+
+        with self.marionette.using_context("content"):
+            # open the install URL
+            self.marionette.navigate(install_url)
+            # open the first link
+            self.marionette.find_element("tag name", "a").click()
+
+        with self.marionette.using_context("chrome"):
+            notif = _InstallNotifications(self.marionette)
+            wait = Wait(self.marionette)
+
+            with notif.wrap_lifetime("addon-install-blocked-notification"):
+                # Allow the XPI to be downloaded.  ("allow" button)
+                (
+                    self.marionette
+                    .find_element("id", "addon-install-blocked-notification")
+                    .find_element("anon attribute", {"anonid": "button"})
+                    .click()
+                )
+
+            with notif.wrap_lifetime("addon-install-confirmation-notification"):
+                # Confirm installation.
+                (
+                    self.marionette
+                    .find_element("id", "addon-install-confirmation-accept")
+                    .click()
+                )
+
+            if install_tab.selected:
+                # If the installation tab is still selected, the
+                # "install complete" notification is shown.
+                # If the selected tab has changed, there is no such
+                # notification.
+
+                with notif.wrap_lifetime("addon-install-complete-notification"):
+                    # Close the "install complete" notification.
+                    (
+                        self.marionette
+                        .find_element("id",
+                                      "addon-install-complete-notification")
+                        .find_element("anon attribute",
+                                      {"anonid": "closebutton"})
+                        .click()
+                    )
+
+        try:
+            yield
+        finally:
+            install_tab.close()
+
+    def install_addon(self, install_url):
+        with self.install_addon_in_two_steps(install_url):
+            pass
+
+    def remove_addon_by_id(self, addon_id):
+        with self.using_addon_list() as about_addons:
+            addon = about_addons.get_addon_by_id(addon_id)
+            about_addons.remove_addon(addon)
+
+    def enable_addon_by_id(self, addon_id):
+        with self.using_addon_list() as about_addons:
+            addon = about_addons.get_addon_by_id(addon_id)
+            about_addons.enable_addon(addon)
+
+    def disable_addon_by_id(self, addon_id):
+        with self.using_addon_list() as about_addons:
+            addon = about_addons.get_addon_by_id(addon_id)
+            about_addons.disable_addon(addon)
+
+
+class AboutAddonsTab(BaseLib):
+    """This class helps handling an `about:addons` tab.
+    """
+
+    def open_tab(self):
+        self._tab = Windows(lambda: self.marionette).current.tabbar.open_tab()
+        with self.marionette.using_context("content"):
+            self.marionette.navigate("about:addons")
+
+    def close_tab(self):
+        with self.marionette.using_context("chrome"):
+            if self._tab != None:
+                self._tab.close()
+                self._tab = None
+
+    def set_category_by_id(self, category_id):
+        with self.marionette.using_context("content"):
+            (
+                self.marionette
+                .find_element("id", "category-" + category_id)
+                .click()
+            )
+
+    def get_addon_by_id(self, addon_id):
+        with self.marionette.using_context("content"):
+            try:
+                handle = (
+                        self.marionette
+                        .find_element("id", "list-view")
+                        .find_element("css selector",
+                                      ".addon[value='{}']".format(addon_id))
+                    )
+                return Addon(lambda: self.marionette, handle)
+            except NoSuchElementException:
+                return None
+
+    def is_addon_installed(self, addon_id):
+        # Switch categories to dispose of the undo link
+        # which is displayed after clicking the "remove" button.
+        self.set_category_by_id("theme")
+        self.set_category_by_id("extension")
+
+        return self.get_addon_by_id(addon_id) != None
+
+    def enable_addon(self, addon):
+        addon.click_enable()
+        (
+            Wait(self.marionette)
+            .until(lambda m: addon.is_enabled(),
+                   message="The addon has been enabled.")
+        )
+
+    def disable_addon(self, addon):
+        addon.click_disable()
+        (
+            Wait(self.marionette)
+            .until(lambda m: not addon.is_enabled(),
+                   message="The addon has been disabled.")
+        )
+
+    def remove_addon(self, addon):
+        if addon == None:
+            # the addon does not exist
+            return
+        addon_id = addon.addon_id
+        addon.click_remove()
+        (
+            Wait(self.marionette)
+            .until(lambda m: not self.is_addon_installed(addon_id),
+                   message="The addon has been uninstalled.")
+        )
+
+
+class Addon(BaseLib):
+    def __init__(self, marionette_getter, addon_handle):
+        BaseLib.__init__(self, marionette_getter)
+        self._handle = addon_handle
+
+    @property
+    def addon_id(self):
+        with self.marionette.using_context("content"):
+            return self._handle.get_attribute("value")
+
+    def is_enabled(self):
+        with self.marionette.using_context("content"):
+            return self._handle.get_attribute("active") == "true"
+
+    def _click_on_button(self, button_name):
+        with self.marionette.using_context("content"):
+            (
+                self._handle
+                .find_element("anon attribute",
+                              {"anonid": button_name + "-btn"})
+                .click()
+            )
+
+    def click_enable(self):
+        if not self.is_enabled():
+            self._click_on_button("enable")
+
+    def click_disable(self):
+        if self.is_enabled():
+            self._click_on_button("disable")
+
+    def click_remove(self):
+        self._click_on_button("remove")
diff --git a/tests/mozrunner-prefs.ini b/tests/mozrunner-prefs.ini
index c716fe6..d9ba8e4 100644
--- a/tests/mozrunner-prefs.ini
+++ b/tests/mozrunner-prefs.ini
@@ -26,3 +26,7 @@ browser.displayedE10SNotice:4
 [marionette]
 # By default, do not show the setup tab.
 extensions.requestpolicy.welcomeWindowShown=true
+
+# speed up installation and enable/disable/uninstall of add-ons
+extensions.webservice.discoverURL=about:home
+security.dialog_enable_delay=0

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-mozext/requestpolicy.git



More information about the Pkg-mozext-commits mailing list