diff options
Diffstat (limited to 'testing/raptor/raptor/browsertime/android.py')
-rw-r--r-- | testing/raptor/raptor/browsertime/android.py | 267 |
1 files changed, 267 insertions, 0 deletions
diff --git a/testing/raptor/raptor/browsertime/android.py b/testing/raptor/raptor/browsertime/android.py new file mode 100644 index 0000000000..d39850cdb9 --- /dev/null +++ b/testing/raptor/raptor/browsertime/android.py @@ -0,0 +1,267 @@ +#!/usr/bin/env python + +# 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/. + +import os +import shutil +import tempfile + +import mozcrash +from logger.logger import RaptorLogger +from mozdevice import ADBDeviceFactory +from performance_tuning import tune_performance +from perftest import PerftestAndroid +from power import disable_charging, enable_charging + +from .base import Browsertime + +LOG = RaptorLogger(component="raptor-browsertime-android") + + +class BrowsertimeAndroid(PerftestAndroid, Browsertime): + """Android setup and configuration for browsertime + + When running raptor-browsertime tests on android, we create the profile (and set the proxy + prefs in the profile that is using playback) but we don't need to copy it onto the device + because geckodriver takes care of that. + We tell browsertime to use our profile (we pass it in with the firefox.profileTemplate arg); + browsertime creates a copy of that and passes that into geckodriver. Geckodriver then takes + the profile and copies it onto the mobile device's test root for us; and then it even writes + the geckoview app config.yaml file onto the device, which points the app to the profile on + the device's test root. + Therefore, raptor doesn't have to copy the profile onto the scard (and create the config.yaml) + file ourselves. Also note when using playback, the nss certificate db is created as usual when + mitmproxy is started (and saved in the profile) so it is already included in the profile that + browsertime/geckodriver copies onto the device. + XXX: bc: This doesn't work with scoped storage in Android 10 since the shell owns the profile + directory that is pushed to the device and the profile can no longer be on the sdcard. But when + geckodriver's android.rs defines the profile to be located on internal storage, it will be + owned by shell but if we are attempting to eliminate root, then when we run shell commands + as the app, they will fail due to the app being unable to write to the shell owned profile + directory. + """ + + def __init__(self, app, binary, activity=None, intent=None, **kwargs): + super(BrowsertimeAndroid, self).__init__( + app, binary, profile_class="firefox", **kwargs + ) + + self.config.update({"activity": activity, "intent": intent}) + self.remote_profile = None + + def _initialize_device(self): + if self.device is None: + self.device = ADBDeviceFactory(verbose=True) + if not self.config.get("disable_perf_tuning", False): + tune_performance(self.device, log=LOG) + + @property + def android_external_storage(self): + if self._remote_test_root is None: + self._initialize_device() + + external_storage = self.device.shell_output("echo $EXTERNAL_STORAGE") + self._remote_test_root = os.path.join( + external_storage, + "Android", + "data", + self.config["binary"], + "files", + "test_root", + ) + + return self._remote_test_root + + @property + def browsertime_args(self): + args_list = [ + "--viewPort", + "1366x695", + "--videoParams.convert", + "false", + "--videoParams.addTimer", + "false", + ] + + if self.config["app"] == "chrome-m": + args_list.extend( + [ + "--browser", + "chrome", + "--android", + ] + ) + else: + activity = self.config["activity"] + if self.config["app"] == "fenix": + LOG.info( + "Changing initial activity to " + "`mozilla.telemetry.glean.debug.GleanDebugActivity`" + ) + activity = "mozilla.telemetry.glean.debug.GleanDebugActivity" + + args_list.extend( + [ + "--browser", + "firefox", + "--android", + "--firefox.android.package", + self.config["binary"], + "--firefox.android.activity", + activity, + ] + ) + + # Setup power testing + if self.config["power_test"]: + args_list.extend(["--androidPower", "true"]) + + if self.config["app"] == "fenix": + # See bug 1768889 + args_list.extend(["--ignoreShutdownFailures", "true"]) + + # If running on Fenix we must add the intent as we use a + # special non-default one there + if self.config.get("intent") is not None: + args_list.extend(["--firefox.android.intentArgument=-a"]) + args_list.extend( + ["--firefox.android.intentArgument", self.config["intent"]] + ) + + # Change glean ping names in all cases on Fenix + args_list.extend( + [ + "--firefox.android.intentArgument=--es", + "--firefox.android.intentArgument=startNext", + "--firefox.android.intentArgument=" + self.config["activity"], + "--firefox.android.intentArgument=--esa", + "--firefox.android.intentArgument=sourceTags", + "--firefox.android.intentArgument=automation", + "--firefox.android.intentArgument=--ez", + "--firefox.android.intentArgument=performancetest", + "--firefox.android.intentArgument=true", + ] + ) + + args_list.extend(["--firefox.android.intentArgument=-d"]) + args_list.extend( + ["--firefox.android.intentArgument", str("about:blank")] + ) + + return args_list + + def setup_chrome_args(self, test): + chrome_args = [ + "--use-mock-keychain", + "--no-default-browser-check", + "--no-first-run", + ] + + if test.get("playback", False): + pb_args = [ + "--proxy-server=%s:%d" % (self.playback.host, self.playback.port), + "--proxy-bypass-list=localhost;127.0.0.1", + "--ignore-certificate-errors", + ] + + if not self.is_localhost: + pb_args[0] = pb_args[0].replace("127.0.0.1", self.config["host"]) + + chrome_args.extend(pb_args) + + if self.debug_mode: + chrome_args.extend(["--auto-open-devtools-for-tabs"]) + + args_list = [] + for arg in chrome_args: + args_list.extend(["--chrome.args=" + str(arg.replace("'", '"'))]) + + return args_list + + def build_browser_profile(self): + super(BrowsertimeAndroid, self).build_browser_profile() + + # Merge in the Android profile. + path = os.path.join(self.profile_data_dir, "raptor-android") + LOG.info("Merging profile: {}".format(path)) + self.profile.merge(path) + self.profile.set_preferences( + {"browser.tabs.remote.autostart": self.config["e10s"]} + ) + + # There's no great way to have "after" advice in Python, so we do this + # in super and then again here since the profile merging re-introduces + # the "#MozRunner" delimiters. + self.remove_mozprofile_delimiters_from_profile() + + def setup_adb_device(self): + self._initialize_device() + + self.clear_app_data() + self.set_debug_app_flag() + self.device.run_as_package = self.config["binary"] + + self.geckodriver_profile = os.path.join( + self.android_external_storage, + "%s-geckodriver-profile" % self.config["binary"], + ) + + # make sure no remote profile exists + if self.device.exists(self.geckodriver_profile): + self.device.rm(self.geckodriver_profile, force=True, recursive=True) + + def check_for_crashes(self): + super(BrowsertimeAndroid, self).check_for_crashes() + + try: + dump_dir = tempfile.mkdtemp() + remote_dir = os.path.join(self.geckodriver_profile, "minidumps") + if not self.device.is_dir(remote_dir): + return + self.device.pull(remote_dir, dump_dir) + self.crashes += mozcrash.log_crashes( + LOG, dump_dir, self.config["symbols_path"] + ) + except Exception as e: + LOG.error( + "Could not pull the crash data!", + exc_info=True, + ) + raise e + finally: + try: + shutil.rmtree(dump_dir) + except Exception: + LOG.warning("unable to remove directory: %s" % dump_dir) + + def run_test_setup(self, test): + super(BrowsertimeAndroid, self).run_test_setup(test) + + self.set_reverse_ports() + + if self.playback: + self.turn_on_android_app_proxy() + self.remove_mozprofile_delimiters_from_profile() + + def run_tests(self, tests, test_names): + self.setup_adb_device() + + if self.config["app"] == "chrome-m": + # Make sure that chrome is enabled on the device + self.device.shell_output("pm enable com.android.chrome") + + try: + if self.config["power_test"]: + disable_charging(self.device) + return super(BrowsertimeAndroid, self).run_tests(tests, test_names) + finally: + if self.config["power_test"]: + enable_charging(self.device) + + def run_test_teardown(self, test): + LOG.info("removing reverse socket connections") + self.device.remove_socket_connections("reverse") + + super(BrowsertimeAndroid, self).run_test_teardown(test) |