Пример #1
0
    async def from_apk_async(cls: Type[_Ty],
                             apk_path: str,
                             device: Device,
                             as_upgrade=False) -> _Ty:
        """
        Install provided application asynchronously.  This allows the output of the install to be processed
        in a streamed fashion.  This can be useful on some devices that are non-standard android where installs
        cause (for example) a pop-up requesting direct user permission for the install -- monitoring for a specific
        message to simulate the tap to confirm the install.

        :param apk_path: path to apk
        :param device: device to install on
        :param as_upgrade: whether to install as upgrade or not

        :return: remote installed application

        :raises: Exception if failure ot install or verify installation

        >>> async def install():
        ...     async with await Application.from_apk_async("/some/local/path/to/apk") as stdout:
        ...         async for line in stdout:
        ...            if "some trigger message" in line:
        ...               perform_tap_to_accept_install()

        """
        parser = AXMLParser.parse(apk_path)
        package = parser.package_name
        await device.install(apk_path, as_upgrade)
        return cls(package, device)
Пример #2
0
 def apk_info(
         apk_file_name: str) -> Tuple[Optional[Any], Optional[Any]]:
     attrs = {
         attr.name: attr.value
         for attr in AXMLParser.parse(apk_file_name).xml_head.attributes
     }
     return attrs.get('package'), attrs.get('versionName')
Пример #3
0
    def from_apk(cls,
                 apk_path: str,
                 device: Device,
                 as_upgrade=False) -> "TestApplication":
        """
        install apk as a test application on the given device

        :param apk_path: path to test apk (containing a runner)
        :param device: device to install on
        :param as_upgrade: whether to install as upgrade or not

        :return: `TestApplication` of installed apk

        >>> test_application = Application.from_apk("/local/path/to/apk", device, as_upgrade=True)
        """
        parser = AXMLParser.parse(apk_path)
        # The manifest of the package should contain an instrumentation section, if not, it is not
        # a test apk:
        valid = (hasattr(parser, "instrumentation")
                 and (parser.instrumentation is not None)
                 and bool(parser.instrumentation.target_package)
                 and bool(parser.instrumentation.runner))
        if not valid:
            raise Exception(
                "Test application's manifest does not specify proper instrumentation element."
                "Are you sure this is a test app")
        device.install_synchronous(apk_path, as_upgrade)
        return cls(parser.package_name, device,
                   parser.instrumentation.target_package,
                   parser.instrumentation.runner)
Пример #4
0
    async def install(self,
                      apk_path: str,
                      as_upgrade: bool,
                      conditions: List[str] = None,
                      on_full_install: Optional[Callable] = None) -> None:
        """
        Install given apk asynchronously, monitoring output for messages containing any of the given conditions,
        executing a callback if given when any such condition is met
        :param apk_path: bundle to install
        :param as_upgrade: whether as upgrade or not
        :param conditions: list of strings to look for in stdout as a trigger for callback
           Some devices are non-standard and will provide a pop-up request explicit user permission for install
           once the apk is fully uploaded and prepared
           This param defaults to ["100\%", "pkg:", "Success"] as indication that bundle was fully prepared (pre-pop-up)
        :param on_full_install: if not None the callback to be called

        :raises: Device.InsufficientStorageError if there is not enough space on device
        """
        conditions = conditions or ["100\%", "pkg:", "Success"]
        parser = AXMLParser.parse(apk_path)
        package = parser.package_name
        if not as_upgrade:
            # avoid unnecessary conflicts with older certs and crap:
            # TODO:  client should handle clean install -- this code really shouldn't be here??
            with suppress(Exception):
                self.execute_remote_cmd("uninstall",
                                        package,
                                        capture_stdout=False)

        # Execute the installation of the app, monitoring output for completion in order to invoke
        # any extra commands
        if as_upgrade:
            cmd = ("install", "-r", apk_path)
        else:
            cmd = ("install", apk_path)
        # do not allow more than one install at a time on a specific device, as
        # this can be problematic
        async with self.lock():
            async with await self.execute_remote_cmd_async(
                    *cmd,
                    proc_completion_timeout=TIMEOUT_LONG_ADB_CMD) as lines:
                async for msg in lines:
                    log.debug(msg)

                    if self.ERROR_MSG_INSUFFICIENT_STORAGE in msg:
                        raise self.InsufficientStorageError(
                            "Insufficient storage for install of %s" %
                            apk_path)
                    # some devices have non-standard pop-ups that must be cleared by accepting usb installs
                    # (non-standard Android):
                    if on_full_install and msg and any(
                        [condition in msg for condition in conditions]):
                        on_full_install()

        # on some devices, a pop-up may prevent successful install even if return code from adb install showed
        # success, so must explicitly verify the install was successful:
        log.debug("Verifying install...")
        self._verify_install(apk_path,
                             package)  # raises exception on failure to verify
