Beispiel #1
0
class AndroidUiautomationPoco(Poco):
    """
    Poco Android implementation for testing **Android native apps**.

    Args:
        device (:py:obj:`Device`): :py:obj:`airtest.core.device.Device` instance provided by ``airtest``. leave the 
         parameter default and the default device will be chosen. more details refer to ``airtest doc``
        using_proxy (:py:obj:`bool`): whether use adb forward to connect the Android device or not
        force_restart (:py:obj:`bool`): whether always restart the poco-service-demo running on Android device or not
        options: see :py:class:`poco.pocofw.Poco`

    Examples:
        The simplest way to initialize AndroidUiautomationPoco instance and no matter your device network status::

            from poco.drivers.android.uiautomation import AndroidUiautomationPoco

            poco = AndroidUiautomationPoco()
            poco('android:id/title').click()
            ...

    """
    def __init__(self,
                 device=None,
                 using_proxy=True,
                 force_restart=False,
                 use_airtest_input=False,
                 **options):
        # 加这个参数为了不在最新的pocounit方案中每步都截图
        self.screenshot_each_action = True
        if options.get('screenshot_each_action') is False:
            self.screenshot_each_action = False

        self.device = device or current_device()
        if not self.device:
            self.device = connect_device("Android:///")

        self.adb_client = self.device.adb
        if using_proxy:
            self.device_ip = self.adb_client.host or "127.0.0.1"
        else:
            self.device_ip = self.device.get_ip_address()

        # save current top activity (@nullable)
        current_top_activity_package = self.device.get_top_activity_name()
        if current_top_activity_package is not None:
            current_top_activity_package = current_top_activity_package.split(
                '/')[0]

        # install ime
        self.ime = YosemiteIme(self.adb_client)
        self.ime.start()

        # install
        self._instrument_proc = None
        self._install_service()

        # forward
        if using_proxy:
            p0, _ = self.adb_client.setup_forward("tcp:10080")
            p1, _ = self.adb_client.setup_forward("tcp:10081")
        else:
            p0 = 10080
            p1 = 10081

        # start
        if self._is_running('com.github.uiautomator'):
            warnings.warn(
                '{} should not run together with "uiautomator". "uiautomator" will be killed.'
                .format(self.__class__.__name__))
            self.adb_client.shell(
                ['am', 'force-stop', 'com.github.uiautomator'])

        ready = self._start_instrument(p0, force_restart=force_restart)
        if not ready:
            # 启动失败则需要卸载再重启,instrument的奇怪之处
            uninstall(self.adb_client, PocoServicePackage)
            self._install_service()
            ready = self._start_instrument(p0)

            if current_top_activity_package is not None:
                current_top_activity2 = self.device.get_top_activity_name()
                if current_top_activity2 is None or current_top_activity_package not in current_top_activity2:
                    self.device.start_app(current_top_activity_package,
                                          activity=True)

            if not ready:
                raise RuntimeError("unable to launch AndroidUiautomationPoco")

        endpoint = "http://{}:{}".format(self.device_ip, p1)
        agent = AndroidPocoAgent(endpoint, self.ime, use_airtest_input)
        super(AndroidUiautomationPoco, self).__init__(agent, **options)

    def _install_service(self):
        updated = install(
            self.adb_client,
            os.path.join(this_dir, 'lib', 'pocoservice-debug.apk'))
        install(
            self.adb_client,
            os.path.join(this_dir, 'lib', 'pocoservice-debug-androidTest.apk'),
            updated)
        return updated

    def _is_running(self, package_name):
        processes = self.adb_client.shell(['ps']).splitlines()
        for ps in processes:
            ps = ps.strip()
            if ps.endswith(package_name):
                return True
        return False

    # def _keep_running_instrumentation(self):
    #     def loop():
    #         while True:
    #             proc = self.adb_client.shell([
    #                 'am', 'instrument', '-w', '-e', 'class',
    #                 '{}.InstrumentedTestAsLauncher#launch'.format(PocoServicePackage),
    #                 '{}.test/android.support.test.runner.AndroidJUnitRunner'.format(PocoServicePackage)],
    #                 not_wait=True)
    #             stdout, stderr = proc.communicate()
    #             print(stdout)
    #             print(stderr)
    #             time.sleep(1)
    #     t = threading.Thread(target=loop)
    #     t.daemon = True
    #     t.start()

    def _start_instrument(self, port_to_ping, force_restart=False):
        if not force_restart:
            try:
                state = requests.get(
                    'http://{}:{}/uiautomation/connectionState'.format(
                        self.device_ip, port_to_ping),
                    timeout=10)
                state = state.json()
                if state.get('connected'):
                    # skip starting instrumentation if UiAutomation Service already connected.
                    return True
            except:
                pass

        if self._instrument_proc is not None:
            if self._instrument_proc.poll() is None:
                self._instrument_proc.kill()
            self._instrument_proc = None

        ready = False
        self.adb_client.shell(['am', 'force-stop', PocoServicePackage])

        # 启动instrument之前,先把主类activity启动起来,不然instrumentation可能失败
        self.adb_client.shell(
            'am start -n {}/.TestActivity'.format(PocoServicePackage))

        instrumentation_cmd = [
            'am', 'instrument', '-w', '-e', 'debug', 'false', '-e', 'class',
            '{}.InstrumentedTestAsLauncher'.format(PocoServicePackage),
            '{}.test/android.support.test.runner.AndroidJUnitRunner'.format(
                PocoServicePackage)
        ]
        self._instrument_proc = self.adb_client.start_shell(
            instrumentation_cmd)
        atexit.register(self._instrument_proc.kill)
        time.sleep(2)
        for i in range(10):
            try:
                requests.get('http://{}:{}'.format(self.device_ip,
                                                   port_to_ping),
                             timeout=10)
                ready = True
                break
            except requests.exceptions.Timeout:
                break
            except requests.exceptions.ConnectionError:
                if self._instrument_proc.poll() is not None:
                    warnings.warn("instrument server process is not alive")
                    output = self._instrument_proc.stdout.read()
                    print(output)
                time.sleep(1)
                print("still waiting for uiautomation ready.")
                continue
        return ready

    def on_pre_action(self, action, ui, args):
        if self.screenshot_each_action:
            # airteset log用
            from airtest.core.api import snapshot
            msg = repr(ui)
            if not isinstance(msg, six.text_type):
                msg = msg.decode('utf-8')
            snapshot(msg=msg)
