Ejemplo n.º 1
0
class AndroidLauncher(Launcher):
    app_info = None
    adb = None
    package_name = None
    profile_class = FirefoxRegressionProfile
    remote_profile = None

    @abstractmethod
    def _get_package_name(self):
        raise NotImplementedError

    @abstractmethod
    def _launch(self):
        raise NotImplementedError

    @classmethod
    def check_is_runnable(cls):
        try:
            devices = ADBHost().devices()
        except ADBError as adb_error:
            raise LauncherNotRunnable(str(adb_error))
        if not devices:
            raise LauncherNotRunnable(
                "No android device connected." " Connect a device and try again."
            )

    def _install(self, dest):
        # get info now, as dest may be removed
        self.app_info = safe_get_version(binary=dest)
        self.package_name = self.app_info.get("package_name", self._get_package_name())
        self.adb = ADBDeviceFactory()
        try:
            self.adb.uninstall_app(self.package_name)
        except ADBError as msg:
            LOG.warning(
                "Failed to uninstall %s (%s)\nThis is normal if it is the"
                " first time the application is installed." % (self.package_name, msg)
            )
        self.adb.run_as_package = self.adb.install_app(dest)

    def _start(
        self,
        profile=None,
        addons=(),
        cmdargs=(),
        preferences=None,
        adb_profile_dir=None,
    ):
        # for now we don't handle addons on the profile for fennec
        profile = self._create_profile(profile=profile, preferences=preferences)
        # send the profile on the device
        if not adb_profile_dir:
            adb_profile_dir = self.adb.test_root
        self.remote_profile = "/".join([adb_profile_dir, os.path.basename(profile.profile)])
        if self.adb.exists(self.remote_profile):
            self.adb.rm(self.remote_profile, recursive=True)
        LOG.debug("Pushing profile to device (%s -> %s)" % (profile.profile, self.remote_profile))
        self.adb.push(profile.profile, self.remote_profile)
        self._launch()

    def _wait(self):
        while self.adb.process_exist(self.package_name):
            time.sleep(0.1)

    def _stop(self):
        self.adb.stop_application(self.package_name)
        if self.adb.exists(self.remote_profile):
            self.adb.rm(self.remote_profile, recursive=True)

    def get_app_info(self):
        return self.app_info
Ejemplo n.º 2
0
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_test_root = None
        self.remote_profile = None

    @property
    def browsertime_args(self):
        args_list = ["--viewPort", "1366x695"]

        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",
                # Work around a `selenium-webdriver` issue where Browsertime
                # fails to find a Firefox binary even though we're going to
                # actually do things on an Android device.
                "--firefox.binaryPath",
                self.browsertime_node,
                "--firefox.android.package",
                self.config["binary"],
                "--firefox.android.activity",
                activity,
            ])
            if self.browsertime_geckodriver:
                args_list.extend([
                    # Set geckoprofile location to internal so we are able to get crashes
                    '--firefox.geckodriverArgs="--android-storage"',
                    "--firefox.geckodriverArgs=internal",
                ])

        # Setup power testing
        if self.config["power_test"]:
            args_list.extend(["--androidPower", "true"])

        # If running on Fenix we must add the intent as we use a special non-default one there
        if self.config["app"] == "fenix" and 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",
            ])

            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):
        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)

        self.clear_app_data()
        self.set_debug_app_flag()
        self.device.run_as_package = self.config["binary"]
        self.remote_test_root = self.device.test_root
        self.geckodriver_profile = os.path.join(
            self.remote_test_root,
            "%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)