diff options
Diffstat (limited to 'browser/components/migration/tests/marionette')
-rw-r--r-- | browser/components/migration/tests/marionette/manifest.ini | 5 | ||||
-rw-r--r-- | browser/components/migration/tests/marionette/test_refresh_firefox.py | 416 |
2 files changed, 421 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 000000000..3f404e724 --- /dev/null +++ b/browser/components/migration/tests/marionette/manifest.ini @@ -0,0 +1,5 @@ +[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 000000000..b348a3dcd --- /dev/null +++ b/browser/components/migration/tests/marionette/test_refresh_firefox.py @@ -0,0 +1,416 @@ +import os +import shutil + +from marionette_harness import MarionetteTestCase + + +class TestFirefoxRefresh(MarionetteTestCase): + _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" + + _expectedURLs = ["about:robots", "about:mozilla"] + + def savePassword(self): + self.runCode(""" + let myLogin = new global.LoginInfo( + "test.marionette.mozilla.com", + "http://test.marionette.mozilla.com/some/form/", + null, + arguments[0], + arguments[1], + "username", + "password" + ); + Services.logins.addLogin(myLogin) + """, script_args=[self._username, self._password]) + + def createBookmark(self): + self.marionette.execute_script(""" + let url = arguments[0]; + let title = arguments[1]; + PlacesUtils.bookmarks.insertBookmark(PlacesUtils.bookmarks.bookmarksMenuFolder, + makeURI(url), 0, title); + """, script_args=[self._bookmarkURL, self._bookmarkText]) + + def createHistory(self): + error = self.runAsyncCode(""" + // Copied from PlacesTestUtils, which isn't available in Marionette tests. + let didReturn; + PlacesUtils.asyncHistory.updatePlaces( + [{title: arguments[1], uri: makeURI(arguments[0]), visits: [{ + transitionType: Ci.nsINavHistoryService.TRANSITION_LINK, + visitDate: (Date.now() - 5000) * 1000, + referrerURI: makeURI("about:mozilla"), + }] + }], + { + handleError(resultCode, place) { + didReturn = true; + marionetteScriptFinished("Unexpected error in adding visit: " + resultCode); + }, + handleResult() {}, + handleCompletion() { + if (!didReturn) { + marionetteScriptFinished(false); + } + }, + } + ); + """, 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 finished = false; + global.FormHistory.update(updateDefinition, { + handleError(error) { + finished = true; + marionetteScriptFinished(error); + }, + handleCompletion() { + if (!finished) { + marionetteScriptFinished(false); + } + } + }); + """, script_args=[self._formHistoryFieldName, self._formHistoryValue]) + if error: + print error + + 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); + """, script_args=[self._cookieHost, self._cookiePath, self._cookieName, self._cookieValue]) + + def createSession(self): + self.runAsyncCode(""" + const COMPLETE_STATE = Ci.nsIWebProgressListener.STATE_STOP + + Ci.nsIWebProgressListener.STATE_IS_NETWORK; + let {TabStateFlusher} = Cu.import("resource:///modules/sessionstore/TabStateFlusher.jsm", {}); + 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); + marionetteScriptFinished(); + } + }); + } + } + }); + for (let url of expectedURLs) { + gBrowser.addTab(url); + } + """, script_args=[self._expectedURLs]) + + 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; + """) + self.assertEqual(loginCount, 1, "No other logins are present") + + def checkBookmark(self): + titleInBookmarks = self.marionette.execute_script(""" + let url = arguments[0]; + let bookmarkIds = PlacesUtils.bookmarks.getBookmarkIdsForURI(makeURI(url), {}, {}); + return bookmarkIds.length == 1 ? PlacesUtils.bookmarks.getItemTitle(bookmarkIds[0]) : ""; + """, script_args=[self._bookmarkURL]) + self.assertEqual(titleInBookmarks, self._bookmarkText) + + def checkHistory(self): + historyResults = self.runAsyncCode(""" + let placeInfos = []; + PlacesUtils.asyncHistory.getPlacesInfo(makeURI(arguments[0]), { + handleError(resultCode, place) { + placeInfos = null; + marionetteScriptFinished("Unexpected error in fetching visit: " + resultCode); + }, + handleResult(placeInfo) { + placeInfos.push(placeInfo); + }, + handleCompletion() { + if (placeInfos) { + if (!placeInfos.length) { + marionetteScriptFinished("No visits found"); + } else { + marionetteScriptFinished(placeInfos); + } + } + }, + }); + """, script_args=[self._historyURL]) + if type(historyResults) == str: + self.fail(historyResults) + return + + historyCount = len(historyResults) + self.assertEqual(historyCount, 1, "Should have exactly 1 entry for URI, got %d" % historyCount) + if historyCount == 1: + self.assertEqual(historyResults[0]['title'], self._historyTitle) + + def checkFormHistory(self): + formFieldResults = self.runAsyncCode(""" + let results = []; + global.FormHistory.search(["value"], {fieldname: arguments[0]}, { + handleError(error) { + results = error; + }, + handleResult(result) { + results.push(result); + }, + handleCompletion() { + marionetteScriptFinished(results); + }, + }); + """, 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 count; + let callbacks = { + handleResult: rv => count = rv, + handleCompletion() { + marionetteScriptFinished(count); + }, + }; + global.FormHistory.count({}, callbacks); + """) + self.assertEqual(formHistoryCount, 1, "There should be only 1 entry in the form history") + + def checkCookie(self): + cookieInfo = self.runCode(""" + try { + let cookieEnum = Services.cookies.getCookiesFromHost(arguments[0]); + let cookie = null; + while (cookieEnum.hasMoreElements()) { + let hostCookie = cookieEnum.getNext(); + hostCookie.QueryInterface(Ci.nsICookie2); + // 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"]) + + tabURIs = self.runAsyncCode(""" + let mm = gBrowser.selectedBrowser.messageManager; + let fs = function() { + content.document.getElementById("errorTryAgain").click(); + }; + let {TabStateFlusher} = Cu.import("resource:///modules/sessionstore/TabStateFlusher.jsm", {}); + window.addEventListener("SSWindowStateReady", function testSSPostReset() { + window.removeEventListener("SSWindowStateReady", testSSPostReset, false); + Promise.all(gBrowser.browsers.map(b => TabStateFlusher.flush(b))).then(function() { + marionetteScriptFinished([... gBrowser.browsers].map(b => b.currentURI && b.currentURI.spec)); + }); + }, false); + mm.loadFrameScript("data:application/javascript,(" + fs.toString() + ")()", true); + """) + self.assertSequenceEqual(tabURIs, ["about:blank"] + self._expectedURLs) + pass + + def checkProfile(self, hasMigrated=False): + self.checkPassword() + self.checkBookmark() + self.checkHistory() + self.checkFormHistory() + self.checkCookie() + if hasMigrated: + self.checkSession() + + def createProfileData(self): + self.savePassword() + self.createBookmark() + self.createHistory() + self.createFormHistory() + self.createCookie() + self.createSession() + + def setUpScriptData(self): + self.marionette.set_context(self.marionette.CONTEXT_CHROME) + self.marionette.execute_script(""" + 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 = Cu.import("resource://gre/modules/Preferences.jsm", {}).Preferences; + global.FormHistory = Cu.import("resource://gre/modules/FormHistory.jsm", {}).FormHistory; + """, new_sandbox=False, sandbox='system') + + def runCode(self, script, *args, **kwargs): + return self.marionette.execute_script(script, new_sandbox=False, sandbox='system', *args, **kwargs) + + def runAsyncCode(self, script, *args, **kwargs): + return self.marionette.execute_async_script(script, new_sandbox=False, sandbox='system', *args, **kwargs) + + def setUp(self): + MarionetteTestCase.setUp(self) + self.setUpScriptData() + + self.reset_profile_path = None + self.desktop_backup_path = None + + self.createProfileData() + + 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(clean=True, in_app=False) + self.setUpScriptData() + + # Super + MarionetteTestCase.tearDown(self) + + # Some helpers to deal with removing a load of files + import errno, stat + def handleRemoveReadonly(func, path, exc): + excvalue = exc[1] + if func in (os.rmdir, os.remove) and excvalue.errno == errno.EACCES: + os.chmod(path, stat.S_IRWXU| stat.S_IRWXG| stat.S_IRWXO) # 0777 + func(path) + else: + raise + + if self.desktop_backup_path: + shutil.rmtree(self.desktop_backup_path, ignore_errors=False, onerror=handleRemoveReadonly) + + if self.reset_profile_path: + # Remove ourselves from profiles.ini + profileLeafName = os.path.basename(os.path.normpath(self.reset_profile_path)) + self.runCode(""" + let [salt, name] = arguments[0].split("."); + let profile = global.profSvc.getProfileByName(name); + profile.remove(false) + global.profSvc.flush(); + """, script_args=[profileLeafName]) + # And delete all the files. + shutil.rmtree(self.reset_profile_path, ignore_errors=False, onerror=handleRemoveReadonly) + + def doReset(self): + self.runCode(""" + // Ensure the current (temporary) profile is in profiles.ini: + let profD = Services.dirsvc.get("ProfD", Ci.nsIFile); + let profileName = "marionette-test-profile-" + Date.now(); + let myProfile = global.profSvc.createProfile(profD, profileName); + global.profSvc.flush() + + // Now add the reset parameters: + let env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment); + let allMarionettePrefs = Services.prefs.getChildList("marionette."); + let prefObj = {}; + for (let pref of allMarionettePrefs) { + let prefSuffix = pref.substr("marionette.".length); + let prefVal = global.Preferences.get(pref); + prefObj[prefSuffix] = prefVal; + } + let marionetteInfo = JSON.stringify(prefObj); + env.set("MOZ_MARIONETTE_PREF_STATE_ACROSS_RESTARTS", marionetteInfo); + env.set("MOZ_RESET_PROFILE_RESTART", "1"); + env.set("XRE_PROFILE_PATH", arguments[0]); + env.set("XRE_PROFILE_NAME", profileName); + """, script_args=[self.marionette.instance.profile.profile]) + + 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) + self.reset_profile_path = self.runCode(""" + let profD = Services.dirsvc.get("ProfD", Ci.nsIFile); + return profD.path; + """) + + # Determine the backup path + self.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], 1); + container.append(dirName); + container.append(arguments[0]); + return container.path; + """, script_args = [profileLeafName]) + + self.assertTrue(os.path.isdir(self.reset_profile_path), "Reset profile path should be present") + self.assertTrue(os.path.isdir(self.desktop_backup_path), "Backup profile path should be present") + + def testReset(self): + self.checkProfile() + + self.doReset() + + # Now check that we're doing OK... + self.checkProfile(hasMigrated=True) |