Beispiel #2
0
class AndroidUiautomationPoco(Poco):
    def __init__(self, device=None, using_proxy=True, **options):
        if not device:
            try:
                # new version
                from airtest.core.api import connect_device, device as current_device
                if not current_device():
                    connect_device("Android:///")
            except ImportError:
                # old version
                from airtest.cli.runner import device as current_device
                from airtest.core.main import set_serialno
                if not current_device():
                    set_serialno()
            self.android = current_device()
        else:
            self.android = device
        self.adb_client = self.android.adb
        if using_proxy:
            self.device_ip = self.adb_client.host or "127.0.0.1"
        else:
            if new_airtest_api:
                self.device_ip = self.adb_client.get_ip_address()
            else:
                self.device_ip = get_ip_address(self.adb_client)

        # save current top activity (@nullable)
        current_top_activity_package = self.android.get_top_activity_name()
        if current_top_activity_package is not None:
            current_top_activity_package = current_top_activity_package.split(
                '/')[0]

        # install ime
        self.ime = YosemiteIme(self.adb_client)
        self.ime.start()

        # install
        self._instrument_proc = None
        self._install_service()

        # forward
        if using_proxy:
            p0, _ = self.adb_client.setup_forward("tcp:10080")
            p1, _ = self.adb_client.setup_forward("tcp:10081")
        else:
            p0 = 10080
            p1 = 10081

        # start
        if self._is_running('com.github.uiautomator'):
            warnings.warn(
                '{} should not run together with "uiautomator". "uiautomator" will be killed.'
                .format(self.__class__.__name__))
            self.adb_client.shell(
                ['am', 'force-stop', 'com.github.uiautomator'])

        ready = self._start_instrument(p0)
        if not ready:
            # 启动失败则需要卸载再重启,instrument的奇怪之处
            uninstall(self.adb_client, PocoServicePackage)
            self._install_service()
            ready = self._start_instrument(p0)

            if current_top_activity_package is not None:
                current_top_activity2 = self.android.get_top_activity_name()
                if current_top_activity2 is None or current_top_activity_package not in current_top_activity2:
                    self.android.start_app(current_top_activity_package,
                                           activity=True)

            if not ready:
                raise RuntimeError("unable to launch AndroidUiautomationPoco")

        endpoint = "http://{}:{}".format(self.device_ip, p1)
        agent = AndroidPocoAgent(endpoint, self.ime)
        super(AndroidUiautomationPoco, self).__init__(agent, **options)

    def _install_service(self):
        updated = install(
            self.adb_client,
            os.path.join(this_dir, 'lib', 'pocoservice-debug.apk'))
        install(
            self.adb_client,
            os.path.join(this_dir, 'lib', 'pocoservice-debug-androidTest.apk'),
            updated)
        return updated

    def _is_running(self, package_name):
        processes = self.adb_client.shell(['ps']).splitlines()
        for ps in processes:
            ps = ps.strip()
            if ps.endswith(package_name):
                return True
        return False

    # def _keep_running_instrumentation(self):
    #     def loop():
    #         while True:
    #             proc = self.adb_client.shell([
    #                 'am', 'instrument', '-w', '-e', 'class',
    #                 '{}.InstrumentedTestAsLauncher#launch'.format(PocoServicePackage),
    #                 '{}.test/android.support.test.runner.AndroidJUnitRunner'.format(PocoServicePackage)],
    #                 not_wait=True)
    #             stdout, stderr = proc.communicate()
    #             print(stdout)
    #             print(stderr)
    #             time.sleep(1)
    #     t = threading.Thread(target=loop)
    #     t.daemon = True
    #     t.start()

    def _start_instrument(self, port_to_ping):
        if self._instrument_proc is not None:
            self._instrument_proc.kill()
            self._instrument_proc = None
        ready = False
        self.adb_client.shell(['am', 'force-stop', PocoServicePackage])

        # 启动instrument之前,先把主类activity启动起来,不然instrumentation可能失败
        self.adb_client.shell(
            'am start -n {}/.TestActivity'.format(PocoServicePackage))

        instrumentation_cmd = [
            'am', 'instrument', '-w', '-e', 'debug', 'false', '-e', 'class',
            '{}.InstrumentedTestAsLauncher'.format(PocoServicePackage),
            '{}.test/android.support.test.runner.AndroidJUnitRunner'.format(
                PocoServicePackage)
        ]
        if new_airtest_api:
            self._instrument_proc = self.adb_client.start_shell(
                instrumentation_cmd)
        else:
            self._instrument_proc = self.adb_client.shell(instrumentation_cmd,
                                                          not_wait=True)
        time.sleep(2)
        for i in range(10):
            try:
                requests.get('http://{}:{}'.format(self.device_ip,
                                                   port_to_ping),
                             timeout=10)
                ready = True
                break
            except requests.exceptions.Timeout:
                break
            except requests.exceptions.ConnectionError:
                time.sleep(1)
                print("still waiting for uiautomation ready.")
                continue
        return ready

    def on_pre_action(self, action, proxy, args):
        # airteset log用
        try:
            from airtest.core.api import snapshot
        except ImportError:
            # 兼容旧airtest
            from airtest.core.main import snapshot
        snapshot(msg=unicode(proxy))