Пример #5
0
def uninstall_apk(apk, device):
    """
    A little of a misnomer, as we don't actually uninstall an apk, however we can get the name of the
    package from the apk and ensure the app is not installed on the device (ensure "cleanliness" for testing)
    :param apk: apk to get package name from
    :param device: device to uninstall package from
    """
    with suppress(Exception):
        Application(AXMLParser.parse(apk).package_name, device).uninstall()
Пример #6
0
 def test_uninstall_upgrade(self, upgrade_apk: str) -> None:
     package = AXMLParser.parse(upgrade_apk).package_name
     if package not in self._device.list_installed_packages():
         return
     app = Application(self._device, {'package_name': package})
     app.stop()
     app.uninstall()
     if package in self._device.list_installed_packages():
         raise UpgradeTestException(
             f"Uninstall upgrade package {package} failed")
Пример #7
0
 def from_apk(
         cls: Type[_TTestApp],
         apk_path: str,
         device: Device,
         as_upgrade: bool = False,
         timeout: Optional[int] = Device.TIMEOUT_LONG_ADB_CMD) -> _TTestApp:
     parser = AXMLParser.parse(apk_path)
     args = ["-t"] if not as_upgrade else ["-r", "-t"]
     cls._install(device, apk_path, *args, timeout=timeout)
     return cls(device, parser)
Пример #8
0
 def test_apk_later_version(self):
     parser = AXMLParser.parse(USER_ACCEPTANCE_APK)
     assert {
         'android.permission.ACCESS_FINE_LOCATION',
         'android.permission.MANAGE_ACCOUNTS',
         'android.permission.WRITE_SYNC_SETTINGS',
         'android.permission.INTERNET',
         'android.permission.READ_SYNC_SETTINGS',
         'android.permission.AUTHENTICATE_ACCOUNTS',
     } < set(parser.permissions)
Пример #9
0
 def test_apk_parsing(self):
     parser = AXMLParser.parse(BASIC_APK)
     assert str(parser.xml) == EXPECTED_XML
     assert parser.package_name == "com.linkedin.mdctest.test"
     assert parser.instrumentation is not None
     assert parser.instrumentation.handle_profiling is False
     assert parser.instrumentation.functional_test is False
     assert parser.instrumentation.runner == "android.support.test.runner.AndroidJUnitRunner"
     assert parser.instrumentation.label == "Tests for com.linkedin.mdctest"
     assert parser.instrumentation.target_package == "com.linkedin.mdctest"
     assert parser.uses_sdk.min_sdk_version == 15
     assert parser.uses_sdk.target_sdk_version == 25
     assert not parser.permissions  # no permissions
Пример #10
0
    async def _monitor_install(
            cls,
            device: Device,
            apk_path: str,
            *args: str,
            timeout: Optional[int] = Device.TIMEOUT_LONG_ADB_CMD,
            callback: Optional[Callable[[Device.Process],
                                        None]] = None) -> None:
        """
        Install given apk asynchronously, monitoring output for messages containing any of the given conditions,
        executing a callback if given when any such condition is met.

        :param device: Device to install on
        :param apk_path: bundle to install
        :param timeout: timeout if install takes too long
        :param callback: if not None, callback to be made on successful push and at start of install; the
           `Device.Process` that is installing from device storage is passed as the only parameter to the callback
        :raises Device.InsufficientStorageError: if there is not enough space on device
        :raises IOError if push of apk to device was unsuccessful
        :raises; TimeoutError if install takes more than timeout parameter
        """
        parser = AXMLParser.parse(apk_path)
        package = parser.package_name
        remote_data_path = f"/data/local/tmp/{package}"
        try:
            # We try Android Studio's method of pushing to device and installing from there, but if push is
            # unsuccessful, we fallback to plain adb install
            push_cmd = ("push", apk_path, remote_data_path)
            device.execute_remote_cmd(*push_cmd, timeout=timeout)

            # Execute the installation of the app, monitoring output for completion in order to invoke any extra
            # commands or detect insufficient storage issues
            cmd: List[str] = ["shell", "pm", "install"
                              ] + list(args) + [remote_data_path]
            # Do not allow more than one install at a time on a specific device, as this can be problematic
            async with _device_lock(device), device.monitor_remote_cmd(
                *cmd) as proc:
                if callback:
                    callback(proc)
                await proc.wait(timeout=Device.TIMEOUT_LONG_ADB_CMD)

        except Device.CommandExecutionFailure as e:
            raise IOError(f"Unable to push apk to device {e}") from e

        finally:
            with suppress(Exception):
                rm_cmd = ("shell", "rm", remote_data_path)
                device.execute_remote_cmd(*rm_cmd,
                                          timeout=Device.TIMEOUT_ADB_CMD)
