marionette hacking draft
authorTom Prince <mozilla@hocat.ca>
Thu, 23 Nov 2017 13:26:03 -0700
changeset 703816 63524d706f5eed5c79a4aee7af6cb2c2b8828e6c
parent 703815 f3e0ba02a0ab77bd7a273104f4c066e6f97f808d
child 703817 031ccf358431ca3b2c2edf066da56cbe28cd5a03
push id90981
push userbmo:mozilla@hocat.ca
push dateMon, 27 Nov 2017 20:23:23 +0000
milestone59.0a1
marionette hacking
python/mozbuild/mozbuild/base.py
testing/marionette/client/marionette_driver/geckoinstance.py
testing/marionette/components/marionette.js
testing/marionette/components/marionette.manifest
testing/marionette/mach_commands.py
--- a/python/mozbuild/mozbuild/base.py
+++ b/python/mozbuild/mozbuild/base.py
@@ -862,16 +862,23 @@ class MachCommandConditions(object):
     @staticmethod
     def is_android(cls):
         """Must have an Android build."""
         if hasattr(cls, 'substs'):
             return cls.substs.get('MOZ_WIDGET_TOOLKIT') == 'android'
         return False
 
     @staticmethod
+    def is_thunderbird(cls):
+        """Must have a Thunderbird build."""
+        if hasattr(cls, 'substs'):
+            return cls.substs.get('MOZ_BUILD_APP') == 'comm/mail'
+        return False
+
+    @staticmethod
     def is_hg(cls):
         """Must have a mercurial source checkout."""
         return getattr(cls, 'substs', {}).get('VCS_CHECKOUT_TYPE') == 'hg'
 
     @staticmethod
     def is_git(cls):
         """Must have a git source checkout."""
         return getattr(cls, 'substs', {}).get('VCS_CHECKOUT_TYPE') == 'git'
--- a/testing/marionette/client/marionette_driver/geckoinstance.py
+++ b/testing/marionette/client/marionette_driver/geckoinstance.py
@@ -481,23 +481,135 @@ class DesktopInstance(GeckoInstance):
         "startup.homepage_welcome_url": "about:blank",
         "startup.homepage_welcome_url.additional": "",
     }
 
     def __init__(self, *args, **kwargs):
         super(DesktopInstance, self).__init__(*args, **kwargs)
         self.required_prefs.update(DesktopInstance.desktop_prefs)
 