Beispiel #3
0
class AndroidUiautomationPoco(Poco):
    """
    Poco Android implementation for testing **Android native apps**.

    Args:
        device (:py:obj:`Device`): :py:obj:`airtest.core.device.Device` instance provided by ``airtest``. leave the 
         parameter default and the default device will be chosen. more details refer to ``airtest doc``
        using_proxy (:py:obj:`bool`): whether use adb forward to connect the Android device or not
        force_restart (:py:obj:`bool`): whether always restart the poco-service-demo running on Android device or not
        options: see :py:class:`poco.pocofw.Poco`

    Examples:
        The simplest way to initialize AndroidUiautomationPoco instance and no matter your device network status::

            from poco.drivers.android.uiautomation import AndroidUiautomationPoco

            poco = AndroidUiautomationPoco()
            poco('android:id/title').click()
            ...

    """

    def __init__(self, device=None, using_proxy=True, force_restart=False, use_airtest_input=False, **options):
        # 加这个参数为了不在最新的pocounit方案中每步都截图
        self.screenshot_each_action = True
        if options.get('screenshot_each_action') is False:
            self.screenshot_each_action = False

        self.device = device or current_device()
        if not self.device:
            self.device = connect_device("Android:///")

        self.adb_client = self.device.adb
        if using_proxy:
            self.device_ip = self.adb_client.host or "127.0.0.1"
        else:
            self.device_ip = self.device.get_ip_address()

        # save current top activity (@nullable)
        current_top_activity_package = self.device.get_top_activity_name()
        if current_top_activity_package is not None:
            current_top_activity_package = current_top_activity_package.split('/')[0]

        # install ime
        self.ime = YosemiteIme(self.adb_client)
        self.ime.start()

        # install
        self._instrument_proc = None
        self._install_service()

        # forward
        if using_proxy:
            p0, _ = self.adb_client.setup_forward("tcp:10080")
            p1, _ = self.adb_client.setup_forward("tcp:10081")
        else:
            p0 = 10080
            p1 = 10081

        # start
        if self._is_running('com.github.uiautomator'):
            warnings.warn('{} should not run together with "uiautomator". "uiautomator" will be killed.'
                          .format(self.__class__.__name__))
            self.adb_client.shell(['am', 'force-stop', 'com.github.uiautomator'])

        ready = self._start_instrument(p0, force_restart=force_restart)
        if not ready:
            # 之前启动失败就卸载重装,现在改为尝试kill进程或卸载uiautomator
            self._kill_uiautomator()
            ready = self._start_instrument(p0)

            if current_top_activity_package is not None:
                current_top_activity2 = self.device.get_top_activity_name()
                if current_top_activity2 is None or current_top_activity_package not in current_top_activity2:
                    self.device.start_app(current_top_activity_package, activity=True)

            if not ready:
                raise RuntimeError("unable to launch AndroidUiautomationPoco")
        if ready:
            # 首次启动成功后,在后台线程里监控这个进程的状态,保持让它不退出
            self._keep_running_thread = KeepRunningInstrumentationThread(self, p0)
            self._keep_running_thread.start()

        endpoint = "http://{}:{}".format(self.device_ip, p1)
        agent = AndroidPocoAgent(endpoint, self.ime, use_airtest_input)
        super(AndroidUiautomationPoco, self).__init__(agent, **options)

    def _install_service(self):
        updated = install(self.adb_client, os.path.join(this_dir, 'lib', 'pocoservice-debug.apk'))
        install(self.adb_client, os.path.join(this_dir, 'lib', 'pocoservice-debug-androidTest.apk'), updated)
        return updated

    def _is_running(self, package_name):
        """
        use ps |grep to check whether the process exists

        :param package_name: package name(e.g., com.github.uiautomator)
                            or regular expression(e.g., poco\|airtest\|uiautomator\|airbase)
        :return: pid or None
        """
        cmd = r' |echo $(grep -E {package_name})'.format(package_name=package_name)
        if self.device.sdk_version > 25:
            cmd = r'ps -A' + cmd
        else:
            cmd = r'ps' + cmd
        processes = self.adb_client.shell(cmd).splitlines()
        for ps in processes:
            if ps:
                ps = ps.split()
                return ps[1]
        return None

    def _start_instrument(self, port_to_ping, force_restart=False):
        if not force_restart:
            try:
                state = requests.get('http://{}:{}/uiautomation/connectionState'.format(self.device_ip, port_to_ping),
                                     timeout=10)
                state = state.json()
                if state.get('connected'):
                    # skip starting instrumentation if UiAutomation Service already connected.
                    return True
            except:
                pass

        if self._instrument_proc is not None:
            if self._instrument_proc.poll() is None:
                self._instrument_proc.kill()
            self._instrument_proc = None

        ready = False
        self.adb_client.shell(['am', 'force-stop', PocoServicePackage])

        # 启动instrument之前,先把主类activity启动起来,不然instrumentation可能失败
        self.adb_client.shell('am start -n {}/.TestActivity'.format(PocoServicePackage))

        instrumentation_cmd = [
                'am', 'instrument', '-w', '-e', 'debug', 'false', '-e', 'class',
                '{}.InstrumentedTestAsLauncher'.format(PocoServicePackage),
                '{}.test/android.support.test.runner.AndroidJUnitRunner'.format(PocoServicePackage)]
        self._instrument_proc = self.adb_client.start_shell(instrumentation_cmd)

        def cleanup_proc(proc):
            def wrapped():
                try:
                    proc.kill()
                except:
                    pass
            return wrapped
        atexit.register(cleanup_proc(self._instrument_proc))

        time.sleep(2)
        for i in range(10):
            try:
                requests.get('http://{}:{}'.format(self.device_ip, port_to_ping), timeout=10)
                ready = True
                break
            except requests.exceptions.Timeout:
                break
            except requests.exceptions.ConnectionError:
                if self._instrument_proc.poll() is not None:
                    warnings.warn("[pocoservice.apk] instrumentation test server process is no longer alive")
                    stdout = self._instrument_proc.stdout.read()
                    stderr = self._instrument_proc.stderr.read()
                    print('[pocoservice.apk] stdout: {}'.format(stdout))
                    print('[pocoservice.apk] stderr: {}'.format(stderr))
                time.sleep(1)
                print("still waiting for uiautomation ready.")
                continue
        return ready

    def _kill_uiautomator(self):
        """
        poco-service无法与其他instrument启动的apk同时存在,因此在启动前,需要杀掉一些可能的进程:
        比如 io.appium.uiautomator2.server, com.github.uiautomator, com.netease.open.pocoservice等

        :return:
        """
        pid = self._is_running("uiautomator")
        if pid:
            warnings.warn('{} should not run together with "uiautomator". "uiautomator" will be killed.'
                          .format(self.__class__.__name__))
            self.adb_client.shell(['am', 'force-stop', PocoServicePackage])

            try:
                self.adb_client.shell(['kill', pid])
            except AdbShellError:
                # 没有root权限
                uninstall(self.adb_client, UiAutomatorPackage)

    def on_pre_action(self, action, ui, args):
        if self.screenshot_each_action:
            # airteset log用
            from airtest.core.api import snapshot
            msg = repr(ui)
            if not isinstance(msg, six.text_type):
                msg = msg.decode('utf-8')
            snapshot(msg=msg)

    def stop_running(self):
        print('[pocoservice.apk] stopping PocoService')
        self._keep_running_thread.stop()
        self._keep_running_thread.join(3)
        self.adb_client.shell(['am', 'force-stop', PocoServicePackage])