Bug 1445944 - [mozprofile] Create a new ChromeProfile class for managing chrome profiles r=rwood
authorAndrew Halberstadt <ahalberstadt@mozilla.com>
Fri, 13 Apr 2018 13:28:30 -0400
changeset 413940 fdad1264866f03b4e7e9a5fae25bcffa8156ec2d
parent 413939 62c954751e6535370566d267024579e88a01b1a6
child 413941 bfabe6333c854f8c003c7a71371f600099a24fd6
push id33853
push usercbrindusan@mozilla.com
push dateTue, 17 Apr 2018 09:51:13 +0000
treeherdermozilla-central@8b0ba3f7d099 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrwood
bugs1445944
milestone61.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 1445944 - [mozprofile] Create a new ChromeProfile class for managing chrome profiles r=rwood In Chrome it doesn't seem to be possible to install extensions by dropping them in the profile directory. Instead we use the --load-extension command line argument. To that end the ChromeProfile uses a 'dummy' AddonManager() class that is actually just a list with an 'install' method. Mozrunner will be responsible for building the command line based on this list. We also need a few other command line arguments to build and create a temporary profile directory. MozReview-Commit-ID: HC2p2ZZMl66
testing/mozbase/mozprofile/mozprofile/profile.py
testing/mozbase/mozprofile/tests/manifest.ini
testing/mozbase/mozprofile/tests/test_chrome_profile.py
testing/mozbase/mozprofile/tests/test_profile.py
testing/mozbase/mozrunner/mozrunner/application.py
testing/mozbase/mozrunner/mozrunner/base/browser.py
--- a/testing/mozbase/mozprofile/mozprofile/profile.py
+++ b/testing/mozbase/mozprofile/mozprofile/profile.py
@@ -1,29 +1,32 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this file,
 # You can obtain one at http://mozilla.org/MPL/2.0/.
 
 from __future__ import absolute_import
 
+import json
 import os
 import platform
 import tempfile
 import time
 import uuid
 from abc import ABCMeta, abstractmethod
 from shutil import copytree
 
 import mozfile
+from six import string_types
 
 from .addons import AddonManager
 from .permissions import Permissions
 from .prefs import Preferences
 
 __all__ = ['BaseProfile',
+           'ChromeProfile',
            'Profile',
            'FirefoxProfile',
            'ThunderbirdProfile',
            'create_profile']
 
 
 class BaseProfile(object):
     __metaclass__ = ABCMeta
@@ -438,17 +441,53 @@ class ThunderbirdProfile(Profile):
                    'browser.tabs.warnOnClose': False,
                    'browser.warnOnQuit': False,
                    'browser.sessionstore.resume_from_crash': False,
                    # prevents the 'new e-mail address' wizard on new profile
                    'mail.provider.enabled': False,
                    }
 
 