+class ThunderbirdInstance(GeckoInstance):
+    preferences = {
+        # say yes to debug output via dump
+        'browser.dom.window.dump.enabled': True,
+        # say no to slow script warnings
+        'dom.max_chrome_script_run_time': 0,
+        'dom.max_script_run_time': 0,
+        # disable extension stuffs
+        'extensions.update.enabled'    : False,
+        'extensions.update.notifyUser' : False,
+        # don't warn about third party extensions in profile or elsewhere.
+        'extensions.autoDisableScopes': 10,
+        # do not ask about being the default mail client
+        'mail.shell.checkDefaultClient': False,
+        # do not tell us about the greatness that is mozilla (about:rights)
+        'mail.rights.override': True,
+        # disable non-gloda indexing daemons
+        'mail.winsearch.enable': False,
+        'mail.winsearch.firstRunDone': True,
+        'mail.spotlight.enable': False,
+        'mail.spotlight.firstRunDone': True,
+        # disable address books for undisclosed reasons
+        'ldap_2.servers.osx.position': 0,
+        'ldap_2.servers.oe.position': 0,
+        # disable the first use junk dialog
+        'mailnews.ui.junk.firstuse': False,
+        # set the relative dirs properly
+        'mail.root.none-rel' :  "[ProfD]Mail",
+        'mail.root.pop3-rel' :  "[ProfD]Mail",
+        # Do not allow check new mail to be set
+        'mail.startup.enabledMailCheckOnce' :  True,
+        # Disable compatibility checking
+        'extensions.checkCompatibility.nightly': False,
+        # Stop any pings to AMO on add-on install
+        'extensions.getAddons.cache.enabled': False,
+        # In case a developer is working on a laptop without a network
+        # connection, don't detect offline mode; hence we'll still startup
+        # online which is what mozmill currently requires. It'll also protect us
+        # from any random network failures.
+        'offline.autoDetect': False,
+        # Don't load what's new or the remote start page - keep everything local
+        # under our control.
+        'mailnews.start_page_override.mstone' :  "ignore",
+        'mailnews.start_page.url': "about:blank",
+        # Do not enable gloda
+        'mailnews.database.global.indexer.enabled': False,
+        # But do have gloda log if it does anything.  (When disabled, queries
+        # are still serviced; they just should not result in any matches.)
+        'mailnews.database.global.logging.upstream': True,
+        # Do not allow fonts to be upgraded
+        'mail.font.windows.version': 2,
+        # No, we don't want to be prompted about Telemetry
+        'toolkit.telemetry.prompted': 999,
+        }
+
+    # Dummied up local accounts to stop the account wizard
+    account_preferences = {
+        'mail.account.account1.server' :  "server1",
+        'mail.account.account2.identities' :  "id1,id2",
+        'mail.account.account2.server' :  "server2",
+        'mail.account.account3.server' :  "server3",
+        'mail.accountmanager.accounts' :  "account1,account2,account3",
+        'mail.accountmanager.defaultaccount' :  "account2",
+        'mail.accountmanager.localfoldersserver' :  "server1",
+        'mail.identity.id1.fullName' :  "Tinderbox",
+        'mail.identity.id1.htmlSigFormat' : False,
+        'mail.identity.id1.htmlSigText' : "Tinderbox is soo 90ies",
+        'mail.identity.id1.smtpServer' :  "smtp1",
+        'mail.identity.id1.useremail' :  "tinderbox@foo.invalid",
+        'mail.identity.id1.valid' :  True,
+        'mail.identity.id2.fullName' : "Tinderboxpushlog",
+        'mail.identity.id2.htmlSigFormat' : True,
+        'mail.identity.id2.htmlSigText' : "Tinderboxpushlog is the new <b>hotness!</b>",
+        'mail.identity.id2.smtpServer' : "smtp1",
+        'mail.identity.id2.useremail' : "tinderboxpushlog@foo.invalid",
+        'mail.identity.id2.valid' : True,
+        'mail.server.server1.directory-rel' :  "[ProfD]Mail/Local Folders",
+        'mail.server.server1.hostname' :  "Local Folders",
+        'mail.server.server1.name' :  "Local Folders",
+        'mail.server.server1.type' :  "none",
+        'mail.server.server1.userName' :  "nobody",
+        'mail.server.server2.check_new_mail' :  False,
+        'mail.server.server2.directory-rel' :  "[ProfD]Mail/tinderbox",
+        'mail.server.server2.download_on_biff' :  True,
+        'mail.server.server2.hostname' :  "tinderbox123",
+        'mail.server.server2.login_at_startup' :  False,
+        'mail.server.server2.name' :  "tinderbox@foo.invalid",
+        'mail.server.server2.type' :  "pop3",
+        'mail.server.server2.userName' :  "tinderbox",
+        'mail.server.server2.whiteListAbURI': "",
+        'mail.server.server3.hostname' :  "prpl-irc",
+        'mail.server.server3.imAccount' :  "account1",
+        'mail.server.server3.type' :  "im",
+        'mail.server.server3.userName' :  "mozmilltest@irc.mozilla.invalid",
+        'mail.smtp.defaultserver' :  "smtp1",
+        'mail.smtpserver.smtp1.hostname' :  "tinderbox123",
+        'mail.smtpserver.smtp1.username' :  "tinderbox",
+        'mail.smtpservers' :  "smtp1",
+        'messenger.account.account1.autoLogin' :  False,
+        'messenger.account.account1.firstConnectionState' :  1,
+        'messenger.account.account1.name' :  "mozmilltest@irc.mozilla.invalid",
+        'messenger.account.account1.prpl' :  "prpl-irc",
+        'messenger.accounts' :  "account1",
+    }
+
+    def __init__(self, *args, **kwargs):
+        super(ThunderbirdInstance, self).__init__(*args, **kwargs)
+        self.required_prefs.update(ThunderbirdInstance.preferences)
+        self.required_prefs.update(ThunderbirdInstance.account_preferences)
+
 
 class NullOutput(object):
     def __call__(self, line):
         pass
 
 
 apps = {
     'fennec': FennecInstance,
     'fxdesktop': DesktopInstance,
+    'thunderbird': ThunderbirdInstance,
 }
 
 app_ids = {
     '{aa3c5121-dab2-40e2-81ca-7ea25febc110}': 'fennec',
     '{ec8030f7-c20a-464f-9b0e-13a3a9e97384}': 'fxdesktop',
+    '{3550f703-e582-4d05-9a08-453d09bdfdc6}': 'thunderbird',
 }
