Bug 1445944 - [mozprofile] Create a new ChromeProfile class for managing chrome profiles draft
authorAndrew Halberstadt <ahalberstadt@mozilla.com>
Fri, 13 Apr 2018 13:28:30 -0400
changeset 783207 72ce07ef3336ef9042d48479e99412d79a78cfe6
parent 783206 e9333107b5bdbe49145de98ee8c77c9a606f209a
child 783208 00e438427218c275d1b735f3dd16b6db95f8f2df
push id106641
push userahalberstadt@mozilla.com
push dateMon, 16 Apr 2018 19:49:26 +0000
bugs1445944
milestone61.0a1
Bug 1445944 - [mozprofile] Create a new ChromeProfile class for managing chrome profiles 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