+class ChromeProfile(BaseProfile):
+    class AddonManager(list):
+        def install(self, addons):
+            if isinstance(addons, string_types):
+                addons = [addons]
+            self.extend(addons)
+
+    def __init__(self, **kwargs):
+        super(ChromeProfile, self).__init__(**kwargs)
+
+        if self.create_new:
+            self.profile = os.path.join(self.profile, 'Default')
+        self._reset()
+
+    def _reset(self):
+        if not os.path.isdir(self.profile):
+            os.makedirs(self.profile)
+
+        if self._preferences:
+            pref_file = os.path.join(self.profile, 'Preferences')
+
+            prefs = {}
+            if os.path.isfile(pref_file):
+                with open(pref_file, 'r') as fh:
+                    prefs.update(json.load(fh))
+
+            prefs.update(self._preferences)
+            with open(pref_file, 'w') as fh:
+                json.dump(prefs, fh)
+
+        self.addons = self.AddonManager()
+        if self._addons:
+            self.addons.install(self._addons)
+
+
 profile_class = {
+    'chrome': ChromeProfile,
     'firefox': FirefoxProfile,
     'thunderbird': ThunderbirdProfile,
 }
 
 
 def create_profile(app, **kwargs):
     """Create a profile given an application name.
 
--- a/testing/mozbase/mozprofile/tests/manifest.ini
+++ b/testing/mozbase/mozprofile/tests/manifest.ini
@@ -5,8 +5,9 @@ subsuite = mozbase, os == "linux"
 [test_preferences.py]
 [test_permissions.py]
 [test_bug758250.py]
 [test_nonce.py]
 [test_clone_cleanup.py]
 [test_profile.py]
 [test_profile_view.py]
 [test_addons.py]
+[test_chrome_profile.py]
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/mozprofile/tests/test_chrome_profile.py
@@ -0,0 +1,75 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from __future__ import absolute_import
+
+import json
+import os
+
+import mozunit
+
+from mozprofile import ChromeProfile
+
+
+def test_chrome_profile_pre_existing(tmpdir):
+    path = tmpdir.strpath
+    profile = ChromeProfile(profile=path)
+    assert not profile.create_new
+    assert os.path.isdir(profile.profile)
+    assert profile.profile == path
+
+
+def test_chrome_profile_create_new():
+    profile = ChromeProfile()
+    assert profile.create_new
+    assert os.path.isdir(profile.profile)
+    assert profile.profile.endswith('Default')
+
+
+def test_chrome_preferences(tmpdir):
+    prefs = {'foo': 'bar'}
+    profile = ChromeProfile(preferences=prefs)
+    prefs_file = os.path.join(profile.profile, 'Preferences')
+
+    assert os.path.isfile(prefs_file)
+
+    with open(prefs_file) as fh:
+        assert json.load(fh) == prefs
+
+    # test with existing prefs
+    prefs_file = tmpdir.join('Preferences').strpath
+    with open(prefs_file, 'w') as fh:
+        json.dump({'num': '1'}, fh)
+
+    profile = ChromeProfile(profile=tmpdir.strpath, preferences=prefs)
+
+    def assert_prefs():
+        with open(prefs_file) as fh:
+            data = json.load(fh)
+
+        assert len(data) == 2
+        assert data.get('foo') == 'bar'
+        assert data.get('num') == '1'
+
+    assert_prefs()
+    profile.reset()
+    assert_prefs()
+
+
+def test_chrome_addons():
+    addons = ['foo', 'bar']
+    profile = ChromeProfile(addons=addons)
+
+    assert isinstance(profile.addons, list)
+    assert profile.addons == addons
+
+    profile.addons.install('baz')
+    assert profile.addons == addons + ['baz']
+
+    profile.reset()
+    assert profile.addons == addons
+
+
+if __name__ == '__main__':
+    mozunit.main()
--- a/testing/mozbase/mozprofile/tests/test_profile.py
+++ b/testing/mozbase/mozprofile/tests/test_profile.py
@@ -9,16 +9,17 @@ from __future__ import absolute_import
 import os
 
 import mozunit
 import pytest
 
 from mozprofile import (
     BaseProfile,
     Profile,
+    ChromeProfile,
     FirefoxProfile,
     ThunderbirdProfile,
     create_profile,
 )
 
 
 def test_with_profile_should_cleanup():
     with Profile() as profile:
@@ -34,16 +35,17 @@ def test_with_profile_should_cleanup_eve
             assert os.path.exists(profile.profile)
             1 / 0  # will raise ZeroDivisionError
 
     # profile is cleaned
     assert not os.path.exists(profile.profile)
 
 
 @pytest.mark.parametrize('app,cls', [
+    ('chrome', ChromeProfile),
     ('firefox', FirefoxProfile),
     ('thunderbird', ThunderbirdProfile),
     ('unknown', None)
 ])
 def test_create_profile(tmpdir, app, cls):
     path = tmpdir.strpath
 
     if cls is None:
--- a/testing/mozbase/mozrunner/mozrunner/application.py
+++ b/testing/mozbase/mozrunner/mozrunner/application.py
@@ -7,16 +7,17 @@ from __future__ import absolute_import
 from abc import ABCMeta, abstractmethod
 from distutils.spawn import find_executable
 import os
 import posixpath
 
 from mozdevice import DeviceManagerADB, DroidADB
 from mozprofile import (
     Profile,
+    ChromeProfile,
     FirefoxProfile,
     ThunderbirdProfile
 )
 
 here = os.path.abspath(os.path.dirname(__file__))
 
 
 def get_app_context(appname):
@@ -131,14 +132,10 @@ class FennecContext(RemoteContext):
 class FirefoxContext(object):
     profile_class = FirefoxProfile
 
 
 class ThunderbirdContext(object):
     profile_class = ThunderbirdProfile
 
 
-class ChromeProfile(object):
-    """Dummy profile class until a proper one is implemented in mozprofile"""
-
-
 class ChromeContext(object):
     profile_class = ChromeProfile
--- a/testing/mozbase/mozrunner/mozrunner/base/browser.py
+++ b/testing/mozbase/mozrunner/mozrunner/base/browser.py
@@ -81,15 +81,25 @@ class GeckoRuntimeRunner(BaseRunner):
 
 class BlinkRuntimeRunner(BaseRunner):
     """A base runner class for running apps like Google Chrome or Chromium."""
     def __init__(self, binary, cmdargs=None, **runner_args):
         super(BlinkRuntimeRunner, self).__init__(**runner_args)
         self.binary = binary
         self.cmdargs = cmdargs or []
 
+        data_dir, name = os.path.split(self.profile.profile)
+        profile_args = [
+            '--user-data-dir={}'.format(data_dir),
+            '--profile-directory={}'.format(name),
+            '--no-first-run',
+        ]
+        self.cmdargs.extend(profile_args)
+
     @property
     def command(self):
         cmd = self.cmdargs[:]
+        if self.profile.addons:
+            cmd.append('--load-extension={}'.format(','.join(self.profile.addons)))
         return [self.binary] + cmd
 
     def check_for_crashes(self, *args, **kwargs):
         raise NotImplementedError