Bug 599233 - add about dialog app update tests. r=mhowell
☠☠ backed out by 913880b6d5c6 ☠ ☠
authorRobert Strong <robert.bugzilla@gmail.com>
Wed, 16 Jan 2019 13:21:55 -0800
changeset 511317 1293016267a73f6408b93c203e8b28242b2e2be6
parent 511316 715c5bd0dc5a6a0b2ee80a0d5cc50076dcf6936c
child 511318 722bd413de72d9422924c052648a098ae48f3d4d
push id10547
push userffxbld-merge
push dateMon, 21 Jan 2019 13:03:58 +0000
treeherdermozilla-beta@24ec1916bffe [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmhowell
bugs599233
milestone66.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 599233 - add about dialog app update tests. r=mhowell Adds disabledForTesting checks to the about dialog app update code Makes a copy of update.sjs for browser-chrome tests so the changes don't break chrome tests Prepares for the removal of the chrome tests and the old app update UI Adds 14 tests for app update in the about dialog
browser/base/content/aboutDialog-appUpdater.js
toolkit/mozapps/update/tests/browser/app_update.sjs
toolkit/mozapps/update/tests/browser/browser.ini
toolkit/mozapps/update/tests/browser/browser_about_bc_downloaded.js
toolkit/mozapps/update/tests/browser/browser_about_bc_downloaded_staged.js
toolkit/mozapps/update/tests/browser/browser_about_fc_check_cantApply.js
toolkit/mozapps/update/tests/browser/browser_about_fc_check_malformedXML.js
toolkit/mozapps/update/tests/browser/browser_about_fc_check_noUpdate.js
toolkit/mozapps/update/tests/browser/browser_about_fc_check_unsupported.js
toolkit/mozapps/update/tests/browser/browser_about_fc_downloadAuto.js
toolkit/mozapps/update/tests/browser/browser_about_fc_downloadAuto_staging.js
toolkit/mozapps/update/tests/browser/browser_about_fc_downloadOptIn.js
toolkit/mozapps/update/tests/browser/browser_about_fc_downloadOptIn_staging.js
toolkit/mozapps/update/tests/browser/browser_about_fc_patch_completeBadSize.js
toolkit/mozapps/update/tests/browser/browser_about_fc_patch_partialBadSize.js
toolkit/mozapps/update/tests/browser/browser_about_fc_patch_partialBadSize_complete.js
toolkit/mozapps/update/tests/browser/browser_about_fc_patch_partialBadSize_completeBadSize.js
toolkit/mozapps/update/tests/browser/browser_updatesBasicPrompt.js
toolkit/mozapps/update/tests/browser/browser_updatesCantApply.js
toolkit/mozapps/update/tests/browser/head.js
toolkit/mozapps/update/tests/browser/testConstants.js
toolkit/mozapps/update/tests/chrome/chrome.ini
toolkit/mozapps/update/tests/chrome/update.sjs
toolkit/mozapps/update/tests/data/update.sjs
toolkit/mozapps/update/tests/moz.build
toolkit/mozapps/update/updater/updater.cpp
--- a/browser/base/content/aboutDialog-appUpdater.js
+++ b/browser/base/content/aboutDialog-appUpdater.js
@@ -8,18 +8,19 @@
 /* import-globals-from aboutDialog.js */
 
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 ChromeUtils.import("resource://gre/modules/DownloadUtils.jsm");
 
 ChromeUtils.defineModuleGetter(this, "UpdateUtils",
                                "resource://gre/modules/UpdateUtils.jsm");
 
-const PREF_APP_UPDATE_CANCELATIONS_OSX = "app.update.cancelations.osx";
-const PREF_APP_UPDATE_ELEVATE_NEVER    = "app.update.elevate.never";
+const PREF_APP_UPDATE_CANCELATIONS_OSX   = "app.update.cancelations.osx";
+const PREF_APP_UPDATE_ELEVATE_NEVER      = "app.update.elevate.never";
+const PREF_APP_UPDATE_DISABLEDFORTESTING = "app.update.disabledForTesting";
 
 var gAppUpdater;
 
 function onUnload(aEvent) {
   if (gAppUpdater.isChecking)
     gAppUpdater.checker.stopCurrentCheck();
   // Safe to call even when there isn't a download in progress.
   gAppUpdater.removeDownloadListener();
@@ -126,17 +127,30 @@ appUpdater.prototype =
     if (this.update)
       return this.update.state == "downloading";
     return this.um.activeUpdate &&
            this.um.activeUpdate.state == "downloading";
   },
 
   // true when updating has been disabled by enterprise policy
   get updateDisabledByPolicy() {
-    return Services.policies && !Services.policies.isAllowed("appUpdate");
+    return Services.policies && !Services.policies.isAllowed("appUpdate") ||
+           this.disabledForTesting;
+  },
+
+  get disabledForTesting() {
+    let marionetteRunning = false;
+
+    if ("nsIMarionette" in Ci) {
+      marionetteRunning = Cc["@mozilla.org/remote/marionette;1"].
+                          createInstance(Ci.nsIMarionette).running;
+    }
+
+    return (Cu.isInAutomation || marionetteRunning) &&
+           Services.prefs.getBoolPref(PREF_APP_UPDATE_DISABLEDFORTESTING, false);
   },
 
   // true when updating in background is enabled.
   get backgroundUpdateEnabled() {
     return !this.updateDisabledByPolicy &&
            gAppUpdater.aus.canStageUpdates;
   },
 
rename from toolkit/mozapps/update/tests/data/update.sjs
rename to toolkit/mozapps/update/tests/browser/app_update.sjs
--- a/toolkit/mozapps/update/tests/data/update.sjs
+++ b/toolkit/mozapps/update/tests/browser/app_update.sjs
@@ -34,18 +34,19 @@ scriptFile.append("testConstants.js");
 loadHelperScript(scriptFile);
 
 scriptFile = getTestDataFile("sharedUpdateXML.js");
 loadHelperScript(scriptFile);
 
 const SERVICE_URL = URL_HOST + "/" + REL_PATH_DATA + FILE_SIMPLE_MAR;
 const BAD_SERVICE_URL = URL_HOST + "/" + REL_PATH_DATA + "not_here.mar";
 
-const SLOW_MAR_DOWNLOAD_INTERVAL = 100;
-var gTimer;
+const SLOW_RESPONSE_INTERVAL = 10;
+var gSlowDownloadTimer;
+var gSlowCheckTimer;
 
 function handleRequest(aRequest, aResponse) {
   let params = { };
   if (aRequest.queryString) {
     params = parseQueryString(aRequest.queryString);
   }
 
   let statusCode = params.statusCode ? parseInt(params.statusCode) : 200;
@@ -57,64 +58,71 @@ function handleRequest(aRequest, aRespon
   // downloading before the ui has loaded. By specifying a serviceURL for the
   // update patch that points to this file and has a slowDownloadMar param the
   // mar will be downloaded asynchronously which will allow the ui to load
   // before the download completes.
   if (params.slowDownloadMar) {
     aResponse.processAsync();
     aResponse.setHeader("Content-Type", "binary/octet-stream");
     aResponse.setHeader("Content-Length", SIZE_SIMPLE_MAR);
-    var continueFile = getTestDataFile("continue");
+    var continueFile = getTestDataFile(CONTINUE_DOWNLOAD);
     var contents = readFileBytes(getTestDataFile(FILE_SIMPLE_MAR));
-    gTimer = Cc["@mozilla.org/timer;1"].
-             createInstance(Ci.nsITimer);
-    gTimer.initWithCallback(function(aTimer) {
+    gSlowDownloadTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+    gSlowDownloadTimer.initWithCallback(function(aTimer) {
       if (continueFile.exists()) {
-        gTimer.cancel();
-        aResponse.write(contents);
-        aResponse.finish();
+        try {
+          // If the continue file is in use try again the next time the timer
+          // fires.
+          continueFile.remove(false);
+          gSlowDownloadTimer.cancel();
+          aResponse.write(contents);
+          aResponse.finish();
+        } catch (e) {
+        }
       }
-    }, SLOW_MAR_DOWNLOAD_INTERVAL, Ci.nsITimer.TYPE_REPEATING_SLACK);
+    }, SLOW_RESPONSE_INTERVAL, Ci.nsITimer.TYPE_REPEATING_SLACK);
     return;
   }
 
   if (params.uiURL) {
-    let remoteType = "";
-    if (!params.remoteNoTypeAttr && params.uiURL == "BILLBOARD") {
-      remoteType = " " + params.uiURL.toLowerCase() + "=\"1\"";
-    }
     aResponse.write("<html><head><meta http-equiv=\"content-type\" content=" +
-                    "\"text/html; charset=utf-8\"></head><body" +
-                    remoteType + ">" + params.uiURL +
-                    "<br><br>this is a test mar that will not affect your " +
-                    "build.</body></html>");
+                    "\"text/html; charset=utf-8\"></head><body>" +
+                    params.uiURL + "<br><br>this is a test mar that will not " +
+                    "affect your build.</body></html>");
     return;
   }
 
   if (params.xmlMalformed) {
-    aResponse.write("xml error");
+    respond(aResponse, params, "xml error");
     return;
   }
 
   if (params.noUpdates) {
-    aResponse.write(getRemoteUpdatesXMLString(""));
+    respond(aResponse, params, getRemoteUpdatesXMLString(""));
     return;
   }
 
   if (params.unsupported) {
-    aResponse.write(getRemoteUpdatesXMLString("  <update type=\"major\" " +
-                                              "unsupported=\"true\" " +
-                                              "detailsURL=\"" + URL_HOST +
-                                              "\"></update>\n"));
+    let detailsURL = params.detailsURL ? params.detailsURL : URL_HOST;
+    let unsupportedXML = getRemoteUpdatesXMLString("  <update type=\"major\" " +
+                                                   "unsupported=\"true\" " +
+                                                   "detailsURL=\"" + detailsURL +
+                                                   "\"></update>\n");
+    respond(aResponse, params, unsupportedXML);
     return;
   }
 
   let size;
   let patches = "";
-  let url = params.badURL ? BAD_SERVICE_URL : SERVICE_URL;
+  let url = "";
+  if (params.useSlowDownloadMar) {
+    url = URL_HTTP_UPDATE_SJS + "?slowDownloadMar=1"
+  } else {
+    url = params.badURL ? BAD_SERVICE_URL : SERVICE_URL
+  }
   if (!params.partialPatchOnly) {
     size = SIZE_SIMPLE_MAR + (params.invalidCompleteSize ? "1" : "");
     let patchProps = {type: "complete",
                       url: url,
                       size: size};
     patches += getRemotePatchString(patchProps);
   }
 
@@ -147,17 +155,41 @@ function handleRequest(aRequest, aRespon
     updateProps.buildID = params.buildID;
   }
 
   if (params.promptWaitTime) {
     updateProps.promptWaitTime = params.promptWaitTime;
   }
 
   let updates = getRemoteUpdateString(updateProps, patches);
-  aResponse.write(getRemoteUpdatesXMLString(updates));
+  let xml = getRemoteUpdatesXMLString(updates);
+  respond(aResponse, params, xml);
+}
+
+function respond(aResponse, aParams, aResponseString) {
+  if (aParams.slowUpdateCheck) {
+    aResponse.processAsync();
+    var continueFile = getTestDataFile(CONTINUE_CHECK);
+    gSlowCheckTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+    gSlowCheckTimer.initWithCallback(function(aTimer) {
+      if (continueFile.exists()) {
+        try {
+          // If the continue file is in use try again the next time the timer
+          // fires.
+          continueFile.remove(false);
+          gSlowCheckTimer.cancel();
+          aResponse.write(aResponseString);
+          aResponse.finish();
+        } catch (e) {
+        }
+      }
+    }, SLOW_RESPONSE_INTERVAL, Ci.nsITimer.TYPE_REPEATING_SLACK);
+  } else {
+    aResponse.write(aResponseString);
+  }
 }
 
 /**
  * Helper function to create a JS object representing the url parameters from
  * the request's queryString.
  *
  * @param  aQueryString
  *         The request's query string.
--- a/toolkit/mozapps/update/tests/browser/browser.ini
+++ b/toolkit/mozapps/update/tests/browser/browser.ini
@@ -1,27 +1,51 @@
 [DEFAULT]
 tags = appupdate
 support-files =
   head.js
   downloadPage.html
   testConstants.js
+  app_update.sjs
 
+[browser_about_bc_downloaded.js]
+[browser_about_bc_downloaded_staged.js]
+skip-if = asan
+reason = Bug 1168003
+[browser_about_fc_check_cantApply.js]
+skip-if = os != 'win'
+reason = test must be able to prevent file deletion.
+[browser_about_fc_check_malformedXML.js]
+[browser_about_fc_check_noUpdate.js]
+[browser_about_fc_check_unsupported.js]
+[browser_about_fc_downloadAuto.js]
+[browser_about_fc_downloadAuto_staging.js]
+skip-if = asan
+reason = Bug 1168003
+[browser_about_fc_downloadOptIn.js]
+[browser_about_fc_downloadOptIn_staging.js]
+skip-if = asan
+reason = Bug 1168003
+[browser_about_fc_patch_completeBadSize.js]
+[browser_about_fc_patch_partialBadSize.js]
+[browser_about_fc_patch_partialBadSize_complete.js]
+[browser_about_fc_patch_partialBadSize_completeBadSize.js]
 [browser_TelemetryUpdatePing.js]
 [browser_updateAutoPrefUI.js]
 skip-if = os != 'win'
 reason = Tests that update config is properly written to file, which is a Windows-only feature
 [browser_updatesBackgroundWindow.js]
 [browser_updatesBackgroundWindowFailures.js]
 [browser_updatesBasicPrompt.js]
 skip-if = asan
 reason = Bug 1168003
 [browser_updatesBasicPromptNoStaging.js]
 [browser_updatesCantApply.js]
 skip-if = os != 'win'
+reason = test must be able to prevent file deletion.
 [browser_updatesCompleteAndPartialPatchesWithBadCompleteSize.js]
 [browser_updatesCompleteAndPartialPatchesWithBadPartialSize.js]
 [browser_updatesCompleteAndPartialPatchesWithBadSizes.js]
 [browser_updatesCompletePatchApplyFailure.js]
 [browser_updatesCompletePatchWithBadCompleteSize.js]
 [browser_updatesDownloadFailures.js]
 [browser_updatesMalformedXml.js]
 [browser_updatesPartialPatchApplyFailure.js]
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_about_bc_downloaded.js
@@ -0,0 +1,17 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for About Dialog background check for updates
+// with the update downloaded.
+add_task(async function aboutDialog_backgroundCheck_downloaded() {
+  let updateParams = "";
+  await runAboutDialogUpdateTest(updateParams, true, [
+    {
+      panelId: "apply",
+      checkActiveUpdate: {state: STATE_PENDING},
+      continueFile: null,
+    },
+  ]);
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_about_bc_downloaded_staged.js
@@ -0,0 +1,25 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for About Dialog background check for updates
+// with the update downloaded and staged.
+add_task(async function aboutDialog_backgroundCheck_downloaded_staged() {
+  await SpecialPowers.pushPrefEnv({
+    set: [
+      [PREF_APP_UPDATE_STAGING_ENABLED, true],
+    ],
+  });
+
+  // Since the partial should be successful specify an invalid size for the
+  // complete update.
+  let updateParams = "&invalidCompleteSize=1";
+  await runAboutDialogUpdateTest(updateParams, true, [
+    {
+      panelId: "apply",
+      checkActiveUpdate: {state: STATE_APPLIED},
+      continueFile: null,
+    },
+  ]);
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_about_fc_check_cantApply.js
@@ -0,0 +1,24 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for About Dialog foreground check for updates
+// without the ability to apply updates.
+add_task(async function aboutDialog_foregroundCheck_cantApply() {
+  lockWriteTestFile();
+
+  let updateParams = "";
+  await runAboutDialogUpdateTest(updateParams, false, [
+    {
+      panelId: "checkingForUpdates",
+      checkActiveUpdate: null,
+      continueFile: CONTINUE_CHECK,
+    },
+    {
+      panelId: "manualUpdate",
+      checkActiveUpdate: null,
+      continueFile: null,
+    },
+  ]);
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_about_fc_check_malformedXML.js
@@ -0,0 +1,22 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for About Dialog foreground check for updates
+// with a malformed update XML file.
+add_task(async function aboutDialog_foregroundCheck_malformedXML() {
+  let updateParams = "&xmlMalformed=1";
+  await runAboutDialogUpdateTest(updateParams, false, [
+    {
+      panelId: "checkingForUpdates",
+      checkActiveUpdate: null,
+      continueFile: CONTINUE_CHECK,
+    },
+    {
+      panelId: "noUpdatesFound",
+      checkActiveUpdate: null,
+      continueFile: null,
+    },
+  ]);
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_about_fc_check_noUpdate.js
@@ -0,0 +1,22 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for About Dialog foreground check for updates
+// with no update available.
+add_task(async function aboutDialog_foregroundCheck_noUpdate() {
+  let updateParams = "&noUpdates=1";
+  await runAboutDialogUpdateTest(updateParams, false, [
+    {
+      panelId: "checkingForUpdates",
+      checkActiveUpdate: null,
+      continueFile: CONTINUE_CHECK,
+    },
+    {
+      panelId: "noUpdatesFound",
+      checkActiveUpdate: null,
+      continueFile: null,
+    },
+  ]);
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_about_fc_check_unsupported.js
@@ -0,0 +1,22 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for About Dialog foreground check for updates
+// with an unsupported update.
+add_task(async function aboutDialog_foregroundCheck_unsupported() {
+  let updateParams = "&unsupported=1";
+  await runAboutDialogUpdateTest(updateParams, false, [
+    {
+      panelId: "checkingForUpdates",
+      checkActiveUpdate: null,
+      continueFile: CONTINUE_CHECK,
+    },
+    {
+      panelId: "unsupportedSystem",
+      checkActiveUpdate: null,
+      continueFile: null,
+    },
+  ]);
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_about_fc_downloadAuto.js
@@ -0,0 +1,29 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for About Dialog foreground check for updates
+// with an automatic download.
+add_task(async function aboutDialog_foregroundCheck_downloadAuto() {
+  // Since the partial should be successful specify an invalid size for the
+  // complete update.
+  let updateParams = "&invalidCompleteSize=1";
+  await runAboutDialogUpdateTest(updateParams, false, [
+    {
+      panelId: "checkingForUpdates",
+      checkActiveUpdate: null,
+      continueFile: CONTINUE_CHECK,
+    },
+    {
+      panelId: "downloading",
+      checkActiveUpdate: {state: STATE_DOWNLOADING},
+      continueFile: CONTINUE_DOWNLOAD,
+    },
+    {
+      panelId: "apply",
+      checkActiveUpdate: {state: STATE_PENDING},
+      continueFile: null,
+    },
+  ]);
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_about_fc_downloadAuto_staging.js
@@ -0,0 +1,40 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for About Dialog foreground check for updates
+// with an automatic download and update staging.
+add_task(async function aboutDialog_foregroundCheck_downloadAuto_staging() {
+  await SpecialPowers.pushPrefEnv({
+    set: [
+      [PREF_APP_UPDATE_STAGING_ENABLED, true],
+    ],
+  });
+
+  // Since the partial should be successful specify an invalid size for the
+  // complete update.
+  let updateParams = "&invalidCompleteSize=1";
+  await runAboutDialogUpdateTest(updateParams, false, [
+    {
+      panelId: "checkingForUpdates",
+      checkActiveUpdate: null,
+      continueFile: CONTINUE_CHECK,
+    },
+    {
+      panelId: "downloading",
+      checkActiveUpdate: {state: STATE_DOWNLOADING},
+      continueFile: CONTINUE_DOWNLOAD,
+    },
+    {
+      panelId: "applying",
+      checkActiveUpdate: {state: STATE_PENDING},
+      continueFile: CONTINUE_STAGING,
+    },
+    {
+      panelId: "apply",
+      checkActiveUpdate: {state: STATE_APPLIED},
+      continueFile: null,
+    },
+  ]);
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_about_fc_downloadOptIn.js
@@ -0,0 +1,36 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for About Dialog foreground check for updates
+// with a manual download.
+add_task(async function aboutDialog_foregroundCheck_downloadOptIn() {
+  await UpdateUtils.setAppUpdateAutoEnabled(false);
+
+  // Since the partial should be successful specify an invalid size for the
+  // complete update.
+  let updateParams = "&invalidCompleteSize=1";
+  await runAboutDialogUpdateTest(updateParams, false, [
+    {
+      panelId: "checkingForUpdates",
+      checkActiveUpdate: null,
+      continueFile: CONTINUE_CHECK,
+    },
+    {
+      panelId: "downloadAndInstall",
+      checkActiveUpdate: null,
+      continueFile: null,
+    },
+    {
+      panelId: "downloading",
+      checkActiveUpdate: {state: STATE_DOWNLOADING},
+      continueFile: CONTINUE_DOWNLOAD,
+    },
+    {
+      panelId: "apply",
+      checkActiveUpdate: {state: STATE_PENDING},
+      continueFile: null,
+    },
+  ]);
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_about_fc_downloadOptIn_staging.js
@@ -0,0 +1,46 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for About Dialog foreground check for updates
+// with a manual download and update staging.
+add_task(async function aboutDialog_foregroundCheck_downloadOptIn_staging() {
+  await SpecialPowers.pushPrefEnv({
+    set: [
+      [PREF_APP_UPDATE_STAGING_ENABLED, true],
+    ],
+  });
+  await UpdateUtils.setAppUpdateAutoEnabled(false);
+
+  // Since the partial should be successful specify an invalid size for the
+  // complete update.
+  let updateParams = "&invalidCompleteSize=1";
+  await runAboutDialogUpdateTest(updateParams, false, [
+    {
+      panelId: "checkingForUpdates",
+      checkActiveUpdate: null,
+      continueFile: CONTINUE_CHECK,
+    },
+    {
+      panelId: "downloadAndInstall",
+      checkActiveUpdate: null,
+      continueFile: null,
+    },
+    {
+      panelId: "downloading",
+      checkActiveUpdate: {state: STATE_DOWNLOADING},
+      continueFile: CONTINUE_DOWNLOAD,
+    },
+    {
+      panelId: "applying",
+      checkActiveUpdate: {state: STATE_PENDING},
+      continueFile: CONTINUE_STAGING,
+    },
+    {
+      panelId: "apply",
+      checkActiveUpdate: {state: STATE_APPLIED},
+      continueFile: null,
+    },
+  ]);
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_about_fc_patch_completeBadSize.js
@@ -0,0 +1,27 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for About Dialog foreground check for updates
+// with a complete bad size patch.
+add_task(async function aboutDialog_foregroundCheck_completeBadSize() {
+  let updateParams = "&completePatchOnly=1&invalidCompleteSize=1";
+  await runAboutDialogUpdateTest(updateParams, false, [
+    {
+      panelId: "checkingForUpdates",
+      checkActiveUpdate: null,
+      continueFile: CONTINUE_CHECK,
+    },
+    {
+      panelId: "downloading",
+      checkActiveUpdate: {state: STATE_DOWNLOADING},
+      continueFile: CONTINUE_DOWNLOAD,
+    },
+    {
+      panelId: "downloadFailed",
+      checkActiveUpdate: null,
+      continueFile: null,
+    },
+  ]);
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_about_fc_patch_partialBadSize.js
@@ -0,0 +1,27 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for About Dialog foreground check for updates
+// with a partial bad size patch.
+add_task(async function aboutDialog_foregroundCheck_partialBadSize() {
+  let updateParams = "&partialPatchOnly=1&invalidPartialSize=1";
+  await runAboutDialogUpdateTest(updateParams, false, [
+    {
+      panelId: "checkingForUpdates",
+      checkActiveUpdate: null,
+      continueFile: CONTINUE_CHECK,
+    },
+    {
+      panelId: "downloading",
+      checkActiveUpdate: {state: STATE_DOWNLOADING},
+      continueFile: CONTINUE_DOWNLOAD,
+    },
+    {
+      panelId: "downloadFailed",
+      checkActiveUpdate: null,
+      continueFile: null,
+    },
+  ]);
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_about_fc_patch_partialBadSize_complete.js
@@ -0,0 +1,32 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for About Dialog foreground check for updates
+// with a partial bad size patch and a complete patch.
+add_task(async function aboutDialog_foregroundCheck_partialBadSize_complete() {
+  let updateParams = "&invalidPartialSize=1";
+  await runAboutDialogUpdateTest(updateParams, false, [
+    {
+      panelId: "checkingForUpdates",
+      checkActiveUpdate: null,
+      continueFile: CONTINUE_CHECK,
+    },
+    {
+      panelId: "downloading",
+      checkActiveUpdate: {state: STATE_DOWNLOADING},
+      continueFile: CONTINUE_DOWNLOAD,
+    },
+    {
+      panelId: "downloading",
+      checkActiveUpdate: {state: STATE_DOWNLOADING},
+      continueFile: CONTINUE_DOWNLOAD,
+    },
+    {
+      panelId: "apply",
+      checkActiveUpdate: {state: STATE_PENDING},
+      continueFile: null,
+    },
+  ]);
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_about_fc_patch_partialBadSize_completeBadSize.js
@@ -0,0 +1,32 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for About Dialog foreground check for updates
+// with a partial bad size patch and a complete bad size patch.
+add_task(async function aboutDialog_foregroundCheck_partialBadSize_completeBadSize() {
+  let updateParams = "&invalidPartialSize=1&invalidCompleteSize=1";
+  await runAboutDialogUpdateTest(updateParams, false, [
+    {
+      panelId: "checkingForUpdates",
+      checkActiveUpdate: null,
+      continueFile: CONTINUE_CHECK,
+    },
+    {
+      panelId: "downloading",
+      checkActiveUpdate: {state: STATE_DOWNLOADING},
+      continueFile: CONTINUE_DOWNLOAD,
+    },
+    {
+      panelId: "downloading",
+      checkActiveUpdate: {state: STATE_DOWNLOADING},
+      continueFile: CONTINUE_DOWNLOAD,
+    },
+    {
+      panelId: "downloadFailed",
+      checkActiveUpdate: null,
+      continueFile: null,
+    },
+  ]);
+});
--- a/toolkit/mozapps/update/tests/browser/browser_updatesBasicPrompt.js
+++ b/toolkit/mozapps/update/tests/browser/browser_updatesBasicPrompt.js
@@ -1,16 +1,15 @@
 add_task(async function testBasicPrompt() {
   SpecialPowers.pushPrefEnv({set: [
     [PREF_APP_UPDATE_STAGING_ENABLED, true],
   ]});
   await UpdateUtils.setAppUpdateAutoEnabled(false);
 
   let updateParams = "promptWaitTime=0";
-  gUseTestUpdater = true;
 
   await runUpdateTest(updateParams, 1, [
     {
       notificationId: "update-available",
       button: "button",
       beforeClick() {
         checkWhatsNewLink(window, "update-available-whats-new");
       },
--- a/toolkit/mozapps/update/tests/browser/browser_updatesCantApply.js
+++ b/toolkit/mozapps/update/tests/browser/browser_updatesCantApply.js
@@ -1,36 +1,19 @@
 add_task(async function testBasicPrompt() {
   SpecialPowers.pushPrefEnv({set: [[PREF_APP_UPDATE_SERVICE_ENABLED, false]]});
+  lockWriteTestFile();
 
   let updateParams = "promptWaitTime=0";
 
-  let file = getWriteTestFile();
-  file.create(file.NORMAL_FILE_TYPE, 0o444);
-  file.fileAttributesWin |= file.WFA_READONLY;
-  file.fileAttributesWin &= ~file.WFA_READWRITE;
-
   await runUpdateTest(updateParams, 1, [
     {
       notificationId: "update-manual",
       button: "button",
       async cleanup() {
         await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
         is(gBrowser.selectedBrowser.currentURI.spec,
            URL_MANUAL_UPDATE, "Landed on manual update page.");
         gBrowser.removeTab(gBrowser.selectedTab);
-        getWriteTestFile();
       },
     },
   ]);
 });
-
-function getWriteTestFile() {
-  let file = getUpdatesRootDir();
-  file.append(FILE_UPDATE_TEST);
-  file.QueryInterface(Ci.nsILocalFileWin);
-  if (file.exists()) {
-    file.fileAttributesWin |= file.WFA_READWRITE;
-    file.fileAttributesWin &= ~file.WFA_READONLY;
-    file.remove(true);
-  }
-  return file;
-}
--- a/toolkit/mozapps/update/tests/browser/head.js
+++ b/toolkit/mozapps/update/tests/browser/head.js
@@ -11,65 +11,134 @@ const IS_WIN = ("@mozilla.org/windows-re
 
 const BIN_SUFFIX = (IS_WIN ? ".exe" : "");
 const FILE_UPDATER_BIN = "updater" + (IS_MACOSX ? ".app" : BIN_SUFFIX);
 const FILE_UPDATER_BIN_BAK = FILE_UPDATER_BIN + ".bak";
 
 const PREF_APP_UPDATE_INTERVAL = "app.update.interval";
 const PREF_APP_UPDATE_LASTUPDATETIME = "app.update.lastUpdateTime.background-update-timer";
 
-let gRembemberedPrefs = [];
-
 const DATA_URI_SPEC =  "chrome://mochitests/content/browser/toolkit/mozapps/update/tests/browser/";
 
 var DEBUG_AUS_TEST = true;
-var gUseTestUpdater = false;
 
 const LOG_FUNCTION = info;
 
 const MAX_UPDATE_COPY_ATTEMPTS = 10;
 
 /* import-globals-from testConstants.js */
 Services.scriptloader.loadSubScript(DATA_URI_SPEC + "testConstants.js", this);
 /* import-globals-from ../data/shared.js */
 Services.scriptloader.loadSubScript(DATA_URI_SPEC + "shared.js", this);
 
 var gURLData = URL_HOST + "/" + REL_PATH_DATA;
 const URL_MANUAL_UPDATE = gURLData + "downloadPage.html";
 
 const gEnv = Cc["@mozilla.org/process/environment;1"].
              getService(Ci.nsIEnvironment);
 
-const NOTIFICATIONS = [
-  "update-available",
-  "update-manual",
-  "update-restart",
-];
-
 let gOriginalUpdateAutoValue = null;
 
 /**
- * Delay for a very short period. Useful for moving the code after this
- * to the back of the event loop.
+ * Creates the continue file used to signal that update staging or the mock http
+ * server should continue. The delay this creates allows the tests to verify the
+ * user interfaces before they auto advance to phases of an update. The continue
+ * file for staging will be deleted by the test updater and the continue file
+ * for update check and update download requests will be deleted by the test
+ * http server handler implemented in app_update.sjs. The test returns a promise
+ * so the test can wait on the deletion of the continue file when necessary.
  *
- * @return A promise which will resolve after a very short period.
+ * @param  leafName
+ *         The leafName of the file to create. This should be one of the
+ *         folowing constants that are defined in testConstants.js:
+ *         CONTINUE_CHECK
+ *         CONTINUE_DOWNLOAD
+ *         CONTINUE_STAGING
+ * @return Promise
+ *         Resolves when the file is deleted.
+ *         Rejects if timeout is exceeded or condition ever throws.
+ * @throws If the file already exists.
  */
-function delay() {
-  return new Promise(resolve => executeSoon(resolve));
+async function continueFileHandler(leafName) {
+  // The default number of retries of 50 in TestUtils.waitForCondition is
+  // sufficient for test http server requests. The total time to wait with the
+  // default interval of 100 is approximately 5 seconds.
+  let retries = undefined;
+  let continueFile;
+  if (leafName == CONTINUE_STAGING) {
+    debugDump("creating " + leafName + " file for slow update staging");
+    // Use 100 retries for staging requests to lessen the likelihood of tests
+    // intermittently failing on debug builds due to launching the updater. The
+    // total time to wait with the default interval of 100 is approximately 10
+    // seconds. The test updater uses the same values.
+    retries = 100;
+    continueFile = getUpdatesPatchDir();
+    continueFile.append(leafName);
+  } else {
+    debugDump("creating " + leafName + " file for slow http server requests");
+    continueFile = Services.dirsvc.get("CurWorkD", Ci.nsIFile);
+    let continuePath = REL_PATH_DATA + leafName;
+    let continuePathParts = continuePath.split("/");
+    for (let i = 0; i < continuePathParts.length; ++i) {
+      continueFile.append(continuePathParts[i]);
+    }
+  }
+  if (continueFile.exists()) {
+    throw new Error("The continue file should not exist, path: " +
+                    continueFile.path);
+  }
+  continueFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, PERMS_FILE);
+  return BrowserTestUtils.waitForCondition(() =>
+    (!continueFile.exists()),
+    "Waiting for file to be deleted, path: " + continueFile.path,
+    undefined, retries);
+
+}
+
+/**
+ * Creates and locks the app update write test file so it is possible to test
+ * when the user doesn't have write access to update. Since this is only
+ * possible on Windows the function throws when it is called on other platforms.
+ * This uses registerCleanupFunction to remove the lock and the file when the
+ * test completes.
+ *
+ * @throws If the function is called on a platform other than Windows.
+ */
+function lockWriteTestFile() {
+  if (AppConstants.platform != "win") {
+    throw new Error("Windows only test function called");
+  }
+  let file = getUpdatesRootDir();
+  file.append(FILE_UPDATE_TEST);
+  file.QueryInterface(Ci.nsILocalFileWin);
+  // Remove the file if it exists just in case.
+  if (file.exists()) {
+    file.fileAttributesWin |= file.WFA_READWRITE;
+    file.fileAttributesWin &= ~file.WFA_READONLY;
+    file.remove(false);
+  }
+  file.create(file.NORMAL_FILE_TYPE, 0o444);
+  file.fileAttributesWin |= file.WFA_READONLY;
+  file.fileAttributesWin &= ~file.WFA_READWRITE;
+  registerCleanupFunction(() => {
+    file.fileAttributesWin |= file.WFA_READWRITE;
+    file.fileAttributesWin &= ~file.WFA_READONLY;
+    file.remove(false);
+  });
 }
 
 /**
  * Gets the update version info for the update url parameters to send to
- * update.sjs.
+ * app_update.sjs.
  *
  * @param  aAppVersion (optional)
  *         The application version for the update snippet. If not specified the
  *         current application version will be used.
  * @return The url parameters for the application and platform version to send
- *         to update.sjs.
+ *         to app_update.sjs.
  */
 function getVersionParams(aAppVersion) {
   let appInfo = Services.appinfo;
   return "&appVersion=" + (aAppVersion ? aAppVersion : appInfo.version);
 }
 
 /**
  * Clean up updates list and the updates directory.
@@ -119,17 +188,17 @@ add_task(async function setDefaults() {
 });
 
 /**
  * Runs a typical update test. Will set various common prefs for using the
  * updater doorhanger, runs the provided list of steps, and makes sure
  * everything is cleaned up afterwards.
  *
  * @param  updateParams
- *         URL-encoded params which will be sent to update.sjs.
+ *         Params which will be sent to app_update.sjs.
  * @param  checkAttempts
  *         How many times to check for updates. Useful for testing the UI
  *         for check failures.
  * @param  steps
  *         A list of test steps to perform, specifying expected doorhangers
  *         and additional validation/cleanup callbacks.
  * @return A promise which will resolve once all of the steps have been run
  *         and cleanup has been performed.
@@ -313,31 +382,30 @@ function checkWhatsNewLink(win, id, url)
   let whatsNewLink = win.document.getElementById(id);
   is(whatsNewLink.href,
      url || URL_HTTP_UPDATE_SJS + "?uiURL=DETAILS",
      "What's new link points to the test_details URL");
   is(whatsNewLink.hidden, false, "What's new link is not hidden.");
 }
 
 /**
- * For tests that use the test updater restores the backed up real updater if
- * it exists and tries again on failure since Windows debug builds at times
- * leave the file in use. After success moveRealUpdater is called to continue
- * the setup of the test updater. For tests that don't use the test updater
- * runTest will be called.
+ * For staging tests the test updater must be used and this restores the backed
+ * up real updater if it exists and tries again on failure since Windows debug
+ * builds at times leave the file in use. After success moveRealUpdater is
+ * called to continue the setup of the test updater.
  */
 function setupTestUpdater() {
   return (async function() {
-    if (gUseTestUpdater) {
+    if (Services.prefs.getBoolPref(PREF_APP_UPDATE_STAGING_ENABLED)) {
       try {
         restoreUpdaterBackup();
       } catch (e) {
         logTestInfo("Attempt to restore the backed up updater failed... " +
                     "will try again, Exception: " + e);
-        await delay();
+        await TestUtils.waitForTick();
         await setupTestUpdater();
         return;
       }
       await moveRealUpdater();
     }
   })();
 }
 
@@ -352,29 +420,28 @@ function moveRealUpdater() {
       // Move away the real updater
       let baseAppDir = getAppBaseDir();
       let updater = baseAppDir.clone();
       updater.append(FILE_UPDATER_BIN);
       updater.moveTo(baseAppDir, FILE_UPDATER_BIN_BAK);
     } catch (e) {
       logTestInfo("Attempt to move the real updater out of the way failed... " +
                   "will try again, Exception: " + e);
-      await delay();
+      await TestUtils.waitForTick();
       await moveRealUpdater();
       return;
     }
 
     await copyTestUpdater();
   })();
 }
 
 /**
- * Copies the test updater so it can be used by tests and tries again on failure
- * since Windows debug builds at times leave the file in use. After success it
- * will call runTest to continue the test.
+ * Copies the test updater and tries again on failure since Windows debug builds
+ * at times leave the file in use.
  */
 function copyTestUpdater(attempt = 0) {
   return (async function() {
     try {
       // Copy the test updater
       let baseAppDir = getAppBaseDir();
       let testUpdaterDir = Services.dirsvc.get("CurWorkD", Ci.nsIFile);
       let relPath = REL_PATH_DATA;
@@ -386,58 +453,215 @@ function copyTestUpdater(attempt = 0) {
       let testUpdater = testUpdaterDir.clone();
       testUpdater.append(FILE_UPDATER_BIN);
 
       testUpdater.copyToFollowingLinks(baseAppDir, FILE_UPDATER_BIN);
     } catch (e) {
       if (attempt < MAX_UPDATE_COPY_ATTEMPTS) {
         logTestInfo("Attempt to copy the test updater failed... " +
                     "will try again, Exception: " + e);
-        await delay();
+        await TestUtils.waitForTick();
         await copyTestUpdater(attempt + 1);
       }
     }
   })();
 }
 
 /**
  * Restores the updater that was backed up. This is called in setupTestUpdater
  * before the backup of the real updater is done in case the previous test
- * failed to restore the updater, in finishTestDefaultWaitForWindowClosed when
- * the test has finished, and in test_9999_cleanup.xul after all tests have
- * finished.
+ * failed to restore the updater when the test has finished.
  */
 function restoreUpdaterBackup() {
   let baseAppDir = getAppBaseDir();
   let updater = baseAppDir.clone();
   let updaterBackup = baseAppDir.clone();
   updater.append(FILE_UPDATER_BIN);
   updaterBackup.append(FILE_UPDATER_BIN_BAK);
   if (updaterBackup.exists()) {
     if (updater.exists()) {
       updater.remove(true);
     }
     updaterBackup.moveTo(baseAppDir, FILE_UPDATER_BIN);
   }
 }
 
 /**
- * When a test finishes this will repeatedly attempt to restore the real updater
- * for tests that use the test updater and then call
- * finishTestDefaultWaitForWindowClosed after the restore is successful.
+ * When a staging test finishes this will repeatedly attempt to restore the real
+ * updater.
  */
 function finishTestRestoreUpdaterBackup() {
   return (async function() {
-    if (gUseTestUpdater) {
+    if (Services.prefs.getBoolPref(PREF_APP_UPDATE_STAGING_ENABLED)) {
       try {
         // Windows debug builds keep the updater file in use for a short period of
         // time after the updater process exits.
         restoreUpdaterBackup();
       } catch (e) {
         logTestInfo("Attempt to restore the backed up updater failed... " +
                     "will try again, Exception: " + e);
 
-        await delay();
+        await TestUtils.waitForTick();
         await finishTestRestoreUpdaterBackup();
       }
     }
   })();
 }
+
+/**
+ * Waits for the About Dialog to load.
+ *
+ * @return A promise that returns the domWindow for the About Dialog and
+ *         resolves when the About Dialog loads.
+ */
+function waitForAboutDialog() {
+  return new Promise(resolve => {
+    var listener = {
+      onOpenWindow: aXULWindow => {
+        debugDump("About dialog shown...");
+        Services.wm.removeListener(listener);
+
+         async function aboutDialogOnLoad() {
+          domwindow.removeEventListener("load", aboutDialogOnLoad, true);
+          let chromeURI = "chrome://browser/content/aboutDialog.xul";
+          is(domwindow.document.location.href, chromeURI, "About dialog appeared");
+          resolve(domwindow);
+        }
+
+        var domwindow = aXULWindow.docShell.domWindow;
+        domwindow.addEventListener("load", aboutDialogOnLoad, true);
+      },
+      onCloseWindow: aXULWindow => {},
+    };
+
+    Services.wm.addListener(listener);
+    openAboutDialog();
+  });
+}
+
+/**
+ * Runs an About Dialog update test. This will set various common prefs for
+ * updating and runs the provided list of steps.
+ *
+ * @param  updateParams
+ *         Params which will be sent to app_update.sjs.
+ * @param  backgroundUpdate
+ *         If true a background check will be performed before opening the About
+ *         Dialog.
+ * @param  steps
+ *         An array of test steps to perform. A step will either be an object
+ *         containing expected conditions and actions or a function to call.
+ * @return A promise which will resolve once all of the steps have been run.
+ */
+function runAboutDialogUpdateTest(updateParams, backgroundUpdate, steps) {
+  let aboutDialog;
+  function processAboutDialogStep(step) {
+    if (typeof(step) == "function") {
+      return step();
+    }
+
+    // Helper function to get the selected panel.
+    function getSelectedPanel() {
+      return aboutDialog.document.getElementById("updateDeck").selectedPanel;
+    }
+
+    // Helper function to get the selected panel's button.
+    function getSelectedPanelButton() {
+      return getSelectedPanel().querySelector("button");
+    }
+
+    // Helper function to get the selected panel's label with a class of
+    // text-link.
+    function getSelectedLabelLink() {
+      return getSelectedPanel().querySelector("label.text-link");
+    }
+
+    const {panelId, checkActiveUpdate, continueFile} = step;
+    return (async function() {
+      await BrowserTestUtils.waitForCondition(() =>
+        (getSelectedPanel() && getSelectedPanel().id == panelId),
+        "Waiting for expected panel ID - expected \"" + panelId + "\"");
+
+      // Skip when checkActiveUpdate evaluates to false.
+      if (checkActiveUpdate) {
+        ok(!!gUpdateManager.activeUpdate, "There should be an active update");
+        is(gUpdateManager.activeUpdate.state, checkActiveUpdate.state,
+           "The active update state should equal " + checkActiveUpdate.state);
+      } else {
+        ok(!gUpdateManager.activeUpdate,
+           "There should not be an active update");
+      }
+
+      if (continueFile) {
+        await continueFileHandler(continueFile);
+      }
+
+      let linkPanels = ["downloadFailed", "manualUpdate", "unsupportedSystem"];
+      if (linkPanels.includes(panelId)) {
+        // The unsupportedSystem panel uses the update's detailsURL and the
+        // downloadFailed and manualUpdate panels use the app.update.url.manual
+        // preference.
+        let labelLink = getSelectedLabelLink();
+        is(labelLink.href, URL_HOST,
+           "The panel's link href should equal the expected value");
+      }
+
+      let buttonPanels = ["downloadAndInstall", "apply"];
+      if (buttonPanels.includes(panelId)) {
+        let buttonEl = getSelectedPanelButton();
+        await BrowserTestUtils.waitForCondition(() =>
+          (aboutDialog.document.activeElement == buttonEl),
+          "The button should receive focus");
+        ok(!buttonEl.disabled, "The button should be enabled");
+        // Don't click the button on the apply panel since this will restart the
+        // application.
+        if (panelId != "apply") {
+          buttonEl.click();
+        }
+      }
+    })();
+  }
+
+  return (async function() {
+    await SpecialPowers.pushPrefEnv({
+      set: [
+        [PREF_APP_UPDATE_SERVICE_ENABLED, false],
+        [PREF_APP_UPDATE_DISABLEDFORTESTING, false],
+        [PREF_APP_UPDATE_URL_MANUAL, URL_HOST],
+      ],
+    });
+    registerCleanupFunction(() => {
+      gEnv.set("MOZ_TEST_SLOW_SKIP_UPDATE_STAGE", "");
+      UpdateListener.reset();
+      cleanUpUpdates();
+    });
+
+    gEnv.set("MOZ_TEST_SLOW_SKIP_UPDATE_STAGE", "1");
+    setUpdateTimerPrefs();
+    removeUpdateDirsAndFiles();
+
+    await setupTestUpdater();
+
+    let url = URL_HTTP_UPDATE_SJS + "?detailsURL=" + URL_HOST +
+              updateParams + getVersionParams();
+    if (backgroundUpdate) {
+      setUpdateURL(url);
+      if (Services.prefs.getBoolPref(PREF_APP_UPDATE_STAGING_ENABLED)) {
+        // Don't wait on the deletion of the continueStaging file
+        continueFileHandler(CONTINUE_STAGING);
+      }
+      gAUS.checkForBackgroundUpdates();
+      await waitForEvent("update-downloaded");
+    } else {
+      url += "&slowUpdateCheck=1&useSlowDownloadMar=1";
+      setUpdateURL(url);
+    }
+
+    aboutDialog = await waitForAboutDialog();
+
+    for (let step of steps) {
+      await processAboutDialogStep(step);
+    }
+
+    aboutDialog.close();
+    await finishTestRestoreUpdaterBackup();
+  })();
+}
--- a/toolkit/mozapps/update/tests/browser/testConstants.js
+++ b/toolkit/mozapps/update/tests/browser/testConstants.js
@@ -1,4 +1,7 @@
 const REL_PATH_DATA = "browser/toolkit/mozapps/update/tests/browser/";
 const URL_HOST = "http://example.com";
-const URL_PATH_UPDATE_XML = "/" + REL_PATH_DATA + "update.sjs";
+const URL_PATH_UPDATE_XML = "/" + REL_PATH_DATA + "app_update.sjs";
 const URL_HTTP_UPDATE_SJS = URL_HOST + URL_PATH_UPDATE_XML;
+const CONTINUE_CHECK = "continueCheck";
+const CONTINUE_DOWNLOAD = "continueDownload";
+const CONTINUE_STAGING = "continueStaging";
--- a/toolkit/mozapps/update/tests/chrome/chrome.ini
+++ b/toolkit/mozapps/update/tests/chrome/chrome.ini
@@ -1,16 +1,17 @@
 # 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/.
 
 [DEFAULT]
 tags = appupdate
 support-files =
   testConstants.js
+  update.sjs
   utils.js
 
 # mochitest-chrome tests must start with "test_" and are executed in sorted
 # order and not in the order specified in the manifest.
 [test_0010_background_basic.xul]
 [test_0011_check_basic.xul]
 [test_0012_check_basic_staging.xul]
 skip-if = asan
copy from toolkit/mozapps/update/tests/data/update.sjs
copy to toolkit/mozapps/update/tests/chrome/update.sjs
--- a/toolkit/mozapps/update/tests/moz.build
+++ b/toolkit/mozapps/update/tests/moz.build
@@ -60,24 +60,22 @@ if CONFIG['OS_ARCH'] == 'WINNT':
     USE_STATIC_LIBS = True
     if CONFIG['CC_TYPE'] in ('clang', 'gcc'):
         WIN32_EXE_LDFLAGS += ['-municode']
 
 TEST_HARNESS_FILES.testing.mochitest.browser.toolkit.mozapps.update.tests.browser += [
     'data/shared.js',
     'data/sharedUpdateXML.js',
     'data/simple.mar',
-    'data/update.sjs',
 ]
 
 TEST_HARNESS_FILES.testing.mochitest.chrome.toolkit.mozapps.update.tests.chrome += [
     'data/shared.js',
     'data/sharedUpdateXML.js',
     'data/simple.mar',
-    'data/update.sjs',
 ]
 
 FINAL_TARGET_FILES += [
     'data/complete.exe',
     'data/complete.mar',
     'data/complete.png',
     'data/complete_log_success_mac',
     'data/complete_log_success_win',
--- a/toolkit/mozapps/update/updater/updater.cpp
+++ b/toolkit/mozapps/update/updater/updater.cpp
@@ -2454,16 +2454,43 @@ static void UpdateThreadFunc(void *param
 
     if (rv == OK && sStagedUpdate) {
 #ifdef TEST_UPDATER
       // The MOZ_TEST_SKIP_UPDATE_STAGE environment variable prevents copying
       // the files in dist/bin in the test updater when staging an update since
       // this can cause tests to timeout.
       if (EnvHasValue("MOZ_TEST_SKIP_UPDATE_STAGE")) {
         rv = OK;
+      } else if (EnvHasValue("MOZ_TEST_SLOW_SKIP_UPDATE_STAGE")) {
+        // The following is to simulate staging so the UI tests have time to
+        // show that the update is being staged.
+        NS_tchar continueFilePath[MAXPATHLEN] = {NS_T('\0')};
+        NS_tsnprintf(continueFilePath,
+                     sizeof(continueFilePath) / sizeof(continueFilePath[0]),
+                     NS_T("%s/continueStaging"), gPatchDirPath);
+        // Use 100 retries for staging requests to lessen the likelihood of
+        // tests intermittently failing on debug builds due to launching the
+        // updater. The total time to wait with the default interval of 100 ms
+        // is approximately 10 seconds. The tests use the same values.
+        const int max_retries = 100;
+        int retries = 1;
+        while (retries++ < max_retries) {
+#ifdef XP_WIN
+          Sleep(100);
+#else
+          usleep(100000);
+#endif
+          // Continue after the continue file exists and it is successfully
+          // removed.
+          if (!NS_taccess(continueFilePath, F_OK) &&
+              !NS_tremove(continueFilePath)) {
+            break;
+          }
+        }
+        rv = OK;
       } else {
         rv = CopyInstallDirToDestDir();
       }
 #else
       rv = CopyInstallDirToDestDir();
 #endif
     }