Bug 1280947 - Dispatch DOM change event on appending file to input; r=automatedtester a=testonly
authorAndreas Tolfsen <ato@mozilla.com>
Thu, 25 Aug 2016 14:18:04 +0100
changeset 348108 31baa11344d77e6901ec93147a606e22e31efff2
parent 348107 f37bc538086ef913c53846b7fe0153604cd0febe
child 348109 cdf6988ace7810606807d1a1d73cfb06b08dc93a
push id6389
push userraliiev@mozilla.com
push dateMon, 19 Sep 2016 13:38:22 +0000
treeherdermozilla-beta@01d67bfe6c81 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersautomatedtester, testonly
bugs1280947
milestone50.0a2
Bug 1280947 - Dispatch DOM change event on appending file to input; r=automatedtester a=testonly CLOSED TREE MozReview-Commit-ID: 6SC01AEkuTs
testing/marionette/addon.js
testing/marionette/harness/marionette/tests/unit/test_certificates.py
testing/marionette/harness/marionette/tests/unit/test_file_upload.py
testing/marionette/interaction.js
testing/marionette/listener.js
new file mode 100644
--- /dev/null
+++ b/testing/marionette/addon.js
@@ -0,0 +1,93 @@
+/* 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/. */
+
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/AddonManager.jsm");
+Cu.import("resource://gre/modules/FileUtils.jsm");
+
+Cu.import("chrome://marionette/content/error.js");
+
+this.EXPORTED_SYMBOLS = ["addon"];
+
+this.addon = {};
+
+/**
+ * Installs Firefox addon.
+ *
+ * If the addon is restartless, it can be used right away. Otherwise a
+ * restart is needed.
+ *
+ * Temporary addons will automatically be unisntalled on shutdown and
+ * do not need to be signed, though they must be restartless.
+ *
+ * @param {string} path
+ *     Full path to the extension package archive to be installed.
+ * @param {boolean=} temporary
+ *     Install the add-on temporarily if true.
+ *
+ * @return {Promise.<string>}
+ *     Addon ID string of the newly installed addon.
+ *
+ * @throws {AddonError}
+ *     if installation fails
+ */
+addon.install = function(path, temporary = false) {
+  return new Promise((resolve, reject) => {
+    let listener = {
+      onInstallEnded: function(install, addon) {
+        resolve(addon.id);
+      },
+
+      onInstallFailed: function(install) {
+        reject(new AddonError(install.error));
+      },
+
+      onInstalled: function(addon) {
+        AddonManager.removeAddonListener(listener);
+        resolve(addon.id);
+      }
+    };
+
+    let file = new FileUtils.File(path);
+
+    // temporary addons
+    if (temp) {
+      AddonManager.addAddonListener(listener);
+      AddonManager.installTemporaryAddon(file);
+    }
+
+    // addons that require restart
+    else {
+      AddonManager.getInstallForFile(file, function(aInstall) {
+        if (aInstall.error != 0) {
+          reject(new AddonError(aInstall.error));
+        }
+        aInstall.addListener(listener);
+        aInstall.install();
+      });
+    }
+  });
+};
+
+/**
+ * Uninstall a Firefox addon.
+ *
+ * If the addon is restartless, it will be uninstalled right
+ * away. Otherwise a restart is necessary.
+ *
+ * @param {string} id
+ *     Addon ID to uninstall.
+ *
+ * @return {Promise}
+ */
+addon.uninstall = function(id) {
+  return new Promise(resolve => {
+    AddonManager.getAddonByID(arguments[0], function(addon) {
+      addon.uninstall();
+    });
+  });
+};
new file mode 100644
--- /dev/null
+++ b/testing/marionette/harness/marionette/tests/unit/test_certificates.py
@@ -0,0 +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/.
+
+from marionette import MarionetteTestCase
+from marionette_driver.errors import UnknownException
+
+
+class TestCertificates(MarionetteTestCase):
+    def test_block_insecure_sites(self):
+        self.marionette.delete_session()
+        self.marionette.start_session()
+
+        self.marionette.navigate(self.fixtures.where_is("test.html", on="http"))
+        self.assertIn("http://", self.marionette.get_url())
+        with self.assertRaises(UnknownException):
+            self.marionette.navigate(self.fixtures.where_is("test.html", on="https"))
+
+    def test_accept_all_insecure(self):
+        self.marionette.delete_session()
+        self.marionette.start_session({"desiredCapability": {"acceptSslCerts": ["*"]}})
+        self.marionette.navigate(self.fixtures.where_is("test.html", on="https"))
+        self.assertIn("https://", self.marionette.url)
+
+    """
+    def test_accept_some_insecure(self):
+        self.marionette.delete_session()
+        self.marionette.start_session({"requiredCapabilities": {"acceptSslCerts": ["127.0.0.1"]}})
+        self.marionette.navigate(self.fixtures.where_is("test.html", on="https"))
+        self.assertIn("https://", self.marionette.url)
+    """
\ No newline at end of file
--- a/testing/marionette/harness/marionette/tests/unit/test_file_upload.py
+++ b/testing/marionette/harness/marionette/tests/unit/test_file_upload.py
@@ -100,16 +100,32 @@ class TestFileUpload(MarionetteTestCase)
             f.write("camembert")
             f.flush()
             self.input.send_keys(f.name)
             self.submit.click()
 
         Wait(self.marionette).until(lambda m: m.get_url() != url)
         self.assertIn("multipart/form-data", self.body.text)
 
