Esempio n. 1
0
class AutomatorServer(object):
    """start and quit rpc server on device.
    """
    __jar_files = {
        "bundle.jar": "libs/bundle.jar",
        "uiautomator-stub.jar": "libs/uiautomator-stub.jar"
    }

    __apk_files = ["libs/app-uiautomator.apk", "libs/app-uiautomator-test.apk"]
    # Used for check if installed
    __apk_vercode = 2
    __apk_pkgname = 'com.github.uiautomator'
    __apk_pkgname_test = 'com.github.uiautomator.test'

    __sdk = 0
    __httpsession = requests.Session()  # use a standalone session

    handlers = NotFoundHandler()  # handler UI Not Found exception

    def __init__(self,
                 serial=None,
                 local_port=None,
                 device_port=None,
                 adb_server_host=None,
                 adb_server_port=None):
        self.uiautomator_process = None
        self.adb = Adb(serial=serial,
                       adb_server_host=adb_server_host,
                       adb_server_port=adb_server_port)
        self.device_port = int(device_port) if device_port else DEVICE_PORT
        self.__local_port = local_port

    def get_forwarded_port(self):
        for s, lp, rp in self.adb.forward_list():
            if s == self.adb.device_serial(
            ) and rp == 'tcp:%d' % self.device_port:
                return int(lp[4:])
        return None

    @property
    def local_port(self):
        if self.__local_port:
            return self.__local_port
        for i in range(10):  # Max retry 10 times
            forwarded_port = self.get_forwarded_port()
            if forwarded_port:
                self.__local_port = forwarded_port
                return self.__local_port

            port = next_local_port(self.adb.adb_server_host)
            self.adb.forward(port, self.device_port, rebind=False)
        raise RuntimeError("Error run: adb forward tcp:<any> tcp:%d" %
                           self.device_port)

    def push(self):
        base_dir = os.path.dirname(__file__)
        for jar, url in self.__jar_files.items():
            filename = os.path.join(base_dir, url)
            self.adb.run_cmd("push", filename, "/data/local/tmp/")
        return list(self.__jar_files.keys())

    def need_install(self):
        pkginfo = self.adb.package_info(self.__apk_pkgname)
        if pkginfo is None:
            return True
        if pkginfo['version_code'] != self.__apk_vercode:
            return True
        if self.adb.package_info(self.__apk_pkgname_test) is None:
            return True
        return False

    def install(self):
        base_dir = os.path.dirname(__file__)
        if self.need_install():
            debug_print("install apks", self.__apk_files)
            for apk in self.__apk_files:
                self.adb.cmd("install", "-r", os.path.join(base_dir,
                                                           apk)).wait()
        else:
            debug_print("already installed, skip")

    @property
    def jsonrpc(self):
        return self.jsonrpc_wrap(
            timeout=int(os.environ.get("jsonrpc_timeout", 90)))

    def jsonrpc_wrap(self, timeout):
        server = self
        error_code_base = -32000

        def _JsonRPCMethod(url, method, timeout, restart=True):
            _method_obj = JsonRPCMethod(url, method, timeout)
            _URLError = requests.exceptions.ConnectionError

            def wrapper(*args, **kwargs):
                try:
                    return _method_obj(*args, **kwargs)
                except (_URLError, socket.error) as e:
                    if restart:
                        debug_print('restart')
                        server.stop()
                        server.start(timeout=30)
                        return _JsonRPCMethod(url, method, timeout,
                                              False)(*args, **kwargs)
                    else:
                        raise
                except JsonRPCError as e:
                    debug_print('rpc error', e.code, e.message)
                    raise
                    # if e.code >= error_code_base - 1:
                    #     server.stop()
                    #     server.start(timeout=10)
                    #     return _method_obj(*args, **kwargs)
                    # elif e.code == error_code_base - 2 and self.handlers['on']:  # Not Found
                    #     try:
                    #         self.handlers['on'] = False
                    #         # any handler returns True will break the left handlers
                    #         any(handler(self.handlers.get('device', None)) for handler in self.handlers['handlers'])
                    #     finally:
                    #         self.handlers['on'] = True
                    #     return _method_obj(*args, **kwargs)
                    # raise

            return wrapper

        return JsonRPCClient(self.rpc_uri,
                             timeout=timeout,
                             method_class=_JsonRPCMethod)

    def __jsonrpc(self):
        return JsonRPCClient(self.rpc_uri,
                             timeout=int(os.environ.get("JSONRPC_TIMEOUT",
                                                        90)))

    def sdk_version(self):
        '''sdk version of connected device.'''
        if self.__sdk == 0:
            try:
                self.__sdk = int(
                    self.adb.cmd("shell", "getprop",
                                 "ro.build.version.sdk").communicate()
                    [0].decode("utf-8").strip())
            except:
                pass
        return self.__sdk

    def ro_product(self):
        return self.adb.shell("getprop", "ro.build.product").strip()

    def ro_manufacturer(self):
        return self.adb.shell("getprop",
                              "ro.product.manufacturer").strip().lower()

    def start(self, timeout=5):
        # 对应关系列表
        # http://www.cnblogs.com/lipeineng/archive/2017/01/06/6257859.html
        # Android 4.3 (sdk=18)
        # Android 5.0 (sdk=21)
        debug_print('sdk version(instrument>=18)', self.sdk_version())
        debug_print('product', self.ro_product())
        debug_print('manufacturer', self.ro_manufacturer())
        if self.sdk_version() >= 100:
            self.install()
            cmd = [
                "shell", "am", "instrument", "-w", "-e", "debug", "false",
                "-e", "class", "com.github.uiautomator.stub.Stub",
                "com.github.uiautomator.test/android.support.test.runner.AndroidJUnitRunner"
            ]
        else:
            files = self.push()
            cmd = list(
                itertools.chain(["shell", "uiautomator", "runtest"], files,
                                ["-c", "com.github.uiautomatorstub.Stub"]))

        debug_print('$ ' + subprocess.list2cmdline(list(cmd)))
        self.uiautomator_process = self.adb.cmd(*cmd)
        self.adb.forward(self.local_port, self.device_port)

        while not self.alive and timeout > 0:
            time.sleep(0.2)
            timeout -= 0.2
            debug_print('poll', self.uiautomator_process.poll())
            if self.uiautomator_process.poll() is not None:
                stdout = self.uiautomator_process.stdout.read()
                raise IOError("uiautomator start failed: " + stdout)
        if not self.alive:
            raise IOError("RPC server not started!")

    def ping(self):
        try:
            return self.__jsonrpc().ping()
        except:
            return None

    def info(self):
        try:
            return self.__jsonrpc().deviceInfo()
        except:
            return False

    @property
    def alive(self):
        '''Check if the rpc server is alive.'''
        return self.ping() == "pong" and self.info()
        # return self.ping() == "pong"

    def stop(self):
        '''Stop the rpc server.'''
        if self.uiautomator_process and self.uiautomator_process.poll(
        ) is None:
            res = None
            try:
                res = requests.get(self.stop_uri)
                self.uiautomator_process.wait()
            except:
                self.uiautomator_process.kill()
            finally:
                if res is not None:
                    res.close()
                self.uiautomator_process = None
        try:
            out = self.adb.cmd("shell", "ps", "-C",
                               "uiautomator").communicate()[0].decode(
                                   "utf-8").strip().splitlines()
            if out:
                index = out[0].split().index("PID")
                for line in out[1:]:
                    if len(line.split()) > index:
                        self.adb.cmd("shell", "kill", "-9",
                                     line.split()[index]).wait()
        except:
            pass

    @property
    def stop_uri(self):
        return "http://%s:%d/stop" % (self.adb.adb_server_host,
                                      self.local_port)

    @property
    def rpc_uri(self):
        return "http://%s:%d/jsonrpc/0" % (self.adb.adb_server_host,
                                           self.local_port)

    @property
    def screenshot_uri(self):
        return "http://%s:%d/screenshot/0" % (self.adb.adb_server_host,
                                              self.local_port)

    def screenshot(self, filename=None, scale=1.0, quality=100):
        # since sdk version is always great 18, so no check here
        # also can not use requests.Session, this will break /jsonrpc/0 requests
        try:
            r = self.__httpsession.get(self.screenshot_uri,
                                       params=dict(scale=scale,
                                                   quality=quality),
                                       timeout=30)
            if filename:
                with open(filename, 'wb') as f:
                    f.write(r.content)
                    return filename
            else:
                return r.content
        except:
            pass
