summaryrefslogtreecommitdiffstats
path: root/browser/components/migration/tests/marionette
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/migration/tests/marionette')
-rw-r--r--browser/components/migration/tests/marionette/manifest.ini4
-rw-r--r--browser/components/migration/tests/marionette/test_refresh_firefox.py690
2 files changed, 694 insertions, 0 deletions
diff --git a/browser/components/migration/tests/marionette/manifest.ini b/browser/components/migration/tests/marionette/manifest.ini
new file mode 100644
index 0000000000..afba7ead73
--- /dev/null
+++ b/browser/components/migration/tests/marionette/manifest.ini
@@ -0,0 +1,4 @@
+[DEFAULT]
+run-if = buildapp == 'browser'
+
+[test_refresh_firefox.py]
diff --git a/browser/components/migration/tests/marionette/test_refresh_firefox.py b/browser/components/migration/tests/marionette/test_refresh_firefox.py
new file mode 100644
index 0000000000..603534ff17
--- /dev/null
+++ b/browser/components/migration/tests/marionette/test_refresh_firefox.py
@@ -0,0 +1,690 @@
+import os
+import time
+
+from marionette_driver.errors import NoAlertPresentException
+from marionette_harness import MarionetteTestCase
+
+
+# Holds info about things we need to cleanup after the tests are done.
+class PendingCleanup:
+ desktop_backup_path = None
+ reset_profile_path = None
+ reset_profile_local_path = None
+
+ def __init__(self, profile_name_to_remove):
+ self.profile_name_to_remove = profile_name_to_remove
+
+
+class TestFirefoxRefresh(MarionetteTestCase):
+ _sandbox = "firefox-refresh"
+
+ _username = "marionette-test-login"
+ _password = "marionette-test-password"
+ _bookmarkURL = "about:mozilla"
+ _bookmarkText = "Some bookmark from Marionette"
+
+ _cookieHost = "firefox-refresh.marionette-test.mozilla.org"
+ _cookiePath = "some/cookie/path"
+ _cookieName = "somecookie"
+ _cookieValue = "some cookie value"
+
+ _historyURL = "http://firefox-refresh.marionette-test.mozilla.org/"
+ _historyTitle = "Test visit for Firefox Reset"
+
+ _formHistoryFieldName = "some-very-unique-marionette-only-firefox-reset-field"
+ _formHistoryValue = "special-pumpkin-value"
+
+ _formAutofillAvailable = False
+ _formAutofillAddressGuid = None
+
+ _expectedURLs = ["about:robots", "about:mozilla"]
+
+ def savePassword(self):
+ self.runAsyncCode(
+ """
+ let [username, password, resolve] = arguments;
+ let myLogin = new global.LoginInfo(
+ "test.marionette.mozilla.com",
+ "http://test.marionette.mozilla.com/some/form/",
+ null,
+ username,
+ password,
+ "username",
+ "password"
+ );
+ Services.logins.addLoginAsync(myLogin)
+ .then(() => resolve(false), resolve);
+ """,
+ script_args=(self._username, self._password),
+ )
+
+ def createBookmarkInMenu(self):
+ error = self.runAsyncCode(
+ """
+ // let url = arguments[0];
+ // let title = arguments[1];
+ // let resolve = arguments[arguments.length - 1];
+ let [url, title, resolve] = arguments;
+ PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.menuGuid, url, title
+ }).then(() => resolve(false), resolve);
+ """,
+ script_args=(self._bookmarkURL, self._bookmarkText),
+ )
+ if error:
+ print(error)
+
+ def createBookmarksOnToolbar(self):
+ error = self.runAsyncCode(
+ """
+ let resolve = arguments[arguments.length - 1];
+ let children = [];
+ for (let i = 1; i <= 5; i++) {
+ children.push({url: `about:rights?p=${i}`, title: `Bookmark ${i}`});
+ }
+ PlacesUtils.bookmarks.insertTree({
+ guid: PlacesUtils.bookmarks.toolbarGuid,
+ children
+ }).then(() => resolve(false), resolve);
+ """
+ )
+ if error:
+ print(error)
+
+ def createHistory(self):
+ error = self.runAsyncCode(
+ """
+ let resolve = arguments[arguments.length - 1];
+ PlacesUtils.history.insert({
+ url: arguments[0],
+ title: arguments[1],
+ visits: [{
+ date: new Date(Date.now() - 5000),
+ referrer: "about:mozilla"
+ }]
+ }).then(() => resolve(false),
+ ex => resolve("Unexpected error in adding visit: " + ex));
+ """,
+ script_args=(self._historyURL, self._historyTitle),
+ )
+ if error:
+ print(error)
+
+ def createFormHistory(self):
+ error = self.runAsyncCode(
+ """
+ let updateDefinition = {
+ op: "add",
+ fieldname: arguments[0],
+ value: arguments[1],
+ firstUsed: (Date.now() - 5000) * 1000,
+ };
+ let resolve = arguments[arguments.length - 1];
+ global.FormHistory.update(updateDefinition).then(() => {
+ resolve(false);
+ }, error => {
+ resolve("Unexpected error in adding formhistory: " + error);
+ });
+ """,
+ script_args=(self._formHistoryFieldName, self._formHistoryValue),
+ )
+ if error:
+ print(error)
+
+ def createFormAutofill(self):
+ if not self._formAutofillAvailable:
+ return
+ self._formAutofillAddressGuid = self.runAsyncCode(
+ """
+ let resolve = arguments[arguments.length - 1];
+ const TEST_ADDRESS_1 = {
+ "given-name": "John",
+ "additional-name": "R.",
+ "family-name": "Smith",
+ organization: "World Wide Web Consortium",
+ "street-address": "32 Vassar Street\\\nMIT Room 32-G524",
+ "address-level2": "Cambridge",
+ "address-level1": "MA",
+ "postal-code": "02139",
+ country: "US",
+ tel: "+15195555555",
+ email: "user@example.com",
+ };
+ return global.formAutofillStorage.initialize().then(() => {
+ return global.formAutofillStorage.addresses.add(TEST_ADDRESS_1);
+ }).then(resolve);
+ """
+ )
+
+ def createCookie(self):
+ self.runCode(
+ """
+ // Expire in 15 minutes:
+ let expireTime = Math.floor(Date.now() / 1000) + 15 * 60;
+ Services.cookies.add(arguments[0], arguments[1], arguments[2], arguments[3],
+ true, false, false, expireTime, {},
+ Ci.nsICookie.SAMESITE_NONE, Ci.nsICookie.SCHEME_UNSET);
+ """,
+ script_args=(
+ self._cookieHost,
+ self._cookiePath,
+ self._cookieName,
+ self._cookieValue,
+ ),
+ )
+
+ def createSession(self):
+ self.runAsyncCode(
+ """
+ let resolve = arguments[arguments.length - 1];
+ const COMPLETE_STATE = Ci.nsIWebProgressListener.STATE_STOP +
+ Ci.nsIWebProgressListener.STATE_IS_NETWORK;
+ let { TabStateFlusher } = ChromeUtils.importESModule(
+ "resource:///modules/sessionstore/TabStateFlusher.sys.mjs"
+ );
+ let expectedURLs = Array.from(arguments[0])
+ gBrowser.addTabsProgressListener({
+ onStateChange(browser, webprogress, request, flags, status) {
+ try {
+ request && request.QueryInterface(Ci.nsIChannel);
+ } catch (ex) {}
+ let uriLoaded = request.originalURI && request.originalURI.spec;
+ if ((flags & COMPLETE_STATE == COMPLETE_STATE) && uriLoaded &&
+ expectedURLs.includes(uriLoaded)) {
+ TabStateFlusher.flush(browser).then(function() {
+ expectedURLs.splice(expectedURLs.indexOf(uriLoaded), 1);
+ if (!expectedURLs.length) {
+ gBrowser.removeTabsProgressListener(this);
+ resolve();
+ }
+ });
+ }
+ }
+ });
+ let expectedTabs = new Set();
+ for (let url of expectedURLs) {
+ expectedTabs.add(gBrowser.addTab(url, {
+ triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
+ }));
+ }
+ // Close any other tabs that might be open:
+ let allTabs = Array.from(gBrowser.tabs);
+ for (let tab of allTabs) {
+ if (!expectedTabs.has(tab)) {
+ gBrowser.removeTab(tab);
+ }
+ }
+ """, # NOQA: E501
+ script_args=(self._expectedURLs,),
+ )
+
+ def createFxa(self):
+ # This script will write an entry to the login manager and create
+ # a signedInUser.json in the profile dir.
+ self.runAsyncCode(
+ """
+ let resolve = arguments[arguments.length - 1];
+ let { FxAccountsStorageManager } = ChromeUtils.import(
+ "resource://gre/modules/FxAccountsStorage.jsm"
+ );
+ let storage = new FxAccountsStorageManager();
+ let data = {email: "test@test.com", uid: "uid", keyFetchToken: "top-secret"};
+ storage.initialize(data);
+ storage.finalize().then(resolve);
+ """
+ )
+
+ def createSync(self):
+ # This script will write the canonical preference which indicates a user
+ # is signed into sync.
+ self.marionette.execute_script(
+ """
+ Services.prefs.setStringPref("services.sync.username", "test@test.com");
+ """
+ )
+
+ def checkPassword(self):
+ loginInfo = self.marionette.execute_script(
+ """
+ let ary = Services.logins.findLogins(
+ "test.marionette.mozilla.com",
+ "http://test.marionette.mozilla.com/some/form/",
+ null, {});
+ return ary.length ? ary : {username: "null", password: "null"};
+ """
+ )
+ self.assertEqual(len(loginInfo), 1)
+ self.assertEqual(loginInfo[0]["username"], self._username)
+ self.assertEqual(loginInfo[0]["password"], self._password)
+
+ loginCount = self.marionette.execute_script(
+ """
+ return Services.logins.getAllLogins().length;
+ """
+ )
+ # Note that we expect 2 logins - one from us, one from sync.
+ self.assertEqual(loginCount, 2, "No other logins are present")
+
+ def checkBookmarkInMenu(self):
+ titleInBookmarks = self.runAsyncCode(
+ """
+ let [url, resolve] = arguments;
+ PlacesUtils.bookmarks.fetch({url}).then(
+ bookmark => resolve(bookmark ? bookmark.title : ""),
+ ex => resolve(ex)
+ );
+ """,
+ script_args=(self._bookmarkURL,),
+ )
+ self.assertEqual(titleInBookmarks, self._bookmarkText)
+
+ def checkBookmarkToolbarVisibility(self):
+ toolbarVisible = self.marionette.execute_script(
+ """
+ const BROWSER_DOCURL = AppConstants.BROWSER_CHROME_URL;
+ return Services.xulStore.getValue(BROWSER_DOCURL, "PersonalToolbar", "collapsed");
+ """
+ )
+ if toolbarVisible == "":
+ toolbarVisible = "false"
+ self.assertEqual(toolbarVisible, "false")
+
+ def checkHistory(self):
+ historyResult = self.runAsyncCode(
+ """
+ let resolve = arguments[arguments.length - 1];
+ PlacesUtils.history.fetch(arguments[0]).then(pageInfo => {
+ if (!pageInfo) {
+ resolve("No visits found");
+ } else {
+ resolve(pageInfo);
+ }
+ }).catch(e => {
+ resolve("Unexpected error in fetching page: " + e);
+ });
+ """,
+ script_args=(self._historyURL,),
+ )
+ if type(historyResult) == str:
+ self.fail(historyResult)
+ return
+
+ self.assertEqual(historyResult["title"], self._historyTitle)
+
+ def checkFormHistory(self):
+ formFieldResults = self.runAsyncCode(
+ """
+ let resolve = arguments[arguments.length - 1];
+ let results = [];
+ global.FormHistory.search(["value"], {fieldname: arguments[0]})
+ .then(resolve);
+ """,
+ script_args=(self._formHistoryFieldName,),
+ )
+ if type(formFieldResults) == str:
+ self.fail(formFieldResults)
+ return
+
+ formFieldResultCount = len(formFieldResults)
+ self.assertEqual(
+ formFieldResultCount,
+ 1,
+ "Should have exactly 1 entry for this field, got %d" % formFieldResultCount,
+ )
+ if formFieldResultCount == 1:
+ self.assertEqual(formFieldResults[0]["value"], self._formHistoryValue)
+
+ formHistoryCount = self.runAsyncCode(
+ """
+ let [resolve] = arguments;
+ global.FormHistory.count({}).then(resolve);
+ """
+ )
+ self.assertEqual(
+ formHistoryCount, 1, "There should be only 1 entry in the form history"
+ )
+
+ def checkFormAutofill(self):
+ if not self._formAutofillAvailable:
+ return
+
+ formAutofillResults = self.runAsyncCode(
+ """
+ let resolve = arguments[arguments.length - 1];
+ return global.formAutofillStorage.initialize().then(() => {
+ return global.formAutofillStorage.addresses.getAll()
+ }).then(resolve);
+ """,
+ )
+ if type(formAutofillResults) == str:
+ self.fail(formAutofillResults)
+ return
+
+ formAutofillAddressCount = len(formAutofillResults)
+ self.assertEqual(
+ formAutofillAddressCount,
+ 1,
+ "Should have exactly 1 saved address, got %d" % formAutofillAddressCount,
+ )
+ if formAutofillAddressCount == 1:
+ self.assertEqual(
+ formAutofillResults[0]["guid"], self._formAutofillAddressGuid
+ )
+
+ def checkCookie(self):
+ cookieInfo = self.runCode(
+ """
+ try {
+ let cookies = Services.cookies.getCookiesFromHost(arguments[0], {});
+ let cookie = null;
+ for (let hostCookie of cookies) {
+ // getCookiesFromHost returns any cookie from the BASE host.
+ if (hostCookie.rawHost != arguments[0])
+ continue;
+ if (cookie != null) {
+ return "more than 1 cookie! That shouldn't happen!";
+ }
+ cookie = hostCookie;
+ }
+ return {path: cookie.path, name: cookie.name, value: cookie.value};
+ } catch (ex) {
+ return "got exception trying to fetch cookie: " + ex;
+ }
+ """,
+ script_args=(self._cookieHost,),
+ )
+ if not isinstance(cookieInfo, dict):
+ self.fail(cookieInfo)
+ return
+ self.assertEqual(cookieInfo["path"], self._cookiePath)
+ self.assertEqual(cookieInfo["value"], self._cookieValue)
+ self.assertEqual(cookieInfo["name"], self._cookieName)
+
+ def checkSession(self):
+ tabURIs = self.runCode(
+ """
+ return [... gBrowser.browsers].map(b => b.currentURI && b.currentURI.spec)
+ """
+ )
+ self.assertSequenceEqual(tabURIs, ["about:welcomeback"])
+
+ # Dismiss modal dialog if any. This is mainly to dismiss the check for
+ # default browser dialog if it shows up.
+ try:
+ alert = self.marionette.switch_to_alert()
+ alert.dismiss()
+ except NoAlertPresentException:
+ pass
+
+ tabURIs = self.runAsyncCode(
+ """
+ let resolve = arguments[arguments.length - 1]
+ let mm = gBrowser.selectedBrowser.messageManager;
+
+ window.addEventListener("SSWindowStateReady", function() {
+ window.addEventListener("SSTabRestored", function() {
+ resolve(Array.from(gBrowser.browsers, b => b.currentURI?.spec));
+ }, { capture: false, once: true });
+ }, { capture: false, once: true });
+
+ let fs = function() {
+ if (content.document.readyState === "complete") {
+ content.document.getElementById("errorTryAgain").click();
+ } else {
+ content.window.addEventListener("load", function(event) {
+ content.document.getElementById("errorTryAgain").click();
+ }, { once: true });
+ }
+ };
+
+ Services.prefs.setBoolPref("security.allow_parent_unrestricted_js_loads", true);
+ mm.loadFrameScript("data:application/javascript,(" + fs.toString() + ")()", true);
+ Services.prefs.setBoolPref("security.allow_parent_unrestricted_js_loads", false);
+ """ # NOQA: E501
+ )
+ self.assertSequenceEqual(tabURIs, self._expectedURLs)
+
+ def checkFxA(self):
+ result = self.runAsyncCode(
+ """
+ let { FxAccountsStorageManager } = ChromeUtils.import(
+ "resource://gre/modules/FxAccountsStorage.jsm"
+ );
+ let resolve = arguments[arguments.length - 1];
+ let storage = new FxAccountsStorageManager();
+ let result = {};
+ storage.initialize();
+ storage.getAccountData().then(data => {
+ result.accountData = data;
+ return storage.finalize();
+ }).then(() => {
+ resolve(result);
+ }).catch(err => {
+ resolve(err.toString());
+ });
+ """
+ )
+ if type(result) != dict:
+ self.fail(result)
+ return
+ self.assertEqual(result["accountData"]["email"], "test@test.com")
+ self.assertEqual(result["accountData"]["uid"], "uid")
+ self.assertEqual(result["accountData"]["keyFetchToken"], "top-secret")
+
+ def checkSync(self, expect_sync_user):
+ pref_value = self.marionette.execute_script(
+ """
+ return Services.prefs.getStringPref("services.sync.username", null);
+ """
+ )
+ expected_value = "test@test.com" if expect_sync_user else None
+ self.assertEqual(pref_value, expected_value)
+
+ def checkProfile(self, has_migrated=False, expect_sync_user=True):
+ self.checkPassword()
+ self.checkBookmarkInMenu()
+ self.checkHistory()
+ self.checkFormHistory()
+ self.checkFormAutofill()
+ self.checkCookie()
+ self.checkFxA()
+ self.checkSync(expect_sync_user)
+ if has_migrated:
+ self.checkBookmarkToolbarVisibility()
+ self.checkSession()
+
+ def createProfileData(self):
+ self.savePassword()
+ self.createBookmarkInMenu()
+ self.createBookmarksOnToolbar()
+ self.createHistory()
+ self.createFormHistory()
+ self.createFormAutofill()
+ self.createCookie()
+ self.createSession()
+ self.createFxa()
+ self.createSync()
+
+ def setUpScriptData(self):
+ self.marionette.set_context(self.marionette.CONTEXT_CHROME)
+ self.runCode(
+ """
+ window.global = {};
+ global.LoginInfo = Components.Constructor("@mozilla.org/login-manager/loginInfo;1", "nsILoginInfo", "init");
+ global.profSvc = Cc["@mozilla.org/toolkit/profile-service;1"].getService(Ci.nsIToolkitProfileService);
+ global.Preferences = ChromeUtils.importESModule(
+ "resource://gre/modules/Preferences.sys.mjs"
+ ).Preferences;
+ global.FormHistory = ChromeUtils.import(
+ "resource://gre/modules/FormHistory.jsm"
+ ).FormHistory;
+ """ # NOQA: E501
+ )
+ self._formAutofillAvailable = self.runCode(
+ """
+ try {
+ global.formAutofillStorage = ChromeUtils.import(
+ "resource://formautofill/FormAutofillStorage.jsm"
+ ).formAutofillStorage;
+ } catch(e) {
+ return false;
+ }
+ return true;
+ """ # NOQA: E501
+ )
+
+ def runCode(self, script, *args, **kwargs):
+ return self.marionette.execute_script(
+ script, new_sandbox=False, sandbox=self._sandbox, *args, **kwargs
+ )
+
+ def runAsyncCode(self, script, *args, **kwargs):
+ return self.marionette.execute_async_script(
+ script, new_sandbox=False, sandbox=self._sandbox, *args, **kwargs
+ )
+
+ def setUp(self):
+ MarionetteTestCase.setUp(self)
+ self.setUpScriptData()
+
+ self.cleanups = []
+
+ def tearDown(self):
+ # Force yet another restart with a clean profile to disconnect from the
+ # profile and environment changes we've made, to leave a more or less
+ # blank slate for the next person.
+ self.marionette.restart(in_app=False, clean=True)
+ self.setUpScriptData()
+
+ # Super
+ MarionetteTestCase.tearDown(self)
+
+ # A helper to deal with removing a load of files
+ import mozfile
+
+ for cleanup in self.cleanups:
+ if cleanup.desktop_backup_path:
+ mozfile.remove(cleanup.desktop_backup_path)
+
+ if cleanup.reset_profile_path:
+ # Remove ourselves from profiles.ini
+ self.runCode(
+ """
+ let name = arguments[0];
+ let profile = global.profSvc.getProfileByName(name);
+ profile.remove(false)
+ global.profSvc.flush();
+ """,
+ script_args=(cleanup.profile_name_to_remove,),
+ )
+ # Remove the local profile dir if it's not the same as the profile dir:
+ different_path = (
+ cleanup.reset_profile_local_path != cleanup.reset_profile_path
+ )
+ if cleanup.reset_profile_local_path and different_path:
+ mozfile.remove(cleanup.reset_profile_local_path)
+
+ # And delete all the files.
+ mozfile.remove(cleanup.reset_profile_path)
+
+ def doReset(self):
+ profileName = "marionette-test-profile-" + str(int(time.time() * 1000))
+ cleanup = PendingCleanup(profileName)
+ self.runCode(
+ """
+ // Ensure the current (temporary) profile is in profiles.ini:
+ let profD = Services.dirsvc.get("ProfD", Ci.nsIFile);
+ let profileName = arguments[1];
+ let myProfile = global.profSvc.createProfile(profD, profileName);
+ global.profSvc.flush()
+
+ // Now add the reset parameters:
+ let prefsToKeep = Array.from(Services.prefs.getChildList("marionette."));
+ // Add all the modified preferences set from geckoinstance.py to avoid
+ // non-local connections.
+ prefsToKeep = prefsToKeep.concat(JSON.parse(
+ Services.env.get("MOZ_MARIONETTE_REQUIRED_PREFS")));
+ let prefObj = {};
+ for (let pref of prefsToKeep) {
+ prefObj[pref] = global.Preferences.get(pref);
+ }
+ Services.env.set("MOZ_MARIONETTE_PREF_STATE_ACROSS_RESTARTS", JSON.stringify(prefObj));
+ Services.env.set("MOZ_RESET_PROFILE_RESTART", "1");
+ Services.env.set("XRE_PROFILE_PATH", arguments[0]);
+ """,
+ script_args=(
+ self.marionette.instance.profile.profile,
+ profileName,
+ ),
+ )
+
+ profileLeafName = os.path.basename(
+ os.path.normpath(self.marionette.instance.profile.profile)
+ )
+
+ # Now restart the browser to get it reset:
+ self.marionette.restart(clean=False, in_app=True)
+ self.setUpScriptData()
+
+ # Determine the new profile path (we'll need to remove it when we're done)
+ [cleanup.reset_profile_path, cleanup.reset_profile_local_path] = self.runCode(
+ """
+ let profD = Services.dirsvc.get("ProfD", Ci.nsIFile);
+ let localD = Services.dirsvc.get("ProfLD", Ci.nsIFile);
+ return [profD.path, localD.path];
+ """
+ )
+
+ # Determine the backup path
+ cleanup.desktop_backup_path = self.runCode(
+ """
+ let container;
+ try {
+ container = Services.dirsvc.get("Desk", Ci.nsIFile);
+ } catch (ex) {
+ container = Services.dirsvc.get("Home", Ci.nsIFile);
+ }
+ let bundle = Services.strings.createBundle("chrome://mozapps/locale/profile/profileSelection.properties");
+ let dirName = bundle.formatStringFromName("resetBackupDirectory", [Services.appinfo.name]);
+ container.append(dirName);
+ container.append(arguments[0]);
+ return container.path;
+ """, # NOQA: E501
+ script_args=(profileLeafName,),
+ )
+
+ self.assertTrue(
+ os.path.isdir(cleanup.reset_profile_path),
+ "Reset profile path should be present",
+ )
+ self.assertTrue(
+ os.path.isdir(cleanup.desktop_backup_path),
+ "Backup profile path should be present",
+ )
+ self.assertIn(cleanup.profile_name_to_remove, cleanup.reset_profile_path)
+ return cleanup
+
+ def testResetEverything(self):
+ self.createProfileData()
+
+ self.checkProfile(expect_sync_user=True)
+
+ this_cleanup = self.doReset()
+ self.cleanups.append(this_cleanup)
+
+ # Now check that we're doing OK...
+ self.checkProfile(has_migrated=True, expect_sync_user=True)
+
+ def testFxANoSync(self):
+ # This test doesn't need to repeat all the non-sync tests...
+ # Setup FxA but *not* sync
+ self.createFxa()
+
+ self.checkFxA()
+ self.checkSync(False)
+
+ this_cleanup = self.doReset()
+ self.cleanups.append(this_cleanup)
+
+ self.checkFxA()
+ self.checkSync(False)