+    def test_change_event(self):
+        self.marionette.navigate(single)
+        self.marionette.execute_script("""
+            window.changeEvs = [];
+            let el = arguments[arguments.length - 1];
+            el.addEventListener("change", ev => window.changeEvs.push(ev));
+            console.log(window.changeEvs.length);
+            """, script_args=(self.input,), sandbox=None)
+
+        with tempfile() as f:
+            self.input.send_keys(f.name)
+
+        nevs = self.marionette.execute_script(
+            "return window.changeEvs.length", sandbox=None)
+        self.assertEqual(1, nevs)
+
     def find_inputs(self):
         return self.marionette.find_elements(By.TAG_NAME, "input")
 
     @property
     def input(self):
         return self.find_inputs()[0]
 
     @property
--- a/testing/marionette/interaction.js
+++ b/testing/marionette/interaction.js
@@ -217,16 +217,43 @@ interaction.selectOption = function(el) 
   el.selected = !el.selected;
 
   event.change(parent);
   event.mouseup(parent);
   event.click(parent);
 };
 
 /**
+ * Appends |path| to an <input type=file>'s file list.
+ *
+ * @param {HTMLInputElement} el
+ *     An <input type=file> element.
+ * @param {File} file
+ *     File object to assign to |el|.
+ */
+interaction.uploadFile = function(el, file) {
+  let fs = Array.prototype.slice.call(el.files);
+  fs.push(file);
+
+  // <input type=file> opens OS widget dialogue
+  // which means the mousedown/focus/mouseup/click events
+  // occur before the change event
+  event.mouseover(el);
+  event.mousemove(el);
+  event.mousedown(el);
+  event.focus(el);
+  event.mouseup(el);
+  event.click(el);
+
+  el.mozSetFileArray(fs);
+
+  event.change(el);
+};
+
+/**
  * Locate the <select> element that encapsulate an <option> element.
  *
  * @param {HTMLOptionElement} optionEl
  *     Option element.
  *
  * @return {HTMLSelectElement}
  *     Select element wrapping |optionEl|.
  *
--- a/testing/marionette/listener.js
+++ b/testing/marionette/listener.js
@@ -590,30 +590,31 @@ function setTestName(msg) {
   sendOk(msg.json.command_id);
 }
 
 /**
  * Receive file objects from chrome in order to complete a
  * sendKeysToElement action on a file input element.
  */
 function receiveFiles(msg) {
-  if ('error' in msg.json) {
+  if ("error" in msg.json) {
     let err = new InvalidArgumentError(msg.json.error);
     sendError(err, msg.json.command_id);
     return;
   }
+
   if (!fileInputElement) {
     let err = new InvalidElementStateError("receiveFiles called with no valid fileInputElement");
     sendError(err, msg.json.command_id);
     return;
   }
-  let fs = Array.prototype.slice.call(fileInputElement.files);
-  fs.push(msg.json.file);
-  fileInputElement.mozSetFileArray(fs);
+
+  interaction.uploadFile(fileInputElement, msg.json.file);
   fileInputElement = null;
+
   sendOk(msg.json.command_id);
 }
 
 /**
  * This function creates a touch event given a touch type and a touch
  */
 function emitTouchEvent(type, touch) {
   if (!wasInterrupted()) {