Esempio n. 2
0
class AutomatorServer(object):
    """start and quit rpc server on device.
    """
    __jar_files = {
        "bundle.jar": "libs/bundle.jar",
        "uiautomator-stub.jar": "libs/uiautomator-stub.jar"
    }

    __apk_files = ["libs/app-uiautomator.apk", "libs/app-uiautomator-test.apk"]
    # Used for check if installed
    # 2: fix bug
    # 3: screenrecord support
    __apk_vercode = 3
    __apk_pkgname = 'com.github.uiautomator'
    __apk_pkgname_test = 'com.github.uiautomator.test'

    __sdk = 0
    __httpsession = requests.Session() # use a standalone session

    handlers = NotFoundHandler()  # handler UI Not Found exception

    def __init__(self, serial=None, local_port=None, device_port=None, adb_server_host=None, adb_server_port=None):
        self.uiautomator_process = None
        self.adb = Adb(serial=serial, adb_server_host=adb_server_host, adb_server_port=adb_server_port)
        self.device_port = int(device_port) if device_port else DEVICE_PORT
        self.__local_port = local_port

    def get_forwarded_port(self):
        for s, lp, rp in self.adb.forward_list():
            if s == self.adb.device_serial() and rp == 'tcp:%d' % self.device_port:
                return int(lp[4:])
        return None

    @property
    def local_port(self):
        if self.__local_port:
            return self.__local_port
        for i in range(10): # Max retry 10 times
            forwarded_port = self.get_forwarded_port()
            if forwarded_port:
                self.__local_port = forwarded_port
                return self.__local_port

            port = next_local_port(self.adb.adb_server_host)
            self.adb.forward(port, self.device_port, rebind=False)
        raise RuntimeError("Error run: adb forward tcp:<any> tcp:%d" % self.device_port)

    def push(self):
        base_dir = os.path.dirname(__file__)
        for jar, url in self.__jar_files.items():
            filename = os.path.join(base_dir, url)
            self.adb.run_cmd("push", filename, "/data/local/tmp/")
        return list(self.__jar_files.keys())

    def need_install(self):
        pkginfo = self.adb.package_info(self.__apk_pkgname)
        if pkginfo is None:
            return True
        if pkginfo['version_code'] != self.__apk_vercode:
            return True
        if self.adb.package_info(self.__apk_pkgname_test) is None:
            return True
        return False
        
    def install(self):
        base_dir = os.path.dirname(__file__)
        if self.need_install():
            debug_print("install apks", self.__apk_files)
            for apk in self.__apk_files:
                self.adb.cmd("install", "-r", os.path.join(base_dir, apk)).wait()
        else:
            debug_print("already installed, skip")
    
    def uninstall(self):
        self.adb.cmd("uninstall", self.__apk_pkgname)
        self.adb.cmd("uninstall", self.__apk_pkgname_test)

    @property
    def jsonrpc(self):
        return self.jsonrpc_wrap(timeout=int(os.environ.get("jsonrpc_timeout", 90)))

    def jsonrpc_wrap(self, timeout):
        server = self
        error_code_base = -32000

        def _JsonRPCMethod(url, method, timeout, restart=True):
            _method_obj = JsonRPCMethod(url, method, timeout)
            _URLError = requests.exceptions.ConnectionError

            def wrapper(*args, **kwargs):
                try:
                    return _method_obj(*args, **kwargs)
                except (_URLError, socket.error) as e:
                    if restart:
                        debug_print('restart')
                        server.stop()
                        server.start(timeout=30)
                        return _JsonRPCMethod(url, method, timeout, False)(*args, **kwargs)
                    else:
                        raise
                except JsonRPCError as e:
                    debug_print('rpc error', e.code, e.message)
                    if 'UiAutomation not connected' in e.message:
                        self.uninstall()
                        return _JsonRPCMethod(url, method, timeout, False)(*args, **kwargs)
                    else:
                    # if e.message
                        raise
                    # if e.code >= error_code_base - 1:
                    #     server.stop()
                    #     server.start(timeout=10)
                    #     return _method_obj(*args, **kwargs)
                    # elif e.code == error_code_base - 2 and self.handlers['on']:  # Not Found
                    #     try:
                    #         self.handlers['on'] = False
                    #         # any handler returns True will break the left handlers
                    #         any(handler(self.handlers.get('device', None)) for handler in self.handlers['handlers'])
                    #     finally:
                    #         self.handlers['on'] = True
                    #     return _method_obj(*args, **kwargs)
                    # raise
            return wrapper

        return JsonRPCClient(self.rpc_uri,
                             timeout=timeout,
                             method_class=_JsonRPCMethod)

    def __jsonrpc(self):
        return JsonRPCClient(self.rpc_uri, timeout=int(os.environ.get("JSONRPC_TIMEOUT", 90)))

    def sdk_version(self):
        '''sdk version of connected device.'''
        if self.__sdk == 0:
            try:
                self.__sdk = int(self.adb.cmd("shell", "getprop", "ro.build.version.sdk").communicate()[0].decode("utf-8").strip())
            except:
                pass
        return self.__sdk

    def ro_product(self):
        return self.adb.shell("getprop", "ro.build.product").strip()

    def ro_manufacturer(self):
        return self.adb.shell("getprop", "ro.product.manufacturer").strip().lower()

    def start(self, timeout=5):
        # 对应关系列表
        # http://www.cnblogs.com/lipeineng/archive/2017/01/06/6257859.html
        # Android 4.3 (sdk=18)
        # Android 5.0 (sdk=21)
        debug_print('sdk version(instrument>=18)', self.sdk_version())
        debug_print('product', self.ro_product())
        debug_print('manufacturer', self.ro_manufacturer())
        if self.sdk_version() >= 18:
            self.install()
            cmd = ["shell", "am", "instrument", "-w", "-r",
                    "-e", "debug", "false", 
                    "-e", "class", "com.github.uiautomator.stub.Stub",
                    "com.github.uiautomator.test/android.support.test.runner.AndroidJUnitRunner"]
        else:
            files = self.push()
            cmd = list(itertools.chain(
                ["shell", "uiautomator", "runtest"],
                files,
                ["-c", "com.github.uiautomatorstub.Stub"]
            ))

        debug_print('$ ' + subprocess.list2cmdline(list(cmd)))
        self.uiautomator_process = self.adb.cmd(*cmd)
        self.adb.forward(self.local_port, self.device_port)

        while not self.alive and timeout > 0:
            time.sleep(0.2)
            timeout -= 0.2
            debug_print('poll', self.uiautomator_process.poll())
            if self.uiautomator_process.poll() is not None:
                stdout = self.uiautomator_process.stdout.read()
                raise IOError("uiautomator start failed: " + str(stdout))
        if not self.alive:
            raise IOError("RPC server not started!")

    def ping(self):
        try:
            return self.__jsonrpc().ping()
        except:
            return None

    def info(self):
        try:
            return self.__jsonrpc().deviceInfo()
        except:
            return False

    @property
    def alive(self):
        '''Check if the rpc server is alive.'''
        return self.ping() == "pong" and self.info()
        # return self.ping() == "pong"

    def stop(self):
        '''Stop the rpc server.'''
        if self.uiautomator_process and self.uiautomator_process.poll() is None:
            res = None
            try:
                res = requests.get(self.stop_uri)
                self.uiautomator_process.wait()
            except:
                self.uiautomator_process.kill()
            finally:
                if res is not None:
                    res.close()
                self.uiautomator_process = None
        try:
            out = self.adb.cmd("shell", "ps", "-C", "uiautomator").communicate()[0].decode("utf-8").strip().splitlines()
            if out:
                index = out[0].split().index("PID")
                for line in out[1:]:
                    if len(line.split()) > index:
                        self.adb.cmd("shell", "kill", "-9", line.split()[index]).wait()
        except:
            pass

    @property
    def stop_uri(self):
        return "http://%s:%d/stop" % (self.adb.adb_server_host, self.local_port)

    @property
    def rpc_uri(self):
        return "http://%s:%d/jsonrpc/0" % (self.adb.adb_server_host, self.local_port)

    @property
    def screenshot_uri(self):
        return "http://%s:%d/screenshot/0" % (self.adb.adb_server_host, self.local_port)

    def screenshot(self, filename=None, scale=1.0, quality=100):
        # since sdk version is always great 18, so no check here
        # also can not use requests.Session, this will break /jsonrpc/0 requests
        try:
            r = self.__httpsession.get(self.screenshot_uri, params=dict(scale=scale, quality=quality), timeout=30)
            if filename:
                with open(filename, 'wb') as f:
                    f.write(r.content)
                    return filename
            else:
                return r.content
        except:
            pass