Пример #11
0
 async def from_apk_async(
     cls: Type[_TTestApp],
     apk_path: str,
     device: Device,
     as_upgrade: bool = False,
     timeout: Optional[int] = Device.TIMEOUT_LONG_ADB_CMD,
     callback: Optional[Callable[[Device.Process],
                                 None]] = None) -> _TTestApp:
     parser = AXMLParser.parse(apk_path)
     args = ["-t"] if not as_upgrade else ["-r", "-t"]
     await cls._monitor_install(device,
                                apk_path,
                                parser.package_name,
                                *args,
                                callback=callback,
                                timeout=timeout)
     return cls(device, parser)
Пример #12
0
    def from_apk(cls: Type[_Ty],
                 apk_path: str,
                 device: Device,
                 as_upgrade=False) -> _Ty:
        """
        Install provided application, blocking until install is complete

        :param apk_path: path to apk
        :param device: device to install on
        :param as_upgrade: whether to install as upgrade or not

        :return: remote installed application

        :raises: Exception if failure ot install or verify installation
        """
        parser = AXMLParser.parse(apk_path)
        package = parser.package_name
        device.install_synchronous(apk_path, as_upgrade)
        return cls(package, device)
Пример #13
0
    async def from_apk_async(
            cls: Type[_TApp],
            apk_path: str,
            device: Device,
            as_upgrade: bool = False,
            timeout: Optional[int] = Device.TIMEOUT_LONG_ADB_CMD,
            callback: Optional[Callable[[Device.Process],
                                        None]] = None) -> _TApp:
        """
        Install provided application asynchronously.  This allows the output of the install to be processed
        in a streamed fashion.  This can be useful on some devices that are non-standard android where installs
        cause (for example) a pop-up requesting direct user permission for the install -- monitoring for a specific
        message to simulate the tap to confirm the install.

        :param apk_path: path to apk
        :param device: device to install on
        :param as_upgrade: whether to install as upgrade or not
        :param timeout: raises TimeoutError if specified and install takes too long
        :param callback: if not None, callback to be made on successful push and at start of install; the
           `Device.Process` that is installing from device storage is passed as the only parameter to the callback
        :return: remote installed application
        :raises: Exception if failure of install or verify installation
        :raises: TimeoutError if install takes more than timeout parameter

        >>> async def install():
        ...     async with Application.from_apk_async("/some/local/path/to/apk", device) as stdout:
        ...         async for line in stdout:
        ...            if "some trigger message" in line:
        ...               print(line)

        """
        parser = AXMLParser.parse(apk_path)
        args = ["-r"] if as_upgrade else []
        await cls._monitor_install(device,
                                   apk_path,
                                   *args,
                                   callback=callback,
                                   timeout=timeout)
        return cls(device, parser)
Пример #14
0
    def from_apk(
            cls: Type[_TApp],
            apk_path: str,
            device: Device,
            as_upgrade: bool = False,
            timeout: Optional[int] = Device.TIMEOUT_LONG_ADB_CMD) -> _TApp:
        """
        Install provided application, blocking until install is complete

        :param apk_path: path to apk
        :param device: device to install on
        :param as_upgrade: whether to install as upgrade or not
        :param timeout: raises TimeoutError if specified and install takes too long
        :return: remote installed application
        :raises: Exception if failure ot install or verify installation
        :raises: TimeoutError if install takes more than timeout parameter

        >>> app = Application.from_apk("/local/path/to/apk", device, as_upgrade=True)
        """
        parser = AXMLParser.parse(apk_path)
        args = ["-r"] if as_upgrade else []
        cls._install(device, apk_path, *args, timeout=timeout)
        return cls(device, parser)
Пример #15
0
 def __init__(self, device: Device, apk_under_test: str):
     self._device = device
     self._apk_under_test = apk_under_test
     self._package_name = AXMLParser.parse(
         self._apk_under_test).package_name
Пример #16
0
 def test_apk_with_permissions(self):
     parser = AXMLParser.parse(COMPLEX_APK)
     assert set(parser.permissions) == {
         "android.permission.WRITE_EXTERNAL_STORAGE",
         "android.permission.READ_EXTERNAL_STORAGE"
     }