--- a/testing/marionette/components/marionette.js
+++ b/testing/marionette/components/marionette.js
@@ -19,17 +19,17 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 const MARIONETTE_CONTRACT_ID = "@mozilla.org/remote/marionette;1";
 const MARIONETTE_CID = Components.ID("{786a1369-dca5-4adc-8486-33d23c88010a}");
 
 const PREF_PORT = "marionette.port";
 const PREF_PORT_FALLBACK = "marionette.defaultPrefs.port";
 const PREF_LOG_LEVEL = "marionette.log.level";
 const PREF_LOG_LEVEL_FALLBACK = "marionette.logging";
 
-const DEFAULT_LOG_LEVEL = "info";
+const DEFAULT_LOG_LEVEL = "trace";
 const LOG_LEVELS = new class extends Map {
   constructor() {
     super([
       ["fatal", Log.Level.Fatal],
       ["error", Log.Level.Error],
       ["warn", Log.Level.Warn],
       ["info", Log.Level.Info],
       ["config", Log.Level.Config],
@@ -170,36 +170,37 @@ MarionetteComponent.prototype = {
   contractID: MARIONETTE_CONTRACT_ID,
   QueryInterface: XPCOMUtils.generateQI([
     Ci.nsICommandLineHandler,
     Ci.nsIMarionette,
   ]),
   // eslint-disable-next-line camelcase
   _xpcom_categories: [
     {category: "command-line-handler", entry: "b-marionette"},
+    {category: "mail-startup-done", entry: "b-marionette"},
     {category: "profile-after-change", service: true},
   ],
   helpInfo: "  --marionette       Enable remote control server.\n",
 };
 
 // Handle -marionette flag
 MarionetteComponent.prototype.handle = function(cmdLine) {
   if (!this.enabled && cmdLine.handleFlag("marionette", false)) {
     this.enabled = true;
     this.logger.info("Enabled via --marionette");
   }
 };
 
 MarionetteComponent.prototype.observe = function(subject, topic) {
-  this.logger.debug(`Received observer notification "${topic}"`);
+  this.logger.info(`Received observer notification "${topic}"`);
 
   switch (topic) {
     case "profile-after-change":
       Services.obs.addObserver(this, "command-line-startup");
-      Services.obs.addObserver(this, "sessionstore-windows-restored");
+      Services.obs.addObserver(this, "mail-startup-done");
 
       prefs.readFromEnvironment(ENV_PRESERVE_PREFS);
       break;
 
     // In safe mode the command line handlers are getting parsed after the
     // safe mode dialog has been closed. To allow Marionette to start
     // earlier, use the CLI startup observer notification for
     // special-cased handlers, which gets fired before the dialog appears.
@@ -225,17 +226,17 @@ MarionetteComponent.prototype.observe = 
       }
       break;
 
     case "domwindowopened":
       Services.obs.removeObserver(this, topic);
       this.suppressSafeModeDialog(subject);
       break;
 
-    case "sessionstore-windows-restored":
+    case "mail-startup-done":
       Services.obs.removeObserver(this, topic);
 
       // When Firefox starts on Windows, an additional GFX sanity test
       // window may appear off-screen.  Marionette should wait for it
       // to close.
       let winEn = Services.wm.getEnumerator(null);
       while (winEn.hasMoreElements()) {
         let win = winEn.getNext();
@@ -268,17 +269,17 @@ MarionetteComponent.prototype.setupLogge
   logger.addAppender(new Log.DumpAppender());
   return logger;
 };
 
 MarionetteComponent.prototype.suppressSafeModeDialog = function(win) {
   win.addEventListener("load", () => {
     if (win.document.getElementById("safeModeDialog")) {
       // accept the dialog to start in safe-mode
-      this.logger.debug("Safe Mode detected. Going to suspress the dialog now.");
+      this.logger.info("Safe Mode detected. Going to suspress the dialog now.");
       win.setTimeout(() => {
         win.document.documentElement.getButton("accept").click();
       });
     }
   }, {once: true});
 };
 
 MarionetteComponent.prototype.init = function() {
--- a/testing/marionette/components/marionette.manifest
+++ b/testing/marionette/components/marionette.manifest
@@ -1,4 +1,5 @@
 component {786a1369-dca5-4adc-8486-33d23c88010a} marionette.js
 contract @mozilla.org/remote/marionette;1 {786a1369-dca5-4adc-8486-33d23c88010a}
 category command-line-handler b-marionette @mozilla.org/remote/marionette;1
+category mail-startup-done b-marionette @mozilla.org/remote/marionette;1
 category profile-after-change Marionette @mozilla.org/remote/marionette;1
--- a/testing/marionette/mach_commands.py
+++ b/testing/marionette/mach_commands.py
@@ -18,17 +18,17 @@ from mach.decorators import (
 from mozbuild.base import (
     MachCommandBase,
     MachCommandConditions as conditions,
 )
 
 
 def is_firefox_or_android(cls):
     """Must have Firefox build or Android build."""
-    return conditions.is_firefox(cls) or conditions.is_android(cls)
+    return conditions.is_firefox(cls) or conditions.is_android(cls) or conditions.is_thunderbird(cls)
 
 
 def create_parser_tests():
     from marionette_harness.runtests import MarionetteArguments
     from mozlog.structured import commandline
     parser = MarionetteArguments()
     commandline.add_logging_group(parser)
     return parser
@@ -101,31 +101,32 @@ class Marionette(MachCommandBase):
         return os.path.join(self.topsrcdir, "testing/marionette")
 
     @Command("marionette",
              category="misc",
              description="Remote control protocol to Gecko, used for functional UI tests and browser automation.",
              conditions=[is_firefox_or_android],
              )
     def marionette(self):
-        self.parser.print_usage()
+        self._mach_context.commands.dispatch("marionette",
+                                             self._mach_context, subcommand="help")
         return 1
 
     @SubCommand("marionette", "test",
                 description="Run browser automation tests based on Marionette harness.",
                 parser=create_parser_tests,
                 )
     def marionette_test(self, tests, **kwargs):
         if "test_objects" in kwargs:
             tests = []
             for obj in kwargs["test_objects"]:
                 tests.append(obj["file_relpath"])
             del kwargs["test_objects"]
 
-        if not kwargs.get("binary") and conditions.is_firefox(self):
+        if not kwargs.get("binary") and conditions.is_firefox(self) or conditions.is_thunderbird(self):
             kwargs["binary"] = self.get_binary_path("app")
         return run_marionette(tests, topsrcdir=self.topsrcdir, **kwargs)
 
     @SubCommand("marionette", "doc",
                 description="Generate Marionette server API documentation in testing/marionette/doc.")
     @CommandArgument("--http",
                      help='HTTP service address (e.g. "127.0.0.1:6060" or just ":6060".'
                      )