Esempio n. 3
0
 def test_forward(self):
     adb = Adb()
     adb.cmd = MagicMock()
     adb.forward(90, 91)
     adb.cmd.assert_called_once_with("forward", "tcp:90", "tcp:91")
     adb.cmd.return_value.wait.assert_called_once_with()
Esempio n. 4
0
 def test_forward(self):
     adb = Adb()
     adb.cmd = MagicMock()
     adb.forward(90, 91)
     adb.cmd.assert_called_once_with("forward", "tcp:90", "tcp:91")
     adb.cmd.return_value.wait.assert_called_once_with()
Esempio n. 5
0
class AutomatorServer(object):
    """start and quit rpc server on device.
    """
    __jar_files = {
        "bundle.jar": "libs/bundle.jar",
        "uiautomator-stub.jar": "libs/uiautomator-stub.jar"
    }

    __apk_files = ["libs/app-uiautomator.apk", "libs/app-uiautomator-test.apk"]
    # Used for check if installed
    __apk_vercode = 1
    __apk_pkgname = 'com.github.uiautomator'
    __apk_pkgname_test = 'com.github.uiautomator.test'

    __sdk = 0

    handlers = NotFoundHandler()  # handler UI Not Found exception

    def __init__(self,
                 serial=None,
                 local_port=None,
                 device_port=None,
                 adb_server_host=None,
                 adb_server_port=None):
        self.uiautomator_process = None
        self.adb = Adb(serial=serial,
                       adb_server_host=adb_server_host,
                       adb_server_port=adb_server_port)
        self.device_port = int(device_port) if device_port else DEVICE_PORT
        if local_port:
            self.local_port = local_port
        else:
            try:  # first we will try to use the local port already adb forwarded
                for s, lp, rp in self.adb.forward_list():
                    if s == self.adb.device_serial(
                    ) and rp == 'tcp:%d' % self.device_port:
                        self.local_port = int(lp[4:])
                        break
                else:
                    self.local_port = next_local_port(adb_server_host)
            except:
                self.local_port = next_local_port(adb_server_host)

    def push(self):
        base_dir = os.path.dirname(__file__)
        for jar, url in self.__jar_files.items():
            filename = os.path.join(base_dir, url)
            self.adb.cmd("push", filename, "/data/local/tmp/").wait()
        return list(self.__jar_files.keys())

    def need_install(self):
        pkginfo = self.adb.package_info(self.__apk_pkgname)
        if pkginfo is None:
            return True
        if pkginfo['version_code'] != self.__apk_vercode:
            return True
        if self.adb.package_info(self.__apk_pkgname_test) is None:
            return True
        return False

    def install(self):
        base_dir = os.path.dirname(__file__)
        if self.need_install():
            for apk in self.__apk_files:
                self.adb.cmd("install", "-rt", os.path.join(base_dir,
                                                            apk)).wait()

    @property
    def jsonrpc(self):
        return self.jsonrpc_wrap(
            timeout=int(os.environ.get("jsonrpc_timeout", 90)))

    def jsonrpc_wrap(self, timeout):
        server = self
        error_code_base = -32000

        def _JsonRPCMethod(url, method, timeout, restart=True):
            _method_obj = JsonRPCMethod(url, method, timeout)
            _URLError = requests.exceptions.ConnectionError

            def wrapper(*args, **kwargs):
                try:
                    return _method_obj(*args, **kwargs)
                except (_URLError, socket.error, HTTPException) as e:
                    if restart:
                        server.stop()
                        server.start(timeout=30)
                        return _JsonRPCMethod(url, method, timeout,
                                              False)(*args, **kwargs)
                    else:
                        raise
                except JsonRPCError as e:
                    if e.code >= error_code_base - 1:
                        server.stop()
                        server.start()
                        return _method_obj(*args, **kwargs)
                    elif e.code == error_code_base - 2 and self.handlers[
                            'on']:  # Not Found
                        try:
                            self.handlers['on'] = False
                            # any handler returns True will break the left handlers
                            any(
                                handler(self.handlers.get('device', None))
                                for handler in self.handlers['handlers'])
                        finally:
                            self.handlers['on'] = True
                        return _method_obj(*args, **kwargs)
                    raise

            return wrapper

        return JsonRPCClient(self.rpc_uri,
                             timeout=timeout,
                             method_class=_JsonRPCMethod)

    def __jsonrpc(self):
        return JsonRPCClient(self.rpc_uri,
                             timeout=int(os.environ.get("JSONRPC_TIMEOUT",
                                                        90)))

    def sdk_version(self):
        '''sdk version of connected device.'''
        if self.__sdk == 0:
            try:
                self.__sdk = int(
                    self.adb.cmd("shell", "getprop",
                                 "ro.build.version.sdk").communicate()
                    [0].decode("utf-8").strip())
            except:
                pass
        return self.__sdk

    def start(self, timeout=5):
        # always use uiautomator runtest
        # This is because use am instrument can not found a way to stop it
        # this will stuck when Automator not started error occurs
        if True or self.sdk_version() < 18:  # FIXME(ssx): hot fix here
            files = self.push()
            cmd = list(
                itertools.chain(["shell", "uiautomator", "runtest"], files,
                                ["-c", "com.github.uiautomatorstub.Stub"]))
        else:
            self.install()
            cmd = [
                "shell", "am", "instrument", "-w",
                "com.github.uiautomator.test/android.support.test.runner.AndroidJUnitRunner"
            ]

        self.uiautomator_process = self.adb.cmd(*cmd)
        self.adb.forward(self.local_port, self.device_port)

        while not self.alive and timeout > 0:
            time.sleep(0.1)
            timeout -= 0.1
        if not self.alive:
            raise IOError("RPC server not started!")

    def ping(self):
        try:
            return self.__jsonrpc().ping()
        except:
            return None

    @property
    def alive(self):
        '''Check if the rpc server is alive.'''
        return self.ping() == "pong"

    def stop(self):
        '''Stop the rpc server.'''
        if self.uiautomator_process and self.uiautomator_process.poll(
        ) is None:
            res = None
            try:
                res = urllib2.urlopen(self.stop_uri)
                self.uiautomator_process.wait()
            except:
                self.uiautomator_process.kill()
            finally:
                if res is not None:
                    res.close()
                self.uiautomator_process = None
        try:
            out = self.adb.cmd("shell", "ps", "-C",
                               "uiautomator").communicate()[0].decode(
                                   "utf-8").strip().splitlines()
            if out:
                index = out[0].split().index("PID")
                for line in out[1:]:
                    if len(line.split()) > index:
                        self.adb.cmd("shell", "kill", "-9",
                                     line.split()[index]).wait()
        except:
            pass

    @property
    def stop_uri(self):
        return "http://%s:%d/stop" % (self.adb.adb_server_host,
                                      self.local_port)

    @property
    def rpc_uri(self):
        return "http://%s:%d/jsonrpc/0" % (self.adb.adb_server_host,
                                           self.local_port)

    @property
    def screenshot_uri(self):
        return "http://%s:%d/screenshot/0" % (self.adb.adb_server_host,
                                              self.local_port)

    def screenshot(self, filename=None, scale=1.0, quality=100):
        if self.sdk_version() >= 18:
            try:
                req = urllib2.Request("%s?scale=%f&quality=%f" %
                                      (self.screenshot_uri, scale, quality))
                result = urllib2.urlopen(req, timeout=30)
                if filename:
                    with open(filename, 'wb') as f:
                        f.write(result.read())
                        return filename
                else:
                    return result.read()
            except:
                pass
        return None